Skip to content

Fix UAF in adapter lifecycle: serialize Adapters ops with a mutex - #2274

Merged
apsonawane merged 3 commits into
mainfrom
asonawane/unloadadapter
Jul 14, 2026
Merged

Fix UAF in adapter lifecycle: serialize Adapters ops with a mutex#2274
apsonawane merged 3 commits into
mainfrom
asonawane/unloadadapter

Conversation

@apsonawane

Copy link
Copy Markdown
Contributor

The Adapter::ref_count_ field was a plain int32_t modified without

synchronization, and Adapters::UnloadAdapter() performed a check-then-erase

on the underlying std::unordered_map without a lock. Concurrent

OgaSetActiveAdapter / OgaUnloadAdapter calls could therefore:

  • race on ref_count_ (data race, undefined behavior, lost updates), and

  • pass the RefCount() > 0 guard in UnloadAdapter while another thread

    was mid-AcquireRef(), destroying the adapter while a reference was

    still held (use-after-free).

Add a mutex to Adapters and take it in Load/Unload/Acquire/ReleaseAdapter.

Since AcquireRef/ReleaseRef/RefCount on Adapter are only reachable through

these Adapters entry points, the single mutex now serializes both map

mutation and per-adapter ref counting, closing the TOCTOU window and the

data race in one place. ReleaseRef also restores the counter before

throwing so a caught exception does not leave it in a negative state.

The Adapter::ref_count_ field was a plain int32_t modified without

synchronization, and Adapters::UnloadAdapter() performed a check-then-erase

on the underlying std::unordered_map without a lock. Concurrent

OgaSetActiveAdapter / OgaUnloadAdapter calls could therefore:

  * race on ref_count_ (data race, undefined behavior, lost updates), and

  * pass the RefCount() > 0 guard in UnloadAdapter while another thread

    was mid-AcquireRef(), destroying the adapter while a reference was

    still held (use-after-free).

Add a mutex to Adapters and take it in Load/Unload/Acquire/ReleaseAdapter.

Since AcquireRef/ReleaseRef/RefCount on Adapter are only reachable through

these Adapters entry points, the single mutex now serializes both map

mutation and per-adapter ref counting, closing the TOCTOU window and the

data race in one place. ReleaseRef also restores the counter before

throwing so a caught exception does not leave it in a negative state.
Copilot AI review requested due to automatic review settings July 6, 2026 18:35
@apsonawane
apsonawane requested a review from a team as a code owner July 6, 2026 18:35

Copilot AI 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.

Pull request overview

This PR fixes a concurrency use-after-free hazard in the LoRA adapter lifecycle by serializing all adapter load/unload and ref-count operations behind a single Adapters mutex, preventing races between SetActiveAdapter/acquire and UnloadAdapter/erase.

Changes:

  • Add Adapters::mutex_ and document the intended locking/ref-counting invariants.
  • Guard LoadAdapter, UnloadAdapter, AcquireAdapter, and ReleaseAdapter with std::lock_guard<std::mutex> to eliminate the check-then-erase TOCTOU window.
  • Adjust Adapter::ReleaseRef() to restore the counter before throwing to avoid leaving ref_count_ negative after an exception.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/models/adapters.h Introduces a mutex on Adapters and documents the serialized access contract for adapter ref counting and map mutation.
src/models/adapters.cpp Applies locking to all adapter operations and hardens ReleaseRef() against leaving ref counts negative on exception.

Comment thread src/models/adapters.h Outdated
Comment thread src/models/adapters.cpp
…ncy regression test

- Make Adapter::AcquireRef/ReleaseRef/RefCount private and grant Adapters

  friend access so all ref-count mutations must go through Adapters::mutex_.

  This prevents future call sites from silently bypassing the mutex and

  reintroducing the data race / TOCTOU that motivated this fix.

- Add CAPITests.AdaptersConcurrentLoadUnload, a multithreaded regression

  test that hammers LoadAdapter/UnloadAdapter to guard against future

  regressions of the race and to give TSAN/ASAN a signal.
@apsonawane
apsonawane enabled auto-merge (squash) July 7, 2026 22:34
Comment thread src/models/adapters.h
Comment thread src/models/adapters.cpp
@apsonawane
apsonawane merged commit b31eea1 into main Jul 14, 2026
63 of 66 checks passed
@apsonawane
apsonawane deleted the asonawane/unloadadapter branch July 14, 2026 18:19
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.

3 participants