Skip to content

Commit ed6f3ba

Browse files
committed
fix(codec): reject ANS streams with n>0 but an empty freq table
Codex P2: decode_modes accepted a stream whose header claimed n > 0 while all four stored frequencies were zero (an "empty model"). from_freqs permits an all-zero table for the n == 0 empty-stream case, so a corrupt or non-encode_modes stream slipped through and rans_decode fabricated Escape tags from the freq-0 table instead of reporting malformed input. Add an explicit n > 0 / all-zero-table guard + regression test. https://claude.ai/code/session_01HbqooFZHAjaUtFEzhA1R2u
1 parent 347f36d commit ed6f3ba

1 file changed

Lines changed: 18 additions & 0 deletions

File tree

src/hpc/codec/ans.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,13 @@ pub fn decode_modes(stream: &[u8]) -> Option<Vec<CellMode>> {
351351
*slot = u16::from_le_bytes([lo, hi]);
352352
}
353353
let table = RansFreqTable::from_freqs(freq)?;
354+
// A non-empty stream needs a non-empty model. An all-zero freq table is
355+
// only valid for n == 0 (the empty stream); with n > 0 it cannot encode
356+
// any symbol, so reject rather than letting `rans_decode` fabricate
357+
// Escape tags from the empty (freq-0) table on a corrupt input.
358+
if n > 0 && freq.iter().all(|&f| f == 0) {
359+
return None;
360+
}
354361
let payload = &stream[HEADER_LEN..];
355362
if n > 0 && payload.len() < 4 {
356363
return None;
@@ -487,6 +494,17 @@ mod tests {
487494
);
488495
}
489496

497+
#[test]
498+
fn decode_rejects_nonempty_count_with_empty_freq_table() {
499+
// Codex P2: header claims n > 0 but the stored freq table is all-zero
500+
// (an "empty model"). `from_freqs` accepts an all-zero table for the
501+
// n == 0 case, so the n > 0 guard must reject here rather than let
502+
// rans_decode fabricate Escape tags from the freq-0 table.
503+
let mut stream = vec![0u8; HEADER_LEN + 8];
504+
stream[0] = 5; // n = 5, but all four u16 freqs stay 0
505+
assert!(decode_modes(&stream).is_none());
506+
}
507+
490508
#[test]
491509
fn decode_rejects_short_header() {
492510
assert!(decode_modes(&[0u8; 11]).is_none());

0 commit comments

Comments
 (0)