internal: mark internal functions as hidden visibility when possible
What changed, and why it matters
This change is a hardening patch for a cryptocurrency wallet library. It tells the compiler to keep certain internal helper functions private to the library, so calls to them stay inside the library instead of going through an external lookup table (the PLT). The commit message says the old route could cause CPU register values containing secret key material to be temporarily saved to the stack, where they might be more easily exposed in a crash dump or memory leak. The patch reduces that risk but does not by itself fix a specific, independently reported vulnerability.
Treat as a worthwhile hardening patch. Review whether any other non-static internal functions were missed, verify the macro is correctly honored by the toolchain, and consider backporting to release branches. No immediate incident response is warranted because no specific vulnerability or exploit is disclosed.
Security signals we found
Hardening against secret material leakage via stack spills
Compiler/linker visibility hardening
PLT/GOT indirection reduction
Defense-in-depth for cryptographic helper functions
Evidence from the diff
The commit adds a WALLY_INTERNAL_API macro that marks non-static internal functions with attribute((visibility(“hidden”))) on non-Windows builds when WALLY_EXPORT_ALL is not set. It applies this attribute to roughly 40 internal helpers in internal.h, mnemonic.h, and wordlist.h (e.g., secp_ctx, wally_clear, pubkey_, seckey_, keypair_, map_, wordlist_, mnemonic_). It also defines WALLY_EXPORT_ALL=1 when configure.ac’s export_all option is enabled, preserving the old behavior for test/debug builds. The stated goal is to avoid PLT indirection for internal calls, which can spill register-held secrets to the stack. This is a defense-in-depth change; no specific bug, CVE, or exploit is described in the commit or supplied references.
Changed components
libwally-core internal API surfacesrc/internal.hsrc/mnemonic.hsrc/wordlist.hsrc/ccan_config.hconfigure.ac build configurationInspect captured patch +78 / −55
diff --git a/configure.ac b/configure.ac
index 9dd4fa5..7fa0a66 100644
--- a/configure.ac
+++ b/configure.ac
@@ -236,6 +236,8 @@ AM_CONDITIONAL([EXPORT_ALL], [test "x$export_all" = "xyes"])
if test "x$export_all" != "xyes"; then
AX_CHECK_COMPILE_FLAG([-fvisibility=hidden], [AM_CFLAGS="$AM_CFLAGS -fvisibility=hidden"])
+else
+ AX_CHECK_COMPILE_FLAG([-DWALLY_EXPORT_ALL=1], [AM_CFLAGS="$AM_CFLAGS -DWALLY_EXPORT_ALL=1"])
fi
# Assume we have no unaligned access if cross-compiling
diff --git a/src/ccan_config.h b/src/ccan_config.h
index c660937..db347ac 100644
--- a/src/ccan_config.h
+++ b/src/ccan_config.h
@@ -59,7 +59,11 @@
#define CCAN_CRYPTO_SHA512_USE_MBEDTLS 1
#endif
+#if !defined(WALLY_EXPORT_ALL) && !defined(_WIN32)
+void __attribute__ ((visibility ("hidden"))) wally_clear(void *p, size_t len);
+#else
void wally_clear(void *p, size_t len);
+#endif
#define CCAN_CLEAR_MEMORY(p, len) wally_clear(p, len)
diff --git a/src/internal.h b/src/internal.h
index 70a93d0..2e26e02 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -16,6 +16,14 @@
#endif /* __clang__/__GNUC__ */
#endif /* BUILD_ELEMENTS */
#endif /* WALLY_ABI_NO_ELEMENTS */
+
+#if !defined(WALLY_EXPORT_ALL) && !defined(_WIN32)
+/* Ensure local symbols are resolved internally */
+#define WALLY_INTERNAL_API __attribute__ ((visibility ("hidden")))
+#else
+#define WALLY_INTERNAL_API
+#endif
+
#include <secp256k1.h>
#include <secp256k1_recovery.h>
#include <secp256k1_extrakeys.h>
@@ -30,48 +38,52 @@
#include <string.h>
/* Fetch an internal secp context */
-const secp256k1_context *secp_ctx(void);
+WALLY_INTERNAL_API const secp256k1_context *secp_ctx(void);
#define secp256k1_context_destroy(c) _do_not_destroy_shared_ctx_pointers(c)
/* secp pub/priv key functions */
#define pubkey_create secp256k1_ec_pubkey_create
#define pubkey_tweak_add secp256k1_ec_pubkey_tweak_add
-int pubkey_combine(secp256k1_pubkey *pubnonce, const secp256k1_pubkey *const *pubnonces, size_t n);
-int pubkey_negate(secp256k1_pubkey *pubkey);
-int pubkey_parse(secp256k1_pubkey *pubkey, const unsigned char *input, size_t input_len);
-int pubkey_serialize(unsigned char *output, size_t *outputlen, const secp256k1_pubkey *pubkey, unsigned int flags);
+WALLY_INTERNAL_API int pubkey_combine(secp256k1_pubkey *pubnonce, const secp256k1_pubkey *const *pubnonces, size_t n);
+WALLY_INTERNAL_API int pubkey_negate(secp256k1_pubkey *pubkey);
+WALLY_INTERNAL_API int pubkey_parse(secp256k1_pubkey *pubkey, const unsigned char *input, size_t input_len);
+WALLY_INTERNAL_API int pubkey_serialize(unsigned char *output, size_t *outputlen, const secp256k1_pubkey *pubkey, unsigned int flags);
/* Note xpubkey_parse accepts standard compressed pubkeys as well as x-only */
-int xpubkey_parse(secp256k1_xonly_pubkey *pubkey, const unsigned char *input, size_t input_len);
-int xpubkey_tweak_add(secp256k1_pubkey *pubkey, const secp256k1_xonly_pubkey *xpubkey, const unsigned char *tweak);
-int xpubkey_serialize(unsigned char *output, const secp256k1_xonly_pubkey *xpubkey);
-int seckey_verify(const unsigned char *seckey);
-int seckey_negate(unsigned char *seckey);
-int seckey_tweak_add(unsigned char *seckey, const unsigned char *tweak);
-int seckey_tweak_mul(unsigned char *seckey, const unsigned char *tweak);
-int keypair_create(secp256k1_keypair *keypair, const unsigned char *priv_key);
-int keypair_xonly_pub(secp256k1_xonly_pubkey *xpubkey, const secp256k1_keypair *keypair);
-int keypair_sec(unsigned char *output, const secp256k1_keypair *keypair);
-int keypair_xonly_tweak_add(secp256k1_keypair *keypair, const unsigned char *tweak);
+WALLY_INTERNAL_API int xpubkey_parse(secp256k1_xonly_pubkey *pubkey, const unsigned char *input, size_t input_len);
+WALLY_INTERNAL_API int xpubkey_tweak_add(secp256k1_pubkey *pubkey, const secp256k1_xonly_pubkey *xpubkey, const unsigned char *tweak);
+WALLY_INTERNAL_API int xpubkey_serialize(unsigned char *output, const secp256k1_xonly_pubkey *xpubkey);
+WALLY_INTERNAL_API int seckey_verify(const unsigned char *seckey);
+WALLY_INTERNAL_API int seckey_negate(unsigned char *seckey);
+WALLY_INTERNAL_API int seckey_tweak_add(unsigned char *seckey, const unsigned char *tweak);
+WALLY_INTERNAL_API int seckey_tweak_mul(unsigned char *seckey, const unsigned char *tweak);
+WALLY_INTERNAL_API int keypair_create(secp256k1_keypair *keypair, const unsigned char *priv_key);
+WALLY_INTERNAL_API int keypair_xonly_pub(secp256k1_xonly_pubkey *xpubkey, const secp256k1_keypair *keypair);
+WALLY_INTERNAL_API int keypair_sec(unsigned char *output, const secp256k1_keypair *keypair);
+WALLY_INTERNAL_API int keypair_xonly_tweak_add(secp256k1_keypair *keypair, const unsigned char *tweak);
#define PUBKEY_COMPRESSED SECP256K1_EC_COMPRESSED
#define PUBKEY_UNCOMPRESSED SECP256K1_EC_UNCOMPRESSED
-void wally_clear(void *p, size_t len);
-void wally_clear_2(void *p, size_t len, void *p2, size_t len2);
-void wally_clear_3(void *p, size_t len, void *p2, size_t len2,
- void *p3, size_t len3);
-void wally_clear_4(void *p, size_t len, void *p2, size_t len2,
- void *p3, size_t len3, void *p4, size_t len4);
+WALLY_INTERNAL_API void wally_clear(void *p, size_t len);
+WALLY_INTERNAL_API void wally_clear_2(void *p, size_t len,
+ void *p2, size_t len2);
+WALLY_INTERNAL_API void wally_clear_3(void *p, size_t len,
+ void *p2, size_t len2,
+ void *p3, size_t len3);
+WALLY_INTERNAL_API void wally_clear_4(void *p, size_t len,
+ void *p2, size_t len2,
+ void *p3, size_t len3,
+ void *p4, size_t len4);
-void clear_and_free(void *p, size_t len);
-void clear_and_free_bytes(unsigned char **p, size_t *len);
+WALLY_INTERNAL_API void clear_and_free(void *p, size_t len);
+WALLY_INTERNAL_API void clear_and_free_bytes(unsigned char **p, size_t *len);
-bool mem_is_zero(const void *mem, size_t len);
+WALLY_INTERNAL_API bool mem_is_zero(const void *mem, size_t len);
/* Fetch our internal operations function pointers */
-const struct wally_operations *wally_ops(void);
+WALLY_INTERNAL_API const struct wally_operations *wally_ops(void);
#ifndef BUILD_AMALGAMATION
#define malloc(size) __use_wally_malloc_internally__
@@ -97,32 +109,34 @@ const struct wally_operations *wally_ops(void);
if (!*output) return WALLY_ENOMEM
/* Helpers for operating on byte buffers */
-bool clone_data(void **dst, const void *src, size_t len);
-bool clone_bytes(unsigned char **dst, const unsigned char *src, size_t len);
-int replace_bytes(const unsigned char *bytes, size_t bytes_len,
- unsigned char **bytes_out, size_t *bytes_len_out);
-void *array_realloc(const void *src, size_t old_n, size_t new_n, size_t size);
+WALLY_INTERNAL_API bool clone_data(void **dst, const void *src, size_t len);
+WALLY_INTERNAL_API bool clone_bytes(unsigned char **dst, const unsigned char *src, size_t len);
+WALLY_INTERNAL_API int replace_bytes(const unsigned char *bytes, size_t bytes_len,
+ unsigned char **bytes_out, size_t *bytes_len_out);
+WALLY_INTERNAL_API void *array_realloc(const void *src, size_t old_n,
+ size_t new_n, size_t size);
-int array_grow(void **src, size_t num_items, size_t *allocation_len,
- size_t item_size);
+WALLY_INTERNAL_API int array_grow(void **src, size_t num_items,
+ size_t *allocation_len, size_t item_size);
struct ext_key;
/* Internal: Create a partial bip32 key from a private key (no chaincode, un-derivable) */
-int bip32_key_from_private_key(uint32_t version, const unsigned char *priv_key,
- size_t priv_key_len, struct ext_key *output);
+WALLY_INTERNAL_API int bip32_key_from_private_key(uint32_t version,
+ const unsigned char *priv_key,
+ size_t priv_key_len,
+ struct ext_key *output);
struct wally_map;
-int map_add(struct wally_map *map_in,
- const unsigned char *key, size_t key_len,
- const unsigned char *value, size_t value_len,
- bool take_value, bool ignore_dups);
-int map_add_preimage_and_hash(struct wally_map *map_in,
- const unsigned char *key, size_t key_len,
- const unsigned char *val, size_t val_len,
- size_t type, bool skip_verify);
-const struct wally_map_item *map_find_equal_integer(const struct wally_map *lhs,
- const struct wally_map *rhs,
- uint32_t key);
+WALLY_INTERNAL_API int map_add(struct wally_map *map_in,
+ const unsigned char *key, size_t key_len,
+ const unsigned char *value, size_t value_len,
+ bool take_value, bool ignore_dups);
+WALLY_INTERNAL_API int map_add_preimage_and_hash(struct wally_map *map_in,
+ const unsigned char *key, size_t key_len,
+ const unsigned char *val, size_t val_len,
+ size_t type, bool skip_verify);
+WALLY_INTERNAL_API const struct wally_map_item *map_find_equal_integer(
+ const struct wally_map *lhs, const struct wally_map *rhs, uint32_t key);
/* Clamp input/output allocation sizing to standard tx sizes for BTC.
* Liquid numbers are smaller; we use the upper limit */
@@ -134,9 +148,9 @@ const struct wally_map_item *map_find_equal_integer(const struct wally_map *lhs,
/* Allows allocating a larger witness for e.g deserializing */
struct wally_tx_witness_stack;
-int tx_witness_stack_init_alloc(size_t allocation_len,
- size_t max_allocation_len,
- struct wally_tx_witness_stack **output);
+WALLY_INTERNAL_API int tx_witness_stack_init_alloc(size_t allocation_len,
+ size_t max_allocation_len,
+ struct wally_tx_witness_stack **output);
/* Absolute maximum number of inputs and outputs for BTC.
* Liquid numbers are smaller; we use the upper limit */
diff --git a/src/mnemonic.h b/src/mnemonic.h
index 19719f9..f8eccb2 100644
--- a/src/mnemonic.h
+++ b/src/mnemonic.h
@@ -1,6 +1,8 @@
#ifndef LIBWALLY_MNEMONIC_H
#define LIBWALLY_MNEMONIC_H
+#include "internal.h"
+
struct words;
/**
@@ -12,7 +14,7 @@ struct words;
*
* @bytes must be an even multiple of the number of bits in the wordlist used.
*/
-char *mnemonic_from_bytes(
+WALLY_INTERNAL_API char * mnemonic_from_bytes(
const struct words *w,
const unsigned char *bytes,
size_t len);
@@ -26,7 +28,7 @@ char *mnemonic_from_bytes(
* @len: The length of @bytes_out in bytes.
* @written: Destination for the number of bytes written to ``bytes_out``.
*/
-int mnemonic_to_bytes(
+WALLY_INTERNAL_API int mnemonic_to_bytes(
const struct words *w,
const char *mnemonic,
unsigned char *bytes_out,
diff --git a/src/wordlist.h b/src/wordlist.h
index c818967..622c5fd 100644
--- a/src/wordlist.h
+++ b/src/wordlist.h
@@ -3,6 +3,7 @@
#include <stdbool.h>
#include <stddef.h>
+#include "internal.h"
/**
* struct words- structure representing a parsed list of words
@@ -30,7 +31,7 @@ struct words {
*
* The returned structure contains a copy of @text.
*/
-struct words *wordlist_init(const char *text);
+WALLY_INTERNAL_API struct words *wordlist_init(const char *text);
/**
* Find a word in a wordlist.
@@ -41,7 +42,7 @@ struct words *wordlist_init(const char *text);
* Returns 0 if not found, idx + 1 otherwise.
* @see wordlist_init.
*/
-size_t wordlist_lookup_word(
+WALLY_INTERNAL_API size_t wordlist_lookup_word(
const struct words *w,
const char *word);
@@ -53,7 +54,7 @@ size_t wordlist_lookup_word(
*
* Returns NULL if not found, the word otherwise.
*/
-const char *wordlist_lookup_index(
+WALLY_INTERNAL_API const char *wordlist_lookup_index(
const struct words *w,
size_t idx);
@@ -61,6 +62,6 @@ const char *wordlist_lookup_index(
* wordlist_free - Free a words structure.
* @w: structure to free.
*/
-void wordlist_free(struct words *w);
+WALLY_INTERNAL_API void wordlist_free(struct words *w);
#endif /* LIBWALLY_WORDLIST_H */
Why this scored 47/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.