Linux Kernel 移除 x86 中理應更快卻不安全的函數:它們在現代 CPU 上也更慢
Linux Kernel Removes Supposedly Fast but Unsafe Functions From x86: They Were Slower on Modern CPUs, Too
Torvalds 從 x86 kernel 移除 __get_user() 和 __put_user() 及其介面,改動涵蓋 48 個 commits、50 個檔案,刪減 801 行。現代 CPU 上,STAC/CLAC 的開關成本令這些略過地址驗證的函數比標準函數更慢,亦帶來安全風險。Claude 參與部分轉換;其他架構仍有非簡單用法,交由各自維護者處理。
Torvalds has removed __get_user() and __put_user(), including their interfaces, from the x86 kernel. Claude was used for part of the conversion, which spans 48 commits and 50 files.
Key points
- What happened: __get_user() and __put_user(), including their interfaces, have been removed from the x86 kernel: 48 commits, 50 files, and a reduction of 801 lines
- Why it matters: The supposedly fast functions were slower than the standard functions because of STAC/CLAC costs on modern CPUs, and their lack of address validation also posed a security risk
- Background: The introduction of scoped user access helpers in 2025 simplified conversion patterns, and Torvalds kicked off the cleanup by writing an example patch
- Remaining work: Nontrivial uses remain on several architectures, including sparc32, m68k, and MIPS, and have been left to their respective maintainers
When the trade-off for speed reversed
The Linux kernel has two pairs of functions for transferring data between user space and kernel space. get_user() and put_user() check that a supplied pointer is a valid user-space address before accessing it. Their counterparts with two leading underscores, __get_user() and __put_user(), skip that check.
Speed was the reason for skipping validation. When reading or writing multiple fields from the same base pointer, manually checking the address range once at the start and then repeatedly accessing it through __get_user() without further checks could produce smaller, faster code.
Torvalds wrote in the merge commit:
The key word here is "used to." That has not been true for years, because range checking is not the real cost today. On modern hardware, the real cost of user access is turning user access itself on and off (using the STAC/CLAC instructions on x86).
STAC (Set AC Flag) and CLAC (Clear AC Flag) control x86's SMAP security feature, opening and closing a window for the kernel to access user-space memory. On modern CPUs, where opening and closing this window is costly, it is faster to open it once with scoped user access, call unsafe_get_user() multiple times inside it, and close it at the end than to open and close it for every call to __get_user().
In other words, __get_user() was supposed to gain speed by skipping validation, but on modern hardware it was slower than the correct approach and also unsafe because it did not validate addresses. In Torvalds' words, "when misused (i.e., in all cases), it is not only slower than doing things correctly, but also unsafe because it does not check the user address range."
Removing four excuses
Performance was not the only reason __get_user() and __put_user() remained in use. Torvalds identified four patterns in the commit message.
The first was performance, as discussed above. It was a relic of an era when range-checking costs dominated, and lost its justification once STAC/CLAC costs became dominant.
The second was access to kernel pointers with exception handling. In fault handlers that needed to read values through unreliable kernel pointers, __get_user() was used to reuse its exception-handling machinery. There is now a dedicated helper, get_kernel_nofault(). This patch series replaced such uses with that helper in functions including dik_show_code() on Alpha and show_registers() on OpenRISC.
The third was avoiding warnings in atomic contexts. The standard get_user() includes might_fault(), which produces a warning when called in an atomic context. Developers used __get_user() to avoid that warning, but should instead have explicitly called pagefault_disable() to tell the kernel that page faults would not cause problems. The vdpa_sim driver fix is a typical example: adding pagefault_disable() alongside local_bh_disable() allowed the standard get_user() to be used without warnings.
The fourth was what Torvalds called "the really nasty cases": handling pointers that could refer to either kernel or user memory. A representative example is the #VC interrupt handler for x86 SEV (Secure Encrypted Virtualization). During MMIO emulation, the #VC handler needs to read or write user or kernel memory without knowing which kind of address it will receive. The patch replaced this with explicit vc_get_mem() and vc_put_mem() helpers, involving 205 lines of changes.
A major factor in making it possible to remove all these excuses was Thomas Gleixner's implementation of scoped user access region helpers in 2025 (commit e497310b4ffb). Block constructs such as scoped_user_read_access() and scoped_user_write_access() automatically manage opening and closing the access window, simplifying conversion patterns. Torvalds said they had "completely removed the old excuses for the horrors."
"A fancy coccinelle script"
The patch series began with a single patch Torvalds wrote himself as an example conversion. It rewrote the sigaction system call's signal-handling code into modern code using scoped user access.
The patches grew from there. Torvalds wrote on the dri-devel mailing list:
Yeah. I was using claude as a fancy coccinelle script and ended up doing the ObviouslyCorrect safe cases too.
Coccinelle is a source-code transformation tool widely used in the Linux kernel, specializing in bulk changes through pattern matching. Torvalds' conversions involved repeatedly applying structured patterns, making this use of an LLM closer to pattern application than code generation. Following last week's associative array bug fixes (in Japanese) and page-cache improvements using Claude Code (in Japanese), Torvalds' integration of AI into practical kernel development is gathering pace.
Review also progressed on the mailing list. x86 maintainer Ingo Molnar confirmed successful builds and boots on both 64-bit and 32-bit systems, while vhost maintainer Michael S. Tsirkin reported passing tests in a VM and supplied an Acked-by.
Torvalds was candid about the decision to merge:
I am not saying the patches are perfect. But they are an improvement. It is better to merge early and find problems than to leave known broken code in place.
The i915 graphics driver still contained an old comment dating back to verify_area(VERIFY_WRITE) in the Linux 2.5.x era. It was a remnant of code written for COW handling in 32-bit kernels, whose purpose had ended more than 20 years ago. This patch series finally cleared it away.
Gone from x86; the rest depends on motivation
The 48-commit series removed all __get_user()/__put_user() calls from generic code (areas shared by all architectures, such as kernel/, drivers/, and net/). On x86, it removed the entire interface, from header macro definitions to assembly implementation functions. The four __get_user_nocheck_1/2/4/8 functions disappeared from getuser.S, along with their four counterparts in putuser.S.
Some uses remain on other architectures. On arm64, __get_user() and get_user() already had identical implementations, so leaving them alone is harmless: they do not pose the risk of skipping validation. On architectures using asm-generic/uaccess.h, the names remain as internal helpers.
Conversions on sparc32, m68k, LoongArch, PA-RISC, and MIPS were nontrivial, and Torvalds did not tackle them. His words on the mailing list capture the nature of the work:
I looked at the sparc32 signal-handling case and decided somebody else should do it. It is too ugly, and I have no energy left to care. The same goes for m68k, loongarch, parisc, and mips. I ran out of motivation.
I confirmed that the commit's diffstat includes changes to 50 files, with signal and trap handling rewritten across architectures including Alpha, ARM, Hexagon, Nios II, OpenRISC, PowerPC, and s390.
Clearing out technical debt is always unglamorous work. Unlike adding features, the effect of deleted code is not visible. But removing __get_user() from the Linux kernel means eliminating the room on x86 for someone to decide that skipping validation is probably acceptable. A compromise once tolerated for speed had lost that speed, leaving only the risk. When Torvalds wrote that this should have been done years ago, he was stating a fact, rather than expressing regret.
Originally published on Joho no Todai. The Japanese original is here.
來源:DEV Community · dev.to