argotorg / argotorg/solidity

Nested calldata partial read aliases sibling offsets

Open
#16,762 0 comments 0 reactions 1 assignee Claimed by @r0qs View on GitHub
bug :bug:
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.