Skip to content

binfmt/builtin: Add an option to match a builtin by path as POSIX specifies. - #21461

Merged
acassis merged 2 commits into
apache:masterfrom
casaroli:builtin-loader-path-up
Nov 5, 2026
Merged

acassis merged 2 commits into
apache:masterfrom
casaroli:builtin-loader-path-up

Conversation

@casaroli

@casaroli casaroli commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The builtin binary format looks up only the last part of a path. exec("/nonexistent/hello") starts the builtin hello, and so does exec("/tmp/hello") for a file that is not a program. POSIX wants ENOENT and ENOEXEC.

This PR adds CONFIG_BINFMT_BUILTIN_STRICT_PATH, default n. With the option, a name with a / matches a builtin only if it names a file in a mounted BINFS. A bare name still matches. Without the option nothing changes. A name that does not match is "not my format", and load_absmodule() of #21454 gives the error: a missing file ENOENT, and a file that exists ENOEXEC.

Impact

Default off: no behaviour change, and the new code is not compiled. The option depends on CONFIG_BUILTIN, so it cannot be set in a kernel build. Without CONFIG_FS_BINFS, a name with a / never matches a builtin.

The default stays because code depends on the old match. With the option and no BINFS at /bin, these fail:

  • Run on qemu-armv8a:nsh: posix_spawn("/bin/sh") gives ENOENT. popen("echo x") without CONFIG_LIBC_ENVPATH uses /bin/sh and fails with ENOENT. A #!/bin/sh script gives ENOENT.
  • Only read, not run: execl("/bin/sh") in apps interpreters/bas, the shell /bin/sh of apps netutils/dropbear, the default of CONFIG_ADBD_SHELL_SERVICE_PATH in apps system/adb, the shell /bin/nsh that getpwnam() reports for root (ROOT_SHELL), and popen() of a command that is a builtin (it starts with no shell).

Not changed by this PR: these callers and apps. A BINFS mounted at /bin should avoid the /bin/sh failures, but this was not run.

Documentation: Documentation/os/filesystem/binfs.rst names the option, in its own commit.

Testing

Test program on qemu-armv8a:nsh (flat, HVF) with FS_BINFS PIPES SCHED_WAITPID, as a builtin. The ret value is the result of posix_spawn().

Case master option off option on
/nonexistent/execbuiltin ret 0, the builtin runs ret 0, the builtin runs ret 2 (ENOENT)
/tmp/execbuiltin, a text file ret 0, the builtin runs ret 0, the builtin runs ret 8 (ENOEXEC)
execbuiltin, a bare name runs runs runs
/ebbin/execbuiltin in a mounted BINFS runs runs runs
./execbuiltin with the cwd in the BINFS runs runs runs
/ebbin/nosuchprogram ret 2 ret 2 ret 2
/ebbin/sub/execbuiltin ret 0, the builtin runs ret 0, the builtin runs ret 2
a directory with the name of a builtin ret 0, the builtin runs ret 0, the builtin runs ret 8
ostest exit status 0 0 0

The option was also run without FS_BINFS, and with ELF and BINFMT_SCRIPT: missing, text file and bare name as above, ostest 0.

Build only, with EXTRAFLAGS="-Wno-cpp -Werror", no compiler warnings: option off and on: qemu-armv8a:nsh (flat), mps2-an500:knsh (protected), rv-virt:citest (flat); option on without FS_BINFS: qemu-armv8a:nsh, mps2-an500:knsh; qemu-armv8a:knsh (kernel, the option cannot be set). The change has no arch code.

@casaroli casaroli added the Area: OS Components OS Components issues label Nov 4, 2026
@github-actions github-actions Bot added Size: M The size of the change in this PR is medium Area: BINFMT and removed Area: OS Components OS Components issues labels Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

MemBrowse Memory Report · Explore what changed →

arduino-mega2560

  • flash: .data +16 B (+0.0%, 66,241 B / 262,144 B, total: 25% used)
  • sram: .data +16 B (+0.6%, 2,811 B / 8,192 B, total: 34% used)

esp32-devkitc

  • ROM: .dram0.data +4 B, .flash.rodata +24 B, .flash.text +4 B (+0.0%, 129,112 B / 4,194,272 B, total: 3% used)
  • dram0_0_seg: .dram0.data +4 B (+0.0%, 10,028 B / 180,736 B, total: 6% used)
  • drom0_0_seg: .flash.rodata +24 B (+0.2%, 13,948 B / 4,194,272 B, total: 0% used)
  • irom0_0_seg: .flash.text +4 B (+0.0%, 94,932 B / 3,342,304 B, total: 3% used)

hifive1-revb

  • flash: .data +8 B, .text +12 B (+0.0%, 85,744 B / 4,194,304 B, total: 2% used)
  • sram: .data +8 B (+0.2%, 3,964 B / 16,384 B, total: 24% used)

mirtoo

  • kseg0_progmem: .data +4 B, .text +12 B (+0.0%, 68,396 B / 131,072 B, total: 52% used)
  • kseg1_datamem: .data +4 B (+0.2%, 2,604 B / 32,256 B, total: 8% used)

qemu-armv8a

  • Code: .rodata +16 B, .text.load_module +60 B (+0.0%, 360,256 B)
  • Data: .data.g_version +2 B (+0.0%, 77,919 B)

qemu-intel64

  • Code: .text +61 B (+0.0%, 8,693,183 B)

rx65n-rsk2mb

  • RAM: .data +4 B (+0.1%, 6,702 B / 262,144 B, total: 3% used)
  • ROM: .rodata +16 B (+0.0%, 91,420 B / 2,097,152 B, total: 4% used)

stm32-nucleo-f103rb

  • flash: .text +16 B (+0.0%, 34,812 B / 131,072 B, total: 27% used)

No memory changes detected for:


Catch memory regressions in CI with MemBrowse. Free for open source.

@casaroli
casaroli force-pushed the builtin-loader-path-up branch from 5574ca0 to a1b2e6f Compare November 4, 2026 18:20
…cifies.

The builtin binary format looks up only the last part of a path, so
exec("/nonexistent/hello") starts the builtin "hello", and so does
exec("/tmp/hello") for a file that is not a program.  POSIX wants ENOENT
and ENOEXEC.

With the new option CONFIG_BINFMT_BUILTIN_STRICT_PATH (default n), a
name with a '/' matches a builtin only if it names a file in a mounted
BINFS.  A bare name still matches.  Without the option nothing changes.

The default stays, because code depends on it: without a BINFS at /bin,
popen() without LIBC_ENVPATH uses "/bin/sh" and that path starts the
builtin "sh".  The help text lists what the option breaks.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
…infs.

Say how exec() matches a path to a builtin application, and what the
option changes.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
@casaroli
casaroli force-pushed the builtin-loader-path-up branch from a1b2e6f to d379864 Compare November 4, 2026 22:10
@casaroli
casaroli marked this pull request as draft November 4, 2026 22:10
@github-actions github-actions Bot added Size: S The size of the change in this PR is small and removed Size: M The size of the change in this PR is medium labels Nov 4, 2026
@casaroli
casaroli marked this pull request as ready for review November 5, 2026 03:17
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

🔗 Cross-repo PR dependencies

The read-only Build run reported the following dependent PR(s) and fetched head SHA(s):

CI run: https://github.com/apache/nuttx/actions/runs/41922851921

@acassis
acassis merged commit bf029a2 into apache:master Nov 5, 2026
89 of 91 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: BINFMT Size: S The size of the change in this PR is small

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants