mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-03-12 22:26:08 +00:00
feat(crypto): improve trezor-crypto fuzzer, add new dictionary extraction program
Introduce fuzzing harnesses for zkp* functions and adapt some differential fuzzing Additional documentation and minor cleanup Add temporary workaround for clang-14 and more explicit Makefile behavior
This commit is contained in:
parent
47a05720aa
commit
cf3c57d0ae
@ -2,10 +2,14 @@ ifeq ($(FUZZER),1)
|
|||||||
CC ?= clang
|
CC ?= clang
|
||||||
LD ?= $(CC)
|
LD ?= $(CC)
|
||||||
SANFLAGS += -fsanitize=fuzzer
|
SANFLAGS += -fsanitize=fuzzer
|
||||||
CFLAGS += -fsanitize-ignorelist=fuzzer/sanitizer_ignorelist.txt
|
|
||||||
|
|
||||||
# this works around clang optimization issues in relation with -fsanitize=undefined
|
# TODO gcc as well as older clang versions <= 12 do not support this feature
|
||||||
|
$(info "info: using -fsanitize-ignorelist")
|
||||||
|
SANFLAGS += -fsanitize-ignorelist=fuzzer/sanitizer_ignorelist.txt
|
||||||
|
|
||||||
# TODO is there a better solution, for example by disabling a specific optimization technique?
|
# TODO is there a better solution, for example by disabling a specific optimization technique?
|
||||||
|
# there is a clang optimization issue in relation with the blake2 code at -fsanitize=undefined
|
||||||
|
$(warning "warning: disable optimization on blake2 code as workaround")
|
||||||
blake2b.o: OPTFLAGS += -O0
|
blake2b.o: OPTFLAGS += -O0
|
||||||
blake2s.o: OPTFLAGS += -O0
|
blake2s.o: OPTFLAGS += -O0
|
||||||
|
|
||||||
@ -56,6 +60,12 @@ ZKP_CFLAGS = \
|
|||||||
ZKP_PATH = ../vendor/secp256k1-zkp
|
ZKP_PATH = ../vendor/secp256k1-zkp
|
||||||
CFLAGS += -DSECP256K1_CONTEXT_SIZE=208
|
CFLAGS += -DSECP256K1_CONTEXT_SIZE=208
|
||||||
|
|
||||||
|
# TODO remove this workaround once possible
|
||||||
|
ifeq ($(CC),clang-14)
|
||||||
|
$(warning "warning: suppress clang-14 compiler warning for secp256k1-zkp code")
|
||||||
|
ZKP_CFLAGS += -Wno-bitwise-instead-of-logical
|
||||||
|
endif
|
||||||
|
|
||||||
VALGRIND ?= 1
|
VALGRIND ?= 1
|
||||||
ifeq ($(VALGRIND),1)
|
ifeq ($(VALGRIND),1)
|
||||||
CFLAGS += -DVALGRIND
|
CFLAGS += -DVALGRIND
|
||||||
|
@ -47,6 +47,9 @@ To be determined:
|
|||||||
* `-fstack-protector-strong` or `-fstack-protector-all`
|
* `-fstack-protector-strong` or `-fstack-protector-all`
|
||||||
* `-m32` to closer evaluate the 32 bit behavior
|
* `-m32` to closer evaluate the 32 bit behavior
|
||||||
* this requires 32bit build support for gcc-multilib, libc and others
|
* this requires 32bit build support for gcc-multilib, libc and others
|
||||||
|
* adjust Makefile to `CFLAGS += -DSECP256K1_CONTEXT_SIZE=192`
|
||||||
|
* `-DSHA2_UNROLL_TRANSFORM` SHA2 optimization flags
|
||||||
|
* `-fsanitize-coverage=edge,trace-cmp,trace-div,indirect-calls,trace-gep,no-prune` to add program counter granularity
|
||||||
|
|
||||||
## Operation
|
## Operation
|
||||||
|
|
||||||
|
389
crypto/fuzzer/extract_fuzzer_dictionary.py
Executable file
389
crypto/fuzzer/extract_fuzzer_dictionary.py
Executable file
@ -0,0 +1,389 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
This experimental program is designed to extract a subset of interesting test
|
||||||
|
case snippets from the trezor-crypto test directory and output them as a
|
||||||
|
standard fuzzer dictionary file.
|
||||||
|
|
||||||
|
The program is built on quick-and-dirty regex matching that is known to be
|
||||||
|
incorrect for parsing code files, but is considered "good enough" for this
|
||||||
|
specific purpose.
|
||||||
|
Note that there are target-specific configurations and internal filter settings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import binascii
|
||||||
|
import glob
|
||||||
|
import re
|
||||||
|
|
||||||
|
# re2 is considered for future use
|
||||||
|
# it requires a system installation and the google-re2 python package
|
||||||
|
# import re2
|
||||||
|
|
||||||
|
|
||||||
|
# Expected target format for strings in code:
|
||||||
|
# Most strings are defined in the general form "example"
|
||||||
|
# There are a few test vectors in crypto/tests/wycheproof/javascript/EcUtil.js
|
||||||
|
# with 'example' style string definitions, these are ignored for now
|
||||||
|
|
||||||
|
TARGET_DIR = "../tests"
|
||||||
|
|
||||||
|
# intentionally excluded file types that currently do not provide enough value:
|
||||||
|
# *.js, *.md, *.sh, *.html and others from the wycheproof subdirectory
|
||||||
|
|
||||||
|
targeted_filetypes_multiline_classA = ("*.c", "*.h", "*.py")
|
||||||
|
# Java files have different multiline strings that are handled differently
|
||||||
|
targeted_filetypes_multiline_classB = ("*.java",)
|
||||||
|
targeted_filetypes_multiline = (
|
||||||
|
targeted_filetypes_multiline_classA + targeted_filetypes_multiline_classB
|
||||||
|
)
|
||||||
|
|
||||||
|
# files without multiline string content
|
||||||
|
# Note: consider switching to actual JSON parsing?
|
||||||
|
# Note: the wycheproof repository has a number of test cases for other
|
||||||
|
# cryptography such as DSA and RSA which are currently less interesting for the
|
||||||
|
# fuzzer dictionary and therefore excluded
|
||||||
|
targeted_filetypes_singleline = (
|
||||||
|
"aes*.json",
|
||||||
|
"ecdh*.json",
|
||||||
|
"ecdsa*.json",
|
||||||
|
"x25519*.json",
|
||||||
|
"chacha20*.json",
|
||||||
|
"kw*.json",
|
||||||
|
)
|
||||||
|
|
||||||
|
verbose = False
|
||||||
|
|
||||||
|
# patterns to extract
|
||||||
|
# singleline:
|
||||||
|
# "4a1e76f133afb"
|
||||||
|
# 0xAF8BBDFE8CDD5 and 0x0488b21e
|
||||||
|
# m/0'/2147483647'/1'/2147483646'/2' in test_check.c via m/[\d'/]+
|
||||||
|
#
|
||||||
|
# multiline:
|
||||||
|
# "fffc" \n "99"
|
||||||
|
# "dpubZ9169K" \n "bTYbcY"
|
||||||
|
# "\x65\xf9" \\n "\xa0\x6a"
|
||||||
|
# { 0x086d8bd5, 0x1018f82f, \n 0xc55ece} , see rg "0x([a-zA-Z0-9])+"
|
||||||
|
|
||||||
|
# patterns to ignore
|
||||||
|
# lines with print statements
|
||||||
|
# lines with exceptions
|
||||||
|
# comments and other metadata in the testvector JSON files
|
||||||
|
# filenames
|
||||||
|
# import statements and other package names
|
||||||
|
|
||||||
|
# patterns to investigate further
|
||||||
|
# public keys with the form BEGIN PUBLIC KEY
|
||||||
|
# TODO "abc" + "def" string concatenation on the same line without newline
|
||||||
|
# strings in comments
|
||||||
|
|
||||||
|
# TODO briefly describe the desired dictionary export file format and its quirks
|
||||||
|
|
||||||
|
# match everything in quotes that doesn't have an internal quote character and
|
||||||
|
# at least one internal character
|
||||||
|
regex_string_general_definition = r"\"[^\"]+\""
|
||||||
|
regex_string_general = re.compile(regex_string_general_definition)
|
||||||
|
# the capturing group ignores prefix and suffix outside of the quotes
|
||||||
|
# Note that this is prone to matching the last line of a C-style multiline string,
|
||||||
|
# which is addressed via extra state handling during the file processing
|
||||||
|
regex_oneline_string = re.compile(
|
||||||
|
r"(" + regex_string_general_definition + r")\s*[\,\)]+"
|
||||||
|
)
|
||||||
|
# ignore lines that have a "+" character preceding a string
|
||||||
|
regex_oneline_string_java_ignore1 = re.compile(r"^\s*\+\s*\"")
|
||||||
|
|
||||||
|
regex_hex_character_segment_inner_definition = "[0-9a-fA-F]+"
|
||||||
|
regex_hex_character_input_complete = re.compile(
|
||||||
|
'^"' + regex_hex_character_segment_inner_definition + '"$'
|
||||||
|
)
|
||||||
|
regex_hex_character_input_inner = re.compile(
|
||||||
|
regex_hex_character_segment_inner_definition
|
||||||
|
)
|
||||||
|
# most constants are preceded by a space, but some have a "(" "[" or "{" before them
|
||||||
|
regex_hex_constant_singleline = re.compile(r"(?<=\(|\[|\{| )0x[a-fA-F0-9]+")
|
||||||
|
|
||||||
|
regex_c_style_multiline = re.compile(r"(?:\".+\"\s*\n\s*)+(?:\".+\")", re.MULTILINE)
|
||||||
|
regex_c_intermediary_content = re.compile(r"\"\s*\n\s*\"", re.MULTILINE)
|
||||||
|
# TODO how to prevent matching in the middle of a multi-line string concatenation?
|
||||||
|
# negative lookbehind for "+" is not possible generically and
|
||||||
|
# (?<!\+ ) and similar patterns are too static
|
||||||
|
|
||||||
|
regex_java_style_multiline = re.compile(
|
||||||
|
r"(?:\".+\"\s*\n\s*\+\s*)+(?:\".+\")", re.MULTILINE
|
||||||
|
)
|
||||||
|
regex_java_intermediary_content = re.compile(r"\"\s*\n\s*\+\s*\"", re.MULTILINE)
|
||||||
|
|
||||||
|
regex_text_newline = re.compile(r"\\n")
|
||||||
|
|
||||||
|
# primitive regex that catches most filenames in the data set
|
||||||
|
regex_filename_heuristic = re.compile(r"\.[a-zA-Z]+")
|
||||||
|
|
||||||
|
counter_hex_content = 0
|
||||||
|
counter_wycheproof_hex_reconstruction = 0
|
||||||
|
|
||||||
|
# TODO add '"curve"' to capture algorithm names?
|
||||||
|
allowlist_keywords_json = (
|
||||||
|
'"uncompressed"',
|
||||||
|
'"wx"',
|
||||||
|
'"wy"',
|
||||||
|
'"msg"',
|
||||||
|
'"sig"',
|
||||||
|
'"key"',
|
||||||
|
'"iv"',
|
||||||
|
'"ct"',
|
||||||
|
'"aad"',
|
||||||
|
'"tag"',
|
||||||
|
'"public"',
|
||||||
|
'"private"',
|
||||||
|
'"shared"',
|
||||||
|
'"padding"',
|
||||||
|
'"x"',
|
||||||
|
'"d"',
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO the "keyPem" entry is only a workaround for an encoding issue
|
||||||
|
ignore_keywords_java = (
|
||||||
|
"println(",
|
||||||
|
"Exception(",
|
||||||
|
'"keyPem"',
|
||||||
|
)
|
||||||
|
ignore_keywords_c = ("printf(",)
|
||||||
|
|
||||||
|
|
||||||
|
def ignore_single_line_json(data):
|
||||||
|
"""return True if the input should be ignored"""
|
||||||
|
# ignore everything that is not matched by the allowlist
|
||||||
|
for keyword in allowlist_keywords_json:
|
||||||
|
if data.find(keyword) > -1:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def ignore_single_line_java(data):
|
||||||
|
"""return True if the input should be ignored"""
|
||||||
|
for keyword in ignore_keywords_java:
|
||||||
|
if data.find(keyword) > -1:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def ignore_single_line_c(data):
|
||||||
|
"""return True if the input should be ignored"""
|
||||||
|
for keyword in ignore_keywords_c:
|
||||||
|
if data.find(keyword) > -1:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def ignore_general(data):
|
||||||
|
"""return True if the input should be ignored"""
|
||||||
|
if regex_filename_heuristic.search(data):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def encode_strings_for_dictionary(data):
|
||||||
|
"""
|
||||||
|
Assumes that inputs are already in string quotes
|
||||||
|
|
||||||
|
Handles dictionary-specific encoding steps
|
||||||
|
"""
|
||||||
|
# libfuzzer does not like "\n" string patterns in dictionary files, replace
|
||||||
|
# it with an encoded newline
|
||||||
|
data = regex_text_newline.sub("\\\\x0a", data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def detect_and_convert_hex(data):
|
||||||
|
"""
|
||||||
|
Convert hex strings
|
||||||
|
|
||||||
|
Directly pass through non-hex content
|
||||||
|
"""
|
||||||
|
global counter_hex_content
|
||||||
|
global counter_wycheproof_hex_reconstruction
|
||||||
|
match_result1 = regex_hex_character_input_complete.search(data)
|
||||||
|
if match_result1:
|
||||||
|
|
||||||
|
match_result2 = regex_hex_character_input_inner.search(match_result1.string)
|
||||||
|
isolated_substring = match_result2.group(0)
|
||||||
|
if len(isolated_substring) % 2 == 1:
|
||||||
|
# Note: the test cases in the wycheproof testvector JSON files have
|
||||||
|
# a custom binary hex format to represent keys
|
||||||
|
# among other things, this results in hex strings with an uneven
|
||||||
|
# number of characters
|
||||||
|
# see tests/wycheproof/java/com/google/security/wycheproof/JsonUtil.java
|
||||||
|
# specifically the asBigInteger() function for more information
|
||||||
|
if isolated_substring[0] >= "0" and isolated_substring[0] <= "7":
|
||||||
|
isolated_substring = "0" + isolated_substring
|
||||||
|
else:
|
||||||
|
isolated_substring = "f" + isolated_substring
|
||||||
|
counter_wycheproof_hex_reconstruction += 1
|
||||||
|
|
||||||
|
converted_result = ""
|
||||||
|
try:
|
||||||
|
# test error-free conversion to binary
|
||||||
|
binascii.unhexlify(isolated_substring)
|
||||||
|
hex_with_c_style_formatting = ""
|
||||||
|
pos = 0
|
||||||
|
while pos < len(isolated_substring) - 1:
|
||||||
|
hex_with_c_style_formatting += "\\x" + isolated_substring[pos : pos + 2]
|
||||||
|
pos += 2
|
||||||
|
|
||||||
|
converted_result = '"%s"' % hex_with_c_style_formatting
|
||||||
|
# TODO binascii.Incomplete exception also relevant?
|
||||||
|
except binascii.Error:
|
||||||
|
# default to the original input
|
||||||
|
return data
|
||||||
|
counter_hex_content += 1
|
||||||
|
return converted_result
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def search_files_recursively(directory, filetype_glob):
|
||||||
|
"""returns glob search results"""
|
||||||
|
target_files = []
|
||||||
|
print_verbose("searching in %s" % directory)
|
||||||
|
for filetype in filetype_glob:
|
||||||
|
print_verbose("searching for %s" % filetype)
|
||||||
|
target_files.extend(glob.glob(f"{directory}/**/{filetype}", recursive=True))
|
||||||
|
return target_files
|
||||||
|
|
||||||
|
|
||||||
|
def print_verbose(text):
|
||||||
|
"""print wrapper"""
|
||||||
|
if verbose:
|
||||||
|
print(text)
|
||||||
|
|
||||||
|
|
||||||
|
def recursive_dictionary_extraction(directory):
|
||||||
|
"""handle the central extraction logic"""
|
||||||
|
# TODO split this function up into subfunctions
|
||||||
|
global counter_hex_content
|
||||||
|
# handle as a set structure to de-duplicate results automatically
|
||||||
|
candidate_lines = set()
|
||||||
|
|
||||||
|
target_files = search_files_recursively(directory, targeted_filetypes_singleline)
|
||||||
|
for filepath in target_files:
|
||||||
|
per_file_result_counter = 0
|
||||||
|
with open(filepath) as _file:
|
||||||
|
print_verbose("processing %s" % filepath)
|
||||||
|
for _, line in enumerate(_file.readlines()):
|
||||||
|
if ignore_single_line_json(line):
|
||||||
|
continue
|
||||||
|
results = regex_oneline_string.findall(line)
|
||||||
|
for result in results:
|
||||||
|
candidate_lines.add(result)
|
||||||
|
per_file_result_counter += 1
|
||||||
|
if per_file_result_counter > 0:
|
||||||
|
print_verbose("results: %d" % per_file_result_counter)
|
||||||
|
|
||||||
|
print_verbose("number of candidate entries: %d" % len(candidate_lines))
|
||||||
|
|
||||||
|
target_files = search_files_recursively(directory, targeted_filetypes_multiline)
|
||||||
|
for filepath in target_files:
|
||||||
|
per_file_result_counter = 0
|
||||||
|
with open(filepath) as _file:
|
||||||
|
last_line_was_multiline_string = False
|
||||||
|
print_verbose("processing %s for single-line strings" % filepath)
|
||||||
|
for _, line in enumerate(_file.readlines()):
|
||||||
|
if ignore_single_line_java(line):
|
||||||
|
last_line_was_multiline_string = False
|
||||||
|
continue
|
||||||
|
if ignore_single_line_c(line):
|
||||||
|
last_line_was_multiline_string = False
|
||||||
|
continue
|
||||||
|
if regex_oneline_string_java_ignore1.search(line):
|
||||||
|
last_line_was_multiline_string = True
|
||||||
|
if regex_oneline_string.search(line):
|
||||||
|
# the Java multiline string apparently ends on this line
|
||||||
|
last_line_was_multiline_string = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
result_general_string = regex_string_general.search(line)
|
||||||
|
if result_general_string:
|
||||||
|
# at least one general string is matched, see if it is
|
||||||
|
# a single-line string
|
||||||
|
results = regex_oneline_string.findall(line)
|
||||||
|
for result in results:
|
||||||
|
if not last_line_was_multiline_string:
|
||||||
|
candidate_lines.add(result)
|
||||||
|
per_file_result_counter += 1
|
||||||
|
last_line_was_multiline_string = False
|
||||||
|
if len(results) == 0:
|
||||||
|
last_line_was_multiline_string = True
|
||||||
|
else:
|
||||||
|
last_line_was_multiline_string = False
|
||||||
|
|
||||||
|
# TODO split this into a separate loop?
|
||||||
|
results = regex_hex_constant_singleline.findall(line)
|
||||||
|
for result in results:
|
||||||
|
# remove the "0x" prefix, add quotes
|
||||||
|
candidate_lines.add('"%s"' % result[2:])
|
||||||
|
per_file_result_counter += 1
|
||||||
|
|
||||||
|
if per_file_result_counter > 0:
|
||||||
|
print_verbose("results: %d" % per_file_result_counter)
|
||||||
|
|
||||||
|
target_files = search_files_recursively(
|
||||||
|
directory, targeted_filetypes_multiline_classA
|
||||||
|
)
|
||||||
|
|
||||||
|
for filepath in target_files:
|
||||||
|
with open(filepath) as _file:
|
||||||
|
print_verbose("processing %s for C-style multi-line strings" % filepath)
|
||||||
|
filecontent = _file.read()
|
||||||
|
multiline_results = regex_c_style_multiline.findall(filecontent)
|
||||||
|
if len(multiline_results) > 0:
|
||||||
|
print_verbose("results: %d" % len(multiline_results))
|
||||||
|
for result in multiline_results:
|
||||||
|
cleanup = regex_c_intermediary_content.sub("", result)
|
||||||
|
candidate_lines.add(cleanup)
|
||||||
|
|
||||||
|
target_files = search_files_recursively(
|
||||||
|
directory, targeted_filetypes_multiline_classB
|
||||||
|
)
|
||||||
|
|
||||||
|
for filepath in target_files:
|
||||||
|
with open(filepath) as _file:
|
||||||
|
print_verbose("processing %s for Java-style multi-line strings" % filepath)
|
||||||
|
filecontent = _file.read()
|
||||||
|
multiline_results = regex_java_style_multiline.findall(filecontent)
|
||||||
|
if len(multiline_results) > 0:
|
||||||
|
print_verbose("results: %d" % len(multiline_results))
|
||||||
|
for result in multiline_results:
|
||||||
|
cleanup = regex_java_intermediary_content.sub("", result)
|
||||||
|
candidate_lines.add(cleanup)
|
||||||
|
|
||||||
|
return candidate_lines
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("dictionary_output_file", help="output file", type=str)
|
||||||
|
parser.add_argument("--verbose", action="store_true", help="verbose stdout output")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
verbose = args.verbose
|
||||||
|
|
||||||
|
collected_candidate_lines = recursive_dictionary_extraction(TARGET_DIR)
|
||||||
|
sorted_candidate_lines = sorted(collected_candidate_lines)
|
||||||
|
result_lines = []
|
||||||
|
for candidate_line in sorted_candidate_lines:
|
||||||
|
if ignore_general(candidate_line):
|
||||||
|
continue
|
||||||
|
result_lines.append(
|
||||||
|
encode_strings_for_dictionary(detect_and_convert_hex(candidate_line))
|
||||||
|
)
|
||||||
|
|
||||||
|
print_verbose("counter_hex_content: %d" % counter_hex_content)
|
||||||
|
print_verbose(
|
||||||
|
"counter_wycheproof_hex_reconstruction: %d"
|
||||||
|
% counter_wycheproof_hex_reconstruction
|
||||||
|
)
|
||||||
|
print_verbose("overall deduplicated entries: %d" % len(sorted_candidate_lines))
|
||||||
|
|
||||||
|
with open(args.dictionary_output_file, "w") as _file:
|
||||||
|
for result_line in result_lines:
|
||||||
|
_file.write("%s\n" % result_line)
|
@ -2,19 +2,28 @@
|
|||||||
|
|
||||||
# usage: script.sh target-dictionary-filename
|
# usage: script.sh target-dictionary-filename
|
||||||
|
|
||||||
# this script searches for interesting strings in the source code and converts
|
# This script searches for interesting strings in the source code and converts
|
||||||
# them into a standard fuzzer dictionary file.
|
# them into a standard fuzzer dictionary file.
|
||||||
|
#
|
||||||
|
# Note that this script is phased out in favor of the more sophisticated
|
||||||
|
# extract_fuzzer_dictionary.py program
|
||||||
|
|
||||||
|
# TODO known issues: the end result has some duplicates in it
|
||||||
|
|
||||||
TARGET_DIR=../tests
|
TARGET_DIR=../tests
|
||||||
OUTPUT_FILE=${1:-fuzzer_crypto_tests_strings_dictionary1.txt}
|
OUTPUT_FILE=${1:-fuzzer_crypto_tests_strings_dictionary1.txt}
|
||||||
|
|
||||||
# empty file
|
multiline_string_search() {
|
||||||
|
# TODO the `find` regex behavior is Linux-specific
|
||||||
|
find $TARGET_DIR -type f -regextype posix-extended -regex '.*\.(c|h|py|json|java|js)' | xargs cat | perl -p0e 's/"\s*\n\s*\"//smg'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ensure empty file
|
||||||
echo -n "" > $OUTPUT_FILE
|
echo -n "" > $OUTPUT_FILE
|
||||||
|
|
||||||
# strip multiline strings and extract them
|
# strip multiline strings and extract them
|
||||||
# exclude some hex strings, but allow hex strings with mixed capitalization (Ethereum, rskip60)
|
# exclude some hex strings, but allow hex strings with mixed capitalization (Ethereum, rskip60)
|
||||||
find $TARGET_DIR -type f | xargs cat | perl -p0e 's/"\s*\n\s*\"//smg' | grep -P -o "\"[\w ]+\"" | grep -v -P "\"(([0-9a-f][0-9a-f])+|([0-9A-F][0-9A-F])+)\"" | sort | uniq | while read -r line ; do
|
multiline_string_search | grep -P -o "\"[\w ]+\"" | grep -v -P "\"(([0-9a-f][0-9a-f])+|([0-9A-F][0-9A-F])+)\"" | sort | uniq | while read -r line ; do
|
||||||
echo "$line" >> $OUTPUT_FILE
|
echo "$line" >> $OUTPUT_FILE
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -26,7 +35,7 @@ done
|
|||||||
# find each file, cat it, concatenate multiline strings, look for hex strings in quotes
|
# find each file, cat it, concatenate multiline strings, look for hex strings in quotes
|
||||||
# note that this returns multiple megabyte of result strings due to the large amount
|
# note that this returns multiple megabyte of result strings due to the large amount
|
||||||
# of test cases in the wycheproof project subfolder
|
# of test cases in the wycheproof project subfolder
|
||||||
find $TARGET_DIR -type f | xargs cat | perl -p0e 's/"\s*\n\s*\"//smg' | grep -P -o "\"([0-9a-fA-F][0-9a-fA-F])+\"" | grep -P -o "([0-9a-fA-F][0-9a-fA-F])+" | sort | uniq | while read -r line ; do
|
multiline_string_search | grep -P -o "\"([0-9a-fA-F][0-9a-fA-F])+\"" | grep -P -o "([0-9a-fA-F][0-9a-fA-F])+" | sort | uniq | while read -r line ; do
|
||||||
# turn ascii hex strings AA into \xaa for the fuzzer format and add quotes
|
# turn ascii hex strings AA into \xaa for the fuzzer format and add quotes
|
||||||
# extra backslash escape due to the bash nesting
|
# extra backslash escape due to the bash nesting
|
||||||
escaped_hex=`echo $line | sed -e 's/../\\\\x&/g'`
|
escaped_hex=`echo $line | sed -e 's/../\\\\x&/g'`
|
||||||
@ -35,7 +44,6 @@ done
|
|||||||
|
|
||||||
# search and reassemble BIP39 test seeds that span multiple lines
|
# search and reassemble BIP39 test seeds that span multiple lines
|
||||||
# find each file, cat it, concatenate multiline strings, look for BIP39 seed combinations with reasonable length
|
# find each file, cat it, concatenate multiline strings, look for BIP39 seed combinations with reasonable length
|
||||||
find $TARGET_DIR -type f | xargs cat | perl -p0e 's/"\s*\n\s*\"//smg' | grep -Po "(\w{3,10} ){11,23}(\w{3,10})" | sort | uniq | while read -r line ; do
|
multiline_string_search | grep -Po "(\w{3,10} ){11,23}(\w{3,10})" | sort | uniq | while read -r line ; do
|
||||||
echo "\"$line\"" >> $OUTPUT_FILE
|
echo "\"$line\"" >> $OUTPUT_FILE
|
||||||
done
|
done
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2020-2021 Christian Reitter
|
* Copyright (c) 2020-2022 Christian Reitter
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
* a copy of this software and associated documentation files (the "Software"),
|
* a copy of this software and associated documentation files (the "Software"),
|
||||||
@ -43,6 +43,7 @@
|
|||||||
#include "ed25519-donna/ed25519-donna.h"
|
#include "ed25519-donna/ed25519-donna.h"
|
||||||
#include "ed25519-donna/ed25519-keccak.h"
|
#include "ed25519-donna/ed25519-keccak.h"
|
||||||
#include "ed25519-donna/ed25519.h"
|
#include "ed25519-donna/ed25519.h"
|
||||||
|
#include "hasher.h"
|
||||||
#include "hmac_drbg.h"
|
#include "hmac_drbg.h"
|
||||||
#include "memzero.h"
|
#include "memzero.h"
|
||||||
#include "monero/monero.h"
|
#include "monero/monero.h"
|
||||||
@ -60,11 +61,9 @@
|
|||||||
#include "shamir.h"
|
#include "shamir.h"
|
||||||
#include "slip39.h"
|
#include "slip39.h"
|
||||||
#include "slip39_wordlist.h"
|
#include "slip39_wordlist.h"
|
||||||
#include "hasher.h"
|
#include "zkp_bip340.h"
|
||||||
#include "nist256p1.h"
|
#include "zkp_context.h"
|
||||||
#include "rand.h"
|
#include "zkp_ecdsa.h"
|
||||||
#include "secp256k1.h"
|
|
||||||
|
|
||||||
|
|
||||||
/* fuzzer input data handling */
|
/* fuzzer input data handling */
|
||||||
const uint8_t *fuzzer_ptr;
|
const uint8_t *fuzzer_ptr;
|
||||||
@ -126,7 +125,7 @@ int fuzz_bn_format(void) {
|
|||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// TODO fuzzer idea: allow prefix=NULL
|
// TODO idea: : allow prefix == NULL
|
||||||
|
|
||||||
uint8_t suffixlen = 0;
|
uint8_t suffixlen = 0;
|
||||||
if (fuzzer_length < 1) {
|
if (fuzzer_length < 1) {
|
||||||
@ -143,7 +142,7 @@ int fuzz_bn_format(void) {
|
|||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// TODO fuzzer idea: allow suffix=NULL
|
// TODO idea: allow suffix == NULL
|
||||||
uint32_t decimals = 0;
|
uint32_t decimals = 0;
|
||||||
int32_t exponent = 0;
|
int32_t exponent = 0;
|
||||||
bool trailing = false;
|
bool trailing = false;
|
||||||
@ -397,7 +396,7 @@ int fuzz_nem_get_address(void) {
|
|||||||
|
|
||||||
#if defined(__has_feature)
|
#if defined(__has_feature)
|
||||||
#if __has_feature(memory_sanitizer)
|
#if __has_feature(memory_sanitizer)
|
||||||
// TODO check `address` for memory info leakage
|
// TODO idea: check `address` for memory info leakage
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -504,20 +503,22 @@ int fuzz_shamir_interpolate(void) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fuzz_ecdsa_sign_digest(void) {
|
int fuzz_ecdsa_sign_digest_functions(void) {
|
||||||
|
// bug result reference: https://github.com/trezor/trezor-firmware/pull/1697
|
||||||
|
|
||||||
uint8_t curve_decider = 0;
|
uint8_t curve_decider = 0;
|
||||||
uint8_t sig[64] = {0};
|
|
||||||
uint8_t priv_key[32] = {0};
|
uint8_t priv_key[32] = {0};
|
||||||
uint8_t digest[32] = {0};
|
uint8_t digest[32] = {0};
|
||||||
|
|
||||||
if (fuzzer_length < 1 + sizeof(sig) + sizeof(priv_key) + sizeof(digest)) {
|
uint8_t sig1[64] = {0};
|
||||||
|
uint8_t sig2[64] = {0};
|
||||||
|
uint8_t pby1, pby2 = 0;
|
||||||
|
if (fuzzer_length < 1 + sizeof(priv_key) + sizeof(digest)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const ecdsa_curve *curve;
|
const ecdsa_curve *curve;
|
||||||
uint8_t pby = 0;
|
|
||||||
|
|
||||||
memcpy(&curve_decider, fuzzer_input(1), 1);
|
memcpy(&curve_decider, fuzzer_input(1), 1);
|
||||||
memcpy(&sig, fuzzer_input(sizeof(sig)), sizeof(sig));
|
|
||||||
memcpy(&priv_key, fuzzer_input(sizeof(priv_key)), sizeof(priv_key));
|
memcpy(&priv_key, fuzzer_input(sizeof(priv_key)), sizeof(priv_key));
|
||||||
memcpy(&digest, fuzzer_input(sizeof(digest)), sizeof(digest));
|
memcpy(&digest, fuzzer_input(sizeof(digest)), sizeof(digest));
|
||||||
|
|
||||||
@ -528,11 +529,29 @@ int fuzz_ecdsa_sign_digest(void) {
|
|||||||
curve = &nist256p1;
|
curve = &nist256p1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO optionally set a function for is_canonical() callback
|
int res = 0;
|
||||||
int res = ecdsa_sign_digest(curve, priv_key, digest, sig, &pby, NULL);
|
|
||||||
|
// TODO idea: optionally set a function for is_canonical() callback
|
||||||
|
int res1 = ecdsa_sign_digest(curve, priv_key, digest, sig1, &pby1, NULL);
|
||||||
|
|
||||||
|
// the zkp function variant is only defined for a specific curve
|
||||||
|
if (curve == &secp256k1) {
|
||||||
|
int res2 =
|
||||||
|
zkp_ecdsa_sign_digest(curve, priv_key, digest, sig2, &pby2, NULL);
|
||||||
|
if ((res1 == 0 && res2 != 0) || (res1 != 0 && res2 == 0)) {
|
||||||
|
// one variant succeeded where the other did not
|
||||||
|
crash();
|
||||||
|
}
|
||||||
|
if (res1 == 0 && res2 == 0) {
|
||||||
|
if ((pby1 != pby2) || memcmp(&sig1, &sig2, sizeof(sig1)) != 0) {
|
||||||
|
// result values are different
|
||||||
|
crash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// successful signing
|
// successful signing
|
||||||
if (res == 0) {
|
if (res1 == 0) {
|
||||||
uint8_t pub_key[33] = {0};
|
uint8_t pub_key[33] = {0};
|
||||||
res = ecdsa_get_public_key33(curve, priv_key, pub_key);
|
res = ecdsa_get_public_key33(curve, priv_key, pub_key);
|
||||||
if (res != 0) {
|
if (res != 0) {
|
||||||
@ -540,7 +559,7 @@ int fuzz_ecdsa_sign_digest(void) {
|
|||||||
crash();
|
crash();
|
||||||
}
|
}
|
||||||
|
|
||||||
res = ecdsa_verify_digest(curve, pub_key, sig, digest);
|
res = ecdsa_verify_digest(curve, pub_key, sig1, digest);
|
||||||
if (res != 0) {
|
if (res != 0) {
|
||||||
// verification did not succeed
|
// verification did not succeed
|
||||||
crash();
|
crash();
|
||||||
@ -549,7 +568,7 @@ int fuzz_ecdsa_sign_digest(void) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fuzz_ecdsa_verify_digest(void) {
|
int fuzz_ecdsa_verify_digest_functions(void) {
|
||||||
uint8_t curve_decider = 0;
|
uint8_t curve_decider = 0;
|
||||||
uint8_t hash[32] = {0};
|
uint8_t hash[32] = {0};
|
||||||
uint8_t sig[64] = {0};
|
uint8_t sig[64] = {0};
|
||||||
@ -572,23 +591,35 @@ int fuzz_ecdsa_verify_digest(void) {
|
|||||||
curve = &nist256p1;
|
curve = &nist256p1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int res = ecdsa_verify_digest(curve, (const uint8_t *)&pub_key,
|
int res1 = ecdsa_verify_digest(curve, (const uint8_t *)&pub_key,
|
||||||
(const uint8_t *)&sig, (const uint8_t *)&hash);
|
(const uint8_t *)&sig, (const uint8_t *)&hash);
|
||||||
|
if (res1 == 0) {
|
||||||
if (res == 0) {
|
|
||||||
// See if the fuzzer ever manages to get find a correct verification
|
// See if the fuzzer ever manages to get find a correct verification
|
||||||
// intentionally trigger a crash to make this case observable
|
// intentionally trigger a crash to make this case observable
|
||||||
// TODO this is not an actual problem, remove in the future
|
// TODO this is not an actual problem, remove in the future
|
||||||
crash();
|
crash();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the zkp_ecdsa* function only accepts the secp256k1 curve
|
||||||
|
if (curve == &secp256k1) {
|
||||||
|
int res2 =
|
||||||
|
zkp_ecdsa_verify_digest(curve, (const uint8_t *)&pub_key,
|
||||||
|
(const uint8_t *)&sig, (const uint8_t *)&hash);
|
||||||
|
|
||||||
|
// the error code behavior is different between both functions, compare only
|
||||||
|
// verification state
|
||||||
|
if ((res1 == 0 && res2 != 0) || (res1 != 0 && res2 == 0)) {
|
||||||
|
// results differ, this is a problem
|
||||||
|
crash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fuzz_word_index(void) {
|
int fuzz_word_index(void) {
|
||||||
#define MAX_WORD_LENGTH 12
|
#define MAX_WORD_LENGTH 12
|
||||||
|
|
||||||
// TODO exact match?
|
|
||||||
if (fuzzer_length < MAX_WORD_LENGTH) {
|
if (fuzzer_length < MAX_WORD_LENGTH) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -683,6 +714,27 @@ int fuzz_mnemonic_to_seed(void) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int fuzz_ethereum_address_checksum(void) {
|
||||||
|
uint8_t addr[20] = {0};
|
||||||
|
char address[41] = {0};
|
||||||
|
uint64_t chain_id = 0;
|
||||||
|
bool rskip60 = false;
|
||||||
|
|
||||||
|
if (fuzzer_length < sizeof(addr) + sizeof(address) + sizeof(chain_id) + 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(addr, fuzzer_input(sizeof(addr)), sizeof(addr));
|
||||||
|
memcpy(address, fuzzer_input(sizeof(address)), sizeof(address));
|
||||||
|
memcpy(&chain_id, fuzzer_input(sizeof(chain_id)), sizeof(chain_id));
|
||||||
|
// usually dependent on chain_id, but determined separately here
|
||||||
|
rskip60 = (*fuzzer_input(1)) & 0x1;
|
||||||
|
|
||||||
|
ethereum_address_checksum(addr, address, rskip60, chain_id);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int fuzz_aes(void) {
|
int fuzz_aes(void) {
|
||||||
if (fuzzer_length < 1 + 16 + 16 + 32) {
|
if (fuzzer_length < 1 + 16 + 16 + 32) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -776,7 +828,7 @@ int fuzz_b58gph_encode_decode(void) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO switch to malloc()'ed buffers for better out of bounds access
|
// TODO idea: switch to malloc()'ed buffers for better out of bounds access
|
||||||
// detection?
|
// detection?
|
||||||
|
|
||||||
uint8_t encode_in_buffer[BASE58_GPH_MAX_INPUT_LEN] = {0};
|
uint8_t encode_in_buffer[BASE58_GPH_MAX_INPUT_LEN] = {0};
|
||||||
@ -819,7 +871,7 @@ int fuzz_schnorr_verify_digest(void) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO optionally try nist256p1 ?
|
// TODO idea: optionally try nist256p1 ?
|
||||||
const ecdsa_curve *curve = &secp256k1;
|
const ecdsa_curve *curve = &secp256k1;
|
||||||
uint8_t digest[SHA256_DIGEST_LENGTH] = {0};
|
uint8_t digest[SHA256_DIGEST_LENGTH] = {0};
|
||||||
uint8_t pub_key[SCHNORR_VERIFY_PUBKEY_DATA_LENGTH] = {0};
|
uint8_t pub_key[SCHNORR_VERIFY_PUBKEY_DATA_LENGTH] = {0};
|
||||||
@ -887,9 +939,6 @@ int fuzz_schnorr_sign_digest(void) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO zkp_bip340_sign, see test_check.c
|
|
||||||
// TODO zkp_bip340_verify, see test_check.c
|
|
||||||
|
|
||||||
int fuzz_chacha_drbg(void) {
|
int fuzz_chacha_drbg(void) {
|
||||||
#define CHACHA_DRBG_ENTROPY_LENGTH 32
|
#define CHACHA_DRBG_ENTROPY_LENGTH 32
|
||||||
#define CHACHA_DRBG_RESEED_LENGTH 32
|
#define CHACHA_DRBG_RESEED_LENGTH 32
|
||||||
@ -907,7 +956,7 @@ int fuzz_chacha_drbg(void) {
|
|||||||
uint8_t result[CHACHA_DRBG_RESULT_LENGTH] = {0};
|
uint8_t result[CHACHA_DRBG_RESULT_LENGTH] = {0};
|
||||||
CHACHA_DRBG_CTX ctx;
|
CHACHA_DRBG_CTX ctx;
|
||||||
|
|
||||||
// TODO improvement idea: switch to variable input sizes
|
// TODO idea: switch to variable input sizes
|
||||||
memcpy(&entropy, fuzzer_input(CHACHA_DRBG_ENTROPY_LENGTH),
|
memcpy(&entropy, fuzzer_input(CHACHA_DRBG_ENTROPY_LENGTH),
|
||||||
CHACHA_DRBG_ENTROPY_LENGTH);
|
CHACHA_DRBG_ENTROPY_LENGTH);
|
||||||
memcpy(&reseed, fuzzer_input(CHACHA_DRBG_RESEED_LENGTH),
|
memcpy(&reseed, fuzzer_input(CHACHA_DRBG_RESEED_LENGTH),
|
||||||
@ -947,24 +996,215 @@ int fuzz_ed25519_sign_verify(void) {
|
|||||||
// verify message, we expect this to work
|
// verify message, we expect this to work
|
||||||
ret = ed25519_sign_open(message, sizeof(message), public_key, signature);
|
ret = ed25519_sign_open(message, sizeof(message), public_key, signature);
|
||||||
|
|
||||||
// TODO are there other error values?
|
if (ret != 0) {
|
||||||
if (ret == -1) {
|
// verification did not succeed
|
||||||
crash();
|
crash();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO more XMR functions
|
int fuzz_zkp_bip340_sign_digest(void) {
|
||||||
// extern void xmr_hash_to_ec(ge25519 *P, const void *data, size_t length);
|
// int res = 0;
|
||||||
|
uint8_t priv_key[32] = {0};
|
||||||
|
uint8_t aux_input[32] = {0};
|
||||||
|
uint8_t digest[32] = {0};
|
||||||
|
uint8_t pub_key[32] = {0};
|
||||||
|
uint8_t sig[64] = {0};
|
||||||
|
|
||||||
// this function directly calls
|
if (fuzzer_length <
|
||||||
// hasher_Raw(HASHER_SHA3K, data, length, hash)
|
sizeof(priv_key) + sizeof(aux_input) + sizeof(digest) + sizeof(sig)) {
|
||||||
// is this interesting at all?
|
return 0;
|
||||||
// extern void xmr_fast_hash(uint8_t *hash, const void *data, size_t length);
|
}
|
||||||
|
memcpy(priv_key, fuzzer_input(sizeof(priv_key)), sizeof(priv_key));
|
||||||
|
memcpy(aux_input, fuzzer_input(sizeof(aux_input)), sizeof(aux_input));
|
||||||
|
memcpy(digest, fuzzer_input(sizeof(digest)), sizeof(digest));
|
||||||
|
memcpy(sig, fuzzer_input(sizeof(sig)), sizeof(sig));
|
||||||
|
|
||||||
// TODO target idea: re-create openssl_check() from test_openssl.c
|
zkp_bip340_get_public_key(priv_key, pub_key);
|
||||||
// to do differential fuzzing against OpenSSL functions
|
zkp_bip340_sign_digest(priv_key, digest, sig, aux_input);
|
||||||
|
// TODO idea: test sign result?
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fuzz_zkp_bip340_verify_digest(void) {
|
||||||
|
int res = 0;
|
||||||
|
uint8_t pub_key[32] = {0};
|
||||||
|
uint8_t digest[32] = {0};
|
||||||
|
uint8_t sig[64] = {0};
|
||||||
|
|
||||||
|
if (fuzzer_length < sizeof(digest) + sizeof(pub_key) + sizeof(sig)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
memcpy(pub_key, fuzzer_input(sizeof(pub_key)), sizeof(pub_key));
|
||||||
|
memcpy(digest, fuzzer_input(sizeof(digest)), sizeof(digest));
|
||||||
|
memcpy(sig, fuzzer_input(sizeof(sig)), sizeof(sig));
|
||||||
|
|
||||||
|
res = zkp_bip340_verify_digest(pub_key, sig, digest);
|
||||||
|
|
||||||
|
// res == 0 is valid, but crash to make successful passes visible
|
||||||
|
if (res == 0) {
|
||||||
|
crash();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fuzz_zkp_bip340_tweak_keys(void) {
|
||||||
|
int res = 0;
|
||||||
|
uint8_t internal_priv[32] = {0};
|
||||||
|
uint8_t root_hash[32] = {0};
|
||||||
|
uint8_t internal_pub[32] = {0};
|
||||||
|
uint8_t result[32] = {0};
|
||||||
|
|
||||||
|
if (fuzzer_length <
|
||||||
|
sizeof(internal_priv) + sizeof(root_hash) + sizeof(internal_pub)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
memcpy(internal_priv, fuzzer_input(sizeof(internal_priv)),
|
||||||
|
sizeof(internal_priv));
|
||||||
|
memcpy(root_hash, fuzzer_input(sizeof(root_hash)), sizeof(root_hash));
|
||||||
|
memcpy(internal_pub, fuzzer_input(sizeof(internal_pub)),
|
||||||
|
sizeof(internal_pub));
|
||||||
|
|
||||||
|
res = zkp_bip340_tweak_private_key(internal_priv, root_hash, result);
|
||||||
|
res = zkp_bip340_tweak_public_key(internal_pub, root_hash, result);
|
||||||
|
(void)res;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fuzz_ecdsa_get_public_key_functions(void) {
|
||||||
|
uint8_t privkey[32] = {0};
|
||||||
|
uint8_t pubkey33_1[33] = {0};
|
||||||
|
uint8_t pubkey33_2[33] = {0};
|
||||||
|
uint8_t pubkey65_1[65] = {0};
|
||||||
|
uint8_t pubkey65_2[65] = {0};
|
||||||
|
|
||||||
|
// note: the zkp_ecdsa_* variants require this specific curve
|
||||||
|
const ecdsa_curve *curve = &secp256k1;
|
||||||
|
|
||||||
|
if (fuzzer_length < sizeof(privkey)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
memcpy(privkey, fuzzer_input(sizeof(privkey)), sizeof(privkey));
|
||||||
|
|
||||||
|
int res_33_1 = ecdsa_get_public_key33(curve, privkey, pubkey33_1);
|
||||||
|
int res_33_2 = zkp_ecdsa_get_public_key33(curve, privkey, pubkey33_2);
|
||||||
|
int res_65_1 = ecdsa_get_public_key65(curve, privkey, pubkey65_1);
|
||||||
|
int res_65_2 = zkp_ecdsa_get_public_key65(curve, privkey, pubkey65_2);
|
||||||
|
|
||||||
|
// the function pairs have different return error codes for the same input
|
||||||
|
// so only fail if the one succeeds where the other does not
|
||||||
|
if ((res_33_1 == 0 && res_33_2 != 0) || (res_33_1 != 0 && res_33_2 == 0)) {
|
||||||
|
// function result mismatch
|
||||||
|
crash();
|
||||||
|
}
|
||||||
|
if ((res_65_1 == 0 && res_65_2 != 0) || (res_65_1 != 0 && res_65_2 == 0)) {
|
||||||
|
// function result mismatch
|
||||||
|
crash();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res_33_1 == 0 && res_33_2 == 0 &&
|
||||||
|
memcmp(&pubkey33_1, &pubkey33_2, sizeof(pubkey33_1)) != 0) {
|
||||||
|
// function result data mismatch
|
||||||
|
crash();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res_65_1 == 0 && res_65_2 == 0 &&
|
||||||
|
memcmp(&pubkey65_1, &pubkey65_2, sizeof(pubkey65_1)) != 0) {
|
||||||
|
// function result data mismatch
|
||||||
|
crash();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fuzz_ecdsa_recover_pub_from_sig_functions(void) {
|
||||||
|
uint8_t digest[32] = {0};
|
||||||
|
uint8_t sig[64] = {0};
|
||||||
|
const ecdsa_curve *curve = &secp256k1;
|
||||||
|
uint8_t recid = 0;
|
||||||
|
uint8_t pubkey1[65] = {0};
|
||||||
|
uint8_t pubkey2[65] = {0};
|
||||||
|
|
||||||
|
if (fuzzer_length < sizeof(digest) + sizeof(sig) + sizeof(recid)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
memcpy(digest, fuzzer_input(sizeof(digest)), sizeof(digest));
|
||||||
|
memcpy(sig, fuzzer_input(sizeof(sig)), sizeof(sig));
|
||||||
|
memcpy(&recid, fuzzer_input(sizeof(recid)), sizeof(recid));
|
||||||
|
// conform to parameter requirements
|
||||||
|
recid = recid & 0x03;
|
||||||
|
|
||||||
|
int res1 = zkp_ecdsa_recover_pub_from_sig(curve, pubkey1, sig, digest, recid);
|
||||||
|
int res2 = ecdsa_recover_pub_from_sig(curve, pubkey2, sig, digest, recid);
|
||||||
|
|
||||||
|
uint8_t zero_pubkey[65] = {0};
|
||||||
|
zero_pubkey[0] = 0x04;
|
||||||
|
|
||||||
|
if ((res1 == 0 && res2 != 0) || (res1 != 0 && res2 == 0)) {
|
||||||
|
// result mismatch
|
||||||
|
// bug result reference: https://github.com/trezor/trezor-firmware/pull/2050
|
||||||
|
crash();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res1 == 0 && res2 == 0 &&
|
||||||
|
memcmp(&pubkey1, &pubkey2, sizeof(pubkey1)) != 0) {
|
||||||
|
// pubkey result mismatch
|
||||||
|
crash();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fuzz_ecdsa_sig_from_der(void) {
|
||||||
|
// bug result reference: https://github.com/trezor/trezor-firmware/pull/2058
|
||||||
|
uint8_t der[72] = {0};
|
||||||
|
uint8_t out[72] = {0};
|
||||||
|
|
||||||
|
if (fuzzer_length < sizeof(der)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
memcpy(der, fuzzer_input(sizeof(der)), sizeof(der));
|
||||||
|
// null-terminate
|
||||||
|
der[sizeof(der) - 1] = 0;
|
||||||
|
size_t der_len = strlen((const char *)der);
|
||||||
|
|
||||||
|
// TODO idea: use different fuzzer-controlled der_len such as 1 to 73
|
||||||
|
int ret = ecdsa_sig_from_der(der, der_len, out);
|
||||||
|
(void)ret;
|
||||||
|
// TODO idea: check if back conversion works
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fuzz_ecdsa_sig_to_der(void) {
|
||||||
|
uint8_t sig[64] = {0};
|
||||||
|
uint8_t der[72] = {0};
|
||||||
|
|
||||||
|
if (fuzzer_length < sizeof(sig)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
memcpy(sig, fuzzer_input(sizeof(sig)), sizeof(sig));
|
||||||
|
|
||||||
|
int ret = ecdsa_sig_to_der((const uint8_t *)&sig, der);
|
||||||
|
(void)ret;
|
||||||
|
// TODO idea: check if back conversion works
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void zkp_initialize_context_or_crash(void) {
|
||||||
|
// The current context usage has persistent side effects
|
||||||
|
// TODO switch to frequent re-initialization where necessary
|
||||||
|
if (!zkp_context_is_initialized()) {
|
||||||
|
if (zkp_context_init() != 0) {
|
||||||
|
crash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#define META_HEADER_SIZE 3
|
#define META_HEADER_SIZE 3
|
||||||
|
|
||||||
@ -977,12 +1217,13 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
|||||||
|
|
||||||
fuzzer_reset_state();
|
fuzzer_reset_state();
|
||||||
|
|
||||||
|
// this controls up to 256 different test cases
|
||||||
uint8_t target_decision = data[0];
|
uint8_t target_decision = data[0];
|
||||||
|
|
||||||
// TODO use once necessary
|
// data[1] is reserved for explicit sub decisions
|
||||||
// uint8_t subdecision = data[1];
|
// uint8_t target_sub_decision = data[1];
|
||||||
|
|
||||||
// note: data[2] is reserved for future use
|
// data[2] is reserved for future use
|
||||||
|
|
||||||
// assign the fuzzer payload data for the target functions
|
// assign the fuzzer payload data for the target functions
|
||||||
fuzzer_ptr = data + META_HEADER_SIZE;
|
fuzzer_ptr = data + META_HEADER_SIZE;
|
||||||
@ -1042,8 +1283,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
|||||||
break;
|
break;
|
||||||
case 14:
|
case 14:
|
||||||
#ifdef FUZZ_ALLOW_SLOW
|
#ifdef FUZZ_ALLOW_SLOW
|
||||||
|
zkp_initialize_context_or_crash();
|
||||||
// slow through expensive bignum operations
|
// slow through expensive bignum operations
|
||||||
fuzz_ecdsa_verify_digest();
|
fuzz_ecdsa_verify_digest_functions();
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case 15:
|
case 15:
|
||||||
@ -1074,8 +1316,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
|||||||
break;
|
break;
|
||||||
case 23:
|
case 23:
|
||||||
#ifdef FUZZ_ALLOW_SLOW
|
#ifdef FUZZ_ALLOW_SLOW
|
||||||
|
zkp_initialize_context_or_crash();
|
||||||
// slow through expensive bignum operations
|
// slow through expensive bignum operations
|
||||||
fuzz_ecdsa_sign_digest();
|
fuzz_ecdsa_sign_digest_functions();
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case 24:
|
case 24:
|
||||||
@ -1087,7 +1330,35 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
|||||||
case 26:
|
case 26:
|
||||||
fuzz_mnemonic_to_seed();
|
fuzz_mnemonic_to_seed();
|
||||||
break;
|
break;
|
||||||
|
case 30:
|
||||||
|
fuzz_ethereum_address_checksum();
|
||||||
|
break;
|
||||||
|
case 41:
|
||||||
|
zkp_initialize_context_or_crash();
|
||||||
|
fuzz_zkp_bip340_sign_digest();
|
||||||
|
break;
|
||||||
|
case 42:
|
||||||
|
zkp_initialize_context_or_crash();
|
||||||
|
fuzz_zkp_bip340_verify_digest();
|
||||||
|
break;
|
||||||
|
case 43:
|
||||||
|
zkp_initialize_context_or_crash();
|
||||||
|
fuzz_zkp_bip340_tweak_keys();
|
||||||
|
break;
|
||||||
|
case 50:
|
||||||
|
zkp_initialize_context_or_crash();
|
||||||
|
fuzz_ecdsa_get_public_key_functions();
|
||||||
|
break;
|
||||||
|
case 51:
|
||||||
|
zkp_initialize_context_or_crash();
|
||||||
|
fuzz_ecdsa_recover_pub_from_sig_functions();
|
||||||
|
break;
|
||||||
|
case 52:
|
||||||
|
fuzz_ecdsa_sig_from_der();
|
||||||
|
break;
|
||||||
|
case 53:
|
||||||
|
fuzz_ecdsa_sig_to_der();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// do nothing
|
// do nothing
|
||||||
break;
|
break;
|
||||||
|
Loading…
Reference in New Issue
Block a user