Last Comment Bug 393537 - Heap corruption on Out-of-Memory in jsopcode.c
: Heap corruption on Out-of-Memory in jsopcode.c
Status: RESOLVED FIXED
[sg:critical?]
: fixed1.8.0.15, verified1.8.1.8
Product: Core
Classification: Components
Component: JavaScript Engine (show other bugs)
: unspecified
: All All
: -- critical (vote)
: ---
Assigned To: Igor Bukanov
:
: Jason Orendorff [:jorendorff]
Mentors:
Depends on:
Blocks:
  Show dependency treegraph
 
Reported: 2007-08-24 03:04 PDT by Igor Bukanov
Modified: 2008-03-20 12:03 PDT (History)
9 users (show)
dveditz: blocking1.8.1.8+
caillon: blocking1.8.0.next+
bob: in‑testsuite-
See Also:
Crash Signature:
(edit)
QA Whiteboard:
Iteration: ---
Points: ---
Has Regression Range: ---
Has STR: ---


Attachments
v1b (1.29 KB, patch)
2007-08-24 09:33 PDT, Igor Bukanov
brendan: review+
brendan: approval1.9+
Details | Diff | Splinter Review
port to 1.8.1 (1.17 KB, patch)
2007-09-24 01:55 PDT, Igor Bukanov
brendan: review+
dveditz: approval1.8.1.8+
asac: approval1.8.0.next+
Details | Diff | Splinter Review

Description Igor Bukanov 2007-08-24 03:04:10 PDT
When testing ./js1_5/extensions/regress-44009.js from the test suite with a patch from bug 393368 comment 9, attachment 278034 [details] [diff] [review], I got heap corruption. Note that the patch has an accounting bug leading to JS_ArenaRealloc returning null at an arbitrary moment, but that simulated realout-of-mmeory rather nicely.

Here is the output from running the test case with that patch:

~/m/trunk/mozilla/js/tests $ ./../src/Linux_All_DBG.OBJ/js  -f ./shell.js -f ./js1_5/shell.js -f ./js1_5/extensions/shell.js -f ./js1_5/extensions/regress-44009.js -f ./js-test-driver-end.js
BUGNUMBER: 44009
STATUS: Testing that we don't crash on obj.toSource()
./js1_5/extensions/regress-44009.js:83: out of memory
*** glibc detected *** /home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js: malloc(): memory corruption (fast): 0x08d71c08 ***
======= Backtrace: =========
/lib/libc.so.6[0x2a7bdc]
/lib/libc.so.6[0x2a87a2]
/lib/libc.so.6(realloc+0xfe)[0x2aa68e]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x80eaf49]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x80e9791]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x80e9ebe]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x80eacc8]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x80eb074]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x80c8e23]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x80d0c77]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x80d0685]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x80d7f67]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x80d88d4]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x8061550]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x8092a8f]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x8092b0a]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x809a9fa]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x809ae4c]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x80c6712]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x8107737]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x80bd546]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x809a9fa]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x80ab017]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x809b3c3]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x806163b]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x8049472]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x8049cb7]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js[0x804ea88]
/lib/libc.so.6(__libc_start_main+0xe0)[0x254f70]
/home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js(strncmp+0xdd)[0x80490d1]
======= Memory map: ========
00110000-00111000 r-xp 00110000 00:00 0          [vdso]
00220000-0023b000 r-xp 00000000 08:02 213335     /lib/ld-2.6.so
0023b000-0023c000 r-xp 0001a000 08:02 213335     /lib/ld-2.6.so
0023c000-0023d000 rwxp 0001b000 08:02 213335     /lib/ld-2.6.so
0023f000-0038d000 r-xp 00000000 08:02 213336     /lib/libc-2.6.so
0038d000-0038f000 r-xp 0014e000 08:02 213336     /lib/libc-2.6.so
0038f000-00390000 rwxp 00150000 08:02 213336     /lib/libc-2.6.so
00390000-00393000 rwxp 00390000 00:00 0 
00395000-003bc000 r-xp 00000000 08:02 213339     /lib/libm-2.6.so
003bc000-003bd000 r-xp 00026000 08:02 213339     /lib/libm-2.6.so
003bd000-003be000 rwxp 00027000 08:02 213339     /lib/libm-2.6.so
00762000-0076d000 r-xp 00000000 08:02 213346     /lib/libgcc_s-4.1.2-20070503.so.1
0076d000-0076e000 rwxp 0000a000 08:02 213346     /lib/libgcc_s-4.1.2-20070503.so.1
08048000-0813e000 r-xp 00000000 08:03 1442033    /home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js
0813e000-08144000 rw-p 000f5000 08:03 1442033    /home/igor/m/trunk/mozilla/js/src/Linux_All_DBG.OBJ/js
08d5b000-08d9c000 rw-p 08d5b000 00:00 0 
b7b00000-b7b21000 rw-p b7b00000 00:00 0 
b7b21000-b7c00000 ---p b7b21000 00:00 0 
b7cef000-b7cf0000 rw-p b7cef000 00:00 0 
b7cf0000-b7cf1000 r--p 00ac8000 08:02 312403     /usr/lib/locale/locale-archive
b7cf1000-b7dd2000 r--p 0026e000 08:02 312403     /usr/lib/locale/locale-archive
b7dd2000-b7fd2000 r--p 00000000 08:02 312403     /usr/lib/locale/locale-archive
b7fd2000-b7fd4000 rw-p b7fd2000 00:00 0 
bf885000-bf89a000 rw-p bf885000 00:00 0          [stack]
aborted


