This is an automated email from the ASF dual-hosted git repository. acassis pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit b85f4a1b49c426c45c3ab63f61f9c48204699593 Author: Marco Casaroli <[email protected]> AuthorDate: Wed Nov 4 13:19:59 2026 +0100 libs/libc/unistd: chdir() needs search permission on the directory. POSIX: chdir() fails with EACCES when search permission is denied for any component of the path, also the last one. chdir() only called stat(), and stat() needs no search permission on the last component: with CONFIG_FS_PERMISSION a user could make a directory of a mounted file system with no x bit the working directory. chdir() now tests the x bit of the directory for the effective IDs. Root needs any x bit, the same rule as access() and exec() use, so root can still enter a directory 0700 of another user. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <[email protected]> --- libs/libc/unistd/lib_chdir.c | 66 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/libs/libc/unistd/lib_chdir.c b/libs/libc/unistd/lib_chdir.c index 65016bfe013..be043d11251 100644 --- a/libs/libc/unistd/lib_chdir.c +++ b/libs/libc/unistd/lib_chdir.c @@ -27,12 +27,68 @@ #include <nuttx/config.h> #include <sys/stat.h> +#include <limits.h> +#include <stdbool.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include "libc.h" +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: chdir_searchable + * + * Description: + * Return true if the effective IDs have search permission on the + * directory. Root needs any execute bit, as in access() and exec(). + * + ****************************************************************************/ + +#ifdef CONFIG_FS_PERMISSION +static bool chdir_searchable(FAR const struct stat *buf) +{ + mode_t xbit = S_IXOTH; + uid_t uid = geteuid(); + + if (uid == 0) + { + return (buf->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; + } + + if (buf->st_uid == uid) + { + xbit = S_IXUSR; + } + else if (buf->st_gid == getegid()) + { + xbit = S_IXGRP; + } +#if NGROUPS_MAX > 0 + else + { + gid_t groups[NGROUPS_MAX]; + int ngroups = getgroups(NGROUPS_MAX, groups); + int i; + + for (i = 0; i < ngroups; i++) + { + if (groups[i] == buf->st_gid) + { + xbit = S_IXGRP; + break; + } + } + } +#endif + + return (buf->st_mode & xbit) != 0; +} +#endif + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -93,6 +149,16 @@ int chdir(FAR const char *path) return ERROR; } +#ifdef CONFIG_FS_PERMISSION + /* stat() needs no search permission on the last path component */ + + if (!chdir_searchable(&buf)) + { + set_errno(EACCES); + return ERROR; + } +#endif + /* Yes, it is a directory. * Remove any trailing '/' characters from the path */
