Fixes the consolidated MR !150 Resolves: RHEL-213662 Resolves: RHEL-213686 Resolves: RHEL-213720 Resolves: RHEL-213729
224 lines
6.5 KiB
Diff
224 lines
6.5 KiB
Diff
From 7800c577023232c994113c42336b84a2138cb561 Mon Sep 17 00:00:00 2001
|
|
From: Nathan Scott <nathans@redhat.com>
|
|
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) <noreply@anthropic.com>
|
|
---
|
|
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;
|
|
}
|