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 7b79bf6c32be9cd589d1e7da034cb4ac1913165e Author: Marco Casaroli <[email protected]> AuthorDate: Wed Nov 4 10:59:03 2026 +0100 fs/inode: Do not require search permission on the last path component. inode_checkpathperm() checked X_OK on the inode itself when it was a pseudo directory or a mount point. So a pseudo directory with no x bit could not be found again, also by root: chmod("/d", 0644) -> 0 stat("/d") -> -1 EACCES chmod("/d", 0755) -> -1 EACCES POSIX needs search permission only on the directories of the path prefix. The helper now checks X_OK on the ancestors only. Each caller says what it needs on the inode itself: - A path from inode_find() needs X_OK on a mount point only when it goes into the volume (a non-empty relative path): INODE_PATH_AMODE(). - inode_reserve() and inode_remove() use the parent as a prefix: W_OK and X_OK. - mount() and umount() keep W_OK and X_OK on the target, as before. A prefix directory with no x bit still fails, for every user. uid 0 gets no new privilege: fs_checkmode() is not changed. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <[email protected]> --- fs/inode/fs_inode.c | 20 +++----------------- fs/inode/fs_inoderemove.c | 3 ++- fs/inode/fs_inodereserve.c | 6 +++--- fs/inode/inode.h | 11 +++++++++-- fs/mount/fs_mount.c | 5 +++-- fs/mount/fs_umount2.c | 2 +- fs/vfs/fs_chstat.c | 2 +- fs/vfs/fs_mkdir.c | 2 +- fs/vfs/fs_open.c | 2 +- fs/vfs/fs_rename.c | 2 +- fs/vfs/fs_rmdir.c | 2 +- fs/vfs/fs_stat.c | 2 +- fs/vfs/fs_statfs.c | 2 +- fs/vfs/fs_unlink.c | 2 +- 14 files changed, 29 insertions(+), 34 deletions(-) diff --git a/fs/inode/fs_inode.c b/fs/inode/fs_inode.c index 4b1838ed7aa..8e9bad2af32 100644 --- a/fs/inode/fs_inode.c +++ b/fs/inode/fs_inode.c @@ -240,10 +240,9 @@ int inode_checkperm(FAR struct inode *inode, int amode) * Name: inode_checkpathperm * * Description: - * Require X_OK on every ancestor of 'inode', and on 'inode' itself when - * it is a directory or mountpoint that must be traversed. If 'amode' is - * non-zero, also require that access on 'inode'. Takes the inode tree - * read lock unless INODE_CHECK_LOCKED is set in 'flags'. + * Require X_OK on every ancestor of 'inode'. If 'amode' is non-zero, + * also require that access on 'inode'. Takes the inode tree read lock + * unless INODE_CHECK_LOCKED is set in 'flags'. * ****************************************************************************/ @@ -263,19 +262,6 @@ int inode_checkpathperm(FAR struct inode *inode, int amode, int flags) inode_rlock(); } - if (INODE_IS_PSEUDODIR(inode) -#ifndef CONFIG_DISABLE_MOUNTPOINT - || INODE_IS_MOUNTPT(inode) -#endif - ) - { - ret = inode_checkperm(inode, X_OK); - if (ret < 0) - { - goto errout; - } - } - for (node = inode->i_parent; node != NULL; node = node->i_parent) { ret = inode_checkperm(node, X_OK); diff --git a/fs/inode/fs_inoderemove.c b/fs/inode/fs_inoderemove.c index 921244bcb80..098039fb2be 100644 --- a/fs/inode/fs_inoderemove.c +++ b/fs/inode/fs_inoderemove.c @@ -85,7 +85,8 @@ static FAR struct inode *inode_unlink(FAR const char *path) { /* Caller holds the inode tree lock. */ - ret = inode_checkpathperm(desc.parent, W_OK, INODE_CHECK_LOCKED); + ret = inode_checkpathperm(desc.parent, W_OK | X_OK, + INODE_CHECK_LOCKED); if (ret < 0) { inode = NULL; diff --git a/fs/inode/fs_inodereserve.c b/fs/inode/fs_inodereserve.c index a547e28026e..9b1a1e73ab0 100644 --- a/fs/inode/fs_inodereserve.c +++ b/fs/inode/fs_inodereserve.c @@ -237,11 +237,11 @@ int inode_reserve(FAR const char *path, if (parent != NULL) { - /* Traverse ancestors (X_OK), then require write on the parent. - * Caller holds the inode tree lock. + /* Search the ancestors and the parent (X_OK), then require write on + * the parent. Caller holds the inode tree lock. */ - ret = inode_checkpathperm(parent, W_OK, INODE_CHECK_LOCKED); + ret = inode_checkpathperm(parent, W_OK | X_OK, INODE_CHECK_LOCKED); if (ret < 0) { goto errout_with_search; diff --git a/fs/inode/inode.h b/fs/inode/inode.h index 4b9db5b66d3..9986c4fbc6d 100644 --- a/fs/inode/inode.h +++ b/fs/inode/inode.h @@ -36,6 +36,7 @@ #include <errno.h> #include <dirent.h> #include <sched.h> +#include <unistd.h> #include <nuttx/kmalloc.h> #include <nuttx/sched.h> @@ -433,6 +434,12 @@ void inode_release(FAR struct inode *inode); #define INODE_CHECK_LOCKED (1 << 0) +/* The access that a path needs on the inode from inode_find(): X_OK on a + * mountpoint to go into the volume, nothing on the last path component. + */ + +#define INODE_PATH_AMODE(desc) (*(desc)->relpath != '\0' ? X_OK : 0) + /**************************************************************************** * Name: inode_checkperm * @@ -449,8 +456,8 @@ void inode_release(FAR struct inode *inode); * Description: * Enforce directory search (X_OK) on the path leading to 'inode', and * optionally check 'amode' on 'inode' itself. Requires X_OK on every - * ancestor, and on 'inode' when it is a pseudo directory or mountpoint. - * If 'amode' is non-zero, also requires that access on 'inode'. + * ancestor. If 'amode' is non-zero, also requires that access on 'inode': + * use INODE_PATH_AMODE() when 'inode' comes from inode_find(). * * Takes the inode tree read lock unless INODE_CHECK_LOCKED is set in * 'flags' (caller already holds inode_lock/inode_rlock). diff --git a/fs/mount/fs_mount.c b/fs/mount/fs_mount.c index 3f420db408e..ed73a084a55 100644 --- a/fs/mount/fs_mount.c +++ b/fs/mount/fs_mount.c @@ -412,9 +412,10 @@ int nx_mount(FAR const char *source, FAR const char *target, goto errout_with_search; } - /* Require search on ancestors and write on the mount target. */ + /* Require search on ancestors, and search and write on the target. */ - ret = inode_checkpathperm(mountpt_inode, W_OK, INODE_CHECK_LOCKED); + ret = inode_checkpathperm(mountpt_inode, W_OK | X_OK, + INODE_CHECK_LOCKED); if (ret < 0) { inode_release(mountpt_inode); diff --git a/fs/mount/fs_umount2.c b/fs/mount/fs_umount2.c index 8acd3de249b..c143763e65b 100644 --- a/fs/mount/fs_umount2.c +++ b/fs/mount/fs_umount2.c @@ -128,7 +128,7 @@ int nx_umount2(FAR const char *target, unsigned int flags) inode_lock(); - ret = inode_checkpathperm(mountpt_inode, W_OK, INODE_CHECK_LOCKED); + ret = inode_checkpathperm(mountpt_inode, W_OK | X_OK, INODE_CHECK_LOCKED); if (ret < 0) { goto errout_with_lock; diff --git a/fs/vfs/fs_chstat.c b/fs/vfs/fs_chstat.c index 44b85dfbcf2..a58e8b3c78e 100644 --- a/fs/vfs/fs_chstat.c +++ b/fs/vfs/fs_chstat.c @@ -74,7 +74,7 @@ static int chstat_recursive(FAR const char *path, DEBUGASSERT(inode != NULL); - ret = inode_checkpathperm(inode, 0, 0); + ret = inode_checkpathperm(inode, INODE_PATH_AMODE(&desc), 0); if (ret < 0) { inode_release(inode); diff --git a/fs/vfs/fs_mkdir.c b/fs/vfs/fs_mkdir.c index 8f0d6613f46..188aff437fa 100644 --- a/fs/vfs/fs_mkdir.c +++ b/fs/vfs/fs_mkdir.c @@ -102,7 +102,7 @@ int mkdir(const char *pathname, mode_t mode) goto errout_with_inode; } - ret = inode_checkpathperm(inode, 0, 0); + ret = inode_checkpathperm(inode, INODE_PATH_AMODE(&desc), 0); if (ret < 0) { goto errout_with_inode; diff --git a/fs/vfs/fs_open.c b/fs/vfs/fs_open.c index c671afe8e79..8d54e3ed10f 100644 --- a/fs/vfs/fs_open.c +++ b/fs/vfs/fs_open.c @@ -240,7 +240,7 @@ static int file_vopen(FAR struct file *filep, FAR const char *path, * so i_mode cannot race with concurrent chmod. */ - ret = inode_checkpathperm(inode, 0, 0); + ret = inode_checkpathperm(inode, INODE_PATH_AMODE(&desc), 0); if (ret < 0) { goto errout_with_inode; diff --git a/fs/vfs/fs_rename.c b/fs/vfs/fs_rename.c index 05a3b689b7d..7b9197d7060 100644 --- a/fs/vfs/fs_rename.c +++ b/fs/vfs/fs_rename.c @@ -576,7 +576,7 @@ int rename(FAR const char *oldpath, FAR const char *newpath) DEBUGASSERT(oldinode != NULL); - ret = inode_checkpathperm(oldinode, 0, 0); + ret = inode_checkpathperm(oldinode, INODE_PATH_AMODE(&olddesc), 0); if (ret < 0) { inode_release(oldinode); diff --git a/fs/vfs/fs_rmdir.c b/fs/vfs/fs_rmdir.c index bcf4dab1a9f..490984fdeea 100644 --- a/fs/vfs/fs_rmdir.c +++ b/fs/vfs/fs_rmdir.c @@ -90,7 +90,7 @@ int rmdir(FAR const char *pathname) if (INODE_IS_MOUNTPT(inode) && inode->u.i_mops) { - ret = inode_checkpathperm(inode, 0, 0); + ret = inode_checkpathperm(inode, INODE_PATH_AMODE(&desc), 0); if (ret < 0) { goto errout_with_inode; diff --git a/fs/vfs/fs_stat.c b/fs/vfs/fs_stat.c index 7f38b82c52c..d231073bebc 100644 --- a/fs/vfs/fs_stat.c +++ b/fs/vfs/fs_stat.c @@ -108,7 +108,7 @@ static int stat_recursive(FAR const char *path, DEBUGASSERT(inode != NULL); - ret = inode_checkpathperm(inode, 0, 0); + ret = inode_checkpathperm(inode, INODE_PATH_AMODE(&desc), 0); if (ret < 0) { inode_release(inode); diff --git a/fs/vfs/fs_statfs.c b/fs/vfs/fs_statfs.c index 212ee783a9a..29d597f8a4a 100644 --- a/fs/vfs/fs_statfs.c +++ b/fs/vfs/fs_statfs.c @@ -113,7 +113,7 @@ int statfs(FAR const char *path, FAR struct statfs *buf) DEBUGASSERT(inode != NULL); - ret = inode_checkpathperm(inode, 0, 0); + ret = inode_checkpathperm(inode, INODE_PATH_AMODE(&desc), 0); if (ret < 0) { inode_release(inode); diff --git a/fs/vfs/fs_unlink.c b/fs/vfs/fs_unlink.c index eb890f43a2c..648caacf5c9 100644 --- a/fs/vfs/fs_unlink.c +++ b/fs/vfs/fs_unlink.c @@ -89,7 +89,7 @@ int nx_unlink(FAR const char *pathname) if (INODE_IS_MOUNTPT(inode) && inode->u.i_mops) { - ret = inode_checkpathperm(inode, 0, 0); + ret = inode_checkpathperm(inode, INODE_PATH_AMODE(&desc), 0); if (ret < 0) { goto errout_with_inode;
