[Consolidated] Backport fixes for pcp (c8s) - fixed MR !150

Fixes the consolidated MR !150
Resolves: RHEL-213662
Resolves: RHEL-213686
Resolves: RHEL-213720
Resolves: RHEL-213729
This commit is contained in:
Jan Kurik 2026-08-06 08:45:08 +02:00
parent 848e02c91d
commit 1999e78348
No known key found for this signature in database
7 changed files with 947 additions and 1 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
SOURCES/pcp-5.3.7.src.tar.gz
/pcp-5.3.7.src.tar.gz
/pdu-getpdu-overflow

View File

@ -1,12 +1,13 @@
Name: pcp
Version: 5.3.7
Release: 22%{?dist}
Release: 22%{?dist}.4
Summary: System-level performance monitoring and performance management
License: GPLv2+ and LGPLv2+ and CC-BY
URL: https://pcp.io
%global artifactory https://performancecopilot.jfrog.io/artifactory
Source0: %{artifactory}/pcp-source-release/pcp-%{version}.src.tar.gz
Source1: pdu-getpdu-overflow
Patch0: redhat-bugzilla-2003956-pmdabcc-update-kernel-version-check-due-to-backporting.patch
Patch1: redhat-bugzilla-1981886-pmdasockets-backporting.patch
Patch2: redhat-bugzilla-2059461-pmie-systemd-fixup.patch
@ -33,6 +34,16 @@ Patch22: redhat-issues-RHEL-57796-pmcd-pmstore-corruption.patch
Patch23: redhat-issues-RHEL-57799-pmpost-symlink-handling.patch
Patch24: redhat-issues-RHEL-34586-pmproxy-pmcd-fd-leak.patch
Patch25: redhat-issues-RHEL-57788-pmdahacluster-update.patch
# https://issues.redhat.com/browse/RHEL-213729
# https://github.com/performancecopilot/pcp/commit/ef848fb978d26335f9676933f541c73b58f130a6
Patch26: redhat-issues-RHEL-213729-pdu-integer-overflow.patch
Patch27: redhat-issues-RHEL-213720-pmproxy-auth-flags.patch
# https://issues.redhat.com/browse/RHEL-213686
# https://github.com/performancecopilot/pcp/commit/7e27614006ff6fc4925991edbedaf1eab6b14731
Patch28: redhat-issues-RHEL-213686-CVE-2026-16526.patch
# https://issues.redhat.com/browse/RHEL-213662
# https://github.com/performancecopilot/pcp/commit/c5cbeceb7d3c2af357c04065cdd911efdc270de0
Patch29: redhat-issues-RHEL-213662-CVE-2026-16524.patch
# The additional linker flags break out-of-tree PMDAs.
# https://bugzilla.redhat.com/show_bug.cgi?id=2043092
@ -2304,6 +2315,7 @@ updated policy package.
%prep
%autosetup -p1
install -D -m 644 %{SOURCE1} qa/pdudata/pdu-getpdu-overflow
%build
# the buildsubdir macro gets defined in %setup and is apparently only available in the next step (i.e. the %build step)
@ -3375,6 +3387,18 @@ fi
%files zeroconf -f pcp-zeroconf-files.rpm
%changelog
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 5.3.7-22.4
- Fix CVE-2026-16524: command injection in linux_sockets PMDA (RHEL-213662)
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 5.3.7-22.3
- Fix CVE-2026-16526: set FD_CLOEXEC on AF_UNIX sockets (RHEL-213686)
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 5.3.7-22.2
- Fix missing pmproxy -Q and -S authentication flags (CVE-2026-16527)
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 5.3.7-22.1
- Fix integer overflow in __pmGetPDU PDU handling (RHEL-213729)
* Mon Sep 09 2024 Nathan Scott <nathans@redhat.com> - 5.3.7-22
- Fix buffer sizing checks in pmstore PDU handling (RHEL-57796)
- Guard against symlink attacks in pmpost program (RHEL-57799)

View File

