commit cfa3559b8ec9e693142638eedb4d6340ecf01c90 Author: Adam Majer Date: Thu Jun 28 13:03:06 2018 +0200 Verify we do not read longer than the buffer Netwide Assembler (NASM) 2.13 has a stack-based buffer over-read in the disasm function of the disasm/disasm.c file. Bug: 3392475 Signed-off-by: Adam Majer diff --git a/disasm/disasm.c b/disasm/disasm.c index a75d839e..a503434b 100644 --- a/disasm/disasm.c +++ b/disasm/disasm.c @@ -474,8 +474,10 @@ static uint8_t *do_ea(uint8_t *data, int modrm, int asize, * stream in data. Return the number of bytes matched if so. */ #define case4(x) case (x): case (x)+1: case (x)+2: case (x)+3 +#define check_can_read_data_byte() if (--remaining_bytes < 0) return 0 +#define check_can_read_data_bytes(n) { if (remaining_bytes < n) return 0; remaining_bytes -= n; } while(0) -static int matches(const struct itemplate *t, uint8_t *data, +static int matches(const struct itemplate *t, uint8_t *data, int remaining_bytes, const struct prefix_info *prefix, int segsize, insn *ins) { uint8_t *r = (uint8_t *)(t->code); @@ -525,9 +527,11 @@ static int matches(const struct itemplate *t, uint8_t *data, case 02: case 03: case 04: - while (c--) + while (c--) { + check_can_read_data_byte(); if (*r++ != *data++) return 0; + } break; case 05: @@ -538,7 +542,9 @@ static int matches(const struct itemplate *t, uint8_t *data, case4(010): { - int t = *r++, d = *data++; + int d, t = *r++; + check_can_read_data_byte(); + d = *data++; if (d < t || d > t + 7) return 0; else { @@ -555,28 +561,34 @@ static int matches(const struct itemplate *t, uint8_t *data, break; case4(0274): + check_can_read_data_byte(); opx->offset = (int8_t)*data++; opx->segment |= SEG_SIGNED; break; case4(020): + check_can_read_data_byte(); opx->offset = *data++; break; case4(024): + check_can_read_data_byte(); opx->offset = *data++; break; case4(030): + check_can_read_data_bytes(2); opx->offset = getu16(data); data += 2; break; case4(034): if (osize == 32) { + check_can_read_data_bytes(4); opx->offset = getu32(data); data += 4; } else { + check_can_read_data_bytes(2); opx->offset = getu16(data); data += 2; } @@ -585,11 +597,13 @@ static int matches(const struct itemplate *t, uint8_t *data, break; case4(040): + check_can_read_data_bytes(4); opx->offset = getu32(data); data += 4; break; case4(0254): + check_can_read_data_bytes(4); opx->offset = gets32(data); data += 4; break; @@ -597,18 +611,21 @@ static int matches(const struct itemplate *t, uint8_t *data, case4(044): switch (asize) { case 16: + check_can_read_data_bytes(2); opx->offset = getu16(data); data += 2; if (segsize != 16) opx->disp_size = 16; break; case 32: + check_can_read_data_bytes(4); opx->offset = getu32(data); data += 4; if (segsize == 16) opx->disp_size = 32; break; case 64: + check_can_read_data_bytes(8); opx->offset = getu64(data); opx->disp_size = 64; data += 8; @@ -617,16 +634,19 @@ static int matches(const struct itemplate *t, uint8_t *data, break; case4(050): + check_can_read_data_byte(); opx->offset = gets8(data++); opx->segment |= SEG_RELATIVE; break; case4(054): + check_can_read_data_bytes(8); opx->offset = getu64(data); data += 8; break; case4(060): + check_can_read_data_bytes(2); opx->offset = gets16(data); data += 2; opx->segment |= SEG_RELATIVE; @@ -637,6 +657,7 @@ static int matches(const struct itemplate *t, uint8_t *data, opx->segment |= SEG_RELATIVE; /* In long mode rel is always 32 bits, sign extended. */ if (segsize == 64 || osize == 32) { + check_can_read_data_bytes(4); opx->offset = gets32(data); data += 4; if (segsize != 64) @@ -644,6 +665,7 @@ static int matches(const struct itemplate *t, uint8_t *data, opx->type = (opx->type & ~SIZE_MASK) | (segsize == 64 ? BITS64 : BITS32); } else { + check_can_read_data_bytes(2); opx->offset = gets16(data); data += 2; opx->segment &= ~SEG_32BIT; @@ -652,6 +674,7 @@ static int matches(const struct itemplate *t, uint8_t *data, break; case4(070): + check_can_read_data_bytes(4); opx->offset = gets32(data); data += 4; opx->segment |= SEG_32BIT | SEG_RELATIVE; @@ -662,11 +685,19 @@ static int matches(const struct itemplate *t, uint8_t *data, case4(0120): case4(0130): { - int modrm = *data++; + int modrm; + uint8_t *new_data; + + check_can_read_data_byte(); + modrm = *data++; opx->segment |= SEG_RMREG; - data = do_ea(data, modrm, asize, segsize, eat, opy, ins); - if (!data) + new_data = do_ea(data, modrm, asize, segsize, eat, opy, ins); + if (!new_data) + return 0; + remaining_bytes = data + remaining_bytes - new_data; + if (remaining_bytes < 0) return 0; + data = new_data; opx->basereg = ((modrm >> 3) & 7) + (ins->rex & REX_R ? 8 : 0); if ((ins->rex & REX_EV) && (segsize == 64)) opx->basereg += (ins->evex_p[0] & EVEX_P0RP ? 0 : 16); @@ -675,7 +706,10 @@ static int matches(const struct itemplate *t, uint8_t *data, case 0172: { - uint8_t ximm = *data++; + uint8_t ximm; + + check_can_read_data_byte(); + ximm = *data++; c = *r++; ins->oprs[c >> 3].basereg = (ximm >> 4) & regmask; ins->oprs[c >> 3].segment |= SEG_RMREG; @@ -685,7 +719,10 @@ static int matches(const struct itemplate *t, uint8_t *data, case 0173: { - uint8_t ximm = *data++; + uint8_t ximm; + + check_can_read_data_byte(); + ximm = *data++; c = *r++; if ((c ^ ximm) & 15) @@ -698,7 +735,10 @@ static int matches(const struct itemplate *t, uint8_t *data, case4(0174): { - uint8_t ximm = *data++; + uint8_t ximm; + + check_can_read_data_byte(); + ximm = *data++; opx->basereg = (ximm >> 4) & regmask; opx->segment |= SEG_RMREG; @@ -714,12 +754,20 @@ static int matches(const struct itemplate *t, uint8_t *data, case4(0230): case4(0234): { - int modrm = *data++; + int modrm; + uint8_t *new_data; + + check_can_read_data_byte(); + modrm = *data++; if (((modrm >> 3) & 07) != (c & 07)) return 0; /* spare field doesn't match up */ - data = do_ea(data, modrm, asize, segsize, eat, opy, ins); - if (!data) + new_data = do_ea(data, modrm, asize, segsize, eat, opy, ins); + if (!new_data) + return 0; + remaining_bytes = data + remaining_bytes - new_data; + if (remaining_bytes < 0) return 0; + data = new_data; break; } @@ -935,7 +983,10 @@ static int matches(const struct itemplate *t, uint8_t *data, case 0330: { - int t = *r++, d = *data++; + int t = *r++, d; + + check_can_read_data_byte(); + d = *data++; if (d < t || d > t + 15) return 0; else @@ -1126,6 +1177,8 @@ int32_t disasm(uint8_t *data, char *output, int outbufsize, int segsize, bool end_prefix; bool is_evex; + int remaining_bytes = INSN_MAX; + memset(&ins, 0, sizeof ins); /* @@ -1141,6 +1194,8 @@ int32_t disasm(uint8_t *data, char *output, int outbufsize, int segsize, end_prefix = false; while (!end_prefix) { + check_can_read_data_byte(); + switch (*data) { case 0xF2: case 0xF3: @@ -1185,6 +1240,7 @@ int32_t disasm(uint8_t *data, char *output, int outbufsize, int segsize, case 0xC4: case 0xC5: + check_can_read_data_byte(); if (segsize == 64 || (data[1] & 0xc0) == 0xc0) { prefix.vex[0] = *data++; prefix.vex[1] = *data++; @@ -1193,6 +1249,7 @@ int32_t disasm(uint8_t *data, char *output, int outbufsize, int segsize, prefix.vex_c = RV_VEX; if (prefix.vex[0] == 0xc4) { + check_can_read_data_byte(); prefix.vex[2] = *data++; prefix.rex |= (~prefix.vex[1] >> 5) & 7; /* REX_RXB */ prefix.rex |= (prefix.vex[2] >> (7-3)) & REX_W; @@ -1213,7 +1270,8 @@ int32_t disasm(uint8_t *data, char *output, int outbufsize, int segsize, case 0x62: { - if (segsize == 64 || ((data[1] & 0xc0) == 0xc0)) { + if (segsize == 64 || (remaining_bytes > 3 && (data[1] & 0xc0) == 0xc0)) { + check_can_read_data_bytes(3); data++; /* 62h EVEX prefix */ prefix.evex[0] = *data++; prefix.evex[1] = *data++; @@ -1235,8 +1293,10 @@ int32_t disasm(uint8_t *data, char *output, int outbufsize, int segsize, } case 0x8F: - if ((data[1] & 030) != 0 && + check_can_read_data_byte(); + if ((data[1] & 030) != 0 && (segsize == 64 || (data[1] & 0xc0) == 0xc0)) { + check_can_read_data_byte(); prefix.vex[0] = *data++; prefix.vex[1] = *data++; prefix.vex[2] = *data++; @@ -1280,6 +1340,7 @@ int32_t disasm(uint8_t *data, char *output, int outbufsize, int segsize, break; default: + remaining_bytes++; /* didn't actually use the last byte */ end_prefix = true; break; } @@ -1293,14 +1354,17 @@ int32_t disasm(uint8_t *data, char *output, int outbufsize, int segsize, return 0; /* No instruction table at all... */ dp = data; + + check_can_read_data_byte(); ix += *dp++; while (ix->n == -1) { + check_can_read_data_byte(); ix = (const struct disasm_index *)ix->p + *dp++; } p = (const struct itemplate * const *)ix->p; for (n = ix->n; n; n--, p++) { - if ((length = matches(*p, data, &prefix, segsize, &tmp_ins))) { + if ((length = matches(*p, data, remaining_bytes, &prefix, segsize, &tmp_ins))) { works = true; /* * Final check to make sure the types of r/m match up.