Skip to content

cell M6: grant-map authentication (GM IE v2, the cell key's tag) - #537

Merged
josephnef merged 1 commit into
masterfrom
feat/m6-grantmap-auth
Nov 6, 2026
Merged

josephnef merged 1 commit into
masterfrom
feat/m6-grantmap-auth

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

M6 (#260, EPIC #254): the grant map IE is now authenticated. This closes the open item from #536.

Why

The grant map was the only scheduling IE without a tag. A forged map could already move a UE's slots, and since #536 it could also reset the UE's grant state through the DU-restart path.

What

  • GM IE v2 (src/cell/GrantMap.h) has the layout … crc16 tag(8). The tag is the first 8 bytes of mac('G' || body..crc) under the cell key (CELL_PSK), the same scheme as the NL/MR/handover IEs.
    • encode_grant_ie, decode_grant_ie and find_grant_map take the Mac. It is mandatory, so every caller decides.
    • With a key, a forged map or an untagged one (zeros) is refused, using a constant-time compare.
    • Without a key (an unauthenticated development cell, or a passive witness), only the structure is checked.
    • v1 maps are refused. A failing Mac airs nothing.
    • Mac moves from Rach.h to GrantMap.h, which Rach.h includes.
  • Callers:
    • the DU's build_du_beacon(map, mac, …);
    • the UE (serving maps, the handover target's maps, the restart path), with auth;
    • the witness, with CELL_PSK if it has one.
  • The UE counts serving maps taken and refused: gm_ok / gm_refused in cell.ue.done, and the first refusal logs cell.ue.gm_refused.
  • Selftests (cell, cell_node): round trip with a key, another key, tag bit flip, zero tag at a keyed receiver, structure-only without a key, an edited map re-CRC'd under another key, v1 refused, a failing Mac, and the 24-grant map fitting 255 bytes (226).

A correction this PR makes

The M5/M6 rig scripts never set CELL_PSK. So the handover, DAPS, inter-frequency, measurement-gap, phase-planning and clock-fit rig results ran as an unauthenticated development cell: zero tags, not checked. F1 was keyed throughout. docs/cell-m6.md now says so. The testrig scripts give every cell role one key, and the suite was re-run keyed (below).

Rig

Wrong-key test (testrig scripts/m6_gmauth.sh, 2/2). Every role holds the cell key except UE2:

maps taken maps refused heard in slot by its DU
UE1 (cell key) 586–587 0 99.8 %
UE2 (another key) 0 586–587 never

The M6 suite keyed, all pass. No keyed UE refused a map (36 UE runs), apart from UE2 above.

  • same-channel handover: 2, plus fallback 1;
  • inter-frequency handover: 2;
  • DAPS: 2 + 2 (missing uplinks 0/0 vs 11/16 without);
  • measurement-gap handover: 2;
  • phase planning: 2;
  • clock fit, transient jump and DU restart with re-attach: 1 + 1.

🤖 Generated with Claude Code

The grant map IE was the one scheduling IE without a tag: a forged map could
move a UE's slots and, since #536, reset its grant state through the restart
path. GM v2 carries the first 8 bytes of mac('G' || body..crc) with the cell
key (CELL_PSK), as the NL/MR/handover IEs do; encode/decode/find take the
Mac (mandatory: every caller decides). With a key a forged or untagged map
is refused before any offer; without one (an unauthenticated development
cell, a passive witness) only the structure is checked; v1 is refused.
Mac moves from Rach.h to GrantMap.h. The UE counts serving maps taken and
refused (cell.ue.done gm_ok/gm_refused, cell.ue.gm_refused).

docs: the M5/M6 rig results before this ran without CELL_PSK (zero tags,
unchecked; F1 was keyed) — stated, and the suite re-run keyed.

Rig (testrig, every cell role on one CELL_PSK): m6_gmauth.sh 2/2 — UE1 586-587
maps taken, 0 refused, in slot 99.8 %; UE2 under another key 0 taken, 586-587
refused, never in slot. The M6 suite keyed: handover 2+1, inter-frequency 2,
DAPS 2+2 (0/0 missing vs 11/16), measurement gaps 2, phase planning 2,
clock fit + restart 1+1 — all pass, no refusal at any keyed UE.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Authenticate v2 grant maps with the cell key

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Authenticate v2 grant maps before UEs accept scheduling or DU-restart state.
• Reject legacy maps, retain keyless structure checks, and expose UE acceptance and refusal
 counters.
• Test forged-map rejection and document keyed rig results and earlier unkeyed runs.
Diagram

graph TD
  Key["Cell key"] --> DU["DU beacon"] --> IE["GM v2 IE"] --> Gate{"CRC and optional tag valid?"} --> UE["UE grant receiver"] --> Metrics["UE map counters"]
  Key --> Gate
  Gate --> Metrics
  IE --> Witness["Witness decoder"]
  Key --> Witness
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Require a cell key for every grant-map decoder
  • ➕ Eliminates the keyless, structure-only mode and its risk of accidental unauthenticated operation.
  • ➖ Breaks unauthenticated development cells and passive witnesses without a key.

Recommendation: Keep the PR's explicit Mac parameter and keyed verification: they close the forged-map exposure while preserving the documented keyless use cases. Requiring keys everywhere is a defensible future policy change, but would change those operating modes beyond this wire-format fix.

Files changed (7) +192 / -61

Enhancement (1) +3 / -3
cell.hRequire a Mac when building DU beacons +3/-3

Require a Mac when building DU beacons

• Passes the cell authentication function into grant-map encoding so beacon construction fails if the map cannot be encoded or tagged.

examples/cell/cell.h

Bug fix (2) +90 / -26
main.cppAuthenticate grant maps across cell roles +28/-9

Authenticate grant maps across cell roles

• Supplies the cell Mac for DU beacons, UE serving and handover-target maps, and witness decoding. Counts accepted and refused serving maps, logs the first refusal, and includes the counts and keyed status in UE completion output.

examples/cell/main.cpp

GrantMap.hDefine and verify tagged grant-map IE v2 +62/-17

Define and verify tagged grant-map IE v2

• Adds an eight-byte cell-key tag after the CRC and requires a Mac argument for encoding and decoding. Keyed decoders reject invalid tags, keyless decoders check structure only, and v1 maps are refused.

src/cell/GrantMap.h

Refactor (1) +1 / -2
Rach.hUse the shared Mac definition from GrantMap.h +1/-2

Use the shared Mac definition from GrantMap.h

• Removes the local Mac type alias now defined in the already-included GrantMap.h, keeping the existing RACH authentication interface on the shared type.

src/cell/Rach.h

Tests (2) +50 / -17
cell_node_selftest.cppAdapt beacon tests to explicit keyless encoding +4/-3

Adapt beacon tests to explicit keyless encoding

• Passes an empty Mac to the DU beacon and grant-map decoder tests, preserving their round-trip and oversized-map checks.

tests/cell_node_selftest.cpp

cell_selftest.cppTest keyed grant-map round trips and rejection +46/-14

Test keyed grant-map round trips and rejection

• Covers wrong keys, altered tags, zero tags, recomputed-CRC forgeries, keyless decoding, legacy-version rejection, and Mac failure. Updates maximum-size and existing malformed-map checks for the v2 layout.

tests/cell_selftest.cpp

Documentation (1) +48 / -13
cell-m6.mdDocument grant-map authentication and keyed rig results +48/-13

Document grant-map authentication and keyed rig results

• Replaces the open forged-map exposure with the v2 authentication behavior and its remaining replay considerations. Records the keyed wrong-key and M6 suite results, and clarifies that earlier M5/M6 rig runs lacked CELL_PSK.

docs/cell-m6.md

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Tip of the day
💡 Did you know, you can switch off images and animations for a plain-text comment

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@josephnef

Copy link
Copy Markdown
Collaborator Author

CI green. Qodo: no bugs, rule violations or requirement gaps; the remaining item is the high-level alternative (require a key at every decoder), for which Qodo recommends this PR's explicit-Mac, keyed-verification approach that keeps the documented keyless modes. Merging with skip-qodo-gate.

@josephnef josephnef added the skip-qodo-gate Bypass the Qodo review gate (outage / maintainer decision) label Nov 6, 2026
@josephnef
josephnef merged commit 699d50d into master Nov 6, 2026
27 of 28 checks passed
@josephnef
josephnef deleted the feat/m6-grantmap-auth branch November 6, 2026 11:43
@josephnef josephnef mentioned this pull request Nov 6, 2026
3 of 5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-qodo-gate Bypass the Qodo review gate (outage / maintainer decision)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant