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