mind-body: the process environment is unreadable by library code, by lint
clippy.toml lists the std::env readers (var, var_os, vars, vars_os, set_var, remove_var, args, args_os, current_exe, temp_dir, home_dir) and libc::getenv as disallowed methods, and lib.rs forbids allowing them back. main.rs and the self-spawning integration test carry the only allows. The forge probe's environment sweep and its 22 guessed variable names go: the lint covers a variable of any name, so the list was a second, weaker path for the same rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
cb6f1a5a4e
commit
47d9a35fcd
5 changed files with 41 additions and 62 deletions
21
crates/mind-body/clippy.toml
Normal file
21
crates/mind-body/clippy.toml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# The brake's root is a constant: no function, argument, variable or flag lets a
|
||||
# caller choose it (control.rs). The strongest form of "no variable" is that no
|
||||
# code in this crate can read the process's environment at all, whatever name it
|
||||
# would read, so the rule is a lint on the readers and not a list of names.
|
||||
# `#![forbid(clippy::disallowed_methods)]` in lib.rs keeps the library from
|
||||
# allowing them back; the binary (main.rs) and the one integration test that
|
||||
# spawns itself (tests/control.rs) carry the only allows, each with its reason.
|
||||
disallowed-methods = [
|
||||
{ path = "std::env::var", reason = "the process environment is caller-chosen input; the brake's root is CONTROL_ROOT" },
|
||||
{ path = "std::env::var_os", reason = "the process environment is caller-chosen input; the brake's root is CONTROL_ROOT" },
|
||||
{ path = "std::env::vars", reason = "the process environment is caller-chosen input; the brake's root is CONTROL_ROOT" },
|
||||
{ path = "std::env::vars_os", reason = "the process environment is caller-chosen input; the brake's root is CONTROL_ROOT" },
|
||||
{ path = "std::env::set_var", reason = "the process environment is caller-chosen input; the brake's root is CONTROL_ROOT" },
|
||||
{ path = "std::env::remove_var", reason = "the process environment is caller-chosen input; the brake's root is CONTROL_ROOT" },
|
||||
{ path = "std::env::args", reason = "the command line is caller-chosen input; only main.rs reads it" },
|
||||
{ path = "std::env::args_os", reason = "the command line is caller-chosen input; only main.rs reads it" },
|
||||
{ path = "std::env::current_exe", reason = "the executable's location is caller-chosen; the brake's root is CONTROL_ROOT" },
|
||||
{ path = "std::env::temp_dir", reason = "reads TMPDIR; the brake's root is CONTROL_ROOT" },
|
||||
{ path = "std::env::home_dir", reason = "reads HOME; the brake's root is CONTROL_ROOT" },
|
||||
{ path = "libc::getenv", reason = "the process environment is caller-chosen input; the brake's root is CONTROL_ROOT" },
|
||||
]
|
||||
|
|
@ -9,6 +9,11 @@
|
|||
//! repetition breaker, the one-live-Self-run rule, and the single path that
|
||||
//! opens a run in the mind and only then starts its unit.
|
||||
|
||||
// The control root is a constant, so the library reads no part of the process
|
||||
// environment (clippy.toml lists the readers); `forbid` keeps an `allow` from
|
||||
// putting one back.
|
||||
#![forbid(clippy::disallowed_methods)]
|
||||
|
||||
pub mod cli;
|
||||
pub mod control;
|
||||
pub mod launch;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ fn restrict_umask() {
|
|||
#[cfg(not(unix))]
|
||||
fn restrict_umask() {}
|
||||
|
||||
// The binary is the one place that reads the process: its arguments and the
|
||||
// operator's name. The library reads neither (clippy.toml, `#![forbid]` in lib.rs).
|
||||
#[allow(clippy::disallowed_methods, reason = "the binary reads its own arguments and the operator's name, and hands them to the library")]
|
||||
fn main() {
|
||||
restrict_umask();
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@
|
|||
//! as root (the verify container does); the tests that need a second user become
|
||||
//! uid 65534 with setpriv and skip with a message when they cannot.
|
||||
|
||||
// The suite re-executes its own test binary as the writer children, and passes
|
||||
// each child its role and paths through the environment. That is test plumbing in
|
||||
// its own crate: it reaches no library code, which `#![forbid]`s these readers.
|
||||
#![allow(clippy::disallowed_methods, reason = "the suite re-executes itself and hands each child its role through the environment")]
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
//! The brake-grant rule seen from outside the crate: the only reader takes the
|
||||
//! instance, so whatever a caller builds elsewhere (a store in a temporary
|
||||
//! directory, a symlink to another instance's directory, a copy of another
|
||||
//! instance's store, the relative root `.`, a tree that every environment
|
||||
//! variable points at) is never what a Grant is made from.
|
||||
//! The signature half of the rule (no root argument, no source reader, no
|
||||
//! struct literal, no test constructor) is the `compile_fail` examples on
|
||||
//! `read_effective`; this is the behavioural half, committed because it is the
|
||||
//! only defence of the rule against a forged store.
|
||||
//! instance's store, the relative root `.`) is never what a Grant is made from.
|
||||
//! The rest of the rule is held where it is decided, not here: the library's
|
||||
//! public surface is pinned as a whole (tests/public_surface.rs: no new public
|
||||
//! way to set a root or build a Grant), the process environment and command line
|
||||
//! are unreadable by library code (clippy.toml, `#![forbid]` in lib.rs), and the
|
||||
//! `compile_fail` examples on `read_effective` show the signature half. This is
|
||||
//! the behavioural half against a forged store.
|
||||
|
||||
#![cfg(unix)]
|
||||
|
||||
|
|
@ -64,60 +65,4 @@ fn no_forged_store_yields_a_grant() {
|
|||
let relative = reads(FORGED);
|
||||
std::env::set_current_dir(before).unwrap();
|
||||
assert!(matches!(relative, Effective::Held { .. }), "relative root");
|
||||
|
||||
// A root named by the environment. The forged tree is laid out for a value
|
||||
// used as the root itself and for one with `minds` joined on, and every
|
||||
// variable the process has, with the names an implementation would likely
|
||||
// read, is pointed at it. A variable of a name neither list holds is not
|
||||
// reached; the library build having no `std::env` reader is the rest of it.
|
||||
let environed = tempfile::tempdir().unwrap();
|
||||
release(environed.path(), FORGED);
|
||||
std::fs::create_dir_all(environed.path().join("minds")).unwrap();
|
||||
release(&environed.path().join("minds"), FORGED);
|
||||
let value = environed.path().as_os_str().to_owned();
|
||||
let mut names: Vec<std::ffi::OsString> = std::env::vars_os().map(|(name, _)| name).collect();
|
||||
names.extend(GUESSED_ROOT_VARIABLES.iter().map(Into::into));
|
||||
let saved: Vec<_> = names.iter().map(|name| (name.clone(), std::env::var_os(name))).collect();
|
||||
// SAFETY: this file's one test is the only thread touching the environment.
|
||||
unsafe {
|
||||
for name in &names {
|
||||
std::env::set_var(name, &value);
|
||||
}
|
||||
}
|
||||
let through_environment = reads(FORGED);
|
||||
unsafe {
|
||||
for (name, before) in saved {
|
||||
match before {
|
||||
Some(before) => std::env::set_var(name, before),
|
||||
None => std::env::remove_var(name),
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(matches!(through_environment, Effective::Held { .. }), "root from an environment variable");
|
||||
}
|
||||
|
||||
/// Names a caller-chosen root would plausibly be read from.
|
||||
const GUESSED_ROOT_VARIABLES: [&str; 22] = [
|
||||
"MIND_CONTROL_ROOT",
|
||||
"MIND_BODY_ROOT",
|
||||
"MIND_ROOT",
|
||||
"MINDS_ROOT",
|
||||
"MINDS_DIR",
|
||||
"CONTROL_ROOT",
|
||||
"CONTROL_DIR",
|
||||
"GAMECULT_MINDS",
|
||||
"GAMECULT_ROOT",
|
||||
"HUGINN_ROOT",
|
||||
"HUGINN_HOME",
|
||||
"EUREKA_ROOT",
|
||||
"EUREKA_HOME",
|
||||
"EUREKA_INSTANCE_ROOT",
|
||||
"STATE_DIRECTORY",
|
||||
"CONFIGURATION_DIRECTORY",
|
||||
"RUNTIME_DIRECTORY",
|
||||
"XDG_CONFIG_HOME",
|
||||
"XDG_STATE_HOME",
|
||||
"XDG_DATA_HOME",
|
||||
"HOME",
|
||||
"TMPDIR",
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in a new issue