| /* |
| * Copyright (C) 2014 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 com.android.internal.os; |
| |
| import static android.system.OsConstants.O_CLOEXEC; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.compat.annotation.ChangeId; |
| import android.compat.annotation.Disabled; |
| import android.compat.annotation.EnabledAfter; |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.ProcessInfo; |
| import android.net.Credentials; |
| import android.net.LocalServerSocket; |
| import android.net.LocalSocket; |
| import android.os.Build; |
| import android.os.FactoryTest; |
| import android.os.IVold; |
| import android.os.Process; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.SystemProperties; |
| import android.os.Trace; |
| import android.provider.DeviceConfig; |
| import android.system.ErrnoException; |
| import android.system.Os; |
| import android.util.Log; |
| |
| import com.android.internal.compat.IPlatformCompat; |
| import com.android.internal.net.NetworkUtilsInternal; |
| |
| import dalvik.annotation.optimization.CriticalNative; |
| import dalvik.annotation.optimization.FastNative; |
| import dalvik.system.ZygoteHooks; |
| |
| import libcore.io.IoUtils; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.DataOutputStream; |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| |
| /** @hide */ |
| public final class Zygote { |
| /* |
| * Bit values for "runtimeFlags" argument. The definitions are duplicated |
| * in the native code. |
| */ |
| |
| /** enable debugging over JDWP */ |
| public static final int DEBUG_ENABLE_JDWP = 1; |
| /** enable JNI checks */ |
| public static final int DEBUG_ENABLE_CHECKJNI = 1 << 1; |
| /** enable Java programming language "assert" statements */ |
| public static final int DEBUG_ENABLE_ASSERT = 1 << 2; |
| /** disable the AOT compiler and JIT */ |
| public static final int DEBUG_ENABLE_SAFEMODE = 1 << 3; |
| /** Enable logging of third-party JNI activity. */ |
| public static final int DEBUG_ENABLE_JNI_LOGGING = 1 << 4; |
| /** Force generation of native debugging information. */ |
| public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 5; |
| /** Always use JIT-ed code. */ |
| public static final int DEBUG_ALWAYS_JIT = 1 << 6; |
| /** Make the code native debuggable by turning off some optimizations. */ |
| public static final int DEBUG_NATIVE_DEBUGGABLE = 1 << 7; |
| /** Make the code Java debuggable by turning off some optimizations. */ |
| public static final int DEBUG_JAVA_DEBUGGABLE = 1 << 8; |
| |
| /** Turn off the verifier. */ |
| public static final int DISABLE_VERIFIER = 1 << 9; |
| /** Only use oat files located in /system. Otherwise use dex/jar/apk . */ |
| public static final int ONLY_USE_SYSTEM_OAT_FILES = 1 << 10; |
| /** Force generation of native debugging information for backtraces. */ |
| public static final int DEBUG_GENERATE_MINI_DEBUG_INFO = 1 << 11; |
| /** |
| * Hidden API access restrictions. This is a mask for bits representing the API enforcement |
| * policy, defined by {@code @ApplicationInfo.HiddenApiEnforcementPolicy}. |
| */ |
| public static final int API_ENFORCEMENT_POLICY_MASK = (1 << 12) | (1 << 13); |
| /** |
| * Bit shift for use with {@link #API_ENFORCEMENT_POLICY_MASK}. |
| * |
| * (flags & API_ENFORCEMENT_POLICY_MASK) >> API_ENFORCEMENT_POLICY_SHIFT gives |
| * {@link ApplicationInfo.HiddenApiEnforcementPolicy} values. |
| */ |
| public static final int API_ENFORCEMENT_POLICY_SHIFT = |
| Integer.numberOfTrailingZeros(API_ENFORCEMENT_POLICY_MASK); |
| /** |
| * Enable system server ART profiling. |
| */ |
| public static final int PROFILE_SYSTEM_SERVER = 1 << 14; |
| |
| /** |
| * Enable profiling from shell. |
| */ |
| public static final int PROFILE_FROM_SHELL = 1 << 15; |
| |
| /* |
| * Enable using the ART app image startup cache |
| */ |
| public static final int USE_APP_IMAGE_STARTUP_CACHE = 1 << 16; |
| |
| /** |
| * When set, application specified signal handlers are not chained (i.e, ignored) |
| * by the runtime. |
| * |
| * Used for debugging only. Usage: set debug.ignoreappsignalhandler to 1. |
| */ |
| public static final int DEBUG_IGNORE_APP_SIGNAL_HANDLER = 1 << 17; |
| |
| /** |
| * Disable runtime access to {@link android.annotation.TestApi} annotated members. |
| * |
| * <p>This only takes effect if Hidden API access restrictions are enabled as well. |
| */ |
| public static final int DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18; |
| |
| public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20); |
| |
| public static final int MEMORY_TAG_LEVEL_NONE = 0; |
| |
| /** |
| * Enable pointer tagging in this process. |
| * Tags are checked during memory deallocation, but not on access. |
| * TBI stands for Top-Byte-Ignore, an ARM CPU feature. |
| * {@link https://developer.arm.com/docs/den0024/latest/the-memory-management-unit/translation-table-configuration/virtual-address-tagging} |
| */ |
| public static final int MEMORY_TAG_LEVEL_TBI = 1 << 19; |
| |
| /** |
| * Enable asynchronous memory tag checks in this process. |
| */ |
| public static final int MEMORY_TAG_LEVEL_ASYNC = 2 << 19; |
| |
| /** |
| * Enable synchronous memory tag checks in this process. |
| */ |
| public static final int MEMORY_TAG_LEVEL_SYNC = 3 << 19; |
| |
| /** |
| * A two-bit field for GWP-ASan level of this process. See the possible values below. |
| */ |
| public static final int GWP_ASAN_LEVEL_MASK = (1 << 21) | (1 << 22); |
| |
| /** |
| * Disable GWP-ASan in this process. |
| * GWP-ASan is a low-overhead memory bug detector using guard pages on a small |
| * subset of heap allocations. |
| */ |
| public static final int GWP_ASAN_LEVEL_NEVER = 0 << 21; |
| |
| /** |
| * Enable GWP-ASan in this process with a small sampling rate. |
| * With approx. 1% chance GWP-ASan will be activated and apply its protection |
| * to a small subset of heap allocations. |
| * Otherwise (~99% chance) this process is unaffected. |
| */ |
| public static final int GWP_ASAN_LEVEL_LOTTERY = 1 << 21; |
| |
| /** |
| * Always enable GWP-ASan in this process. |
| * GWP-ASan is activated unconditionally (but still, only a small subset of |
| * allocations is protected). |
| */ |
| public static final int GWP_ASAN_LEVEL_ALWAYS = 2 << 21; |
| |
| /** |
| * GWP-ASan's `gwpAsanMode` manifest flag was unspecified. Currently, this |
| * means GWP_ASAN_LEVEL_LOTTERY for system apps, and GWP_ASAN_LEVEL_NONE for |
| * non-system apps. |
| */ |
| public static final int GWP_ASAN_LEVEL_DEFAULT = 3 << 21; |
| |
| /** Enable automatic zero-initialization of native heap memory allocations. */ |
| public static final int NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23; |
| |
| /** |
| * Enable profiling from system services. This loads profiling related plugins in ART. |
| */ |
| public static final int PROFILEABLE = 1 << 24; |
| |
| /** |
| * Enable ptrace. This is enabled on eng, if the app is debuggable, or if |
| * the persist.debug.ptrace.enabled property is set. |
| */ |
| public static final int DEBUG_ENABLE_PTRACE = 1 << 25; |
| |
| /** No external storage should be mounted. */ |
| public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE; |
| /** Default external storage should be mounted. */ |
| public static final int MOUNT_EXTERNAL_DEFAULT = IVold.REMOUNT_MODE_DEFAULT; |
| /** |
| * Mount mode for package installers which should give them access to |
| * all obb dirs in addition to their package sandboxes |
| */ |
| public static final int MOUNT_EXTERNAL_INSTALLER = IVold.REMOUNT_MODE_INSTALLER; |
| /** The lower file system should be bind mounted directly on external storage */ |
| public static final int MOUNT_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH; |
| |
| /** Use the regular scoped storage filesystem, but Android/ should be writable. |
| * Used to support the applications hosting DownloadManager and the MTP server. |
| */ |
| public static final int MOUNT_EXTERNAL_ANDROID_WRITABLE = IVold.REMOUNT_MODE_ANDROID_WRITABLE; |
| |
| /** Number of bytes sent to the Zygote over USAP pipes or the pool event FD */ |
| static final int USAP_MANAGEMENT_MESSAGE_BYTES = 8; |
| |
| /** Make the new process have top application priority. */ |
| public static final String START_AS_TOP_APP_ARG = "--is-top-app"; |
| |
| /** List of packages with the same uid, and its app data info: volume uuid and inode. */ |
| public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map"; |
| |
| /** List of allowlisted packages and its app data info: volume uuid and inode. */ |
| public static final String ALLOWLISTED_DATA_INFO_MAP = "--allowlisted-data-info-map"; |
| |
| /** Bind mount app storage dirs to lower fs not via fuse */ |
| public static final String BIND_MOUNT_APP_STORAGE_DIRS = "--bind-mount-storage-dirs"; |
| |
| /** Bind mount app storage dirs to lower fs not via fuse */ |
| public static final String BIND_MOUNT_APP_DATA_DIRS = "--bind-mount-data-dirs"; |
| |
| /** Bind the system properties to an alternate set, for appcompat reasons */ |
| public static final String BIND_MOUNT_SYSPROP_OVERRIDES = "--bind-mount-sysprop-overrides"; |
| |
| /** |
| * An extraArg passed when a zygote process is forking a child-zygote, specifying a name |
| * in the abstract socket namespace. This socket name is what the new child zygote |
| * should listen for connections on. |
| */ |
| public static final String CHILD_ZYGOTE_SOCKET_NAME_ARG = "--zygote-socket="; |
| |
| /** |
| * An extraArg passed when a zygote process is forking a child-zygote, specifying the |
| * requested ABI for the child Zygote. |
| */ |
| public static final String CHILD_ZYGOTE_ABI_LIST_ARG = "--abi-list="; |
| |
| /** |
| * An extraArg passed when a zygote process is forking a child-zygote, specifying the |
| * start of the UID range the children of the Zygote may setuid()/setgid() to. This |
| * will be enforced with a seccomp filter. |
| */ |
| public static final String CHILD_ZYGOTE_UID_RANGE_START = "--uid-range-start="; |
| |
| /** |
| * An extraArg passed when a zygote process is forking a child-zygote, specifying the |
| * end of the UID range the children of the Zygote may setuid()/setgid() to. This |
| * will be enforced with a seccomp filter. |
| */ |
| public static final String CHILD_ZYGOTE_UID_RANGE_END = "--uid-range-end="; |
| |
| private static final String TAG = "Zygote"; |
| |
| /** Prefix prepended to socket names created by init */ |
| private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_"; |
| |
| /** |
| * The duration to wait before re-checking Zygote related system properties. |
| * |
| * One minute in milliseconds. |
| */ |
| public static final long PROPERTY_CHECK_INTERVAL = 60000; |
| |
| /** |
| * @hide for internal use only |
| */ |
| public static final int SOCKET_BUFFER_SIZE = 256; |
| |
| /** |
| * @hide for internal use only |
| */ |
| private static final int PRIORITY_MAX = -20; |
| |
| /** a prototype instance for a future List.toArray() */ |
| static final int[][] INT_ARRAY_2D = new int[0][0]; |
| |
| /** |
| * @hide for internal use only. |
| */ |
| public static final String PRIMARY_SOCKET_NAME = "zygote"; |
| |
| /** |
| * @hide for internal use only. |
| */ |
| public static final String SECONDARY_SOCKET_NAME = "zygote_secondary"; |
| |
| /** |
| * @hide for internal use only |
| */ |
| public static final String USAP_POOL_PRIMARY_SOCKET_NAME = "usap_pool_primary"; |
| |
| /** |
| * @hide for internal use only |
| */ |
| public static final String USAP_POOL_SECONDARY_SOCKET_NAME = "usap_pool_secondary"; |
| |
| private Zygote() {} |
| |
| private static boolean containsInetGid(int[] gids) { |
| for (int i = 0; i < gids.length; i++) { |
| if (gids[i] == android.os.Process.INET_GID) return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Forks a new VM instance. The current VM must have been started |
| * with the -Xzygote flag. <b>NOTE: new instance keeps all |
| * root capabilities. The new process is expected to call capset()</b>. |
| * |
| * @param uid the UNIX uid that the new process should setuid() to after |
| * fork()ing and and before spawning any threads. |
| * @param gid the UNIX gid that the new process should setgid() to after |
| * fork()ing and and before spawning any threads. |
| * @param gids null-ok; a list of UNIX gids that the new process should |
| * setgroups() to after fork and before spawning any threads. |
| * @param runtimeFlags bit flags that enable ART features. |
| * @param rlimits null-ok an array of rlimit tuples, with the second |
| * dimension having a length of 3 and representing |
| * (resource, rlim_cur, rlim_max). These are set via the posix |
| * setrlimit(2) call. |
| * @param seInfo null-ok a string specifying SELinux information for |
| * the new process. |
| * @param niceName null-ok a string specifying the process name. |
| * @param fdsToClose an array of ints, holding one or more POSIX |
| * file descriptor numbers that are to be closed by the child |
| * (and replaced by /dev/null) after forking. An integer value |
| * of -1 in any entry in the array means "ignore this one". |
| * @param fdsToIgnore null-ok an array of ints, either null or holding |
| * one or more POSIX file descriptor numbers that are to be ignored |
| * in the file descriptor table check. |
| * @param startChildZygote if true, the new child process will itself be a |
| * new zygote process. |
| * @param instructionSet null-ok the instruction set to use. |
| * @param appDataDir null-ok the data directory of the app. |
| * @param isTopApp true if the process is for top (high priority) application. |
| * @param pkgDataInfoList A list that stores related packages and its app data |
| * info: volume uuid and inode. |
| * @param allowlistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps. |
| * @param bindMountAppDataDirs True if the zygote needs to mount data dirs. |
| * @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs. |
| * @param bindMountSyspropOverrides True if the zygote needs to mount the override system |
| * properties |
| * |
| * @return 0 if this is the child, pid of the child |
| * if this is the parent, or -1 on error. |
| */ |
| static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, |
| int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, |
| int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, |
| boolean isTopApp, String[] pkgDataInfoList, String[] allowlistedDataInfoList, |
| boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs, |
| boolean bindMountSyspropOverrides) { |
| ZygoteHooks.preFork(); |
| |
| int pid = nativeForkAndSpecialize( |
| uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, |
| fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp, |
| pkgDataInfoList, allowlistedDataInfoList, bindMountAppDataDirs, |
| bindMountAppStorageDirs, bindMountSyspropOverrides); |
| if (pid == 0) { |
| // Note that this event ends at the end of handleChildProc, |
| Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); |
| |
| // If no GIDs were specified, don't make any permissions changes based on groups. |
| if (gids != null && gids.length > 0) { |
| NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids)); |
| } |
| } |
| |
| // Set the Java Language thread priority to the default value for new apps. |
| Thread.currentThread().setPriority(Thread.NORM_PRIORITY); |
| |
| ZygoteHooks.postForkCommon(); |
| return pid; |
| } |
| |
| private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids, |
| int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, |
| int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, |
| String appDataDir, boolean isTopApp, String[] pkgDataInfoList, |
| String[] allowlistedDataInfoList, boolean bindMountAppDataDirs, |
| boolean bindMountAppStorageDirs, boolean bindMountSyspropOverrides); |
| |
| /** |
| * Specialize an unspecialized app process. The current VM must have been started |
| * with the -Xzygote flag. |
| * |
| * @param uid The UNIX uid that the new process should setuid() to before spawning any threads |
| * @param gid The UNIX gid that the new process should setgid() to before spawning any threads |
| * @param gids null-ok; A list of UNIX gids that the new process should |
| * setgroups() to before spawning any threads |
| * @param runtimeFlags Bit flags that enable ART features |
| * @param rlimits null-ok An array of rlimit tuples, with the second |
| * dimension having a length of 3 and representing |
| * (resource, rlim_cur, rlim_max). These are set via the posix |
| * setrlimit(2) call. |
| * @param seInfo null-ok A string specifying SELinux information for |
| * the new process. |
| * @param niceName null-ok A string specifying the process name. |
| * @param startChildZygote If true, the new child process will itself be a |
| * new zygote process. |
| * @param instructionSet null-ok The instruction set to use. |
| * @param appDataDir null-ok The data directory of the app. |
| * @param isTopApp True if the process is for top (high priority) application. |
| * @param pkgDataInfoList A list that stores related packages and its app data |
| * volume uuid and CE dir inode. For example, pkgDataInfoList = [app_a_pkg_name, |
| * app_a_data_volume_uuid, app_a_ce_inode, app_b_pkg_name, app_b_data_volume_uuid, |
| * app_b_ce_inode, ...]; |
| * @param allowlistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps. |
| * @param bindMountAppDataDirs True if the zygote needs to mount data dirs. |
| * @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs. |
| * @param bindMountSyspropOverrides True if the zygote needs to mount the override system |
| * properties |
| */ |
| private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags, |
| int[][] rlimits, int mountExternal, String seInfo, String niceName, |
| boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp, |
| String[] pkgDataInfoList, String[] allowlistedDataInfoList, |
| boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs, |
| boolean bindMountSyspropOverrides) { |
| nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, |
| niceName, startChildZygote, instructionSet, appDataDir, isTopApp, |
| pkgDataInfoList, allowlistedDataInfoList, |
| bindMountAppDataDirs, bindMountAppStorageDirs, bindMountSyspropOverrides); |
| |
| // Note that this event ends at the end of handleChildProc. |
| Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); |
| |
| if (gids != null && gids.length > 0) { |
| NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids)); |
| } |
| |
| // Set the Java Language thread priority to the default value for new apps. |
| Thread.currentThread().setPriority(Thread.NORM_PRIORITY); |
| |
| /* |
| * This is called here (instead of after the fork but before the specialize) to maintain |
| * consistancy with the code paths for forkAndSpecialize. |
| * |
| * TODO (chriswailes): Look into moving this to immediately after the fork. |
| */ |
| ZygoteHooks.postForkCommon(); |
| } |
| |
| private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids, |
| int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, |
| boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp, |
| String[] pkgDataInfoList, String[] allowlistedDataInfoList, |
| boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs, |
| boolean bindMountSyspropOverrides); |
| |
| /** |
| * Called to do any initialization before starting an application. |
| */ |
| static native void nativePreApplicationInit(); |
| |
| /** |
| * Special method to start the system server process. In addition to the |
| * common actions performed in forkAndSpecialize, the pid of the child |
| * process is recorded such that the death of the child process will cause |
| * zygote to exit. |
| * |
| * @param uid the UNIX uid that the new process should setuid() to after |
| * fork()ing and and before spawning any threads. |
| * @param gid the UNIX gid that the new process should setgid() to after |
| * fork()ing and and before spawning any threads. |
| * @param gids null-ok; a list of UNIX gids that the new process should |
| * setgroups() to after fork and before spawning any threads. |
| * @param runtimeFlags bit flags that enable ART features. |
| * @param rlimits null-ok an array of rlimit tuples, with the second |
| * dimension having a length of 3 and representing |
| * (resource, rlim_cur, rlim_max). These are set via the posix |
| * setrlimit(2) call. |
| * @param permittedCapabilities argument for setcap() |
| * @param effectiveCapabilities argument for setcap() |
| * |
| * @return 0 if this is the child, pid of the child |
| * if this is the parent, or -1 on error. |
| */ |
| static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, |
| int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) { |
| ZygoteHooks.preFork(); |
| |
| int pid = nativeForkSystemServer( |
| uid, gid, gids, runtimeFlags, rlimits, |
| permittedCapabilities, effectiveCapabilities); |
| |
| // Set the Java Language thread priority to the default value for new apps. |
| Thread.currentThread().setPriority(Thread.NORM_PRIORITY); |
| |
| ZygoteHooks.postForkCommon(); |
| return pid; |
| } |
| |
| private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, |
| int[][] rlimits, long permittedCapabilities, long effectiveCapabilities); |
| |
| /** |
| * Lets children of the zygote inherit open file descriptors to this path. |
| */ |
| protected static native void nativeAllowFileAcrossFork(String path); |
| |
| /** |
| * Lets children of the zygote inherit open file descriptors that belong to the |
| * ApplicationInfo that is passed in. |
| * |
| * @param appInfo ApplicationInfo of the application |
| */ |
| static void allowAppFilesAcrossFork(ApplicationInfo appInfo) { |
| for (String path : appInfo.getAllApkPaths()) { |
| Zygote.nativeAllowFileAcrossFork(path); |
| } |
| } |
| |
| /** |
| * Scans file descriptors in /proc/self/fd/, stores their metadata from readlink(2)/stat(2) when |
| * available. Saves this information in a global on native side, to be used by subsequent call |
| * to allowFilesOpenedByPreload(). Fatally fails if the FDs are of unsupported type and are not |
| * explicitly allowed. Ignores repeated invocations. |
| * |
| * Inspecting the FDs is more permissive than in forkAndSpecialize() because preload is invoked |
| * earlier and hence needs to allow a few open sockets. The checks in forkAndSpecialize() |
| * enforce that these sockets are closed when forking. |
| */ |
| static void markOpenedFilesBeforePreload() { |
| nativeMarkOpenedFilesBeforePreload(); |
| } |
| |
| private static native void nativeMarkOpenedFilesBeforePreload(); |
| |
| /** |
| * By scanning /proc/self/fd/ determines file descriptor numbers in this process opened since |
| * the first call to markOpenedFilesBeforePreload(). These FDs are treated as 'owned' by the |
| * custom preload of the App Zygote - the app is responsible for not sharing data with its other |
| * processes using these FDs, including by lseek(2). File descriptor types and file names are |
| * not checked. Changes in FDs recorded by markOpenedFilesBeforePreload() are not expected and |
| * kill the current process. |
| */ |
| static void allowFilesOpenedByPreload() { |
| nativeAllowFilesOpenedByPreload(); |
| } |
| |
| private static native void nativeAllowFilesOpenedByPreload(); |
| |
| /** |
| * Installs a seccomp filter that limits setresuid()/setresgid() to the passed-in range |
| * @param uidGidMin The smallest allowed uid/gid |
| * @param uidGidMax The largest allowed uid/gid |
| */ |
| native protected static void nativeInstallSeccompUidGidFilter(int uidGidMin, int uidGidMax); |
| |
| /** |
| * Initialize the native state of the Zygote. This inclues |
| * - Fetching socket FDs from the environment |
| * - Initializing security properties |
| * - Unmounting storage as appropriate |
| * - Loading necessary performance profile information |
| * |
| * @param isPrimary True if this is the zygote process, false if it is zygote_secondary |
| */ |
| static void initNativeState(boolean isPrimary) { |
| nativeInitNativeState(isPrimary); |
| } |
| |
| protected static native void nativeInitNativeState(boolean isPrimary); |
| |
| /** |
| * Returns the raw string value of a system property. |
| * |
| * Note that Device Config is not available without an application so SystemProperties is used |
| * instead. |
| * |
| * TODO (chriswailes): Cache the system property location in native code and then write a JNI |
| * function to fetch it. |
| */ |
| public static String getConfigurationProperty(String propertyName, String defaultValue) { |
| return SystemProperties.get( |
| String.join(".", |
| "persist.device_config", |
| DeviceConfig.NAMESPACE_RUNTIME_NATIVE, |
| propertyName), |
| defaultValue); |
| } |
| |
| static void emptyUsapPool() { |
| nativeEmptyUsapPool(); |
| } |
| |
| private static native void nativeEmptyUsapPool(); |
| |
| /** |
| * Returns the value of a system property converted to a boolean using specific logic. |
| * |
| * Note that Device Config is not available without an application so SystemProperties is used |
| * instead. |
| * |
| * @see SystemProperties#getBoolean |
| * |
| * TODO (chriswailes): Cache the system property location in native code and then write a JNI |
| * function to fetch it. |
| * TODO (chriswailes): Move into ZygoteConfig.java once the necessary CL lands (go/ag/6580627) |
| */ |
| public static boolean getConfigurationPropertyBoolean( |
| String propertyName, Boolean defaultValue) { |
| return SystemProperties.getBoolean( |
| String.join(".", |
| "persist.device_config", |
| DeviceConfig.NAMESPACE_RUNTIME_NATIVE, |
| propertyName), |
| defaultValue); |
| } |
| |
| /** |
| * @return Number of unspecialized app processes currently in the pool |
| */ |
| static int getUsapPoolCount() { |
| return nativeGetUsapPoolCount(); |
| } |
| |
| private static native int nativeGetUsapPoolCount(); |
| |
| /** |
| * @return The event FD used for communication between the signal handler and the ZygoteServer |
| * poll loop |
| */ |
| static FileDescriptor getUsapPoolEventFD() { |
| FileDescriptor fd = new FileDescriptor(); |
| fd.setInt$(nativeGetUsapPoolEventFD()); |
| |
| return fd; |
| } |
| |
| private static native int nativeGetUsapPoolEventFD(); |
| |
| /** |
| * Fork a new unspecialized app process from the zygote. Adds the Usap to the native |
| * Usap table. |
| * |
| * @param usapPoolSocket The server socket the USAP will call accept on |
| * @param sessionSocketRawFDs Anonymous session sockets that are currently open. |
| * These are closed in the child. |
| * @param isPriorityFork Raise the initial process priority level because this is on the |
| * critical path for application startup. |
| * @return In the child process, this returns a Runnable that waits for specialization |
| * info to start an app process. In the sygote/parent process this returns null. |
| */ |
| static @Nullable Runnable forkUsap(LocalServerSocket usapPoolSocket, |
| int[] sessionSocketRawFDs, |
| boolean isPriorityFork) { |
| FileDescriptor readFD; |
| FileDescriptor writeFD; |
| |
| try { |
| FileDescriptor[] pipeFDs = Os.pipe2(O_CLOEXEC); |
| readFD = pipeFDs[0]; |
| writeFD = pipeFDs[1]; |
| } catch (ErrnoException errnoEx) { |
| throw new IllegalStateException("Unable to create USAP pipe.", errnoEx); |
| } |
| |
| int pid = nativeForkApp(readFD.getInt$(), writeFD.getInt$(), |
| sessionSocketRawFDs, /*argsKnown=*/ false, isPriorityFork); |
| if (pid == 0) { |
| IoUtils.closeQuietly(readFD); |
| return childMain(null, usapPoolSocket, writeFD); |
| } else if (pid == -1) { |
| // Fork failed. |
| return null; |
| } else { |
| // readFD will be closed by the native code. See removeUsapTableEntry(); |
| IoUtils.closeQuietly(writeFD); |
| nativeAddUsapTableEntry(pid, readFD.getInt$()); |
| return null; |
| } |
| } |
| |
| private static native int nativeForkApp(int readPipeFD, |
| int writePipeFD, |
| int[] sessionSocketRawFDs, |
| boolean argsKnown, |
| boolean isPriorityFork); |
| |
| /** |
| * Add an entry for a new Usap to the table maintained in native code. |
| */ |
| @CriticalNative |
| private static native void nativeAddUsapTableEntry(int pid, int readPipeFD); |
| |
| /** |
| * Fork a new app process from the zygote. argBuffer contains a fork command that |
| * request neither a child zygote, nor a wrapped process. Continue to accept connections |
| * on the specified socket, use those to refill argBuffer, and continue to process |
| * sufficiently simple fork requests. We presume that the only open file descriptors |
| * requiring special treatment are the session socket embedded in argBuffer, and |
| * zygoteSocket. |
| * @param argBuffer containing initial command and the connected socket from which to |
| * read more |
| * @param zygoteSocket socket from which to obtain new connections when current argBuffer |
| * one is disconnected |
| * @param expectedUId Uid of peer for initial requests. Subsequent requests from a different |
| * peer will cause us to return rather than perform the requested fork. |
| * @param minUid Minimum Uid enforced for all but first fork request. The caller checks |
| * the Uid policy for the initial request. |
| * @param firstNiceName name of first created process. Used for error reporting only. |
| * @return A Runnable in each child process, null in the parent. |
| * If this returns in then argBuffer still contains a command needing to be executed. |
| */ |
| static @Nullable Runnable forkSimpleApps(@NonNull ZygoteCommandBuffer argBuffer, |
| @NonNull FileDescriptor zygoteSocket, |
| int expectedUid, |
| int minUid, |
| @Nullable String firstNiceName) { |
| boolean in_child = |
| argBuffer.forkRepeatedly(zygoteSocket, expectedUid, minUid, firstNiceName); |
| if (in_child) { |
| return childMain(argBuffer, /*usapPoolSocket=*/null, /*writePipe=*/null); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Specialize the current process into one described by argBuffer or the command read from |
| * usapPoolSocket. Exactly one of those must be null. If we are given an argBuffer, we close |
| * it. Used both for a specializing a USAP process, and for process creation without USAPs. |
| * In both cases, we specialize the process after first returning to Java code. |
| * |
| * @param writePipe The write end of the reporting pipe used to communicate with the poll loop |
| * of the ZygoteServer. |
| * @return A runnable oject representing the new application. |
| */ |
| private static Runnable childMain(@Nullable ZygoteCommandBuffer argBuffer, |
| @Nullable LocalServerSocket usapPoolSocket, |
| FileDescriptor writePipe) { |
| final int pid = Process.myPid(); |
| |
| DataOutputStream usapOutputStream = null; |
| ZygoteArguments args = null; |
| |
| LocalSocket sessionSocket = null; |
| if (argBuffer == null) { |
| // Read arguments from usapPoolSocket instead. |
| |
| Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32"); |
| |
| // Change the priority to max before calling accept so we can respond to new |
| // specialization requests as quickly as possible. This will be reverted to the |
| // default priority in the native specialization code. |
| boostUsapPriority(); |
| |
| while (true) { |
| ZygoteCommandBuffer tmpArgBuffer = null; |
| try { |
| sessionSocket = usapPoolSocket.accept(); |
| // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool. |
| // This is safe from a race condition because the pool is only flushed after |
| // the SystemServer changes its internal state to stop using the USAP pool. |
| blockSigTerm(); |
| |
| usapOutputStream = |
| new DataOutputStream(sessionSocket.getOutputStream()); |
| Credentials peerCredentials = sessionSocket.getPeerCredentials(); |
| tmpArgBuffer = new ZygoteCommandBuffer(sessionSocket); |
| args = ZygoteArguments.getInstance(tmpArgBuffer); |
| applyUidSecurityPolicy(args, peerCredentials); |
| // TODO (chriswailes): Should this only be run for debug builds? |
| validateUsapCommand(args); |
| break; |
| } catch (Exception ex) { |
| Log.e("USAP", ex.getMessage()); |
| } |
| // Re-enable SIGTERM so the USAP can be flushed from the pool if necessary. |
| unblockSigTerm(); |
| IoUtils.closeQuietly(sessionSocket); |
| IoUtils.closeQuietly(tmpArgBuffer); |
| } |
| } else { |
| // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool. |
| blockSigTerm(); |
| try { |
| args = ZygoteArguments.getInstance(argBuffer); |
| } catch (Exception ex) { |
| Log.e("AppStartup", ex.getMessage()); |
| throw new AssertionError("Failed to parse application start command", ex); |
| } |
| // peerCredentials were checked in parent. |
| } |
| if (args == null) { |
| throw new AssertionError("Empty command line"); |
| } |
| try { |
| // SIGTERM is blocked here. This prevents a USAP that is specializing from being |
| // killed during a pool flush. |
| |
| applyDebuggerSystemProperty(args); |
| |
| int[][] rlimits = null; |
| |
| if (args.mRLimits != null) { |
| rlimits = args.mRLimits.toArray(INT_ARRAY_2D); |
| } |
| |
| if (argBuffer == null) { |
| // This must happen before the SELinux policy for this process is |
| // changed when specializing. |
| try { |
| // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a |
| // Process.ProcessStartResult object. |
| usapOutputStream.writeInt(pid); |
| } catch (IOException ioEx) { |
| Log.e("USAP", "Failed to write response to session socket: " |
| + ioEx.getMessage()); |
| throw new RuntimeException(ioEx); |
| } finally { |
| try { |
| // Since the raw FD is created by init and then loaded from an environment |
| // variable (as opposed to being created by the LocalSocketImpl itself), |
| // the LocalSocket/LocalSocketImpl does not own the Os-level socket. See |
| // the spec for LocalSocket.createConnectedLocalSocket(FileDescriptor fd). |
| // Thus closing the LocalSocket does not suffice. See b/130309968 for more |
| // discussion. |
| FileDescriptor fd = usapPoolSocket.getFileDescriptor(); |
| usapPoolSocket.close(); |
| Os.close(fd); |
| } catch (ErrnoException | IOException ex) { |
| Log.e("USAP", "Failed to close USAP pool socket"); |
| throw new RuntimeException(ex); |
| } |
| } |
| } |
| |
| if (writePipe != null) { |
| try { |
| ByteArrayOutputStream buffer = |
| new ByteArrayOutputStream(Zygote.USAP_MANAGEMENT_MESSAGE_BYTES); |
| DataOutputStream outputStream = new DataOutputStream(buffer); |
| |
| // This is written as a long so that the USAP reporting pipe and USAP pool |
| // event FD handlers in ZygoteServer.runSelectLoop can be unified. These two |
| // cases should both send/receive 8 bytes. |
| // TODO: Needs tweaking to handle the non-Usap invoke-with case, which expects |
| // a different format. |
| outputStream.writeLong(pid); |
| outputStream.flush(); |
| Os.write(writePipe, buffer.toByteArray(), 0, buffer.size()); |
| } catch (Exception ex) { |
| Log.e("USAP", |
| String.format("Failed to write PID (%d) to pipe (%d): %s", |
| pid, writePipe.getInt$(), ex.getMessage())); |
| throw new RuntimeException(ex); |
| } finally { |
| IoUtils.closeQuietly(writePipe); |
| } |
| } |
| |
| specializeAppProcess(args.mUid, args.mGid, args.mGids, |
| args.mRuntimeFlags, rlimits, args.mMountExternal, |
| args.mSeInfo, args.mNiceName, args.mStartChildZygote, |
| args.mInstructionSet, args.mAppDataDir, args.mIsTopApp, |
| args.mPkgDataInfoList, args.mAllowlistedDataInfoList, |
| args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs, |
| args.mBindMountSyspropOverrides); |
| |
| // While `specializeAppProcess` sets the thread name on the process's main thread, this |
| // is distinct from the app process name which appears in stack traces, as the latter is |
| // sourced from the argument buffer of the Process class. Set the app process name here. |
| Zygote.setAppProcessName(args, TAG); |
| |
| Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); |
| |
| return ZygoteInit.zygoteInit(args.mTargetSdkVersion, |
| args.mDisabledCompatChanges, |
| args.mRemainingArgs, |
| null /* classLoader */); |
| } finally { |
| // Unblock SIGTERM to restore the process to default behavior. |
| unblockSigTerm(); |
| } |
| } |
| |
| private static void blockSigTerm() { |
| nativeBlockSigTerm(); |
| } |
| |
| private static native void nativeBlockSigTerm(); |
| |
| private static void unblockSigTerm() { |
| nativeUnblockSigTerm(); |
| } |
| |
| private static native void nativeUnblockSigTerm(); |
| |
| private static void boostUsapPriority() { |
| nativeBoostUsapPriority(); |
| } |
| |
| private static native void nativeBoostUsapPriority(); |
| |
| static void setAppProcessName(ZygoteArguments args, String loggingTag) { |
| if (args.mNiceName != null) { |
| Process.setArgV0(args.mNiceName); |
| } else if (args.mPackageName != null) { |
| Process.setArgV0(args.mPackageName); |
| } else { |
| Log.w(loggingTag, "Unable to set package name."); |
| } |
| } |
| |
| private static final String USAP_ERROR_PREFIX = "Invalid command to USAP: "; |
| |
| /** |
| * Checks a set of zygote arguments to see if they can be handled by a USAP. Throws an |
| * exception if an invalid arugment is encountered. |
| * @param args The arguments to test |
| */ |
| private static void validateUsapCommand(ZygoteArguments args) { |
| if (args.mAbiListQuery) { |
| throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--query-abi-list"); |
| } else if (args.mPidQuery) { |
| throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--get-pid"); |
| } else if (args.mPreloadDefault) { |
| throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-default"); |
| } else if (args.mPreloadPackage != null) { |
| throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-package"); |
| } else if (args.mPreloadApp != null) { |
| throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-app"); |
| } else if (args.mStartChildZygote) { |
| throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--start-child-zygote"); |
| } else if (args.mApiDenylistExemptions != null) { |
| throw new IllegalArgumentException( |
| USAP_ERROR_PREFIX + "--set-api-denylist-exemptions"); |
| } else if (args.mHiddenApiAccessLogSampleRate != -1) { |
| throw new IllegalArgumentException( |
| USAP_ERROR_PREFIX + "--hidden-api-log-sampling-rate="); |
| } else if (args.mHiddenApiAccessStatslogSampleRate != -1) { |
| throw new IllegalArgumentException( |
| USAP_ERROR_PREFIX + "--hidden-api-statslog-sampling-rate="); |
| } else if (args.mInvokeWith != null) { |
| throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--invoke-with"); |
| } else if (args.mPermittedCapabilities != 0 || args.mEffectiveCapabilities != 0) { |
| throw new ZygoteSecurityException("Client may not specify capabilities: " |
| + "permitted=0x" + Long.toHexString(args.mPermittedCapabilities) |
| + ", effective=0x" + Long.toHexString(args.mEffectiveCapabilities)); |
| } |
| } |
| |
| /** |
| * @return Raw file descriptors for the read-end of USAP reporting pipes. |
| */ |
| static int[] getUsapPipeFDs() { |
| return nativeGetUsapPipeFDs(); |
| } |
| |
| private static native int[] nativeGetUsapPipeFDs(); |
| |
| /** |
| * Remove the USAP table entry for the provided process ID. |
| * |
| * @param usapPID Process ID of the entry to remove |
| * @return True if the entry was removed; false if it doesn't exist |
| */ |
| static boolean removeUsapTableEntry(int usapPID) { |
| return nativeRemoveUsapTableEntry(usapPID); |
| } |
| |
| @CriticalNative |
| private static native boolean nativeRemoveUsapTableEntry(int usapPID); |
| |
| /** |
| * Return the minimum child uid that the given peer is allowed to create. |
| * uid 1000 (Process.SYSTEM_UID) may specify any uid ≥ 1000 in normal |
| * operation. It may also specify any gid and setgroups() list it chooses. |
| * In factory test mode, it may specify any UID. |
| */ |
| static int minChildUid(Credentials peer) { |
| if (peer.getUid() == Process.SYSTEM_UID |
| && FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF) { |
| /* In normal operation, SYSTEM_UID can only specify a restricted |
| * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid. |
| */ |
| return Process.SYSTEM_UID; |
| } else { |
| return 0; |
| } |
| } |
| |
| /* |
| * Adjust uid and gid arguments, ensuring that the security policy is satisfied. |
| * @param args non-null; zygote spawner arguments |
| * @param peer non-null; peer credentials |
| * @throws ZygoteSecurityException Indicates a security issue when applying the UID based |
| * security policies |
| */ |
| static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer) |
| throws ZygoteSecurityException { |
| |
| if (args.mUidSpecified && (args.mUid < minChildUid(peer))) { |
| throw new ZygoteSecurityException( |
| "System UID may not launch process with UID < " |
| + Process.SYSTEM_UID); |
| } |
| |
| // If not otherwise specified, uid and gid are inherited from peer |
| if (!args.mUidSpecified) { |
| args.mUid = peer.getUid(); |
| args.mUidSpecified = true; |
| } |
| if (!args.mGidSpecified) { |
| args.mGid = peer.getGid(); |
| args.mGidSpecified = true; |
| } |
| } |
| |
| /** |
| * This will enable jdwp by default for all apps. It is OK to cache this property |
| * because we expect to reboot the system whenever this property changes |
| */ |
| private static final boolean ENABLE_JDWP = SystemProperties.get( |
| "persist.debug.dalvik.vm.jdwp.enabled").equals("1"); |
| |
| /** |
| * This will enable ptrace by default for all apps. It is OK to cache this property |
| * because we expect to reboot the system whenever this property changes |
| */ |
| private static final boolean ENABLE_PTRACE = SystemProperties.get( |
| "persist.debug.ptrace.enabled").equals("1"); |
| |
| /** |
| * Applies debugger system properties to the zygote arguments. |
| * |
| * For eng builds all apps are debuggable with JDWP and ptrace. |
| * |
| * On userdebug builds if persist.debug.dalvik.vm.jdwp.enabled |
| * is 1 all apps are debuggable with JDWP and ptrace. Otherwise, the |
| * debugger state is specified via the "--enable-jdwp" flag in the |
| * spawn request. |
| * |
| * On userdebug builds if persist.debug.ptrace.enabled is 1 all |
| * apps are debuggable with ptrace. |
| * |
| * @param args non-null; zygote spawner args |
| */ |
| static void applyDebuggerSystemProperty(ZygoteArguments args) { |
| if (Build.IS_ENG || (Build.IS_USERDEBUG && ENABLE_JDWP)) { |
| args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP; |
| // Also enable ptrace when JDWP is enabled for consistency with |
| // before persist.debug.ptrace.enabled existed. |
| args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_PTRACE; |
| } |
| if (Build.IS_ENG || (Build.IS_USERDEBUG && ENABLE_PTRACE)) { |
| args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_PTRACE; |
| } |
| } |
| |
| /** |
| * Applies zygote security policy. |
| * Based on the credentials of the process issuing a zygote command: |
| * <ol> |
| * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a |
| * wrapper command. |
| * <li> Any other uid may not specify any invoke-with argument. |
| * </ul> |
| * |
| * @param args non-null; zygote spawner arguments |
| * @param peer non-null; peer credentials |
| * @throws ZygoteSecurityException Thrown when `--invoke-with` is specified for a non-debuggable |
| * application. |
| */ |
| static void applyInvokeWithSecurityPolicy(ZygoteArguments args, Credentials peer) |
| throws ZygoteSecurityException { |
| int peerUid = peer.getUid(); |
| |
| if (args.mInvokeWith != null && peerUid != 0 |
| && (args.mRuntimeFlags |
| & (Zygote.DEBUG_ENABLE_JDWP | Zygote.DEBUG_ENABLE_PTRACE)) == 0) { |
| throw new ZygoteSecurityException("Peer is permitted to specify an " |
| + "explicit invoke-with wrapper command only for debuggable " |
| + "applications."); |
| } |
| } |
| |
| /** |
| * Gets the wrap property if set. |
| * |
| * @param appName the application name to check |
| * @return value of wrap property or null if property not set or |
| * null if app_name is null or null if app_name is empty |
| */ |
| public static String getWrapProperty(String appName) { |
| if (appName == null || appName.isEmpty()) { |
| return null; |
| } |
| |
| String propertyValue = SystemProperties.get("wrap." + appName); |
| if (propertyValue != null && !propertyValue.isEmpty()) { |
| return propertyValue; |
| } |
| return null; |
| } |
| |
| /** |
| * Applies invoke-with system properties to the zygote arguments. |
| * |
| * @param args non-null; zygote args |
| */ |
| static void applyInvokeWithSystemProperty(ZygoteArguments args) { |
| if (args.mInvokeWith == null) { |
| args.mInvokeWith = getWrapProperty(args.mNiceName); |
| } |
| } |
| |
| /** |
| * Creates a managed LocalServerSocket object using a file descriptor |
| * created by an init.rc script. The init scripts that specify the |
| * sockets name can be found in system/core/rootdir. The socket is bound |
| * to the file system in the /dev/sockets/ directory, and the file |
| * descriptor is shared via the ANDROID_SOCKET_<socketName> environment |
| * variable. |
| */ |
| static LocalServerSocket createManagedSocketFromInitSocket(String socketName) { |
| int fileDesc; |
| final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName; |
| |
| try { |
| String env = System.getenv(fullSocketName); |
| fileDesc = Integer.parseInt(env); |
| } catch (RuntimeException ex) { |
| throw new RuntimeException("Socket unset or invalid: " + fullSocketName, ex); |
| } |
| |
| try { |
| FileDescriptor fd = new FileDescriptor(); |
| fd.setInt$(fileDesc); |
| return new LocalServerSocket(fd); |
| } catch (IOException ex) { |
| throw new RuntimeException( |
| "Error building socket from file descriptor: " + fileDesc, ex); |
| } |
| } |
| |
| // This function is called from native code in com_android_internal_os_Zygote.cpp |
| @SuppressWarnings("unused") |
| private static void callPostForkSystemServerHooks(int runtimeFlags) { |
| // SystemServer specific post fork hooks run before child post fork hooks. |
| ZygoteHooks.postForkSystemServer(runtimeFlags); |
| } |
| |
| // This function is called from native code in com_android_internal_os_Zygote.cpp |
| @SuppressWarnings("unused") |
| private static void callPostForkChildHooks(int runtimeFlags, boolean isSystemServer, |
| boolean isZygote, String instructionSet) { |
| ZygoteHooks.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet); |
| } |
| |
| /** |
| * Executes "/system/bin/sh -c <command>" using the exec() system call. |
| * This method throws a runtime exception if exec() failed, otherwise, this |
| * method never returns. |
| * |
| * @param command The shell command to execute. |
| */ |
| static void execShell(String command) { |
| String[] args = { "/system/bin/sh", "-c", command }; |
| try { |
| Os.execv(args[0], args); |
| } catch (ErrnoException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| /** |
| * Appends quotes shell arguments to the specified string builder. |
| * The arguments are quoted using single-quotes, escaped if necessary, |
| * prefixed with a space, and appended to the command. |
| * |
| * @param command A string builder for the shell command being constructed. |
| * @param args An array of argument strings to be quoted and appended to the command. |
| * @see #execShell(String) |
| */ |
| static void appendQuotedShellArgs(StringBuilder command, String[] args) { |
| for (String arg : args) { |
| command.append(" '").append(arg.replace("'", "'\\''")).append("'"); |
| } |
| } |
| |
| /** |
| * Parse the given unsolicited zygote message as type SIGCHLD, |
| * extract the payload information into the given output buffer. |
| * |
| * @param in The unsolicited zygote message to be parsed |
| * @param length The number of bytes in the message |
| * @param out The output buffer where the payload information will be placed |
| * @return Number of elements being place into output buffer, or -1 if |
| * either the message is malformed or not the type as expected here. |
| * |
| * @hide |
| */ |
| @FastNative |
| public static native int nativeParseSigChld(byte[] in, int length, int[] out); |
| |
| /** |
| * Returns whether the hardware supports memory tagging (ARM MTE). |
| */ |
| public static native boolean nativeSupportsMemoryTagging(); |
| |
| /** |
| * Returns whether the kernel supports tagged pointers. Present in the |
| * Android Common Kernel from 4.14 and up. By default, you should prefer |
| * fully-feature Memory Tagging, rather than the static Tagged Pointers. |
| */ |
| public static native boolean nativeSupportsTaggedPointers(); |
| |
| /** |
| * Returns the current native tagging level, as one of the |
| * MEMORY_TAG_LEVEL_* constants. Returns zero if no tagging is present, or |
| * we failed to determine the level. |
| */ |
| public static native int nativeCurrentTaggingLevel(); |
| |
| /** |
| * Native heap allocations will now have a non-zero tag in the most significant byte. |
| * |
| * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged |
| * Pointers</a> |
| */ |
| @ChangeId |
| @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) |
| private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id. |
| |
| /** |
| * Native heap allocations in AppZygote process and its descendants will now have a non-zero tag |
| * in the most significant byte. |
| * |
| * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged |
| * Pointers</a> |
| */ |
| @ChangeId |
| @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S) |
| private static final long NATIVE_HEAP_POINTER_TAGGING_SECONDARY_ZYGOTE = 207557677; |
| |
| /** |
| * Enable asynchronous (ASYNC) memory tag checking in this process. This flag will only have an |
| * effect on hardware supporting the ARM Memory Tagging Extension (MTE). |
| */ |
| @ChangeId @Disabled |
| private static final long NATIVE_MEMTAG_ASYNC = 135772972; // This is a bug id. |
| |
| /** |
| * Enable synchronous (SYNC) memory tag checking in this process. This flag will only have an |
| * effect on hardware supporting the ARM Memory Tagging Extension (MTE). If both |
| * NATIVE_MEMTAG_ASYNC and this option is selected, this option takes preference and MTE is |
| * enabled in SYNC mode. |
| */ |
| @ChangeId @Disabled |
| private static final long NATIVE_MEMTAG_SYNC = 177438394; // This is a bug id. |
| |
| /** Enable automatic zero-initialization of native heap memory allocations. */ |
| @ChangeId @Disabled |
| private static final long NATIVE_HEAP_ZERO_INIT = 178038272; // This is a bug id. |
| |
| /** |
| * Enable sampled memory bug detection in the app. |
| * |
| * @see <a href="https://source.android.com/devices/tech/debug/gwp-asan">GWP-ASan</a>. |
| */ |
| @ChangeId @Disabled private static final long GWP_ASAN = 135634846; // This is a bug id. |
| |
| private static int memtagModeToZygoteMemtagLevel(int memtagMode) { |
| switch (memtagMode) { |
| case ApplicationInfo.MEMTAG_ASYNC: |
| return MEMORY_TAG_LEVEL_ASYNC; |
| case ApplicationInfo.MEMTAG_SYNC: |
| return MEMORY_TAG_LEVEL_SYNC; |
| default: |
| return MEMORY_TAG_LEVEL_NONE; |
| } |
| } |
| |
| private static boolean isCompatChangeEnabled( |
| long change, |
| @NonNull ApplicationInfo info, |
| @Nullable IPlatformCompat platformCompat, |
| int enabledAfter) { |
| try { |
| if (platformCompat != null) return platformCompat.isChangeEnabled(change, info); |
| } catch (RemoteException ignore) { |
| } |
| return enabledAfter > 0 && info.targetSdkVersion > enabledAfter; |
| } |
| |
| // Returns the requested memory tagging level. |
| private static int getRequestedMemtagLevel( |
| @NonNull ApplicationInfo info, |
| @Nullable ProcessInfo processInfo, |
| @Nullable IPlatformCompat platformCompat) { |
| String appOverride = SystemProperties.get("persist.arm64.memtag.app." + info.packageName); |
| if ("sync".equals(appOverride)) { |
| return MEMORY_TAG_LEVEL_SYNC; |
| } else if ("async".equals(appOverride)) { |
| return MEMORY_TAG_LEVEL_ASYNC; |
| } else if ("off".equals(appOverride)) { |
| return MEMORY_TAG_LEVEL_NONE; |
| } |
| |
| // Look at the process attribute first. |
| if (processInfo != null && processInfo.memtagMode != ApplicationInfo.MEMTAG_DEFAULT) { |
| return memtagModeToZygoteMemtagLevel(processInfo.memtagMode); |
| } |
| |
| // Then at the application attribute. |
| if (info.getMemtagMode() != ApplicationInfo.MEMTAG_DEFAULT) { |
| return memtagModeToZygoteMemtagLevel(info.getMemtagMode()); |
| } |
| |
| if (isCompatChangeEnabled(NATIVE_MEMTAG_SYNC, info, platformCompat, 0)) { |
| return MEMORY_TAG_LEVEL_SYNC; |
| } |
| |
| if (isCompatChangeEnabled(NATIVE_MEMTAG_ASYNC, info, platformCompat, 0)) { |
| return MEMORY_TAG_LEVEL_ASYNC; |
| } |
| |
| // Check to ensure the app hasn't explicitly opted-out of TBI via. the manifest attribute. |
| if (!info.allowsNativeHeapPointerTagging()) { |
| return MEMORY_TAG_LEVEL_NONE; |
| } |
| |
| String defaultLevel = SystemProperties.get("persist.arm64.memtag.app_default"); |
| if ("sync".equals(defaultLevel)) { |
| return MEMORY_TAG_LEVEL_SYNC; |
| } else if ("async".equals(defaultLevel)) { |
| return MEMORY_TAG_LEVEL_ASYNC; |
| } |
| |
| // Check to see that the compat feature for TBI is enabled. |
| if (isCompatChangeEnabled( |
| NATIVE_HEAP_POINTER_TAGGING, info, platformCompat, Build.VERSION_CODES.Q)) { |
| return MEMORY_TAG_LEVEL_TBI; |
| } |
| |
| return MEMORY_TAG_LEVEL_NONE; |
| } |
| |
| private static int decideTaggingLevel( |
| @NonNull ApplicationInfo info, |
| @Nullable ProcessInfo processInfo, |
| @Nullable IPlatformCompat platformCompat) { |
| // Get the desired tagging level (app manifest + compat features). |
| int level = getRequestedMemtagLevel(info, processInfo, platformCompat); |
| |
| // Take into account the hardware capabilities. |
| if (nativeSupportsMemoryTagging()) { |
| // MTE devices can not do TBI, because the Zygote process already has live MTE |
| // allocations. Downgrade TBI to NONE. |
| if (level == MEMORY_TAG_LEVEL_TBI) { |
| level = MEMORY_TAG_LEVEL_NONE; |
| } |
| } else if (nativeSupportsTaggedPointers()) { |
| // TBI-but-not-MTE devices downgrade MTE modes to TBI. |
| // The idea is that if an app opts into full hardware tagging (MTE), it must be ok with |
| // the "fake" pointer tagging (TBI). |
| if (level == MEMORY_TAG_LEVEL_ASYNC || level == MEMORY_TAG_LEVEL_SYNC) { |
| level = MEMORY_TAG_LEVEL_TBI; |
| } |
| } else { |
| // Otherwise disable all tagging. |
| level = MEMORY_TAG_LEVEL_NONE; |
| } |
| |
| // If we requested "sync" mode for the whole platform, upgrade mode for apps that enable |
| // MTE. |
| // This makes debugging a lot easier. |
| if (level == MEMORY_TAG_LEVEL_ASYNC |
| && (Build.IS_USERDEBUG || Build.IS_ENG) |
| && "sync".equals(SystemProperties.get("persist.arm64.memtag.default"))) { |
| level = MEMORY_TAG_LEVEL_SYNC; |
| } |
| |
| return level; |
| } |
| |
| private static int decideGwpAsanLevel( |
| @NonNull ApplicationInfo info, |
| @Nullable ProcessInfo processInfo, |
| @Nullable IPlatformCompat platformCompat) { |
| // Look at the process attribute first. |
| if (processInfo != null && processInfo.gwpAsanMode != ApplicationInfo.GWP_ASAN_DEFAULT) { |
| return processInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_ALWAYS |
| ? GWP_ASAN_LEVEL_ALWAYS |
| : GWP_ASAN_LEVEL_NEVER; |
| } |
| // Then at the application attribute. |
| if (info.getGwpAsanMode() != ApplicationInfo.GWP_ASAN_DEFAULT) { |
| return info.getGwpAsanMode() == ApplicationInfo.GWP_ASAN_ALWAYS |
| ? GWP_ASAN_LEVEL_ALWAYS |
| : GWP_ASAN_LEVEL_NEVER; |
| } |
| if (isCompatChangeEnabled(GWP_ASAN, info, platformCompat, 0)) { |
| return GWP_ASAN_LEVEL_ALWAYS; |
| } |
| if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { |
| return GWP_ASAN_LEVEL_LOTTERY; |
| } |
| return GWP_ASAN_LEVEL_DEFAULT; |
| } |
| |
| private static boolean enableNativeHeapZeroInit( |
| @NonNull ApplicationInfo info, |
| @Nullable ProcessInfo processInfo, |
| @Nullable IPlatformCompat platformCompat) { |
| // Look at the process attribute first. |
| if (processInfo != null |
| && processInfo.nativeHeapZeroInitialized != ApplicationInfo.ZEROINIT_DEFAULT) { |
| return processInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_ENABLED; |
| } |
| // Then at the application attribute. |
| if (info.getNativeHeapZeroInitialized() != ApplicationInfo.ZEROINIT_DEFAULT) { |
| return info.getNativeHeapZeroInitialized() == ApplicationInfo.ZEROINIT_ENABLED; |
| } |
| // Compat feature last. |
| if (isCompatChangeEnabled(NATIVE_HEAP_ZERO_INIT, info, platformCompat, 0)) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns Zygote runtimeFlags for memory safety features (MTE, GWP-ASan, nativeHeadZeroInit) |
| * for a given app. |
| */ |
| public static int getMemorySafetyRuntimeFlags( |
| @NonNull ApplicationInfo info, |
| @Nullable ProcessInfo processInfo, |
| @Nullable String instructionSet, |
| @Nullable IPlatformCompat platformCompat) { |
| int runtimeFlags = decideGwpAsanLevel(info, processInfo, platformCompat); |
| // If instructionSet is non-null, this indicates that the system_server is spawning a |
| // process with an ISA that may be different from its own. System (kernel and hardware) |
| // compatibility for these features is checked in the decideTaggingLevel in the |
| // system_server process (not the child process). As both MTE and TBI are only supported |
| // in aarch64, we can simply ensure that the new process is also aarch64. This prevents |
| // the mismatch where a 64-bit system server spawns a 32-bit child that thinks it should |
| // enable some tagging variant. Theoretically, a 32-bit system server could exist that |
| // spawns 64-bit processes, in which case the new process won't get any tagging. This is |
| // fine as we haven't seen this configuration in practice, and we can reasonable assume |
| // that if tagging is desired, the system server will be 64-bit. |
| if (instructionSet == null || instructionSet.equals("arm64")) { |
| runtimeFlags |= decideTaggingLevel(info, processInfo, platformCompat); |
| } |
| if (enableNativeHeapZeroInit(info, processInfo, platformCompat)) { |
| runtimeFlags |= NATIVE_HEAP_ZERO_INIT_ENABLED; |
| } |
| return runtimeFlags; |
| } |
| |
| /** |
| * Returns Zygote runtimeFlags for memory safety features (MTE, GWP-ASan, nativeHeadZeroInit) |
| * for a secondary zygote (AppZygote or WebViewZygote). |
| */ |
| public static int getMemorySafetyRuntimeFlagsForSecondaryZygote( |
| @NonNull ApplicationInfo info, @Nullable ProcessInfo processInfo) { |
| final IPlatformCompat platformCompat = |
| IPlatformCompat.Stub.asInterface( |
| ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); |
| int runtimeFlags = |
| getMemorySafetyRuntimeFlags( |
| info, processInfo, null /*instructionSet*/, platformCompat); |
| |
| // TBI ("fake" pointer tagging) in AppZygote is controlled by a separate compat feature. |
| if ((runtimeFlags & MEMORY_TAG_LEVEL_MASK) == MEMORY_TAG_LEVEL_TBI |
| && isCompatChangeEnabled( |
| NATIVE_HEAP_POINTER_TAGGING_SECONDARY_ZYGOTE, |
| info, |
| platformCompat, |
| Build.VERSION_CODES.S)) { |
| // Reset memory tag level to NONE. |
| runtimeFlags &= ~MEMORY_TAG_LEVEL_MASK; |
| runtimeFlags |= MEMORY_TAG_LEVEL_NONE; |
| } |
| return runtimeFlags; |
| } |
| } |