Skip to content

such: stop writing through a string literal in the -e path - #411

Open
xanimo wants to merge 1 commit into
dogecoinfoundation:0.1.5-devfrom
xanimo:0.1.5-dev-such-entropy-size
Open

such: stop writing through a string literal in the -e path#411
xanimo wants to merge 1 commit into
dogecoinfoundation:0.1.5-devfrom
xanimo:0.1.5-dev-such-entropy-size

Conversation

@xanimo

@xanimo xanimo commented Aug 6, 2026

Copy link
Copy Markdown
Member

such -c generate_mnemonic -e <hex> 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. Literals live in read-only memory, so the sprintf faults before it writes anything.

Reproduced directly rather than inferred:

$ such -c generate_mnemonic -e 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
Segmentation fault          (exit 139)

and with a four-line C program doing the same thing, which also takes SIGSEGV — confirming the mechanism, not just the symptom.

There's a second defect underneath the first

"256" is four bytes including its terminator. The value derived here is strlen(optarg) / 2 * 8 — and optarg is a command-line argument, so it's unbounded. Even against a writable four-byte buffer this overflows for any -e argument longer than 128 hex characters:

-e length derived value digits
64 256 3
1,000 4,000 4
1,000,000,000 4,000,000,000 10

The crash just arrives first, which is why the overflow was never observed.

The fix

entropy_size can't become an array — the -z case assigns optarg to it, so it has to stay a pointer. So this 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.

Verification

invocation result
-e <32 hex> 12 words (was: segfault)
-e <64 hex> 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 actually being used, not merely that the crash stopped.

Affected versions

Not 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. Found via CodeQL's cpp/overrunning-write, which flagged the buffer size — the read-only write turned out to be the more immediate problem.

`such -c generate_mnemonic -e <hex>` 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant