Add --env and --clear-env launch flag support. - #295
Closed
henrybear327 wants to merge 4 commits into
Closed
Conversation
An OCI image front end needs to set the guest identity and working directory without patching the runtime; both flags map onto launch_args_t fields and `elfuse-oci run` drives exactly this interface. The --user identity is staged before bring-up (proc_set_initial_ids) so the auxv AT_UID/AT_GID snapshot taken by build_linux_stack matches what getuid()/getgid() later report. proc_identity_init consumes the staged value, and the elfuse_launch fail path calls proc_clear_initial_ids: a bring-up that fails before proc_init would otherwise leave the value staged for the next launch in the same host process. --workdir rejects non-absolute paths up front instead of silently resolving them against the host cwd, and is applied by elfuse_launch after the casefold probe so the translation sees the sysroot's real case behavior. The resolved host path must sit inside the sysroot prefix: proc_resolve_sysroot_path falls back to the host spelling when the sysroot has no entry at the path, which is the overlay contract for guest syscalls but would start the guest in a same-named host directory here, so the launch refuses it. --fakeroot and a non-root --user are refused together. Fakeroot starts the guest as uid/gid 0 and uid_is_permitted() grants every id switch on that basis; a non-root --user would keep that grant while the guest reported an unprivileged uid, letting the guest call setuid(0) at will. The refusal lives in elfuse_launch beside the Rosetta GDB check, so every launcher inherits the privilege rule, and it exits through the shared fail unwind so a FUSE-materialized temp ELF is unlinked even when bring-up never starts. tests/test-launch-flags.sh covers the refusal, the parse rules, and the --workdir sysroot containment; tests/test-identity-override-host.c pins the staging consume-once and clear semantics as regression guards (the cross-launch leak needs two bring-ups in one host process, which no launcher performs). docs/usage.md documents both flags, the containment rule, and the two-sided root requirement behind the fakeroot refusal.
path_translate_at redirects /dev/shm/<leaf> into the per-UID host backing dir and returns before sysroot resolution, so elfuse_launch ran only sys_chdir's real-directory branch for such a workdir. The plain chdir followed a symlink leaf that shm_open_leaf's O_NOFOLLOW fd refuses, and proc_cwd_refresh published the backing location, so getcwd reported /private/tmp/elfuse-shm-<uid>/<leaf> where a guest chdir into the same directory reports /dev/shm/<leaf>. Entering the leaf correctly needs that O_NOFOLLOW fd and the virtual-cwd publish, which would make launch.c a second holder of the never-follow duty dev_shm_resolve_path enumerates, for a workdir no image asks for. The flag refuses the path instead, on the same grounds guest_bootstrap_prepare refuses a /dev/shm ELF interpreter. The test creates the leaf first, so the case measures the refusal rather than a missing directory, and chmods the backing root because create_private_dir rejects a group or other permission bit. The test-launch-flags.sh header claimed every refusal happens before any VM exists. guest_bootstrap_prepare creates and initializes the VM before the workdir block runs, so the header now names both timings: --user before the VM is created, --workdir during bring-up.
An OCI image config carries an environment the guest must start with, so
elfuse needs a merge that follows `docker run -e` rather than one that
approximates it. guest_env_build in src/core/guest-env.c is that merge,
kept out of main() so it can be exercised against base vectors no live
process could be given: the host environment arrives as a parameter, so
nothing in the module reads global state.
"KEY=VALUE" replaces that name where env_find() locates it and appends
otherwise; a bare "KEY" imports the launcher's value, appending nothing
rather than an empty value when the launcher does not set it; clear_env
drops the base while leaving a bare "KEY" resolvable. An empty variable
name is refused, as setenv(3) refuses it.
host_env overrides *out_envp
-------------- --------- ----------
[0] PATH=/bin PATH=/opt PATH=/opt replaced in place
[1] WEIRD dropped: no '='
[2] TERM=xterm TERM=xterm forwarded
[3] PATH=/dup dropped: dup of [0]
NEW=1 NEW=1 appended
An entry the overrides cannot address is dropped rather than forwarded:
entry_key_len() returns 0 for [1], and env_find() matches only [0] for
[3]. Forwarding either would leave the guest holding both an override
and the entry it was meant to replace. getenv(3) reads the first copy
either way, but anything walking environ directly, /proc/self/environ
included, would see the stale one.
free_guest_argv generalizes to strv_free in src/utils.h, which frees the
guest argv and a built envp alike.
tests/test-guest-env-host.c enumerates the merge as a cross product of
base x clear_env x override sequence, holding every cell to a reference
merge and to structural invariants no cell may violate. Its alphabet
carries a refused token, so a rejection lands at every base and either
override position rather than at one hand-picked base, and one base sets
a name to the empty string, so a bare "KEY" import separates a name the
launcher set to "" from one it never set. Both widen a coverage guard
rather than fix an observed failure. The oracle compares whole names
with strcmp where env_find compares klen bytes and then the boundary
byte, which is the axis it is independent on; it shares the drop and
refusal rules, so it rules out a mechanism bug rather than a wrong
reading of `docker run -e`.
`elfuse-oci run` launches every guest as `elfuse --clear-env --env K=V ... --`, so a front end needs to set the guest environment through the CLI rather than by patching the runtime. Both flags map onto launch_args_t beside --user and --workdir. The new launch_args_t envp field generalizes elfuse_launch's hard-coded environ on guest_env_build's rule: a NULL vector means the host environ, which is what leaves callers that set neither flag unchanged. main() collects the --env tokens as borrowed argv pointers into one argc-sized allocation and builds the vector before runtime_set_process_title clobbers the argv block, which is what lets the refusal name the offending token and keeps a malformed --env from reaching --create-sysroot. tests/test-launch-flags.sh adds only what the guest_env_build cross product cannot reach: main() collecting the tokens out of argv, build_linux_stack copying the vector onto the initial guest stack, and the /proc/self/environ sink, which is held to the same entries build_linux_stack copied rather than compared against another sink of one run. tests/test-env-dump.c is the guest that prints its environ for those lanes. docs/usage.md documents the import and replace rules and the empty-name refusal.
Contributor
Never submit stacking pull requests! |
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.
In preparation for the OCI support, we need to support the ENV flag.
Summary by cubic
Adds
--env/--clear-env,--user, and--workdirlaunch flags so launchers can set the guest environment, identity, and cwd precisely. Previously the guest always inherited the host environ, ran with compiled-in uid/gid (or fakeroot), and started in the host cwd; now flags control each and reject contradictory or unsafe requests before bring-up.guest_env_buildto merge the environment likedocker run -e:KEY=VALUEreplaces or appends; bareKEYimports the host value;--clear-envstarts empty; empty names are refused.elfuse_launchnow acceptsenvp(NULL means inherit host environ).--user:proc_set_initial_idsapplies beforeproc_identity_initso auxvAT_UID/AT_GIDmatchgetuid/getgid.--fakerootwith a non-root or mixed--useris refused;--fakeroot --user 0:0is allowed. Fail paths callproc_clear_initial_ids.--workdir(guest-absolute). Resolves under--sysroot, requires containment within the sysroot, and rejects/dev/shmand relative paths. Applies after sysroot casefold probing. On refusal, no VM is started.launch_args_twithenvp,has_creds/uid/gid, andcwd_guest. Ownership rules updated:elf_pathunlink now always belongs toelfuse_launch, even on pre-prepare refusals. Introducesstrv_freefor argv/env cleanup.Tests & docs
--user,--workdir,--env, and--clear-envindocs/usage.md, including rules and refusals.tests/test-guest-env-host.c(cross-product oracle forguest_env_build) andtests/test-launch-flags.sh(CLI parsing, pre-bring-up refusals, sysroot containment, and guest//procenvironment sinks). Addstests/test-env-dump.c.Written for commit 28a6d5a. Summary will update on new commits.