blob: d428c600a97087b613ebf1e85ff359dc8a5de861 [file] [log] [blame]
/*
* 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.content.Context;
import android.content.pm.PackageManager;
import android.net.cts.NetlinkSocket;
import android.os.storage.StorageManager;
import android.test.AndroidTestCase;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
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 AndroidTestCase {
/**
* Try to inject vold commands.
*/
public void testTryCommandInjection() throws Exception {
final StorageManager sm = (StorageManager) getContext().getSystemService(
Context.STORAGE_SERVICE);
try {
sm.getMountedObbPath("/dev/null\0asec list");
fail("able to inject vold commands");
} catch(IllegalArgumentException e) {
// expected
}
}
/**
* Validate that this device isn't vulnerable to the "ZergRush"
* vold vulnerability (CVE-2011-3874).
*
* https://github.com/revolutionary/zergRush/blob/master/zergRush.c
*
* Note: If the ZergRush vulnerability is present, the call to
* {@link StorageManager#getMountedObbPath(String)} below hangs until CTS
* kills the testsuite (10 minutes). A timeout, while not desirable,
* is the typical failure for this test.
*/
public void testZergRushCrash() throws Exception {
int pid = findVold();
StorageManager sm = (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE);
sm.getMountedObbPath("AAAA AAAA AAAA AAAA "
+ "AAAA AAAA AAAA AAAA "
+ "AAAA AAAA AAAA AAAA "
+ "AAAA AAAA AAAA AAAA"
+ "AAAA AAAA AAAA AAAA"
+ "AAAA AAAA AAAA AAAA"
+ "AAAA AAAA AAAA AAAA"
+ "AAAA AAAA AAAA AAAA");
Thread.sleep(2000); // give vold some time to crash
// Check to see if vold is still alive.
assertTrue(
"PID=" + pid + " crashed due to a malformed mount message."
+ " Detected unpatched ZergRush vulnerability (CVE-2011-3874).",
new File("/proc/" + pid + "/cmdline").exists());
}
/**
* Try to crash the vold program using CVE-2011-1823.
*
* 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"));
if (devices.isEmpty()) {
// This vulnerability is not exploitable if there's
// no entry in vold.fstab
return;
}
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) {
// Ignore the exception. The process either:
//
// 1) Crashed
// 2) Closed the netlink socket and refused further messages
//
// If #1 occurs, our PID check in testTryToCrashVold() will
// detect the process crashed and trigger an error.
//
// #2 is not a security bug. It's perfectly acceptable to
// refuse messages from someone trying to send you
// malicious content.
}
}
/**
* 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);
if (!netlink.canRead()) {
return retval;
}
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" or approximately 10 seconds have passed.
*
* This indicates that either the netlink message was received,
* or the process took too long to process the incoming netlink
* message.
*
* See http://code.google.com/p/android/issues/detail?id=25099
* for information on why the timeout is needed.
*/
private static void confirmNetlinkMsgReceived() {
try {
for (int ct = 0; ct < 200; ct++) {
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);
}
}
private static int findVold() throws IOException {
File f = new File("/proc");
for (File d : f.listFiles()) {
String cmdLineString = d.getAbsolutePath() + "/cmdline";
File cmdLine = new File(cmdLineString);
if (cmdLine.exists()) {
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(cmdLine));
String line = in.readLine();
if ((line != null) && line.startsWith("/system/bin/vold")) {
return Integer.decode(d.getName());
}
} finally {
if (in != null) {
in.close();
}
}
}
}
throw new RuntimeException("should never get here");
}
/**
* 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 (new File("/proc/" + pid + "/cmdline").exists()) {
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);
}
}
}