Skip to content

[mpegts] Validate PSI section CRC32 before parsing PAT/PMT - Backport#2085

Merged
CastagnaIT merged 1 commit into
xbmc:Omegafrom
wmdebrito:fix/mpegts-validate-psi-crc-backport
Jun 29, 2026
Merged

[mpegts] Validate PSI section CRC32 before parsing PAT/PMT - Backport#2085
CastagnaIT merged 1 commit into
xbmc:Omegafrom
wmdebrito:fix/mpegts-validate-psi-crc-backport

Conversation

@wmdebrito

Copy link
Copy Markdown

Backport from: #2084
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.

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

Description

Motivation and context

How has this been tested?

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

… from Piers

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>
@kodiai

kodiai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Decision: APPROVE

Issues: none

Evidence:

  • Review prompt covered 1 changed file.
Review Details
  • Review plan: ready hash=cfcc827ec85e 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=2085 key=kodiai-review-output:v1:inst-109141824:xbmc-inputstream.adaptive:pr-2085:action- delivery=88fbb680-7333-11f1-90e7-ccbc0264cd4f

  • 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:54b67f99432a9d6a6c9cb97d64b411c3; recordKey=candidate-publication-record:54b67f99432a9d6a6c9cb97d64b411c3; correlationKey=candidate-publication-bridge:bf80afd5aef9348b85de5dedab36c6e6; source=review-handler-publication; candidateRef=candidate-publication-summary-59f0d65d; 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:1,minor:0; 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-6549409e075db08c: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=6c67c71eed3125db deliveryId=88fbb680-7333-11f1-90e7-ccbc026… reviewOutputKey=kodiai-review-output:v1:inst-10…

  • Review completed: 2026-06-28T21:02:20.005Z

  • Total wall-clock: 7m 59s

  • Phase timings:

    • queue wait: 0ms
    • workspace preparation: 830ms
    • retrieval/context assembly: 3.3s
    • executor handoff: 60s
    • remote runtime: 6m 50s
    • publication: 2.3s
  • Tokens: 98 in / 18,539 out | 0.5033

  • 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.

@wmdebrito

Copy link
Copy Markdown
Author

@CastagnaIT it seems to be failing on a lib download. Not related to the code:
-- Using src='https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz'
CMake Error at googletest-stamp/download-googletest.cmake:163 (message):
Each download failed!

CUSTOMBUILD : error : downloading 'https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz' failed [D:\a\1\s\build\googletest.vcxproj]

@CastagnaIT CastagnaIT added Type: Fix non-breaking change which fixes an issue Type: Backport v21 Omega labels Jun 29, 2026
@CastagnaIT CastagnaIT merged commit 3d5a06d into xbmc:Omega Jun 29, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Backport Type: Fix non-breaking change which fixes an issue v21 Omega

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants