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);
