Coordinated Disclosure Timeline
- 2020-07-22 Reported as Chromium Issue 1108299
- 2020-07-29 Origin trial ended and bug has no effect
- 2020-10-06 Fixed in version 86.0.4240.75
Summary
UaF in NFCHost::GetNFC
Product
Chrome
CVE
CVE-2020-15970
Tested Version
Tested on 2 settings:
- Pixel 3a XL emulator on Android 10 with master branch commit 79552e1
- 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
- CVE-2020-15970
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.