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