fix overflow when truncating in submod2k, fix Guido fuzzing failure 8 (#251)
This commit is contained in:
parent
cb038bb515
commit
47b4f48dfb
|
@ -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) =
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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]
|
||||
x.toOpenArray(0, r.len-1).mod2k_vartime(k)
|
||||
for i in 0 ..< r.len:
|
||||
r[i] = x[i]
|
|
@ -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
|
Loading…
Reference in New Issue