aboutsummaryrefslogtreecommitdiff
path: root/sys/compat/linux
diff options
context:
space:
mode:
authorDevin Teske <dteske@FreeBSD.org>2026-08-16 00:58:19 +0000
committerDevin Teske <dteske@FreeBSD.org>2026-08-16 00:58:43 +0000
commitbdb561843e865eaa5bbdc5394ed9d9c91136240c (patch)
tree7fe259ff3acd58946f51470a14653efde87650ca /sys/compat/linux
parent5b48968c1a57bd1a7f086d7e09add59afa158340 (diff)
linux: implement pkey_alloc, pkey_free and pkey_mprotect
Bridge the Linux memory protection key syscalls to FreeBSD's native MPK support instead of returning ENOSYS. Modern Linux software probes these at startup: Chromium-based browsers (found via www/linux-brave) use protection keys for V8's heap and JIT sandboxing, and glibc >= 2.27 exposes the full API. pkey_alloc() allocates from a per-process bitmap kept in the process emuldata (key 0 implicitly allocated, matching Linux's mm_pkey_allocation_map; ENOSPC once keys 1..15 are exhausted or when PKU is absent, as Linux returns on such hardware) and applies the requested initial access rights to the calling thread's PKRU, located in the XSAVE area via xsave_area_offset(). pkey_free() is bookkeeping only: as on Linux, freeing neither untags pages nor updates PKRU. pkey_mprotect() performs the protection change and tags the range through amd64_pkru_update(), factored out of sysarch(2)'s AMD64_SET_PKRU/AMD64_CLEAR_PKRU implementation so that both share the same argument checking and map read lock synchronization with a parallel pmap_vmspace_copy() on fork; tags die with the mapping, matching Linux VMA semantics. A pkey of -1 degrades to plain mprotect. The allocation map is inherited on fork and reset on exec. At exec the Linux sysvecs initialize PKRU to 0x55555554, Linux's init_pkru default (access disabled for keys 1..15), so memory tagged with a not yet allocated key is inaccessible to threads that were never granted rights -- the property V8's thread isolation relies on. Setting PKRU at exec initializes the user FPU state slightly earlier than the lazy first-use path; the state would be initialized moments later in rtld/libc startup regardless. Protection key faults already deliver SEGV_PKUERR through the existing siginfo translation. The common code carries no architecture ifdefs. Machine-dependent state lives in struct linux_pemuldata_md, embedded in the process emuldata in the manner of struct mdthread, and common code calls per-arch lifecycle hooks (linux_pemuldata_init_md/_exec_md) and pkey back ends after performing the parameter validation Linux applies regardless of hardware support. On amd64 the implementation lives in sys/amd64/linux/linux_pkru.c, compiled into linux_common and serving both the 64-bit and 32-bit Linux ABIs. Elsewhere (arm64, i386) linux_emul_md.c provides stubs returning what Linux returns on hardware without protection keys (ENOSPC from pkey_alloc; pkey_mprotect with a pkey of -1 acts as plain mprotect), so applications take their normal no-PKU fallback instead of the ENOSYS path. PR: 297427 MFC after: 1 month Reviewed by: kib Differential Revision: https://reviews.freebsd.org/D58782
Diffstat (limited to 'sys/compat/linux')
-rw-r--r--sys/compat/linux/linux_dummy.c3
-rw-r--r--sys/compat/linux/linux_emul.c2
-rw-r--r--sys/compat/linux/linux_emul.h12
-rw-r--r--sys/compat/linux/linux_misc.c22
-rw-r--r--sys/compat/linux/linux_mmap.c38
-rw-r--r--sys/compat/linux/linux_mmap.h21
6 files changed, 95 insertions, 3 deletions
diff --git a/sys/compat/linux/linux_dummy.c b/sys/compat/linux/linux_dummy.c
index 971e070e90aa..dfb1c90e9abf 100644
--- a/sys/compat/linux/linux_dummy.c
+++ b/sys/compat/linux/linux_dummy.c
@@ -110,9 +110,6 @@ DUMMY(mlock2);
DUMMY(preadv2);
DUMMY(pwritev2);
/* Linux 4.8: */
-DUMMY(pkey_mprotect);
-DUMMY(pkey_alloc);
-DUMMY(pkey_free);
/* Linux 4.18: */
DUMMY(io_pgetevents);
/* Linux 5.1: */
diff --git a/sys/compat/linux/linux_emul.c b/sys/compat/linux/linux_emul.c
index e5ab51802468..8ee17e3e484b 100644
--- a/sys/compat/linux/linux_emul.c
+++ b/sys/compat/linux/linux_emul.c
@@ -157,6 +157,7 @@ linux_proc_init(struct thread *td, struct thread *newtd, bool init_thread)
pem = malloc(sizeof(*pem), M_LINUX, M_WAITOK | M_ZERO);
sx_init(&pem->pem_sx, "lpemlk");
+ linux_pemuldata_init_md(td, pem);
p->p_emuldata = pem;
}
newtd->td_emuldata = em;
@@ -183,6 +184,7 @@ linux_proc_init(struct thread *td, struct thread *newtd, bool init_thread)
KASSERT(pem != NULL, ("proc_init: proc emuldata not found.\n"));
pem->persona = 0;
pem->oom_score_adj = 0;
+ linux_pemuldata_exec_md(pem);
}
}
diff --git a/sys/compat/linux/linux_emul.h b/sys/compat/linux/linux_emul.h
index 6dfa31f6edf7..685cbcaa45b4 100644
--- a/sys/compat/linux/linux_emul.h
+++ b/sys/compat/linux/linux_emul.h
@@ -30,6 +30,8 @@
#ifndef _LINUX_EMUL_H_
#define _LINUX_EMUL_H_
+#include <machine/../linux/linux_emul_md.h>
+
struct image_params;
/*
@@ -69,6 +71,7 @@ struct linux_pemuldata {
uint32_t oom_score_adj; /* /proc/self/oom_score_adj */
uint32_t so_timestamp; /* requested timeval */
uint32_t so_timestampns; /* requested timespec */
+ struct linux_pemuldata_md pem_md; /* machine-dependent state */
};
#define LINUX_PEM_XLOCK(p) sx_xlock(&(p)->pem_sx)
@@ -78,4 +81,13 @@ struct linux_pemuldata {
struct linux_pemuldata *pem_find(struct proc *);
+/*
+ * Machine-dependent hooks for struct linux_pemuldata lifecycle
+ * events, implemented per-arch: initialization of pem_md when the
+ * emuldata is created (fork, or a process switching to the Linux
+ * ABI) and reset at exec.
+ */
+void linux_pemuldata_init_md(struct thread *, struct linux_pemuldata *);
+void linux_pemuldata_exec_md(struct linux_pemuldata *);
+
#endif /* !_LINUX_EMUL_H_ */
diff --git a/sys/compat/linux/linux_misc.c b/sys/compat/linux/linux_misc.c
index 3aecc0106aaa..96c0ab81beef 100644
--- a/sys/compat/linux/linux_misc.c
+++ b/sys/compat/linux/linux_misc.c
@@ -360,6 +360,28 @@ linux_mprotect(struct thread *td, struct linux_mprotect_args *uap)
}
int
+linux_pkey_mprotect(struct thread *td, struct linux_pkey_mprotect_args *uap)
+{
+
+ return (linux_pkey_mprotect_common(td, uap->start, uap->len,
+ uap->prot, uap->pkey));
+}
+
+int
+linux_pkey_alloc(struct thread *td, struct linux_pkey_alloc_args *uap)
+{
+
+ return (linux_pkey_alloc_common(td, uap->flags, uap->init_val));
+}
+
+int
+linux_pkey_free(struct thread *td, struct linux_pkey_free_args *uap)
+{
+
+ return (linux_pkey_free_common(td, uap->pkey));
+}
+
+int
linux_madvise(struct thread *td, struct linux_madvise_args *uap)
{
diff --git a/sys/compat/linux/linux_mmap.c b/sys/compat/linux/linux_mmap.c
index 9fecb6ebb2ad..4fe0df4d5d07 100644
--- a/sys/compat/linux/linux_mmap.c
+++ b/sys/compat/linux/linux_mmap.c
@@ -248,6 +248,44 @@ linux_mprotect_common(struct thread *td, uintptr_t addr, size_t len, int prot)
}
/*
+ * x86 memory protection keys. The common entry points perform the
+ * parameter validation Linux applies regardless of hardware support,
+ * then defer to the machine-dependent back end.
+ */
+
+int
+linux_pkey_alloc_common(struct thread *td, uint64_t flags, uint64_t init_val)
+{
+
+ if (flags != 0)
+ return (EINVAL);
+ if ((init_val & ~(uint64_t)LINUX_PKEY_ACCESS_MASK) != 0)
+ return (EINVAL);
+ return (linux_pkey_alloc_machdep(td, init_val));
+}
+
+int
+linux_pkey_free_common(struct thread *td, int pkey)
+{
+
+ if (pkey < 0 || pkey >= LINUX_PKEY_MAX)
+ return (EINVAL);
+ return (linux_pkey_free_machdep(td, pkey));
+}
+
+int
+linux_pkey_mprotect_common(struct thread *td, uintptr_t addr, size_t len,
+ int prot, int pkey)
+{
+
+ if (pkey < -1 || pkey >= LINUX_PKEY_MAX)
+ return (EINVAL);
+ if (pkey == -1)
+ return (linux_mprotect_common(td, addr, len, prot));
+ return (linux_pkey_mprotect_machdep(td, addr, len, prot, pkey));
+}
+
+/*
* Implement Linux madvise(MADV_DONTNEED), which has unusual semantics: for
* anonymous memory, pages in the range are immediately discarded.
*/
diff --git a/sys/compat/linux/linux_mmap.h b/sys/compat/linux/linux_mmap.h
index 043dec9d40b7..75af79ae1316 100644
--- a/sys/compat/linux/linux_mmap.h
+++ b/sys/compat/linux/linux_mmap.h
@@ -66,6 +66,27 @@
int linux_mmap_common(struct thread *, uintptr_t, size_t, int, int,
int, off_t);
int linux_mprotect_common(struct thread *, uintptr_t, size_t, int);
+int linux_pkey_alloc_common(struct thread *, uint64_t, uint64_t);
+int linux_pkey_free_common(struct thread *, int);
+int linux_pkey_mprotect_common(struct thread *, uintptr_t, size_t, int, int);
+
+/*
+ * Machine-dependent protection key back ends: the real implementation
+ * on amd64 (sys/amd64/linux/linux_pkru.c), stubs behaving as Linux
+ * does on hardware without protection keys elsewhere (each arch's
+ * linux_emul_md.c).
+ */
+int linux_pkey_alloc_machdep(struct thread *, uint64_t);
+int linux_pkey_free_machdep(struct thread *, int);
+int linux_pkey_mprotect_machdep(struct thread *, uintptr_t, size_t, int, int);
+
+/* x86 memory protection keys (pkey_alloc(2) access rights) */
+#define LINUX_PKEY_DISABLE_ACCESS 0x1
+#define LINUX_PKEY_DISABLE_WRITE 0x2
+#define LINUX_PKEY_ACCESS_MASK (LINUX_PKEY_DISABLE_ACCESS | \
+ LINUX_PKEY_DISABLE_WRITE)
+#define LINUX_PKEY_MAX 16 /* keys 0..15; 0 is default */
+
int linux_madvise_common(struct thread *, uintptr_t, size_t, int);
#endif /* _LINUX_MMAP_H_ */