diff --git a/constantine.nimble b/constantine.nimble index 18ad3f18..99334cc5 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -549,7 +549,8 @@ const benchDesc = [ "bench_poly1305", "bench_sha256", "bench_hash_to_curve", - "bench_ethereum_bls_signatures" + "bench_ethereum_bls_signatures", + "bench_evm_modexp_dos", ] # For temporary (hopefully) investigation that can only be reproduced in CI @@ -558,28 +559,37 @@ const useDebug = [ "tests/t_hash_sha256_vs_openssl.nim", ] -# Skip sanitizers for specific tests -const skipSanitizers = [ +# Skip stack hardening for specific tests +const skipStackHardening = [ "tests/t_" ] +# use sanitizers for specific tests +const useSanitizers = [ + "tests/math_arbitrary_precision/t_bigints_powmod_vs_gmp.nim", + "tests/t_ethereum_evm_modexp.nim", + "tests/t_etherem_evm_precompiles.nim", +] when defined(windows): # UBSAN is not available on mingw # https://github.com/libressl-portable/portable/issues/54 const sanitizers = "" + const stackHardening = "" else: - const sanitizers = + const stackHardening = " --passC:-fstack-protector-strong " & - # Fortify source wouldn't help us detect errors in cosntantine + # Fortify source wouldn't help us detect errors in Constantine # because everything is stack allocated # except with the threadpool: # - https://developers.redhat.com/blog/2021/04/16/broadening-compiler-checks-for-buffer-overflows-in-_fortify_source#what_s_next_for__fortify_source # - https://developers.redhat.com/articles/2023/02/06/how-improve-application-security-using-fortifysource3#how_to_improve_application_fortification # We also don't use memcpy as it is not constant-time and our copy is compile-time sized. - " --passC:-D_FORTIFY_SOURCE=3 " & + " --passC:-D_FORTIFY_SOURCE=3 " + + const sanitizers = # Sanitizers are incompatible with nim default GC # The conservative stack scanning of Nim default GC triggers, alignment UB and stack-buffer-overflow check. @@ -588,10 +598,10 @@ else: # # Sanitizers are deactivated by default as they slow down CI by at least 6x - # " --passC:-fsanitize=undefined --passL:-fsanitize=undefined" & - # " --passC:-fsanitize=address --passL:-fsanitize=address" & - # " --passC:-fno-sanitize-recover" # Enforce crash on undefined behaviour - "" + " --mm:arc -d:useMalloc" & + " --passC:-fsanitize=undefined --passL:-fsanitize=undefined" & + " --passC:-fsanitize=address --passL:-fsanitize=address" & + " --passC:-fno-sanitize-recover" # Enforce crash on undefined behaviour # Tests & Benchmarks helper functions # ---------------------------------------------------------------- @@ -669,7 +679,9 @@ proc addTestSet(cmdFile: var string, requireGMP: bool) = var flags = "" # Beware of https://github.com/nim-lang/Nim/issues/21704 if td.path in useDebug: flags = flags & " -d:CTT_DEBUG " - if td.path notin skipSanitizers: + if td.path notin skipStackHardening: + flags = flags & stackHardening + if td.path in useSanitizers: flags = flags & sanitizers cmdFile.testBatch(flags, td.path) @@ -681,7 +693,9 @@ proc addTestSetNvidia(cmdFile: var string) = for path in testDescNvidia: var flags = "" # Beware of https://github.com/nim-lang/Nim/issues/21704 - if path notin skipSanitizers: + if path notin skipStackHardening: + flags = flags & stackHardening + if path in useSanitizers: flags = flags & sanitizers cmdFile.testBatch(flags, path) @@ -692,7 +706,9 @@ proc addTestSetThreadpool(cmdFile: var string) = for path in testDescThreadpool: var flags = " --threads:on --debugger:native " - if path notin skipSanitizers: + if path notin skipStackHardening: + flags = flags & stackHardening + if path in useSanitizers: flags = flags & sanitizers cmdFile.testBatch(flags, path) @@ -705,9 +721,10 @@ proc addTestSetMultithreadedCrypto(cmdFile: var string) = var flags = " --threads:on --debugger:native" if td in useDebug: flags = flags & " -d:CTT_DEBUG " - if td notin skipSanitizers: + if td notin skipStackHardening: + flags = flags & stackHardening + if td in useSanitizers: flags = flags & sanitizers - cmdFile.testBatch(flags, td) proc addBenchSet(cmdFile: var string) = diff --git a/constantine/math_arbitrary_precision/arithmetic/bigints_views.nim b/constantine/math_arbitrary_precision/arithmetic/bigints_views.nim index 7bbd79e7..26ad7c98 100644 --- a/constantine/math_arbitrary_precision/arithmetic/bigints_views.nim +++ b/constantine/math_arbitrary_precision/arithmetic/bigints_views.nim @@ -165,7 +165,7 @@ func powMod_vartime*( let qBits = mBits-ctz let pBits = 1+ctz - # let qWords = qBits.wordsRequired() # TODO: use minimum size for q + let qWords = qBits.wordsRequired() let pWords = pBits.wordsRequired() var qBuf = allocStackArray(SecretWord, M.len) @@ -174,7 +174,7 @@ func powMod_vartime*( var yBuf = allocStackArray(SecretWord, pWords) var qInv2kBuf = allocStackArray(SecretWord, pWords) - template q: untyped = qBuf.toOpenArray(0, M.len-1) + template q: untyped = qBuf.toOpenArray(0, M.len-1) # TODO use qWords instead of M.len template a1: untyped = a1Buf.toOpenArray(0, M.len-1) template a2: untyped = a2Buf.toOpenArray(0, pWords-1) template y: untyped = yBuf.toOpenArray(0, pWords-1) @@ -185,14 +185,7 @@ func powMod_vartime*( a1.powOddMod_vartime(a, exponent, q, window) a2.powMod2k_vartime(a, exponent, k = uint ctz) - block: - let min = min(pWords, M.len) - for i in 0 ..< min: - qInv2k[i] = q[i] - for i in min ..< pWords: - qInv2k[i] = Zero - - qInv2k.invMod2k_vartime(uint ctz) + qInv2k.invMod2k_vartime(qBuf.toOpenArray(0, qWords-1), uint ctz) y.submod2k_vartime(a2, a1, uint ctz) y.mulmod2k_vartime(y, qInv2k, uint ctz) diff --git a/constantine/math_arbitrary_precision/arithmetic/limbs_mod2k.nim b/constantine/math_arbitrary_precision/arithmetic/limbs_mod2k.nim index c9f2fc95..59df47ac 100644 --- a/constantine/math_arbitrary_precision/arithmetic/limbs_mod2k.nim +++ b/constantine/math_arbitrary_precision/arithmetic/limbs_mod2k.nim @@ -43,14 +43,17 @@ func submod2k_vartime*(r{.noAlias.}: var openArray[SecretWord], a, b: openArray[ debug: const SlotShift = log2_vartime(WordBitWidth.uint32) doAssert r.len >= k.int shr SlotShift, block: - "r.len: " & $r.len & "\n" & - "k: " & $k & "\n" & - "k/WordBitWidth: " & $(k.int shr SlotShift) + "\n" & + " r.len: " & $r.len & "\n" & + " k: " & $k & "\n" & + " k/WordBitWidth: " & $(k.int shr SlotShift) & + "\n" # [AssertionDefect] # We can compute (mod 2ʷ) with w >= k # Hence we truncate the substraction to the next multiple of the word size template trunc(x: openArray[SecretWord]): openArray[SecretWord] = - x.toOpenArray(0, k.int.wordsRequired()-1) + let truncHi = min(x.len, k.int.wordsRequired()) - 1 + x.toOpenArray(0, truncHi) if a.len >= b.len: let underflow {.used.} = r.subMP(a.trunc(), b.trunc()) @@ -135,7 +138,7 @@ func powMod2k_vartime*( var sBuf = allocStackArray(SecretWord, r.len) template s: untyped = sBuf.toOpenArray(0, r.len-1) - for i in 0 ..< r.len: + for i in 0 ..< min(r.len, a.len): # range [r.len, a.len) will be truncated (mod 2ᵏ) sBuf[i] = a[i] @@ -152,7 +155,7 @@ func powMod2k_vartime*( func invModBitwidth(a: SecretWord): SecretWord {.borrow.} ## Inversion a⁻¹ (mod 2³²) or a⁻¹ (mod 2⁶⁴) -func invMod2k_vartime*(a: var openArray[SecretWord], k: uint) {.noInline, tags: [Alloca].} = +func invMod2k_vartime*(r: var openArray[SecretWord], a: openArray[SecretWord], k: uint) {.noInline, tags: [Alloca].} = ## Inversion a⁻¹ (mod 2ᵏ) ## with 2ᵏ a multi-precision integer. # @@ -165,23 +168,32 @@ func invMod2k_vartime*(a: var openArray[SecretWord], k: uint) {.noInline, tags: # - Double the number of correct bits at each Dumas iteration # - once n >= k, reduce mod 2ᵏ - var x = allocStackArray(SecretWord, a.len) - var t = allocStackArray(SecretWord, a.len) - var u = allocStackArray(SecretWord, a.len) + debug: + const SlotShift = log2_vartime(WordBitWidth.uint32) + doAssert r.len >= k.int shr SlotShift, block: + "\n" & + " r.len: " & $r.len & "\n" & + " k: " & $k & "\n" & + " k/WordBitWidth: " & $(k.int shr SlotShift) & + "\n" # [AssertionDefect] + + var x = allocStackArray(SecretWord, r.len) + var t = allocStackArray(SecretWord, r.len) + var u = allocStackArray(SecretWord, r.len) x[0] = a[0].invModBitwidth() - for i in 1 ..< a.len: + for i in 1 ..< r.len: x[i] = Zero var correctWords = 1 while correctWords.uint*WordBitWidth < k: # x *= 2-ax - let words = 2*correctWords + let words = min(r.len, 2*correctWords) t.toOpenArray(0, words-1) .mulmod2k_vartime( x.toOpenArray(0, correctWords-1), - a.toOpenArray(0, words-1), + a.toOpenArray(0, a.len-1), words.uint*WordBitWidth) u.toOpenArray(0, words-1) @@ -198,6 +210,6 @@ func invMod2k_vartime*(a: var openArray[SecretWord], k: uint) {.noInline, tags: correctWords = words - x.toOpenArray(0, a.len-1).mod2k_vartime(k) - for i in 0 ..< a.len: - a[i] = x[i] \ No newline at end of file + x.toOpenArray(0, r.len-1).mod2k_vartime(k) + for i in 0 ..< r.len: + r[i] = x[i] \ No newline at end of file diff --git a/tests/t_ethereum_evm_modexp.nim b/tests/t_ethereum_evm_modexp.nim index 01f37fc2..b69a1032 100644 --- a/tests/t_ethereum_evm_modexp.nim +++ b/tests/t_ethereum_evm_modexp.nim @@ -41,3 +41,38 @@ suite "EVM ModExp precompile (EIP-198)": let status = r.eth_evm_modexp(input) doAssert status == cttEVM_Success doAssert r[0] == 0, ". Result was " & $r[0] + + test "Audit #8 - off-by-1 buffer overflow - ptr + length exclusive vs openArray(lo, hi) inclusive": + let input = [ + # Length of base (24) + 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, 0x18, + + # Length of exponent (36) + 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, 0x24, + + # Length of modulus (56) + 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, 0x38, + + # Base + 0x07, 0x19, 0x2b, 0x95, 0xff, 0xc8, 0xda, 0x78, 0x63, 0x10, 0x11, 0xed, 0x6b, 0x24, 0xcd, 0xd5, + 0x73, 0xf9, 0x77, 0xa1, 0x1e, 0x79, 0x48, 0x11, + + # Exponent + 0x03, 0x67, 0x68, 0x54, 0xfe, 0x24, 0x14, 0x1c, 0xb9, 0x8f, 0xe6, 0xd4, 0xb2, 0x0d, 0x02, 0xb4, + 0x51, 0x6f, 0xf7, 0x02, 0x35, 0x0e, 0xdd, 0xb0, 0x82, 0x67, 0x79, 0xc8, 0x13, 0xf0, 0xdf, 0x45, + 0xbe, 0x81, 0x12, 0xf4, + + # Modulus + 0x1a, 0xbf, 0x81, 0x1f, 0x86, 0xe1, 0x02, 0x78, 0x66, 0xe4, 0x23, 0x65, 0x49, 0x0f, 0x8d, 0x6e, + 0xc2, 0x23, 0x94, 0x18, 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, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + var r = newSeq[byte](56) + let status = r.eth_evm_modexp(input) + doAssert status == cttEVM_Success \ No newline at end of file