Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[env]
# Allocation accounting is unused; its global mutex serializes SQLite callers.
LIBSQLITE3_FLAGS = "-DSQLITE_DEFAULT_MEMSTATUS=0"

[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,11 @@ jobs:
continue-on-error: true
timeout-minutes: 40
run: cargo test --workspace --locked --no-fail-fast 2>&1 | tee "$RUNNER_TEMP/windows-baseline/test-no-fail-fast.log"
- name: Test basal-launch with deviations
id: test-basal-launch
continue-on-error: true
timeout-minutes: 20
run: cargo test -p basal-launch --features deviations --locked 2>&1 | tee "$RUNNER_TEMP/windows-baseline/test-basal-launch.log"
- name: Summarize the baseline
if: always()
run: |
Expand All @@ -414,6 +419,7 @@ jobs:
echo
echo "- cargo check --workspace --all-targets --locked: ${{ steps.check.outcome }}"
echo "- cargo test --workspace --locked: ${{ steps.test.outcome }}"
echo "- cargo test -p basal-launch --features deviations: ${{ steps.test-basal-launch.outcome }}"
} >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: always()
Expand Down
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"crates/basal-core",
"crates/basal-testkit",
"crates/basal-module",
"crates/basal-launch",
"crates/basal-rig",
]

Expand All @@ -20,6 +21,7 @@ rust-version = "1.88"
# Dependencies do not require sibling checkouts.
basal-core = { path = "crates/basal-core" }
basal-host = { path = "crates/basal-host" }
basal-launch = { path = "crates/basal-launch" }
basal-proto = { path = "crates/basal-proto" }
basal-testkit = { path = "crates/basal-testkit" }
blake3 = "1.8"
Expand Down
39 changes: 39 additions & 0 deletions crates/basal-launch/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[package]
name = "basal-launch"
version = "0.1.0"
description = "Windows process launcher, AppContainer profile and confinement setup."
edition.workspace = true
license.workspace = true
publish.workspace = true
rust-version.workspace = true

[[bin]]
name = "basal-launch-test-helper"
path = "src/bin/test-helper.rs"
test = false

[dependencies]
serde = { workspace = true, optional = true }

[features]
default = []
serde = ["dep:serde"]
deviations = []

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = [
"Win32_Foundation",
"Win32_Security",
"Win32_Security_Authorization",
"Win32_Security_Isolation",
"Win32_System_Threading",
"Win32_System_JobObjects",
"Win32_System_Memory",
"Win32_System_Pipes",
"Win32_System_Registry",
"Win32_System_Diagnostics_Debug",
"Win32_System_Environment",
"Win32_System_LibraryLoader",
"Win32_Storage_FileSystem",
"Win32_System_SystemServices",
] }
10 changes: 10 additions & 0 deletions crates/basal-launch/src/bin/test-helper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#![cfg_attr(windows, windows_subsystem = "windows")]

fn main() {
#[cfg(windows)]
{
use std::io::Read;
let mut buf = [0u8; 1];
let _ = std::io::stdin().read(&mut buf);
}
}
9 changes: 9 additions & 0 deletions crates/basal-launch/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//! Process launching, AppContainer profile creation, and confinement configuration for Windows.
//!
//! Off Windows this crate contains no code.

#[cfg(windows)]
pub mod windows;

#[cfg(windows)]
pub use windows::*;
230 changes: 230 additions & 0 deletions crates/basal-launch/src/windows/deviation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
//! Confinement launch deviation modes and controls.

use std::fmt;
use std::str::FromStr;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// Confinement deviation requested for a spawn.
///
/// Under production builds (without the `deviations` feature), only [`Deviation::Full`]
/// is compiled. Non-`Full` deviations are test and development controls gated behind
/// the `deviations` feature.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum Deviation {
/// Full production confinement recipe.
#[default]
Full,

/// Weaker LPAC-only positive control: CreateProcessW, zero capabilities,
/// no restricted primary token, no mitigations, no job.
#[cfg(feature = "deviations")]
LpacOnly,
/// Weaker plain positive control: CreateProcessW without AppContainer or mitigations.
#[cfg(feature = "deviations")]
Plain,

