Repository navigation
fix: four high findings of the manual review, and the reason for v=6 - #255
Merged
Merged
Conversation
Finding 9 of docs/analysis/PLANO_REVISAO_EXTERNA.md. An account whose PIN must change (a stricter policy, or the admin's factory 1234) is identified and sent straight to Novo PIN. SAIR there opened the Settings menu, with the session's every bit and the PIN unchanged; the change was asked again only at the next identification. showPinEntry( ) takes a `forced` flag, and the two forced calls in panelIdentify( ) set it. pinCancel( ) sends a forced PIN_FOR_OWN to the dashboard, where Settings asks for the account again; the voluntary change, from the menu, still goes back to the menu. _pinForced has no initializer: only pinCancel( ) reads it, on the PIN screen, and showPinEntry( ), the one way onto that screen, writes it first. Zeroing it cost the constructor 16 B. Tested on the rig with pico_w_test, each image started from the S1 bench dump. Tightening the policy from 4/3/0 to 5/3/0 marks every account holding a PIN. pmgr identified with its old 5-digit PIN (SEC_PIN_OK, 308), Novo PIN opened on the ordered keypad, and SAIR then gave: - main e5438e7: UI mode 5, the Settings menu (screenshot); - this branch: UI mode 0, the dashboard; CFG then opened the account picker (mode 26). The factory-PIN path is the same call with the same flag; it was not run on the rig. The panel's screens have no native suite. Manual chapter 8 says what SAIR does now; MANUAL.pt-BR.html regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BniVDEDSHGQgcoqmCRn5um
Finding 8 of docs/analysis/PLANO_REVISAO_EXTERNA.md. At the panel, an account with Users (PERM_USER_MGR) gave Limits, Block and Maintenance to any account, a new one or its own, whatever it held itself. The web has refused that since V-09 (commitGrantAllowed). panelGrantAllowed(before, after, caller), in SessionCheck.h, is the rule: a change of an account's panel bits may only ADD bits the caller holds. Taking one away is allowed, and so is leaving one the caller lacks where it was. panelAllowed( ) gains an overload that asks it, used by EVT_USER_ADD (from 0) and EVT_USER_PERMS. A refusal is the one panelAllowed( ) already gives: the error tone, APP_UI_PERM_DENIED (458) in the log, "Sem permissão", and nothing written. An overload, not two default arguments: those cost each of the other call sites 4 B, 56 B in all on pico_w_release. Tests first: test_panel_grant_adds_only_bits_the_caller_holds, in the native suite, which passes 284 of 284, under ASan and UBSan too. On the rig, pmgr holding Users alone (0x0100), from its Users menu: - a new account with L and M: main created it with 0x1400; this branch refuses it; - ptgt, 0x0, plus L: main 0x0400; this branch refuses, and it stays 0x0; - pkeep, 0x0800, minus B (a bit pmgr lacks): 0x0 on both; - pmgr itself plus M: main 0x1100; this branch refuses, 0x0100; - the log: main 450 once and 453 three times; this branch 458 three times and 453 once. Manual chapter 8 says who grants what; MANUAL.pt-BR.html regenerated. The READMEs count the new case. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BniVDEDSHGQgcoqmCRn5um
Finding 1 of docs/analysis/PLANO_REVISAO_EXTERNA.md. "Adopt the probe wired
here" read the 1-Wire bus on the GPIO whose number is the slot's, and wrote
that number into the slot's pins[0]. A slot is not a pin. And it saved the
new ROM without reloading the running sensor, which keeps its own copy and
checks the probe against it every fifth read.
sensor_accept now reads the GPIO the slot has saved and keeps it, and saves
and reloads the sensors under quiet mode, as the calibration commit and the
console's accept do. A slot that is not saved as an active DS18B20 has no
GPIO to read: it answers 409 {"error":"notds18b20"}, and the page says
"Save this slot as an active DS18B20 first." (new key sens_accept_unsaved,
in both packs). checkAndAutoHealSensors( ), the 3 s health check, had the
same mistake: it read GP<slot> and flagged by that number. It reads the
slot's GPIO now, and logs it as the read path does.
On the rig, pico_w_test, each image from the S1 bench dump:
- slot 1 on GP1, told by `sensor define` it holds slot 0's probe, as after
a swap, sits in quarantine. Adopt answered 200 on both. main: 0 valid
reads of 15 in the next 90 s. This branch: 14 of 14, the first at once.
- the probe on GP1 moved to slot 7: main read GP7 and answered 422
"badrom" for a probe GP1 was reading at 23.7 °C; this branch answered 200
and slot 7 stayed on GP1, reading again 8 s after the reload.
- in 160 s neither logged a "missing" (106) for slot 7: the rig's empty GP7
answers a ROM that fails its CRC, which the health check skips.
Manual chapter 6 says what Adopt does now, and the warning about the slot's
number is gone; MANUAL.pt-BR.html regenerated.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BniVDEDSHGQgcoqmCRn5um
A Pico W refuses the Pico 2 W's .bin with validation code 6: its first 256 bytes are no RP2040 boot stage. Since v2.11.0 the two .bin sit side by side on the release page, and the Files page said only "Upload failed (validation v=6). Cancelled." (under Known in v2.11.0). On the Pico 2 W, 6 is an image with no RP2350 image definition, which a Pico W .bin does not reach: its env tag refuses it first, with 7. fwStageMessage( ) explains 6: "The image is for another chip, or it is damaged. The Pico 2 W takes the releasetwo .bin, and the Pico W the others." New key fil_fw_v6, in both packs. Tests first: tools/test_webui_firmware_refusal.py gains the v=6 reply, and its pack check asks for fil_fw_v6. Against main's WebUI.h and packs it fails in 6 places: both packs, and the reply as written and as minified. It passes here. Manual chapter 17 quotes the message; MANUAL.pt-BR.html regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BniVDEDSHGQgcoqmCRn5um
Finding 25 of docs/analysis/PLANO_REVISAO_EXTERNA.md. An AP on a unit with
a network gives way to it after 15 min (AP_MODE_TIMEOUT_MS) by restarting
into the station, and asks for the network by its name. Only begin( )
wrote that name, and the boot the gesture sends into AP mode never calls
begin( ): the limit read 32 bytes of a heap object nothing had written.
Zeros kept the AP of a unit with a network up for good, which is the
finding as written; anything else closed the AP of a unit with none, the
one way to give it a network.
beginAP( ) takes the network's name, and both callers pass cfg.wifiSsid.
It writes the name every time, empty for a unit with none.
Tests first, in native_network (39 of 39, under ASan and UBSan too):
- test_an_ap_given_the_network_gives_way_to_it: up at 895 s, restarted by
905 s;
- test_an_ap_with_no_network_stays_open: no restart in 30 min, with no
name and with an empty one;
- test_the_ap_limit_reads_no_leftover_bytes: built over memory holding
0x01, a unit with no network keeps its AP.
Against main's NetworkManager, called the way main calls it, the first
fails over a zeroed object ("the AP outlived its limit on a unit with a
network") and the third over 0x01 ("leftover bytes closed the AP of a unit
with no network").
On the rig, a bench build of pico_w_test with -DTOUCH_IRQ=17 -DTOUCH_CS=21
(AGENTS.md section 1), the finger held with CHARGER OFF across a reset, the
unit configured for the bench network. The event log has, on both images,
code 403 with context 1 (the gesture) 10 s into the boot, and code 525,
"AP mode timeout, rebooting to STA", at 910 s:
- main e5438e7: the AP gave way. The 32 bytes were not zero on that boot,
so the rig did not show "never expires";
- this branch: the AP gave way, and the next boot joined the network; the
web answered on :8080.
A unit with no network was not tried on the rig: neither the console nor
the web clears a configured network, so it takes a factory reset.
Found on the way, now in AGENTS.md section 1: with the hand at CHARGER HIZ,
that image's PENIRQ line read low once the boot's first look was past
(GP3 level=L), so every one of its boots opened the AP with no finger,
including the one after the 15 min. CHARGER ON holds the line high.
Manual chapter 9 says how long the gesture's AP lasts; MANUAL.pt-BR.html
regenerated. The READMEs count the three new cases.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BniVDEDSHGQgcoqmCRn5um
All seven images built from this branch, on main e5438e7: - release: 'measured' and the .bin +280 B (1,020,940 and 1,032,972); budget 1,020,732 -> 1,021,012; - test: +272 B (1,025,516 and 1,037,548); its budget holds, 144 B under; - test_https: +208 B (1,030,492); budget 1,030,284 -> 1,030,492; - asserts: +272 B (1,023,236 and 1,035,268); budget 1,022,996 -> 1,023,268; - alpha: +40 B (974,908), Air: +48 B (1,018,224); both keep their .bin, the growth inside the 4 KiB pad before .data, and their budgets; - pico2_w_release: +264 B (997,000 and 997,024); budget 996,864 -> 997,128. Each budget that moves moves by its image's growth, which keeps its margin. By commit, on pico_w_test: the forced PIN change +40 B, the panel's grant rule +80 B, Adopt +80 B, the v=6 reason +56 B (the Files page's gzip), the AP limit +16 B. Three choices kept it there, from +472 B on release at first: panelAllowed( ) has an overload instead of two default arguments (-56 B), beginAP( ) writes the name instead of the constructor zeroing four buffers (-96 B), and _pinForced has no initializer (-16 B). The OTA headroom table of docs/analysis/PLANO_STABLE.md section 3.1 follows: release 7,171 B (7,451 on v2.11.0), test 2,595 B, which was already under one sector and warned, asserts 4,875 B, pico2_w_release 571,503 B against its slot; alpha and Air unchanged. check_flash_budget.py passes on the seven builds, and --table passes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BniVDEDSHGQgcoqmCRn5um
docs/analysis/PLANO_REVISAO_EXTERNA.md marks the four high findings #255 fixes, each with the number the bench measured against main, and counts 52 remaining. Of the high ones, 20 and 65 still need code, and 41 is the documented limitation of plain HTTP. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BniVDEDSHGQgcoqmCRn5um
This was referenced Oct 5, 2026
Merged
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.
Four of the high-severity findings that
docs/analysis/PLANO_REVISAO_EXTERNA.mdstill listed after v2.11.0 (1, 8, 9 and 25), and the reason the Files page did not give forv=6, which v2.11.0 listed under Known. One commit per finding, then the flash record and the plan. Each finding was checked on the rig againstmaine5438e7 (v2.11.0 and two docs PRs), both images started from the same bench state.What changes
main1234)panelGrantAllowed, the web's V-09 rule)beginAP( )gets the network's name: with one, the AP gives way at 900 s; with none, it staysThe 3 s sensor health check had finding 1's mistake too: it read GP<slot> for each DS18B20 slot. It reads the slot's GPIO now.
Tests first
native:test_panel_grant_adds_only_bits_the_caller_holds(finding 8). 284 of 284.native_network: three cases, 39 of 39. Againstmain'sNetworkManager, called the waymaincalls it, two fail:test_an_ap_given_the_network_gives_way_to_itfails over a zeroed object: "the AP outlived its limit on a unit with a network";test_the_ap_limit_reads_no_leftover_bytesfails over memory holding 0x01: "leftover bytes closed the AP of a unit with no network";test_an_ap_with_no_network_stays_openpasses on both.tools/test_webui_firmware_refusal.py: thev=6reply and thefil_fw_v6key. Againstmain's page and packs it fails in 6 places; here it passes.check_readme_numbers.pywith 636 cases) andbuild_manual.py --check.pico_w_test.On the rig
For findings 9, 8 and 1, each run used
pico_w_testof its own tree, from the S1 bench dump, restored and read back byte for byte before each image. The web is on :8080, the panel is driven withPOST /api/touch, and the log is read from/api/logs. Finding 25 ran afterwards on the same unit, from bench builds with the touch remapped.Finding 9. Tightening the PIN policy from 4/3/0 to 5/3/0 marks every account holding a PIN.
pmgridentified with its old 5-digit PIN (code 308), Novo PIN opened on the ordered keypad, and SAIR gave:main: UI mode 5, the Settings menu;Finding 8.
pmgrholds Users alone (0x0100):mainptgt0x0, plus Lpkeep0x0800, minus B (a bitpmgrlacks)pmgritself, plus MFinding 1. Two cases:
sensor definetold the slot it holds slot 0's probe, so it sat in quarantine. Adopt answered 200 on both. Over the next 90 s,mainread 0 valid values of 15; this branch read 14 of 14, the first one at once.mainread GP7 and answered 422badrom, for a probe GP1 was reading at 23.7 °C. This branch answered 200, the slot stayed on GP1, and it read again 8 s after the reload.{"error":"notds18b20"}.Finding 25. The image is a bench build of
pico_w_testwith-DTOUCH_IRQ=17 -DTOUCH_CS=21(AGENTS.md §1). The hand held the finger down across a reset, on a unit configured for the bench network. The event log has, on both images, code 403 with context 1 (the gesture) 10 s into the boot, and code 525, "AP mode timeout, rebooting to STA", at 910 s:main: the AP gave way too. The 32 bytes were not zero on that boot, so the rig did not show "never expires". The host tests pin both sides: the zero case for a unit with a network, and the leftover-bytes case for a unit with none.The rig did not try a unit with no network. Neither the console nor the web clears a configured network, so that takes a factory reset.
Flash
main.binunchanged.binunchangedBudgets move by the growth, which keeps their margins. The signed release
.binhas 7,171 B left under the OTA ceiling (7,451 B on v2.11.0). The test image's slack, 2,595 B, was already under one sector.Three trims kept the growth there, from +472 B on release:
panelAllowed( )gets an overload, not default arguments (−56 B);beginAP( )writes the name, instead of the constructor zeroing four buffers (−96 B);_pinForcedhas no initializer (−16 B).Found on the way, not fixed here
DisplayManager::pinSubmit( )refuses an entry shorter than the current minimum before Core 0 sees it.user policysays tightening marks the PINs precisely so that this does not happen.CHARGER HIZ, the line reads low once the boot's first look is past.CHARGER ONholds it high, and AGENTS.md §1 now says so.Docs
MANUAL.pt-BR.htmlis regenerated.PLANO_REVISAO_EXTERNA.mdmarks the four findings, 52 remaining.tools/flash_budget.jsonand §3.1 ofPLANO_STABLE.mdcarry the record.🤖 Generated with Claude Code
https://claude.ai/code/session_01BniVDEDSHGQgcoqmCRn5um