Last Comment Bug 538308 - (CVE-2010-0176) nsTreeContentView Dangling Pointer Vulnerability (ZDI-CAN-633)
(CVE-2010-0176)
: nsTreeContentView Dangling Pointer Vulnerability (ZDI-CAN-633)
Status: RESOLVED FIXED
[sg:critical?]
: verified1.9.0.19, verified1.9.1, verified1.9.2
Product: Core
Classification: Components
Component: XUL (show other bugs)
: 1.9.1 Branch
: All All
: -- normal (vote)
: mozilla1.9.3a1
Assigned To: Timothy Nikkel (:tnikkel)
:
Mentors:
Depends on:
Blocks:
  Show dependency treegraph
 
Reported: 2010-01-06 16:53 PST by Daniel Veditz [:dveditz]
Modified: 2010-07-25 16:42 PDT (History)
13 users (show)
dveditz: blocking1.9.0.19+
dveditz: wanted1.9.0.x+
tnikkel: in‑testsuite+
See Also:
Crash Signature:
(edit)
QA Whiteboard:
Iteration: ---
Points: ---
Has Regression Range: ---
Has STR: ---
.2+
.2-fixed
.9+
.9-fixed


Attachments
XUL testcase (923 bytes, application/vnd.mozilla.xul+xml)
2010-01-06 16:53 PST, Daniel Veditz [:dveditz]
no flags Details
Proposed patch (1.22 KB, patch)
2010-01-07 12:39 PST, neil@parkwaycc.co.uk
bzbarsky: superreview+
Details | Diff | Splinter Review
alternate patch (5.11 KB, patch)
2010-01-28 20:16 PST, Timothy Nikkel (:tnikkel)
neil: review+
bzbarsky: superreview+
Details | Diff | Splinter Review
alternate patch v2 (4.77 KB, patch)
2010-01-29 18:53 PST, Timothy Nikkel (:tnikkel)
dveditz: approval1.9.2.2+
dveditz: approval1.9.1.9+
Details | Diff | Splinter Review
alternate patch v2 1.9.0 (5.05 KB, patch)
2010-03-04 16:52 PST, Timothy Nikkel (:tnikkel)
dveditz: approval1.9.0.19+
Details | Diff | Splinter Review
1.8 patch (3.33 KB, patch)
2010-03-18 02:15 PDT, Martin Stránský
no flags Details | Diff | Splinter Review

Description Daniel Veditz [:dveditz] 2010-01-06 16:53:13 PST
Created attachment 420452 [details]
XUL testcase

ZDI-CAN-633: Mozilla Firefox nsTreeContentView Dangling Pointer Vulnerability

-- ABSTRACT ------------------------------------------------------------

TippingPoint has identified a vulnerability affecting the following 
products:

    Mozilla Firefox 3.5.x

-- VULNERABILITY DETAILS -----------------------------------------------

This vulnerability allows remote attackers to execute arbitrary code on
vulnerable installations of Mozilla Firefox. User interaction is
required in that the victim must visit a malicious website or be coerced
into opening a malicious document.

The specific flaw exists within the way that Mozilla's Firefox parses
.XUL files. While appending a particular tag to a treechildren
container, the application will create more than one reference to a
particular element without increasing its reference count. Upon removal
of one of the elements, the refcount will be decreased causing the
application to free the memory associated with the object. Due to the
rogue reference occurring, the next time the application attempts to
reference that container, the application will access memory that has
been freed which can lead to code execution under the context of the
application.

Version(s)  tested: Mozilla Firefox 3.5.6
Platform(s) tested: Windows XP SP3

The vulnerability occurs due to the application incorrectly creating
more than one "option" element inside an "optgroup" element within a
"treechildren" container.

The XML used to describe this vulnerability is shown below.

    <tree id="tr" flex="1">
        <treecols>
            <treecol/>
        </treecols>
        <treechildren>
            <html:optgroup id="optgroup">
                <html:option id="disappear" />
            </html:optgroup>
        </treechildren>
    </tree>

To reproduce the issue, one will need to manipulate this document via
javascript.