@ -0,0 +1,271 @@
From 37171c2042b007037730eb3c61719548c43b5142 Mon Sep 17 00:00:00 2001
From: Nathan Scott <nathans@redhat.com>
Date: Thu, 2 Jul 2026 15:41:11 +1000
Subject: [PATCH] linux_sockets pmda: fix command injection via
network.persocket.filter (CWE-78)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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) <noreply@anthropic.com>
---
qa/2101 | 70 +++++++++++++++++++++++++++++
qa/2101.out | 15 +++++++
qa/group | 1 +
src/pmdas/linux_sockets/pmda.c | 8 ++--
src/pmdas/linux_sockets/ss_stream.c | 53 ++++++++++++++++++----
5 files changed, 133 insertions(+), 14 deletions(-)
create mode 100755 qa/2101
create mode 100644 qa/2101.out
diff --git a/qa/2101 b/qa/2101
new file mode 100755
index 000000000..733b7f70c
--- /dev/null
+++ b/qa/2101
@@ -0,0 +1,70 @@
+#!/bin/sh
+# PCP QA Test No. 2101
+# Verify linux_sockets PMDA filter validation rejects shell metacharacters
+# and accepts valid filter expressions (CWE-78 fix verification)
+#
+# Copyright (c) 2026 Red Hat. All Rights Reserved.
+#
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+# get standard environment, filters and checks
+. ./common.product
+. ./common.filter
+. ./common.check
+
+[ $PCP_PLATFORM = linux ] || _notrun "Linux-specific sockets testing"
+[ -f $PCP_PMDAS_DIR/sockets/pmdasockets ] || _notrun "sockets PMDA not installed"
+
+_cleanup()
+{
+ _cleanup_pmda sockets
+ cd $here
+ $sudo rm -rf $tmp $tmp.*
+}
+
+status=0 # success is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_prepare_pmda sockets
+_stop_auto_restart pmcd
+
+# install the sockets PMDA
+cd $PCP_PMDAS_DIR/sockets
+$sudo ./Remove >/dev/null 2>&1
+$sudo ./Install </dev/null >$tmp.out 2>&1
+cat $tmp.out >>$seq_full
+
+# check the PMDA is alive
+pmprobe -v network.persocket.filter >$tmp.probe 2>&1
+grep -q 'No PMCD agent' $tmp.probe && _notrun "sockets PMDA failed to install"
+
+# real QA test starts here
+
+echo "=== valid filter should be accepted ==="
+pmstore network.persocket.filter "sport == 22" 2>&1 \
+| grep -q 'Bad input' && echo "FAIL: valid filter rejected" || echo "valid filter accepted"
+
+echo
+echo "=== shell metacharacter semicolon should be rejected ==="
+pmstore network.persocket.filter ';id' 2>&1 \
+| grep -q 'Bad input' && echo "metacharacter rejected" || echo "FAIL: metacharacter not rejected"
+
+echo
+echo "=== shell metacharacter backtick should be rejected ==="
+pmstore network.persocket.filter '`id`' 2>&1 \
+| grep -q 'Bad input' && echo "metacharacter rejected" || echo "FAIL: metacharacter not rejected"
+
+echo
+echo "=== shell metacharacter pipe should be rejected ==="
+pmstore network.persocket.filter '|cat /etc/passwd' 2>&1 \
+| grep -q 'Bad input' && echo "metacharacter rejected" || echo "FAIL: metacharacter not rejected"
+
+echo
+echo "=== shell metacharacter dollar should be rejected ==="
+pmstore network.persocket.filter '${IFS}id' 2>&1 \
+| grep -q 'Bad input' && echo "metacharacter rejected" || echo "FAIL: metacharacter not rejected"
+
+# success, all done
+exit
diff --git a/qa/2101.out b/qa/2101.out
new file mode 100644
index 000000000..02f9ac565
--- /dev/null
+++ b/qa/2101.out
@@ -0,0 +1,15 @@
+QA output created by 2101
+=== valid filter should be accepted ===
+valid filter accepted
+
+=== shell metacharacter semicolon should be rejected ===
+metacharacter rejected
+
+=== shell metacharacter backtick should be rejected ===
+metacharacter rejected
+
+=== shell metacharacter pipe should be rejected ===
+metacharacter rejected
+
+=== shell metacharacter dollar should be rejected ===
+metacharacter rejected
diff --git a/qa/group b/qa/group
index cf7a93e56..updated 100644
--- a/qa/group
+++ b/qa/group
@@ -1980,7 +1980,8 @@ x11
1984 pmlogconf pmda.redis local
1985 pmfind local valgrind
1986 pmfind local
+2101 pmda.sockets local security
2104 libpcp local security
2100 pmproxy local security
2105 libpcp pmcd local security pmcd.pdu
4751 libpcp threads valgrind local pcp helgrind
diff --git a/src/pmdas/linux_sockets/pmda.c b/src/pmdas/linux_sockets/pmda.c
index 5a3018d8a..59e69a675 100644
--- a/src/pmdas/linux_sockets/pmda.c
+++ b/src/pmdas/linux_sockets/pmda.c
@@ -162,11 +162,9 @@ sockets_check_filter(const char *string)
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 @@ sockets_store(pmResult *result, pmdaExt *pmda)
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;
diff --git a/src/pmdas/linux_sockets/ss_stream.c b/src/pmdas/linux_sockets/ss_stream.c
index 421c65fd1..833fc275a 100644
--- 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 @@ ss_open_stream()
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 @@ ss_open_stream()
void
ss_close_stream(FILE *fp)
{
- if (getenv("PCPQA_PMDA_SOCKETS") != NULL)
- fclose(fp);
+ if (using_pipe)
+ __pmProcessPipeClose(fp);
else
- pclose(fp);
+ fclose(fp);
}

