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", |
Madan Ankapura | a08f54a | 2013-07-03 16:13:38 -0700 | [diff] [blame] | 34 | "netd", |
Brian Muramatsu | f176eaf | 2010-07-27 14:27:01 -0700 | [diff] [blame] | 35 | "servicemanager", |
Madan Ankapura | a08f54a | 2013-07-03 16:13:38 -0700 | [diff] [blame] | 36 | "ueventd", |
Brian Muramatsu | f176eaf | 2010-07-27 14:27:01 -0700 | [diff] [blame] | 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 | } |