optgroup = document.getElementById("optgroup");
treechildren = document.createElement("treechildren");
optgroup.appendChild(tc);

v = document.getElementById("disappear");
v.parentNode.removeChild(v);
v = null;

tree = document.getElementById("tr");
col = tree.columns[0];
alert(tree.view.getItemAtIndex(1, col)); 

When appending an element to an XUL tree, the application will call the
following code.

layout/xul/base/src/tree/src/nsTreeContentView.cpp:966
void
nsTreeContentView::ContentInserted(nsIDocument *aDocument,
                                   nsIContent* aContainer,
                                   nsIContent* aChild,
                                   PRInt32 aIndexInContainer)
{

Upon an optgroup being appended to a tree, the following code will add a
row for the optgroup.

layout/xul/base/src/tree/src/nsTreecontentView.cpp:1038
  else if (childTag == nsGkAtoms::optgroup) {
    InsertRowFor(aContainer, aChild);       // XXX
  }
  else if (childTag == nsGkAtoms::option) {
    PRInt32 parentIndex = FindContent(aContainer);
    PRInt32 index = 0;
    GetIndexInSubtree(aContainer, aChild, &index);
    PRInt32 count = InsertRow(parentIndex, index, aChild);  // XXX
    if (mBoxObject)
      mBoxObject->RowCountChanged(parentIndex + index + 1, count);
  }

Upon adding another treechildren element, the application will duplicate
the element and add a new row to the tree.

layout/xul/base/src/tree/src/nsTreeContentView.cpp:1007

  if (childTag == nsGkAtoms::treechildren) {
    PRInt32 index = FindContent(aContainer);
    if (index >= 0) {
      Row* row = (Row*)mRows[index];
      row->SetEmpty(PR_FALSE);
      if (mBoxObject)
        mBoxObject->InvalidateRow(index);
      if (row->IsContainer() && row->IsOpen()) {
        PRInt32 count = EnsureSubtree(index);   // XXX: index = 0, and
should be pointing to the optgroup
        if (mBoxObject)
          mBoxObject->RowCountChanged(index + 1, count);
      }
    }
  }

  nsCOMPtr<nsIContent> child;
  if (row->mContent->Tag() == nsGkAtoms::optgroup)
    child = row->mContent;
  else {
    nsTreeUtils::GetImmediateChild(row->mContent,
nsGkAtoms::treechildren, getter_AddRefs(child));
    if (! child) {
      return 0;
    }
  }

Calling EnsureSubtree will serialize the object at the specified row
index.

layout/xul/base/src/tree/src/nsTreeContentView.cpp:1297
PRInt32
nsTreeContentView::EnsureSubtree(PRInt32 aIndex)
{
  Row* row = (Row*)mRows[aIndex];

  nsCOMPtr<nsIContent> child;
  if (row->mContent->Tag() == nsGkAtoms::optgroup)
    child = row->mContent;
  else {
    nsTreeUtils::GetImmediateChild(row->mContent,
nsGkAtoms::treechildren, getter_AddRefs(child));
    if (! child) {
      return 0;
    }
  }
  nsAutoVoidArray rows;
  PRInt32 index = 0;
  Serialize(child, aIndex, &index, rows);   // XXX: serialize the
optgroup again due to both indexes referencing the same variable
  mRows.InsertElementsAt(rows, aIndex + 1);
  PRInt32 count = rows.Count();

Calling Serialize will serialize an element and add it to the row
specified by index. 

layout/xul/base/src/tree/src/nsTreeContentView.cpp:1139
// Recursively serialize content, starting with aContent.
void
nsTreeContentView::Serialize(nsIContent* aContent, PRInt32 aParentIndex,
PRInt32* aIndex, nsVoidArray& aRows)
{
  ChildIterator iter, last;
  for (ChildIterator::Init(aContent, &iter, &last); iter != last;
++iter) {
    nsCOMPtr<nsIContent> content = *iter;
    nsIAtom *tag = content->Tag();
    PRInt32 count = aRows.Count();

    if (content->IsNodeOfType(nsINode::eXUL)) {
      if (tag == nsGkAtoms::treeitem)
        SerializeItem(content, aParentIndex, aIndex, aRows);
      else if (tag == nsGkAtoms::treeseparator)
        SerializeSeparator(content, aParentIndex, aIndex, aRows);
    }
    else if (content->IsNodeOfType(nsINode::eHTML)) {
      if (tag == nsGkAtoms::option)
        SerializeOption(content, aParentIndex, aIndex, aRows);  // XXX
      else if (tag == nsGkAtoms::optgroup)
        SerializeOptGroup(content, aParentIndex, aIndex, aRows);
    }
    *aIndex += aRows.Count() - count;
  }
}

When serializing an optgroup, a new row is created. The optgroup will
then serialize it's child "option" element.

void
nsTreeContentView::SerializeOptGroup(nsIContent* aContent, PRInt32
aParentIndex,
                                     PRInt32* aIndex, nsVoidArray&
aRows)
{
  Row* row = Row::Create(mAllocator, aContent, aParentIndex);
  aRows.AppendElement(row);
  row->SetContainer(PR_TRUE);
  row->SetOpen(PR_TRUE);

  nsCOMPtr<nsIContent> child;
  nsTreeUtils::GetImmediateChild(aContent, nsGkAtoms::option,
getter_AddRefs(child));
  if (child) {
    // Now, recursively serialize our child.
    PRInt32 count = aRows.Count();
    PRInt32 index = 0;
    Serialize(aContent, aParentIndex + *aIndex + 1, &index, aRows);    
// XXX: child element
    row->mSubtreeSize += aRows.Count() - count;
  }
  else
    row->SetEmpty(PR_TRUE);
}

When an element is removed from the tree, the following code is called
to remove a row from the document. This will deallocate all the contents
of a particular row.

layout/xul/base/src/tree/src/nsTreeContentView.cpp:1051
void
nsTreeContentView::ContentRemoved(nsIDocument *aDocument,
                                     nsIContent* aContainer,
                                     nsIContent* aChild,
                                     PRInt32 aIndexInContainer)
{

layout/xul/base/src/tree/src/nsTreeContentView.cpp:1105
  else if (tag == nsGkAtoms::treeitem ||
           tag == nsGkAtoms::treeseparator ||
           tag == nsGkAtoms::option ||
           tag == nsGkAtoms::optgroup
          ) {
    PRInt32 index = FindContent(aChild);
    if (index >= 0) {
      PRInt32 count = RemoveRow(index);     // XXX: remove the option
element
      if (mBoxObject)
        mBoxObject->RowCountChanged(index, -count);
    }
  }

PRInt32
nsTreeContentView::RemoveRow(PRInt32 aIndex)
{
  Row* row = (Row*)mRows[aIndex];
  PRInt32 count = row->mSubtreeSize + 1;
  PRInt32 parentIndex = row->mParentIndex;

  Row::Destroy(mAllocator, row);
  for(PRInt32 i = 1; i < count; i++) {
    Row* nextRow = (Row*)mRows[aIndex + i];
    Row::Destroy(mAllocator, nextRow);          // XXX
  }
  mRows.RemoveElementsAt(aIndex, count);

  UpdateSubtreeSizes(parentIndex, -count);
  
  UpdateParentIndexes(aIndex, 0, -count);

  return count;
}

Upon the deallocation, the duplicate variable reference can be fetched
via the GetItemAtIndex method. Due to this reference being freed, the
contents of the option tag point to recently freed memory.

layout/xul/base/src/tree/src/nsTreeContentView.cpp:759
NS_IMETHODIMP
nsTreeContentView::GetItemAtIndex(PRInt32 aIndex, nsIDOMElement**
_retval)
{
  NS_PRECONDITION(aIndex >= 0 && aIndex < mRows.Count(), "bad index");
  if (aIndex < 0 || aIndex >= mRows.Count())
    return NS_ERROR_INVALID_ARG;   

  Row* row = (Row*)mRows[aIndex];
  row->mContent->QueryInterface(NS_GET_IID(nsIDOMElement),
(void**)_retval);

  return NS_OK;
}


-- CREDIT --------------------------------------------------------------

This vulnerability was discovered by:
    * regenrecht
Comment 1 Daniel Veditz [:dveditz] 2010-01-06 18:43:50 PST
Shiretoko crash: bp-b848e49b-6227-485f-80cf-d9cd42100106
Comment 2 Daniel Veditz [:dveditz] 2010-01-06 19:02:40 PST
I also crash in Namoroka:
  bp-45c05fb1-be24-4726-ade7-35d822100106
  bp-b16e2c0b-e4ad-4116-8e2e-8d9352100106

That one didn't happen immediately, in fact I forgot I had run the testcase but crashed later when I was switching tabs. Then crashed again after restoring.

We fixed crashes in this file before:
https://bugzilla.mozilla.org/buglist.cgi?quicksearch=FIX+nsTreeContentView

Most recently we fixed security bug 479931 in 1.9.1.6 which may have inspired regenrecht to code-inspect this file. Funtimes.
Comment 3 Timothy Nikkel (:tnikkel) 2010-01-06 22:13:02 PST
In bug 366203 we tried one way of not allowing multiple treechildren, but it caused a regression. Maybe we can disallow multiple treechildren some other way.
Comment 4 neil@parkwaycc.co.uk 2010-01-07 05:15:56 PST
(In reply to comment #3)
> In bug 366203 we tried one way of not allowing multiple treechildren, but it
> caused a regression. Maybe we can disallow multiple treechildren some other
> way.
But that case was about multiple top-level treechildren, wasn't it?
Comment 5 neil@parkwaycc.co.uk 2010-01-07 12:39:55 PST
Created attachment 420596 [details] [diff] [review]
Proposed patch

Unlike ContentInserted, I noticed that Serialise doesn't verify the type of the child against the type of the parent. Bringing the two back into sync seems to resolve this bug. Note that I don't check the tag of the parent because my belief is that the callers already ensure a suitable parent.
Comment 6 Timothy Nikkel (:tnikkel) 2010-01-07 14:12:08 PST
(In reply to comment #4)
> But that case was about multiple top-level treechildren, wasn't it?

Ah, okay, I just skimmed the bug report and saw "multiple treechildren".
Comment 7 Boris Zbarsky [:bz] 2010-01-07 18:59:18 PST
Comment on attachment 420596 [details] [diff] [review]
Proposed patch

This looks fine.... but why is there any HTML anything in here?  Is that a carryover from XBL form controls?  If so, can we kill it?
Comment 8 Timothy Nikkel (:tnikkel) 2010-01-08 02:16:52 PST
Comment on attachment 420596 [details] [diff] [review]
Proposed patch

I don't think this actually addresses the real issue here. This patch will refuse to construct a row for the optgroup at all (because it is HTML and its parent is XUL), thereby avoiding the problem, but regressing anyone using optgroup/options in a tree.
Comment 9 Olli Pettay [:smaug] 2010-01-08 02:52:38 PST
using optgroupt/option in a tree is/was for XBL based form controls, right?
All that code should be just removed.
Comment 10 neil@parkwaycc.co.uk 2010-01-08 09:25:42 PST
And it was already not supported for dynamic changes to a XUL tree anyway.
Comment 11 Timothy Nikkel (:tnikkel) 2010-01-13 21:43:23 PST
If we can drop optgroup/option inside trees (I can't speak about this), then a patch that clearly does that would be better than one that does that in an obfuscated way.
Comment 12 Timothy Nikkel (:tnikkel) 2010-01-28 20:16:08 PST
Created attachment 424181 [details] [diff] [review]
alternate patch

Don't allow XUL nodes under non-XUL nodes.
Comment 13 neil@parkwaycc.co.uk 2010-01-29 01:59:29 PST
Comment on attachment 424181 [details] [diff] [review]
alternate patch

>@@ -923,35 +923,35 @@ nsTreeContentView::AttributeChanged(nsID
>+      if (parent && parent->IsXUL()) {
>         PRInt32 index = FindContent(parent);
[Unnecessary?]

>@@ -1075,16 +1078,19 @@ nsTreeContentView::ContentRemoved(nsIDoc
>+    if (!aContainer->IsXUL())
[Unnecessary?]

>@@ -1267,17 +1276,17 @@ nsTreeContentView::GetIndexInSubtree(nsI
>+    if (content->IsXUL() && aContainer->IsXUL()) {
[You didn't cache containerIsXUL this time?]
Comment 14 Timothy Nikkel (:tnikkel) 2010-01-29 02:22:31 PST
(In reply to comment #13)
> (From update of attachment 424181 [details] [diff] [review])
> >@@ -923,35 +923,35 @@ nsTreeContentView::AttributeChanged(nsID
> >+      if (parent && parent->IsXUL()) {
> >         PRInt32 index = FindContent(parent);
> [Unnecessary?]

Better safe than sorry I guess.

> >@@ -1075,16 +1078,19 @@ nsTreeContentView::ContentRemoved(nsIDoc
> >+    if (!aContainer->IsXUL())
> [Unnecessary?]

I think this is needed, if say a treechildren is removed from an optgroup parent.

> >@@ -1267,17 +1276,17 @@ nsTreeContentView::GetIndexInSubtree(nsI
> >+    if (content->IsXUL() && aContainer->IsXUL()) {
> [You didn't cache containerIsXUL this time?]

I overlooked that, I can cache it.
Comment 15 neil@parkwaycc.co.uk 2010-01-29 02:37:40 PST
I was thinking that if you can find the row to remove then the parent must be XUL otherwise you wouldn't have added the row. (And in the unlikely event that you somehow find a way of breaking that again then you would still want to remove the row so that you don't dangle for next time...)
Comment 16 Boris Zbarsky [:bz] 2010-01-29 10:48:55 PST
> If we can drop optgroup/option inside trees (I can't speak about this), then a
> patch that clearly does that would be better than one that does that in an
> obfuscated way.

Agreed, and I think we can in fact drop them.
Comment 17 Boris Zbarsky [:bz] 2010-01-29 10:49:14 PST
Comment on attachment 424181 [details] [diff] [review]
alternate patch

sr=me, but if we dropped the optgroup/whatever stuff, could we undo some or all of this?
Comment 18 Timothy Nikkel (:tnikkel) 2010-01-29 12:55:41 PST
(In reply to comment #15)
> I was thinking that if you can find the row to remove then the parent must be
> XUL otherwise you wouldn't have added the row. (And in the unlikely event that
> you somehow find a way of breaking that again then you would still want to
> remove the row so that you don't dangle for next time...)

Sure, if aChild has a row then we would want to remove it. The situation I know we can't let happen is if aChild is a treechildren and aContainer is an optgroup. Because then we execute this code

  if (tag == nsGkAtoms::treechildren) {
    PRInt32 index = FindContent(aContainer);
    if (index >= 0) {
      Row* row = mRows[index];
      row->SetEmpty(PR_TRUE);
      PRInt32 count = RemoveSubtree(index);

and now we have deleted the rows for any options/optgroups that were children of the container optgroup. Would you prefer if I just checked aContainer->IsXUL() inside the treechildren if?
Comment 19 neil@parkwaycc.co.uk 2010-01-29 14:05:23 PST
Actually I think it might be clearer if you move the aContainer->IsXUL() check earlier in AttributeChanged to match the other methods.
Comment 20 Timothy Nikkel (:tnikkel) 2010-01-29 14:44:07 PST
(In reply to comment #19)
> Actually I think it might be clearer if you move the aContainer->IsXUL() check
> earlier in AttributeChanged to match the other methods.

Ok.
Comment 21 Timothy Nikkel (:tnikkel) 2010-01-29 15:29:06 PST
> Would you prefer if I just checked
> aContainer->IsXUL() inside the treechildren if?

So is that a 'no' to this then?
Comment 22 neil@parkwaycc.co.uk 2010-01-29 16:11:21 PST
Correct, otherwise that would defeat the point of comment #19 ;-)
Comment 23 Timothy Nikkel (:tnikkel) 2010-01-29 18:53:21 PST
Created attachment 424371 [details] [diff] [review]
alternate patch v2

For reference here is what I will land with the changes made.
Comment 24 Timothy Nikkel (:tnikkel) 2010-01-29 18:56:01 PST
Comment on attachment 420596 [details] [diff] [review]
Proposed patch

Clearing review request.
Comment 25 Timothy Nikkel (:tnikkel) 2010-02-03 22:19:59 PST
http://hg.mozilla.org/mozilla-central/rev/faef4d3ee45e
Comment 26 Timothy Nikkel (:tnikkel) 2010-02-03 22:24:17 PST
And after some bake time on trunk we'll want this on the branches, which is now just 1.9.2 and 1.9.1, correct?
Comment 27 Mike Beltzner [:beltzner, not reading bugmail] 2010-02-10 12:55:29 PST
Blocking on 1.9.1 and 1.9.2, does it affect 1.9.0?
Comment 28 Timothy Nikkel (:tnikkel) 2010-02-10 13:06:08 PST
It does affect 1.9.0, but I am under the impression that the freeze for the last ever 1.9.0.x release is long passed.
Comment 29 Boris Zbarsky [:bz] 2010-02-10 13:11:41 PST
The freeze for the last MoCo firefox release may be passed, but we generally land changes like this on branches that we may not be using but other products are, as needed.
Comment 30 Timothy Nikkel (:tnikkel) 2010-02-10 13:19:37 PST
Ok, so what flags do we use in that case? Just an approval flag on the patch?
Comment 31 Boris Zbarsky [:bz] 2010-02-10 14:05:58 PST
Yep.
Comment 32 Mike Beltzner [:beltzner, not reading bugmail] 2010-03-03 22:25:30 PST
Does this apply cleanly to 1.9.2 and 1.9.1? If so, please request approval on the patch.
Comment 33 Timothy Nikkel (:tnikkel) 2010-03-04 16:52:06 PST
Created attachment 430466 [details] [diff] [review]
alternate patch v2 1.9.0

Merge was trivial.
Comment 34 Daniel Veditz [:dveditz] 2010-03-04 23:10:31 PST
Comment on attachment 424371 [details] [diff] [review]
alternate patch v2

Approved for 1.9.2.2, 1.9.1.9, and 1.9.0.19, a=dveditz
Comment 35 Timothy Nikkel (:tnikkel) 2010-03-05 15:14:24 PST
http://hg.mozilla.org/releases/mozilla-1.9.1/rev/976224d9dfb3
Comment 36 Timothy Nikkel (:tnikkel) 2010-03-05 15:15:56 PST
http://hg.mozilla.org/releases/mozilla-1.9.2/rev/353af8416a4f
Comment 37 :Gavin Sharp [email: gavin@gavinsharp.com] 2010-03-08 10:50:24 PST
mozilla/layout/xul/base/src/tree/src/nsTreeContentView.cpp 	1.72
Comment 38 Al Billings [:abillings] 2010-03-11 12:54:33 PST
Verified fixed for 1.9.0 using testcase with Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.19pre) Gecko/2010031106 GranParadiso/3.0.19pre (.NET CLR 3.5.30729).

Verified for 1.9.1 with Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.9pre) Gecko/20100311 Shiretoko/3.5.9pre (.NET CLR 3.5.30729)

Verified for 1.9.2 with Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.2pre) Gecko/20100311 Namoroka/3.6.2pre (.NET CLR 3.5.30729).
Comment 39 Martin Stránský 2010-03-18 02:15:00 PDT
Created attachment 433288 [details] [diff] [review]
1.8 patch
Comment 40 Timothy Nikkel (:tnikkel) 2010-07-17 13:54:12 PDT
Where did the testcase in this bug go? I wanted to check it in as a crashtest now that this bug is public.
Comment 41 Robert O'Callahan (:roc) (Exited; email my personal email if necessary) 2010-07-18 17:19:58 PDT
You should be able to see it now.
Comment 42 Timothy Nikkel (:tnikkel) 2010-07-25 16:42:09 PDT
Added crashtest
http://hg.mozilla.org/mozilla-central/rev/9c4c5b4232b3

Note You need to log in before you can comment on or make changes to this bug.