Skip to content

utils, random: fix slice(), and make the TESTING RNG unbuildable in release - #407

Open
xanimo wants to merge 1 commit into
dogecoinfoundation:0.1.5-devfrom
xanimo:0.1.5-dev-api-hardening
Open

utils, random: fix slice(), and make the TESTING RNG unbuildable in release#407
xanimo wants to merge 1 commit into
dogecoinfoundation:0.1.5-devfrom
xanimo:0.1.5-dev-api-hardening

Conversation

@xanimo

@xanimo xanimo commented Aug 6, 2026

Copy link
Copy Markdown
Member

Two findings from the audit sweep. Unrelated to each other except in kind: both are guards that looked like they were doing something and weren't.

slice() — unterminated, and underflows

LIBDOGECOIN_API, with no in-tree callers, which is why neither defect was noticed:

void slice(const char *str, char *result, size_t start, size_t end)
{
    strncpy(result, str + start, end - start);
}

It never terminated. strncpy writes no NUL when it copies its full count — and the count here is always exactly the number of bytes copied. So result came back unterminated for every input, and a caller doing slice(s, buf, 0, 3); printf("%s", buf); reads past the buffer.

And end < start underflows. end - start is size_t, so it wraps to a length near SIZE_MAX and strncpy runs until it hits the source NUL or faults.

Now it measures the source, clamps end to it, rejects end <= start and a start past the end, copies with memcpy, and terminates. The header records the contract the function never stated: result needs (end - start) + 1 bytes, since the signature carries no size for it.

Verified, not assumed

With slice() reverted to the original one-liner, the new test fails at the first assertion:

FAILED - test_utils_slice() - Line 156
	Expect: 	cde
	Receive:	cdeXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

That trailing XXXX… is the buffer's prior contents — the missing terminator, made visible.

Tests cover: an ordinary slice, the whole string, end < start, end == start, start past the end, end past the end, and NULL arguments.

The TESTING RNG branch

src/random.c carries a branch that replaces the CSPRNG with srand(time(NULL)) and rand().

Nothing in the build system defines TESTING — not CMakeLists.txt, not configure.ac. It's unreachable today, and this PR doesn't change that. What it adds is an #error when TESTING is combined with NDEBUG, so a release build that somehow enabled it fails loudly at compile time rather than quietly generating keys guessable from the wall clock.

A dormant path that swaps out the RNG should be impossible to ship, not merely unlikely to be reached. I've left the branch in place rather than deleting it, in case something out-of-tree uses it — deleting it is also defensible and I'm happy to do that instead if you'd prefer.


82/82, clean under ASan.

Note this will show red on cppcheck until #403 lands — that job fails on 0.1.5-dev itself, not on this branch.

…elease

Two findings from an audit sweep, unrelated except that both are guards
that were not doing what they appeared to do.

slice() is LIBDOGECOIN_API and had no in-tree callers, which is why
neither of its defects was noticed:

    strncpy(result, str + start, end - start);

strncpy writes no terminator when it copies its full count, and the count
here was always exactly the number of bytes copied -- so result came back
unterminated for every input, and a caller printing it read past the
buffer. Separately, `end - start` is size_t arithmetic, so end < start
wrapped to a length near SIZE_MAX and strncpy ran until it hit the source
NUL or faulted.

Now measures the source, clamps end to it, rejects end <= start and a
start past the end, copies with memcpy and terminates. The header records
the contract the function never stated: result needs (end - start) + 1
bytes, since the signature carries no size for it.

Verified the test catches the original rather than merely passing with
the fix: with slice() reverted, test_utils_slice fails at the first
assertion with

    Expect:  cde
    Receive: cdeXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX...

which is the unterminated buffer.

Separately, src/random.c carries a TESTING branch that replaces the
CSPRNG with srand(time(NULL)) and rand(). Nothing in the build system
defines TESTING -- not CMakeLists.txt, not configure.ac -- so it is
unreachable today, and this does not change that. It adds an #error when
TESTING is combined with NDEBUG, so that a release build which somehow
enabled it fails loudly at compile time instead of quietly generating
keys guessable from the wall clock. A dormant path that swaps out the RNG
should be impossible to ship, not merely unlikely to be reached.

82/82, clean under ASan.
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