1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *       http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  */
16 
17 package com.android.packageinstaller;
18 
19 import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid;
20 
21 import android.Manifest;
22 import android.app.Activity;
23 import android.app.DialogFragment;
24 import android.app.admin.DevicePolicyManager;
25 import android.content.ContentResolver;
26 import android.content.Intent;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageInstaller;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ProviderInfo;
32 import android.net.Uri;
33 import android.os.Build;
34 import android.os.Bundle;
35 import android.os.Process;
36 import android.os.UserManager;
37 import android.text.TextUtils;
38 import android.util.EventLog;
39 import android.util.Log;
40 
41 import androidx.annotation.NonNull;
42 import androidx.annotation.Nullable;
43 
44 import java.util.Arrays;
45 
46 /**
47  * Select which activity is the first visible activity of the installation and forward the intent to
48  * it.
49  */
50 public class InstallStart extends Activity {
51     private static final String TAG = InstallStart.class.getSimpleName();
52 
53     private static final String DOWNLOADS_AUTHORITY = "downloads";
54 
55     private PackageManager mPackageManager;
56     private UserManager mUserManager;
57     private boolean mAbortInstall = false;
58     private boolean mShouldFinish = true;
59 
60     private final boolean mLocalLOGV = false;
61 
62     @Override
onCreate(@ullable Bundle savedInstanceState)63     protected void onCreate(@Nullable Bundle savedInstanceState) {
64         super.onCreate(savedInstanceState);
65         mPackageManager = getPackageManager();
66         mUserManager = getSystemService(UserManager.class);
67 
68         Intent intent = getIntent();
69         String callingPackage = getCallingPackage();
70         String callingAttributionTag = null;
71 
72         final boolean isSessionInstall =
73                 PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction())
74                         || PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
75 
76         // If the activity was started via a PackageInstaller session, we retrieve the calling
77         // package from that session
78         final int sessionId = (isSessionInstall
79                 ? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1)
80                 : -1);
81         if (callingPackage == null && sessionId != -1) {
82             PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
83             PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
84             callingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
85             callingAttributionTag =
86                     (sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : null;
87         }
88 
89         final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
90         // Uid of the source package, coming from ActivityManager
91         int callingUid = getLaunchedFromUid();
92         if (callingUid == Process.INVALID_UID) {
93             Log.e(TAG, "Could not determine the launching uid.");
94         }
95         // Uid of the source package, with a preference to uid from ApplicationInfo
96         final int originatingUid = sourceInfo != null ? sourceInfo.uid : callingUid;
97 
98         if (callingUid == Process.INVALID_UID && sourceInfo == null) {
99             mAbortInstall = true;
100         }
101 
102         boolean isDocumentsManager = checkPermission(Manifest.permission.MANAGE_DOCUMENTS,
103                 -1, callingUid) == PackageManager.PERMISSION_GRANTED;
104         boolean isTrustedSource = false;
105         if (sourceInfo != null && sourceInfo.isPrivilegedApp()) {
106             isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false) || (
107                     originatingUid != Process.INVALID_UID && checkPermission(
108                             Manifest.permission.INSTALL_PACKAGES, -1 /* pid */, originatingUid)
109                             == PackageManager.PERMISSION_GRANTED);
110         }
111 
112         if (!isTrustedSource && !isSystemDownloadsProvider(callingUid) && !isDocumentsManager
113                 && originatingUid != Process.INVALID_UID) {
114             final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
115             if (targetSdkVersion < 0) {
116                 Log.w(TAG, "Cannot get target sdk version for uid " + originatingUid);
117                 // Invalid originating uid supplied. Abort install.
118                 mAbortInstall = true;
119             } else if (targetSdkVersion >= Build.VERSION_CODES.O && !isUidRequestingPermission(
120                     originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
121                 Log.e(TAG, "Requesting uid " + originatingUid + " needs to declare permission "
122                         + Manifest.permission.REQUEST_INSTALL_PACKAGES);
123                 mAbortInstall = true;
124             }
125         }
126 
127         if (sessionId != -1 && !isCallerSessionOwner(originatingUid, sessionId)) {
128             mAbortInstall = true;
129         }
130 
131         checkDevicePolicyRestrictions();
132 
133         final String installerPackageNameFromIntent = getIntent().getStringExtra(
134                 Intent.EXTRA_INSTALLER_PACKAGE_NAME);
135         if (installerPackageNameFromIntent != null) {
136             final String callingPkgName = getLaunchedFromPackage();
137             if (!TextUtils.equals(installerPackageNameFromIntent, callingPkgName)
138                     && mPackageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES,
139                     callingPkgName) != PackageManager.PERMISSION_GRANTED) {
140                 Log.e(TAG, "The given installer package name " + installerPackageNameFromIntent
141                         + " is invalid. Remove it.");
142                 EventLog.writeEvent(0x534e4554, "236687884", getLaunchedFromUid(),
143                         "Invalid EXTRA_INSTALLER_PACKAGE_NAME");
144                 getIntent().removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
145             }
146         }
147 
148         if (mAbortInstall) {
149             setResult(RESULT_CANCELED);
150             if (mShouldFinish) {
151                 finish();
152             }
153             return;
154         }
155 
156         Intent nextActivity = new Intent(intent);
157         nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
158                 | Intent.FLAG_GRANT_READ_URI_PERMISSION);
159 
160         // The the installation source as the nextActivity thinks this activity is the source, hence
161         // set the originating UID and sourceInfo explicitly
162         nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
163         nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_ATTRIBUTION_TAG,
164                 callingAttributionTag);
165         nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
166         nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
167 
168         if (isSessionInstall) {
169             nextActivity.setClass(this, PackageInstallerActivity.class);
170         } else {
171             Uri packageUri = intent.getData();
172 
173             if (packageUri != null
174                     && packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)
175                     && canPackageQuery(callingUid, packageUri)) {
176                 // [IMPORTANT] This path is deprecated, but should still work. Only necessary
177                 // features should be added.
178 
179                 // Stage a session with this file to prevent it from being changed underneath
180                 // this process.
181                 nextActivity.setClass(this, InstallStaging.class);
182             } else if (packageUri != null && PackageInstallerActivity.SCHEME_PACKAGE.equals(
183                     packageUri.getScheme())) {
184                 nextActivity.setClass(this, PackageInstallerActivity.class);
185             } else {
186                 Intent result = new Intent();
187                 result.putExtra(Intent.EXTRA_INSTALL_RESULT,
188                         PackageManager.INSTALL_FAILED_INVALID_URI);
189                 setResult(RESULT_FIRST_USER, result);
190 
191                 nextActivity = null;
192             }
193         }
194 
195         if (nextActivity != null) {
196             try {
197                 startActivity(nextActivity);
198             } catch (SecurityException e) {
199                 Intent result = new Intent();
200                 result.putExtra(Intent.EXTRA_INSTALL_RESULT,
201                         PackageManager.INSTALL_FAILED_INVALID_URI);
202                 setResult(RESULT_FIRST_USER, result);
203             }
204         }
205         finish();
206     }
207 
isUidRequestingPermission(int uid, String permission)208     private boolean isUidRequestingPermission(int uid, String permission) {
209         final String[] packageNames = mPackageManager.getPackagesForUid(uid);
210         if (packageNames == null) {
211             return false;
212         }
213         for (final String packageName : packageNames) {
214             final PackageInfo packageInfo;
215             try {
216                 packageInfo = mPackageManager.getPackageInfo(packageName,
217                         PackageManager.GET_PERMISSIONS);
218             } catch (PackageManager.NameNotFoundException e) {
219                 // Ignore and try the next package
220                 continue;
221             }
222             if (packageInfo.requestedPermissions != null
223                     && Arrays.asList(packageInfo.requestedPermissions).contains(permission)) {
224                 return true;
225             }
226         }
227         return false;
228     }
229 
230     /**
231      * @return the ApplicationInfo for the installation source (the calling package), if available
232      */
getSourceInfo(@ullable String callingPackage)233     private ApplicationInfo getSourceInfo(@Nullable String callingPackage) {
234         if (callingPackage != null) {
235             try {
236                 return getPackageManager().getApplicationInfo(callingPackage, 0);
237             } catch (PackageManager.NameNotFoundException ex) {
238                 // ignore
239             }
240         }
241         return null;
242     }
243 
isSystemDownloadsProvider(int uid)244     private boolean isSystemDownloadsProvider(int uid) {
245         final ProviderInfo downloadProviderPackage = getPackageManager().resolveContentProvider(
246                 DOWNLOADS_AUTHORITY, 0);
247         if (downloadProviderPackage == null) {
248             // There seems to be no currently enabled downloads provider on the system.
249             return false;
250         }
251         final ApplicationInfo appInfo = downloadProviderPackage.applicationInfo;
252         return ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
253                 && uid == appInfo.uid);
254     }
255 
256     @NonNull
canPackageQuery(int callingUid, Uri packageUri)257     private boolean canPackageQuery(int callingUid, Uri packageUri) {
258         ProviderInfo info = mPackageManager.resolveContentProvider(packageUri.getAuthority(),
259                 PackageManager.ComponentInfoFlags.of(0));
260         if (info == null) {
261             return false;
262         }
263         String targetPackage = info.packageName;
264 
265         String[] callingPackages = mPackageManager.getPackagesForUid(callingUid);
266         if (callingPackages == null) {
267             return false;
268         }
269         for (String callingPackage: callingPackages) {
270             try {
271                 if (mPackageManager.canPackageQuery(callingPackage, targetPackage)) {
272                     return true;
273                 }
274             } catch (PackageManager.NameNotFoundException e) {
275                 // no-op
276             }
277         }
278         return false;
279     }
280 
isCallerSessionOwner(int originatingUid, int sessionId)281     private boolean isCallerSessionOwner(int originatingUid, int sessionId) {
282         if (originatingUid == Process.ROOT_UID) {
283             return true;
284         }
285         PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
286         PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
287         if (sessionInfo == null) {
288             return false;
289         }
290         int installerUid = sessionInfo.getInstallerUid();
291         return originatingUid == installerUid;
292     }
293 
checkDevicePolicyRestrictions()294     private void checkDevicePolicyRestrictions() {
295         final String[] restrictions = new String[] {
296             UserManager.DISALLOW_INSTALL_APPS,
297             UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
298             UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
299         };
300 
301         final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
302         for (String restriction : restrictions) {
303             if (!mUserManager.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
304                 continue;
305             }
306 
307             mAbortInstall = true;
308 
309             // If the given restriction is set by an admin, display information about the
310             // admin enforcing the restriction for the affected user. If not enforced by the admin,
311             // show the system dialog.
312             final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction);
313             if (showAdminSupportDetailsIntent != null) {
314                 if (mLocalLOGV) Log.i(TAG, "starting " + showAdminSupportDetailsIntent);
315                 startActivity(showAdminSupportDetailsIntent);
316             } else {
317                 if (mLocalLOGV) Log.i(TAG, "Restriction set by system: " + restriction);
318                 mShouldFinish = false;
319                 showDialogInner(restriction);
320             }
321             break;
322         }
323     }
324 
325     /**
326      * Replace any dialog shown by the dialog with the one for the given
327      * {@link #createDialog(String)}.
328      *
329      * @param restriction The restriction to create the dialog for
330      */
showDialogInner(String restriction)331     private void showDialogInner(String restriction) {
332         if (mLocalLOGV) Log.i(TAG, "showDialogInner(" + restriction + ")");
333         DialogFragment currentDialog =
334                 (DialogFragment) getFragmentManager().findFragmentByTag("dialog");
335         if (currentDialog != null) {
336             currentDialog.dismissAllowingStateLoss();
337         }
338 
339         DialogFragment newDialog = createDialog(restriction);
340         if (newDialog != null) {
341             getFragmentManager().beginTransaction()
342                     .add(newDialog, "dialog").commitAllowingStateLoss();
343         }
344     }
345 
346     /**
347      * Create a new dialog.
348      *
349      * @param restriction The restriction to create the dialog for
350      * @return The dialog
351      */
createDialog(String restriction)352     private DialogFragment createDialog(String restriction) {
353         if (mLocalLOGV) Log.i(TAG, "createDialog(" + restriction + ")");
354         switch (restriction) {
355             case UserManager.DISALLOW_INSTALL_APPS:
356                 return PackageUtil.SimpleErrorDialog.newInstance(
357                         R.string.install_apps_user_restriction_dlg_text);
358             case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES:
359             case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY:
360                 return PackageUtil.SimpleErrorDialog.newInstance(
361                         R.string.unknown_apps_user_restriction_dlg_text);
362         }
363         return null;
364     }
365 }
366