Skip to content

stop_task use-after-free crash on macOS 11 during WKWebView dealloc #1733

@x93008

Description

@x93008

Describe the bug
On macOS 11 (Big Sur), the app crashes with EXC_BAD_ACCESS (objc_release on a freed pointer) during WKWebView deallocation when
custom URL scheme handlers are registered.

The root cause is that during WKWebView dealloc, WebKit internally calls stopAllTasksForPageplatformStopTask, which invokes
the stop_task ObjC callback. On macOS 11, the task and/or webview pointers passed to this callback may already be freed. The
current stop_task function signature uses objc2 reference types (&WryWebView, &ProtocolObject<dyn WKURLSchemeTask>), which
trigger implicit objc_retain on function entry — calling objc_retain on a freed pointer causes the crash.

There is also a related race condition: when the async protocol response handler (running on a tokio worker thread) drops its
Retained<WryWebView>, this can trigger WKWebView dealloc on that thread. The dealloc path then calls stop_task with freed task
pointers, and the Retained<ProtocolObject<dyn WKURLSchemeTask>> in the response closure may still be alive — creating a
use-after-free if drop ordering is not enforced.

Steps To Reproduce

  1. Register a custom URL scheme handler via WebViewBuilder::with_custom_protocol
  2. Load a page that triggers requests to the custom protocol
  3. Drop/close the WebView while async protocol responses may still be in-flight
  4. Crash occurs during WKWebView dealloc on macOS 11

Minimal reproduction (conceptual):

use wry::WebViewBuilder;

fn main() {
    // ... create window ...

    let webview = WebViewBuilder::new()
        .with_custom_protocol("app".into(), |_webview_id, request, responder| {
            // Simulate async response (e.g., via tokio::spawn)
            std::thread::spawn(move || {
                let response = http::Response::builder()
                    .status(200)
                    .body(std::borrow::Cow::from("hello".as_bytes()))
                    .unwrap();
                responder.respond(response);
            });
        })
        .with_url("app://localhost/index.html")
        .build(&window)
        .unwrap();

    // Drop webview shortly after — on macOS 11, this triggers the crash
    // in stop_task during WKWebView dealloc
    drop(webview);
}

Note: The crash is timing-dependent and more likely when async responses are pending during webview destruction.

**Expected behavior**
Dropping the WebView should not crash. The stop_task callback should safely handle the case where WebKit passes freed pointers during
 dealloc on macOS 11.


**Screenshots**
None

**Platform and Versions (please complete the following information):**
- OS: macOS 11.7.10 (Big Sur)
- Rustc: 1.88
- wry: 0.51 and 0.48


**Additional context**
Proposed fix:

  _this: &ProtocolObject<dyn WKURLSchemeHandler>,
  _sel: objc2::runtime::Sel,
  _webview: *mut AnyObject,  // raw pointer — no implicit objc_retain
  _task: *mut AnyObject,     // raw pointer — no implicit objc_retain
) {
  // no-op: avoid accessing task/webview — macOS 11 may pass freed pointers here
}

2. Enforce explicit drop ordering in the async response handler:

// webview must drop before task: if webview drop triggers dealloc →
// stopAllTasksForPage → platformStopTask, the task must still be alive.
drop(webview);
drop(task);
result

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions