Nested calldata partial read aliases sibling offsets
- Dominant language
- C++
- Stars
- 25.7k
- Forks
- 6.2k
- Avg merge
- 2d 19h
- Merged PRs (30d)
- 29
Description
## Description
For `uint256[][2][] calldata`, an inner dynamic-array offset can point back into the fixed-array head. When the caller only reads `x[0][0].length` or `x[0][0][0]`, Solidity decodes the sibling offset word as the length and the following word as element data. Returning or fully re-encoding the same malformed pair still rejects, so this is a partial-read variant of the nested-calldata lazy-validation cluster rather than another full-canonicalization case.
The ABI spec describes a dynamic head as an offset to the beginning of that element's tail, relative to the start of that value's encoding. However, the available-length decoder validates the immediate copied range, then decodes dynamic-base elements from `add(offset, innerOffset)` without requiring the offset to point past the head region:
```cpp
// libsolidity/codegen/ABIFunctions.cpp:1190-1203
let srcEnd := add(offset, mul(length, ))
if gt(srcEnd, end) {
()
}
for { let src := offset } lt(src, srcEnd) { src := add(src, ) }
{
let innerOffset := (src)
if gt(innerOffset, 0xffffffffffffffff) { () }
let elementPos := add(offset, innerOffset)
mstore(dst, (elementPos, end))
}
```
Expected: an inner dynamic-array offset that points back into the sibling head region should be rejected, since the sibling word is not a valid tail encoding for that element.
## Environment
- Compiler version: 0.8.35-develop.2026.5.5+commit.47b9dedd.Linux.g++
- Operating system: Linux Ubuntu Jammy
## Steps to Reproduce
```solidity
contract C {
function firstLength(uint256[][2][] calldata x) external pure returns (uint256) {
return x[0][0].length;
}
function firstElement(uint256[][2][] calldata x) external pure returns (uint256) {
return x[0][0][0];
}
}
```
Call either function with the following ABI words after the selector:
```text
0x20, 1, 0x20, 0x20, 1, 0xabc
```
`firstLength` returns `1`, and `firstElement` returns `0xabc`. The same words sent to a function returning `x` fail with empty revert data, confirming that the decoded shape is not a valid full re-encoding but is still accepted for partial reads.
Contributor guide
Assessment
This issue has not been assessed yet.