From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Manjeet Rulhania <mrulhania@google.com>
Date: Thu, 28 Apr 2022 20:23:58 +0000
Subject: [PATCH] Fix duplicate permission privilege escalation

Duplicate permissions definition with different group allows
privilege permission escalation to a different permission group.

Android studio and gradle plugin does not allow duplicate
permissions with different attributes, these tools only allow
if duplicate permissions are exact copies.

Also platform stores permissions in map at multiple places with
permission name as key. This suggests that we can disallow
duplicate permissions during package install/update.

Bug: 213323615
Test: manual
Change-Id: I6f44e740897305e7a0553c1cf6c3af37faf02a2e
Merged-In: I1910dca44104e35a57eba4acfa8188cd9b8626ac
Merged-In: I34120fff2ec2a158dfa55779d2afd4bbd49487ff
Merged-In: I9bc839836786a0876e67fd73c05f8944bb532249
(cherry picked from commit 31bd425bb66b108cdec357a00f4a586379bcd33a)
Merged-In: I6f44e740897305e7a0553c1cf6c3af37faf02a2e
---
 .../android/content/pm/PackageParser.java     | 53 +++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 8c66fb227cf9..349f021c16ad 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -80,6 +80,7 @@ import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Base64;
 import android.util.DisplayMetrics;
+import android.util.EventLog;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -121,6 +122,7 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.Callable;
@@ -2637,6 +2639,12 @@ public class PackageParser {
             }
         }
 
+        if (declareDuplicatePermission(pkg)) {
+            outError[0] = "Found duplicate permission with a different attribute value.";
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            return null;
+        }
+
         if (supportsSmallScreens < 0 || (supportsSmallScreens > 0
                 && pkg.applicationInfo.targetSdkVersion
                         >= android.os.Build.VERSION_CODES.DONUT)) {
@@ -2675,6 +2683,51 @@ public class PackageParser {
         return pkg;
     }
 
+    /**
+     * @return {@code true} if the package declares malformed duplicate permissions.
+     */
+    public static boolean declareDuplicatePermission(@NonNull Package pkg) {
+        final List<Permission> permissions = pkg.permissions;
+        final int size = permissions.size();
+        if (size > 0) {
+            final ArrayMap<String, Permission> checkDuplicatePerm = new ArrayMap<>(size);
+            for (int i = 0; i < size; i++) {
+                final Permission permissionDefinition = permissions.get(i);
+                final String name = permissionDefinition.info.name;
+                final Permission perm = checkDuplicatePerm.get(name);
+                if (isMalformedDuplicate(permissionDefinition, perm)) {
+                    // Fix for b/213323615
+                    EventLog.writeEvent(0x534e4554, "213323615",
+                            "The package " + pkg.packageName + " seems malicious");
+                    return true;
+                }
+                checkDuplicatePerm.put(name, permissionDefinition);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Determines if a duplicate permission is malformed .i.e. defines different protection level
+     * or group.
+     */
+    private static boolean isMalformedDuplicate(Permission p1, Permission p2) {
+        // Since a permission tree is also added as a permission with normal protection
+        // level, we need to skip if the parsedPermission is a permission tree.
+        if (p1 == null || p2 == null || p1.tree || p2.tree) {
+            return false;
+        }
+
+        if (p1.info.getProtection() != p2.info.getProtection()) {
+            return true;
+        }
+        if (!Objects.equals(p1.info.group, p2.info.group)) {
+            return true;
+        }
+
+        return false;
+    }
+
     private boolean checkOverlayRequiredSystemProperty(String propName, String propValue) {
 
         if (TextUtils.isEmpty(propName) || TextUtils.isEmpty(propValue)) {
