Skip to content

Fix path migration UAF: guard path promotion with InUse check - #6217

Merged
Guillaume Hetier (guhetier) merged 1 commit into
mainfrom
guhetier/path_uaf
Aug 11, 2026
Merged

Fix path migration UAF: guard path promotion with InUse check#6217
Guillaume Hetier (guhetier) merged 1 commit into
mainfrom
guhetier/path_uaf

Conversation

@guhetier

Copy link
Copy Markdown
Collaborator

Description

Fixes a use-after-free in QUIC path migration where a stale/removed path could be promoted into Paths[0] during post-processing.

Root Cause

When QuicConnReplaceRetiredCids is called during NEW_CONNECTION_ID frame processing, it may invoke QuicPathRemove, which frees the path. However, QuicConnRecvPostProcessing later attempts to promote that same path (via the *Path pointer) if it has non-probing frames and a new largest packet number — without verifying the path is still valid.

Fix

Add a (*Path)->InUse check in QuicConnRecvPostProcessing before promoting a path to active. This ensures we never promote a path that has already been removed by QuicPathRemove.

Testing

CI and validation on a repro of the issue

Documentation

N/A

Summary

Fixes a use-after-free in QUIC path migration where a stale/removed path could be promoted into Paths[0] during post-processing.

Root Cause

When QuicConnReplaceRetiredCids is called during NEW_CONNECTION_ID frame processing, it may invoke QuicPathRemove, which frees the path. However, QuicConnRecvPostProcessing later attempts to promote that same path (via the *Path pointer) if it has non-probing frames and a new largest packet number — without verifying the path is still valid.

Fix

Add a (*Path)->InUse check in QuicConnRecvPostProcessing before promoting a path to active. This ensures we never promote a path that has already been removed by QuicPathRemove.
@guhetier
Guillaume Hetier (guhetier) requested a review from a team as a code owner August 11, 2026 16:47
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.48%. Comparing base (85ee8f0) to head (1555f3c).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6217      +/-   ##
==========================================
- Coverage   86.03%   85.48%   -0.56%     
==========================================
  Files          60       60              
  Lines       18981    18981              
