Skip to content

fix: Fix msgpack decode and auth-method encoding bugs#895

Merged
gtema merged 1 commit into
mainfrom
claude/review-token-driver-fernet-21ssvf
Jul 2, 2026
Merged

fix: Fix msgpack decode and auth-method encoding bugs#895
gtema merged 1 commit into
mainfrom
claude/review-token-driver-fernet-21ssvf

Conversation

@gtema

@gtema gtema commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes several correctness/security bugs found while reviewing the Fernet
token driver:

  • read_str/read_uuid/read_audit_ids used read_pfix (a marker-aware
    positive-fixint reader) to read the raw length byte that follows a
    Bin8 marker, and read_str didn't handle the Str8/Str16/Str32 markers
    that write_str actually emits for strings >=32 bytes. Any non-UUID
    identifier >=128 bytes or string field >=32 bytes (system_id,
    protocol_id, long federated user/group ids, etc.) failed to decode.
    Both readers now correctly parse the raw (non-MessagePack) length that
    follows Bin8/16/32 and Str8/16/32 markers.

  • The auth-methods bitmask byte was written with write_pfix, which
    asserts val < 128 and panics for any combination using the 8th
    configured auth method (bit 128). Added write_auth_methods_code /
    read_auth_methods_code helpers that use a positive fixint for values
    <128 (unchanged wire format, still fully backward compatible) and a
    MessagePack uint8 for values >=128, and switched all ten token payload
    modules to use them.

  • FernetTokenProvider::reload_config computed 1u8 << k for each
    configured auth method by index; with more than 8 methods configured
    this shift-overflows (panics in debug, silently collides bits in
    release). Now caps the bitmask to the first 8 methods and warns if
    more are configured.

  • FernetTokenProvider::decode_auth_methods's cache-miss fallback walked
    the bit->name map in ascending order, but the "auth / idx == 1" bit
    test is only valid from the largest bit down to the smallest;
    ascending order silently dropped smaller bits whenever a larger bit
    was also set, under-reporting the auth methods used for a token.
    Fixed to iterate in descending order.

  • FernetUtils::create_tmp_new_key installed the scopeguard that restores
    the original euid/egid only after both setegid and seteuid succeeded,
    so a seteuid failure after a successful setegid left the process with
    its effective group permanently changed. The guard is now installed
    right after setegid succeeds, before seteuid is attempted.

Added regression tests for each of the above.

Signed-off-by: Artem Goncharov artem.goncharov@gmail.com

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🦢 Load Test Results

Goose Attack Report

Plan Overview

Action Started Stopped Elapsed Users
Increasing 26-07-02 07:37:30 26-07-02 07:37:40 00:00:10 0 → 20
Maintaining 26-07-02 07:37:40 26-07-02 07:38:10 00:00:30 20
Decreasing 26-07-02 07:38:10 26-07-02 07:38:10 00:00:00 0 ← 20

Request Metrics

Method Name # Requests # Fails Average (ms) Min (ms) Max (ms) RPS Failures/s
DELETE DELETE /v3/auth/tokens 825 0 66.12 16 109 27.50 0.00
DELETE DELETE /v3/projects/:id (teardown) 2 0 38.00 31 45 0.07 0.00
DELETE DELETE /v3/users/:id (teardown) 3 0 37.67 34 45 0.10 0.00
GET 4861 0 61.42 42 127 162.03 0.00
GET GET /v3/auth/tokens (validate new) 824 0 66.04 39 97 27.47 0.00
GET GET /v3/projects/:id 1179 0 50.52 43 73 39.30 0.00
GET GET /v3/users/:id 1647 0 54.35 46 79 54.90 0.00
POST POST /v3/auth/tokens 821 0 49.31 42 68 27.37 0.00
Aggregated 10162 0 58.78 16 127 338.73 0.00

Response Time Metrics

Method Name 50%ile (ms) 60%ile (ms) 70%ile (ms) 80%ile (ms) 90%ile (ms) 95%ile (ms) 99%ile (ms) 100%ile (ms)
DELETE DELETE /v3/auth/tokens 66 67 67 68 70 72 82 109
DELETE DELETE /v3/projects/:id (teardown) 31 31 31 45 45 45 45 45
DELETE DELETE /v3/users/:id (teardown) 34 34 34 34 45 45 45 45
GET 54 58 60 68 95 97 100 127
GET GET /v3/auth/tokens (validate new) 66 66 67 68 70 71 78 97
GET GET /v3/projects/:id 50 51 52 52 54 55 59 73
GET GET /v3/users/:id 54 55 55 56 58 59 64 79
POST POST /v3/auth/tokens 49 50 50 51 52 54 58 68
Aggregated 54 57 61 65 73 95 99 127

