diff mbox

[v6,11/11] lkdtm: Add test for XPFO

Message ID 20170907173609.22696-12-tycho@docker.com (mailing list archive)
State New, archived
Headers show

Commit Message

Tycho Andersen Sept. 7, 2017, 5:36 p.m. UTC
From: Juerg Haefliger <juerg.haefliger@canonical.com>

This test simply reads from userspace memory via the kernel's linear
map.

v6: * drop an #ifdef, just let the test fail if XPFO is not supported
    * add XPFO_SMP test to try and test the case when one CPU does an xpfo
      unmap of an address, that it can't be used accidentally by other
      CPUs.

Signed-off-by: Juerg Haefliger <juerg.haefliger@canonical.com>
Signed-off-by: Tycho Andersen <tycho@docker.com>
Tested-by: Marco Benatto <marco.antonio.780@gmail.com>
---
 drivers/misc/Makefile     |   1 +
 drivers/misc/lkdtm.h      |   5 ++
 drivers/misc/lkdtm_core.c |   3 +
 drivers/misc/lkdtm_xpfo.c | 194 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 203 insertions(+)

Comments

Kees Cook Sept. 7, 2017, 7:08 p.m. UTC | #1
On Thu, Sep 7, 2017 at 10:36 AM, Tycho Andersen <tycho@docker.com> wrote:
> From: Juerg Haefliger <juerg.haefliger@canonical.com>
>
> This test simply reads from userspace memory via the kernel's linear
> map.
>
> v6: * drop an #ifdef, just let the test fail if XPFO is not supported
>     * add XPFO_SMP test to try and test the case when one CPU does an xpfo
>       unmap of an address, that it can't be used accidentally by other
>       CPUs.

This is very close! Thanks for the updates. :) Notes below...

