1*bdb56184SDevin Teske /*
2*bdb56184SDevin Teske * SPDX-License-Identifier: BSD-2-Clause
3*bdb56184SDevin Teske *
4*bdb56184SDevin Teske * Copyright (c) 2026 Devin Teske <dteske@FreeBSD.org>
5*bdb56184SDevin Teske */
6*bdb56184SDevin Teske
7*bdb56184SDevin Teske /*
8*bdb56184SDevin Teske * x86 memory protection keys (PKU) for the Linuxulator, serving both
9*bdb56184SDevin Teske * the 64-bit and 32-bit Linux ABIs.
10*bdb56184SDevin Teske *
11*bdb56184SDevin Teske * The PKRU register is directly user-visible: Linux programs read and
12*bdb56184SDevin Teske * write it with RDPKRU/WRPKRU, which execute natively. The kernel's
13*bdb56184SDevin Teske * part is key allocation bookkeeping (per address space: inherited on
14*bdb56184SDevin Teske * fork, reset on exec, as with Linux mm->context.pkey_allocation_map),
15*bdb56184SDevin Teske * tagging pages (pkey_mprotect), and applying the initial access
16*bdb56184SDevin Teske * rights of pkey_alloc() to the calling thread's PKRU.
17*bdb56184SDevin Teske */
18*bdb56184SDevin Teske
19*bdb56184SDevin Teske #include <sys/systm.h>
20*bdb56184SDevin Teske #include <sys/imgact.h>
21*bdb56184SDevin Teske #include <sys/lock.h>
22*bdb56184SDevin Teske #include <sys/pcpu.h>
23*bdb56184SDevin Teske #include <sys/proc.h>
24*bdb56184SDevin Teske #include <sys/sx.h>
25*bdb56184SDevin Teske
26*bdb56184SDevin Teske #include <machine/cpufunc.h>
27*bdb56184SDevin Teske #include <machine/fpu.h>
28*bdb56184SDevin Teske #include <machine/md_var.h>
29*bdb56184SDevin Teske #include <machine/pcb.h>
30*bdb56184SDevin Teske #include <machine/specialreg.h>
31*bdb56184SDevin Teske #include <machine/sysarch.h>
32*bdb56184SDevin Teske #include <x86/x86_var.h>
33*bdb56184SDevin Teske
34*bdb56184SDevin Teske #include <compat/linux/linux_emul.h>
35*bdb56184SDevin Teske #include <compat/linux/linux_mmap.h>
36*bdb56184SDevin Teske
37*bdb56184SDevin Teske static bool
linux_pkey_supported(void)38*bdb56184SDevin Teske linux_pkey_supported(void)
39*bdb56184SDevin Teske {
40*bdb56184SDevin Teske
41*bdb56184SDevin Teske return ((cpu_stdext_feature2 & CPUID_STDEXT2_OSPKE) != 0);
42*bdb56184SDevin Teske }
43*bdb56184SDevin Teske
44*bdb56184SDevin Teske /*
45*bdb56184SDevin Teske * Update the calling thread's PKRU: new value is (PKRU & keep) | set.
46*bdb56184SDevin Teske */
47*bdb56184SDevin Teske static void
linux_pkru_write(struct thread * td,uint32_t keep,uint32_t set)48*bdb56184SDevin Teske linux_pkru_write(struct thread *td, uint32_t keep, uint32_t set)
49*bdb56184SDevin Teske {
50*bdb56184SDevin Teske struct pcb *pcb;
51*bdb56184SDevin Teske struct xstate_hdr *hdr;
52*bdb56184SDevin Teske char *sa;
53*bdb56184SDevin Teske uint32_t *pkru;
54*bdb56184SDevin Teske
55*bdb56184SDevin Teske MPASS(td == curthread);
56*bdb56184SDevin Teske pcb = td->td_pcb;
57*bdb56184SDevin Teske
58*bdb56184SDevin Teske /*
59*bdb56184SDevin Teske * The critical section is held across the save area update to
60*bdb56184SDevin Teske * exclude preemption: a context switch could otherwise load the
61*bdb56184SDevin Teske * xsave area back into the CPU after fpugetregs(), and the
62*bdb56184SDevin Teske * stores below would then be lost to the next save.
63*bdb56184SDevin Teske */
64*bdb56184SDevin Teske critical_enter();
65*bdb56184SDevin Teske if ((pcb->pcb_flags & PCB_USERFPUINITDONE) != 0 &&
66*bdb56184SDevin Teske td == PCPU_GET(fpcurthread) && PCB_USER_FPU(pcb)) {
67*bdb56184SDevin Teske wrpkru((rdpkru() & keep) | set);
68*bdb56184SDevin Teske critical_exit();
69*bdb56184SDevin Teske return;
70*bdb56184SDevin Teske }
71*bdb56184SDevin Teske
72*bdb56184SDevin Teske /*
73*bdb56184SDevin Teske * The user FPU state is in the PCB save area, or is not yet
74*bdb56184SDevin Teske * initialized, in which case fpugetregs() installs the initial
75*bdb56184SDevin Teske * state there.
76*bdb56184SDevin Teske */
77*bdb56184SDevin Teske (void)fpugetregs(td);
78*bdb56184SDevin Teske sa = (char *)get_pcb_user_save_td(td);
79*bdb56184SDevin Teske hdr = (struct xstate_hdr *)(sa + xsave_area_hdr_offset());
80*bdb56184SDevin Teske pkru = (uint32_t *)(sa + xsave_area_offset(xsave_mask,
81*bdb56184SDevin Teske XFEATURE_ENABLED_PKRU, false, false));
82*bdb56184SDevin Teske if ((hdr->xstate_bv & XFEATURE_ENABLED_PKRU) == 0) {
83*bdb56184SDevin Teske hdr->xstate_bv |= XFEATURE_ENABLED_PKRU;
84*bdb56184SDevin Teske *pkru = 0;
85*bdb56184SDevin Teske }
86*bdb56184SDevin Teske *pkru = (*pkru & keep) | set;
87*bdb56184SDevin Teske critical_exit();
88*bdb56184SDevin Teske }
89*bdb56184SDevin Teske
90*bdb56184SDevin Teske /*
91*bdb56184SDevin Teske * Set the calling thread's PKRU access rights for the given key.
92*bdb56184SDevin Teske */
93*bdb56184SDevin Teske static void
linux_pkru_set_perm(struct thread * td,u_int keyidx,uint32_t rights)94*bdb56184SDevin Teske linux_pkru_set_perm(struct thread *td, u_int keyidx, uint32_t rights)
95*bdb56184SDevin Teske {
96*bdb56184SDevin Teske
97*bdb56184SDevin Teske linux_pkru_write(td, ~(LINUX_PKEY_ACCESS_MASK << (keyidx * 2)),
98*bdb56184SDevin Teske rights << (keyidx * 2));
99*bdb56184SDevin Teske }
100*bdb56184SDevin Teske
101*bdb56184SDevin Teske /*
102*bdb56184SDevin Teske * Called from the Linux sysvecs' exec_setregs. Linux initializes
103*bdb56184SDevin Teske * PKRU at exec to deny access to all keys but key 0
104*bdb56184SDevin Teske * (arch/x86/mm/pkeys.c init_pkru_value), so memory tagged with a not
105*bdb56184SDevin Teske * yet allocated key is inaccessible; FreeBSD's initial PKRU is 0.
106*bdb56184SDevin Teske * This initializes the user FPU state slightly earlier than the lazy
107*bdb56184SDevin Teske * first-use path; the state would be initialized moments later in
108*bdb56184SDevin Teske * rtld/libc startup regardless.
109*bdb56184SDevin Teske */
110*bdb56184SDevin Teske void
linux_pkru_exec_init(struct thread * td)111*bdb56184SDevin Teske linux_pkru_exec_init(struct thread *td)
112*bdb56184SDevin Teske {
113*bdb56184SDevin Teske
114*bdb56184SDevin Teske if (!linux_pkey_supported())
115*bdb56184SDevin Teske return;
116*bdb56184SDevin Teske linux_pkru_write(td, 0, LINUX_PKRU_INIT);
117*bdb56184SDevin Teske }
118*bdb56184SDevin Teske
119*bdb56184SDevin Teske /*
120*bdb56184SDevin Teske * Protection keys are a property of the address space: inherit the
121*bdb56184SDevin Teske * allocation map on fork, as Linux does. When a FreeBSD process is
122*bdb56184SDevin Teske * switching to the Linux ABI there is no parent emuldata; start from
123*bdb56184SDevin Teske * the initial map. The unlocked read is atomic on the aligned word;
124*bdb56184SDevin Teske * a pkey_alloc() racing the fork in another thread yields a valid
125*bdb56184SDevin Teske * serialization either way.
126*bdb56184SDevin Teske */
127*bdb56184SDevin Teske void
linux_pemuldata_init_md(struct thread * td,struct linux_pemuldata * pem)128*bdb56184SDevin Teske linux_pemuldata_init_md(struct thread *td, struct linux_pemuldata *pem)
129*bdb56184SDevin Teske {
130*bdb56184SDevin Teske struct linux_pemuldata *ppem;
131*bdb56184SDevin Teske
132*bdb56184SDevin Teske ppem = pem_find(td->td_proc);
133*bdb56184SDevin Teske if (ppem != NULL)
134*bdb56184SDevin Teske pem->pem_md.md_pkey_allocation_map =
135*bdb56184SDevin Teske ppem->pem_md.md_pkey_allocation_map;
136*bdb56184SDevin Teske else
137*bdb56184SDevin Teske pem->pem_md.md_pkey_allocation_map = LINUX_PKEY_INITIAL_MAP;
138*bdb56184SDevin Teske }
139*bdb56184SDevin Teske
140*bdb56184SDevin Teske void
linux_pemuldata_exec_md(struct linux_pemuldata * pem)141*bdb56184SDevin Teske linux_pemuldata_exec_md(struct linux_pemuldata *pem)
142*bdb56184SDevin Teske {
143*bdb56184SDevin Teske
144*bdb56184SDevin Teske pem->pem_md.md_pkey_allocation_map = LINUX_PKEY_INITIAL_MAP;
145*bdb56184SDevin Teske }
146*bdb56184SDevin Teske
147*bdb56184SDevin Teske int
linux_pkey_alloc_machdep(struct thread * td,uint64_t init_val)148*bdb56184SDevin Teske linux_pkey_alloc_machdep(struct thread *td, uint64_t init_val)
149*bdb56184SDevin Teske {
150*bdb56184SDevin Teske struct linux_pemuldata *pem;
151*bdb56184SDevin Teske uint32_t free_keys;
152*bdb56184SDevin Teske int key;
153*bdb56184SDevin Teske
154*bdb56184SDevin Teske if (!linux_pkey_supported())
155*bdb56184SDevin Teske return (ENOSPC);
156*bdb56184SDevin Teske
157*bdb56184SDevin Teske pem = pem_find(td->td_proc);
158*bdb56184SDevin Teske LINUX_PEM_XLOCK(pem);
159*bdb56184SDevin Teske free_keys = ~pem->pem_md.md_pkey_allocation_map &
160*bdb56184SDevin Teske ((1u << LINUX_PKEY_MAX) - 1) & ~LINUX_PKEY_INITIAL_MAP;
161*bdb56184SDevin Teske if (free_keys == 0) {
162*bdb56184SDevin Teske LINUX_PEM_XUNLOCK(pem);
163*bdb56184SDevin Teske return (ENOSPC);
164*bdb56184SDevin Teske }
165*bdb56184SDevin Teske key = ffs(free_keys) - 1;
166*bdb56184SDevin Teske pem->pem_md.md_pkey_allocation_map |= 1u << key;
167*bdb56184SDevin Teske LINUX_PEM_XUNLOCK(pem);
168*bdb56184SDevin Teske
169*bdb56184SDevin Teske linux_pkru_set_perm(td, key, init_val);
170*bdb56184SDevin Teske td->td_retval[0] = key;
171*bdb56184SDevin Teske return (0);
172*bdb56184SDevin Teske }
173*bdb56184SDevin Teske
174*bdb56184SDevin Teske int
linux_pkey_free_machdep(struct thread * td,int pkey)175*bdb56184SDevin Teske linux_pkey_free_machdep(struct thread *td, int pkey)
176*bdb56184SDevin Teske {
177*bdb56184SDevin Teske struct linux_pemuldata *pem;
178*bdb56184SDevin Teske
179*bdb56184SDevin Teske if (!linux_pkey_supported())
180*bdb56184SDevin Teske return (EINVAL);
181*bdb56184SDevin Teske
182*bdb56184SDevin Teske pem = pem_find(td->td_proc);
183*bdb56184SDevin Teske LINUX_PEM_XLOCK(pem);
184*bdb56184SDevin Teske if ((pem->pem_md.md_pkey_allocation_map & (1u << pkey)) == 0) {
185*bdb56184SDevin Teske LINUX_PEM_XUNLOCK(pem);
186*bdb56184SDevin Teske return (EINVAL);
187*bdb56184SDevin Teske }
188*bdb56184SDevin Teske pem->pem_md.md_pkey_allocation_map &= ~(1u << pkey);
189*bdb56184SDevin Teske LINUX_PEM_XUNLOCK(pem);
190*bdb56184SDevin Teske
191*bdb56184SDevin Teske /*
192*bdb56184SDevin Teske * As on Linux, freeing a key neither untags pages nor updates
193*bdb56184SDevin Teske * PKRU; that is the application's responsibility.
194*bdb56184SDevin Teske */
195*bdb56184SDevin Teske return (0);
196*bdb56184SDevin Teske }
197*bdb56184SDevin Teske
198*bdb56184SDevin Teske int
linux_pkey_mprotect_machdep(struct thread * td,uintptr_t addr,size_t len,int prot,int pkey)199*bdb56184SDevin Teske linux_pkey_mprotect_machdep(struct thread *td, uintptr_t addr, size_t len,
200*bdb56184SDevin Teske int prot, int pkey)
201*bdb56184SDevin Teske {
202*bdb56184SDevin Teske struct linux_pemuldata *pem;
203*bdb56184SDevin Teske int error;
204*bdb56184SDevin Teske
205*bdb56184SDevin Teske if (!linux_pkey_supported())
206*bdb56184SDevin Teske return (EINVAL);
207*bdb56184SDevin Teske
208*bdb56184SDevin Teske pem = pem_find(td->td_proc);
209*bdb56184SDevin Teske LINUX_PEM_SLOCK(pem);
210*bdb56184SDevin Teske if ((pem->pem_md.md_pkey_allocation_map & (1u << pkey)) == 0) {
211*bdb56184SDevin Teske LINUX_PEM_SUNLOCK(pem);
212*bdb56184SDevin Teske return (EINVAL);
213*bdb56184SDevin Teske }
214*bdb56184SDevin Teske LINUX_PEM_SUNLOCK(pem);
215*bdb56184SDevin Teske
216*bdb56184SDevin Teske error = linux_mprotect_common(td, addr, len, prot);
217*bdb56184SDevin Teske if (error != 0 || len == 0)
218*bdb56184SDevin Teske return (error);
219*bdb56184SDevin Teske
220*bdb56184SDevin Teske /*
221*bdb56184SDevin Teske * Tag the range; a pkey of 0 untags it. The tag is not
222*bdb56184SDevin Teske * persistent: it dies with the mapping, matching Linux VMA
223*bdb56184SDevin Teske * semantics.
224*bdb56184SDevin Teske */
225*bdb56184SDevin Teske return (amd64_pkru_update(td, addr, len, pkey, 0, pkey == 0));
226*bdb56184SDevin Teske }
227