• 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             "servicemanager",
35             "vold",
36             "zygote"
37     );
38 
39     /** Combine the individual patterns into one super pattern. */
getRootProcessWhitelistPattern(String... patterns)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. */
getRootProcesses()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 
getRootProcessDirs()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      */
isUnapprovedRootProcess(File pathname)86     private static boolean isUnapprovedRootProcess(File pathname)
87             throws FileNotFoundException, MalformedStatMException {
88         return isPidDirectory(pathname)
89                 && !isKernelProcess(pathname)
90                 && isRootProcess(pathname);
91     }
92 
isPidDirectory(File pathname)93     private static boolean isPidDirectory(File pathname) {
94         return pathname.isDirectory() && Pattern.matches("\\d+", pathname.getName());
95     }
96 
isKernelProcess(File processDir)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 
getProcessStatM(File processDir)124     private static File getProcessStatM(File processDir) {
125         return new File(processDir, "statm");
126     }
127 
128     public static class MalformedStatMException extends Exception {
MalformedStatMException(String detailMessage)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      */
isRootProcess(File processDir)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      */
getProcessStatus(File processDir)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      */
findToken(Scanner scanner, String token)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      */
hasRootId(Scanner scanner)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. */
getProcessName(File processDir)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 }
222