Skip to content

Export the syscall trampoline so raw syscalls interpose through the guard - #722

Merged
omkhar merged 3 commits into
mainfrom
fixup/export-syscall-trampoline
Sep 8, 2026
Merged

Export the syscall trampoline so raw syscalls interpose through the guard#722
omkhar merged 3 commits into
mainfrom
fixup/export-syscall-trampoline

Conversation

@omkhar

@omkhar omkhar commented Sep 8, 2026

Copy link
Copy Markdown
Owner

The interposition gap

The exec guard interposes the libc execve/execveat family, but a program that calls the libc syscall(SYS_execve, ...) wrapper directly was never routed through the guard. The syscall trampoline existed in runtime/container/rust/src/lib.rs as a pair of global_asm! blocks (.globl syscall tail-jumping to workcell_syscall_shim), but an assembler .globl does not make rustc export the symbol. rustc only puts #[no_mangle] Rust items in the cdylib's export list and localizes everything else through its generated version script (local: *). So syscall was defined inside the .so but absent from its dynamic symbol table, and nothing overrode the libc wrapper at load time. A raw syscall(SYS_execve, ...) reached the kernel unguarded.

Confirmed on the built cdylib: nm -D before this change lists workcell_syscall_shim but not syscall.

The export mechanism, and why the asm stays

The libc wrapper is variadic (long syscall(long number, ...)), which Rust cannot express as a function definition, so the implementation must remain a bare register-forwarding trampoline: it tail-jumps into workcell_syscall_shim with every argument register untouched, and the shim reads number plus six args and routes SYS_execve/SYS_execveat to guarded_execve/guarded_execveat.

The repo already has exactly the right idiom for an exported register-forwarding trampoline: define_variadic_exec_trampoline!, used for execl/execlp/execle. It emits a #[unsafe(naked)] #[unsafe(no_mangle)] pub unsafe extern "C" fn whose body is a single jmp/b to the target. Being #[no_mangle], rustc places it in the export list, so it survives the version script and -Bsymbolic-functions. Replacing the two global_asm! blocks with one invocation of that macro (syscall -> workcell_syscall_shim) exports the symbol while preserving the identical register-forwarding behavior on both x86_64 and aarch64. This is a smaller, more idiomatic change than a bespoke --export-dynamic-symbol link arg (which the version script's local: * overrides anyway -- verified: it did not export the symbol) or a hand-written version script.

After the change, nm -D lists syscall as an exported T symbol.

The test row

runtime/container/rust/tests/loader_forms.rs recorded this form as pending because an in-process row would have reported a false pass while the deployed .so did not actually interpose. With the export in place that gap is closed, so the row is flipped from Pending(RAW_SYSCALL_PENDING) to a real Refused(MUTABLE_NATIVE) expectation, reached through a new RawSyscallExecve call variant that invokes the libc syscall(SYS_execve, ...) wrapper. PENDING_FORMS drops from 3 to 2, and the now-unused RAW_SYSCALL_PENDING constant is removed. The two remaining pending rows (env cluster, split-at-equals) are unchanged. The harness control that must observe a real refusal still holds.

Evidence

  • nm -D libworkcell_exec_guard.so: before -> only workcell_syscall_shim; after -> syscall and workcell_syscall_shim.
  • LD_PRELOAD interposition proof (rust:1.97.1, aarch64): a C program calling syscall(SYS_execve, "/tmp/nativebin", ...) with the approved guard preload in its child env is refused with the mutable-native message under the patched .so; under the baseline .so the same call reaches the kernel and runs, confirming the gap and that the export closes it.
  • Non-regression: a raw syscall(SYS_execveat, ..., AT_EMPTY_PATH) on a mutable shell script is still allowed, so the container-smoke raw-execveat expectation is preserved.
  • cargo fmt --check, cargo clippy --all-targets -- -D warnings, and cargo test --locked --offline all pass in the Linux container, including the now-active raw-syscall row and the deterministic exec-guard tests (a_replaced_pathname_no_longer_matches_its_descriptor, the snapshot/pin family). On darwin, cargo fmt --check and cargo clippy pass; the interposition assertions ran only in the Linux container.

…(guard suite + nm -D and LD_PRELOAD interposition proof pass; closes a raw-syscall interposition gap)
@omkhar

omkhar commented Sep 8, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-08T22:43:45.001369Z c985b7b New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 426247fc26

ℹ️ 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".

Comment thread runtime/container/rust/tests/loader_forms.rs
…verified refuses under the export and passes through an unexported build; secondary test pinning the deployment invariant)
@omkhar

omkhar commented Sep 8, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: bd0818e21a

ℹ️ 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".

@omkhar
omkhar merged commit 0448dac into main Sep 8, 2026
18 of 19 checks passed
@omkhar
omkhar deleted the fixup/export-syscall-trampoline branch September 8, 2026 22:59
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.

1 participant