Skip to content

fs,sched: add chroot() filesystem jail - #19900

Open
Abhishekmishra2808 wants to merge 5 commits into
apache:masterfrom
Abhishekmishra2808:feature/fs-chroot
Open

fs,sched: add chroot() filesystem jail#19900
Abhishekmishra2808 wants to merge 5 commits into
apache:masterfrom
Abhishekmishra2808:feature/fs-chroot

Conversation

@Abhishekmishra2808

@Abhishekmishra2808 Abhishekmishra2808 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

CONFIG_FS_CHROOT adds POSIX chroot() so a task group can pin a directory as its filesystem root. Absolute lookups start at that inode, children inherit the jail, and PWD is rewritten so relative paths cannot walk out. This is a filesystem jail, not a container: descriptors opened before chroot() that already point outside the tree remain usable.

Impact

The option is off by default. When enabled, chroot() is a new syscall and, with SCHED_USER_IDENTITY, requires effective UID 0. On CONFIG_BUILD_FLAT the euid == 0 gate and tg_root share the same trust boundary as credentials. Companion apps change: apache/nuttx-apps#3735.

Testing

Host: WSL2 x86_64. Board: sim (CONFIG_FS_CHROOT=y). Companion apps PR: apache/nuttx-apps#3735.

$ ./tools/checkpatch.sh -f fs/inode/fs_inodesearch.c fs/vfs/fs_chroot.c \
    include/nuttx/sched.h include/unistd.h sched/group/group_create.c \
    sched/group/group_leave.c include/nuttx/fs/fs.h syscall/syscall.csv \
    fs/Kconfig fs/vfs/Make.defs fs/vfs/CMakeLists.txt \
    Documentation/implementation/chroot.rst \
    Documentation/implementation/user_identity.rst \
    Documentation/applications/nsh/commands.rst
✔️ All checks pass.
$ cd Documentation && make html
build succeeded.

The HTML pages are in _build/html.

NSH no-command form (syscall from userspace; chroot builtin is in apache/nuttx-apps#3735):

login: root
User Logged-in!
nsh> mkdir /tmp/jail
nsh> echo hello > /tmp/jail/marker
nsh> echo secret > /tmp/secret
nsh> chroot /tmp/jail
nsh> pwd
/
nsh> cat /marker
hello
nsh> ls /dev
nsh: ls: stat failed: 2
nsh> cat /tmp/secret
nsh: cat: open failed: 2

ostest (kernel jail, leftover fd, inheritance):

nsh> ostest
...
user_main: chroot test
chroot_test: Starting test
chroot_test: /marker is visible inside the jail
chroot_test: host paths are not visible inside the jail
chroot_test: pre-opened host fd still usable after chroot
chroot_test: grandchild still sees the jail
chroot_test: PASSED

chroot() holds a directory inode in the task group, so sched needs the
existing refcount helpers instead of duplicating them.

Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
Comment thread Documentation/applications/nsh/commands.rst
Comment thread Documentation/implementation/chroot.rst Outdated
Comment thread Documentation/implementation/chroot.rst
Comment thread Documentation/implementation/chroot.rst Outdated
Comment thread Documentation/implementation/user_identity.rst Outdated
Comment thread fs/inode/fs_inodesearch.c
@acassis

acassis commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Nice work @Abhishekmishra2808 !
@xiaoxiang781216 @raiden00pl maybe we could use it to create separated jails/arenas to use with telnet / ssh to avoid user to damage the original root filesystems. But I think in this case we need to be able to mount a limited /dev, /proc, etc

@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

s698pm-dkit

Comment thread include/nuttx/fs/fs.h
void fs_initialize(void);

/****************************************************************************
* Name: inode_addref

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you need remove the prototype from the internal header file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped the public prototypes; they stay in fs/inode/inode.h

# define group_inherit_identity(group)
#endif

#ifdef CONFIG_FS_CHROOT

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where we define this config

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fs/Kconfig

Comment thread sched/group/group_create.c Outdated
Comment thread fs/vfs/fs_chroot.c Outdated
Comment thread sched/group/group_create.c Outdated
Comment thread fs/vfs/fs_chroot.c Outdated
Comment thread fs/inode/fs_inodesearch.c
FAR const char *end = src;
size_t seglen;

while (*end != '\0' && *end != '/')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not assign to out directly? we should touch in only once in the loop

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we copy into out in one pass without walking in again?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread include/nuttx/sched.h
/* chroot() jail **********************************************************/

FAR struct inode *tg_root; /* NULL means global pseudo-root */
FAR char *tg_rootrel; /* Relpath prefix if tg_root is a mount */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not save the absolute path directly? it's complex to handle tg_root and tg_rootrel in the late patch.
BTW, the same path may remount to other file system, the current implementation can't handle it correctly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Store the jail as an absolute path and re-resolve it on lookup instead of tg_root / tg_rootrel?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this make the code simpler and handle the mount point destroy/create correctly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread fs/vfs/fs_chroot.c Outdated
Comment thread fs/inode/fs_inodesearch.c
FAR struct inode *above = NULL;
FAR const char *relpath = NULL;
#ifdef CONFIG_FS_CHROOT
FAR struct inode *search_root = g_root_inode;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's more simple to:

  1. prepend the root to the path
  2. normalize the new path
  3. ensure the result is under the root
  4. continue the original flow

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prepend jail path, normalize, check still under root, then keep the original walk?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Store tg_root / tg_rootrel on the task group, copy them to children,
and drop the inode when the last member leaves.

Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
Add CONFIG_FS_CHROOT and POSIX chroot(). Pin a directory as the group
root, rewrite PWD, and require euid 0 when user identity is enabled.

Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
Walk from tg_root instead of the global pseudo-root, clamp ".." at the
jail, and normalize absolute paths so chroot(".") under a mount does
not pass a leftover "." as relpath.

Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
Describe the jail, leftover pre-opened fds, the NSH command-form scrub,
and the flat-build trust boundary shared with credentials.

Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Documentation Improvements or additions to documentation Size: L The size of the change in this PR is large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants