use std::time::{Duration, Instant};
use compress_lib::{
ErrorKind, Level, Limits,
deflate::{self, Deflater, Flush, Inflater, Status},
gzip,
message::{Compressor, Decompressor, MessageConfig},
zlib,
};
fn next(x: &mut u32) -> u32 {
*x ^= *x << 13;
*x ^= *x >> 17;
*x ^= *x << 5;
*x
}
struct Bits {
out: Vec<u8>,
acc: u64,
n: u32,
}
impl Bits {
fn new() -> Self {
Self {
out: Vec::new(),
acc: 0,
n: 0,
}
}
fn bits(&mut self, v: u32, n: u32) -> &mut Self {
self.acc |= u64::from(v) << self.n;
self.n += n;
while self.n >= 8 {
self.out.push(self.acc as u8);
self.acc >>= 8;
self.n -= 8;
}
self
}
fn done(&mut self) -> Vec<u8> {
if self.n > 0 {
let pad = 8 - self.n;
self.bits(0, pad);
}
std::mem::take(&mut self.out)
}
}
#[test]
fn test_attack_t1_bomb_stops_at_cap_in_every_format() {
let zeros = vec![0u8; 32 << 20];
let cap = 1 << 20;
let limits = Limits::new().with_max_output(cap);
let raw = deflate::compress_with(&zeros, Level::BEST);
assert!(
raw.len() < 64 * 1024,
"the bomb is small: {} bytes",
raw.len()
);
assert_eq!(
deflate::decompress_with(&raw, limits).unwrap_err().kind(),
ErrorKind::OutputLimit
);
assert_eq!(
zlib::decompress_with(&zlib::compress_with(&zeros, Level::BEST), limits)
.unwrap_err()
.kind(),
ErrorKind::OutputLimit
);
assert_eq!(
gzip::decompress_with(&gzip::compress_with(&zeros, Level::BEST), limits)
.unwrap_err()
.kind(),
ErrorKind::OutputLimit
);
assert_eq!(
compress_lib::decompress_with(&gzip::compress(&zeros), limits)
.unwrap_err()
.kind(),
ErrorKind::OutputLimit
);
let mut inflater = Inflater::new().with_limits(limits);
let mut out = vec![0xAAu8; 2 << 20];
assert_eq!(
inflater.finish(&raw, &mut out).unwrap_err().kind(),
ErrorKind::OutputLimit
);
assert_eq!(inflater.total_out(), cap);
assert!(out[cap as usize..].iter().all(|&b| b == 0xAA));
let big = deflate::compress_with(&vec![0u8; 80 << 20], Level::BEST);
assert_eq!(
deflate::decompress(&big).unwrap_err().kind(),
ErrorKind::OutputLimit
);
}
#[test]
fn test_attack_t2_ratio_bomb_caught_by_ratio_cap() {
let packed = gzip::compress(&vec![b'a'; 8 << 20]);
let limits = Limits::new()
.with_max_ratio(100)
.with_ratio_floor(64 * 1024);
assert_eq!(
gzip::decompress_with(&packed, limits).unwrap_err().kind(),
ErrorKind::RatioLimit
);
let mut x = 9u32;
let text: Vec<u8> = (0..200_000)
.map(|_| b"etaoin shrdlu\n"[(next(&mut x) % 14) as usize])
.collect();
assert_eq!(
gzip::decompress_with(&gzip::compress(&text), limits).unwrap(),
text
);
}
#[test]
fn test_attack_t3_malformed_tables_refused() {
let mut w = Bits::new();
w.bits(1, 1).bits(2, 2).bits(0, 5).bits(0, 5).bits(15, 4);
for _ in 0..19 {
w.bits(7, 3);
}
assert_eq!(
deflate::decompress(&w.done()).unwrap_err().kind(),
ErrorKind::InvalidCodeLengths
);
let mut w = Bits::new();
w.bits(1, 1).bits(2, 2).bits(0, 5).bits(0, 5).bits(15, 4);
for _ in 0..19 {
w.bits(0, 3);
}
w.bits(0, 16);
assert_eq!(
deflate::decompress(&w.done()).unwrap_err().kind(),
ErrorKind::InvalidCodeLengths
);
let mut x = 77u32;
let start = Instant::now();
for _ in 0..20_000 {
let mut data = vec![0x05u8];
data.extend((0..64).map(|_| next(&mut x) as u8));
let _ = deflate::decompress(&data);
}
assert!(start.elapsed() < Duration::from_secs(30));
}
#[test]
fn test_attack_t4_distance_beyond_data_or_window() {
let mut w = Bits::new();
w.bits(1, 1).bits(1, 2);
w.bits(0b1001_0001u32.reverse_bits() >> 24, 8); w.bits(1u32.reverse_bits() >> 25, 7); w.bits(1u32.reverse_bits() >> 27, 5); w.bits(0, 7);
assert_eq!(
deflate::decompress(&w.done()).unwrap_err().kind(),
ErrorKind::DistanceTooFar
);
let mut x = 3u32;
let mut data: Vec<u8> = (0..4000).map(|_| next(&mut x) as u8).collect();
let copy = data[..1000].to_vec();
data.extend_from_slice(©);
let packed = deflate::compress(&data);
let mut small = Inflater::new().with_window_bits(9).unwrap();
assert_eq!(
small
.finish_vec(&packed, &mut Vec::new())
.unwrap_err()
.kind(),
ErrorKind::DistanceTooFar
);
}
#[test]
fn test_attack_t5_tiny_buffers_linear() {
let mut x = 5u32;
let text: Vec<u8> = (0..1 << 20)
.map(|_| b"abcdefgh ijk\n"[(next(&mut x) % 13) as usize])
.collect();
let packed = deflate::compress(&text);
let t = Instant::now();
let whole = deflate::decompress_with(&packed, Limits::unlimited()).unwrap();
let one_shot = t.elapsed();
let t = Instant::now();
let mut inflater = Inflater::new().with_limits(Limits::unlimited());
let mut out = Vec::with_capacity(whole.len());
let mut byte = [0u8; 1];
let mut input = &packed[..];
loop {
let p = inflater.decompress(input, &mut byte).unwrap();
input = &input[p.consumed..];
out.extend_from_slice(&byte[..p.produced]);
if p.status == Status::Done {
break;
}
}
let tiny = t.elapsed();
assert_eq!(out, whole);
assert!(
tiny < one_shot * 400 + Duration::from_secs(2),
"one shot {one_shot:?}, one-byte buffers {tiny:?}"
);
}
#[test]
fn test_attack_t5_byte_by_byte_message_fragments_linear() {
let mut x = 11u32;
let text: Vec<u8> = (0..4 << 20)
.map(|_| {
b"abcdefgh ijk
"[(next(&mut x) % 13) as usize]
})
.collect();
let mut wire = Vec::new();
Compressor::new(MessageConfig::new())
.unwrap()
.compress_message(&text, &mut wire)
.unwrap();
let mut inflater = Inflater::new().with_limits(Limits::unlimited());
let mut flat = vec![0u8; text.len()];
let mut made = 0;
let t = Instant::now();
for b in &wire {
let p = inflater.decompress(&[*b], &mut flat[made..]).unwrap();
assert_eq!(p.consumed, 1);
made += p.produced;
}
let sliced = t.elapsed();
assert_eq!(&flat[..made], &text[..]);
let mut rx = Decompressor::new(MessageConfig::new()).unwrap();
let t = Instant::now();
let mut out = Vec::new();
for (i, b) in wire.iter().enumerate() {
rx.decompress_fragment(&[*b], i + 1 == wire.len(), &mut out, 8 << 20)
.unwrap();
}
let tiny = t.elapsed();
assert_eq!(out, text);
assert!(
tiny < sliced * 20 + Duration::from_secs(2),
"slice API byte by byte {sliced:?}, message byte by byte {tiny:?}"
);
}
#[test]
fn test_attack_t6_empty_blocks_and_members_bounded() {
let mut stream = [0x00u8, 0x00, 0x00, 0xFF, 0xFF].repeat(200_000);
stream.extend_from_slice(&[0x03, 0x00]);
let t = Instant::now();
assert_eq!(deflate::decompress(&stream).unwrap(), b"");
assert!(t.elapsed() < Duration::from_secs(10));
let empty = gzip::compress(b"");
let many = empty.repeat(5000);
assert_eq!(
gzip::decompress(&many).unwrap_err().kind(),
ErrorKind::TooManyMembers
);
}
#[test]
fn test_attack_t7_gzip_header_bounded() {
let mut member = vec![0x1F, 0x8B, 8, 0x08 | 0x10, 0, 0, 0, 0, 0, 255];
member.extend(std::iter::repeat_n(b'n', 1 << 20));
member.push(0);
assert_eq!(
gzip::decompress(&member).unwrap_err().kind(),
ErrorKind::HeaderTooLarge
);
let mut dec = gzip::Decoder::new().with_limits(Limits::new().with_max_header(1000));
let mut out = [0u8; 16];
let mut err = None;
for chunk in member.chunks(100) {
if let Err(e) = dec.decompress(chunk, &mut out) {
err = Some(e.kind());
break;
}
}
assert_eq!(err, Some(ErrorKind::HeaderTooLarge));
}
#[test]
fn test_attack_t8_checksums_always_verified() {
let data = b"integrity matters".repeat(50);
let packed = gzip::compress(&data);
for i in packed.len() - 8..packed.len() {
let mut bad = packed.clone();
bad[i] ^= 0x01;
let kind = gzip::decompress(&bad).unwrap_err().kind();
assert!(
matches!(
kind,
ErrorKind::ChecksumMismatch | ErrorKind::LengthMismatch
),
"gzip byte {i}: {kind:?}"
);
}
let packed = zlib::compress(&data);
for i in packed.len() - 4..packed.len() {
let mut bad = packed.clone();
bad[i] ^= 0x01;
assert_eq!(
zlib::decompress(&bad).unwrap_err().kind(),
ErrorKind::ChecksumMismatch,
"zlib byte {i}"
);
}
}
#[test]
fn test_attack_t9_no_takeover_no_cross_message_oracle() {
let mut tx = Compressor::new(MessageConfig::new()).unwrap();
let mut sizes = Vec::new();
for secret in [&b"token=7f3a9c2e"[..], b"token=00000000"] {
let mut wire = Vec::new();
tx.compress_message(secret, &mut wire).unwrap();
wire.clear();
tx.compress_message(b"token=7f3a9c2e", &mut wire).unwrap();
sizes.push(wire.len());
}
assert_eq!(
sizes[0], sizes[1],
"the guess compressed the same whatever the earlier secret was"
);
let mut tx = Compressor::new(MessageConfig::new().with_context_takeover(true)).unwrap();
let mut first = Vec::new();
tx.compress_message(b"token=7f3a9c2e", &mut first).unwrap();
let mut guess = Vec::new();
tx.compress_message(b"token=7f3a9c2e", &mut guess).unwrap();
assert!(guess.len() < first.len());
}
#[test]
fn test_attack_t10_memory_fixed_per_engine() {
let config = MessageConfig::new().with_window_bits(9);
let mut rx = Decompressor::new(config).unwrap();
let mut tx = Compressor::new(config).unwrap();
let (rx0, tx0) = (rx.memory_in_use(), tx.memory_in_use());
let mut x = 1u32;
for _ in 0..200 {
let msg: Vec<u8> = (0..(next(&mut x) % 20_000))
.map(|_| next(&mut x) as u8 % 7)
.collect();
let mut wire = Vec::new();
tx.compress_message(&msg, &mut wire).unwrap();
let mut out = Vec::new();
rx.decompress_message(&wire, &mut out, 1 << 20).unwrap();
}
assert_eq!((rx.memory_in_use(), tx.memory_in_use()), (rx0, tx0));
assert!(
rx0 < 16 * 1024,
"a decompressor without takeover holds only its tables: {rx0}"
);
}
#[test]
fn test_attack_t11_no_panic_on_arbitrary_input() {
let mut x = 11u32;
for len in [0usize, 1, 2, 3, 10, 100, 1000] {
for _ in 0..300 {
let data: Vec<u8> = (0..len).map(|_| next(&mut x) as u8).collect();
let _ = deflate::decompress(&data);
let _ = zlib::decompress(&data);
let _ = gzip::decompress(&data);
let mut rx = Decompressor::new(MessageConfig::new().with_window_bits(8)).unwrap();
let _ = rx.decompress_message(&data, &mut Vec::new(), 1 << 16);
}
}
}
#[test]
fn test_attack_t12_truncation_detected() {
let data = b"complete or nothing ".repeat(500);
let raw = deflate::compress(&data);
let z = zlib::compress(&data);
let g = gzip::compress(&data);
for cut in 0..g.len() {
if cut < raw.len() {
assert_eq!(
deflate::decompress(&raw[..cut]).unwrap_err().kind(),
ErrorKind::UnexpectedEnd,
"raw {cut}"
);
}
if cut < z.len() {
assert_eq!(
zlib::decompress(&z[..cut]).unwrap_err().kind(),
ErrorKind::UnexpectedEnd,
"zlib {cut}"
);
}
assert_eq!(
gzip::decompress(&g[..cut]).unwrap_err().kind(),
ErrorKind::UnexpectedEnd,
"gzip {cut}"
);
}
}
#[test]
fn test_attack_t12_truncated_message_block_refused() {
let cut: [&[u8]; 3] = [
&[0x00, 0xE8, 0x03, 0x17, 0xFC, b'h', b'e', b'l', b'l', b'o'],
&[0xF2, 0x48],
&[0xF2, 0x48, 0xCD, 0xC9, 0xC9],
];
for takeover in [false, true] {
let config = MessageConfig::new().with_context_takeover(takeover);
for payload in cut {
let mut rx = Decompressor::new(config).unwrap();
let mut out = Vec::new();
let err = rx
.decompress_message(payload, &mut out, 1 << 20)
.unwrap_err();
assert_eq!(err.kind(), ErrorKind::UnexpectedEnd, "{payload:02x?}");
let mut rx = Decompressor::new(config).unwrap();
let mut out = Vec::new();
let mut result = Ok(0);
for (i, b) in payload.iter().enumerate() {
result = rx.decompress_fragment(&[*b], i + 1 == payload.len(), &mut out, 1 << 20);
if result.is_err() {
break;
}
}
assert_eq!(result.unwrap_err().kind(), ErrorKind::UnexpectedEnd);
let mut tx = Compressor::new(config).unwrap();
let mut wire = Vec::new();
tx.compress_message(b"next message", &mut wire).unwrap();
let mut out = Vec::new();
rx.decompress_message(&wire, &mut out, 1 << 20).unwrap();
assert_eq!(out, b"next message");
}
}
for bits in [8u8, 9, 12, 15] {
for level in [0u8, 1, 6, 9] {
let config = MessageConfig::new()
.with_context_takeover(true)
.with_window_bits(bits)
.with_level(Level::new(level).unwrap());
let mut tx = Compressor::new(config).unwrap();
let mut rx = Decompressor::new(config).unwrap();
for n in [0usize, 1, 100, 70_000] {
let msg = b"block boundary ".repeat(n / 15 + 1)[..n].to_vec();
let mut wire = Vec::new();
tx.compress_message(&msg, &mut wire).unwrap();
let mut out = Vec::new();
rx.decompress_message(&wire, &mut out, 1 << 20).unwrap();
assert_eq!(out, msg, "bits {bits} level {level} n {n}");
}
}
}
let mut rx = Decompressor::new(MessageConfig::new()).unwrap();
let mut out = Vec::new();
rx.decompress_message(&[0xF3, 0x48, 0xCD, 0xC9, 0xC9, 0x07, 0x00], &mut out, 64)
.unwrap();
assert_eq!(out, b"Hello");
let unstripped = [
0xF2, 0x48, 0xCD, 0xC9, 0xC9, 0x07, 0x00, 0x00, 0x00, 0xFF, 0xFF,
];
assert_eq!(
rx.decompress_message(&unstripped, &mut out, 64)
.unwrap_err()
.kind(),
ErrorKind::UnexpectedEnd
);
}
#[test]
fn test_attack_t13_trailing_data_refused() {
let tail = b"SMUGGLED";
let with_tail = |mut v: Vec<u8>| {
v.extend_from_slice(tail);
v
};
assert_eq!(
deflate::decompress(&with_tail(deflate::compress(b"x")))
.unwrap_err()
.kind(),
ErrorKind::TrailingData
);
assert_eq!(
zlib::decompress(&with_tail(zlib::compress(b"x")))
.unwrap_err()
.kind(),
ErrorKind::TrailingData
);
assert_eq!(
gzip::decompress(&with_tail(gzip::compress(b"x")))
.unwrap_err()
.kind(),
ErrorKind::TrailingData
);
let mut rx = Decompressor::new(MessageConfig::new()).unwrap();
let mut final_block = deflate::compress(b"x");
final_block.push(0x42);
assert_eq!(
rx.decompress_message(&final_block, &mut Vec::new(), 64)
.unwrap_err()
.kind(),
ErrorKind::TrailingData
);
}
#[test]
fn test_attack_t14_adversarial_encoder_input_bounded() {
let mut x = 13u32;
let mut data = Vec::new();
while data.len() < 1 << 20 {
data.extend_from_slice(b"abc");
data.push(next(&mut x) as u8);
}
let t = Instant::now();
let six = deflate::compress_with(&data, Level::DEFAULT);
let level6 = t.elapsed();
let t = Instant::now();
let nine = deflate::compress_with(&data, Level::BEST);
let level9 = t.elapsed();
assert_eq!(
deflate::decompress_with(&nine, Limits::unlimited()).unwrap(),
data
);
assert_eq!(
deflate::decompress_with(&six, Limits::unlimited()).unwrap(),
data
);
assert!(
level9 < level6 * 40 + Duration::from_secs(1),
"level 6 {level6:?}, level 9 {level9:?}"
);
let mut d = Deflater::new(Level::BEST);
let mut out = Vec::new();
for chunk in data.chunks(997) {
d.compress_vec(chunk, &mut out, Flush::Sync).unwrap();
}
d.compress_vec(b"", &mut out, Flush::Finish).unwrap();
assert_eq!(
deflate::decompress_with(&out, Limits::unlimited()).unwrap(),
data
);
}
#[test]
fn test_attack_wipe_leaves_no_plaintext() {
let secret = b"session=4f9a2c7e1b; password=correct horse battery staple";
let config = MessageConfig::new().with_context_takeover(true);
let mut tx = Compressor::new(config).unwrap();
let mut rx = Decompressor::new(config).unwrap();
let mut wire = Vec::new();
tx.compress_message(secret, &mut wire).unwrap();
let mut out = Vec::new();
rx.decompress_message(&wire, &mut out, 1 << 10).unwrap();
tx.wipe();
rx.wipe();
let mut again = Vec::new();
tx.compress_message(secret, &mut again).unwrap();
assert_eq!(
again, wire,
"wiped compressor still remembered the first message"
);
let mut out2 = Vec::new();
rx.decompress_message(&again, &mut out2, 1 << 10).unwrap();
assert_eq!(out2, secret);
let mut d = Deflater::new(Level::FAST);
let mut packed = Vec::new();
d.compress_vec(secret, &mut packed, Flush::Sync).unwrap();
d.wipe();
let mut i = Inflater::new();
i.decompress_vec(&packed, &mut Vec::new()).unwrap();
i.wipe();
assert_eq!((d.total_in(), i.total_out()), (0, 0));
}