Share
## https://sploitus.com/exploit?id=AFB54C5F-461A-5A0D-828A-2E3995744A32
# CVE-2022-28282
**Firefox: heap-use-after-free in DocumentL10n::TranslateDocument**
## Old Trick with Simple QL

It's a simple old trick that:  
If we define the Getter of "then" in Object's prototype, Promise->resolve will trigger user's JS callback when it gets the Object as a parameter:
```
https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-promise.resolve
```

I wrote a few simple and "violent" rules to search for UAF problems that may be caused by this trick.  
Such as:  
- Callback between two raw pointer 
- A member variable freed in the callback 
- ......  

Like one of them:  

```
from Function magic_func,Function g_resolve,FunctionCall g_setva_fc,VariableAccess va,VariableAccess setnull_va,Expr e_tmp1,Expr e,FunctionCall fc,FunctionCall fc_tmp1,FunctionCall fc_tmp2,FunctionCall fc_tmp3,FunctionCall fc_tmp4,FunctionCall fc_tmp5
where g_resolve.getName().regexpMatch("ResolvePromiseInternal")
and g_resolve.getParentScope().toString().matches("js")
// mMember
and va.getControlFlowScope()=magic_func
and va.getEnclosingFunction() = magic_func
and va.getTarget().getName().regexpMatch("m([A-Z])((.)*)")
and not va.getTarget().getName().matches("mImpl")
// mMember->trigger()->js::ResolvePromiseInternal()
and e.getEnclosingFunction() = magic_func
and fc.getEnclosingFunction() = magic_func
and fc.getTarget().getName().matches("operator->")
and va.getParent() = fc
and fc.getParent() = fc_tmp1
and fc_tmp1.getTarget() = fc_tmp2.getEnclosingFunction()
and fc_tmp2.getTarget() = fc_tmp3.getEnclosingFunction()
and fc_tmp3.getTarget() = fc_tmp4.getEnclosingFunction()
and fc_tmp4.getTarget() = fc_tmp5.getEnclosingFunction()
and fc_tmp5.getTarget() = g_resolve
// exists: mMember = NULL
and setnull_va.getTarget()=va.getTarget()
and g_setva_fc.getTarget().getName().matches("operator=")
and setnull_va.getParent() = g_setva_fc
and e_tmp1.toString().matches("0")
and e_tmp1.getParent() = g_setva_fc
select magic_func,magic_func.getParentScope(),va,g_setva_fc,fc_tmp1
```

**As you can see: this rule is very simple and there are many things that can be improved:**
- Violent way for search 
- Member variables don鈥榯 have to be set to null 
- ......

(But it was indeed an initial version of one of my QLs, and by improving these rules, I found some other interesting things.)

By adjusting the number of levels of FunctionCall recursion, I get a collection of results.  
Unfortunately, there is only one element in the initial result set. Luckily, there is something interesting in this result.  
The result:

```
1  OnParsingCompleted     Document  mDocumentL10n  ......
2  LocalizationLinkAdded  Document  mDocumentL10n  ......
```

## Analyze

```
void Document::LocalizationLinkAdded(Element* aLinkElement) {
  if (!AllowsL10n()) {
    return;
  }
......
......
  if (!mDocumentL10n) {
    Element* elem = GetDocumentElement();
    MOZ_DIAGNOSTIC_ASSERT(elem);

    bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
    mDocumentL10n = DocumentL10n::Create(this, isSync);
    ......
  }

  mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));

  if (mReadyState >= READYSTATE_INTERACTIVE) {
    mDocumentL10n->TriggerInitialTranslation(); // **** 1 ****
  } else {
......
......
  }
}

void DocumentL10n::TriggerInitialTranslation() {

  路路路路路路
  路路路路路路
  nsTArray<RefPtr<Promise>> promises;

  ErrorResult rv;
  promises.AppendElement(TranslateDocument(rv));
  if (NS_WARN_IF(rv.Failed())) {
    InitialTranslationCompleted(false);
    mReady->MaybeRejectWithUndefined();
    return;
  }
  promises.AppendElement(TranslateRoots(rv));   // **** 2 ****
  Element* documentElement = mDocument->GetDocumentElement(); // // **** 3 ****
  ......
  ......
}
```

When an HTMLLinkElement(with rel="localization") is added to the document, the document will create a DocumentL10n and trigger TriggerInitialTranslation. In the path from (1) in Document::LocalizationLinkAdded to (2) in DocumentL10n::TriggerInitialTranslation, only the document hold the reference of DocumentL10n in mDocumentL10n.  
In TranslateRoots(2), the function will use a Promise to resolve ErrorResult rv. If we define the Getter of "then" in Object's prototype before, Promise->resolve will trigger user's JS callback in TranslateRoots(2). And we can set HTMLLinkElement's rel to null to trigger Document::LocalizationLinkRemoved. It will set mDocumentL10n to nullptr and remove the reference of DocumentL10n, then we can destroy the object in gc. When the program return to TriggerInitialTranslation, it will lead to a Use-After-Free in (3)(with accessing the member variable: mDocument).

### PoC

Since there is a page privilege check in AllowsL10n, the vulnerability need to be triggered through a Web Extension.

PoC:

```
- manifest.json
- background.js
```