This is an automated email from the ASF dual-hosted git repository. xiaoxiang781216 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 2977db2632735ed608fa8db26e095d581fe8178c Author: Abhishek Mishra <[email protected]> AuthorDate: Wed Aug 19 08:07:17 2026 +0000 fs: add chroot() syscall Add CONFIG_FS_CHROOT and POSIX chroot(). Store the jail as an absolute path on the task group, and require euid 0 when user identity is enabled. Signed-off-by: Abhishek Mishra <[email protected]> --- fs/Kconfig | 12 +++++ fs/vfs/CMakeLists.txt | 4 ++ fs/vfs/Make.defs | 4 ++ fs/vfs/fs_chroot.c | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++ include/unistd.h | 3 ++ syscall/syscall.csv | 1 + 6 files changed, 155 insertions(+) diff --git a/fs/Kconfig b/fs/Kconfig index b42c5d513d8..2ad6dbf4259 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -105,6 +105,18 @@ config FS_PIN The pin is held for as long as the module is loaded and given back when it is unloaded. +config FS_CHROOT + bool "chroot() filesystem jail" + default n + ---help--- + Enable POSIX chroot(). Each task group may pin a directory as its + root; absolute path lookup starts there so the group cannot see + files outside that tree. This is a filesystem jail, not a + container (open file descriptors that already point outside the + tree remain usable). Nested chroot() is relative to the current + root. When SCHED_USER_IDENTITY is enabled, chroot() requires + effective UID 0. + config FS_PERMISSION bool "Enable UNIX Filesystem Permission Support" default n diff --git a/fs/vfs/CMakeLists.txt b/fs/vfs/CMakeLists.txt index b5318de6719..85a99add6ba 100644 --- a/fs/vfs/CMakeLists.txt +++ b/fs/vfs/CMakeLists.txt @@ -54,6 +54,10 @@ set(SRCS fs_truncate.c fs_link.c) +if(CONFIG_FS_CHROOT) + list(APPEND SRCS fs_chroot.c) +endif() + # File notify support if(CONFIG_FS_NOTIFY) diff --git a/fs/vfs/Make.defs b/fs/vfs/Make.defs index e8e588680e9..172d779fc95 100644 --- a/fs/vfs/Make.defs +++ b/fs/vfs/Make.defs @@ -29,6 +29,10 @@ CSRCS += fs_rename.c fs_rmdir.c fs_select.c fs_sendfile.c fs_stat.c CSRCS += fs_statfs.c fs_uio.c fs_unlink.c fs_write.c fs_dir.c fs_fsync.c CSRCS += fs_syncfs.c fs_truncate.c fs_link.c +ifeq ($(CONFIG_FS_CHROOT),y) +CSRCS += fs_chroot.c +endif + ifeq ($(CONFIG_FS_NOTIFY),y) CSRCS += fs_inotify.c endif diff --git a/fs/vfs/fs_chroot.c b/fs/vfs/fs_chroot.c new file mode 100644 index 00000000000..84d380a925f --- /dev/null +++ b/fs/vfs/fs_chroot.c @@ -0,0 +1,131 @@ +/**************************************************************************** + * fs/vfs/fs_chroot.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/stat.h> +#include <assert.h> +#include <errno.h> +#include <string.h> + +#include <nuttx/fs/fs.h> +#include <nuttx/sched.h> + +#include "inode/inode.h" +#include "fs_heap.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: chroot + * + * Description: + * Cause the named directory to become the root directory, that is, the + * starting point for path names beginning with '/'. + * + * Input Parameters: + * path - Directory to use as the new root + * + * Returned Value: + * 0(OK) on success; -1(ERROR) on failure with errno set appropriately. + * + ****************************************************************************/ + +int chroot(FAR const char *path) +{ + FAR struct tcb_s *rtcb; + FAR struct task_group_s *group; + FAR char *newroot; + struct inode_search_s desc; + struct stat buf; + int ret; + + if (path == NULL || path[0] == '\0') + { + set_errno(ENOENT); + return ERROR; + } + + rtcb = nxsched_self(); + DEBUGASSERT(rtcb != NULL && rtcb->group != NULL); + group = rtcb->group; + +#ifdef CONFIG_SCHED_USER_IDENTITY + if (group->tg_euid != 0) + { + set_errno(EPERM); + return ERROR; + } +#endif + + ret = stat(path, &buf); + if (ret < 0) + { + return ERROR; + } + + if (!S_ISDIR(buf.st_mode)) + { + set_errno(ENOTDIR); + return ERROR; + } + + /* Resolve to a host absolute path the same way lookups do: make + * absolute, prepend the current jail, and canonicalize. No second + * inode walk. + */ + + ret = inode_search_setup(&desc, path, true); + if (ret < 0) + { + set_errno(-ret); + return ERROR; + } + + /* Host "/" means no jail. Clear any previous root. */ + + if (strcmp(desc.path, "/") == 0) + { + fs_heap_free(group->tg_root); + group->tg_root = NULL; + inode_search_release(&desc); + return OK; + } + + newroot = fs_heap_strdup(desc.path); + inode_search_release(&desc); + if (newroot == NULL) + { + set_errno(ENOMEM); + return ERROR; + } + + fs_heap_free(group->tg_root); + group->tg_root = newroot; + return OK; +} diff --git a/include/unistd.h b/include/unistd.h index e46dda9d3cb..c719ff9f952 100644 --- a/include/unistd.h +++ b/include/unistd.h @@ -428,6 +428,9 @@ unsigned int alarm(unsigned int seconds); int chdir(FAR const char *path); int fchdir(int fd); +#ifdef CONFIG_FS_CHROOT +int chroot(FAR const char *path); +#endif FAR char *getcwd(FAR char *buf, size_t size); FAR char *get_current_dir_name(void); diff --git a/syscall/syscall.csv b/syscall/syscall.csv index 439630d7130..1b93dcc4d2d 100644 --- a/syscall/syscall.csv +++ b/syscall/syscall.csv @@ -10,6 +10,7 @@ "boardctl","sys/boardctl.h","defined(CONFIG_BOARDCTL)","int","unsigned int","uintptr_t" "chmod","sys/stat.h","","int","FAR const char *","mode_t" "chown","unistd.h","","int","FAR const char *","uid_t","gid_t" +"chroot","unistd.h","defined(CONFIG_FS_CHROOT)","int","FAR const char *" "clearenv","stdlib.h","!defined(CONFIG_DISABLE_ENVIRON)","int" "clock","time.h","","clock_t" "clock_adjtime","sys/timex.h","defined(CONFIG_CLOCK_ADJTIME)","int","clockid_t","struct timex *"
