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.compatibility.common.util.CrashUtils; 20 import com.android.compatibility.common.util.MetricsReportLog; 21 import com.android.compatibility.common.util.ResultType; 22 import com.android.compatibility.common.util.ResultUnit; 23 import com.android.ddmlib.IShellOutputReceiver; 24 import com.android.ddmlib.NullOutputReceiver; 25 import com.android.ddmlib.CollectingOutputReceiver; 26 import com.android.tradefed.device.DeviceNotAvailableException; 27 import com.android.tradefed.device.ITestDevice; 28 import com.android.tradefed.device.NativeDevice; 29 import com.android.tradefed.log.LogUtil.CLog; 30 31 import java.io.BufferedOutputStream; 32 import java.io.File; 33 import java.io.FileOutputStream; 34 import java.io.InputStream; 35 import java.io.OutputStream; 36 import java.util.concurrent.TimeoutException; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.regex.Pattern; 40 import java.util.concurrent.TimeUnit; 41 import java.util.Scanner; 42 import java.util.Arrays; 43 import java.util.ArrayList; 44 import java.util.concurrent.Callable; 45 import java.util.Collections; 46 47 import org.json.JSONArray; 48 import org.json.JSONException; 49 import org.json.JSONObject; 50 51 import java.util.regex.Pattern; 52 import java.lang.Thread; 53 54 import static org.junit.Assert.*; 55 import static org.junit.Assume.*; 56 57 public class AdbUtils { 58 59 final static String TMP_PATH = "/data/local/tmp/"; 60 final static int TIMEOUT_SEC = 9 * 60; 61 final static String RESOURCE_ROOT = "/"; 62 63 public static class pocConfig { 64 String binaryName; 65 String arguments; 66 Map<String, String> envVars; 67 String inputFilesDestination; 68 ITestDevice device; 69 CrashUtils.Config config; 70 List<String> inputFiles = Collections.emptyList(); 71 boolean checkCrash = true; 72 pocConfig(String binaryName, ITestDevice device)73 pocConfig(String binaryName, ITestDevice device) { 74 this.binaryName = binaryName; 75 this.device = device; 76 } 77 } 78 79 /** Runs a commandline on the specified device 80 * 81 * @param command the command to be ran 82 * @param device device for the command to be ran on 83 * @return the console output from running the command 84 */ runCommandLine(String command, ITestDevice device)85 public static String runCommandLine(String command, ITestDevice device) throws Exception { 86 if ("reboot".equals(command)) { 87 throw new IllegalArgumentException( 88 "You called a forbidden command! Please fix your tests."); 89 } 90 return device.executeShellCommand(command); 91 } 92 93 /** 94 * Pushes and runs a binary to the selected device 95 * 96 * @param pocName name of the poc binary 97 * @param device device to be ran on 98 * @return the console output from the binary 99 */ runPoc(String pocName, ITestDevice device)100 public static String runPoc(String pocName, ITestDevice device) throws Exception { 101 return runPoc(pocName, device, SecurityTestCase.TIMEOUT_NONDETERMINISTIC); 102 } 103 104 /** 105 * Pushes and runs a binary to the selected device 106 * 107 * @param pocName name of the poc binary 108 * @param device device to be ran on 109 * @param timeout time to wait for output in seconds 110 * @return the console output from the binary 111 */ runPoc(String pocName, ITestDevice device, int timeout)112 public static String runPoc(String pocName, ITestDevice device, int timeout) throws Exception { 113 return runPoc(pocName, device, timeout, null); 114 } 115 116 /** 117 * Pushes and runs a binary to the selected device 118 * 119 * @param pocName name of the poc binary 120 * @param device device to be ran on 121 * @param timeout time to wait for output in seconds 122 * @param arguments the input arguments for the poc 123 * @return the console output from the binary 124 */ runPoc(String pocName, ITestDevice device, int timeout, String arguments)125 public static String runPoc(String pocName, ITestDevice device, int timeout, String arguments) 126 throws Exception { 127 CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 128 runPoc(pocName, device, timeout, arguments, receiver); 129 return receiver.getOutput(); 130 } 131 132 /** 133 * Pushes and runs a binary to the selected device and ignores any of its output. 134 * 135 * @param pocName name of the poc binary 136 * @param device device to be ran on 137 * @param timeout time to wait for output in seconds 138 */ runPocNoOutput(String pocName, ITestDevice device, int timeout)139 public static void runPocNoOutput(String pocName, ITestDevice device, int timeout) 140 throws Exception { 141 runPocNoOutput(pocName, device, timeout, null); 142 } 143 144 /** 145 * Pushes and runs a binary with arguments to the selected device and 146 * ignores any of its output. 147 * 148 * @param pocName name of the poc binary 149 * @param device device to be ran on 150 * @param timeout time to wait for output in seconds 151 * @param arguments input arguments for the poc 152 */ runPocNoOutput(String pocName, ITestDevice device, int timeout, String arguments)153 public static void runPocNoOutput(String pocName, ITestDevice device, int timeout, 154 String arguments) throws Exception { 155 runPoc(pocName, device, timeout, arguments, null); 156 } 157 158 /** 159 * Pushes and runs a binary with arguments to the selected device and 160 * ignores any of its output. 161 * 162 * @param pocName name of the poc binary 163 * @param device device to be ran on 164 * @param timeout time to wait for output in seconds 165 * @param arguments input arguments for the poc 166 * @param receiver the type of receiver to run against 167 */ runPoc(String pocName, ITestDevice device, int timeout, String arguments, IShellOutputReceiver receiver)168 public static int runPoc(String pocName, ITestDevice device, int timeout, 169 String arguments, IShellOutputReceiver receiver) throws Exception { 170 return runPoc(pocName, device, timeout, arguments, null, receiver); 171 } 172 173 /** 174 * Pushes and runs a binary with arguments to the selected device and 175 * ignores any of its output. 176 * 177 * @param pocName name of the poc binary 178 * @param device device to be ran on 179 * @param timeout time to wait for output in seconds 180 * @param arguments input arguments for the poc 181 * @param envVars run the poc with environment variables 182 * @param receiver the type of receiver to run against 183 */ runPoc(String pocName, ITestDevice device, int timeout, String arguments, Map<String, String> envVars, IShellOutputReceiver receiver)184 public static int runPoc(String pocName, ITestDevice device, int timeout, 185 String arguments, Map<String, String> envVars, 186 IShellOutputReceiver receiver) throws Exception { 187 String remoteFile = String.format("%s%s", TMP_PATH, pocName); 188 SecurityTestCase.getPocPusher(device).pushFile(pocName + "_sts", remoteFile); 189 190 assertPocExecutable(pocName, device); 191 if (receiver == null) { 192 receiver = new NullOutputReceiver(); 193 } 194 if (arguments == null) { 195 arguments = ""; 196 } 197 198 String env = ""; 199 if (envVars != null) { 200 StringBuilder sb = new StringBuilder(); 201 for (Map.Entry<String, String> entry : envVars.entrySet()) { 202 sb 203 .append(entry.getKey().trim()) 204 .append('=') 205 .append(entry.getValue().trim()) 206 .append(' '); 207 } 208 env = sb.toString(); 209 CLog.i("Running poc '%s' with env variables '%s'", pocName, env); 210 } 211 212 // since we have to return the exit status AND the poc stdout+stderr we redirect the exit 213 // status to a file temporarily 214 String exitStatusFilepath = TMP_PATH + "exit_status"; 215 runCommandLine("rm " + exitStatusFilepath, device); // remove any old exit status 216 device.executeShellCommand( 217 env + TMP_PATH + pocName + " " + arguments + 218 "; echo $? > " + exitStatusFilepath, // echo exit status to file 219 receiver, timeout, TimeUnit.SECONDS, 0); 220 221 // cat the exit status 222 String exitStatusString = runCommandLine("cat " + exitStatusFilepath, device).trim(); 223 224 MetricsReportLog reportLog = SecurityTestCase.buildMetricsReportLog(device); 225 reportLog.addValue("poc_name", pocName, ResultType.NEUTRAL, ResultUnit.NONE); 226 int exitStatus = -1; 227 try { 228 exitStatus = Integer.parseInt(exitStatusString); 229 reportLog.addValue("exit_status", exitStatus, ResultType.NEUTRAL, ResultUnit.NONE); 230 } catch (NumberFormatException e) { 231 // Getting the exit status is a bonus. We can continue without it. 232 CLog.w("Could not parse exit status to int: %s", exitStatusString); 233 } 234 reportLog.submit(); 235 236 runCommandLine("rm " + exitStatusFilepath, device); 237 return exitStatus; 238 } 239 240 /** 241 * Assert the poc is executable 242 * @param pocName name of the poc binary 243 * @param device device to be ran on 244 */ assertPocExecutable(String pocName, ITestDevice device)245 private static void assertPocExecutable(String pocName, ITestDevice device) throws Exception { 246 String fullPocPath = TMP_PATH + pocName; 247 device.executeShellCommand("chmod 777 " + fullPocPath); 248 assertEquals("'" + pocName + "' must exist and be readable.", 0, 249 runCommandGetExitCode("test -r " + fullPocPath, device)); 250 assertEquals("'" + pocName + "'poc must exist and be writable.", 0, 251 runCommandGetExitCode("test -w " + fullPocPath, device)); 252 assertEquals("'" + pocName + "'poc must exist and be executable.", 0, 253 runCommandGetExitCode("test -x " + fullPocPath, device)); 254 } 255 256 /** 257 * Enables malloc debug on a given process. 258 * 259 * @param processName the name of the process to run with libc malloc debug 260 * @param device the device to use 261 * @return true if enabling malloc debug succeeded 262 */ enableLibcMallocDebug(String processName, ITestDevice device)263 public static boolean enableLibcMallocDebug(String processName, ITestDevice device) throws Exception { 264 device.executeShellCommand("setprop libc.debug.malloc.program " + processName); 265 device.executeShellCommand("setprop libc.debug.malloc.options \"backtrace guard\""); 266 /** 267 * The pidof command is being avoided because it does not exist on versions before M, and 268 * it behaves differently between M and N. 269 * Also considered was the ps -AoPID,CMDLINE command, but ps does not support options on 270 * versions before O. 271 * The [^]] prefix is being used for the grep command to avoid the case where the output of 272 * ps includes the grep command itself. 273 */ 274 String cmdOut = device.executeShellCommand("ps -A | grep '[^]]" + processName + "'"); 275 /** 276 * .hasNextInt() checks if the next token can be parsed as an integer, not if any remaining 277 * token is an integer. 278 * Example command: $ ps | fgrep mediaserver 279 * Out: media 269 1 77016 24416 binder_thr 00f35142ec S /system/bin/mediaserver 280 * The second field of the output is the PID, which is needed to restart the process. 281 */ 282 Scanner s = new Scanner(cmdOut).useDelimiter("\\D+"); 283 if(!s.hasNextInt()) { 284 CLog.w("Could not find pid for process: " + processName); 285 return false; 286 } 287 288 String result = device.executeShellCommand("kill -9 " + s.nextInt()); 289 if(!result.equals("")) { 290 CLog.w("Could not restart process: " + processName); 291 return false; 292 } 293 294 TimeUnit.SECONDS.sleep(1); 295 return true; 296 } 297 298 /** 299 * Pushes and installs an apk to the selected device 300 * 301 * @param pathToApk a string path to apk from the /res folder 302 * @param device device to be ran on 303 * @return the output from attempting to install the apk 304 */ installApk(String pathToApk, ITestDevice device)305 public static String installApk(String pathToApk, ITestDevice device) throws Exception { 306 307 String fullResourceName = pathToApk; 308 File apkFile = File.createTempFile("apkFile", ".apk"); 309 try { 310 apkFile = extractResource(fullResourceName, apkFile); 311 return device.installPackage(apkFile, true); 312 } finally { 313 apkFile.delete(); 314 } 315 } 316 317 /** 318 * Extracts a resource and pushes it to the device 319 * 320 * @param fullResourceName a string path to resource from the res folder 321 * @param deviceFilePath the remote destination absolute file path 322 * @param device device to be ran on 323 */ pushResource(String fullResourceName, String deviceFilePath, ITestDevice device)324 public static void pushResource(String fullResourceName, String deviceFilePath, 325 ITestDevice device) throws Exception { 326 File resFile = File.createTempFile("CTSResource", ""); 327 try { 328 resFile = extractResource(fullResourceName, resFile); 329 device.pushFile(resFile, deviceFilePath); 330 } finally { 331 resFile.delete(); 332 } 333 } 334 335 /** 336 * Pushes the specified files to the specified destination directory 337 * 338 * @param inputFiles files required as input 339 * @param inputFilesDestination destination directory to which input files are 340 * pushed 341 * @param device device to be run on 342 */ pushResources(String[] inputFiles, String inputFilesDestination, ITestDevice device)343 public static void pushResources(String[] inputFiles, String inputFilesDestination, 344 ITestDevice device) throws Exception { 345 if (inputFiles == null || inputFilesDestination == null) { 346 throw new IllegalArgumentException( 347 "Can't push resources: input files or destination is null"); 348 } 349 for (String tempFile : inputFiles) { 350 pushResource(RESOURCE_ROOT + tempFile, inputFilesDestination + tempFile, device); 351 } 352 } 353 354 /** 355 * Removes the specified files from the specified destination directory 356 * 357 * @param inputFiles files required as input 358 * @param inputFilesDestination destination directory where input files are 359 * present 360 * @param device device to be run on 361 */ removeResources(String[] inputFiles, String inputFilesDestination, ITestDevice device)362 public static void removeResources(String[] inputFiles, String inputFilesDestination, 363 ITestDevice device) throws Exception { 364 if (inputFiles == null || inputFilesDestination == null) { 365 throw new IllegalArgumentException( 366 "Can't remove resources: input files or destination is null"); 367 } 368 for (String tempFile : inputFiles) { 369 runCommandLine("rm " + inputFilesDestination + tempFile, device); 370 } 371 } 372 373 /** 374 * Extracts the binary data from a resource and writes it to a temp file 375 */ extractResource(String fullResourceName, File file)376 private static File extractResource(String fullResourceName, File file) throws Exception { 377 try (InputStream in = AdbUtils.class.getResourceAsStream(fullResourceName); 378 OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) { 379 if (in == null) { 380 throw new IllegalArgumentException("Resource not found: " + fullResourceName); 381 } 382 byte[] buf = new byte[65536]; 383 int chunkSize; 384 while ((chunkSize = in.read(buf)) != -1) { 385 out.write(buf, 0, chunkSize); 386 } 387 return file; 388 } 389 390 } 391 /** 392 * Utility function to help check the exit code of a shell command 393 */ runCommandGetExitCode(String cmd, ITestDevice device)394 public static int runCommandGetExitCode(String cmd, ITestDevice device) throws Exception { 395 long time = System.currentTimeMillis(); 396 String exitStatusString = runCommandLine( 397 "(" + cmd + ") > /dev/null 2>&1; echo $?", device).trim(); 398 time = System.currentTimeMillis() - time; 399 400 try { 401 int exitStatus = Integer.parseInt(exitStatusString); 402 MetricsReportLog reportLog = SecurityTestCase.buildMetricsReportLog(device); 403 reportLog.addValue("command", cmd, ResultType.NEUTRAL, ResultUnit.NONE); 404 reportLog.addValue("exit_status", exitStatus, ResultType.NEUTRAL, ResultUnit.NONE); 405 reportLog.submit(); 406 return exitStatus; 407 } catch (NumberFormatException e) { 408 throw new IllegalArgumentException(String.format( 409 "Could not get the exit status (%s) for '%s' (%d ms).", 410 exitStatusString, cmd, time)); 411 } 412 } 413 414 /** 415 * Pushes and runs a binary to the selected device and checks exit code 416 * Return code 113 is used to indicate the vulnerability 417 * 418 * @param pocName a string path to poc from the /res folder 419 * @param device device to be ran on 420 * @param timeout time to wait for output in seconds 421 */ 422 @Deprecated runPocCheckExitCode(String pocName, ITestDevice device, int timeout)423 public static boolean runPocCheckExitCode(String pocName, ITestDevice device, 424 int timeout) throws Exception { 425 426 //Refer to go/asdl-sts-guide Test section for knowing the significance of 113 code 427 return runPocGetExitStatus(pocName, device, timeout) == 113; 428 } 429 430 /** 431 * Pushes and runs a binary to the device and returns the exit status. 432 * @param pocName a string path to poc from the /res folder 433 * @param device device to be ran on 434 * @param timeout time to wait for output in seconds 435 436 */ runPocGetExitStatus(String pocName, ITestDevice device, int timeout)437 public static int runPocGetExitStatus(String pocName, ITestDevice device, int timeout) 438 throws Exception { 439 return runPocGetExitStatus(pocName, null, device, timeout); 440 } 441 442 /** 443 * Pushes and runs a binary to the device and returns the exit status. 444 * @param pocName a string path to poc from the /res folder 445 * @param arguments input arguments for the poc 446 * @param device device to be ran on 447 * @param timeout time to wait for output in seconds 448 */ runPocGetExitStatus(String pocName, String arguments, ITestDevice device, int timeout)449 public static int runPocGetExitStatus(String pocName, String arguments, ITestDevice device, 450 int timeout) throws Exception { 451 return runPocGetExitStatus(pocName, arguments, null, device, timeout); 452 } 453 454 /** 455 * Pushes and runs a binary to the device and returns the exit status. 456 * @param pocName name of the poc binary 457 * @param arguments input arguments for the poc 458 * @param envVars run the poc with environment variables 459 * @param device device to be run on 460 * @param timeout time to wait for output in seconds 461 */ runPocGetExitStatus( String pocName, String arguments, Map<String, String> envVars, ITestDevice device, int timeout)462 public static int runPocGetExitStatus( 463 String pocName, String arguments, Map<String, String> envVars, 464 ITestDevice device, int timeout) throws Exception { 465 return runPoc(pocName, device, timeout, arguments, envVars, null); 466 } 467 468 /** 469 * Pushes and runs a binary and asserts that the exit status isn't 113: vulnerable. 470 * @param pocName a string path to poc from the /res folder 471 * @param device device to be ran on 472 * @param timeout time to wait for output in seconds 473 */ runPocAssertExitStatusNotVulnerable( String pocName, ITestDevice device, int timeout)474 public static void runPocAssertExitStatusNotVulnerable( 475 String pocName, ITestDevice device, int timeout) throws Exception { 476 runPocAssertExitStatusNotVulnerable(pocName, null, device, timeout); 477 } 478 479 /** 480 * Pushes and runs a binary and asserts that the exit status isn't 113: vulnerable. 481 * @param pocName a string path to poc from the /res folder 482 * @param arguments input arguments for the poc 483 * @param device device to be ran on 484 * @param timeout time to wait for output in seconds 485 */ runPocAssertExitStatusNotVulnerable(String pocName, String arguments, ITestDevice device, int timeout)486 public static void runPocAssertExitStatusNotVulnerable(String pocName, String arguments, 487 ITestDevice device, int timeout) throws Exception { 488 runPocGetExitStatus(pocName, arguments, null, device, timeout); 489 } 490 491 /** 492 * Pushes and runs a binary and asserts that the exit status isn't 113: vulnerable. 493 * @param pocName name of the poc binary 494 * @param arguments input arguments for the poc 495 * @param envVars run the poc with environment variables 496 * @param device device to be ran on 497 * @param timeout time to wait for output in seconds 498 */ runPocAssertExitStatusNotVulnerable( String pocName, String arguments, Map<String, String> envVars, ITestDevice device, int timeout)499 public static void runPocAssertExitStatusNotVulnerable( 500 String pocName, String arguments, Map<String, String> envVars, 501 ITestDevice device, int timeout) throws Exception { 502 assertTrue("PoC returned exit status 113: vulnerable", 503 runPocGetExitStatus(pocName, arguments, envVars, device, timeout) != 113); 504 } 505 506 /** 507 * Runs the poc binary and asserts that there are no security crashes that match the expected 508 * process pattern. 509 * @param pocName a string path to poc from the /res folder 510 * @param device device to be ran on 511 * @param processPatternStrings a Pattern string to match the crash tombstone process 512 */ runPocAssertNoCrashes(String pocName, ITestDevice device, String... processPatternStrings)513 public static void runPocAssertNoCrashes(String pocName, ITestDevice device, 514 String... processPatternStrings) throws Exception { 515 runPocAssertNoCrashes(pocName, device, 516 new CrashUtils.Config().setProcessPatterns(processPatternStrings)); 517 } 518 519 /** 520 * Runs the poc binary and asserts that there are no security crashes that match the expected 521 * process pattern. 522 * @param pocName a string path to poc from the /res folder 523 * @param device device to be ran on 524 * @param config a crash parser configuration 525 */ runPocAssertNoCrashes(String pocName, ITestDevice device, CrashUtils.Config config)526 public static void runPocAssertNoCrashes(String pocName, ITestDevice device, 527 CrashUtils.Config config) throws Exception { 528 runPocAssertNoCrashes(pocName, device, null, config); 529 } 530 531 /** 532 * Runs the poc binary and asserts that there are no security crashes that match the expected 533 * process pattern, including arguments when running. 534 * @param pocName a string path to poc from the /res folder 535 * @param device device to be ran on 536 * @param arguments input arguments for the poc 537 * @param config a crash parser configuration 538 */ runPocAssertNoCrashes(String pocName, ITestDevice device, String arguments, CrashUtils.Config config)539 public static void runPocAssertNoCrashes(String pocName, ITestDevice device, String arguments, 540 CrashUtils.Config config) throws Exception { 541 AdbUtils.runCommandLine("logcat -c", device); 542 AdbUtils.runPocNoOutput(pocName, device, 543 SecurityTestCase.TIMEOUT_NONDETERMINISTIC, arguments); 544 assertNoCrashes(device, config); 545 } 546 547 /** 548 * Runs the poc binary and asserts following 2 conditions. 549 * 1. There are no security crashes in the binary. 550 * 2. The exit status isn't 113 (Code 113 is used to indicate the vulnerability condition). 551 * 552 * @param binaryName name of the binary 553 * @param arguments arguments for running the binary 554 * @param device device to be run on 555 */ runPocAssertNoCrashesNotVulnerable(String binaryName, String arguments, ITestDevice device)556 public static void runPocAssertNoCrashesNotVulnerable(String binaryName, String arguments, 557 ITestDevice device) throws Exception { 558 runPocAssertNoCrashesNotVulnerable(binaryName, arguments, null, null, device, null); 559 } 560 561 /** 562 * Runs the poc binary and asserts following 2 conditions. 563 * 1. There are no security crashes in the binary. 564 * 2. The exit status isn't 113 (Code 113 is used to indicate the vulnerability condition). 565 * 566 * @param binaryName name of the binary 567 * @param arguments arguments for running the binary 568 * @param device device to be run on 569 * @param processPatternStrings a Pattern string to match the crash tombstone 570 * process 571 */ runPocAssertNoCrashesNotVulnerable(String binaryName, String arguments, ITestDevice device, String processPatternStrings[])572 public static void runPocAssertNoCrashesNotVulnerable(String binaryName, String arguments, 573 ITestDevice device, String processPatternStrings[]) throws Exception { 574 runPocAssertNoCrashesNotVulnerable(binaryName, arguments, null, null, device, 575 processPatternStrings); 576 } 577 578 /** 579 * Runs the poc binary and asserts following 2 conditions. 580 * 1. There are no security crashes in the binary. 581 * 2. The exit status isn't 113 (Code 113 is used to indicate the vulnerability condition). 582 * 583 * @param binaryName name of the binary 584 * @param arguments arguments for running the binary 585 * @param inputFiles files required as input 586 * @param inputFilesDestination destination directory to which input files are 587 * pushed 588 * @param device device to be run on 589 */ runPocAssertNoCrashesNotVulnerable(String binaryName, String arguments, String inputFiles[], String inputFilesDestination, ITestDevice device)590 public static void runPocAssertNoCrashesNotVulnerable(String binaryName, String arguments, 591 String inputFiles[], String inputFilesDestination, ITestDevice device) 592 throws Exception { 593 runPocAssertNoCrashesNotVulnerable(binaryName, arguments, inputFiles, inputFilesDestination, 594 device, null); 595 } 596 597 /** 598 * Runs the poc binary and asserts following 3 conditions. 599 * 1. There are no security crashes in the binary. 600 * 2. There are no security crashes that match the expected process pattern. 601 * 3. The exit status isn't 113 (Code 113 is used to indicate the vulnerability condition). 602 * 603 * @param binaryName name of the binary 604 * @param arguments arguments for running the binary 605 * @param inputFiles files required as input 606 * @param inputFilesDestination destination directory to which input files are 607 * pushed 608 * @param device device to be run on 609 * @param processPatternStrings a Pattern string to match the crash tombstone 610 * process 611 */ runPocAssertNoCrashesNotVulnerable(String binaryName, String arguments, String inputFiles[], String inputFilesDestination, ITestDevice device, String processPatternStrings[])612 public static void runPocAssertNoCrashesNotVulnerable(String binaryName, String arguments, 613 String inputFiles[], String inputFilesDestination, ITestDevice device, 614 String processPatternStrings[]) throws Exception { 615 runPocAssertNoCrashesNotVulnerable(binaryName, arguments, null, 616 inputFiles, inputFilesDestination, device, processPatternStrings); 617 } 618 619 /** 620 * Runs the poc binary and asserts following 3 conditions. 621 * 1. There are no security crashes in the binary. 622 * 2. There are no security crashes that match the expected process pattern. 623 * 3. The exit status isn't 113 (Code 113 is used to indicate the vulnerability condition). 624 * 625 * @param binaryName name of the binary 626 * @param arguments arguments for running the binary 627 * @param envVars run the poc with environment variables 628 * @param inputFiles files required as input 629 * @param inputFilesDestination destination directory to which input files are 630 * pushed 631 * @param device device to be run on 632 * @param processPatternStrings a Pattern string (other than binary name) to match the crash 633 * tombstone process 634 */ runPocAssertNoCrashesNotVulnerable( String binaryName, String arguments, Map<String, String> envVars, String inputFiles[], String inputFilesDestination, ITestDevice device, String... processPatternStrings)635 public static void runPocAssertNoCrashesNotVulnerable( 636 String binaryName, String arguments, Map<String, String> envVars, 637 String inputFiles[], String inputFilesDestination, ITestDevice device, 638 String... processPatternStrings) throws Exception { 639 pocConfig testConfig = new pocConfig(binaryName, device); 640 testConfig.arguments = arguments; 641 testConfig.envVars = envVars; 642 643 if (inputFiles != null) { 644 testConfig.inputFiles = Arrays.asList(inputFiles); 645 testConfig.inputFilesDestination = inputFilesDestination; 646 } 647 648 List<String> processPatternList = new ArrayList<>(); 649 if (processPatternStrings != null) { 650 processPatternList.addAll(Arrays.asList(processPatternStrings)); 651 } 652 processPatternList.add(binaryName); 653 String[] processPatternStringsWithSelf = new String[processPatternList.size()]; 654 processPatternList.toArray(processPatternStringsWithSelf); 655 testConfig.config = 656 new CrashUtils.Config().setProcessPatterns(processPatternStringsWithSelf); 657 658 runPocAssertNoCrashesNotVulnerable(testConfig); 659 } 660 661 /** 662 * Runs the poc binary and asserts following 3 conditions. 663 * 1. There are no security crashes in the binary. 664 * 2. There are no security crashes that match the expected process pattern. 665 * 3. The exit status isn't 113 (Code 113 is used to indicate the vulnerability condition). 666 * 667 * @param testConfig test configuration 668 */ runPocAssertNoCrashesNotVulnerable(pocConfig testConfig)669 public static void runPocAssertNoCrashesNotVulnerable(pocConfig testConfig) throws Exception { 670 String[] inputFiles = null; 671 if(!testConfig.inputFiles.isEmpty()) { 672 inputFiles = testConfig.inputFiles.toArray(new String[testConfig.inputFiles.size()]); 673 pushResources(inputFiles, testConfig.inputFilesDestination, testConfig.device); 674 } 675 runCommandLine("logcat -c", testConfig.device); 676 try { 677 runPocAssertExitStatusNotVulnerable(testConfig.binaryName, testConfig.arguments, 678 testConfig.envVars, testConfig.device, TIMEOUT_SEC); 679 } catch (IllegalArgumentException e) { 680 /* 681 * Since 'runPocGetExitStatus' method raises IllegalArgumentException upon 682 * hang/timeout, catching the exception here and ignoring it. Hangs are of 683 * Moderate severity and hence patches may not be ported. This piece of code can 684 * be removed once 'runPocGetExitStatus' is updated to handle hangs. 685 */ 686 CLog.w("Ignoring IllegalArgumentException: " + e); 687 } finally { 688 if (!testConfig.inputFiles.isEmpty()) { 689 removeResources(inputFiles, testConfig.inputFilesDestination, testConfig.device); 690 } 691 } 692 if(testConfig.checkCrash) { 693 if (testConfig.config == null) { 694 testConfig.config = new CrashUtils.Config(); 695 } 696 assertNoCrashes(testConfig.device, testConfig.config); 697 } 698 } 699 700 /** 701 * Dumps logcat and asserts that there are no security crashes that match the expected process. 702 * By default, checks min crash addresses 703 * pattern. Ensure that adb logcat -c is called beforehand. 704 * @param device device to be ran on 705 * @param processPatternStrings a Pattern string to match the crash tombstone process 706 */ assertNoCrashes(ITestDevice device, String... processPatternStrings)707 public static void assertNoCrashes(ITestDevice device, String... processPatternStrings) 708 throws Exception { 709 assertNoCrashes(device, new CrashUtils.Config().setProcessPatterns(processPatternStrings)); 710 } 711 712 /** 713 * Dumps logcat and asserts that there are no security crashes that match the expected process 714 * pattern. Ensure that adb logcat -c is called beforehand. 715 * @param device device to be ran on 716 * @param config a crash parser configuration 717 */ assertNoCrashes(ITestDevice device, CrashUtils.Config config)718 public static void assertNoCrashes(ITestDevice device, 719 CrashUtils.Config config) throws Exception { 720 String logcat = AdbUtils.runCommandLine("logcat -d *:S DEBUG:V", device); 721 722 JSONArray crashes = CrashUtils.addAllCrashes(logcat, new JSONArray()); 723 JSONArray securityCrashes = CrashUtils.matchSecurityCrashes(crashes, config); 724 725 MetricsReportLog reportLog = SecurityTestCase.buildMetricsReportLog(device); 726 reportLog.addValue("all_crashes", crashes.toString(), ResultType.NEUTRAL, ResultUnit.NONE); 727 reportLog.addValue("security_crashes", securityCrashes.toString(), 728 ResultType.NEUTRAL, ResultUnit.NONE); 729 reportLog.submit(); 730 731 if (securityCrashes.length() == 0) { 732 return; // no security crashes detected 733 } 734 735 StringBuilder error = new StringBuilder(); 736 error.append("Security crash detected:\n"); 737 error.append("Process patterns:"); 738 for (Pattern pattern : config.getProcessPatterns()) { 739 error.append(String.format(" '%s'", pattern.toString())); 740 } 741 error.append("\nCrashes:\n"); 742 for (int i = 0; i < crashes.length(); i++) { 743 try { 744 JSONObject crash = crashes.getJSONObject(i); 745 error.append(String.format("%s\n", crash)); 746 } catch (JSONException e) {} 747 } 748 fail(error.toString()); 749 } 750 assumeHasNfc(ITestDevice device)751 public static void assumeHasNfc(ITestDevice device) throws DeviceNotAvailableException { 752 assumeTrue("nfc not available on device", device.hasFeature("android.hardware.nfc")); 753 } 754 } 755