View File

@ -0,0 +1,221 @@
From 3af40048474ed643d3904497312e710ee378329c Mon Sep 17 00:00:00 2001
From: Nathan Scott <nathans@redhat.com>
Date: Thu, 2 Jul 2026 16:23:26 +1000
Subject: [PATCH] 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) <noreply@anthropic.com>
---
qa/2104 | 31 ++++++++++++++++++++++++++++++
qa/2104.out | 2 ++
qa/group | 1 +
qa/src/GNUlocaldefs | 11 ++++++++++-
qa/src/check_cloexec.c | 38 +++++++++++++++++++++++++++++++++++++
src/libpcp/src/auxconnect.c | 6 +++++-
src/libpcp_pmda/src/root.c | 5 ++++-
7 files changed, 91 insertions(+), 3 deletions(-)
create mode 100755 qa/2104
create mode 100644 qa/2104.out
create mode 100644 qa/src/check_cloexec.c
diff --git a/qa/2104 b/qa/2104
new file mode 100755
index 000000000..0b2b0b602
--- /dev/null
+++ b/qa/2104
@@ -0,0 +1,31 @@
+#!/bin/sh
+# PCP QA Test No. 2104
+# Verify AF_UNIX sockets have FD_CLOEXEC set (CWE-403 fix)
+#
+# Copyright (c) 2026 Red Hat. All Rights Reserved.
+#
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+# get standard environment, filters and checks
+. ./common.product
+. ./common.filter
+. ./common.check
+
+[ -f src/check_cloexec ] || _notrun "check_cloexec not built"
+
+_cleanup()
+{
+ cd $here
+ $sudo rm -rf $tmp $tmp.*
+}
+
+status=0 # success is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# real QA test starts here
+src/check_cloexec
+
+# success, all done
+exit
diff --git a/qa/2104.out b/qa/2104.out
new file mode 100644
index 000000000..900a95ec7
--- /dev/null
+++ b/qa/2104.out
@@ -0,0 +1,2 @@
+QA output created by 2104
+FD_CLOEXEC is set
diff --git a/qa/group b/qa/group
index a8b52b68e..cf7a93e56 100644
--- a/qa/group
+++ b/qa/group
@@ -1982,5 +1982,6 @@ x11
1985 pmfind local valgrind
1986 pmfind local
+2104 libpcp local security
2100 pmproxy local security
2105 libpcp pmcd local security pmcd.pdu
4751 libpcp threads valgrind local pcp helgrind
diff --git a/qa/src/GNUlocaldefs b/qa/src/GNUlocaldefs
index c5abe0bd3..5238b7a52 100644
--- a/qa/src/GNUlocaldefs
+++ b/qa/src/GNUlocaldefs
@@ -51,7 +51,9 @@ CFILES = disk_test.c exercise.c context_test.c chkoptfetch.c \
timeshift.c checkstructs.c bcc_profile.c sha1int2ext.c \
getdomainname.c profilecrash.c store_and_fetch.c test_service_notify.c \
ctx_derive.c pmstrn.c pmfstring.c pmfg-derived.c mmv_help.c sizeof.c \
- stampconv.c
+ stampconv.c \
+ check_cloexec.c
+
ifeq ($(shell test -f ../localconfig && echo 1), 1)
include ../localconfig
@@ -533,6 +535,11 @@ sortinst: sortinst.c
# --- need libpcp_import
#
+check_cloexec: check_cloexec.c
+ rm -f $@
+ $(CCF) $(CDEFS) -o $@ $@.c $(LDLIBS)
+ $(LINKER_MAKERULE)
+
check_import: check_import.c
rm -f $@
$(CCF) $(CDEFS) -o $@ $@.c $(LDLIBS) -lpcp_import
@@ -814,6 +821,8 @@ xlog.o: libpcp.h
xmktime.o: libpcp.h
xxx.o: libpcp.h
+check_cloexec.o: libpcp.h
+
bozo:
@echo CFILES_TARGETS=$(CFILES_TARGETS)
@echo "patsubst ->" $(patsubst %.c,%,$(CFILES_TARGETS))
diff --git a/qa/src/check_cloexec.c b/qa/src/check_cloexec.c
new file mode 100644
index 000000000..ebc438d30
--- /dev/null
+++ b/qa/src/check_cloexec.c
@@ -0,0 +1,38 @@
+/*
+ * Verify that AF_UNIX sockets created by __pmCreateUnixSocket()
+ * have FD_CLOEXEC set.
+ */
+
+#include <pcp/pmapi.h>
+#include "libpcp.h"
+#include <fcntl.h>
+
+int
+main(int argc, char **argv)
+{
+ int fd, flags;
+
+ pmSetProgname(argv[0]);
+
+ fd = __pmCreateUnixSocket();
+ if (fd < 0) {
+ fprintf(stderr, "Error: __pmCreateUnixSocket failed: %s\n",
+ pmErrStr(fd));
+ return 1;
+ }
+
+ flags = fcntl(fd, F_GETFD);
+ if (flags < 0) {
+ fprintf(stderr, "Error: fcntl F_GETFD failed\n");
+ close(fd);
+ return 1;
+ }
+
+ if (flags & FD_CLOEXEC)
+ printf("FD_CLOEXEC is set\n");
+ else
+ printf("FAIL: FD_CLOEXEC is NOT set\n");
+
+ close(fd);
+ return 0;
+}
diff --git a/src/libpcp/src/auxconnect.c b/src/libpcp/src/auxconnect.c
index 2afd53cb7..ee6e93fa0 100644
--- a/src/libpcp/src/auxconnect.c
+++ b/src/libpcp/src/auxconnect.c
@@ -516,8 +516,12 @@ __pmInitSocket(int fd, int family)
}
#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. */
diff --git a/src/libpcp_pmda/src/root.c b/src/libpcp_pmda/src/root.c
index 1d3223b57..3950e4f1e 100644
--- a/src/libpcp_pmda/src/root.c
+++ b/src/libpcp_pmda/src/root.c
@@ -32,7 +32,7 @@ pmdaRootConnect(const char *path)
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 @@ pmdaRootConnect(const char *path)
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,

View File

@ -0,0 +1,205 @@
From ccbe2d6cb72c2f3f8969564e59f864e588b3d54e Mon Sep 17 00:00:00 2001
From: Nathan Scott <nathans@redhat.com>
Date: Thu, 2 Jul 2026 15:28:34 +1000
Subject: [PATCH] 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) <noreply@anthropic.com>
---
man/man1/pmproxy.1 | 6 +++
qa/2100 | 85 +++++++++++++++++++++++++++++++++++++++
qa/2100.out | 12 ++++++
qa/group | 1 +
src/pmproxy/src/pmproxy.c | 4 +-
5 files changed, 107 insertions(+), 1 deletion(-)
create mode 100755 qa/2100
create mode 100644 qa/2100.out
diff --git a/man/man1/pmproxy.1 b/man/man1/pmproxy.1
index 90ec61be8..30758a114 100644
--- a/man/man1/pmproxy.1
+++ b/man/man1/pmproxy.1
@@ -271,6 +271,9 @@ be unreadable by any user other than the user running the
.B pmproxy
process).
.TP
+\fB\-Q\f1, \fB\-\-certreqd\f1
+Require that all client connections provide a trusted client certificate.
+.TP
\f3\-r\f1 \f2port\f1, \f3\-\-redisport\f1=\f2port\f1
Specify an alternate Redis
.I port
@@ -285,6 +288,9 @@ The default value is
.IR $PCP_RUN_DIR/pmproxy.socket .
This option implies \f3pmproxy\f1 is running in \f3timeseries\f1 mode.
.TP
+\fB\-S\f1, \fB\-\-reqauth\f1
+Require that all client connections be authenticated.
+.TP
\fB\-t\f1, \fB\-\-timeseries\f1
Operate in automatic archive timeseries discovery mode.
This mode of operation will enable the
diff --git a/qa/2100 b/qa/2100
new file mode 100755
index 000000000..1de957a2f
--- /dev/null
+++ b/qa/2100
@@ -0,0 +1,85 @@
+#!/bin/sh
+# PCP QA Test No. 2100
+# Verify pmproxy -Q and -S authentication flags are accepted
+# and that -S (reqauth) enforces authentication on REST API
+#
+# Copyright (c) 2026 Red Hat. All Rights Reserved.
+#
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+# get standard environment, filters and checks
+. ./common.product
+. ./common.filter
+. ./common.check
+
+which curl >/dev/null 2>&1 || _notrun "no curl executable installed"
+
+_cleanup()
+{
+ [ -n "$__pid" ] && kill $__pid 2>/dev/null
+ wait $__pid 2>/dev/null
+ cd $here
+ $sudo rm -rf $tmp $tmp.*
+}
+
+status=0 # success is the default!
+__pid=""
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# real QA test starts here
+
+echo "=== checking -Q and -S appear in usage ==="
+pmproxy --help 2>&1 | grep -E '\-[QS]' | sed -e 's/^ *//'
+
+echo
+echo "=== checking -S enforces authentication on REST API ==="
+__port=`_find_free_port`
+$PCP_BINADM_DIR/pmproxy -S -f -p $__port -l $tmp.log &
+__pid=$!
+sleep 1
+if kill -0 $__pid 2>/dev/null; then
+ echo "pmproxy with -S started"
+
+ # unauthenticated request should be rejected
+ __code=`curl -s -o /dev/null -w '%{http_code}' "http://localhost:$__port/pmapi/context?hostspec=localhost" 2>/dev/null`
+ if [ "$__code" = "403" ]; then
+ echo "unauthenticated request correctly rejected (HTTP $__code)"
+ else
+ echo "FAIL: expected HTTP 403, got HTTP $__code"
+ fi
+
+ kill $__pid
+ wait $__pid 2>/dev/null
+ __pid=""
+else
+ echo "FAIL: pmproxy with -S did not start"
+fi
+
+echo
+echo "=== checking without -S allows unauthenticated access ==="
+__port=`_find_free_port`
+$PCP_BINADM_DIR/pmproxy -f -p $__port -l $tmp.log2 &
+__pid=$!
+sleep 1
+if kill -0 $__pid 2>/dev/null; then
+ echo "pmproxy without -S started"
+
+ # unauthenticated request should succeed
+ __code=`curl -s -o /dev/null -w '%{http_code}' "http://localhost:$__port/pmapi/context?hostspec=localhost" 2>/dev/null`
+ if [ "$__code" = "200" ]; then
+ echo "unauthenticated request correctly allowed (HTTP $__code)"
+ else
+ echo "FAIL: expected HTTP 200, got HTTP $__code"
+ fi
+
+ kill $__pid
+ wait $__pid 2>/dev/null
+ __pid=""
+else
+ echo "FAIL: pmproxy without -S did not start"
+fi
+
+# success, all done
+exit
diff --git a/qa/2100.out b/qa/2100.out
new file mode 100644
index 000000000..5302aea77
--- /dev/null
+++ b/qa/2100.out
@@ -0,0 +1,12 @@
+QA output created by 2100
+=== checking -Q and -S appear in usage ===
+-Q, --certreqd require client certificate authentication
+-S, --reqauth require all client connections to be authenticated
+
+=== checking -S enforces authentication on REST API ===
+pmproxy with -S started
+unauthenticated request correctly rejected (HTTP 403)
+
+=== checking without -S allows unauthenticated access ===
+pmproxy without -S started
+unauthenticated request correctly allowed (HTTP 200)
diff --git a/qa/group b/qa/group
index 742d3b54b..a8b52b68e 100644
--- a/qa/group
+++ b/qa/group
@@ -1980,5 +1980,6 @@ x11
1984 pmlogconf pmda.redis local
1985 pmfind local valgrind
1986 pmfind local
+2100 pmproxy local security
2105 libpcp pmcd local security pmcd.pdu
4751 libpcp threads valgrind local pcp helgrind
diff --git a/src/pmproxy/src/pmproxy.c b/src/pmproxy/src/pmproxy.c
index d975ef37a..43766c20f 100644
--- a/src/pmproxy/src/pmproxy.c
+++ b/src/pmproxy/src/pmproxy.c
@@ -86,7 +86,9 @@ static pmLongOptions longopts[] = {
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" },
{ "redisport", 1, 'r', "PORT", "Connect to Redis instance on this TCP/IP port (implies --timeseries)" },
{ "redishost", 1, 'h', "HOST", "Connect to Redis instance on this host name (implies --timeseries)" },
PMAPI_OPTIONS_HEADER("Diagnostic options"),
@@ -96,7 +98,7 @@ static pmLongOptions longopts[] = {
};
static pmOptions opts = {
- .short_options = "Ac:C:dD:Ffh:i:l:L:M:p:P:r:s:tU:x:?",
+ .short_options = "Ac:C:dD:Ffh:i:l:L:M:p:P:Qr:s:StU:x:?",
.long_options = longopts,
};

View File

@ -0,0 +1,223 @@
From 7800c577023232c994113c42336b84a2138cb561 Mon Sep 17 00:00:00 2001
From: Nathan Scott <nathans@redhat.com>
Date: Thu, 2 Jul 2026 16:59:54 +1000
Subject: [PATCH] libpcp: fix integer overflow in __pmGetPDU() (CWE-190)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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) <noreply@anthropic.com>
---
qa/2105 | 17 +++++++
qa/2105.out | 10 ++++
qa/common.pmcd.pdu | 108 +++++++++++++++++++++++++++++++++++++++++++
qa/group | 1 +
src/libpcp/src/pdu.c | 8 ++++
5 files changed, 144 insertions(+)
create mode 100755 qa/2105
create mode 100644 qa/2105.out
create mode 100644 qa/common.pmcd.pdu
diff --git a/qa/2105 b/qa/2105
new file mode 100755
index 000000000..44b849ad3
--- /dev/null
+++ b/qa/2105
@@ -0,0 +1,17 @@
+#!/bin/sh
+# PCP QA Test No. 2105
+# Verify __pmGetPDU rejects PDU with len near INT_MAX
+# (integer overflow in buffer size computation, CWE-190)
+#
+# Copyright (c) 2026 Red Hat. All Rights Reserved.
+#
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+pdu_data=pdudata/pdu-getpdu-overflow
+grep_pattern="bad PDU len=.*exceeds maximum|PDU len=.*too large"
+
+# this is one of the generic pmcd PDU exerciser tests ...
+#
+. ./common.pmcd.pdu
diff --git a/qa/2105.out b/qa/2105.out
new file mode 100644
index 000000000..a304f9d91
--- /dev/null
+++ b/qa/2105.out
@@ -0,0 +1,10 @@
+QA output created by 2105
+expect error(s) to be logged ...
+__pmGetPDU: fd=N type=0x8000 bad PDU len=2147483647 in hdr exceeds maximum client PDU size (65536)
+
+and no valgrind badness ...
+Memcheck, a memory error detector
+LEAK SUMMARY:
+definitely lost: 0 bytes in 0 blocks
+indirectly lost: 0 bytes in 0 blocks
+ERROR SUMMARY: 0 errors from 0 contexts ...
diff --git a/qa/common.pmcd.pdu b/qa/common.pmcd.pdu
new file mode 100644
index 000000000..8fa832aa8
--- /dev/null
+++ b/qa/common.pmcd.pdu
@@ -0,0 +1,108 @@
+# This is really a full QA test for stress testing pmcd's protocol
+# handling ... set the shell variables
+# $pdu_data
+# ascii pdu specifications suitable for src/pdu-gadget or binary
+# protocol data stream, these typically contain malformed PDUs
+# (see the qa/pdudata directory for examples) ... this needs
+# to be a relative path
+# $grep_pattern
+# a grep -E pattern for scanning pmcd.log once the test has been
+# run
+# and then source this file
+
+[ -z "$pdu_data" ] && echo "Botch! \$pdu_data not set" && exit 1
+[ -z "$grep_pattern" ] && echo "Botch! \$grep_pattern not set" && exit 1
+
+# get standard environment, filters and checks
+. ./common.product
+. ./common.filter
+. ./common.check
+
+[ ! -f "$here/$pdu_data" ] && echo "Botch! PDU data file $here/$pdu_data not found" && exit 1
+
+
+which nc >/dev/null 2>&1 || _notrun "no nc executable installed"
+# Need nc option to terminate when EOF encountered on stdin,
+# but unfortunately there are multiple versions of nc(1) ...
+nc --help >$tmp.out 2>&1
+if grep -q '.-[^ ]*N' $tmp.out
+then
+ __nc_opt=-N
+elif grep -q '.--send-only' $tmp.out
+then
+ __nc_opt="--send-only"
+else
+ cat $tmp.out >>$seq_full
+ _notrun 'nc --help is not helping ... see $seq_full'
+fi
+_check_valgrind
+
+_cleanup()
+{
+ cat pmcd.log >>$seq_full
+ cd $here
+ $sudo rm -rf $tmp $tmp.*
+}
+
+status=0 # success is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_filter()
+{
+ sed \
+ -e '/^Command: /d' \
+ # end
+}
+
+_filter_pmcd()
+{
+ sed \
+ -e 's/fd=[0-9][0-9]*/fd=N/g' \
+ -e 's/^\[.*\] pmcd([0-9]*) [A-Za-z]*: //' \
+ # end
+}
+
+mkdir $tmp || exit 1
+cd $tmp
+grep sampledso $PCP_PMCDCONF_PATH >pmcd.conf
+$PCP_AWK_PROG <$PCP_PMCDCONF_PATH >>pmcd.conf '
+$1 == "[access]" { print ""; want = 1 }
+want == 1 { print }'
+cat pmcd.conf >>$seq_full
+__port=`_find_free_port`
+echo "__port=$__port" >>$seq_full
+
+# real QA test starts here
+valgrind --leak-check=full $PCP_BINADM_DIR/pmcd -f -Dpdu,appl3,attr -c ./pmcd.conf -s ./pmcd.socket -p $__port >out 2>err &
+valgrind_pid=$!
+sleep 2
+__pmcd_pid=`$PCP_PS_PROG $PCP_PS_ALL_FLAGS | grep '[p]mcd -f -Dpdu' | $PCP_AWK_PROG '{ print $2 }'`
+echo "__pmcd_pid=$__pmcd_pid" >>$seq_full
+case "`file $here/$pdu_data`"
+in
+ *'ASCII text'*)
+ # assume ascii PDU specification as input to pdu-gadget
+ $here/src/pdu-gadget -p $__port <$here/$pdu_data 2>$tmp.err
+ ;;
+ *)
+ # assume binary data, e.g. SUSE reproducers
+ rm -f $tmp.err
+ nc $__nc_opt <$here/$pdu_data localhost $__port 2>&1 \
+ ;;
+esac \
+| od -X >>$seq_full
+sleep 2
+kill -TERM $__pmcd_pid
+wait
+
+[ -s $tmp.err ] && cat $tmp.err
+
+echo "expect error(s) to be logged ..."
+grep -E "$grep_pattern" pmcd.log | _filter_pmcd
+
+echo
+echo "and no valgrind badness ..."
+cat out err | _filter_valgrind | _filter
+
+# success, all done
+exit
diff --git a/qa/group b/qa/group
index 86265eb1f..742d3b54b 100644
--- a/qa/group
+++ b/qa/group
@@ -1980,4 +1980,5 @@ x11
1984 pmlogconf pmda.redis local
1985 pmfind local valgrind
1986 pmfind local
+2105 libpcp pmcd local security pmcd.pdu
4751 libpcp threads valgrind local pcp helgrind
diff --git a/src/libpcp/src/pdu.c b/src/libpcp/src/pdu.c
index 4afe5719c..5b934513a 100644
--- a/src/libpcp/src/pdu.c
+++ b/src/libpcp/src/pdu.c
@@ -585,6 +585,14 @@ check_read_len:
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",
+ "__pmGetPDU", fd, php->len);
+ __pmUnpinPDUBuf(pdubuf);
+ return PM_ERR_TOOBIG;
+ }
tmpsize = PDU_CHUNK * ( 1 + php->len / PDU_CHUNK);
maxsize = tmpsize;
}

View File

@ -1 +1,2 @@
SHA512 (pcp-5.3.7.src.tar.gz) = 04f494a7f161dea414fca14433ffa883272a9b68fb32c2954632f6e9a94d9a292714264db6860e1c670743023b90831eb787e757a29e3affeb196aa6a8918086
SHA512 (pdu-getpdu-overflow) = 38a702c745ee526956b6536ca8f8e31e2cc5505492c8708914ebd72ab2ddc6aa9978af6818e5b09ef7ce291878b43fbb9ac87d0350e9159da7c3c2ed155a0779