Vulnerability Description: CWE-403: Exposure of File Descriptor to Unintended Control Sphere ('File Descriptor Leak')
Software Version: 7.1.3
CVSS: 8.8
Severity: High
NVD: https://nvd.nist.gov/vuln/detail/cve-2026-16526
Credits: Francisco Alisson Bezerra, Lucas Gabriel Alves, Massimiliano Brolli
When the linux_sockets PMDA is loaded as a DSO inside PMCD, code execution can be escalated from user pcp to root. The pmdarootfd Unix socket connection to pmdaroot is created without O_CLOEXEC, causing all child processes spawned by popen() to inherit it. pmdaroot runs as root and processes PDUROOT_STARTPMDA_REQ without per-request authentication, executing attacker-controlled arguments via execvp() as root.
Prerequisites:
Image 1 - PMDA sockets configured as DSO in pmcd.conf.
Step-by-step Instructions and PoC
When the linux_sockets PMDA is loaded as a DSO inside PMCD, code execution can be escalated to root. The underlying condition is that the pmdarootfd Unix socket - the connection between PMCD and the pmdaroot daemon (which runs as root) - is created without the O_CLOEXEC / FD_CLOEXEC flag.
The __pmInitSocket() function in auxconnect.c applies FD_CLOEXEC via __pmConnectRestoreFlags() only for TCP connections. For AF_UNIX sockets, the function returns early (line 517) before reaching the flag-setting code. As a result, every child process spawned by popen() in the PMDA inherits the open file descriptor. An exploit running inside the popen() child can identify the inherited fd by scanning /proc/self/fd, then send a PDUROOT_STARTPMDA_REQ PDU with attacker-controlled arguments. pmdaroot processes this request without per-operation authentication and executes the arguments via fork() + execvp() as root.
Since Vulnerability 1 is exploitable remotely via the pmproxy REST API (TCP 44322, no authentication, exposed on all interfaces by default), this full escalation chain - from unauthenticated remote attacker to root - is achievable without any local access when pmproxy is running, which represents the default configuration of networked PCP installations.
Affected Source Files and Lines
src/libpcp/src/auxconnect.c:514-525: __pmInitSocket() - early return at line 517 skips FD_CLOEXEC for AF_UNIX
src/libpcp_pmda/src/root.c: pmdaRootConnect() - fd returned without FD_CLOEXEC applied
Image 2 - __pmInitSocket() (auxconnect.c:517, highlighted). The early return for AF_UNIX skips all subsequent socket hardening.
Affected Endpoints
Unix socket (inherited fd): /var/lib/pcp/pmcd/root.socket
pmproxy TCP 44322 (all interfaces, no auth): Initial access via POST /pmapi/{ctx}/store
The proof-of-concept (rce_remote.py) chains three vulnerabilities into a full remote-to-root exploit: it creates an unauthenticated pmproxy context, injects a base64-encoded Python stage-2 payload into network.persocket.filter, then triggers its execution via a fetch request. The stage-2 payload, running as user pcp, enumerates inherited file descriptors, locates the open pmdaroot Unix socket leaked without FD_CLOEXEC, and sends a PDUROOT_STARTPMDA_REQ PDU to execute an arbitrary shell command as root.
The exploit chains Vulnerability CVE-2026-16524 to achieve root. The initial code execution can be triggered locally (pmcd, TCP 44321, localhost only) or remotely (pmproxy REST API, TCP 44322, all interfaces, no authentication). Step 1 plants the payload via the inverted guard - either via pmstore locally or via POST /pmapi/{ctx}/store remotely. Step 2 triggers popen(); the child inherits pmdarootfd and sends a privileged PDU:
Image 3 - start netcat listener to wait for connection.
Image 4 - run the remote exploit that will spawn a shell at our listener.
Image 5 - connection received from the victim.
Security Impact
Full privilege escalation from user pcp to root in a single HTTP request to pmproxy when combined with CVE-2026-16527 (missing authentication in pmproxy REST API), or via two local commands (pmstore + pminfo).