Cleanup vroot test and fix false positive.
The previous test was too complicated. Use the kernel binary
sysctl to exercise the get_user call instead. Besides being
simpler, it has the advantage of not crashing the device
if the vulnerability is present.
Credit for the idea comes from https://twitter.com/grsecurity/status/401443359912239105
(cherrypicked from db7aafb58c829d48bf0ba5346ea873ab859365de)
Bug: 15408470
Change-Id: I25137913f0671037b2e24101bf0097f2c2fadee6
diff --git a/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp b/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
index 7c05008..73ca50c 100644
--- a/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
+++ b/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
@@ -35,6 +35,7 @@
#include <linux/perf_event.h>
#include <errno.h>
#include <inttypes.h>
+#include <linux/sysctl.h>
#define PASSED 0
#define UNKNOWN_ERROR -1
@@ -154,67 +155,6 @@
return UNKNOWN_ERROR;
}
-#define SEARCH_SIZE 0x4000
-
-static int secret;
-
-static bool isValidChildAddress(pid_t child, uintptr_t addr) {
- long word;
- long ret = syscall(__NR_ptrace, PTRACE_PEEKDATA, child, addr, &word);
- return (ret == 0);
-}
-
-/* A lazy, do nothing child. GET A JOB. */
-static void child() {
- int res;
- ALOGE("in child");
- secret = 0xbaadadd4;
- res = prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
- if (res != 0) {
- ALOGE("prctl failed");
- }
- res = ptrace(PTRACE_TRACEME, 0, 0, 0);
- if (res != 0) {
- ALOGE("child ptrace failed");
- }
- signal(SIGSTOP, SIG_IGN);
- kill(getpid(), SIGSTOP);
-}
-
-static jboolean parent(pid_t child) {
- int status;
- // Wait for the child to suspend itself so we can trace it.
- waitpid(child, &status, 0);
- jboolean result = true;
-
- uintptr_t addr;
- for (addr = 0x00000000; addr < 0xFFFF1000; addr+=SEARCH_SIZE) {
- if (isValidChildAddress(child, addr)) {
- // Don't scribble on our memory.
- // (which has the same mapping as our child)
- // We don't want to corrupt ourself.
- continue;
- }
-
- errno = 0;
- syscall(__NR_ptrace, PTRACE_PEEKDATA, child, &secret, addr);
- if (errno == 0) {
- result = false;
- // We found an address which isn't in our our, or our child's,
- // address space, but yet which is still writable. Scribble
- // all over it.
- ALOGE("parent: found writable at %" PRIxPTR, addr);
- uintptr_t addr2;
- for (addr2 = addr; addr2 < addr + SEARCH_SIZE; addr2++) {
- syscall(__NR_ptrace, PTRACE_PEEKDATA, child, &secret, addr2);
- }
- }
- }
-
- ptrace(PTRACE_DETACH, child, 0, 0);
- return result;
-}
-
/*
* Prior to https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/arch/arm/include/asm/uaccess.h?id=8404663f81d212918ff85f493649a7991209fa04
* there was a flaw in the kernel's handling of get_user and put_user
@@ -222,25 +162,32 @@
* that reads/writes outside the process's address space are not
* allowed.
*
- * In this test, we use prctl(PTRACE_PEEKDATA) to force a write to
- * an address outside of our address space. Without the patch applied,
- * this write succeeds, because prctl(PTRACE_PEEKDATA) uses the
- * vulnerable put_user call.
+ * In this test, we use sysctl to force a read from an address outside
+ * of our address space (but in the kernel's address space). Without the
+ * patch applied, this read succeeds, because sysctl uses the
+ * vulnerable get_user call.
+ *
+ * This function returns true if the patch above is applied, or false
+ * otherwise.
+ *
+ * Credit: https://twitter.com/grsecurity/status/401443359912239105
*/
static jboolean android_security_cts_NativeCodeTest_doVrootTest(JNIEnv*, jobject)
{
ALOGE("Starting doVrootTest");
- pid_t pid = fork();
- if (pid == -1) {
- return false;
- }
- if (pid == 0) {
- child();
- exit(0);
- }
+ struct __sysctl_args args;
+ char osname[100];
+ int name[] = { CTL_KERN, KERN_OSTYPE };
- return parent(pid);
+ memset(&args, 0, sizeof(struct __sysctl_args));
+ args.name = name;
+ args.nlen = sizeof(name)/sizeof(name[0]);
+ args.oldval = osname;
+ args.oldlenp = (size_t *) 0xc0000000; // PAGE_OFFSET
+
+ int result = syscall(__NR__sysctl, &args);
+ return ((result == -1) && (errno == EFAULT));
}
static void* mmap_syscall(void* addr, size_t len, int prot, int flags, int fd, off_t offset)
diff --git a/tests/tests/security/src/android/security/cts/NativeCodeTest.java b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
index 685036f..4be00b6 100644
--- a/tests/tests/security/src/android/security/cts/NativeCodeTest.java
+++ b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
@@ -25,7 +25,10 @@
}
public void testVroot() throws Exception {
- assertTrue(doVrootTest());
+ assertTrue("Device is vulnerable to CVE-2013-6282. Please apply security patch at "
+ + "https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/"
+ + "commit/arch/arm/include/asm/uaccess.h?id="
+ + "8404663f81d212918ff85f493649a7991209fa04", doVrootTest());
}
public void testPerfEvent() throws Exception {
@@ -84,11 +87,7 @@
/**
* ANDROID-11234878 / CVE-2013-6282
*
- * Returns true if the device is patched against the vroot
- * vulnerability. Returns false if there was some problem running
- * the test (for example, out of memory), or the test fails but wasn't
- * able to crash the device. Most of the time, however, the device will
- * crash if the vulnerability is present.
+ * Returns true if the device is patched against the vroot vulnerability, false otherwise.
*
* The following patch addresses this bug:
* https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/arch/arm/include/asm/uaccess.h?id=8404663f81d212918ff85f493649a7991209fa04