Skip to content

Limit node database churn from unauthenticated packets - #11102

Merged
caveman99 merged 2 commits into
developfrom
throttle-nodedb-admission
Jul 21, 2026
Merged

Limit node database churn from unauthenticated packets#11102
caveman99 merged 2 commits into
developfrom
throttle-nodedb-admission

Conversation

@caveman99

@caveman99 caveman99 commented Jul 20, 2026

Copy link
Copy Markdown
Member

Two related problems on the unauthenticated NodeDB path.

Ordering. updateUser() called getOrCreateMeshNode() before the signature gate added in 5ded0ec. Creating an entry can evict another node and write the warm tier, so a refused identity update had already caused both side effects before returning false. The gate now runs against the existing record, and the entry is created only once it passes. Behaviour is otherwise identical: the gate only ever fired for nodes already present and already known to sign.

Admission churn. updateFrom() creates an entry for any decoded packet, and mp.from is unauthenticated header data. On a channel whose key is known, including the default public channel, invented node numbers churn the database at packet rate. Eviction prefers keyless nodes, so a flood pushes real keyless neighbours out, and each eviction also writes the warm tier.

Admission from the packet path is now spaced once the database is full. The limit is deliberately narrow:

  • only the packet-driven path, so explicit admission and internal callers still evict freely
  • only while the database is full, so discovery is untouched while slots remain
  • existing nodes update normally, since the throttle only applies to creating an entry

An earlier revision put the throttle inside getOrCreateMeshNode(); test_nodedb_blocked correctly rejected it, because that also gated eviction paths that must succeed. Narrowing it to updateFrom() addresses the threat without changing those semantics.

The nRF52840 warm store was also examined. Its immediate persistEntry write is by design, buffered through the shared flash page cache with saveIfDirty() as the durability point, so it is left alone.

test_nodedb_blocked, test_packet_signing, test_warm_store and test_optin_migration pass, 75 cases.

Summary by CodeRabbit

  • Bug Fixes
    • Reduced unnecessary node database churn when updates arrive while storage is full.
    • Prevented rejected unsigned identity updates from creating or evicting node records after prior valid signing.
    • Added rate-limiting for admitting unknown nodes when the node database reaches capacity, improving stability and performance.
    • Improved handling of decoded node updates by only creating new records when needed, deferring fabricated/unknown node admissions when at capacity.

updateUser created the node entry before checking the signature gate, so a
refused identity update had already evicted another node and written the
warm tier. The gate now runs against the existing record first.

updateFrom admits a node for any decoded packet, and mp.from is
unauthenticated header data, so invented node numbers could churn the
database at packet rate and push real neighbours out. Admission is now
spaced once the database is full. Only the packet-driven path is limited;
explicit admission still evicts freely and discovery is untouched while
slots remain.
@caveman99 caveman99 added the bugfix Pull request that fixes bugs label Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d5628513-b02c-4edc-aa2a-f8804f7dafff

📥 Commits

Reviewing files that changed from the base of the PR and between f23e6ee and 071c15c.

📒 Files selected for processing (1)
  • src/mesh/NodeDB.cpp

📝 Walkthrough

Walkthrough

NodeDB now validates unsigned identity updates before allocation and changes decoded-packet admission to avoid unnecessary churn. When the database is full, new unknown node admission is throttled using a timestamp and interval constant.

Changes

NodeDB admission safeguards

Layer / File(s) Summary
Pre-allocation identity validation
src/mesh/NodeDB.cpp
updateUser() checks existing signed-node state before calling getOrCreateMeshNode(), preventing rejected updates from allocating or evicting entries.
Throttled full-database admission
src/mesh/NodeDB.cpp, src/mesh/NodeDB.h
updateFrom() checks for existing nodes first and rate-limits admission of unknown nodes when the database is full using lastFullEvictionMs and the new interval constant.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • meshtastic/firmware#11020: Both changes modify NodeDB admission or rehydration behavior involving warm-tier state and XEdDSA signer information.
  • meshtastic/firmware#11084: Both changes update NodeDB::updateUser() handling for unsigned identity updates on previously XEdDSA-signed nodes.

Suggested reviewers: thebentern, jp-bennett, nomdetom

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: reducing NodeDB churn from unauthenticated packet-driven updates.
Description check ✅ Passed The description explains both fixes and test results, but it omits the template's attestation checklist and device regression details.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch throttle-nodedb-admission

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/mesh/NodeDB.cpp (2)

3423-3427: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Condense the explanatory comments.

As per coding guidelines, code comments should be kept minimal (one or two lines maximum) without adding multi-paragraph explanatory blocks. Please condense this comment block.

