Skip to content

Commit f8ed2f1

Browse files
authored
Merge pull request #799 from Dstack-TEE/luks2-keyslot-bounds
dstack-util: bound LUKS2 keyslot area to the metadata region
2 parents 2c2604b + 2c74e0a commit f8ed2f1

1 file changed

Lines changed: 37 additions & 0 deletions

File tree

dstack/dstack-util/src/system_setup.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3033,6 +3033,16 @@ fn validate_single_luks2_header(mut reader: impl std::io::Read, hdr_ind: u64) ->
30333033
if area.encryption() != "aes-xts-plain64" {
30343034
bail!("Invalid LUKS keyslot encryption: {}", area.encryption());
30353035
}
3036+
// Pin where the encrypted key material is read from. The binary area
3037+
// must sit between the two header copies and the encrypted payload;
3038+
// otherwise a host with raw disk access could redirect it elsewhere.
3039+
if area.offset() < 2 * hdr_size || area.offset() + area.size() > PAYLOAD_OFFSET {
3040+
bail!(
3041+
"Invalid LUKS keyslot area: offset={} size={}",
3042+
area.offset(),
3043+
area.size()
3044+
);
3045+
}
30363046
if *key_size != 64 {
30373047
bail!("Invalid LUKS keyslot key size: {key_size}");
30383048
}
@@ -3043,6 +3053,9 @@ fn validate_single_luks2_header(mut reader: impl std::io::Read, hdr_ind: u64) ->
30433053
let LuksKdf::pbkdf2 {
30443054
hash,
30453055
iterations: _,
3056+
// Salts are left unchecked on purpose: the passphrase is
3057+
// high-entropy and KMS-derived, so an attacker-chosen salt
3058+
// buys nothing without it (see security report #552).
30463059
salt: _,
30473060
} = kdf
30483061
else {
@@ -3133,6 +3146,30 @@ fn test_validate_luks2_header() {
31333146
.contains("Invalid LUKS keyslot encryption"));
31343147
}
31353148

3149+
#[test]
3150+
fn test_validate_luks2_header_rejects_out_of_range_keyslot_area() {
3151+
// Redirect the keyslot binary area below the header region. Same length
3152+
// so the surrounding header stays intact; "00768" parses to 768, which is
3153+
// inside the header copies (< 2 * hdr_size) rather than the metadata gap.
3154+
let mut header = include_bytes!("../tests/fixtures/luks_header_good").to_vec();
3155+
let needle = br#""offset":"32768""#;
3156+
let replacement = br#""offset":"00768""#;
3157+
let mut patched = 0;
3158+
let mut i = 0;
3159+
while i + needle.len() <= header.len() {
3160+
if &header[i..i + needle.len()] == needle {
3161+
header[i..i + needle.len()].copy_from_slice(replacement);
3162+
patched += 1;
3163+
i += needle.len();
3164+
} else {
3165+
i += 1;
3166+
}
3167+
}
3168+
assert_eq!(patched, 2, "expected to patch both header copies");
3169+
let error = validate_luks2_headers(&mut &header[..]).unwrap_err();
3170+
assert!(error.to_string().contains("Invalid LUKS keyslot area"));
3171+
}
3172+
31363173
#[cfg(test)]
31373174
fn test_app_compose(
31383175
manifest_version: serde_json::Value,

0 commit comments

Comments
 (0)