>
> Signed-off-by: Juerg Haefliger <juerg.haefliger@canonical.com>
> Signed-off-by: Tycho Andersen <tycho@docker.com>
> Tested-by: Marco Benatto <marco.antonio.780@gmail.com>
> ---
>  drivers/misc/Makefile     |   1 +
>  drivers/misc/lkdtm.h      |   5 ++
>  drivers/misc/lkdtm_core.c |   3 +
>  drivers/misc/lkdtm_xpfo.c | 194 ++++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 203 insertions(+)
>
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index b0b766416306..8447b42a447d 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -62,6 +62,7 @@ lkdtm-$(CONFIG_LKDTM)         += lkdtm_heap.o
>  lkdtm-$(CONFIG_LKDTM)          += lkdtm_perms.o
>  lkdtm-$(CONFIG_LKDTM)          += lkdtm_rodata_objcopy.o
>  lkdtm-$(CONFIG_LKDTM)          += lkdtm_usercopy.o
> +lkdtm-$(CONFIG_LKDTM)          += lkdtm_xpfo.o
>
>  KCOV_INSTRUMENT_lkdtm_rodata.o := n
>
> diff --git a/drivers/misc/lkdtm.h b/drivers/misc/lkdtm.h
> index 3b4976396ec4..34a6ee37f216 100644
> --- a/drivers/misc/lkdtm.h
> +++ b/drivers/misc/lkdtm.h
> @@ -64,4 +64,9 @@ void lkdtm_USERCOPY_STACK_FRAME_FROM(void);
>  void lkdtm_USERCOPY_STACK_BEYOND(void);
>  void lkdtm_USERCOPY_KERNEL(void);
>
> +/* lkdtm_xpfo.c */
> +void lkdtm_XPFO_READ_USER(void);
> +void lkdtm_XPFO_READ_USER_HUGE(void);
> +void lkdtm_XPFO_SMP(void);
> +
>  #endif
> diff --git a/drivers/misc/lkdtm_core.c b/drivers/misc/lkdtm_core.c
> index 42d2b8e31e6b..9544e329de4b 100644
> --- a/drivers/misc/lkdtm_core.c
> +++ b/drivers/misc/lkdtm_core.c
> @@ -235,6 +235,9 @@ struct crashtype crashtypes[] = {
>         CRASHTYPE(USERCOPY_STACK_FRAME_FROM),
>         CRASHTYPE(USERCOPY_STACK_BEYOND),
>         CRASHTYPE(USERCOPY_KERNEL),
> +       CRASHTYPE(XPFO_READ_USER),
> +       CRASHTYPE(XPFO_READ_USER_HUGE),
> +       CRASHTYPE(XPFO_SMP),
>  };
>
>
> diff --git a/drivers/misc/lkdtm_xpfo.c b/drivers/misc/lkdtm_xpfo.c
> new file mode 100644
> index 000000000000..d903063bdd0b
> --- /dev/null
> +++ b/drivers/misc/lkdtm_xpfo.c
> @@ -0,0 +1,194 @@
> +/*
> + * This is for all the tests related to XPFO (eXclusive Page Frame Ownership).
> + */
> +
> +#include "lkdtm.h"
> +
> +#include <linux/cpumask.h>
> +#include <linux/mman.h>
> +#include <linux/uaccess.h>
> +#include <linux/xpfo.h>
> +#include <linux/kthread.h>
> +
> +#include <linux/delay.h>
> +#include <linux/sched/task.h>
> +
> +#define XPFO_DATA 0xdeadbeef
> +
> +static unsigned long do_map(unsigned long flags)
> +{
> +       unsigned long user_addr, user_data = XPFO_DATA;
> +
> +       user_addr = vm_mmap(NULL, 0, PAGE_SIZE,
> +                           PROT_READ | PROT_WRITE | PROT_EXEC,
> +                           flags, 0);
> +       if (user_addr >= TASK_SIZE) {
> +               pr_warn("Failed to allocate user memory\n");
> +               return 0;
> +       }
> +
> +       if (copy_to_user((void __user *)user_addr, &user_data,
> +                        sizeof(user_data))) {
> +               pr_warn("copy_to_user failed\n");
> +               goto free_user;
> +       }
> +
> +       return user_addr;
> +
> +free_user:
> +       vm_munmap(user_addr, PAGE_SIZE);
> +       return 0;
> +}
> +
> +static unsigned long *user_to_kernel(unsigned long user_addr)
> +{
> +       phys_addr_t phys_addr;
> +       void *virt_addr;
> +
> +       phys_addr = user_virt_to_phys(user_addr);
> +       if (!phys_addr) {
> +               pr_warn("Failed to get physical address of user memory\n");
> +               return NULL;
> +       }
> +
> +       virt_addr = phys_to_virt(phys_addr);
> +       if (phys_addr != virt_to_phys(virt_addr)) {
> +               pr_warn("Physical address of user memory seems incorrect\n");
> +               return NULL;
> +       }
> +
> +       return virt_addr;
> +}
> +
> +static void read_map(unsigned long *virt_addr)
> +{
> +       pr_info("Attempting bad read from kernel address %p\n", virt_addr);
> +       if (*(unsigned long *)virt_addr == XPFO_DATA)
> +               pr_err("FAIL: Bad read succeeded?!\n");
> +       else
> +               pr_err("FAIL: Bad read didn't fail but data is incorrect?!\n");
> +}
> +
> +static void read_user_with_flags(unsigned long flags)
> +{
> +       unsigned long user_addr, *kernel;
> +
> +       user_addr = do_map(flags);
> +       if (!user_addr) {
> +               pr_err("FAIL: map failed\n");
> +               return;
> +       }
> +
> +       kernel = user_to_kernel(user_addr);
> +       if (!kernel) {
> +               pr_err("FAIL: user to kernel conversion failed\n");
> +               goto free_user;
> +       }
> +
> +       read_map(kernel);
> +
> +free_user:
> +       vm_munmap(user_addr, PAGE_SIZE);
> +}
> +
> +/* Read from userspace via the kernel's linear map. */
> +void lkdtm_XPFO_READ_USER(void)
> +{
> +       read_user_with_flags(MAP_PRIVATE | MAP_ANONYMOUS);
> +}
> +
> +void lkdtm_XPFO_READ_USER_HUGE(void)
> +{
> +       read_user_with_flags(MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB);
> +}
> +
> +struct smp_arg {
> +       unsigned long *virt_addr;
> +       unsigned int cpu;
> +};
> +
> +static int smp_reader(void *parg)
> +{
> +       struct smp_arg *arg = parg;
> +       unsigned long *virt_addr;
> +
> +       if (arg->cpu != smp_processor_id()) {
> +               pr_err("FAIL: scheduled on wrong CPU?\n");
> +               return 0;
> +       }
> +
> +       virt_addr = smp_cond_load_acquire(&arg->virt_addr, VAL != NULL);
> +       read_map(virt_addr);
> +
> +       return 0;
> +}
> +
> +#ifdef CONFIG_X86
> +#define XPFO_SMP_KILLED SIGKILL
> +#elif CONFIG_ARM64
> +#define XPFO_SMP_KILLED SIGSEGV
> +#else
> +#error unsupported arch
> +#endif

This will fail the build for other architectures, so I would just do
this as an single if/else:

#ifdef CONFIG_ARM64
# define XPFO_SMP_KILLED SIGSEGV
#else
# define XPFO_SMP_KILLED SIGKILL
#endif

> +
> +/* The idea here is to read from the kernel's map on a different thread than

Comment style nit: leading /*\n please...

> + * did the mapping (and thus the TLB flushing), to make sure that the page
> + * faults on other cores too.
> + */
> +void lkdtm_XPFO_SMP(void)
> +{
> +       unsigned long user_addr, *virt_addr;
> +       struct task_struct *thread;
> +       int ret;
> +       struct smp_arg arg;
> +
> +       if (num_online_cpus() < 2) {
> +               pr_err("not enough to do a multi cpu test\n");
> +               return;
> +       }
> +
> +       arg.virt_addr = NULL;
> +       arg.cpu = (smp_processor_id() + 1) % num_online_cpus();
> +       thread = kthread_create(smp_reader, &arg, "lkdtm_xpfo_test");
> +       if (IS_ERR(thread)) {
> +               pr_err("couldn't create kthread? %ld\n", PTR_ERR(thread));
> +               return;
> +       }
> +
> +       kthread_bind(thread, arg.cpu);
> +       get_task_struct(thread);
> +       wake_up_process(thread);
> +
> +       user_addr = do_map(MAP_PRIVATE | MAP_ANONYMOUS);
> +       if (!user_addr)
> +               goto kill_thread;
> +
> +       virt_addr = user_to_kernel(user_addr);
> +       if (!virt_addr) {
> +               /*
> +                * let's store something that will fail, so we can unblock the
> +                * thread
> +                */
> +               smp_store_release(&arg.virt_addr, &arg);
> +               goto free_user;
> +       }
> +
> +       smp_store_release(&arg.virt_addr, virt_addr);
> +
> +       /* there must be a better way to do this. */
> +       while (1) {
> +               if (thread->exit_state)
> +                       break;
> +               msleep_interruptible(100);
> +       }

I don't like infinite loops. How about giving this a 1 second max runtime?

> +
> +free_user:
> +       if (user_addr)
> +               vm_munmap(user_addr, PAGE_SIZE);
> +
> +kill_thread:
> +       ret = kthread_stop(thread);
> +       if (ret != XPFO_SMP_KILLED)
> +               pr_err("FAIL: thread wasn't killed: %d\n", ret);
> +       put_task_struct(thread);
> +}
> --
> 2.11.0
>

Otherwise it looks great, thanks!

-Kees
kernel test robot Sept. 10, 2017, 12:57 a.m. UTC | #2
Hi Juerg,

[auto build test ERROR on arm64/for-next/core]
[also build test ERROR on v4.13]
[cannot apply to mmotm/master next-20170908]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Tycho-Andersen/Add-support-for-eXclusive-Page-Frame-Ownership/20170910-073030
base:   https://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git for-next/core
config: xtensa-allmodconfig (attached as .config)
compiler: xtensa-linux-gcc (GCC) 4.9.0
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        make.cross ARCH=xtensa 

All error/warnings (new ones prefixed by >>):

   drivers/misc/lkdtm_xpfo.c: In function 'user_to_kernel':
>> drivers/misc/lkdtm_xpfo.c:54:2: error: implicit declaration of function 'phys_to_virt' [-Werror=implicit-function-declaration]
     virt_addr = phys_to_virt(phys_addr);
     ^
   drivers/misc/lkdtm_xpfo.c:54:12: warning: assignment makes pointer from integer without a cast
     virt_addr = phys_to_virt(phys_addr);
               ^
>> drivers/misc/lkdtm_xpfo.c:55:2: error: implicit declaration of function 'virt_to_phys' [-Werror=implicit-function-declaration]
     if (phys_addr != virt_to_phys(virt_addr)) {
     ^
   drivers/misc/lkdtm_xpfo.c: At top level:
>> drivers/misc/lkdtm_xpfo.c:128:7: warning: "CONFIG_ARM64" is not defined [-Wundef]
    #elif CONFIG_ARM64
          ^
>> drivers/misc/lkdtm_xpfo.c:131:2: error: #error unsupported arch
    #error unsupported arch
     ^
   drivers/misc/lkdtm_xpfo.c: In function 'lkdtm_XPFO_SMP':
>> drivers/misc/lkdtm_xpfo.c:191:13: error: 'XPFO_SMP_KILLED' undeclared (first use in this function)
     if (ret != XPFO_SMP_KILLED)
                ^
   drivers/misc/lkdtm_xpfo.c:191:13: note: each undeclared identifier is reported only once for each function it appears in
   cc1: some warnings being treated as errors

vim +/phys_to_virt +54 drivers/misc/lkdtm_xpfo.c

    42	
    43	static unsigned long *user_to_kernel(unsigned long user_addr)
    44	{
    45		phys_addr_t phys_addr;
    46		void *virt_addr;
    47	
    48		phys_addr = user_virt_to_phys(user_addr);
    49		if (!phys_addr) {
    50			pr_warn("Failed to get physical address of user memory\n");
    51			return NULL;
    52		}
    53	
  > 54		virt_addr = phys_to_virt(phys_addr);
  > 55		if (phys_addr != virt_to_phys(virt_addr)) {
    56			pr_warn("Physical address of user memory seems incorrect\n");
    57			return NULL;
    58		}
    59	
    60		return virt_addr;
    61	}
    62	
    63	static void read_map(unsigned long *virt_addr)
    64	{
    65		pr_info("Attempting bad read from kernel address %p\n", virt_addr);
    66		if (*(unsigned long *)virt_addr == XPFO_DATA)
    67			pr_err("FAIL: Bad read succeeded?!\n");
    68		else
    69			pr_err("FAIL: Bad read didn't fail but data is incorrect?!\n");
    70	}
    71	
    72	static void read_user_with_flags(unsigned long flags)
    73	{
    74		unsigned long user_addr, *kernel;
    75	
    76		user_addr = do_map(flags);
    77		if (!user_addr) {
    78			pr_err("FAIL: map failed\n");
    79			return;
    80		}
    81	
    82		kernel = user_to_kernel(user_addr);
    83		if (!kernel) {
    84			pr_err("FAIL: user to kernel conversion failed\n");
    85			goto free_user;
    86		}
    87	
    88		read_map(kernel);
    89	
    90	free_user:
    91		vm_munmap(user_addr, PAGE_SIZE);
    92	}
    93	
    94	/* Read from userspace via the kernel's linear map. */
    95	void lkdtm_XPFO_READ_USER(void)
    96	{
    97		read_user_with_flags(MAP_PRIVATE | MAP_ANONYMOUS);
    98	}
    99	
   100	void lkdtm_XPFO_READ_USER_HUGE(void)
   101	{
   102		read_user_with_flags(MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB);
   103	}
   104	
   105	struct smp_arg {
   106		unsigned long *virt_addr;
   107		unsigned int cpu;
   108	};
   109	
   110	static int smp_reader(void *parg)
   111	{
   112		struct smp_arg *arg = parg;
   113		unsigned long *virt_addr;
   114	
   115		if (arg->cpu != smp_processor_id()) {
   116			pr_err("FAIL: scheduled on wrong CPU?\n");
   117			return 0;
   118		}
   119	
   120		virt_addr = smp_cond_load_acquire(&arg->virt_addr, VAL != NULL);
   121		read_map(virt_addr);
   122	
   123		return 0;
   124	}
   125	
   126	#ifdef CONFIG_X86
   127	#define XPFO_SMP_KILLED SIGKILL
 > 128	#elif CONFIG_ARM64
   129	#define XPFO_SMP_KILLED SIGSEGV
   130	#else
 > 131	#error unsupported arch
   132	#endif
   133	
   134	/* The idea here is to read from the kernel's map on a different thread than
   135	 * did the mapping (and thus the TLB flushing), to make sure that the page
   136	 * faults on other cores too.
   137	 */
   138	void lkdtm_XPFO_SMP(void)
   139	{
   140		unsigned long user_addr, *virt_addr;
   141		struct task_struct *thread;
   142		int ret;
   143		struct smp_arg arg;
   144	
   145		if (num_online_cpus() < 2) {
   146			pr_err("not enough to do a multi cpu test\n");
   147			return;
   148		}
   149	
   150		arg.virt_addr = NULL;
   151		arg.cpu = (smp_processor_id() + 1) % num_online_cpus();
   152		thread = kthread_create(smp_reader, &arg, "lkdtm_xpfo_test");
   153		if (IS_ERR(thread)) {
   154			pr_err("couldn't create kthread? %ld\n", PTR_ERR(thread));
   155			return;
   156		}
   157	
   158		kthread_bind(thread, arg.cpu);
   159		get_task_struct(thread);
   160		wake_up_process(thread);
   161	
   162		user_addr = do_map(MAP_PRIVATE | MAP_ANONYMOUS);
   163		if (!user_addr)
   164			goto kill_thread;
   165	
   166		virt_addr = user_to_kernel(user_addr);
   167		if (!virt_addr) {
   168			/*
   169			 * let's store something that will fail, so we can unblock the
   170			 * thread
   171			 */
   172			smp_store_release(&arg.virt_addr, &arg);
   173			goto free_user;
   174		}
   175	
   176		smp_store_release(&arg.virt_addr, virt_addr);
   177	
   178		/* there must be a better way to do this. */
   179		while (1) {
   180			if (thread->exit_state)
   181				break;
   182			msleep_interruptible(100);
   183		}
   184	
   185	free_user:
   186		if (user_addr)
   187			vm_munmap(user_addr, PAGE_SIZE);
   188	
   189	kill_thread:
   190		ret = kthread_stop(thread);
 > 191		if (ret != XPFO_SMP_KILLED)

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
diff mbox

Patch

diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index b0b766416306..8447b42a447d 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -62,6 +62,7 @@  lkdtm-$(CONFIG_LKDTM)		+= lkdtm_heap.o
 lkdtm-$(CONFIG_LKDTM)		+= lkdtm_perms.o
 lkdtm-$(CONFIG_LKDTM)		+= lkdtm_rodata_objcopy.o
 lkdtm-$(CONFIG_LKDTM)		+= lkdtm_usercopy.o
+lkdtm-$(CONFIG_LKDTM)		+= lkdtm_xpfo.o
 
 KCOV_INSTRUMENT_lkdtm_rodata.o	:= n
 
diff --git a/drivers/misc/lkdtm.h b/drivers/misc/lkdtm.h
index 3b4976396ec4..34a6ee37f216 100644
--- a/drivers/misc/lkdtm.h
+++ b/drivers/misc/lkdtm.h
@@ -64,4 +64,9 @@  void lkdtm_USERCOPY_STACK_FRAME_FROM(void);
 void lkdtm_USERCOPY_STACK_BEYOND(void);
 void lkdtm_USERCOPY_KERNEL(void);
 
+/* lkdtm_xpfo.c */
+void lkdtm_XPFO_READ_USER(void);
+void lkdtm_XPFO_READ_USER_HUGE(void);
+void lkdtm_XPFO_SMP(void);
+
 #endif
diff --git a/drivers/misc/lkdtm_core.c b/drivers/misc/lkdtm_core.c
index 42d2b8e31e6b..9544e329de4b 100644
--- a/drivers/misc/lkdtm_core.c
+++ b/drivers/misc/lkdtm_core.c
@@ -235,6 +235,9 @@  struct crashtype crashtypes[] = {
 	CRASHTYPE(USERCOPY_STACK_FRAME_FROM),
 	CRASHTYPE(USERCOPY_STACK_BEYOND),
 	CRASHTYPE(USERCOPY_KERNEL),
+	CRASHTYPE(XPFO_READ_USER),
+	CRASHTYPE(XPFO_READ_USER_HUGE),
+	CRASHTYPE(XPFO_SMP),
 };
 
 
diff --git a/drivers/misc/lkdtm_xpfo.c b/drivers/misc/lkdtm_xpfo.c
new file mode 100644
index 000000000000..d903063bdd0b
--- /dev/null
+++ b/drivers/misc/lkdtm_xpfo.c
@@ -0,0 +1,194 @@ 
+/*
+ * This is for all the tests related to XPFO (eXclusive Page Frame Ownership).
+ */
+
+#include "lkdtm.h"
+
+#include <linux/cpumask.h>
+#include <linux/mman.h>
+#include <linux/uaccess.h>
+#include <linux/xpfo.h>
+#include <linux/kthread.h>
+
+#include <linux/delay.h>
+#include <linux/sched/task.h>
+
+#define XPFO_DATA 0xdeadbeef
+
+static unsigned long do_map(unsigned long flags)
+{
+	unsigned long user_addr, user_data = XPFO_DATA;
+
+	user_addr = vm_mmap(NULL, 0, PAGE_SIZE,
+			    PROT_READ | PROT_WRITE | PROT_EXEC,
+			    flags, 0);
+	if (user_addr >= TASK_SIZE) {
+		pr_warn("Failed to allocate user memory\n");
+		return 0;
+	}
+
+	if (copy_to_user((void __user *)user_addr, &user_data,
+			 sizeof(user_data))) {
+		pr_warn("copy_to_user failed\n");
+		goto free_user;
+	}
+
+	return user_addr;
+
+free_user:
+	vm_munmap(user_addr, PAGE_SIZE);
+	return 0;
+}
+
+static unsigned long *user_to_kernel(unsigned long user_addr)
+{
+	phys_addr_t phys_addr;
+	void *virt_addr;
+
+	phys_addr = user_virt_to_phys(user_addr);
+	if (!phys_addr) {
+		pr_warn("Failed to get physical address of user memory\n");
+		return NULL;
+	}
+
+	virt_addr = phys_to_virt(phys_addr);
+	if (phys_addr != virt_to_phys(virt_addr)) {
+		pr_warn("Physical address of user memory seems incorrect\n");
+		return NULL;
+	}
+
+	return virt_addr;
+}
+
+static void read_map(unsigned long *virt_addr)
+{
+	pr_info("Attempting bad read from kernel address %p\n", virt_addr);
+	if (*(unsigned long *)virt_addr == XPFO_DATA)
+		pr_err("FAIL: Bad read succeeded?!\n");
+	else
+		pr_err("FAIL: Bad read didn't fail but data is incorrect?!\n");
+}
+
+static void read_user_with_flags(unsigned long flags)
+{
+	unsigned long user_addr, *kernel;
+
+	user_addr = do_map(flags);
+	if (!user_addr) {
+		pr_err("FAIL: map failed\n");
+		return;
+	}
+
+	kernel = user_to_kernel(user_addr);
+	if (!kernel) {
+		pr_err("FAIL: user to kernel conversion failed\n");
+		goto free_user;
+	}
+
+	read_map(kernel);
+
+free_user:
+	vm_munmap(user_addr, PAGE_SIZE);
+}
+
+/* Read from userspace via the kernel's linear map. */
+void lkdtm_XPFO_READ_USER(void)
+{
+	read_user_with_flags(MAP_PRIVATE | MAP_ANONYMOUS);
+}
+
+void lkdtm_XPFO_READ_USER_HUGE(void)
+{
+	read_user_with_flags(MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB);
+}
+
+struct smp_arg {
+	unsigned long *virt_addr;
+	unsigned int cpu;
+};
+
+static int smp_reader(void *parg)
+{
+	struct smp_arg *arg = parg;
+	unsigned long *virt_addr;
+
+	if (arg->cpu != smp_processor_id()) {
+		pr_err("FAIL: scheduled on wrong CPU?\n");
+		return 0;
+	}
+
+	virt_addr = smp_cond_load_acquire(&arg->virt_addr, VAL != NULL);
+	read_map(virt_addr);
+
+	return 0;
+}
+
+#ifdef CONFIG_X86
+#define XPFO_SMP_KILLED SIGKILL
+#elif CONFIG_ARM64
+#define XPFO_SMP_KILLED SIGSEGV
+#else
+#error unsupported arch
+#endif
+
+/* The idea here is to read from the kernel's map on a different thread than
+ * did the mapping (and thus the TLB flushing), to make sure that the page
+ * faults on other cores too.
+ */
+void lkdtm_XPFO_SMP(void)
+{
+	unsigned long user_addr, *virt_addr;
+	struct task_struct *thread;
+	int ret;
+	struct smp_arg arg;
+
+	if (num_online_cpus() < 2) {
+		pr_err("not enough to do a multi cpu test\n");
+		return;
+	}
+
+	arg.virt_addr = NULL;
+	arg.cpu = (smp_processor_id() + 1) % num_online_cpus();
+	thread = kthread_create(smp_reader, &arg, "lkdtm_xpfo_test");
+	if (IS_ERR(thread)) {
+		pr_err("couldn't create kthread? %ld\n", PTR_ERR(thread));
+		return;
+	}
+
+	kthread_bind(thread, arg.cpu);
+	get_task_struct(thread);
+	wake_up_process(thread);
+
+	user_addr = do_map(MAP_PRIVATE | MAP_ANONYMOUS);
+	if (!user_addr)
+		goto kill_thread;
+
+	virt_addr = user_to_kernel(user_addr);
+	if (!virt_addr) {
+		/*
+		 * let's store something that will fail, so we can unblock the
+		 * thread
+		 */
+		smp_store_release(&arg.virt_addr, &arg);
+		goto free_user;
+	}
+
+	smp_store_release(&arg.virt_addr, virt_addr);
+
+	/* there must be a better way to do this. */
+	while (1) {
+		if (thread->exit_state)
+			break;
+		msleep_interruptible(100);
+	}
+
+free_user:
+	if (user_addr)
+		vm_munmap(user_addr, PAGE_SIZE);
+
+kill_thread:
+	ret = kthread_stop(thread);
+	if (ret != XPFO_SMP_KILLED)
+		pr_err("FAIL: thread wasn't killed: %d\n", ret);
+	put_task_struct(thread);
+}