From 732cbc13eafdc691fd86a3ba5ffd453dad23d8f9 Mon Sep 17 00:00:00 2001 From: Wagner Brito Date: Sat, 27 Jun 2026 15:22:37 -0300 Subject: [PATCH] [mpegts] Validate PSI section CRC32 before parsing PAT/PMT - Backport 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 (59fd1b3e84), 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 59fd1b3e84 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): - #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 --- lib/mpegts/mpegts/tsDemuxer.cpp | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/lib/mpegts/mpegts/tsDemuxer.cpp b/lib/mpegts/mpegts/tsDemuxer.cpp index 052f81783..9b7e05f56 100644 --- a/lib/mpegts/mpegts/tsDemuxer.cpp +++ b/lib/mpegts/mpegts/tsDemuxer.cpp @@ -615,6 +615,20 @@ void AVContext::clear_pes(uint16_t channel) * AVCONTEXT_TS_ERROR * Parsing error ! */ +// MPEG-2 systems CRC-32 (poly 0x04C11DB7, MSB-first, init 0xFFFFFFFF, no final +// XOR / no reflection). Computing it over a whole PSI section (table_id .. CRC) +// yields 0 when the section is intact. +static uint32_t crc32_mpeg(uint32_t crc, const unsigned char* data, size_t len) +{ + for (size_t i = 0; i < len; ++i) + { + crc ^= static_cast(data[i]) << 24; + for (int b = 0; b < 8; ++b) + crc = (crc & 0x80000000) ? ((crc << 1) ^ 0x04c11db7) : (crc << 1); + } + return crc; +} + int AVContext::parse_ts_psi() { size_t len; @@ -691,6 +705,36 @@ int AVContext::parse_ts_psi() } // now entire table is filled + + // Validate the section CRC32 before trusting it. Every PSI section (PAT/PMT) + // is protected by a trailing CRC32; random TS payload that merely began with a + // PSI-like table_id will not satisfy it. Without this check such payload is + // misparsed as a PAT/PMT, spuriously (de)registers streams / forces a program + // change, corrupts the demux state and can crash on the next resync. This also + // lets us safely accept a PMT carried on a non-standard PID (issue #2010). + { + const uint16_t section_len = this->packet->packet_table.len; + if (section_len < 4 || section_len > TABLE_BUFFER_SIZE) + return AVCONTEXT_TS_ERROR; + // The 3-byte section header (table_id + syntax/length word) is not kept in + // buf; reconstruct it (PAT/PMT are long syntax sections, top bits '1011'). + const unsigned char header[3] = { + this->packet->packet_table.table_id, + static_cast(0xb0 | ((section_len >> 8) & 0x0f)), + static_cast(section_len & 0xff) + }; + uint32_t crc = 0xffffffff; + crc = crc32_mpeg(crc, header, 3); + crc = crc32_mpeg(crc, this->packet->packet_table.buf, section_len); + if (crc != 0) + { + DBG(DEMUX_DBG_DEBUG, "%s: bad PSI CRC on pid %.4x table_id %.2x; ignoring section\n", + __FUNCTION__, this->packet->pid, this->packet->packet_table.table_id); + this->packet->packet_table.Reset(); + return AVCONTEXT_CONTINUE; + } + } + const unsigned char* psi = this->packet->packet_table.buf; const unsigned char* end_psi = psi + this->packet->packet_table.len;