Linux x86 Shadow Stacks: Verify CET Across Hardware, Kernel, and Loader
Check Linux x86 shadow-stack support from CPU and kernel through ELF metadata and each running thread, without mistaking one probe for enablement.
Intel Control-flow Enforcement Technology (CET) includes shadow stacks and indirect branch tracking (IBT), which protect different control-flow edges. A shadow stack keeps protected return addresses alongside the ordinary stack and detects a mismatch when a function returns. IBT constrains indirect call and jump targets. On 64-bit Linux, the kernel documentation describes userspace shadow-stack support and kernel IBT; do not describe that as general userspace IBT support or as complete control-flow integrity.
An application uses a shadow stack only when the relevant processor, kernel configuration, executable or runtime, and loader all line up. A CPU flag, an ELF note, or a kernel version by itself is not proof that a particular thread is protected.
Check each layer independently
The Linux x86 documentation lists X86_USER_SHADOW_STACK as the kernel configuration option. At runtime, /proc/cpuinfo reports user_shstk when the current CPU and kernel support userspace shadow stacks. The same documentation requires compatible hardware, a configured kernel, and userspace compiled with the feature. A distribution can omit the configuration even when the processor implements CET.
An executable can carry the ELF SHSTK feature property. Inspect it with readelf -n; that note describes the binary’s declared capability, not the status of a running process. The kernel documentation says it does not enable the feature directly from the ELF marker. A dynamic loader or static runtime uses the arch_prctl() interface to enable it.
Use the following as a diagnostic sequence, substituting the actual executable and process/thread IDs:
uname -m
grep -m1 -w user_shstk /proc/cpuinfo || true
readelf -n ./service | grep -a SHSTK || true
grep -E '^x86_Thread_features(_locked)?:' \
"/proc/$PID/task/$TID/status"
The first check scopes the question to x86-64; the second is a CPU/kernel capability probe; the third inspects one file’s notes; the last checks the feature state reported for a specific task. shstk in the thread-feature field is evidence that the thread has shadow stack enabled. wrss is a distinct write-to-shadow-stack feature bit, but Linux only permits it together with shstk and disables it when shadow stack is disabled. A missing ELF note does not prove that every library or runtime lacks support, and a present note does not prove the loader enabled CET for the running thread.
Account for thread and exec lifetimes
The arch_prctl() operations are per-thread. Linux documents that enablement is inherited when a thread is created with clone, while exec disables the feature in the kernel so userspace can choose whether to enable or lock it again. A probe of the main thread therefore does not establish the state of every worker after a complex startup sequence. Inspect the task that performs the sensitive work, and repeat the check after the program’s loader and worker initialization have completed.
The API exposes ARCH_SHSTK_ENABLE, ARCH_SHSTK_DISABLE, ARCH_SHSTK_LOCK, and ARCH_SHSTK_STATUS. STATUS copies the currently enabled feature mask to userspace; unsupported hardware or kernel support can produce an unsupported-operation error. A lock prevents later changes to selected feature bits, while unlocking is restricted to a ptrace path. Applications should use a supported loader/runtime integration where possible rather than sprinkling architecture-specific syscalls through unrelated code.
Shadow stacks protect return-address integrity; they do not stop data-only corruption, authenticate a process, or make arbitrary code in the same address space trustworthy. They are also not a substitute for memory safety, process isolation, or a separately reviewed policy for indirect branches. Describe the exact property enabled, not a broad claim that “CET is on.”
Test a deployment, not only its build output
For each supported machine and kernel, record the CPU capability, kernel release/configuration, compiler and linker versions, libc/loader, ELF note, and relevant thread status. Test both the main execution path and worker creation, then repeat after exec boundaries and normal service restart. If a feature is required by the threat model, fail closed or select a tested platform when any layer is unavailable; do not silently treat an ELF note as runtime enforcement.
The commands above are read-only probes. They were not exercised on a representative CET-capable Linux host for this draft, so they are a validation recipe rather than a compatibility claim. Verify exact output and deployment behavior on the minimum kernel and distribution you support.
Related:
- Linux x86 Protection Keys: Per-Thread PKRU Policy for Data Pages
- SELinux vs. AppArmor: Mandatory Access Control on Linux
Sources: