Fix RAM overflow from a crafted TZRULES.BIN, plus seven smaller bounds / ISR-safety bugs#7
Draft
peterlewis wants to merge 8 commits into
Draft
Fix RAM overflow from a crafted TZRULES.BIN, plus seven smaller bounds / ISR-safety bugs#7peterlewis wants to merge 8 commits into
peterlewis wants to merge 8 commits into
Conversation
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 6, 2026
mk4-time/main.c conflicted whole-file (EOL mismatch between the PR branches); resolved to the validated rollup integration. The final integration-delta commit records the exact residue of the whole union against the PR branches. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 6, 2026
The exact residue between the pure union of the five draft PRs (mitxela#5 $PMTXTS, mitxela#6 astro pack, mitxela#7 hardening, mitxela#8 sidereal/solar, mitxela#9 tempcomp + significance-fade + tc_seed — mitxela#5 and mitxela#6 arrive as ancestors of mitxela#9 and mitxela#8) and the rollup tree that has been emulator-verified and hardware-run: - astro.c/astro.h/test_astro.c: rollup astro refinements not yet folded back into the refreshed PR mitxela#6/mitxela#8 heads (candidates for a future PR update) - version.c: rollup version string - qspi/output/*.bin: rollup's built firmware images - .settings IDE noise After this commit the megapack tree is BYTE-IDENTICAL to the previously validated rollup (feaf970) while the merge ancestry proves containment of all five PR heads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 7, 2026
The committed date-board image predated the current tree; rebuilt so both flash images (fwt + fwd) are products of exactly this source, including the PR mitxela#7 date-board hardening. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 7, 2026
The date board carries the CMD_LOAD_TEXT OOB fix (mitxela#7) over mitxela's shipped 0.0.1, so it's a new release — bump mk4-date VERSION_STRING 0.0.1 -> 0.0.2 and rebuild fwd.bin (mirrors the time board's 0.0.4 -> 0.0.5). Also commits the current fwt.bin (0.0.5, $PMTXTS + SOF correlation + audit fixes) so rollup's qspi/output/*.bin both match their source. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With `pps = on` in config.txt, emit one proprietary NMEA sentence per PPS edge over the existing CDC stream, carrying the sub-second phase captured at the edge plus calibration / holdover / die-temperature telemetry. Time-critical fields are snapshotted in the PPS ISR; formatting and CDC submission happen in the main loop, serialised against the NMEA passthrough. With no enumerated host the record is dropped before any formatting. mk4-time/Core/Src/main.c: capturePPS(), emitPPSTimestamp(), measure_temp(), a `pps` config key and one main-loop hook. qspi/config.txt documents the key. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ver USB Experimental attack on the ~6ms host-driven USB read jitter that swamps the clock's true ~180us precision — without a hardware PPS wire. The OTG_FS core rules out mitxela's last-microsecond FIFO injection, so instead we let the host anchor each PPS to a USB Start-Of-Frame, whose own arrival time the host can read in hardware (macOS IOKit GetBusFrameNumberWithTime). - Enable DWT->CYCCNT (free-running 12.5ns core-cycle counter) as the timebase both the PPS edge and each SOF are latched against. Unaffected by tempcomp SysTick steering (counts raw core clock), so it's a stable monotonic ruler. - Enable the USB SOF interrupt; PCD_SOFCallback latches (DWT, 11-bit frame from DSTS[13:8]) every 1ms, as its first act for minimal latency. - capturePPS() latches DWT at the edge; emitPPSTimestamp() appends the tail ,dwt_pps,sof_frame,dwt_sof to $PMTXTS (read under __disable_irq, no torn read). - Bump NMEA_BUF_SIZE 90->128 for the longer sentence. Host places the edge at hostTime(sof_frame) + (dwt_pps-dwt_sof)/f_dwt, immune to delivery lateness; dwt_pps deltas self-calibrate f_dwt (no core-clock assumption). Backward-compatible: lenient $PMTXTS parsers read the first 9 fields. Bench-only; +1kHz SOF ISR (~0.05% CPU) is the cost to watch. Builds clean (0 errors). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adversarial audit of the SOF-correlation change surfaced two real issues: - Stale first anchor: pps_sof_dwt/frame init to 0, so the first PPS emitted after enumeration (or with pps just toggled on, or on the emulator which has no USB SOF) carried a (0,0) anchor → a ~53s/−729ms host error on that record. Now a pps_sof_valid flag gates the tail: emit the plain 9-field sentence until a real SOF has latched. Backward-compatible; also makes the emulator correct. - SOF work ran unconditionally. Gate the latch on pps_ts_enabled (the SOF IRQ still fires — cheap, below the priority-0 display DMA — but does nothing when the feature is off); clear valid when off so a re-enable can't reuse a stale anchor. config.txt documents that pps=on enables the SOF tail. Builds clean (0 errors). Refuted findings (torn read, 16-bit atomicity, buffer truncation, display preemption, scope) left as-is. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Five GPS-derived astronomy read-outs, each opt-in via a MODE_* config key and shown on the 10-char date row while the live clock keeps running on the time row (SATVIEW-style, COUNT_NORMAL): MODE_SUN sunrise / sunset / solar noon (local), auto-paged MODE_SUN_AZEL sun azimuth & elevation, now MODE_MOON moon phase index (0-7) + illuminated % MODE_GRID Maidenhead grid locator MODE_LATLON latitude / longitude, auto-paged The astronomy maths is a self-contained Core/Src/astro.c (+ astro.h): low- precision NOAA/Meeus sun alt-az and event times, moon phase, equation of time, and Maidenhead. It has no firmware dependencies, so it unit-tests natively (test/test_astro.c, 45 reference vectors) and was validated to ~1e-5 before touching the firmware. The L4 has only a single-precision FPU, so the double maths would be slow soft- float. It is therefore computed in the main loop (astro_update(), gated on the active mode exactly as measure_vbat() is for MODE_VBAT) and swapped into a cache under a brief __disable_irq() mask; the sendDate() cases that run inside the SysTick ISR only format the cached scalars -- no trig in the interrupt. Modes are disabled by default; with no GPS fix they use fake_latitude/longitude if set, else show "----". config.txt documents the keys and the moon-phase index legend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"LAT 51.48" vs "LAT-51.48" let the minus swallow the separator. Add a sign
slot after the label space — "LAT 51.48" / "LAT -51.48" — so the digits
start in the same column either way, matching the RISE/SET layout. A 3-digit
longitude can't fit both the separator and the slot in 10 characters, so the
separator alone is dropped there ("LON-179.99").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The paged modes (SUN, LATLON) previously flipped sub-screen every 2 s hard-coded. Make the dwell a config key (page_ms, ms; unset -> 5500, floored at 250 so a tiny value can't flood the date-board UART), driven off uwTick, and repaint the moment a page flips rather than waiting for the 1 Hz date-row refresh (guarded by decisec!=9 to avoid racing the SysTick sendDate). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
The committed date-board image predated the current tree; rebuilt so both flash images (fwt + fwd) are products of exactly this source, including the PR mitxela#7 date-board hardening. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
The date board carries the CMD_LOAD_TEXT OOB fix (mitxela#7) over mitxela's shipped 0.0.1, so it's a new release — bump mk4-date VERSION_STRING 0.0.1 -> 0.0.2 and rebuild fwd.bin (mirrors the time board's 0.0.4 -> 0.0.5). Also commits the current fwt.bin (0.0.5, $PMTXTS + SOF correlation + audit fixes) so rollup's qspi/output/*.bin both match their source. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
a2cd14b to
0be8ead
Compare
- loadRules: reject untrusted rowLength/numEntries from TZRULES.BIN (RAM overflow) - MODE_TEXT: clamp snprintf untruncated-return index (uart2_tx_buffer OOB) - mk4-date latchDisplay: bound dp_pos per orientation (buffer_a/b OOB) - readConfigFile: don't treat a zero FAT timestamp as a cache hit (first-boot hang) - config-over-USB: defer postConfigCleanup out of the USB ISR (sendDate/nextMode races) - firmwareCheckOnEject: fatfs_busy guard for FATFS reentrancy on USB eject Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two further pre-existing bugs from the same read-through. - mk4-time NMEA-over-CDC: CDC_Copy_Transmit() / CDC_Transmit_FS() dereference hUsbDeviceFS.pClassDataCDC unconditionally, but it is NULL until a USB host enumerates. The default nmea_cdc_level = NMEA_ALL forwards every GPS sentence to CDC, so on charger-only power the first forwarded sentence faults in ISR context. NULL guard on both, plus a length clamp before the memcpy. - mk4-date CMD_LOAD_TEXT: 'if (text_idx > MAX_TEXT_LEN) return;' passes at text_idx == MAX_TEXT_LEN and writes text[32], one past the 32-byte array. '>' -> '>='. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
The committed date-board image predated the current tree; rebuilt so both flash images (fwt + fwd) are products of exactly this source, including the PR mitxela#7 date-board hardening. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
The date board carries the CMD_LOAD_TEXT OOB fix (mitxela#7) over mitxela's shipped 0.0.1, so it's a new release — bump mk4-date VERSION_STRING 0.0.1 -> 0.0.2 and rebuild fwd.bin (mirrors the time board's 0.0.4 -> 0.0.5). Also commits the current fwt.bin (0.0.5, $PMTXTS + SOF correlation + audit fixes) so rollup's qspi/output/*.bin both match their source. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
0be8ead to
aff121d
Compare
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.
A bundle of defensive fixes found while building the other PRs: bounds-checking of host-writable files on the CLOCK drive (the USB-MSC/FAT volume) (TZRULES.BIN length fields could overrun RAM), text/
snprintfclamps, the zero-mtimeconfig.txtfirst-boot bug, and related ISR/reentrancy guards.Stack: based on
astro-pack(#6).Try it now: fwt.bin · fwd.bin — the union of this PR stack, byte-identical to the merged result.
Docs:
docs/firmware-additions.md, shipped in the final (menu) PR.Note
You can try this PR without hardware: the PCC web app runs the real clock4 firmware compiled to WebAssembly, and its clock is built from a rollup of the whole PR series together, so this PR's behaviour is live there alongside the others. The app's DEVICE → UPDATES panel shows the exact commit it was built from. Fair warning: the app around the firmware is beta and still has bugs — the firmware behaviour is the real thing, the app chrome less so.
The fixes
loadRules()TZRULES.BINfirmwareCheckOnEject()sendDate()MODE_TEXTuart2_tx_buffer[32](time board)latchDisplay()(date board)dp_poswrites outsidebuffer_a/b[]rxConfigString()nextMode()/sendDate()in the ISRreadConfigFile()config.txtis never loaded → first-boot hangCDC_Copy_Transmit()/CDC_Transmit_FS()CMD_LOAD_TEXT(date board)text[32]1 —
loadRules()RAM overflowTZRULES.BINlives on the FAT volume the clock exposes over USB MSC, so its bytes are host-writable.loadRules()readsrowLengthandnumEntries(each auint8_t) straight from the file, then runsfor (i=0; i<numEntries; i++) f_read(&rules[i], rowLength, …)into the fixedrules[MAX_RULES](162 × 8 B) with no check. With both bytes maxed at 255 a malformed file writes ~990 bytes past the end of the array into adjacent globals — and it does so at cold boot and on every zone re-lookup, so it faults reliably.Fix: reject the file (
RULES_HEADER_ERR) whenrowLength > sizeof rules[0]ornumEntries > MAX_RULES, before the read loop. Valid files are unaffected.Repro: on the mounted MSC volume, set the
rowLengthbyte (file offset 4, normally 8) to0xFF— or any zone'snumEntriesto> 162— then trigger a zone load (power-cycle, or move position so the timezone is re-looked-up).2 —
firmwareCheckOnEject()FATFS re-entrancyThis runs from the USB MSC eject command, i.e. in the USB OTG ISR, and calls FATFS (
f_open/f_read). It already guards withif (QSPI_Locked()) { delayedCheckOnEject=1; return; }, which defers the whole check to the main loop when it sees QSPI contention — so the design isn't naive. The gap is thatQSPI_Locked()is only true during a QSPI transfer; in the lulls between transfers within a single main-loop FATFS call it reads false, the deferral doesn't fire, and the ISR re-enters non-reentrant FATFS anyway (corrupting FATFS state / risking a spurious reset).Fix: a
volatile fatfs_busyflag set around the main-loop FATFS regions; the guard becomesif (fatfs_busy || QSPI_Locked()). It's set/cleared only at the main-loop level, so it stays balanced and can't get stuck and block a legitimate firmware-update-on-eject.3 —
MODE_TEXTone-byte overrun (time board)i = snprintf((char*)&uart2_tx_buffer[1], 30, "%s", textDisplay)—snprintfreturns the length it would have written, not the truncated count. A 31-charTEXT=(the parser caps the value at 31) leavesi == 31, so the followinguart2_tx_buffer[++i] = …writes[32], one past the 32-byte buffer, and the DMA length reads one byte OOB. Self-inflicted via config and only one byte, hence Minor — but easy to close.Fix:
if (i > 29) i = 29;— clamp to the bytes thesnprintfactually wrote.4 — date-board
dp_posout-of-boundsdp_poscomes fromtext_idx, which the inter-board UART stream can advance past the 10-digit display.latchDisplay()then indexesbuffer_a[5]/buffer_b[5]with9-dp_pos(goes negative when the display is inverted) anddp_pos-6(overruns when it isn't) → OOB writes into adjacent RAM.Fix: bail out at the top of the decimal-point block when
dp_posis out of range for the current orientation (> 9inverted,> 10otherwise), so neither expression can leave[0,4].5 — config-over-USB runs
nextMode()/sendDate()in the ISRrxConfigString()runs in the USB OTG ISR (CDC). On each completed line it calledpostConfigCleanup(), which callsnextMode()andsendDate(). The priority-0 SysTick repaint preempts the USB ISR and callssendDate(0)directly — sosendDate()is genuinely re-entered — andnextMode()mutates display state (displayMode, the segment buffers) that the repaint reads, so it races too. Result: a torn date row or inconsistent mode state.Fix: defer
postConfigCleanup()to the main loop via a newdelayedPostConfigCleanupflag. The file-config path already runspostConfigCleanup()in thread context (becausereadConfigFileitself is the thing deferred to the main loop); this gives the USB-CDC path the same thread-context guarantee.6 — zero-timestamp
config.txt→ first-boot hangconfigis{0}-initialised, so thefdate/ftimemtime-cache key starts at 0. Aconfig.txtwritten while the volume's FAT clock was unset carries a zero timestamp, which matches that initial key on the very first boot →readConfigFile()returns early → config is never applied → no display mode is enabled → the first MODE-button press spinsnextMode()forever (there's no watchdog).Fix: never treat a zero stamp as a cache hit —
if ((fno.fdate || fno.ftime) && fno.fdate==config.fdate && fno.ftime==config.ftime) return;.7 — NMEA-over-CDC NULL deref → hard-fault on charger-only power
CDC_Copy_Transmit()and stockCDC_Transmit_FS()dereferencehUsbDeviceFS.pClassDataCDCunconditionally, but it is NULL until a USB host enumerates. With the defaultnmea_cdc_level = NMEA_ALL, every GPS sentence is forwarded to CDC, so on charger-only power — no host — the first forwarded sentence dereferences NULL in ISR context and hard-faults. This is the most common deployment (a dumb USB charger), so it faults reliably there.Fix: a NULL guard at the top of both functions (return cleanly when not enumerated), plus a length clamp before the
memcpyinto the static tx buffer.mk4-time/USB_DEVICE/App/usbd_cdc_if.c.8 — date-board
CMD_LOAD_TEXTone-byte OOBDistinct from #3 (that's
uart2_tx_bufferon the time board; this is thetext[]buffer on the date board).CMD_LOAD_TEXTbounds withif (text_idx > MAX_TEXT_LEN) return;then writestext[text_idx++] = x;. Attext_idx == MAX_TEXT_LEN(32) the guard passes and it writestext[32], one past the 32-byte array, into the adjacent global. Reachable with a 32-plus-char text-mode string.Fix:
>→>=.mk4-date/Core/Src/main.c.Verification
TZRULES.BIN), so the per-bug case above rests on the code, not on a hardware repro of each fault.TZRULES.BIN/config.txtstill loads;fatfs_busyis balanced so it can't latch on).Files
mk4-time/Core/Src/main.c— fixes 1, 3, 5, 6, plus thefatfs_busywraps for 2mk4-time/Core/Src/chainloader.candCore/Inc/main.h— fix 2mk4-time/USB_DEVICE/App/usbd_cdc_if.c— fix 7mk4-date/Core/Src/main.c— fixes 4, 8