
바이너리 계측 및 퍼징을 통해 발견된 일부 버그
media-gen은 두 가지 미디어 파서 테스트 케이스를 만들기 위한 작고 의존성이 적은 Rust 명령줄 도구입니다. 기존의 유효한 미디어 파일을 제자리에서 편집하며, FFmpeg를 호출하거나 브라우저를 패치하거나 서버에 접속하거나 무언가를 업로드하지 않습니다.
WebM 케이스는 구형 one-buffer-delayed Vorbis 경로와 현재의 immediate-discard 경로 모두를 위한 브리지 레이아웃을 포함합니다. 체크인된 짧은 샘플은 Discord Desktop 1.0.9257 (Electron 42.11.1 / Chromium 148.0.7778.280) 및 Chrome 153.0.8010.48에서 검증되었습니다. M4A 케이스는 고정된 Discord/FFmpeg 빌드에 특화되어 있습니다. 다른 릴리스에서는 이 파일들을 거부하거나, 안전하게 처리하거나, 다르게 실패할 수 있습니다.
| 케이스 | 입력 | 영향을 받는 빌드에서의 효과 | 트리거 |
|---|---|---|---|
| WebM/Vorbis discard bridge | A_VORBIS 트랙이 있는 기존 WebM | 테스트된 두 discard 모드 모두에서 Chromium의 AudioDiscardHelper 릴리스 검사에서 렌더러가 0x80000003 (STATUS_BREAKPOINT)으로 종료됨 | 재생/디코드; 메타데이터 로딩만으로는 충분하지 않음 |
M4A constant stsz count | 기존 fast-start AAC/M4A 시드 | 메타데이터를 로딩하는 동안 렌더러가 약 6.6 GB (6.16 GiB)의 프라이빗 메모리를 일시적으로 할당함 | 오디오 요소가 preload="none"에서 메타데이터로 변경된 후의 메타데이터 로드 |
이들은 서비스 거부/자원 소비 테스트 케이스이며, 입증된 코드 실행 익스플로잇이 아닙니다. 격리되고 제한된 테스트 환경에서만 실행하십시오.
media-gen은 재현성 계층이며, 이 케이스들이 발견된 방식이 아닙니다. 어려운 부분은 대형 네이티브 Discord 실행 파일과 그에 번들된 미디어 라이브러리 내부에서 동작을 찾아내고 입증하는 것이었습니다. blare2는 정확한 런타임의 격리된 복사본을 재작성하고 좁은 프로브가 설치된 애플리케이션을 변경하지 않고 실행을 관찰할 수 있게 함으로써 이를 실용적으로 만들었습니다.
WebM 케이스의 경우, blare2의 시맨틱 프로브는 정확히 AudioDiscardHelper::ProcessBuffers 검사에서 멈추고 실패 상태를 기록했습니다: discarded_frames = 129 및 decoder_delay = 128. 그러한 관찰이 없었다면 STATUS_BREAKPOINT로 인한 렌더러 종료는 일반적인 릴리스 어서션을 식별할 뿐이며, 어떤 미디어 불변식이 실패했는지 또는 조작된 패딩이 실제로 그 지점에 도달했는지를 입증하지 못했을 것입니다.
M4A 케이스의 경우, 대규모 할당은 일시적이며 일반적인 프로세스 샘플이 채취되기 전에 사라질 수 있습니다. blare2의 정확한 런타임 커버리지와 표적화된 계측은 고빈도 프로세스 샘플링 및 디스어셈블리와 결합되어, 작은 파일이 MOV 샘플 테이블 빌더에 도달했고 선언된 카운트가 AVIndexEntry 및 타이밍 테이블 할당을 확장했음을 보여주었습니다. 이는 해당 문제를 일반적인 AAC 디코드 실패나 오해의 소지가 있는 파일 크기/OOM 상관관계와 분리시켰습니다. 광범위한 함수 진입 커버리지만으로는 충분하지 않았습니다: 낮은 카운트와 높은 카운트 파일은 동일한 함수들을 따랐지만, 그 할당 크기는 극단적으로 달랐습니다.
blare2는 컨테이너 분석, 소스 리뷰 또는 통제를 대체하지 않았으며, Discord의 프로덕션 업로드/CDN 경로가 이 바이트들을 보존한다는 것을 입증하지도 않았습니다. 그 가치는 런타임 귀속에 있었습니다: 의심스러운 파서 동작을 정확한 실패 상태와 방어 가능한 할당 설명을 갖춘 재현 가능하고 버전이 고정된 발견으로 바꾸어 주었습니다. 이러한 종류의 바이너리 계측이 없었다면 이 두 케이스를 찾는 것은 상당히 느리고 검증하기 훨씬 어려웠을 것입니다.
저장소 루트에서:
cargo build --release -p media-gen
바이너리는 target/release/media-gen (Windows에서는 media-gen.exe)입니다. 생성기 자체는 Rust와 Cargo.toml의 의존성만 필요로 합니다.
cargo run --release -p media-gen -- --help
cargo run --release -p media-gen -- webm --help
cargo run --release -p media-gen -- m4a --help
Matroska의 음수 DiscardPadding은 Vorbis front skip이 됩니다. Chromium의 구형 Vorbis 경로는 해당 메타데이터를 한 인코딩 패킷만큼 지연시키고, 현재 Chromium은 이를 현재 디코드된 출력에 적용하며 해당 프라이밍 패킷이 PCM을 출력하지 않을 때 패킷 0의 메타데이터를 버립니다. 패킷 0과 1에 걸쳐 하나의 큰 skip을 반복하면 두 동작을 연결합니다:
| 오디오 패킷 | 디코드된 PCM | Front skip |
|---|---|---|
| 0 | 없음 (Vorbis 프라이밍) | 577 프레임 |
| 1 | 576 프레임 | 577 프레임 |
트랙에는 128프레임 CodecDelay가 있습니다. 구형 지연 모드에서는 패킷 0의 577프레임 skip이 패킷 1에 적용됩니다. 현재 모드에서는 패킷 0의 skip이 버려지고 패킷 1의 동일한 skip이 직접 적용됩니다. 어느 쪽이든 디코더 지연 오프셋 이후에는 448프레임만 제거될 수 있으므로 129프레임이 패킷 2로 넘어갑니다. 이 패킷의 양수 front skip은 그 129프레임이 이미 제거된 후 Chromium의 릴리스 검사에 도달합니다. 필요한 불변식은 discarded_frames <= decoder_delay이며, 129 <= 128은 실패하고 렌더러를 종료시킵니다.
생성기는 Vorbis setup 헤더를 파싱하고, 패킷 1과 2의 디코드된 크기를 계산하며, 브리지가 실행 가능하지 않으면 fail closed합니다. 그런 다음:
SimpleBlock 요소를 BlockGroup 요소로 변환합니다;DiscardPadding을, 패킷 2에 양수 1프레임 트리거를 기록합니다;SeekHead, Cues 및 영향을 받은 클러스터 CRC 요소를 교체합니다.매니페스트는 immediate 및 delayed 경로를 별도로 보고합니다. 체크인된 샘플의 경우, 두 경로 모두 패킷 2를 검사 패킷으로 지목하고 expected_carry_frames: 129 및 expected_check_fails: true를 보고합니다.
저장소에는 43 ms, 4,185바이트의 제어 WebM이 포함되어 있습니다:
cargo run --release -- webm \
--input samples/short-vorbis-dual-control.webm \
--output .build/short-vorbis-dual-crash.webm \
--manifest .build/short-vorbis-dual-crash.json \
--force
입력에는 다음이 포함되어야 합니다:
A_VORBIS 트랙;CodecDelay (일반적으로 트랙에서 읽음); 그리고유용한 옵션은 --trigger-skip, --codec-delay, --sample-rate입니다. --second-skip은 이름이 변경된 --trigger-skip 옵션의 별칭으로 남아 있습니다. 기본값은 알려진 실패 상태에 필요한 값입니다. 이 명령은 JSON 보고서를 stdout에 출력하고, 해당 옵션이 제공되면 동일한 보고서를 --manifest에 기록합니다.
체크인된 후보는 43 ms, 4,206바이트입니다. SHA-256은:
a0ab9e146c629f037b86612addc1ab6fff9200711d45aa5f5edbb9576cc206ac
입력, 패킷 윈도우 또는 옵션이 변경되면 출력 해시도 변경될 것으로 예상됩니다.
M4A 케이스는 인코딩된 AAC 페이로드가 아니라 유효한 MP4 샘플 테이블 형태를 악용합니다. 생성기는 시드를 다음과 같이 변경합니다:
stsz 샘플 크기 배열을 sample_size = 1로 교체합니다.stsz, 첫 번째 stsc run, 첫 번째 stts run의 선언된 샘플 카운트를 동일한 큰 값으로 설정합니다.mdat 내부를 가리키도록 절대 stco 오프셋을 복구합니다.고정된 FFmpeg 디먹서는 인덱스 및 타이밍 테이블을 구축하는 동안 선언된 카운트를 권위 있는 것으로 취급합니다. 관련 구조체는 AVIndexEntry에 대해 선언된 샘플당 약 24바이트, 타이밍 데이터에 대해 샘플당 12바이트를 사용합니다: 카운트 항목당 총 36바이트입니다. 테스트된 빌드에서 허용되는 최대 카운트는 178,956,969 (0x0AAAAAA9)이며, 이는 다음과 같이 예상됩니다:
178,956,969 * 24 = 4,294,967,256 bytes
178,956,969 * 12 = 2,147,483,628 bytes
combined = 6,442,450,884 bytes
정확한 Discord 렌더러는 메타데이터를 로딩하는 동안 6,614,761,472바이트의 프라이빗 메모리 피크에 도달했습니다. 인접한 카운트 178,956,970은 고정된 FFmpeg 경계에 의해 거부되었고 정상에 가까운 메모리를 유지했습니다. 이는 통제되지 않은 자원 소비(CWE-400)이며, 관찰된 정수 랩, 음수 크기 할당 또는 범위를 벗어난 쓰기가 아닙니다. 실제 AAC 페이로드는 잘릴 수 있으며, 큰 할당에 도달하는 데 성공적인 재생은 필요하지 않습니다.
생성기는 AAC 인코더를 내장하는 대신 의도적으로 시드를 받습니다. 하나의 명시적 stsz 테이블, 하나의 stsc run, 하나 이상의 stts 항목, 그리고 stco 청크 오프셋을 가진 fast-start AAC/M4A 파일을 사용하십시오. 필요한 구조가 없으면 fail closed합니다.
cargo run --release -p media-gen -- m4a \
--input path/to/base-faststart.m4a \
--output .build/media-gen/constant-stsz.m4a \
--manifest .build/media-gen/constant-stsz.json \
--force
기본 --sample-count는 178956969이며, 고정된 빌드에서 허용되는 최대값입니다. 저메모리 스모크 테스트에는 더 작은 값을 사용하십시오. 예를 들어:
cargo run --release -p media-gen -- m4a \
--input path/to/base-faststart.m4a \
--output .build/media-gen/constant-stsz-32000000.m4a \
--sample-count 32000000 \
--force
--sample-count 178956970은 유용한 인접 거부 제어이며, 트리거 값이 아닙니다. 선택적 --moov-at-end 플래그는 moov를 mdat 뒤로 이동시키고 stco/co64를 업데이트합니다; 물리적 AAC 바이트가 메타데이터보다 먼저 나오는 파일을 테스트할 때 유용하지만, 할당 메커니즘에 필수는 아닙니다.
해당 레이아웃을 명시적으로 생성하려면:
cargo run --release -p media-gen -- m4a \
--input path/to/base-faststart.m4a \
--output .build/media-gen/constant-stsz-moov-end.m4a \
--moov-at-end \
--force
일치하는 아티팩트는
samples/short-vorbis-dual-control.webm,
samples/short-vorbis-dual-crash.webm,
그리고 samples/short-vorbis-dual-crash.json입니다.
| 2 | 1,024 프레임 | 1 프레임 |