Closed Bug 586878 (ZDI-CAN-883) Opened 14 years ago Closed 13 years ago

nsFrameManager Remote Code Execution Vulnerability (ZDI-CAN-883)

Categories

(Core :: CSS Parsing and Computation, defect)

defect
Not set
critical

Tracking

()

RESOLVED FIXED
mozilla2.0
Tracking Status
status2.0 --- unaffected
blocking1.9.2 --- -
status1.9.2 --- wontfix
status1.9.1 --- ?

People

(Reporter: reed, Unassigned)

References

(Blocks 1 open bug)

Details

(Keywords: crash, regression, testcase, Whiteboard: [sg:dos] frame-poisoned crash, fixed on trunk by 508473)

Attachments

(2 files)

Attached file PoC
ZDI-CAN-883: Mozilla Firefox nsFrameManager Remote Code Execution Vulnerability

-- CVSS ----------------------------------------------------------------
10, (AV:N/AC:L/Au:N/C:C/I:C/A:C)

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

TippingPoint has identified a vulnerability affecting the following 
products:

    Mozilla Firefox 3.6.x

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

This vulnerability allows remote attackers to execute arbitrary code on
vulnerable installations of Mozilla Firefox. User interaction is
required to exploit this vulnerability in that the target must visit a
malicious page or open a malicious file.

The specific flaw exists within the way the application recursively
destroys frames. When a frame contains a child element with a particular
style during destruction, the application will add 2 references of the
overflown element to a list. On destruction of the frame, the
application will then iterate through this list, and then free the
reference twice.  This can lead to code execution under the context of
the application.


The issue is located within the implementation of
nsCSSFrameConstructor.cpp. When calling DoDeletingFrameSubtree, the
application will add a reference to the div with the large attribute to
the destroyQueue. 
When the application destroys the current frame, the following function
will get called. This will determine which frames will be destroyed by
calling a recursive function and then adding each subelement to a list
called destroyQueue. This will then call DoDeletingFrameSubtree.

layout/base/nsCSSFrameConstructor.cpp:7129
static nsresult
DeletingFrameSubtree(nsFrameManager* aFrameManager,
                     nsIFrame*       aFrame)
{
  NS_ENSURE_TRUE(aFrame, NS_OK); // XXXldb Remove this sometime in the
future.

  // If there's no frame manager it's probably because the pres shell
is
  // being destroyed.
  if (NS_UNLIKELY(!aFrameManager)) {
    return NS_OK;
  }

  nsAutoTArray<nsIFrame*, 8> destroyQueue;

  // If it's a "special" block-in-inline frame, then we can't really
deal.
  // That really shouldn't be happening.
  NS_ASSERTION(!IsFrameSpecial(aFrame),
               "DeletingFrameSubtree on a special frame.  Prepare to
crash.");

  do {
    DoDeletingFrameSubtree(aFrameManager, destroyQueue, aFrame, aFrame);
   // XXX
  ...


This will enter the recursive function. This function will identify
containers and frames that are out of flow and will add them to an
argument that's provided by the caller.

layout/base/nsCSSFrameConstructor.cpp:7148
static void
DoDeletingFrameSubtree(nsFrameManager*      aFrameManager,
                       nsTArray<nsIFrame*>& aDestroyQueue,
                       nsIFrame*            aRemovedFrame,
                       nsIFrame*            aFrame)
{
#undef RECURSE
#define RECURSE(top, child)                                             
    \
  DoDeletingFrameSubtree(aFrameManager, aDestroyQueue, (top), (child)); 
    \  // XXX
  DoDeletingOverflowContainers(aFrameManager, aDestroyQueue, (top),
(child));   // XXX

  // Remove the mapping from the content object to its frame.
  nsIContent* content = aFrame->GetContent();
  if (content) {
    aFrameManager->RemoveAsPrimaryFrame(content, aFrame);
    aFrameManager->ClearAllUndisplayedContentIn(content);
  }

  nsIAtom* childListName = nsnull;
  PRInt32 childListIndex = 0;

  ...
        
        // Queue the out-of-flow frame to be destroyed only if
aRemovedFrame is _not_
        // one of its ancestor frames or if it is a popup frame. 
        // If aRemovedFrame is an ancestor of the out-of-flow frame,
then 
        // the out-of-flow frame will be destroyed by aRemovedFrame.
        if (outOfFlowFrame->GetStyleDisplay()->mDisplay ==
NS_STYLE_DISPLAY_POPUP ||
            !nsLayoutUtils::IsProperAncestorFrame(aRemovedFrame,
outOfFlowFrame)) {
          NS_ASSERTION(aDestroyQueue.IndexOf(outOfFlowFrame) ==
kNotFound,
                       "out-of-flow is already in the destroy queue");
          aDestroyQueue.AppendElement(outOfFlowFrame);          // XXX
          // Recurse into the out-of-flow, it is now the aRemovedFrame.
          RECURSE(outOfFlowFrame, outOfFlowFrame);
        }
        else {
          // Also recurse into the out-of-flow when it's a descendant of
aRemovedFrame
          // since we don't walk those lists, see |childListName|
increment below.
          RECURSE(aRemovedFrame, outOfFlowFrame);
        }
      }
    }

One of the recursive functions will add an overflown container to the
destroyQueue.

layout/base/nsCSSFrameConstructor.cpp:7014
static void
DoDeletingOverflowContainers(nsFrameManager*      aFrameManager,
                             nsTArray<nsIFrame*>& aDestroyQueue,
                             nsIFrame*            aRemovedFrame,
                             nsIFrame*            aFrame)
{
  // The invariant that "continuing frames should be found as part of
the
  // walk over the top-most frame's continuing frames" does not hold
for
  // out-of-flow overflow containers, so we need to walk them too.
  // Note that DoDeletingFrameSubtree() skips the child lists where
  // overflow containers live so we won't process them twice.
  const PRBool orphanSubtree = aRemovedFrame == aFrame;
  for (nsIFrame* next = aFrame->GetNextContinuation();
       next && (next->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER);
       next = next->GetNextContinuation()) {
    DoDeletingFrameSubtree(aFrameManager, aDestroyQueue,        // XXX
                           orphanSubtree ? next : aRemovedFrame,
                           next);
  }
}

After populating the destroyQueue via the recursive function, the
application will iterate through each element and destroy them. This
will free the element twice.

layout/base/nsCSSFrameConstructor.cpp:7125
  // Now destroy any out-of-flow frames that have been enqueued for
  // destruction.
  for (PRInt32 i = destroyQueue.Length() - 1; i >= 0; --i) {
    nsIFrame* outOfFlowFrame = destroyQueue[i];

    // Ask the out-of-flow's parent to delete the out-of-flow
    // frame from the right list.
    aFrameManager->RemoveFrame(outOfFlowFrame->GetParent(),      // XXX
                               GetChildListNameFor(outOfFlowFrame),
                               outOfFlowFrame);
  }

  return NS_OK;
}

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

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

This vulnerability was discovered by:
    * regenrecht
blocking1.9.1: --- → ?
blocking1.9.2: --- → ?
blocking2.0: --- → ?
status1.9.1: --- → ?
status1.9.2: --- → ?
status2.0: --- → ?
I am unable to get this to crash on trunk or 3.6.x on Linux 32-bit...

Mozilla/5.0 (X11; Linux i686; rv:2.0b4pre) Gecko/20100812 Minefield/4.0b4pre

Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.9pre) Gecko/20100812 Namoroka/3.6.9pre
Keywords: crash
Whiteboard: [sg:critical?]
In FF3.5.x I only see the assertion

