fix overflow when truncating in submod2k, fix Guido fuzzing failure 8 (#251)

This commit is contained in:
Mamy Ratsimbazafy 2023-07-11 09:06:46 +02:00 committed by GitHub
parent cb038bb515
commit 47b4f48dfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 97 additions and 40 deletions

View File

@ -549,7 +549,8 @@ const benchDesc = [
"bench_poly1305", "bench_poly1305",
"bench_sha256", "bench_sha256",
"bench_hash_to_curve", "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 # For temporary (hopefully) investigation that can only be reproduced in CI
@ -558,28 +559,37 @@ const useDebug = [
"tests/t_hash_sha256_vs_openssl.nim", "tests/t_hash_sha256_vs_openssl.nim",
] ]
# Skip sanitizers for specific tests # Skip stack hardening for specific tests
const skipSanitizers = [ const skipStackHardening = [
"tests/t_" "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): when defined(windows):
# UBSAN is not available on mingw # UBSAN is not available on mingw
# https://github.com/libressl-portable/portable/issues/54 # https://github.com/libressl-portable/portable/issues/54
const sanitizers = "" const sanitizers = ""
const stackHardening = ""
else: else:
const sanitizers = const stackHardening =
" --passC:-fstack-protector-strong " & " --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 # because everything is stack allocated
# except with the threadpool: # 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/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 # - 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. # 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 # Sanitizers are incompatible with nim default GC
# The conservative stack scanning of Nim default GC triggers, alignment UB and stack-buffer-overflow check. # 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 # Sanitizers are deactivated by default as they slow down CI by at least 6x
# " --passC:-fsanitize=undefined --passL:-fsanitize=undefined" & " --mm:arc -d:useMalloc" &
# " --passC:-fsanitize=address --passL:-fsanitize=address" & " --passC:-fsanitize=undefined --passL:-fsanitize=undefined" &
# " --passC:-fno-sanitize-recover" # Enforce crash on undefined behaviour " --passC:-fsanitize=address --passL:-fsanitize=address" &
"" " --passC:-fno-sanitize-recover" # Enforce crash on undefined behaviour
# Tests & Benchmarks helper functions # 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 var flags = "" # Beware of https://github.com/nim-lang/Nim/issues/21704
if td.path in useDebug: if td.path in useDebug:
flags = flags & " -d:CTT_DEBUG " 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 flags = flags & sanitizers
cmdFile.testBatch(flags, td.path) cmdFile.testBatch(flags, td.path)
@ -681,7 +693,9 @@ proc addTestSetNvidia(cmdFile: var string) =
for path in testDescNvidia: for path in testDescNvidia:
var flags = "" # Beware of https://github.com/nim-lang/Nim/issues/21704 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 flags = flags & sanitizers
cmdFile.testBatch(flags, path) cmdFile.testBatch(flags, path)
@ -692,7 +706,9 @@ proc addTestSetThreadpool(cmdFile: var string) =
for path in testDescThreadpool: for path in testDescThreadpool:
var flags = " --threads:on --debugger:native " var flags = " --threads:on --debugger:native "
if path notin skipSanitizers: if path notin skipStackHardening:
flags = flags & stackHardening
if path in useSanitizers:
flags = flags & sanitizers flags = flags & sanitizers
cmdFile.testBatch(flags, path) cmdFile.testBatch(flags, path)
@ -705,9 +721,10 @@ proc addTestSetMultithreadedCrypto(cmdFile: var string) =
var flags = " --threads:on --debugger:native" var flags = " --threads:on --debugger:native"
if td in useDebug: if td in useDebug:
flags = flags & " -d:CTT_DEBUG " flags = flags & " -d:CTT_DEBUG "
if td notin skipSanitizers: if td notin skipStackHardening:
flags = flags & stackHardening
if td in useSanitizers:
flags = flags & sanitizers flags = flags & sanitizers
cmdFile.testBatch(flags, td) cmdFile.testBatch(flags, td)
proc addBenchSet(cmdFile: var string) = proc addBenchSet(cmdFile: var string) =

View File

@ -165,7 +165,7 @@ func powMod_vartime*(
let qBits = mBits-ctz let qBits = mBits-ctz
let pBits = 1+ctz let pBits = 1+ctz
# let qWords = qBits.wordsRequired() # TODO: use minimum size for q let qWords = qBits.wordsRequired()
let pWords = pBits.wordsRequired() let pWords = pBits.wordsRequired()
var qBuf = allocStackArray(SecretWord, M.len) var qBuf = allocStackArray(SecretWord, M.len)
@ -174,7 +174,7 @@ func powMod_vartime*(
var yBuf = allocStackArray(SecretWord, pWords) var yBuf = allocStackArray(SecretWord, pWords)
var qInv2kBuf = 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 a1: untyped = a1Buf.toOpenArray(0, M.len-1)
template a2: untyped = a2Buf.toOpenArray(0, pWords-1) template a2: untyped = a2Buf.toOpenArray(0, pWords-1)
template y: untyped = yBuf.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) a1.powOddMod_vartime(a, exponent, q, window)
a2.powMod2k_vartime(a, exponent, k = uint ctz) a2.powMod2k_vartime(a, exponent, k = uint ctz)
block: qInv2k.invMod2k_vartime(qBuf.toOpenArray(0, qWords-1), uint ctz)
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)
y.submod2k_vartime(a2, a1, uint ctz) y.submod2k_vartime(a2, a1, uint ctz)
y.mulmod2k_vartime(y, qInv2k, uint ctz) y.mulmod2k_vartime(y, qInv2k, uint ctz)

View File

@ -43,14 +43,17 @@ func submod2k_vartime*(r{.noAlias.}: var openArray[SecretWord], a, b: openArray[
debug: debug:
const SlotShift = log2_vartime(WordBitWidth.uint32) const SlotShift = log2_vartime(WordBitWidth.uint32)
doAssert r.len >= k.int shr SlotShift, block: doAssert r.len >= k.int shr SlotShift, block:
"\n" &
" r.len: " & $r.len & "\n" & " r.len: " & $r.len & "\n" &
" k: " & $k & "\n" & " k: " & $k & "\n" &
"k/WordBitWidth: " & $(k.int shr SlotShift) " k/WordBitWidth: " & $(k.int shr SlotShift) &
"\n" # [AssertionDefect]
# We can compute (mod 2ʷ) with w >= k # We can compute (mod 2ʷ) with w >= k
# Hence we truncate the substraction to the next multiple of the word size # Hence we truncate the substraction to the next multiple of the word size
template trunc(x: openArray[SecretWord]): openArray[SecretWord] = 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: if a.len >= b.len:
let underflow {.used.} = r.subMP(a.trunc(), b.trunc()) let underflow {.used.} = r.subMP(a.trunc(), b.trunc())
@ -135,7 +138,7 @@ func powMod2k_vartime*(
var sBuf = allocStackArray(SecretWord, r.len) var sBuf = allocStackArray(SecretWord, r.len)
template s: untyped = sBuf.toOpenArray(0, r.len-1) 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ᵏ) # range [r.len, a.len) will be truncated (mod 2ᵏ)
sBuf[i] = a[i] sBuf[i] = a[i]
@ -152,7 +155,7 @@ func powMod2k_vartime*(
func invModBitwidth(a: SecretWord): SecretWord {.borrow.} func invModBitwidth(a: SecretWord): SecretWord {.borrow.}
## Inversion a⁻¹ (mod 2³²) or a⁻¹ (mod 2⁶⁴) ## 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ᵏ) ## Inversion a⁻¹ (mod 2ᵏ)
## with 2ᵏ a multi-precision integer. ## 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 # - Double the number of correct bits at each Dumas iteration
# - once n >= k, reduce mod 2ᵏ # - once n >= k, reduce mod 2ᵏ
var x = allocStackArray(SecretWord, a.len) debug:
var t = allocStackArray(SecretWord, a.len) const SlotShift = log2_vartime(WordBitWidth.uint32)
var u = allocStackArray(SecretWord, a.len) 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() x[0] = a[0].invModBitwidth()
for i in 1 ..< a.len: for i in 1 ..< r.len:
x[i] = Zero x[i] = Zero
var correctWords = 1 var correctWords = 1
while correctWords.uint*WordBitWidth < k: while correctWords.uint*WordBitWidth < k:
# x *= 2-ax # x *= 2-ax
let words = 2*correctWords let words = min(r.len, 2*correctWords)
t.toOpenArray(0, words-1) t.toOpenArray(0, words-1)
.mulmod2k_vartime( .mulmod2k_vartime(
x.toOpenArray(0, correctWords-1), x.toOpenArray(0, correctWords-1),
a.toOpenArray(0, words-1), a.toOpenArray(0, a.len-1),
words.uint*WordBitWidth) words.uint*WordBitWidth)
u.toOpenArray(0, words-1) u.toOpenArray(0, words-1)
@ -198,6 +210,6 @@ func invMod2k_vartime*(a: var openArray[SecretWord], k: uint) {.noInline, tags:
correctWords = words correctWords = words
x.toOpenArray(0, a.len-1).mod2k_vartime(k) x.toOpenArray(0, r.len-1).mod2k_vartime(k)
for i in 0 ..< a.len: for i in 0 ..< r.len:
a[i] = x[i] r[i] = x[i]

View File

@ -41,3 +41,38 @@ suite "EVM ModExp precompile (EIP-198)":
let status = r.eth_evm_modexp(input) let status = r.eth_evm_modexp(input)
doAssert status == cttEVM_Success doAssert status == cttEVM_Success
doAssert r[0] == 0, ". Result was " & $r[0] 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