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)
Tracking
()
RESOLVED
FIXED
mozilla1.9.3a1
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)
923 bytes,
application/vnd.mozilla.xul+xml
|
Details | |
1.22 KB,
patch
|
bzbarsky
:
superreview+
|
Details | Diff | Splinter Review |
4.77 KB,
patch
|
dveditz
:
approval1.9.2.2+
dveditz
:
approval1.9.1.9+
|
Details | Diff | Splinter Review |
5.05 KB,
patch
|
dveditz
:
approval1.9.0.19+
|
Details | Diff | Splinter Review |
3.33 KB,
patch
|
Details | Diff | Splinter Review |
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
Reporter | ||
Comment 1•15 years ago
|
||
Shiretoko crash: bp-b848e49b-6227-485f-80cf-d9cd42100106
Reporter | ||
Comment 2•15 years ago
|
||
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.
Assignee | ||
Comment 3•15 years ago
|
||
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•15 years ago
|
||
(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•15 years ago
|
||
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)
Assignee | ||
Comment 6•15 years ago
|
||
(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•15 years ago
|
||
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+
Assignee | ||
Comment 8•15 years ago
|
||
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•15 years ago
|
||
using optgroupt/option in a tree is/was for XBL based form controls, right?
All that code should be just removed.
Comment 10•15 years ago
|
||
And it was already not supported for dynamic changes to a XUL tree anyway.
Assignee | ||
Comment 11•15 years ago
|
||
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.
Assignee | ||
Comment 12•15 years ago
|
||
Don't allow XUL nodes under non-XUL nodes.
Attachment #424181 -
Flags: superreview?(bzbarsky)
Attachment #424181 -
Flags: review?(neil)
Comment 13•15 years ago
|
||
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+
Assignee | ||
Comment 14•15 years ago
|
||
(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•15 years ago
|
||
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•15 years ago
|
||
> 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•15 years ago
|
||
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+
Assignee | ||
Comment 18•15 years ago
|
||
(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•15 years ago
|
||
Actually I think it might be clearer if you move the aContainer->IsXUL() check earlier in AttributeChanged to match the other methods.
Assignee | ||
Comment 20•15 years ago
|
||
(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
Assignee | ||
Comment 21•15 years ago
|
||
> Would you prefer if I just checked
> aContainer->IsXUL() inside the treechildren if?
So is that a 'no' to this then?
Comment 22•15 years ago
|
||
Correct, otherwise that would defeat the point of comment #19 ;-)
Assignee | ||
Comment 23•15 years ago
|
||
For reference here is what I will land with the changes made.
Attachment #424181 -
Attachment is obsolete: true
Assignee | ||
Comment 24•15 years ago
|
||
Comment on attachment 420596 [details] [diff] [review]
Proposed patch
Clearing review request.
Attachment #420596 -
Flags: review?(tnikkel)
Assignee | ||
Comment 25•15 years ago
|
||
Status: ASSIGNED → RESOLVED
Closed: 15 years ago
Flags: in-testsuite?
Resolution: --- → FIXED
Target Milestone: --- → mozilla1.9.3a1
Assignee | ||
Comment 26•15 years ago
|
||
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: --- → ?
Comment 27•15 years ago
|
||
Blocking on 1.9.1 and 1.9.2, does it affect 1.9.0?
blocking1.9.1: ? → .9+
blocking1.9.2: ? → .2+
Assignee | ||
Comment 28•15 years ago
|
||
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•15 years ago
|
||
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.
Assignee | ||
Comment 30•15 years ago
|
||
Ok, so what flags do we use in that case? Just an approval flag on the patch?
![]() |
||
Comment 31•15 years ago
|
||
Yep.
Comment 32•15 years ago
|
||
Does this apply cleanly to 1.9.2 and 1.9.1? If so, please request approval on the patch.
Assignee | ||
Updated•15 years ago
|
Attachment #424371 -
Flags: approval1.9.2.2?
Attachment #424371 -
Flags: approval1.9.1.9?
Reporter | ||
Updated•15 years ago
|
Reporter | ||
Comment 34•15 years ago
|
||
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+
Reporter | ||
Updated•15 years ago
|
Attachment #430466 -
Flags: approval1.9.0.19? → approval1.9.0.19+
Assignee | ||
Comment 35•15 years ago
|
||
Assignee | ||
Comment 36•15 years ago
|
||
Whiteboard: [sg:critical?] → [sg:critical?][needs 190 landing]
Comment 37•15 years ago
|
||
mozilla/layout/xul/base/src/tree/src/nsTreeContentView.cpp 1.72
Keywords: fixed1.9.0.19
Whiteboard: [sg:critical?][needs 190 landing] → [sg:critical?]
Comment 38•15 years ago
|
||
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•15 years ago
|
||
Updated•15 years ago
|
Alias: CVE-2010-0176
Reporter | ||
Updated•15 years ago
|
Attachment #420452 -
Attachment is private: true
Reporter | ||
Updated•15 years ago
|
Group: core-security
Assignee | ||
Comment 40•15 years ago
|
||
Where did the testcase in this bug go? I wanted to check it in as a crashtest now that this bug is public.
Attachment #420452 -
Attachment is private: false
You should be able to see it now.
Assignee | ||
Comment 42•15 years ago
|
||
Added crashtest
http://hg.mozilla.org/mozilla-central/rev/9c4c5b4232b3
Flags: in-testsuite? → in-testsuite+
You need to log in
before you can comment on or make changes to this bug.
Description
•