Skip to content

Commit e32f6d4

Browse files
authored
Merge pull request #14 from A3S-Lab/feat/join-existing-namespaces
feat(executor): join existing Linux namespaces
2 parents 1ddb790 + 8f057fb commit e32f6d4

26 files changed

Lines changed: 1637 additions & 273 deletions

File tree

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ run_multi_container_smoke() {
189189
fi
190190
jq --exit-status \
191191
--argjson expected "$expected_kvm_present" \
192-
'.schema_version == "a3s.oci.native-linux-multi-container-smoke.v2"
192+
'.schema_version == "a3s.oci.native-linux-multi-container-smoke.v3"
193193
and .platform == "linux" and .status == "available"
194194
and .kvm_device_present == $expected
195195
and .bundles_loaded
@@ -235,6 +235,14 @@ run_multi_container_smoke() {
235235
and .lifecycle.wait_b_replayed
236236
and .lifecycle.delete_b_replayed
237237
and .lifecycle.b_missing_after_delete
238+
and (.namespace_join.donor_pid > 0)
239+
and .namespace_join.wrong_type_rejected_before_state
240+
and .namespace_join.joined_non_mount_namespaces
241+
and .namespace_join.joined_pid_time_workload_verified
242+
and .namespace_join.joined_mount_namespace
243+
and .namespace_join.retained_rootfs_verified
244+
and .namespace_join.donor_unchanged_after_joins
245+
and .namespace_join.all_state_removed
238246
and .markers_removed
239247
and .executor_runtime_clean
240248
and .session_root_clean

.github/workflows/ci.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ jobs:
496496
if [[ "$support" == "1" ]]; then
497497
test "$status" -eq 0
498498
jq --exit-status \
499-
'.schema_version == "a3s.oci.oci-vm-multi-container-smoke.v2"
499+
'.schema_version == "a3s.oci.oci-vm-multi-container-smoke.v3"
500500
and .platform == "macos" and .status == "available"
501501
and .bundles_loaded
502502
and .lifecycle.distinct_bundle_directories
@@ -541,6 +541,14 @@ jobs:
541541
and .lifecycle.wait_b_replayed
542542
and .lifecycle.delete_b_replayed
543543
and .lifecycle.b_missing_after_delete
544+
and (.namespace_join.donor_pid > 0)
545+
and .namespace_join.wrong_type_rejected_before_state
546+
and .namespace_join.joined_non_mount_namespaces
547+
and .namespace_join.joined_pid_time_workload_verified
548+
and .namespace_join.joined_mount_namespace
549+
and .namespace_join.retained_rootfs_verified
550+
and .namespace_join.donor_unchanged_after_joins
551+
and .namespace_join.all_state_removed
544552
and .markers_removed and .guest_runtime_clean
545553
and .bridge.platform == "macos"
546554
and .bridge.status == "available"
@@ -563,7 +571,7 @@ jobs:
563571
else
564572
test "$status" -eq 2
565573
jq --exit-status \
566-
'.schema_version == "a3s.oci.oci-vm-multi-container-smoke.v2"
574+
'.schema_version == "a3s.oci.oci-vm-multi-container-smoke.v3"
567575
and .platform == "macos" and .status == "unavailable"
568576
and .bundles_loaded
569577
and .bridge.platform == "macos"

README.md

Lines changed: 79 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<a href="#runtime-model">Runtime Model</a> •
1616
<a href="#platform-status">Platform Status</a> •
1717
<a href="#architecture">Architecture</a> •
18+
<a href="#conformance-and-security">Conformance</a> •
1819
<a href="#development">Development</a>
1920
</p>
2021

@@ -350,12 +351,13 @@ target/debug/a3s-oci oci-vm-multi-container-smoke \
350351
--console "$rootfs_dir/oci-multi-container.log"
351352
```
352353

353-
`a3s.oci.oci-vm-multi-container-smoke.v2` requires that starting, killing,
354+
`a3s.oci.oci-vm-multi-container-smoke.v3` requires that starting, killing,
354355
waiting for, and deleting A never changes or blocks B; both waits return and
355356
replay the exact normal exit status; recreating A advances generation 1 to 2;
356357
stale and cross-container replay requests fail; B then completes
357-
independently; and VM shutdown restores guest-runtime, endpoint, descriptor,
358-
shim, and worker inventories.
358+
independently; existing namespace descriptors are type-checked and joined
359+
across the shared executor; and VM shutdown restores guest-runtime, endpoint,
360+
descriptor, shim, and worker inventories.
359361

360362
Fault cleanup reuses the same signed shim and bundle but stops after each
361363
successful lifecycle boundary:
@@ -433,6 +435,8 @@ The current executor implements a reviewed bootstrap vertical slice:
433435
- parent-authenticated rootful UID/GID mappings plus read-back verification,
434436
with normalized monotonic and boottime offsets applied before the first
435437
time-namespace child;
438+
- type-checked joins for existing UTS, mount, IPC, network, cgroup, PID, user,
439+
and time namespaces, including retained rootfs access after a mount join;
436440
- hostname and domain name configuration;
437441
- recursively private mount propagation and `pivot_root`;
438442
- ordered existing-target OCI mounts with bind/rbind and common VFS options;
@@ -443,8 +447,9 @@ The current executor implements a reviewed bootstrap vertical slice:
443447

444448
The supported user-namespace slice is rootful: it requires both UID and GID
445449
mappings, coverage for every configured process ID, and an `allow` setgroups
446-
policy. Unimplemented OCI fields are rejected instead of ignored. Rootless
447-
mapping policy, namespace joins, complete mount semantics, cgroup resources,
450+
policy. Mount entries remain unsupported when joining an existing mount
451+
namespace. Other unimplemented OCI fields are rejected instead of ignored.
452+
Rootless mapping policy, complete mount semantics, cgroup resources,
448453
capabilities, hooks, seccomp, full I/O, recovery, and the remaining SDK
449454
operations are still release gates.
450455

@@ -473,9 +478,9 @@ boundary.
473478

474479
| Host | Execution path | Retained evidence | Current readiness |
475480
| --- | --- | --- | --- |
476-
| Linux x86_64/aarch64 | Native Linux executor | Kernel pidfd signaling probe, real rootful lifecycle with exact SIGKILL status and repeated wait, two-container wait 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` |
481+
| Linux x86_64/aarch64 | Native Linux executor | Kernel pidfd signaling probe, real rootful lifecycle with exact SIGKILL status and repeated wait, two-container isolation, type-checked existing-namespace joins, 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` |
477482
| Linux x86_64/aarch64 | libkrun + KVM utility VM | Device access, ioctl result, and KVM API version | `probe-only`; VM driver not implemented |
478-
| macOS arm64 | libkrun + HVF utility VM | Direct HVF VM create/destroy, checksum-pinned context lifecycle, authenticated protocol-v2 arm64 guest agent, pidfd-backed fixed and two-container OCI lifecycles with exact repeated exit status and nonblocking wait evidence, and no-delete cleanup after create, start, and kill | `probe-only`; complete enforcement and recovery pending |
483+
| macOS arm64 | libkrun + HVF utility VM | Direct HVF VM create/destroy, checksum-pinned context lifecycle, authenticated protocol-v2 arm64 guest agent, pidfd-backed fixed and two-container OCI lifecycles, type-checked existing-namespace joins, exact repeated exit status and nonblocking wait evidence, and no-delete cleanup after create, start, and kill | `probe-only`; complete enforcement and recovery pending |
479484
| 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 |
480485

481486
Linux installation, feature inspection, and the native SDK path must work when
@@ -484,47 +489,69 @@ runtime prerequisite.
484489

485490
## Architecture
486491

487-
The platform-neutral control plane is independent of the host isolation
488-
mechanism. Native libraries and Linux-specific execution stay behind explicit
489-
driver, shim, and guest-agent boundaries:
490-
491-
```text
492-
A3S Box / a3s-oci CLI / Rust SDK consumers
493-
494-
RuntimeClient / OciRuntimeService
495-
496-
in-process call or bounded local IPC
497-
498-
OCI validation and lifecycle
499-
500-
HostRuntimeService
501-
exact bundle · generations · journal · reconciliation
502-
503-
RuntimeDriver
504-
┌─────────┴──────────┐
505-
│ │
506-
Native Linux host Utility VM qualification
507-
NativeLinuxDriver a3s-oci-krun-shim → libkrun
508-
│ KVM · HVF · WHPX
509-
│ │
510-
│ authenticated guest protocol
511-
│ │
512-
│ a3s-oci-agent
513-
│ │
514-
└─────────┬──────────┘
515-
516-
LinuxExecutor
517-
namespaces · mounts · PID 1 · pidfds · wait · cleanup
492+
The public contract is separated from replaceable infrastructure. The SDK and
493+
lifecycle control plane remain platform-neutral, while native isolation and
494+
hypervisor libraries stay behind explicit driver, shim, and guest boundaries:
495+
496+
```mermaid
497+
flowchart TB
498+
subgraph consumers["Consumers and SDK"]
499+
box["A3S Box"]
500+
cli["a3s-oci CLI"]
501+
containerd["containerd shim<br/>(planned)"]
502+
client["RuntimeClient<br/>in-process service or bounded local IPC"]
503+
box --> client
504+
cli --> client
505+
containerd -.-> client
506+
end
507+
508+
subgraph control["Platform-neutral host control plane"]
509+
service["OciRuntimeService<br/>HostRuntimeService"]
510+
validation["OCI schema and semantic validation"]
511+
lifecycle["Durable lifecycle<br/>generations · replay · fencing · reconciliation"]
512+
state[("Runtime-owned state<br/>exact config · operation journal")]
513+
selection{"RuntimeDriver<br/>explicit isolation selection"}
514+
515+
service --> validation --> lifecycle --> selection
516+
lifecycle <--> state
517+
end
518+
519+
subgraph native["Native Linux — experimental opt-in"]
520+
native_driver["NativeLinuxDriver<br/>shared host kernel"]
521+
end
522+
523+
subgraph utility["Utility VM — qualification path"]
524+
utility_driver["Utility VM RuntimeDriver<br/>(integration pending)"]
525+
shim["a3s-oci-krun-shim<br/>checksum-pinned native loading"]
526+
hypervisor["libkrun<br/>HVF · WHPX<br/>KVM probe only"]
527+
bridge["Authenticated host/guest bridge<br/>Unix socket or named pipe → AF_VSOCK"]
528+
agent["a3s-oci-agent<br/>static Linux guest service"]
529+
530+
utility_driver -.-> shim
531+
shim --> hypervisor --> bridge --> agent
532+
end
533+
534+
executor["Shared LinuxExecutor<br/>new and joined namespaces · mounts · create/start barrier<br/>PID 1 · pidfds · wait · scoped cleanup"]
535+
536+
client --> service
537+
selection -->|"shared-host-kernel"| native_driver
538+
selection -.->|"dedicated-vm / shared-guest-kernel"| utility_driver
539+
native_driver --> executor
540+
agent --> executor
518541
```
519542

520-
The same `LinuxExecutor` is called directly on Linux and through the guest
521-
agent in a utility VM. The utility-VM branch represents the qualification
522-
architecture; readiness remains defined by the
523-
[platform status](#platform-status), not by presence in the diagram.
543+
The two paths compile and place the same `LinuxExecutor` differently: directly
544+
behind `NativeLinuxDriver` on Linux, or inside `a3s-oci-agent` in a utility VM.
545+
The utility-VM driver edge is dashed because it is a qualification path and is
546+
not yet wired into `HostRuntimeService`. Solid edges show implemented or
547+
directly exercised boundaries, not `supported` readiness; the
548+
[platform status](#platform-status) remains authoritative.
524549

525-
A3S Box owns product-level images, builds, volumes, networks, and policy. A3S
526-
OCI Runtime owns the validated OCI lifecycle, platform execution, durable
527-
state, guest protocol, and runtime-scoped cleanup.
550+
| Boundary | Owns | Deliberately leaves outside |
551+
| --- | --- | --- |
552+
| A3S Box product plane | Images, builds, volumes, networks, and product policy | OCI process and isolation enforcement |
553+
| OCI Runtime control plane | Exact OCI validation, lifecycle state, replay, reconciliation, capability reporting, and driver selection | Silent isolation fallback and product policy |
554+
| Platform execution plane | Hypervisor bridge, Linux namespaces and mounts, PID 1 lifecycle, signaling, and runtime-scoped cleanup | Image distribution and workload orchestration |
528555

529556
The main runtime, CLI, and SDK do not link libkrun. Only
530557
`a3s-oci-krun-shim` loads the checksum-verified native runtime bundle, keeping
@@ -597,6 +624,7 @@ Run checks from the OCI Runtime repository root:
597624
cargo fmt --all -- --check
598625
cargo test --workspace --all-targets
599626
cargo clippy --workspace --all-targets -- -D warnings
627+
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps
600628
```
601629

602630
Cross-check Linux compilation without treating the monorepo root as a Rust
@@ -615,13 +643,13 @@ Platform CI covers:
615643

616644
- the 237-point durable commit matrix and all 14 `RuntimeDriver` call
617645
boundaries on Linux, macOS, and Windows;
618-
- Ubuntu x86_64 native pidfd probe, lifecycle, multi-container isolation, and
619-
three-phase no-delete cleanup without KVM;
620-
- Ubuntu aarch64 native pidfd probe, lifecycle, multi-container isolation, and
621-
three-phase no-delete cleanup without KVM;
646+
- Ubuntu x86_64 native pidfd probe, lifecycle, multi-container and
647+
existing-namespace isolation, and three-phase no-delete cleanup without KVM;
648+
- Ubuntu aarch64 native pidfd probe, lifecycle, multi-container and
649+
existing-namespace isolation, and three-phase no-delete cleanup without KVM;
622650
- macOS HVF, isolated libkrun context, guest-marker, authenticated-agent,
623-
pidfd-backed fixed and multi-container OCI lifecycles, three-phase no-delete
624-
cleanup, and missing-entitlement fail-closed gates;
651+
pidfd-backed fixed, multi-container, and existing-namespace OCI lifecycles,
652+
three-phase no-delete cleanup, and missing-entitlement fail-closed gates;
625653
- Windows WHPX and libkrun context gates;
626654
- static x86_64 and aarch64 musl guest-agent output.
627655

ROADMAP.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ Completed:
7070
- real native Linux create/state/start/kill/wait/delete SDK evidence on x86_64
7171
and aarch64, including exact repeated SIGKILL status and bounded running
7272
wait, repeated with `/dev/kvm` absent and present but unusable;
73+
- type-checked joins for existing UTS, mount, IPC, network, cgroup, PID, user,
74+
and time namespaces, including retained rootfs execution after a mount join,
75+
three-pass user-namespace permission recovery, and shared native Linux/macOS
76+
utility-VM lifecycle evidence;
7377
- real native Linux no-delete cleanup after create, start, and kill on x86_64
7478
and aarch64, including init-PID reap and executor, durable-state, marker, and
7579
session-root removal;
@@ -305,7 +309,11 @@ then may HVF become `experimental`.
305309
UID/GID mappings through the authenticated parent, apply and verify
306310
monotonic/boottime offsets before the first child, and prove the path through
307311
native Linux and the macOS utility VM.
308-
- [ ] Join existing namespaces.
312+
- [x] Open and type-check all existing namespace descriptors before mutation,
313+
join non-user namespaces around the user-namespace capability transition,
314+
preserve PID/time next-child semantics, and prove UTS, mount, IPC, network,
315+
cgroup, PID, user, and time joins through native Linux and the macOS
316+
utility-VM path.
309317
- [ ] Mount-target creation, rootfs propagation overrides, idmapped and
310318
recursive-attribute mounts, masked paths, read-only paths, and read-only
311319
rootfs.

0 commit comments

Comments
 (0)