JakeChampion / JakeChampion/trafficserver

[audit][plugin-api] TOCTOU between PluginDso::release() zero-refcount deletion and findByEffectivePath()+acquire() can resurrect a dying DSO

Open
#32 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area:plugin-api audit severity:medium
Dominant language
C++
Stars
0
Forks
0
Avg merge
8h 2m
Merged PRs (30d)
21

Description

Severity: medium · Category: race
Location: src/proxy/http/remap/PluginDso.cc:296

What's wrong

PluginDso::release() decides to delete on 0 == this->refcount_dec() without holding LoadedPlugins::_mutex; only afterwards does remove() take the mutex, erase the plugin, and schedule a DeleterContinuation (PluginDso.cc:337-351). Meanwhile PluginFactory::getRemapPlugin() on the reload thread does findByEffectivePath() under the mutex (PluginDso.cc:358-375) and then calls acquire() outside it, in the RemapPluginInst constructor (PluginFactory.cc:42-45). Interleaving: net thread T1 (tearing down an old UrlRewrite config whose last transaction just finished) drops the refcount to 0; before T1 enters remove(), reload thread T2 finds the same PluginDso in the list and returns it; T1 erases it and schedules deletion; T2 then acquire()s the doomed object (0 -> 1) and hands out a RemapPluginInst referring to a PluginDso that is deleted (and dlclose'd) one event-loop later — use-after-free on every subsequent doRemap through that instance. Reachable when back-to-back remap reloads overlap with old-config teardown on net threads.

Evidence
PluginDso.cc:293-300:
void PluginDso::release() {
  ...
  if (0 == this->refcount_dec()) {
    ...
    _plugins->remove(this);
  }
}
PluginFactory.cc:42-45:
RemapPluginInst::RemapPluginInst(RemapPluginInfo &plugin) : _plugin(plugin) {
  _plugin.acquire();
}
Suggested fix

Make find-and-acquire atomic: have LoadedPlugins::findByEffectivePath() call acquire() while still holding _mutex and skip entries whose refcount is already 0 (or perform the refcount_dec-to-zero check and list erase under the same _mutex so a concurrent finder either sees the entry gone or safely bumps it from a nonzero count).


Filed from an automated multi-lens codebase audit. Full report: CODEBASE_AUDIT.md / audit-report.html on branch claude/codebase-audit-review-9nw7vz.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with PluginDso::release() and remove() in src/proxy/http/remap/PluginDso.cc:293-300 and 337-351, then trace findByEffectivePath() at 358-375 and RemapPluginInst in PluginFactory.cc:42-45. Verify the reload and teardown interleaving, and ensure a DSO cannot be acquired after reaching zero references or being scheduled for deletion without introducing a use-after-free.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.