--- /dev/null
+/* $OpenBSD: xonly.c,v 1.1 2023/01/18 19:18:49 anton Exp $ */
+
+#include <sys/types.h>
+#include <sys/mman.h>
+
+#include <dlfcn.h>
+#include <err.h>
+#include <fcntl.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNREADABLE 0
+#define READABLE 1
+
+int main(void);
+void sigsegv(int);
+void setup_table(void);
+
+void *setup_ldso(void);
+void *setup_mmap_xz(void);
+void *setup_mmap_x(void);
+void *setup_mmap_nrx(void);
+void *setup_mmap_nwx(void);
+void *setup_mmap_xnwx(void);
+
+struct outcome {
+ int uu;
+ int ku;
+};
+
+struct readable {
+ const char *name;
+ void *(*setup)(void);
+ int isfn;
+ void *addr;
+ int skip;
+ struct outcome got;
+} readables[] = {
+ { "ld.so", setup_ldso, 1, NULL, 0, {} },
+ { "mmap xz", setup_mmap_xz, 0, NULL, 0, {} },
+ { "mmap x", setup_mmap_x, 0, NULL, 0, {} },
+ { "mmap nrx", setup_mmap_nrx, 0, NULL, 0, {} },
+ { "mmap nwx", setup_mmap_nwx, 0, NULL, 0, {} },
+ { "mmap xnwx", setup_mmap_xnwx, 0, NULL, 0, {} },
+ { "main", NULL, 1, &main, 0, {} },
+ { "libc", NULL, 1, &open, 0, {} },
+};
+
+static struct outcome expectations[2][8] = {
+#if defined(__aarch64__)
+ [0] = {
+ /* ld.so */ { UNREADABLE, UNREADABLE },
+ /* mmap xz */ { UNREADABLE, UNREADABLE },
+ /* mmap x */ { UNREADABLE, UNREADABLE },
+ /* mmap nrx */ { UNREADABLE, UNREADABLE },
+ /* mmap nwx */ { UNREADABLE, UNREADABLE },
+ /* mmap xnwx */ { UNREADABLE, UNREADABLE },
+ /* main */ { UNREADABLE, UNREADABLE },
+ /* libc */ { UNREADABLE, UNREADABLE },
+ },
+#elif defined(__amd64__)
+ /* PKU not available. */
+ [0] = {
+ /* ld.so */ { READABLE, READABLE },
+ /* mmap xz */ { UNREADABLE, UNREADABLE },
+ /* mmap x */ { READABLE, READABLE },
+ /* mmap nrx */ { READABLE, READABLE },
+ /* mmap nwx */ { READABLE, READABLE },
+ /* mmap xnwx */ { READABLE, READABLE },
+ /* main */ { READABLE, READABLE },
+ /* libc */ { READABLE, READABLE },
+ },
+#else
+#error "unknown architecture"
+#endif
+};
+
+jmp_buf fail;
+
+void
+sigsegv(__unused int signo)
+{
+ longjmp(fail, 1);
+}
+
+void *
+setup_mmap_xz(void)
+{
+ return mmap(NULL, getpagesize(), PROT_EXEC,
+ MAP_ANON | MAP_PRIVATE, -1, 0);
+ /* no data written. tests read-fault of an unbacked exec-only page */
+}
+
+void *
+setup_mmap_x(void)
+{
+ char *addr;
+
+ addr = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE,
+ MAP_ANON | MAP_PRIVATE, -1, 0);
+ explicit_bzero(addr, getpagesize());
+ mprotect(addr, getpagesize(), PROT_EXEC);
+ return addr;
+}
+
+void *
+setup_mmap_nrx(void)
+{
+ char *addr;
+
+ addr = mmap(NULL, getpagesize(), PROT_NONE,
+ MAP_ANON | MAP_PRIVATE, -1, 0);
+ mprotect(addr, getpagesize(), PROT_READ | PROT_WRITE);
+ explicit_bzero(addr, getpagesize());
+ mprotect(addr, getpagesize(), PROT_EXEC);
+ return addr;
+}
+
+void *
+setup_mmap_nwx(void)
+{
+ char *addr;
+
+ addr = mmap(NULL, getpagesize(), PROT_NONE,
+ MAP_ANON | MAP_PRIVATE, -1, 0);
+ mprotect(addr, getpagesize(), PROT_WRITE);
+ explicit_bzero(addr, getpagesize());
+ mprotect(addr, getpagesize(), PROT_EXEC);
+ return addr;
+}
+
+void *
+setup_mmap_xnwx(void)
+{
+ char *addr;
+
+ addr = mmap(NULL, getpagesize(), PROT_EXEC,
+ MAP_ANON | MAP_PRIVATE, -1, 0);
+ mprotect(addr, getpagesize(), PROT_NONE);
+ mprotect(addr, getpagesize(), PROT_WRITE);
+ explicit_bzero(addr, getpagesize());
+ mprotect(addr, getpagesize(), PROT_EXEC);
+ return addr;
+}
+
+void *
+setup_ldso(void)
+{
+ void *dlopenp, *handle;
+
+ handle = dlopen("ld.so", RTLD_NOW);
+ if (handle == NULL)
+ errx(1, "dlopen");
+ dlopenp = dlsym(handle, "dlopen");
+ return dlopenp;
+}
+
+void
+setup_table(void)
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(readables)/sizeof(readables[0]); i++) {
+ if (setjmp(fail) == 0) {
+ if (readables[i].setup)
+ readables[i].addr = readables[i].setup();
+ } else {
+ readables[i].skip = 1;
+ }
+#ifdef __hppa__
+ /* hppa ptable headers point at the instructions */
+ if (readables[i].isfn) {
+ readables[i].addr = (void *)*(u_int *)
+ ((u_int)readables[i].addr & ~3);
+ }
+#endif
+ }
+}
+
+int
+main(void)
+{
+ size_t i;
+ int p[2];
+ int error = 0;
+
+ signal(SIGSEGV, sigsegv);
+ signal(SIGBUS, sigsegv);
+
+ setup_table();
+
+ for (i = 0; i < sizeof(readables)/sizeof(readables[0]); i++) {
+ struct readable *r = &readables[i];
+ char c;
+
+ if (r->skip)
+ continue;
+ pipe(p);
+ fcntl(p[0], F_SETFL, O_NONBLOCK);
+
+ if (write(p[1], r->addr, 1) == 1 && read(p[0], &c, 1) == 1)
+ r->got.ku = 1;
+
+ if (setjmp(fail) == 0) {
+ volatile int x = *(int *)(r->addr);
+ (void)x;
+ r->got.uu = 1;
+ }
+
+ close(p[0]);
+ close(p[1]);
+ }
+
+ printf("%-16s %18s userland kernel\n", "", "");
+ for (i = 0; i < sizeof(readables)/sizeof(readables[0]); i++) {
+ struct readable *r = &readables[i];
+
+ if (r->skip) {
+ printf("%-16s %18p %-12s %-12s\n", r->name, r->addr,
+ "skipped", "skipped");
+ } else {
+ const struct outcome *want = &expectations[0][i];
+
+ if (r->got.uu != want->uu || r->got.ku != want->ku)
+ error++;
+
+ printf("%-16s %18p %s%-10s %s%-10s\n",
+ r->name, r->addr,
+ r->got.uu == want->uu ? "P " : "F ",
+ r->got.uu ? "readable" : "unreadable",
+ r->got.ku == want->ku ? "P " : "F ",
+ r->got.ku ? "readable" : "unreadable");
+ }
+ }
+ return error;
+}