• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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             "netd",
35             "servicemanager",
36             "ueventd",
37             "vold",
38             "zygote"
39     );
40 
41     /** Combine the individual patterns into one super pattern. */
getRootProcessWhitelistPattern(String... patterns)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. */
getRootProcesses()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 
getRootProcessDirs()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      */
isUnapprovedRootProcess(File pathname)88     private static boolean isUnapprovedRootProcess(File pathname)
89             throws FileNotFoundException, MalformedStatMException {
90         return isPidDirectory(pathname)
91                 && !isKernelProcess(pathname)
92                 && isRootProcess(pathname);
93     }
94 
isPidDirectory(File pathname)95     private static boolean isPidDirectory(File pathname) {
96         return pathname.isDirectory() && Pattern.matches("\\d+", pathname.getName());
97     }
98 
isKernelProcess(File processDir)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 
getProcessStatM(File processDir)126     private static File getProcessStatM(File processDir) {
127         return new File(processDir, "statm");
128     }
129 
130     public static class MalformedStatMException extends Exception {
MalformedStatMException(String detailMessage)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      */
isRootProcess(File processDir)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             findToken(scanner, "Name:");
150             String name = scanner.next();
151 
152             findToken(scanner, "Uid:");
153             boolean rootUid = hasRootId(scanner);
154 
155             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      */
getProcessStatus(File processDir)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      */
findToken(Scanner scanner, String token)186     private static void findToken(Scanner scanner, String token) {
187         while (true) {
188             String next = scanner.next();
189             if (next.equals(token)) {
190                 return;
191             }
192         }
193 
194         // Scanner will exhaust input and throw an exception before getting here.
195     }
196 
197     /**
198      * Uid and Gid lines have four values: "Uid:    0       0       0       0"
199      *
200      * @param scanner that has just processed the "Uid:" or "Gid:" token
201      * @return whether or not any of the ids are root
202      */
hasRootId(Scanner scanner)203     private static boolean hasRootId(Scanner scanner) {
204         int realUid = scanner.nextInt();
205         int effectiveUid = scanner.nextInt();
206         int savedSetUid = scanner.nextInt();
207         int fileSystemUid = scanner.nextInt();
208         return realUid == 0 || effectiveUid == 0 || savedSetUid == 0 || fileSystemUid == 0;
209     }
210 
211     /** Returns the name of the process corresponding to its process directory in /proc. */
getProcessName(File processDir)212     private static String getProcessName(File processDir) throws FileNotFoundException {
213         File status = getProcessStatus(processDir);
214         Scanner scanner = new Scanner(status);
215         try {
216             findToken(scanner, "Name:");
217             return scanner.next();
218         } finally {
219             scanner.close();
220         }
221     }
222 }
223