TL;DR
tpmlsm은 바이너리 경로와 SHA-256 해시로 허용 목록을 구성해, root 권한을 포함해 목록에 없는 프로그램의/dev/tpm0및/dev/tpmrm0접근을 차단하는 eBPF 기반 커널 가드임BPF LSM과bpf_ima_file_hash를 사용하며, 파일이 바뀌거나 다른 경로로 복사되면 허용 목록에서 제외됨- 허용된
tpm2_getrandom은 TPM에서 난수를 읽고cat은 장치 열기가 거부되는 동작을 확인할 수 있음 - Linux 5.18 이상과 필수 커널 설정이 필요하며, Ubuntu에서는
BPF LSM활성화와 SHA-256 IMA 해시 설정이 요구됨 - 허용 바이너리의 동작 범위, 로드 라이브러리, 실행 중인 프로세스, root의 핀 삭제 등 제한이 있으며, 저장소 코드는 고객 환경에서 실행되는 코드가 아닌 참고 구현임
실행
tpmlsm은 Linux 시스템에서 TPM을 열 수 있는 프로그램을 지정하며, 바이너리 목록을 컴파일하면 목록에 없는 프로그램의/dev/tpm0또는/dev/tpmrm0열기를 커널이 거부함- 이 제한은 root 권한으로 실행되는 프로그램에도 적용되며, 그 밖의 시스템 동작은 이전과 동일함
- 허용 항목은 바이너리 경로와 파일 내용의 SHA-256 해시로 식별됨. 파일을 변경하거나 다른 위치에 복사하면 허용 대상에서 제외됨
- 목록에
tpm2_getrandom을 넣으면 root로 실행해도 TPM에서 난수를 읽을 수 있지만,cat의/dev/tpm0접근은Operation not permitted오류로 거부됨 -watch옵션을 사용하면tpmlsm이 포그라운드에서 실행되며 허용·거부된 TPM 열기 시도를 기록함. 예시에서는tpm2_getrandom이ALLOW,cat이DENY로 표시됨- 고객용 상호 TLS(mTLS) 개인 키를 TPM으로 보호하는 eBPF 및 가상 TPM(vTPM) 작업 중 시제품을 제작함. 저장소는 블로그 게시물 ‘A TPM bouncer in eBPF’와 함께 공개한 참고 구현이며, 고객 환경에서 실제 실행되는 코드는 아님
요구 사항
bpf_ima_file_hash를 위해 Linux 커널 5.18 이상이 필요하며, Ubuntu 24.04의6.8.0-146-generic에서 테스트됨- 필요한 커널 설정은
CONFIG_BPF_SYSCALL=y,CONFIG_DEBUG_INFO_BTF=y,CONFIG_BPF_LSM=y,CONFIG_IMA=y임.CONFIG_BPF_LSM은 부팅 시에도 활성화해야 하며, IMA는 실행 시 바이너리를 해시함 /boot/config-$(uname -r)에서grep $OPTION으로 각 설정의 활성화 여부를 확인할 수 있음BPF LSM은 활성 LSM 목록에도 포함돼야 하며, Ubuntu에서는 기본적으로 포함되지 않음./sys/kernel/security/lsm을 확인하고bpf가 없으면 목록에 추가한 뒤 커널 명령줄에lsm=으로 전달해야 함- Ubuntu 설정 예시는
/etc/default/grub.d/99-tpmlsm.cfg에lsm=lockdown,capability,landlock,yama,apparmor,bpf ima_hash=sha256을 추가하고,update-grub을 실행한 다음 재부팅하는 방식임 BPF LSM이 활성화되지 않으면tpmlsm은 시작을 거부함. IMA 해시 알고리즘은 SHA-256이어야 하며, Ubuntu의 기본값인ima_hash=sha256외의 알고리즘을 사용하면 모든 바이너리가 거부됨
허용 목록
- TPM을 열 수 있어야 하는 각 바이너리를
allowlist.txt에sha256sum형식으로 추가해야 함. 형식은 SHA-256 해시 뒤에 파일의 실제 경로를 두는 방식임 /usr/bin/tpm2_getrandom의 실제 경로를 확인해 해시와 함께 목록에 추가한 뒤make로 다시 빌드하는 절차임- 허용 목록이 비어 있거나 형식이 잘못되면
tpmlsm은 시작을 거부함 - TPM을 사용하는 프로그램보다 먼저 부팅 때마다
sudo ./tpmlsm을 실행해야 함. 허용·거부된 열기 시도를 기록하려면-watch를 추가해 포그라운드에서 실행함 - 목록 항목을 제거하는 명령은 없음. 목록 변경에는 새
tpmlsm을 빌드해 배포하고 재부팅해야 함 - root는 핀을 삭제할 수 있으며, 관련 제한 사항은 아래와 같음
제한 사항
- 바이너리 하나를 허용하면 그 바이너리가 수행할 수 있는 모든 동작이 허용됨.
python3을 허용하면 모든 Python 스크립트가 허용되며, Ubuntu에서는 모든tpm2_*명령이 하나의tpm2바이너리임 - 검사 대상은 바이너리뿐이며 로드하는 라이브러리는 검사하지 않음. 허용 프로그램을 실행할 수 있는 사용자는
LD_PRELOAD로 코드를 주입할 수 있고, root는/etc/ld.so.preload나 디버거도 사용할 수 있음.CGO_ENABLED=0으로 빌드한 Go 프로그램처럼 정적 바이너리를 선호하는 방식임 ext4에서 테스트됨.btrfs에서는 파일 조회가 일치하지 않아 모든 TPM 열기가 거부됨- 허용 바이너리를 업데이트하면 새
tpmlsm을 배포하고 재부팅할 때까지 해당 바이너리의 접근이 차단됨 tpmlsm로드 당시 이미 실행 중인 프로그램은 재시작할 때까지 접근이 거부됨- root는 핀 삭제, 커널 모듈 로드, 다른 커널로 부팅해
tpmlsm을 끌 수 있음. 보안 부팅(Secure Boot)과 커널 잠금(kernel lockdown)의 두 모드는 이를 더 어렵게 함 - 핀 삭제,
bpffs언마운트, 프로그램 분리를 거부하는 후크를 추가하면 시스템이 실행되는 동안 나머지 우회 경로도 막을 수 있으며, 재부팅만 가능한 상태가 됨. 이후 원격 검증자는 재부팅 뒤tpmlsm이 다시 로드됐는지 증명(attestation)으로 확인할 수 있음
개발
의존성
- Go 1.26 이상
- LLVM/clang
- libbpf 헤더(
libbpf-dev) bpftool(linux-tools)make- Ubuntu에서는
clang,llvm,libbpf-dev,golang-go,linux-tools-$(uname -r),linux-tools-common,make패키지를 설치하는 방식임
빌드
make로 빌드함cmd/tpmlsm의 생성된 BPF 객체가 저장소에 포함돼 있어 일반 빌드에는 Go만 필요함- 빌드는 현재 사용 중인 시스템용으로 생성됨. macOS에서는
make GOOS=linux로 Linux 바이너리를 빌드할 수 있음 bpf/tpmlsm.c를 변경한 뒤 객체를 다시 생성하려면 Linux에서make generate를 실행함
라이선스
- Go 코드는 BSD 3-Clause License로 제공됨
bpf/의 BPF 코드는 GPL-2.0-only와 BSD 3-Clause License의 이중 라이선스이며, 둘 중 하나의 조건을 선택해 사용할 수 있음
로고 크레딧
- 경비원 고퍼 그림은 Renee French가 디자인한 Go 고퍼를 바탕으로 함
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요