Commit message: Prevent a 1-byte underread of the input buffer if an odd-sized data block comes just before an uncompressed block header
CVE: CVE-2015-4471
CWE: CWE-189
Vulnerability description: Off-by-one error in the lzxd_decompress function in lzxd.c in libmspack before 0.5 allows remote attackers to cause a denial of service (buffer under-read and application crash) via a crafted CAB archive.

Project: libmspack
File: lzxd.c

void lzxd_free(struct lzxd_stream *lzx) {
  struct mspack_system *sys;
  if (lzx) {
    sys = lzx->sys;
    sys->free(lzx->inbuf);
    sys->free(lzx->window);
    sys->free(lzx);
  }
}

Project: libmspack
File: lzxd.c

struct lzxd_stream *lzxd_init(struct mspack_system *system,
			      struct mspack_file *input,
			      struct mspack_file *output,
			      int window_bits,
			      int reset_interval,
			      int input_buffer_size,
			      off_t output_length,
			      char is_delta)
{
  unsigned int window_size = 1 << window_bits;
  struct lzxd_stream *lzx;

  if (!system) return NULL;

  /* LZX DELTA window sizes are between 2^17 (128KiB) and 2^25 (32MiB),
   * regular LZX windows are between 2^15 (32KiB) and 2^21 (2MiB)
   */
  if (is_delta) {
      if (window_bits < 17 || window_bits > 25) return NULL;
  }
  else {
      if (window_bits < 15 || window_bits > 21) return NULL;
  }

  input_buffer_size = (input_buffer_size + 1) & -2;
  if (!input_buffer_size) return NULL;

  /* allocate decompression state */
  if (!(lzx = (struct lzxd_stream *) system->alloc(system, sizeof(struct lzxd_stream)))) {
    return NULL;
  }

  /* allocate decompression window and input buffer */
  lzx->window = (unsigned char *) system->alloc(system, (size_t) window_size);
  lzx->inbuf  = (unsigned char *) system->alloc(system, (size_t) input_buffer_size);
  if (!lzx->window || !lzx->inbuf) {
    system->free(lzx->window);
    system->free(lzx->inbuf);
    system->free(lzx);
    return NULL;
  }

  /* initialise decompression state */
  lzx->sys             = system;
  lzx->input           = input;
  lzx->output          = output;
  lzx->offset          = 0;
  lzx->length          = output_length;

  lzx->inbuf_size      = input_buffer_size;
  lzx->window_size     = 1 << window_bits;
  lzx->ref_data_size   = 0;
  lzx->window_posn     = 0;
  lzx->frame_posn      = 0;
  lzx->frame           = 0;
  lzx->reset_interval  = reset_interval;
  lzx->intel_filesize  = 0;
  lzx->intel_curpos    = 0;
  lzx->intel_started   = 0;
  lzx->error           = MSPACK_ERR_OK;
  lzx->num_offsets     = position_slots[window_bits - 15] << 3;
  lzx->is_delta        = is_delta;

  lzx->o_ptr = lzx->o_end = &lzx->e8_buf[0];
  lzxd_reset_state(lzx);
  INIT_BITS;
  return lzx;
}

Project: libmspack
File: lzxd.c

static void lzxd_reset_state(struct lzxd_stream *lzx) {
  int i;

  lzx->R0              = 1;
  lzx->R1              = 1;
  lzx->R2              = 1;
  lzx->header_read     = 0;
  lzx->block_remaining = 0;
  lzx->block_type      = LZX_BLOCKTYPE_INVALID;

  /* initialise tables to 0 (because deltas will be applied to them) */
  for (i = 0; i < LZX_MAINTREE_MAXSYMBOLS; i++) lzx->MAINTREE_len[i] = 0;
  for (i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++)   lzx->LENGTH_len[i]   = 0;
}

Project: libmspack
File: lzxd.c

void lzxd_set_output_length(struct lzxd_stream *lzx, off_t out_bytes) {
  if (lzx) lzx->length = out_bytes;
}

Project: libmspack
File: lzxd.c

int lzxd_set_reference_data(struct lzxd_stream *lzx,
			    struct mspack_system *system,
			    struct mspack_file *input,
			    unsigned int length)
{
    if (!lzx) return MSPACK_ERR_ARGS;

    if (!lzx->is_delta) {
        D(("only LZX DELTA streams support reference data"))
        return MSPACK_ERR_ARGS;
    }
    if (lzx->offset) {
	D(("too late to set reference data after decoding starts"))
	return MSPACK_ERR_ARGS;
    }
    if (length > lzx->window_size) {
	D(("reference length (%u) is longer than the window", length))
	return MSPACK_ERR_ARGS;
    }
    if (length > 0 && (!system || !input)) {
        D(("length > 0 but no system or input"))
        return MSPACK_ERR_ARGS;
    }

    lzx->ref_data_size = length;
    if (length > 0) {
        /* copy reference data */
        unsigned char *pos = &lzx->window[lzx->window_size - length];
	int bytes = system->read(input, pos, length);
        /* length can't be more than 2^25, so no signedness problem */
	if (bytes < (int)length) return MSPACK_ERR_READ;
    }
    lzx->ref_data_size = length;
    return MSPACK_ERR_OK;
}