1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.security.cts; 18 19 import android.content.Context; 20 import android.content.pm.PackageManager; 21 import android.os.storage.StorageManager; 22 import android.test.AndroidTestCase; 23 24 import java.io.BufferedReader; 25 import java.io.File; 26 import java.io.FileReader; 27 import java.io.IOException; 28 import java.io.UnsupportedEncodingException; 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Scanner; 34 import java.util.Set; 35 36 public class VoldExploitTest extends AndroidTestCase { 37 38 /** 39 * Try to inject vold commands. 40 */ testTryCommandInjection()41 public void testTryCommandInjection() throws Exception { 42 final StorageManager sm = (StorageManager) getContext().getSystemService( 43 Context.STORAGE_SERVICE); 44 String path = sm.getMountedObbPath("/dev/null\0asec list"); 45 assertNull(path); 46 } 47 48 /** 49 * Validate that this device isn't vulnerable to the "ZergRush" 50 * vold vulnerability (CVE-2011-3874). 51 * 52 * https://github.com/revolutionary/zergRush/blob/master/zergRush.c 53 * 54 * Note: If the ZergRush vulnerability is present, the call to 55 * {@link StorageManager#getMountedObbPath(String)} below hangs until CTS 56 * kills the testsuite (10 minutes). A timeout, while not desirable, 57 * is the typical failure for this test. 58 */ testZergRushCrash()59 public void testZergRushCrash() throws Exception { 60 int pid = Proc.findPidFor("/system/bin/vold"); 61 62 StorageManager sm = (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE); 63 sm.getMountedObbPath("AAAA AAAA AAAA AAAA " 64 + "AAAA AAAA AAAA AAAA " 65 + "AAAA AAAA AAAA AAAA " 66 + "AAAA AAAA AAAA AAAA" 67 + "AAAA AAAA AAAA AAAA" 68 + "AAAA AAAA AAAA AAAA" 69 + "AAAA AAAA AAAA AAAA" 70 + "AAAA AAAA AAAA AAAA"); 71 72 Thread.sleep(2000); // give vold some time to crash 73 74 // Check to see if vold is still alive. 75 assertTrue( 76 "PID=" + pid + " crashed due to a malformed mount message." 77 + " Detected unpatched ZergRush vulnerability (CVE-2011-3874).", 78 new File("/proc/" + pid + "/cmdline").exists()); 79 } 80 81 /** 82 * Try to crash the vold program using CVE-2011-1823. 83 * 84 * This test attempts to send an invalid netlink messages to 85 * any process which is listening for the messages. If we detect 86 * that any process crashed as a result of our message, then 87 * we know that we found a bug. 88 * 89 * If this test fails, it's due to CVE-2011-1823 90 * 91 * http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-1823 92 */ testTryToCrashVold()93 public void testTryToCrashVold() throws IOException { 94 Set<Integer> pids = getPids(); 95 assertTrue(pids.size() > 1); // at least vold and netd should exist 96 97 Set<String> devices = new HashSet<String>(); 98 devices.addAll(getSysFsPath("/etc/vold.fstab")); 99 devices.addAll(getSysFsPath("/system/etc/vold.fstab")); 100 if (devices.isEmpty()) { 101 // This vulnerability is not exploitable if there's 102 // no entry in vold.fstab 103 return; 104 } 105 106 NetlinkSocket ns = NetlinkSocket.create(); 107 for (int i : pids) { 108 for (String j : devices) { 109 doAttack(ns, i, j); 110 } 111 } 112 113 // Check to see if all the processes are still alive. If 114 // any of them have died, we found an exploitable bug. 115 for (int i : pids) { 116 assertTrue( 117 "PID=" + i + " crashed due to a malformed netlink message." 118 + " Detected unpatched vulnerability CVE-2011-1823.", 119 new File("/proc/" + i + "/cmdline").exists()); 120 } 121 } 122 123 /** 124 * Try to actually crash the program, by first sending a fake 125 * request to add a new disk, followed by a fake request to add 126 * a partition. 127 */ doAttack(NetlinkSocket ns, int pid, String path)128 private static void doAttack(NetlinkSocket ns, int pid, String path) 129 throws IOException { 130 try { 131 ns.sendmsg(pid, getDiskAddedMessage(path)); 132 confirmNetlinkMsgReceived(); 133 134 for (int i = -1000; i > -5000; i-=1000) { 135 ns.sendmsg(pid, getPartitionAddedMessage(path, i)); 136 confirmNetlinkMsgReceived(); 137 } 138 } catch (IOException e) { 139 // Ignore the exception. The process either: 140 // 141 // 1) Crashed 142 // 2) Closed the netlink socket and refused further messages 143 // 144 // If #1 occurs, our PID check in testTryToCrashVold() will 145 // detect the process crashed and trigger an error. 146 // 147 // #2 is not a security bug. It's perfectly acceptable to 148 // refuse messages from someone trying to send you 149 // malicious content. 150 } 151 } 152 153 /** 154 * Parse the fstab.vold file, and extract out the "sysfs_path" field. 155 */ getSysFsPath(String file)156 private static Set<String> getSysFsPath(String file) throws IOException { 157 Set<String> retval = new HashSet<String>(); 158 File netlink = new File(file); 159 if (!netlink.canRead()) { 160 return retval; 161 } 162 Scanner scanner = null; 163 try { 164 scanner = new Scanner(netlink); 165 while(scanner.hasNextLine()) { 166 String line = scanner.nextLine().trim(); 167 if (!line.startsWith("dev_mount")) { 168 continue; 169 } 170 171 String[] fields = line.split("\\s+"); 172 assertTrue(fields.length >= 5); 173 // Column 5 and beyond is "sysfs_path" 174 retval.addAll(Arrays.asList(fields).subList(4, fields.length)); 175 } 176 } finally { 177 if (scanner != null) { 178 scanner.close(); 179 } 180 } 181 return retval; 182 } 183 184 /** 185 * Poll /proc/net/netlink until all the "Rmem" fields contain 186 * "0" or approximately 10 seconds have passed. 187 * 188 * This indicates that either the netlink message was received, 189 * or the process took too long to process the incoming netlink 190 * message. 191 * 192 * See http://code.google.com/p/android/issues/detail?id=25099 193 * for information on why the timeout is needed. 194 */ confirmNetlinkMsgReceived()195 private static void confirmNetlinkMsgReceived() { 196 try { 197 for (int ct = 0; ct < 200; ct++) { 198 boolean foundAllZeros = true; 199 for (List<String> i : parseNetlink()) { 200 // Column 5 is the "Rmem" field, which is the 201 // amount of kernel memory for received netlink messages. 202 if (!i.get(4).equals("0")) { 203 foundAllZeros = false; 204 } 205 } 206 if (foundAllZeros) { 207 return; 208 } 209 Thread.sleep(50); 210 } 211 } catch (InterruptedException e) { 212 throw new RuntimeException(e); 213 } 214 } 215 216 /** 217 * Extract all the PIDs listening for netlink messages. 218 */ getPids()219 private static Set<Integer> getPids() { 220 List<List<String>> netlink = parseNetlink(); 221 Set<Integer> retval = new HashSet<Integer>(); 222 for (List<String> i : netlink) { 223 // The PID is in column 3 224 int pid = Long.decode(i.get(2)).intValue(); 225 if (new File("/proc/" + pid + "/cmdline").exists()) { 226 retval.add(pid); 227 } 228 } 229 return retval; 230 } 231 232 /** 233 * Parse /proc/net/netlink and return a List of lines 234 * (excluding the first line) 235 */ parseNetlink()236 private static List<List<String>> parseNetlink() { 237 List<List<String>> retval = new ArrayList<List<String>>(); 238 File netlink = new File("/proc/net/netlink"); 239 Scanner scanner = null; 240 try { 241 scanner = new Scanner(netlink); 242 while(scanner.hasNextLine()) { 243 String line = scanner.nextLine().trim(); 244 if (line.startsWith("sk")) { 245 continue; 246 } 247 248 List<String> lineList = Arrays.asList(line.split("\\s+")); 249 retval.add(lineList); 250 } 251 } catch (IOException e) { 252 throw new RuntimeException(e); 253 } finally { 254 if (scanner != null) { 255 scanner.close(); 256 } 257 } 258 return retval; 259 } 260 getDiskAddedMessage(String path)261 private static byte[] getDiskAddedMessage(String path) { 262 try { 263 return ("@/foo\0ACTION=add\0SUBSYSTEM=block\0" 264 + "DEVPATH=" + path + "\0MAJOR=179\0MINOR=12345" 265 + "\0DEVTYPE=disk\0").getBytes("ASCII"); 266 } catch (UnsupportedEncodingException e) { 267 throw new RuntimeException(e); 268 } 269 } 270 getPartitionAddedMessage( String path, int partitionNum)271 private static byte[] getPartitionAddedMessage( 272 String path, int partitionNum) { 273 try { 274 return ("@/foo\0ACTION=add\0SUBSYSTEM=block\0" 275 + "DEVPATH=" + path + "\0MAJOR=179\0MINOR=12345" 276 + "\0DEVTYPE=blah\0PARTN=" + partitionNum + "\0") 277 .getBytes("ASCII"); 278 } catch (UnsupportedEncodingException e) { 279 throw new RuntimeException(e); 280 } 281 } 282 } 283