Commit message: Fix integer overflow in rwpng.h (CVE-2016-5735)

Reported by Choi Jaeseung 
Found with Sparrow (http://ropas.snu.ac.kr/sparrow)
CVE: CVE-2016-5735
CWE: CWE-190
Vulnerability description: Integer overflow in the rwpng_read_image24_libpng function in rwpng.c in pngquant 2.7.0 allows remote attackers to have unspecified impact via a crafted PNG file, which triggers a buffer overflow.

Project: pngquant
File: rwpng.c

static int read_chunk_callback(png_structp png_ptr, png_unknown_chunkp in_chunk)
{
    if (0 == memcmp("iCCP", in_chunk->name, 5) ||
        0 == memcmp("cHRM", in_chunk->name, 5) ||
        0 == memcmp("gAMA", in_chunk->name, 5)) {
        return 0; // not handled
    }

    struct rwpng_chunk **head = (struct rwpng_chunk **)png_get_user_chunk_ptr(png_ptr);

    struct rwpng_chunk *chunk = malloc(sizeof(struct rwpng_chunk));
    memcpy(chunk->name, in_chunk->name, 5);
    chunk->size = in_chunk->size;
    chunk->location = in_chunk->location;
    chunk->data = in_chunk->size ? malloc(in_chunk->size) : NULL;
    if (in_chunk->size) {
        memcpy(chunk->data, in_chunk->data, in_chunk->size);
    }

    chunk->next = *head;
    *head = chunk;

    return 1; // marks as "handled", libpng won't store it
}

Project: pngquant
File: rwpng.c

static png_bytepp rwpng_create_row_pointers(png_infop info_ptr, png_structp png_ptr, unsigned char *base, unsigned int height, png_size_t rowbytes)
{
    if (!rowbytes) {
        rowbytes = png_get_rowbytes(png_ptr, info_ptr);
    }

    png_bytepp row_pointers = malloc(height * sizeof(row_pointers[0]));
    if (!row_pointers) return NULL;
    for(size_t row = 0; row < height; row++) {
        row_pointers[row] = base + row * rowbytes;
    }
    return row_pointers;
}

Project: pngquant
File: rwpng.c

static void rwpng_error_handler(png_structp png_ptr, png_const_charp msg)
{
    rwpng_png_image  *mainprog_ptr;

    /* This function, aside from the extra step of retrieving the "error
     * pointer" (below) and the fact that it exists within the application
     * rather than within libpng, is essentially identical to libpng's
     * default error handler.  The second point is critical:  since both
     * setjmp() and longjmp() are called from the same code, they are
     * guaranteed to have compatible notions of how big a jmp_buf is,
     * regardless of whether _BSD_SOURCE or anything else has (or has not)
     * been defined. */

    fprintf(stderr, "  error: %s (libpng failed)\n", msg);
    fflush(stderr);

    mainprog_ptr = png_get_error_ptr(png_ptr);
    if (mainprog_ptr == NULL) abort();

    longjmp(mainprog_ptr->jmpbuf, 1);
}

Project: pngquant
File: rwpng.c

static void rwpng_free_chunks(struct rwpng_chunk *chunk) {
    if (!chunk) return;
    rwpng_free_chunks(chunk->next);
    free(chunk->data);
    free(chunk);
}

Project: pngquant
File: rwpng.c

void rwpng_free_image24(png24_image *image)
{
    free(image->row_pointers);
    image->row_pointers = NULL;

    free(image->rgba_data);
    image->rgba_data = NULL;

    rwpng_free_chunks(image->chunks);
    image->chunks = NULL;
}

Project: pngquant
File: rwpng.c

void rwpng_free_image8(png8_image *image)
{
    free(image->indexed_data);
    image->indexed_data = NULL;

    free(image->row_pointers);
    image->row_pointers = NULL;

    rwpng_free_chunks(image->chunks);
    image->chunks = NULL;
}

Project: pngquant
File: rwpng.c

static void rwpng_set_gamma(png_infop info_ptr, png_structp png_ptr, double gamma, rwpng_color_transform color)
{
    if (color != RWPNG_GAMA_ONLY && color != RWPNG_NONE) {
        png_set_gAMA(png_ptr, info_ptr, gamma);
    }
    if (color == RWPNG_SRGB) {
        png_set_sRGB(png_ptr, info_ptr, 0); // 0 = Perceptual
    }
}

Project: pngquant
File: rwpng.c

void rwpng_version_info(FILE *fp)
{
    const char *pngver = png_get_header_ver(NULL);

#if USE_COCOA
    fprintf(fp, "   Color profiles are supported via Cocoa. Using libpng %s.\n", pngver);
#elif USE_LCMS
    fprintf(fp, "   Color profiles are supported via Little CMS. Using libpng %s.\n", pngver);
#else
    fprintf(fp, "   Compiled with no support for color profiles. Using libpng %s.\n", pngver);
#endif

#if PNG_LIBPNG_VER < 10600
    if (strcmp(pngver, "1.3.") < 0) {
        fputs("\nWARNING: Your version of libpng is outdated and may produce corrupted files.\n"
              "Please recompile pngquant with the current version of libpng (1.6 or later).\n", fp);
    } else if (strcmp(pngver, "1.6.") < 0) {
        #if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED)
        fputs("\nWARNING: Your version of libpng is old and has buggy support for custom chunks.\n"
              "Please recompile pngquant with the current version of libpng (1.6 or later).\n", fp);
        #endif
    }
#endif
}

Project: pngquant
File: rwpng.c

static void rwpng_warning_silent_handler(png_structp png_ptr, png_const_charp msg) {
}

Project: pngquant
File: rwpng.c

static void rwpng_warning_stderr_handler(png_structp png_ptr, png_const_charp msg) {
    fprintf(stderr, "  libpng warning: %s\n", msg);
}

Project: pngquant
File: rwpng.c

static void rwpng_write_end(png_infopp info_ptr_p, png_structpp png_ptr_p, png_bytepp row_pointers)
{
    png_write_info(*png_ptr_p, *info_ptr_p);

    png_set_packing(*png_ptr_p);

    png_write_image(*png_ptr_p, row_pointers);

    png_write_end(*png_ptr_p, NULL);

    png_destroy_write_struct(png_ptr_p, info_ptr_p);
}

Project: pngquant
File: rwpng.c

pngquant_error rwpng_write_image24(FILE *outfile, const png24_image *mainprog_ptr)
{
    png_structp png_ptr;
    png_infop info_ptr;

    pngquant_error retval = rwpng_write_image_init((rwpng_png_image*)mainprog_ptr, &png_ptr, &info_ptr, 0);
    if (retval) return retval;

    png_init_io(png_ptr, outfile);

    rwpng_set_gamma(info_ptr, png_ptr, mainprog_ptr->gamma, mainprog_ptr->output_color);

    png_set_IHDR(png_ptr, info_ptr, mainprog_ptr->width, mainprog_ptr->height,
                 8, PNG_COLOR_TYPE_RGB_ALPHA,
                 0, PNG_COMPRESSION_TYPE_DEFAULT,
                 PNG_FILTER_TYPE_BASE);


    png_bytepp row_pointers = rwpng_create_row_pointers(info_ptr, png_ptr, mainprog_ptr->rgba_data, mainprog_ptr->height, 0);

    rwpng_write_end(&info_ptr, &png_ptr, row_pointers);

    free(row_pointers);

    return SUCCESS;
}

Project: pngquant

pngquant_error rwpng_write_image8(FILE *outfile, const png8_image *mainprog_ptr)
{
    png_structp png_ptr;
    png_infop info_ptr;

    if (mainprog_ptr->num_palette > 256) return INVALID_ARGUMENT;

    pngquant_error retval = rwpng_write_image_init((rwpng_png_image*)mainprog_ptr, &png_ptr, &info_ptr, mainprog_ptr->fast_compression);
    if (retval) return retval;

    struct rwpng_write_state write_state;
    write_state = (struct rwpng_write_state){
        .outfile = outfile,
        .maximum_file_size = mainprog_ptr->maximum_file_size,
        .retval = SUCCESS,
    };
    png_set_write_fn(png_ptr, &write_state, user_write_data, user_flush_data);

    png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_FILTER_VALUE_NONE);

    rwpng_set_gamma(info_ptr, png_ptr, mainprog_ptr->gamma, mainprog_ptr->output_color);

    /* set the image parameters appropriately */
    int sample_depth;
#if PNG_LIBPNG_VER > 10400 /* old libpng corrupts files with low depth */
    if (mainprog_ptr->num_palette <= 2)
        sample_depth = 1;
    else if (mainprog_ptr->num_palette <= 4)
        sample_depth = 2;
    else if (mainprog_ptr->num_palette <= 16)
        sample_depth = 4;
    else
#endif
        sample_depth = 8;

    struct rwpng_chunk *chunk = mainprog_ptr->chunks;
    int chunk_num=0;
    while(chunk) {
        png_unknown_chunk pngchunk = {
            .size = chunk->size,
            .data = chunk->data,
            .location = chunk->location,
        };
        memcpy(pngchunk.name, chunk->name, 5);
        png_set_unknown_chunks(png_ptr, info_ptr, &pngchunk, 1);

        #if defined(PNG_HAVE_IHDR) && PNG_LIBPNG_VER < 10600
        png_set_unknown_chunk_location(png_ptr, info_ptr, chunk_num, pngchunk.location ? pngchunk.location : PNG_HAVE_IHDR);
        #endif

        chunk = chunk->next;
        chunk_num++;
    }

    png_set_IHDR(png_ptr, info_ptr, mainprog_ptr->width, mainprog_ptr->height,
      sample_depth, PNG_COLOR_TYPE_PALETTE,
      0, PNG_COMPRESSION_TYPE_DEFAULT,
      PNG_FILTER_TYPE_BASE);

    png_color palette[256];
    png_byte trans[256];
    unsigned int num_trans = 0;
    for(unsigned int i = 0; i < mainprog_ptr->num_palette; i++) {
        palette[i] = (png_color){
            .red   = mainprog_ptr->palette[i].r,
            .green = mainprog_ptr->palette[i].g,
            .blue  = mainprog_ptr->palette[i].b,
        };
        trans[i] = mainprog_ptr->palette[i].a;
        if (mainprog_ptr->palette[i].a < 255) {
            num_trans = i+1;
        }
    }

    png_set_PLTE(png_ptr, info_ptr, palette, mainprog_ptr->num_palette);

    if (num_trans > 0) {
        png_set_tRNS(png_ptr, info_ptr, trans, num_trans, NULL);
    }

    rwpng_write_end(&info_ptr, &png_ptr, mainprog_ptr->row_pointers);

    if (SUCCESS == write_state.retval && write_state.maximum_file_size && write_state.bytes_written > write_state.maximum_file_size) {
        return TOO_LARGE_FILE;
    }

    return write_state.retval;
}

Project: pngquant
File: rwpng.c

static pngquant_error rwpng_write_image_init(rwpng_png_image *mainprog_ptr, png_structpp png_ptr_p, png_infopp info_ptr_p, int fast_compression)
{
    /* could also replace libpng warning-handler (final NULL), but no need: */

    *png_ptr_p = png_create_write_struct(PNG_LIBPNG_VER_STRING, mainprog_ptr, rwpng_error_handler, NULL);

    if (!(*png_ptr_p)) {
        return LIBPNG_INIT_ERROR;   /* out of memory */
    }

    *info_ptr_p = png_create_info_struct(*png_ptr_p);
    if (!(*info_ptr_p)) {
        png_destroy_write_struct(png_ptr_p, NULL);
        return LIBPNG_INIT_ERROR;   /* out of memory */
    }

    /* setjmp() must be called in every function that calls a PNG-writing
     * libpng function, unless an alternate error handler was installed--
     * but compatible error handlers must either use longjmp() themselves
     * (as in this program) or exit immediately, so here we go: */

    if (setjmp(mainprog_ptr->jmpbuf)) {
        png_destroy_write_struct(png_ptr_p, info_ptr_p);
        return LIBPNG_INIT_ERROR;   /* libpng error (via longjmp()) */
    }

    png_set_compression_level(*png_ptr_p, fast_compression ? Z_BEST_SPEED : Z_BEST_COMPRESSION);
    png_set_compression_mem_level(*png_ptr_p, fast_compression ? 9 : 5); // judging by optipng results, smaller mem makes libpng compress slightly better

    return SUCCESS;
}

Project: pngquant

static void user_flush_data(png_structp png_ptr)
{
}

Project: pngquant
File: rwpng.c

static void user_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
{
    struct rwpng_read_data *read_data = (struct rwpng_read_data *)png_get_io_ptr(png_ptr);

    png_size_t read = fread(data, 1, length, read_data->fp);
    if (!read) {
        png_error(png_ptr, "Read error");
    }
    read_data->bytes_read += read;
}

Project: pngquant
File: rwpng.c

static void user_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
{
    struct rwpng_write_state *write_state = (struct rwpng_write_state *)png_get_io_ptr(png_ptr);

    if (SUCCESS != write_state->retval) {
        return;
    }

    if (!fwrite(data, length, 1, write_state->outfile)) {
        write_state->retval = CANT_WRITE_ERROR;
    }

    write_state->bytes_written += length;
}