Fix CVE-2026-16531: path traversal via hostname in pmproxy

Backport upstream commit dd6ed05f to fix a path traversal
vulnerability (CVE-2026-16531, CWE-22) in the pmproxy logger
servlet. The hostname field from a __pmLogLabel PDU was used
directly in path construction without sanitization, allowing
creation of files at arbitrary paths. The fix adds a
check_hostname() function that validates hostnames against an
allowlist of alphanumeric characters, hyphens, dots, and
underscores (per RFC 952/1123), rejecting invalid hostnames
with -EINVAL before any path construction occurs.

CVE: CVE-2026-16531
Upstream patches:
 - dd6ed05f14.patch
Resolves: RHEL-213756

This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent.

Assisted-by: Ymir
This commit is contained in:
RHEL Packaging Agent 2026-07-30 12:41:07 +00:00
parent 5d3ebc9889
commit 3168b52f62
2 changed files with 225 additions and 1 deletions

View File

@ -0,0 +1,217 @@
From 4356ff59f4d6c45e149c881a6a0d910380adcdad Mon Sep 17 00:00:00 2001
From: Nathan Scott <nathans@redhat.com>
Date: Thu, 2 Jul 2026 17:12:52 +1000
Subject: [PATCH] libpcp_web: fix path traversal via hostname in logger servlet
(CWE-22)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The pmproxy logger servlet (POST /logger/label) accepts a binary
__pmLogLabel PDU and uses the hostname field directly in path
construction without sanitization. An attacker can supply
hostname='../../../../tmp/target' causing pmproxy to create .meta
and .index files at arbitrary paths writable by the pcp user.
Fix: add check_hostname() allowlist check — only alphanumeric, hyphen,
dot, and underscore characters are permitted (per RFC 952/1123 plus
underscore for real-world compatibility). Leading dots are rejected
to prevent relative path components. Invalid hostnames are rejected
with -EINVAL before any path construction occurs.
Add qa/2106 verifying that a label with a path-traversal hostname
is rejected and no files are created outside the log directory.
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/2106 | 102 ++++++++++++++++++++++++++++++++++
qa/2106.out | 9 +++
qa/group | 1 +
src/libpcp_web/src/loggroup.c | 27 +++++++++
4 files changed, 139 insertions(+)
create mode 100755 qa/2106
create mode 100644 qa/2106.out
diff --git a/qa/2106 b/qa/2106
new file mode 100755
index 000000000..4345edd52
--- /dev/null
+++ b/qa/2106
@@ -0,0 +1,102 @@
+#!/bin/sh
+# PCP QA Test No. 2106
+# Verify pmproxy logger servlet rejects hostnames with path traversal
+# characters (CWE-22 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
+
+which curl >/dev/null 2>&1 || _notrun "no curl executable installed"
+which python3 >/dev/null 2>&1 || _notrun "no python3 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
+__port=`_find_free_port`
+$PCP_BINADM_DIR/pmproxy -f -p $__port -l $tmp.log &
+__pid=$!
+sleep 2
+
+if ! kill -0 $__pid 2>/dev/null; then
+ echo "FAIL: pmproxy did not start"
+ exit
+fi
+
+# build a valid __pmLogLabel_v3 binary record with a traversal hostname
+# and POST it to the /logger/label endpoint
+python3 -c "
+import struct, sys
+
+hostname = b'../../../../tmp/pwned'
+timezone = b'UTC'
+zoneinfo = b'UTC'
+
+# __pmLogLabel fields (V3 binary format)
+magic = 0x50052602 # PM_LOG_MAGIC | PM_LOG_VERS03
+pid = 1337
+sec_hi = 0
+sec_lo = 0x50000000
+nsec = 0
+
+# pack the label
+label = struct.pack('>I', magic)
+label += struct.pack('>i', pid)
+label += struct.pack('>I', sec_hi)
+label += struct.pack('>I', sec_lo)
+label += struct.pack('>I', nsec)
+label += struct.pack('>i', 0) # vol
+label += struct.pack('>i', len(hostname))
+label += hostname
+label += struct.pack('>i', len(timezone))
+label += timezone
+label += struct.pack('>i', len(zoneinfo))
+label += zoneinfo
+
+sys.stdout.buffer.write(label)
+" > $tmp.label
+
+echo "=== POST label with traversal hostname ==="
+__code=$(curl -s -o $tmp.resp -w '%{http_code}' \
+ -X POST "http://localhost:$__port/logger/label" \
+ -H 'Content-Type: application/octet-stream' \
+ --data-binary @$tmp.label 2>/dev/null)
+echo "HTTP response: $__code"
+
+echo
+echo "=== verify no directory created outside log dir ==="
+if [ -d /tmp/pwned ]; then
+ echo "FAIL: path traversal succeeded - /tmp/pwned exists"
+else
+ echo "no traversal directory created"
+fi
+
+echo
+echo "=== check pmproxy log for rejection ==="
+if grep -q "unsafe hostname" $tmp.log; then
+ echo "hostname validation rejected the traversal"
+elif grep -q "DecodeLabel" $tmp.log; then
+ echo "no traversal directory created"
+else
+ echo "no traversal directory created"
+fi
+
+# success, all done
+exit
diff --git a/qa/2106.out b/qa/2106.out
new file mode 100644
index 000000000..cd5f31be4
--- /dev/null
+++ b/qa/2106.out
@@ -0,0 +1,9 @@
+QA output created by 2106
+=== POST label with traversal hostname ===
+HTTP response: 400
+
+=== verify no directory created outside log dir ===
+no traversal directory created
+
+=== check pmproxy log for rejection ===
+no traversal directory created
diff --git a/qa/group b/qa/group
index d146ff9e4..51e9e4d76 100644
--- a/qa/group
+++ b/qa/group
@@ -2371,5 +2371,6 @@ suse
1996 pmda.infiniband local
1997 pcp nfsiostat python local
1998 pmda.rds local python
+2106 pmproxy local security
4751 libpcp threads valgrind local pcp helgrind
9000 other local
diff --git a/src/libpcp_web/src/loggroup.c b/src/libpcp_web/src/loggroup.c
index 7c730cb0b..cef3a8d2c 100644
--- a/src/libpcp_web/src/loggroup.c
+++ b/src/libpcp_web/src/loggroup.c
@@ -581,6 +581,26 @@ renamed: /* return here during name conflict resolution */
return count;
}
+/*
+ * Check that a hostname string (which can arrive from a remote host),
+ * conforms to simple validity checks to ensure suspicious file system
+ * path names are not being injected.
+ */
+static int
+check_hostname(const char *hostname)
+{
+ const char *p;
+
+ if (hostname == NULL || hostname[0] == '\0' || hostname[0] == '.')
+ return 0;
+ for (p = hostname; *p; p++) {
+ if (!isalnum((unsigned char)*p) &&
+ *p != '-' && *p != '.' && *p != '_')
+ return 0;
+ }
+ return 1;
+}
+
int
pmLogGroupLabel(pmLogGroupSettings *sp, const char *content, size_t length,
dict *params, void *arg)
@@ -610,6 +630,13 @@ pmLogGroupLabel(pmLogGroupSettings *sp, const char *content, size_t length,
if (pmDebugOptions.log)
fprintf(stderr, "New archive label for host: %s\n", loglabel.hostname);
+ if (!check_hostname(loglabel.hostname)) {
+ pmNotifyErr(LOG_ERR, "Rejecting archive with unsafe hostname: %s",
+ loglabel.hostname ? loglabel.hostname : "(null)");
+ sts = -EINVAL;
+ goto fail;
+ }
+
start = (time_t)loglabel.start.sec;
if (localtime_r(&start, &tm) == NULL ||
strftime(timebuf, sizeof(timebuf), TIME_FORMAT, &tm) < 2) {

View File

@ -1,6 +1,6 @@
Name: pcp
Version: 7.1.5
Release: 3%{?dist}
Release: 4%{?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
@ -15,6 +15,10 @@ ExcludeArch: %{ix86}
# https://github.com/performancecopilot/pcp/commit/edfd909edac8ad4762aa150e330f92211df98c1c
Patch0: pcp-7.1.5-CVE-2026-16530.patch
# https://issues.redhat.com/browse/RHEL-213756
# https://github.com/performancecopilot/pcp/commit/dd6ed05f143f97e00198cb4b0180c6375d758cbe
Patch1: pcp-7.1.5-CVE-2026-16531.patch
# The additional linker flags break out-of-tree PMDAs.
# https://bugzilla.redhat.com/show_bug.cgi?id=2043092
%undefine _package_note_flags
@ -3428,6 +3432,9 @@ fi
%files zeroconf -f pcp-zeroconf-files.rpm
%changelog
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 7.1.5-4
- Fix CVE-2026-16531: path traversal via hostname in pmproxy (RHEL-213756)
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 7.1.5-3
- Fix arbitrary pointer deref in __pmLogLoadInDom (CVE-2026-16530) (RHEL-213746)