nuttx/fs/inode/fs_files.c
dongjiuzhu1 148f0ce7dd fs/inode: use file_allocate,file_dup to avoid racecondition to allocate fd
issue description:
task A:                                            NSH:
1.open->                                           reboot->sync->task_fsfsync
2.nx_vopen->               context switch
3.fdlist_allocate:            ---->                4.fsync->file_sync->assert(inode or priv is empty)
(new fd with empty filep)
5.file_vopen:
(init empty filep)
6.return fd

Task A allocates a new fd with an empty filep in fdlist_allocate. Before
it can fully initialize the filep in file_vopen, the NSH task triggers a
file - system sync operation. The sync operation encounters the empty
filep associated with the newly allocated fd, causing the assertion to
fail and the system to crash.

To resolve this race condition, we should modify the fd allocation
process. Instead of allocating a new fd with an empty filep first and
then initializing it later, we should use the file_allocate_from_inode
function. This function allows us to initialize the file structure first
and then bind it to the new filep when allocating the fd. By doing so,
we ensure that the filep is always properly initialized before it is
used in any file - system operations, thus preventing the assertion
failure and the subsequent system crash.

Signed-off-by: dongjiuzhu1 <dongjiuzhu1@xiaomi.com>
2026-01-09 17:11:25 +08:00

982 lines
24 KiB
C

