Description
FileStreamer::start() in source/extensions/filters/http/file_server/file_streamer.cc passes [this] captures to AsyncFileManager::stat() and AsyncFileManager::read():
cancel_callback_ = file_server_config_->asyncFileManager()->stat(
dispatcher_, file_path_.string(), [this](absl::StatusOr<struct stat> result) {
// 'this' freed if FileStreamer was destroyed before stat completed
...
});
The async-file manager guarantees the callback eventually runs (cancellation does not prevent the callback from firing). If the request is cancelled (downstream RST, stream timeout, filter chain destruction) before the async-file operation completes, the callback fires against a destroyed FileStreamer and dereferences freed memory.
Reproduction
- Configure the
file_server filter to serve a path backed by a slow AsyncFileManager (large file, or a backend that is intentionally throttled).
- Send a
GET and quickly cancel / reset the request before file_server sees the stat / read callback.
- Repeat under load — ASAN surfaces the UAF in
FileStreamer::start()'s stat callback or FileStreamer::read()'s read callback.
Severity / scope
CWE-416 use-after-free; reachable from any deployment that uses the file_server HTTP filter when downstream clients can cancel or reset requests while an async-file operation is in flight. Heap-grooming under the right object lifetimes can produce remote code execution; otherwise the worker process crashes.
Proposed fix
Hold a std::shared_ptr<bool> sentinel in FileStreamer and capture [this, alive = std::weak_ptr<bool>(alive_)] in the async-file callbacks. When the sentinel cannot be locked the callback short-circuits to a no-op. Pull request follows.
Background
This was first reported privately as GHSA-c4w8-wqj5-4xph. The envoy security team (@wbpcode) asked for this to be fixed in the open given the file_server filter's WIP status, so I'm filing here per their redirect.
Description
FileStreamer::start()insource/extensions/filters/http/file_server/file_streamer.ccpasses[this]captures toAsyncFileManager::stat()andAsyncFileManager::read():The async-file manager guarantees the callback eventually runs (cancellation does not prevent the callback from firing). If the request is cancelled (downstream RST, stream timeout, filter chain destruction) before the async-file operation completes, the callback fires against a destroyed
FileStreamerand dereferences freed memory.Reproduction
file_serverfilter to serve a path backed by a slowAsyncFileManager(large file, or a backend that is intentionally throttled).GETand quickly cancel / reset the request beforefile_serversees thestat/readcallback.FileStreamer::start()'s stat callback orFileStreamer::read()'s read callback.Severity / scope
CWE-416 use-after-free; reachable from any deployment that uses the
file_serverHTTP filter when downstream clients can cancel or reset requests while an async-file operation is in flight. Heap-grooming under the right object lifetimes can produce remote code execution; otherwise the worker process crashes.Proposed fix
Hold a
std::shared_ptr<bool>sentinel inFileStreamerand capture[this, alive = std::weak_ptr<bool>(alive_)]in the async-file callbacks. When the sentinel cannot be locked the callback short-circuits to a no-op. Pull request follows.Background
This was first reported privately as
GHSA-c4w8-wqj5-4xph. The envoy security team (@wbpcode) asked for this to be fixed in the open given thefile_serverfilter's WIP status, so I'm filing here per their redirect.