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 9308e7871972d21deae8cd1b6a1cc2cca262ece7
Author: Marco Casaroli <[email protected]>
AuthorDate: Sun Oct 18 00:16:44 2026 +0200

    binfmt: Check the execute permission in every binary format.
    
    The ELF, NXFLAT and builtin loaders checked the execute permission only
    with CONFIG_SCHED_USER_IDENTITY, and the script loader did not check it
    at all.  Without user identities, NuttX started any file that a loader
    recognized, whatever its mode.
    
    Add CONFIG_BINFMT_CHECKEXEC, which is the default with
    CONFIG_SCHED_USER_IDENTITY.  Each loader then checks the mode of the
    file that it already opened, so no extra lookup is needed: the ELF and
    NXFLAT loaders, the builtin loader with the mode of the builtin table,
    and the script loader, which now checks the script too; its interpreter
    is checked by its own loader.  A file without execute permission gives
    EACCES.  Without user identities, any execute bit is enough, as for
    root.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <[email protected]>
---
 binfmt/CMakeLists.txt     |  2 +-
 binfmt/Kconfig            | 16 +++++++++++++
 binfmt/Makefile           |  2 +-
 binfmt/binfmt.h           | 13 ++++++-----
 binfmt/binfmt_checkexec.c | 58 ++++++++++++++++++++++++-----------------------
 binfmt/builtin.c          | 21 ++++++++---------
 binfmt/elf.c              |  5 +++-
 binfmt/nxflat.c           | 10 +++++---
 binfmt/script.c           | 20 ++++++++++++++++
 9 files changed, 96 insertions(+), 51 deletions(-)

diff --git a/binfmt/CMakeLists.txt b/binfmt/CMakeLists.txt
index 8852722f1b0..91c7665cf79 100644
--- a/binfmt/CMakeLists.txt
+++ b/binfmt/CMakeLists.txt
@@ -41,7 +41,7 @@ list(
   binfmt_copyactions.c
   binfmt_dumpmodule.c)
 
-if(CONFIG_SCHED_USER_IDENTITY)
+if(CONFIG_BINFMT_CHECKEXEC)
   list(APPEND SRCS binfmt_checkexec.c)
 endif()
 
diff --git a/binfmt/Kconfig b/binfmt/Kconfig
index 69b11466b71..37bf0962588 100644
--- a/binfmt/Kconfig
+++ b/binfmt/Kconfig
@@ -135,6 +135,22 @@ config BINFMT_ELF_EXECUTABLE
 
 endchoice
 
+config BINFMT_CHECKEXEC
+       bool "Check the execute permission of programs"
+       default SCHED_USER_IDENTITY
+       ---help---
+               Refuse to start a program from a file that has no execute
+               permission, with EACCES.  Each binary format checks the mode of
+               the file that it opens, also for scripts and their 
interpreters.  With
+               SCHED_USER_IDENTITY, the owner, group or other execute bit is
+               checked for the effective user and group IDs.  Without it, any
+               execute bit is enough.
+
+               File systems without a stored mode (for example FAT) report
+               execute permission for all files, unless they are mounted with
+               MS_NOEXEC.  On a file system that stores the mode, a program 
that
+               was created with a mode without execute bits needs chmod first.
+
 config BINFMT_STORE_FILENAME
        bool "Store the binary filename"
        default n
diff --git a/binfmt/Makefile b/binfmt/Makefile
index 4b9a3048d54..711b335a81a 100644
--- a/binfmt/Makefile
+++ b/binfmt/Makefile
@@ -28,7 +28,7 @@ CSRCS  = binfmt_globals.c binfmt_initialize.c 
binfmt_register.c binfmt_unregiste
 CSRCS += binfmt_loadmodule.c binfmt_unloadmodule.c binfmt_execmodule.c
 CSRCS += binfmt_exec.c binfmt_copyargv.c binfmt_copyactions.c 
binfmt_dumpmodule.c
 
-ifeq ($(CONFIG_SCHED_USER_IDENTITY),y)
+ifeq ($(CONFIG_BINFMT_CHECKEXEC),y)
 CSRCS += binfmt_checkexec.c
 endif
 
diff --git a/binfmt/binfmt.h b/binfmt/binfmt.h
index c8103990503..ca2c6bb1efe 100644
--- a/binfmt/binfmt.h
+++ b/binfmt/binfmt.h
@@ -213,24 +213,25 @@ void binfmt_freeactions(FAR const 
posix_spawn_file_actions_t *copy);
 #  define binfmt_freeactions(copy)
 #endif
 
