← Notes

CVE-2026-34235: Heap OOB Read in PJSIP VP9 RTP Unpacketizer

Overview

CVECVE-2026-34235
AdvisoryGHSA-pqrm-53pc-wx28
Componentpjmedia/src/pjmedia-codec/vpx_packetizer.c (pjmedia_vpx_unpacketize())
Affectedpjproject < 2.17
Fixedpjproject 2.17 (f4c7d08)
ClassCWE-125 (Out-of-Bounds Read)
CVSS v3.19.1 (Critical, NVD) -- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H
Found byLukas Johannes Moeller (@JohannesLks)

TL;DR

pjproject's VP9 RTP unpacketizer carefully bounds-checks descriptor bytes one at a time, then skips VP9 Scalability Structure resolution data in bulk without checking the new offset. A 3-byte RTP payload can make the parser read 31 bytes past the heap buffer during VP9 media handling.

Root Cause

The parser uses INC_DESC_LEN() for safe single-byte descriptor progress:

#define INC_DESC_LEN() \
    desc_len++; \
    if (desc_len >= payload_len) \
        return PJ_ETOOSMALL;

The vulnerable path handles VP9 Scalability Structure data. N_S is attacker-controlled through three bits and can be 1-8. When the Y bit is set, the code skips N_S * 4 bytes without applying the same invariant:

unsigned N_S = (*q >> 5) + 1;

INC_DESC_LEN();
if (*q & 0x10)
    desc_len += N_S * 4;      // unchecked bulk advance

if (*q & 0x08) {
    unsigned N_G = *(p + desc_len); // OOB read
}

With N_S = 8, the unchecked advance is 32 bytes. On a 3-byte payload, desc_len lands 31 bytes beyond the allocation before the N_G read.

Minimal Trigger

// byte 0: VP9 descriptor with V flag set
// byte 1: N_S=8, Y=1, G=1
// byte 2: one available byte before the unchecked skip
unsigned char payload[] = {0x02, 0xF8, 0x00};
==115140==ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 1 in pjmedia_vpx_unpacketize vpx_packetizer.c
0x502000000052 is located 31 bytes after 3-byte region

Impact

  • Remote crash of applications using pjproject VP9 RTP receive paths during an active media session.
  • No authentication is implied by the media parser itself; exploitability depends on whether the application accepts attacker-controlled VP9 RTP.
  • No code execution primitive was demonstrated. The read result is parser-internal and the practical outcome is denial of service.

Fix

The fix mirrors the parser's existing invariant after the bulk skip:

if (*q & 0x10) {
    desc_len += N_S * 4;
    if (desc_len >= payload_len)
        return PJ_ETOOSMALL;
}

Released in pjproject 2.17 and tracked as GHSA-pqrm-53pc-wx28.