Skip to content
LinuxDeep Dive Published Updated 4 min readViews unavailable

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:

Sources:

Comments