Home | History | Annotate | Download | only in util
      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 com.android.compatibility.common.util;
     18 
     19 import com.android.tradefed.device.DeviceNotAvailableException;
     20 import com.android.tradefed.device.ITestDevice;
     21 import com.android.tradefed.log.LogUtil.CLog;
     22 
     23 import java.util.HashSet;
     24 import java.util.InputMismatchException;
     25 import java.util.Scanner;
     26 import java.util.Set;
     27 import java.util.regex.Pattern;
     28 
     29 /** Crawls /proc to find processes that are running as root. */
     30 public class RootProcessScanner {
     31 
     32     private Set<String> mPidDirs;
     33     private ITestDevice mDevice;
     34 
     35     public RootProcessScanner(ITestDevice device) throws DeviceNotAvailableException {
     36         mDevice = device;
     37         mPidDirs = new HashSet<>();
     38         String lsOutput = device.executeShellCommand("ls -F /proc | grep /$"); // directories only
     39         String[] lines = lsOutput.split("/?\r?\n"); // split by line, shave "/" suffix if present
     40         for (String line : lines) {
     41             if (Pattern.matches("\\d+", line)) {
     42                 mPidDirs.add(String.format("/proc/%s", line));
     43             }
     44         }
     45     }
     46 
     47     /** Processes that are allowed to run as root. */
     48     private static final Pattern ROOT_PROCESS_WHITELIST_PATTERN = getRootProcessWhitelistPattern(
     49             "debuggerd",
     50             "debuggerd64",
     51             "healthd",
     52             "init",
     53             "installd",
     54             "lmkd",
     55             "netd",
     56             "servicemanager",
     57             "ueventd",
     58             "vold",
     59             "watchdogd",
     60             "zygote"
     61     );
     62 
     63     /** Combine the individual patterns into one super pattern. */
     64     private static Pattern getRootProcessWhitelistPattern(String... patterns) {
     65         StringBuilder rootProcessPattern = new StringBuilder();
     66         for (int i = 0; i < patterns.length; i++) {
     67             rootProcessPattern.append(patterns[i]);
     68             if (i + 1 < patterns.length) {
     69                 rootProcessPattern.append('|');
     70             }
     71         }
     72         return Pattern.compile(rootProcessPattern.toString());
     73     }
     74 
     75     /**
     76      * Get the names of approved or unapproved root processes running on the system.
     77      * @param approved whether to retrieve approved (true) or unapproved (false) processes
     78      * @return names of approved or unapproved root processes running on the system
     79      */
     80     public Set<String> getRootProcesses(boolean approved)
     81             throws DeviceNotAvailableException, MalformedStatMException {
     82         Set<String> rootProcessDirs = getRootProcessDirs(approved);
     83         Set<String> rootProcessNames = new HashSet<>();
     84         for (String dir : rootProcessDirs) {
     85             rootProcessNames.add(getProcessName(dir));
     86         }
     87         return rootProcessNames;
     88     }
     89 
     90     private Set<String> getRootProcessDirs(boolean approved)
     91             throws DeviceNotAvailableException, MalformedStatMException {
     92         Set<String> rootProcesses = new HashSet<>();
     93         if (mPidDirs != null && mPidDirs.size() > 0) {
     94             for (String processDir : mPidDirs) {
     95                 if (isRootProcessDir(processDir, approved)) {
     96                     rootProcesses.add(processDir);
     97                 }
     98             }
     99         } else {
    100             CLog.e("RootProcessScanner Failed to collect PID directories.");
    101         }
    102         return rootProcesses;
    103     }
    104 
    105     /**
    106      * Returns processes in /proc that are running as root with a certain approval status.
    107      * @throws FileNotFoundException
    108      * @throws MalformedStatMException
    109      */
    110     private boolean isRootProcessDir(String pathname, boolean approved)
    111             throws DeviceNotAvailableException, MalformedStatMException {
    112         try {
    113             return !isKernelProcess(pathname)
    114                     && isRootProcess(pathname)
    115                     && (isApproved(pathname) == approved);
    116         } catch (InputMismatchException e) {
    117             CLog.d("Path %s determined not to be a root process directory", pathname);
    118             return false;
    119         }
    120     }
    121 
    122     private boolean isKernelProcess(String processDir)
    123             throws DeviceNotAvailableException, MalformedStatMException {
    124         String statm = getProcessStatM(processDir);
    125         try (Scanner scanner = new Scanner(statm)) {
    126             boolean allZero = true;
    127             for (int i = 0; i < 7; i++) {
    128                 if (scanner.nextInt() != 0) {
    129                     allZero = false;
    130                 }
    131             }
    132 
    133             if (scanner.hasNext()) {
    134                 throw new MalformedStatMException(processDir
    135                         + " statm expected to have 7 integers (man 5 proc)");
    136             }
    137 
    138             return allZero;
    139         }
    140     }
    141 
    142     private String getProcessStatM(String processDir) throws DeviceNotAvailableException {
    143         return mDevice.executeShellCommand(String.format("cat %s/statm", processDir));
    144     }
    145 
    146     public static class MalformedStatMException extends Exception {
    147         MalformedStatMException(String detailMessage) {
    148             super(detailMessage);
    149         }
    150     }
    151 
    152     /**
    153      * Return whether or not this process is running as root.
    154      *
    155      * @param processDir with the status file
    156      * @return whether or not it is a root process
    157      */
    158     private boolean isRootProcess(String processDir) throws DeviceNotAvailableException {
    159         String status = getProcessStatus(processDir);
    160         try (Scanner scanner = new Scanner(status)) {
    161             findToken(scanner, "Uid:");
    162             boolean rootUid = hasRootId(scanner);
    163             findToken(scanner, "Gid:");
    164             boolean rootGid = hasRootId(scanner);
    165             return rootUid || rootGid;
    166         }
    167     }
    168 
    169     /**
    170      * Return whether or not this process is approved to run as root.
    171      *
    172      * @param processDir with the status file
    173      * @return whether or not it is a root-whitelisted process
    174      * @throws FileNotFoundException
    175      */
    176     private boolean isApproved(String processDir) throws DeviceNotAvailableException {
    177         String status = getProcessStatus(processDir);
    178         try (Scanner scanner = new Scanner(status)) {
    179             findToken(scanner, "Name:");
    180             String name = scanner.next();
    181             return ROOT_PROCESS_WHITELIST_PATTERN.matcher(name).matches();
    182         }
    183     }
    184 
    185     /**
    186      * Get the status File path that has name:value pairs.
    187      * <pre>
    188      * Name:   init
    189      * ...
    190      * Uid:    0       0       0       0
    191      * Gid:    0       0       0       0
    192      * </pre>
    193      */
    194     private String getProcessStatus(String processDir) throws DeviceNotAvailableException {
    195         return mDevice.executeShellCommand(String.format("cat %s/status", processDir));
    196     }
    197 
    198     /**
    199      * Convenience method to move the scanner's position to the point after the given token.
    200      *
    201      * @param scanner to call next() until the token is found
    202      * @param token to find like "Name:"
    203      */
    204     private static void findToken(Scanner scanner, String token) {
    205         while (true) {
    206             String next = scanner.next();
    207             if (next.equals(token)) {
    208                 return;
    209             }
    210         }
    211 
    212         // Scanner will exhaust input and throw an exception before getting here.
    213     }
    214 
    215     /**
    216      * Uid and Gid lines have four values: "Uid:    0       0       0       0"
    217      *
    218      * @param scanner that has just processed the "Uid:" or "Gid:" token
    219      * @return whether or not any of the ids are root
    220      */
    221     private static boolean hasRootId(Scanner scanner) {
    222         int realUid = scanner.nextInt();
    223         int effectiveUid = scanner.nextInt();
    224         int savedSetUid = scanner.nextInt();
    225         int fileSystemUid = scanner.nextInt();
    226         return realUid == 0 || effectiveUid == 0 || savedSetUid == 0 || fileSystemUid == 0;
    227     }
    228 
    229     /** Returns the name of the process corresponding to its process directory in /proc. */
    230     private String getProcessName(String processDir) throws DeviceNotAvailableException {
    231         String status = getProcessStatus(processDir);
    232         try (Scanner scanner = new Scanner(status)) {
    233             findToken(scanner, "Name:");
    234             return scanner.next();
    235         }
    236     }
    237 }
    238