Assembly (MIPS, SPIM), interpreter bug, 15 14 bytes
main:sb$0 f
f:
Here's a cute one I found by accident.
This attempts to store a byte to the address at label f
. This is in the .text
section.
SPIM is pretty clever in that writing out of bounds of a section will just grow the section. However, writing to .text
is tricky.
SPIM stores instructions in a struct for easier parsing it and debugging, instead of just storing data directly.
CPU/inst.h:57
:
/* Representation of an instruction. Store the instruction fields in an
overlapping manner similar to the real encoding (but not identical, to
speed decoding in C code, as opposed to hardware).. */
typedef struct inst_s
{
short opcode;
union
{
// Snip
} r_t;
int32 encoding;
imm_expr *expr;
char *source_line;
} instruction;
Therefore, to store to .text
, SPIM will give it a special treatment in bad_mem_write()
.
CPU/mem.cpp:395
:
void
set_mem_byte(mem_addr addr, reg_word value)
{
data_modified = true;
// .data
if ((addr >= DATA_BOT) && (addr < data_top))
data_seg_b [addr - DATA_BOT] = (BYTE_TYPE) value;
// .stack
else if ((addr >= stack_bot) && (addr < STACK_TOP))
stack_seg_b [addr - stack_bot] = (BYTE_TYPE) value;
// .data
else if ((addr >= K_DATA_BOT) && (addr < k_data_top))
k_data_seg_b [addr - K_DATA_BOT] = (BYTE_TYPE) value;
// .text or section out of bounds
else
bad_mem_write (addr, value, 0); // <--
}
In bad_mem_write()
, SPIM will attempt to splice and recompile the instruction as if you modified the memory directly.
CPU/mem.cpp:506
:
static void
bad_mem_write (mem_addr addr, mem_word value, int mask)
{
mem_word tmp;
if ((addr & mask) != 0)
/* Unaligned address fault */
RAISE_EXCEPTION (ExcCode_AdES, CP0_BadVAddr = addr)
else if (addr >= TEXT_BOT && addr < text_top)
{
// For halfword and byte writes, attempt to overwrite part of the instruction.
switch (mask)
{
case 0x0:
tmp = ENCODING (text_seg [(addr - TEXT_BOT) >> 2]);
#ifdef SPIM_BIGENDIAN
tmp = ((tmp & ~(0xff << (8 * (3 - (addr & 0x3)))))
| (value & 0xff) << (8 * (3 - (addr & 0x3))));
#else
tmp = ((tmp & ~(0xff << (8 * (addr & 0x3))))
| (value & 0xff) << (8 * (addr & 0x3)));
#endif
break;
// ...
}
// Free instruction if it isn't NULL
if (text_seg [(addr - TEXT_BOT) >> 2] != NULL)
{
free_inst (text_seg[(addr - TEXT_BOT) >> 2]);
}
// create a new instruction with the encoding
text_seg [(addr - TEXT_BOT) >> 2] = inst_decode (tmp);
But wait a second....
SPIM checks for NULL...
CPU/mem.cpp:549
:
if (text_seg [(addr - TEXT_BOT) >> 2] != NULL)
...after it already dereferenced it!
CPU/mem.cpp:519
(after macro expansion)
tmp = text_seg [(addr - TEXT_BOT) >> 2]->encoding;
And what would cause this instruction to be NULL? If it is not an instruction.
And what is at the label f
? Nothing, so SPIM dereferences a NULL pointer.
- -1 byte: Parser abuse