// Worker refusal reason token deviations
/// Retains or injects an impersonation thread token before first frame read.
#[cfg(feature = "deviations")]
ThreadTokenPresent,
/// Prevents lowering primary token integrity to Untrusted.
#[cfg(feature = "deviations")]
IntegrityLowerFailed,
/// Omits LPAC attributes (e.g. WIN://NOALLAPPPKG claim or wrong package SID).
#[cfg(feature = "deviations")]
NotLpac,
/// Injects a non-empty capability list into the token.
#[cfg(feature = "deviations")]
CapabilitiesPresent,
/// Primary token restricting SIDs are not exactly `{S-1-0-0}`.
#[cfg(feature = "deviations")]
RestrictingSidMismatch,
/// Retains an access group as enabled rather than deny-only.
#[cfg(feature = "deviations")]
GroupNotDenyOnly,
/// Retains privileges on the primary token instead of clearing them.
#[cfg(feature = "deviations")]
PrivilegesPresent,
/// Primary token integrity is not lowered to Untrusted.
#[cfg(feature = "deviations")]
IntegrityNotUntrusted,
/// Omits required mitigation policy flags or enables forbidden bits.
#[cfg(feature = "deviations")]
MitigationMismatch,
/// Leaks unpermitted handles or omits handle list restriction.
#[cfg(feature = "deviations")]
HandleNotAllowed,

// Parent pre-resume check deviations
/// Intentionally mismatches job limits or flags readback.
#[cfg(feature = "deviations")]
JobLimitsMismatch,
/// Omits the job list attribute so the process is not in the owned job.
#[cfg(feature = "deviations")]
NotInOwnedJob,
/// Suspended primary token differs from expected birth token attributes.
#[cfg(feature = "deviations")]
BirthTokenMismatch,
/// Thread token handle is not closed before resume, or token level differs.
#[cfg(feature = "deviations")]
InitialTokenOpen,
}

impl Deviation {
/// Returns the canonical kebab-case string name of this deviation.
pub const fn as_str(&self) -> &'static str {
match self {
Self::Full => "full",
#[cfg(feature = "deviations")]
Self::LpacOnly => "lpac-only",
#[cfg(feature = "deviations")]
Self::Plain => "plain",
#[cfg(feature = "deviations")]
Self::ThreadTokenPresent => "thread-token-present",
#[cfg(feature = "deviations")]
Self::IntegrityLowerFailed => "integrity-lower-failed",
#[cfg(feature = "deviations")]
Self::NotLpac => "not-lpac",
#[cfg(feature = "deviations")]
Self::CapabilitiesPresent => "capabilities-present",
#[cfg(feature = "deviations")]
Self::RestrictingSidMismatch => "restricting-sid-mismatch",
#[cfg(feature = "deviations")]
Self::GroupNotDenyOnly => "group-not-deny-only",
#[cfg(feature = "deviations")]
Self::PrivilegesPresent => "privileges-present",
#[cfg(feature = "deviations")]
Self::IntegrityNotUntrusted => "integrity-not-untrusted",
#[cfg(feature = "deviations")]
Self::MitigationMismatch => "mitigation-mismatch",
#[cfg(feature = "deviations")]
Self::HandleNotAllowed => "handle-not-allowed",
#[cfg(feature = "deviations")]
Self::JobLimitsMismatch => "job-limits-mismatch",
#[cfg(feature = "deviations")]
Self::NotInOwnedJob => "not-in-owned-job",
#[cfg(feature = "deviations")]
Self::BirthTokenMismatch => "birth-token-mismatch",
#[cfg(feature = "deviations")]
Self::InitialTokenOpen => "initial-token-open",
}
}

