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