Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package pcp for openSUSE:Factory checked in at 2026-08-09 21:32:34 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/pcp (Old) and /work/SRC/openSUSE:Factory/.pcp.new.16738 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "pcp" Sun Aug 9 21:32:34 2026 rev:79 rq:1370102 version:6.3.8 Changes: -------- --- /work/SRC/openSUSE:Factory/pcp/pcp.changes 2026-05-13 20:58:50.111896671 +0200 +++ /work/SRC/openSUSE:Factory/.pcp.new.16738/pcp.changes 2026-08-09 21:33:38.270699474 +0200 @@ -1,0 +2,26 @@ +Thu Jul 30 18:47:41 UTC 2026 - Martin Schreiner <[email protected]> + +- Fix bsc#1272922 / CVE-2026-16524. +- Fix bsc#1272923 / CVE-2026-16526. +- Fix bsc#1272924 / CVE-2026-16527. +- Fix bsc#1272925 / CVE-2026-16529. +- Fix bsc#1272926 / CVE-2026-16530. +- Fix bsc#1272927 / CVE-2026-16531. +- Fix bsc#1272928. +- Fix bsc#1272930. +- Add patches: + * pcp-CVE-2026-16524.patch + * pcp-CVE-2026-16526.patch + * pcp-CVE-2026-16527.patch + * pcp-CVE-2026-16529.patch + * pcp-CVE-2026-16530-1.patch + * pcp-CVE-2026-16530-2.patch + * pcp-CVE-2026-16530-3.patch + * pcp-CVE-2026-16530-4.patch + * pcp-CVE-2026-16530-5.patch + * pcp-CVE-2026-16530-6.patch + * pcp-CVE-2026-16531.patch + * pcp-bsc-1272928.patch + * pcp-bsc-1272930.patch + +------------------------------------------------------------------- New: ---- pcp-CVE-2026-16524.patch pcp-CVE-2026-16526.patch pcp-CVE-2026-16527.patch pcp-CVE-2026-16529.patch pcp-CVE-2026-16530-1.patch pcp-CVE-2026-16530-2.patch pcp-CVE-2026-16530-3.patch pcp-CVE-2026-16530-4.patch pcp-CVE-2026-16530-5.patch pcp-CVE-2026-16530-6.patch pcp-CVE-2026-16531.patch pcp-bsc-1272928.patch pcp-bsc-1272930.patch ----------(New B)---------- New:- Add patches: * pcp-CVE-2026-16524.patch * pcp-CVE-2026-16526.patch New: * pcp-CVE-2026-16524.patch * pcp-CVE-2026-16526.patch * pcp-CVE-2026-16527.patch New: * pcp-CVE-2026-16526.patch * pcp-CVE-2026-16527.patch * pcp-CVE-2026-16529.patch New: * pcp-CVE-2026-16527.patch * pcp-CVE-2026-16529.patch * pcp-CVE-2026-16530-1.patch New: * pcp-CVE-2026-16529.patch * pcp-CVE-2026-16530-1.patch * pcp-CVE-2026-16530-2.patch New: * pcp-CVE-2026-16530-1.patch * pcp-CVE-2026-16530-2.patch * pcp-CVE-2026-16530-3.patch New: * pcp-CVE-2026-16530-2.patch * pcp-CVE-2026-16530-3.patch * pcp-CVE-2026-16530-4.patch New: * pcp-CVE-2026-16530-3.patch * pcp-CVE-2026-16530-4.patch * pcp-CVE-2026-16530-5.patch New: * pcp-CVE-2026-16530-4.patch * pcp-CVE-2026-16530-5.patch * pcp-CVE-2026-16530-6.patch New: * pcp-CVE-2026-16530-5.patch * pcp-CVE-2026-16530-6.patch * pcp-CVE-2026-16531.patch New: * pcp-CVE-2026-16530-6.patch * pcp-CVE-2026-16531.patch * pcp-bsc-1272928.patch New: * pcp-CVE-2026-16531.patch * pcp-bsc-1272928.patch * pcp-bsc-1272930.patch New: * pcp-bsc-1272928.patch * pcp-bsc-1272930.patch ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ pcp.spec ++++++ --- /var/tmp/diff_new_pack.wwonwV/_old 2026-08-09 21:33:39.274733550 +0200 +++ /var/tmp/diff_new_pack.wwonwV/_new 2026-08-09 21:33:39.294734228 +0200 @@ -97,6 +97,19 @@ Patch7: 0001-selinux-add-permissions-allowing-proc_psi_t-access.patch # PATCH-FIX-OPENSUSE, [email protected] Patch8: 0008-selinux-additional-labeling-rules.patch +Patch9: pcp-bsc-1272928.patch +Patch10: pcp-bsc-1272930.patch +Patch11: pcp-CVE-2026-16524.patch +Patch12: pcp-CVE-2026-16526.patch +Patch13: pcp-CVE-2026-16527.patch +Patch14: pcp-CVE-2026-16529.patch +Patch15: pcp-CVE-2026-16530-1.patch +Patch16: pcp-CVE-2026-16530-2.patch +Patch17: pcp-CVE-2026-16530-3.patch +Patch18: pcp-CVE-2026-16530-4.patch +Patch19: pcp-CVE-2026-16530-5.patch +Patch20: pcp-CVE-2026-16530-6.patch +Patch21: pcp-CVE-2026-16531.patch %global disable_selinux 0 %if 0%{?suse_version} < 1600 ++++++ pcp-CVE-2026-16524.patch ++++++ References: CVE-2026-16524 Description: linux_sockets pmda: fix command injection via network.persocket.filter (CWE-78) The sockets_check_filter() validation helper returns 1 for safe input and 0 for unsafe input. The guard in sockets_store() tested if (sockets_check_filter(av.cp)) — rejecting safe input and accepting malicious input containing shell metacharacters. The accepted filter was later passed to popen() via shell interpretation, enabling arbitrary command execution as the PMDA process user. . Fix: - Invert the guard: if (!sockets_check_filter(av.cp)) - Replace popen()/pclose() in ss_open_stream() with the libpcp __pmProcessAddArg()/__pmProcessPipe()/__pmProcessPipeClose() API which uses execvp() internally, eliminating shell interpretation of the filter string entirely - Add qa/2101 verifying that valid filters are accepted and shell metacharacters (semicolons, backticks, pipes) are rejected . Reported-by: Francisco Alisson Bezerra, TIM Security Red Team Reported-by: Lucas Gabriel Alves, TIM Security Red Team Reported-by: Massimiliano Brolli, TIM Security Red Team . Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> . Author: Nathan Scott <[email protected]> Origin: upstream, https://github.com/performancecopilot/pcp/commit/c5cbeceb7d3c2af357c04065cdd911efdc270de0 Last-Update: 2026-07-25 Backport-Note: Backported to PCP 6.3.8 (applies unchanged). --- a/src/pmdas/linux_sockets/pmda.c +++ b/src/pmdas/linux_sockets/pmda.c @@ -162,11 +162,9 @@ const char *p; for (p = string; *p; p++) { - if (isspace(*p)) + if (isspace(*p) || isalnum(*p)) continue; - if (isalnum(*p)) - continue; - if (*p == '(' || *p == ')') + if (strchr("()=!<>:.*/-,", *p) != NULL) continue; return 0; /* disallow */ } @@ -191,7 +189,7 @@ case 0: /* network.persocket.filter */ if ((sts = pmExtractValue(vsp->valfmt, &vsp->vlist[0], PM_TYPE_STRING, &av, PM_TYPE_STRING)) >= 0) { - if (sockets_check_filter(av.cp)) { + if (!sockets_check_filter(av.cp)) { sts = PM_ERR_BADSTORE; free(av.cp); break; --- a/src/pmdas/linux_sockets/ss_stream.c +++ b/src/pmdas/linux_sockets/ss_stream.c @@ -14,18 +14,19 @@ #include <pcp/pmapi.h> #include <pcp/pmda.h> +#include <pcp/libpcp.h> #include "ss_stats.h" #define SS_OPTIONS "-noemitauO" -char *ss_filter = NULL; /* storable: network.persocket.filter */ +char *ss_filter; /* storable: network.persocket.filter */ +static int using_pipe; /* pipe is normal operation, QA uses files */ FILE * ss_open_stream() { - FILE *fp; + FILE *fp = NULL; char *path; - char cmd[MAXPATHLEN]; if (ss_filter == NULL) { /* pmstore to network.persocket.filter frees this if changing */ @@ -38,17 +39,51 @@ fp = fopen(path, "r"); if (pmDebugOptions.appl0) fprintf(stderr, "ss_open_stream: open PCPQA_PMDA_SOCKETS=%s\n", path); + using_pipe = 0; } else { + __pmExecCtl_t *argp = NULL; + int sts; + if (access((path = "/usr/sbin/ss"), X_OK) != 0) { if (access((path = "/usr/bin/ss"), X_OK) != 0) { fprintf(stderr, "Error: no \"ss\" binary found\n"); return NULL; } } - pmsprintf(cmd, sizeof(cmd), "%s %s %s", path, SS_OPTIONS, ss_filter); - fp = popen(cmd, "r"); + if ((sts = __pmProcessAddArg(&argp, path)) < 0 || + (sts = __pmProcessAddArg(&argp, SS_OPTIONS)) < 0) { + if (pmDebugOptions.appl0) + fprintf(stderr, "ss_open_stream: __pmProcessAddArg failed: %s\n", + pmErrStr(sts)); + return NULL; + } + if (ss_filter[0] != '\0') { + char *s, *tok, *saveptr; + + if ((s = strdup(ss_filter)) == NULL) + return NULL; + for (tok = strtok_r(s, " \t", &saveptr); tok != NULL; + tok = strtok_r(NULL, " \t", &saveptr)) { + if ((sts = __pmProcessAddArg(&argp, tok)) < 0) { + free(s); + if (pmDebugOptions.appl0) + fprintf(stderr, "ss_open_stream: __pmProcessAddArg failed: %s\n", + pmErrStr(sts)); + return NULL; + } + } + free(s); + } + if ((sts = __pmProcessPipe(&argp, "r", PM_EXEC_TOSS_NONE, &fp)) < 0) { + if (pmDebugOptions.appl0) + fprintf(stderr, "ss_open_stream: __pmProcessPipe failed: %s\n", + pmErrStr(sts)); + return NULL; + } if (pmDebugOptions.appl0) - fprintf(stderr, "ss_open_stream: popen %s\n", cmd); + fprintf(stderr, "ss_open_stream: exec %s %s %s\n", + path, SS_OPTIONS, ss_filter); + using_pipe = 1; } return fp; @@ -57,8 +92,8 @@ void ss_close_stream(FILE *fp) { - if (getenv("PCPQA_PMDA_SOCKETS") != NULL) - fclose(fp); + if (using_pipe) + __pmProcessPipeClose(fp); else - pclose(fp); + fclose(fp); } ++++++ pcp-CVE-2026-16526.patch ++++++ References: CVE-2026-16526 Description: libpcp, libpcp_pmda: set FD_CLOEXEC on AF_UNIX sockets (CWE-403) The __pmInitSocket() function returns early for AF_UNIX sockets, skipping all subsequent socket hardening including FD_CLOEXEC. This causes the pmdaroot Unix socket fd to be inherited by child processes spawned via popen()/fork(), enabling privilege escalation when combined with the linux_sockets command injection (vuln 3): an attacker's popen() child inherits the pmdaroot fd and can send a PDUROOT_STARTPMDA_REQ to execute commands as root. . Fix: - Set FD_CLOEXEC on AF_UNIX sockets in __pmInitSocket() before the early return, matching the behavior TCP sockets get via __pmConnectRestoreFlags() - Set FD_CLOEXEC on pmdarootfd in pmdaRootConnect() after connect() succeeds, as belt-and-suspenders for this critical fd - Add qa/src/check_cloexec.c and qa/2104 verifying FD_CLOEXEC is set on sockets created by __pmCreateUnixSocket() . Note: SO_PEERCRED peer credential verification on the pmdaroot server side is a separate hardening measure to be addressed as a follow-up. . Reported-by: Francisco Alisson Bezerra, TIM Security Red Team Reported-by: Lucas Gabriel Alves, TIM Security Red Team Reported-by: Massimiliano Brolli, TIM Security Red Team . Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> . Author: Nathan Scott <[email protected]> Origin: upstream, https://github.com/performancecopilot/pcp/commit/7e27614006ff6fc4925991edbedaf1eab6b14731 https://github.com/performancecopilot/pcp/commit/2a4bd9f81a3a59fcba4cf726cc615fd51d107054 Last-Update: 2026-07-25 Backport-Note: Backported to PCP 6.3.8 (applies unchanged). No libpcp3 tree in 6.3.8, so that mirror hunk is dropped. --- a/src/libpcp/src/auxconnect.c +++ b/src/libpcp/src/auxconnect.c @@ -516,8 +516,12 @@ } #if defined(HAVE_STRUCT_SOCKADDR_UN) - if (family == AF_UNIX) + if (family == AF_UNIX) { + int fdFlags; + if ((fdFlags = __pmGetFileDescriptorFlags(fd)) >= 0) + __pmSetFileDescriptorFlags(fd, fdFlags | FD_CLOEXEC); return fd; + } #endif /* Avoid 200 ms delay. This option is not supported for unix domain sockets. */ --- a/src/libpcp_pmda/src/root.c +++ b/src/libpcp_pmda/src/root.c @@ -32,7 +32,7 @@ char *tmpdir; char socketpath[MAXPATHLEN]; char errmsg[PM_MAXERRMSGLEN]; - int fd, sts, version, features; + int fd, sts, version, features, fdFlags; /* Initialize the socket address. */ if ((addr = __pmSockAddrAlloc()) == NULL) @@ -72,6 +72,9 @@ return sts; } + if ((fdFlags = __pmGetFileDescriptorFlags(fd)) >= 0) + __pmSetFileDescriptorFlags(fd, fdFlags | FD_CLOEXEC); + /* Check server connection information */ if ((sts = __pmdaRecvRootPDUInfo(fd, &version, &features)) < 0) { pmNotifyErr(LOG_ERR, --- a/src/pmdas/root/root.c +++ b/src/pmdas/root/root.c @@ -23,6 +23,9 @@ #include "docker.h" #include "podman.h" #include "domain.h" +#if defined(HAVE_PWD_H) +#include <pwd.h> +#endif #ifndef S_IRWXU /* @@ -37,6 +40,7 @@ static __pmSockAddr *socket_addr; static int socket_fd = -1; static int pmcd_fd = -1; +static uid_t pcp_uid; static __pmFdSet connected_fds; int root_maximum_fd; @@ -461,6 +465,42 @@ exit(1); } } +#if defined(HAVE_STRUCT_UCRED) + { + struct ucred cred; + __pmSockLen len = sizeof(cred); + + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == 0) { + if (cred.uid != 0 && cred.uid != pcp_uid) { + pmNotifyErr(LOG_ERR, + "root_accept_client: rejected uid=%d (expected root or pcp[%d])\n", + cred.uid, pcp_uid); + close(fd); + root_client[i].fd = -1; + root_delete_client(&root_client[i]); + return NULL; + } + } + } +#elif defined(HAVE_GETPEEREID) + { + uid_t uid; + gid_t gid; + + if (getpeereid(fd, &uid, &gid) == 0) { + if (uid != 0 && uid != pcp_uid) { + pmNotifyErr(LOG_ERR, + "root_accept_client: rejected uid=%d (expected root or pcp[%d])\n", + uid, pcp_uid); + close(fd); + root_client[i].fd = -1; + root_delete_client(&root_client[i]); + return NULL; + } + } + } +#endif + if (fd > root_maximum_fd) root_maximum_fd = fd; __pmFD_SET(fd, &connected_fds); @@ -797,6 +837,19 @@ } static void +root_get_pcp_uid(void) +{ +#if defined(HAVE_PWD_H) + char *username; + struct passwd *pw; + + pmGetUsername(&username); + if ((pw = getpwnam(username)) != NULL) + pcp_uid = pw->pw_uid; +#endif +} + +static void root_check_user(void) { #ifdef HAVE_GETUID @@ -815,6 +868,7 @@ root_prep(void) { root_check_user(); + root_get_pcp_uid(); root_setup_socket(); atexit(root_close_socket); } ++++++ pcp-CVE-2026-16527.patch ++++++ References: CVE-2026-16527 Description: pmproxy: fix missing -Q and -S authentication flags (CWE-306) The pmproxy -Q (require client certificate) and -S (require authenticated clients) flags existed as case blocks in the option parser but were absent from the short_options string and the longopts table, making them permanently unreachable. An unauthenticated HTTP client could access all REST API endpoints including /store and /derive. . Fix: - Add Q and S to short_options so pmgetopt_r() delivers them - Add --certreqd and --reqauth entries to the longopts table - Document both flags in the pmproxy(1) man page - Add qa/2100 verifying the flags are accepted and that -S correctly rejects unauthenticated REST API requests with HTTP 403 . Note: -S enforcement in the REST API path already exists in http.c and webapi.c. -Q (CERT_REQD) enforcement is only implemented for the legacy PCP wire protocol path, not the REST API; this is a pre-existing limitation to be addressed separately. . Reported-by: Francisco Alisson Bezerra, TIM Security Red Team Reported-by: Lucas Gabriel Alves, TIM Security Red Team Reported-by: Massimiliano Brolli, TIM Security Red Team . Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> . Author: Nathan Scott <[email protected]> Origin: upstream, https://github.com/performancecopilot/pcp/commit/d96ba5a716eeff7840138eb08fbab0d11a57f641 https://github.com/performancecopilot/pcp/commit/81a9efe96db6f7764a1a1646b64eb85de5cdaf77 https://github.com/performancecopilot/pcp/commit/4121ae06f54ab2089adf7270197c774d60dd03c8 Last-Update: 2026-07-25 Backport-Note: Backported to PCP 6.3.8 (applies unchanged). The logger-servlet authentication (logger.c, pmproxy.conf) and man page are absent in 6.3.8 and dropped; the core -Q/-S flag enablement and REST CERT_REQD enforcement are carried. --- a/src/pmproxy/src/pmproxy.c +++ b/src/pmproxy/src/pmproxy.c @@ -82,7 +82,9 @@ PMAPI_OPTIONS_HEADER("Connection options"), { "interface", 1, 'i', "ADDR", "accept connections on this IP address" }, { "port", 1, 'p', "PORT", "accept connections on this port" }, + { "certreqd", 0, 'Q', 0, "require client certificate authentication" }, { "socket", 1, 's', "PATH", "Unix domain socket file [default $PCP_RUN_DIR/pmproxy.socket]" }, + { "reqauth", 0, 'S', 0, "require all client connections to be authenticated" }, { "keyport", 1, 'r', "PORT", "Connect to key server on this TCP/IP port (implies --timeseries)" }, { "keyhost", 1, 'h', "HOST", "Connect to key server on this host name (implies --timeseries)" }, { "redisport", 1, 'r', "PORT", "Backwards-compatibility option, do not use" }, @@ -95,7 +97,7 @@ }; static pmOptions opts = { - .short_options = "Ac:dD:Ffh:i:l:L:p:r:s:tT:U:x:?", + .short_options = "Ac:dD:Ffh:i:l:L:p:Qr:s:StT:U:x:?", .long_options = longopts, }; --- a/src/pmproxy/src/http.c +++ b/src/pmproxy/src/http.c @@ -1091,6 +1091,20 @@ } } + /* client certificate required for all servlets */ + if (__pmServerHasFeature(PM_SERVER_FEATURE_CERT_REQD)) { +#ifdef HAVE_OPENSSL + if (!client->stream.secure || + !client->secure.ssl || + SSL_get_peer_certificate(client->secure.ssl) == NULL) { + client->u.http.parser.status_code = HTTP_STATUS_FORBIDDEN; + } +#else + /* no TLS support compiled in, reject all connections */ + client->u.http.parser.status_code = HTTP_STATUS_FORBIDDEN; +#endif + } + return sts; } ++++++ pcp-CVE-2026-16529.patch ++++++ References: CVE-2026-16529 Description: libpcp: fix integer overflow in __pmGetPDU() (CWE-190) When php->len is near INT_MAX (e.g. 0x7FFFFFFF), the buffer size computation PDU_CHUNK * (1 + php->len / PDU_CHUNK) overflows signed int, producing a negative value that permanently corrupts the static maxsize variable. Every subsequent __pmFindPDUBuf() call returns NULL, rendering the affected daemon (pmlogger, pmcd) unable to process any further PDUs for the remainder of its lifetime — a persistent denial of service requiring a restart. . Fix: add an overflow guard (php->len > INT_MAX - PDU_CHUNK) before the multiplication, returning PM_ERR_TOOBIG for absurdly large PDU lengths. This protects the NO_LIMIT code path used by pmcd and pmlogger that is not covered by the existing ceiling check. . Also add _filter_pmcd() to qa/common.pmcd.pdu to normalize fd=N in pmcd log output, and qa/2105 with a crafted PDU exercising the overflow. . Reported-by: Francisco Alisson Bezerra, TIM Security Red Team Reported-by: Lucas Gabriel Alves, TIM Security Red Team Reported-by: Massimiliano Brolli, TIM Security Red Team . Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> . Author: Nathan Scott <[email protected]> Origin: upstream, https://github.com/performancecopilot/pcp/commit/ef848fb978d26335f9676933f541c73b58f130a6 Last-Update: 2026-07-25 Backport-Note: Backported to PCP 6.3.8 (applies unchanged). No libpcp3 tree in 6.3.8, so that mirror hunk is dropped. --- a/src/libpcp/src/pdu.c +++ b/src/libpcp/src/pdu.c @@ -658,6 +658,14 @@ PM_LOCK(pdu_lock); if (php->len > maxsize) { + if (php->len > INT_MAX - PDU_CHUNK) { + PM_UNLOCK(pdu_lock); + if (pmDebugOptions.pdu) + pmNotifyErr(LOG_ERR, "%s: fd=%d PDU len=%d too large", + __FUNCTION__, fd, php->len); + __pmUnpinPDUBuf(pdubuf); + return PM_ERR_TOOBIG; + } tmpsize = PDU_CHUNK * ( 1 + php->len / PDU_CHUNK); maxsize = tmpsize; } ++++++ pcp-CVE-2026-16530-1.patch ++++++ References: CVE-2026-16530 Description: libpcp: fix arbitrary pointer deref in __pmLogLoadInDom (CWE-125/822) The bounds check on string indices (idx > max_idx) in __pmLogLoadInDom() was guarded by if (acp != NULL), making it unreachable from the streaming path used by pmproxy (which passes acp=NULL). An attacker could submit a TYPE_INDOM record with an out-of-range stridx value via POST /logger/meta, causing namelist[i] to point to arbitrary heap memory. . Fix: - Add minimum rlen checks before reading fixed fields, using macros derived from the on-disk struct sizes (INDOM_V3_MINRLEN, INDOM_V2_MINRLEN) - Validate numinst against rlen before using it in arithmetic, preventing integer overflow in the max_idx computation - Make max_idx computation and idx bounds check unconditional (remove the acp != NULL guard) so they protect both archive and streaming paths - Extend qa/src/pducrash.c with decode_log_indom() exercising all four failure modes via __pmLogLoadInDom(NULL, ...) . Reported-by: Francisco Alisson Bezerra, TIM Security Red Team Reported-by: Lucas Gabriel Alves, TIM Security Red Team Reported-by: Massimiliano Brolli, TIM Security Red Team . Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> . Author: Nathan Scott <[email protected]> Origin: upstream, https://github.com/performancecopilot/pcp/commit/ec81e2b35c7dc19a69406d2712d1b9904ac22112 https://github.com/performancecopilot/pcp/commit/b72da5135df7fb08ca432db1e2db24478f43aea2 https://github.com/performancecopilot/pcp/commit/edfd909edac8ad4762aa150e330f92211df98c1c Last-Update: 2026-07-25 Backport-Note: Backported to PCP 6.3.8 (applies unchanged). No libpcp3 tree in 6.3.8, so that mirror hunk is dropped. --- a/src/libpcp/src/e_indom.c +++ b/src/libpcp/src/e_indom.c @@ -51,6 +51,10 @@ /* will be expanded if numinst > 0 */ } __pmInDom_v2; +/* Minimum rlen (record body without len+type header) to read fixed fields */ +#define INDOM_V3_MINRLEN (sizeof(__pmInDom_v3) - 2 * sizeof(__int32_t)) +#define INDOM_V2_MINRLEN (sizeof(__pmInDom_v2) - 2 * sizeof(__int32_t)) + /* * pack an indom into a physical metadata record * - lcp required to provide archive version (else NULL) @@ -258,33 +262,55 @@ if (type == TYPE_INDOM || type == TYPE_INDOM_DELTA) { __pmInDom_v3 *v3; + if (rlen < (int)INDOM_V3_MINRLEN) { + if (pmDebugOptions.logmeta) + fprintf(stderr, "__pmLogLoadInDom: v3 rlen=%d too small (min=%d)\n", + rlen, (int)INDOM_V3_MINRLEN); + goto bad; + } v3 = (__pmInDom_v3 *)&lbuf[-2]; /* len+type not in buf */ __pmLoadTimestamp(&v3->sec[0], &lidp->stamp); k = (sizeof(v3->sec)+sizeof(v3->nsec))/sizeof(__int32_t); lidp->indom = __ntohpmInDom(v3->indom); k++; lidp->numinst = ntohl(v3->numinst); + if (lidp->numinst < 0 || + lidp->numinst > (rlen - (int)INDOM_V3_MINRLEN) / (2 * (int)sizeof(__int32_t))) { + if (pmDebugOptions.logmeta) + fprintf(stderr, "__pmLogLoadInDom: v3 numinst=%d not consistent with rlen=%d\n", + lidp->numinst, rlen); + goto bad; + } k++; lidp->instlist = (int *)&v3->data; - if (acp != NULL) { - /* rlen minus fixed fields (plus len+type), minus instlist[], minus strindex[] */ - max_idx = rlen - 5*sizeof(__int32_t) - 2*lidp->numinst*sizeof(__int32_t); - } + /* rlen minus fixed fields (plus len+type), minus instlist[], minus strindex[] */ + max_idx = rlen - (int)INDOM_V3_MINRLEN - 2 * lidp->numinst * (int)sizeof(__int32_t); } else if (type == TYPE_INDOM_V2) { __pmInDom_v2 *v2; + if (rlen < (int)INDOM_V2_MINRLEN) { + if (pmDebugOptions.logmeta) + fprintf(stderr, "__pmLogLoadInDom: v2 rlen=%d too small (min=%d)\n", + rlen, (int)INDOM_V2_MINRLEN); + goto bad; + } v2 = (__pmInDom_v2 *)&lbuf[-2]; /* len+type not in lbuf */ __pmLoadTimeval(&v2->sec, &lidp->stamp); k = (sizeof(v2->sec)+sizeof(v2->usec))/sizeof(__int32_t); lidp->indom = __ntohpmInDom(v2->indom); k++; lidp->numinst = ntohl(v2->numinst); + if (lidp->numinst < 0 || + lidp->numinst > (rlen - (int)INDOM_V2_MINRLEN) / (2 * (int)sizeof(__int32_t))) { + if (pmDebugOptions.logmeta) + fprintf(stderr, "__pmLogLoadInDom: v2 numinst=%d not consistent with rlen=%d\n", + lidp->numinst, rlen); + goto bad; + } k++; lidp->instlist = (int *)&v2->data; - if (acp != NULL) { - /* rlen minus fixed fields (plus len+type), minus instlist[], minus strindex[] */ - max_idx = rlen - 4*sizeof(__int32_t) - 2*lidp->numinst*sizeof(__int32_t); - } + /* rlen minus fixed fields (plus len+type), minus instlist[], minus strindex[] */ + max_idx = rlen - (int)INDOM_V2_MINRLEN - 2 * lidp->numinst * (int)sizeof(__int32_t); } else { if (pmDebugOptions.logmeta) @@ -327,21 +353,14 @@ } idx = ntohl(stridx[i]); if (idx >= 0) { - if (acp != NULL) { - /* - * crude sanity check ... if the index points to the - * start of the name that is past the end of the input - * record, the record is corrupted - */ - if (idx > max_idx) { - if (pmDebugOptions.logmeta) { - char strbuf[20]; - fprintf(stderr, "__pmLogLoadInDom: InDom: %s instance[%d]: bad string index (%d) > max index based on record length (%d)\n", - pmInDomStr_r(lidp->indom, strbuf, sizeof(strbuf)), - i, idx, max_idx); - } - goto bad; + if (idx > max_idx) { + if (pmDebugOptions.logmeta) { + char strbuf[20]; + fprintf(stderr, "__pmLogLoadInDom: InDom: %s instance[%d]: bad string index (%d) > max index based on record length (%d)\n", + pmInDomStr_r(lidp->indom, strbuf, sizeof(strbuf)), + i, idx, max_idx); } + goto bad; } lidp->namelist[i] = &namebase[idx]; if (pmDebugOptions.logmeta && pmDebugOptions.desperate) --- a/src/pmlogextract/pmlogextract.c +++ b/src/pmlogextract/pmlogextract.c @@ -1300,7 +1300,7 @@ memcpy(buf, rec->pdu, rlen); ibuf = &buf[2]; - sts = __pmLogLoadInDom(NULL, 0, type, &lid, &ibuf); + sts = __pmLogLoadInDom(NULL, rlen, type, &lid, &ibuf); if (sts < 0) { fprintf(stderr, "write_rec: __pmLogLoadInDom(type=%s (%d)): failed: %s\n", __pmLogMetaTypeStr(type), type, pmErrStr(sts)); } --- a/src/pmlogrewrite/indom.c +++ b/src/pmlogrewrite/indom.c @@ -223,9 +223,10 @@ } else { __int32_t *buf; + int len = htonl(hdr->len); /* buffer for __pmLogLoadInDom has to start AFTER the header */ buf = &recbuf[2]; - sts = __pmLogLoadInDom(NULL, 0, type, lidp, &buf); + sts = __pmLogLoadInDom(NULL, len, type, lidp, &buf); if (sts < 0) { fprintf(stderr, "_pmUnpackInDom: __pmLogLoadInDom(type=%d): failed: %s\n", type, pmErrStr(sts)); abandon(); ++++++ pcp-CVE-2026-16530-2.patch ++++++ References: CVE-2026-16530 Description: libpcp: fix OOB read in __pmLogLoadLabelSet (CWE-125) __pmLogLoadLabelSet() reads timestamp, type, ident, and nsets fields from tbuf at sequential offsets without checking that rlen is large enough to contain them. When the pmproxy logger servlet delivers a TYPE_LABEL record with hdr.len=13 (minimum accepted by the dispatcher), rlen=1 and the function reads 20-24 bytes from a 1-byte buffer. . Fix: add minimum-length guard at the top of __pmLogLoadLabelSet() using LABELSET_V3_MINRLEN / LABELSET_V2_MINRLEN macros derived from the on-disk __pmExtLabelSet_v3/v2 struct sizes (minus the len+type header that rlen excludes). . Test coverage will be added in a consolidated pducrash.c extension covering vulns 9-13. . Reported-by: Francisco Alisson Bezerra, TIM Security Red Team Reported-by: Lucas Gabriel Alves, TIM Security Red Team Reported-by: Massimiliano Brolli, TIM Security Red Team . Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> . Author: Nathan Scott <[email protected]> Origin: upstream, https://github.com/performancecopilot/pcp/commit/ccd1bb167934b5e4a60d39bacf25e12e0a2decb4 Last-Update: 2026-07-25 Backport-Note: Backported to PCP 6.3.8 (applies unchanged). No libpcp3 tree in 6.3.8, so that mirror hunk is dropped. --- a/src/libpcp/src/e_labels.c +++ b/src/libpcp/src/e_labels.c @@ -56,6 +56,10 @@ /* will be expanded if nsets > 0 */ } __pmExtLabelSet_v2; +/* Minimum rlen (record body without len+type header) to read fixed fields */ +#define LABELSET_V3_MINRLEN (sizeof(__pmExtLabelSet_v3) - 2 * sizeof(__int32_t)) +#define LABELSET_V2_MINRLEN (sizeof(__pmExtLabelSet_v2) - 2 * sizeof(__int32_t)) + /* * pack a set of labels into a physical metadata record * - lcp required to provide archive version @@ -215,6 +219,23 @@ *nsetsp = 0; *labelsetsp = NULL; + if (rtype == TYPE_LABEL_V2) { + if (rlen < (int)LABELSET_V2_MINRLEN) { + if (pmDebugOptions.logmeta) + fprintf(stderr, "%s: v2 rlen=%d too small (min=%d)\n", + __FUNCTION__, rlen, (int)LABELSET_V2_MINRLEN); + return PM_ERR_LOGREC; + } + } + else { + if (rlen < (int)LABELSET_V3_MINRLEN) { + if (pmDebugOptions.logmeta) + fprintf(stderr, "%s: v3 rlen=%d too small (min=%d)\n", + __FUNCTION__, rlen, (int)LABELSET_V3_MINRLEN); + return PM_ERR_LOGREC; + } + } + k = 0; if (rtype == TYPE_LABEL_V2) { __pmLoadTimeval((__int32_t *)&tbuf[k], stamp); ++++++ pcp-CVE-2026-16530-3.patch ++++++ References: CVE-2026-16530 Description: libpcp_web: add numinst overflow check in pmDiscoverDecodeMetaInDom (CWE-125/190) Defense-in-depth for the __pmLogLoadInDom streaming path fix (commit 1). When __pmLogLoadInDom is called with acp=NULL from the pmproxy discover code, a garbage numinst value read from a too-small buffer could be passed to calloc(numinst, sizeof(char *)), causing an integer overflow in the allocation size. . Add explicit validation that numinst > 0 and does not overflow SIZE_MAX before the calloc in pmDiscoverDecodeMetaInDom(). The primary fix (rlen and numinst validation in __pmLogLoadInDom itself) prevents this value from being garbage in the first place. . Reported-by: Francisco Alisson Bezerra, TIM Security Red Team Reported-by: Lucas Gabriel Alves, TIM Security Red Team Reported-by: Massimiliano Brolli, TIM Security Red Team . Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> . Author: Nathan Scott <[email protected]> Origin: upstream, https://github.com/performancecopilot/pcp/commit/7f42013d33a6717a45e8f9ac9e7dfcf76ee6384f Last-Update: 2026-07-25 Backport-Note: Backported to PCP 6.3.8. Adapted to the 6.3.8 allocation site (malloc form) in pmDiscoverDecodeMetaInDom(). Defence-in-depth: the read side is already closed by patch 01. The remote vector (pmproxy logger servlet) is absent in 6.3.8. --- a/src/libpcp_web/src/discover.c +++ b/src/libpcp_web/src/discover.c @@ -1954,6 +1954,11 @@ */ char **namelist; int i; + if (lid.numinst <= 0 || + (size_t)lid.numinst > SIZE_MAX / sizeof(char *)) { + __pmFreeLogInDom(&lid); + return -EINVAL; + } namelist = (char **)malloc(lid.numinst * sizeof(char *)); if (namelist == NULL) { pmNoMem("pmDiscoverDecodeMetaInDom", lid.numinst * sizeof(char *), PM_FATAL_ERR); ++++++ pcp-CVE-2026-16530-4.patch ++++++ References: CVE-2026-16530 Description: libpcp: fix OOB read in __pmDecodeLabel via negative jsonoff (CWE-125) The bounds check 'if (pdu_length < jsonoff + jsonlen)' uses signed arithmetic. When jsonoff is negative (high bit set after ntohl) and jsonlen is a small positive value, their sum wraps to a small positive number, passing the check. The subsequent memcpy reads from label_pdu + jsonoff, an address before the start of the PDU buffer. . Fix: reject negative jsonoff and jsonlen explicitly, then use unsigned (size_t) arithmetic for the bounds check to prevent signed wraparound. . Reported-by: Francisco Alisson Bezerra, TIM Security Red Team Reported-by: Lucas Gabriel Alves, TIM Security Red Team Reported-by: Massimiliano Brolli, TIM Security Red Team . Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> . Author: Nathan Scott <[email protected]> Origin: upstream, https://github.com/performancecopilot/pcp/commit/e512482e7d33aa6c46fbe569d0de7951c0fcec75 Last-Update: 2026-07-25 Backport-Note: Backported to PCP 6.3.8 (applies unchanged). No libpcp3 tree in 6.3.8, so that mirror hunk is dropped. --- a/src/libpcp/src/p_label.c +++ b/src/libpcp/src/p_label.c @@ -446,10 +446,11 @@ } /* check JSON content fits within the PDU bounds */ - if (pdu_length < jsonoff + jsonlen) { + if (jsonoff < 0 || jsonlen < 0 || + (size_t)jsonoff + (size_t)jsonlen > pdu_length) { if (pmDebugOptions.pdu) { - fprintf(stderr, "__pmDecodeLabel: PM_ERR_IPC: labelset[%d] pdu_length %d < jsonoff %d + jsonlen %d\n", - i, (int)pdu_length, jsonoff, jsonlen); + fprintf(stderr, "%s: PM_ERR_IPC: labelset[%d] pdu_length %d < jsonoff %d + jsonlen %d\n", + __FUNCTION__, i, (int)pdu_length, jsonoff, jsonlen); } goto corrupt; } ++++++ pcp-CVE-2026-16530-5.patch ++++++ References: CVE-2026-16530 Description: libpcp: fix OOB read in __pmDecodeLogStatus (CWE-125) For each of the six length-prefixed string fields in PDU_LOG_STATUS (hostname, fqdn, timezone, zoneinfo for both pmcd and pmlogger), strdup(p) was called before verifying that p+len falls within the PDU buffer. strdup reads until a null byte, so a non-null-terminated string causes reads past the PDU boundary into adjacent heap memory. . Fix: for all six fields, move the p+len > pduend bounds check before the string copy, and replace strdup(p) with strndup(p, len) to respect the declared length regardless of null terminator presence. . Reported-by: Francisco Alisson Bezerra, TIM Security Red Team Reported-by: Lucas Gabriel Alves, TIM Security Red Team Reported-by: Massimiliano Brolli, TIM Security Red Team . Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> . Author: Nathan Scott <[email protected]> Origin: upstream, https://github.com/performancecopilot/pcp/commit/5366a546d66c0a085958dd84b08b3e1ca165f7ab Last-Update: 2026-07-25 Backport-Note: Backported to PCP 6.3.8 (applies unchanged). No libpcp3 tree in 6.3.8, so that mirror hunk is dropped. --- a/src/libpcp/src/p_lstatus.c +++ b/src/libpcp/src/p_lstatus.c @@ -265,157 +265,157 @@ if (len == 0) lsp->pmcd.hostname = NULL; else { - if (len > PM_MAX_HOSTNAMELEN) { - /* cannot be longer than hostname in archive label */ + if (len < 0 || len > PM_MAX_HOSTNAMELEN) { + /* cannot be negative or longer than hostname in archive label */ if (pmDebugOptions.pmlc || pmDebugOptions.pdu) - fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: pmcd.hostname too long (%d)\n", len); + fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: invalid pmcd.hostname (%d)\n", len); __pmFreeLogStatus(lsp, 1); return PM_ERR_IPC; } - if ((lsp->pmcd.hostname = strdup(p)) == NULL) { + if (p + len > pduend) { + if (pmDebugOptions.pmlc || pmDebugOptions.pdu) + fprintf(stderr, "%s: PM_ERR_IPC: pmcd.hostname data[%ld] > PDU len (%d)\n", + __FUNCTION__, (long)(p + len - (char *)&pp->data[0]), pp->hdr.len); + __pmFreeLogStatus(lsp, 1); + return PM_ERR_IPC; + } + if ((lsp->pmcd.hostname = strndup(p, len)) == NULL) { sts = -oserror(); pmNoMem("__pmDecodeLogStatus: pmcd.hostname", len, PM_RECOV_ERR); __pmFreeLogStatus(lsp, 1); return sts; } p += len; - if (p > pduend) { - if (pmDebugOptions.pmlc || pmDebugOptions.pdu) - fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: pmcd.hostname data[%ld] > PDU len (%d)\n", - (long)(p - (char *)&pp->data[0]), pp->hdr.len); - __pmFreeLogStatus(lsp, 1); - return PM_ERR_IPC; - } } len = ntohl(pp->pmcd_fqdn_len); if (len == 0) lsp->pmcd.fqdn = NULL; else { - if (len > PM_MAX_HOSTNAMELEN) { - /* cannot be longer than hostname in archive label */ + if (len < 0 || len > PM_MAX_HOSTNAMELEN) { + /* cannot be negative or longer than hostname in archive label */ if (pmDebugOptions.pmlc || pmDebugOptions.pdu) - fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: pmcd.fqdn too long (%d)\n", len); + fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: invalid pmcd.fqdn (%d)\n", len); __pmFreeLogStatus(lsp, 1); return PM_ERR_IPC; } - if ((lsp->pmcd.fqdn = strdup(p)) == NULL) { + if (p + len > pduend) { + if (pmDebugOptions.pmlc || pmDebugOptions.pdu) + fprintf(stderr, "%s: PM_ERR_IPC: pmcd.fqdn data[%ld] > PDU len (%d)\n", + __FUNCTION__, (long)(p + len - (char *)&pp->data[0]), pp->hdr.len); + __pmFreeLogStatus(lsp, 1); + return PM_ERR_IPC; + } + if ((lsp->pmcd.fqdn = strndup(p, len)) == NULL) { sts = -oserror(); pmNoMem("__pmDecodeLogStatus: pmcd.fqdn", len, PM_RECOV_ERR); __pmFreeLogStatus(lsp, 1); return sts; } p += len; - if (p > pduend) { - if (pmDebugOptions.pmlc || pmDebugOptions.pdu) - fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: pmcd.fqdn data[%ld] > PDU len (%d)\n", - (long)(p - (char *)&pp->data[0]), pp->hdr.len); - __pmFreeLogStatus(lsp, 1); - return PM_ERR_IPC; - } } len = ntohl(pp->pmcd_timezone_len); if (len == 0) lsp->pmcd.timezone = NULL; else { - if (len > PM_MAX_TIMEZONELEN) { - /* cannot be longer than timezone in archive label */ + if (len < 0 || len > PM_MAX_TIMEZONELEN) { + /* cannot be negative or longer than timezone in archive label */ if (pmDebugOptions.pmlc || pmDebugOptions.pdu) - fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: pmcd.timezone too long (%d)\n", len); + fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: invalid pmcd.timezone (%d)\n", len); __pmFreeLogStatus(lsp, 1); return PM_ERR_IPC; } - if ((lsp->pmcd.timezone = strdup(p)) == NULL) { + if (p + len > pduend) { + if (pmDebugOptions.pmlc || pmDebugOptions.pdu) + fprintf(stderr, "%s: PM_ERR_IPC: pmcd.timezone data[%ld] > PDU len (%d)\n", + __FUNCTION__, (long)(p + len - (char *)&pp->data[0]), pp->hdr.len); + __pmFreeLogStatus(lsp, 1); + return PM_ERR_IPC; + } + if ((lsp->pmcd.timezone = strndup(p, len)) == NULL) { sts = -oserror(); pmNoMem("__pmDecodeLogStatus: pmcd.timezone", len, PM_RECOV_ERR); __pmFreeLogStatus(lsp, 1); return sts; } p += len; - if (p > pduend) { - if (pmDebugOptions.pmlc || pmDebugOptions.pdu) - fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: pmcd.timezone data[%ld] > PDU len (%d)\n", - (long)(p - (char *)&pp->data[0]), pp->hdr.len); - __pmFreeLogStatus(lsp, 1); - return PM_ERR_IPC; - } } len = ntohl(pp->pmcd_zoneinfo_len); if (len == 0) lsp->pmcd.zoneinfo = NULL; else { - if (len > PM_MAX_ZONEINFOLEN) { - /* cannot be longer than zoneinfo in archive label */ + if (len < 0 || len > PM_MAX_ZONEINFOLEN) { + /* cannot be negative or longer than zoneinfo in archive label */ if (pmDebugOptions.pmlc || pmDebugOptions.pdu) - fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: pmcd.zoneinfo too long (%d)\n", len); + fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: invalid pmcd.zoneinfo (%d)\n", len); __pmFreeLogStatus(lsp, 1); return PM_ERR_IPC; } - if ((lsp->pmcd.zoneinfo = strdup(p)) == NULL) { + if (p + len > pduend) { + if (pmDebugOptions.pmlc || pmDebugOptions.pdu) + fprintf(stderr, "%s: PM_ERR_IPC: pmcd.zoneinfo data[%ld] > PDU len (%d)\n", + __FUNCTION__, (long)(p + len - (char *)&pp->data[0]), pp->hdr.len); + __pmFreeLogStatus(lsp, 1); + return PM_ERR_IPC; + } + if ((lsp->pmcd.zoneinfo = strndup(p, len)) == NULL) { sts = -oserror(); pmNoMem("__pmDecodeLogStatus: pmcd.zoneinfo", len, PM_RECOV_ERR); __pmFreeLogStatus(lsp, 1); return sts; } p += len; - if (p > pduend) { - if (pmDebugOptions.pmlc || pmDebugOptions.pdu) - fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: pmcd.zoneinfo data[%ld] > PDU len (%d)\n", - (long)(p - (char *)&pp->data[0]), pp->hdr.len); - __pmFreeLogStatus(lsp, 1); - return PM_ERR_IPC; - } } len = ntohl(pp->pmlogger_timezone_len); if (len == 0) lsp->pmlogger.timezone = NULL; else { if (len > PM_MAX_TIMEZONELEN) { - /* cannot be longer than timezone in archive label */ + /* cannot be negative or longer than timezone in archive label */ if (pmDebugOptions.pmlc || pmDebugOptions.pdu) fprintf(stderr, "__pmDecodeLogStatusPM_ERR_IPC: : pmlogger.timezone too long (%d)\n", len); __pmFreeLogStatus(lsp, 1); return PM_ERR_IPC; } - if ((lsp->pmlogger.timezone = strdup(p)) == NULL) { + if (p + len > pduend) { + if (pmDebugOptions.pmlc || pmDebugOptions.pdu) + fprintf(stderr, "%s: PM_ERR_IPC: pmlogger.timezone data[%ld] > PDU len (%d)\n", + __FUNCTION__, (long)(p + len - (char *)&pp->data[0]), pp->hdr.len); + __pmFreeLogStatus(lsp, 1); + return PM_ERR_IPC; + } + if ((lsp->pmlogger.timezone = strndup(p, len)) == NULL) { sts = -oserror(); pmNoMem("__pmDecodeLogStatus: pmlogger.timezone", len, PM_RECOV_ERR); __pmFreeLogStatus(lsp, 1); return sts; } p += len; - if (p > pduend) { - if (pmDebugOptions.pmlc || pmDebugOptions.pdu) - fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: pmlogger.timezone data[%ld] > PDU len (%d)\n", - (long)(p - (char *)&pp->data[0]), pp->hdr.len); - __pmFreeLogStatus(lsp, 1); - return PM_ERR_IPC; - } } len = ntohl(pp->pmlogger_zoneinfo_len); if (len == 0) lsp->pmlogger.zoneinfo = NULL; else { - if (len > PM_MAX_ZONEINFOLEN) { - /* cannot be longer than zoneinfo in archive label */ + if (len < 0 || len > PM_MAX_ZONEINFOLEN) { + /* cannot be negative or longer than zoneinfo in archive label */ if (pmDebugOptions.pmlc || pmDebugOptions.pdu) - fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: pmlogger.zoneinfo too long (%d)\n", len); + fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: invalid pmlogger.zoneinfo (%d)\n", len); + __pmFreeLogStatus(lsp, 1); + return PM_ERR_IPC; + } + if (p + len > pduend) { + if (pmDebugOptions.pmlc || pmDebugOptions.pdu) + fprintf(stderr, "%s: PM_ERR_IPC: pmlogger.zoneinfo data[%ld] > PDU len (%d)\n", + __FUNCTION__, (long)(p + len - (char *)&pp->data[0]), pp->hdr.len); __pmFreeLogStatus(lsp, 1); return PM_ERR_IPC; } - if ((lsp->pmlogger.zoneinfo = strdup(p)) == NULL) { + if ((lsp->pmlogger.zoneinfo = strndup(p, len)) == NULL) { sts = -oserror(); pmNoMem("__pmDecodeLogStatus: pmlogger.zoneinfo", len, PM_RECOV_ERR); __pmFreeLogStatus(lsp, 1); return sts; } p += len; - if (p > pduend) { - if (pmDebugOptions.pmlc || pmDebugOptions.pdu) - fprintf(stderr, "__pmDecodeLogStatus: PM_ERR_IPC: pmlogger.zoneinfo data[%ld] > PDU len (%d)\n", - (long)(p - (char *)&pp->data[0]), pp->hdr.len); - __pmFreeLogStatus(lsp, 1); - return PM_ERR_IPC; - } } } else if (version == LOG_PDU_VERSION2) { ++++++ pcp-CVE-2026-16530-6.patch ++++++ References: CVE-2026-16530 Description: libpcp: fix OOB read in __pmDecodeInstance (CWE-125/195) The __pmDecodeInstance() loop advances ip by the PDU alignment-padded entry size after each instance. When namelen % 4 != 0, the padding advance can push ip past pdu_end. The existing bounds check casts the pointer difference to size_t: (size_t)(pdu_end - (char *)ip). When ip is past pdu_end, this produces a negative ptrdiff_t that wraps to a very large size_t, causing both bounds checks to silently pass. Execution falls through to memcpy reading past the PDU buffer. . Fix: add an explicit signed pointer guard at the top of each loop iteration — if ((char *)ip >= pdu_end) — before the size_t cast. This ensures the subsequent unsigned comparison is always valid. . Reported-by: Francisco Alisson Bezerra, TIM Security Red Team Reported-by: Lucas Gabriel Alves, TIM Security Red Team Reported-by: Massimiliano Brolli, TIM Security Red Team . Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> . Author: Nathan Scott <[email protected]> Origin: upstream, https://github.com/performancecopilot/pcp/commit/b743fc58795bd3667b78e3b0759251fef27a983b Last-Update: 2026-07-25 Backport-Note: Backported to PCP 6.3.8 (applies unchanged). No libpcp3 tree in 6.3.8, so that mirror hunk is dropped. --- a/src/libpcp/src/p_instance.c +++ b/src/libpcp/src/p_instance.c @@ -290,6 +290,13 @@ pdu_used = (char *)&pp->rest[0]; for (i = j = 0; i < res->numinst; i++) { ip = (instlist_t *)&pp->rest[j/sizeof(__pmPDU)]; + if ((char *)ip >= pdu_end) { + if (pmDebugOptions.pdu) + fprintf(stderr, "%s: PM_ERR_IPC: inst[%d] ip past pdu_end\n", + __FUNCTION__, i); + sts = PM_ERR_IPC; + goto badsts; + } if (sizeof(instlist_t) - sizeof(ip->name) > (size_t)(pdu_end - (char *)ip)) { if (pmDebugOptions.pdu) { fprintf(stderr, "__pmDecodeInstance: PM_ERR_IPC: sizeof(instlist_t) %d - sizeof(name) %d > remainder %d\n", ++++++ pcp-CVE-2026-16531.patch ++++++ References: CVE-2026-16531 Description: libpcp_web: fix path traversal via hostname in logger servlet (CWE-22) The pmproxy logger servlet (POST /logger/label) accepts a binary __pmLogLabel PDU and uses the hostname field directly in path construction without sanitization. An attacker can supply hostname='../../../../tmp/target' causing pmproxy to create .meta and .index files at arbitrary paths writable by the pcp user. . Fix: add check_hostname() allowlist check — only alphanumeric, hyphen, dot, and underscore characters are permitted (per RFC 952/1123 plus underscore for real-world compatibility). Leading dots are rejected to prevent relative path components. Invalid hostnames are rejected with -EINVAL before any path construction occurs. . Add qa/2106 verifying that a label with a path-traversal hostname is rejected and no files are created outside the log directory. . Reported-by: Francisco Alisson Bezerra, TIM Security Red Team Reported-by: Lucas Gabriel Alves, TIM Security Red Team Reported-by: Massimiliano Brolli, TIM Security Red Team . Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> . Author: Nathan Scott <[email protected]> Origin: upstream, https://github.com/performancecopilot/pcp/commit/dd6ed05f143f97e00198cb4b0180c6375d758cbe https://github.com/performancecopilot/pcp/commit/f86c0f4cdac8df0b6fb05f60872f6f29bbc96429 Last-Update: 2026-07-25 Backport-Note: PARTIAL / hardening-only for PCP 6.3.8. The pmproxy logger servlet (loggroup.c) does not exist in 6.3.8 (logpush is a post-6.3.8 feature), so the reported path-traversal vector is not present. Only the timezone/zoneinfo validation in pmNewZone() (tz.c) is carried, as defence-in-depth. --- a/src/libpcp/src/tz.c +++ b/src/libpcp/src/tz.c @@ -25,6 +25,7 @@ * lock initialization in pmNewContext(). */ +#include <ctype.h> #include "pmapi.h" #include "libpcp.h" #include "sha256.h" @@ -573,6 +574,22 @@ return 0; } +static int +valid_tz(const char *tz) +{ + const char *p; + + if (tz == NULL || tz[0] == '\0' || tz[0] == '/') + return 0; + for (p = tz; *p; p++) { + if (!isalnum((unsigned char)*p) && strchr("/_+-.:,\"'", *p) == NULL) + return 0; + } + if (strstr(tz, "..") != NULL) + return 0; + return 1; +} + int pmNewZone(const char *tz) { @@ -580,6 +597,13 @@ int hack = 0; int sts; + if (!valid_tz(tz)) { + if (pmDebugOptions.context) + fprintf(stderr, "%s: rejecting unsafe timezone: %s\n", + __FUNCTION__, tz ? tz : "(null)"); + return -EINVAL; + } + PM_LOCK(__pmLock_extcall); len = (int)strlen(tz); ++++++ pcp-bsc-1272928.patch ++++++ References: no CVE assigned (runs as the invoking user; no privilege boundary crossed) Description: pmieconf: fix command injection via $HOME and -f (CWE-78) The write_pmiefile() function constructed a shell command via pmsprintf("/bin/mkdir -p %s", fname) and passed it to system(). The fname value derives from either $HOME or the -f command-line argument without sanitization, enabling command injection through shell metacharacters in the path. . Fix: replace system("/bin/mkdir -p ...") with __pmMakePath() which creates directories recursively using mkdir() syscalls directly, with no shell involvement. . Add qa/2103 verifying that legitimate directory creation works and that shell metacharacters in -f and $HOME paths do not result in command execution. . Reported-by: Francisco Alisson Bezerra, TIM Security Red Team Reported-by: Lucas Gabriel Alves, TIM Security Red Team Reported-by: Massimiliano Brolli, TIM Security Red Team . Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> . Author: Nathan Scott <[email protected]> Origin: upstream, https://github.com/performancecopilot/pcp/commit/cdc9676ab60176476a23f323c145d8d0fc71e165 Last-Update: 2026-07-25 Backport-Note: Backported to PCP 6.3.8 (applies unchanged). --- a/src/pmieconf/rules.c +++ b/src/pmieconf/rules.c @@ -1805,7 +1805,6 @@ { time_t now = time(NULL); char *p, *msg = NULL; - char buf[MAXPATHLEN+10]; char *fname = get_pmiefile(); FILE *fp; int i; @@ -1816,9 +1815,8 @@ *p = '\0'; /* p is the dirname of fname */ if (stat(fname, &sbuf) < 0) { - pmsprintf(buf, sizeof(buf), "/bin/mkdir -p %s", fname); - if (system(buf) < 0) { - pmsprintf(errmsg, sizeof(errmsg), "failed to create directory \"%s\"", p); + if (__pmMakePath(fname, 0755) < 0) { + pmsprintf(errmsg, sizeof(errmsg), "failed to create directory \"%s\"", fname); return errmsg; } } ++++++ pcp-bsc-1272930.patch ++++++ References: no CVE assigned (runs as the invoking user; no privilege boundary crossed) Description: pmlogmv: fix command injection in pmlogcp/pmlogmv (CWE-78) The do_link() function used system("cp src dst") to copy archive files when link() fails with EXDEV. The source filename was not validated by check_name() and was embedded directly into the shell command, enabling command injection via crafted archive filenames. The do_checksum() function similarly used system() for command detection and popen() for checksum execution. . Fix: - Replace system("cp ...") with copy_file() using open/read/write syscalls directly, eliminating shell involvement entirely - Replace system("if which ...") checksum detection with access() checks - Replace popen("md5sum <file") with __pmProcessPipe() which uses execvp() internally, passing filenames as argv not shell words - Expand check_name() blocklist to include backtick, braces, backslash, bang, newline and tab (defense-in-depth, no longer the security boundary) - Apply check_name() to source names as well as destination names - Add qa/2102 verifying metacharacter rejection and normal copy . Reported-by: Francisco Alisson Bezerra, TIM Security Red Team Reported-by: Lucas Gabriel Alves, TIM Security Red Team Reported-by: Massimiliano Brolli, TIM Security Red Team . Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> . Author: Nathan Scott <[email protected]> Origin: upstream, https://github.com/performancecopilot/pcp/commit/dc73ec0f579028620e408114427d27ad0afd24a8 https://github.com/performancecopilot/pcp/commit/3ae202a08b680944add196a30fe5fdc6f247e711 Last-Update: 2026-07-25 Backport-Note: Minimal backport for PCP 6.3.8. pmlogmv.c differs structurally from the 7.x tree; rather than the full open/read/write copy + __pmProcessPipe rewrite, this closes the injection by validating the source name (oldname) as well as the destination and by hardening the check_name() blocklist (adds backtick, braces, backslash). This report item carries no CVE - pmlogmv runs as the invoking user, crossing no privilege boundary. --- a/src/pmlogmv/pmlogmv.c +++ b/src/pmlogmv/pmlogmv.c @@ -73,7 +73,7 @@ static int check_name(char *name) { - char *meta = " $?*[(|;&<>"; + char *meta = " $?*[(|;&<>`{}\\"; char *p; for (p = meta; *p; p++) { @@ -481,6 +481,11 @@ /* error reported in check_name() */ exit(1); } + + if (!force && check_name(oldname) < 0) { + /* error reported in check_name() */ + exit(1); + } if (setup_sufftab() < 0) { /* error reported in setup_sufftab() */
