#[cfg(test)]
pub(crate) fn credential_ladder(caller: (u32, u32), owner: Option<(u32, u32)>) -> Vec<(u32, u32)> {
credential_ladder_with(caller, owner, None, &[])
}
pub(crate) fn credential_ladder_with(caller: (u32, u32), owner: Option<(u32, u32)>, mode: Option<u32>, observed: &[(u32, u32)]) -> Vec<(u32, u32)> {
let mut list = Vec::with_capacity(16);
if let Some((file_uid, file_group)) = owner {
list.push((file_uid, file_group));
if file_group != caller.1 {
list.push((caller.0, file_group));
}
}
list.push((0, 0));
let other_has_access = mode.is_none_or(|m| m & 0o007 != 0);
if !other_has_access {
let mut seen = std::collections::HashSet::new();
list.retain(|pair| seen.insert(*pair));
return list;
}
list.extend_from_slice(observed);
list.push((65534, 65534));
list.push((1000, 1000));
list.push((33, 33)); list.push((27, 27)); list.push((26, 26)); list.push((1001, 1001));
list.push((1002, 1002));
let mut seen = std::collections::HashSet::new();
list.retain(|pair| seen.insert(*pair));
list
}
#[cfg(test)]
mod tests {
#![expect(clippy::pedantic, reason = "unit test -- lints are suppressed per project policy")]
use super::*;
#[test]
fn escalation_list_removes_nonadjacent_duplicates() {
let list = credential_ladder((1001, 1001), Some((0, 0)));
let mut seen = std::collections::HashSet::new();
for pair in &list {
assert!(seen.insert(*pair), "duplicate credential {pair:?} in escalation ladder");
}
assert_eq!(list.iter().filter(|p| **p == (0, 0)).count(), 1, "root (0,0) must appear once");
assert_eq!(list[0], (0, 0), "owner keeps highest priority");
}
#[test]
fn escalation_list_dedups_owner_matching_service_account() {
let list = credential_ladder((42, 42), Some((1000, 1000)));
assert_eq!(list.iter().filter(|p| **p == (1000, 1000)).count(), 1);
assert_eq!(list[0], (1000, 1000));
}
#[test]
fn escalation_list_has_no_duplicates_without_owner() {
let list = credential_ladder((33, 33), None);
let mut seen = std::collections::HashSet::new();
for pair in &list {
assert!(seen.insert(*pair), "duplicate credential {pair:?}");
}
}
}
#[cfg(test)]
mod evidence_tests {
use super::*;
#[test]
fn no_other_access_prunes_the_guess_rungs() {
let full = credential_ladder_with((1000, 1000), Some((0, 42)), None, &[]);
let pruned = credential_ladder_with((1000, 1000), Some((0, 42)), Some(0o640), &[]);
assert!(pruned.len() < full.len(), "mode 0640 must shorten the ladder");
assert!(!pruned.contains(&(33, 33)), "www-data cannot read a 0640 file it does not own");
assert!(!pruned.contains(&(65534, 65534)), "nobody cannot either");
}
#[test]
fn owner_and_group_rungs_survive_pruning() {
let pruned = credential_ladder_with((1000, 1000), Some((0, 42)), Some(0o640), &[]);
assert!(pruned.contains(&(0, 42)), "the owner must always be tried");
assert!(pruned.contains(&(1000, 42)), "caller claiming the file's group must be tried");
}
#[test]
fn root_survives_pruning_because_no_root_squash_bypasses_mode() {
let pruned = credential_ladder_with((1000, 1000), Some((5000, 5000)), Some(0o600), &[]);
assert!(pruned.contains(&(0, 0)), "root bypasses the permission check where the export allows it");
}
#[test]
fn world_readable_keeps_the_full_ladder() {
let full = credential_ladder_with((1000, 1000), Some((0, 42)), None, &[]);
let with_mode = credential_ladder_with((1000, 1000), Some((0, 42)), Some(0o644), &[]);
assert_eq!(full.len(), with_mode.len(), "0644 grants other-read, so nothing can be ruled out");
}
#[test]
fn observed_identities_outrank_the_guesses() {
let ladder = credential_ladder_with((1000, 1000), None, Some(0o644), &[(5000, 5000)]);
let observed_at = ladder.iter().position(|p| *p == (5000, 5000)).expect("observed pair present");
let guess_at = ladder.iter().position(|p| *p == (33, 33)).expect("guess present");
assert!(observed_at < guess_at, "an identity seen owning files beats a guess at www-data");
}
#[test]
fn ladder_never_repeats_a_credential() {
let ladder = credential_ladder_with((0, 0), Some((0, 0)), None, &[(0, 0), (1000, 1000)]);
let mut seen = std::collections::HashSet::new();
for pair in &ladder {
assert!(seen.insert(*pair), "credential {pair:?} tried twice");
}
}
}