nuttx/fs/inode/fs_files.c
hujun5 8fe8417ffb libc/fdcheck: add fdcheck module
In embedded development environments, due to the lack of address isolation between processes,
fd may be passed between processes and lead to misuse,

We have designed an fd cross-process automatic detection tool,
fdcheck_protect returns the fd containing the pid information,
indicating that the ownership of the current fd belongs to the pid and is not allowed to be used by other processes.
fdcheck_restore will obtain the true fd and check if the ownership of the fd is legal

For ease of understanding, let's give an example where
the following information is represented in 32-bit binary format

fd        00000000 00000000 00000000 10001010
pid       00000000 00000000 00000011 01010101
ret       00000000 00000011 01010101 10001010

Signed-off-by: hujun5 <hujun5@xiaomi.com>
2023-06-10 02:19:58 +08:00

761 lines
19 KiB
C

/****************************************************************************
* fs/inode/fs_files.c
*
* 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 <sched.h>
#include <errno.h>
#include <fcntl.h>
#include <nuttx/fs/fs.h>
#include <nuttx/kmalloc.h>
#include <nuttx/cancelpt.h>
#include <nuttx/mutex.h>
#include <nuttx/sched.h>
#ifdef CONFIG_FDSAN
# include <android/fdsan.h>
#endif
#ifdef CONFIG_FDCHECK
# include <nuttx/fdcheck.h>
#endif
#include "inode/inode.h"
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: files_extend
****************************************************************************/
static int files_extend(FAR struct filelist *list, size_t row)
{
FAR struct file **tmp;
int i;
if (row <= list->fl_rows)
{
return 0;
}
if (row * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK > OPEN_MAX)
{
return -EMFILE;
}
tmp = kmm_realloc(list->fl_files, sizeof(FAR struct file *) * row);
DEBUGASSERT(tmp);
if (tmp == NULL)
{
return -ENFILE;
}
i = list->fl_rows;
do
{
tmp[i] = kmm_zalloc(sizeof(struct file) *
CONFIG_NFILE_DESCRIPTORS_PER_BLOCK);
if (tmp[i] == NULL)
{
while (--i >= list->fl_rows)
{
kmm_free(tmp[i]);
}
kmm_free(tmp);
return -ENFILE;
}
}
while (++i < row);
list->fl_files = tmp;
list->fl_rows = row;
/* Note: If assertion occurs, the fl_rows has a overflow.
* And there may be file descriptors leak in system.
*/
DEBUGASSERT(list->fl_rows == row);
return 0;
}
static void task_fssync(FAR struct tcb_s *tcb, FAR void *arg)
{
FAR struct filelist *list;
int i;
int j;
list = &tcb->group->tg_filelist;
if (nxmutex_lock(&list->fl_lock) < 0)
{
return;
}
for (i = 0; i < list->fl_rows; i++)
{
for (j = 0; j < CONFIG_NFILE_DESCRIPTORS_PER_BLOCK; j++)
{
FAR struct file *filep;
filep = &list->fl_files[i][j];
if (filep != NULL && filep->f_inode != NULL)
{
file_fsync(filep);
}
}
}
nxmutex_unlock(&list->fl_lock);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: files_initlist
*
* Description: Initializes the list of files for a new task
*
****************************************************************************/
void files_initlist(FAR struct filelist *list)
{
DEBUGASSERT(list);
/* Initialize the list access mutex */
nxmutex_init(&list->fl_lock);
}
/****************************************************************************
* Name: files_releaselist
*
* Description:
* Release a reference to the file list
*
****************************************************************************/
void files_releaselist(FAR struct filelist *list)
{
int i;
int j;
DEBUGASSERT(list);
/* 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--)
{
file_close(&list->fl_files[i][j]);
}
kmm_free(list->fl_files[i]);
}
kmm_free(list->fl_files);
/* Destroy the mutex */
nxmutex_destroy(&list->fl_lock);
}
/****************************************************************************
* Name: file_allocate_from_tcb
*
* Description:
* Allocate a struct files instance and associate it with an inode
* instance.
*
* 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_tcb(FAR struct tcb_s *tcb, FAR struct inode *inode,
int oflags, off_t pos, FAR void *priv, int minfd,
bool addref)
{
FAR struct filelist *list;
int ret;
int i;
int j;
/* Get the file descriptor list. It should not be NULL in this context. */
list = nxsched_get_files_from_tcb(tcb);
DEBUGASSERT(list != NULL);
ret = nxmutex_lock(&list->fl_lock);
if (ret < 0)
{
/* Probably canceled */
return ret;
}
/* Calculate minfd whether is in list->fl_files.
* if not, allocate a new filechunk.
*/
i = minfd / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK;
if (i >= list->fl_rows)
{
ret = files_extend(list, i + 1);
if (ret < 0)
{
nxmutex_unlock(&list->fl_lock);
return ret;
}
}
/* Find free file */
j = minfd % CONFIG_NFILE_DESCRIPTORS_PER_BLOCK;
do
{
do
{
if (!list->fl_files[i][j].f_inode)
{
list->fl_files[i][j].f_oflags = oflags;
list->fl_files[i][j].f_pos = pos;
list->fl_files[i][j].f_inode = inode;
list->fl_files[i][j].f_priv = priv;
nxmutex_unlock(&list->fl_lock);
if (addref)
{
inode_addref(inode);
}
#ifdef CONFIG_FDCHECK
return
fdcheck_protect(i * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + j);
#else
return i * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + j;
#endif
}
}
while (++j < CONFIG_NFILE_DESCRIPTORS_PER_BLOCK);
j = 0;
}
while (++i < list->fl_rows);
/* The space of file array isn't enough, allocate a new filechunk */
ret = files_extend(list, i + 1);
if (ret < 0)
{
nxmutex_unlock(&list->fl_lock);
return ret;
}
list->fl_files[i][0].f_oflags = oflags;
list->fl_files[i][0].f_pos = pos;
list->fl_files[i][0].f_inode = inode;
list->fl_files[i][0].f_priv = priv;
nxmutex_unlock(&list->fl_lock);
if (addref)
{
inode_addref(inode);
}
#ifdef CONFIG_FDCHECK
return fdcheck_protect(i * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK);
#else
return i * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK;
#endif
}
/****************************************************************************
* Name: file_allocate
*
* Description:
* Allocate a struct files instance and associate it with an inode
* instance.
*
* 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(FAR struct inode *inode, int oflags, off_t pos,
FAR void *priv, int minfd, bool addref)
{
return file_allocate_from_tcb(nxsched_self(), inode, oflags,
pos, priv, minfd, addref);
}
/****************************************************************************
* Name: files_duplist
*
* Description:
* Duplicate parent task's file descriptors.
*
****************************************************************************/
int files_duplist(FAR struct filelist *plist, FAR struct filelist *clist)
{
int ret;
int i;
int j;
DEBUGASSERT(clist);
DEBUGASSERT(plist);
ret = nxmutex_lock(&plist->fl_lock);
if (ret < 0)
{
/* Probably canceled */
return ret;
}
for (i = 0; i < plist->fl_rows; i++)
{
for (j = 0; j < CONFIG_NFILE_DESCRIPTORS_PER_BLOCK; j++)
{
FAR struct file *filep;
#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 (i * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + j >= 3)
{
goto out;
}
#endif
filep = &plist->fl_files[i][j];
DEBUGASSERT(filep);
if (filep && (filep->f_inode == NULL ||
(filep->f_oflags & O_CLOEXEC) != 0))
{
continue;
}
ret = files_extend(clist, i + 1);
if (ret < 0)
{
goto out;
}
/* Yes... duplicate it for the child */
ret = file_dup2(filep, &clist->fl_files[i][j]);
if (ret < 0)
{
goto out;
}
}
}
out:
nxmutex_unlock(&plist->fl_lock);
return ret;
}
/****************************************************************************
* Name: fs_getfilep
*
* Description:
* Given a file descriptor, return the corresponding instance of struct
* file.
*
* Input Parameters:
* fd - The file descriptor
* filep - The location to return the struct file instance
*
* Returned Value:
* Zero (OK) is returned on success; a negated errno value is returned on
* any failure.
*
****************************************************************************/
int fs_getfilep(int fd, FAR struct file **filep)
{
FAR struct filelist *list;
int ret;
#ifdef CONFIG_FDCHECK
fd = fdcheck_restore(fd);
#endif
DEBUGASSERT(filep != NULL);
*filep = NULL;
list = nxsched_get_files();
/* The file list can be NULL under two cases: (1) One is an obscure
* cornercase: When memory management debug output is enabled. Then
* there may be attempts to write to stdout from malloc before the group
* data has been allocated. The other other is (2) if this is a kernel
* thread. Kernel threads have no allocated file descriptors.
*/
if (list == NULL)
{
return -EAGAIN;
}
if (fd < 0 || fd >= list->fl_rows * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK)
{
return -EBADF;
}
/* The descriptor is in a valid range to file descriptor... Get the
* thread-specific file list.
*/
/* And return the file pointer from the list */
ret = nxmutex_lock(&list->fl_lock);
if (ret < 0)
{
return ret;
}
*filep = &list->fl_files[fd / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK]
[fd % CONFIG_NFILE_DESCRIPTORS_PER_BLOCK];
/* if f_inode is NULL, fd was closed */
if (!(*filep)->f_inode)
{
*filep = NULL;
ret = -EBADF;
}
nxmutex_unlock(&list->fl_lock);
return ret;
}
/****************************************************************************
* Name: nx_dup2_from_tcb
*
* Description:
* nx_dup2_from_tcb() is similar to the standard 'dup2' interface
* except that is not a cancellation point and it does not modify the
* errno variable.
*
* nx_dup2_from_tcb() 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 nx_dup2_from_tcb(FAR struct tcb_s *tcb, int fd1, int fd2)
{
FAR struct filelist *list;
FAR struct file *filep;
FAR struct file file;
int ret;
if (fd1 == fd2)
{
return fd1;
}
#ifdef CONFIG_FDCHECK
fd1 = fdcheck_restore(fd1);
fd2 = fdcheck_restore(fd2);
#endif
list = nxsched_get_files_from_tcb(tcb);
/* Get the file descriptor list. It should not be NULL in this context. */
if (fd1 < 0 || fd1 >= CONFIG_NFILE_DESCRIPTORS_PER_BLOCK * list->fl_rows ||
fd2 < 0)
{
return -EBADF;
}
ret = nxmutex_lock(&list->fl_lock);
if (ret < 0)
{
/* Probably canceled */
return ret;
}
if (fd2 >= CONFIG_NFILE_DESCRIPTORS_PER_BLOCK * list->fl_rows)
{
ret = files_extend(list, fd2 / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + 1);
if (ret < 0)
{
nxmutex_unlock(&list->fl_lock);
return ret;
}
}
filep = &list->fl_files[fd2 / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK]
[fd2 % CONFIG_NFILE_DESCRIPTORS_PER_BLOCK];
memcpy(&file, filep, sizeof(struct file));
memset(filep, 0, sizeof(struct file));
/* Perform the dup2 operation */
ret = file_dup2(&list->fl_files[fd1 / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK]
[fd1 % CONFIG_NFILE_DESCRIPTORS_PER_BLOCK],
filep);
#ifdef CONFIG_FDSAN
filep->f_tag = file.f_tag;
#endif
nxmutex_unlock(&list->fl_lock);
file_close(&file);
#ifdef CONFIG_FDCHECK
return ret < 0 ? ret : fdcheck_protect(fd2);
#else
return ret < 0 ? ret : fd2;
#endif
}
/****************************************************************************
* Name: nx_dup2
*
* Description:
* nx_dup2() is similar to the standard 'dup2' interface except that is
* not a cancellation point and it does not modify the errno variable.
*
* nx_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 nx_dup2(int fd1, int fd2)
{
return nx_dup2_from_tcb(nxsched_self(), fd1, fd2);
}
/****************************************************************************
* Name: dup2
*
* Description:
* Clone a file descriptor or socket descriptor to a specific descriptor
* number
*
****************************************************************************/
int dup2(int fd1, int fd2)
{
int ret;
ret = nx_dup2(fd1, fd2);
if (ret < 0)
{
set_errno(-ret);
ret = ERROR;
}
return ret;
}
/****************************************************************************
* Name: nx_close_from_tcb
*
* Description:
* nx_close_from_tcb() is similar to the standard 'close' interface
* except that is not a cancellation point and it does not modify the
* errno variable.
*
* nx_close_from_tcb() 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 nx_close_from_tcb(FAR struct tcb_s *tcb, int fd)
{
FAR struct file *filep;
FAR struct file file;
FAR struct filelist *list;
int ret;
#ifdef CONFIG_FDCHECK
fd = fdcheck_restore(fd);
#endif
list = nxsched_get_files_from_tcb(tcb);
/* Perform the protected close operation */
ret = nxmutex_lock(&list->fl_lock);
if (ret < 0)
{
return ret;
}
/* If the file was properly opened, there should be an inode assigned */
if (fd < 0 || fd >= list->fl_rows * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK ||
!list->fl_files[fd / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK]
[fd % CONFIG_NFILE_DESCRIPTORS_PER_BLOCK].f_inode)
{
nxmutex_unlock(&list->fl_lock);
return -EBADF;
}
filep = &list->fl_files[fd / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK]
[fd % CONFIG_NFILE_DESCRIPTORS_PER_BLOCK];
memcpy(&file, filep, sizeof(struct file));
memset(filep, 0, sizeof(struct file));
nxmutex_unlock(&list->fl_lock);
return file_close(&file);
}
/****************************************************************************
* Name: nx_close
*
* Description:
* nx_close() is similar to the standard 'close' interface except that is
* not a cancellation point and it does not modify the errno variable.
*
* nx_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 nx_close(int fd)
{
return nx_close_from_tcb(nxsched_self(), fd);
}
/****************************************************************************
* Name: close
*
* Description:
* close() closes a file descriptor, so that it no longer refers to any
* file and may be reused. Any record locks (see fcntl(2)) held on the file
* it was associated with, and owned by the process, are removed
* (regardless of the file descriptor that was used to obtain the lock).
*
* If fd is the last copy of a particular file descriptor the resources
* associated with it are freed; if the descriptor was the last reference
* to a file which has been removed using unlink(2) the file is deleted.
*
* Input Parameters:
* fd file descriptor to close
*
* Returned Value:
* 0 on success; -1 on error with errno set appropriately.
*
* Assumptions:
*
****************************************************************************/
int close(int fd)
{
int ret;
#ifdef CONFIG_FDSAN
android_fdsan_exchange_owner_tag(fd, 0, 0);
#endif
/* close() is a cancellation point */
enter_cancellation_point();
ret = nx_close(fd);
if (ret < 0)
{
set_errno(-ret);
ret = ERROR;
}
leave_cancellation_point();
return ret;
}
/****************************************************************************
* 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);
}