gatewayd: read a secret file from the file that was checked

Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
2026-09-24 01:33:36 -07:00
parent 6feffadd11
commit 10c80b74f3
3 changed files with 135 additions and 21 deletions
+91
View File
@@ -0,0 +1,91 @@
//! A secret file is read from the file that was checked, never from whatever the path names a
//! moment later (M4a review, finding 2). `read_checked` runs `between` after checking the path and
//! before opening it; each test swaps something there. Do not edit.
#[path = "support/tmp.rs"]
mod tmp;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use gatewayd::secrets::read_checked;
use tmp::TempDir;
const TOKEN: &str = "the-real-token";
const OTHER: &str = "a-file-the-owner-never-chose";
fn owner_file(dir: &TempDir, name: &str, text: &str) -> PathBuf {
let path = dir.write(name, text);
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)).unwrap();
path
}
fn refused(path: &Path, between: &dyn Fn(), word: &str) {
let why = read_checked(path, between).expect_err(word);
assert!(why.contains(word), "{word}: {why}");
assert!(why.contains(&path.display().to_string()), "{why}");
assert!(
!why.contains(TOKEN) && !why.contains(OTHER),
"never a value: {why}"
);
}
#[test]
fn an_untouched_file_is_read() {
let dir = TempDir::new("race-ok");
let path = owner_file(&dir, "token", &format!("{TOKEN}\n"));
let bytes = read_checked(&path, &|| {}).unwrap();
assert_eq!(bytes.as_slice(), format!("{TOKEN}\n").as_bytes());
}
#[test]
fn a_file_swapped_for_a_link_is_refused() {
let dir = TempDir::new("race-link");
let path = owner_file(&dir, "token", TOKEN);
let other = owner_file(&dir, "other", OTHER);
let swap = || {
std::fs::remove_file(&path).unwrap();
std::os::unix::fs::symlink(&other, &path).unwrap();
};
refused(&path, &swap, "changed while it was read");
}
#[test]
fn a_file_swapped_for_another_file_is_refused() {
let dir = TempDir::new("race-rename");
let path = owner_file(&dir, "token", TOKEN);
let other = owner_file(&dir, "other", OTHER);
let swap = || std::fs::rename(&other, &path).unwrap();
refused(&path, &swap, "changed while it was read");
}
#[test]
fn the_checks_hold_for_the_file_that_is_read() {
let dir = TempDir::new("race-mode");
let path = owner_file(&dir, "token", TOKEN);
let widen = || {
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644)).unwrap();
};
refused(&path, &widen, "has mode 644");
}
#[test]
fn the_path_checks_still_come_first() {
let dir = TempDir::new("race-first");
let other = owner_file(&dir, "other", OTHER);
let link = dir.path().join("link");
std::os::unix::fs::symlink(&other, &link).unwrap();
refused(
&link,
&|| panic!("never reached for a link"),
"is a symbolic link",
);
refused(
dir.path(),
&|| panic!("never reached for a directory"),
"is not a regular file",
);
let relative = Path::new("relative/token");
let why = read_checked(relative, &|| panic!("never reached")).unwrap_err();
assert!(why.contains("is not an absolute path"), "{why}");
}