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 <mishra.abhishek2808@gmail.com>
This commit is contained in:
Abhishek Mishra 2026-06-09 08:02:57 +00:00 committed by Xiang Xiao
parent 2cddf4e638
commit 72192fc8bc
9 changed files with 199 additions and 16 deletions

View file

@ -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()

View file

@ -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

View file

@ -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

100
binfmt/binfmt_checkexec.c Normal file
View file

@ -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;
}

View file

@ -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

View file

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

View file

@ -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;
}

View file

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

View file

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