blob: fefff99f0ca4776c14a84f4b5d4b532fc39f508b [file] [log] [blame]
Brian Muramatsuf176eaf2010-07-27 14:27:01 -07001/*
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
17package android.tests.getinfo;
18
19import java.io.File;
20import java.io.FileNotFoundException;
21import java.util.ArrayList;
22import java.util.List;
23import java.util.Scanner;
24import java.util.regex.Pattern;
25
26/** Crawls /proc to find processes that are running as root. */
27class 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 Ankapuraa08f54a2013-07-03 16:13:38 -070034 "netd",
Brian Muramatsuf176eaf2010-07-27 14:27:01 -070035 "servicemanager",
Madan Ankapuraa08f54a2013-07-03 16:13:38 -070036 "ueventd",
Brian Muramatsuf176eaf2010-07-27 14:27:01 -070037 "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}