Home | History | Annotate | Download | only in getinfo
      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. */
     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. */
     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 
     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      */
     92     private static boolean isUnapprovedRootProcess(File pathname)
     93             throws FileNotFoundException, MalformedStatMException {
     94         return isPidDirectory(pathname)
     95                 && !isKernelProcess(pathname)
     96                 && isRootProcess(pathname);
     97     }
     98 
     99     private static boolean isPidDirectory(File pathname) {
    100         return pathname.isDirectory() && Pattern.matches("\\d+", pathname.getName());
    101     }
    102 
    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 
    130     private static File getProcessStatM(File processDir) {
    131         return new File(processDir, "statm");
    132     }
    133 
    134     public static class MalformedStatMException extends Exception {
    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      */
    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      */
    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      */
    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      */
    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. */
    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