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>
256 lines
7.9 KiB
Diff
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);
|
|
}
|