/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.tests.getinfo; import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.util.regex.Pattern; /** Crawls /proc to find processes that are running as root. */ class RootProcessScanner { /** Processes that are allowed to run as root. */ private static final Pattern ROOT_PROCESS_WHITELIST_PATTERN = getRootProcessWhitelistPattern( "debuggerd", "debuggerd64", "healthd", "init", "installd", "lmkd", "netd", "servicemanager", "ueventd", "vold", "watchdogd", "zygote" ); /** Combine the individual patterns into one super pattern. */ private static Pattern getRootProcessWhitelistPattern(String... patterns) { StringBuilder rootProcessPattern = new StringBuilder(); for (int i = 0; i < patterns.length; i++) { rootProcessPattern.append(patterns[i]); if (i + 1 < patterns.length) { rootProcessPattern.append('|'); } } return Pattern.compile(rootProcessPattern.toString()); } /** Test that there are no unapproved root processes running on the system. */ public static String[] getRootProcesses() throws FileNotFoundException, MalformedStatMException { List rootProcessDirs = getRootProcessDirs(); String[] rootProcessNames = new String[rootProcessDirs.size()]; for (int i = 0; i < rootProcessNames.length; i++) { rootProcessNames[i] = getProcessName(rootProcessDirs.get(i)); } return rootProcessNames; } private static List getRootProcessDirs() throws FileNotFoundException, MalformedStatMException { File proc = new File("/proc"); if (!proc.exists()) { throw new FileNotFoundException(proc + " is missing (man 5 proc)"); } List rootProcesses = new ArrayList(); File[] processDirs = proc.listFiles(); if (processDirs != null && processDirs.length > 0) { for (File processDir : processDirs) { if (isUnapprovedRootProcess(processDir)) { rootProcesses.add(processDir); } } } return rootProcesses; } /** * Filters out processes in /proc that are not approved. * @throws FileNotFoundException * @throws MalformedStatMException */ private static boolean isUnapprovedRootProcess(File pathname) throws FileNotFoundException, MalformedStatMException { return isPidDirectory(pathname) && !isKernelProcess(pathname) && isRootProcess(pathname); } private static boolean isPidDirectory(File pathname) { return pathname.isDirectory() && Pattern.matches("\\d+", pathname.getName()); } private static boolean isKernelProcess(File processDir) throws FileNotFoundException, MalformedStatMException { File statm = getProcessStatM(processDir); Scanner scanner = null; try { scanner = new Scanner(statm); boolean allZero = true; for (int i = 0; i < 7; i++) { if (scanner.nextInt() != 0) { allZero = false; } } if (scanner.hasNext()) { throw new MalformedStatMException(processDir + " statm expected to have 7 integers (man 5 proc)"); } return allZero; } finally { if (scanner != null) { scanner.close(); } } } private static File getProcessStatM(File processDir) { return new File(processDir, "statm"); } public static class MalformedStatMException extends Exception { MalformedStatMException(String detailMessage) { super(detailMessage); } } /** * Return whether or not this process is running as root without being approved. * * @param processDir with the status file * @return whether or not it is a unwhitelisted root process * @throws FileNotFoundException */ private static boolean isRootProcess(File processDir) throws FileNotFoundException { File status = getProcessStatus(processDir); Scanner scanner = null; try { scanner = new Scanner(status); findToken(scanner, "Name:"); String name = scanner.next(); findToken(scanner, "Uid:"); boolean rootUid = hasRootId(scanner); findToken(scanner, "Gid:"); boolean rootGid = hasRootId(scanner); return !ROOT_PROCESS_WHITELIST_PATTERN.matcher(name).matches() && (rootUid || rootGid); } finally { if (scanner != null) { scanner.close(); } } } /** * Get the status {@link File} that has name:value pairs. *
     * Name:   init
     * ...
     * Uid:    0       0       0       0
     * Gid:    0       0       0       0
     * 
*/ private static File getProcessStatus(File processDir) { return new File(processDir, "status"); } /** * Convenience method to move the scanner's position to the point after the given token. * * @param scanner to call next() until the token is found * @param token to find like "Name:" */ private static void findToken(Scanner scanner, String token) { while (true) { String next = scanner.next(); if (next.equals(token)) { return; } } // Scanner will exhaust input and throw an exception before getting here. } /** * Uid and Gid lines have four values: "Uid: 0 0 0 0" * * @param scanner that has just processed the "Uid:" or "Gid:" token * @return whether or not any of the ids are root */ private static boolean hasRootId(Scanner scanner) { int realUid = scanner.nextInt(); int effectiveUid = scanner.nextInt(); int savedSetUid = scanner.nextInt(); int fileSystemUid = scanner.nextInt(); return realUid == 0 || effectiveUid == 0 || savedSetUid == 0 || fileSystemUid == 0; } /** Returns the name of the process corresponding to its process directory in /proc. */ private static String getProcessName(File processDir) throws FileNotFoundException { File status = getProcessStatus(processDir); Scanner scanner = new Scanner(status); try { findToken(scanner, "Name:"); return scanner.next(); } finally { scanner.close(); } } }