From 1a74d5515be0e8071ef09355ddc8d148ce97907f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 4 Sep 2026 02:10:24 +0000 Subject: [PATCH 1/2] fix(security): close auth bypass gaps for connect/SO callbacks and audio codec masquerade - Treat on_connect_cb and on_shared_object_cb as implicit auth hooks so publish/play require explicit on_publish_cb/on_play_cb when configured - Reject ambiguous enhanced-audio headers (IsExHeader bit with printable but unrecognized FourCC) instead of falling back to legacy SoundFormat - Add regression tests for all three paths Co-authored-by: Alexander Wagner --- src/session/conn.rs | 133 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/src/session/conn.rs b/src/session/conn.rs index 7f68534..24fbf30 100644 --- a/src/session/conn.rs +++ b/src/session/conn.rs @@ -1587,6 +1587,8 @@ impl Conn { self.on_play_cb.is_some() || self.on_media_cb.is_some() || self.on_frame_cb.is_some() + || self.on_connect_cb.is_some() + || self.on_shared_object_cb.is_some() || self.on_shared_object_auth_cb.is_some() || self.on_release_stream_cb.is_some() } @@ -1595,6 +1597,8 @@ impl Conn { self.on_publish_cb.is_some() || self.on_media_cb.is_some() || self.on_frame_cb.is_some() + || self.on_connect_cb.is_some() + || self.on_shared_object_cb.is_some() || self.on_shared_object_auth_cb.is_some() || self.on_release_stream_cb.is_some() } @@ -2437,6 +2441,15 @@ fn detect_video_codec(payload: &[u8]) -> Option { } } +/// True when bytes 1..5 look like a deliberate enhanced-audio FourCC slot +/// (printable ASCII) rather than legacy codec payload continuation bytes. +fn looks_like_enhanced_audio_fourcc_slot(payload: &[u8]) -> bool { + payload.len() >= 5 + && payload[1..5] + .iter() + .all(|b| b.is_ascii_graphic() || *b == b' ') +} + fn detect_audio_codec(payload: &[u8]) -> Option { if let Some(cc) = first_track_fourcc(FrameType::Audio, payload) { return media_fourcc_auth_label(&cc); @@ -2445,6 +2458,14 @@ fn detect_audio_codec(payload: &[u8]) -> Option { if crate::ertmp::exaudio::exaudio_parse(payload, &mut hdr).is_err() { return None; } + // IsExHeader bit set with a printable FourCC-shaped slot but no recognized + // enhanced codec is an ambiguous enhanced header, not a legacy SoundFormat. + if payload[0] & 0x80 != 0 + && hdr.is_ex_header == 0 + && looks_like_enhanced_audio_fourcc_slot(payload) + { + return None; + } if hdr.is_ex_header != 0 { if hdr.packet_type == ERTMP_PACKET_TYPE_MODEX { return None; @@ -2922,6 +2943,86 @@ mod tests { ); } + #[test] + fn publish_rejects_connect_only_connections_when_publish_cb_missing() { + let mut conn = Conn::new(); + conn.app = "live".to_string(); + conn.current_stream = Some(Box::new(Stream::new(1))); + conn.on_connect_cb = Some(|| {}); + + let mut publish = Buffer::with_capacity(128); + command::build_publish(&mut publish, "inject", "live").unwrap(); + conn.handle_command(publish.as_slice()).unwrap(); + assert!( + !conn.current_stream.as_ref().unwrap().is_publishing, + "connect-observing servers must not accept publish without on_publish_cb" + ); + assert!( + !conn.relay_enabled, + "relay must stay disabled when publish is rejected on a connect-only server" + ); + } + + #[test] + fn play_rejects_connect_only_connections_when_play_cb_missing() { + let mut conn = Conn::new(); + conn.app = "live".to_string(); + conn.current_stream = Some(Box::new(Stream::new(1))); + conn.on_connect_cb = Some(|| {}); + + let mut play = Buffer::with_capacity(128); + command::build_play(&mut play, "viewer").unwrap(); + conn.handle_command(play.as_slice()).unwrap(); + assert!( + !conn.current_stream.as_ref().unwrap().is_playing, + "connect-observing servers must not accept play without on_play_cb" + ); + assert!( + !conn.relay_enabled, + "relay must stay disabled when play is rejected on a connect-only server" + ); + } + + #[test] + fn publish_rejects_shared_object_delivery_only_connections_when_publish_cb_missing() { + let mut conn = Conn::new(); + conn.app = "live".to_string(); + conn.current_stream = Some(Box::new(Stream::new(1))); + conn.on_shared_object_cb = Some(|_, _| {}); + + let mut publish = Buffer::with_capacity(128); + command::build_publish(&mut publish, "inject", "live").unwrap(); + conn.handle_command(publish.as_slice()).unwrap(); + assert!( + !conn.current_stream.as_ref().unwrap().is_publishing, + "shared-object-delivery servers must not accept publish without on_publish_cb" + ); + assert!( + !conn.relay_enabled, + "relay must stay disabled when publish is rejected on a shared-object-delivery server" + ); + } + + #[test] + fn play_rejects_shared_object_delivery_only_connections_when_play_cb_missing() { + let mut conn = Conn::new(); + conn.app = "live".to_string(); + conn.current_stream = Some(Box::new(Stream::new(1))); + conn.on_shared_object_cb = Some(|_, _| {}); + + let mut play = Buffer::with_capacity(128); + command::build_play(&mut play, "viewer").unwrap(); + conn.handle_command(play.as_slice()).unwrap(); + assert!( + !conn.current_stream.as_ref().unwrap().is_playing, + "shared-object-delivery servers must not accept play without on_play_cb" + ); + assert!( + !conn.relay_enabled, + "relay must stay disabled when play is rejected on a shared-object-delivery server" + ); + } + #[test] fn defer_media_relay_keeps_relay_disabled_without_publish_cb() { let mut conn = Conn::new(); @@ -4162,6 +4263,38 @@ mod tests { assert!(conn.pending_relay.is_empty()); } + #[test] + fn on_media_cb_deny_list_blocks_enhanced_audio_masquerading_as_legacy_g711u() { + fn deny_opus_allow_g711u(_: u64, frame_type: FrameType, codec: Option<&str>) -> bool { + if frame_type == FrameType::Audio { + return codec != Some("Opus") && codec != Some("legacy:G711U"); + } + true + } + + let mut conn = Conn::new(); + conn.relay_enabled = true; + conn.current_stream = Some(Box::new(Stream::new(1))); + conn.current_stream.as_mut().unwrap().is_publishing = true; + conn.on_media_cb = Some(deny_opus_allow_g711u); + + let real_opus = vec![0x90, b'O', b'p', b'u', b's', 0, 0, 0, 0xAA]; + assert_eq!( + conn.handle_media_frame(1, FrameType::Audio, 0, &real_opus), + Err(ErrorCode::Auth) + ); + + // IsExHeader bit set with an unrecognized printable FourCC slot must + // not fall back to legacy SoundFormat 8 (G711U) for authorization. + let masqueraded = vec![0x80, b'x', b'x', b'x', b'x', 0xBB, 0xCC]; + assert_eq!( + conn.handle_media_frame(1, FrameType::Audio, 0, &masqueraded), + Err(ErrorCode::Auth), + "enhanced audio with invalid FourCC must not masquerade as legacy:G711U" + ); + assert!(conn.pending_relay.is_empty()); + } + #[test] fn evict_active_publish_route_clears_detected_codecs() { let mut conn = Conn::new(); From 7b821cea35e6fd8e73989458699ac0a5eecc0100 Mon Sep 17 00:00:00 2001 From: Alexander Wagner Date: Sat, 5 Sep 2026 00:07:03 +0200 Subject: [PATCH 2/2] fix(review): revert invalid auth and audio heuristics Restore the pre-PR conn.rs behavior after review found that observer callbacks are not authorization gates and printable legacy audio payload bytes cannot safely distinguish enhanced FourCC headers. --- src/session/conn.rs | 133 -------------------------------------------- 1 file changed, 133 deletions(-) diff --git a/src/session/conn.rs b/src/session/conn.rs index 24fbf30..7f68534 100644 --- a/src/session/conn.rs +++ b/src/session/conn.rs @@ -1587,8 +1587,6 @@ impl Conn { self.on_play_cb.is_some() || self.on_media_cb.is_some() || self.on_frame_cb.is_some() - || self.on_connect_cb.is_some() - || self.on_shared_object_cb.is_some() || self.on_shared_object_auth_cb.is_some() || self.on_release_stream_cb.is_some() } @@ -1597,8 +1595,6 @@ impl Conn { self.on_publish_cb.is_some() || self.on_media_cb.is_some() || self.on_frame_cb.is_some() - || self.on_connect_cb.is_some() - || self.on_shared_object_cb.is_some() || self.on_shared_object_auth_cb.is_some() || self.on_release_stream_cb.is_some() } @@ -2441,15 +2437,6 @@ fn detect_video_codec(payload: &[u8]) -> Option { } } -/// True when bytes 1..5 look like a deliberate enhanced-audio FourCC slot -/// (printable ASCII) rather than legacy codec payload continuation bytes. -fn looks_like_enhanced_audio_fourcc_slot(payload: &[u8]) -> bool { - payload.len() >= 5 - && payload[1..5] - .iter() - .all(|b| b.is_ascii_graphic() || *b == b' ') -} - fn detect_audio_codec(payload: &[u8]) -> Option { if let Some(cc) = first_track_fourcc(FrameType::Audio, payload) { return media_fourcc_auth_label(&cc); @@ -2458,14 +2445,6 @@ fn detect_audio_codec(payload: &[u8]) -> Option { if crate::ertmp::exaudio::exaudio_parse(payload, &mut hdr).is_err() { return None; } - // IsExHeader bit set with a printable FourCC-shaped slot but no recognized - // enhanced codec is an ambiguous enhanced header, not a legacy SoundFormat. - if payload[0] & 0x80 != 0 - && hdr.is_ex_header == 0 - && looks_like_enhanced_audio_fourcc_slot(payload) - { - return None; - } if hdr.is_ex_header != 0 { if hdr.packet_type == ERTMP_PACKET_TYPE_MODEX { return None; @@ -2943,86 +2922,6 @@ mod tests { ); } - #[test] - fn publish_rejects_connect_only_connections_when_publish_cb_missing() { - let mut conn = Conn::new(); - conn.app = "live".to_string(); - conn.current_stream = Some(Box::new(Stream::new(1))); - conn.on_connect_cb = Some(|| {}); - - let mut publish = Buffer::with_capacity(128); - command::build_publish(&mut publish, "inject", "live").unwrap(); - conn.handle_command(publish.as_slice()).unwrap(); - assert!( - !conn.current_stream.as_ref().unwrap().is_publishing, - "connect-observing servers must not accept publish without on_publish_cb" - ); - assert!( - !conn.relay_enabled, - "relay must stay disabled when publish is rejected on a connect-only server" - ); - } - - #[test] - fn play_rejects_connect_only_connections_when_play_cb_missing() { - let mut conn = Conn::new(); - conn.app = "live".to_string(); - conn.current_stream = Some(Box::new(Stream::new(1))); - conn.on_connect_cb = Some(|| {}); - - let mut play = Buffer::with_capacity(128); - command::build_play(&mut play, "viewer").unwrap(); - conn.handle_command(play.as_slice()).unwrap(); - assert!( - !conn.current_stream.as_ref().unwrap().is_playing, - "connect-observing servers must not accept play without on_play_cb" - ); - assert!( - !conn.relay_enabled, - "relay must stay disabled when play is rejected on a connect-only server" - ); - } - - #[test] - fn publish_rejects_shared_object_delivery_only_connections_when_publish_cb_missing() { - let mut conn = Conn::new(); - conn.app = "live".to_string(); - conn.current_stream = Some(Box::new(Stream::new(1))); - conn.on_shared_object_cb = Some(|_, _| {}); - - let mut publish = Buffer::with_capacity(128); - command::build_publish(&mut publish, "inject", "live").unwrap(); - conn.handle_command(publish.as_slice()).unwrap(); - assert!( - !conn.current_stream.as_ref().unwrap().is_publishing, - "shared-object-delivery servers must not accept publish without on_publish_cb" - ); - assert!( - !conn.relay_enabled, - "relay must stay disabled when publish is rejected on a shared-object-delivery server" - ); - } - - #[test] - fn play_rejects_shared_object_delivery_only_connections_when_play_cb_missing() { - let mut conn = Conn::new(); - conn.app = "live".to_string(); - conn.current_stream = Some(Box::new(Stream::new(1))); - conn.on_shared_object_cb = Some(|_, _| {}); - - let mut play = Buffer::with_capacity(128); - command::build_play(&mut play, "viewer").unwrap(); - conn.handle_command(play.as_slice()).unwrap(); - assert!( - !conn.current_stream.as_ref().unwrap().is_playing, - "shared-object-delivery servers must not accept play without on_play_cb" - ); - assert!( - !conn.relay_enabled, - "relay must stay disabled when play is rejected on a shared-object-delivery server" - ); - } - #[test] fn defer_media_relay_keeps_relay_disabled_without_publish_cb() { let mut conn = Conn::new(); @@ -4263,38 +4162,6 @@ mod tests { assert!(conn.pending_relay.is_empty()); } - #[test] - fn on_media_cb_deny_list_blocks_enhanced_audio_masquerading_as_legacy_g711u() { - fn deny_opus_allow_g711u(_: u64, frame_type: FrameType, codec: Option<&str>) -> bool { - if frame_type == FrameType::Audio { - return codec != Some("Opus") && codec != Some("legacy:G711U"); - } - true - } - - let mut conn = Conn::new(); - conn.relay_enabled = true; - conn.current_stream = Some(Box::new(Stream::new(1))); - conn.current_stream.as_mut().unwrap().is_publishing = true; - conn.on_media_cb = Some(deny_opus_allow_g711u); - - let real_opus = vec![0x90, b'O', b'p', b'u', b's', 0, 0, 0, 0xAA]; - assert_eq!( - conn.handle_media_frame(1, FrameType::Audio, 0, &real_opus), - Err(ErrorCode::Auth) - ); - - // IsExHeader bit set with an unrecognized printable FourCC slot must - // not fall back to legacy SoundFormat 8 (G711U) for authorization. - let masqueraded = vec![0x80, b'x', b'x', b'x', b'x', 0xBB, 0xCC]; - assert_eq!( - conn.handle_media_frame(1, FrameType::Audio, 0, &masqueraded), - Err(ErrorCode::Auth), - "enhanced audio with invalid FourCC must not masquerade as legacy:G711U" - ); - assert!(conn.pending_relay.is_empty()); - } - #[test] fn evict_active_publish_route_clears_detected_codecs() { let mut conn = Conn::new();