/* * Copyright (C) 2018 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.server.am; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.view.Display.INVALID_DISPLAY; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.TaskRecord.INVALID_TASK_ID; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.PendingIntent; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Binder; import android.os.Bundle; import android.os.Process; import android.os.UserHandle; import android.util.Slog; import android.view.RemoteAnimationAdapter; import com.android.internal.annotations.VisibleForTesting; /** * Wraps {@link ActivityOptions}, records binder identity, and checks permission when retrieving * the inner options. Also supports having two set of options: Once from the original caller, and * once from the caller that is overriding it, which happens when sending a {@link PendingIntent}. */ class SafeActivityOptions { private static final String TAG = TAG_WITH_CLASS_NAME ? "SafeActivityOptions" : TAG_AM; private final int mOriginalCallingPid; private final int mOriginalCallingUid; private int mRealCallingPid; private int mRealCallingUid; private final @Nullable ActivityOptions mOriginalOptions; private @Nullable ActivityOptions mCallerOptions; /** * Constructs a new instance from a bundle and records {@link Binder#getCallingPid}/ * {@link Binder#getCallingUid}. Thus, calling identity MUST NOT be cleared when constructing * this object. * * @param bOptions The {@link ActivityOptions} as {@link Bundle}. */ static SafeActivityOptions fromBundle(Bundle bOptions) { return bOptions != null ? new SafeActivityOptions(ActivityOptions.fromBundle(bOptions)) : null; } /** * Constructs a new instance and records {@link Binder#getCallingPid}/ * {@link Binder#getCallingUid}. Thus, calling identity MUST NOT be cleared when constructing * this object. * * @param options The options to wrap. */ SafeActivityOptions(@Nullable ActivityOptions options) { mOriginalCallingPid = Binder.getCallingPid(); mOriginalCallingUid = Binder.getCallingUid(); mOriginalOptions = options; } /** * Overrides options with options from a caller and records {@link Binder#getCallingPid}/ * {@link Binder#getCallingUid}. Thus, calling identity MUST NOT be cleared when calling this * method. */ void setCallerOptions(@Nullable ActivityOptions options) { mRealCallingPid = Binder.getCallingPid(); mRealCallingUid = Binder.getCallingUid(); mCallerOptions = options; } /** * Performs permission check and retrieves the options. * * @param r The record of the being started activity. */ ActivityOptions getOptions(ActivityRecord r) throws SecurityException { return getOptions(r.intent, r.info, r.app, r.mStackSupervisor); } /** * Performs permission check and retrieves the options when options are not being used to launch * a specific activity (i.e. a task is moved to front). */ ActivityOptions getOptions(ActivityStackSupervisor supervisor) throws SecurityException { return getOptions(null, null, null, supervisor); } /** * Performs permission check and retrieves the options. * * @param intent The intent that is being launched. * @param aInfo The info of the activity being launched. * @param callerApp The record of the caller. */ ActivityOptions getOptions(@Nullable Intent intent, @Nullable ActivityInfo aInfo, @Nullable ProcessRecord callerApp, ActivityStackSupervisor supervisor) throws SecurityException { if (mOriginalOptions != null) { checkPermissions(intent, aInfo, callerApp, supervisor, mOriginalOptions, mOriginalCallingPid, mOriginalCallingUid); setCallingPidForRemoteAnimationAdapter(mOriginalOptions, mOriginalCallingPid); } if (mCallerOptions != null) { checkPermissions(intent, aInfo, callerApp, supervisor, mCallerOptions, mRealCallingPid, mRealCallingUid); setCallingPidForRemoteAnimationAdapter(mCallerOptions, mRealCallingPid); } return mergeActivityOptions(mOriginalOptions, mCallerOptions); } private void setCallingPidForRemoteAnimationAdapter(ActivityOptions options, int callingPid) { final RemoteAnimationAdapter adapter = options.getRemoteAnimationAdapter(); if (adapter == null) { return; } if (callingPid == Process.myPid()) { Slog.wtf(TAG, "Safe activity options constructed after clearing calling id"); return; } adapter.setCallingPid(callingPid); } /** * @see ActivityOptions#popAppVerificationBundle */ Bundle popAppVerificationBundle() { return mOriginalOptions != null ? mOriginalOptions.popAppVerificationBundle() : null; } private void abort() { if (mOriginalOptions != null) { ActivityOptions.abort(mOriginalOptions); } if (mCallerOptions != null) { ActivityOptions.abort(mCallerOptions); } } static void abort(@Nullable SafeActivityOptions options) { if (options != null) { options.abort(); } } /** * Merges two activity options into one, with {@code options2} taking precedence in case of a * conflict. */ @VisibleForTesting @Nullable ActivityOptions mergeActivityOptions(@Nullable ActivityOptions options1, @Nullable ActivityOptions options2) { if (options1 == null) { return options2; } if (options2 == null) { return options1; } final Bundle b1 = options1.toBundle(); final Bundle b2 = options2.toBundle(); b1.putAll(b2); return ActivityOptions.fromBundle(b1); } private void checkPermissions(@Nullable Intent intent, @Nullable ActivityInfo aInfo, @Nullable ProcessRecord callerApp, ActivityStackSupervisor supervisor, ActivityOptions options, int callingPid, int callingUid) { // If a launch task id is specified, then ensure that the caller is the recents // component or has the START_TASKS_FROM_RECENTS permission if (options.getLaunchTaskId() != INVALID_TASK_ID && !supervisor.mRecentTasks.isCallerRecents(callingUid)) { final int startInTaskPerm = supervisor.mService.checkPermission( START_TASKS_FROM_RECENTS, callingPid, callingUid); if (startInTaskPerm == PERMISSION_DENIED) { final String msg = "Permission Denial: starting " + getIntentString(intent) + " from " + callerApp + " (pid=" + callingPid + ", uid=" + callingUid + ") with launchTaskId=" + options.getLaunchTaskId(); Slog.w(TAG, msg); throw new SecurityException(msg); } } // Check if someone tries to launch an activity on a private display with a different // owner. final int launchDisplayId = options.getLaunchDisplayId(); if (aInfo != null && launchDisplayId != INVALID_DISPLAY && !supervisor.isCallerAllowedToLaunchOnDisplay(callingPid, callingUid, launchDisplayId, aInfo)) { final String msg = "Permission Denial: starting " + getIntentString(intent) + " from " + callerApp + " (pid=" + callingPid + ", uid=" + callingUid + ") with launchDisplayId=" + launchDisplayId; Slog.w(TAG, msg); throw new SecurityException(msg); } // Check if someone tries to launch an unwhitelisted activity into LockTask mode. final boolean lockTaskMode = options.getLockTaskMode(); if (aInfo != null && lockTaskMode && !supervisor.mService.getLockTaskController().isPackageWhitelisted( UserHandle.getUserId(callingUid), aInfo.packageName)) { final String msg = "Permission Denial: starting " + getIntentString(intent) + " from " + callerApp + " (pid=" + callingPid + ", uid=" + callingUid + ") with lockTaskMode=true"; Slog.w(TAG, msg); throw new SecurityException(msg); } // Check permission for remote animations final RemoteAnimationAdapter adapter = options.getRemoteAnimationAdapter(); if (adapter != null && supervisor.mService.checkPermission( CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, callingPid, callingUid) != PERMISSION_GRANTED) { final String msg = "Permission Denial: starting " + getIntentString(intent) + " from " + callerApp + " (pid=" + callingPid + ", uid=" + callingUid + ") with remoteAnimationAdapter"; Slog.w(TAG, msg); throw new SecurityException(msg); } } private String getIntentString(Intent intent) { return intent != null ? intent.toString() : "(no intent)"; } }