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.annotation.Nullable;
23 import android.app.Activity;
24 import android.app.ActivityManager;
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.RemoteException;
36 import android.os.UserManager;
37 import android.util.Log;
38 
39 import java.util.Arrays;
40 
41 /**
42  * Select which activity is the first visible activity of the installation and forward the intent to
43  * it.
44  */
45 public class InstallStart extends Activity {
46     private static final String LOG_TAG = InstallStart.class.getSimpleName();
47 
48     private static final String DOWNLOADS_AUTHORITY = "downloads";
49     private PackageManager mPackageManager;
50     private UserManager mUserManager;
51     private boolean mAbortInstall = false;
52 
53     @Override
onCreate(@ullable Bundle savedInstanceState)54     protected void onCreate(@Nullable Bundle savedInstanceState) {
55         super.onCreate(savedInstanceState);
56         mPackageManager = getPackageManager();
57         mUserManager = getSystemService(UserManager.class);
58         Intent intent = getIntent();
59         String callingPackage = getCallingPackage();
60         String callingAttributionTag = null;
61 
62         final boolean isSessionInstall =
63                 PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
64 
65         // If the activity was started via a PackageInstaller session, we retrieve the calling
66         // package from that session
67         final int sessionId = (isSessionInstall
68                 ? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1)
69                 : -1);
70         if (callingPackage == null && sessionId != -1) {
71             PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
72             PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
73             callingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
74             callingAttributionTag =
75                     (sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : null;
76         }
77 
78         final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
79         final int originatingUid = getOriginatingUid(sourceInfo);
80         boolean isTrustedSource = false;
81         if (sourceInfo != null
82                 && (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
83             isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
84         }
85 
86         if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
87             final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
88             if (targetSdkVersion < 0) {
89                 Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
90                 // Invalid originating uid supplied. Abort install.
91                 mAbortInstall = true;
92             } else if (targetSdkVersion >= Build.VERSION_CODES.O && !isUidRequestingPermission(
93                     originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
94                 Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
95                         + Manifest.permission.REQUEST_INSTALL_PACKAGES);
96                 mAbortInstall = true;
97             }
98         }
99         if (mAbortInstall) {
100             setResult(RESULT_CANCELED);
101             finish();
102             return;
103         }
104 
105         Intent nextActivity = new Intent(intent);
106         nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
107                 | Intent.FLAG_GRANT_READ_URI_PERMISSION);
108 
109         // The the installation source as the nextActivity thinks this activity is the source, hence
110         // set the originating UID and sourceInfo explicitly
111         nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
112         nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_ATTRIBUTION_TAG,
113                 callingAttributionTag);
114         nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
115         nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
116 
117         if (isSessionInstall) {
118             nextActivity.setClass(this, PackageInstallerActivity.class);
119         } else {
120             Uri packageUri = intent.getData();
121 
122             if (packageUri != null && packageUri.getScheme().equals(
123                     ContentResolver.SCHEME_CONTENT)) {
124                 // [IMPORTANT] This path is deprecated, but should still work. Only necessary
125                 // features should be added.
126 
127                 // Copy file to prevent it from being changed underneath this process
128                 nextActivity.setClass(this, InstallStaging.class);
129             } else if (packageUri != null && packageUri.getScheme().equals(
130                     PackageInstallerActivity.SCHEME_PACKAGE)) {
131                 nextActivity.setClass(this, PackageInstallerActivity.class);
132             } else {
133                 Intent result = new Intent();
134                 result.putExtra(Intent.EXTRA_INSTALL_RESULT,
135                         PackageManager.INSTALL_FAILED_INVALID_URI);
136                 setResult(RESULT_FIRST_USER, result);
137 
138                 nextActivity = null;
139             }
140         }
141 
142         if (nextActivity != null) {
143             startActivity(nextActivity);
144         }
145         finish();
146     }
147 
isUidRequestingPermission(int uid, String permission)148     private boolean isUidRequestingPermission(int uid, String permission) {
149         final String[] packageNames = mPackageManager.getPackagesForUid(uid);
150         if (packageNames == null) {
151             return false;
152         }
153         for (final String packageName : packageNames) {
154             final PackageInfo packageInfo;
155             try {
156                 packageInfo = mPackageManager.getPackageInfo(packageName,
157                         PackageManager.GET_PERMISSIONS);
158             } catch (PackageManager.NameNotFoundException e) {
159                 // Ignore and try the next package
160                 continue;
161             }
162             if (packageInfo.requestedPermissions != null
163                     && Arrays.asList(packageInfo.requestedPermissions).contains(permission)) {
164                 return true;
165             }
166         }
167         return false;
168     }
169 
170     /**
171      * @return the ApplicationInfo for the installation source (the calling package), if available
172      */
getSourceInfo(@ullable String callingPackage)173     private ApplicationInfo getSourceInfo(@Nullable String callingPackage) {
174         if (callingPackage != null) {
175             try {
176                 return getPackageManager().getApplicationInfo(callingPackage, 0);
177             } catch (PackageManager.NameNotFoundException ex) {
178                 // ignore
179             }
180         }
181         return null;
182     }
183 
184     /**
185      * Get the originating uid if possible, or
186      * {@link android.content.pm.PackageInstaller.SessionParams#UID_UNKNOWN} if not available
187      *
188      * @param sourceInfo The source of this installation
189      * @return The UID of the installation source or UID_UNKNOWN
190      */
getOriginatingUid(@ullable ApplicationInfo sourceInfo)191     private int getOriginatingUid(@Nullable ApplicationInfo sourceInfo) {
192         // The originating uid from the intent. We only trust/use this if it comes from either
193         // the document manager app or the downloads provider
194         final int uidFromIntent = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
195                 PackageInstaller.SessionParams.UID_UNKNOWN);
196 
197         final int callingUid;
198         if (sourceInfo != null) {
199             callingUid = sourceInfo.uid;
200         } else {
201             try {
202                 callingUid = ActivityManager.getService()
203                         .getLaunchedFromUid(getActivityToken());
204             } catch (RemoteException ex) {
205                 // Cannot reach ActivityManager. Aborting install.
206                 Log.e(LOG_TAG, "Could not determine the launching uid.");
207                 mAbortInstall = true;
208                 return PackageInstaller.SessionParams.UID_UNKNOWN;
209             }
210         }
211         if (checkPermission(Manifest.permission.MANAGE_DOCUMENTS, -1, callingUid)
212                 == PackageManager.PERMISSION_GRANTED) {
213             return uidFromIntent;
214         }
215         if (isSystemDownloadsProvider(callingUid)) {
216             return uidFromIntent;
217         }
218         // We don't trust uid from the intent. Use the calling uid instead.
219         return callingUid;
220     }
221 
isSystemDownloadsProvider(int uid)222     private boolean isSystemDownloadsProvider(int uid) {
223         final ProviderInfo downloadProviderPackage = getPackageManager().resolveContentProvider(
224                 DOWNLOADS_AUTHORITY, 0);
225         if (downloadProviderPackage == null) {
226             // There seems to be no currently enabled downloads provider on the system.
227             return false;
228         }
229         final ApplicationInfo appInfo = downloadProviderPackage.applicationInfo;
230         return (appInfo.isSystemApp() && uid == appInfo.uid);
231     }
232 }
233