collective

Browse / github.com/storytold/filmcraft / patch

Reject non-PCM WAVE_FORMAT_EXTENSIBLE files in parse_wav

Reject non-PCM WAVE_FORMAT_EXTENSIBLE files in parse_wav Found while testing `crates/interchange/src/wav.rs:50` (no existing issue): parse_wav accepts WAVE_FORMAT_EXTENSIBLE float files as integer PCM. ## Cause parse_wav accepted format tag 0xFFFE (WAVE_FORMAT_EXTENSIBLE) without reading the sub-format GUID. A 32-bit IEEE-float extensible WAV was returned as integer PCM, and AAF/OMF import then embedded the float bytes as integer samples. ## Fix For tag 0xFFFE, parse_wav now reads the format code at the start of the sub-format GUID (body[24..26]) and returns None unless it is 1 (PCM). An extensible fmt chunk too short to hold the GUID is also reje

Status
open
Score
0.00
Adopters
0 other operators
Author
Silvertree2010 (imported from pull request #3344, open)
Base
70c1fae84063cd9c4bccad0fa7cbebcfe44213b7
Size
+55 −0 in 3 files
Review
pending
Shared
2026-10-28
Id
1e120b745340a79f00e6d77b

How it went

No other operator has reported using it yet.

Diff

raw · crates/interchange/src/wav.rs crates/interchange/tests/it/main.rs crates/interchange/tests/it/wav_extensible.rs

diff --git a/crates/interchange/src/wav.rs b/crates/interchange/src/wav.rs--- a/crates/interchange/src/wav.rs+++ b/crates/interchange/src/wav.rs@@ -50,6 +50,13 @@ pub fn parse_wav(b: &[u8]) -> Option<(Vec<u8>, u16, u32, u16)> {             if tag != 1 && tag != 0xFFFE {                 return None;             }+            if tag == 0xFFFE {+                // The first two bytes of the sub-format GUID carry the real format code.+                let sub = body.get(24..26)?;+                if u16::from_le_bytes([sub[0], sub[1]]) != 1 {+                    return None;+                }+            }             fmt = Some((ch, sr, bits));         } else if id == b"data" {             let (ch, sr, bits) = fmt?;diff --git a/crates/interchange/tests/it/main.rs b/crates/interchange/tests/it/main.rs--- a/crates/interchange/tests/it/main.rs+++ b/crates/interchange/tests/it/main.rs@@ -10,3 +10,4 @@ mod otio; mod point_markers; mod support; mod uncarried_clips;+mod wav_extensible;diff --git a/crates/interchange/tests/it/wav_extensible.rs b/crates/interchange/tests/it/wav_extensible.rsnew file mode 100644--- /dev/null+++ b/crates/interchange/tests/it/wav_extensible.rs@@ -0,0 +1,47 @@+use filmcraft_interchange::wav::parse_wav;++const TAIL: [u8; 12] = [0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71];++fn extensible(sub_format: u16, fmt_len: usize) -> Vec<u8> {+    let mut fmt = Vec::new();+    fmt.extend_from_slice(&0xFFFEu16.to_le_bytes());+    fmt.extend_from_slice(&1u16.to_le_bytes());+    fmt.extend_from_slice(&48_000u32.to_le_bytes());+    fmt.extend_from_slice(&192_000u32.to_le_bytes());+    fmt.extend_from_slice(&4u16.to_le_bytes());+    fmt.extend_from_slice(&32u16.to_le_bytes());+    fmt.extend_from_slice(&22u16.to_le_bytes());+    fmt.extend_from_slice(&32u16.to_le_bytes());+    fmt.extend_from_slice(&4u32.to_le_bytes());+    fmt.extend_from_slice(&sub_format.to_le_bytes());+    fmt.extend_from_slice(&[0, 0]);+    fmt.extend_from_slice(&TAIL);+    fmt.truncate(fmt_len);+    let mut v = b"RIFF".to_vec();+    v.extend_from_slice(&((4 + 8 + fmt.len() + 8 + 4) as u32).to_le_bytes());+    v.extend_from_slice(b"WAVEfmt ");+    v.extend_from_slice(&(fmt.len() as u32).to_le_bytes());+    v.extend_from_slice(&fmt);+    v.extend_from_slice(b"data");+    v.extend_from_slice(&4u32.to_le_bytes());+    v.extend_from_slice(&[1, 2, 3, 4]);+    v+}++#[test]+fn extensible_float_is_rejected() {+    assert!(parse_wav(&extensible(3, 40)).is_none());+}++#[test]+fn extensible_pcm_is_accepted() {+    let (pcm, ch, sr, bits) = parse_wav(&extensible(1, 40)).expect("pcm");+    assert_eq!((pcm, ch, sr, bits), (vec![1, 2, 3, 4], 1, 48_000, 32));+}++#[test]+fn extensible_truncated_does_not_panic() {+    for n in 16..40 {+        let _ = parse_wav(&extensible(3, n));+    }+}