1 /* 2 * Copyright (C) 2016 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 com.android.ddmlib.NullOutputReceiver; 20 import com.android.tradefed.device.CollectingOutputReceiver; 21 import com.android.tradefed.device.ITestDevice; 22 import com.android.tradefed.log.LogUtil.CLog; 23 24 import java.io.BufferedOutputStream; 25 import java.io.File; 26 import java.io.FileOutputStream; 27 import java.io.InputStream; 28 import java.io.OutputStream; 29 import java.util.concurrent.TimeUnit; 30 import java.util.Scanner; 31 32 import static org.junit.Assert.*; 33 34 public class AdbUtils { 35 36 /** Runs a commandline on the specified device 37 * 38 * @param command the command to be ran 39 * @param device device for the command to be ran on 40 * @return the console output from running the command 41 */ 42 public static String runCommandLine(String command, ITestDevice device) throws Exception { 43 if ("reboot".equals(command)) { 44 throw new IllegalArgumentException( 45 "You called a forbidden command! Please fix your tests."); 46 } 47 return device.executeShellCommand(command); 48 } 49 50 /** 51 * Pushes and runs a binary to the selected device 52 * 53 * @param pocName a string path to poc from the /res folder 54 * @param device device to be ran on 55 * @return the console output from the binary 56 */ 57 public static String runPoc(String pocName, ITestDevice device) throws Exception { 58 device.executeShellCommand("chmod +x /data/local/tmp/" + pocName); 59 return device.executeShellCommand("/data/local/tmp/" + pocName); 60 } 61 62 /** 63 * Pushes and runs a binary to the selected device 64 * 65 * @param pocName a string path to poc from the /res folder 66 * @param device device to be ran on 67 * @param timeout time to wait for output in seconds 68 * @return the console output from the binary 69 */ 70 public static String runPoc(String pocName, ITestDevice device, int timeout) throws Exception { 71 device.executeShellCommand("chmod +x /data/local/tmp/" + pocName); 72 CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 73 device.executeShellCommand("/data/local/tmp/" + pocName, receiver, timeout, TimeUnit.SECONDS, 0); 74 String output = receiver.getOutput(); 75 return output; 76 } 77 78 /** 79 * Pushes and runs a binary to the selected device and ignores any of its output. 80 * 81 * @param pocName a string path to poc from the /res folder 82 * @param device device to be ran on 83 * @param timeout time to wait for output in seconds 84 */ 85 public static void runPocNoOutput(String pocName, ITestDevice device, int timeout) 86 throws Exception { 87 device.executeShellCommand("chmod +x /data/local/tmp/" + pocName); 88 NullOutputReceiver receiver = new NullOutputReceiver(); 89 device.executeShellCommand("/data/local/tmp/" + pocName, receiver, timeout, 90 TimeUnit.SECONDS, 0); 91 } 92 93 /** 94 * Enables malloc debug on a given process. 95 * 96 * @param processName the name of the process to run with libc malloc debug 97 * @param device the device to use 98 * @return true if enabling malloc debug succeeded 99 */ 100 public static boolean enableLibcMallocDebug(String processName, ITestDevice device) throws Exception { 101 device.executeShellCommand("setprop libc.debug.malloc.program " + processName); 102 device.executeShellCommand("setprop libc.debug.malloc.options \"backtrace guard\""); 103 /** 104 * The pidof command is being avoided because it does not exist on versions before M, and 105 * it behaves differently between M and N. 106 * Also considered was the ps -AoPID,CMDLINE command, but ps does not support options on 107 * versions before O. 108 * The [^]] prefix is being used for the grep command to avoid the case where the output of 109 * ps includes the grep command itself. 110 */ 111 String cmdOut = device.executeShellCommand("ps -A | grep '[^]]" + processName + "'"); 112 /** 113 * .hasNextInt() checks if the next token can be parsed as an integer, not if any remaining 114 * token is an integer. 115 * Example command: $ ps | fgrep mediaserver 116 * Out: media 269 1 77016 24416 binder_thr 00f35142ec S /system/bin/mediaserver 117 * The second field of the output is the PID, which is needed to restart the process. 118 */ 119 Scanner s = new Scanner(cmdOut).useDelimiter("\\D+"); 120 if(!s.hasNextInt()) { 121 CLog.w("Could not find pid for process: " + processName); 122 return false; 123 } 124 125 String result = device.executeShellCommand("kill -9 " + s.nextInt()); 126 if(!result.equals("")) { 127 CLog.w("Could not restart process: " + processName); 128 return false; 129 } 130 131 TimeUnit.SECONDS.sleep(1); 132 return true; 133 } 134 135 /** 136 * Pushes and installs an apk to the selected device 137 * 138 * @param pathToApk a string path to apk from the /res folder 139 * @param device device to be ran on 140 * @return the output from attempting to install the apk 141 */ 142 public static String installApk(String pathToApk, ITestDevice device) throws Exception { 143 144 String fullResourceName = pathToApk; 145 File apkFile = File.createTempFile("apkFile", ".apk"); 146 try { 147 apkFile = extractResource(fullResourceName, apkFile); 148 return device.installPackage(apkFile, true); 149 } finally { 150 apkFile.delete(); 151 } 152 } 153 154 /** 155 * Extracts a resource and pushes it to the device 156 * 157 * @param fullResourceName a string path to resource from the res folder 158 * @param deviceFilePath the remote destination absolute file path 159 * @param device device to be ran on 160 */ 161 public static void pushResource(String fullResourceName, String deviceFilePath, 162 ITestDevice device) throws Exception { 163 File resFile = File.createTempFile("CTSResource", ""); 164 try { 165 resFile = extractResource(fullResourceName, resFile); 166 device.pushFile(resFile, deviceFilePath); 167 } finally { 168 resFile.delete(); 169 } 170 } 171 172 /** 173 * Extracts the binary data from a resource and writes it to a temp file 174 */ 175 private static File extractResource(String fullResourceName, File file) throws Exception { 176 try (InputStream in = AdbUtils.class.getResourceAsStream(fullResourceName); 177 OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) { 178 if (in == null) { 179 throw new IllegalArgumentException("Resource not found: " + fullResourceName); 180 } 181 byte[] buf = new byte[65536]; 182 int chunkSize; 183 while ((chunkSize = in.read(buf)) != -1) { 184 out.write(buf, 0, chunkSize); 185 } 186 return file; 187 } 188 189 } 190 /** 191 * Utility function to help check the exit code of a shell command 192 */ 193 public static int runCommandGetExitCode(String cmd, ITestDevice device) throws Exception { 194 return Integer.parseInt( 195 AdbUtils.runCommandLine( "(" + cmd + ") > /dev/null 2>&1; echo $?", 196 device).replaceAll("[^0-9]", "")); 197 } 198 199 /** 200 * Pushes and runs a binary to the selected device and checks exit code 201 * Return code 113 is used to indicate the vulnerability 202 * 203 * @param pocName a string path to poc from the /res folder 204 * @param device device to be ran on 205 * @param timeout time to wait for output in seconds 206 */ 207 @Deprecated 208 public static boolean runPocCheckExitCode(String pocName, ITestDevice device, 209 int timeout) throws Exception { 210 211 //Refer to go/asdl-sts-guide Test section for knowing the significance of 113 code 212 return runPocGetExitStatus(pocName, device, timeout) == 113; 213 } 214 215 /** 216 * Pushes and runs a binary to the device and returns the exit status. 217 * @param pocName a string path to poc from the /res folder 218 * @param device device to be ran on 219 * @param timeout time to wait for output in seconds 220 221 */ 222 public static int runPocGetExitStatus(String pocName, ITestDevice device, int timeout) 223 throws Exception { 224 device.executeShellCommand("chmod +x /data/local/tmp/" + pocName); 225 CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 226 device.executeShellCommand("/data/local/tmp/" + pocName + " > /dev/null 2>&1; echo $?", 227 receiver, timeout, TimeUnit.SECONDS, 0); 228 229 String exitStatus = receiver.getOutput().replaceAll("[^0-9]", ""); 230 return Integer.parseInt(exitStatus); 231 } 232 233 /** 234 * Pushes and runs a binary and asserts that the exit status isn't 113: vulnerable. 235 * @param pocName a string path to poc from the /res folder 236 * @param device device to be ran on 237 * @param timeout time to wait for output in seconds 238 */ 239 public static void runPocAssertExitStatusNotVulnerable( 240 String pocName, ITestDevice device, int timeout) throws Exception { 241 assertTrue("PoC returned exit status 113: vulnerable", 242 runPocGetExitStatus(pocName, device, timeout) != 113); 243 } 244 } 245