Skip to content

Out of bounds memory access with more than 1 pixel per byte pixel formats, e.g. SIXEL_PIXELFORMAT_G1 #222

@xyzzy42

Description

@xyzzy42

Some pixel formats put more than one pixel in a single byte. For example, SIXEL_PIXELFORMAT_G1 encodes a pixel as a single bit and packs 8 pixels into 1 byte.

sixel_helper_compute_depth() returns 1 for these formats. I.e., 1 pixel per byte, not 8. So width * height * depth is too large for the image data when using these formats.

This is then used in, e.g., sixel_encoder_output_without_macro():

    depth = sixel_helper_compute_depth(pixelformat);
    size = (size_t)(width * height * depth);
    p = (unsigned char *)sixel_allocator_malloc(encoder->allocator, size);
    memcpy(p, pixbuf, (size_t)(width * height * depth));

Here the memcpy will read too much data from pixbuf. And also the allocated buffer is larger than it needs to be.

Common solution is to create a helper than returns number of bytes in one line, rather than in one pixel. The width must be passed as well. This avoids the need to return fractional value. Also can take into account padding line to end on a byte boundary.

int compute_line_length(int pixelformat, int width) {
  if (pixelformat_less_than_1_byte) {
    //  Just example for < 1 byte formats
    switch (pixelformat) {
      case SIXEL_PIXELFORMAT_G1: bpp = 1; break;
      case SIXEL_PIXELFORMAT_G2: bpp = 2; break;
       …
    }
    return (width * bpp + 7) / 8;
  } else {
    return sixel_helper_compute_depth(pixelformat) * width;
  }
}

// usage
  size = (size_t)height * compute_line_length(pixelformat, width);

P.S., (size_t)(width * height * depth) is not correct casting. If total size is > 2GB, e.g. overflows 32-bit int on system with 32-bit ints, then the result of the multiplication will produce an out of range value truncated to 32-bits. Then the truncated value will be upcast to 64-bit (if size_t is 64-bits). This is too late, truncation is already done. If you want to handle image pixel size > 2GB, you must cast to 64-bit before multiplying so that the product is 64-bits. E.g., (size_t)width * height * depth. That will upcast width to size_t (probably 64-bit), and then standard C rules will promote the other operands to the multiplication to match it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions