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
23 changes: 17 additions & 6 deletions crates/sandlock-core/src/sandbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,23 @@ impl ByteSize {
.trim()
.parse()
.map_err(|_| SandboxError::Invalid(format!("invalid byte size: {}", s)))?;
match suffix.to_ascii_uppercase().as_str() {
"K" => Ok(ByteSize::kib(n)),
"M" => Ok(ByteSize::mib(n)),
"G" => Ok(ByteSize::gib(n)),
other => Err(SandboxError::Invalid(format!("unknown byte size suffix: {}", other))),
}
let scale: u64 = match suffix.to_ascii_uppercase().as_str() {
"K" => 1024,
"M" => 1024 * 1024,
"G" => 1024 * 1024 * 1024,
other => {
return Err(SandboxError::Invalid(format!(
"unknown byte size suffix: {}",
other
)))
}
};
// Checked: the multiply wraps in a release build, so a value that
// parses cleanly but does not fit, such as "17179869184G", used to
// come back as a ceiling of zero bytes rather than as an error.
n.checked_mul(scale)
.map(ByteSize)
.ok_or_else(|| SandboxError::Invalid(format!("byte size out of range: {}", s)))
} else {
let n: u64 = s
.parse()
Expand Down
23 changes: 23 additions & 0 deletions crates/sandlock-core/tests/integration/test_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,29 @@ fn test_bytesize_parse_invalid() {
assert!(ByteSize::parse("M").is_err());
}

#[test]
fn test_bytesize_that_does_not_fit_is_an_error_not_a_ceiling_of_zero() {
// The digits parse and the suffix is known, so the value reaches the
// multiply. In a release build that multiply wrapped, and every one of
// these came back as a byte count the caller never asked for: 17179869184G
// is exactly 2^64 bytes, which wrapped to 0 and installed a ceiling of
// nothing. A memory ceiling of zero is not inert, the guest is SIGKILLed on
// its first allocation, and nothing anywhere named the setting.
for spec in ["17179869184G", "17592186044416M", "18014398509481984K"] {
let err = ByteSize::parse(spec).expect_err(&format!("{spec} must not parse"));
let msg = err.to_string();
assert!(
msg.contains("out of range") && msg.contains(spec),
"{spec} must be refused by name and by reason, got {msg:?}"
);
}

// The largest value each suffix can carry still parses, so the check
// refuses what does not fit rather than trimming the usable range.
assert_eq!(ByteSize::parse("17179869183G").unwrap().0, 17179869183 * 1024 * 1024 * 1024);
assert_eq!(ByteSize::parse("18446744073709551615").unwrap().0, u64::MAX);
}

#[test]
fn test_clean_env() {
let p = Sandbox::builder().build().unwrap();
Expand Down
Loading