Repository navigation
Reject malformed hexadecimal in machine:readmem, machine:writemem and machine:debugreg - #888
Merged
chrisgleissner merged 3 commits intoSep 11, 2026
Conversation
strtol() yields 0 for an unparseable address, and the end pointer was passed as NULL and never checked, so a non-hex address passed the 0..65535 range check and the request was served against $0000. For writemem that is a destructive write to zero page answered with HTTP 200: the response even names "0000-..." as the address it wrote. Check the end pointer at the three handlers that parse an address -- PUT and POST machine:writemem, and GET machine:readmem -- rejecting input that consumed no digits or left trailing characters, folded into the existing range check so the error path is unchanged. 400 "Invalid address" is already the documented response at all three, so the API documentation is unaffected. An optional 0x prefix still parses; overflow needs no errno check because strtol's LONG_MAX/LONG_MIN are outside the accepted range. Cover it with a bad-address stage in the readmem/writemem E2E suite, gated through the FIXES table so machines whose released firmware predates the change skip with a named reason rather than going red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
strtol skips whitespace and takes a sign, so "-0", "+1" and " 1234" still reached an address. parse_address accepts hex digits only.
The same parser, with a byte limit. "ZZ" wrote 00 and "1FF" wrote FF, both answered HTTP 200.
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.
Cherry-pick of #884 onto
master. That PR was merged totest-merge(bce4535) but belongs on
master, so the same three commits are replayed herewith their original authorship. They applied without conflict, and all four
files are byte-identical to the merged
test-mergeresult.What it fixes
machine:readmem,machine:writemem(PUT and POST) andmachine:debugregparsed their hexadecimal parameter with
strtol(..., NULL, 16)and neverchecked the end pointer.
strtolskips leading whitespace, takes an optionalsign, and stops at the first character it cannot use, so input outside the
documented grammar parsed to something and was acted on:
readmem/writemem:ZZZZ,0xZZZZ,gggg,-0,+1and12GGallanswered HTTP 200. For
writememthat is a write to$0000reported assuccess.
machine:debugreg(machine:debugreg accepts malformed and out-of-range hexadecimal values #885): with the register set to1Ffirst,ZZ,0xZZand
-0wrote00,1Gwrote01, and1FFwas truncated toFF, eachanswered HTTP 200.
The change
One parser for all four call sites, built on the
chartohexhelper already atthe top of
route_machine.cc. It implements the documented grammar and nothingelse: hex digits, no larger than the caller's limit, no sign, no whitespace, no
0xprefix, no trailing characters. The limit is0xFFFFfor an address and0xFFfor the debug register, and it is checked inside the loop, so anover-long value is refused as it is read rather than truncated.
machine:debugreg's API doc gains the400 Invalid valueit now returns, anddoc/api/rest_api_openapi_u64.yamlis regenerated to match.openapi_checkisa build gate, so the committed document cannot drift from the source.
The one form that used to be accepted and now is not is the
0xprefix. Noclient in this repository sends it: the test harness formats
%04X, and thefirmware's own web UI uses
toString(16).padStart(4, "0")and literalfour-digit strings, with its typed-address path validating as a 16-bit hex
number first.
Tests
Two stages in
tests/e2e/api/readmem_writemem_test.py, each gated through theFIXEStable so a machine whose released firmware predates the fix skips with anamed reason:
bad-addresscovers the six address forms on all three endpoints.bad-debugregcovers the five value forms.machine:debugreghas a GET aswell as a PUT, so "the refused request wrote nothing" is asserted directly:
the register is read before and after each rejected write and has to be
unchanged. It skips where the route does not exist, both debugreg routes
being inside
#if U64.Verification on this branch
Built from
masterand JTAG-deployed to an Ultimate 64 Elite:and on an Ultimate II+L in a C64 Ultimate, flashed from this branch's CI
artifact (
git_commit_hash 27cfe033):bad-debugregskips on the cartridge, both debugreg routes being inside#if U64.freezer-audio, whichmastergained in #886, also passes on bothmachines against this build (u64 6/6 in 7.7s, u2@c64u 6/6 in 9.4s), so the
change sits cleanly on top of it.
Offline:
lint_test,registry_test,openapi_contract_test,check_transport_usage,stale_gates_test,make openapi_checkandmake openapi_test(191 tests) all pass.The red measurements above were taken on #884 by reverting each part of the
firmware in turn and redeploying. The code here is byte-identical to what was
verified there.