Skip to content

Commit 7c66d2f

Browse files
authored
Merge pull request #262 from Zondax/dev
Fix secp256k1 for long transactions (#261)
2 parents a6f701f + b9b033b commit 7c66d2f

136 files changed

Lines changed: 419 additions & 153 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,15 @@ fuzz-*.log
7878
/scan-build
7979

8080
.DS_Store
81+
82+
# Additional fuzzing artifacts
83+
fuzz/corpora/
84+
fuzz/build/
85+
fuzz/coverage/
86+
fuzz/logs/
87+
fuzz-*.log
88+
89+
# Python bytecode
90+
__pycache__/
91+
*.pyc
92+
*.pyo

CMakeLists.txt

Lines changed: 23 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ endif()
3030
########################################################
3131

3232
project(ledger-polkadot VERSION 0.0.0)
33+
34+
# Include common fuzzing configuration
35+
include(deps/ledger-zxlib/fuzzing/FuzzingCommon.cmake)
36+
3337
set(CMAKE_CXX_STANDARD 17)
3438
cmake_policy(SET CMP0025 NEW)
3539
cmake_policy(SET CMP0144 NEW)
@@ -39,10 +43,6 @@ set(HUNTER_TLS_VERIFY OFF)
3943

4044
enable_testing()
4145

42-
option(ENABLE_FUZZING "Enable fuzzing instrumentation and build fuzz targets" OFF)
43-
option(ENABLE_COVERAGE "Enable source code coverage instrumentation" OFF)
44-
option(ENABLE_SANITIZERS "Enable ASAN and UBSAN" OFF)
45-
4646
string(APPEND CMAKE_C_FLAGS " -fno-omit-frame-pointer -g")
4747
string(APPEND CMAKE_CXX_FLAGS " -fno-omit-frame-pointer -g")
4848
string(APPEND CMAKE_LINKER_FLAGS " -fno-omit-frame-pointer -g")
@@ -64,46 +64,7 @@ FetchContent_MakeAvailable(nlohmann_json)
6464
hunter_add_package(GTest)
6565
find_package(GTest CONFIG REQUIRED)
6666

67-
if(ENABLE_FUZZING)
68-
add_definitions(-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1)
69-
SET(ENABLE_SANITIZERS ON CACHE BOOL "Sanitizer automatically enabled" FORCE)
70-
SET(CMAKE_BUILD_TYPE Debug)
71-
72-
if(DEFINED ENV{FUZZ_LOGGING})
73-
add_definitions(-DFUZZING_LOGGING)
74-
message(FATAL_ERROR "Fuzz logging enabled")
75-
endif()
76-
77-
set(CMAKE_CXX_CLANG_TIDY clang-tidy -checks=-*,bugprone-*,cert-*,clang-analyzer-*,-cert-err58-cpp,misc-*)
78-
79-
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
80-
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0)
81-
message(FATAL_ERROR "Clang version must be at least 10.0!")
82-
endif()
83-
else()
84-
message(FATAL_ERROR
85-
"Unsupported compiler! Fuzzing requires Clang 10.\n"
86-
"1. Install clang-10 \n"
87-
"2. Use -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10")
88-
endif()
89-
90-
string(APPEND CMAKE_C_FLAGS " -fsanitize=fuzzer-no-link")
91-
string(APPEND CMAKE_CXX_FLAGS " -fsanitize=fuzzer-no-link")
92-
string(APPEND CMAKE_LINKER_FLAGS " -fsanitize=fuzzer-no-link")
93-
endif()
94-
95-
if(ENABLE_COVERAGE)
96-
string(APPEND CMAKE_C_FLAGS " -fprofile-instr-generate -fcoverage-mapping")
97-
string(APPEND CMAKE_CXX_FLAGS " -fprofile-instr-generate -fcoverage-mapping")
98-
string(APPEND CMAKE_LINKER_FLAGS " -fprofile-instr-generate -fcoverage-mapping")
99-
endif()
100-
101-
if(ENABLE_SANITIZERS)
102-
string(APPEND CMAKE_C_FLAGS " -fsanitize=address,undefined -fsanitize-recover=address,undefined")
103-
string(APPEND CMAKE_CXX_FLAGS " -fsanitize=address,undefined -fsanitize-recover=address,undefined")
104-
string(APPEND CMAKE_LINKER_FLAGS " -fsanitize=address,undefined -fsanitize-recover=address,undefined")
105-
endif()
106-
67+
# Fuzzing configuration is now handled by FuzzingCommon.cmake
10768
set(RETRIEVE_MAJOR_CMD
10869
"cat ${CMAKE_CURRENT_SOURCE_DIR}/app/Makefile.version | grep APPVERSION_M | cut -b 14- | tr -d '\n'"
10970
)
@@ -171,25 +132,24 @@ target_include_directories(app_lib PUBLIC
171132
##############################################################
172133
##############################################################
173134
# Fuzz Targets
174-
if(ENABLE_FUZZING)
175-
set(FUZZ_TARGETS
176-
parser_parse
177-
metadata_parser_array
178-
metadata_parser_composite
179-
metadata_parser_primitives
180-
metadata_parser_sequence
181-
metadata_parser_variant
182-
)
183-
184-
foreach(target ${FUZZ_TARGETS})
185-
add_executable(fuzz-${target} ${CMAKE_CURRENT_SOURCE_DIR}/fuzz/${target}.cpp)
186-
target_link_libraries(fuzz-${target} PRIVATE app_lib)
187-
target_link_options(fuzz-${target} PRIVATE "-fsanitize=fuzzer")
188-
endforeach()
135+
if(ENABLE_FUZZING) # Fuzz Targets
136+
# Setup fuzzing directories
137+
setup_fuzz_directories()
138+
139+
# Add fuzz targets using the common macro
140+
add_fuzz_target(parser_parse ${CMAKE_CURRENT_SOURCE_DIR}/fuzz/parser_parse.cpp app_lib)
141+
add_fuzz_target(metadata_parser_composite ${CMAKE_CURRENT_SOURCE_DIR}/fuzz/metadata_parser_composite.cpp app_lib)
142+
add_fuzz_target(metadata_parser_sequence ${CMAKE_CURRENT_SOURCE_DIR}/fuzz/metadata_parser_sequence.cpp app_lib)
143+
add_fuzz_target(metadata_parser_array ${CMAKE_CURRENT_SOURCE_DIR}/fuzz/metadata_parser_array.cpp app_lib)
144+
add_fuzz_target(metadata_parser_tuple ${CMAKE_CURRENT_SOURCE_DIR}/fuzz/metadata_parser_tuple.cpp app_lib)
145+
add_fuzz_target(metadata_parser_primitives ${CMAKE_CURRENT_SOURCE_DIR}/fuzz/metadata_parser_primitives.cpp app_lib)
146+
add_fuzz_target(metadata_parser_variant ${CMAKE_CURRENT_SOURCE_DIR}/fuzz/metadata_parser_variant.cpp app_lib)
147+
189148
else()
190-
#############################################################
191-
# Tests
192-
file(GLOB_RECURSE TESTS_SRC
149+
# #############################################################
150+
# #############################################################
151+
# Tests
152+
file(GLOB_RECURSE TESTS_SRC
193153
${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp)
194154

195155
add_executable(unittests ${TESTS_SRC})
@@ -208,4 +168,4 @@ else()
208168
add_compile_definitions(TESTVECTORS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/tests/")
209169
add_test(NAME unittests COMMAND unittests)
210170
set_tests_properties(unittests PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests)
211-
endif()
171+
endif()

app/Makefile.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ APPVERSION_M=100
33
# This is the `spec_version` field of `Runtime`
44
APPVERSION_N=0
55
# This is the patch version of this release
6-
APPVERSION_P=17
6+
APPVERSION_P=18

app/src/crypto.c

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,23 @@ zxerr_t crypto_sign_secp256k1(
155155

156156
// Hash the message
157157
uint8_t messageDigest[CX_KECCAK_256_SIZE] = {0};
158-
CHECK_CX_OK(cx_keccak_256_hash(message, messageLen, messageDigest));
158+
zxerr_t error = zxerr_unknown;
159+
160+
// When the payload is bigger than MAX_SIGN_SIZE, it needs to be hashed with blake2b_256 before hashing with keccak_256.
161+
// https://github.com/paritytech/polkadot-sdk/blob/1a512570552119a49a8ecb2abfb7021954c4422d/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs#L567
162+
if (messageLen > MAX_SIGN_SIZE) {
163+
uint8_t intermediate_digest[BLAKE2B_DIGEST_SIZE];
164+
// Hash it with blake2b
165+
cx_blake2b_t ctx;
166+
CATCH_CXERROR(cx_blake2b_init_no_throw(&ctx, 256));
167+
CATCH_CXERROR(cx_hash_no_throw(&ctx.header, CX_LAST, message, messageLen, intermediate_digest, BLAKE2B_DIGEST_SIZE));
168+
169+
// Then hash with keccak_256
170+
CHECK_CX_OK(cx_keccak_256_hash(intermediate_digest, BLAKE2B_DIGEST_SIZE, messageDigest));
171+
} else {
172+
// Mandatory, hash the message with keccak_256
173+
CHECK_CX_OK(cx_keccak_256_hash(message, messageLen, messageDigest));
174+
}
159175

160176
cx_ecfp_private_key_t cx_privateKey;
161177
uint8_t privateKeyData[SECP256K1_PK_LEN_UNCOMPRESSED - 1] = {0};
@@ -164,7 +180,6 @@ zxerr_t crypto_sign_secp256k1(
164180
*sigSize = 0;
165181

166182
signature_t *const signature = (signature_t *)output;
167-
zxerr_t error = zxerr_unknown;
168183

169184
CATCH_CXERROR(os_derive_bip32_with_seed_no_throw(HDW_NORMAL, CX_CURVE_SECP256K1, hdPath, HDPATH_LEN_DEFAULT,
170185
privateKeyData, NULL, NULL, 0));

app/src/metadata_reader.c

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,9 +335,21 @@ static parser_error_t readMerkleIndices(parser_context_t *ctx, MerkleIndices_t *
335335
}
336336

337337
CHECK_ERROR(readCompactU32(ctx, &indices->entries));
338+
339+
// Check for overflow when computing buffer length
340+
// Each index is 4 bytes (u32), so total size = 4 * entries
341+
// First check if multiplication would overflow uint32_t
342+
if (indices->entries > UINT32_MAX / 4) {
343+
return parser_value_out_of_range;
344+
}
345+
const uint32_t totalSize = 4 * indices->entries;
346+
if (totalSize > UINT16_MAX) {
347+
return parser_value_out_of_range;
348+
}
349+
338350
indices->indices.buffer = ctx->buffer + ctx->offset;
339351
indices->indices.offset = 0;
340-
indices->indices.bufferLen = 4 * indices->entries;
352+
indices->indices.bufferLen = (uint16_t)totalSize;
341353

342354
uint32_t tmpIndex = 0;
343355
for (uint32_t idx = 0; idx < indices->entries; idx++) {
@@ -360,9 +372,20 @@ static parser_error_t readMerkleLemmas(parser_context_t *ctx, MerkleLemmas_t *le
360372

361373
CHECK_ERROR(readCompactU32(ctx, &lemmas->entries));
362374

375+
// Check for overflow when computing buffer length
376+
// Each lemma is 32 bytes, so total size = 32 * entries
377+
// First check if multiplication would overflow uint32_t
378+
if (lemmas->entries > UINT32_MAX / 32) {
379+
return parser_value_out_of_range;
380+
}
381+
const uint32_t totalSize = 32 * lemmas->entries;
382+
if (totalSize > UINT16_MAX) {
383+
return parser_value_out_of_range;
384+
}
385+
363386
lemmas->lemmas.buffer = ctx->buffer + ctx->offset;
364387
lemmas->lemmas.offset = 0;
365-
lemmas->lemmas.bufferLen = 32 * lemmas->entries;
388+
lemmas->lemmas.bufferLen = (uint16_t)totalSize;
366389
for (uint32_t idx = 0; idx < lemmas->entries; idx++) {
367390
CTX_CHECK_AND_ADVANCE(ctx, 32); // lemmas are [u8;32]
368391
}

deps/BLAKE3-c/blake3_portable.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
11
#include "blake3_impl.h"
22
#include <string.h>
33

4+
// Disable UndefinedBehaviorSanitizer for BLAKE3 cryptographic operations
5+
// Unsigned integer overflow is intentional (modular arithmetic)
6+
#if defined(__has_attribute)
7+
#if __has_attribute(no_sanitize)
8+
#define NO_SANITIZE_UNSIGNED __attribute__((no_sanitize("unsigned-integer-overflow", "unsigned-shift-base")))
9+
#else
10+
#define NO_SANITIZE_UNSIGNED
11+
#endif
12+
#else
13+
#define NO_SANITIZE_UNSIGNED
14+
#endif
15+
16+
NO_SANITIZE_UNSIGNED
417
INLINE uint32_t rotr32(uint32_t w, uint32_t c) {
518
return (w >> c) | (w << (32 - c));
619
}
720

21+
NO_SANITIZE_UNSIGNED
822
INLINE void g(uint32_t *state, size_t a, size_t b, size_t c, size_t d,
923
uint32_t x, uint32_t y) {
1024
state[a] = state[a] + state[b] + x;
@@ -17,6 +31,7 @@ INLINE void g(uint32_t *state, size_t a, size_t b, size_t c, size_t d,
1731
state[b] = rotr32(state[b] ^ state[c], 7);
1832
}
1933

34+
NO_SANITIZE_UNSIGNED
2035
INLINE void round_fn(uint32_t state[16], const uint32_t *msg, size_t round) {
2136
// Select the message schedule based on the round.
2237
const uint8_t *schedule = MSG_SCHEDULE[round];
@@ -34,6 +49,7 @@ INLINE void round_fn(uint32_t state[16], const uint32_t *msg, size_t round) {
3449
g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]]);
3550
}
3651

52+
NO_SANITIZE_UNSIGNED
3753
INLINE void compress_pre(uint32_t state[16], const uint32_t cv[8],
3854
const uint8_t block[BLAKE3_BLOCK_LEN],
3955
uint8_t block_len, uint64_t counter, uint8_t flags) {
@@ -81,6 +97,7 @@ INLINE void compress_pre(uint32_t state[16], const uint32_t cv[8],
8197
round_fn(state, &block_words[0], 6);
8298
}
8399

100+
NO_SANITIZE_UNSIGNED
84101
void blake3_compress_in_place_portable(uint32_t cv[8],
85102
const uint8_t block[BLAKE3_BLOCK_LEN],
86103
uint8_t block_len, uint64_t counter,
@@ -97,6 +114,7 @@ void blake3_compress_in_place_portable(uint32_t cv[8],
97114
cv[7] = state[7] ^ state[15];
98115
}
99116

117+
NO_SANITIZE_UNSIGNED
100118
void blake3_compress_xof_portable(const uint32_t cv[8],
101119
const uint8_t block[BLAKE3_BLOCK_LEN],
102120
uint8_t block_len, uint64_t counter,

docs/build.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,28 @@ You can get pre-releases in two ways (*THESE ARE UNVETTED DEVELOPMENT RELEASES*)
2323
- Install Docker CE
2424
- Instructions can be found here: https://docs.docker.com/install/
2525
26-
- We typically use Ubuntu or MacOS. Install the following packages:
26+
- We typically use Ubuntu. Install the following packages:
2727
```sh
2828
sudo apt update && apt-get -y install build-essential git wget cmake \
2929
libssl-dev libgmp-dev autoconf libtool
3030
```
3131

32+
Or for MacOS, install the following:
33+
```sh
34+
brew install python@3.11 cmake
35+
echo 'export PATH="$PATH:/opt/homebrew/opt/python@3.11/libexec/bin"' >> .zprofile
36+
source ~/.zshrc
37+
```
38+
3239
- Install [poetry](https://python-poetry.org/docs/#installing-with-the-official-installer). This simplifies python environments
3340

3441
```sh
35-
https://python-poetry.org/docs/#installing-with-the-official-installer
42+
curl -sSL https://install.python-poetry.org | python3 -
43+
```
44+
45+
After installing Poetry, restart your shell or run:
46+
```sh
47+
source ~/.bashrc # or ~/.zshrc on MacOS
3648
```
3749

3850
- Install [volta](https://docs.volta.sh/guide/getting-started)
@@ -47,6 +59,7 @@ You can get pre-releases in two ways (*THESE ARE UNVETTED DEVELOPMENT RELEASES*)
4759
```
4860

4961
- You will need python 3 and then run
62+
- `poetry shell`
5063
- `make deps`
5164

5265
- The current repository keeps track of Ledger's SDK but it is possible to override it by changing the git submodule.

fuzz/fuzz_config.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Project-specific fuzzing configuration
4+
"""
5+
6+
import sys
7+
import os
8+
9+
# Configuration constants
10+
MAX_SECONDS = 3600 # 1 hour for quick test
11+
FUZZER_JOBS = 8 # Number of parallel jobs for fuzzing
12+
13+
# Add the common fuzzing module to path
14+
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "deps", "ledger-zxlib", "fuzzing"))
15+
16+
try:
17+
from run_fuzzers import FuzzConfig
18+
except ImportError:
19+
# Fallback if common module is not available
20+
class FuzzConfig:
21+
def __init__(self, name: str, max_len: int = 17000):
22+
self.name = name
23+
self.max_len = max_len
24+
25+
26+
def get_fuzzer_configs():
27+
"""Get project-specific fuzzer configurations"""
28+
return [
29+
FuzzConfig(name="metadata_parser_array", max_len=17000), # Tests metadata_parser_array functionality
30+
FuzzConfig(name="metadata_parser_composite", max_len=17000), # Tests metadata_parser_composite functionality
31+
FuzzConfig(name="metadata_parser_primitives", max_len=17000), # Tests metadata_parser_primitives functionality
32+
FuzzConfig(name="metadata_parser_sequence", max_len=17000), # Tests metadata_parser_sequence functionality
33+
FuzzConfig(name="metadata_parser_variant", max_len=17000), # Tests metadata_parser_variant functionality
34+
FuzzConfig(name="metadata_parser_tuple", max_len=17000), # Tests metadata_parser_variant functionality
35+
FuzzConfig(name="parser_parse", max_len=17000), # Tests parser_parse functionality
36+
# Add more fuzzers here as needed
37+
]
38+
39+
40+
if __name__ == "__main__":
41+
# Show available configurations
42+
configs = get_fuzzer_configs()
43+
print("Available fuzzer configurations:")
44+
for config in configs:
45+
print(f" - {config.name}: max_len={config.max_len}")

0 commit comments

Comments
 (0)