Conversation
Two faults in the Linux target, the second found by following the first. **_start never passed envp.** It computed argc into x0 and argv into x1 and left x2 untouched. _main's prologue then stored that garbage into _rail_envp, and _rail_shell handed it to execve, so every Linux Rail program that shelled out ran with no environment. Measured with one probe compiled for both targets: shell() saw ONE variable and no HOME on a Pi whose parent had a full environment, against 43 on macOS. macOS always worked because its prologue receives envp in x2 under the main(argc,argv,envp) convention, which a static Linux binary with its own _start never gets. The Linux initial stack is argc, argv[0..argc-1], NULL, envp[0]..., so envp is argv + (argc+1)*8. Now computed in _start, stored to _rail_envp directly so it is live during _rail_arena_init, and left in x2 for the prologue. **getenv could not link.** The FFI string-return path calls _strdup to copy a borrowed C string onto the Rail heap, and linux_libc.s had no _strdup at all, so anything using `foreign getenv` failed to build for Linux rather than misbehaving at runtime. _getenv was separately a stub returning 0, justified by envp being unpopulated, which the above fixes. It now walks envp comparing NAME=value against the name up to the '=', so HOME does not match HOMEBREW_PREFIX. _strdup is built from the _strlen/_malloc/_strcpy already present, with NULL in giving an empty string out rather than faulting, which is the shape getenv's not-found case needs. This does NOT unblock RAIL_ARENA_MB on Linux, despite the natural guess: that arena_init never consults _getenv, it uses a statically reserved BSS heap of fixed size and would need an mmap path to gain the variable. Verified on the Pi against macOS: HOME, USER and a missing name all agree, including the prefix-collision case. macOS untouched at 190/190, and the rebuilt seed still reproduces itself byte-identically. How it surfaced: the fleet agent on the Pi could not read ~/.fleet/token, because `~` will not expand without HOME under dash. That left its auth token empty, hit a fail-open branch, and left the control plane accepting any token or none. Patched at the caller first; this is the cause underneath, and it affects every Linux Rail program that shells out or reads the environment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EzjMXh1wxgasCH81MJfTCk
zemo-g
force-pushed
the
fix/linux-envp-not-passed
branch
from
August 29, 2026 02:36
8567b86 to
d769c9e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two commits. The second was found by following the first.
1.
_startnever passed envpIt computed
argcinto x0 andargvinto x1 and left x2 untouched._main's prologue then stored that garbage into_rail_envp, and_rail_shellhanded it toexecve. Every Linux Rail program that shelledout ran with no environment.
One probe, both targets:
cd ~cat ~/.fleet/token/home/zemog/~/home/zemog/home/zemog/Users/…macOS always worked because its prologue receives envp in x2 under the
main(argc, argv, envp)convention. A static Linux binary with its own_startnever gets that, and nothing computed it. The Linux initial stackis
argc, argv[…], NULL, envp[…], so envp isargv + (argc+1)*8.2.
getenvcould not link, and_getenvwas a stubforeign getenvdid not work on Linux, and not subtly: the FFIstring-return path calls
_strdupto copy a borrowed C string onto theRail heap, and
linux_libc.shad no_strdupat all. Anything usinggetenv would not build for the Pi.
_getenvwas separately a stub returning 0, justified by envp not beingpopulated at arena-init time, which commit 1 fixes. It now walks envp
comparing
NAME=valueagainst the name up to the=, soHOMEdoesnot match
HOMEBREW_PREFIX.Verified on the Pi against macOS:
One claim retracted
The first commit said storing envp in
_startunblocksRAIL_ARENA_MBonLinux. It does not. The Linux
_rail_arena_initnever consults_getenv; it uses a statically reserved BSS heap of fixed size, so thatvariable would need an mmap path to have any effect there. Corrected in the
second commit. What the envp fix actually unblocks is
_getenvitself.How it surfaced
The long way round. The fleet agent on the Pi could not read
~/.fleet/token, because~will not expand without HOME under dash. Thatleft its auth token empty, which hit a fail-open branch, which meant the
control plane accepted any token or none. Patched at the caller first
(
security/fleet-v2-logs-jobs-injection); this is the cause underneath, andit affects every Linux Rail program that shells out or reads the
environment.
Verification
conformance harness would be the place for one.