| /* |
| * 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.util.ArrayList; |
| import java.util.Arrays; |
| |
| /** |
| * 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 blacklisting. These are sent to the pre-forked zygote at boot time, or |
| * when they change, via --set-api-blacklist-exemptions. |
| */ |
| String[] mApiBlacklistExemptions; |
| |
| /** |
| * 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; |
| |
| /** |
| * Constructs instance and parses args |
| * |
| * @param args zygote command-line args |
| */ |
| ZygoteArguments(String[] args) throws IllegalArgumentException { |
| parseArgs(args); |
| } |
| |
| /** |
| * Parses the commandline arguments intended for the Zygote spawner (such as "--setuid=" and |
| * "--setgid=") and creates an array containing the remaining args. |
| * |
| * Per security review bug #1112214, duplicate args are disallowed in critical cases to make |
| * injection harder. |
| */ |
| private void parseArgs(String[] args) throws IllegalArgumentException { |
| /* |
| * 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. |
| */ |
| |
| int curArg = 0; |
| |
| boolean seenRuntimeArgs = false; |
| |
| boolean expectRuntimeArgs = true; |
| for ( /* curArg */ ; curArg < args.length; curArg++) { |
| String arg = args[curArg]; |
| |
| 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 { |
| mInvokeWith = args[++curArg]; |
| } 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-read")) { |
| mMountExternal = Zygote.MOUNT_EXTERNAL_READ; |
| } else if (arg.equals("--mount-external-write")) { |
| mMountExternal = Zygote.MOUNT_EXTERNAL_WRITE; |
| } else if (arg.equals("--mount-external-full")) { |
| mMountExternal = Zygote.MOUNT_EXTERNAL_FULL; |
| } else if (arg.equals("--mount-external-installer")) { |
| mMountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER; |
| } else if (arg.equals("--mount-external-legacy")) { |
| mMountExternal = Zygote.MOUNT_EXTERNAL_LEGACY; |
| } else if (arg.equals("--mount-external-pass-through")) { |
| mMountExternal = Zygote.MOUNT_EXTERNAL_PASS_THROUGH; |
| } 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")) { |
| mPreloadApp = args[++curArg]; |
| } else if (arg.equals("--preload-package")) { |
| mPreloadPackage = args[++curArg]; |
| mPreloadPackageLibs = args[++curArg]; |
| mPreloadPackageLibFileName = args[++curArg]; |
| mPreloadPackageCacheKey = args[++curArg]; |
| } 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-blacklist-exemptions")) { |
| // consume all remaining args; this is a stand-alone command, never included |
| // with the regular fork command. |
| mApiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length); |
| curArg = args.length; |
| 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 { |
| break; |
| } |
| } |
| |
| if (mBootCompleted) { |
| if (args.length - curArg > 0) { |
| throw new IllegalArgumentException("Unexpected arguments after --boot-completed"); |
| } |
| } else if (mAbiListQuery || mPidQuery) { |
| if (args.length - curArg > 0) { |
| throw new IllegalArgumentException("Unexpected arguments after --query-abi-list."); |
| } |
| } else if (mPreloadPackage != null) { |
| if (args.length - curArg > 0) { |
| throw new IllegalArgumentException( |
| "Unexpected arguments after --preload-package."); |
| } |
| } else if (mPreloadApp != null) { |
| if (args.length - curArg > 0) { |
| throw new IllegalArgumentException( |
| "Unexpected arguments after --preload-app."); |
| } |
| } else if (expectRuntimeArgs) { |
| if (!seenRuntimeArgs) { |
| throw new IllegalArgumentException("Unexpected argument : " + args[curArg]); |
| } |
| |
| mRemainingArgs = new String[args.length - curArg]; |
| System.arraycopy(args, curArg, mRemainingArgs, 0, mRemainingArgs.length); |
| } |
| |
| 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(","); |
| } |
| } |