Bug 168278

Summary: CAN-2005-2978 Crash running pnmtopng -trans on some pnm files
Product: Red Hat Enterprise Linux 4 Reporter: Bastien Nocera <bnocera>
Component: netpbmAssignee: Jindrich Novy <jnovy>
Status: CLOSED ERRATA QA Contact: Ben Levenson <benl>
Severity: medium Docs Contact:
Priority: medium    
Version: 4.0CC: pknirsch, psplicha, security-response-team
Target Milestone: ---Keywords: Security
Target Release: ---   
Hardware: x86_64   
OS: Linux   
Whiteboard: impact=moderate,source=redhat,embargo=20051018,reported=20050914
Fixed In Version: RHSA-2005-793 Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2005-10-18 15:41:58 UTC Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Attachments:
Description Flags
well.pnm
none
sample.pnm.bz2
none
Patch to fix the segfault. none

Description Bastien Nocera 2005-09-14 13:24:24 UTC
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.

Comment 1 Bastien Nocera 2005-09-14 13:24:26 UTC
Created attachment 118793 [details]
well.pnm

Comment 2 Bastien Nocera 2005-09-14 14:07:34 UTC
Can be reproduced with netpbm-progs-10.29-1 from rawhide.

Comment 3 Bastien Nocera 2005-09-14 14:12:26 UTC
Created attachment 118798 [details]
sample.pnm.bz2

Another test file, bzipped2.

Comment 4 Jindrich Novy 2005-09-15 13:14:00 UTC
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.

Comment 5 Josh Bressers 2005-09-15 13:35:42 UTC
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.

Comment 6 Josh Bressers 2005-09-15 13:37:52 UTC
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).

Comment 7 Jindrich Novy 2005-09-15 16:50:02 UTC
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.

Comment 8 Jindrich Novy 2005-09-16 10:30:27 UTC
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;


Comment 9 Jindrich Novy 2005-09-19 07:55:00 UTC
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...

Comment 10 Josh Bressers 2005-09-28 20:37:53 UTC
Yes, we're going to want to fix this as a security update.  Does this affect
everything we ship?

Comment 11 Jindrich Novy 2005-09-29 09:16:46 UTC
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.

Comment 15 Josh Bressers 2005-10-18 15:28:09 UTC
Lifting embargo

Comment 16 Red Hat Bugzilla 2005-10-18 15:41:58 UTC
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