pcp/pcp-RHEL-213687.patch
Jan Kurik d6aade7578
Backport PCP security CVE fixes and hardening for 6.3.7-11
Backport applicable private-pcp security fixes to PCP 6.3.7 for RHEL 9.9.
CVE-2026-16531 is not applicable because the pmproxy logger servlet is
absent in this release.

Resolves: RHEL-213747 CVE-2026-16530
Resolves: RHEL-213736 CVE-2026-16529
Resolves: RHEL-213711 CVE-2026-16527
Resolves: RHEL-213687 CVE-2026-16526
Resolves: RHEL-213658 CVE-2026-16524
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 08:10:41 +02:00

208 lines
5.8 KiB
Diff

From 7e27614006 Mon Sep 17 00:00:00 2001
From: Nathan Scott <nathans@redhat.com>
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>
Resolves: RHEL-213687 CVE-2026-16526
---
diff --git a/qa/2104 b/qa/2104
new file mode 100755
index 0000000000..0b2b0b602e
--- /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 0000000000..900a95ec75
--- /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 e538db58e6..c7db6290b2 100644
--- a/qa/group
+++ b/qa/group
@@ -2218,4 +2218,5 @@ pmcd.pdu
1992 pmda.uwsgi local
2105 libpcp local security
2100 pmproxy local security
+2104 libpcp local security
4751 libpcp threads valgrind local pcp helgrind
diff --git a/qa/src/GNUlocaldefs b/qa/src/GNUlocaldefs
index f838260680..6ee74c7f16 100644
--- a/qa/src/GNUlocaldefs
+++ b/qa/src/GNUlocaldefs
@@ -54,7 +54,8 @@ CFILES = disk_test.c exercise.c context_test.c chkoptfetch.c \
stampconv.c time_stamp.c archend.c scandata.c wait_for_values.c \
dumpstack.c usergroup.c derived_help.c ready-or-not.c cleanmapdir.c \
throttle.c throttle_timeout.c y2038.c bigpmcdpmids.c pdu-gadget.c \
- newcontext.c
+ newcontext.c \
+ check_cloexec.c
ifeq ($(shell test -f ../localconfig && echo 1), 1)
include ../localconfig
@@ -574,6 +575,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
@@ -880,6 +886,8 @@ xmktime.o: libpcp.h
xxx.o: libpcp.h
y2038.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 0000000000..ebc438d301
--- /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 62f6f54517..67227e3c6b 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 1d3223b572..3950e4f1ec 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,