Status Code Metrics

Method Name Status Codes
DELETE DELETE /v3/auth/tokens 825 [204]
DELETE DELETE /v3/projects/:id (teardown) 2 [204]
DELETE DELETE /v3/users/:id (teardown) 3 [204]
GET 4,861 [200]
GET GET /v3/auth/tokens (validate new) 824 [200]
GET GET /v3/projects/:id 1,179 [200]
GET GET /v3/users/:id 1,647 [200]
POST POST /v3/auth/tokens 821 [200]
Aggregated 830 [204], 9,332 [200]

Transaction Metrics

Transaction # Times Run # Fails Average (ms) Min (ms) Max (ms) RPS Failures/s
ReadHeavy
0.0 0 0 0.00 0 0 0.00 0.00
0.1 1306 0 59.59 52 84 43.53 0.00
0.2 1305 0 50.08 42 76 43.50 0.00
0.3 1306 0 50.20 44 72 43.53 0.00
TokenLifecycle
1.0 0 0 0.00 0 0 0.00 0.00
1.1 825 0 182.61 104 231 27.50 0.00
ValidateToken
2.0 0 0 0.00 0 0 0.00 0.00
2.1 944 0 95.38 64 127 31.47 0.00
UserCRUD
3.0 0 0 0.00 0 0 0.00 0.00
3.1 0 0 0.00 0 0 0.00 0.00
3.2 1647 0 54.39 46 79 54.90 0.00
3.3 3 0 37.67 34 45 0.10 0.00
ProjectCRUD
4.0 0 0 0.00 0 0 0.00 0.00
4.1 0 0 0.00 0 0 0.00 0.00
4.2 1179 0 50.56 43 73 39.30 0.00
4.3 2 0 38.00 31 45 0.07 0.00
Aggregated 8517 0 70.13 31 231 283.90 0.00

Scenario Metrics

Transaction # Users # Times Run Average (ms) Min (ms) Max (ms) Scenarios/s Iterations
ReadHeavy 7 1303 160.86 148 201 43.43 186.14
TokenLifecycle 5 820 182.85 168 231 27.33 164.00
ValidateToken 3 941 95.43 84 127 31.37 313.67
UserCRUD 3 1644 54.41 46 79 54.80 548.00
ProjectCRUD 2 1177 50.57 43 73 39.23 588.50
Aggregated 20 5885 101.66 43 231 196.17 1800.31

