Skip to content

Commit

Permalink
fix overflow when truncating in submod2k, fix Guido fuzzing failure 8
Browse files Browse the repository at this point in the history
  • Loading branch information
mratsim committed Jul 10, 2023
1 parent cb038bb commit 47d0b57
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 40 deletions.
47 changes: 32 additions & 15 deletions constantine.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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
# ----------------------------------------------------------------
Expand Down Expand Up @@ -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)
Expand All @@ -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)

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

Expand All @@ -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) =
Expand Down
13 changes: 3 additions & 10 deletions constantine/math_arbitrary_precision/arithmetic/bigints_views.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)

Expand Down
42 changes: 27 additions & 15 deletions constantine/math_arbitrary_precision/arithmetic/limbs_mod2k.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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]

Expand All @@ -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.
#
Expand All @@ -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)
Expand All @@ -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]
35 changes: 35 additions & 0 deletions tests/t_ethereum_evm_modexp.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 47d0b57

Please sign in to comment.