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_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) =

View File

@ -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)

View File

@ -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:
"\n" &
" r.len: " & $r.len & "\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
# 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]

View File

@ -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