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