Red Hat Bugzilla – Bug 168278
CAN-2005-2978 Crash running pnmtopng -trans on some pnm files
Last modified: 2013-07-02 19:09:13 EDT
netpbm-progs-10.25-2.EL4.1 The crash only occurs with -fPIC, but the generated file is broken when it is compiled without (as expected). The reproducer is: cat ~/well.pnm | ./pnmtopng -trans '#ffffff' > well.png paletteIndex is different for each crash. (gdb) frame 0 #0 0x0000000000401b21 in closestColorInPalette (targetColor= {r = 255, g = 255, b = 255}, palette_pnm=0x7fbfffa080, paletteSize=3014898611, bestIndexP=0x7fbfff93ac, bestMatchP=0x7fbfff93a8) at pnmtopng.c:168 168 unsigned int const dist = (gdb) list 163 unsigned int bestMatch; 164 165 bestMatch = UINT_MAX; 166 for (paletteIndex = 0; paletteIndex < paletteSize; ++paletteIndex) {167 pm_message ("%u", paletteIndex); 168 unsigned int const dist = 169 PPM_DISTANCE(palette_pnm[paletteIndex], targetColor); 170 171 if (dist < bestMatch) { 172 bestMatch = dist; (gdb) (gdb) list 173 bestIndex = paletteIndex; 174 } 175 } 176 if (bestIndexP != NULL) 177 *bestIndexP = bestIndex; 178 if (bestMatchP != NULL) 179 *bestMatchP = bestMatch; 180 } 181 182 (gdb) frame 0 #0 0x0000000000401b21 in closestColorInPalette (targetColor= {r = 255, g = 255, b = 255}, palette_pnm=0x7fbfffa080, paletteSize=3014898611, bestIndexP=0x7fbfff93ac, bestMatchP=0x7fbfff93a8) at pnmtopng.c:168 168 unsigned int const dist = (gdb) list 163 unsigned int bestMatch; 164 165 bestMatch = UINT_MAX; 166 for (paletteIndex = 0; paletteIndex < paletteSize; ++paletteIndex) {167 pm_message ("%u", paletteIndex); 168 unsigned int const dist = 169 PPM_DISTANCE(palette_pnm[paletteIndex], targetColor); 170 171 if (dist < bestMatch) { 172 bestMatch = dist; (gdb) bt #0 0x0000000000401b21 in closestColorInPalette (targetColor= {r = 255, g = 255, b = 255}, palette_pnm=0x7fbfffa080, paletteSize=3014898611, bestIndexP=0x7fbfff93ac, bestMatchP=0x7fbfff93a8) at pnmtopng.c:168 #1 0x00000000004035a3 in makeOneColorTransparentInPalette (transColor= {r = 255, g = 255, b = 255}, exact=0, palette_pnm=0x7fbfffa080, paletteSize=3014898611, trans_pnm=0x7fbfff9970, transSizeP=0x7fbfff986c) at pnmtopng.c:1003 #2 0x0000000000403e7a in computeColorMap (ifP=0x50a010, imagepos=12, cols=8, rows=32, maxval=255, format=20534, force=0, pfP=0x0, alpha=0, transparent=1, transcolor={r = 255, g = 255, b = 255}, transexact=0, background=-1, backcolor={r = 3221204828, g = 127, b = 5280632}, alpha_mask=0x0, pnm_meaningful_bits=8, palette_pnm=0x7fbfffa080, paletteSizeP=0x7fbfff9d7c, trans_pnm=0x7fbfff9970, transSizeP=0x7fbfff986c, backgroundIndexP=0x7fbfff9854, noColormapReasonP=0x7fbfff9620) at pnmtopng.c:1367 #3 0x0000000000404a7e in convertpnm (ifp=0x50a010, afp=0x0, pfp=0x0, tfp=0x0, gamma=-1, interlace=0, downscale=0, transparent_opt=1, transstring=0x7fbfffdb4b "#ffffff", alpha_opt=0, alpha_file=0x2a95da0260 "", background=-1, backstring=0x2 <Address 0x2 out of bounds>, hist=0, chroma= {wx = -1, wy = -1, rx = -1, ry = -1, gx = -1, gy = -1, bx = -1, by = -1}, phys_x=-1, phys_y=-1, phys_unit=-1, text=0, ztxt=0, text_file=0x0, mtime=0, date_string=0x7fbfffaf00 "", time_string=0x0, filterSet=0, force=0, zlib_compression= {level = -1, mem_level = 127, strategy = 5280464, window_bits = 0, method = 4200704, buffer_size = 0}) at pnmtopng.c:1756 #4 0x0000000000406cf9 in main (argc=3, argv=0x7fbfffb068) at pnmtopng.c:2388 And with valgrind-3.0.1-1 (from rawhide for x86-64): $ cat ~/well.pnm | valgrind --tool=memcheck ./pnmtopng -trans '#ffffff' > well.png ==14841== Memcheck, a memory error detector. ==14841== Copyright (C) 2002-2005, and GNU GPL'd, by Julian Seward et al. ==14841== Using LibVEX rev 1367, a library for dynamic binary translation. ==14841== Copyright (C) 2004-2005, and GNU GPL'd, by OpenWorks LLP. ==14841== Using valgrind-3.0.1, a dynamic binary instrumentation framework. ==14841== Copyright (C) 2000-2005, and GNU GPL'd, by Julian Seward et al. ==14841== For more details, rerun with: -v ==14841== pnmtopng: 3 colors found ==14841== Conditional jump or move depends on uninitialised value(s) ==14841== at 0x403554: makeOneColorTransparentInPalette (pnmtopng.c:998) ==14841== by 0x403E61: computeColorMap (pnmtopng.c:1366) ==14841== by 0x404A65: convertpnm (pnmtopng.c:1755) ==14841== by 0x406CE0: main (pnmtopng.c:2387) ==14841== ==14841== Conditional jump or move depends on uninitialised value(s) ==14841== at 0x401AA0: closestColorInPalette (pnmtopng.c:166) ==14841== by 0x40358A: makeOneColorTransparentInPalette (pnmtopng.c:1002) ==14841== by 0x403E61: computeColorMap (pnmtopng.c:1366) ==14841== by 0x404A65: convertpnm (pnmtopng.c:1755) ==14841== by 0x406CE0: main (pnmtopng.c:2387) ==14841== ==14841== Conditional jump or move depends on uninitialised value(s) ==14841== at 0x401B8A: closestColorInPalette (pnmtopng.c:170) ==14841== by 0x40358A: makeOneColorTransparentInPalette (pnmtopng.c:1002) ==14841== by 0x403E61: computeColorMap (pnmtopng.c:1366) ==14841== by 0x404A65: convertpnm (pnmtopng.c:1755) ==14841== by 0x406CE0: main (pnmtopng.c:2387) ==14841== ==14841== Invalid read of size 4 ==14841== at 0x401B52: closestColorInPalette (pnmtopng.c:167) ==14841== by 0x40358A: makeOneColorTransparentInPalette (pnmtopng.c:1002) ==14841== by 0x403E61: computeColorMap (pnmtopng.c:1366) ==14841== by 0x404A65: convertpnm (pnmtopng.c:1755) ==14841== by 0x406CE0: main (pnmtopng.c:2387) ==14841== Address 0x34B4A00000 is not stack'd, malloc'd or (recently) free'd ==14841== ==14841== Process terminating with default action of signal 11 (SIGSEGV): dumping core ==14841== Bad permissions for mapped region at address 0x34B4A00000 ==14841== at 0x401B52: closestColorInPalette (pnmtopng.c:167) ==14841== by 0x40358A: makeOneColorTransparentInPalette (pnmtopng.c:1002) ==14841== by 0x403E61: computeColorMap (pnmtopng.c:1366) ==14841== by 0x404A65: convertpnm (pnmtopng.c:1755) ==14841== by 0x406CE0: main (pnmtopng.c:2387) ==14841== ==14841== ERROR SUMMARY: 1134 errors from 4 contexts (suppressed: 7 from 4) ==14841== malloc/free: in use at exit: 14596 bytes in 8 blocks. ==14841== malloc/free: 14 allocs, 6 frees, 175044 bytes allocated. ==14841== For counts of detected errors, rerun with: -v ==14841== searching for pointers to 8 not-freed blocks. ==14841== checked 170240 bytes. ==14841== ==14841== LEAK SUMMARY: ==14841== definitely lost: 0 bytes in 0 blocks. ==14841== possibly lost: 0 bytes in 0 blocks. ==14841== still reachable: 14596 bytes in 8 blocks. ==14841== suppressed: 0 bytes in 0 blocks. ==14841== Reachable blocks (those to which a pointer was found) are not shown. ==14841== To see them, rerun with: --show-reachable=yes Segmentation fault (core dumped) The errors shown in valgrind also appear on x86, which might be a better platform for debugging, although the problem has only been reproduced on x86-64 so far.
Created attachment 118793 [details] well.pnm
Can be reproduced with netpbm-progs-10.29-1 from rawhide.
Created attachment 118798 [details] sample.pnm.bz2 Another test file, bzipped2.
btw. I see the crash elsewhere when compiled with -O0 -g3: gdb --args ./pnmtopng -trans '#ffffff' ~/well.pnm Program received signal SIGSEGV, Segmentation fault. 0x0000000000404952 in writeRaster (png_ptr=0x50a250, info_ptr=0x50c760, ifP=0x50a010, imagepos=12, cols=8, rows=32, maxval=255, format=20534, png_maxval=3, depth=2, alpha=0, alpha_mask=0x0, cht=0x2a95558010, caht=0x0) at pnmtopng.c:1693 1693 makePngLine(line, xelrow, cols, maxval, alpha, alpha_mask[row], (gdb) bt #0 0x0000000000404952 in writeRaster (png_ptr=0x50a250, info_ptr=0x50c760, ifP=0x50a010, imagepos=12, cols=8, rows=32, maxval=255, format=20534, png_maxval=3, depth=2, alpha=0, alpha_mask=0x0, cht=0x2a95558010, caht=0x0) at pnmtopng.c:1693 #1 0x0000000000405df0 in convertpnm (ifp=0x50a010, afp=0x0, pfp=0x0, tfp=0x0, gamma=-1, interlace=0, downscale=0, transparent_opt=1, transstring=0x7fbffffb17 "#ffffff", alpha_opt=0, alpha_file=0x0, background=-1, backstring=0x0, hist=0, chroma= {wx = -1, wy = -1, rx = -1, ry = -1, gx = -1, gy = -1, bx = -1, by = -1}, phys_x=-1, phys_y=-1, phys_unit=-1, text=0, ztxt=0, text_file=0x2a95583f88 "\uffff\017@", mtime=0, date_string=0x0, time_string=0x3436 <Address 0x3436 out of bounds>, filterSet=0, force=0, zlib_compression= {level = -1, mem_level = -1, strategy = -1, window_bits = -1, method = -1, buffer_size = -1}) at pnmtopng.c:2201 #2 0x0000000000407060 in main (argc=4, argv=0x7fbffff8b8) at pnmtopng.c:2520 and can reproduce the segfault on i386 as well.
Jindrich, Can you paste the output of a "list" command in gdb at that crash? I'm not sure which version of netpbm you're running as the version shipped in RHEL4 doesn't seem to match up when I look at the line numbers gdb is reporting.
Additionally, if this issue is caused by an OOB read and has no potential for exploitation, it will not be considered a security issue. We do not consider crashes of userland programs such as this security issues (the user is running it interactively so if it crashes a few times, they'll figure out it's just a broken image file).
Josh, I discussed this with Bastien and it seems the error is present at least in RHEL4 and rawhide netpbm (10.29-1). The gdb dump above in comment #4 is done from pnmtopng present in netpbm-10.29-1, where something seems seriously broken, as the crash is caused by call of makePngLine(), but it segfaults even before the code within the makePngLine() is executed (likely somewhere in the calling convention stuff or similar). Consider this patch: --- pnmtopng.c.segfault 2005-08-06 19:26:55.000000000 +0200 +++ pnmtopng.c 2005-09-15 18:38:33.000000000 +0200 @@ -1605,6 +1605,9 @@ makePngLine(png_byte * const l unsigned int col; png_byte *pp; + + fprintf(stderr, "*** called\n"); + fflush(stderr); /* fflush() to be *sure* the above is displayed */ pp = line; /* start at beginning of line */ for (col = 0; col < cols; ++col) { @@ -1690,6 +1693,7 @@ writeRaster(png_struct * const p pnm_promoteformatrow(xelrow, cols, maxval, format, maxval, PPM_TYPE); + fprintf(stderr,"*** before call\n"); makePngLine(line, xelrow, cols, maxval, alpha, alpha_mask[row], cht, caht, info_ptr, png_maxval, depth); then: ./pnmtopng -trans '#ffffff' ~/well.pnm >/dev/null displays: pnmtopng: 3 colors found *** before call Segmentation fault /me is afraid it's needed to dig into assembly a bit I'll focus on the RHEL4 netpbm now. BTW. the images are completely sane.
Got it... the pnmtopng in RHEL4 netpbm doesn't bother to initialize palette size when requesting encoding of a pixmap line to PNG... Furthermore it doesn't initialize bestIndex, so that in case paletteSize==0 (paletteSize is uninitialized) it returns uninitialized output to bestIndexP, what could be likely another source of segfault: unsigned int paletteIndex; unsigned int bestIndex; unsigned int bestMatch; bestMatch = UINT_MAX; for (paletteIndex = 0; paletteIndex < paletteSize; ++paletteIndex) { unsigned int const dist = PPM_DISTANCE(palette_pnm[paletteIndex], targetColor); if (dist < bestMatch) { bestMatch = dist; bestIndex = paletteIndex; } } if (bestIndexP != NULL) *bestIndexP = bestIndex; if (bestMatchP != NULL) *bestMatchP = bestMatch;
Created attachment 118961 [details] Patch to fix the segfault. Josh, Mark, maybe this is also dangerous from the security point of view. If you consider that the palette array that is indexed by the uninitialized variable is not allocated by malloc, but is a local array of convertpnm(), therefore it's allocated on stack. If an attacker could make the uninitialized paletteSize=0 (what is likely the case of i386 - this is the reason why it doesn't segfault there) and make bestIndex to be initialized by something he wants, he can swap any pixel sized memory on the stack, because this code is executed right after the closestColorInPalette() call that returns the uninitialized bestIndex in transparentIndex variable: /* Swap this with the first entry in the palette */ pixel tmp; tmp = palette_pnm[transparentIndex]; palette_pnm[transparentIndex] = palette_pnm[0]; palette_pnm[0] = tmp; One can make a lot by modifying the stack contents...
Yes, we're going to want to fix this as a security update. Does this affect everything we ship?
There's a different implementation of pnmtopng in netpbm-9.24 RHEL3 and RHEL2.1 that doesn't seem to be broken (I tested this case on the both RHELs and everything is fine) so the only affected is RHEL4 from RHELs and all currently supported Fedora Cores. I applied patch to RHEL4 and errata is comming soon.
Lifting embargo
An advisory has been issued which should help the problem described in this bug report. This report is therefore being closed with a resolution of ERRATA. For more information on the solution and/or where to find the updated files, please follow the link below. You may reopen this bug report if the solution does not work for you. http://rhn.redhat.com/errata/RHSA-2005-793.html