Skip to content

fix(hls-pull): cap segment + playlist body size so one upstream can't OOM the host#17

Merged
ntt0601zcoder merged 1 commit into
mainfrom
fix/hls-segment-cap
Jun 13, 2026
Merged

fix(hls-pull): cap segment + playlist body size so one upstream can't OOM the host#17
ntt0601zcoder merged 1 commit into
mainfrom
fix/hls-segment-cap

Conversation

@ntt0601zcoder

Copy link
Copy Markdown
Owner

Problem (audit A-7, HIGH)

fetchSegmentOnce read the segment body with io.ReadAll(resp.Body) and no bound, and parseM3U8 scanned the playlist body with only a 256 KB per-line cap. A pull HLS source — or any host its segment URI / a 3xx redirect points to (combines with the SSRF vector S-4) — that returns a multi-GB body forced unbounded allocation; the only limit was the 60 s segment timeout (~7.5 GB at 1 Gbps). One malicious / compromised / redirected upstream could OOM-kill the shared process and take every stream on the host down, with no config write access.

Fix

  • readCapped(r, max): reads at most max+1 bytes through an io.LimitReader and returns errBodyTooLarge when exceeded — so an oversized body is rejected without allocating it (the OOM the cap exists to prevent).
  • fetchSegmentOnce uses readCapped(resp.Body, hlsMaxSegmentBytes) (64 MiB default); fetchSegmentWithRetry already treats the error as a failed → skipped segment, so one bad segment doesn't kill the stream.
  • parseM3U8 wraps its body in io.LimitReader(body, hlsMaxPlaylistBytes) (8 MiB), bounding the scanner input and transitively the parsed segment/variant slice.
  • Both caps are package vars so ops can tune them and tests can shrink them.

Test

TestReadCapped (under / at / over cap; a counting reader proves ≤ cap+1 bytes are consumed), TestFetchSegmentOnce_RejectsOversizedSegment (httptest server + shrunk cap → errBodyTooLarge; under-cap succeeds), TestParseM3U8_PlaylistBodyCapped.

go test -race ./internal/ingestor/pull/ green for the new tests, golangci-lint 0 issues, full go build ./... green. (The pre-existing TestRTMPReader_PullsFromMediaMTX integration test requires a local MediaMTX/Docker/ffmpeg and is unrelated to this change.)

… OOM the host

fetchSegmentOnce read the segment body with io.ReadAll and no bound, and the
M3U8 parser scanned the playlist body with only a 256 KB per-line cap. A pull
HLS source — or any host its segment URI / a 3xx redirect points to (combines
with the SSRF vector) — that returns a multi-GB body forced unbounded
allocation; the only limit was the 60 s segment timeout (~7.5 GB at 1 Gbps).
One malicious/compromised/redirected upstream could OOM-kill the shared
process and take every stream on the host down, with no config write access.

Add response-body caps. readCapped reads at most cap+1 bytes through an
io.LimitReader and returns errBodyTooLarge when exceeded, so an oversized body
is rejected WITHOUT allocating it; fetchSegmentOnce uses it with a 64 MiB
default and fetchSegmentWithRetry treats over-limit as a failed (then skipped)
segment rather than killing the stream. parseM3U8 wraps its body in an
io.LimitReader (8 MiB), which bounds the scanner input and transitively the
parsed segment/variant slice. Both caps are package vars so ops can tune and
tests can shrink them.

Tests: TestReadCapped (under/at/over cap; a counting reader proves no more than
cap+1 bytes are consumed), TestFetchSegmentOnce_RejectsOversizedSegment
(httptest with a shrunk cap), TestParseM3U8_PlaylistBodyCapped.
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 77.77778% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/ingestor/pull/hls.go 77.77% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@ntt0601zcoder ntt0601zcoder merged commit e86f399 into main Jun 13, 2026
4 checks passed
@ntt0601zcoder ntt0601zcoder deleted the fix/hls-segment-cap branch June 13, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants