eBPF-based Linux kernel guard that lets only allowlisted binaries use the TPM.
Go
0
40 commits
updated Oct 4, 2026

tpmlsm decides which programs may open the TPM on a Linux machine. You give it
a list of binaries, it compiles that list in, and from then on the kernel
refuses every other program that tries to open /dev/tpm0 or /dev/tpmrm0,
also when it runs as root. Everything else on the machine works as before.
It's built on eBPF and BPF LSM. A binary is on the list by its path and the SHA-256 of its contents, so changing the file, or copying it somewhere else, takes it off the list.
Here tpm2_getrandom is on the list. Run as root, it can still read random
bytes from the TPM, but cat can't open the TPM device:
$ sudo tpm2_getrandom --hex 8 -T device:/dev/tpmrm0
05fad09798a92bca
$ sudo cat /dev/tpm0
cat: /dev/tpm0: Operation not permitted
With -watch, tpmlsm stays in the foreground and logs every attempt to open
the TPM, here the allowed tpm2_getrandom and the refused cat:
$ sudo ./tpmlsm -watch
2026/10/04 22:35:32 allow sha256=97e1fc0f22d92de63204eec74076a003d1ca820d1d6db3c8fde3227f1ca7f4fc /usr/bin/tpm2
2026/10/04 22:35:33 enforcing until reboot; pinned to /sys/fs/bpf/tpmlsm
2026/10/04 22:35:33 watching, Ctrl-C to stop (enforcement stays)
2026/10/04 22:35:34 ALLOW pid=1130 comm=tpm2_getrandom dev=252:65536
2026/10/04 22:35:34 DENY pid=1132 comm=cat dev=10:224
tpmlsm was prototyped during eBPF and vTPM work for a customer, where the TPM
protects the private keys used for mTLS. This repository is a reference
implementation, published alongside the blog post A TPM bouncer in
eBPF; it is not the
code that runs there.
tpmlsm requires >= 5.18 kernel, for bpf_ima_file_hash. It has been tested
on Ubuntu 24.04 with 6.8.0-146-generic.
The following kernel configuration is required.
| Option | Note |
|---|---|
| CONFIG_BPF_SYSCALL=y | |
| CONFIG_DEBUG_INFO_BTF=y | |
| CONFIG_BPF_LSM=y | must also be enabled at boot, see below |
| CONFIG_IMA=y | hashes the binary at exec |
You can use grep $OPTION /boot/config-$(uname -r) to validate whether an
option is enabled.
BPF LSM also has to be in the active LSM list, which on Ubuntu it is not by default. Check with:
cat /sys/kernel/security/lsm
If bpf is missing, append it to that list and pass it as lsm= on the kernel
command line. On Ubuntu:
echo 'GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT lsm=lockdown,capability,landlock,yama,apparmor,bpf ima_hash=sha256"' | sudo tee /etc/default/grub.d/99-tpmlsm.cfg
sudo update-grub && sudo reboot
tpmlsm refuses to start when BPF LSM is not active. IMA has to hash with
SHA-256 (ima_hash=sha256, the default on Ubuntu); with any other algorithm
every binary is denied.
Add every binary that may open the TPM to allowlist.txt as sha256sum
prints it, the SHA-256 followed by the file's real path, and rebuild:
sha256sum "$(readlink -f /usr/bin/tpm2_getrandom)" >> allowlist.txt
make
tpmlsm refuses to start with an empty or malformed allowlist. Run
sudo ./tpmlsm at every boot, before anything that uses the TPM, and add
-watch to run it in the foreground and log every allowed and denied open.
There's no command to remove it, so changing the list means building and
shipping a new tpmlsm and rebooting. Root can still delete its pins; see
Limitations.
python3 allows
every Python script, and on Ubuntu all tpm2_* commands are one tpm2
binary.LD_PRELOAD, and root also with
/etc/ld.so.preload or a debugger. Prefer static binaries, such as Go built
with CGO_ENABLED=0.tpmlsm and
reboot.tpmlsm loads are denied until they restart.tpmlsm off by deleting its pins, loading a kernel
module or booting another kernel. Secure Boot and kernel lockdown (both modes
work) make that harder. More hooks that refuse to delete the pins, unmount
bpffs or detach the programs would close the rest while the machine runs,
leaving a reboot as the only way. Attestation can then show a verifier whether
tpmlsm loaded again after the reboot.libbpf-dev)linux-tools)On Ubuntu:
sudo apt-get install -y clang llvm libbpf-dev golang-go linux-tools-$(uname -r) linux-tools-common make
make
The generated BPF objects in cmd/tpmlsm are checked in, so make only needs
Go. It builds for the machine you're on; from macOS, make GOOS=linux builds
the Linux binary. To regenerate the objects after changing bpf/tpmlsm.c, on
Linux:
make generate
The Go code is licensed under the BSD 3-Clause License. The BPF
code in bpf/ is dual-licensed under the
GPL-2.0-only and the BSD 3-Clause License;
you can use the terms of either license, at your option.
The bouncer gopher is based on the Go gopher designed by Renee French.
Go
61.9%
C
30.4%
Makefile
7.7%
eBPF-based Linux kernel guard that lets only allowlisted binaries use the TPM.
Go
0
40 commits
updated Oct 4, 2026

tpmlsm decides which programs may open the TPM on a Linux machine. You give it
a list of binaries, it compiles that list in, and from then on the kernel
refuses every other program that tries to open /dev/tpm0 or /dev/tpmrm0,
also when it runs as root. Everything else on the machine works as before.
It's built on eBPF and BPF LSM. A binary is on the list by its path and the SHA-256 of its contents, so changing the file, or copying it somewhere else, takes it off the list.
Here tpm2_getrandom is on the list. Run as root, it can still read random
bytes from the TPM, but cat can't open the TPM device:
$ sudo tpm2_getrandom --hex 8 -T device:/dev/tpmrm0
05fad09798a92bca
$ sudo cat /dev/tpm0
cat: /dev/tpm0: Operation not permitted
With -watch, tpmlsm stays in the foreground and logs every attempt to open
the TPM, here the allowed tpm2_getrandom and the refused cat:
$ sudo ./tpmlsm -watch
2026/10/04 22:35:32 allow sha256=97e1fc0f22d92de63204eec74076a003d1ca820d1d6db3c8fde3227f1ca7f4fc /usr/bin/tpm2
2026/10/04 22:35:33 enforcing until reboot; pinned to /sys/fs/bpf/tpmlsm
2026/10/04 22:35:33 watching, Ctrl-C to stop (enforcement stays)
2026/10/04 22:35:34 ALLOW pid=1130 comm=tpm2_getrandom dev=252:65536
2026/10/04 22:35:34 DENY pid=1132 comm=cat dev=10:224
tpmlsm was prototyped during eBPF and vTPM work for a customer, where the TPM
protects the private keys used for mTLS. This repository is a reference
implementation, published alongside the blog post A TPM bouncer in
eBPF; it is not the
code that runs there.
tpmlsm requires >= 5.18 kernel, for bpf_ima_file_hash. It has been tested
on Ubuntu 24.04 with 6.8.0-146-generic.
The following kernel configuration is required.
| Option | Note |
|---|---|
| CONFIG_BPF_SYSCALL=y | |
| CONFIG_DEBUG_INFO_BTF=y | |
| CONFIG_BPF_LSM=y | must also be enabled at boot, see below |
| CONFIG_IMA=y | hashes the binary at exec |
You can use grep $OPTION /boot/config-$(uname -r) to validate whether an
option is enabled.
BPF LSM also has to be in the active LSM list, which on Ubuntu it is not by default. Check with:
cat /sys/kernel/security/lsm
If bpf is missing, append it to that list and pass it as lsm= on the kernel
command line. On Ubuntu:
echo 'GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT lsm=lockdown,capability,landlock,yama,apparmor,bpf ima_hash=sha256"' | sudo tee /etc/default/grub.d/99-tpmlsm.cfg
sudo update-grub && sudo reboot
tpmlsm refuses to start when BPF LSM is not active. IMA has to hash with
SHA-256 (ima_hash=sha256, the default on Ubuntu); with any other algorithm
every binary is denied.
Add every binary that may open the TPM to allowlist.txt as sha256sum
prints it, the SHA-256 followed by the file's real path, and rebuild:
sha256sum "$(readlink -f /usr/bin/tpm2_getrandom)" >> allowlist.txt
make
tpmlsm refuses to start with an empty or malformed allowlist. Run
sudo ./tpmlsm at every boot, before anything that uses the TPM, and add
-watch to run it in the foreground and log every allowed and denied open.
There's no command to remove it, so changing the list means building and
shipping a new tpmlsm and rebooting. Root can still delete its pins; see
Limitations.
python3 allows
every Python script, and on Ubuntu all tpm2_* commands are one tpm2
binary.LD_PRELOAD, and root also with
/etc/ld.so.preload or a debugger. Prefer static binaries, such as Go built
with CGO_ENABLED=0.tpmlsm and
reboot.tpmlsm loads are denied until they restart.tpmlsm off by deleting its pins, loading a kernel
module or booting another kernel. Secure Boot and kernel lockdown (both modes
work) make that harder. More hooks that refuse to delete the pins, unmount
bpffs or detach the programs would close the rest while the machine runs,
leaving a reboot as the only way. Attestation can then show a verifier whether
tpmlsm loaded again after the reboot.libbpf-dev)linux-tools)On Ubuntu:
sudo apt-get install -y clang llvm libbpf-dev golang-go linux-tools-$(uname -r) linux-tools-common make
make
The generated BPF objects in cmd/tpmlsm are checked in, so make only needs
Go. It builds for the machine you're on; from macOS, make GOOS=linux builds
the Linux binary. To regenerate the objects after changing bpf/tpmlsm.c, on
Linux:
make generate
The Go code is licensed under the BSD 3-Clause License. The BPF
code in bpf/ is dual-licensed under the
GPL-2.0-only and the BSD 3-Clause License;
you can use the terms of either license, at your option.
The bouncer gopher is based on the Go gopher designed by Renee French.
Go
61.9%
C
30.4%
Makefile
7.7%