From c85ffb069a34eb42999c0d5dbbfff7e72e8ff1ca Mon Sep 17 00:00:00 2001 From: Mamy Ratsimbazafy Date: Wed, 6 Sep 2023 15:00:29 +0200 Subject: [PATCH] fix fuzz 18: modexp - handling of infinitely right-padded inputs leading to buffer overflow or stack overflow (#264) --- constantine/ethereum_evm_precompiles.nim | 49 +++++++++++++++++++----- tests/t_ethereum_evm_modexp.nim | 36 +++++++++++++++++ 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/constantine/ethereum_evm_precompiles.nim b/constantine/ethereum_evm_precompiles.nim index 9f2126e9..6c58f7a5 100644 --- a/constantine/ethereum_evm_precompiles.nim +++ b/constantine/ethereum_evm_precompiles.nim @@ -120,7 +120,7 @@ func eth_evm_ecadd*(r: var openArray[byte], inputs: openarray[byte]): CttEVMStat # Auto-pad with zero var padded: array[128, byte] - padded.rawCopy(0, inputs, 0, min(inputs.len, 128)) + padded.rawCopy(0, inputs, 0, min(inputs.len, padded.len)) var P{.noInit.}, Q{.noInit.}, R{.noInit.}: ECP_ShortW_Jac[Fp[BN254_Snarks], G1] @@ -173,8 +173,8 @@ func eth_evm_ecmul*(r: var openArray[byte], inputs: openarray[byte]): CttEVMStat return cttEVM_InvalidOutputSize # Auto-pad with zero - var padded: array[128, byte] - padded.rawCopy(0, inputs, 0, min(inputs.len, 128)) + var padded: array[96, byte] + padded.rawCopy(0, inputs, 0, min(inputs.len, padded.len)) var P{.noInit.}: ECP_ShortW_Jac[Fp[BN254_Snarks], G1] @@ -401,10 +401,15 @@ func eth_evm_modexp*(r: var openArray[byte], inputs: openArray[byte]): CttEVMSta # Input parse sizes # ----------------- + + # Auto-pad with zero + var paddedLengths: array[96, byte] + paddedLengths.rawCopy(0, inputs, 0, min(inputs.len, paddedLengths.len)) + let - bL = BigInt[256].unmarshal(inputs.toOpenArray(0, 31), bigEndian) - eL = BigInt[256].unmarshal(inputs.toOpenArray(32, 63), bigEndian) - mL = BigInt[256].unmarshal(inputs.toOpenArray(64, 95), bigEndian) + bL = BigInt[256].unmarshal(paddedLengths.toOpenArray(0, 31), bigEndian) + eL = BigInt[256].unmarshal(paddedLengths.toOpenArray(32, 63), bigEndian) + mL = BigInt[256].unmarshal(paddedLengths.toOpenArray(64, 95), bigEndian) maxSize = BigInt[256].fromUint(high(uint)) # A CPU can only address up to high(uint) @@ -433,13 +438,20 @@ func eth_evm_modexp*(r: var openArray[byte], inputs: openArray[byte]): CttEVMSta # Special cases # ---------------------- + if paddedLengths.len + baseByteLen + exponentByteLen >= inputs.len: + # Modulus value is in the infinitely right padded zeros input, hence is zero. + r.setZero() + return cttEVM_Success + if modulusByteLen == 0: r.setZero() return cttEVM_Success + if exponentByteLen == 0: r.setZero() r[r.len-1] = byte 1 # 0^0 = 1 and x^0 = 1 return cttEVM_Success + if baseByteLen == 0: r.setZero() return cttEVM_Success @@ -448,25 +460,42 @@ func eth_evm_modexp*(r: var openArray[byte], inputs: openArray[byte]): CttEVMSta # --------------------- # Inclusive stops - let baseStart = 96 + # Due to special-case checks and early returns, + # only the modulus can require right-padding with zeros here + # inputs[expStop] cannot buffer overflow + let baseStart = paddedLengths.len let baseStop = baseStart+baseByteLen-1 let expStart = baseStop+1 let expStop = expStart+exponentByteLen-1 let modStart = expStop+1 let modStop = modStart+modulusByteLen-1 + # We assume that gas checks prevent numbers too big for stack allocation. var baseBuf = allocStackArray(SecretWord, baseWordLen) var modulusBuf = allocStackArray(SecretWord, modulusWordLen) var outputBuf = allocStackArray(SecretWord, modulusWordLen) template base(): untyped = baseBuf.toOpenArray(0, baseWordLen-1) + template exponent(): untyped = inputs.toOpenArray(expStart, expStop) template modulus(): untyped = modulusBuf.toOpenArray(0, modulusWordLen-1) template output(): untyped = outputBuf.toOpenArray(0, modulusWordLen-1) + # Base deserialization base.toOpenArray(0, baseWordLen-1).unmarshal(inputs.toOpenArray(baseStart, baseStop), WordBitWidth, bigEndian) - modulus.toOpenArray(0, modulusWordLen-1).unmarshal(inputs.toOpenArray(modStart, modStop), WordBitWidth, bigEndian) - template exponent(): untyped = - inputs.toOpenArray(expStart, expStop) + + # Modulus deserialization + let realLen = paddedLengths.len + baseByteLen + exponentByteLen + modulusByteLen + let overflowLen = realLen - inputs.len + if overflowLen > 0: + let physLen = inputs.len-modStart # Length of data physically present (i.e. excluding padded zeros) + var paddedModBuf = allocStackArray(byte, modulusByteLen) + template paddedMod(): untyped = paddedModBuf.toOpenArray(0, modulusByteLen-1) + + paddedMod.rawCopy(0, inputs, modStart, physLen) + zeroMem(paddedMod[physLen].addr, overflowLen) + modulus.unmarshal(paddedMod, WordBitWidth, bigEndian) + else: + modulus.unmarshal(inputs.toOpenArray(modStart, modStop), WordBitWidth, bigEndian) # Computation # --------------------- diff --git a/tests/t_ethereum_evm_modexp.nim b/tests/t_ethereum_evm_modexp.nim index b69a1032..0de31ed2 100644 --- a/tests/t_ethereum_evm_modexp.nim +++ b/tests/t_ethereum_evm_modexp.nim @@ -75,4 +75,40 @@ suite "EVM ModExp precompile (EIP-198)": var r = newSeq[byte](56) let status = r.eth_evm_modexp(input) + doAssert status == cttEVM_Success + + test "Audit #18 - Handling of inputs infinitely right-padded with zeros (read past buffers or stack overflow for temporaries)": + let input = [ + # Base length + uint8 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + # Exponent length + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0xa8, 0xfd, + # Modulus length + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + + 0xc1, 0x00, 0x00, 0x00, 0x51, 0x00, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x00, 0x50, 0x50, 0x50, + 0x50, 0x50, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0xbc, 0x9b, 0xa0, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x00, 0x50, 0x50, 0x50, + 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, + 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, + 0x50, 0x50, 0x50, 0x50, 0x50, 0x00, 0x00, 0x00, + 0xa0] + var r = newSeq[byte](1) + let status = r.eth_evm_modexp(input) doAssert status == cttEVM_Success \ No newline at end of file