/* * 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: * */ 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 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(","); } }