Fix fuzzer to truncate inputs to 32-bit integers#374
Conversation
koute
left a comment
There was a problem hiding this comment.
Few notes:
memsetis an experimental instruction and isn't currently enabled "in production".- Truncating
a0isn't necessarily a problem. All loads and stores in PVM are defined to ignore the upper 32-bits and the address space is always 32-bit (even on a 64-bit target). Somemsetshould also ignore the upper bits. The remaining question is however: do we preserve the full value of the destination pointer (with the upper bits intact), or do we truncate it? Either one is fine (whichever one is faster on the recompiler backend in preferable). - We have to be careful here so that the program can't memset any of the recompiler's internal structures (which live in the upper 32-bits of the VM sandbox's address space), which is probably why I was truncating the pointer. The truncation of
countin the recompiler might have been a typo, but I don't remember at this point. We should probably add a test which deliberately tries to corrupt the VM's internal structures withmemset.
| asm::load_imm(Reg::A0, 0xffff0000), | ||
| asm::load_imm(Reg::A1, 0), | ||
| asm::mul_64(Reg::A0, Reg::A0, Reg::A0), | ||
| asm::load_imm(Reg::A2, 0), | ||
| asm::memset(), | ||
| asm::ret(), |
There was a problem hiding this comment.
This is unnecessarily roundabout.
- No need to set regs to zero, as they're zero by default.
- No need to
mul_64becauseload_imm64exists, however it's even better to justset_regit directly on the instance.
| instance.set_next_program_counter(ProgramCounter(0)); | ||
| assert!(matches!(instance.run().unwrap(), InterruptKind::Finished)); | ||
| let a0 = instance.reg(Reg::A0); | ||
| assert_eq!(a0 >> 32 != 0 || a0 == 0, true, "A0 was unexpectedly truncated: {:#018x}", a0); |
There was a problem hiding this comment.
This should check the exact value.
| if reg_size == RegSize::R32 { | ||
| self.asm.push(mov(RegSize::R32, count, count)); | ||
| } | ||
|
|
There was a problem hiding this comment.
You've added this but there's no test.
There was a problem hiding this comment.
Sure, I can add a test if needed, but the change simply avoids zext in 64-bit mode for which test is added.
Thanks for explanation. |
ffb5e05 to
5589353
Compare
That's.... not a proper fix? (: The whole point of the fuzzer is to find divergences between the interpreter and the recompiler, that is: the same program should behave exactly the same regardless of which backend is used. So if a user (in their program) can pass a 64-bit value to Or in other words: for any possible program blob (except the only exception with validation when a |
hm... thanks. I think I have fully misunderstood recompiler and how it's used in fuzzer. |
|
How it's sandboxed is an implementation detail that's not exposed to the program.
No; there can be bugs in either one of them. If they diverge one has to look at the behavior of both and see which one makes sense. |
|
@koute gentle ping |
|
@copilot resolve the merge conflicts in this pull request |
This reverts commit 5589353.
- The fuzzer identified divergency in interpreter and recompiler when
following code was executed
```
// All registers are zeroed
1 fallthrough
2 shift_logical_left_imm_alt_64(A0, A0, 0x8F8F030F)
3 sub_32(SP, T0, A2)
4 branch_less_signed(A0, A0, target=0)
5 memset()
6 branch_less_signed(S1, A0, target=0)
7 store_imm_indirect_u8(RA, 0, 0)
```
when `memset` is exeucted, A0 is `0xFFFFFFFF8F8F030F`, which interpreter
truncated to `0x000000008F8F030F`, while recompiler preserved A.
Later that resulted that 6 branch instruction is taken by interpreter
and it runs out of gas, while recompiler tries to executed store which
resulted to trap.
To fix it, bookeep `dst` register before 32-bit truncation, restore the
register to original ptr + number of executed iterations. This prevets `A0`
clobbering that may be used later
- the other problem was due inconsistent behavior on `count`:
recompiler truncates that register to 32-bit value, while interpreter
keeps it as 64-bit value.
To fix it, keep `count` register as 64-bit integer in recompiler
6be8eb4 to
397b0f0
Compare
| // A0 = 0xffffffffffff0000 * 0xffffffffffff0000 = 0x0000000100000000 | ||
| asm::load_imm(Reg::A0, 0xffff0000), | ||
| asm::mul_64(Reg::A0, Reg::A0, Reg::A0), | ||
| // A2 = sign_extend(0xff08bdbd) = 0xffffffffff08bdbd | ||
| asm::load_imm(Reg::A2, 0xff08bdbd), |
There was a problem hiding this comment.
Again, this part is unnecessarily complicated because you can just use set_reg to set the desired values directly instead of calculating them.
The whole set_code ideally should just look like this:
builder.set_code(&[asm::memset(), asm::ret()]);
There's no need to involve mul_64, load_imm, etc. and overcomplicate the test because this test is supposed to test memset and memset only.
Uh oh!
There was an error while loading. Please reload this page.