Skip to content

Commit 2fb6e45

Browse files
authored
Merge pull request #595 from Dstack-TEE/fix/issue-566-gzip-bomb
fix(ra-tls): limit RA-TLS cert extension decompression size
2 parents 8eac462 + 64ce9f9 commit 2fb6e45

1 file changed

Lines changed: 20 additions & 13 deletions

File tree

ra-tls/src/cert.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,13 @@ pub struct CertPair {
478478
/// Magic prefix for gzip-compressed event log (version 1)
479479
pub const EVENTLOG_GZIP_MAGIC: &[u8] = b"ELGZv1";
480480

481+
/// Maximum allowed decompressed size of the event log extension (in bytes).
482+
///
483+
/// This protects against gzip decompression bombs in RA-TLS certificate
484+
/// extensions by bounding the amount of memory we are willing to allocate.
485+
/// 16 KiB is sufficient for typical event logs we embed in certs.
486+
pub const MAX_EVENTLOG_EXT_SIZE: u64 = 16 * 1024;
487+
481488
/// Compress a certificate extension value
482489
pub fn compress_ext_value(data: &[u8]) -> Result<Vec<u8>> {
483490
use flate2::write::GzEncoder;
@@ -507,11 +514,19 @@ pub fn decompress_ext_value(data: &[u8]) -> Result<Vec<u8>> {
507514
if data.starts_with(EVENTLOG_GZIP_MAGIC) {
508515
// Compressed format
509516
let compressed = &data[EVENTLOG_GZIP_MAGIC.len()..];
510-
let mut decoder = GzDecoder::new(compressed);
517+
let decoder = GzDecoder::new(compressed);
518+
// Limit the total amount of decompressed data to avoid gzip bombs.
519+
let mut limited = decoder.take(MAX_EVENTLOG_EXT_SIZE + 1);
511520
let mut decompressed = Vec::new();
512-
decoder
521+
limited
513522
.read_to_end(&mut decompressed)
514523
.context("failed to decompress event log")?;
524+
if decompressed.len() as u64 > MAX_EVENTLOG_EXT_SIZE {
525+
bail!(
526+
"event log extension too large (>{} bytes)",
527+
MAX_EVENTLOG_EXT_SIZE
528+
);
529+
}
515530
Ok(decompressed)
516531
} else {
517532
// Uncompressed format (backwards compatibility)
@@ -639,17 +654,9 @@ mod tests {
639654

640655
#[test]
641656
fn test_event_log_compression_ratio() {
642-
// Simulate a large event log with repetitive data (like certificates)
643-
let mut large_data = Vec::new();
644-
for i in 0..100 {
645-
large_data.extend_from_slice(format!(
646-
r#"{{"imr":{},"event_type":1,"digest":"{}","event":"test{}","event_payload":"{}"}},"#,
647-
i % 4,
648-
"a".repeat(96),
649-
i,
650-
"deadbeef".repeat(100)
651-
).as_bytes());
652-
}
657+
// Simulate a reasonably large, highly repetitive event log payload.
658+
// Keep it well below MAX_EVENTLOG_EXT_SIZE so decompression succeeds.
659+
let large_data = vec![b'a'; (MAX_EVENTLOG_EXT_SIZE / 2) as usize];
653660

654661
let compressed = compress_ext_value(&large_data).unwrap();
655662
let ratio = compressed.len() as f64 / large_data.len() as f64;

0 commit comments

Comments
 (0)