Page MenuHomeFreeBSD

D58782.id183862.diff
No OneTemporary

D58782.id183862.diff

diff --git a/sys/amd64/linux/linux_sysvec.c b/sys/amd64/linux/linux_sysvec.c
--- a/sys/amd64/linux/linux_sysvec.c
+++ b/sys/amd64/linux/linux_sysvec.c
@@ -64,6 +64,7 @@
#include <compat/linux/linux_ioctl.h>
#include <compat/linux/linux_mib.h>
#include <compat/linux/linux_misc.h>
+#include <compat/linux/linux_mmap.h>
#include <compat/linux/linux_signal.h>
#include <compat/linux/linux_util.h>
#include <compat/linux/linux_vdso.h>
@@ -271,6 +272,9 @@
* clean FP state if it uses the FPU again.
*/
fpstate_drop(td);
+
+ /* Linux processes start with PKRU denying unallocated keys. */
+ linux_pkru_exec_init(td);
}
static int
diff --git a/sys/amd64/linux32/linux32_sysvec.c b/sys/amd64/linux32/linux32_sysvec.c
--- a/sys/amd64/linux32/linux32_sysvec.c
+++ b/sys/amd64/linux32/linux32_sysvec.c
@@ -69,6 +69,7 @@
#include <compat/linux/linux_ioctl.h>
#include <compat/linux/linux_mib.h>
#include <compat/linux/linux_misc.h>
+#include <compat/linux/linux_mmap.h>
#include <compat/linux/linux_signal.h>
#include <compat/linux/linux_util.h>
#include <compat/linux/linux_vdso.h>
@@ -609,6 +610,9 @@
x86_clear_dbregs(pcb);
fpstate_drop(td);
+
+ /* Linux processes start with PKRU denying unallocated keys. */
+ linux_pkru_exec_init(td);
}
/*
diff --git a/sys/compat/linux/linux_dummy.c b/sys/compat/linux/linux_dummy.c
--- a/sys/compat/linux/linux_dummy.c
+++ b/sys/compat/linux/linux_dummy.c
@@ -110,9 +110,6 @@
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.h b/sys/compat/linux/linux_emul.h
--- a/sys/compat/linux/linux_emul.h
+++ b/sys/compat/linux/linux_emul.h
@@ -69,8 +69,16 @@
uint32_t oom_score_adj; /* /proc/self/oom_score_adj */
uint32_t so_timestamp; /* requested timeval */
uint32_t so_timestampns; /* requested timespec */
+ uint32_t pkey_allocation_map; /* x86 protection keys */
};
+/*
+ * Initial protection key allocation map: key 0 is the default key,
+ * implicitly allocated on Linux (mm_pkey_allocation_map is initialized
+ * to 0x1). Inherited on fork, reset on exec.
+ */
+#define LINUX_PKEY_INITIAL_MAP 0x1
+
#define LINUX_PEM_XLOCK(p) sx_xlock(&(p)->pem_sx)
#define LINUX_PEM_XUNLOCK(p) sx_xunlock(&(p)->pem_sx)
#define LINUX_PEM_SLOCK(p) sx_slock(&(p)->pem_sx)
diff --git a/sys/compat/linux/linux_emul.c b/sys/compat/linux/linux_emul.c
--- a/sys/compat/linux/linux_emul.c
+++ b/sys/compat/linux/linux_emul.c
@@ -137,7 +137,7 @@
linux_proc_init(struct thread *td, struct thread *newtd, bool init_thread)
{
struct linux_emuldata *em;
- struct linux_pemuldata *pem;
+ struct linux_pemuldata *pem, *ppem;
struct proc *p;
if (newtd != NULL) {
@@ -157,6 +157,23 @@
pem = malloc(sizeof(*pem), M_LINUX, M_WAITOK | M_ZERO);
sx_init(&pem->pem_sx, "lpemlk");
+
+ /*
+ * Protection keys are a property of the address
+ * space: inherit the allocation map on fork, as
+ * Linux does. When a FreeBSD process is
+ * switching to the Linux ABI there is no parent
+ * emuldata; start from the initial map.
+ */
+ ppem = pem_find(td->td_proc);
+ if (ppem != NULL) {
+ LINUX_PEM_SLOCK(ppem);
+ pem->pkey_allocation_map =
+ ppem->pkey_allocation_map;
+ LINUX_PEM_SUNLOCK(ppem);
+ } else
+ pem->pkey_allocation_map =
+ LINUX_PKEY_INITIAL_MAP;
p->p_emuldata = pem;
}
newtd->td_emuldata = em;
@@ -183,6 +200,7 @@
KASSERT(pem != NULL, ("proc_init: proc emuldata not found.\n"));
pem->persona = 0;
pem->oom_score_adj = 0;
+ pem->pkey_allocation_map = LINUX_PKEY_INITIAL_MAP;
}
}
diff --git a/sys/compat/linux/linux_misc.c b/sys/compat/linux/linux_misc.c
--- a/sys/compat/linux/linux_misc.c
+++ b/sys/compat/linux/linux_misc.c
@@ -359,6 +359,28 @@
uap->prot));
}
+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.h b/sys/compat/linux/linux_mmap.h
--- a/sys/compat/linux/linux_mmap.h
+++ b/sys/compat/linux/linux_mmap.h
@@ -66,6 +66,25 @@
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);
+
+/* 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 */
+
+#if defined(__amd64__)
+/*
+ * Initial PKRU at exec: access disabled for keys 1..15, key 0 open;
+ * the Linux init_pkru default.
+ */
+#define LINUX_PKRU_INIT 0x55555554
+void linux_pkru_exec_init(struct thread *);
+#endif
int linux_madvise_common(struct thread *, uintptr_t, size_t, int);
#endif /* _LINUX_MMAP_H_ */
diff --git a/sys/compat/linux/linux_mmap.c b/sys/compat/linux/linux_mmap.c
--- a/sys/compat/linux/linux_mmap.c
+++ b/sys/compat/linux/linux_mmap.c
@@ -35,18 +35,28 @@
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mman.h>
+#include <sys/pcpu.h>
#include <sys/proc.h>
#include <sys/resourcevar.h>
#include <sys/rwlock.h>
#include <sys/syscallsubr.h>
#include <sys/sysent.h>
#include <sys/sysproto.h>
+#include <sys/systm.h>
#include <vm/pmap.h>
#include <vm/vm_extern.h>
#include <vm/vm_map.h>
#include <vm/vm_object.h>
+#if defined(__amd64__)
+#include <machine/cpufunc.h>
+#include <machine/fpu.h>
+#include <machine/md_var.h>
+#include <machine/pcb.h>
+#include <machine/specialreg.h>
+#endif
+
#include <compat/linux/linux_emul.h>
#include <compat/linux/linux_mmap.h>
#include <compat/linux/linux_persona.h>
@@ -247,6 +257,236 @@
return (kern_mprotect(td, addr, len, prot, flags));
}
+#if defined(__amd64__)
+/*
+ * x86 memory protection keys (PKU).
+ *
+ * The PKRU register is directly user-visible: Linux programs read and
+ * write it with RDPKRU/WRPKRU, which execute natively. The kernel's
+ * part is key allocation bookkeeping (per address space: inherited on
+ * fork, reset on exec, as with Linux mm->context.pkey_allocation_map),
+ * tagging pages (pkey_mprotect), and applying the initial access
+ * rights of pkey_alloc() to the calling thread's PKRU.
+ */
+
+static bool
+linux_pkey_supported(void)
+{
+
+ return ((cpu_stdext_feature2 & CPUID_STDEXT2_OSPKE) != 0);
+}
+
+/*
+ * Offset of the PKRU component in the standard-format XSAVE area.
+ */
+static u_int
+linux_pkru_xstate_offset(void)
+{
+ static u_int off;
+ u_int cp[4];
+
+ if (off == 0) {
+ cpuid_count(0xd, 9, cp); /* PKRU state component */
+ off = cp[1];
+ }
+ return (off);
+}
+
+/*
+ * Update the calling thread's PKRU: new value is (PKRU & keep) | set.
+ */
+static void
+linux_pkru_write(struct thread *td, uint32_t keep, uint32_t set)
+{
+ struct pcb *pcb;
+ struct xstate_hdr *hdr;
+ char *sa;
+ uint32_t *pkru;
+
+ MPASS(td == curthread);
+ pcb = td->td_pcb;
+ critical_enter();
+ if ((pcb->pcb_flags & PCB_USERFPUINITDONE) != 0 &&
+ td == PCPU_GET(fpcurthread) && PCB_USER_FPU(pcb)) {
+ wrpkru((rdpkru() & keep) | set);
+ critical_exit();
+ return;
+ }
+ critical_exit();
+
+ /*
+ * The user FPU state is in the PCB save area, or is not yet
+ * initialized, in which case fpugetregs() installs the initial
+ * state there. curthread cannot regain FPU ownership while
+ * executing in the kernel, so the save area is stable.
+ */
+ (void)fpugetregs(td);
+ sa = (char *)get_pcb_user_save_td(td);
+ hdr = (struct xstate_hdr *)(sa + sizeof(struct savefpu));
+ pkru = (uint32_t *)(sa + linux_pkru_xstate_offset());
+ if ((hdr->xstate_bv & XFEATURE_ENABLED_PKRU) == 0) {
+ hdr->xstate_bv |= XFEATURE_ENABLED_PKRU;
+ *pkru = 0;
+ }
+ *pkru = (*pkru & keep) | set;
+}
+
+/*
+ * Set the calling thread's PKRU access rights for the given key.
+ */
+static void
+linux_pkru_set_perm(struct thread *td, u_int keyidx, uint32_t rights)
+{
+
+ linux_pkru_write(td, ~(LINUX_PKEY_ACCESS_MASK << (keyidx * 2)),
+ rights << (keyidx * 2));
+}
+
+/*
+ * Called from the Linux sysvecs' exec_setregs. Linux initializes
+ * PKRU at exec to deny access to all keys but key 0
+ * (arch/x86/mm/pkeys.c init_pkru_value), so memory tagged with a not
+ * yet allocated key is inaccessible; FreeBSD's initial PKRU is 0.
+ * This 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.
+ */
+void
+linux_pkru_exec_init(struct thread *td)
+{
+
+ if (!linux_pkey_supported())
+ return;
+ linux_pkru_write(td, 0, LINUX_PKRU_INIT);
+}
+#endif /* __amd64__ */
+
+int
+linux_pkey_alloc_common(struct thread *td, uint64_t flags, uint64_t init_val)
+{
+#if defined(__amd64__)
+ struct linux_pemuldata *pem;
+ uint32_t free_keys;
+ int key;
+
+ if (flags != 0)
+ return (EINVAL);
+ if ((init_val & ~(uint64_t)LINUX_PKEY_ACCESS_MASK) != 0)
+ return (EINVAL);
+ if (!linux_pkey_supported())
+ return (ENOSPC);
+
+ pem = pem_find(td->td_proc);
+ LINUX_PEM_XLOCK(pem);
+ free_keys = ~pem->pkey_allocation_map &
+ ((1u << LINUX_PKEY_MAX) - 1) & ~LINUX_PKEY_INITIAL_MAP;
+ if (free_keys == 0) {
+ LINUX_PEM_XUNLOCK(pem);
+ return (ENOSPC);
+ }
+ key = ffs(free_keys) - 1;
+ pem->pkey_allocation_map |= 1u << key;
+ LINUX_PEM_XUNLOCK(pem);
+
+ linux_pkru_set_perm(td, key, init_val);
+ td->td_retval[0] = key;
+ return (0);
+#else
+ return (ENOSPC);
+#endif
+}
+
+int
+linux_pkey_free_common(struct thread *td, int pkey)
+{
+#if defined(__amd64__)
+ struct linux_pemuldata *pem;
+
+ if (pkey < 0 || pkey >= LINUX_PKEY_MAX)
+ return (EINVAL);
+ if (!linux_pkey_supported())
+ return (EINVAL);
+
+ pem = pem_find(td->td_proc);
+ LINUX_PEM_XLOCK(pem);
+ if ((pem->pkey_allocation_map & (1u << pkey)) == 0) {
+ LINUX_PEM_XUNLOCK(pem);
+ return (EINVAL);
+ }
+ pem->pkey_allocation_map &= ~(1u << pkey);
+ LINUX_PEM_XUNLOCK(pem);
+
+ /*
+ * As on Linux, freeing a key neither untags pages nor updates
+ * PKRU; that is the application's responsibility.
+ */
+ return (0);
+#else
+ return (EINVAL);
+#endif
+}
+
+int
+linux_pkey_mprotect_common(struct thread *td, uintptr_t addr, size_t len,
+ int prot, int pkey)
+{
+#if defined(__amd64__)
+ struct linux_pemuldata *pem;
+ vm_map_t map;
+ vm_offset_t start, end;
+ int error;
+
+ if (pkey < -1 || pkey >= LINUX_PKEY_MAX)
+ return (EINVAL);
+ if (pkey == -1)
+ return (linux_mprotect_common(td, addr, len, prot));
+
+ if (!linux_pkey_supported())
+ return (EINVAL);
+
+ pem = pem_find(td->td_proc);
+ LINUX_PEM_SLOCK(pem);
+ if ((pem->pkey_allocation_map & (1u << pkey)) == 0) {
+ LINUX_PEM_SUNLOCK(pem);
+ return (EINVAL);
+ }
+ LINUX_PEM_SUNLOCK(pem);
+
+ error = linux_mprotect_common(td, addr, len, prot);
+ if (error != 0)
+ return (error);
+
+ start = trunc_page(addr);
+ end = round_page(addr + len);
+ if (start == end)
+ return (0);
+
+ /*
+ * Read-lock the map to synchronize with a parallel
+ * pmap_vmspace_copy() on fork, as sysarch(AMD64_SET_PKRU)
+ * does. The key tag is not persistent: it dies with the
+ * mapping, matching Linux VMA semantics.
+ */
+ map = &td->td_proc->p_vmspace->vm_map;
+ vm_map_lock_read(map);
+ if (!vm_map_check_boundary(map, start, end)) {
+ vm_map_unlock_read(map);
+ return (EINVAL);
+ }
+ if (pkey == 0)
+ error = pmap_pkru_clear(vm_map_pmap(map), start, end);
+ else
+ error = pmap_pkru_set(vm_map_pmap(map), start, end,
+ pkey, 0);
+ vm_map_unlock_read(map);
+ return (error);
+#else
+ if (pkey == -1)
+ return (linux_mprotect_common(td, addr, len, prot));
+ return (EINVAL);
+#endif
+}
+
/*
* Implement Linux madvise(MADV_DONTNEED), which has unusual semantics: for
* anonymous memory, pages in the range are immediately discarded.

File Metadata

Mime Type
text/plain
Expires
Wed, Aug 19, 3:06 PM (7 h, 26 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
36918089
Default Alt Text
D58782.id183862.diff (12 KB)

Event Timeline