security(audio): bound sequence sample rates and AAF/OMF export channel buffers (#3082)

* security(audio): bound sequence sample rates and AAF/OMF export channel buffers

* fix(engine): bound export audio channels and samples independently
This commit is contained in:
Dylan M. Taylor
2026-10-24 05:28:22 -04:00
committed by GitHub
parent 48c7a27226
commit 552f008d27
3 changed files with 16 additions and 4 deletions
+4 -1
View File
@@ -20,6 +20,9 @@ pub struct StreamResampler {
next_out: Option<i64>,
}
/// Maximum source frames fetched in a single resampler pull.
pub const MAX_RESAMPLE_SOURCE_SPAN: usize = 384_000 * 10;
impl StreamResampler {
/// Rates of zero are treated as 1 Hz rather than dividing by zero.
pub fn new(from: u32, to: u32) -> Self {
@@ -53,7 +56,7 @@ impl StreamResampler {
}
let held_end = self.held_start + self.held.first().map_or(0, Vec::len) as i64;
if last + 1 > held_end {
let want = (last + 1 - held_end) as usize;
let want = ((last + 1 - held_end) as usize).min(MAX_RESAMPLE_SOURCE_SPAN);
let got = pull(held_end, want);
if self.held.is_empty() {
self.held = vec![Vec::new(); got.len().max(1)];
+9 -1
View File
@@ -130,6 +130,9 @@ fn dequantize(pcm: &[u8], channels: usize, bits: u16) -> Vec<Vec<f32>> {
out
}
const MAX_EXPORT_AUDIO_CHANNELS: usize = 64;
const MAX_EXPORT_AUDIO_SAMPLES: usize = 64 * 1024 * 1024;
/// Decode a media range at `sample_rate` (planar).
fn decode(s: &Session, need: &AudioNeed, sample_rate: u32) -> Result<Vec<Vec<f32>>> {
let src = s
@@ -138,7 +141,12 @@ fn decode(s: &Session, need: &AudioNeed, sample_rate: u32) -> Result<Vec<Vec<f32
.ok_or_else(|| EngineError::Other(format!("media {} is offline", need.path.as_deref().unwrap_or("?"))))?;
let start = need.start.to_units_floor(sample_rate as i64);
let frames = (need.end.to_units_floor(sample_rate as i64) - start).max(0) as usize;
let mut out: Vec<Vec<f32>> = vec![Vec::with_capacity(frames); need.channels.max(1) as usize];
let ch = (need.channels.max(1) as usize).min(MAX_EXPORT_AUDIO_CHANNELS);
let total_samples = ch.checked_mul(frames).ok_or_else(|| EngineError::Other("audio export buffer size overflow".into()))?;
if total_samples > MAX_EXPORT_AUDIO_SAMPLES {
return Err(EngineError::Other(format!("audio export buffer size {total_samples} samples exceeds maximum allowed ({MAX_EXPORT_AUDIO_SAMPLES})")));
}
let mut out: Vec<Vec<f32>> = vec![Vec::with_capacity(frames); ch];
let chunk = sample_rate as usize * 10;
let mut done = 0usize;
while done < frames {
+3 -2
View File
@@ -105,10 +105,11 @@ fn audio_rate_str(sr: u32) -> String {
fn parse_audio_rate(s: &str) -> Option<u32> {
let s = s.trim();
match s.strip_suffix('k') {
let r = match s.strip_suffix('k') {
Some(k) => k.parse::<f64>().ok().map(|v| (v * 1000.0).round() as u32),
None => s.parse().ok(),
}
};
r.filter(|&sr| sr > 0 && sr <= 384_000)
}
// ---------------------------------------------------------------------------------------------