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_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) =
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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]
|
|
@ -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
|
Loading…
Reference in New Issue