diff --git a/.circleci/config.yml b/.circleci/config.yml index 2562a10a..e0525411 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: build: docker: - - image: zondax/circleci:latest + - image: zondax/circleci@sha256:37f78ab294b35a055768c2305b3f13813e55fb9db4e65f72745ede61dd842c08 steps: - checkout - run: git submodule update --init --recursive @@ -13,7 +13,7 @@ jobs: build_ledger: docker: - - image: zondax/builder-bolos:latest + - image: zondax/builder-bolos@sha256:5af9542b68b92c12c5c6ae5bd862ff9dbcce063b38755fe9d8153175f6a53338 environment: - BOLOS_SDK=/home/zondax/project/deps/nanos-secure-sdk - BOLOS_ENV=/opt/bolos @@ -28,6 +28,26 @@ jobs: cd /home/zondax/project make + test_fuzz_crash_fixes: + docker: + - image: zondax/circleci@sha256:37f78ab294b35a055768c2305b3f13813e55fb9db4e65f72745ede61dd842c08 + steps: + - checkout + - run: git submodule update --init --recursive + - run: sudo apt update && sudo apt -y install clang-10 + - run: + name: Build + command: | + cmake -B build \ + -DCMAKE_C_COMPILER=clang-10 \ + -DCMAKE_CXX_COMPILER=clang++-10 \ + -DCMAKE_BUILD_TYPE=Debug \ + -DENABLE_FUZZING=1 \ + -DENABLE_SANITIZERS=1 \ + . + make -C build + - run: ./run-fuzz-crashes.py + build_ledger_ledgeracio: docker: - image: zondax/builder-bolos:latest @@ -48,12 +68,14 @@ jobs: test_zemu: machine: image: ubuntu-1604:201903-01 + resource_class: large working_directory: ~/repo environment: BASH_ENV: "/opt/circleci/.nvm/nvm.sh" steps: - checkout - run: git submodule update --init --recursive + - run: sudo apt-get update -y && sudo apt-get install -y libusb-1.0.0 libudev-dev - run: name: Build Ledger app command: | @@ -108,7 +130,7 @@ jobs: build_package: docker: - - image: zondax/builder-bolos:latest + - image: zondax/builder-bolos@sha256:5af9542b68b92c12c5c6ae5bd862ff9dbcce063b38755fe9d8153175f6a53338 environment: - BOLOS_SDK=/home/zondax/project/deps/nanos-secure-sdk - BOLOS_ENV=/opt/bolos diff --git a/.gitignore b/.gitignore index 7c6aaa4c..4d7652a6 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,6 @@ cmake-build-fuzz/ tests_zemu/yarn.lock /tests_zemu/snapshots-tmp/ +/build +/fuzz-*.log +/fuzz/corpora diff --git a/CMakeLists.txt b/CMakeLists.txt index 8823a94b..07cabae5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,15 +20,53 @@ enable_testing() cmake_policy(SET CMP0025 NEW) set(CMAKE_CXX_STANDARD 11) -include(cmake/conan/CMakeLists.txt) -add_subdirectory(cmake/gtest) +option(ENABLE_FUZZING "Build with fuzzing instrumentation and build fuzz targets" OFF) +option(ENABLE_COVERAGE "Build with source code coverage instrumentation" OFF) +option(ENABLE_SANITIZERS "Build with ASAN and UBSAN" OFF) -string(APPEND CMAKE_C_FLAGS " -fsanitize=address -fno-omit-frame-pointer -Wno-extern-c-compat") -string(APPEND CMAKE_CXX_FLAGS " -fsanitize=address -fno-omit-frame-pointer -Wno-extern-c-compat") -string(APPEND CMAKE_LINKER_FLAGS " -fsanitize=address -fno-omit-frame-pointer") +string(APPEND CMAKE_C_FLAGS " -fno-omit-frame-pointer -g") +string(APPEND CMAKE_CXX_FLAGS " -fno-omit-frame-pointer -g") +string(APPEND CMAKE_LINKER_FLAGS " -fno-omit-frame-pointer -g") add_definitions(-DAPP_STANDARD) +if(ENABLE_FUZZING) + add_definitions(-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1) + SET(ENABLE_SANITIZERS ON CACHE BOOL "Sanitizer automatically enabled" FORCE) + SET(CMAKE_BUILD_TYPE Debug) + + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + # require at least clang 3.2 + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0) + message(FATAL_ERROR "Clang version must be at least 10.0!") + endif() + else() + message(FATAL_ERROR + "You are using an unsupported compiler! Fuzzing only works with Clang 10.\n" + "1. Install clang-10 \n" + "2. Pass -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10") + endif() + + string(APPEND CMAKE_C_FLAGS " -fsanitize=fuzzer-no-link") + string(APPEND CMAKE_CXX_FLAGS " -fsanitize=fuzzer-no-link") + string(APPEND CMAKE_LINKER_FLAGS " -fsanitize=fuzzer-no-link") +endif() + +if(ENABLE_COVERAGE) + string(APPEND CMAKE_C_FLAGS " -fprofile-instr-generate -fcoverage-mapping") + string(APPEND CMAKE_CXX_FLAGS " -fprofile-instr-generate -fcoverage-mapping") + string(APPEND CMAKE_LINKER_FLAGS " -fprofile-instr-generate -fcoverage-mapping") +endif() + +if(ENABLE_SANITIZERS) + string(APPEND CMAKE_C_FLAGS " -fsanitize=address,undefined -fsanitize-recover=address,undefined") + string(APPEND CMAKE_CXX_FLAGS " -fsanitize=address,undefined -fsanitize-recover=address,undefined") + string(APPEND CMAKE_LINKER_FLAGS " -fsanitize=address,undefined -fsanitize-recover=address,undefined") +endif() + +include(cmake/conan/CMakeLists.txt) +add_subdirectory(cmake/gtest) + set (RETRIEVE_MAJOR_CMD "cat ${CMAKE_CURRENT_SOURCE_DIR}/app/Makefile.version | grep APPVERSION_M | cut -b 14- | tr -d '\n'" ) @@ -65,6 +103,7 @@ file(GLOB_RECURSE LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/hexutils.c ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/zxmacros.c ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/zbuffer.c + ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/zxformat.c #### ${CMAKE_CURRENT_SOURCE_DIR}/app/src/crypto.c ${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser.c @@ -79,8 +118,8 @@ target_include_directories(app_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/BLAKE2/ref ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/include ${CMAKE_CURRENT_SOURCE_DIR}/app/src - ${CMAKE_CURRENT_SOURCE_DIR}/app/src/common ${CMAKE_CURRENT_SOURCE_DIR}/app/src/lib + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/common ) ############################################################## @@ -107,3 +146,18 @@ target_link_libraries(unittests PRIVATE add_test(unittests ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittests) set_tests_properties(unittests PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests) + +############################################################## +############################################################## +# Fuzz Targets +if(ENABLE_FUZZING) + set(FUZZ_TARGETS + parser_parse + ) + + foreach(target ${FUZZ_TARGETS}) + add_executable(fuzz-${target} ${CMAKE_CURRENT_SOURCE_DIR}/fuzz/${target}.cpp) + target_link_libraries(fuzz-${target} PRIVATE app_lib) + target_link_options(fuzz-${target} PRIVATE "-fsanitize=fuzzer") + endforeach() +endif() diff --git a/app/Makefile.version b/app/Makefile.version index b447e404..d56760e8 100644 --- a/app/Makefile.version +++ b/app/Makefile.version @@ -1,3 +1,3 @@ APPVERSION_M=2 APPVERSION_N=2019 -APPVERSION_P=2 +APPVERSION_P=3 diff --git a/app/src/parser.c b/app/src/parser.c index 452ef113..5d349840 100644 --- a/app/src/parser.c +++ b/app/src/parser.c @@ -83,6 +83,18 @@ parser_error_t parser_validate_vecLookupSource(pd_VecLookupSource_t *targets) { #endif parser_error_t parser_validate(const parser_context_t *ctx) { + // Iterate through all items to check that all can be shown and are valid + uint8_t numItems = 0; + CHECK_PARSER_ERR(parser_getNumItems(ctx, &numItems)); + + char tmpKey[40]; + char tmpVal[40]; + + for (uint8_t idx = 0; idx < numItems; idx++) { + uint8_t pageCount = 0; + CHECK_PARSER_ERR(parser_getItem(ctx, idx, tmpKey, sizeof(tmpKey), tmpVal, sizeof(tmpVal), 0, &pageCount)) + } + #if defined(APP_RESTRICTED) if (hdPath[2] == HDPATH_2_STASH) { if (ctx->tx_obj->callIndex.moduleIdx == PD_CALL_STAKING) { diff --git a/app/src/parser_impl.c b/app/src/parser_impl.c index 3e112186..c03ae8dd 100644 --- a/app/src/parser_impl.c +++ b/app/src/parser_impl.c @@ -190,65 +190,31 @@ parser_error_t _toStringCompactInt(const compactInt_t *c, if (c->len <= 4) { uint64_t v; _getValue(c, &v); - - if (decimalPlaces == 0) { - if (uint64_to_str(bufferUI, sizeof(bufferUI), v) != NULL){ - return parser_unexpected_value; - } - } else { - if (fpuint64_to_str(bufferUI, sizeof(bufferUI), v, decimalPlaces) == 0) { - return parser_unexpected_value; - } - } - - // Add postfix - if (postfix > 32 && postfix < 127) { - const uint16_t p = strlen(bufferUI); - bufferUI[p] = postfix; + if (uint64_to_str(bufferUI, sizeof(bufferUI), v) != NULL) { + return parser_unexpected_value; } - - pageString(outValue, outValueLen, bufferUI, pageIdx, pageCount); } else { + // This is longer number uint8_t bcdOut[100]; const uint16_t bcdOutLen = sizeof(bcdOut); - bignumLittleEndian_to_bcd(bcdOut, bcdOutLen, c->ptr + 1, c->len - 1); if (!bignumLittleEndian_bcdprint(bufferUI, sizeof(bufferUI), bcdOut, bcdOutLen)) return parser_unexpected_buffer_end; + } - if (decimalPlaces > 0) { - uint16_t numChars = strlen(bufferUI); - -// 0123456789012 <-decimal places -// abcd < numChars = 4 -// abcd < shift -// 000000000abcd < fill -// 0.00000000abcd < add decimal point - - if (numChars < decimalPlaces) { - for (uint16_t j = 0; j < numChars; j++) { - bufferUI[decimalPlaces + 1 - j] = bufferUI[numChars - 1 - j]; - } - MEMSET(bufferUI, '0', decimalPlaces - numChars + 1); - numChars = strlen(bufferUI); - } - - // add decimal point - for (uint16_t j = 0; j < decimalPlaces + 1; j++) { - bufferUI[numChars + 1 - j] = bufferUI[numChars - j]; - } - bufferUI[numChars - decimalPlaces] = '.'; - } - - // Add postfix - if (postfix > 32 && postfix < 127) { - const uint16_t p = strlen(bufferUI); - bufferUI[p] = postfix; - } + // Format number + if (intstr_to_fpstr_inplace(bufferUI, sizeof(bufferUI), decimalPlaces) == 0){ + return parser_unexpected_value; + } - pageString(outValue, outValueLen, bufferUI, pageIdx, pageCount); + // Add postfix + if (postfix > 32 && postfix < 127) { + const uint16_t p = strlen(bufferUI); + bufferUI[p] = postfix; } + pageString(outValue, outValueLen, bufferUI, pageIdx, pageCount); + return parser_ok; } @@ -346,17 +312,17 @@ parser_error_t _checkVersions(parser_context_t *c) { uint8_t *p = (uint8_t *) (c->buffer + c->bufferLen - specOffsetFromBack); uint32_t specVersion = 0; - specVersion += p[0] << 0u; - specVersion += p[1] << 8u; - specVersion += p[2] << 16u; - specVersion += p[3] << 24u; + specVersion += (uint32_t) p[0] << 0u; + specVersion += (uint32_t) p[1] << 8u; + specVersion += (uint32_t) p[2] << 16u; + specVersion += (uint32_t) p[3] << 24u; p += 4; uint32_t transactionVersion = 0; - transactionVersion += p[0] << 0u; - transactionVersion += p[1] << 8u; - transactionVersion += p[2] << 16u; - transactionVersion += p[3] << 24u; + transactionVersion += (uint32_t) p[0] << 0u; + transactionVersion += (uint32_t) p[1] << 8u; + transactionVersion += (uint32_t) p[2] << 16u; + transactionVersion += (uint32_t) p[3] << 24u; if (specVersion < SUPPORTED_MINIMUM_SPEC_VERSION) { return parser_spec_not_supported; diff --git a/app/src/parser_impl.h b/app/src/parser_impl.h index 2e2bce89..e55a911d 100644 --- a/app/src/parser_impl.h +++ b/app/src/parser_impl.h @@ -42,7 +42,8 @@ extern "C" { if (v == NULL) { return parser_no_data; } \ CTX_CHECK_AVAIL(c, 1) // Checks that there is something available in the buffer -#define CLEAN_AND_CHECK() MEMZERO(outValue, outValueLen); \ +#define CLEAN_AND_CHECK() \ + MEMZERO(outValue, outValueLen); \ if (v == NULL) { *pageCount = 0; return parser_no_data; } #define GEN_DEF_READARRAY(SIZE) \ @@ -51,24 +52,21 @@ extern "C" { return parser_ok; #define GEN_DEF_TOSTRING_ARRAY(SIZE) \ - CLEAN_AND_CHECK();\ - if (v->_ptr == NULL) return parser_unexpected_buffer_end; \ - const uint16_t outLenNormalized = ((outValueLen - 1u) >> 1u);\ - const uint16_t pageOffset = pageIdx * outLenNormalized;\ - *pageCount = SIZE / outLenNormalized; \ - if (SIZE % outLenNormalized != 0) \ - (*pageCount)++; \ - uint16_t loopmax = outLenNormalized; \ - if (loopmax > SIZE - pageOffset) { \ - loopmax = SIZE - pageOffset; \ - };\ - for (uint16_t i = 0; i < loopmax; i++) {\ - const uint16_t offset = i << 1u;\ - snprintf(outValue + offset,\ - outValueLen - offset,\ - "%02x", *(v->_ptr + pageOffset + i));\ - }\ + CLEAN_AND_CHECK(); \ + if (v->_ptr == NULL || outValueLen == 0 ) return parser_unexpected_buffer_end; \ + const uint16_t outLenNormalized = (outValueLen - 1) / 2; \ + *pageCount = SIZE / outLenNormalized; \ + if (SIZE % outLenNormalized != 0) *pageCount+=1; \ + const uint16_t pageOffset = pageIdx * outLenNormalized; \ + uint16_t loopmax = outLenNormalized; \ + if (loopmax > SIZE - pageOffset) loopmax = SIZE - pageOffset; \ + for (uint16_t i = 0; i < loopmax; i++) { \ + const uint16_t offset = i << 1u; \ + const uint8_t *c = v->_ptr + pageOffset; \ + snprintf(outValue + offset, outValueLen - offset, "%02x", c[i]); \ + } \ return parser_ok; + #define GEN_DEC_READFIX_UNSIGNED(BITS) parser_error_t _readUInt ## BITS(parser_context_t *ctx, uint ## BITS ##_t *value) #define GEN_DEF_READFIX_UNSIGNED(BITS) parser_error_t _readUInt ## BITS(parser_context_t *ctx, uint ## BITS ##_t *value) \ { \ diff --git a/app/src/substrate_methods.h b/app/src/substrate_methods.h index cfa37523..b203402e 100644 --- a/app/src/substrate_methods.h +++ b/app/src/substrate_methods.h @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wextern-c-compat" #pragma once #ifdef __cplusplus @@ -1362,3 +1364,5 @@ typedef union { #ifdef __cplusplus } #endif + +#pragma clang diagnostic pop diff --git a/app/src/substrate_types.c b/app/src/substrate_types.c index 578efb38..557c91df 100644 --- a/app/src/substrate_types.c +++ b/app/src/substrate_types.c @@ -170,36 +170,36 @@ parser_error_t _readConviction(parser_context_t *c, pd_Conviction_t *v) { parser_error_t _readData(parser_context_t *c, pd_Data_t *v) { CHECK_INPUT(); + MEMZERO(v, sizeof(pd_Data_t)); CHECK_ERROR(_readUInt8(c, (uint8_t *) &v->type)) + v->_ptr = NULL; + v->_len = 0; + // based on: // https://github.com/paritytech/substrate/blob/effe489951d1edab9d34846b1eefdfaf9511dab9/frame/identity/src/lib.rs#L139 - - if (v->type > Data_e_NONE && v->type <= Data_e_RAW_VECU8) { - const uint8_t bufferSize = ((uint8_t) v->type - 1); - v->_ptr = c->buffer + c->offset; - v->_len = bufferSize; - CTX_CHECK_AND_ADVANCE(c, v->_len); - return parser_ok; - } - switch (v->type) { - case Data_e_NONE: + case Data_e_NONE: { v->_ptr = NULL; v->_len = 0; - break; - case Data_e_RAW_VECU8: - // This should have been handled before (1..33) - return parser_unexpected_value; + return parser_ok; + } case Data_e_BLAKETWO256U8_32: case Data_e_SHA256_U8_32: case Data_e_KECCAK256_U8_32: case Data_e_SHATHREE256_U8_32: - default: return parser_not_supported; + default: { + if (v->type > Data_e_NONE && v->type <= Data_e_RAW_VECU8) { + const uint8_t bufferSize = ((uint8_t) v->type - 1); + v->_ptr = c->buffer + c->offset; + v->_len = bufferSize; + CTX_CHECK_AND_ADVANCE(c, v->_len); + return parser_ok; + } + return parser_not_supported; + } } - - return parser_ok; } parser_error_t _readDefunctVoter(parser_context_t *c, pd_DefunctVoter_t *v) { @@ -382,7 +382,7 @@ parser_error_t _readTupleBalanceOfBalanceOfBlockNumber(parser_context_t *c, pd_T parser_error_t _readTupleDataData(parser_context_t *c, pd_TupleDataData_t *v) { CHECK_INPUT(); CHECK_ERROR(_readData(c, &v->data1)) - CHECK_ERROR(_readData(c, &v->data1)) + CHECK_ERROR(_readData(c, &v->data2)) return parser_ok; } @@ -751,7 +751,7 @@ parser_error_t _toStringAccountVoteStandard( pages[0] = 1; CHECK_ERROR(_toStringVote(&v->vote, outValue, outValueLen, 0, &pages[1])) CHECK_ERROR(_toStringBalanceOf(&v->balance, outValue, outValueLen, 0, &pages[2])); - + *pageCount = 0; for (uint8_t i = 0; i < (uint8_t) sizeof(pages); i++) { *pageCount += pages[i]; @@ -955,7 +955,6 @@ parser_error_t _toStringData( if (v->type > Data_e_NONE && v->type <= Data_e_RAW_VECU8) { const uint8_t bufferSize = ((uint8_t) v->type - 1); - // FIXME: page+print utf8 GEN_DEF_TOSTRING_ARRAY(bufferSize) } @@ -1123,7 +1122,7 @@ parser_error_t _toStringIdentityInfo( CHECK_ERROR(_toStringOptionu8_array_20(&v->pgp_fingerprint, outValue, outValueLen, 0, &pages[6])) CHECK_ERROR(_toStringData(&v->image, outValue, outValueLen, 0, &pages[7])) CHECK_ERROR(_toStringData(&v->twitter, outValue, outValueLen, 0, &pages[8])) - + *pageCount = 0; for (uint8_t i = 0; i < (uint8_t) sizeof(pages); i++) { *pageCount += pages[i]; @@ -1254,7 +1253,7 @@ parser_error_t _toStringKey( uint8_t pageIdx, uint8_t *pageCount) { CLEAN_AND_CHECK() - + return parser_print_not_supported; } @@ -1659,7 +1658,7 @@ parser_error_t _toStringVestingInfo( return parser_ok; } pageIdx -= pages[1]; - + ////// if (pageIdx < pages[2]) { CHECK_ERROR(_toStringBlockNumber(&v->starting_block, outValue, outValueLen, pageIdx, &pages[2])) diff --git a/conanfile.txt b/conanfile.txt index 501ad7d1..0d3d2a77 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,6 +1,6 @@ [requires] -jsoncpp/1.9.0@theirix/stable -fmt/6.0.0@bincrafters/stable +jsoncpp/1.9.3 +fmt/7.0.2 [generators] cmake diff --git a/deps/ledger-zxlib/dockerized_build.mk b/deps/ledger-zxlib/dockerized_build.mk index fc3b4312..128c9ac6 100644 --- a/deps/ledger-zxlib/dockerized_build.mk +++ b/deps/ledger-zxlib/dockerized_build.mk @@ -191,8 +191,8 @@ vue_install_js_link: @echo endif -.PHONY: zemu -vue: +.PHONY: vue +vue: vue_install_js_link cd $(EXAMPLE_VUE_DIR) && yarn install && yarn serve ########################## VUE Section ############################### @@ -247,3 +247,18 @@ rust_test: cpp_test: mkdir -p build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. && make cd build && GTEST_COLOR=1 ASAN_OPTIONS=detect_leaks=0 ctest -VV + +########################## FUZZING Section ############################### + +.PHONY: fuzz_build +fuzz_build: + cmake -B build -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 -DCMAKE_BUILD_TYPE=Debug -DENABLE_FUZZING=1 -DENABLE_SANITIZERS=1 . + make -C build + +.PHONY: fuzz +fuzz: fuzz_build + ./fuzz/run-fuzzers.py + +.PHONY: fuzz_crash +fuzz_crash: fuzz_build + ./fuzz/run-fuzz-crashes.py diff --git a/deps/ledger-zxlib/include/zxformat.h b/deps/ledger-zxlib/include/zxformat.h index d4b6c9fb..783eef98 100644 --- a/deps/ledger-zxlib/include/zxformat.h +++ b/deps/ledger-zxlib/include/zxformat.h @@ -19,6 +19,8 @@ extern "C" { #endif +#include "zxmacros.h" + #define NUM_TO_STR(TYPE) __Z_INLINE const char * TYPE##_to_str(char *data, int dataLen, TYPE##_t number) { \ if (dataLen < 2) return "Buffer too small"; \ MEMZERO(data, dataLen); \ @@ -155,6 +157,8 @@ __Z_INLINE int64_t str_to_int64(const char *start, const char *end, char *error) return value * sign; } +uint8_t intstr_to_fpstr_inplace(char *number, size_t number_max_size, uint8_t decimalPlaces); + __Z_INLINE uint8_t fpstr_to_str(char *out, uint16_t outLen, const char *number, uint8_t decimals) { MEMZERO(out, outLen); size_t digits = strlen(number); diff --git a/deps/ledger-zxlib/include/zxversion.h b/deps/ledger-zxlib/include/zxversion.h index e7c834a3..e5f303d2 100644 --- a/deps/ledger-zxlib/include/zxversion.h +++ b/deps/ledger-zxlib/include/zxversion.h @@ -16,5 +16,5 @@ #pragma once #define ZXLIB_MAJOR 4 -#define ZXLIB_MINOR 0 -#define ZXLIB_PATCH 1 +#define ZXLIB_MINOR 1 +#define ZXLIB_PATCH 0 diff --git a/deps/ledger-zxlib/src/zxformat.c b/deps/ledger-zxlib/src/zxformat.c new file mode 100644 index 00000000..d553afdc --- /dev/null +++ b/deps/ledger-zxlib/src/zxformat.c @@ -0,0 +1,83 @@ +/******************************************************************************* +* (c) 2020 Zondax GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#include "zxformat.h" + +uint8_t intstr_to_fpstr_inplace(char *number, size_t number_max_size, uint8_t decimalPlaces) { + uint16_t numChars = strnlen(number, number_max_size); + MEMZERO(number + numChars, number_max_size - numChars); + + if (number_max_size < 1) { + // No space to do anything + return 0; + } + + if (numChars == 0) { + // Empty number, make a zero + snprintf(number, number_max_size, "0"); + numChars = 1; + } + + // Check all are numbers + uint16_t firstDigit = numChars; + for (int i = 0; i < numChars; i++) { + if (number[i] < '0' || number[i] > '9') { + snprintf(number, number_max_size, "ERR"); + return 0; + } + if (number[i] != '0' && firstDigit > i) { + firstDigit = i; + } + } + + // Trim any incorrect leading zeros + if (firstDigit == numChars) { + snprintf(number, number_max_size, "0"); + numChars = 1; + } else { + // Trim leading zeros + MEMCPY(number, number + firstDigit, numChars - firstDigit); + MEMZERO(number + numChars - firstDigit, firstDigit); + } + + // If there are no decimal places return + if (decimalPlaces == 0) { + return numChars; + } + + // Now insert decimal point + +// 0123456789012 <-decimal places +// abcd < numChars = 4 +// abcd < shift +// 000000000abcd < fill +// 0.00000000abcd < add decimal point + + if (numChars < decimalPlaces + 1) { + // Move to end + const uint16_t padSize = decimalPlaces - numChars + 1; + MEMMOVE(number + padSize, number, numChars); + MEMSET(number, '0', padSize); + numChars = strlen(number); + } + + // add decimal point + const uint16_t pointPosition = numChars - decimalPlaces; + MEMMOVE(number + pointPosition + 1, number + pointPosition, decimalPlaces); // shift content + number[pointPosition] = '.'; + + numChars = strlen(number); + return numChars; +} diff --git a/deps/ledger-zxlib/tests/macros.cpp b/deps/ledger-zxlib/tests/macros.cpp index 3cf4a99d..b6b11fff 100644 --- a/deps/ledger-zxlib/tests/macros.cpp +++ b/deps/ledger-zxlib/tests/macros.cpp @@ -234,6 +234,89 @@ namespace { EXPECT_EQ(std::string(output), "0.01"); } + TEST(FORMAT, intstr_to_fpstr_inplace_trimming_leading) { + char number[100]; + printf("\n"); + + strcpy(number, "0"); + intstr_to_fpstr_inplace(number, sizeof(number), 0); + EXPECT_EQ(std::string(number), "0"); + + strcpy(number, "00"); + intstr_to_fpstr_inplace(number, sizeof(number), 0); + EXPECT_EQ(std::string(number), "0"); + + strcpy(number, "0000"); + intstr_to_fpstr_inplace(number, sizeof(number), 0); + EXPECT_EQ(std::string(number), "0"); + + strcpy(number, "00001"); + intstr_to_fpstr_inplace(number, sizeof(number), 0); + EXPECT_EQ(std::string(number), "1"); + + strcpy(number, "000011"); + intstr_to_fpstr_inplace(number, sizeof(number), 0); + EXPECT_EQ(std::string(number), "11"); + + strcpy(number, "10000"); + intstr_to_fpstr_inplace(number, sizeof(number), 0); + EXPECT_EQ(std::string(number), "10000"); + } + + TEST(FORMAT, intstr_to_fpstr_inplace_empty) { + char number[100]; + printf("\n"); + + strcpy(number, ""); + intstr_to_fpstr_inplace(number, sizeof(number), 0); + EXPECT_EQ(std::string(number), "0"); + + strcpy(number, ""); + intstr_to_fpstr_inplace(number, sizeof(number), 5); + EXPECT_EQ(std::string(number), "0.00000"); + + strcpy(number, ""); + intstr_to_fpstr_inplace(number, sizeof(number), 10); + EXPECT_EQ(std::string(number), "0.0000000000"); + } + + TEST(FORMAT, intstr_to_fpstr_inplace) { + char number[100]; + printf("\n"); + + strcpy(number, "1"); + intstr_to_fpstr_inplace(number, sizeof(number), 0); + EXPECT_EQ(std::string(number), "1"); + + strcpy(number, "123"); + intstr_to_fpstr_inplace(number, sizeof(number), 0); + EXPECT_EQ(std::string(number), "123"); + + strcpy(number, "0"); + intstr_to_fpstr_inplace(number, sizeof(number), 5); + EXPECT_EQ(std::string(number), "0.00000"); + + strcpy(number, "123"); + intstr_to_fpstr_inplace(number, sizeof(number), 5); + EXPECT_EQ(std::string(number), "0.00123"); + + strcpy(number, "1234"); + intstr_to_fpstr_inplace(number, sizeof(number), 5); + EXPECT_EQ(std::string(number), "0.01234"); + + strcpy(number, "12345"); + intstr_to_fpstr_inplace(number, sizeof(number), 5); + EXPECT_EQ(std::string(number), "0.12345"); + + strcpy(number, "123456"); + intstr_to_fpstr_inplace(number, sizeof(number), 5); + EXPECT_EQ(std::string(number), "1.23456"); + + strcpy(number, "1234567"); + intstr_to_fpstr_inplace(number, sizeof(number), 5); + EXPECT_EQ(std::string(number), "12.34567"); + } + TEST(INT64_TO_STR, Zero) { char temp[10]; const char *error = int64_to_str(temp, sizeof(temp), int64_t(0)); diff --git a/fuzz/corpora/.gitkeep b/fuzz/corpora/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/fuzz/parser_parse.cpp b/fuzz/parser_parse.cpp new file mode 100644 index 00000000..a8755d44 --- /dev/null +++ b/fuzz/parser_parse.cpp @@ -0,0 +1,70 @@ +#include +#include +#include + +#include "parser.h" + + +#ifdef NDEBUG +#error "This fuzz target won't work correctly with NDEBUG defined, which will cause asserts to be eliminated" +#endif + + +using std::size_t; + +static char PARSER_KEY[16384]; +static char PARSER_VALUE[16384]; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + parser_tx_t txObj; + parser_context_t ctx; + parser_error_t rc; + + rc = parser_parse(&ctx, data, size, &txObj); + if (rc != parser_ok) { + return 0; + } + + rc = parser_validate(&ctx); + if (rc != parser_ok) { + return 0; + } + + uint8_t num_items; + rc = parser_getNumItems(&ctx, &num_items); + if (rc != parser_ok) { + fprintf(stderr, + "error in parser_getNumItems: %s\n", + parser_getErrorDescription(rc)); + assert(false); + } + +// fprintf(stderr, "----------------------------------------------\n"); + + for (uint8_t i = 0; i < num_items; i += 1) { + uint8_t page_idx = 0; + uint8_t page_count = 1; + while (page_idx < page_count) { + rc = parser_getItem(&ctx, i, + PARSER_KEY, sizeof(PARSER_KEY), + PARSER_VALUE, sizeof(PARSER_VALUE), + page_idx, &page_count); + +// fprintf(stderr, "%s = %s\n", PARSER_KEY, PARSER_VALUE); + + if (rc != parser_ok) { + fprintf(stderr, + "error getting item %u at page index %u: %s\n", + (unsigned)i, + (unsigned)page_idx, + parser_getErrorDescription(rc)); + assert(false); + } + + page_idx += 1; + } + } + + return 0; +} diff --git a/fuzz/parser_vectupledatadata.cpp b/fuzz/parser_vectupledatadata.cpp new file mode 100644 index 00000000..9227e57a --- /dev/null +++ b/fuzz/parser_vectupledatadata.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include "substrate_types.h" +#include "substrate_functions.h" + +#ifdef NDEBUG +#error "This fuzz target won't work correctly with NDEBUG defined, which will cause asserts to be eliminated" +#endif + + +using std::size_t; + +static char PARSER_KEY[16384]; +static char PARSER_VALUE[16384]; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + pd_VecTupleDataData_t obj; + parser_context_t ctx; + parser_error_t rc; + + ctx.buffer = data; + ctx.bufferLen = size; + ctx.offset = 0; + + rc = _readVecTupleDataData(&ctx, &obj); + if (rc != parser_ok) { + return 0; + } + + uint8_t page_idx = 0; + uint8_t page_count = 1; + while (page_idx < page_count) { + rc = _toStringVecTupleDataData(&obj, + PARSER_VALUE, sizeof(PARSER_VALUE), + page_idx, &page_count); + if (rc != parser_ok) { + return 0; + } + + fprintf(stderr, "value %d / %d = %s\n", page_idx, page_count, PARSER_VALUE); + page_idx += 1; + } + + return 0; +} diff --git a/fuzz/run-fuzz-crashes.py b/fuzz/run-fuzz-crashes.py new file mode 100755 index 00000000..d6d32be4 --- /dev/null +++ b/fuzz/run-fuzz-crashes.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +import os +import random +import shlex +import subprocess + +MAX_SECONDS_PER_RUN = 600 +MUTATE_DEPTH = random.randint(1, 20) + +# (fuzzer name, max length, max time scale factor) +CONFIGS = [ + ('parser_parse', 17000, 4), +] + +for config in CONFIGS: + fuzzer, max_len, scale_factor = config + max_time = MAX_SECONDS_PER_RUN * scale_factor + print(f'######## {fuzzer} ########') + + artifact_dir = os.path.join('fuzz', 'corpora', f'{fuzzer}-artifacts') + corpus_dir = os.path.join('fuzz', 'corpora', f'{fuzzer}') + fuzz_path = os.path.join(f'build/bin/fuzz-{fuzzer}') + + os.makedirs(artifact_dir, exist_ok=True) + os.makedirs(corpus_dir, exist_ok=True) + + env = os.environ.copy() + env['ASAN_OPTIONS'] = 'halt_on_error=1:print_stacktrace=1' + env['UBSAN_OPTIONS'] = 'halt_on_error=1:print_stacktrace=1' + + crash_files = os.listdir(artifact_dir) + for c in crash_files: + c_full_path = os.path.join(artifact_dir, c) + cmd = [fuzz_path, f'{c_full_path}'] + print(' '.join(shlex.quote(c) for c in cmd)) + error_code = subprocess.call(cmd, env=env) + if error_code != 0: + exit(error_code) + + diff --git a/fuzz/run-fuzzers.py b/fuzz/run-fuzzers.py new file mode 100755 index 00000000..0b121329 --- /dev/null +++ b/fuzz/run-fuzzers.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +import os +import random +import shlex +import subprocess + +MAX_SECONDS_PER_RUN = 600 +MUTATE_DEPTH = random.randint(1, 20) + +# (fuzzer name, max length, max time scale factor) +CONFIGS = [ + ('parser_parse', 17000, 4), +] + +for config in CONFIGS: + fuzzer, max_len, scale_factor = config + max_time = MAX_SECONDS_PER_RUN * scale_factor + print(f'######## {fuzzer} ########') + + artifact_dir = os.path.join('fuzz', 'corpora', f'{fuzzer}-artifacts') + corpus_dir = os.path.join('fuzz', 'corpora', f'{fuzzer}') + fuzz_path = os.path.join(f'build/bin/fuzz-{fuzzer}') + + os.makedirs(artifact_dir, exist_ok=True) + os.makedirs(corpus_dir, exist_ok=True) + + env = os.environ.copy() + env['ASAN_OPTIONS'] = 'halt_on_error=1:print_stacktrace=1' + env['UBSAN_OPTIONS'] = 'halt_on_error=1:print_stacktrace=1' + + cmd = [fuzz_path, f'-max_total_time={max_time}', + f'-jobs=16' + f'-max_len={max_len}', + f'-mutate_depth={MUTATE_DEPTH}', + f'-artifact_prefix={artifact_dir}/', + corpus_dir] + print(' '.join(shlex.quote(c) for c in cmd)) + subprocess.call(cmd, env=env)