Re: Save a WRMSR GS.base?

From: H. Peter Anvin

Date: Mon Jun 08 2026 - 17:58:45 EST


On June 8, 2026 2:21:00 PM PDT, Borislav Petkov <bp@xxxxxxxxx> wrote:
>On Mon, Jun 08, 2026 at 01:05:28PM -0700, Borislav Petkov wrote:
>> I think this is begging to be written down somewhere. Lemme point AI to it and
>> see what it would generate.
>
>Something like the below... I am thinking of sticking that somewhere under
>Documentation... oh look: Documentation/arch/x86/x86_64/fsgs.rst. Looks like
>there was already need to document this stuff which is clearly not really
>transparent. What's there is covering the userspace side more tho.
>
>Anyway, the below is a summary of our thread with AI, it ain't half-bad and
>I think we should write it down for future reference.
>
>Thx.
>
>---
>GS handling on context switches
>
>SWAPGS
>
>Swaps the base-address value in MSR_KERNEL_GS_BASE with the active GS.base in
>the hidden portion of the GS selector register.
>
>MOV <segment selector>, GS
>
>(legacy path, non-FRED) Loads the GS selector and fetches the descriptor
>attributes and base from the GDT/LDT. Writes a 32-bit base into the active
>GS.base. Does not touch MSR_KERNEL_GS_BASE. Useless for task switching the
>user GS base on its own without surrounding SWAPGS calls.
>
>LKGS <selector>
>
>(FRED path, replaces MOV GS) Like MOV GS in that it loads the selector and
>descriptor attributes, but it redirects the base write — instead of updating
>the active GS.base, it writes the descriptor base into IA32_KERNEL_GS_BASE
>(i.e. MSR_KERNEL_GS_BASE).
>
>Critical caveat: it only writes a zero-extended 32-bit value, because GDT/LDT
>descriptors only encode 32-bit bases. This means it cannot correctly represent
>a full 64-bit user-space GS base (e.g. a TLS pointer), so a full 64-bit WRMSR
>is still required afterwards.
>
>WRGSBASE <reg>
>
>(with REX.W) Writes a full 64-bit value directly into the currently active
>GS.base as FS.base and GS.base in 64-bit mode are expanded to 64-bit to cover
>the full address space.
>
>The problem in kernel context: the currently active GS.base belongs to the
>kernel, not the user task. So using this during context switching would
>corrupt the kernel's own GS.base, unless surrounded by SWAPGS (only safe in
>IDT mode). Without REX.W, the upper 32 bits of the base are cleared instead.
>
>WRMSR MSR_KERNEL_GS_BASE / WRMSRNS MSR_KERNEL_GS_BASE
>
>Writes a full 64-bit value into MSR_KERNEL_GS_BASE, which holds the inactive
>(user-space) GS base — the one that gets swapped into the active GS.base on
>SWAPGS. This is the only instruction that can correctly set a 64-bit user GS
>base during a context switch from kernel mode. WRMSRNS is the non-serializing
>variant, which is preferable here since this MSR is architecturally
>non-serializing anyway.
>

Looks good to me.

You might want to add that despite the name, while running in the kernel (which is the only time MSRs are accessible) KEENEL_GS_BASE, despite its name, actually contains the *user* GS base.

The naming was confusing to begin with, and is even more so with FRED.

Also, MOV GS/LKGS is the only way to update the *other* fields of the GS descriptor (the base and the flags.)