Clear the output buffer on failures in merkle-tree related functions
What changed, and why it matters
This commit hardens Ledger's Bitcoin app so that when it asks the host computer for Merkle-tree data and the request fails or the data is rejected, the memory buffer that was supposed to receive the data is wiped clean with zeros. Before the change, an attacker-controlled host could leave chosen bytes in that buffer even after the app decided the data was invalid. The patch is a defense-in-depth measure; it does not by itself fix a known exploitable bug, but it removes a class of subtle mistakes where later code might accidentally trust leftover hostile data.
Treat as a worthwhile hardening patch. Review all callers of the affected functions to confirm they check return codes before using output buffers, since zeroed buffers are not a substitute for status checking. Include the new unit tests in CI. No urgent incident response is indicated absent evidence of a reachable exploit chain.
Security signals we found
explicit_bzero added to clear output buffers on failure paths
Host-supplied data is streamed into buffers before hash/Merkle verification
New unit tests verify buffer clearing on failure, including MAP_VALUE_ABSENT
Commit message frames change as defense-in-depth against uninitialized/host-chosen buffer content
No CVE, advisory, or independent researcher attribution present in commit
Evidence from the diff
The change wraps several Merkle helper functions (call_get_preimage, call_get_merkle_preimage, call_get_merkle_leaf_hash, call_get_merkle_leaf_element, call_get_merkleized_map_value, call_get_merkleized_map_value_hash) so that on any negative status they call explicit_bzero on the caller-supplied output buffer. The core issue is that these functions stream host-provided preimages/proofs into the output buffer before cryptographic verification completes; if verification later fails, the buffer would retain attacker-chosen content. The patch also updates unit tests to pre-fill buffers with 0xEE and assert they are cleared after failure, including a new adversarial test for corrupted map value data. It is explicitly described by the author as defense-in-depth, not a fix for a currently reachable vulnerability.
Changed components
src/handler/lib/get_preimage.csrc/handler/lib/get_merkle_preimage.csrc/handler/lib/get_merkle_leaf_hash.csrc/handler/lib/get_merkle_leaf_element.csrc/handler/lib/get_merkleized_map_value.csrc/handler/lib/get_merkleized_map_value_hash.cunit-tests for the above modulesInspect captured patch +247 / −30
### src/handler/lib/get_merkle_leaf_element.c
@@ -1,3 +1,5 @@
+#include <string.h>
+
#include "get_merkle_leaf_element.h"
/* Local headers */
@@ -20,6 +22,9 @@ int call_get_merkle_leaf_element(dispatcher_context_t *dispatcher_context,
leaf_index,
leaf_hash);
if (res < 0) {
+ // Nothing was written to `out_ptr` here, but clear it anyway to keep the postcondition
+ // uniform; call_get_merkle_preimage covers the other failures.
+ explicit_bzero(out_ptr, out_ptr_len);
return res;
}
return call_get_merkle_preimage(dispatcher_context, leaf_hash, out_ptr, out_ptr_len);
### src/handler/lib/get_merkle_leaf_element.h
@@ -7,7 +7,8 @@
* Retrieves the preimage of the leaf at `leaf_index` in the Merkle tree identified by
* `merkle_root` and `tree_size`, and stores it in `out_ptr`.
*
- * Returns the length of the preimage on success, or a negative number on failure.
+ * Returns the length of the preimage on success, or a negative number on failure; `out_ptr` is
+ * then fully zeroed (see call_get_preimage).
*/
int call_get_merkle_leaf_element(dispatcher_context_t *dispatcher_context,
const uint8_t merkle_root[static 32],
### src/handler/lib/get_merkle_leaf_hash.c
@@ -13,12 +13,13 @@
#include "merkle.h"
#include "sw.h"
-// Reads the inputs and sends the GET_MERKLE_LEAF_PROOF request.
-int call_get_merkle_leaf_hash(dispatcher_context_t *dc,
- const uint8_t merkle_root[static 32],
- uint32_t tree_size,
- uint32_t leaf_index,
- uint8_t out[static 32]) {
+// Sends the GET_MERKLE_LEAF_PROOF request and verifies the reply. Body of
+// call_get_merkle_leaf_hash; the wrapper below clears `out` on failure.
+static int get_merkle_leaf_hash(dispatcher_context_t *dc,
+ const uint8_t merkle_root[static 32],
+ uint32_t tree_size,
+ uint32_t leaf_index,
+ uint8_t out[static 32]) {
// LOG_PROCESSOR(__FILE__, __LINE__, __func__);
PRINT_STACK_POINTER();
@@ -146,3 +147,16 @@ int call_get_merkle_leaf_hash(dispatcher_context_t *dc,
return 0;
}
+
+int call_get_merkle_leaf_hash(dispatcher_context_t *dc,
+ const uint8_t merkle_root[static 32],
+ uint32_t tree_size,
+ uint32_t leaf_index,
+ uint8_t out[static 32]) {
+ int res = get_merkle_leaf_hash(dc, merkle_root, tree_size, leaf_index, out);
+ if (res < 0) {
+ // The leaf hash is copied out before the proof is verified; see call_get_preimage.
+ explicit_bzero(out, 32);
+ }
+ return res;
+}
### src/handler/lib/get_merkle_leaf_hash.h
@@ -8,7 +8,8 @@
* and `tree_size`, and verifies the proof returned by the host against `merkle_root`.
*
* On success, writes the 32-byte leaf hash to `out` and returns 0. Returns a negative value on
- * failure.
+ * failure; `out` is then fully zeroed, as the leaf hash is copied out before the proof is
+ * verified (see call_get_preimage).
*/
int call_get_merkle_leaf_hash(dispatcher_context_t *dispatcher_context,
const uint8_t merkle_root[static 32],
### src/handler/lib/get_merkle_preimage.c
@@ -11,10 +11,11 @@
#include "debug.h"
#include "sw.h"
-int call_get_merkle_preimage(dispatcher_context_t *dispatcher_context,
- const uint8_t hash[static 32],
- uint8_t *out_ptr,
- size_t out_ptr_len) {
+// Body of call_get_merkle_preimage; the wrapper below clears `out_ptr` on failure.
+static int get_merkle_preimage(dispatcher_context_t *dispatcher_context,
+ const uint8_t hash[static 32],
+ uint8_t *out_ptr,
+ size_t out_ptr_len) {
// LOG_PROCESSOR(__FILE__, __LINE__, __func__);
PRINT_STACK_POINTER();
@@ -130,3 +131,15 @@ int call_get_merkle_preimage(dispatcher_context_t *dispatcher_context,
return (int) (preimage_len - 1);
}
+
+int call_get_merkle_preimage(dispatcher_context_t *dispatcher_context,
+ const uint8_t hash[static 32],
+ uint8_t *out_ptr,
+ size_t out_ptr_len) {
+ int res = get_merkle_preimage(dispatcher_context, hash, out_ptr, out_ptr_len);
+ if (res < 0) {
+ // The preimage is streamed in before its hash can be checked; see call_get_preimage.
+ explicit_bzero(out_ptr, out_ptr_len);
+ }
+ return res;
+}
### src/handler/lib/get_merkle_preimage.h
@@ -14,6 +14,8 @@
*
* Returns the length of the preimage without the prefix on success, or a negative number in case
* of failure.
+ *
+ * On any negative return `out_ptr` is fully zeroed; see call_get_preimage.
*/
int call_get_merkle_preimage(dispatcher_context_t *dispatcher_context,
const uint8_t hash[static 32],
### src/handler/lib/get_merkleized_map_value.c
@@ -27,10 +27,13 @@ int call_get_merkleized_map_value(dispatcher_context_t *dispatcher_context,
int index =
call_get_merkle_leaf_index(dispatcher_context, map->size, map->keys_root, key_merkle_hash);
- if (index == MERKLE_LEAF_NOT_FOUND) {
- return MAP_VALUE_ABSENT;
- }
if (index < 0) {
+ // Nothing was written to `out` here, but clear it anyway to keep the postcondition
+ // uniform, MAP_VALUE_ABSENT included: no caller pre-seeds a default into the buffer.
+ explicit_bzero(out, out_len);
+ if (index == MERKLE_LEAF_NOT_FOUND) {
+ return MAP_VALUE_ABSENT;
+ }
PRINTF("Failed to look up the key.\n");
return MAP_VALUE_ERROR;
}
@@ -44,4 +47,4 @@ int call_get_merkleized_map_value(dispatcher_context_t *dispatcher_context,
// Normalize: the failure codes of the underlying flows overlap with MAP_VALUE_ABSENT, and
// leaking them would make a transport error look like a missing key.
return res < 0 ? MAP_VALUE_ERROR : res;
-}
\ No newline at end of file
+}
### src/handler/lib/get_merkleized_map_value.h
@@ -16,6 +16,9 @@
* long to fit into the output buffer. See map_value_status.h; in particular, callers must branch
* on MAP_VALUE_ABSENT explicitly rather than on `res < 0` when a missing key is not an error.
*
+ * On any non-success return - MAP_VALUE_ABSENT included - `out` is fully zeroed; see
+ * call_get_preimage.
+ *
* PRECONDITION: the map's keys must have already been verified to be lexicographically sorted (and
* therefore unique); this is what makes a by-key lookup unambiguous. A map is validated either by
* `call_get_merkleized_map[_with_callback]` (which validates before returning) or by
### src/handler/lib/get_merkleized_map_value_hash.c
@@ -25,10 +25,12 @@ int call_get_merkleized_map_value_hash(dispatcher_context_t *dispatcher_context,
int index =
call_get_merkle_leaf_index(dispatcher_context, map->size, map->keys_root, key_merkle_hash);
- if (index == MERKLE_LEAF_NOT_FOUND) {
- return MAP_VALUE_ABSENT;
- }
if (index < 0) {
+ // Nothing was written to `out` here, but clear it anyway to keep the postcondition uniform.
+ explicit_bzero(out, 32);
+ if (index == MERKLE_LEAF_NOT_FOUND) {
+ return MAP_VALUE_ABSENT;
+ }
PRINTF("Failed to look up the key.\n");
return MAP_VALUE_ERROR;
}
### src/handler/lib/get_merkleized_map_value_hash.h
@@ -15,6 +15,9 @@
* of the proofs failed. See map_value_status.h; in particular, callers must branch on
* MAP_VALUE_ABSENT explicitly rather than on `res < 0` when a missing key is not an error.
*
+ * On any non-success return - MAP_VALUE_ABSENT included - `out` is fully zeroed; see
+ * call_get_preimage.
+ *
* PRECONDITION: the map's keys must have already been verified to be lexicographically sorted;
* this function asserts it (LEDGER_ASSERT on `map->_keys_are_sorted`).
*/
### src/handler/lib/get_preimage.c
@@ -7,10 +7,11 @@
#include "stream_preimage.h"
#include "sw.h"
-int call_get_preimage(dispatcher_context_t *dispatcher_context,
- const uint8_t hash[static 32],
- uint8_t *out,
- size_t out_len) {
+// Body of call_get_preimage; the wrapper below clears `out` on failure.
+static int get_preimage(dispatcher_context_t *dispatcher_context,
+ const uint8_t hash[static 32],
+ uint8_t *out,
+ size_t out_len) {
// LOG_PROCESSOR(__FILE__, __LINE__, __func__);
uint8_t cmd = CCMD_GET_PREIMAGE;
@@ -123,3 +124,16 @@ int call_get_preimage(dispatcher_context_t *dispatcher_context,
return (int) preimage_len;
}
+
+int call_get_preimage(dispatcher_context_t *dispatcher_context,
+ const uint8_t hash[static 32],
+ uint8_t *out,
+ size_t out_len) {
+ int res = get_preimage(dispatcher_context, hash, out, out_len);
+ if (res < 0) {
+ // The preimage is streamed into `out` before its hash can be checked, so a failed read
+ // would otherwise leave bytes there that the client chose and the device rejected.
+ explicit_bzero(out, out_len);
+ }
+ return res;
+}
### src/handler/lib/get_preimage.h
@@ -8,6 +8,10 @@
*
* Returns a negative number on error, or the preimage length on success. This function validates
* that the SHA256 of the data provided by the host does indeed match the expected hash.
+ *
+ * On any negative return `out` is fully zeroed: the preimage is streamed in before its hash can be
+ * verified, so a rejected read must not leave host-chosen bytes behind. This does not make the
+ * contents meaningful - the status must still be checked.
*/
int call_get_preimage(dispatcher_context_t *dispatcher_context,
const uint8_t hash[static 32],
### unit-tests/libs/test_assertions.h
@@ -0,0 +1,15 @@
+#pragma once
+
+/**
+ * Shared cmocka assertions. Include after <cmocka.h>.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+
+/** Fails unless every byte of `buf` is zero. */
+static inline void assert_cleared(const uint8_t *buf, size_t len) {
+ for (size_t i = 0; i < len; i++) {
+ assert_int_equal(buf[i], 0);
+ }
+}
### unit-tests/test_get_merkle_leaf_element.c
@@ -17,6 +17,7 @@
#include <cmocka.h>
#include "mock_dispatcher.h"
+#include "test_assertions.h"
#include "client_commands.h"
#include "handler/lib/get_merkle_leaf_element.h"
@@ -157,11 +158,13 @@ static void test_get_leaf_element_buffer_too_small(void **state) {
build_tree(mock, elems, lens, 1, root);
uint8_t out[10]; /* Too small */
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_element(dc, root, 1, 0, out, sizeof(out));
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -182,11 +185,13 @@ static void test_get_leaf_element_wrong_root(void **state) {
memset(bad_root, 0xFF, 32);
uint8_t out[256];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_element(dc, bad_root, 1, 0, out, sizeof(out));
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -202,11 +207,13 @@ static void test_get_leaf_element_index_out_of_bounds(void **state) {
build_tree(mock, elems, lens, 2, root);
uint8_t out[256];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_element(dc, root, 2, 5, out, sizeof(out));
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -283,10 +290,12 @@ static void test_get_leaf_element_corrupted_proof(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_corrupt_leaf_in_proof, NULL);
uint8_t out[256];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_element(dc, root, 4, 0, out, sizeof(out));
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -321,11 +330,13 @@ static void test_get_leaf_element_corrupted_preimage(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_corrupt_preimage_after_proof, NULL);
uint8_t out[256];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_element(dc, root, 1, 0, out, sizeof(out));
/* Preimage hash mismatch → must fail */
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/* ---------- Main ---------- */
### unit-tests/test_get_merkle_leaf_hash.c
@@ -16,6 +16,7 @@
#include <cmocka.h>
#include "mock_dispatcher.h"
+#include "test_assertions.h"
#include "client_commands.h"
#include "handler/lib/get_merkle_leaf_hash.h"
@@ -266,10 +267,12 @@ static void test_get_leaf_hash_wrong_root(void **state) {
memset(bad_root, 0xFF, 32);
uint8_t out[32];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_hash(dc, bad_root, 1, 0, out);
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -343,10 +346,12 @@ static void test_get_leaf_hash_corrupted_proof(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_corrupt_proof_hash, NULL);
uint8_t out[32];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_hash(dc, root, 4, 0, out);
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -382,10 +387,12 @@ static void test_get_leaf_hash_corrupted_leaf(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_corrupt_leaf_hash, NULL);
uint8_t out[32];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_hash(dc, root, 2, 0, out);
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -421,10 +428,12 @@ static void test_get_leaf_hash_proof_elements_overflow(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_proof_elements_overflow, NULL);
uint8_t out[32];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_hash(dc, root, 2, 0, out);
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -463,10 +472,12 @@ static void test_get_leaf_hash_zero_proof_size(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_zero_proof_size, NULL);
uint8_t out[32];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_hash(dc, root, 3, 0, out);
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -509,10 +520,12 @@ static void test_get_leaf_hash_bad_proof_element_size(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_bad_proof_element_size, NULL);
uint8_t out[32];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_hash(dc, root, 128, 0, out);
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -552,10 +565,12 @@ static void test_get_leaf_hash_truncated_proof_elements(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_truncate_proof_elements, NULL);
uint8_t out[32];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_hash(dc, root, 4, 0, out);
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -599,10 +614,12 @@ static void test_get_leaf_hash_more_comm_failure(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_fail_more, NULL);
uint8_t out[32];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_hash(dc, root, 128, 0, out);
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -644,10 +661,12 @@ static void test_get_leaf_hash_truncated_more(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_truncate_more, NULL);
uint8_t out[32];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_hash(dc, root, 128, 0, out);
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -694,10 +713,12 @@ static void test_get_leaf_hash_more_proof_overflow(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_more_proof_overflow, NULL);
uint8_t out[32];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_hash(dc, root, 128, 0, out);
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -762,10 +783,12 @@ static void test_get_leaf_hash_internal_node_as_leaf(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_internal_node_as_leaf, forged);
uint8_t out[32];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_hash(dc, root, 4, 0, out);
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -870,10 +893,12 @@ static void test_get_leaf_hash_overlong_proof(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_overlong_proof, NULL);
uint8_t out[32];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_leaf_hash(dc, root, 5, 4, out);
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
### unit-tests/test_get_merkle_preimage.c
@@ -17,6 +17,7 @@
#include <cmocka.h>
#include "mock_dispatcher.h"
+#include "test_assertions.h"
#include "client_commands.h"
#include "handler/lib/get_merkle_preimage.h"
@@ -111,12 +112,14 @@ static void test_get_merkle_preimage_unknown_hash(void **state) {
/* Don't register any preimage; just call with a random hash */
uint8_t hash[32] = {0xDE, 0xAD, 0xBE, 0xEF};
uint8_t out[256];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
/* process_interruption returns -1 → call_get_merkle_preimage returns -1 */
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -135,11 +138,13 @@ static void test_get_merkle_preimage_buffer_too_small(void **state) {
add_merkle_preimage(mock, element, sizeof(element), hash);
uint8_t out[50]; /* Too small for 100-byte element */
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
assert_int_equal(result, -4);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -282,11 +287,13 @@ static void test_get_merkle_preimage_corrupted_data(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_corrupt_data, NULL);
uint8_t out[256];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
/* Must detect hash mismatch */
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -321,11 +328,13 @@ static void test_get_merkle_preimage_corrupted_continuation(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_corrupt_continuation, NULL);
uint8_t out[512];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
/* Must detect the corruption (hash mismatch or protocol error) */
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -360,10 +369,12 @@ static void test_get_merkle_preimage_truncated(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_truncate, NULL);
uint8_t out[64];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
assert_int_equal(result, -2);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -395,10 +406,12 @@ static void test_get_merkle_preimage_zero_len(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_zero_len, NULL);
uint8_t out[64];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
assert_int_equal(result, -3);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -432,10 +445,12 @@ static void test_get_merkle_preimage_partial_len_over(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_partial_len_over, NULL);
uint8_t out[64];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
assert_int_equal(result, -5);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -472,10 +487,12 @@ static void test_get_merkle_preimage_comm_failure(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_fail_second, NULL);
uint8_t out[512];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
assert_int_equal(result, -6);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -510,10 +527,12 @@ static void test_get_merkle_preimage_truncated_more(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_truncate_more, NULL);
uint8_t out[512];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
assert_int_equal(result, -7);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -548,10 +567,12 @@ static void test_get_merkle_preimage_bad_element_size(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_more_bad_size, NULL);
uint8_t out[512];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
assert_int_equal(result, -8);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -590,10 +611,12 @@ static void test_get_merkle_preimage_more_bytes(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_more_bytes, NULL);
uint8_t out[512];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
assert_int_equal(result, -9);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -627,17 +650,17 @@ static void test_get_merkle_preimage_internal_node_preimage(void **state) {
assert_memory_equal(hash, mock->trees[0].root, 32);
uint8_t out[256];
- memset(out, 0xAA, sizeof(out));
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
assert_int_equal(result, -12);
+ assert_cleared(out, sizeof(out));
}
/**
- * Adversarial: same rejection for an arbitrary non-zero prefix byte, and nothing is written to
- * the output buffer.
+ * Adversarial: same rejection for an arbitrary non-zero prefix byte.
*/
static void test_get_merkle_preimage_bad_prefix(void **state) {
mock_dispatcher_t *mock = *state;
@@ -654,15 +677,13 @@ static void test_get_merkle_preimage_bad_prefix(void **state) {
compute_sha256(preimage, sizeof(preimage), hash);
uint8_t out[64];
- memset(out, 0xAA, sizeof(out));
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
assert_int_equal(result, -12);
- for (size_t i = 0; i < sizeof(out); i++) {
- assert_int_equal(out[i], 0xAA);
- }
+ assert_cleared(out, sizeof(out));
}
/* ---------- Main ---------- */
### unit-tests/test_get_merkleized_map_value.c
@@ -17,6 +17,7 @@
#include <cmocka.h>
#include "mock_dispatcher.h"
+#include "test_assertions.h"
#include "client_commands.h"
#include "common/merkle.h"
@@ -143,6 +144,7 @@ static void test_map_value_key_not_found(void **state) {
const uint8_t missing_key[] = {0xFF};
uint8_t out[64];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkleized_map_value(dc,
&commitment,
@@ -153,6 +155,7 @@ static void test_map_value_key_not_found(void **state) {
/* A genuinely missing key must be reported as absent, distinctly from a failed lookup, so
* that callers may safely apply a default for an optional field. */
assert_int_equal(result, MAP_VALUE_ABSENT);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -175,12 +178,14 @@ static void test_map_value_out_buffer_too_small(void **state) {
/* Provide an output buffer smaller than the value length. */
uint8_t out[2];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkleized_map_value(dc, &commitment, key, sizeof(key), out, sizeof(out));
/* Present but too long for the buffer is an ERROR, NOT ABSENT. Before the statuses were
* separated both surfaced as -1, so a caller substituting a default for an optional field
* would have signed over a value the client never committed to. */
assert_int_equal(result, MAP_VALUE_ERROR);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -252,10 +257,57 @@ static void test_map_value_corrupted_value_proof(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_corrupt_value_proof, &proof_call);
uint8_t out[64];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_merkleized_map_value(dc, &commitment, key, sizeof(key), out, sizeof(out));
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
+}
+
+/**
+ * Adversarial: the proofs all check out, but the client corrupts the value preimage itself, so it
+ * lands in `out` before it is found not to hash to the committed leaf.
+ */
+static int tamper_corrupt_value_data(uint8_t *response_buf,
+ size_t *response_len,
+ uint8_t cmd,
+ int call_count,
+ void *user_data) {
+ (void) call_count;
+ (void) user_data;
+
+ /* Byte 0 is the 0x00 leaf prefix, rejected before any write; corrupt the value instead. */
+ if (cmd == CCMD_GET_PREIMAGE && *response_len > 3) {
+ response_buf[3] ^= 0xFF;
+ }
+ return 0;
+}
+
+static void test_map_value_corrupted_value_data(void **state) {
+ mock_dispatcher_t *mock = *state;
+
+ const uint8_t key[] = {0x01};
+ uint8_t value[40];
+ memset(value, 0x5A, sizeof(value));
+
+ const uint8_t *keys[] = {key};
+ const size_t key_lens[] = {sizeof(key)};
+ const uint8_t *values[] = {value};
+ const size_t value_lens[] = {sizeof(value)};
+
+ merkleized_map_commitment_t commitment;
+ mock_dispatcher_add_map(mock, keys, key_lens, values, value_lens, 1, &commitment);
+
+ mock_dispatcher_set_tamper_hook(mock, tamper_corrupt_value_data, NULL);
+
+ uint8_t out[64];
+ memset(out, 0xEE, sizeof(out));
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
+ int result = call_get_merkleized_map_value(dc, &commitment, key, sizeof(key), out, sizeof(out));
+
+ assert_int_equal(result, MAP_VALUE_ERROR);
+ assert_cleared(out, sizeof(out));
}
/* ---------- Main ---------- */
@@ -270,6 +322,7 @@ int main(void) {
T(test_map_value_out_buffer_too_small),
T(test_map_value_empty_value),
T(test_map_value_corrupted_value_proof),
+ T(test_map_value_corrupted_value_data),
};
#undef T
### unit-tests/test_get_preimage.c
@@ -17,6 +17,7 @@
#include <cmocka.h>
#include "mock_dispatcher.h"
+#include "test_assertions.h"
#include "client_commands.h"
#include "handler/lib/get_preimage.h"
@@ -94,12 +95,14 @@ static void test_get_preimage_unknown_hash(void **state) {
/* Don't register any preimage; just call with a random hash */
uint8_t hash[32] = {0xDE, 0xAD, 0xBE, 0xEF};
uint8_t out[256];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_preimage(dc, hash, out, sizeof(out));
/* process_interruption returns -1 → call_get_preimage returns -1 */
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -120,11 +123,13 @@ static void test_get_preimage_buffer_too_small(void **state) {
compute_sha256(preimage, sizeof(preimage), hash);
uint8_t out[50]; /* Too small! */
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_preimage(dc, hash, out, sizeof(out));
assert_int_equal(result, -10);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -251,11 +256,13 @@ static void test_get_preimage_corrupted_data(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_corrupt_preimage_data, NULL);
uint8_t out[256];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_preimage(dc, hash, out, sizeof(out));
/* Must detect the hash mismatch */
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -294,11 +301,13 @@ static void test_get_preimage_partial_len_overflow(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_partial_len_overflow, NULL);
uint8_t out[256];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_preimage(dc, hash, out, sizeof(out));
/* Detected via buffer_can_read or partial_data_len > preimage_len */
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -333,10 +342,12 @@ static void test_get_preimage_zero_len(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_zero_preimage_len, NULL);
uint8_t out[256];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_preimage(dc, hash, out, sizeof(out));
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -372,11 +383,13 @@ static void test_get_preimage_bad_element_size(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_more_elements_bad_size, NULL);
uint8_t out[512];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_preimage(dc, hash, out, sizeof(out));
/* Detected via buffer_can_read or elements_len != 1 */
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -413,11 +426,13 @@ static void test_get_preimage_more_bytes_than_remaining(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_more_bytes_than_remaining, NULL);
uint8_t out[512];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_preimage(dc, hash, out, sizeof(out));
/* n_bytes > bytes_remaining → -8, or buffer_can_read fails → -6 */
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -454,10 +469,12 @@ static void test_get_preimage_corrupted_continuation(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_corrupt_continuation, NULL);
uint8_t out[512];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_preimage(dc, hash, out, sizeof(out));
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -502,11 +519,13 @@ static void test_get_preimage_overflow_len(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_preimage_len_too_big, NULL);
uint8_t out[256];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_preimage(dc, hash, out, sizeof(out));
/* preimage_len_u64 > UINT32_MAX → return -11 */
assert_int_equal(result, -11);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -545,10 +564,12 @@ static void test_get_preimage_partial_len_strictly_over(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_partial_len_strictly_over, NULL);
uint8_t out[256];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_preimage(dc, hash, out, sizeof(out));
assert_int_equal(result, -4);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -587,10 +608,12 @@ static void test_get_preimage_bad_element_size_strict(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_more_elements_bad_size_strict, NULL);
uint8_t out[512];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_preimage(dc, hash, out, sizeof(out));
assert_int_equal(result, -7);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -632,10 +655,12 @@ static void test_get_preimage_more_bytes_strict(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_more_bytes_with_padding, NULL);
uint8_t out[512];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_preimage(dc, hash, out, sizeof(out));
assert_int_equal(result, -8);
+ assert_cleared(out, sizeof(out));
}
/**
@@ -674,10 +699,12 @@ static void test_get_preimage_communication_failure(void **state) {
mock_dispatcher_set_tamper_hook(mock, tamper_fail_second_call, NULL);
uint8_t out[512];
+ memset(out, 0xEE, sizeof(out));
dispatcher_context_t *dc = mock_dispatcher_get_dc(mock);
int result = call_get_preimage(dc, hash, out, sizeof(out));
assert_true(result < 0);
+ assert_cleared(out, sizeof(out));
}
/* ---------- Main ---------- */Why this scored 51/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.