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
The following commit(s) were added to refs/heads/master by this push:
new 03685825c9f fs/binfmt: Enforce POSIX execute permissions prior to
binary load
03685825c9f is described below
commit 03685825c9f56ca2221ed455ed497e9c3eea17f3
Author: Abhishek Mishra <[email protected]>
AuthorDate: Tue Jun 9 08:02:57 2026 +0000
fs/binfmt: Enforce POSIX execute permissions prior to binary load
Adds a pre-load permission check in exec_internal() to verify the
calling task has the required execute (x) bits for the target file.
Properly evaluates root (euid == 0), owner, group, and other permissions.
This ensures POSIX compliance and cleanly rejects unauthorized files
with -EACCES before they reach the ELF loader, preventing unnecessary
memory allocation and downstream hardware execution faults.
Signed-off-by: Abhishek Mishra <[email protected]>
---
binfmt/CMakeLists.txt | 4 ++
binfmt/Makefile | 4 ++
binfmt/binfmt.h | 20 +++++++++
binfmt/binfmt_checkexec.c | 100 +++++++++++++++++++++++++++++++++++++++++++++
binfmt/binfmt_exec.c | 7 ++--
binfmt/binfmt_loadmodule.c | 8 ++++
binfmt/builtin.c | 22 +++++++---
binfmt/elf.c | 24 +++++++----
binfmt/nxflat.c | 26 ++++++++++++
9 files changed, 199 insertions(+), 16 deletions(-)
diff --git a/binfmt/CMakeLists.txt b/binfmt/CMakeLists.txt
index 0f0578ebce3..1af4cd3042b 100644
--- a/binfmt/CMakeLists.txt
+++ b/binfmt/CMakeLists.txt
@@ -41,6 +41,10 @@ list(
binfmt_copyactions.c
binfmt_dumpmodule.c)
+if(CONFIG_SCHED_USER_IDENTITY)
+ list(APPEND SRCS binfmt_checkexec.c)
+endif()
+
if(CONFIG_BINFMT_LOADABLE)
list(APPEND SRCS binfmt_exit.c)
endif()
diff --git a/binfmt/Makefile b/binfmt/Makefile
index e88e1a9c27e..31d84b1d50c 100644
--- a/binfmt/Makefile
+++ b/binfmt/Makefile
@@ -28,6 +28,10 @@ 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)
+CSRCS += binfmt_checkexec.c
+endif
+
ifeq ($(CONFIG_BINFMT_LOADABLE),y)
CSRCS += binfmt_exit.c
endif
diff --git a/binfmt/binfmt.h b/binfmt/binfmt.h
index 3cdf07ecbfa..c362bf041c6 100644
--- a/binfmt/binfmt.h
+++ b/binfmt/binfmt.h
@@ -213,6 +213,26 @@ void binfmt_freeactions(FAR const
posix_spawn_file_actions_t *copy);
# define binfmt_freeactions(copy)
#endif
+#ifdef CONFIG_SCHED_USER_IDENTITY
+/****************************************************************************
+ * 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.
+ *
+ * Input Parameters:
+ * bin - Load structure with uid, gid, and mode populated
+ *
+ * Returned Value:
+ * Zero (OK) on success; -EACCES if execute permission is denied.
+ *
+ ****************************************************************************/
+
+int binfmt_checkexecperm(FAR struct binary_s *bin);
+#endif
+
#ifdef CONFIG_BUILTIN
/****************************************************************************
* Name: builtin_initialize
diff --git a/binfmt/binfmt_checkexec.c b/binfmt/binfmt_checkexec.c
new file mode 100644
index 00000000000..1436298dc24
--- /dev/null
+++ b/binfmt/binfmt_checkexec.c
@@ -0,0 +1,100 @@
+/****************************************************************************
+ * binfmt/binfmt_checkexec.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 <errno.h>
+
+#include <nuttx/sched.h>
+#include <nuttx/binfmt/binfmt.h>
+
+#include "binfmt.h"
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * 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.
+ *
+ * Input Parameters:
+ * bin - Pointer to the binary descriptor with uid, gid, and mode set.
+ *
+ * Returned Value:
+ * Zero (OK) on success; -EACCES if execute permission is denied.
+ *
+ ****************************************************************************/
+
+int binfmt_checkexecperm(FAR struct binary_s *bin)
+{
+ FAR struct tcb_s *rtcb;
+ mode_t xbits;
+
+ rtcb = nxsched_self();
+
+ if (bin == NULL || rtcb == NULL || rtcb->group == NULL)
+ {
+ return OK;
+ }
+
+ if (rtcb->group->tg_euid == 0)
+ {
+ /* Root can execute any file that has at least one execute bit set */
+
+ if ((bin->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0)
+ {
+ return -EACCES;
+ }
+
+ return OK;
+ }
+
+ if (rtcb->group->tg_euid == bin->uid)
+ {
+ xbits = S_IXUSR;
+ }
+ else if (rtcb->group->tg_egid == bin->gid)
+ {
+ xbits = S_IXGRP;
+ }
+ else
+ {
+ xbits = S_IXOTH;
+ }
+
+ if ((bin->mode & xbits) == 0)
+ {
+ return -EACCES;
+ }
+
+ return OK;
+}
diff --git a/binfmt/binfmt_exec.c b/binfmt/binfmt_exec.c
index c2c73ee876b..ae3e4643b1e 100644
--- a/binfmt/binfmt_exec.c
+++ b/binfmt/binfmt_exec.c
@@ -31,7 +31,6 @@
#include <errno.h>
#include <nuttx/kmalloc.h>
-#include <nuttx/sched.h>
#include <nuttx/binfmt/binfmt.h>
#include "binfmt.h"
@@ -81,11 +80,13 @@ static int exec_internal(FAR const char *filename,
FAR const posix_spawnattr_t *attr, bool spawn)
{
FAR struct binary_s *bin;
- int pid;
- int ret;
#ifndef CONFIG_BINFMT_LOADABLE
struct binary_s sbin;
+#endif
+ int pid;
+ int ret;
+#ifndef CONFIG_BINFMT_LOADABLE
bin = &sbin;
memset(bin, 0, sizeof(*bin));
#else
diff --git a/binfmt/binfmt_loadmodule.c b/binfmt/binfmt_loadmodule.c
index 4d5f9416de0..06d56c0c9b5 100644
--- a/binfmt/binfmt_loadmodule.c
+++ b/binfmt/binfmt_loadmodule.c
@@ -134,6 +134,14 @@ static int load_absmodule(FAR struct binary_s *bin, FAR
const char *filename,
binfmt_dumpmodule(bin);
break;
}
+ else if (ret == -EACCES)
+ {
+ /* Access explicitly denied -- stop here; do not let a fallback
+ * loader bypass the execute-permission check that already ran.
+ */
+
+ break;
+ }
}
return ret;
diff --git a/binfmt/builtin.c b/binfmt/builtin.c
index b2116806f46..5ec818b3dd9 100644
--- a/binfmt/builtin.c
+++ b/binfmt/builtin.c
@@ -34,6 +34,8 @@
#include <nuttx/binfmt/binfmt.h>
#include <nuttx/lib/builtin.h>
+#include "binfmt.h"
+
#ifdef CONFIG_BUILTIN
/****************************************************************************
@@ -76,6 +78,9 @@ 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
binfo("Loading file: %s\n", filename);
@@ -105,14 +110,21 @@ static int builtin_loadbinary(FAR struct binary_s *binp,
return -ENOENT;
}
+#ifdef CONFIG_SCHED_USER_IDENTITY
+ binp->uid = builtin->uid;
+ binp->gid = builtin->gid;
+ binp->mode = builtin->mode;
+
+ chk = binfmt_checkexecperm(binp);
+ if (chk < 0)
+ {
+ return chk;
+ }
+#endif
+
binp->entrypt = builtin->main;
binp->stacksize = builtin->stacksize;
binp->priority = builtin->priority;
-#ifdef CONFIG_SCHED_USER_IDENTITY
- binp->uid = builtin->uid;
- binp->gid = builtin->gid;
- binp->mode = builtin->mode;
-#endif
return OK;
}
diff --git a/binfmt/elf.c b/binfmt/elf.c
index 087c73589ae..cfe146cb26e 100644
--- a/binfmt/elf.c
+++ b/binfmt/elf.c
@@ -37,6 +37,8 @@
#include <nuttx/binfmt/binfmt.h>
#include <nuttx/kmalloc.h>
+#include "binfmt.h"
+
#ifdef CONFIG_ELF
/****************************************************************************
@@ -110,6 +112,20 @@ static int elf_loadbinary(FAR struct binary_s *binp,
goto errout_with_init;
}
+#ifdef CONFIG_SCHED_USER_IDENTITY
+ /* Save IDs and mode from file system before loading segments */
+
+ binp->uid = loadinfo.fileuid;
+ binp->gid = loadinfo.filegid;
+ binp->mode = loadinfo.filemode;
+
+ ret = binfmt_checkexecperm(binp);
+ if (ret < 0)
+ {
+ goto errout_with_init;
+ }
+#endif
+
/* Load the program binary */
ret = libelf_load_with_addrenv(&loadinfo);
@@ -201,14 +217,6 @@ static int elf_loadbinary(FAR struct binary_s *binp,
binp->mod.nfini = loadinfo.nfini;
#endif
-#ifdef CONFIG_SCHED_USER_IDENTITY
- /* Save IDs and mode from file system */
-
- binp->uid = loadinfo.fileuid;
- binp->gid = loadinfo.filegid;
- binp->mode = loadinfo.filemode;
-#endif
-
libelf_dumpentrypt(&loadinfo);
#ifdef CONFIG_PIC
if (loadinfo.gotindex >= 0)
diff --git a/binfmt/nxflat.c b/binfmt/nxflat.c
index 010cf42546a..f390e5081f5 100644
--- a/binfmt/nxflat.c
+++ b/binfmt/nxflat.c
@@ -27,6 +27,7 @@
#include <nuttx/config.h>
#include <sys/param.h>
+#include <sys/stat.h>
#include <sys/types.h>
#include <stdint.h>
#include <string.h>
@@ -36,10 +37,13 @@
#include <arpa/inet.h>
+#include <nuttx/fs/fs.h>
#include <nuttx/kmalloc.h>
#include <nuttx/binfmt/binfmt.h>
#include <nuttx/binfmt/nxflat.h>
+#include "binfmt.h"
+
#ifdef CONFIG_NXFLAT
/****************************************************************************
@@ -142,6 +146,9 @@ 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
+ struct stat st;
+#endif
int ret;
binfo("Loading file: %s\n", filename);
@@ -156,6 +163,25 @@ static int nxflat_loadbinary(FAR struct binary_s *binp,
goto errout;
}
+#ifdef CONFIG_SCHED_USER_IDENTITY
+ ret = file_fstat(&loadinfo.file, &st);
+ if (ret < 0)
+ {
+ berr("Failed to stat NXFLAT program binary: %d\n", ret);
+ goto errout_with_init;
+ }
+
+ binp->uid = st.st_uid;
+ binp->gid = st.st_gid;
+ binp->mode = st.st_mode;
+
+ ret = binfmt_checkexecperm(binp);
+ if (ret < 0)
+ {
+ goto errout_with_init;
+ }
+#endif
+
/* Load the program binary */
ret = nxflat_load(&loadinfo);