CVE-2026-16530 - Performance Co-Pilot

CVE-2026-16530 - Performance Co-Pilot (PCP)

Vulnerability Description: CWE-125: Out-of-bounds Read

Software Version: 7.1.3

CVSS: 6.5

Severity:  Medium

NVD: https://nvd.nist.gov/vuln/detail/cve-2026-16530

Credits: Francisco Alisson Bezerra, Lucas Gabriel Alves, Massimiliano Brolli

The pmproxy logger servlet accepts metadata records via POST /logger/meta without authentication. When a TYPE_LABEL record is submitted with hdr.len=13 (the minimum accepted value, yielding rlen=1), __pmLogLoadLabelSet reads up to 24 bytes from a 1-byte buffer without any per-field bounds check. The function has no check of the form k < rlen before accessing timestamp, type, ident, and nsets fields. If nsets reads as non-zero from adjacent heap memory, the outer loop multiplies OOB accesses, potentially crashing pmproxy.

Prerequisites: pmproxy reachable on TCP 44322. A valid archive ID obtained via a prior POST /logger/label (no authentication required for either request).

Step-by-step Instructions and PoC

__pmLogLoadLabelSet() in e_labels.c decodes TYPE_LABEL metadata records from an in-memory buffer. The function receives a buffer pointer (tbuf) and a declared record length (rlen). It reads multiple fields sequentially using byte offsets into tbuf: a timestamp (12 bytes), type (4 bytes), ident (4 bytes), and nsets (4 bytes), advancing k by each field's size.

There is no check of the form 'k < rlen' before any field access. When the pmproxy logger servlet delivers a TYPE_LABEL record with hdr.len=13 - the minimum value accepted by the dispatcher, which yields rlen=1 - all field reads occur beyond the single allocated byte. Reading nsets from heap+20 may return an arbitrary positive value, causing the per-labelset processing loop to iterate that many times, each accessing further OOB memory.

Affected Source Files and Lines

src/libpcp/src/e_labels.c:203-232: __pmLogLoadLabelSet() - no rlen check before first field access; field reads at k=0,12,16,20 with rlen=1

Image 1 - __pmLogLoadLabelSet() (e_labels.c:203). Highlighted lines 219-228: sequential field reads at k=12,16,20 with no rlen guard.

Click to Enlarge

Affected Endpoints

URL (TCP 44322): POST /logger/label

URL (TCP 44322): POST /logger/meta/{archive_id}

The proof-of-concept (poc11.py) creates an archive by POSTing a valid label to the unauthenticated /logger/label endpoint, then sends a 13-byte TYPE_LABEL metadata record to /logger/meta/<id>. With hdr.len=13, only one byte of record body is allocated; __pmLogLoadLabelSet reads 20-24 bytes past that single byte. On ASAN builds pmproxy aborts with a heap-buffer-overflow; on non-ASAN builds the process crashes, producing a denial of service.

Image 2 - Start the pmproxy service compiled with ASAN.

Click to Enlarge

Image 3 - Execute the proof-of-concept script to connect to pmproxy and trigger the vulnerability

Click to Enlarge

Image 4 - generated ASAN report in pmproxy log file.

Click to Enlarge

Security Impact

The vulnerability can cause a complete denial of service of the PCP monitoring infrastructure. All client connections fail, metric collection stops, PMDA agents disconnect, and no new agents can be loaded. In addition, pmlogger ceases archive recording and pmlc becomes unable to perform management operations, requiring a service restart to restore functionality.

Other Affected Source Files and Lines

  • src/libpcp/src/e_indom.c:320-345: __pmLogLoadInDom() - 'if (acp != NULL)' guard at line 324; namelist assignment at line 340 reads idx without bounds check
  • src/libpcp_web/src/discover.c:2265-2273: pmDiscoverDecodeMetaInDom() - hardcoded NULL at line 2268
  • src/libpcp/src/e_indom.c:252-268: __pmLogLoadInDom() - lbuf[-2] cast at line 255; numinst read at line 260 - 16 bytes past the 1-byte buffer
  • src/libpcp_web/src/discover.c:2275-2285: pmDiscoverDecodeMetaInDom() - calloc at line 2279 with unchecked numinst
  • src/libpcp/src/p_label.c:424-457: __pmDecodeLabel() - jsonoff and jsonlen decoded at lines 425-426; flawed bounds check at line 449; memcpy at line 479
  • src/libpcp/src/p_lstatus.c:264-290: __pmDecodeLogStatus() - strdup at line 275; 'p > pduend' check at line 282 comes after the OOB read
  • src/libpcp/src/p_instance.c:290-335: __pmDecodeInstance() - decode loop starting at line 290; bounds check with size_t cast at line 292; j+= at line 333