From 794a2448f4481c6eb992270e979e6fde51aff863 Mon Sep 17 00:00:00 2001 From: xiejing Date: Tue, 4 Aug 2026 20:12:34 +0800 Subject: [PATCH] libpcp: fix stack buffer overflow in __pmLogLoadMeta pmDesc name read A TYPE_DESC metadata record in a PCP archive stores each metric name as a length-prefixed string. __pmLogLoadMeta() read the 4-byte length, byte-swapped it with ntohl(), and used it directly as the count for __pmFread(name, 1, len, f); name[len] = '\0'; where name is a fixed char name[MAXPATHLEN] (4096-byte) stack buffer, with no upper-bound check. A crafted archive setting the name length > 4096 overflows the buffer and smashes the stack (observed: SIGABRT from __stack_chk_fail). Add a bounds check that rejects len < 0 or len >= MAXPATHLEN with PM_ERR_LOGREC ("Corrupted record in a PCP archive"). QA: add test 2110 (crafts a malformed archive with an oversized pmDesc name length and asserts pmlogcheck/pminfo reject it gracefully instead of crashing). --- qa/2110 | 77 ++++++++++++++++++++++++++++++++++++++++ qa/2110.out | 8 +++++ qa/group | 1 + src/libpcp/src/logmeta.c | 13 +++++++ 4 files changed, 99 insertions(+) create mode 100755 qa/2110 create mode 100644 qa/2110.out diff --git a/qa/2110 b/qa/2110 new file mode 100755 index 0000000000..3147ca1361 --- /dev/null +++ b/qa/2110 @@ -0,0 +1,77 @@ +#!/bin/sh +# PCP QA Test No. 2110 +# Verify a crafted archive with an oversized pmDesc name length is rejected +# gracefully by __pmLogLoadMeta, instead of overflowing the fixed stack +# buffer name[MAXPATHLEN] in src/libpcp/src/logmeta.c. +# +# Copyright (c) 2026 KylinSoft Co., Ltd. All Rights Reserved. +# + +seq=`basename $0` +echo "QA output created by $seq" + +# get standard environment, filters and checks +. ./common.product +. ./common.filter +. ./common.check + +command -v python3 >/dev/null 2>&1 || _notrun "python3 not installed" + +_cleanup() +{ + cd $here + $sudo rm -rf $tmp $tmp.* +} + +_filter() +{ + sed -e "s@$tmp@TMP@g" +} + +status=1 # failure is the default! +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# real QA test starts here +# +# Build a malformed archive from a valid one: in the first TYPE_DESC +# metadata record, set the name length field to 8192 (> MAXPATHLEN, 4096) +# and pad with that many bytes. Without the bounds check in +# __pmLogLoadMeta(), the subsequent +# __pmFread(name, 1, len, f); name[len] = '\0'; +# overflows name[MAXPATHLEN] on the stack (observed: "stack smashing +# detected" / SIGABRT). With the fix, loading is rejected with +# PM_ERR_LOGREC ("Corrupted record in a PCP archive") and no crash. +# +python3 - "$tmp/evil" <<'EOF' +import struct, shutil, sys +src = "archives/19970807.09.54" +dst = sys.argv[1] +BE = ">i" +def be(x): return struct.pack(BE, x) +EVIL = 8192 # >> MAXPATHLEN (4096) +meta = open(src + ".meta", "rb").read() +label_total = struct.unpack(BE, meta[0:4])[0] +label = meta[0:label_total] +off = label_total +rec_len = struct.unpack(BE, meta[off:off+4])[0] +body = meta[off+8:off+rec_len] # pmDesc + numnames + name[len] + trailer +pmDesc = body[0:20] +numnames = struct.unpack(BE, body[20:24])[0] +new_body = pmDesc + be(numnames) + be(EVIL) + (b"A" * EVIL) +new_rec_len = 8 + len(new_body) + 4 # header + body + trailer +new_rec = be(new_rec_len) + be(1) + new_body + be(new_rec_len) +open(dst + ".meta", "wb").write(label + new_rec) +shutil.copyfile(src + ".0", dst + ".0") +shutil.copyfile(src + ".index", dst + ".index") +EOF + +echo "=== pmlogcheck (must not crash) ===" +pmlogcheck $tmp/evil 2>&1 | _filter + +echo +echo "=== pminfo -a (must not crash) ===" +pminfo -a $tmp/evil 2>&1 | _filter + +# success, all done +status=0 +exit diff --git a/qa/2110.out b/qa/2110.out new file mode 100644 index 0000000000..74be20b9a1 --- /dev/null +++ b/qa/2110.out @@ -0,0 +1,8 @@ +QA output created by 2110 + +=== pmlogcheck (must not crash) === +pmlogcheck: cannot open archive "TMP/evil": Corrupted record in a PCP archive +Checking abandoned. + +=== pminfo -a (must not crash) === +pminfo: Cannot open archive "TMP/evil": Corrupted record in a PCP archive diff --git a/qa/group b/qa/group index fda6415c53..cd44a002c3 100644 --- a/qa/group +++ b/qa/group @@ -2421,5 +2421,6 @@ suse 2107 libpcp local security 2108 pmproxy local security 2109 pmproxy local security +2110 libpcp archive local security 4751 libpcp threads valgrind local pcp helgrind 9000 other local diff --git a/src/libpcp/src/logmeta.c b/src/libpcp/src/logmeta.c index f849927175..a68b260c12 100644 --- a/src/libpcp/src/logmeta.c +++ b/src/libpcp/src/logmeta.c @@ -886,6 +886,19 @@ __pmLogLoadMeta(__pmArchCtl *acp) len = ntohl(len); } + if (len < 0 || len >= MAXPATHLEN) { + /* + * name[] is a fixed MAXPATHLEN-byte stack buffer; a crafted + * archive could otherwise overflow it via the unchecked len. + */ + if (pmDebugOptions.logmeta) { + fprintf(stderr, "%s: name[%d] length %d exceeds name buffer\n", + "__pmLogLoadMeta", i, len); + } + sts = PM_ERR_LOGREC; + goto end; + } + if ((n = (int)__pmFread(name, 1, len, f)) != len) { if (pmDebugOptions.logmeta) { fprintf(stderr, "%s: name[%d] read -> %d: expected: %d\n",