Closed Bug 538308 (CVE-2010-0176) Opened 15 years ago Closed 15 years ago

nsTreeContentView Dangling Pointer Vulnerability (ZDI-CAN-633)

Categories

(Core :: XUL, defect)

1.9.1 Branch
defect
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla1.9.3a1
Tracking Status
blocking1.9.2 --- .2+
status1.9.2 --- .2-fixed
blocking1.9.1 --- .9+
status1.9.1 --- .9-fixed

People

(Reporter: dveditz, Assigned: tnikkel)

Details

(Keywords: verified1.9.0.19, verified1.9.1, verified1.9.2, Whiteboard: [sg:critical?])

Attachments

(5 files, 1 obsolete file)

Attached file 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
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.
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.
(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?
Attached patch Proposed patchSplinter Review
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.
Assignee: nobody → neil
Status: NEW → ASSIGNED
Attachment #420596 - Flags: superreview?(bzbarsky)
Attachment #420596 - Flags: review?(tnikkel)
(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 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?
Attachment #420596 - Flags: superreview?(bzbarsky) → superreview+
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.
using optgroupt/option in a tree is/was for XBL based form controls, right? All that code should be just removed.
And it was already not supported for dynamic changes to a XUL tree anyway.
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.
Attached patch alternate patch (obsolete) — Splinter Review
Don't allow XUL nodes under non-XUL nodes.
Attachment #424181 - Flags: superreview?(bzbarsky)
Attachment #424181 - Flags: review?(neil)
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?]
Attachment #424181 - Flags: review?(neil) → review+
(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.
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...)
> 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 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?
Attachment #424181 - Flags: superreview?(bzbarsky) → superreview+
(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?
Actually I think it might be clearer if you move the aContainer->IsXUL() check earlier in AttributeChanged to match the other methods.
(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.
Assignee: neil → tnikkel
> Would you prefer if I just checked > aContainer->IsXUL() inside the treechildren if? So is that a 'no' to this then?
Correct, otherwise that would defeat the point of comment #19 ;-)
For reference here is what I will land with the changes made.
Attachment #424181 - Attachment is obsolete: true
Comment on attachment 420596 [details] [diff] [review] Proposed patch Clearing review request.
Attachment #420596 - Flags: review?(tnikkel)
Status: ASSIGNED → RESOLVED
Closed: 15 years ago
Flags: in-testsuite?
Resolution: --- → FIXED
Target Milestone: --- → mozilla1.9.3a1
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?
blocking1.9.1: --- → ?
blocking1.9.2: --- → ?
Blocking on 1.9.1 and 1.9.2, does it affect 1.9.0?
blocking1.9.1: ? → .9+
blocking1.9.2: ? → .2+
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.
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.
Ok, so what flags do we use in that case? Just an approval flag on the patch?
Does this apply cleanly to 1.9.2 and 1.9.1? If so, please request approval on the patch.
Merge was trivial.
Attachment #430466 - Flags: approval1.9.0.19?
Attachment #424371 - Flags: approval1.9.2.2?
Attachment #424371 - Flags: approval1.9.1.9?
Flags: wanted1.9.0.x+
Flags: blocking1.9.0.19+
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
Attachment #424371 - Flags: approval1.9.2.2?
Attachment #424371 - Flags: approval1.9.2.2+
Attachment #424371 - Flags: approval1.9.1.9?
Attachment #424371 - Flags: approval1.9.1.9+
Attachment #430466 - Flags: approval1.9.0.19? → approval1.9.0.19+
Whiteboard: [sg:critical?] → [sg:critical?][needs 190 landing]
mozilla/layout/xul/base/src/tree/src/nsTreeContentView.cpp 1.72
Keywords: fixed1.9.0.19
Whiteboard: [sg:critical?][needs 190 landing] → [sg:critical?]
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).
Alias: CVE-2010-0176
Attachment #420452 - Attachment is private: true
Group: core-security
Where did the testcase in this bug go? I wanted to check it in as a crashtest now that this bug is public.
You should be able to see it now.
Flags: in-testsuite? → in-testsuite+
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: