mirror of
https://github.com/meshtastic/firmware.git
synced 2026-11-07 06:44:33 -05:00
create-pull-request/bump-version
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e6ed4b5f6a |
AdminModule: trust from == 0 only when the dispatcher reports a local origin (#12242)
AdminModule treats from == 0 as a local client, skipping the admin_key and session-passkey checks. That trust rested entirely on every ingress path dropping from == 0 (RadioLibInterface, MQTT downlink, UDP multicast and frame injection all do). Make the handler check it itself: - MeshModule gains a static currentRequestSource, set by callModules() alongside currentRequest, so modules can see the RxSource of the packet they are handling. - AdminModule drops a from == 0 payload unless callModules() is dispatching that exact packet with RX_SRC_USER or RX_SRC_LOCAL. It fails closed for a radio source, for no dispatch context, and for a context that belongs to another packet. The drop is silent (no NAK to a forged sender). Tests: AdminModuleTestShim::handleReceivedProtobuf now runs the handler inside a dispatch context (default RX_SRC_USER, the phone client existing suites stand in for), plus handleWithDispatchContext() for the negative cases. test_admin_session_repro adds six cases: from == 0 via RX_SRC_RADIO, with no context, and with another packet's context are rejected, both directly and through MeshModule::callModules(); the phone-client controls still apply the setter. With the gate removed, the four rejection cases fail. Claude-Session: https://claude.ai/code/session_01Y1BYe4ahUHxyhw3nGTX67D Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
bca7c0b480 |
Tom fiddles with the test suite - again (#11517)
* test: make every suite run its own binary, and fail the run when it does not PlatformIO links every native test program to the one $BUILD_DIR/$PROGNAME path and attributes Unity output by text alone, never checking that the source file a case came from belongs to the suite it thinks it ran. Both harnesses had been split into a build pass (--without-testing) and a run pass (--without-building), and for a non-embedded platform the run pass never relinks - so all 57 suites executed whichever suite was linked last, each reporting PASSED under its own name. Introduced for CI in |
||
|
|
8acb7fec73 | test: cover coerced packet signature policy | ||
|
|
d195ec6748 | fix(security): round-trip packet policy defaults | ||
|
|
6fc7c1b1db |
test: unset force_simradio so admin request pinning is exercised
The native test harness boots Portduino in simulated mode, and
wouldEncryptWithPKC() short-circuits to false whenever
portduino_config.force_simradio is set. noteOutgoingAdminRequest() derives its
pin from that predicate:
keyValid = haveDestKey && wouldEncryptWithPKC(&p, p.channel, haveDestKey);
so under the harness no outgoing admin request is ever key-pinned, and
responseIsSolicited() admits a plaintext response to a PKC-pinned request.
test_pinned_request_keeps_its_key_after_an_unpinned_request and
test_request_to_keyed_node_pins_the_stored_key have therefore failed since
#11092 added them, on develop and on every branch that merges it.
Instrumenting the predicate shows every other term already satisfied
(haveDestKey=1, isFromUs=1, private_key=32, unicast, ADMIN_APP, channel
LongFast) with wouldEncryptWithPKC=0, leaving only the simradio guard.
Clear the flag in setUp so the fixture models a real device. Skipping PKC under
force_simradio is correct for a simulated radio - there is no PKC to pin - so
the predicate is left alone rather than relaxed to make a test pass. The only
sim-mode path in this suite is AdminModule's exit_simulator intercept, which no
test here exercises.
Native suite: 38 suites, 723/723 cases, no sanitizer findings.
|
||
|
|
d587f08481 |
Pin admin responses to the stored peer key and request id (#11092)
* Pin admin responses to the stored peer key and request id noteOutgoingAdminRequest derived its PKC pin from p.public_key, which nothing populates on the outgoing path, so keyValid was false for every client request and the pin never engaged. The accepted-response predicate reduced to an unauthenticated from plus variant and subtype. Resolve the destination key from NodeDB the way perhapsEncode does, and pin it only when the request would actually be PKC-encrypted. Extract that condition from perhapsEncode as wouldEncryptWithPKC so both use one predicate. Also record the request's packet id and require the response to echo it. * Treat a zero request id as no pairing token |
||
|
|
6f522aad17 |
Return the secret sentinel for remote admin config gets (#11093)
writeSecret is a setter, so calling it on the NETWORK_CONFIG get path was a no-op: the buffer already holds the stored psk, never the sentinel. MQTT_CONFIG returned the broker password verbatim. Both get paths now return secretReserved when req.from != 0, and the matching set paths call writeSecret so a read-modify-write round trip keeps the stored value. |
||
|
|
8147970957 |
AdminModule: only accept admin responses to requests we sent (#11024)
* AdminModule: only accept admin responses to requests we sent An admin *_response short-circuited the auth and session-passkey checks that gate every other admin message, so any node could deliver one. On a channel the module listens to unauthenticated, a get_module_config_response drives the remote-hardware pin handler with attacker-supplied values. Track the destination of outgoing admin requests (per remote, with the pinned PKC key when there is one) and accept a response only from a node with a matching outstanding request, inside the same window as the session passkey. Local (from == 0) admin is unchanged; PhoneAPI already gates it. Also fix the response dispatch: get_module_config_response.which_payload_variant is a ModuleConfig oneof tag, but it was compared against the AdminMessage ModuleConfigType enum (different numbering), so the handler never ran. Compare against the oneof tag. * AdminModule: rollover-safe request window, bind response to request type Two review refinements to the request/response pairing: Use Throttle::isWithinTimespanMs for the outstanding-request expiry instead of comparing millis()/1000 sums, which mis-expired across the millis() rollover. Bind each accepted response to a request type actually sent to that node. Each outstanding record now carries a bitmask of the response variants its requests authorize, so a get_owner request no longer admits a get_module_config response. The mask accumulates per remote, so a client may still pipeline several request types to one node and have every answer accepted. * AdminModule: track admin requests per-request, not per-node Reworks the outstanding-request table so each request is its own entry with its own expiry window and pinned key, replacing the per-node bitmask that shared one timestamp and one key across every response variant. That sharing let a later request to the same node extend an earlier one's window and, worse, clear its PKC pin: an unpinned request cleared keyValid, so a plaintext response to an earlier PKC-pinned request was then accepted. Per-request entries keep each pin intact. Identical requests are de-duplicated (a client may fetch several config subtypes, all answered by one response variant) and eviction compares elapsed time, which is rollover-safe. Test: a pinned request's response still requires its key after an unpinned request to the same node. * AdminModule: match module-config subtype and consume answered requests Two refinements to the request/response pairing: Only remote_hardware get_module_config_response mutates state (the pin table), so it must answer a request for that exact ModuleConfigType, not just any module-config request. Each entry records the requested subtype and the gate checks it. A matched request is now consumed on accept, so a node cannot replay a state-mutating response within the window. Because one request yields one response, request de-dup is dropped (a client's N indexed get_channel requests are N entries, each consumed once). Tests: a non-remote-hardware request does not admit a remote_hardware response, and a second copy of an answered response is rejected. |
||
|
|
d3d02af817 |
AdminModule: don't return the device private key to remote config reads (#11030)
A SECURITY_CONFIG get_config response copied config.security verbatim, so a remote request was answered with the device identity private_key and sent over the air. Only the local owner needs it, for backup. Zero private_key in the SECURITY_CONFIG response when the request is remote (from != 0); the local BLE/USB/TCP path (from == 0) still receives it. public_key and admin_key are public and stay as they were. |
||
|
|
952c825167 |
Allow key verification to work for unknown nodes. (#10669)
* Allow key verification to work for unknown nodes. * trunk * More reliable admin key decryption * Add admin key fallback tests * Actually check haveRemoteKey * Logging cleanup * Address review feedback - Persist the committed key + manually-verified flag in commitVerifiedRemoteNode via saveToDisk(SEGMENT_NODEDATABASE), replacing the "todo: initiate save" - Guard the CryptoEngine pending-key slot with a dedicated internal lock; the Router reads it while already holding the non-recursive cryptLock, so the accessors cannot reuse that lock - Draw the security number from the hardware RNG (CryptRNG fallback) under cryptLock instead of random(); on nRF52 the entropy fill toggles the same CC310 the BLE task's packet crypto uses - Return true after fully handling the hash2 response (restores develop behavior; consistent with the hash1 branch) - Take uint32_t in the number-picker callbacks so 8-digit hex nodenums can't truncate through int - Trim over-long comments flagged by review * feat(tests): add deterministic tests for admin session-key behavior --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> |