| /* | |
| * 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_ALLOWLIST_PATTERN = getRootProcessAllowlistPattern( | |
| "debuggerd", | |
| "debuggerd64", | |
| "healthd", | |
| "init", | |
| "installd", | |
| "lmkd", | |
| "netd", | |
| "servicemanager", | |
| "ueventd", | |
| "vold", | |
| "watchdogd", | |
| "zygote" | |
| ); | |
| /** Combine the individual patterns into one super pattern. */ | |
| private static Pattern getRootProcessAllowlistPattern(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<File> 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<File> getRootProcessDirs() | |
| throws FileNotFoundException, MalformedStatMException { | |
| File proc = new File("/proc"); | |
| if (!proc.exists()) { | |
| throw new FileNotFoundException(proc + " is missing (man 5 proc)"); | |
| } | |
| List<File> rootProcesses = new ArrayList<File>(); | |
| 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 unallowlisted 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_ALLOWLIST_PATTERN.matcher(name).matches() | |
| && (rootUid || rootGid); | |
| } finally { | |
| if (scanner != null) { | |
| scanner.close(); | |
| } | |
| } | |
| } | |
| /** | |
| * Get the status {@link File} that has name:value pairs. | |
| * <pre> | |
| * Name: init | |
| * ... | |
| * Uid: 0 0 0 0 | |
| * Gid: 0 0 0 0 | |
| * </pre> | |
| */ | |
| 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(); | |
| } | |
| } | |
| } |