Skip to content

guest-init: pass argv[0] when exec'ing cube-agent - #1451

Closed
dwin-gharibi wants to merge 1 commit into
TencentCloud:masterfrom
dwin-gharibi:guest-init-empty-argv
Closed

dwin-gharibi wants to merge 1 commit into
TencentCloud:masterfrom
dwin-gharibi:guest-init-empty-argv

Conversation

@dwin-gharibi

Copy link
Copy Markdown
Contributor

Closes #1450.

Motivation

start_agent called execvp with an empty Vec<CString>, so nix built argv = [NULL] and
cube-agent started with argc == 0. POSIX expects argv[0] to be the program name, and without it
/proc/1/cmdline inside the guest is empty — so nothing in the guest can identify PID 1 by name.

What this changes

guest-init/src/main.rs:

  • agent_argv() builds the one-element argument vector, and start_agent passes it to execvp,
    using args[0] as the program path so the path and argv[0] cannot drift apart.
fn agent_argv() -> [CString; 1] {
    [CString::new(CUBE_AGENT).expect("new cmd failed")]
}

fn start_agent() -> ! {
    let args = agent_argv();
    let err = unistd::execvp(args[0].as_c_str(), &args).unwrap_err();
    panic!("exec agent failed:{}", err);
}

Extracting agent_argv is what makes the behaviour testable — start_agent itself cannot be unit
tested because a successful execvp replaces the process.

No comment changes.

Testing

New test agent_argv_carries_program_name in the existing mod tests: asserts the vector has exactly
one element, that it equals CUBE_AGENT, and that it is non-empty (the last one is the actual
regression guard — an empty vector was the bug).

$ docker run --rm ... -w /w/guest-init rust:1.90 cargo test --all
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

(9 before this change, 10 after.)

CI gates checked locally:

  • cargo fmt --check — clean (fmt-check runs make fmt per component).
  • cargo clippy --all-targets — no new warnings.
  • cargo test --all — 10 passed.

What this does not verify

The test covers argv construction, not the exec itself — asserting on /proc/1/cmdline would need a
booted guest, which is e2e territory. The mechanism between the two is nix::unistd::execvp, which
passes the slice through verbatim.

Risk / rollout

Very low. The agent does not read argv for configuration (it uses /proc/cmdline), so nothing changes
functionally — only that PID 1 now has a name.

Signed-off-by: Dwin Gharibi <dwin.gharibi@email.kntu.ac.ir>
Copilot AI lite review requested due to automatic review settings August 21, 2026 11:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread guest-init/src/main.rs
let cmd = CString::new(CUBE_AGENT).expect("new cmd failed");
let err = unistd::execvp(cmd.as_c_str(), &args).unwrap_err();
let args = agent_argv();
let err = unistd::execvp(args[0].as_c_str(), &args).unwrap_err();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential gap — nix 0.26's execvp does not append a NULL terminator to argv. nix::unistd::execvp in the pinned nix 0.26.4 builds a Vec<*const c_char> from the slice and passes args_p.as_ptr() straight to libc::execvp without appending a terminating NULL (nix only fixed this in 0.29 via the CArray type). With this one-element slice, the kernel reads argv[1] from uninitialized heap memory right after the Vec's single pointer. If that slot is non-NULL garbage, the exec'd agent either receives bogus extra arguments (argc > 1) or the exec fails with EFAULT; it only reliably yields argc == 1 when the allocator hands back zeroed memory.

The new test covers agent_argv() construction only, not the exec path, and the PR explicitly defers /proc/1/cmdline verification to e2e territory. Since the entire point of this change is that argv is correct in the guest, please verify in a booted guest that /proc/1/cmdline actually shows cube-agent, and consider either upgrading nix to >= 0.29 or calling libc::execvp directly with an explicitly NULL-terminated array (libc is already a dependency here).

@cubesandboxbot

Copy link
Copy Markdown

Review: PR #1451 — guest-init: pass argv[0] when exec'ing cube-agent

AI-generated review. Reviewed against base master at 72af66c. Diff scope: guest-init/src/main.rs only (14+/3-). No truncation.

Summary

The change is correct in direction and is a strict improvement. Previously start_agent passed an empty Vec<CString> to nix::unistd::execvp, so the libc argv array began with NULL and cube-agent was launched with argc == 0 (and nothing in /proc/1/cmdline). The new agent_argv() builds a one-element argv whose argv[0] is the program path, and start_agent uses args[0].as_c_str() for the executable path too, so path and argv[0] cannot drift. This matches POSIX expectations (argv[0] = program name, argc >= 1). Extracting agent_argv() for testability is reasonable since a successful execvp would replace the process.

The added unit test is a sensible regression guard: it asserts the vector has exactly one element and equals CUBE_AGENT.

Findings

1. [Medium / needs e2e verification] The fix may not reliably produce argc == 1 because nix 0.26.4's execvp does not NULL-terminate the argv array it passes to the kernel. (Inline comment on start_agent.) nix 0.26 collects Vec<*const c_char> from the slice and hands as_ptr() to libc::execvp without appending a terminating NULL (nix only guarantees termination in >= 0.29 via CArray). With a one-element slice, the kernel reads argv[1] from uninitialized heap memory — possibly a stale heap pointer (bogus extra args) or an unmapped address (EFAULT, exec fails). It works only when the allocator happens to return zeroed memory. The test covers argv construction, not the exec, and the PR explicitly does not verify /proc/1/cmdline in a booted guest. Recommendation: boot the guest and confirm /proc/1/cmdline shows cube-agent; alternatively upgrade nix to >= 0.29 or call libc::execvp directly with an explicitly NULL-terminated array (libc is already a dependency).

2. [Nit] Redundant assertion in the test. assert!(!argv[0].as_bytes().is_empty()) at guest-init/src/main.rs:84 is implied by assert_eq!(argv[0].to_str().unwrap(), CUBE_AGENT) (a non-empty literal), and the actual regression guard for the empty-vector bug is assert_eq!(argv.len(), 1). Harmless; can be dropped if you want the test to be minimal.

Positive notes

  • Using args[0] for both the exec path and argv[0] guarantees the two never diverge.
  • [CString; 1] is a better fit than Vec for a fixed, always-present argv.
  • The behavior change is limited to the argument vector; the agent's configuration comes from /proc/cmdline, so no functional change is expected.

Verdict

Approve with follow-up: verify in a booted guest that /proc/1/cmdline exposes the program name (given finding #1), and consider hardening the argv construction against nix 0.26's missing NULL terminator.

@fslongjin fslongjin closed this Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug Report] guest-init execs cube-agent with an empty argv, so /proc/1/cmdline is empty

3 participants