fix(audit): address C2, C3, M4, M5 from 2026-05-25 audit
C2: Add EncryptingTransport wrapper — all media I/O now goes through ChaChaSession encrypt/decrypt before hitting the QUIC datagram path. cli.rs run_live/run_silence/run_file_mode accept Arc<dyn MediaTransport> and receive a wrapped transport after the handshake. C3: Wire VideoScorer::observe() into both plain and trunked forwarding loops in room.rs. Packets from participants with Abusive verdict are dropped before forwarding. last_bwe_kbps tracked from quality reports. M4: Widen FEC repair symbol index from u8 to u16 throughout (FecEncoder::generate_repair, FecDecoder::add_symbol, all call sites in call.rs, bench.rs, pipeline.rs, wzp-android). Eliminates theoretical wrapping when num_source + repair_count > 255. M5: Track last_encrypt_timestamp in ChaChaSession. debug_assert in encrypt() that timestamp is non-decreasing across calls (including post- rekey). complete_rekey() explicitly preserves last_encrypt_timestamp to prevent accidental timestamp reset regressions. 583 tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,8 @@ pub struct ChaChaSession {
|
||||
sas_code: Option<u32>,
|
||||
/// Per-stream anti-replay windows, keyed by (stream_id, media_type).
|
||||
anti_replay: HashMap<(u8, MediaType), AntiReplayWindow>,
|
||||
/// Last timestamp seen in encrypt() — used to assert monotonicity across rekeys.
|
||||
last_encrypt_timestamp: Option<u32>,
|
||||
}
|
||||
|
||||
impl ChaChaSession {
|
||||
@@ -55,6 +57,7 @@ impl ChaChaSession {
|
||||
pending_rekey_secret: None,
|
||||
sas_code: None,
|
||||
anti_replay: HashMap::new(),
|
||||
last_encrypt_timestamp: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +125,18 @@ impl CryptoSession for ChaChaSession {
|
||||
|
||||
out.extend_from_slice(&ciphertext);
|
||||
self.send_seq = self.send_seq.wrapping_add(1); // packet counter for rekey trigger only
|
||||
|
||||
// M5: assert timestamp_ms is non-decreasing across calls (including post-rekey).
|
||||
// Timestamps are u32 and wrap at 2^32 ms (~49 days); allow wrapping.
|
||||
debug_assert!(
|
||||
self.last_encrypt_timestamp
|
||||
.map_or(true, |last| header.timestamp.wrapping_sub(last) < u32::MAX / 2),
|
||||
"encrypt: timestamp must not decrease (last={:?}, now={})",
|
||||
self.last_encrypt_timestamp,
|
||||
header.timestamp,
|
||||
);
|
||||
self.last_encrypt_timestamp = Some(header.timestamp);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -189,7 +204,9 @@ impl CryptoSession for ChaChaSession {
|
||||
.perform_rekey(peer_ephemeral_pub, secret, total_packets);
|
||||
self.install_key(new_key);
|
||||
|
||||
// Reset sequence counters after rekey for nonce uniqueness
|
||||
// Reset sequence counters after rekey for nonce uniqueness.
|
||||
// last_encrypt_timestamp is intentionally NOT reset — spec requires
|
||||
// timestamp_ms to be monotonic across rekeys.
|
||||
self.send_seq = 0;
|
||||
self.recv_seq = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user