Skip to content

Reject Async receivers in low-level Python call_0/call_1 before vtable dispatch #196

Description

@lei9444

Problem Description

The Python binding's low-level DynWinRTValue.call_0() and call_1() can accept a WinRTValue::Async as their receiver. WinRTValue::as_object() converts the stored IAsyncInfo to an IUnknown pointer (crates/dynwinrt/src/value.rs), and these helpers then pass that pointer, a caller-supplied vtable index, and a caller-supplied ABI signature directly to call_dynamic() (bindings/py/src/runtime.rs). The native dispatcher reads the function pointer at that slot (crates/dynwinrt/src/call.rs). It does not first QueryInterface to the concrete IAsyncOperation<T>/IAsyncAction interface or verify that the selected slot has the requested signature.

A wrong receiver/slot/signature can call an unrelated method with an incompatible ABI or read past the vtable, potentially causing an access violation instead of a Python exception. This is a source-level risk; no crashing invocation was run during this investigation. Ordinary async wait()/generated await uses the concrete async interface, and DynWinRTMethodHandle.invoke() already rejects Async receivers. This is distinct from the apartment-shutdown release crash tracked in #189 and is not introduced or resolved by #188.

Steps To Reproduce

  1. Obtain a raw DynWinRTValue holding the result of a WinRT async method (WinRTValue::Async internally).
  2. Inspect DynWinRTValue.call_0() / call_1() in bindings/py/src/runtime.rs: both use .as_object() to obtain the receiver pointer; WinRTValue::as_object() accepts Async in crates/dynwinrt/src/value.rs.
  3. These helpers pass that pointer to a dynamically built method with a caller-provided index/signature. Calling with an index/signature intended for another interface reaches get_vtable_function_ptr() without a receiver-interface check. Do not run such a potentially crashing call outside an isolated process.

No live crash reproduction is claimed here; the steps establish the unsafe dispatch path.

Expected Results

call_0() and call_1() should reject an Async receiver before native dispatch, with a clear Python exception, consistent with call() and DynWinRTMethodHandle.invoke(). Async completion should continue to use wait()/the generated await path, which resolves the concrete async interface before calling GetResults. Add a regression test that verifies rejection without invoking an invalid native vtable slot.

Component

Python bindings (dynwinrt)

Windows SDK Version

10.0.26100

Environment

  • Windows; Python binding using the WinRT dynamic invocation core.
  • Source-level analysis only; a specific OS/Python version or observed crash is not required to establish the missing receiver check.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions