Re: [PATCH] arm64: kernel: memory corruptions due non-disabled PAN

From: Pavel Tatashin
Date: Wed Nov 20 2019 - 14:46:19 EST


> > I see that with CONFIG_ARM64_SW_TTBR0_PAN=y, this means that we may
> > leave the stale TTBR0 value installed across a context-switch (and have
> > reproduced that locally), but I'm having some difficulty reproducing the
> > corruption that you see.
>
> I will send the full test shortly. Note, I was never able to reproduce
> it in QEMU, only on real hardware. Also, for some unknown reason after
> kexec I could not reproduce it only during first boot, so it is
> somewhat fragile, but I am sure it can be reproduced in other cases as
> well, it is just my reproducer is not tunes for that.
>

Attached is the test program that I used to reproduce memory corruption.
Test on board with Broadcom's Stingray SoC.

Without fix:
# time /tmp/repro
Corruption: pid 1474 map[0] 1488 cpu 3
Terminated

real 0m0.088s
user 0m0.004s
sys 0m0.071s

With the fix:

# time /tmp/repro
Test passed, all good
Terminated

real 1m1.286s
user 0m0.004s
sys 0m0.970s



Pasha
#define _GNU_SOURCE
#include <linux/perf_event.h>
#include <sys/mman.h>
#include <sys/sysinfo.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <sched.h>
#include <time.h>
#include <unistd.h>

#define RUN_TIME 60
#define SIZE 4096
#define NPROCS 4096
#define NCPU get_nprocs_conf()
#define PAGEMAP_LENGTH 8
unsigned long get_pa(void *addr) {
FILE *pagemap = fopen("/proc/self/pagemap", "rb");
unsigned long offset = (unsigned long)addr / getpagesize() * PAGEMAP_LENGTH;
unsigned long pfn = 0;

if(fseek(pagemap, (unsigned long)offset, SEEK_SET) != 0) {
fprintf(stderr, "Failed to seek pagemap to proper location\n");
exit(1);
}

fread(&pfn, 1, PAGEMAP_LENGTH-1, pagemap);

pfn &= 0x7FFFFFFFFFFFFF;

fclose(pagemap);

return pfn * getpagesize();
}

int
do_work()
{
int *map, pid;
unsigned long pa;

if (fork())
exit(0);

pid = getpid();
map = mmap(NULL, SIZE, PROT_READ|PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,
-1, 0);
map[0] = pid;
sched_yield();
if (map[0] != pid) {
fprintf(stderr, "Corruption: pid %d map[0] %d cpu %d\n",
pid, map[0], sched_getcpu());
kill(0, SIGTERM);
return 1;
}
munmap(map, SIZE);
return 0;
}

static void event_open(struct perf_event_attr *ctx_event_attr, int config)
{
int i, fd;
ctx_event_attr->config = config;
for (i = 0; i < NCPU; i++) {
fd = syscall(__NR_perf_event_open, ctx_event_attr,
-1, i, -1, 0);
ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
}
}

static void
perf_events()
{
struct perf_event_attr ctx_event_attr;

memset(&ctx_event_attr, 0, sizeof(struct perf_event_attr));
ctx_event_attr.type = PERF_TYPE_SOFTWARE;
ctx_event_attr.size = sizeof (struct perf_event_attr);
ctx_event_attr.sample_period = 1;
ctx_event_attr.sample_type = PERF_SAMPLE_CALLCHAIN;

event_open(&ctx_event_attr, PERF_COUNT_SW_CPU_CLOCK);
}

int
main(int argc, char **argv)
{
pid_t p[NPROCS];
int i, fd;
cpu_set_t mask;

CPU_ZERO(&mask);
for (i = 0; i < NCPU; i++)
CPU_SET(i, &mask);
sched_setaffinity(0, sizeof(mask), &mask);

perf_events();
for (i = 0; i < NPROCS; i++) {
p[i] = fork();
if (p[i] == 0) {
for (;;) {
if (do_work()) {
fprintf(stderr, "Bug is detected\n");
kill(0, SIGTERM);
exit(1);
}
}
}
}

sleep(RUN_TIME);
printf("Test passed, all good\n");
kill(0, SIGTERM);
return 0;
}