Data might be arranged incorrectly by rawData[T].list
Description
As also discussed in Finding ref↗, the rawData
type in sdk/app.go is used to hold receipt, storage, and transaction data. Raw data can be added by pinning to a specific index (stored in the field special
) and added to a list ordered
. The list
function is then used to convert a rawData
to an actual list (slice). The pinned entries from special
will be added at the respective index, and the remaining entries will be filled with the entries from ordered
. It is implemented as follows:
func (q *rawData[T]) list(max int) []T {
var empty T
var l []T
ordered := q.ordered
for i := 0; i < max; i++ {
if q.special[i] != empty {
l = append(l, q.special[i])
} else if len(ordered) > 0 {
l = append(l, ordered[0])
ordered = ordered[1:]
}
}
return l
}
Detection of whether q.special
holds an entry at a particular index is done by comparing the value against the default value of the data type, which is what map access will return at uninitialized keys. This will correctly identify indexes at which no data point was added to q.special
. However, data points that were added but happened to be the default value of that type will also be found, as false positives.
Because of this, the ultimate order of the data returned may be other than intended whenever a pinned entry has the default value.
For example, assume that there is a pinned entry A at index 0
, and there is additionally an ordered entry B. The expectation is that B occurs as the second entry and A as the first. But if A happens to have the default value, then B will actually occur as the first entry. As A is not detected as a real data entry, it will not explicitly be added, but padding with default values may ultimately act as if A had been added as the second entry, as A also has the default value.
Entries with default value are unlikely to occur in practice. However, their occurrence is not impossible, as the default value of a StorageData
data point would state that at the genesis block, address zero had value zero at storage slot zero, which is a sensible statement about historical Ethereum storage data.
Impact
In very rare edge cases, list
might return incorrectly ordered data.
Recommendations
We recommend to check whether the map has a value at the relevant key rather than only checking whether the returned value is the default. This can be done by replacing the q.special[i] != empty
check by something like the following:
-if q.special[i] != empty {
+// The second return value of a map access is a Boolean indicating whether a value was found at that key.
+value, exists := q.special[i]
+if exists {
Remediation
This issue has been acknowledged by Brevis, and a fix was implemented in commit 7b2df59a↗.