Skip to content
Merged
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
51 changes: 41 additions & 10 deletions dstack/dstack-util/src/system_setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3346,7 +3346,8 @@ fn validate_single_luks2_header(mut reader: impl std::io::Read, hdr_ind: u64) ->
// Pin where the encrypted key material is read from. The binary area
// must sit between the two header copies and the encrypted payload;
// otherwise a host with raw disk access could redirect it elsewhere.
if area.offset() < 2 * hdr_size || area.offset() + area.size() > PAYLOAD_OFFSET {
let area_end = area.offset().checked_add(area.size());
if area.offset() < 2 * hdr_size || area_end.is_none_or(|end| end > PAYLOAD_OFFSET) {
bail!(
"Invalid LUKS keyslot area: offset={} size={}",
area.offset(),
Expand Down Expand Up @@ -3462,22 +3463,52 @@ fn test_validate_luks2_header_rejects_out_of_range_keyslot_area() {
// so the surrounding header stays intact; "00768" parses to 768, which is
// inside the header copies (< 2 * hdr_size) rather than the metadata gap.
let mut header = include_bytes!("../tests/fixtures/luks_header_good").to_vec();
let needle = br#""offset":"32768""#;
let replacement = br#""offset":"00768""#;
let patched = patch_luks_json(&mut header, br#""offset":"32768""#, br#""offset":"00768""#);
assert_eq!(patched, 2, "expected to patch both header copies");
let error = validate_luks2_headers(&mut &header[..]).unwrap_err();
assert!(error.to_string().contains("Invalid LUKS keyslot area"));
}

/// Replace `needle` with `replacement` inside every LUKS JSON region, keeping
/// each region the same length by giving back trailing NUL padding.
#[cfg(test)]
fn patch_luks_json(header: &mut [u8], needle: &[u8], replacement: &[u8]) -> usize {
assert!(replacement.len() >= needle.len());
let grow = replacement.len() - needle.len();
let mut patched = 0;
let mut i = 0;
while i + needle.len() <= header.len() {
if &header[i..i + needle.len()] == needle {
header[i..i + needle.len()].copy_from_slice(replacement);
patched += 1;
i += needle.len();
} else {
if &header[i..i + needle.len()] != needle {
i += 1;
continue;
}
}
let tail = &mut header[i..];
let end = tail.iter().position(|b| *b == 0).expect("NUL padding");
assert!(end + grow < tail.len(), "not enough NUL padding");
tail.copy_within(needle.len()..end, replacement.len());
tail[..replacement.len()].copy_from_slice(replacement);
patched += 1;
i += replacement.len();
}
patched
}

#[test]
fn test_validate_luks2_header_rejects_keyslot_area_that_overflows() {
let mut header = include_bytes!("../tests/fixtures/luks_header_good").to_vec();
// 2**64 - 32768 + 1000: added to the accepted offset 32768 it wraps to
// 1000, which is below PAYLOAD_OFFSET.
let patched = patch_luks_json(
&mut header,
br#""size":"258048""#,
br#""size":"18446744073709519848""#,
);
assert_eq!(patched, 2, "expected to patch both header copies");
let error = validate_luks2_headers(&mut &header[..]).unwrap_err();
assert!(error.to_string().contains("Invalid LUKS keyslot area"));
assert!(
error.to_string().contains("Invalid LUKS keyslot area"),
"{error:#}"
);
}

#[cfg(test)]
Expand Down
Loading