From 13ca6f3ed2702635f466216f324fff327deb6041 Mon Sep 17 00:00:00 2001 From: bibi samina Date: Thu, 9 Jul 2026 10:39:28 +0530 Subject: [PATCH] mips: zero-extend microMIPS LWM16/SWM16 reglist offset The ISA defines the offset as zero_extend(offset||0^2), but the non-MMR6 path in DecodeMemMMReglistImm4Lsl2 sign-extended the 4-bit field before the << 2. When the field's top bit is set that makes Offset negative, so Offset << 2 is a left shift of a negative value (UB), and the decoded offset came out negative instead of the zero-extended value the ISA requires. Use fieldFromInstruction_4(Insn, 0, 4) to extract the field unsigned, matching the MMR6 branch and the ISA. The shift is now always applied to a non-negative value. Verified under -fsanitize=undefined: before, cstool micromips 4508 and test_poc abort at MipsDisassembler.c:2432 with 'left shift of negative value -8'; after, test_poc exits clean and cstool prints 'lwm16 $s0, $ra, 0x20($sp)' (8 << 2 == 0x20). Signed-off-by: bibi samina --- arch/Mips/MipsDisassembler.c | 2 +- tests/integration/test_poc.c | 23 +++++++++++++++++++++++ tests/issues/issues.yaml | 12 ++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/arch/Mips/MipsDisassembler.c b/arch/Mips/MipsDisassembler.c index e9d4f06a54..89b15c5f5e 100644 --- a/arch/Mips/MipsDisassembler.c +++ b/arch/Mips/MipsDisassembler.c @@ -2420,7 +2420,7 @@ static DecodeStatus DecodeMemMMReglistImm4Lsl2(MCInst *Inst, uint32_t Insn, Offset = fieldFromInstruction_4(Insn, 4, 4); break; default: - Offset = SignExtend32((Insn & 0xf), 4); + Offset = fieldFromInstruction_4(Insn, 0, 4); break; } diff --git a/tests/integration/test_poc.c b/tests/integration/test_poc.c index adf9a351eb..efe032be99 100644 --- a/tests/integration/test_poc.c +++ b/tests/integration/test_poc.c @@ -208,6 +208,28 @@ static void test_ub_isintn_xtensa_offset(void) return; } +/// Signed left shift of a negative value when decoding a microMIPS +/// LWM16/SWM16 offset. The ISA defines the offset as zero_extend(offset||0^2), +/// but the 4-bit field was sign-extended to a negative int and then shifted +/// left by 2, which is UB whenever the field's top bit is set (field >= 8). +static void test_ub_shift_mips_mm_reglist(void) +{ + static const uint8_t code[] = { 0x45, 0x08 }; + + csh handle; + if (cs_open(CS_ARCH_MIPS, + CS_MODE_MICRO | CS_MODE_MIPS32 | CS_MODE_BIG_ENDIAN, + &handle) != CS_ERR_OK) + return; + cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON); + + cs_insn *insn = NULL; + size_t count = cs_disasm(handle, code, sizeof(code), 0x1000, 0, &insn); + cs_free(insn, count); + cs_close(&handle); + return; +} + int main() { test_overflow_cs_insn_bytes(); @@ -216,6 +238,7 @@ int main() test_integer_overflow(); test_ub_shift_sh_dsp_p(); test_ub_isintn_xtensa_offset(); + test_ub_shift_mips_mm_reglist(); return 0; } diff --git a/tests/issues/issues.yaml b/tests/issues/issues.yaml index 914f3cfc09..f40f95d3c0 100644 --- a/tests/issues/issues.yaml +++ b/tests/issues/issues.yaml @@ -6734,3 +6734,15 @@ test_cases: - asm_text: "ptrue pn10.h" op_str: "pn10.h" + - + input: + name: "issue 2989 microMIPS LWM16 reglist offset zero-extended" + bytes: [ 0x45, 0x08 ] + arch: "CS_ARCH_MIPS" + options: [ CS_MODE_MICRO, CS_MODE_MIPS32, CS_MODE_BIG_ENDIAN ] + address: 0x0 + expected: + insns: + - + asm_text: "lwm16 $s0, $ra, 0x20($sp)" + op_str: "$s0, $ra, 0x20($sp)"