View full report

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchclaude/review-token-driver-fernet-21ssvf
Testbedubuntu-latest
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
nanoseconds (ns)
(Result Δ%)
Upper Boundary
nanoseconds (ns)
(Limit %)
Command_Serde/apply/remove📈 view plot
🚷 view threshold
218,270.00 ns
(-21.41%)Baseline: 277,737.50 ns
1,683,725.46 ns
(12.96%)
Command_Serde/apply/set📈 view plot
🚷 view threshold
209,560.00 ns
(-7.91%)Baseline: 227,551.36 ns
980,590.97 ns
(21.37%)
Command_Serde/pack/delete📈 view plot
🚷 view threshold
118.93 ns
(-1.30%)Baseline: 120.49 ns
140.35 ns
(84.74%)
Command_Serde/pack/delete_index📈 view plot
🚷 view threshold
109.05 ns
(-0.84%)Baseline: 109.97 ns
127.97 ns
(85.22%)
Command_Serde/pack/set📈 view plot
🚷 view threshold
191.30 ns
(-1.01%)Baseline: 193.25 ns
228.86 ns
(83.59%)
Command_Serde/pack/set_index📈 view plot
🚷 view threshold
108.85 ns
(-1.04%)Baseline: 109.99 ns
127.91 ns
(85.10%)
Command_Serde/unpack/delete📈 view plot
🚷 view threshold
199.03 ns
(+2.91%)Baseline: 193.40 ns
234.43 ns
(84.90%)
Command_Serde/unpack/delete_index📈 view plot
🚷 view threshold
162.56 ns
(+0.50%)Baseline: 161.75 ns
198.41 ns
(81.93%)
Command_Serde/unpack/set📈 view plot
🚷 view threshold
272.64 ns
(+4.15%)Baseline: 261.76 ns
321.95 ns
(84.68%)
Command_Serde/unpack/set_index📈 view plot
🚷 view threshold
160.69 ns
(+0.30%)Baseline: 160.21 ns
195.58 ns
(82.16%)
Payload_encryption/pack/remove_cmd📈 view plot
🚷 view threshold
116.94 ns
(-1.05%)Baseline: 118.18 ns
150.85 ns
(77.52%)
Payload_encryption/pack/set_cmd📈 view plot
🚷 view threshold
191.72 ns
(-6.96%)Baseline: 206.06 ns
269.63 ns
(71.11%)
Payload_encryption/unpack/remove_cmd📈 view plot
🚷 view threshold
213.84 ns
(+4.19%)Baseline: 205.24 ns
246.43 ns
(86.78%)
Payload_encryption/unpack/set_cmd📈 view plot
🚷 view threshold
286.12 ns
(+4.56%)Baseline: 273.63 ns
336.40 ns
(85.05%)
Raft_1Node_Latency/prefix/1node📈 view plot
🚷 view threshold
1,929,400.00 ns
(-33.47%)Baseline: 2,899,955.78 ns
5,886,565.94 ns
(32.78%)
Raft_1Node_Latency/read/1node📈 view plot
🚷 view threshold
39,507.00 ns
(+534.13%)Baseline: 6,230.07 ns
40,011.94 ns
(98.74%)
Raft_1Node_Latency/remove/1node📈 view plot
🚷 view threshold
476,860.00 ns
(-10.94%)Baseline: 535,433.75 ns
2,288,831.35 ns
(20.83%)
Raft_1Node_Latency/write/1node📈 view plot
🚷 view threshold
533,330.00 ns
(-3.76%)Baseline: 554,192.03 ns
2,122,030.61 ns
(25.13%)
build_snapshot/default📈 view plot
🚷 view threshold
102,450.00 ns
(-1.13%)Baseline: 103,618.45 ns
162,411.12 ns
(63.08%)
fernet token/project📈 view plot
🚷 view threshold
1,403.40 ns
(+0.77%)Baseline: 1,392.64 ns
1,616.66 ns
(86.81%)
get_data_keyspace📈 view plot
🚷 view threshold
0.31 ns
(-1.69%)Baseline: 0.32 ns
0.37 ns
(83.99%)
get_db📈 view plot
🚷 view threshold
0.31 ns
(-1.44%)Baseline: 0.32 ns
0.37 ns
(84.32%)
get_fernet_token_timestamp/project📈 view plot
🚷 view threshold
144.98 ns
(-0.51%)Baseline: 145.72 ns
179.69 ns
(80.68%)
get_keyspace📈 view plot
🚷 view threshold
4.36 ns
(-7.90%)Baseline: 4.74 ns
8.76 ns
(49.81%)
🐰 View full continuous benchmarking report in Bencher

Fixes several correctness/security bugs found while reviewing the Fernet
token driver:

- read_str/read_uuid/read_audit_ids used read_pfix (a marker-aware
  positive-fixint reader) to read the raw length byte that follows a
  Bin8 marker, and read_str didn't handle the Str8/Str16/Str32 markers
  that write_str actually emits for strings >=32 bytes. Any non-UUID
  identifier >=128 bytes or string field >=32 bytes (system_id,
  protocol_id, long federated user/group ids, etc.) failed to decode.
  Both readers now correctly parse the raw (non-MessagePack) length that
  follows Bin8/16/32 and Str8/16/32 markers.

- The auth-methods bitmask byte was written with write_pfix, which
  asserts val < 128 and panics for any combination using the 8th
  configured auth method (bit 128). Added write_auth_methods_code /
  read_auth_methods_code helpers that use a positive fixint for values
  <128 (unchanged wire format, still fully backward compatible) and a
  MessagePack uint8 for values >=128, and switched all ten token payload
  modules to use them.

- FernetTokenProvider::reload_config computed 1u8 << k for each
  configured auth method by index; with more than 8 methods configured
  this shift-overflows (panics in debug, silently collides bits in
  release). Now caps the bitmask to the first 8 methods and warns if
  more are configured.

- FernetTokenProvider::decode_auth_methods's cache-miss fallback walked
  the bit->name map in ascending order, but the "auth / idx == 1" bit
  test is only valid from the largest bit down to the smallest;
  ascending order silently dropped smaller bits whenever a larger bit
  was also set, under-reporting the auth methods used for a token.
  Fixed to iterate in descending order.

- FernetUtils::create_tmp_new_key installed the scopeguard that restores
  the original euid/egid only after both setegid and seteuid succeeded,
  so a seteuid failure after a successful setegid left the process with
  its effective group permanently changed. The guard is now installed
  right after setegid succeeds, before seteuid is attempted.

Added regression tests for each of the above.

Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
@gtema gtema force-pushed the claude/review-token-driver-fernet-21ssvf branch from f52e7b3 to 9402b7a Compare July 2, 2026 07:26
@gtema gtema merged commit 1e92488 into main Jul 2, 2026
30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant