pcp/pcp-RHEL-213658.patch
Jan Kurik 211742c239
Fix gating QA tests for CVE-2026-16524 and CVE-2026-16529 on 6.3.7
QA 2101 referenced undefined $seq_full on the 6.3.7 testsuite, and QA 2105
shipped an empty pdu-getpdu-overflow file so the integer overflow case was
never exercised.

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 13:35:05 +02:00

256 lines
7.9 KiB
Diff

From c5cbeceb7d Mon Sep 17 00:00:00 2001
From: Nathan Scott <nathans@redhat.com>
Subject: [PATCH] linux_sockets pmda: fix command injection via network.persocket.filter (CWE-78)
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>
Resolves: RHEL-213658 CVE-2026-16524
---
diff --git a/qa/2101 b/qa/2101
new file mode 100755
index 0000000000..733b7f70c9
--- /dev/null
+++ b/qa/2101
@@ -0,0 +1,69 @@
+#!/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
+
+# 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 0000000000..02f9ac5655
--- /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 c7db6290b2..0d9ef9ffb6 100644
--- a/qa/group
+++ b/qa/group
@@ -2219,4 +2219,5 @@ pmcd.pdu
2105 libpcp local security
2100 pmproxy local security
2104 libpcp local security
+2101 linux_sockets local security
4751 libpcp threads valgrind local pcp helgrind
diff --git a/src/pmdas/linux_sockets/pmda.c b/src/pmdas/linux_sockets/pmda.c
index 5a3018d8aa..59e69a6756 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 421c65fd16..833fc275a0 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);
}