Brian Muramatsu | f176eaf | 2010-07-27 14:27:01 -0700 | [diff] [blame] | 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 | } |