| /* |
| * Copyright (C) 2007 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 java.io.EOFException; |
| import java.util.ArrayList; |
| |
| /** |
| * Handles argument parsing for args related to the zygote spawner. |
| * |
| * Current recognized args: |
| * <ul> |
| * <li> --setuid=<i>uid of child process, defaults to 0</i> |
| * <li> --setgid=<i>gid of child process, defaults to 0</i> |
| * <li> --setgroups=<i>comma-separated list of supplimentary gid's</i> |
| * <li> --capabilities=<i>a pair of comma-separated integer strings |
| * indicating Linux capabilities(2) set for child. The first string |
| * represents the <code>permitted</code> set, and the second the |
| * <code>effective</code> set. Precede each with 0 or |
| * 0x for octal or hexidecimal value. If unspecified, both default to 0. |
| * This parameter is only applied if the uid of the new process will |
| * be non-0. </i> |
| * <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call. |
| * <code>r</code> is the resource, <code>c</code> and <code>m</code> |
| * are the settings for current and max value.</i> |
| * <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate. |
| * <li> --nice-name=<i>nice name to appear in ps</i> |
| * <li> --package-name=<i>package name this process belongs to</i> |
| * <li> --runtime-args indicates that the remaining arg list should |
| * be handed off to com.android.internal.os.RuntimeInit, rather than |
| * processed directly. |
| * Android runtime startup (eg, Binder initialization) is also eschewed. |
| * <li> [--] <args for RuntimeInit > |
| * </ul> |
| */ |
| class ZygoteArguments { |
| |
| /** |
| * from --setuid |
| */ |
| int mUid = 0; |
| boolean mUidSpecified; |
| |
| /** |
| * from --setgid |
| */ |
| int mGid = 0; |
| boolean mGidSpecified; |
| |
| /** |
| * from --setgroups |
| */ |
| int[] mGids; |
| |
| /** |
| * From --runtime-flags. |
| */ |
| int mRuntimeFlags; |
| |
| /** |
| * From --mount-external |
| */ |
| int mMountExternal = Zygote.MOUNT_EXTERNAL_NONE; |
| |
| /** |
| * from --target-sdk-version. |
| */ |
| private boolean mTargetSdkVersionSpecified; |
| int mTargetSdkVersion; |
| |
| /** |
| * from --nice-name |
| */ |
| String mNiceName; |
| |
| /** |
| * from --capabilities |
| */ |
| private boolean mCapabilitiesSpecified; |
| long mPermittedCapabilities; |
| long mEffectiveCapabilities; |
| |
| /** |
| * from --seinfo |
| */ |
| private boolean mSeInfoSpecified; |
| String mSeInfo; |
| |
| /** |
| * |
| */ |
| boolean mUsapPoolEnabled; |
| boolean mUsapPoolStatusSpecified = false; |
| |
| /** |
| * from all --rlimit=r,c,m |
| */ |
| ArrayList<int[]> mRLimits; |
| |
| /** |
| * from --invoke-with |
| */ |
| String mInvokeWith; |
| |
| /** from --package-name */ |
| String mPackageName; |
| |
| /** |
| * Any args after and including the first non-option arg (or after a '--') |
| */ |
| String[] mRemainingArgs; |
| |
| /** |
| * Whether the current arguments constitute an ABI list query. |
| */ |
| boolean mAbiListQuery; |
| |
| /** |
| * The instruction set to use, or null when not important. |
| */ |
| String mInstructionSet; |
| |
| /** |
| * The app data directory. May be null, e.g., for the system server. Note that this might not be |
| * reliable in the case of process-sharing apps. |
| */ |
| String mAppDataDir; |
| |
| /** |
| * The APK path of the package to preload, when using --preload-package. |
| */ |
| String mPreloadPackage; |
| |
| /** |
| * A Base64 string representing a serialize ApplicationInfo Parcel, |
| when using --preload-app. |
| */ |
| String mPreloadApp; |
| |
| /** |
| * The native library path of the package to preload, when using --preload-package. |
| */ |
| String mPreloadPackageLibs; |
| |
| /** |
| * The filename of the native library to preload, when using --preload-package. |
| */ |
| String mPreloadPackageLibFileName; |
| |
| /** |
| * The cache key under which to enter the preloaded package into the classloader cache, when |
| * using --preload-package. |
| */ |
| String mPreloadPackageCacheKey; |
| |
| /** |
| * Whether this is a request to start preloading the default resources and classes. This |
| * argument only makes sense when the zygote is in lazy preload mode (i.e, when it's started |
| * with --enable-lazy-preload). |
| */ |
| boolean mPreloadDefault; |
| |
| /** |
| * Whether this is a request to start a zygote process as a child of this zygote. Set with |
| * --start-child-zygote. The remaining arguments must include the CHILD_ZYGOTE_SOCKET_NAME_ARG |
| * flag to indicate the abstract socket name that should be used for communication. |
| */ |
| boolean mStartChildZygote; |
| |
| /** |
| * Whether the current arguments constitute a request for the zygote's PID. |
| */ |
| boolean mPidQuery; |
| |
| /** |
| * Whether the current arguments constitute a notification that boot completed. |
| */ |
| boolean mBootCompleted; |
| |
| /** |
| * Exemptions from API deny-listing. These are sent to the pre-forked zygote at boot time, or |
| * when they change, via --set-api-denylist-exemptions. |
| */ |
| String[] mApiDenylistExemptions; |
| |
| /** |
| * Sampling rate for logging hidden API accesses to the event log. This is sent to the |
| * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate. |
| */ |
| int mHiddenApiAccessLogSampleRate = -1; |
| |
| /** |
| * Sampling rate for logging hidden API accesses to statslog. This is sent to the |
| * pre-forked zygote at boot time, or when it changes, via --hidden-api-statslog-sampling-rate. |
| */ |
| int mHiddenApiAccessStatslogSampleRate = -1; |
| |
| /** |
| * @see Zygote#START_AS_TOP_APP_ARG |
| */ |
| boolean mIsTopApp; |
| |
| /** |
| * A set of disabled app compatibility changes for the running app. From |
| * --disabled-compat-changes. |
| */ |
| long[] mDisabledCompatChanges = null; |
| |
| /** |
| * A list that stores all related packages and its data info: volume uuid and inode. |
| * Null if it does need to do app data isolation. |
| */ |
| String[] mPkgDataInfoList; |
| |
| /** |
| * A list that stores all allowlisted app data info: volume uuid and inode. |
| * Null if it does need to do app data isolation. |
| */ |
| String[] mAllowlistedDataInfoList; |
| |
| /** |
| * @see Zygote#BIND_MOUNT_APP_STORAGE_DIRS |
| */ |
| boolean mBindMountAppStorageDirs; |
| |
| /** |
| * @see Zygote#BIND_MOUNT_APP_DATA_DIRS |
| */ |
| boolean mBindMountAppDataDirs; |
| |
| /** |
| * @see Zygote#BIND_MOUNT_SYSPROP_OVERRIDES |
| */ |
| boolean mBindMountSyspropOverrides; |
| |
| /** |
| * Constructs instance and parses args |
| * |
| * @param args zygote command-line args as ZygoteCommandBuffer, positioned after argument count. |
| */ |
| private ZygoteArguments(ZygoteCommandBuffer args, int argCount) |
| throws IllegalArgumentException, EOFException { |
| parseArgs(args, argCount); |
| } |
| |
| /** |
| * Return a new ZygoteArguments reflecting the contents of the given ZygoteCommandBuffer. Return |
| * null if the ZygoteCommandBuffer was positioned at EOF. Assumes the buffer is initially |
| * positioned at the beginning of the command. |
| */ |
| public static ZygoteArguments getInstance(ZygoteCommandBuffer args) |
| throws IllegalArgumentException, EOFException { |
| int argCount = args.getCount(); |
| return argCount == 0 ? null : new ZygoteArguments(args, argCount); |
| } |
| |
| /** |
| * Parses the commandline arguments intended for the Zygote spawner (such as "--setuid=" and |
| * "--setgid=") and creates an array containing the remaining args. Return false if we were |
| * at EOF. |
| * |
| * Per security review bug #1112214, duplicate args are disallowed in critical cases to make |
| * injection harder. |
| */ |
| private void parseArgs(ZygoteCommandBuffer args, int argCount) |
| throws IllegalArgumentException, EOFException { |
| /* |
| * See android.os.ZygoteProcess.zygoteSendArgsAndGetResult() |
| * Presently the wire format to the zygote process is: |
| * a) a count of arguments (argc, in essence) |
| * b) a number of newline-separated argument strings equal to count |
| * |
| * After the zygote process reads these it will write the pid of |
| * the child or -1 on failure. |
| */ |
| |
| String unprocessedArg = null; |
| int curArg = 0; // Index of arg |
| boolean seenRuntimeArgs = false; |
| boolean expectRuntimeArgs = true; |
| |
| for ( /* curArg */ ; curArg < argCount; ++curArg) { |
| String arg = args.nextArg(); |
| |
| if (arg.equals("--")) { |
| curArg++; |
| break; |
| } else if (arg.startsWith("--setuid=")) { |
| if (mUidSpecified) { |
| throw new IllegalArgumentException( |
| "Duplicate arg specified"); |
| } |
| mUidSpecified = true; |
| mUid = Integer.parseInt(getAssignmentValue(arg)); |
| } else if (arg.startsWith("--setgid=")) { |
| if (mGidSpecified) { |
| throw new IllegalArgumentException( |
| "Duplicate arg specified"); |
| } |
| mGidSpecified = true; |
| mGid = Integer.parseInt(getAssignmentValue(arg)); |
| } else if (arg.startsWith("--target-sdk-version=")) { |
| if (mTargetSdkVersionSpecified) { |
| throw new IllegalArgumentException( |
| "Duplicate target-sdk-version specified"); |
| } |
| mTargetSdkVersionSpecified = true; |
| mTargetSdkVersion = Integer.parseInt(getAssignmentValue(arg)); |
| } else if (arg.equals("--runtime-args")) { |
| seenRuntimeArgs = true; |
| } else if (arg.startsWith("--runtime-flags=")) { |
| mRuntimeFlags = Integer.parseInt(getAssignmentValue(arg)); |
| } else if (arg.startsWith("--seinfo=")) { |
| if (mSeInfoSpecified) { |
| throw new IllegalArgumentException( |
| "Duplicate arg specified"); |
| } |
| mSeInfoSpecified = true; |
| mSeInfo = getAssignmentValue(arg); |
| } else if (arg.startsWith("--capabilities=")) { |
| if (mCapabilitiesSpecified) { |
| throw new IllegalArgumentException( |
| "Duplicate arg specified"); |
| } |
| mCapabilitiesSpecified = true; |
| String capString = getAssignmentValue(arg); |
| |
| String[] capStrings = capString.split(",", 2); |
| |
| if (capStrings.length == 1) { |
| mEffectiveCapabilities = Long.decode(capStrings[0]); |
| mPermittedCapabilities = mEffectiveCapabilities; |
| } else { |
| mPermittedCapabilities = Long.decode(capStrings[0]); |
| mEffectiveCapabilities = Long.decode(capStrings[1]); |
| } |
| } else if (arg.startsWith("--rlimit=")) { |
| // Duplicate --rlimit arguments are specifically allowed. |
| String[] limitStrings = getAssignmentList(arg); |
| |
| if (limitStrings.length != 3) { |
| throw new IllegalArgumentException( |
| "--rlimit= should have 3 comma-delimited ints"); |
| } |
| int[] rlimitTuple = new int[limitStrings.length]; |
| |
| for (int i = 0; i < limitStrings.length; i++) { |
| rlimitTuple[i] = Integer.parseInt(limitStrings[i]); |
| } |
| |
| if (mRLimits == null) { |
| mRLimits = new ArrayList<>(); |
| } |
| |
| mRLimits.add(rlimitTuple); |
| } else if (arg.startsWith("--setgroups=")) { |
| if (mGids != null) { |
| throw new IllegalArgumentException( |
| "Duplicate arg specified"); |
| } |
| |
| String[] params = getAssignmentList(arg); |
| |
| mGids = new int[params.length]; |
| |
| for (int i = params.length - 1; i >= 0; i--) { |
| mGids[i] = Integer.parseInt(params[i]); |
| } |
| } else if (arg.equals("--invoke-with")) { |
| if (mInvokeWith != null) { |
| throw new IllegalArgumentException( |
| "Duplicate arg specified"); |
| } |
| try { |
| ++curArg; |
| mInvokeWith = args.nextArg(); |
| } catch (IndexOutOfBoundsException ex) { |
| throw new IllegalArgumentException( |
| "--invoke-with requires argument"); |
| } |
| } else if (arg.startsWith("--nice-name=")) { |
| if (mNiceName != null) { |
| throw new IllegalArgumentException( |
| "Duplicate arg specified"); |
| } |
| mNiceName = getAssignmentValue(arg); |
| } else if (arg.equals("--mount-external-default")) { |
| mMountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT; |
| } else if (arg.equals("--mount-external-installer")) { |
| mMountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER; |
| } else if (arg.equals("--mount-external-pass-through")) { |
| mMountExternal = Zygote.MOUNT_EXTERNAL_PASS_THROUGH; |
| } else if (arg.equals("--mount-external-android-writable")) { |
| mMountExternal = Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE; |
| } else if (arg.equals("--query-abi-list")) { |
| mAbiListQuery = true; |
| } else if (arg.equals("--get-pid")) { |
| mPidQuery = true; |
| } else if (arg.equals("--boot-completed")) { |
| mBootCompleted = true; |
| } else if (arg.startsWith("--instruction-set=")) { |
| mInstructionSet = getAssignmentValue(arg); |
| } else if (arg.startsWith("--app-data-dir=")) { |
| mAppDataDir = getAssignmentValue(arg); |
| } else if (arg.equals("--preload-app")) { |
| ++curArg; |
| mPreloadApp = args.nextArg(); |
| } else if (arg.equals("--preload-package")) { |
| curArg += 4; |
| mPreloadPackage = args.nextArg(); |
| mPreloadPackageLibs = args.nextArg(); |
| mPreloadPackageLibFileName = args.nextArg(); |
| mPreloadPackageCacheKey = args.nextArg(); |
| } else if (arg.equals("--preload-default")) { |
| mPreloadDefault = true; |
| expectRuntimeArgs = false; |
| } else if (arg.equals("--start-child-zygote")) { |
| mStartChildZygote = true; |
| } else if (arg.equals("--set-api-denylist-exemptions")) { |
| // consume all remaining args; this is a stand-alone command, never included |
| // with the regular fork command. |
| mApiDenylistExemptions = new String[argCount - curArg - 1]; |
| ++curArg; |
| for (int i = 0; curArg < argCount; ++curArg, ++i) { |
| mApiDenylistExemptions[i] = args.nextArg(); |
| } |
| expectRuntimeArgs = false; |
| } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) { |
| String rateStr = getAssignmentValue(arg); |
| try { |
| mHiddenApiAccessLogSampleRate = Integer.parseInt(rateStr); |
| } catch (NumberFormatException nfe) { |
| throw new IllegalArgumentException( |
| "Invalid log sampling rate: " + rateStr, nfe); |
| } |
| expectRuntimeArgs = false; |
| } else if (arg.startsWith("--hidden-api-statslog-sampling-rate=")) { |
| String rateStr = getAssignmentValue(arg); |
| try { |
| mHiddenApiAccessStatslogSampleRate = Integer.parseInt(rateStr); |
| } catch (NumberFormatException nfe) { |
| throw new IllegalArgumentException( |
| "Invalid statslog sampling rate: " + rateStr, nfe); |
| } |
| expectRuntimeArgs = false; |
| } else if (arg.startsWith("--package-name=")) { |
| if (mPackageName != null) { |
| throw new IllegalArgumentException("Duplicate arg specified"); |
| } |
| mPackageName = getAssignmentValue(arg); |
| } else if (arg.startsWith("--usap-pool-enabled=")) { |
| mUsapPoolStatusSpecified = true; |
| mUsapPoolEnabled = Boolean.parseBoolean(getAssignmentValue(arg)); |
| expectRuntimeArgs = false; |
| } else if (arg.startsWith(Zygote.START_AS_TOP_APP_ARG)) { |
| mIsTopApp = true; |
| } else if (arg.startsWith("--disabled-compat-changes=")) { |
| if (mDisabledCompatChanges != null) { |
| throw new IllegalArgumentException("Duplicate arg specified"); |
| } |
| final String[] params = getAssignmentList(arg); |
| final int length = params.length; |
| mDisabledCompatChanges = new long[length]; |
| for (int i = 0; i < length; i++) { |
| mDisabledCompatChanges[i] = Long.parseLong(params[i]); |
| } |
| } else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) { |
| mPkgDataInfoList = getAssignmentList(arg); |
| } else if (arg.startsWith(Zygote.ALLOWLISTED_DATA_INFO_MAP)) { |
| mAllowlistedDataInfoList = getAssignmentList(arg); |
| } else if (arg.equals(Zygote.BIND_MOUNT_APP_STORAGE_DIRS)) { |
| mBindMountAppStorageDirs = true; |
| } else if (arg.equals(Zygote.BIND_MOUNT_APP_DATA_DIRS)) { |
| mBindMountAppDataDirs = true; |
| } else if (arg.equals(Zygote.BIND_MOUNT_SYSPROP_OVERRIDES)) { |
| mBindMountSyspropOverrides = true; |
| } else { |
| unprocessedArg = arg; |
| break; |
| } |
| } |
| // curArg is the index of the first unprocessed argument. That argument is either referenced |
| // by unprocessedArg or not read yet. |
| |
| if (mBootCompleted) { |
| if (argCount > curArg) { |
| throw new IllegalArgumentException("Unexpected arguments after --boot-completed"); |
| } |
| } else if (mAbiListQuery || mPidQuery) { |
| if (argCount > curArg) { |
| throw new IllegalArgumentException("Unexpected arguments after --query-abi-list."); |
| } |
| } else if (mPreloadPackage != null) { |
| if (argCount > curArg) { |
| throw new IllegalArgumentException( |
| "Unexpected arguments after --preload-package."); |
| } |
| } else if (mPreloadApp != null) { |
| if (argCount > curArg) { |
| throw new IllegalArgumentException( |
| "Unexpected arguments after --preload-app."); |
| } |
| } else if (expectRuntimeArgs) { |
| if (!seenRuntimeArgs) { |
| throw new IllegalArgumentException("Unexpected argument : " |
| + (unprocessedArg == null ? args.nextArg() : unprocessedArg)); |
| } |
| |
| mRemainingArgs = new String[argCount - curArg]; |
| int i = 0; |
| if (unprocessedArg != null) { |
| mRemainingArgs[0] = unprocessedArg; |
| ++i; |
| } |
| for (; i < argCount - curArg; ++i) { |
| mRemainingArgs[i] = args.nextArg(); |
| } |
| } |
| |
| if (mStartChildZygote) { |
| boolean seenChildSocketArg = false; |
| for (String arg : mRemainingArgs) { |
| if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) { |
| seenChildSocketArg = true; |
| break; |
| } |
| } |
| if (!seenChildSocketArg) { |
| throw new IllegalArgumentException("--start-child-zygote specified " |
| + "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG); |
| } |
| } |
| } |
| |
| private static String getAssignmentValue(String arg) { |
| return arg.substring(arg.indexOf('=') + 1); |
| } |
| |
| private static String[] getAssignmentList(String arg) { |
| return getAssignmentValue(arg).split(","); |
| } |
| } |