Use-After-Free in PContentPermissionRequest via Double prompt() IPC Message
Categories
(Core :: DOM: Content Processes, defect, P2)
Tracking
()
People
(Reporter: jkratzer, Assigned: mccr8)
Details
(4 keywords, Whiteboard: [adv-main149+r][adv-ESR140.9+r][adv-ESR115.34+r])
Attachments
(8 files)
|
2.50 KB,
patch
|
Details | Diff | Splinter Review | |
|
3.70 KB,
text/plain
|
Details | |
|
989 bytes,
text/html
|
Details | |
|
48 bytes,
text/x-phabricator-request
|
Details | Review | |
|
48 bytes,
text/x-phabricator-request
|
dveditz
:
sec-approval+
|
Details | Review |
|
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-beta+
|
Details | Review |
|
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-esr115+
|
Details | Review |
|
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-esr140+
|
Details | Review |
Bug Description
A use-after-free vulnerability exists in Firefox's PContentPermissionRequest IPC protocol handler. The ContentPermissionRequestParent::Recvprompt() method creates a new nsContentPermissionRequestProxy object each time a prompt() message is received, storing it in the mProxy member field. When a second prompt() message is received, the previous proxy object (proxy1) is replaced but may still be referenced by the browser's PopupNotifications system via a stored JavaScript callback. When the actor is subsequently destroyed via Destroy(), only the current proxy's mParent pointer is nullified via ActorDestroy(), while the orphaned proxy1 retains a dangling raw pointer to the freed ContentPermissionRequestParent actor. When the PopupNotification's event callback fires (e.g., on notification removal), it calls methods on proxy1 that dereference the dangling mParent pointer, resulting in a heap-use-after-free in the parent (browser) process.
Vulnerability Details
- Type: Use-After-Free (CWE-416)
- Component:
PContentPermissionRequestIPC protocol /nsContentPermissionHelper.cpp - Process: Parent (browser) process
- Trigger: Compromised content process sends malicious IPC message sequence
- Severity: Critical (sandbox escape - parent process memory corruption)
Root Cause
The vulnerability is in ContentPermissionRequestParent::Recvprompt() at dom/base/nsContentPermissionHelper.cpp:94-101:
mozilla::ipc::IPCResult ContentPermissionRequestParent::Recvprompt() {
mProxy = new nsContentPermissionRequestProxy(this); // Overwrites previous proxy!
if (NS_FAILED(mProxy->Init(mRequests))) {
RefPtr<nsContentPermissionRequestProxy> proxy(mProxy);
proxy->Cancel();
}
return IPC_OK();
}
And in ContentPermissionRequestParent::ActorDestroy() at line 108-112:
void ContentPermissionRequestParent::ActorDestroy(ActorDestroyReason why) {
if (mProxy) {
mProxy->OnParentDestroyed(); // Only nullifies CURRENT proxy's mParent
}
}
The nsContentPermissionRequestProxy class holds a non-owning raw pointer (mParent) to its parent ContentPermissionRequestParent actor (declared at line 183 of nsContentPermissionHelper.h):
// Non-owning pointer to the ContentPermissionRequestParent object which owns this proxy.
ContentPermissionRequestParent* mParent;
There are two critical design flaws:
-
No duplicate message guard:
Recvprompt()does not check ifmProxyis already set. Each call unconditionally creates a new proxy and overwrites the previous one. -
Incomplete cleanup on actor destruction:
ActorDestroy()only nullifies themParentpointer on the currentmProxy. Any previously created proxies that are still alive (held by the PopupNotifications system) retain their danglingmParentpointers.
Attack Sequence
A compromised content process executes the following IPC message sequence:
SendPContentPermissionRequestConstructor(): Creates the actor with a valid permission type (e.g., "desktop-notification")Sendprompt()(1st): Parent createsproxy1, stores it inmProxy. The permission prompt service creates a PopupNotification that holds a reference toproxy1via JavaScript.Sendprompt()(2nd): Parent createsproxy2, overwritesmProxy.proxy1is released frommProxybut the PopupNotification still holds a reference to it.SendDestroy(): Parent callsSend__delete__(this)→ActorDestroy()nullifiesproxy2->mParent→ actor memory is freed viaDeallocPContentPermissionRequestParent().proxy1->mParentis never nullified — it now points to freed heap memory.- PopupNotification callback fires: When the notification is removed (tab close, shutdown, etc.), the JS callback calls
proxy1->Cancel()or accessesproxy1->GetPrincipal(), which dereferences the danglingmParentpointer → heap-use-after-free.
ASAN Crash Trace
==249785==ERROR: AddressSanitizer: heap-use-after-free on address 0x71781115f748
READ of size 8 at 0x71781115f748 thread T0
#0 nsContentPermissionRequestProxy::GetPrincipal() nsContentPermissionHelper.cpp:736
#1 PermissionDelegateHandler::GetDelegatePrincipal() PermissionDelegateHandler.cpp
...
freed by thread T0:
#1 ContentParent::DeallocPContentPermissionRequestParent() ContentParent.cpp:5129
...
previously allocated by thread T0:
#3 nsContentPermissionUtils::CreateContentPermissionRequestParent() nsContentPermissionHelper.cpp:247
Exploitability
This vulnerability is highly exploitable for sandbox escape:
- Attacker-controlled timing: The compromised content process fully controls when the actor is created, when duplicate prompts are sent, and when destruction occurs.
- Heap-use-after-free: The freed
ContentPermissionRequestParentobject (104 bytes) can be replaced with attacker-controlled data via heap spraying from the content process. Whenproxy1->mParent->mPrincipalis subsequently dereferenced, the attacker controls the virtual table pointer and data, enabling arbitrary code execution. - Parent process context: The crash occurs in the parent (browser) process main thread, which has full system privileges outside the content sandbox.
- No IPDL state machine protection: The protocol definition has no state machine restricting duplicate
prompt()messages, making this attack trivially reliable.
Suggested Fix
In Recvprompt(), either:
- Check if
mProxyis already set and reject duplicate prompts, or - Properly clean up the previous proxy before creating a new one:
mozilla::ipc::IPCResult ContentPermissionRequestParent::Recvprompt() {
if (mProxy) {
// Prevent duplicate prompt - reject or clean up previous proxy
mProxy->OnParentDestroyed(); // Nullify the old proxy's mParent
}
mProxy = new nsContentPermissionRequestProxy(this);
if (NS_FAILED(mProxy->Init(mRequests))) {
RefPtr<nsContentPermissionRequestProxy> proxy(mProxy);
proxy->Cancel();
}
return IPC_OK();
}
Alternatively, add a state check to reject duplicate prompt() messages entirely:
mozilla::ipc::IPCResult ContentPermissionRequestParent::Recvprompt() {
if (mProxy) {
return IPC_FAIL(this, "duplicate prompt message");
}
mProxy = new nsContentPermissionRequestProxy(this);
...
}
| Reporter | ||
Comment 1•3 months ago
|
||
| Reporter | ||
Updated•3 months ago
|
| Reporter | ||
Comment 2•3 months ago
|
||
| Reporter | ||
Comment 3•3 months ago
|
||
| Assignee | ||
Comment 5•3 months ago
|
||
AFAICT this dates back to the initial geolocation e10s work in 2010.
| Assignee | ||
Comment 6•3 months ago
|
||
Nika pointed out that the prompt message is pointless because we always send it exactly once after the constructor, and fixing that would avoid this problem. Funnily enough, not long after this bug was landed, a proposal to do that got filed as an "optimization", in bug 596015.
| Assignee | ||
Updated•3 months ago
|
| Assignee | ||
Comment 7•3 months ago
|
||
Fold the "prompt" message into the constructor, as we only ever send it
once, right after we create the actor. Also modernize the initialization.
Updated•3 months ago
|
| Assignee | ||
Comment 8•3 months ago
|
||
| Assignee | ||
Comment 9•3 months ago
|
||
Comment on attachment 9548407 [details]
(secure)
Security Approval Request
- How easily could an exploit be constructed based on the patch?: The fix here consolidates two messages, so it isn't obviously a security problem. There's even an old bug on file about making this change, bug 596015 (fun fact: this sandbox escapes dates back to 2010, before we even shipped e10s). However, it probably isn't too hard to figure out where the security issue is once you think about why we aren't leaving a separate message. eg the problem is either going to happen when you send that message 0 times or more than once.
- Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: No
- Which branches (beta, release, and/or ESR) are affected by this flaw, and do the release status flags reflect this affected/unaffected state correctly?: all
- If not all supported branches, which bug introduced the flaw?: None
- Do you have backports for the affected branches?: No
- If not, how different, hard to create, and risky will they be?: Shouldn't be an issue, aside from rebase issues in adjacent code. This code hasn't changed for around 5 years.
- How likely is this patch to cause regressions; how much testing does it need?: Hopefully it won't be too bad. We just do some stuff earlier. We do have some tests for this code.
- Is the patch ready to land after security approval is given?: Yes
- Is Android affected?: Yes
| Assignee | ||
Comment 10•3 months ago
|
||
(my approval also applies to the second patch, but it is just a cleanup with no security properties)
Updated•3 months ago
|
Comment 11•3 months ago
|
||
Comment on attachment 9548407 [details]
(secure)
sec-approval+ to land now and request uplifts
Comment 12•3 months ago
|
||
| Assignee | ||
Comment 13•3 months ago
|
||
Bug 596015 should get duped to this whenever this bug is opened.
Comment 14•3 months ago
|
||
https://hg-edge.mozilla.org/mozilla-central/rev/477114802b13
https://hg-edge.mozilla.org/mozilla-central/rev/2158d234f858
Comment 15•3 months ago
|
||
The patch landed in nightly and beta is affected.
:mccr8, is this bug important enough to require an uplift?
- If yes, please nominate the patch for beta approval.
- See https://wiki.mozilla.org/Release_Management/Requesting_an_Uplift for documentation on how to request an uplift.
- If no, please set
status-firefox149towontfix.
For more information, please visit BugBot documentation.
| Assignee | ||
Comment 16•3 months ago
|
||
This bug has two patches, but only the "Fold the prompt message" is needed to fix the security issue. I'm not sure whether I requested to uplift them both or not.
Comment 17•3 months ago
|
||
firefox-beta Uplift Approval Request
- User impact if declined: very old security issue
- Code covered by automated testing: yes
- Fix verified in Nightly: yes
- Needs manual QE test: no
- Steps to reproduce for manual QE testing:
- Risk associated with taking this patch: medium
- Explanation of risk level: This combines 2 messages into 1, which should be simple, but maybe making the code run at a slightly different time could cause problems. I'm not an expert on this code.
- String changes made/needed: none
- Is Android affected?: yes
| Assignee | ||
Comment 18•3 months ago
|
||
Instead of sending a separate message, immediately do the initialization after the
actor is created and registered, via a new override of RecvPContentPermissionRequestConstructor.
I didn't move the initialization inside AllocPContentPermissionRequestParent because
the Init() method runs JS so it seems like a bad idea to do when the actor isn't fully set up.
aRequests is now set inside the Init() method instead of the constructor, which allows us
to avoid a copy of this array at the low cost of making the diff gross.
Original Revision: https://phabricator.services.mozilla.com/D284774
Comment 19•3 months ago
|
||
firefox-esr115 Uplift Approval Request
- User impact if declined: very old security issue
- Code covered by automated testing: yes
- Fix verified in Nightly: yes
- Needs manual QE test: no
- Steps to reproduce for manual QE testing:
- Risk associated with taking this patch: medium
- Explanation of risk level: This combines 2 messages into 1, which should be simple, but maybe making the code run at a slightly different time could cause problems. I'm not an expert on this code.
- String changes made/needed: none
- Is Android affected?: yes
| Assignee | ||
Comment 20•3 months ago
|
||
Instead of sending a separate message, immediately do the initialization after the
actor is created and registered, via a new override of RecvPContentPermissionRequestConstructor.
I didn't move the initialization inside AllocPContentPermissionRequestParent because
the Init() method runs JS so it seems like a bad idea to do when the actor isn't fully set up.
aRequests is now set inside the Init() method instead of the constructor, which allows us
to avoid a copy of this array at the low cost of making the diff gross.
Original Revision: https://phabricator.services.mozilla.com/D284774
Comment 21•3 months ago
|
||
firefox-esr140 Uplift Approval Request
- User impact if declined: very old security issue
- Code covered by automated testing: yes
- Fix verified in Nightly: yes
- Needs manual QE test: no
- Steps to reproduce for manual QE testing:
- Risk associated with taking this patch: medium
- Explanation of risk level: This combines 2 messages into 1, which should be simple, but maybe making the code run at a slightly different time could cause problems. I'm not an expert on this code.
- String changes made/needed: none
- Is Android affected?: yes
| Assignee | ||
Comment 22•3 months ago
|
||
Instead of sending a separate message, immediately do the initialization after the
actor is created and registered, via a new override of RecvPContentPermissionRequestConstructor.
I didn't move the initialization inside AllocPContentPermissionRequestParent because
the Init() method runs JS so it seems like a bad idea to do when the actor isn't fully set up.
aRequests is now set inside the Init() method instead of the constructor, which allows us
to avoid a copy of this array at the low cost of making the diff gross.
Original Revision: https://phabricator.services.mozilla.com/D284774
Updated•3 months ago
|
Updated•3 months ago
|
Comment 23•3 months ago
|
||
| uplift | ||
Updated•3 months ago
|
Updated•2 months ago
|
Updated•2 months ago
|
Updated•2 months ago
|
Updated•2 months ago
|
Comment 24•2 months ago
|
||
| uplift | ||
Comment 25•2 months ago
|
||
| uplift | ||
Updated•2 months ago
|
Updated•6 days ago
|
Description
•