==========================================
- Hits        16330    16225     -105     
- Misses       2651     2756     +105     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@guhetier
Guillaume Hetier (guhetier) merged commit e0f55b5 into main Aug 11, 2026
611 of 612 checks passed
@guhetier
Guillaume Hetier (guhetier) deleted the guhetier/path_uaf branch August 11, 2026 18:26
Guillaume Hetier (guhetier) added a commit that referenced this pull request Aug 11, 2026
…6217) (#6220)

## Description

Fixes a use-after-free in QUIC path migration where a stale/removed path
could be promoted into Paths[0] during post-processing.

### Root Cause

When QuicConnReplaceRetiredCids is called during NEW_CONNECTION_ID frame
processing, it may invoke QuicPathRemove, which frees the path. However,
QuicConnRecvPostProcessing later attempts to promote that same path (via
the *Path pointer) if it has non-probing frames and a new largest packet
number — without verifying the path is still valid.

### Fix

Add a (*Path)->InUse check in QuicConnRecvPostProcessing before
promoting a path to active. This ensures we never promote a path that
has already been removed by QuicPathRemove.


## Testing

CI and validation on a repro of the issue

## Documentation

N/A

Co-authored-by: Sujeet Kumar <sujkum@microsoft.com>
Guillaume Hetier (guhetier) added a commit that referenced this pull request Aug 11, 2026
…6217) (#6219)

## Description

Fixes a use-after-free in QUIC path migration where a stale/removed path
could be promoted into Paths[0] during post-processing.

### Root Cause

When QuicConnReplaceRetiredCids is called during NEW_CONNECTION_ID frame
processing, it may invoke QuicPathRemove, which frees the path. However,
QuicConnRecvPostProcessing later attempts to promote that same path (via
the *Path pointer) if it has non-probing frames and a new largest packet
number — without verifying the path is still valid.

### Fix

Add a (*Path)->InUse check in QuicConnRecvPostProcessing before
promoting a path to active. This ensures we never promote a path that
has already been removed by QuicPathRemove.


## Testing

CI and validation on a repro of the issue

## Documentation

N/A

Co-authored-by: Sujeet Kumar <sujkum@microsoft.com>
Guillaume Hetier (guhetier) added a commit that referenced this pull request Aug 12, 2026
…6217) (#6218)

## Description

Fixes a use-after-free in QUIC path migration where a stale/removed path
could be promoted into Paths[0] during post-processing.

### Root Cause

When QuicConnReplaceRetiredCids is called during NEW_CONNECTION_ID frame
processing, it may invoke QuicPathRemove, which frees the path. However,
QuicConnRecvPostProcessing later attempts to promote that same path (via
the *Path pointer) if it has non-probing frames and a new largest packet
number — without verifying the path is still valid.

### Fix

Add a (*Path)->InUse check in QuicConnRecvPostProcessing before
promoting a path to active. This ensures we never promote a path that
has already been removed by QuicPathRemove.


## Testing

CI and validation on a repro of the issue

## Documentation

N/A

Co-authored-by: Sujeet Kumar <sujkum@microsoft.com>
Masahiro Kozuka (masa-koz) added a commit to seera-networks/msquic that referenced this pull request Aug 14, 2026
## Description

Merge `main` (tracking upstream microsoft/msquic) into `seera-main`. 25 upstream commits since the last sync at `c2271990`.

Notable ones for the fork:

| Upstream | What it changes |
|---|---|
| microsoft#6217 | Path migration UAF — guards path promotion with an `InUse` check |
| microsoft#6213 | Clamps the advertised `MaxAckDelay` to `MinAckDelay` |
| microsoft#6047 | Discards client Initial keys when Handshake write keys are installed; adds `QUIC_PACKET_BUILDER::InitialKeysDiscarded` so a flush that frees allowance reschedules |
| microsoft#6167 | Validates the Version Negotiation packet's Source CID against our Destination CID |
| microsoft#6181 | Handles zero-length datagrams in epoll and io_uring |
| microsoft#6198 | Upgrades the XDP dependency to v1.4 |
| microsoft#6199 | Removes the obsolete Linux raw datapath (`datapath_raw_linux.c`, `datapath_raw_socket_linux.c`) and their clog artifacts |
| microsoft#6155 | Shifts Linux OpenSSL CI to Ubuntu 26.04, and adds `NOLINTNEXTLINE` clang-tidy suppressions across core |

Submodules move with it: googletest `fa005b2`→`c0afb7f`, openssl `1d6826b`→`3d9ddf4`, xdp-for-windows to v1.4.

### Conflicts

Four, all resolved keeping the fork's behaviour and taking upstream's change where the two were independent.

**`src/core/bbr.c`** — upstream added a `NOLINTNEXTLINE(clang-analyzer-security.ArrayBound)` above `QuicPathGetDatagramPayloadSize(&Connection->Paths[0])`. The fork reads `PathID->Path` there instead, since congestion control is per path ID under multipath, and has no `Connection` local at all. Kept the fork's expression with upstream's suppression above it.

**`src/core/crypto.c`** — upstream added the same kind of suppression above `Connection->State.HandshakeConfirmed = TRUE`; the fork had added an early return when it is already set. Independent, so both are kept, guard first.

Both suppressions are needed, and the one in `crypto.c` had to move. The analyzer reports at the *first* access to the containing record, which in the fork's `crypto.c` is the guard, not the assignment upstream put their comment above; `NOLINTBEGIN`/`NOLINTEND` now covers both statements so it does not have to be revisited if the reported line shifts. Verified by running the CI's own toolchain — clang-tidy 21.1.8, from `ghcr.io/microsoft/msquic/linux-build-xcomp:ubuntu-26.04-cross` — over every source file this fork changes relative to upstream: all 26 pass, and removing either suppression reproduces the exact CI error.

**`.github/workflows/test.yml`, `.github/workflows/stress.yml`** — upstream bumped the Windows XDP jobs from `xdp-v1.1` to `xdp-v1.4` and, in the same region, added `WinServerPrerelease` matrix entries. Took the XDP bump; did not reintroduce `WinServerPrerelease`, which #14 removed from this fork because those jobs need Microsoft's self-hosted `1ES.Pool=1es-msquic-pool`. The fork's `windows-2025` substitutes for the two `Full.Verbose` schannel jobs are kept. Upstream's deletion of the openssl+XDP recvfuzz entry is taken as a plain deletion.

After the merge no `xdp-v1.1` reference and no `WinServerPrerelease` matrix entry remains.

## Testing

| Sweep | Result |
|---|---|
| `*Multipath*:*Path*:*Migration*:*UnconnectedSocket*:*KeepAlive*` | 109 tests pass |
| `*Basic*:*Datagram*:*Receive*:*Recv*:*Handshake*` | 986 tests pass |

Build is clean, no compiler warnings.

The first push failed the six `-CodeCheck` jobs on a single diagnostic at `crypto.c:490`, fixed in the follow-up commit. Local clang-tidy is 18 and does not reproduce it at all, which is why the merge commit shipped with the suppression in the wrong place; the CI container was used for verification afterwards.

Spot-checked that the fork's own work survived the merge intact: the per-path keep alive (`Path->LastSendTimeUs` stamped in `QuicPacketBuilderSendBatch`, read by `QuicConnPathKeepAliveTimerUpdate`), and that microsoft#6217's active-path promotion guard sits inside the existing `!MultipathNegotiated` branch so it does not affect multipath connections.

## Documentation

No documentation change of its own. `docs/Settings.md`, `docs/XDP.md`, `docs/Release.md` and `docs/TEST.md` move with upstream.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Masahiro Kozuka (masa-koz) added a commit to masa-koz/msquic-async-rs that referenced this pull request Aug 14, 2026
## Summary

Bumps the `seera-msquic` submodule `5b21c4f` → `146714f`. Unlike the recent bumps this is not a fork change: it is `seera-main` taking **25 commits of upstream microsoft/msquic** ([seera#77](seera-networks/msquic@146714f)), plus one fork commit adjusting a static-analysis suppression the merge displaced. 106 files, +544/-5355 — most of the deletions are obsolete Linux raw datapath files and Azure pipeline definitions upstream removed.

**Nothing in this crate needed changing.** `src/rs` is untouched, so the Rust bindings and every signature this crate calls are unchanged; the only public header change is one line in `msquic.hpp`, which is C++ and unused here.

## What is worth knowing

Upstream fixes that reach this crate's users:

- **Path migration use-after-free** ([#6217](microsoft/msquic#6217)). `QuicConnRecvPostProcessing` promoted a path on a non-probing frame without checking `(*Path)->InUse`, so a packet on a stale path entry could promote freed state. A one-line guard — and it is in the path handling this project exercises through `Connection::add_path()`.
- Zero length datagrams are handled in the epoll and iouring datapaths ([#6181](microsoft/msquic#6181)).
- `MinAckDelay` is clamped to `MaxAckDelay` in the local transport parameters ([#6213](microsoft/msquic#6213)).
- Client Initial keys are discarded when Handshake write keys are installed ([#6047](microsoft/msquic#6047)), and VN packet source CIDs are validated ([#6167](microsoft/msquic#6167)).
- `build.rs` no longer fails to link on lib64 distros ([#6168](microsoft/msquic#6168)) — about building this crate rather than running it.

The fork commit `f465ff0` moves a clang-analyzer `ArrayBound` suppression in `crypto.c` onto the fork's early return, which is where the analyzer now reports because that guard reads `Connection` one statement before upstream's assignment. Build-only.

## Nested submodules

The merge moves seera-msquic's own submodules: **openssl** `1d6826b` → `3d9ddf4`, googletest `fa005b2` → `c0afb7f`, xdp-for-windows `ccd59c2` → `d372b52`.

A checkout that only updates the top-level submodule will build the new source against the *old* openssl, so this bump wants `git submodule update --init --recursive` rather than a plain `git submodule update`. Both were updated here before building.

## Test plan

- `cargo test -p msquic-async --lib --no-default-features --features tokio,msquic-seera` — 43 passed, on a full rebuild of the vendored C library and the updated openssl, not a cached one

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Masahiro Kozuka (masa-koz) added a commit to seera-networks/ISEKAI-link that referenced this pull request Aug 15, 2026
`4722f6c..5b0144f` on `masa-koz/qmux-01-fix-h3-datagram`. Three commits, and the
substance is a `seera-msquic` bump (`bdf732d..8c3e201`) merging 25 commits of
upstream microsoft/msquic.

## The one that is ours

**A use-after-free in path migration** ([msquic#6217]). `QuicConnRecvPostProcessing`
promoted a path on a non-probing frame without checking `(*Path)->InUse`, so a
packet arriving on a stale path entry could promote freed state.

That is the code this repository exercises through `add_path`, on every
connection holding a relay path and a direct one at once — which is all of them
since #95 stopped switching between the two and started keeping both.

[msquic#6217]: microsoft/msquic#6217

## The rest

Upstream hygiene that reaches us regardless: zero-length datagrams handled in
the epoll and iouring datapaths, `MinAckDelay` clamped to `MaxAckDelay` in the
local transport parameters, client Initial keys discarded when Handshake write
keys are installed, VN packet source CIDs validated, and a build fix for lib64
distros.

The third commit syncs the submodule's own `Cargo.lock` to the h3 revision its
branch pins. **Nothing to do on this side** — `rust/Cargo.lock` has been on
`c916ed5` since #101; the submodule's copy was the one lagging.

## Checked

The interesting part of this change is C, so checking it needed care.

`cargo check` passed in five seconds and proved nothing: the build script does
not rerun on submodule sources, so `libmsquic.so` was still the one built on
20 July. After `cargo clean -p seera-msquic` it rebuilt and linked —
9,139,304 → 9,423,200 bytes, today's timestamp — which also exercises the
merge's `scripts/build.rs` conflict resolution, the one that touches the link
search path.

Then `cargo test --workspace --lib --bins`: `isekai-p2p` 39, `isekai-p2p-core`
34, `camera-core` 22, `camera-server` 5 and 1 ignored, and the rest.

## What is not covered

`cargo test --workspace` — without `--lib` — **hangs**, in
`camera-core/tests/video_loopback.rs`. Not this change: three abandoned runs of
that same test binary are still alive on the build machine from 2 and 3 August,
which is eleven days before this bump. CI has never noticed because
`migration-spike.yml` runs `cargo test -p camera-core --lib`, and `--lib`
excludes `tests/`.

Being reported separately. It means the loopback path has no working test
coverage on either side of this change, so what this bump rests on is the unit
tests, the link, and the CI builds.
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.

2 participants