aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShuai Xue <xueshuai@linux.alibaba.com>2022-11-29 21:28:03 +0800
committerTony Luck <tony.luck@intel.com>2022-11-29 09:56:28 -0800
commit132428d89a7c09744b8fd57c002ca4b13cf92107 (patch)
tree01b7d6edfd1303838c387b65a9fe8e1ea55c07f7
parent58d3e234d515e820febaa9fba5e7dfabf5baee95 (diff)
downloadras-tools-132428d89a7c09744b8fd57c002ca4b13cf92107.tar.gz
victim: add a victim to provide target injection memory under user context
Victim workes under user context, which provides target memory chunk for error injection. It can be used for all kinds of error types, including Corrected error and Uncorrected error(IFU/DCU). Here is an simple example for DCU: Mmap one page memory and returns starting address, and then translate virtual address to physical address. Caller like shell script can inject UC error (error type 0x10 in EINJ table) on returned physical address. Meanwhile, victim continues to read/write on returned memory space to trigger DCU happening ASAP. NOTE: this workload is borrowed from mce-test. Thanks to the origial authors, Tony Luck, Gong Chen, Wen Jin. Signed-off-by: Shuai Xue <xueshuai@linux.alibaba.com> Signed-off-by: Tony Luck <tony.luck@intel.com>
-rw-r--r--.gitignore1
-rw-r--r--Makefile7
-rw-r--r--victim.c366
3 files changed, 372 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index d25b558..d0eb8d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@ mca-recover
vtop
memattr
rep_ce_page
+victim
.vscode
modules.order
Module.symvers
diff --git a/Makefile b/Makefile
index 0546b60..4c902ee 100644
--- a/Makefile
+++ b/Makefile
@@ -2,10 +2,10 @@
CFLAGS = -O
-all: mca-recover vtop cmcistorm hornet einj_mem_uc lmce rep_ce_page memattr
+all: mca-recover vtop cmcistorm hornet einj_mem_uc lmce rep_ce_page memattr victim
clean:
- rm -f *.o mca-recover vtop cmcistorm hornet einj_mem_uc lmce rep_ce_page memattr
+ rm -f *.o mca-recover vtop cmcistorm hornet einj_mem_uc lmce rep_ce_page memattr victim
mca-recover: mca-recover.c
cc -o mca-recover $(CFLAGS) mca-recover.c
@@ -30,3 +30,6 @@ lmce: proc_pagemap.o lmce.o
memattr: memattr.c
cc -o memattr $(CFLAGS) memattr.c
+
+victim: victim.c
+ cc -o victim $(CFLAGS) victim.c
diff --git a/victim.c b/victim.c
new file mode 100644
index 0000000..bfcf065
--- /dev/null
+++ b/victim.c
@@ -0,0 +1,366 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Set up to get zapped by a machine check (injected elsewhere)
+ * To use this test case please ensure your SUT(System Under Test)
+ * can support MCE/SRAR.
+ *
+ * This file is released under the GPLv2.
+ *
+ * Copyright (C) 2012-2015 Intel corporation
+ *
+ * Author:
+ * Tony Luck <tony.luck@intel.com>
+ * Gong Chen <gong.chen@intel.com>
+ * Wen Jin <wenx.jin@intel.com>
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <errno.h>
+
+/*
+ * Definition of /proc/pid/pagemap
+ * Bits 0-54 page frame number (PFN) if present
+ * Bits 0-4 swap type if swapped
+ * Bits 5-54 swap offset if swapped
+ * Bits 55-60 page shift, the bits definition is legacy.
+ * Bit 61 reserved for future use
+ * Bit 62 page swapped
+ * Bit 63 page present
+ */
+
+struct pagemaps {
+ unsigned long long pfn:55;
+ unsigned long long pgshift:6; /*legacy*/
+ unsigned long long rsvd:1;
+ unsigned long long swapped:1;
+ unsigned long long present:1;
+};
+
+static int pagesize;
+
+/*
+ * dummyfunc size should be less than one page after complied,
+ * otherwise, caller will not return from this function
+ */
+void dummyfunc(void)
+{
+ int fatarray[64];
+
+ fatarray[0] = 0xdeadbeaf;
+ fatarray[8] = 0xdeadbeaf;
+ fatarray[16] = 0xdeadbeaf;
+ fatarray[32] = 0xdeadbeaf;
+}
+
+/*
+ * get information about address from /proc/{pid}/pagemap
+ */
+unsigned long long vtop(unsigned long long addr, pid_t pid)
+{
+ struct pagemaps pinfo;
+ unsigned int pinfo_size = sizeof(pinfo);
+ unsigned long long offset = addr / pagesize * pinfo_size;
+ int fd, pgmask;
+ char pagemapname[64];
+
+ sprintf(pagemapname, "/proc/%d/pagemap", pid);
+ fd = open(pagemapname, O_RDONLY);
+ if (fd == -1) {
+ perror(pagemapname);
+ return 0;
+ }
+ if (pread(fd, (void *)&pinfo, pinfo_size, offset) != pinfo_size) {
+ perror(pagemapname);
+ close(fd);
+ return 0;
+ }
+ close(fd);
+ pgmask = pagesize - 1;
+ return (pinfo.pfn * pagesize) | (addr & pgmask);
+}
+
+static void usage(void)
+{
+ printf(
+"victim [options]\n"
+" -a|--address vaddr=val, pid=val Translate process virtual address into physical address\n"
+" -d|--data Inject data error(DCU error) under user context\n"
+" -i|--instruction Inject instruction error(IFU error) under user context\n"
+" -k|--kick 0/1 Kick off trigger. Auto(0), Manual(1, by default)\n"
+" -p|--pfa help to test memory PFA(Predictive Failure Analysis) function\n"
+" -h|--help Show this usage message\n"
+ );
+}
+
+static const struct option opts[] = {
+ { "address" , 1, NULL, 'a' },
+ { "data" , 0, NULL, 'd' },
+ { "instruction" , 0, NULL, 'i' },
+ { "help" , 0, NULL, 'h' },
+ { "kick" , 1, NULL, 'k' },
+ { "pfa" , 0, NULL, 'p' },
+ { NULL , 0, NULL, 0 }
+};
+
+static void pfa_helper(char *p, pid_t pid, unsigned long long old_phys)
+{
+ int i;
+ int total;
+ unsigned long long new_phys;
+
+ for (;;) {
+ for (i = 0; i < pagesize; i += sizeof(int)) {
+ total += *(int*)(p + i);
+ *(int*)(p + i) = total;
+ }
+
+ new_phys = vtop((unsigned long long)p, pid);
+ if (old_phys == new_phys) {
+ for (i = 0; i < pagesize; i += sizeof(int)) {
+ total += *(int*)(p + i);
+ *(int*)(p + i) = i;
+ }
+ sleep(2);
+ new_phys = vtop((unsigned long long)p, pid);
+ if (old_phys != new_phys) {
+ printf("Page was replaced. New physical address = 0x%llx\n", new_phys);
+ fflush(stdout);
+ old_phys = new_phys;
+ }
+ } else {
+ printf("Page was replaced. New physical address = 0x%llx\n", new_phys);
+ fflush(stdout);
+ old_phys = new_phys;
+ }
+ }
+}
+
+static int parse_addr_subopts(char *subopts, unsigned long long *virt,
+ pid_t *pid)
+{
+ int err = 0;
+ int index;
+ enum {
+ I_VADDR = 0,
+ I_PID
+ };
+ char *const token[] = {
+ [I_VADDR] = "vaddr",
+ [I_PID] = "pid",
+ NULL
+ };
+ char *p = subopts;
+ char *subval;
+ char *svaddr;
+ char *spid;
+
+ while (*p != '\0' && !err) {
+ index = getsubopt(&p, token, &subval);
+ switch (index) {
+ case I_VADDR:
+ if (subval != NULL) {
+ svaddr = subval;
+ break;
+ } else {
+ fprintf(stderr,
+ "miss value for %s\n",
+ token[I_VADDR]);
+ err++;
+ continue;
+ }
+ case I_PID:
+ if (subval != NULL) {
+ spid = subval;
+ break;
+ } else {
+ fprintf(stderr,
+ "miss value for %s\n",
+ token[I_PID]);
+ err++;
+ continue;
+ }
+ default:
+ err++;
+ break;
+ }
+ }
+ if (err > 0) {
+ usage();
+ return 1;
+ }
+ errno = 0;
+ *virt = strtoull(svaddr, NULL, 0);
+ if ((*virt == 0 && svaddr[0] != '0') || errno != 0) {
+ fprintf(stderr, "Invalid virtual address: %s\n",
+ svaddr);
+ return 1;
+ }
+ errno = 0;
+ *pid = strtoul(spid, NULL, 0);
+ if ((*pid == 0 && spid[0] != '0') || errno != 0) {
+ fprintf(stderr, "Invalid process pid number: %s\n",
+ spid);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * The "SRAR DCU" test case failed on a CLX-AP server. It's root caused
+ * that the gcc v8.2.1 optimized out the access to the injected location.
+ *
+ * If keep "total" as a local, even mark it "volatile", the gcc v8.2.1
+ * still optimizes out the memory access. Therefore, move the "total" from
+ * being a local variable to a global to avoid such optimization.
+ */
+long total;
+
+int main(int argc, char **argv)
+{
+ unsigned long long virt, phys;
+ char *buf;
+ int c, i;
+ int iflag = 0, dflag = 0;
+ int kick = 1;
+ int pfa = 0;
+ pid_t pid;
+ const char *trigger = "./trigger_start";
+ const char *trigger_flag = "trigger";
+ int fd;
+ int count = 100;
+ char trigger_buf[16];
+ char answer[16];
+ time_t now;
+
+ if (argc <= 1) {
+ usage();
+ return 0;
+ }
+
+ pagesize = getpagesize();
+
+ while ((c = getopt_long(argc, argv, "a:dihk:p", opts, NULL)) != -1) {
+ switch (c) {
+ case 'a':
+ if (parse_addr_subopts(optarg, &virt, &pid) == 0) {
+ phys = vtop(virt, pid);
+ printf("physical address of (%d,0x%llx) = 0x%llx\n",
+ pid, virt, phys);
+ return 0;
+ }
+ return 1;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'k':
+ errno = 0;
+ kick = strtol(optarg, NULL, 0);
+ if ((kick == 0 && optarg[0] != '0') || errno != 0) {
+ fprintf(stderr, "Invalid parameter: %s\n", optarg);
+ return 1;
+ }
+ if (kick != 0 && kick != 1) {
+ fprintf(stderr, "Invalid parameter: %s\n", optarg);
+ return 1;
+ }
+ break;
+ case 'p':
+ pfa = 1;
+ break;
+ case 'h':
+ default:
+ usage();
+ return 0;
+ }
+ }
+
+ /* The MAP_LOCKED flag should not be used here, because it will cause a failure
+ * in KVM mce-inject test. But to prevent the mapped buffer from being swapped out,
+ * the system under test should not run in a heavy load environment.
+ */
+ buf = mmap(NULL, pagesize, PROT_READ|PROT_WRITE|PROT_EXEC,
+ MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
+
+ if (buf == MAP_FAILED) {
+ fprintf(stderr, "Can't get a single page of memory!\n");
+ return 1;
+ }
+ memset(buf, '*', pagesize);
+ pid = getpid();
+ phys = vtop((unsigned long long)buf, pid);
+ if (phys == 0) {
+ fprintf(stderr, "Can't get physical address of the page!\n");
+ return 1;
+ }
+
+ if (iflag)
+ memcpy(buf, (void *)dummyfunc, pagesize);
+
+ printf("physical address of (0x%llx) = 0x%llx\n",
+ (unsigned long long)buf, phys);
+ fflush(stdout);
+
+ if (pfa == 1)
+ pfa_helper(buf, pid, phys);
+
+ if (kick == 0) {
+ errno = 0;
+ if (unlink(trigger) < 0 && errno != ENOENT) {
+ fprintf(stderr, "fail to remove trigger file\n");
+ return 1;
+ }
+ memset(trigger_buf, 0, sizeof(trigger_buf));
+ while (--count) {
+ if ((fd = open(trigger, O_RDONLY)) < 0) {
+ sleep(1);
+ continue;
+ }
+ if (read(fd, trigger_buf, sizeof(trigger_buf)) > 0 &&
+ strstr(trigger_buf, trigger_flag) != NULL) {
+ break;
+ }
+ sleep(1);
+ }
+ if (count == 0) {
+ fprintf(stderr,
+ "Timeout to get trigger flag file\n");
+ return 1;
+ }
+ } else {
+ printf("Hit any key to trigger error: ");
+ fflush(stdout);
+ read(0, answer, 16);
+ now = time(NULL);
+ printf("Access time at %s\n", ctime(&now));
+ }
+
+ if (iflag) {
+ void (*f)(void) = (void (*)(void))buf;
+
+ while (1) f();
+ }
+
+ if (dflag) {
+ while (1) {
+ for (i = 0; i < pagesize; i += sizeof(int))
+ total += *(int *)(buf + i);
+ }
+ }
+
+ return 0;
+} \ No newline at end of file