diff --git a/pcp-RHEL-213747.patch b/pcp-RHEL-213747.patch new file mode 100644 index 0000000..ef981ae --- /dev/null +++ b/pcp-RHEL-213747.patch @@ -0,0 +1,317 @@ +From 1f232730588b2f0bf8c6777bb478635aba6c6dc5 Mon Sep 17 00:00:00 2001 +From: Nathan Scott +Date: Thu, 2 Jul 2026 15:10:16 +1000 +Subject: [PATCH 1/3] 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) +--- + qa/513.out | 8 +++++ + qa/src/pducrash.c | 77 ++++++++++++++++++++++++++++++++++++++++ + src/libpcp/src/e_indom.c | 63 ++++++++++++++++++++------------ + 3 files changed, 126 insertions(+), 22 deletions(-) + +diff --git a/qa/513.out b/qa/513.out +index 5894ca159..7ccaa1f37 100644 +--- a/qa/513.out ++++ b/qa/513.out +@@ -269,6 +269,14 @@ QA output created by 513 + __pmDecodeDescs: sts = -12366 (IPC protocol failure) + [descs] checking access beyond extended buffer + __pmDecodeDescs: sts = -12366 (IPC protocol failure) ++[log_indom] checking rlen too small for v3 header ++ __pmLogLoadInDom: sts = -12373 (Corrupted record in a PCP archive) ++[log_indom] checking rlen too small for v2 header ++ __pmLogLoadInDom: sts = -12373 (Corrupted record in a PCP archive) ++[log_indom] checking numinst larger than rlen allows ++ __pmLogLoadInDom: sts = -12373 (Corrupted record in a PCP archive) ++[log_indom] checking out-of-range stridx with acp==NULL ++ __pmLogLoadInDom: sts = -12373 (Corrupted record in a PCP archive) + === filtered valgrind report === + Memcheck, a memory error detector + Command: src/pducrash +diff --git a/qa/src/pducrash.c b/qa/src/pducrash.c +index 26dbd2414..76e1e0ce7 100644 +--- a/qa/src/pducrash.c ++++ b/qa/src/pducrash.c +@@ -1627,6 +1627,82 @@ decode_trace_data(const char *name) + free(trace_data); + } + ++/* ++ * Test __pmLogLoadInDom with acp==NULL (streaming path used by pmproxy). ++ * The on-disk v3 record layout (after len+type header) is: ++ * sec[2], nsec, indom, numinst, instlist[numinst], ++ * stridx[numinst], name_strings ++ * rlen is the body length excluding the 2-word header. ++ */ ++static void ++decode_log_indom(const char *name) ++{ ++ int sts; ++ __pmLogInDom lid; ++ __int32_t *buf; ++ ++ /* TYPE_INDOM (v3): rlen too small for fixed header fields */ ++ fprintf(stderr, "[%s] checking rlen too small for v3 header\n", name); ++ { ++ __int32_t tiny[1]; ++ memset(&lid, 0, sizeof(lid)); ++ memset(tiny, 0, sizeof(tiny)); ++ buf = tiny; ++ sts = __pmLogLoadInDom(NULL, 4, TYPE_INDOM, &lid, &buf); ++ fprintf(stderr, " __pmLogLoadInDom: sts = %d (%s)\n", sts, pmErrStr(sts)); ++ } ++ ++ /* TYPE_INDOM_V2: rlen too small for fixed header fields */ ++ fprintf(stderr, "[%s] checking rlen too small for v2 header\n", name); ++ { ++ __int32_t tiny[1]; ++ memset(&lid, 0, sizeof(lid)); ++ memset(tiny, 0, sizeof(tiny)); ++ buf = tiny; ++ sts = __pmLogLoadInDom(NULL, 4, TYPE_INDOM_V2, &lid, &buf); ++ fprintf(stderr, " __pmLogLoadInDom: sts = %d (%s)\n", sts, pmErrStr(sts)); ++ } ++ ++ /* TYPE_INDOM (v3): numinst too large for rlen */ ++ fprintf(stderr, "[%s] checking numinst larger than rlen allows\n", name); ++ { ++ /* v3 fixed fields: sec[2]+nsec+indom+numinst = 5 words (20 bytes) */ ++ __int32_t rec[7]; /* room for header + fixed fields */ ++ memset(&lid, 0, sizeof(lid)); ++ memset(rec, 0, sizeof(rec)); ++ rec[0] = htonl(sizeof(rec)); /* len (not used but for completeness) */ ++ rec[1] = htonl(TYPE_INDOM); /* type */ ++ /* sec[2], nsec, indom are zero */ ++ rec[6] = htonl(999999); /* numinst - way too large */ ++ buf = &rec[2]; /* skip len+type, as __pmLogLoadInDom expects */ ++ sts = __pmLogLoadInDom(NULL, 20, TYPE_INDOM, &lid, &buf); ++ fprintf(stderr, " __pmLogLoadInDom: sts = %d (%s)\n", sts, pmErrStr(sts)); ++ } ++ ++ /* TYPE_INDOM (v3): valid numinst=1 but stridx out of range */ ++ fprintf(stderr, "[%s] checking out-of-range stridx with acp==NULL\n", name); ++ { ++ /* ++ * Layout after len+type: sec[2], nsec, indom, numinst, ++ * instlist[1], stridx[1], (no string data) ++ * That's 5 + 1 + 1 = 7 words = 28 bytes of body. ++ */ ++ __int32_t rec[9]; /* 2 (header) + 7 (body) */ ++ memset(&lid, 0, sizeof(lid)); ++ memset(rec, 0, sizeof(rec)); ++ rec[0] = htonl(sizeof(rec)); /* len */ ++ rec[1] = htonl(TYPE_INDOM); /* type */ ++ /* sec[2], nsec, indom are zero */ ++ rec[6] = htonl(1); /* numinst */ ++ rec[7] = htonl(0); /* instlist[0] = 0 */ ++ rec[8] = htonl(0x7FFFFFFF); /* stridx[0] = huge OOB index */ ++ buf = &rec[2]; ++ sts = __pmLogLoadInDom(NULL, 28, TYPE_INDOM, &lid, &buf); ++ fprintf(stderr, " __pmLogLoadInDom: sts = %d (%s)\n", sts, pmErrStr(sts)); ++ if (sts >= 0) __pmFreeLogInDom(&lid); ++ } ++} ++ + typedef void (*decode_t)(const char *); + + struct pdu { +@@ -1661,6 +1737,7 @@ struct pdu { + { "highres_result", decode_highres_result }, + { "desc_ids", decode_desc_ids }, + { "descs", decode_descs }, ++ { "log_indom", decode_log_indom }, + }; + + int +diff --git a/src/libpcp/src/e_indom.c b/src/libpcp/src/e_indom.c +index 8036fc6cc..de44751f0 100644 +--- a/src/libpcp/src/e_indom.c ++++ b/src/libpcp/src/e_indom.c +@@ -51,6 +51,10 @@ typedef struct { + /* 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 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":3", PM_FAULT_ALLOC); + + 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 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":4", PM_FAULT_ALLOC); + } + 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) + +From d16c0cbdc680a539a64433716d6888003e086c81 Mon Sep 17 00:00:00 2001 +From: Ken McDonell +Date: Sat, 11 Jul 2026 07:29:38 +1000 +Subject: [PATCH 2/3] src/pmlogrewrite/indom.c: fix call to __pmLogLoadInDom() + +Turns out pmlogrewrite was *using* the acp == NULL guard to dodge the +rlen test and calling with rlen == 0 (this was correct as the *same* record +had previously been processed elsewhere with the correct rlen so the +buffer was know to be good). + +Fix involves re-extracting the correct record length and calling +__pmLogLoadInDom() with rlen != 0. +--- + src/pmlogrewrite/indom.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/pmlogrewrite/indom.c b/src/pmlogrewrite/indom.c +index eeef646fc..0300aa9a2 100644 +--- a/src/pmlogrewrite/indom.c ++++ b/src/pmlogrewrite/indom.c +@@ -223,9 +223,10 @@ _pmUnpackInDom(__int32_t *recbuf, __pmLogInDom *lidp) + } + 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(); + +From b07e33d85a93a66b63e2c4e579100d0ae999a622 Mon Sep 17 00:00:00 2001 +From: Ken McDonell +Date: Sat, 11 Jul 2026 08:12:58 +1000 +Subject: [PATCH 3/3] src/pmlogextract/pmlogextract.c: fix call to + __pmLogLoadInDom() + +pmlogextract was also *using* the acp == NULL guard to dodge the rlen +test and calling with rlen == 0. + +Fix involves using the correct record length and calling +__pmLogLoadInDom() with rlen != 0. +--- + src/pmlogextract/pmlogextract.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/pmlogextract/pmlogextract.c b/src/pmlogextract/pmlogextract.c +index 93a3d3988..eac7bf610 100644 +--- a/src/pmlogextract/pmlogextract.c ++++ b/src/pmlogextract/pmlogextract.c +@@ -1300,7 +1300,7 @@ write_rec(reclist_t *rec) + 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)); + } diff --git a/pcp.spec b/pcp.spec index 1d5afb5..7745940 100644 --- a/pcp.spec +++ b/pcp.spec @@ -1,6 +1,6 @@ Name: pcp Version: 6.3.7 -Release: 9%{?dist} +Release: 10%{?dist} Summary: System-level performance monitoring and performance management License: GPL-2.0-or-later AND LGPL-2.1-or-later AND CC-BY-3.0 URL: https://pcp.io @@ -26,6 +26,11 @@ Patch14: pmda-openmetrics-performance.patch Patch15: pcp-RHEL-133548.patch Patch16: memory-leaks.patch Patch17: ds389.patch +# https://issues.redhat.com/browse/RHEL-213747 +# https://github.com/performancecopilot/pcp/commit/ec81e2b35c7dc19a69406d2712d1b9904ac22112 +# https://github.com/performancecopilot/pcp/commit/b72da5135df7fb08ca432db1e2db24478f43aea2 +# https://github.com/performancecopilot/pcp/commit/edfd909edac8ad4762aa150e330f92211df98c1c +Patch18: pcp-RHEL-213747.patch %if 0%{?fedora} >= 40 || 0%{?rhel} >= 10 ExcludeArch: %{ix86} @@ -3636,6 +3641,9 @@ fi %files zeroconf -f pcp-zeroconf-files.rpm %changelog +* Thu Jul 30 2026 RHEL Packaging Agent - 6.3.7-10 +- Fix arbitrary pointer dereference in __pmLogLoadInDom (CVE-2026-16530) + * Thu Apr 2 2026 Jan Kurik - 6.3.7-9 - Backported ds389 patch from the upstream (pcp-7.1.1)