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;

Reply via email to