From ba8eb408ea1d33739832191c7c6cca8746c3ea85 Mon Sep 17 00:00:00 2001 From: Christian Reitter Date: Sun, 25 Jul 2021 23:23:31 +0200 Subject: [PATCH] crypto: new iteration of fuzz testing code, improved dictionary script, minor documentation changes --- crypto/fuzzer/README.md | 8 +- crypto/fuzzer/extract_fuzzer_dictionary.sh | 42 ++- crypto/fuzzer/fuzzer.c | 417 ++++++++++++++++----- 3 files changed, 351 insertions(+), 116 deletions(-) diff --git a/crypto/fuzzer/README.md b/crypto/fuzzer/README.md index 989283333..d465d30ef 100644 --- a/crypto/fuzzer/README.md +++ b/crypto/fuzzer/README.md @@ -21,7 +21,6 @@ Examples: * `SANFLAGS="-fsanitize=address,undefined"` * `SANFLAGS="-fsanitize=memory -fsanitize-memory-track-origins"` - ### Optimizations Override `OPTFLAGS` to test the library at different optimization levels or simplify the debugging of detected issues. @@ -31,6 +30,12 @@ Examples: * `OPTFLAGS="-O0 -ggdb3"` * `OPTFLAGS="-O3 -march=native"` +### Other Flags + +To be determined: +* `-DNDEBUG` +* `-DUSE_BIP39_CACHE=0 -DUSE_BIP32_CACHE=0` + ## Operation See the [libFuzzer documentation](https://llvm.org/docs/LibFuzzer.html#options) for valid options and usage. Detailed fuzzer usage and relevant considerations are out of scope of this document. @@ -62,7 +67,6 @@ cd fuzzer ``` The resulting file can be used as a fuzzer dictionary. - ## Evaluate Source Coverage 1. build the fuzzer binary with `CFLAGS="-fprofile-instr-generate -fcoverage-mapping"` diff --git a/crypto/fuzzer/extract_fuzzer_dictionary.sh b/crypto/fuzzer/extract_fuzzer_dictionary.sh index bc6c761b3..47a9ae047 100755 --- a/crypto/fuzzer/extract_fuzzer_dictionary.sh +++ b/crypto/fuzzer/extract_fuzzer_dictionary.sh @@ -5,27 +5,35 @@ # this script searches for interesting strings in the source code and converts # them into a standard fuzzer dictionary file. -TARGETFILE=${1:-fuzzer_crypto_tests_strings_dictionary1.txt} -# collect strings with normal words from the tests -grep -r -P -o -h "\"[\w ]+\"" ../tests | sort | uniq > $TARGETFILE +TARGET_DIR=../tests +OUTPUT_FILE=${1:-fuzzer_crypto_tests_strings_dictionary1.txt} -# collect BIP39 and SLIP39 words -grep -r -P -o -h "\"\w+\"" ../slip39_wordlist.h ../bip39_english.h | sort | uniq >> $TARGETFILE +# empty file +echo -n "" > $OUTPUT_FILE -# hex string to quoted escaped hex conversion -# TODO add an inverted output variant with swapped endian order? -grep -r -P -o -h "([0-9a-fA-F][0-9a-fA-F])+" ../tests | sort | uniq | \ -while read -r line ; do - # double escape since it is going to be used in bash +# strip multiline strings and extract them +# 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 + echo "$line" >> $OUTPUT_FILE +done + +# extract individual BIP39 and SLIP39 words +# TODO are those actually valuable as fuzzer dictionary input? +# grep -r -P -o -h "\"\w+\"" ../slip39_wordlist.h ../bip39_english.h | sort | uniq >> fuzzer_crypto_tests_strings_dictionary1.txt + +# extract and convert binary input data from the unit tests +# find each file, cat it, concatenate multiline strings, look for hex strings in quotes +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 + # turn ascii hex strings AA into \xaa for the fuzzer format and add quotes + # extra backslash escape due to the bash nesting escaped_hex=`echo $line | sed -e 's/../\\\\x&/g'` - echo "\"$escaped_hex\"" >> $TARGETFILE + echo "\"$escaped_hex\"" >> $OUTPUT_FILE done -# search and reassemble BIP39 seed mnemonics that span multiple lines from the tests -# valid words are 3 to 10 characters long and there are 12, 18 or 24 words in a valid mnemonic -grep -r -P -o -h "\"(\w{3,10} ?)+\",?" ../tests | grep -vP "[0-9A-Z]" | tr '"\n' ' ' | \ -sed 's/ / /g' | sed 's/ / /g'| grep -Po "(\w{3,10} ){11,23}(\w{3,10})" | sort | uniq | \ -while read -r line ; do - echo "\"$line\"" >> $TARGETFILE +# 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 $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 + echo "\"$line\"" >> $OUTPUT_FILE done + diff --git a/crypto/fuzzer/fuzzer.c b/crypto/fuzzer/fuzzer.c index 47f7659cc..7f13b7332 100644 --- a/crypto/fuzzer/fuzzer.c +++ b/crypto/fuzzer/fuzzer.c @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 Christian Reitter + * Copyright (c) 2020-2021 Christian Reitter * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the "Software"), @@ -20,25 +20,46 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include #include #include #include #include -// necessary for the target functions +// includes for potential target functions +// based on test_check.c +#include "address.h" #include "aes/aes.h" +#include "base32.h" +#include "base58.h" #include "bignum.h" +#include "bip32.h" +#include "bip39.h" +#include "blake256.h" +#include "blake2b.h" +#include "blake2s.h" +#include "chacha_drbg.h" +#include "curves.h" #include "ecdsa.h" -#include "hasher.h" -#include "nist256p1.h" -#include "rand.h" -#include "secp256k1.h" - #include "ed25519-donna/ed25519-donna.h" +#include "ed25519-donna/ed25519-keccak.h" #include "ed25519-donna/ed25519.h" +#include "hmac_drbg.h" +#include "memzero.h" +#include "monero/monero.h" #include "nem.h" +#include "nist256p1.h" +#include "pbkdf2.h" +#include "rand.h" +#include "rc4.h" +#include "rfc6979.h" +#include "schnorr.h" +#include "script.h" +#include "secp256k1.h" +#include "sha2.h" +#include "sha3.h" #include "shamir.h" +#include "slip39.h" +#include "slip39_wordlist.h" /* fuzzer input data handling */ const uint8_t *fuzzer_ptr; @@ -64,9 +85,6 @@ void fuzzer_reset_state(void) { /* individual fuzzer harness functions */ -extern size_t bn_format(const bignum256 *amnt, const char *prefix, - const char *suffix, unsigned int decimals, int exponent, - bool trailing, char *out, size_t outlen); int fuzz_bn_format(void) { bignum256 target_bignum; // we need some amount of data, bail if the input is too short @@ -132,10 +150,6 @@ int fuzz_bn_format(void) { return 0; } -extern const char *BASE32_ALPHABET_RFC4648; -extern uint8_t *base32_decode(const char *in, size_t inlen, uint8_t *out, - size_t outlen, const char *alphabet); - // arbitrarily chosen maximum size #define BASE32_DECODE_MAX_INPUT_LEN 512 @@ -161,9 +175,6 @@ int fuzz_base32_decode(void) { return 0; } -extern char *base32_encode(const uint8_t *in, size_t inlen, char *out, - size_t outlen, const char *alphabet); - // arbitrarily chosen maximum size #define BASE32_ENCODE_MAX_INPUT_LEN 512 @@ -186,9 +197,6 @@ int fuzz_base32_encode(void) { return 0; } -extern int base58_encode_check(const uint8_t *data, int datalen, - HasherType hasher_type, char *str, int strsize); - // internal limit is 128, try some extra bytes #define BASE58_ENCODE_MAX_INPUT_LEN 140 @@ -215,9 +223,6 @@ int fuzz_base58_encode_check(void) { return 0; } -extern int base58_decode_check(const char *str, HasherType hasher_type, - uint8_t *data, int datalen); - // internal limit is 128, try some extra bytes #define BASE58_DECODE_MAX_INPUT_LEN 140 @@ -247,10 +252,6 @@ int fuzz_base58_decode_check(void) { return 0; } -extern int xmr_base58_addr_decode_check(const char *addr, size_t sz, - uint64_t *tag, void *data, - size_t datalen); - // arbitrarily chosen maximum size #define XMR_BASE58_ADDR_DECODE_MAX_INPUT_LEN 512 @@ -273,9 +274,6 @@ int fuzz_xmr_base58_addr_decode_check(void) { return 0; } -extern int xmr_base58_addr_encode_check(uint64_t tag, const uint8_t *data, - size_t binsz, char *b58, size_t b58sz); - // arbitrarily chosen maximum size #define XMR_BASE58_ADDR_ENCODE_MAX_INPUT_LEN 512 @@ -305,10 +303,6 @@ int fuzz_xmr_base58_addr_encode_check(void) { return 0; } -extern int xmr_size_varint(uint64_t num); -extern int xmr_write_varint(uint8_t *buff, size_t buff_size, uint64_t num); -extern int xmr_read_varint(uint8_t *buff, size_t buff_size, uint64_t *val); - // arbitrarily chosen maximum size #define XMR_SERIALIZE_VARINT_MAX_INPUT_LEN 128 @@ -342,8 +336,6 @@ int fuzz_xmr_serialize_varint(void) { return 0; } -extern bool nem_validate_address(const char *address, uint8_t network); - // arbitrarily chosen maximum size #define NEM_VALIDATE_ADDRESS_MAX_INPUT_LEN 128 @@ -355,26 +347,22 @@ int fuzz_nem_validate_address(void) { char in_buffer[NEM_VALIDATE_ADDRESS_MAX_INPUT_LEN] = {0}; - // TODO potential BUG: is it clearly specified that the address has to be null - // terminated? - in_buffer[NEM_VALIDATE_ADDRESS_MAX_INPUT_LEN - 1] = 0; - uint8_t network = *fuzzer_ptr; fuzzer_input(1); - // mutate the buffer + // mutate the buffer with the remaining fuzzer input data memcpy(&in_buffer, fuzzer_ptr, fuzzer_length); size_t raw_inlen = fuzzer_length; fuzzer_input(raw_inlen); + // TODO potential bug: is it clearly specified that the address has to be null + // terminated? + in_buffer[NEM_VALIDATE_ADDRESS_MAX_INPUT_LEN - 1] = 0; nem_validate_address(in_buffer, network); return 0; } -extern bool nem_get_address(const ed25519_public_key public_key, - uint8_t version, char *address); - int fuzz_nem_get_address(void) { unsigned char ed25519_public_key[32] = {0}; uint32_t network = 0; @@ -394,10 +382,6 @@ int fuzz_nem_get_address(void) { return 0; } -extern void xmr_get_subaddress_secret_key(bignum256modm r, uint32_t major, - uint32_t minor, - const bignum256modm m); - int fuzz_xmr_get_subaddress_secret_key(void) { bignum256modm m = {0}; uint32_t major = 0; @@ -417,9 +401,6 @@ int fuzz_xmr_get_subaddress_secret_key(void) { return 0; } -extern void xmr_derive_private_key(bignum256modm s, const ge25519 *deriv, - uint32_t idx, const bignum256modm base); - int fuzz_xmr_derive_private_key(void) { bignum256modm base = {0}; ge25519 deriv = {0}; @@ -441,9 +422,6 @@ int fuzz_xmr_derive_private_key(void) { return 0; } -extern void xmr_derive_public_key(ge25519 *r, const ge25519 *deriv, - uint32_t idx, const ge25519 *base); - int fuzz_xmr_derive_public_key(void) { ge25519 base = {0}; ge25519 deriv = {0}; @@ -464,16 +442,11 @@ int fuzz_xmr_derive_public_key(void) { return 0; } -extern bool shamir_interpolate(uint8_t *result, uint8_t result_index, - const uint8_t *share_indices, - const uint8_t **share_values, - uint8_t share_count, size_t len); - #define SHAMIR_MAX_SHARE_COUNT 16 #define SHAMIR_MAX_DATA_LEN (SHAMIR_MAX_SHARE_COUNT * SHAMIR_MAX_LEN) int fuzz_shamir_interpolate(void) { if (fuzzer_length != (2 * sizeof(uint8_t) + SHAMIR_MAX_SHARE_COUNT + - SHAMIR_MAX_DATA_LEN + 2)) { + SHAMIR_MAX_DATA_LEN + sizeof(size_t))) { return 0; } @@ -495,24 +468,77 @@ int fuzz_shamir_interpolate(void) { memcpy(&share_values_content, fuzzer_input(SHAMIR_MAX_DATA_LEN), SHAMIR_MAX_DATA_LEN); memcpy(&share_count, fuzzer_input(sizeof(uint8_t)), sizeof(uint8_t)); + // note: this is platform specific via byte length of size_t + memcpy(&len, fuzzer_input(sizeof(size_t)), sizeof(size_t)); - // the target function checks for (len > SHAMIR_MAX_LEN), - // so we don't have to test the whole size_t length value - len = (fuzzer_ptr[0] << 8) + fuzzer_ptr[1]; - fuzzer_input(2); - - // mirror the checks in mod_trezorcrypto_shamir_interpolate() + // mirror a check that the real code does if (share_count < 1 || share_count > SHAMIR_MAX_SHARE_COUNT) { return 0; } + // (len > SHAMIR_MAX_LEN) is handled in the target function shamir_interpolate(result, result_index, share_indices, share_values, share_count, len); return 0; } -extern int ecdsa_verify_digest(const ecdsa_curve *curve, const uint8_t *pub_key, - const uint8_t *sig, const uint8_t *digest); +int fuzz_ecdsa_sign_digest(void) { + uint8_t curve_decider = 0; + uint8_t sig[64] = {0}; + uint8_t priv_key[32] = {0}; + uint8_t digest[32] = {0}; + + if (fuzzer_length < 1 + sizeof(sig) + sizeof(priv_key) + sizeof(digest)) { + return 0; + } + const ecdsa_curve *curve; + uint8_t pby = 0; + + 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(&digest, fuzzer_input(sizeof(digest)), sizeof(digest)); + + // pick one of the standard curves + if ((curve_decider & 0x1) == 1) { + curve = &secp256k1; + } else { + curve = &nist256p1; + } + + // TODO optionally set a function for is_canonical() + int res = ecdsa_sign_digest(curve, priv_key, digest, sig, &pby, NULL); + + // successful signing + if (res == 0) { + uint8_t pub_key[33] = {0}; + ecdsa_get_public_key33(curve, priv_key, pub_key); + res = ecdsa_verify_digest(curve, pub_key, sig, digest); + + if (res != 0) { + // verification did not succeed + + // case: all zero pubkey value + uint8_t pub_key_zero[33] = + "\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + + // case: all zero digest value + uint8_t digest_zero[32] = + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + + if (memcmp(&pub_key, &pub_key_zero, sizeof(pub_key_zero)) == 0 || + memcmp(&digest, &digest_zero, sizeof(digest_zero)) == 0) { + return 0; + } + + // handle as crash + exit(1); + } + } + return 0; +} int fuzz_ecdsa_verify_digest(void) { uint8_t curve_decider = 0; @@ -540,14 +566,16 @@ int fuzz_ecdsa_verify_digest(void) { int res = ecdsa_verify_digest(curve, (const uint8_t *)&pub_key, (const uint8_t *)&sig, (const uint8_t *)&hash); - // TODO check if the fuzzer ever manages to get the return value to 0 - (void)res; + if (res == 0) { + // See if the fuzzer ever manages to get find a correct verification + // intentionally trigger a crash to make this case observable + // TODO this is not an actual problem, remove in the future + exit(1); + } return 0; } -extern bool word_index(uint16_t *index, const char *word, uint8_t word_length); - int fuzz_word_index(void) { #define MAX_WORD_LENGTH 12 @@ -559,7 +587,6 @@ int fuzz_word_index(void) { char word[MAX_WORD_LENGTH + 1] = {0}; memcpy(&word, fuzzer_ptr, MAX_WORD_LENGTH); size_t word_length = strlen(word); - uint16_t index = 0; word_index(&index, (const char *)&word, word_length); @@ -567,8 +594,6 @@ int fuzz_word_index(void) { return 0; } -extern uint16_t slip39_word_completion_mask(uint16_t sequence); - int fuzz_slip39_word_completion_mask(void) { if (fuzzer_length != 2) { return 0; @@ -576,16 +601,13 @@ int fuzz_slip39_word_completion_mask(void) { uint16_t sequence = (fuzzer_ptr[0] << 8) + fuzzer_ptr[1]; fuzzer_input(2); - // TODO perform additional checks on the output? slip39_word_completion_mask(sequence); return 0; } -extern int mnemonic_to_bits(const char *mnemonic, uint8_t *mnemonic_bits); - int fuzz_mnemonic_to_bits(void) { - // slightly longer than MAX_MNEMONIC_LEN from config.h + // length chosen somewhat arbitrarily #define MAX_MNEMONIC_LENGTH 256 if (fuzzer_length < MAX_MNEMONIC_LENGTH) { @@ -596,8 +618,7 @@ int fuzz_mnemonic_to_bits(void) { memcpy(&mnemonic, fuzzer_ptr, MAX_MNEMONIC_LENGTH); uint8_t mnemonic_bits[32 + 1] = {0}; - int number_of_bits = mnemonic_to_bits((const char *)&mnemonic, mnemonic_bits); - assert(0 <= number_of_bits && number_of_bits <= 264); + mnemonic_to_bits((const char *)&mnemonic, mnemonic_bits); return 0; } @@ -674,37 +695,221 @@ int fuzz_aes(void) { return 0; } -extern int base58gph_encode_check(const uint8_t *data, int datalen, char *str, - int strsize); - -extern int base58gph_decode_check(const char *str, uint8_t *data, int datalen); - int fuzz_b58gph_encode_decode(void) { - // note: encode and decode have an internal limit of 128 -#define BASE58_GPH_MAX_INPUT_LEN 140 + // note: encode and decode functions have an internal limit of 128 +#define BASE58_GPH_MAX_INPUT_LEN 130 + + if (fuzzer_length < 1 + 1 + BASE58_GPH_MAX_INPUT_LEN) { + return 0; + } - if (fuzzer_length > BASE58_GPH_MAX_INPUT_LEN) { + // use a flexible output buffer target size + uint8_t chosen_outlen = 0; + memcpy(&chosen_outlen, fuzzer_input(1), 1); + if (chosen_outlen > BASE58_GPH_MAX_INPUT_LEN) { + return 0; + } + // use a flexible input buffer target size + uint8_t chosen_inlen = 0; + memcpy(&chosen_inlen, fuzzer_input(1), 1); + if (chosen_inlen > BASE58_GPH_MAX_INPUT_LEN) { return 0; } + // TODO switch to malloc()'ed buffers for better out of bounds access + // detection? + uint8_t encode_in_buffer[BASE58_GPH_MAX_INPUT_LEN] = {0}; // with null termination char decode_in_buffer[BASE58_GPH_MAX_INPUT_LEN + 1] = {0}; char out_buffer[BASE58_GPH_MAX_INPUT_LEN] = {0}; - size_t outlen = sizeof(out_buffer); - size_t raw_inlen = fuzzer_length; - memcpy(&encode_in_buffer, fuzzer_input(raw_inlen), raw_inlen); - memcpy(&decode_in_buffer, &encode_in_buffer, raw_inlen); + memcpy(&encode_in_buffer, fuzzer_input(chosen_inlen), chosen_inlen); + memcpy(&decode_in_buffer, &encode_in_buffer, chosen_inlen); + + int ret = 0; + ret = base58gph_encode_check(encode_in_buffer, chosen_inlen, out_buffer, + chosen_outlen); + + if (ret != 0) { + // successful encode, try decode + uint8_t dummy_buffer[BASE58_GPH_MAX_INPUT_LEN] = {0}; + ret = base58gph_decode_check(out_buffer, (uint8_t *)&dummy_buffer, + chosen_outlen); + if (ret == 0) { + // mark as exception + // TODO POTENTIAL BUG - followup + // exit(1); + } + } - base58gph_encode_check(encode_in_buffer, raw_inlen, out_buffer, outlen); - base58gph_decode_check(decode_in_buffer, (uint8_t *)&out_buffer, outlen); + // do a second operation with the same input, without relationship to the + // previously computed output + base58gph_decode_check(decode_in_buffer, (uint8_t *)&out_buffer, + chosen_outlen); + return 0; +} + +#define SCHNORR_VERIFY_PUBKEY_DATA_LENGTH 33 +#define SCHNORR_VERIFY_PRIVKEY_DATA_LENGTH 32 - // TODO do logical encode<>decode comparison checks? +int fuzz_schnorr_verify_digest(void) { + if (fuzzer_length < SHA256_DIGEST_LENGTH + SCHNORR_VERIFY_PUBKEY_DATA_LENGTH + + SCHNORR_SIG_LENGTH) { + return 0; + } + + // TODO optionally try nist256p1 ? + const ecdsa_curve *curve = &secp256k1; + uint8_t digest[SHA256_DIGEST_LENGTH] = {0}; + uint8_t pub_key[SCHNORR_VERIFY_PUBKEY_DATA_LENGTH] = {0}; + uint8_t signature[SCHNORR_SIG_LENGTH] = {0}; + + memcpy(&digest, fuzzer_input(SHA256_DIGEST_LENGTH), SHA256_DIGEST_LENGTH); + memcpy(&pub_key, fuzzer_input(SCHNORR_VERIFY_PUBKEY_DATA_LENGTH), + SCHNORR_VERIFY_PUBKEY_DATA_LENGTH); + memcpy(&signature, fuzzer_input(SCHNORR_SIG_LENGTH), SCHNORR_SIG_LENGTH); + + // TODO this limitation is a bug workaround + if (pub_key[0] != 0x04) { + int ret = schnorr_verify_digest(curve, pub_key, digest, signature); + if (ret == 0) { + // assuming that the fuzzer can't puzzle together validly signed inputs, + // exit with a forced crash if a successful verification is observed + exit(1); + } + } return 0; } +int fuzz_schnorr_sign_digest(void) { + if (fuzzer_length < + 1 + SHA256_DIGEST_LENGTH + SCHNORR_VERIFY_PRIVKEY_DATA_LENGTH) { + return 0; + } + + const ecdsa_curve *curve; + uint8_t digest[SHA256_DIGEST_LENGTH] = {0}; + uint8_t priv_key[SCHNORR_VERIFY_PRIVKEY_DATA_LENGTH] = {0}; + uint8_t signature[SCHNORR_SIG_LENGTH] = {0}; + int ret = 0; + + uint8_t curve_decider = 0; + memcpy(&curve_decider, fuzzer_input(1), 1); + + if ((curve_decider & 0x1) == 1) { + curve = &secp256k1; + } else { + curve = &nist256p1; + } + + memcpy(&digest, fuzzer_input(SHA256_DIGEST_LENGTH), SHA256_DIGEST_LENGTH); + memcpy(&priv_key, fuzzer_input(SCHNORR_VERIFY_PRIVKEY_DATA_LENGTH), + SCHNORR_VERIFY_PRIVKEY_DATA_LENGTH); + + ret = schnorr_sign_digest(curve, priv_key, digest, signature); + + if (ret == 0) { + // signing was successful, check if the verification works + + // compute matching pubkey + uint8_t pub_key[33] = {0}; + ecdsa_get_public_key33(curve, priv_key, pub_key); + + if (schnorr_verify_digest(curve, pub_key, digest, signature) != 0) { + // ignore known case + uint8_t pub_key_null[33] = + "\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + if (memcmp(&pub_key, &pub_key_null, 33) == 0) { + return 0; + } + + // something is wrong, mark as crash + exit(1); + } + } + return 0; +} + +int fuzz_chacha_drbg(void) { +#define CHACHA_DRBG_ENTROPY_LENGTH 32 +#define CHACHA_DRBG_RESEED_LENGTH 32 +#define CHACHA_DRBG_NONCE_LENGTH 16 +#define CHACHA_DRBG_RESULT_LENGTH 16 + + if (fuzzer_length < CHACHA_DRBG_ENTROPY_LENGTH + CHACHA_DRBG_RESEED_LENGTH + + CHACHA_DRBG_NONCE_LENGTH) { + return 0; + } + + uint8_t entropy[CHACHA_DRBG_ENTROPY_LENGTH] = {0}; + uint8_t reseed[CHACHA_DRBG_RESEED_LENGTH] = {0}; + uint8_t nonce_bytes[CHACHA_DRBG_NONCE_LENGTH] = {0}; + uint8_t result[CHACHA_DRBG_RESULT_LENGTH] = {0}; + CHACHA_DRBG_CTX ctx; + + // TODO improvement idea: switch to variable input sizes + memcpy(&entropy, fuzzer_input(CHACHA_DRBG_ENTROPY_LENGTH), + CHACHA_DRBG_ENTROPY_LENGTH); + memcpy(&reseed, fuzzer_input(CHACHA_DRBG_RESEED_LENGTH), + CHACHA_DRBG_RESEED_LENGTH); + memcpy(&nonce_bytes, fuzzer_input(CHACHA_DRBG_NONCE_LENGTH), + CHACHA_DRBG_NONCE_LENGTH); + + chacha_drbg_init(&ctx, entropy, sizeof(entropy), nonce_bytes, + sizeof(nonce_bytes)); + chacha_drbg_reseed(&ctx, reseed, sizeof(reseed), NULL, 0); + chacha_drbg_generate(&ctx, result, sizeof(result)); + + return 0; +} + +int fuzz_ed25519_sign_verify(void) { + ed25519_secret_key secret_key; + ed25519_signature signature; + ed25519_public_key public_key; + // length chosen arbitrarily + uint8_t message[32] = {0}; + int ret = 0; + + if (fuzzer_length < + sizeof(secret_key) + sizeof(signature) + sizeof(message)) { + return 0; + } + + memcpy(&secret_key, fuzzer_input(sizeof(secret_key)), sizeof(secret_key)); + memcpy(&signature, fuzzer_input(sizeof(signature)), sizeof(signature)); + memcpy(&message, fuzzer_input(sizeof(message)), sizeof(message)); + + ed25519_publickey(secret_key, public_key); + // sign message, this should always succeed + ed25519_sign(message, sizeof(message), secret_key, public_key, signature); + + // verify message, we expect this to work + ret = ed25519_sign_open(message, sizeof(message), public_key, signature); + + // TODO are there other error values? + if (ret == -1) { + // mark as exception + exit(1); + } + + return 0; +} + +// TODO more XMR functions +// extern void xmr_hash_to_ec(ge25519 *P, const void *data, size_t length); + +// this function directly calls +// hasher_Raw(HASHER_SHA3K, data, length, hash) +// is this interesting at all? +// extern void xmr_fast_hash(uint8_t *hash, const void *data, size_t length); + +// TODO target idea: re-create openssl_check() from test_openssl.c +// to do differential fuzzing against OpenSSL functions + #define META_HEADER_SIZE 3 // main fuzzer entry @@ -802,6 +1007,24 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { case 19: fuzz_b58gph_encode_decode(); break; + case 20: + fuzz_schnorr_verify_digest(); + break; + case 21: + fuzz_schnorr_sign_digest(); + break; + case 22: + fuzz_chacha_drbg(); + break; + case 23: +#ifdef FUZZ_ALLOW_SLOW + // slow through expensive bignum operations + fuzz_ecdsa_sign_digest(); +#endif + break; + case 24: + fuzz_ed25519_sign_verify(); + break; default: // do nothing