-#ifdef CONFIG_SCHED_USER_IDENTITY
+#ifdef CONFIG_BINFMT_CHECKEXEC
 /****************************************************************************
  * Name: binfmt_checkexecperm
  *
  * Description:
- *   Verify that the calling task has execute permission on the file
- *   described by 'bin'.  The file owner, group, and mode must already be
- *   populated in the binary_s structure.
+ *   Verify that the calling task has execute permission on a file with
+ *   the given mode, owner and group.
  *
  * Input Parameters:
- *   bin - Load structure with uid, gid, and mode populated
+ *   mode - The mode of the file
+ *   uid  - The owner of the file
+ *   gid  - The group of the file
  *
  * Returned Value:
  *   Zero (OK) on success; -EACCES if execute permission is denied.
  *
  ****************************************************************************/
 
-int binfmt_checkexecperm(FAR struct binary_s *bin);
+int binfmt_checkexecperm(mode_t mode, uid_t uid, gid_t gid);
 #endif
 
 #ifdef CONFIG_BUILTIN
diff --git a/binfmt/binfmt_checkexec.c b/binfmt/binfmt_checkexec.c
index 9d94d36300c..cab623d3eda 100644
--- a/binfmt/binfmt_checkexec.c
+++ b/binfmt/binfmt_checkexec.c
@@ -42,59 +42,61 @@
  * Name: binfmt_checkexecperm
  *
  * Description:
- *   Verify that the calling task has execute permission on the file
- *   described by 'bin'.  The file owner, group, and mode must already be
- *   populated in the binary_s structure before calling this function.
+ *   Verify that the calling task has execute permission on a file with
+ *   the given mode, owner and group.
  *
  * Input Parameters:
- *   bin - Pointer to the binary descriptor with uid, gid, and mode set.
+ *   mode - The mode of the file
+ *   uid  - The owner of the file
+ *   gid  - The group of the file
  *
  * Returned Value:
  *   Zero (OK) on success; -EACCES if execute permission is denied.
  *
  ****************************************************************************/
 