/****************************************************************************
* fs/inode/fs_files.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <string.h>
#include <assert.h>
#include <execinfo.h>
#include <sched.h>
#include <errno.h>
#include <fcntl.h>
#include <debug.h>
#include <stdio.h>
#include <nuttx/fs/fs.h>
#include <nuttx/kmalloc.h>
#include <nuttx/cancelpt.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/mutex.h>
#include <nuttx/sched.h>
#include <nuttx/spawn.h>
#include <nuttx/spinlock.h>
#include <nuttx/lib/lib.h>
#ifdef CONFIG_FDCHECK
# include <nuttx/fdcheck.h>
#endif
#include "sched/sched.h"
#include "inode/inode.h"
#include "fs_heap.h"
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: fdlist_get_by_index
****************************************************************************/
static void fdlist_get_by_index(FAR struct fdlist *list,
int l1, int l2,
FAR struct file **filep,
FAR struct fd **fdp)
{
FAR struct fd *fdp1;
irqstate_t flags;
flags = spin_lock_irqsave_notrace(&list->fl_lock);
fdp1 = &list->fl_fds[l1][l2];
*filep = fdp1->f_file;
if (*filep != NULL)
{
atomic_fetch_add(&(*filep)->f_refs, 1);
}
spin_unlock_irqrestore_notrace(&list->fl_lock, flags);
if (fdp != NULL)
{
*fdp = fdp1;
}
}
/****************************************************************************
* Name: fdlist_extend
****************************************************************************/
static int fdlist_extend(FAR struct fdlist *list, size_t row)
{
FAR struct fd **fds;
uint8_t orig_rows;
FAR void *tmp;
int flags;
int i;
int j;
orig_rows = list->fl_rows;
if (row <= orig_rows)
{
return 0;
}
if (CONFIG_NFILE_DESCRIPTORS_PER_BLOCK * (orig_rows + 1) > OPEN_MAX)
{
fdlist_dump(list);
return -EMFILE;
}
fds = fs_heap_malloc(sizeof(FAR struct fd *) * row);
DEBUGASSERT(fds);
if (fds == NULL)
{
return -ENFILE;
}
i = orig_rows;
do
{
fds[i] = fs_heap_zalloc(sizeof(struct fd) *
CONFIG_NFILE_DESCRIPTORS_PER_BLOCK);
if (fds[i] == NULL)
{
while (--i >= orig_rows)
{
fs_heap_free(fds[i]);
}
fs_heap_free(fds);
return -ENFILE;
}
}
while (++i < row);
flags = spin_lock_irqsave_notrace(&list->fl_lock);
/* To avoid race condition, if the file list is updated by other threads
* and list rows is greater or equal than temp list,
* release the obsolete buffers
*/
if (orig_rows != list->fl_rows && list->fl_rows >= row)
{
spin_unlock_irqrestore_notrace(&list->fl_lock, flags);
for (j = orig_rows; j < i; j++)
{
fs_heap_free(fds[j]);
}
fs_heap_free(fds);
return OK;
}
if (list->fl_fds != NULL)
{
memcpy(fds, list->fl_fds, list->fl_rows * sizeof(FAR struct fd *));
}
tmp = list->fl_fds;
list->fl_fds = fds;
list->fl_rows = row;
spin_unlock_irqrestore_notrace(&list->fl_lock, flags);
if (tmp != NULL && tmp != &list->fl_prefd)
{
fs_heap_free(tmp);
}
return OK;
}
/****************************************************************************
* Name: fdlist_uninstall
****************************************************************************/
static void fdlist_uninstall(FAR struct fdlist *list, FAR struct fd *fdp)
{
FAR struct file *filep = NULL;
irqstate_t flags;
flags = spin_lock_irqsave_notrace(&list->fl_lock);
if (fdp->f_file != NULL)
{
#ifdef CONFIG_FDSAN
fdp->f_tag_fdsan = 0;
#endif
#ifdef CONFIG_FDCHECK
fdp->f_tag_fdcheck = 0;
#endif
filep = fdp->f_file;
fdp->f_file = NULL;
}
spin_unlock_irqrestore_notrace(&list->fl_lock, flags);
file_put(filep);
}
static void fdlist_install(FAR struct fdlist *list, int fd,
FAR struct file *filep, FAR struct fd *fdp,
int oflags, bool copy)
{
FAR struct file *filep1;
FAR struct fd *fdp1;
irqstate_t flags;
int l1;
int l2;
l1 = fd / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK;
l2 = fd % CONFIG_NFILE_DESCRIPTORS_PER_BLOCK;
flags = spin_lock_irqsave_notrace(&list->fl_lock);
fdp1 = &list->fl_fds[l1][l2];
filep1 = fdp1->f_file;
fdp1->f_file = filep;
file_ref(filep);
fdp1->f_cloexec = !!(oflags & O_CLOEXEC);
FS_ADD_BACKTRACE(fdp1);
if (copy)
{
#ifdef CONFIG_FDSAN
fdp1->f_tag_fdsan = fdp->f_tag_fdsan;
#endif
#ifdef CONFIG_FDCHECK
fdp1->f_tag_fdcheck = fdp->f_tag_fdcheck;
#endif
}
spin_unlock_irqrestore_notrace(&list->fl_lock, flags);
file_put(filep1);
}
/****************************************************************************
* Name: task_fssync
****************************************************************************/
static void task_fssync(FAR struct tcb_s *tcb, FAR void *arg)
{
FAR struct tcb_s *ctcb;
int pid = tcb->pid;
uint8_t rows;
int i;
int j;
if (tcb->group == NULL)
{
return;
}
rows = tcb->group->tg_fdlist.fl_rows;
for (i = 0; i < rows; i++)
{
for (j = 0; j < CONFIG_NFILE_DESCRIPTORS_PER_BLOCK; j++)
{
FAR struct file *filep;
ctcb = nxsched_get_tcb(pid);
if (ctcb == NULL || ctcb->group == NULL || ctcb != tcb)
{
return;
}
fdlist_get_by_index(&ctcb->group->tg_fdlist, i, j, &filep, NULL);
if (filep != NULL)
{
file_fsync(filep);
file_put(filep);
}
}
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: fdlist_dup3
*
* Description:
* fdlist_dup3() is similar to the standard 'dup3' interface
* except that is not a cancellation point and it does not modify the
* errno variable.
*
* fdlist_dup3() is an internal NuttX interface and should not be
* called from applications.
*
* Clone a file descriptor to a specific descriptor number and
* specific flags.
*
* Returned Value:
* fd2 is returned on success; a negated errno value is return on
* any failure.
*
****************************************************************************/
int fdlist_dup3(FAR struct fdlist *list, int fd1, int fd2, int flags)
{
FAR struct file *filep1;
FAR struct fd *fdp1;
int ret;
if (fd1 == fd2)
{
return -EINVAL;
}
#ifdef CONFIG_FDCHECK
fd2 = fdcheck_restore(fd2);
#endif
/* Get the file descriptor list. It should not be NULL in this context. */
if (fd2 < 0)
{
return -EBADF;
}
if (fd2 >= fdlist_count(list))
{
ret = fdlist_extend(list,
fd2 / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + 1);
if (ret < 0)
{
return ret;
}
}
ret = fdlist_get2(list, fd1, &filep1, &fdp1);
if (ret < 0)
{
return ret;
}
fdlist_install(list, fd2, filep1, fdp1, flags, false);
file_put(filep1);
#ifdef CONFIG_FDCHECK
return fdcheck_protect(fd2);
#else
return fd2;
#endif
}
/****************************************************************************
* Name: fdlist_init
*
* Description: Initializes the list of file descriptors for a new task.
*
****************************************************************************/
void fdlist_init(FAR struct fdlist *list)
{
/* The first row will reuse pre-allocated files, which will avoid
* unnecessary allocator accesses during file initialization.
*/
list->fl_rows = 1;
list->fl_fds = &list->fl_prefd;
list->fl_prefd = list->fl_prefds;
spin_lock_init(&list->fl_lock);
}
/****************************************************************************
* Name: fdlist_dump
*
* Description:
* Dump the list of file descriptors.
*
****************************************************************************/
#ifdef CONFIG_SCHED_DUMP_ON_EXIT
void fdlist_dump(FAR struct fdlist *list)
{
FAR char *path;
int count = fdlist_count(list);
int i;
syslog(LOG_INFO, "%-4s%-4s%-8s%-5s%-10s%-14s"
#if CONFIG_FS_BACKTRACE > 0
" BACKTRACE"
#endif
"\n",
"PID", "FD", "FLAGS", "TYPE", "POS", "PATH"
);
path = lib_get_pathbuffer();
if (path == NULL)
{
return;
}
for (i = 0; i < count; i++)
{
FAR struct fd *fdp;
FAR struct file *filep;
#if CONFIG_FS_BACKTRACE > 0
char buf[BACKTRACE_BUFFER_SIZE(CONFIG_FS_BACKTRACE)];
#endif
/* Is there an inode associated with the file descriptor? */
if (fdlist_get2(list, i, &filep, &fdp) < 0)
{
continue;
}
if (file_ioctl(filep, FIOC_FILEPATH, path) < 0)
{
path[0] = '\0';
}
#if CONFIG_FS_BACKTRACE > 0
backtrace_format(buf, sizeof(buf), fdp->f_backtrace,
CONFIG_FS_BACKTRACE);
#endif
syslog(LOG_INFO, "%-4d%-4d%-8d%-5x%-10ld%-14s"
#if CONFIG_FS_BACKTRACE > 0
" %s"
#endif
"\n", getpid(), i, filep->f_oflags,
INODE_GET_TYPE(filep->f_inode),
(long)filep->f_pos, path
#if CONFIG_FS_BACKTRACE > 0
, buf
#endif
);
file_put(filep);
}
lib_put_pathbuffer(path);
}
#endif
/****************************************************************************
* Name: fdlist_free
*
* Description:
* Release the list of file descriptors.
*
* Assumptions:
* Called during task deletion in a safe context.
*
****************************************************************************/
void fdlist_free(FAR struct fdlist *list)
{
int i;
int j;
/* Close each file descriptor .. Normally, you would need take the list
* mutex, but it is safe to ignore the mutex in this context
* because there should not be any references in this context.
*/
for (i = list->fl_rows - 1; i >= 0; i--)
{
for (j = CONFIG_NFILE_DESCRIPTORS_PER_BLOCK - 1; j >= 0; j--)
{
fdlist_close(list, i * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + j);
}
if (i != 0)
{
fs_heap_free(list->fl_fds[i]);
}
}
if (list->fl_fds != &list->fl_prefd)
{
fs_heap_free(list->fl_fds);
}
}
/****************************************************************************
* Name: fdlist_count
*
* Description:
* Get file descriptor count from file list.
*
* Input Parameters:
* list - Pointer to the file descriptor list structure.
*
* Returned Value:
* file count of file list.
*
****************************************************************************/
int fdlist_count(FAR struct fdlist *list)
{
return list->fl_rows * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK;
}
/****************************************************************************
* Name: fdlist_get2
*
* Description:
* Given a file descriptor, return the corresponding instance of struct
* fd and filep.
*
* Input Parameters:
* list - Pointer to the file descriptor list structure.
* fd - The file descriptor
* filep - The location to return the struct file instance
* fdp - The location to return the struct fd instance
*
* Returned Value:
* Return the pointer to file structure of list[fd] when list[fd].f_file
* is valid, otherwise, a null pointer is returned.
*
****************************************************************************/
int fdlist_get2(FAR struct fdlist *list, int fd,
FAR struct file **filep, FAR struct fd **fdp)
{
if (list == NULL)
{
return -EINVAL;
}
#ifdef CONFIG_FDCHECK
fd = fdcheck_restore(fd);
#endif
if (fd < 0 || fd >= fdlist_count(list))
{
return -EBADF;
}
fdlist_get_by_index(list,
fd / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK,
fd % CONFIG_NFILE_DESCRIPTORS_PER_BLOCK,
filep, fdp);
if (*filep == NULL)
{
return -EBADF;
}
return 0;
}
/****************************************************************************
* Name: fdlist_dupfile
*
* Description:
* Allocate a struct fd instance and bind it to the corresponding file
* handle.
*
* Returned Value:
* Returns the file descriptor == index into the files array on success;
* a negated errno value is returned on any failure.
*
****************************************************************************/
int fdlist_dupfile(FAR struct fdlist *list, int oflags, int minfd,
FAR struct file *filep)
{
FAR struct fd *fdp;
irqstate_t flags;
int ret;
int i;
int j;
DEBUGASSERT(filep);
#ifdef CONFIG_FDCHECK
minfd = fdcheck_restore(minfd);
#endif
i = minfd / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK;
j = minfd % CONFIG_NFILE_DESCRIPTORS_PER_BLOCK;
/* Find free file descriptor */
flags = spin_lock_irqsave_notrace(&list->fl_lock);
for (; ; i++, j = 0)
{
if (i >= list->fl_rows)
{
spin_unlock_irqrestore_notrace(&list->fl_lock, flags);
ret = fdlist_extend(list, i + 1);
if (ret < 0)
{
return ret;
}
flags = spin_lock_irqsave_notrace(&list->fl_lock);
}
do
{
fdp = &list->fl_fds[i][j];
if (fdp->f_file == NULL)
{
atomic_fetch_add(&filep->f_refs, 1);
fdp->f_file = filep;
fdp->f_cloexec = !!(oflags & O_CLOEXEC);
#ifdef CONFIG_FDSAN
fdp->f_tag_fdsan = 0;
#endif
#ifdef CONFIG_FDCHECK
fdp->f_tag_fdcheck = 0;
#endif
goto found;
}
}
while (++j < CONFIG_NFILE_DESCRIPTORS_PER_BLOCK);
}
found:
spin_unlock_irqrestore_notrace(&list->fl_lock, flags);
FS_ADD_BACKTRACE(fdp);
#ifdef CONFIG_FDCHECK
return fdcheck_protect(i * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + j);
#else
return i * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + j;
#endif
}
/****************************************************************************
* Name: file_allocate_from_inode
*
* Description:
* Allocate a struct fd instance and associate it with an file instance.
* And initialize them with inode, oflags, pos and priv.
*
* Returned Value:
* Returns the file descriptor == index into the files array on success;
* a negated errno value is returned on any failure.
*
****************************************************************************/
int file_allocate_from_inode(FAR struct inode *inode, int oflags, off_t pos,
FAR void *priv, int minfd)
{
FAR struct file *filep;
int fd;
filep = file_allocate();
if (filep == NULL)
{
return -ENOMEM;
}
inode_addref(inode);
filep->f_inode = inode;
filep->f_pos = pos;
filep->f_oflags = oflags & ~O_CLOEXEC;
filep->f_priv = priv;
#if CONFIG_FS_LOCK_BUCKET_SIZE > 0
filep->f_locked = false;
#endif
fd = file_dup(filep, minfd, oflags);
if (fd < 0)
{
inode_release(inode);
file_deallocate(filep);
}
return fd;
}
/****************************************************************************
* Name: fdlist_copy
*
* Description:
* Copy parent task's file descriptors to child task.
*
* Returned Value:
* Zero (OK) is returned on success; a negated errno value is returned on
* any failure.
*
****************************************************************************/
int fdlist_copy(FAR struct fdlist *plist, FAR struct fdlist *clist,
FAR const posix_spawn_file_actions_t *actions,
bool cloexec)
{
bool fcloexec;
int ret;
int fd;
int i;
int j;
for (i = 0; i < plist->fl_rows; i++)
{
for (j = 0; j < CONFIG_NFILE_DESCRIPTORS_PER_BLOCK; j++)
{
FAR struct file *filep;
FAR struct fd *fdp;
fd = i * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + j;
#ifdef CONFIG_FDCLONE_STDIO
/* Determine how many file descriptors to clone. If
* CONFIG_FDCLONE_DISABLE is set, no file descriptors will be
* cloned. If CONFIG_FDCLONE_STDIO is set, only the first
* three descriptors (stdin, stdout, and stderr) will be
* cloned. Otherwise all file descriptors will be cloned.
*/
if (fd >= 3)
{
return OK;
}
#endif
fdlist_get_by_index(plist, i, j, &filep, &fdp);
if (filep == NULL)
{
continue;
}
fcloexec = cloexec && fdp->f_cloexec;
/* Skip file dup if file action is unnecessary to duplicate */
if (actions != NULL)
{
#ifdef CONFIG_FDCHECK
fd = fdcheck_protect(fd);
#endif
if (!spawn_file_is_duplicateable(actions, fd, fcloexec))
{
file_put(filep);
continue;
}
#ifdef CONFIG_FDCHECK
fd = fdcheck_restore(fd);
#endif
}
else if (fcloexec)
{
file_put(filep);
continue;
}
ret = fdlist_extend(clist, i + 1);
if (ret < 0)
{
file_put(filep);
return ret;
}
/* Assign filep to the child's descriptor list. Omit the flags */
fdlist_install(clist, fd, filep, fdp, 0, true);
file_put(filep);
}
}
return OK;
}
/****************************************************************************
* Name: file_allocate
*
* Description:
* Allocate a file instance and return
*
****************************************************************************/
FAR struct file *file_allocate(void)
{
return fs_heap_zalloc(sizeof(struct file));
}
/****************************************************************************
* Name: file_deallocate
*
* Description:
* Free a file instance.
*
****************************************************************************/
void file_deallocate(FAR struct file *filep)
{
fs_heap_free(filep);
}
/****************************************************************************
* Name: file_get2
*
* Description:
* Given a file descriptor, return the corresponding instance of struct
* fd and filep
*
* Input Parameters:
* fd - The file descriptor
* filep - The location to return the struct file instance
* fdp - The location to return the struct fd instance
*
* Returned Value:
* Zero (OK) is returned on success; a negated errno value is returned on
* any failure.
*
****************************************************************************/
int file_get2(int fd, FAR struct file **filep, FAR struct fd **fdp)
{
return fdlist_get2(nxsched_get_fdlist(), fd, filep, fdp);
}
/****************************************************************************
* Name: file_ref
*
* Description:
* To specify filep increase the reference count.
*
* Input Parameters:
* None.
*
* Returned Value:
* None.
*
****************************************************************************/
void file_ref(FAR struct file *filep)
{
/* This interface is used to increase the reference count of filep */
DEBUGASSERT(filep);
atomic_fetch_add(&filep->f_refs, 1);
}
/****************************************************************************
* Name: file_put
*
* Description:
* Handles reference counts for files, less than or equal to 0 and close
* the file
*
* Input Parameters:
* filep - The caller provided location in which to return the 'struct
* file' instance.
****************************************************************************/
int file_put(FAR struct file *filep)
{
int ret = 0;
if (filep == NULL)
{
return ret;
}
/* If refs is zero, the close() had called, closing it now. */
if (atomic_fetch_sub(&filep->f_refs, 1) == 1)
{
ret = file_close(filep);
if (ret < 0)
{
ferr("ERROR: fs putfilep file_close() failed: %d\n", ret);
}
fs_heap_free(filep);
}
return ret;
}
/****************************************************************************
* Name: fdlist_dup2
*
* Description:
* fdlist_dup2() is similar to the standard 'dup2' interface
* except that is not a cancellation point and it does not modify the
* errno variable.
*
* fdlist_dup2() is an internal NuttX interface and should not be
* called from applications.
*
* Clone a file descriptor to a specific descriptor number.
*
* Returned Value:
* fd2 is returned on success; a negated errno value is return on
* any failure.
*
****************************************************************************/
int fdlist_dup2(FAR struct fdlist *list, int fd1, int fd2)
{
/* If fd1 is a valid file descriptor, and fd1 == fd2, then dup2() does
* nothing, and returns fd2.
*/
if (fd1 == fd2)
{
FAR struct file *filep;
int ret;
ret = file_get(fd1, &filep);
if (ret < 0)
{
return ret;
}
/* Release the reference */
file_put(filep);
return fd2;
}
return fdlist_dup3(list, fd1, fd2, 0);
}
/****************************************************************************
* Name: fdlist_close
*
* Description:
* fdlist_close() is similar to the standard 'close' interface
* except that is not a cancellation point and it does not modify the
* errno variable.
*
* fdlist_close() is an internal NuttX interface and should not
* be called from applications.
*
* Close an inode (if open)
*
* Returned Value:
* Zero (OK) is returned on success; A negated errno value is returned on
* on any failure.
*
* Assumptions:
* Caller holds the list mutex because the file descriptor will be
* freed.
*
****************************************************************************/
int fdlist_close(FAR struct fdlist *list, int fd)
{
FAR struct file *filep;
FAR struct fd *fdp;
int ret;
ret = fdlist_get2(list, fd, &filep, &fdp);
if (ret < 0)
{
return ret;
}
/* Perform the protected close operation */
fdlist_uninstall(list, fdp);
/* fdlist_get2 will increase the reference count, there call
* file_put reduce reference count.
*/
return file_put(filep);
}
/****************************************************************************
* Name: sync
*
* Description:
* sync() causes all pending modifications to filesystem metadata and
* cached file data to be written to the underlying filesystems.
*
****************************************************************************/
void sync(void)
{
nxsched_foreach(task_fssync, NULL);
}