Skip to content

Commit d92d5f6

Browse files
fix: reject wildcard E-RTMP media FourCC for on_media_cb authorization
Wildcard FourCC is only meaningful in capability negotiation objects. Publisher media tags that use '*' bypass codec-specific deny lists because on_media_cb receives a non-blocked label instead of a concrete codec identity. Treat wildcard media FourCC like unknown codec identity (None) so authorization policies cannot be circumvented. Co-authored-by: Alexander Wagner <info@alexanderwagnerdev.com>
1 parent c21193b commit d92d5f6

1 file changed

Lines changed: 51 additions & 5 deletions

File tree

src/session/conn.rs

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,7 @@ impl Conn {
770770
if auth_denied {
771771
return;
772772
}
773-
let track_codec = Some(fourcc_auth_label(&track.fourcc));
773+
let track_codec = media_fourcc_auth_label(&track.fourcc);
774774
if !self.media_allowed(frame_type, track_codec.as_deref()) {
775775
auth_denied = true;
776776
}
@@ -2283,6 +2283,22 @@ fn read_data_event_name(buf: &mut Buffer, is_string: bool, out: &mut [u8; 64]) -
22832283
}
22842284
}
22852285

2286+
/// Wildcard FourCC (`*`) is valid in E-RTMP capability objects but must not
2287+
/// appear on publisher media tags — it carries no concrete codec identity and
2288+
/// would let codec-specific `on_media_cb` deny lists be bypassed.
2289+
fn is_wildcard_media_fourcc(fourcc: &[u8; 4]) -> bool {
2290+
fourcc[0] == b'*'
2291+
}
2292+
2293+
/// Codec label for `on_media_cb`, or `None` when the wire FourCC cannot be
2294+
/// authorized (wildcard / unknown-enhanced identity).
2295+
fn media_fourcc_auth_label(fourcc: &[u8; 4]) -> Option<String> {
2296+
if is_wildcard_media_fourcc(fourcc) {
2297+
return None;
2298+
}
2299+
Some(fourcc_auth_label(fourcc))
2300+
}
2301+
22862302
/// Stable codec label for `on_media_cb` authorization. Valid UTF-8 FourCCs are
22872303
/// passed through; non-UTF-8 bytes are hex-encoded so deny-list policies can
22882304
/// still observe the identity instead of receiving `None`.
@@ -2298,7 +2314,7 @@ fn fourcc_auth_label(fourcc: &[u8; 4]) -> String {
22982314

22992315
fn detect_video_codec(payload: &[u8]) -> Option<String> {
23002316
if let Some(cc) = first_track_fourcc(FrameType::Video, payload) {
2301-
return Some(fourcc_auth_label(&cc));
2317+
return media_fourcc_auth_label(&cc);
23022318
}
23032319
let mut hdr = VideoHeader::default();
23042320
if crate::ertmp::exvideo::exvideo_parse(payload, &mut hdr).is_err() {
@@ -2307,7 +2323,7 @@ fn detect_video_codec(payload: &[u8]) -> Option<String> {
23072323
if hdr.is_ex_header != 0 {
23082324
let mut fourcc = [0u8; 4];
23092325
fourcc.copy_from_slice(&hdr.fourcc[..4]);
2310-
Some(fourcc_auth_label(&fourcc))
2326+
return media_fourcc_auth_label(&fourcc);
23112327
} else {
23122328
match payload[0] & 0x0F {
23132329
7 => Some("avc1".to_string()),
@@ -2320,7 +2336,7 @@ fn detect_video_codec(payload: &[u8]) -> Option<String> {
23202336

23212337
fn detect_audio_codec(payload: &[u8]) -> Option<String> {
23222338
if let Some(cc) = first_track_fourcc(FrameType::Audio, payload) {
2323-
return Some(fourcc_auth_label(&cc));
2339+
return media_fourcc_auth_label(&cc);
23242340
}
23252341
let mut hdr = AudioHeader::default();
23262342
if crate::ertmp::exaudio::exaudio_parse(payload, &mut hdr).is_err() {
@@ -2329,7 +2345,7 @@ fn detect_audio_codec(payload: &[u8]) -> Option<String> {
23292345
if hdr.is_ex_header != 0 {
23302346
let mut fourcc = [0u8; 4];
23312347
fourcc.copy_from_slice(&hdr.fourcc[..4]);
2332-
Some(fourcc_auth_label(&fourcc))
2348+
return media_fourcc_auth_label(&fourcc);
23332349
} else {
23342350
match hdr.audio_codec {
23352351
AudioCodec::Aac => Some("mp4a".to_string()),
@@ -3751,6 +3767,36 @@ mod tests {
37513767
assert!(conn.pending_relay.is_empty());
37523768
}
37533769

3770+
#[test]
3771+
fn on_media_cb_deny_list_blocks_wildcard_enhanced_fourcc() {
3772+
fn deny_hevc_and_av1(_: u64, frame_type: FrameType, codec: Option<&str>) -> bool {
3773+
if frame_type == FrameType::Video {
3774+
return !matches!(codec, Some("hvc1") | Some("av01"));
3775+
}
3776+
true
3777+
}
3778+
3779+
let mut conn = Conn::new();
3780+
conn.relay_enabled = true;
3781+
conn.current_stream = Some(Box::new(Stream::new(1)));
3782+
conn.current_stream.as_mut().unwrap().is_publishing = true;
3783+
conn.on_media_cb = Some(deny_hevc_and_av1);
3784+
3785+
let hvc1 = vec![0x90, b'h', b'v', b'c', b'1', 0, 0, 0, 1, 0xAA];
3786+
assert_eq!(
3787+
conn.handle_media_frame(1, FrameType::Video, 0, &hvc1),
3788+
Err(ErrorCode::Auth)
3789+
);
3790+
3791+
let wildcard = vec![0x90, b'*', b' ', b' ', b' ', 0, 0, 0, 1, 0xAA];
3792+
assert_eq!(
3793+
conn.handle_media_frame(1, FrameType::Video, 0, &wildcard),
3794+
Err(ErrorCode::Auth),
3795+
"wildcard media FourCC must not bypass codec-specific deny lists"
3796+
);
3797+
assert!(conn.pending_relay.is_empty());
3798+
}
3799+
37543800
#[test]
37553801
fn evict_active_publish_route_clears_detected_codecs() {
37563802
let mut conn = Conn::new();

0 commit comments

Comments
 (0)