Closed Bug 311497 Opened 19 years ago Closed 19 years ago

Unrooted pivot in js_HeapSort

Categories

(Core :: JavaScript Engine, defect)

x86
Linux
defect
Not set
critical

Tracking

()

VERIFIED FIXED

People

(Reporter: igor, Assigned: brendan)

References

Details

(Keywords: verified1.7.13, verified1.8, Whiteboard: [sg:critical] arbitrary code execution (see comment 10))

Attachments

(7 files, 3 obsolete files)

js_HeapSort does not GC-root its pivot element. Thus if GC would be triggered during swap of elements in HeapSortHelper from jsarray.c while pivot holding value temporarily taken out from the array as a part of element swap and that value refers to GC-thing, then the sorted array would contain a pointer to a memory area GC considers free. I am not sure if this is exploitable, but it definitely allows to manipulate GC free list if one can trigger that GC in the middle of swap. To play it safe I mark the bug as a security problem.
The patch changes js_HeapSort to use memory for pivot suplied by client which can take all the necessary measures to root it.
Attachment #198796 - Flags: review?(brendan)
It turned out that the bug does not require multiple threads to exploit and can be used from single thread. The problem is that there is a code path which calls compare when pivot holds a reference that is overwritten in the working array. Thus the idea is to check in custom compare function for that condition and when it is met, remove all the references to affected elements and call operation that would trigger garbage collection. This attachment is demonstrates the problem for JS shell.
This can be used to crash JS engine embedding.
Severity: normal → critical
Comment on attachment 198796 [details] [diff] [review] Fix: pivot memory is passed to js_HeapSort Thanks, Igor. I'll check this (with cosmetic changes only, I promise) in on the trunk right now. /be
Attachment #198796 - Flags: review?(brendan)
Attachment #198796 - Flags: review+
Attachment #198796 - Flags: approval1.8rc1+
Attachment #198980 - Flags: review+
Attachment #198980 - Flags: approval1.8rc1?
Fixed on trunk. /be
Status: NEW → RESOLVED
Closed: 19 years ago
Flags: blocking1.8rc1+
Resolution: --- → FIXED
(In reply to comment #6) > Fixed on trunk. I believe this should be applied to Firefox 1.0.* since a variation of the above example allows to read 64 bits stored in GC things and to change 64 bits that GC thing holds to almost arbitrary predefined value. This is quite reliable since I can make Firefox to visit other pages before crashing after replacing context of a pointer to JSObject. Now I would not claim that this can be used to arbitrary code execution but knowledge of and ability to replace the value JSObject.map and JSObject.slots to chosen value can facilitate potential exploits. So I reopen this to make patch to apply to 1.0.* branch as well.
Status: RESOLVED → REOPENED
Resolution: FIXED → ---
We're using "fixed" for the trunk state, and nomination flags for the branches. Re-closing and plussing for aviary/17 branches
Status: REOPENED → RESOLVED
Closed: 19 years ago19 years ago
Flags: blocking1.7.13+
Flags: blocking-aviary1.0.8+
Resolution: --- → FIXED
Whiteboard: [sg:low] memory read at least, maybe more
Patches for 1.0.x and 1.7.y will take work -- the patch for the 1.8 branch here won't apply as is. /be
> [sg:low] memory read at least, maybe more So far it looks like it can be used for arbitrary code execution. Step 1. One starts with subverting a reference to a double value to point to JSString where JSString->chars holds the code to execute and extracts from double bits the address of JSString->chars. Step 2. Subvert another double reference to point to string where chars bytes when interpreted as JSObjectOps would give in JSObjectOps->defaultValue a pointer to the code to execute obtained on the previous step. Then one use double value to extract a pointer to JSObjectOps image. Step 3. Subvert third double reference into string that is an image of JSObjectMap with JSObjectMap->ops pointing to JSObjectOps from the previous step and extract the address of JSObjectMap. Step 4. Construct a double value which when interpreted as JSObject would point to JSObjectMap from the previous step through JSObject->map. Step 5. Subvert a reference to object to point to the double value from the previous step. Step 6. Call Math.sin() on the subverted object reference. Since runtime would assume that this is an object, it would call JSObject->map->ops->defaultValue and execute the code from step 1. For details of pointer subversion see bug 311792. Note that it is important to keep references to subverted doubles from step 1-3 to prevent strings they point to from GC forced at the following steps. Now what did I miss here?
Igor, you missed nothing -- you are ahead of the curve as usual ;-). We had another bug this year where I worked with a friendly hacker to develop an exploit based on forging an XPCOM object using a JSString. We treat heap overruns as exploitable by default. Even easier if instead of overrunning a malloc chunk, you can control GC-things. /be
Whiteboard: [sg:low] memory read at least, maybe more → [sg:critical] arbitrary code execution (see comment 10)
This testcase is not to be checked in until the bug is made public.
Flags: testcase?
There is no guaranty that the original test case would crash so it is better to verify the array structure on exit explicitly. In addition the test case force GC on each compare to avoid dependancy on details of array.sort implementation so if somebody commits the currently proposed version of merge sort to replace heap sort from bug 224128, the test case would spot unrooted access there.
Attachment #199039 - Attachment is obsolete: true
Attachment #198980 - Flags: approval1.8rc1? → approval1.8rc1+
Fixed on branch too. /be
Keywords: fixed1.8
Blocks: sbb?
(In reply to comment #13) Neither 1.8 or trunk cvs builds from this morning crash. 1.8 branch (opt/dbg) passes, trunk build (opt/dbg) fails Testcase js1_5/Regress/regress-311497.js failed Bug Number 311497 [ Top of Page ] STATUS: Root pivots in js_HeapSort Failure messages were: FAILED!: Root pivots in js_HeapSort FAILED!: Expected value '10', Actual value '5' FAILED!:
Igor, can you reproduce the difference for your "Better" testcase in the patched (bug 312069) trunk vs. the unpatched 1.8? Is this meaningful
(In reply to comment #16) > Igor, can you reproduce the difference for your "Better" testcase in the patched > (bug 312069) trunk vs. the unpatched 1.8? Is this meaningful Yes, this is meaningful! The trunk already contain changes for Bug 312069 which was a regression caused by my fix for this bug. Now that "fix for the fix" caused regression in this bug! It seems that extra argv + argc and argv + argc + 1 GC-roots that the last patch uses are not treated as GC roots at all! I do not know why it is so. Since the 1.8 branch contains only "fix", not "fix" + "fix for the fix", the example works there.
Reopening per above comment :(
Status: RESOLVED → REOPENED
Resolution: FIXED → ---
With the following patch to jsgc.c the test case no longer crashes the jsshell. But the patch can not be right since it assumes local roots where missed from GC-marking and that can not be true! So what is wrong there? --- jsgc.c 29 Jul 2005 15:15:48 -0000 3.104 +++ jsgc.c 13 Oct 2005 14:34:34 -0000 @@ -1747,8 +1747,8 @@ GC_MARK(cx, fp->thisp, "this", NULL); if (fp->argv) { nslots = fp->argc; - if (fp->fun && fp->fun->nargs > nslots) - nslots = fp->fun->nargs; + if (fp->fun && fp->fun->nargs + fp->fun->extra > nslots) + nslots = fp->fun->nargs + fp->fun->extra; GC_MARK_JSVALS(cx, nslots, fp->argv, "arg"); } if (JSVAL_IS_GCTHING(fp->rval))
(In reply to comment #19) > With the following patch to jsgc.c the test case no longer crashes the jsshell. > But the patch can not be right since it assumes local roots where missed from > GC-marking and that can not be true! So what is wrong there? This is a very old bug. Normally, arguments that the callee accesses via fp->argv come from the caller's operand stack, so they lie within fp->down->spbase and fp->down->sp. All native functions are invoked via js_Invoke, and it adds fun->extra to fun->nargs (see the minargs local in js_Invoke) to ensure that fun->nargs + fun->extra contiguous jsvals are available at fp->argv for the callee to use, irrespective of argc -- and especially including the local roots at the end. This implies that typically, the GC redundantly marks, by scanning fp->argv[0..MAX(fp->argc,fp->nargs)] the same operand stack space it will mark when it moves fp down one frame to the caller and scans: fp->spbase[0..MIN(fp->sp - fp->spbase, fp->script->depth)] There are cases where argv is not on the operand stack, or there could be, and I didn't want to rule that out, so I left this redundant scanning in. But there's a bug in it, and igor's patch fixes that bug. Question is, why does the bug only bite rarely, as in the present case? It's because normally the caller opernad stack space includes enough room for the local roots. But not so in the testcase. When the caller's operand stack budget, fp->script->depth for fp denoting the caller frame (fp->down where fp denotes the callee's frame), is not big enough to hold the actual arguments, any missing formal parameters, and the fp->fun->extra local roots, then whatever doesn't fit will not be scanned. Igor's patch is a reasonable fix. There might be a tighter fix that overscans fp->spbase (fp the caller's frame) to include missing formals and extra local roots that fit in the stack arena, but that exceeded the depth budget. Igor, what do you think? If your patch seems best, please attach it. Thanks for finding this very old bug! /be
(In reply to comment #20) > There might be a tighter fix that overscans > fp->spbase (fp the caller's frame) to include missing formals and extra local > roots that fit in the stack arena, but that exceeded the depth budget. In other words, perhaps the operand stack scanning should cover fp->spbase to fp->sp, never mind depth. But that has a bug, because we could have a hole due to hitting end of a stack arena, and fp->sp is in the next arena (along with any fp->vars allocated for a scripted function -- not an issue for natives as in the present case). So it's tricky, and perhaps Igor's patch is best, but I'll let him noodle on this and shut up now. /be
Blocks: 312069
This version of the test case contains a proof that the patch in comment 19 is not enough. The problem is that array_sort assumes that extra slots starts from argv + argc while the patch fixes GC scanning code to scan from fun->nargs + fun->extra. Now if in the test case from attachment 199062 [details] one replaces array.sort(cmp) by array.sort(cmp,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18); then one would get segmentation fault after the first GC. The problem is in js_Invoke which assumes that a native function would use for fun->extra slots after fun->nargs, not after MAX(fun->nargs, argc). So here is a test case that covers both the original array.sort(cmp) and the extra run with array.sort(cmp,0,1...);
Attachment #199062 - Attachment is obsolete: true
Attachment #199543 - Flags: review?(brendan)
Comment on attachment 199543 [details] [diff] [review] Fix for fun->extra bug Thanks, Igor -- you are the best! This bug goes back 8 years, I recall fur reviewing this code. My fault, should have re-read it more carefully at some point. I will attach a version of this patch that tries to minimize change in some places (using nslots instead of extraslots), while getting rid of unnecessary intN usage nearby. /be
Attachment #199543 - Flags: review?(brendan) → review+
Looking for review to make sure I didn't fat-finger (or fat-brain) anything. I'll take the bug to make up for 8 years of its existence, and get this checked into the trunk when reviewed, and then get branch approval. Thanks again, /be
Assignee: general → brendan
Status: REOPENED → ASSIGNED
Attachment #199564 - Flags: superreview?(shaver)
Attachment #199564 - Flags: review?(igor.bukanov)
Comment on attachment 199564 [details] [diff] [review] minimize using nslots, eliminate intN locals and casts > /* Now allocate stack space for local variables. */ >- nslots = (intN)frame.nvars; >+ nslots = frame.nvars; > if (nslots) { BTW, frame.nvars is nvars here. Thus replacing nslots by nvars in the "if" body one gets IMO more clear code especially with extra JS_ASSERT(nvars == 0) after the "if". But then the patch would affect 3 additional lines.
Attachment #199564 - Flags: review?(igor.bukanov) → review+
From the quick look at the bug 311161 it *CAN* be the case that extra underscan is at fault teher as well. Object.prototype.toSource requires 3 extra slots so GC can wipe out them if an extra stack would be allocated for them. With 3 extras and the fact that script contains calls with no more then 2 arguments the bug is exposed here as well.
Comment on attachment 199564 [details] [diff] [review] minimize using nslots, eliminate intN locals and casts Fat-fingered it: nalloc must be tested in a signed sense > 0, it can underflow. I left its type unsigned to minimize casts. New patch in a minute. /be
Attachment #199564 - Attachment is obsolete: true
Attachment #199564 - Flags: superreview?(shaver)
Attachment #199564 - Flags: review+
Blocks: 311161
Attached patch fix v2Splinter Review
Works, even. /be
Attachment #199612 - Flags: superreview?(shaver)
Attachment #199612 - Flags: review+
Comment on attachment 199612 [details] [diff] [review] fix v2 Bugzilla's interdiff works, but warns that it may not. Check it out. This patch uses nvars instead of nslots = frame.nvars as Igor suggested, too. /be
Comment on attachment 199612 [details] [diff] [review] fix v2 sr=shaver
Attachment #199612 - Flags: superreview?(shaver) → superreview+
Comment on attachment 199612 [details] [diff] [review] fix v2 This is in the trunk now. It fixes a critical security bug, which is in essence 8+ years old, and which has probably caused other mystery-crashes over the years. It looks like it should fix another bug on our current critical blocking1.8rc1 list. How about approving it on Monday if all is well on the trunk? /be
Attachment #199612 - Flags: approval1.8rc1?
Fixed on the trunk. /be
Status: ASSIGNED → RESOLVED
Closed: 19 years ago19 years ago
Resolution: --- → FIXED
No longer blocks: 311161
*** Bug 311161 has been marked as a duplicate of this bug. ***
Attachment #199612 - Flags: approval1.8rc1? → approval1.8rc1+
bug 312069 comment 19 claims this was checked in on the branch, and the patch for bug 312069 was checked in. I am now getting Testcase js1_5/Regress/regress-311497.js failed Bug Number 311497 [ Top of Page ] STATUS: Root pivots in js_HeapSort Failure messages were: FAILED!: Root pivots in js_HeapSort FAILED!: Expected value '10', Actual value '5' FAILED!: on the branch as well. Was this really checked in to the branch?
Fix v2 checked in. /be
verified firefox 1.5 rc2 linux/win32 2005-11-07
Keywords: fixed1.8verified1.8
testcase+ to get this off my radar. when this is made public, i will check in the test.
Flags: testcase? → testcase+
Comment on attachment 199612 [details] [diff] [review] fix v2 aviary101/moz17 landing approval: a=dveditz for drivers. Please add the fixed1.7.13 and fixed-aviary1.0.8 keywords when landed.
Attachment #199612 - Flags: approval1.7.13+
Attachment #199612 - Flags: approval-aviary1.0.8+
Comment on attachment 199612 [details] [diff] [review] fix v2 per comment 3 it sounds like this patch won't work on the old branches. Can we get a new one?
Attachment #199612 - Flags: approval1.7.13?
Attachment #199612 - Flags: approval1.7.13+
Attachment #199612 - Flags: approval-aviary1.0.8?
Attachment #199612 - Flags: approval-aviary1.0.8+
Attachment #198980 - Flags: approval1.7.13?
Attachment #198980 - Flags: approval-aviary1.0.8?
In discussion with BKap tonight both patches (#3, and #6 above) are needed and need minor mechnical backporting for 1.7 tree. Please approve the above patches if you want them to be backported to the 1.7 tree.
Comment on attachment 198980 [details] [diff] [review] patch extracted from trunk for branch checkin a=dveditz
Attachment #198980 - Flags: approval1.7.13?
Attachment #198980 - Flags: approval1.7.13+
Attachment #198980 - Flags: approval-aviary1.0.8?
Attachment #198980 - Flags: approval-aviary1.0.8+
Comment on attachment 199612 [details] [diff] [review] fix v2 a=dveditz
Attachment #199612 - Flags: approval1.7.13?
Attachment #199612 - Flags: approval1.7.13+
Attachment #199612 - Flags: approval-aviary1.0.8?
Attachment #199612 - Flags: approval-aviary1.0.8+
The easiest way to verify this might be to apply it and diff against the MOZILLA_1_8_BRANCH.
Attachment #211904 - Flags: review?(brendan)
Attachment #211904 - Flags: approval1.7.13?
Attachment #211904 - Flags: approval-aviary1.0.8?
Comment on attachment 211904 [details] [diff] [review] array fixes, backporteed a=timr for drivers. Approved pending review.
Attachment #211904 - Flags: approval1.7.13?
Attachment #211904 - Flags: approval1.7.13+
Attachment #211904 - Flags: approval-aviary1.0.8?
Attachment #211904 - Flags: approval-aviary1.0.8+
Comment on attachment 211904 [details] [diff] [review] array fixes, backporteed Looks good, thanks for doing this. /be
Attachment #211904 - Flags: review?(brendan) → review+
Fixes checked into the 1.7 branch.
v ff on 1.7.13, 1.8.0.1, 1.8, 1.9a1 on 20060221 windows/linux/mac builds, moz on 1.7.13 20060221 windows build.
Status: RESOLVED → VERIFIED
Group: security
RCS file: /cvsroot/mozilla/js/tests/js1_5/GC/regress-311497.js,v done Checking in regress-311497.js; /cvsroot/mozilla/js/tests/js1_5/GC/regress-311497.js,v <-- regress-311497.js initial revision: 1.1 done
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: