Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
15b09ce
core: introduce Protection enum with per-variant ABI floor
dzerik May 25, 2026
fee0f0a
core: add ProtectionState and ProtectionPolicy with Strict default
dzerik May 25, 2026
45f34f6
core: add Sandbox::protection_policy field defaulting to strict-all
dzerik May 25, 2026
43e0551
core: add ConfinementError::ProtectionUnavailable variant
dzerik May 25, 2026
14993ab
core: per-protection availability resolution in confine_inner
dzerik May 25, 2026
bf9490d
core: mask Degraded fs protections; consolidate net_wildcard computation
dzerik May 25, 2026
30ad30c
cli: extend 'sandlock check' with per-protection availability report
dzerik May 25, 2026
76e21ae
core: add Sandbox::active_protections() runtime accessor
dzerik May 25, 2026
08d10c1
core: SandboxBuilder::allow_degraded and ::disable polarity-out methods
dzerik May 26, 2026
265b3c1
ffi: C ABI for Protection + allow_degraded / disable builders
dzerik May 26, 2026
53af1d1
python: Sandbox allow_degraded / disable kwargs + Protection IntEnum
dzerik May 26, 2026
2be594b
cli: --allow-degraded and --disable flags for sandlock run
dzerik May 26, 2026
b9db29e
docs: document Protection opt-out (allow_degraded / disable)
dzerik May 26, 2026
ceae31c
ffi: validate Protection discriminant at the C ABI boundary
dzerik May 27, 2026
a7c726d
protection: rename AbstractUnixScope to AbstractUnixSocketScope
dzerik May 27, 2026
0d5e5fa
core: test compute_*_mask security contract; document scope-mask prec…
dzerik May 27, 2026
22a7dc9
ci: add ubuntu-22.04 to the Rust matrix and log host Landlock ABI
dzerik May 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

jobs:
rust:
Expand All @@ -13,7 +14,11 @@ jobs:
strategy:
fail-fast: false
matrix:
runner: [ubuntu-latest, ubuntu-24.04-arm]
# ubuntu-22.04 (kernel 5.15, Landlock ABI v3) catches a regression
# that ubuntu-latest (24.04, kernel 6.8, ABI v4) cannot — the
# FsTruncate / NetTcp / FsIoctlDev path stays exercised even after
# the runner image rolls forward.
runner: [ubuntu-22.04, ubuntu-latest, ubuntu-24.04-arm]
steps:
- uses: actions/checkout@v4

Expand All @@ -26,11 +31,33 @@ jobs:
- name: Build
run: cargo build --release

- name: Report Landlock ABI on this runner
# Surface the host's Landlock ABI in the job log so a future
# Landlock-version-sensitive regression is visible at a glance,
# without needing to dig into individual test outputs.
run: ./target/release/sandlock check || true

- name: Run unit tests
run: cargo test --release --lib -- --test-threads=1

- name: Run integration tests
run: cargo test --release --test integration -- --test-threads=1
run: |
# On a runner whose Landlock ABI is below the project floor
# (MIN_ABI = 6), the bulk of the integration suite cannot
# execute: every test driver constructs a default
# `Sandbox::builder()` whose `ProtectionPolicy::strict_all()`
# requires every Protection to resolve to Active. On
# ubuntu-22.04 (Landlock ABI v4 in the current GHA image)
# we therefore restrict the integration cell to
# `test_protection` — the policy/resolution-mechanics tests
# that run against a synthetic ABI and are host-ABI-
# independent. The remaining integration tests stay on the
# v6+ runners where their host assumption holds.
if [ "${{ matrix.runner }}" = "ubuntu-22.04" ]; then
cargo test --release --test integration test_protection -- --test-threads=1
else
cargo test --release --test integration -- --test-threads=1
fi

python:
name: Python tests (${{ matrix.runner }}, py${{ matrix.python-version }})
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ Sandlock is implemented in **Rust** for performance and safety:
| Landlock TCP port rules | 6.7 (ABI v4) |
| Landlock IPC scoping | 6.12 (ABI v6) |

Protections can be selectively waived per-policy when needed — see
[`docs/extension-handlers.md#protection-opt-out`](docs/extension-handlers.md#protection-opt-out).

## Install

### From source
Expand Down
52 changes: 52 additions & 0 deletions crates/sandlock-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,46 @@ struct RunArgs {
#[arg(long)]
no_supervisor: bool,

/// Allow the named protection to degrade silently if the host kernel ABI lacks support.
/// Repeatable. Accepted values: fs-refer, fs-truncate, net-tcp, fs-ioctl-dev,
/// signal-scope, abstract-unix-scope-socket.
#[arg(long = "allow-degraded", value_name = "PROTECTION")]
allow_degraded: Vec<String>,

/// Disable the named protection entirely (no rule emitted, no error on missing ABI).
/// Repeatable. Accepts the same values as --allow-degraded.
#[arg(long = "disable", value_name = "PROTECTION")]
disable: Vec<String>,

#[arg(last = true)]
cmd: Vec<String>,
}

/// Parse a kebab-case protection name into a `Protection` value.
///
/// The canonical names match the Landlock kernel constants
/// (`LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET` → `abstract-unix-socket-scope`,
/// etc.) and are case-insensitive. Accepted: `fs-refer`, `fs-truncate`,
/// `net-tcp`, `fs-ioctl-dev`, `signal-scope`,
/// `abstract-unix-socket-scope` (alias: `abstract-unix-scope-socket`).
fn parse_protection(s: &str) -> Result<sandlock_core::Protection, String> {
use sandlock_core::Protection;
match s.to_ascii_lowercase().as_str() {
"fs-refer" => Ok(Protection::FsRefer),
"fs-truncate" => Ok(Protection::FsTruncate),
"net-tcp" => Ok(Protection::NetTcp),
"fs-ioctl-dev" => Ok(Protection::FsIoctlDev),
"signal-scope" => Ok(Protection::SignalScope),
"abstract-unix-socket-scope" | "abstract-unix-scope-socket" => {
Ok(Protection::AbstractUnixSocketScope)
}
other => Err(format!(
"unknown protection: {} (valid: fs-refer, fs-truncate, net-tcp, fs-ioctl-dev, signal-scope, abstract-unix-socket-scope)",
other,
)),
}
}

#[derive(Subcommand)]
enum ProfileAction {
/// List available profiles
Expand Down Expand Up @@ -207,6 +243,14 @@ async fn main() -> Result<()> {
println!(" Device ioctl: {}", if v >= 5 { "supported (ABI v5+)" } else { "not supported" });
println!(" IPC scoping: {}", if v >= 6 { "supported (ABI v6+)" } else { "not supported" });
println!(" Signal scoping: {}", if v >= 6 { "supported (ABI v6+)" } else { "not supported" });

println!();
println!("Per-protection availability (host Landlock ABI v{}):", v);
for p in sandlock_core::Protection::all() {
let available = v >= p.min_abi();
let marker = if available { "available" } else { "unavailable" };
println!(" {:<22} requires v{} — {}", format!("{:?}", p), p.min_abi(), marker);
}
}
Err(e) => {
println!(" Landlock: unavailable ({})", e);
Expand Down Expand Up @@ -459,6 +503,14 @@ async fn run_command(args: RunArgs) -> Result<()> {
builder = builder.no_supervisor(true);
}

// CLI overrides — protection policy
for s in &args.allow_degraded {
builder = builder.allow_degraded(parse_protection(s).map_err(|e| anyhow!(e))?);
}
for s in &args.disable {
builder = builder.disable(parse_protection(s).map_err(|e| anyhow!(e))?);
}

let policy = builder.build()?;
let cmd_strs: Vec<&str> = if let Some(ref shell_cmd) = args.exec_shell {
vec!["/bin/sh", "-c", shell_cmd.as_str()]
Expand Down
13 changes: 13 additions & 0 deletions crates/sandlock-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ pub enum ConfinementError {
feature: String,
},

/// A `Protection` in `ProtectionState::Strict` is unavailable
/// because the host kernel's Landlock ABI is below the
/// protection's `min_abi()`. Build (or `confine`) refuses to
/// proceed; the caller can resolve by setting that protection to
/// `Degradable` or `Disabled`, or by running on a kernel that
/// supports it.
#[error("required protection {protection:?} is not available: host Landlock ABI is v{host_abi}, requires v{required_abi}")]
ProtectionUnavailable {
protection: crate::protection::Protection,
required_abi: u32,
host_abi: u32,
},

#[error("landlock error: {0}")]
Landlock(String),

Expand Down
Loading
Loading