/// If this deviation targets a worker startup check, returns the expected
/// exit 70 stderr reason token.
pub const fn worker_reason(&self) -> Option<&'static str> {
match self {
#[cfg(feature = "deviations")]
Self::ThreadTokenPresent => Some("thread-token-present"),
#[cfg(feature = "deviations")]
Self::IntegrityLowerFailed => Some("integrity-lower-failed"),
#[cfg(feature = "deviations")]
Self::NotLpac => Some("not-lpac"),
#[cfg(feature = "deviations")]
Self::CapabilitiesPresent => Some("capabilities-present"),
#[cfg(feature = "deviations")]
Self::RestrictingSidMismatch => Some("restricting-sid-mismatch"),
#[cfg(feature = "deviations")]
Self::GroupNotDenyOnly => Some("group-not-deny-only"),
#[cfg(feature = "deviations")]
Self::PrivilegesPresent => Some("privileges-present"),
#[cfg(feature = "deviations")]
Self::IntegrityNotUntrusted => Some("integrity-not-untrusted"),
#[cfg(feature = "deviations")]
Self::MitigationMismatch => Some("mitigation-mismatch"),
#[cfg(feature = "deviations")]
Self::HandleNotAllowed => Some("handle-not-allowed"),
_ => None,
}
}

/// If this deviation targets a parent pre-resume check, returns the expected
/// parent pre-resume refusal reason.
pub const fn parent_reason(&self) -> Option<&'static str> {
match self {
#[cfg(feature = "deviations")]
Self::JobLimitsMismatch => Some("job-limits-mismatch"),
#[cfg(feature = "deviations")]
Self::NotInOwnedJob => Some("not-in-owned-job"),
#[cfg(feature = "deviations")]
Self::BirthTokenMismatch => Some("birth-token-mismatch"),
#[cfg(feature = "deviations")]
Self::InitialTokenOpen => Some("initial-token-open"),
_ => None,
}
}

/// Returns true if this is one of the positive control modes (Full, LpacOnly, Plain).
pub const fn is_control(&self) -> bool {
match self {
Self::Full => true,
#[cfg(feature = "deviations")]
Self::LpacOnly | Self::Plain => true,
#[cfg(feature = "deviations")]
_ => false,
}
}

/// Returns true if this deviation targets a parent pre-resume check.
pub const fn is_parent_check(&self) -> bool {
self.parent_reason().is_some()
}

/// Returns true if this deviation targets a worker startup check.
pub const fn is_worker_check(&self) -> bool {
self.worker_reason().is_some()
}
}

impl fmt::Display for Deviation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}

impl FromStr for Deviation {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"full" => Ok(Self::Full),
#[cfg(feature = "deviations")]
"lpac-only" => Ok(Self::LpacOnly),
#[cfg(feature = "deviations")]
"plain" => Ok(Self::Plain),
#[cfg(feature = "deviations")]
"thread-token-present" => Ok(Self::ThreadTokenPresent),
#[cfg(feature = "deviations")]
"integrity-lower-failed" => Ok(Self::IntegrityLowerFailed),
#[cfg(feature = "deviations")]
"not-lpac" => Ok(Self::NotLpac),
#[cfg(feature = "deviations")]
"capabilities-present" => Ok(Self::CapabilitiesPresent),
#[cfg(feature = "deviations")]
"restricting-sid-mismatch" => Ok(Self::RestrictingSidMismatch),
#[cfg(feature = "deviations")]
"group-not-deny-only" => Ok(Self::GroupNotDenyOnly),
#[cfg(feature = "deviations")]
"privileges-present" => Ok(Self::PrivilegesPresent),
#[cfg(feature = "deviations")]
"integrity-not-untrusted" => Ok(Self::IntegrityNotUntrusted),
#[cfg(feature = "deviations")]
"mitigation-mismatch" => Ok(Self::MitigationMismatch),
#[cfg(feature = "deviations")]
"handle-not-allowed" => Ok(Self::HandleNotAllowed),
#[cfg(feature = "deviations")]
"job-limits-mismatch" => Ok(Self::JobLimitsMismatch),
#[cfg(feature = "deviations")]
"not-in-owned-job" => Ok(Self::NotInOwnedJob),
#[cfg(feature = "deviations")]
"birth-token-mismatch" => Ok(Self::BirthTokenMismatch),
#[cfg(feature = "deviations")]
"initial-token-open" => Ok(Self::InitialTokenOpen),
other => Err(format!("unknown deviation: {other}")),
}
}
}
Loading
Loading