Skip to content

fix: Don't recurse when parsing SCONE#3460

Merged
mxinden merged 2 commits into
mozilla:mainfrom
larseggert:fix-3458
Mar 18, 2026
Merged

fix: Don't recurse when parsing SCONE#3460
mxinden merged 2 commits into
mozilla:mainfrom
larseggert:fix-3458

Conversation

@larseggert
Copy link
Copy Markdown
Collaborator

Replace recursion in decode_inner with a loop. A crafted datagram of back-to-back SCONE packets can otherwise overflow the stack.

Fixes #3458

Replace recursion in `decode_inner` with a loop. A crafted datagram of
back-to-back SCONE packets can otherwise overflow the stack.

Fixes mozilla#3458
Copilot AI review requested due to automatic review settings March 15, 2026 07:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens QUIC packet parsing in neqo-transport by removing recursion when skipping SCONE packets, preventing stack overflow from crafted inputs (per OSS-Fuzz #3458).

Changes:

  • Replaces recursive re-entry in Public::decode_inner() (when encountering SCONE) with an iterative loop.
  • Preserves existing SCONE “ignore” behavior while ensuring parsing continues safely on remaining bytes.
  • Adds a regression test that feeds a long chain of SCONE packets to validate no stack overflow occurs.

You can also share your feedback on Copilot code review. Take the survey.

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 15, 2026

Codecov Report

❌ Patch coverage is 96.42857% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.17%. Comparing base (6a953c2) to head (1cc40b8).
⚠️ Report is 19 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3460      +/-   ##
==========================================
- Coverage   94.29%   94.17%   -0.13%     
==========================================
  Files         127      131       +4     
  Lines       38680    39021     +341     
  Branches    38680    39021     +341     
==========================================
+ Hits        36474    36747     +273     
- Misses       1365     1424      +59     
- Partials      841      850       +9     
Flag Coverage Δ
freebsd 93.21% <96.42%> (-0.09%) ⬇️
linux 94.27% <96.42%> (-0.01%) ⬇️
macos 94.15% <96.42%> (-0.01%) ⬇️
windows 94.27% <96.42%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
neqo-common 98.49% <ø> (ø)
neqo-crypto 86.90% <ø> (ø)
neqo-http3 93.91% <ø> (ø)
neqo-qpack 94.81% <ø> (ø)
neqo-transport 95.19% <96.42%> (-0.05%) ⬇️
neqo-udp 82.97% <ø> (ø)
mtu 86.61% <ø> (ø)

@larseggert
Copy link
Copy Markdown
Collaborator Author

@martinthomson or would a simpler approach be to allow only one SCONE indication in a packet?

@github-actions
Copy link
Copy Markdown
Contributor

Failed Interop Tests

QUIC Interop Runner, client vs. server, differences relative to main at 70a6bc5.

neqo-pr as clientneqo-pr as server
neqo-pr vs. go-x-net: BP BA
neqo-pr vs. haproxy: BP BA
neqo-pr vs. kwik: S L1 C1 BP BA
neqo-pr vs. lsquic: run cancelled after 20 min
neqo-pr vs. msquic: A L1 🚀L2 C1
neqo-pr vs. mvfst: H DC LR M R Z 3 B U A L1 L2 C1 C2 6 BP BA
neqo-pr vs. neqo: Z A
neqo-pr vs. nginx: ⚠️L1 BP BA
neqo-pr vs. ngtcp2: CM
neqo-pr vs. picoquic: A
neqo-pr vs. quic-go: A
neqo-pr vs. quiche: 🚀L1 BP BA
neqo-pr vs. s2n-quic: BP BA CM
neqo-pr vs. tquic: S BP BA
neqo-pr vs. xquic: A 🚀C1
aioquic vs. neqo-pr: Z CM
go-x-net vs. neqo-pr: CM
kwik vs. neqo-pr: Z BP BA CM
linuxquic vs. neqo-pr: Z
lsquic vs. neqo-pr: Z
msquic vs. neqo-pr: Z ⚠️BA CM
mvfst vs. neqo-pr: Z A L1 C1 CM
neqo vs. neqo-pr: Z A
openssl vs. neqo-pr: LR M A CM
picoquic vs. neqo-pr: Z CM
quic-go vs. neqo-pr: 🚀BA CM
quiche vs. neqo-pr: Z CM
quinn vs. neqo-pr: Z V2 CM
s2n-quic vs. neqo-pr: B CM
tquic vs. neqo-pr: Z CM
xquic vs. neqo-pr: M CM
All results

Succeeded Interop Tests

QUIC Interop Runner, client vs. server

neqo-pr as client

neqo-pr as server

Unsupported Interop Tests

QUIC Interop Runner, client vs. server

neqo-pr as client

neqo-pr as server

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results

Significant performance differences relative to 2fbd329.

transfer/1-conn/1-100mb-resp (aka. Download)/mtu-1504: 💔 Performance has regressed by +2.0157%.
       time:   [204.22 ms 204.60 ms 205.00 ms]
       thrpt:  [487.81 MiB/s 488.76 MiB/s 489.66 MiB/s]
change:
       time:   [+1.6390% +2.0157% +2.3763] (p = 0.00 < 0.05)
       thrpt:  [-2.3212% -1.9759% -1.6125]
       Performance has regressed.
Found 4 outliers among 100 measurements (4.00%)
1 (1.00%) low mild
2 (2.00%) high mild
1 (1.00%) high severe
All results
transfer/1-conn/1-100mb-resp (aka. Download)/mtu-1504: 💔 Performance has regressed by +2.0157%.
       time:   [204.22 ms 204.60 ms 205.00 ms]
       thrpt:  [487.81 MiB/s 488.76 MiB/s 489.66 MiB/s]
change:
       time:   [+1.6390% +2.0157% +2.3763] (p = 0.00 < 0.05)
       thrpt:  [-2.3212% -1.9759% -1.6125]
       Performance has regressed.
Found 4 outliers among 100 measurements (4.00%)
1 (1.00%) low mild
2 (2.00%) high mild
1 (1.00%) high severe
transfer/1-conn/10_000-parallel-1b-resp (aka. RPS)/mtu-1504: No change in performance detected.
       time:   [285.09 ms 286.96 ms 288.82 ms]
       thrpt:  [34.623 Kelem/s 34.849 Kelem/s 35.077 Kelem/s]
change:
       time:   [-0.6680% +0.3438% +1.4595] (p = 0.53 > 0.05)
       thrpt:  [-1.4386% -0.3426% +0.6725]
       No change in performance detected.
transfer/1-conn/1-1b-resp (aka. HPS)/mtu-1504: No change in performance detected.
       time:   [38.557 ms 38.722 ms 38.903 ms]
       thrpt:  [25.705   B/s 25.825   B/s 25.936   B/s]
change:
       time:   [-0.4973% +0.1460% +0.7203] (p = 0.64 > 0.05)
       thrpt:  [-0.7151% -0.1458% +0.4998]
       No change in performance detected.
Found 10 outliers among 100 measurements (10.00%)
3 (3.00%) high mild
7 (7.00%) high severe
transfer/1-conn/1-100mb-req (aka. Upload)/mtu-1504: Change within noise threshold.
       time:   [205.96 ms 206.56 ms 207.37 ms]
       thrpt:  [482.23 MiB/s 484.11 MiB/s 485.54 MiB/s]
change:
       time:   [+0.9163% +1.2987% +1.7758] (p = 0.00 < 0.05)
       thrpt:  [-1.7448% -1.2820% -0.9080]
       Change within noise threshold.
Found 3 outliers among 100 measurements (3.00%)
1 (1.00%) high mild
2 (2.00%) high severe
streams/walltime/1-streams/each-1000-bytes: Change within noise threshold.
       time:   [582.80 µs 584.85 µs 587.25 µs]
       change: [-1.5092% -0.9411% -0.3108] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 8 outliers among 100 measurements (8.00%)
8 (8.00%) high severe
streams/walltime/1000-streams/each-1-bytes: Change within noise threshold.
       time:   [12.380 ms 12.409 ms 12.449 ms]
       change: [+0.1834% +0.4695% +0.7795] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high severe
streams/walltime/1000-streams/each-1000-bytes: Change within noise threshold.
       time:   [44.614 ms 44.657 ms 44.701 ms]
       change: [+0.3846% +0.5858% +0.7670] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 3 outliers among 100 measurements (3.00%)
3 (3.00%) high mild
transfer/walltime/pacing-false/varying-seeds: Change within noise threshold.
       time:   [78.419 ms 78.509 ms 78.612 ms]
       change: [-2.9660% -2.8088% -2.6519] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high severe
transfer/walltime/pacing-true/varying-seeds: No change in performance detected.
       time:   [80.082 ms 80.245 ms 80.441 ms]
       change: [-0.4766% -0.2120% +0.0788] (p = 0.13 > 0.05)
       No change in performance detected.
Found 14 outliers among 100 measurements (14.00%)
3 (3.00%) high mild
11 (11.00%) high severe
transfer/walltime/pacing-false/same-seed: Change within noise threshold.
       time:   [78.057 ms 78.152 ms 78.275 ms]
       change: [-3.2581% -3.0508% -2.8618] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 4 outliers among 100 measurements (4.00%)
1 (1.00%) high mild
3 (3.00%) high severe
transfer/walltime/pacing-true/same-seed: Change within noise threshold.
       time:   [80.083 ms 80.223 ms 80.382 ms]
       change: [-0.6675% -0.4298% -0.1741] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 11 outliers among 100 measurements (11.00%)
3 (3.00%) high mild
8 (8.00%) high severe

Download data for profiler.firefox.com or download performance comparison data.

@github-actions
Copy link
Copy Markdown
Contributor

Client/server transfer results

Performance differences relative to 2fbd329.

Transfer of 33554432 bytes over loopback, min. 100 runs. All unit-less numbers are in milliseconds.

Client vs. server (params) Mean ± σ Min Max MiB/s ± σ Δ main Δ main
msquic-neqo-cubic 187.4 ± 66.4 127.5 380.5 170.7 ± 0.5 💚 -30.4 -14.0%
neqo-s2n-cubic 217.6 ± 4.0 211.7 225.9 147.0 ± 8.0 💚 -1.8 -0.8%
quiche-neqo-cubic 179.5 ± 5.6 169.6 205.6 178.3 ± 5.7 💚 -2.4 -1.3%

Table above only shows statistically significant changes. See all results below.

All results

Transfer of 33554432 bytes over loopback, min. 100 runs. All unit-less numbers are in milliseconds.

Client vs. server (params) Mean ± σ Min Max MiB/s ± σ Δ main Δ main
google-google-nopacing 453.2 ± 4.3 447.0 465.5 70.6 ± 7.4
google-neqo-cubic 270.4 ± 5.1 261.6 289.1 118.3 ± 6.3 0.8 0.3%
msquic-msquic-nopacing 174.1 ± 71.0 118.8 392.6 183.8 ± 0.5
msquic-neqo-cubic 187.4 ± 66.4 127.5 380.5 170.7 ± 0.5 💚 -30.4 -14.0%
neqo-google-cubic 755.6 ± 4.9 749.5 775.9 42.3 ± 6.5 -0.8 -0.1%
neqo-msquic-cubic 154.3 ± 5.4 147.4 180.4 207.4 ± 5.9 0.8 0.5%
neqo-neqo-cubic 96.8 ± 4.3 87.6 106.8 330.6 ± 7.4 0.6 0.6%
neqo-neqo-cubic-nopacing 94.8 ± 4.0 86.8 102.2 337.7 ± 8.0 -0.9 -0.9%
neqo-neqo-newreno 97.8 ± 4.6 87.6 107.8 327.3 ± 7.0 1.0 1.0%
neqo-neqo-newreno-nopacing 94.5 ± 4.1 86.6 103.9 338.6 ± 7.8 -0.3 -0.3%
neqo-quiche-cubic 192.4 ± 4.2 185.9 204.3 166.3 ± 7.6 0.8 0.4%
neqo-s2n-cubic 217.6 ± 4.0 211.7 225.9 147.0 ± 8.0 💚 -1.8 -0.8%
quiche-neqo-cubic 179.5 ± 5.6 169.6 205.6 178.3 ± 5.7 💚 -2.4 -1.3%
quiche-quiche-nopacing 142.5 ± 4.9 135.5 166.6 224.6 ± 6.5
s2n-neqo-cubic 221.1 ± 7.8 208.3 278.3 144.8 ± 4.1 -1.0 -0.5%
s2n-s2n-nopacing 295.1 ± 17.6 278.2 391.9 108.4 ± 1.8

Download data for profiler.firefox.com or download performance comparison data.

Copy link
Copy Markdown
Member

@mxinden mxinden left a comment

Choose a reason for hiding this comment

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

In favor of looping instead of recursing.

@mxinden mxinden enabled auto-merge March 18, 2026 12:11
@larseggert
Copy link
Copy Markdown
Collaborator Author

@martinthomson suggest to land this and then revise if/when SCONE changes?

@mxinden mxinden added this pull request to the merge queue Mar 18, 2026
Merged via the queue into mozilla:main with commit 7c5faa7 Mar 18, 2026
186 of 188 checks passed
@larseggert larseggert deleted the fix-3458 branch March 20, 2026 05:05
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.

OSS-Fuzz issue 492849329

3 participants