Validate mining.notify fields before use - #1932
Open
Raoulito wants to merge 1 commit into
Open
Conversation
parse_mining_notify() checked params[0] with cJSON_IsString but used params
1, 2, 3, 5, 6, 7 and the merkle branch entries without any type check:
new_work->prev_block_hash = strdup(cJSON_GetArrayItem(params, 1)->valuestring);
new_work->coinbase_1 = strdup(cJSON_GetArrayItem(params, 2)->valuestring);
new_work->coinbase_2 = strdup(cJSON_GetArrayItem(params, 3)->valuestring);
...
hex2bin(cJSON_GetArrayItem(merkle_branch, i)->valuestring, ...);
new_work->version = strtoul(cJSON_GetArrayItem(params, 5)->valuestring, NULL, 16);
cJSON leaves valuestring NULL for any item that is not a string, and strdup(),
hex2bin() and strtoul() all dereference their argument immediately. The
params_count < 8 guard only ensures the items exist, not that they are strings,
so a mining.notify with eight parameters where one of these is a number, bool,
null, object or array dereferences address 0.
On the ESP32-S3 that is an unhandled LoadProhibited exception and a reboot:
Running Parse stratum mining.notify rejects non-string params...
Guru Meditation Error: Core 0 panic'ed (LoadProhibited).
Rebooting...
Add a require_string_param() helper and use it for every string field, and
type-check the merkle branch entries.
Separately, calculate_coinbase_tx_hash() sized a variable length array from the
pool-supplied coinbase strings:
size_t coinbase_tx_bin_len = (len1 + len2 + len3 + len4) / 2;
uint8_t coinbase_tx_bin[coinbase_tx_bin_len];
Nothing bounded those strings. The receive path grows its buffer until
allocation fails, and with CONFIG_SPIRAM_USE_MALLOC and
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 the large allocations land in PSRAM,
so the strings can be far larger than the 8 KB stack of the calling task.
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY only inspects the canary at a
context switch and there is no end-of-stack watchpoint, so an overshoot
corrupts adjacent DRAM before anything notices.
Move that buffer to the heap, matching calculate_coinbase_tx_hash_bin() in the
same file, and bound each coinbase half at MAX_COINBASE_HEX_LEN in the parser.
Real coinbase transactions are a few hundred hex characters.
Also restructure the allocation so validation happens before any allocation,
which removes the four repeated free() blocks and their divergence risk.
Tests: adds coverage for a non-string at each affected position, for a
non-string merkle branch entry, and for an oversized coinbase. Verified that
every new case panics the device before this change and passes after it.
74 tests, 0 failures.
Raoulito
force-pushed
the
fix/stratum-notify-input-validation
branch
from
August 30, 2026 09:23
0364be5 to
50dd69a
Compare
Collaborator
|
We're handing all stratum protocol issues in PR #1897. Can you verify this against that PR, please? |
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I run a Supra 401 and went digging through the stratum parser this weekend. Turns out a single malformed
mining.notifyreboots the miner. No big payload needed, no timing trick.parse_mining_notify()checksparams[0]properly but then just uses the rest :cJSON sets
valuestringto NULL for anything that is not a string, andstrdup,strtoulandhex2binall dereference straight away. Theparams_count < 8check only tells you the items are there, not that they are strings. So a notify with 8 params where one of those is a number or a bool or null goes and reads address 0.I compiled a small thing against
managed_components/espressif__cjsonto be sure I was not imagining it :Same thing with
null,true,{}.The test cases I added crash current master. Built test-ci and ran it in QEMU :
LoadProhibited is the NULL read. If you want to see it yourself :
While I was in there I noticed
calculate_coinbase_tx_hash()builds a VLA out of the same unchecked strings :Nothing bounds the coinbase anywhere.
realloc_json_buffer()keeps growing until malloc fails, and withCONFIG_SPIRAM_USE_MALLOC=yandALWAYSINTERNAL=16384the big allocations go to PSRAM, so those strings can end up much bigger than the 8 KB stack ofcreate_jobs_task. The canary is only checked at a context switch andWATCHPOINT_END_OF_STACKis off, so you would corrupt DRAM before anything complains. This one needs a hostile or really broken pool, so it is less urgent than the NULL read, but it sits in the same function.calculate_coinbase_tx_hash_bin()a few lines below already does it the right way with malloc and a NULL check, so I just made the V1 path match.What I changed :
require_string_param()helper, used for all seven string fields, same asparams[0]was already doinghex2binMAX_COINBASE_HEX_LEN(8192 hex chars). solo.ckpool.org sends me 116 and 290, so 406 combined, which leaves plenty of roomfree()blocks and their one shared exit pathNo change to rounding, byte order or hashing. Only validation.
Tests in
test_stratum_json.ccover a non-string at each position (1, 2, 3, 5, 6, 7), a non-string merkle entry, and an oversized coinbase. All of them panic before the patch and pass after. 74 tests, 0 failures, and the 27 existing parser tests plus thetest_miningones still pass so moving to the heap did not change any results.Builds clean on ESP-IDF v6.0.2 for esp32s3, no new warnings, and it has been running on my 401 at 500 MHz against solo.ckpool with no rejects.
Two things I did not do : I never set up a hostile pool to actually trigger the VLA on hardware, and I only own the one board so I have not tried this on a Hex or a Gamma. Also
MAX_COINBASE_HEX_LENis a guess on my side, if someone knows a pool with a bigger coinbase that number should change.