Fix CVE-2026-16530: arbitrary pointer deref in __pmLogLoadInDom
Backport fix for CVE-2026-16530 (CWE-125/822) - an arbitrary pointer dereference in __pmLogLoadInDom. The bounds check on string indices was guarded by acp!=NULL, making it unreachable from the streaming path used by pmproxy. Three upstream commits are backported: the primary fix adding minimum rlen validation and unconditional bounds checks, plus two follow-up fixes for pmlogrewrite and pmlogextract that relied on the removed guard. CVE: CVE-2026-16530 Upstream patches: -ec81e2b35c.patch -b72da5135d.patch -edfd909eda.patch Resolves: RHEL-213747 This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent. Assisted-by: Ymir
This commit is contained in:
parent
3401c03047
commit
4f4f4d25c8
317
pcp-RHEL-213747.patch
Normal file
317
pcp-RHEL-213747.patch
Normal file
@ -0,0 +1,317 @@
|
||||
From 1f232730588b2f0bf8c6777bb478635aba6c6dc5 Mon Sep 17 00:00:00 2001
|
||||
From: Nathan Scott <nathans@redhat.com>
|
||||
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) <noreply@anthropic.com>
|
||||
---
|
||||
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 <kenj@kenj.id.au>
|
||||
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 <kenj@kenj.id.au>
|
||||
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));
|
||||
}
|
||||
10
pcp.spec
10
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 <redhat-ymir-agent@redhat.com> - 6.3.7-10
|
||||
- Fix arbitrary pointer dereference in __pmLogLoadInDom (CVE-2026-16530)
|
||||
|
||||
* Thu Apr 2 2026 Jan Kurik <jkurik@redhat.com> - 6.3.7-9
|
||||
- Backported ds389 patch from the upstream (pcp-7.1.1)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user