JakeChampion / JakeChampion/trafficserver
[audit][plugin-api] TOCTOU between PluginDso::release() zero-refcount deletion and findByEffectivePath()+acquire() can resurrect a dying DSO
Nobody has claimed this yet.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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