xref: /freebsd/sys/amd64/linux/linux_pkru.c (revision bdb561843e865eaa5bbdc5394ed9d9c91136240c)
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