summaryrefslogtreecommitdiff
path: root/com/android/server/am/SafeActivityOptions.java
blob: ac6f01fa855f3c5714760640ffa0f67875ab763c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/*
 * 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.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);
            if (mOriginalOptions.getRemoteAnimationAdapter() != null) {
                mOriginalOptions.getRemoteAnimationAdapter().setCallingPid(mOriginalCallingPid);
            }
        }
        if (mCallerOptions != null) {
            checkPermissions(intent, aInfo, callerApp, supervisor, mCallerOptions,
                    mRealCallingPid, mRealCallingUid);
            if (mCallerOptions.getRemoteAnimationAdapter() != null) {
                mCallerOptions.getRemoteAnimationAdapter().setCallingPid(mRealCallingPid);
            }
        }
        return mergeActivityOptions(mOriginalOptions, mCallerOptions);
    }

    /**
     * @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.mLockTaskController.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)";
    }
}