Skip to content

[mpegts] Validate PSI section CRC32 before parsing PAT/PMT#2084

Open
wmdebrito wants to merge 1 commit into
xbmc:Piersfrom
wmdebrito:fix/mpegts-validate-psi-crc
Open

[mpegts] Validate PSI section CRC32 before parsing PAT/PMT#2084
wmdebrito wants to merge 1 commit into
xbmc:Piersfrom
wmdebrito:fix/mpegts-validate-psi-crc

Conversation

@wmdebrito

@wmdebrito wmdebrito commented Jun 27, 2026

Copy link
Copy Markdown

Description

The demuxer skipped the CRC32 of every PSI section (it only did end_psi -= 4 to drop it) and never verified it. Combined with accepting PAT/PMT on any PID (59fd1b3), this means random TS payload whose bytes happen to start with a PSI-like table_id (0x00/0x02) is misparsed as a PAT/PMT: it spuriously (de)registers streams, forces a program change, corrupts the demux state and can crash on the next TSResync() (SIGSEGV in AP4_ByteStream::Read on the async TS reader thread, at HLS segment boundaries on discontinuous live streams).

Validate the section CRC32 once the section is fully assembled, before acting on it. The 3-byte section header (table_id + syntax/length word) is reconstructed, since only the section body is kept in the table buffer.

This keeps the 59fd1b3 behaviour intact: a real PAT/PMT carried on a non-standard PID has a valid CRC and is still accepted (issue #2010, whose sample has no PAT and only a PMT on PID 0x1001). Only sections that fail the CRC -- i.e. payload that was never a PSI section -- are dropped.

Motivation and context

Playback PlutoTv and other malformed streams on IA 21.5.21

How has this been tested?

A custom version with those changes was built and installed on Kodi.

The same stream that was failing after a few minutes, every time, now run for more than 24h without crashing. Same thing was happening with PlutoTv when ads were playing, it crashed every time, but now works fine.

Verified with a standalone harness driving the unmodified demuxer over the issue #2010 sample and synthetic streams (AddressSanitizer/UBSan clean):

Screenshots (if appropriate):

Types of change

  • Bug fix (non-breaking change which fixes an issue)
  • Clean up (non-breaking change which removes non-working, unmaintained functionality)
  • Improvement (non-breaking change which improves existing functionality)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that will cause existing functionality to change)
  • Cosmetic change (non-breaking change that doesn't touch code)
  • None of the above (please explain below)

Checklist:

  • I have read the Contributing document
  • My code follows the Code Guidelines of this project
  • My change requires a change to the Wiki documentation
  • I have updated the documentation accordingly

The demuxer skipped the CRC32 of every PSI section (it only did `end_psi -= 4`
to drop it) and never verified it. Combined with accepting PAT/PMT on any PID
(59fd1b3), this means random TS payload whose bytes happen to start with a
PSI-like table_id (0x00/0x02) is misparsed as a PAT/PMT: it spuriously
(de)registers streams, forces a program change, corrupts the demux state and can
crash on the next TSResync() (SIGSEGV in AP4_ByteStream::Read on the async TS
reader thread, at HLS segment boundaries on discontinuous live streams).

Validate the section CRC32 once the section is fully assembled, before acting on
it. The 3-byte section header (table_id + syntax/length word) is reconstructed,
since only the section body is kept in the table buffer.

This keeps the 59fd1b3 behaviour intact: a real PAT/PMT carried on a
non-standard PID has a valid CRC and is still accepted (issue xbmc#2010, whose
sample has no PAT and only a PMT on PID 0x1001). Only sections that fail the
CRC -- i.e. payload that was never a PSI section -- are dropped.

Verified with a standalone harness driving the unmodified demuxer over the
issue xbmc#2010 sample and synthetic streams (AddressSanitizer/UBSan clean):
- xbmc#2010 (no PAT, PMT on 0x1001, valid CRC): h264+aac discovered, before & after.
- valid PMT on an arbitrary PID: still discovered.
- corrupt PMT (wrong CRC): accepted by baseline (spurious program change + bogus
  stream), correctly ignored after the fix.

Signed-off-by: Wagner Brito <wagnerbrito@gmail.com>
@wmdebrito

Copy link
Copy Markdown
Author

This was tested manually for both scenarios that were failing. Part of the code was written by AI.

@kodiai

kodiai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Decision: APPROVE

Issues: none

Evidence:

  • Review prompt covered 1 changed file.
Review Details
  • Review plan: ready hash=3d9077a8cc46 route=standard task=review.full files=1 lines=44(local-diff) budget=na/755s gates=3/3 publish=canonical-visible-surface graph=skipped candidates=preferred doctrine=disabled/0/0/0 reasons=disabled

  • Review reducer: ready input=1 kept=1 suppressed=0 rewritten=0 deprioritized=0 lowConfidence=0 auditEvents=0 severityDemoted=0 graphValidated=0 graphUncertain=0 doctrine=disabled/0/0/0 reasons=disabled

  • Review candidates: shadow recorded=1 rejected=0 errors=0 artifact=present repo=xbmc-inputstream.adaptive pr=2084 key=kodiai-review-output:v1:inst-109141824:xbmc-inputstream.adaptive:pr-2084:action- delivery=4916c950-727b-11f1-8e77-e1b587b1f09b

  • Review candidate publication: mode=blocked approved=1 rewritten=0 publishable=0 nonPublishable=1 fixBlocked=1 published=0 directFallback=0 reasons=fix-eligibility-blocked movedToDetails=0 detailsOmitted=0 buckets=blocked:1:fix-eligibility-blocked+missing-replacement

  • M072 candidate publication bridge: status=denied; bridgeVersion=candidate-publication-bridge.v1; bridgeId=candidate-publication-record:86bf5f50d30c65fb38016181bcffe72a; recordKey=candidate-publication-record:86bf5f50d30c65fb38016181bcffe72a; correlationKey=candidate-publication-bridge:74958d132f7b0d6e5ce5e848a583020b; source=review-handler-publication; candidateRef=candidate-publication-summary-91c44f07; verification=none; counts=candidateCount:0,evidenceCount:0,verifiedCount:0,partiallyVerifiedCount:0,unverifiedCount:0,disprovenCount:0,publicationEligibleCount:0,malformedRecordCount:0,unsafeInputFieldCount:0; reasons=no-evidence,publication-ineligible; malformed=none; presence=deliveryId:y,reviewOutputKey:y,upstreamCorrelationKey:y,policyCorrelationKey:y; handoffOwner=available; redaction=privateOnly:y,rawPayloads:n,publicationFields:n,evidencePayloads:n,githubCommentBody:n,reducerRawPayload:n,discardedRawPayload:n,discardedPublicationFields:n,discardedEvidencePayloads:n

  • Review finding lifecycle: status=normalized; counts=input:1,recorded:1,rejected:0,unsafeInputFields:0; correlation=repo:y,pull:y,reviewOutputKey:y,deliveryId:y,commit:y; statuses=detected:1,open:1,suggested:0,validated:0,revalidated:0,resolved:0,blocked:0,degraded:0; severity=critical:0,major:0,medium:0,minor:1; actionability=actionable:0,needs-human-review:1,needs-reproduction:0,blocked:0,not-actionable:0; reasons=automatic-detected,automatic-open,automatic-review; rejected=none; redaction=privateOnly:y,rawPrompts:n,rawModelOutput:n,candidateBodies:n,toolPayloads:n,secretLike:n,diffs:n,unboundedArrays:n,unsafeFields:0

  • Review validation truth: status=normalized; counts=detected:1,suggested:0,validated:0,revalidated:0,resolved:0,blocked:0,degraded:0,open:1,uncertain:0,inputFindings:1,unsafeInputFields:0; evidence=fresh:0,stale:0,missingValidation:1,missingRevalidation:1; reasons=validation-missing:1; refs=rfl-05f2c7c640e48c83:open:validation-missing:fix:n:validation:n:revalidation:n; correlation=reviewOutputKey:y,deliveryId:y; redaction=privateOnly:y,rawPrompts:n,rawModelOutput:n,candidateBodies:n,replacementText:n,toolPayloads:n,secretLike:n,diffs:n,unboundedArrays:n,unsafeFields:0

  • Files reviewed: 1

  • Findings: 0 critical, 0 major, 0 medium, 0 minor

  • Lines changed: +44 -0

  • Profile: strict (auto, lines changed: 44)

  • Contributor experience: coarse-fallback (using coarse fallback signals only)

  • Shadow specialist: lane=docs-config-truth status=skipped reason=no-operator-truth-paths candidateCount=0 decisionCount=0 decisionCounts=candidate:0,duplicate:0,disagreement:0,dismissed:0,unclassifiable:0 duplicateCount=0 disagreementCount=0 dismissedCount=0 unclassifiableCount=0 truncatedCandidateCount=0 metricAvailability=token:n,cost:n,latency:n visiblePublicationDenied=true approvalPublicationDenied=true privateOnly=true shadowOnly=true redacted=raw:n,publication:n,approval:n,unsafe:0 correlationKey=fe6ed87c463624b6 deliveryId=4916c950-727b-11f1-8e77-e1b587b… reviewOutputKey=kodiai-review-output:v1:inst-10…

  • Review completed: 2026-06-27T23:06:57.926Z

  • Total wall-clock: 11m 31s

  • Phase timings:

    • queue wait: 0ms
    • workspace preparation: 779ms
    • retrieval/context assembly: 3.3s
    • executor handoff: 57s
    • remote runtime: 10m 25s
    • publication: 2.4s
  • Tokens: 126 in / 28,354 out | 0.7221

  • Keyword parsing: No keywords detected

  • Budget behavior: complete (within-budget).

  • Prompt budget: 5 sections, 0 trimmed, 0 bypassed, 0 trimmed tokens.

  • Cache behavior: 2 observations, 1 hits, 1 misses, 0 degraded, 0 bypassed.

  • Continuation behavior: 0 observations, 0 compacted, 0 fallback, 0 degraded, 0 bypassed.

@CastagnaIT

Copy link
Copy Markdown
Collaborator

@kodiai please review PR

@kodiai

kodiai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Decision: APPROVE

Issues: none

Evidence:

  • Review prompt covered 1 changed file.
  • Repo inspection tools were used to verify the changed code.
  • Review finding lifecycle: status=normalized; counts=input:0,recorded:0,rejected:0,unsafeInputFields:0; statuses=detected:0,open:0,validated:0,degraded:0; severity=critical:0,major:0,medium:0,minor:0; actionability=actionable:0,needs-human-review:0,blocked:0; rejected=none; redaction=privateOnly:y,rawPrompts:n,rawModelOutput:n,candidateBodies:n,toolPayloads:n,secretLike:n,diffs:n,unboundedArrays:n,unsafeFields:0

@CastagnaIT CastagnaIT left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thank you!
please do backport

@CastagnaIT CastagnaIT added Type: Fix non-breaking change which fixes an issue v22 Piers labels Jun 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Fix non-breaking change which fixes an issue v22 Piers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants