Preventing a shortcut which requires permissions from being added to
homescreen

A shortcut can be added by any app as INSTALL_SHORTCUT is a normal
level permission. But the intent is actually launched by the launcher
app which can have other permission as well.

> When adding a shortcut from the broadcast, verify that the intent does
not require any permission
> When adding a shortcut using the two-step drop process, verify that
the source app also has the permission to create such a shortcut

Bug: 30778130
Change-Id: I710a490d69019dc25709db5a97020c20d9325007
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index df87cc2..d8e58d8 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Thunk;
 
 import org.json.JSONException;
@@ -146,6 +147,15 @@
         }
         PendingInstallShortcutInfo info = createPendingInfo(context, data);
         if (info != null) {
+            if (!info.isLauncherActivity()) {
+                // Since its a custom shortcut, verify that it is safe to launch.
+                if (!PackageManagerHelper.hasPermissionForActivity(
+                        context, info.launchIntent, null)) {
+                    // Target cannot be launched, or requires some special permission to launch
+                    Log.e(TAG, "Ignoring malicious intent " + info.launchIntent.toUri(0));
+                    return;
+                }
+            }
             queuePendingShortcutInfo(info, context);
         }
     }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 901900e..07c2903 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -652,7 +652,7 @@
 
         switch (requestCode) {
             case REQUEST_CREATE_SHORTCUT:
-                completeAddShortcut(intent, info.container, screenId, info.cellX, info.cellY);
+                completeAddShortcut(intent, info.container, screenId, info.cellX, info.cellY, info);
                 break;
             case REQUEST_CREATE_APPWIDGET:
                 completeAddAppWidget(appWidgetId, info, null, null);
@@ -1456,12 +1456,19 @@
      * @param data The intent describing the shortcut.
      */
     private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
-            int cellY) {
+            int cellY, PendingRequestArgs args) {
         int[] cellXY = mTmpAddItemCellCoordinates;
         CellLayout layout = getCellLayout(container, screenId);
 
         ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(this, data);
-        if (info == null) {
+        if (info == null || args.getRequestCode() != REQUEST_CREATE_SHORTCUT ||
+                args.getPendingIntent().getComponent() == null) {
+            return;
+        }
+        if (!PackageManagerHelper.hasPermissionForActivity(
+                this, info.intent, args.getPendingIntent().getComponent().getPackageName())) {
+            // The app is trying to add a shortcut without sufficient permissions
+            Log.e(TAG, "Ignoring malicious intent " + info.intent.toUri(0));
             return;
         }
         final View view = createShortcut(info);
@@ -2178,10 +2185,9 @@
      * Process a shortcut drop.
      */
     private void processShortcutFromDrop(PendingAddItemInfo info) {
-        setWaitingForResult(new PendingRequestArgs(info));
-        Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
-        createShortcutIntent.setComponent(info.componentName);
-        Utilities.startActivityForResultSafely(this, createShortcutIntent, REQUEST_CREATE_SHORTCUT);
+        Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(info.componentName);
+        setWaitingForResult(PendingRequestArgs.forIntent(REQUEST_CREATE_SHORTCUT, intent, info));
+        Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
     }
 
     /**
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 3c4c79a..3e15d05 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -16,10 +16,15 @@
 
 package com.android.launcher3.util;
 
+import android.app.AppOpsManager;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.text.TextUtils;
 
 import com.android.launcher3.Utilities;
 
@@ -96,4 +101,53 @@
         }
         return excludePackages.get(0);
     }
+
+    /**
+     * Returns true if {@param srcPackage} has the permission required to start the activity from
+     * {@param intent}. If {@param srcPackage} is null, then the activity should not need
+     * any permissions
+     */
+    public static boolean hasPermissionForActivity(Context context, Intent intent,
+            String srcPackage) {
+        PackageManager pm = context.getPackageManager();
+        ResolveInfo target = pm.resolveActivity(intent, 0);
+        if (target == null) {
+            // Not a valid target
+            return false;
+        }
+        if (TextUtils.isEmpty(target.activityInfo.permission)) {
+            // No permission is needed
+            return true;
+        }
+        if (TextUtils.isEmpty(srcPackage)) {
+            // The activity requires some permission but there is no source.
+            return false;
+        }
+
+        // Source does not have sufficient permissions.
+        if(pm.checkPermission(target.activityInfo.permission, srcPackage) !=
+                PackageManager.PERMISSION_GRANTED) {
+            return false;
+        }
+
+        if (!Utilities.ATLEAST_MARSHMALLOW) {
+            // These checks are sufficient for below M devices.
+            return true;
+        }
+
+        // On M and above also check AppOpsManager for compatibility mode permissions.
+        if (TextUtils.isEmpty(AppOpsManager.permissionToOp(target.activityInfo.permission))) {
+            // There is no app-op for this permission, which could have been disabled.
+            return true;
+        }
+
+        // There is no direct way to check if the app-op is allowed for a particular app. Since
+        // app-op is only enabled for apps running in compatibility mode, simply block such apps.
+
+        try {
+            return pm.getApplicationInfo(srcPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M;
+        } catch (NameNotFoundException e) { }
+
+        return false;
+    }
 }