1 /* 2 * Copyright (C) 2010 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 com.android.compatibility.common.util; 18 19 import com.android.tradefed.device.DeviceNotAvailableException; 20 import com.android.tradefed.device.ITestDevice; 21 import com.android.tradefed.log.LogUtil.CLog; 22 23 import java.util.HashSet; 24 import java.util.InputMismatchException; 25 import java.util.Scanner; 26 import java.util.Set; 27 import java.util.regex.Pattern; 28 29 /** Crawls /proc to find processes that are running as root. */ 30 public class RootProcessScanner { 31 32 private Set<String> mPidDirs; 33 private ITestDevice mDevice; 34 RootProcessScanner(ITestDevice device)35 public RootProcessScanner(ITestDevice device) throws DeviceNotAvailableException { 36 mDevice = device; 37 mPidDirs = new HashSet<>(); 38 String lsOutput = device.executeShellCommand("ls -F /proc | grep /$"); // directories only 39 String[] lines = lsOutput.split("/?\r?\n"); // split by line, shave "/" suffix if present 40 for (String line : lines) { 41 if (Pattern.matches("\\d+", line)) { 42 mPidDirs.add(String.format("/proc/%s", line)); 43 } 44 } 45 } 46 47 /** Processes that are allowed to run as root. */ 48 private static final Pattern ROOT_PROCESS_WHITELIST_PATTERN = getRootProcessWhitelistPattern( 49 "debuggerd", 50 "debuggerd64", 51 "healthd", 52 "init", 53 "installd", 54 "lmkd", 55 "netd", 56 "servicemanager", 57 "ueventd", 58 "vold", 59 "watchdogd", 60 "zygote" 61 ); 62 63 /** Combine the individual patterns into one super pattern. */ getRootProcessWhitelistPattern(String... patterns)64 private static Pattern getRootProcessWhitelistPattern(String... patterns) { 65 StringBuilder rootProcessPattern = new StringBuilder(); 66 for (int i = 0; i < patterns.length; i++) { 67 rootProcessPattern.append(patterns[i]); 68 if (i + 1 < patterns.length) { 69 rootProcessPattern.append('|'); 70 } 71 } 72 return Pattern.compile(rootProcessPattern.toString()); 73 } 74 75 /** 76 * Get the names of approved or unapproved root processes running on the system. 77 * @param approved whether to retrieve approved (true) or unapproved (false) processes 78 * @return names of approved or unapproved root processes running on the system 79 */ getRootProcesses(boolean approved)80 public Set<String> getRootProcesses(boolean approved) 81 throws DeviceNotAvailableException, MalformedStatMException { 82 Set<String> rootProcessDirs = getRootProcessDirs(approved); 83 Set<String> rootProcessNames = new HashSet<>(); 84 for (String dir : rootProcessDirs) { 85 rootProcessNames.add(getProcessName(dir)); 86 } 87 return rootProcessNames; 88 } 89 getRootProcessDirs(boolean approved)90 private Set<String> getRootProcessDirs(boolean approved) 91 throws DeviceNotAvailableException, MalformedStatMException { 92 Set<String> rootProcesses = new HashSet<>(); 93 if (mPidDirs != null && mPidDirs.size() > 0) { 94 for (String processDir : mPidDirs) { 95 if (isRootProcessDir(processDir, approved)) { 96 rootProcesses.add(processDir); 97 } 98 } 99 } else { 100 CLog.e("RootProcessScanner Failed to collect PID directories."); 101 } 102 return rootProcesses; 103 } 104 105 /** 106 * Returns processes in /proc that are running as root with a certain approval status. 107 * @throws FileNotFoundException 108 * @throws MalformedStatMException 109 */ isRootProcessDir(String pathname, boolean approved)110 private boolean isRootProcessDir(String pathname, boolean approved) 111 throws DeviceNotAvailableException, MalformedStatMException { 112 try { 113 return !isKernelProcess(pathname) 114 && isRootProcess(pathname) 115 && (isApproved(pathname) == approved); 116 } catch (InputMismatchException e) { 117 CLog.d("Path %s determined not to be a root process directory", pathname); 118 return false; 119 } 120 } 121 isKernelProcess(String processDir)122 private boolean isKernelProcess(String processDir) 123 throws DeviceNotAvailableException, MalformedStatMException { 124 String statm = getProcessStatM(processDir); 125 try (Scanner scanner = new Scanner(statm)) { 126 boolean allZero = true; 127 for (int i = 0; i < 7; i++) { 128 if (scanner.nextInt() != 0) { 129 allZero = false; 130 } 131 } 132 133 if (scanner.hasNext()) { 134 throw new MalformedStatMException(processDir 135 + " statm expected to have 7 integers (man 5 proc)"); 136 } 137 138 return allZero; 139 } 140 } 141 getProcessStatM(String processDir)142 private String getProcessStatM(String processDir) throws DeviceNotAvailableException { 143 return mDevice.executeShellCommand(String.format("cat %s/statm", processDir)); 144 } 145 146 public static class MalformedStatMException extends Exception { MalformedStatMException(String detailMessage)147 MalformedStatMException(String detailMessage) { 148 super(detailMessage); 149 } 150 } 151 152 /** 153 * Return whether or not this process is running as root. 154 * 155 * @param processDir with the status file 156 * @return whether or not it is a root process 157 */ isRootProcess(String processDir)158 private boolean isRootProcess(String processDir) throws DeviceNotAvailableException { 159 String status = getProcessStatus(processDir); 160 try (Scanner scanner = new Scanner(status)) { 161 findToken(scanner, "Uid:"); 162 boolean rootUid = hasRootId(scanner); 163 findToken(scanner, "Gid:"); 164 boolean rootGid = hasRootId(scanner); 165 return rootUid || rootGid; 166 } 167 } 168 169 /** 170 * Return whether or not this process is approved to run as root. 171 * 172 * @param processDir with the status file 173 * @return whether or not it is a root-whitelisted process 174 * @throws FileNotFoundException 175 */ isApproved(String processDir)176 private boolean isApproved(String processDir) throws DeviceNotAvailableException { 177 String status = getProcessStatus(processDir); 178 try (Scanner scanner = new Scanner(status)) { 179 findToken(scanner, "Name:"); 180 String name = scanner.next(); 181 return ROOT_PROCESS_WHITELIST_PATTERN.matcher(name).matches(); 182 } 183 } 184 185 /** 186 * Get the status File path that has name:value pairs. 187 * <pre> 188 * Name: init 189 * ... 190 * Uid: 0 0 0 0 191 * Gid: 0 0 0 0 192 * </pre> 193 */ getProcessStatus(String processDir)194 private String getProcessStatus(String processDir) throws DeviceNotAvailableException { 195 return mDevice.executeShellCommand(String.format("cat %s/status", processDir)); 196 } 197 198 /** 199 * Convenience method to move the scanner's position to the point after the given token. 200 * 201 * @param scanner to call next() until the token is found 202 * @param token to find like "Name:" 203 */ findToken(Scanner scanner, String token)204 private static void findToken(Scanner scanner, String token) { 205 while (true) { 206 String next = scanner.next(); 207 if (next.equals(token)) { 208 return; 209 } 210 } 211 212 // Scanner will exhaust input and throw an exception before getting here. 213 } 214 215 /** 216 * Uid and Gid lines have four values: "Uid: 0 0 0 0" 217 * 218 * @param scanner that has just processed the "Uid:" or "Gid:" token 219 * @return whether or not any of the ids are root 220 */ hasRootId(Scanner scanner)221 private static boolean hasRootId(Scanner scanner) { 222 int realUid = scanner.nextInt(); 223 int effectiveUid = scanner.nextInt(); 224 int savedSetUid = scanner.nextInt(); 225 int fileSystemUid = scanner.nextInt(); 226 return realUid == 0 || effectiveUid == 0 || savedSetUid == 0 || fileSystemUid == 0; 227 } 228 229 /** Returns the name of the process corresponding to its process directory in /proc. */ getProcessName(String processDir)230 private String getProcessName(String processDir) throws DeviceNotAvailableException { 231 String status = getProcessStatus(processDir); 232 try (Scanner scanner = new Scanner(status)) { 233 findToken(scanner, "Name:"); 234 return scanner.next(); 235 } 236 } 237 } 238