fix: exec exit-code 127 vs 126 misclassification on 32-bit musl/seccomp#217
Conversation
On musl i486 under Ubuntu 24.04, the 32-bit execve syscall may return EACCES instead of ENOENT for a non-existent path (seccomp filtering of 32-bit syscalls). The old code checked errno==ENOENT to decide between exit code 127 (not found) and 126 (not executable), so it emitted 126 when EACCES was returned even though the binary does not exist, failing the test assertion. For explicit paths (those containing '/'), switch to using access(F_OK) to determine 127 vs 126: if the file does not exist the command was not found (127); if it exists it was found but could not be executed (126). This is independent of errno and handles any EACCES/ENOENT variant. PATH-resolved names (no '/' in argument) continue to use errno because access() cannot evaluate PATH-relative lookups.
There was a problem hiding this comment.
Pull request overview
Fixes misclassification of exec failure exit codes (127 vs 126) on 32-bit musl/seccomp environments where execvp returns EACCES instead of ENOENT for non-existent paths. For explicit paths containing /, the code now uses access(F_OK) as the authoritative existence check rather than relying on errno.
Changes:
- Replace errno-based 127-vs-126 decision with
access(F_OK)for explicit paths inexec_child_process. - Update the surrounding comment to document the rationale (musl/seccomp
EACCESbehavior).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 296e9ff763
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| _exit(access(cfg->command_args[0], F_OK) == 0 | ||
| ? EXIT_CMD_NOT_EXECUTABLE | ||
| : EXIT_CMD_NOT_FOUND); |
There was a problem hiding this comment.
Preserve permission errors for explicit paths
When an explicit command path is inside a directory the caller cannot search, execvp() fails with EACCES and access(path, F_OK) also fails with EACCES even though the target may exist; the new ternary maps every access() failure to 127. This changes permission-denied/other lookup failures from the documented shell-compatible 126 to “command not found”; only ENOENT/ENOTDIR from the existence check should become 127, while EACCES and similar errors should remain 126.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
@copilot address the issue:
When an explicit command path is inside a directory the caller cannot search, execvp() fails with EACCES and access(path, F_OK) also fails with EACCES even though the target may exist; the new ternary maps every access() failure to 127. This changes permission-denied/other lookup failures from the documented shell-compatible 126 to “command not found”; only ENOENT/ENOTDIR from the existence check should become 127, while EACCES and similar errors should remain 126.
On musl i486 (32-bit static) under Ubuntu 24.04,
execvpreturnsEACCESinstead ofENOENTfor a non-existent path — likely because the GitHub Actions seccomp profile filters 32-bitexecvesyscalls differently. The old code usederrno == ENOENTto distinguish exit 127 (not found) from exit 126 (not executable), soEACCESwas misclassified as 126, failingassert(exit_code == 127)intest_limiter_run_command_mode_nonexistent.Change
src/limiter.c—exec_child_processFor explicit paths (argument contains
/), replace theerrno-based 127-vs-126 decision withaccess(path, F_OK):access(F_OK)is the authoritative answer to "does the file exist" and is unaffected by whatever errnoexecvpreturns. PATH-resolved names (no/) still fall back to errno sinceaccesscannot resolve them against$PATH.