-int binfmt_checkexecperm(FAR struct binary_s *bin)
+int binfmt_checkexecperm(mode_t mode, uid_t uid, gid_t gid)
 {
+#ifdef CONFIG_SCHED_USER_IDENTITY
   FAR struct tcb_s *rtcb;
   FAR struct task_group_s *rgroup;
-  mode_t xbits;
+#endif
+  mode_t xbits = S_IXUSR | S_IXGRP | S_IXOTH;
 
+#ifdef CONFIG_SCHED_USER_IDENTITY
   rtcb = nxsched_self();
-
-  if (bin == NULL || rtcb == NULL || rtcb->group == NULL)
+  if (rtcb == NULL || rtcb->group == NULL)
     {
       return OK;
     }
 
   rgroup = rtcb->group;
 
-  if (rgroup->tg_euid == 0)
-    {
-      /* Root can execute any file that has at least one execute bit set */
+  /* Root can execute any file that has at least one execute bit set */
 
-      if ((bin->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0)
+  if (rgroup->tg_euid != 0)
+    {
+      if (rgroup->tg_euid == uid)
         {
-          return -EACCES;
+          xbits = S_IXUSR;
+        }
+      else if (nxsched_has_gid(rtcb, gid))
+        {
+          xbits = S_IXGRP;
+        }
+      else
+        {
+          xbits = S_IXOTH;
         }
-
-      return OK;
     }
+#else
+  /* Without user identities, any execute bit is enough */
 
-  if (rgroup->tg_euid == bin->uid)
-    {
-      xbits = S_IXUSR;
-    }
-  else if (nxsched_has_gid(rtcb, bin->gid))
-    {
-      xbits = S_IXGRP;
-    }
-  else
-    {
-      xbits = S_IXOTH;
-    }
+  UNUSED(uid);
+  UNUSED(gid);
+#endif
 
-  if ((bin->mode & xbits) == 0)
+  if ((mode & xbits) == 0)
     {
       return -EACCES;
     }
diff --git a/binfmt/builtin.c b/binfmt/builtin.c
index 5ec818b3dd9..04b5e4b3073 100644
--- a/binfmt/builtin.c
+++ b/binfmt/builtin.c
@@ -77,10 +77,7 @@ static int builtin_loadbinary(FAR struct binary_s *binp,
 {
   FAR const struct builtin_s *builtin;
   FAR char *name;
-  int index;
-#ifdef CONFIG_SCHED_USER_IDENTITY
-  int chk;
-#endif
+  int ret;
 
   binfo("Loading file: %s\n", filename);
 
@@ -92,18 +89,18 @@ static int builtin_loadbinary(FAR struct binary_s *binp,
 
   /* Looking up the index to this name in g_builtins[] */
 
-  index = builtin_isavail(filename);
-  if (index < 0)
+  ret = builtin_isavail(filename);
+  if (ret < 0)
     {
       berr("ERROR: %s is not a builtin application\n", filename);
-      return index;
+      return ret;
     }
 
   /* Return the load information.  NOTE: that there is no way to configure
    * the priority.  That is a bug and needs to be fixed.
    */
 
-  builtin = builtin_for_index(index);
+  builtin = builtin_for_index(ret);
   if (builtin == NULL)
     {
       berr("ERROR: %s is not a builtin application\n", filename);
@@ -115,11 +112,13 @@ static int builtin_loadbinary(FAR struct binary_s *binp,
   binp->gid  = builtin->gid;
   binp->mode = builtin->mode;
 
-  chk = binfmt_checkexecperm(binp);
-  if (chk < 0)
+#  ifdef CONFIG_BINFMT_CHECKEXEC
+  ret = binfmt_checkexecperm(binp->mode, binp->uid, binp->gid);
+  if (ret < 0)
     {
-      return chk;
+      return ret;
     }
+#  endif
 #endif
 
   binp->entrypt   = builtin->main;
diff --git a/binfmt/elf.c b/binfmt/elf.c
index 72580c95650..c6ec0eeb907 100644
--- a/binfmt/elf.c
+++ b/binfmt/elf.c
@@ -119,8 +119,11 @@ static int elf_loadbinary(FAR struct binary_s *binp,
   binp->uid  = loadinfo.fileuid;
   binp->gid  = loadinfo.filegid;
   binp->mode = loadinfo.filemode;
+#endif
 
-  ret = binfmt_checkexecperm(binp);
+#ifdef CONFIG_BINFMT_CHECKEXEC
+  ret = binfmt_checkexecperm(loadinfo.filemode, loadinfo.fileuid,
+                             loadinfo.filegid);
   if (ret < 0)
     {
       goto errout_with_init;
diff --git a/binfmt/nxflat.c b/binfmt/nxflat.c
index f390e5081f5..f476ca88eb7 100644
--- a/binfmt/nxflat.c
+++ b/binfmt/nxflat.c
@@ -146,7 +146,7 @@ static int nxflat_loadbinary(FAR struct binary_s *binp,
                              int nexports)
 {
   struct nxflat_loadinfo_s loadinfo;  /* Contains globals for libnxflat */
-#ifdef CONFIG_SCHED_USER_IDENTITY
+#if defined(CONFIG_SCHED_USER_IDENTITY) || defined(CONFIG_BINFMT_CHECKEXEC)
   struct stat              st;
 #endif
   int                      ret;
@@ -163,19 +163,23 @@ static int nxflat_loadbinary(FAR struct binary_s *binp,
       goto errout;
     }
 
-#ifdef CONFIG_SCHED_USER_IDENTITY
+#if defined(CONFIG_SCHED_USER_IDENTITY) || defined(CONFIG_BINFMT_CHECKEXEC)
   ret = file_fstat(&loadinfo.file, &st);
   if (ret < 0)
     {
       berr("Failed to stat NXFLAT program binary: %d\n", ret);
       goto errout_with_init;
     }
+#endif
 
+#ifdef CONFIG_SCHED_USER_IDENTITY
   binp->uid  = st.st_uid;
   binp->gid  = st.st_gid;
   binp->mode = st.st_mode;
+#endif
 
-  ret = binfmt_checkexecperm(binp);
+#ifdef CONFIG_BINFMT_CHECKEXEC
+  ret = binfmt_checkexecperm(st.st_mode, st.st_uid, st.st_gid);
   if (ret < 0)
     {
       goto errout_with_init;
diff --git a/binfmt/script.c b/binfmt/script.c
index d729c293705..a7d66b08dd4 100644
--- a/binfmt/script.c
+++ b/binfmt/script.c
@@ -26,6 +26,7 @@
 
 #include <nuttx/config.h>
 
+#include <sys/stat.h>
 #include <ctype.h>
 #include <fcntl.h>
 #include <string.h>
@@ -167,6 +168,9 @@ static int script_loadbinary(FAR struct binary_s *binp,
   FAR char *arg = NULL;
   FAR char *end;
   struct file file;
+#ifdef CONFIG_BINFMT_CHECKEXEC
+  struct stat st;
+#endif
   int ret;
 
   binfo("Attempting script load: %s\n", filename);
@@ -177,6 +181,22 @@ static int script_loadbinary(FAR struct binary_s *binp,
       return ret;
     }
 
+#ifdef CONFIG_BINFMT_CHECKEXEC
+  /* The script needs execute permission, as its interpreter does */
+
+  ret = file_fstat(&file, &st);
+  if (ret >= 0)
+    {
+      ret = binfmt_checkexecperm(st.st_mode, st.st_uid, st.st_gid);
+    }
+
+  if (ret < 0)
+    {
+      file_close(&file);
+      return ret;
+    }
+#endif
+
   ret = file_read(&file, header, sizeof(header) - 1);
   file_close(&file);
 

Reply via email to