consomme: fix deadlock and UAF in Windows DNS resolver - #4032
consomme: fix deadlock and UAF in Windows DNS resolver#4032Steven Malis (smalis-msft) merged 5 commits into
Conversation
|
This PR modifies files containing For more on why we check whole files, instead of just diffs, check out the Rustonomicon |
There was a problem hiding this comment.
Pull request overview
This PR updates the Windows DnsQueryRaw backend in net_consomme to reduce mutex hold times around pending_requests, specifically avoiding holding the lock while calling DnsCancelQueryRaw during shutdown to prevent potential callback/lock re-entrancy deadlocks.
Changes:
- Narrow
pending_requestslock scope when pre-inserting the cancel-handle placeholder and readingpending_count. - Avoid holding the
pending_requestsmutex acrossDnsCancelQueryRawcalls bymem::take-ing the slab incancel_all. - Minor lock-scope tightening in the callback removal path.
|
This fix does fix a real deadlock, but it exacerbates a use after free of the cancel context. We could fix this with ref counts, but I want to explore fixing this with a task-oriented approach. Let me see if I can coax a better fix out of copilot. |
|
No, that's not practical due to other structural limitations (no real way for an endpoint to spawn its own tasks right now), plus it may interfere with some multiqueue refactoring going on. Can you add a ref count and have cancel_io take refs on everything under the lock and drop, rather than drain? And then the callback should assert that it removed its own thing from the slab, rather than just try_remove. That should fix the use after free and deadlock, I believe. |
|
I'm not 100% sure I understood, is this what you meant? |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
vm/devices/net/net_consomme/consomme/src/dns_resolver/windows/mod.rs:268
Slab::removepanics if the key is vacant. Since this code explicitly allows the entry to already be gone (e.g. callback raced before handle update / cancel_all clears), this should usetry_removelike before to avoid a potential panic in the Windows callback thread.
let _ = context.pending_requests.lock().remove(context.slab_key);
vm/devices/net/net_consomme/consomme/src/dns_resolver/windows/mod.rs:199
cancel_allno longer clears the slab; it clones handles but leaves all entries tracked until callbacks run. This keeps memory/live entries longer than necessary and makes repeated cancellation attempts possible ifcancel_allis ever called more than once. You can keep the narrower lock window while preserving the old “drain then cancel” semantics by draining into a Vec under the lock and cancelling after releasing it.
This issue also appears on line 268 of the same file.
fn cancel_all(&mut self) {
let pending: Vec<_> = self
.pending_requests
.lock()
.iter()
.map(|(_, cancel_handle)| cancel_handle.clone())
.collect();
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
vm/devices/net/net_consomme/consomme/src/dns_resolver/windows/mod.rs:263
Slab::removewill panic if the entry was already removed (e.g.,cancel_all()now swaps/drains the slab, and callbacks can still fire afterward). The callback should tolerate missing entries and avoid panicking in this race.
let context = unsafe { Box::from_raw(query_context.cast::<RawCallbackContext>().cast_mut()) };
let _ = context.pending_requests.lock().remove(context.slab_key);
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
vm/devices/net/net_consomme/consomme/src/dns_resolver/windows/mod.rs:183
Slab::removepanics if the key is already vacant. Usingtry_removehere makes the error path resilient to unexpected callback timing (or future changes that may remove the entry earlier) and avoids a potential panic.
self.pending_requests.lock().remove(slab_key);
vm/devices/net/net_consomme/consomme/src/dns_resolver/windows/mod.rs:282
Slab::removewill panic if the entry is already missing. The previous code usedtry_remove, which keeps the callback panic-free if the slab entry was removed elsewhere (e.g., due to an unexpected Windows API behavior or a future refactor).
let _cancel_handle = context.pending_requests.lock().remove(context.slab_key);
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
vm/devices/net/net_consomme/consomme/src/dns_resolver/windows/mod.rs:282
Slab::removewill panic if the entry was already removed (e.g., by the error path inquery()or an unexpected callback ordering). Usingtry_removekeeps the cancel-handle-lifetime intent without risking a crash.
let _cancel_handle = context.pending_requests.lock().remove(context.slab_key);
Daman Mulye (damanm24)
left a comment
There was a problem hiding this comment.
LGTM - thanks for fixing this!
ed36005
into
microsoft:main
…4122) Backport of #4032 to `release/1.8.2607`. The cherry-pick of ed36005 applied cleanly onto `release/1.8.2607` with no conflicts and no manual edits. Original PR: #4032 --- *This backport PR was created by an AI agent (GitHub Copilot) on behalf of @smalis-msft.*
We had a DNS vmm test flake by timing out. The lock on pending_requests is being held inside cancel_all while a
completion called the callback, which tries to take the same mutex, resulting in a deadlock. Fix this by not holding the lock over the calls to DnsCancelQueryRaw, but also narrow the lock windows everywhere else too. Then wrap the handle in an Arc to ensure that it stays alive across all possible asynchronous flows, fixing a potential UAF.