diff --git a/.gitignore b/.gitignore index 906471f..6f04cf9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ SOURCES/pcp-5.3.7.src.tar.gz /pcp-5.3.7.src.tar.gz +/pdu-getpdu-overflow diff --git a/pcp.spec b/pcp.spec index 086765d..7c36325 100644 --- a/pcp.spec +++ b/pcp.spec @@ -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 - 5.3.7-22.4 +- Fix CVE-2026-16524: command injection in linux_sockets PMDA (RHEL-213662) + +* Thu Jul 30 2026 RHEL Packaging Agent - 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 - 5.3.7-22.2 +- Fix missing pmproxy -Q and -S authentication flags (CVE-2026-16527) + +* Thu Jul 30 2026 RHEL Packaging Agent - 5.3.7-22.1 +- Fix integer overflow in __pmGetPDU PDU handling (RHEL-213729) + * Mon Sep 09 2024 Nathan Scott - 5.3.7-22 - Fix buffer sizing checks in pmstore PDU handling (RHEL-57796) - Guard against symlink attacks in pmpost program (RHEL-57799) diff --git a/redhat-issues-RHEL-213662-CVE-2026-16524.patch b/redhat-issues-RHEL-213662-CVE-2026-16524.patch new file mode 100644 index 0000000..aa7ff8c --- /dev/null +++ b/redhat-issues-RHEL-213662-CVE-2026-16524.patch @@ -0,0 +1,271 @@ +From 37171c2042b007037730eb3c61719548c43b5142 Mon Sep 17 00:00:00 2001 +From: Nathan Scott +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) +--- + 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 $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 + #include ++#include + #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); + } diff --git a/redhat-issues-RHEL-213686-CVE-2026-16526.patch b/redhat-issues-RHEL-213686-CVE-2026-16526.patch new file mode 100644 index 0000000..562b2e7 --- /dev/null +++ b/redhat-issues-RHEL-213686-CVE-2026-16526.patch @@ -0,0 +1,221 @@ +From 3af40048474ed643d3904497312e710ee378329c Mon Sep 17 00:00:00 2001 +From: Nathan Scott +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) +--- + 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 ++#include "libpcp.h" ++#include ++ ++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, diff --git a/redhat-issues-RHEL-213720-pmproxy-auth-flags.patch b/redhat-issues-RHEL-213720-pmproxy-auth-flags.patch new file mode 100644 index 0000000..318e10b --- /dev/null +++ b/redhat-issues-RHEL-213720-pmproxy-auth-flags.patch @@ -0,0 +1,205 @@ +From ccbe2d6cb72c2f3f8969564e59f864e588b3d54e Mon Sep 17 00:00:00 2001 +From: Nathan Scott +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) +--- + 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, + }; + diff --git a/redhat-issues-RHEL-213729-pdu-integer-overflow.patch b/redhat-issues-RHEL-213729-pdu-integer-overflow.patch new file mode 100644 index 0000000..c81498d --- /dev/null +++ b/redhat-issues-RHEL-213729-pdu-integer-overflow.patch @@ -0,0 +1,223 @@ +From 7800c577023232c994113c42336b84a2138cb561 Mon Sep 17 00:00:00 2001 +From: Nathan Scott +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) +--- + 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; + } diff --git a/sources b/sources index 3c69425..48af9e2 100644 --- a/sources +++ b/sources @@ -1 +1,2 @@ SHA512 (pcp-5.3.7.src.tar.gz) = 04f494a7f161dea414fca14433ffa883272a9b68fb32c2954632f6e9a94d9a292714264db6860e1c670743023b90831eb787e757a29e3affeb196aa6a8918086 +SHA512 (pdu-getpdu-overflow) = 38a702c745ee526956b6536ca8f8e31e2cc5505492c8708914ebd72ab2ddc6aa9978af6818e5b09ef7ce291878b43fbb9ac87d0350e9159da7c3c2ed155a0779