| /* |
| * Copyright (C) 2016 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 com.android.ddmlib.NullOutputReceiver; |
| import com.android.tradefed.device.CollectingOutputReceiver; |
| import com.android.tradefed.device.DeviceNotAvailableException; |
| import com.android.tradefed.device.ITestDevice; |
| import com.android.tradefed.log.LogUtil.CLog; |
| |
| import java.io.BufferedOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.concurrent.TimeUnit; |
| import java.util.Scanner; |
| |
| import java.util.regex.Pattern; |
| import java.lang.Thread; |
| import static org.junit.Assert.*; |
| import junit.framework.Assert; |
| |
| public class AdbUtils { |
| |
| /** Runs a commandline on the specified device |
| * |
| * @param command the command to be ran |
| * @param device device for the command to be ran on |
| * @return the console output from running the command |
| */ |
| public static String runCommandLine(String command, ITestDevice device) throws Exception { |
| return device.executeShellCommand(command); |
| } |
| |
| /** |
| * Pushes and runs a binary to the selected device |
| * |
| * @param pocName name of the poc binary |
| * @param device device to be ran on |
| * @return the console output from the binary |
| */ |
| public static String runPoc(String pocName, ITestDevice device) throws Exception { |
| device.executeShellCommand("chmod +x /data/local/tmp/" + pocName); |
| return device.executeShellCommand("/data/local/tmp/" + pocName); |
| } |
| |
| /** |
| * Pushes and runs a binary to the selected device |
| * |
| * @param pocName name of the poc binary |
| * @param device device to be ran on |
| * @param timeout time to wait for output in seconds |
| * @return the console output from the binary |
| */ |
| public static String runPoc(String pocName, ITestDevice device, int timeout) throws Exception { |
| return runPoc(pocName, device, timeout, null); |
| } |
| |
| /** |
| * Pushes and runs a binary to the selected device |
| * |
| * @param pocName name of the poc binary |
| * @param device device to be ran on |
| * @param timeout time to wait for output in seconds |
| * @param arguments the input arguments for the poc |
| * @return the console output from the binary |
| */ |
| public static String runPoc(String pocName, ITestDevice device, int timeout, String arguments) |
| throws Exception { |
| device.executeShellCommand("chmod +x /data/local/tmp/" + pocName); |
| CollectingOutputReceiver receiver = new CollectingOutputReceiver(); |
| if (arguments != null) { |
| device.executeShellCommand("/data/local/tmp/" + pocName + " " + arguments, receiver, |
| timeout, TimeUnit.SECONDS, 0); |
| } else { |
| device.executeShellCommand("/data/local/tmp/" + pocName, receiver, timeout, |
| TimeUnit.SECONDS, 0); |
| } |
| String output = receiver.getOutput(); |
| return output; |
| } |
| |
| /** |
| * Pushes and runs a binary to the selected device and ignores any of its output. |
| * |
| * @param pocName name of the poc binary |
| * @param device device to be ran on |
| * @param timeout time to wait for output in seconds |
| */ |
| public static void runPocNoOutput(String pocName, ITestDevice device, int timeout) |
| throws Exception { |
| runPocNoOutput(pocName, device, timeout, null); |
| } |
| |
| /** |
| * Pushes and runs a binary with arguments to the selected device and |
| * ignores any of its output. |
| * |
| * @param pocName name of the poc binary |
| * @param device device to be ran on |
| * @param timeout time to wait for output in seconds |
| * @param arguments input arguments for the poc |
| */ |
| public static void runPocNoOutput(String pocName, ITestDevice device, int timeout, |
| String arguments) throws Exception { |
| device.executeShellCommand("chmod +x /data/local/tmp/" + pocName); |
| NullOutputReceiver receiver = new NullOutputReceiver(); |
| if (arguments != null) { |
| device.executeShellCommand("/data/local/tmp/" + pocName + " " + arguments, receiver, |
| timeout, TimeUnit.SECONDS, 0); |
| } else { |
| device.executeShellCommand("/data/local/tmp/" + pocName, receiver, timeout, |
| TimeUnit.SECONDS, 0); |
| } |
| } |
| |
| /** |
| * Enables malloc debug on a given process. |
| * |
| * @param processName the name of the process to run with libc malloc debug |
| * @param device the device to use |
| * @return true if enabling malloc debug succeeded |
| */ |
| public static boolean enableLibcMallocDebug(String processName, ITestDevice device) throws Exception { |
| device.executeShellCommand("setprop libc.debug.malloc.program " + processName); |
| device.executeShellCommand("setprop libc.debug.malloc.options \"backtrace guard\""); |
| /** |
| * The pidof command is being avoided because it does not exist on versions before M, and |
| * it behaves differently between M and N. |
| * Also considered was the ps -AoPID,CMDLINE command, but ps does not support options on |
| * versions before O. |
| * The [^]] prefix is being used for the grep command to avoid the case where the output of |
| * ps includes the grep command itself. |
| */ |
| String cmdOut = device.executeShellCommand("ps -A | grep '[^]]" + processName + "'"); |
| /** |
| * .hasNextInt() checks if the next token can be parsed as an integer, not if any remaining |
| * token is an integer. |
| * Example command: $ ps | fgrep mediaserver |
| * Out: media 269 1 77016 24416 binder_thr 00f35142ec S /system/bin/mediaserver |
| * The second field of the output is the PID, which is needed to restart the process. |
| */ |
| Scanner s = new Scanner(cmdOut).useDelimiter("\\D+"); |
| if(!s.hasNextInt()) { |
| CLog.w("Could not find pid for process: " + processName); |
| return false; |
| } |
| |
| String result = device.executeShellCommand("kill -9 " + s.nextInt()); |
| if(!result.equals("")) { |
| CLog.w("Could not restart process: " + processName); |
| return false; |
| } |
| |
| TimeUnit.SECONDS.sleep(1); |
| return true; |
| } |
| |
| /** |
| * Pushes and installs an apk to the selected device |
| * |
| * @param pathToApk a string path to apk from the /res folder |
| * @param device device to be ran on |
| * @return the output from attempting to install the apk |
| */ |
| public static String installApk(String pathToApk, ITestDevice device) throws Exception { |
| |
| String fullResourceName = pathToApk; |
| File apkFile = File.createTempFile("apkFile", ".apk"); |
| try { |
| apkFile = extractResource(fullResourceName, apkFile); |
| return device.installPackage(apkFile, true); |
| } finally { |
| apkFile.delete(); |
| } |
| } |
| |
| /** |
| * Extracts a resource and pushes it to the device |
| * |
| * @param fullResourceName a string path to resource from the res folder |
| * @param deviceFilePath the remote destination absolute file path |
| * @param device device to be ran on |
| */ |
| public static void pushResource(String fullResourceName, String deviceFilePath, |
| ITestDevice device) throws Exception { |
| File resFile = File.createTempFile("CTSResource", ""); |
| try { |
| resFile = extractResource(fullResourceName, resFile); |
| device.pushFile(resFile, deviceFilePath); |
| } finally { |
| resFile.delete(); |
| } |
| } |
| |
| /** |
| * Extracts the binary data from a resource and writes it to a temp file |
| */ |
| private static File extractResource(String fullResourceName, File file) throws Exception { |
| try (InputStream in = AdbUtils.class.getResourceAsStream(fullResourceName); |
| OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) { |
| if (in == null) { |
| throw new IllegalArgumentException("Resource not found: " + fullResourceName); |
| } |
| byte[] buf = new byte[65536]; |
| int chunkSize; |
| while ((chunkSize = in.read(buf)) != -1) { |
| out.write(buf, 0, chunkSize); |
| } |
| return file; |
| } |
| |
| } |
| /** |
| * Utility function to help check the exit code of a shell command |
| */ |
| public static int runCommandGetExitCode(String cmd, ITestDevice device) throws Exception { |
| return Integer.parseInt( |
| AdbUtils.runCommandLine( "(" + cmd + ") > /dev/null 2>&1; echo $?", |
| device).replaceAll("[^0-9]", "")); |
| } |
| |
| /** |
| * Pushes and runs a binary to the selected device and checks exit code |
| * Return code 113 is used to indicate the vulnerability |
| * |
| * @param pocName a string path to poc from the /res folder |
| * @param device device to be ran on |
| * @param timeout time to wait for output in seconds |
| */ |
| @Deprecated |
| public static boolean runPocCheckExitCode(String pocName, ITestDevice device, |
| int timeout) throws Exception { |
| |
| //Refer to go/asdl-sts-guide Test section for knowing the significance of 113 code |
| return runPocGetExitStatus(pocName, device, timeout) == 113; |
| } |
| |
| /** |
| * Pushes and runs a binary to the device and returns the exit status. |
| * @param pocName a string path to poc from the /res folder |
| * @param device device to be ran on |
| * @param timeout time to wait for output in seconds |
| |
| */ |
| public static int runPocGetExitStatus(String pocName, ITestDevice device, int timeout) |
| throws Exception { |
| device.executeShellCommand("chmod +x /data/local/tmp/" + pocName); |
| CollectingOutputReceiver receiver = new CollectingOutputReceiver(); |
| device.executeShellCommand("/data/local/tmp/" + pocName + " > /dev/null 2>&1; echo $?", |
| receiver, timeout, TimeUnit.SECONDS, 0); |
| |
| String exitStatus = receiver.getOutput().replaceAll("[^0-9]", ""); |
| return Integer.parseInt(exitStatus); |
| } |
| |
| /** |
| * Pushes and runs a binary and asserts that the exit status isn't 113: vulnerable. |
| * @param pocName a string path to poc from the /res folder |
| * @param device device to be ran on |
| * @param timeout time to wait for output in seconds |
| */ |
| public static void runPocAssertExitStatusNotVulnerable( |
| String pocName, ITestDevice device, int timeout) throws Exception { |
| assertTrue("PoC returned exit status 113: vulnerable", |
| runPocGetExitStatus(pocName, device, timeout) != 113); |
| } |
| |
| /** |
| * Executes a given poc within a given timeout. Returns error if the |
| * given poc doesnt complete its execution within timeout. It also deletes |
| * the list of files provided. |
| * |
| * @param runner the thread which will be run |
| * @param timeout the timeout within which the thread's execution should |
| * complete |
| * @param device device to be ran on |
| * @param inputFiles list of files to be deleted |
| */ |
| public static void runWithTimeoutDeleteFiles(Runnable runner, int timeout, ITestDevice device, |
| String[] inputFiles) throws Exception { |
| Thread t = new Thread(runner); |
| t.start(); |
| boolean test_failed = false; |
| try { |
| t.join(timeout); |
| } catch (InterruptedException e) { |
| test_failed = true; |
| } finally { |
| if (inputFiles != null) { |
| for (int i = 0; i < inputFiles.length; i++) { |
| AdbUtils.runCommandLine("rm /data/local/tmp/" + inputFiles[i], device); |
| } |
| } |
| if (test_failed == true) { |
| Assert.fail("PoC was interrupted"); |
| } |
| } |
| if (t.isAlive()) { |
| Assert.fail("PoC not completed within timeout of " + timeout + " ms"); |
| } |
| } |
| |
| /** |
| * Raises assert exception upon crash/error occurence |
| * |
| * @param crashPatternList array of crash log patterns to be checked for |
| * @param logcat String to be parsed |
| */ |
| public static void checkCrash(String crashPatternList[], String logcat) |
| throws Exception { |
| for (int i = 0; i < crashPatternList.length; i++) { |
| assertFalse("Crash log pattern found!", |
| Pattern.compile(crashPatternList[i], |
| Pattern.MULTILINE).matcher(logcat).find()); |
| } |
| } |
| } |