fs/inode: Fix fd_tag_san/fd_tag_fdcheck loss during parent-to-child inheritance

When a child process inherits file descriptors from its parent via
fdlist_copy(), the fd tags (fd_tag_fdsan and fd_tag_fdcheck) were not
being copied. This caused assertion failures when the child process
closed inherited file descriptors, because the fdcheck/fdsan subsystems
expected the tags to match.

Root Cause:
----------
The fdlist_install() function was not preserving the fd tags during
fd duplication. When copying fds from parent to child in fdlist_copy(),
the tags were lost, resulting in:
- fd_tag_fdsan: Used for file descriptor ownership tracking (FDSAN)
- fd_tag_fdcheck: Used for fd validity checking (FDCHECK)

Both tags being reset to 0/NULL instead of copied from parent.

Symptom:
--------
Child processes would crash with assertion failure in fdcheck_restore()
when closing inherited file descriptors:

  fdcheck_restore+0x69/0xa0
  fdlist_get2+0x11/0x48
  fdlist_close+0xd/0x94
  close+0x15/0x30

This occurred because fdcheck_restore() validates that the fd_tag_fdcheck
matches the expected value, and the mismatch triggered an assertion.

Solution:
---------
1. Add a 'copy' parameter to fdlist_install() to distinguish between:
   - New fd allocation (copy=false): Initialize fresh tags
   - Fd duplication (copy=true): Preserve tags from source fd

2. Add fdp parameter to fdlist_install() to access source fd tags

3. In fdlist_copy(), pass copy=true to preserve parent's fd tags

4. In fdlist_dup3(), pass copy=false since dup operations should
   create independent fd tracking (not copy parent tags)

Changes:
--------
- fdlist_install(): Added 'fdp' and 'copy' parameters
- When copy=true, preserve fd_tag_fdsan and fd_tag_fdcheck from source
- fdlist_copy(): Pass copy=true to preserve parent tags
- fdlist_dup3(): Pass copy=false for normal dup behavior

Impact:
-------
This fix ensures that file descriptor ownership tracking and validity
checking work correctly across fork/clone operations, preventing
crashes when child processes close inherited file descriptors.

Without this fix, any program using fork() with inherited fds would
crash if CONFIG_FDSAN or CONFIG_FDCHECK were enabled.

Issue backtrace:
backtrace_unwind+0x105/0x108
sched_backtrace+0x6f/0x80
sched_dumpstack+0x33/0x80
_assert+0x229/0x510
arm_syscall+0x81/0x98
up_assert+0xd/0x18
__assert+0x1d/0x24
fdcheck_restore+0x69/0xa0
fdlist_get2+0x11/0x48
fdlist_close+0xd/0x94
close+0x15/0x30
closefd+0x5/0x30
notify_parent_process+0x2b/0x40
run_helper_tcp4_echo_server+0x75/0x114
run_test_part+0x5f/0x68
uv_run_tests_main+0x35/0x60
run_test_part+0x5f/0x68
uv_run_tests_main+0x35/0x60
nxtask_startup+0x13/0x2c
nxtask_start+0x4d/0x64

Signed-off-by: dongjiuzhu1 <dongjiuzhu1@xiaomi.com>
This commit is contained in:
dongjiuzhu1
2025-07-29 10:26:50 +08:00
committed by Alan C. Assis
parent e399c93cf3
commit 50c78843b4
+23 -12
View File
@@ -203,10 +203,11 @@ static void fdlist_uninstall(FAR struct fdlist *list, FAR struct fd *fdp)
}
static void fdlist_install(FAR struct fdlist *list, int fd,
FAR struct file *filep, int oflags)
FAR struct file *filep, FAR struct fd *fdp,
int oflags, bool copy)
{
FAR struct file *oldfilep;
FAR struct fd *fdp;
FAR struct file *filep1;
FAR struct fd *fdp1;
irqstate_t flags;
int l1;
int l2;
@@ -216,15 +217,24 @@ static void fdlist_install(FAR struct fdlist *list, int fd,
flags = spin_lock_irqsave_notrace(&list->fl_lock);
fdp = &list->fl_fds[l1][l2];
oldfilep = fdp->f_file;
fdp->f_file = filep;
fdp1 = &list->fl_fds[l1][l2];
filep1 = fdp1->f_file;
fdp1->f_file = filep;
file_ref(filep);
fdp->f_cloexec = !!(oflags & O_CLOEXEC);
FS_ADD_BACKTRACE(fdp);
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(oldfilep);
file_put(filep1);
}
/****************************************************************************
@@ -295,6 +305,7 @@ static void task_fssync(FAR struct tcb_s *tcb, FAR void *arg)
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)
@@ -323,13 +334,13 @@ int fdlist_dup3(FAR struct fdlist *list, int fd1, int fd2, int flags)
}
}
ret = fdlist_get(list, fd1, &filep1);
ret = fdlist_get2(list, fd1, &filep1, &fdp1);
if (ret < 0)
{
return ret;
}
fdlist_install(list, fd2, filep1, flags);
fdlist_install(list, fd2, filep1, fdp1, flags, false);
file_put(filep1);
#ifdef CONFIG_FDCHECK
@@ -820,7 +831,7 @@ int fdlist_copy(FAR struct fdlist *plist, FAR struct fdlist *clist,
/* Assign filep to the child's descriptor list. Omit the flags */
fdlist_install(clist, fd, filep, 0);
fdlist_install(clist, fd, filep, fdp, 0, true);
file_put(filep);
}
}