Skip to content
Merged
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
14 changes: 13 additions & 1 deletion Cargo.lock

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

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ members = [
"system/compositor/desktop",
"system/compositor/virtio-gpu",
"system/console",
"system/disks",
"system/diskserver",
"system/filepicker",
"system/fileserver",
Expand Down Expand Up @@ -301,8 +302,6 @@ pem = "4"
[dev-dependencies]
# The FAT implementation `build/fatformat.rs` is judged against, byte for byte.
fatfs = "0.3.6"
# The GPT writer `build/gptwrite.rs` is judged against, byte for byte.
gpt = "3.1.0"
# `USER_TOP`, so the harness judges a held `rsp` against the bound the kernel
# checks every user pointer against and not against a second copy of it.
toyos-userbound = { path = "lib/userbound" }
Expand Down
50 changes: 50 additions & 0 deletions build/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ struct ProgramConfig {
syscap: Vec<String>,
/// The idle slot (`toyos_manifest::Program::slots`).
slots: bool,
/// Disk authorizations minted at its start (`toyos_manifest::authorize`).
authorize: Vec<String>,
/// A system service: the supervisor starts it with `HOME` at its own `/state/<name>`
/// and makes that directory, where every other row gets the session's.
service: bool,
Expand Down Expand Up @@ -717,6 +719,22 @@ fn held_by_their_holders_alone(config: &SystemConfig) -> Result<(), String> {
if program.slots && config.boot.start.contains(name) {
return Err(format!("`{name}` asks for `slots` and is in `[boot] start`, and the slots are granted to a launch alone"));
}
// A row any login could start would hand every login what it holds,
// and so would a login authority, whose namespace reaches every
// program a login runs directly: an authorization that changes a
// disk is the image's to grant, to a row that is neither.
for word in &program.authorize {
let Some(authorization) = toyos_manifest::authorize::Authorization::parse(word) else {
return Err(format!("`{name}` asks to be authorized for `{word}`, which is no disk authorization"));
};
let starters: Vec<&String> = config.programs.iter().filter(|(_, p)| p.starts.contains(name)).map(|(n, _)| n).collect();
if authorization.changes() && !starters.is_empty() {
return Err(format!("`{name}` is authorized for `{word}`, which changes a disk, and {starters:?} may start it"));
}
if authorization.changes() && program.login {
return Err(format!("`{name}` is authorized for `{word}`, which changes a disk, and is a login authority"));
}
}
}
for (port, holders) in HELD_ALONE {
if config.apps.receives.iter().any(|r| r == port) {
Expand Down Expand Up @@ -844,6 +862,7 @@ fn render_manifest(config: &SystemConfig) -> Vec<u8> {
drives: cfg.drives.clone(),
syscap: cfg.syscap.clone(),
slots: cfg.slots,
authorize: cfg.authorize.clone(),
service: cfg.service,
state: cfg.state,
roles: cfg.roles.clone(),
Expand Down Expand Up @@ -2967,6 +2986,36 @@ mod tests {
);
}

/// No image this repository ships authorizes a change to a disk: that is
/// a test image's, for the one boot that makes it.
#[test]
fn no_shipped_boot_config_authorizes_a_disk_change() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
for boot in [Boot::shipped(root), Boot::diag(root), Boot::console(root)] {
let config = parse_config(&boot.config);
for (name, program) in &config.programs {
let changes = program.authorize.iter().filter_map(|w| toyos_manifest::authorize::Authorization::parse(w)).any(|a| a.changes());
assert!(!changes, "{} authorizes `{name}` to change a disk: {:?}", boot.config.display(), program.authorize);
}
}
}

/// An authorization that changes a disk goes to a row only the boot
/// starts, never one a login can launch; reading the tables may go to
/// either; and a word that names nothing is refused.
#[test]
fn a_disk_changing_authorization_goes_to_no_row_a_login_can_start() {
let gate = |text: &str| held_by_their_holders_alone(&toml::from_str(text).unwrap());
assert!(gate("[programs.runner]\nauthorize = [\"designate:data\"]\n").is_ok());
assert!(gate("[programs.disks]\nauthorize = [\"tables\"]\n[programs.shell]\nstarts = [\"disks\"]\n").is_ok());
let launched = gate("[programs.disks]\nauthorize = [\"designate:data\"]\n[programs.shell]\nstarts = [\"disks\"]\n");
assert!(launched.unwrap_err().contains("may start it"));
let login = gate("[boot]\nstart = [\"sshserver\"]\n[programs.sshserver]\nlogin = true\nauthorize = [\"designate:data\"]\n");
assert!(login.unwrap_err().contains("is a login authority"));
assert!(gate("[programs.sshserver]\nlogin = true\nauthorize = [\"tables\"]\n").is_ok(), "reading the tables is no change");
assert!(gate("[programs.disks]\nauthorize = [\"format:everything\"]\n").unwrap_err().contains("no disk authorization"));
}

/// No image this repository ships starts sshserver.
///
/// It listens on every interface and authenticates against a file that is
Expand Down Expand Up @@ -3200,6 +3249,7 @@ mod tests {
"tests/images/acpicase.toml",
"tests/images/benchcase.toml",
"tests/images/clipboardcase.toml",
"tests/images/designatecase.toml",
"tests/images/drivescase.toml",
"tests/images/hostedclangcase.toml",
"tests/images/jobcase.toml",
Expand Down
237 changes: 0 additions & 237 deletions build/gptwrite.rs

This file was deleted.

Loading