Skip to content

Commit 91cb3c8

Browse files
authored
Merge pull request #23 from A3S-Lab/feat/pidfd-signaling
feat(executor): signal container init through pidfds
2 parents 30a68c5 + bf72b72 commit 91cb3c8

12 files changed

Lines changed: 379 additions & 85 deletions

File tree

.github/scripts/native-linux-smoke.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,19 @@ sudo apt-get update
1010
sudo apt-get install --yes busybox-static jq
1111
cargo build -p a3s-oci-agent -p a3s-oci-cli
1212

13+
features="$("$PWD/target/debug/a3s-oci" features)"
14+
printf '%s\n' "$features"
15+
jq --exit-status \
16+
'.platform == "linux"
17+
and any(
18+
.drivers[];
19+
.driver == "native-linux"
20+
and .status == "available"
21+
and .readiness == "probe-only"
22+
and .evidence.pidfd_signaling == "true"
23+
)' \
24+
<<<"$features" >/dev/null
25+
1326
bundle="$runner_temp/a3s-native-bundle"
1427
bundle_b="$runner_temp/a3s-native-bundle-b"
1528
work_parent="$runner_temp/a3s-native-work"

README.md

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ profile.
3636

3737
The runtime has two execution paths:
3838

39-
- **Native Linux** uses namespaces, mounts, cgroup v2, seccomp, capabilities,
40-
and process supervision without requiring KVM.
39+
- **Native Linux** runs the reviewed namespace and mount profile with
40+
PID-reuse-safe process control without requiring KVM. Complete cgroup,
41+
seccomp, capability, and supervision enforcement remains a release gate.
4142
- **Utility VM** hosts the same Linux executor behind an authenticated guest
4243
agent, using KVM on Linux, HVF on macOS, or WHPX on Windows.
4344

@@ -83,9 +84,9 @@ Workload calls require an explicitly supplied launch-ready `RuntimeDriver`.
8384
monotonic generations, operation IDs, replay, fencing, reconciliation, and
8485
quarantine, backed by exhaustive durable-write and driver-boundary fault
8586
matrices
86-
- **Shared Linux Executor**: Reuse one fail-closed namespace, mount, process,
87-
and cleanup implementation directly on Linux and through the guest agent,
88-
with independently fenced per-container generations
87+
- **Shared Linux Executor**: Reuse one fail-closed namespace, mount, pidfd
88+
process-control, and cleanup implementation directly on Linux and through
89+
the guest agent, with independently fenced per-container generations
8990
- **Cross-Platform Drivers**: Inspect native Linux, KVM, HVF, and WHPX
9091
prerequisites without silently weakening requested isolation
9192
- **Typed SDK and IPC**: Expose an async `Send + Sync` Rust contract with
@@ -422,8 +423,8 @@ The current executor implements a reviewed bootstrap vertical slice:
422423
- recursively private mount propagation and `pivot_root`;
423424
- ordered existing-target OCI mounts with bind/rbind and common VFS options;
424425
- PID-authenticated create/start barrier;
425-
- credentials, umask, `no_new_privileges`, `execve`, signaling, observation,
426-
and scoped cleanup.
426+
- credentials, umask, `no_new_privileges`, `execve`, PID-reuse-safe pidfd
427+
signaling, observation, and scoped cleanup.
427428

428429
Unimplemented OCI fields are rejected instead of ignored. User and time
429430
namespaces, namespace joins, complete mount semantics, cgroup resources,
@@ -452,9 +453,9 @@ boundary.
452453

453454
| Host | Execution path | Retained evidence | Current readiness |
454455
| --- | --- | --- | --- |
455-
| Linux x86_64/aarch64 | Native Linux executor | Real rootful core lifecycle, two-container generation and mutation isolation, plus shutdown cleanup after create, start, and kill without delete; `/dev/kvm` absent and present-but-unusable | Default inventory `probe-only`; explicitly opened development instance `experimental` |
456+
| Linux x86_64/aarch64 | Native Linux executor | Kernel pidfd signaling probe, real rootful core lifecycle, two-container generation and mutation isolation, plus shutdown cleanup after create, start, and kill without delete; `/dev/kvm` absent and present-but-unusable | Default inventory `probe-only`; explicitly opened development instance `experimental` |
456457
| Linux x86_64/aarch64 | libkrun + KVM utility VM | Device access, ioctl result, and KVM API version | `probe-only`; VM driver not implemented |
457-
| macOS arm64 | libkrun + HVF utility VM | Direct HVF VM create/destroy, checksum-pinned context lifecycle, authenticated static arm64 guest agent, fixed and two-container OCI lifecycles, and no-delete cleanup after create, start, and kill | `probe-only`; complete enforcement and recovery pending |
458+
| macOS arm64 | libkrun + HVF utility VM | Direct HVF VM create/destroy, checksum-pinned context lifecycle, authenticated static arm64 guest agent, pidfd-backed fixed and two-container OCI lifecycles, and no-delete cleanup after create, start, and kill | `probe-only`; complete enforcement and recovery pending |
458459
| Windows x86_64 | libkrun + WHPX utility VM | Partition, context, guest command, authenticated agent, and fixed OCI core lifecycle | `probe-only`; complete enforcement and recovery pending |
459460

460461
Linux installation, feature inspection, and the native SDK path must work when
@@ -475,7 +476,7 @@ flowchart TB
475476
native["Native Linux<br/>NativeLinuxDriver · experimental opt-in"]
476477
utility["Utility VM host · qualification path<br/>isolated a3s-oci-krun-shim → libkrun<br/>KVM · HVF · WHPX"]
477478
agent["A3S Linux guest<br/>authenticated AF_VSOCK → a3s-oci-agent"]
478-
executor["Shared LinuxExecutor<br/>namespaces · mounts · PID 1 · process lifecycle"]
479+
executor["Shared LinuxExecutor<br/>namespaces · mounts · PID 1 · pidfd process control"]
479480
480481
consumers --> control --> selection
481482
control <--> state
@@ -538,6 +539,9 @@ Security-sensitive platform controls include:
538539
natural zero exit, worker reap, and marker cleanup;
539540
- private `0700` macOS agent directories and `0600` Unix sockets, with
540541
`LOCAL_PEERPID` plus direct shim-child verification before token negotiation;
542+
- retained pidfds for every authenticated init PID, with all lifecycle and
543+
cleanup signals delivered through the descriptor rather than a reused
544+
numeric PID;
541545
- isolated macOS shim process groups so timeout and failure cleanup terminate
542546
both the public shim and its VM worker;
543547
- shared Windows/macOS fixed-lifecycle evidence with exact mutation replay,
@@ -578,11 +582,13 @@ Platform CI covers:
578582

579583
- the 237-point durable commit matrix and all 12 `RuntimeDriver` call
580584
boundaries on Linux, macOS, and Windows;
581-
- Ubuntu x86_64 native lifecycle and three-phase no-delete cleanup without KVM;
582-
- Ubuntu aarch64 native lifecycle and three-phase no-delete cleanup without KVM;
585+
- Ubuntu x86_64 native pidfd probe, lifecycle, multi-container isolation, and
586+
three-phase no-delete cleanup without KVM;
587+
- Ubuntu aarch64 native pidfd probe, lifecycle, multi-container isolation, and
588+
three-phase no-delete cleanup without KVM;
583589
- macOS HVF, isolated libkrun context, guest-marker, authenticated-agent,
584-
fixed OCI lifecycle, three-phase no-delete cleanup, and missing-entitlement
585-
fail-closed gates;
590+
pidfd-backed fixed and multi-container OCI lifecycles, three-phase no-delete
591+
cleanup, and missing-entitlement fail-closed gates;
586592
- Windows WHPX and libkrun context gates;
587593
- static x86_64 and aarch64 musl guest-agent output.
588594

ROADMAP.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ Completed:
3838
- pure OCI lifecycle transition contract;
3939
- versioned driver status, readiness, isolation, and evidence;
4040
- secure WHPX DLL loading and hypervisor capability probe;
41-
- native Linux namespace and cgroup v2 prerequisite reporting that does not
42-
touch `/dev/kvm`;
41+
- native Linux namespace, cgroup v2, and pidfd signaling prerequisite
42+
reporting that does not touch `/dev/kvm`;
4343
- Linux KVM device, access, ioctl, and API-version reporting without libkrun
4444
initialization;
4545
- Apple Silicon and Hypervisor.framework capability reporting through a
@@ -86,7 +86,7 @@ Completed:
8686
and domainname, recursively private mount propagation, ordered
8787
existing-target OCI mounts, `pivot_root`, authenticated host-visible PID
8888
reporting, exact-generation state, bounded typed init rejection reporting,
89-
session idempotency, signaling, and cleanup;
89+
session idempotency, retained pidfd signaling, and cleanup;
9090
- real WHPX fixed-bundle create/state/start/kill/delete evidence, including
9191
exact mutation retries, pre-start non-execution, running and stopped
9292
observation, marker verification, post-delete NotFound, and nominal leak
@@ -289,6 +289,10 @@ then may HVF become `experimental`.
289289
and authenticate its host-visible PID before the created barrier.
290290
- [x] Prove executor shutdown cleanup without delete after successful create,
291291
start, and kill through native Linux and the macOS utility-VM path.
292+
- [x] Open and retain a pidfd for every authenticated init process, reject
293+
kernels without `pidfd_open` and `pidfd_send_signal`, and deliver lifecycle
294+
and cleanup signals without a numeric-PID reuse race. Prove the path through
295+
native Linux and the macOS utility VM.
292296
- [ ] Namespace creation for user and time namespaces, plus joining existing
293297
namespaces.
294298
- [ ] Mount-target creation, rootfs propagation overrides, idmapped and
@@ -298,7 +302,7 @@ then may HVF become `experimental`.
298302
priority, affinity, `no_new_privileges`, LSMs, and seccomp.
299303
- [ ] cgroup v2 CPU, memory, pids, I/O, hugepage, RDMA, device, and unified
300304
resource enforcement.
301-
- [ ] Init supervision, zombie reaping, pidfd signaling, exec, and wait.
305+
- [ ] Init supervision, zombie reaping, exec, and wait.
302306
- [ ] Ordered hooks with OCI state on stdin.
303307
- [ ] Backpressured stdin/stdout/stderr, PTY, resize, signals, and output
304308
cursors.
@@ -309,8 +313,8 @@ and recovery suites in the Windows guest and on native Linux.
309313

310314
### R4 — Native Linux Without KVM
311315

312-
- [x] Report native namespace and cgroup v2 prerequisites without opening
313-
`/dev/kvm` or initializing libkrun.
316+
- [x] Report native namespace, cgroup v2, and pidfd signaling prerequisites
317+
without opening `/dev/kvm` or initializing libkrun.
314318
- [x] Report optional KVM absence, permission failure, ioctl failure, and API
315319
version independently from native readiness.
316320
- [x] Add the native Linux driver without linking or initializing libkrun.

crates/agent/src/executor/mod.rs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod mount;
44
#[cfg(test)]
55
mod mount_tests;
66
mod pid;
7+
mod pidfd;
78
mod plan;
89
#[cfg(test)]
910
mod plan_tests;
@@ -24,13 +25,15 @@ use tokio::io::AsyncWriteExt;
2425
use tokio::sync::Mutex;
2526

2627
use crate::AGENT_VERSION;
28+
use pidfd::SignalOutcome;
2729
use plan::InitPlan;
2830
use process::PreparedProcess;
2931
use state::{
3032
ContainerKey, ContainerRecord, ExecutorState, MutationKind, RecordedOutcome, RecordedRequest,
3133
};
3234

3335
pub(crate) use init::run_container_init_if_requested;
36+
pub(crate) use pidfd::verify_support as verify_pidfd_support;
3437

3538
const DEFAULT_RUNTIME_PARENT: &str = "/run";
3639
const MAX_OPERATION_RECORDS: usize = 4_096;
@@ -70,6 +73,7 @@ impl LinuxExecutor {
7073
"the Linux executor must run as root",
7174
));
7275
}
76+
pidfd::verify_support()?;
7377
let parent = runtime_parent.as_ref();
7478
if !parent.is_absolute() {
7579
return Err(executor_error(
@@ -340,8 +344,10 @@ impl LinuxExecutor {
340344
"cannot signal a stopped container",
341345
));
342346
}
343-
record.process.signal(request.signal.get())?;
344-
record.refresh()?;
347+
match record.process.signal(request.signal.get())? {
348+
SignalOutcome::Delivered => record.refresh()?,
349+
SignalOutcome::Exited => record.status = ContainerState::Stopped,
350+
}
345351
record.state()
346352
}
347353

@@ -363,16 +369,17 @@ impl LinuxExecutor {
363369
)
364370
})?;
365371
record.refresh()?;
366-
if record.status != ContainerState::Stopped {
367-
if request.mode == DeleteMode::StoppedOnly {
368-
return Err(executor_error(
369-
ErrorCode::FailedPrecondition,
370-
"stopped-only delete requires a stopped container",
371-
));
372-
}
373-
record.process.force_stop().await?;
374-
record.status = ContainerState::Stopped;
372+
if record.status != ContainerState::Stopped && request.mode == DeleteMode::StoppedOnly {
373+
return Err(executor_error(
374+
ErrorCode::FailedPrecondition,
375+
"stopped-only delete requires a stopped container",
376+
));
375377
}
378+
// Even an already-stopped init may have an authenticated wrapper
379+
// that has not completed its final wait yet. Always reap that
380+
// wrapper before releasing the runtime directory.
381+
record.process.force_stop().await?;
382+
record.status = ContainerState::Stopped;
376383
record.runtime_directory.clone()
377384
};
378385
remove_container_directory(&self.runtime_root, &runtime_directory).await?;

0 commit comments

Comments
 (0)