Here is the stack trace when JS_ArenaRealloc returned null the first time:

#0  JS_ArenaRealloc (pool=0x9efe944, p=0x9efea28, size=236, incr=52) at jsarena.c:229
#1  0x080c8b1b in SprintEnsureBuffer (sp=0x9efe930, len=52) at jsopcode.c:428
#2  0x080c8be2 in SprintPut (sp=0x9efe930, s=0x9efe988 " if (VERSION == \"JS_1.7\" || gTestsuite == \"js1_7\") {", len=52) at jsopcode.c:445
#3  0x080c9a5c in js_printf (jp=0x9efe930, format=0x0) at jsopcode.c:857
#4  0x080d0437 in Decompile (ss=0xbfc81524, pc=0x9efc5ea "\a", nb=318, nextop=JSOP_NOP) at jsopcode.c:2962
#5  0x080d7f67 in js_DecompileCode (jp=0x9efe930, script=0x9efc4d0, pc=0x9efc580 ";", len=318, pcdepth=0) at jsopcode.c:4665
#6  0x080d88d4 in js_DecompileFunction (jp=0x9efe930, fun=0x9ef3d48) at jsopcode.c:4867
#7  0x08061550 in JS_DecompileFunction (cx=0x9eeddb8, fun=0x9ef3d48, indent=32768) at jsapi.c:4743
#8  0x08092a8f in fun_toStringHelper (cx=0x9eeddb8, indent=32768, argc=0, vp=0x9f048e4) at jsfun.c:1567
#9  0x08092b0a in fun_toSource (cx=0x9eeddb8, argc=0, vp=0x9f048e4) at jsfun.c:1584
#10 0x0809a9fa in js_Invoke (cx=0x9eeddb8, argc=0, flags=2) at jsinterp.c:1351
#11 0x0809ae4c in js_InternalInvoke (cx=0x9eeddb8, obj=0x9ef1960, fval=166661248, flags=0, argc=0, argv=0x0, rval=0xbfc81900) at jsinterp.c:1474
#12 0x080c6712 in js_TryMethod (cx=0x9eeddb8, obj=0x9ef1960, atom=0x9eee23c, argc=0, argv=0x0, rval=0xbfc81900) at jsobj.c:4606
#13 0x08107737 in js_ValueToSource (cx=0x9eeddb8, v=166664544) at jsstr.c:2741
#14 0x080bd546 in obj_toSource (cx=0x9eeddb8, argc=0, vp=0x9f048c4) at jsobj.c:944
#15 0x0809a9fa in js_Invoke (cx=0x9eeddb8, argc=0, flags=0) at jsinterp.c:1351
#16 0x080ab017 in js_Interpret (cx=0x9eeddb8, pc=0x9efbb8d ":", result=0xbfc820e4) at jsinterp.c:4111
#17 0x0809b3c3 in js_Execute (cx=0x9eeddb8, chain=0x9ef0c20, script=0x9f01540, down=0x0, flags=0, result=0xbfc831b4) at jsinterp.c:1645
#18 0x0806163b in JS_ExecuteScript (cx=0x9eeddb8, obj=0x9ef0c20, script=0x9f01540, rval=0xbfc831b4) at jsapi.c:4777
#19 0x08049472 in Process (cx=0x9eeddb8, obj=0x9ef0c20, filename=0xbfc848d7 "./js1_5/extensions/regress-44009.js", forceTTY=0) at js.c:230
#20 0x08049cb7 in ProcessArgs (cx=0x9eeddb8, obj=0x9ef0c20, argv=0xbfc83348, argc=10) at js.c:464
#21 0x0804ea88 in main (argc=10, argv=0xbfc83348, envp=0xbfc83374) at js.c:3254


That lead later to the following abort from malloc:

#0  0x00110402 in __kernel_vsyscall ()
#1  0x00267fa0 in raise () from /lib/libc.so.6
#2  0x002698b1 in abort () from /lib/libc.so.6
#3  0x0029ed6b in __libc_message () from /lib/libc.so.6
#4  0x002a7bdc in _int_malloc () from /lib/libc.so.6
#5  0x002a87a2 in _int_realloc () from /lib/libc.so.6
#6  0x002aa68e in realloc () from /lib/libc.so.6
#7  0x080eaf49 in GrowStuff (ss=0xbfc80b9c, sp=0x9efacdb "gTestsuite == \"js1_6\"", len=21) at jsprf.c:1103
#8  0x080e9791 in fill2 (ss=0xbfc80b9c, src=0x9efacdb "gTestsuite == \"js1_6\"", srclen=21, width=-21, flags=0) at jsprf.c:142
#9  0x080e9ebe in cvt_s (ss=0xbfc80b9c, s=0x9efacdb "gTestsuite == \"js1_6\"", width=0, prec=-1, flags=0) at jsprf.c:398
#10 0x080eacc8 in dosprintf (ss=0xbfc80b9c, fmt=0x8134f1b "", ap=0xbfc80c04 "hïï\t(") at jsprf.c:1008
#11 0x080eb074 in JS_vsmprintf (fmt=0x8134f13 "%s %s %s", ap=0xbfc80bf8 "") at jsprf.c:1156
#12 0x080c8e23 in Sprint (sp=0xbfc81524, format=0x8134f13 "%s %s %s") at jsopcode.c:495
#13 0x080d0c77 in Decompile (ss=0xbfc81524, pc=0x9efc604 ";", nb=17, nextop=JSOP_NOP) at jsopcode.c:3074
#14 0x080d0685 in Decompile (ss=0xbfc81524, pc=0x9efc5f7 "\006", nb=318, nextop=JSOP_NOP) at jsopcode.c:2991
#15 0x080d7f67 in js_DecompileCode (jp=0x9efe930, script=0x9efc4d0, pc=0x9efc580 ";", len=318, pcdepth=0) at jsopcode.c:4665
#16 0x080d88d4 in js_DecompileFunction (jp=0x9efe930, fun=0x9ef3d48) at jsopcode.c:4867
#17 0x08061550 in JS_DecompileFunction (cx=0x9eeddb8, fun=0x9ef3d48, indent=32768) at jsapi.c:4743
#18 0x08092a8f in fun_toStringHelper (cx=0x9eeddb8, indent=32768, argc=0, vp=0x9f048e4) at jsfun.c:1567
#19 0x08092b0a in fun_toSource (cx=0x9eeddb8, argc=0, vp=0x9f048e4) at jsfun.c:1584
#20 0x0809a9fa in js_Invoke (cx=0x9eeddb8, argc=0, flags=2) at jsinterp.c:1351
#21 0x0809ae4c in js_InternalInvoke (cx=0x9eeddb8, obj=0x9ef1960, fval=166661248, flags=0, argc=0, argv=0x0, rval=0xbfc81900) at jsinterp.c:1474
#22 0x080c6712 in js_TryMethod (cx=0x9eeddb8, obj=0x9ef1960, atom=0x9eee23c, argc=0, argv=0x0, rval=0xbfc81900) at jsobj.c:4606
#23 0x08107737 in js_ValueToSource (cx=0x9eeddb8, v=166664544) at jsstr.c:2741
#24 0x080bd546 in obj_toSource (cx=0x9eeddb8, argc=0, vp=0x9f048c4) at jsobj.c:944
#25 0x0809a9fa in js_Invoke (cx=0x9eeddb8, argc=0, flags=0) at jsinterp.c:1351
#26 0x080ab017 in js_Interpret (cx=0x9eeddb8, pc=0x9efbb8d ":", result=0xbfc820e4) at jsinterp.c:4111
#27 0x0809b3c3 in js_Execute (cx=0x9eeddb8, chain=0x9ef0c20, script=0x9f01540, down=0x0, flags=0, result=0xbfc831b4) at jsinterp.c:1645
#28 0x0806163b in JS_ExecuteScript (cx=0x9eeddb8, obj=0x9ef0c20, script=0x9f01540, rval=0xbfc831b4) at jsapi.c:4777
#29 0x08049472 in Process (cx=0x9eeddb8, obj=0x9ef0c20, filename=0xbfc848d7 "./js1_5/extensions/regress-44009.js", forceTTY=0) at js.c:230
#30 0x08049cb7 in ProcessArgs (cx=0x9eeddb8, obj=0x9ef0c20, argv=0xbfc83348, argc=10) at js.c:464
#31 0x0804ea88 in main (argc=10, argv=0xbfc83348, envp=0xbfc83374) at js.c:3254
Comment 1 Igor Bukanov 2007-08-24 03:14:26 PDT
(In reply to bug 393368 comment #6)
>  By this reasoning, is it worth
> doing more here now? I am open to argument...

I guess this bug demonstrates pretty nicely how useful  the heap accounting can be for testing as it allows to simulate out-of-memory. It would be interesting to set the script stack limit to a low value and run the fuzzier.
Comment 2 Igor Bukanov 2007-08-24 09:33:58 PDT
Created attachment 278070 [details] [diff] [review]
v1b

The reason for the bug is that SprintEnsureBuffer on failed realloc sets sp->base to null while keeping sp->size at the previous value. Thus the following call to SprintEnsureBuffer would set sp->base to freshly allocated memory of size just nb bytes and then write past malloc boundary.

I think this is exploitable on systems with sufficient memory as one can use huge strings to trigger OOM failure in the decompiler at the right moment.
Comment 3 Brendan Eich [:brendan] 2007-08-24 18:08:45 PDT
Cc'ing fuzzier peeps ;-).

/be
Comment 4 Brendan Eich [:brendan] 2007-08-24 18:10:47 PDT
I would like to cc: gavin@picsel.com, since he has helped a great deal with simulated OOM testing. Comments?

/be
Comment 5 Brendan Eich [:brendan] 2007-08-24 18:11:25 PDT
Comment on attachment 278070 [details] [diff] [review]
v1b

Ancient, ancient bug of mine -- 1995, Netscape 2 late night hacking.

/be
Comment 6 Brian Crowder 2007-08-24 21:15:21 PDT
I would definitely second the idea of CCing Gavin on OOM bugs like this.
Comment 8 Jesse Ruderman 2007-08-25 00:50:43 PDT
Gavin, we think you might be interested in this bug.
Comment 9 Igor Bukanov 2007-08-25 13:11:43 PDT
I think the bug can be used for an exploit fairly straightforwardly as long as a system has enough swap to exhaust the whole address swap. The idea is to take advantage of the current Array.prototype.sort implementation that uses malloc to preallocate a buffer to hold Array.length elements even if for an array with many holes. So with code like:

var a = Array(7 << 26);
a.__defineGetter_(0, someFunction)
a.sort()

someFunction will be called when the code just allocated 1.75GB buffer but have not touched it yet. With a reasonable VM this would not cause any swapping, so if the system allows only 2GB of addressable space per process someFunction should be able to trigger OOM with 250 MB of data.
Comment 10 Gavin Reaney 2007-08-26 18:05:01 PDT
(In reply to comment #8)
> Gavin, we think you might be interested in this bug.
> 

Thank you, yes. I will ponder whether I can incorporate more rigorous testing of arena allocations into bug 383932 (when my day job becomes less demanding).
Comment 11 Igor Bukanov 2007-09-24 01:55:37 PDT
Created attachment 282082 [details] [diff] [review]
port to 1.8.1

This is a backport of the fix to 1.8.1 branch. On the branch the function containing the memory allocation code is in SprintAlloc, otherwise the patch is same as the following diff between the trunk and branch patches shows.

14c15
<  SprintEnsureBuffer(Sprinter *sp, size_t len)
---
>  SprintAlloc(Sprinter *sp, size_t nb)
16,21d16
<      ptrdiff_t nb;
< +    char *base;
<  
<      nb = (sp->offset + len + 1) - sp->size;
<      if (nb < 0)
<          return JS_TRUE;
23a19,20
> +    char *base;
> +
44c41
<      ptrdiff_t offset;
---
>      ptrdiff_t nb, offset;
Comment 12 Daniel Veditz [:dveditz] 2007-09-24 12:22:49 PDT
Comment on attachment 282082 [details] [diff] [review]
port to 1.8.1

approved for 1.8.1.8, a=dveditz for release-drivers
Comment 13 Igor Bukanov 2007-09-27 11:31:56 PDT
I checked in the patch from comment 11 to MOZILLA_1_8_BRANCH:

Checking in jsopcode.c;
/cvsroot/mozilla/js/src/jsopcode.c,v  <--  jsopcode.c
new revision: 3.89.2.73; previous revision: 3.89.2.72
done
Comment 14 Igor Bukanov 2007-09-27 11:33:28 PDT
The bug exists on MOZILLA_1_8_0_BRANCH.
Comment 15 Martijn Wargers [:mwargers] (not working for Mozilla) 2007-10-04 15:15:55 PDT
Igor, could you perhaps verify this for the 1.8 branch?
Comment 16 Igor Bukanov 2007-10-04 15:44:34 PDT
(In reply to comment #15)
> Igor, could you perhaps verify this for the 1.8 branch?

So far the verification is only possible through code review. That I did when back porting the patch to 1.8 branch so I could add verified* if necessary.

I can also try to implement a test case based on comment 9. Given that reliably working test case would mean an exploit boilerplate, is it desired?   

Comment 17 Martijn Wargers [:mwargers] (not working for Mozilla) 2007-10-04 16:01:28 PDT
Well, if you code reviewed the 1.8 branch after check in, I guess that should be enough. So if you could add verified1.8.1.8, that would be great.
Comment 18 Igor Bukanov 2007-10-04 16:06:57 PDT
I checked the 1.8.1 once again and the correct code is in place.
Comment 19 Alexander Sack 2008-02-28 07:07:42 PST
Comment on attachment 282082 [details] [diff] [review]
port to 1.8.1

a=asac for 1.8.0.15

(this patch shipped in distros for some time)
Comment 20 Christopher Aillon (sabbatical, not receiving bugmail) 2008-03-20 12:03:33 PDT
Patch committed to 1.8.0 branch

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