###!!! ASSERTION: Nothing to handle this event!: 'frame', file /Users/daniel/dev/ffcentral/moz191/layout/base/nsPresShell.cpp, line 6006

ten times with the PoC, but no crash.
Attached file testcase
With a slight tweak, it is crashing for me, by removing the style of the first styled element.

This crashes in 3.6.8, but not in trunk:
http://crash-stats.mozilla.com/report/index/0efe3227-7adf-43fa-aa1c-12fe12100813
0  	xul.dll  	nsFrameManager::RemoveFrame  	 layout/base/nsFrameManager.cpp:735
1 	xul.dll 	nsCSSFrameConstructor::ContentRemoved
On trunk, this was fixed between 2009-12-16 and 2009-12-26, I guess bug 508473 fixed this.
On 3.6.9pre I get bp-7aab4c4c-cc7a-4b32-a44c-b1fba2100814 so that's looking like a frame-poisoned crash in 3.6.x

I still don't crash in 3.5.x even with Martijn's modified testcase.
Whiteboard: [sg:dos] frame-poisoned crash
Not blocking a 1.9.2 release if it's not exploitable. Appears WFM on 2.0 (I agree w/Martijn in comment 4 that bug 508473 looks like a likely fix).

Unsure whether 3.5 is truly unaffected or if it's just masked and could be exposed through a different testcase.
blocking1.9.1: ? → ---
blocking1.9.2: ? → -
blocking2.0: ? → ---
Keywords: crash
Version: unspecified → 1.9.2 Branch
Alias: ZDI-CAN-883
Alias: ZDI-CAN-883
Depends on: 508473
Whiteboard: [sg:dos] frame-poisoned crash → [sg:dos] frame-poisoned crash, fixed on trunk by 508473
Alias: ZDI-CAN-883
We decided bug 508473 is out of scope for the 1.9.2 branch to fix a DoS so not much point to tracking this as a branch bug. Fixed on trunk, wontfix on branch.
Status: NEW → RESOLVED
Closed: 13 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla2.0
Version: 1.9.2 Branch → unspecified
Group: core-security
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: