Coordinated Disclosure Timeline

Summary

UaF in NFCHost::GetNFC

Product

Chrome

CVE

CVE-2020-15970

Tested Version

Tested on 2 settings:

  1. Pixel 3a XL emulator on Android 10 with master branch commit 79552e1
  2. Actual Pixel 3a device on Android 10 with stable version 84.0.4147.89

Details

In the GetNFC method of NFCHost, the NFCHost is passed into a callback as a raw pointer [1]:

 // base::Unretained() is safe here because the subscription is canceled when
 // this object is destroyed.
 subscription_id_ = permission_controller_->SubscribePermissionStatusChange(
      PermissionType::NFC, render_frame_host, origin_url,
      base::BindRepeating(&NFCHost::OnPermissionStatusChange,
                          base::Unretained(this)));

As the comment suggested, when NFCHost is destroyed, it will cancel the subscription by calling the Close method:

NFCHost::~NFCHost() {
  Close();
}

void NFCHost::Close() {
  nfc_provider_.reset();
  if (subscription_id_ != 0)
    permission_controller_->UnsubscribePermissionStatusChange(subscription_id_);
}

However, this only unsubscribes the callback associated with the current subscription_id_. Every time GetNFC is called, a new subscription with a different subscription_id is created, all of them store the same NFCHost raw pointer in the callback, while subscription_id_ is being replaced. When NFCHost is destroyed, only the most recent subscription is cancelled, which leaves dangling pointers in previously subscribed callbacks. These callbacks will then be triggered when a permission for the website is changed, for example, by changing it in the site settings menu.

Note that although WebNFC is currently an experimental feature, it has an (origin trial that ends on July 29th)(https://developers.chrome.com/origintrials/#/view_trial/236438980436951041), so any website can request an origin trial token to trigger this bug with the default settings.

CVE

Reproduction case

This issue requires some user interactions to reproduce, but does not require a compromised renderer. This also seems to only trigger if there is at least one other tab already opened.

First enable the #experimental-web-platform-features flag in chrome://flags and relaunch. As explained before, this is only needed to make testing easier. Any website could trigger this bug without this flag by requesting an origin trial token until the end of the trial period.

Host the attached nfc.html and nfc2.html on localhost and connect the Android device to the host machine.

Launch Chrome on the device and open 2 tabs. In one of the tabs, open nfc.html. Click on the button and when prompted, select allow to grant permission for the site to use WebNFC. Wait about 10 seconds or until the page is refreshed, then close the tab that has the page opened. Now go to Settings > Site settings > All sites. Select http://localhost:8000 and clear all the permissions. If successful, the screen will have refreshed back to the All sites menu with http://localhost:8000 still appearing, but Chrome will not have crashed. Inspecting with logcat (use adb logcat grep chrome on the host machine), however, shows that a privileged process belonging to Chrome has crashed (See the last 2 lines in stable_crash_log). I’m afraid I do not have a symbolized asan log for this bug, but I’ve attached the crash log from the two tested environments (stable_crash_log for 84.0.4147.89 and head_crash_log for master branch) From the source code, I’d expect the crash to happen in the browser process, so I’m not entirely sure what actually happened here.

Impact

Use-after-free in browser. Can be reached directly from a malicious website, but requires non trivial user interactions to trigger.

Credit

This issue was discovered and reported by GHSL team member @m-y-mo (Man Yue Mo).

Contact

You can contact the GHSL team at securitylab@github.com, please include the GHSL-2020-163 in any communication regarding this issue.