Skip to content

protocol: Bound the job-validation parser and guard clz(0) - #236

Open
jasonsopko wants to merge 2 commits into
OCEAN-xyz:masterfrom
jasonsopko:datum-parser-bounds-oc
Open

jasonsopko wants to merge 2 commits into
OCEAN-xyz:masterfrom
jasonsopko:datum-parser-bounds-oc

Conversation

@jasonsopko

Copy link
Copy Markdown

What

Two out-of-bounds reads in the DATUM protocol parser, both reachable from the pool the gateway connects to. Same code as CONVOY, where these went up as separate PRs.

  • datum_protocol_job_validation_cmd() read the subcommand byte before its len < 2 check and passed no length to the stxlist, stxlist-by-id and sblock handlers. _stxlist_byid() then read a three-byte header and two bytes per requested id, the id count bounded only against the template's transaction count, never against the received length. A short or crafted request reads past the frame, and with a valid job the surplus is copied into the reply and sent back to the pool.
  • roundDownToPowerOfTwo_64(0) evaluated 1ULL << (63 - __builtin_clzll(0)); __builtin_clzll(0) is undefined. floorPoT() next to it already guarded its zero case. Reached through a server minimum-difficulty of 0 and through the vardiff path.

Why

datum_protocol_mining_cmd5() dispatches with cmd_len taken from the server's header, and the receive loop delivers exactly that many bytes, which can be as few as one, so the handlers read frames shorter than they assume. server_recv_buffer is a large static array, so on x86 these are reads of stale bytes rather than faults, but they are out-of-bounds reads of attacker-influenced length in a network parser.

How I tested

Found by a libFuzzer harness over datum_protocol_mining_cmd5 on the CONVOY tree; the same functions are byte-identical here. Built this fork with gcc -Wall -Werror, datum_gateway --test passes, and the three reproducing inputs replay clean under AddressSanitizer and UndefinedBehaviorSanitizer with the fixes. Each guard is a return 0 on a frame too short for what the code then reads, a no-op on well-formed traffic.

Risk and rollback

Valid pool traffic takes the same path. Revert either commit independently.

datum_protocol_job_validation_cmd() read the subcommand byte before its
length check, and passed no length to the stxlist, stxlist-by-id and
sblock handlers, so they read the DATUM server's frame without knowing
how many bytes arrived. stxlist-by-id read a three-byte header and then
two bytes per requested id, the id count bounded only against the
template's transaction count, never against the received bytes: a short
or crafted request reads past the frame and, with a valid job, streams
the surplus back to the pool. The reply side of the same exchange was
bounded already; this is the request side.

Check the length before reading the subcommand byte, pass the remaining
length to each handler, reject a short header, and stop the id loop
before it reads past what arrived.

Found by the new fuzz_protocol_cmd5 harness.
1ULL << (63 - __builtin_clzll(x)) is undefined for x == 0, since
__builtin_clzll(0) is undefined. floorPoT next to it already guards its
zero case. A DATUM server minimum-difficulty of 0 reaches it through
client configuration, and the vardiff path reaches it from miner input.

Return 0 for 0.

Found by the new fuzz_protocol_cmd5 harness.
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