Closed Bug 393537 Opened 17 years ago Closed 17 years ago

Heap corruption on Out-of-Memory in jsopcode.c

Categories

(Core :: JavaScript Engine, defect)

defect
Not set
critical

Tracking

()

RESOLVED FIXED

People

(Reporter: igor, Assigned: igor)

Details

(Keywords: fixed1.8.0.15, verified1.8.1.8, Whiteboard: [sg:critical?])

Attachments

(1 file, 1 obsolete file)

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
(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.
Assignee: general → igor
Attached patch v1b (obsolete) — Splinter Review
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.
Attachment #278070 - Flags: review?(brendan)
Attachment #278070 - Flags: approval1.9?
Cc'ing fuzzier peeps ;-).

/be
I would like to cc: gavin@picsel.com, since he has helped a great deal with simulated OOM testing. Comments?

/be
Comment on attachment 278070 [details] [diff] [review]
v1b

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

/be
Attachment #278070 - Flags: review?(brendan)
Attachment #278070 - Flags: review+
Attachment #278070 - Flags: approval1.9?
Attachment #278070 - Flags: approval1.9+
I would definitely second the idea of CCing Gavin on OOM bugs like this.
I checked in the patch from comment 2 to the trunk:

http://bonsai.mozilla.org/cvsquery.cgi?module=PhoenixTinderbox&branch=HEAD&cvsroot=%2Fcvsroot&date=explicit&mindate=1188026220&maxdate=1188026282&who=igor%mir2.org
Status: NEW → RESOLVED
Closed: 17 years ago
Resolution: --- → FIXED
Gavin, we think you might be interested in this bug.
Whiteboard: [sg:critical?]
Flags: blocking1.8.1.7?
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.
(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).
Flags: blocking1.8.1.7? → blocking1.8.1.7+
Flags: in-testsuite-
Attached patch port to 1.8.1Splinter Review
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;
Attachment #278070 - Attachment is obsolete: true
Attachment #282082 - Flags: review?(brendan)
Attachment #282082 - Flags: approval1.8.1.8?
Attachment #282082 - Flags: review?(brendan) → review+
Comment on attachment 282082 [details] [diff] [review]
port to 1.8.1

approved for 1.8.1.8, a=dveditz for release-drivers
Attachment #282082 - Flags: approval1.8.1.8? → approval1.8.1.8+
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
Keywords: fixed1.8.1.8
The bug exists on MOZILLA_1_8_0_BRANCH.
Flags: blocking1.8.0.14?
Igor, could you perhaps verify this for the 1.8 branch?
(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?   

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.
I checked the 1.8.1 once again and the correct code is in place.
Keywords: verified1.8.1.8
Group: security
Flags: blocking1.8.0.14? → blocking1.8.0.15?
Flags: blocking1.8.0.15? → blocking1.8.0.15+
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)
Attachment #282082 - Flags: approval1.8.0.15+
Patch committed to 1.8.0 branch
Keywords: fixed1.8.0.15
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: