Skip to content

Commit 6e0de9d

Browse files
dzerikcongwang-mk
authored andcommitted
ci: add ubuntu-22.04 low-ABI job + end-to-end degrade test
The CI matrix only had v6-capable runners (ubuntu-latest, ubuntu-24.04-arm), so the Protection opt-out path added in #71 — which lets a sandbox confine on kernels below Landlock ABI 6 by degrading the scopes the host lacks — had no automated coverage on a real low-ABI kernel. Add a dedicated rust-low-abi job on ubuntu-22.04 (a 6.8 Azure kernel, Landlock ABI v4). The default strict_all() suite hard-requires v6 and would panic there, so the job runs the host-ABI-independent resolution tests plus a new end-to-end test, and reports the runner's Landlock ABI for visibility. degraded_policy_confines_and_runs_below_v6 builds a fully-degradable policy, asserts every scope the host cannot provide resolves to Degraded (not Unavailable), and runs a genuinely confined child — proving confinement still works below v6. Verified on real kernels via multipass: ABI 1 (Ubuntu 22.04), ABI 5 (Rocky 9.6), ABI 8 (host). Also add a workflow_dispatch trigger for manual runs.
1 parent 088b4af commit 6e0de9d

2 files changed

Lines changed: 107 additions & 3 deletions

File tree

.github/workflows/ci.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
branches: [main]
66
pull_request:
77
branches: [main]
8+
workflow_dispatch:
89

910
jobs:
1011
rust:
@@ -32,6 +33,36 @@ jobs:
3233
- name: Run integration tests
3334
run: cargo test --release --test integration -- --test-threads=1
3435

36+
rust-low-abi:
37+
name: Rust low-ABI (ubuntu-22.04)
38+
runs-on: ubuntu-22.04
39+
steps:
40+
- uses: actions/checkout@v4
41+
42+
- name: Install Rust
43+
uses: dtolnay/rust-toolchain@stable
44+
45+
- name: Rust cache
46+
uses: Swatinem/rust-cache@v2
47+
48+
- name: Build
49+
run: cargo build --release
50+
51+
# The ubuntu-22.04 image runs a 6.8 Azure kernel (Landlock ABI v4),
52+
# below the project's MIN_ABI = 6. Surface the host ABI so a future
53+
# runner-image kernel bump that changes it is visible at a glance.
54+
- name: Report Landlock ABI on this runner
55+
run: ./target/release/sandlock check || true
56+
57+
# The default strict_all() suite (test_sandbox, test_network, ...)
58+
# hard-requires v6 and would panic on this runner, so we run only the
59+
# host-ABI-independent resolution tests plus the end-to-end degrade
60+
# test. That degrade test is the point of having a sub-v6 runner: it
61+
# proves a fully-degradable policy still builds and confines on v4,
62+
# exercising the Protection opt-out path that the v6 runners cannot.
63+
- name: Run host-ABI-independent and degrade tests
64+
run: cargo test --release --test integration test_protection -- --test-threads=1
65+
3566
rust-riscv64-cross:
3667
name: Rust cross-build (riscv64)
3768
runs-on: ubuntu-latest

crates/sandlock-core/tests/integration/test_protection.rs

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
//! Integration tests for the per-protection availability resolution
22
//! in `landlock::confine_inner`.
33
//!
4-
//! These tests exercise the policy-driven resolution path directly via
5-
//! the `ProtectionStatus::resolve()` helper with synthetic ABI values,
6-
//! so they are independent of the host kernel's actual Landlock ABI.
4+
//! Most of these tests exercise the policy-driven resolution path
5+
//! directly via the `ProtectionStatus::resolve()` helper with synthetic
6+
//! ABI values, so they are independent of the host kernel's actual
7+
//! Landlock ABI. The one exception is
8+
//! `degraded_policy_confines_and_runs_below_v6`, which builds and runs a
9+
//! genuinely confined child to cover the end-to-end degrade path on a
10+
//! real low-ABI kernel; it self-skips where Landlock is unavailable.
711
812
use sandlock_core::landlock::compute_fs_mask;
913
use sandlock_core::{Protection, ProtectionPolicy, ProtectionState, ProtectionStatus};
@@ -460,3 +464,72 @@ fn disable_fsrefer_is_rejected_at_build() {
460464
.build()
461465
.expect("allow_degraded(FsRefer) must remain allowed");
462466
}
467+
468+
// ----------------------------------------------------------------------
469+
// End-to-end degrade path: real confinement on a low-ABI host
470+
//
471+
// Unlike the synthetic resolve() tests above, this one builds and runs a
472+
// genuinely confined child. A *fully degradable* policy must build on
473+
// any Landlock-capable kernel and confine with whatever the host
474+
// supports, degrading (silently skipping) the scopes it lacks instead of
475+
// hard-failing the way the default `strict_all()` does below ABI 6.
476+
//
477+
// This is the test that gives the ubuntu-22.04 CI runner (a 6.8 Azure
478+
// kernel, Landlock ABI v4) its purpose: there, SignalScope /
479+
// AbstractUnixSocketScope (v6) and FsIoctlDev (v5) resolve to Degraded,
480+
// exercising the opt-out path end-to-end. On a v6 host the same scopes
481+
// resolve Active and the degrade assertion is skipped. Where Landlock is
482+
// unavailable entirely, the test self-skips rather than failing.
483+
// ----------------------------------------------------------------------
484+
485+
#[tokio::test]
486+
async fn degraded_policy_confines_and_runs_below_v6() {
487+
let abi = match sandlock_core::landlock_abi_version() {
488+
Ok(v) => v,
489+
Err(e) => {
490+
eprintln!("Skipping: Landlock unavailable ({e})");
491+
return;
492+
}
493+
};
494+
495+
let mut builder = sandlock_core::Sandbox::builder();
496+
for p in Protection::all() {
497+
builder = builder.allow_degraded(p);
498+
}
499+
let sandbox = builder
500+
.fs_read("/usr")
501+
.fs_read("/lib")
502+
.fs_read_if_exists("/lib64")
503+
.fs_read("/bin")
504+
.fs_read("/etc")
505+
.build()
506+
.expect("a fully-degradable policy must build on any Landlock host");
507+
508+
// Below ABI 6, every scope the host cannot provide must resolve to
509+
// Degraded — not Unavailable, which is what makes `confine` refuse.
510+
if abi < 6 {
511+
let states = sandbox
512+
.active_protections()
513+
.expect("resolve protections against host ABI");
514+
for (p, status) in &states {
515+
if p.min_abi() > abi {
516+
assert_eq!(
517+
*status,
518+
ProtectionStatus::Degraded,
519+
"{p:?} (needs v{}) on a v{abi} host must degrade, not fail",
520+
p.min_abi()
521+
);
522+
}
523+
}
524+
}
525+
526+
let result = sandbox
527+
.with_name("degraded")
528+
.run(&["echo", "ok"])
529+
.await
530+
.expect("a confined child must run under a degraded policy");
531+
assert!(
532+
result.success(),
533+
"degraded-mode confinement must still execute the child"
534+
);
535+
}

0 commit comments

Comments
 (0)