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