From e96517d079deca4d95cfee1c1a29ce11a3f8203c Mon Sep 17 00:00:00 2001 From: bluezr Date: Thu, 6 Aug 2026 14:06:25 -0700 Subject: [PATCH 1/2] such: stop writing through a string literal in the -e path `such -c generate_mnemonic -e ` segfaults. Every time, on a documented flag. char* entropy_size = "256"; ... sprintf(entropy_size, "%zu", strlen(entropy) / HEX_CHARS_PER_BYTE * 8); entropy_size points at a string literal, and string literals live in read-only memory, so the sprintf faults before it writes anything. Reproduced directly: $ such -c generate_mnemonic -e 000102...1f Segmentation fault (exit 139) and with a four-line C program doing the same thing, which also takes SIGSEGV, confirming the mechanism rather than inferring it. There is a second defect underneath the first. "256" is four bytes including its terminator, while the value derived here is strlen(optarg) / 2 * 8 -- unbounded, since optarg is a command-line argument -- so even against a writable four-byte buffer this overflows for any -e argument longer than 128 hex characters. The crash simply arrives first. entropy_size cannot become an array: the -z case assigns optarg to it, so it has to stay a pointer. Adds a local buffer for the -e case to derive into, repoints entropy_size at it, and uses snprintf. 21 bytes holds any size_t in decimal plus a terminator. Verified across the paths that share this variable: -e <32 hex chars> 12 words (was: segfault) -e <64 hex chars> 24 words (was: segfault) -z 128 12 words no flags 24 words The 128-bit and 256-bit runs agree on their first eleven words, which is what should happen when the shorter entropy is a prefix of the longer -- evidence the value is being used, not merely that the crash stopped. Not present in v0.1.2, v0.1.3 or v0.1.4. Present in v0.1.5-pre, which is the current release on GitHub. 81/81. --- src/cli/such.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/cli/such.c b/src/cli/such.c index b48995251..3de757ce5 100644 --- a/src/cli/such.c +++ b/src/cli/such.c @@ -1210,6 +1210,11 @@ int main(int argc, char* argv[]) char* mnemonic_in = 0; char* pass = 0; char* entropy = 0; + /* Points at a literal by default and is reassigned to optarg for -z, so it + must never be written through. The -e path needs somewhere writable to + derive a bit count into; this is it. 21 bytes holds any size_t in + decimal plus a terminator. */ + char entropy_size_buf[21]; char* entropy_size = "256"; MNEMONIC mnemonic = {0}; SEED seed = {0}; @@ -1252,7 +1257,13 @@ int main(int argc, char* argv[]) return showError("Parameter -e cannot be used with -y"); entropy = optarg; if (entropy != NULL){ - sprintf(entropy_size, "%zu", strlen(entropy) / HEX_CHARS_PER_BYTE * 8); + /* entropy_size still points at the "256" literal here. + sprintf'ing through it wrote to read-only memory and + crashed on every use of -e. Derive into the local + buffer and repoint instead. */ + snprintf(entropy_size_buf, sizeof(entropy_size_buf), "%zu", + strlen(entropy) / HEX_CHARS_PER_BYTE * 8); + entropy_size = entropy_size_buf; } break; From 1a7ddd512de2deb1c32e922fbe965808ccc7b605 Mon Sep 17 00:00:00 2001 From: bluezr Date: Tue, 11 Aug 2026 13:06:01 -0700 Subject: [PATCH 2/2] such: size the -e entropy buffer from ENTROPY_SIZE_STRING_SIZE The bit count is strlen(optarg) / HEX_CHARS_PER_BYTE * 8 with no bound, so a long -e gives four or more digits. snprintf truncates rather than overflows: "1024" becomes "102" and a wrong bit count reaches mnemonic generation. Bound entropy to MAX_ENTROPY_STRING_SIZE - 1 first. That caps the value at MAX_ENTROPY_BITS, so three digits plus a terminator is exact. 83/83, and 64 hex characters still produces a mnemonic through the CLI. --- src/cli/such.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/cli/such.c b/src/cli/such.c index 3de757ce5..61522501f 100644 --- a/src/cli/such.c +++ b/src/cli/such.c @@ -1210,11 +1210,9 @@ int main(int argc, char* argv[]) char* mnemonic_in = 0; char* pass = 0; char* entropy = 0; - /* Points at a literal by default and is reassigned to optarg for -z, so it - must never be written through. The -e path needs somewhere writable to - derive a bit count into; this is it. 21 bytes holds any size_t in - decimal plus a terminator. */ - char entropy_size_buf[21]; + /* entropy_size points at a literal, so -e needs somewhere writable. + + 1 for the terminator ENTROPY_SIZE_STRING_SIZE does not include. */ + char entropy_size_buf[ENTROPY_SIZE_STRING_SIZE + 1]; char* entropy_size = "256"; MNEMONIC mnemonic = {0}; SEED seed = {0}; @@ -1257,12 +1255,16 @@ int main(int argc, char* argv[]) return showError("Parameter -e cannot be used with -y"); entropy = optarg; if (entropy != NULL){ - /* entropy_size still points at the "256" literal here. - sprintf'ing through it wrote to read-only memory and - crashed on every use of -e. Derive into the local - buffer and repoint instead. */ + /* Bound first: unbounded optarg gives four or more + digits and snprintf truncates rather than overflows. */ + size_t entropy_hex_len = strlen(entropy); + if (entropy_hex_len > MAX_ENTROPY_STRING_SIZE - 1) + return showError("Parameter -e exceeds the maximum entropy size"); + + /* sprintf through the "256" literal wrote to read-only + memory and crashed on every use of -e. */ snprintf(entropy_size_buf, sizeof(entropy_size_buf), "%zu", - strlen(entropy) / HEX_CHARS_PER_BYTE * 8); + entropy_hex_len / HEX_CHARS_PER_BYTE * 8); entropy_size = entropy_size_buf; }