Fix/ny unaligned mload native byteorder (#562)

Another bug found by @jubnzv:

`--newyork`: an unaligned full-word `mload`/`mstore` overlapping a word
kept native (little-endian) by the heap optimization returned
byte-swapped bytes.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
xermicusandClaude Opus 4.8 authored and GitHub committed 2026-07-07 19:38:21 +02:00
1 parent 9b177f6400
commit c18d29b861
5 files changed
+83 -4

No files matched your search

@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
/// Soundness PoC (newyork heap optimization): a full-word `mload` at a
/// non-word-aligned offset that overlaps a word the heap analysis classified
/// as native (little-endian, byte-swap elided) reads that word's bytes in the
/// wrong order.
///
/// `mstore(0x20, PAT)` is word-aligned and does not escape, so newyork keeps
/// it little-endian (native mode, no byte-swap). `mload(0x08)` reads
/// `[0x08, 0x28)`, overlapping the native word at `0x20`, but is itself
/// unaligned so it lowers to a big-endian (byte-swapped) read. The two
/// accesses disagree on byte order for the shared bytes, so the load returns
/// byte-swapped garbage. The fix taints every word an unaligned full-word
/// access covers, forcing those words big-endian for all accesses.
contract UnalignedMload {
function f() external pure returns (bytes32 r) {
assembly {
mstore(0x20, 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20)
r := mload(0x08)
}
}
}
+12
View File
@@ -317,6 +317,18 @@ case!(
unaligned_mstore_bug,
);
sol!(
contract UnalignedMload {
function f() external pure returns (bytes32);
}
);
case!(
"UnalignedMloadNativeBug.sol",
UnalignedMload,
fCall,
unaligned_mload_native_bug,
);
sol!(
contract ConstReturnOverflowBug {
function bug() external pure returns (uint256);
+23
View File
@@ -469,6 +469,29 @@ fn unaligned_mstore_forwarding() {
run_differential(actions);
}
/// Regression (newyork heap optimization): a full-word `mload` at a
/// non-word-aligned offset that overlaps a word classified native
/// (little-endian, byte-swap elided) returned byte-swapped garbage. In the
/// PoC `mstore(0x20, PAT)` is word-aligned and stays native, but `mload(0x08)`
/// reads `[0x08, 0x28)` — overlapping the native word `0x20` — and lowers to a
/// big-endian read, so the two accesses disagree on byte order for the shared
/// bytes. The heap analysis now taints every word an unaligned full-word
/// access covers, forcing them big-endian so all accesses agree. Compared
/// newyork-PVM vs solc-EVM.
#[test]
fn unaligned_mload_native_byte_order() {
let mut actions = instantiate("contracts/UnalignedMloadNativeBug.sol", "UnalignedMload");
actions.push(Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::unaligned_mload_native_bug().calldata,
});
run_differential(actions);
}
/// Regression: `to_llvm.rs::get_or_create_return_block` (and revert / Stop
/// siblings) build the constant offset/length via
/// `context.xlen_type().const_int(const_offset, false)`, which silently