♻️ Proposed refactor
-        // Admitting a node this way costs an eviction once the database is full, and on some platforms a
-        // warm-tier write. mp.from is unauthenticated header data, so a flood of invented node numbers
-        // would otherwise churn the database at packet rate and push real neighbours out. Only the
-        // packet-driven path is limited, and only while the database is full: explicit admission still
-        // evicts freely, and discovery is untouched while slots remain.
+        // Throttle new node admission from unauthenticated packets when the database is full
+        // to prevent rapid eviction of real neighbors by spoofed node numbers.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mesh/NodeDB.cpp` around lines 3423 - 3427, Condense the comment
immediately preceding the packet-driven node admission logic to one or two
lines, retaining only that unauthenticated packet node numbers are rate-limited
when the database is full to prevent eviction churn. Remove the extended
platform, eviction, and unaffected-path explanation.

Source: Coding guidelines


3318-3321: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Condense the explanatory comments.

As per coding guidelines, code comments should be kept minimal (one or two lines maximum) without adding multi-paragraph explanatory blocks. Please condense this comment block.

♻️ Proposed refactor
-    // Once a node has proven it signs, only a signed update may change its identity. The public-key guard
-    // below is no help - an attacker can replay the victim's real (public) key. Our own record is exempt.
-    // Checked against the existing record before getOrCreateMeshNode, because creating an entry can evict
-    // another node and write the warm tier; a refused update must not leave those side effects behind.
+    // Pre-creation check: only signed updates can change a known signer's identity.
+    // Prevents replay attacks and stops refused updates from causing side-effect evictions.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mesh/NodeDB.cpp` around lines 3318 - 3321, Condense the comment above the
existing-record check into one or two lines, retaining only that signed updates
may change a proven node’s identity and that the local record is exempt. Remove
the detailed attack, public-key, eviction, and warm-tier explanations without
changing the surrounding logic.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/mesh/NodeDB.cpp`:
- Around line 3423-3427: Condense the comment immediately preceding the
packet-driven node admission logic to one or two lines, retaining only that
unauthenticated packet node numbers are rate-limited when the database is full
to prevent eviction churn. Remove the extended platform, eviction, and
unaffected-path explanation.
- Around line 3318-3321: Condense the comment above the existing-record check
into one or two lines, retaining only that signed updates may change a proven
node’s identity and that the local record is exempt. Remove the detailed attack,
public-key, eviction, and warm-tier explanations without changing the
surrounding logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 355e841b-1722-4755-8d4c-669af87f3d0e

📥 Commits

Reviewing files that changed from the base of the PR and between 0199a1f and f23e6ee.

📒 Files selected for processing (2)
  • src/mesh/NodeDB.cpp
  • src/mesh/NodeDB.h

@caveman99

Copy link
Copy Markdown
Member Author

Both comment blocks condensed. No logic change; native-windows still builds clean.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

⚡ Try this PR in the Web Flasher

Flash this PR in the Web Flasher

firmware commit boards expires

Warning

This is an automated, unreviewed CI test build. Back up your device configuration
before flashing, and only flash devices you are able to recover.

Supported boards built by this PR (30)
Device Board Platform
Crowpanel Adv 3.5 TFT elecrow-adv-35-tft esp32-s3
Heltec HT62 heltec-ht62-esp32c3-sx1262 esp32-c3
Heltec Mesh Node 096 heltec-mesh-node-t096 nrf52840
Heltec Mesh Node T1 heltec-mesh-node-t1 nrf52840
Heltec Mesh Node T114 heltec-mesh-node-t114 nrf52840
Heltec V3 heltec-v3 esp32-s3
Heltec V4 heltec-v4 esp32-s3
Meshnology W10 meshnology_w10 esp32-s3
Raspberry Pi Pico pico rp2040
Raspberry Pi Pico W picow rp2040
RAK WisMesh Pocket V3 rak_wismesh_pocket nrf52840
RAK WisMesh Pod rak_wismesh_pod nrf52840
RAK WisMesh Repeater Mini V2 rak_wismesh_repeater_mini nrf52840
RAK WisMesh Tag rak_wismeshtag nrf52840
RAK WisBlock 11200 rak11200 esp32
RAK WisBlock 11310 rak11310 rp2040
RAK3312 rak3312 esp32-s3
RAK WisBlock 4631 rak4631 nrf52840
Seeed SenseCAP Mesh-Tracker-X1 seeed_mesh_tracker_X1 nrf52840
Seeed Wio Tracker L1 seeed_wio_tracker_L1 nrf52840
Seeed Xiao NRF52840 Kit seeed_xiao_nrf52840_kit nrf52840
Seeed Xiao ESP32-S3 seeed-xiao-s3 esp32-s3
Station G2 station-g2 esp32-s3
Station G3 station-g3 esp32-s3
LILYGO T-Deck t-deck-tft esp32-s3
LILYGO T-Echo t-echo nrf52840
LILYGO T-Echo Plus t-echo-plus nrf52840
LILYGO T-Impulse Plus t-impulse-plus nrf52840
LilyGo T3-C6 tlora-c6 esp32-c6
Seeed SenseCAP T1000-E tracker-t1000-e nrf52840

Build artifacts expire on 2026-08-19. Updated for 071c15c.

@caveman99
caveman99 merged commit 40e583c into develop Jul 21, 2026
104 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Pull request that fixes bugs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant