Use-after-free: `Managed<>` releases UR resource through a freed `adapter_impl` (Windows/icx SEH 0xC0000005)
- Dominant language
- LLVM
- Stars
- 1.5k
- Forks
- 854
- Avg merge
- 3d 17h
- Merged PRs (30d)
- 137
Description
### Describe the bug
## Summary
`sycl::detail::Managed` / `Managed`
hold a **raw** `adapter_impl *` and, in `~Managed`, call `urProgramRelease` /
`urKernelRelease` through it. Adapters are owned by `GlobalHandler` as raw
`new`/`delete` and are destroyed in `GlobalHandler::unloadAdapters()`. A
`Managed` that outlives adapter teardown therefore releases its UR resource
through a **freed** `adapter_impl` — a use-after-free.
On Windows built with **icx** this is deterministic and fatal: the freed
adapter's per-instance Windows function-pointer table (`UrFuncPtrs`) reads back
all-zero, so `call_nocheck` calls a **null** function pointer and the process
takes an access violation jumping to address `0x0`
(`SEH exception with code 0x3221225477` == `0xC0000005`). On other
configurations (MSVC `cl`, Linux/gcc) the freed memory stays usable, so the
bug is latent and the same tests pass.
## When it started
Bisected to #22047 ("[SYCL] Remove iostream from the SYCL header path",
commit 94a600cd6fd5). That PR is **not** the cause — it only re-ordered the
include graph of `adapter_impl.hpp` (added `#include `
because the moved `iostream_proxy.hpp` no longer pulled it transitively). The
re-order shifted heap/object layout and teardown timing just enough to turn the
pre-existing latent use-after-free into a deterministic crash under icx.
`build-win` was green on the parent commit (#22305) and red from #22047 onward;
the follow-up fixes #22354 and #22356 did not address this (they fixed an
unrelated `undefined reference` and a separate real heap-overflow in the
`BuildLog` test mock respectively).
## Root cause (confirmed via crash dump)
Full crash dump captured on the failing icx runner (Sysinternals procdump),
analyzed with the test exe + PDBs:
- `EXCEPTION_ACCESS_VIOLATION`, `RIP = 0x0`, exception info `[8, 0]` (execute
fault) — a call through a null function pointer.
- Faulting stack:
```
SubDevices.cpp:~191 (discarded Managed temporary from getBuiltURProgram)
sycl::detail::Managed::~Managed adapter_impl.hpp
sycl::detail::adapter_impl::call adapter_impl.hpp:124
call_nocheck: F = UrApiInfo.getFuncPtr(&UrFuncPtrs); F(...) adapter_impl.hpp:112 (F == nullptr)
```
- The `adapter_impl` the `Managed` points at has:
- `MBackend` and `adapterReleased` carrying `0xBAADF00D` (uninitialized-heap)
bytes, `0xFEEEFEEE` (freed-heap) immediately before the object;
- `UrFuncPtrs` (the Windows GetProcAddress table) **entirely zero**.
A separate, correctly-populated mock adapter exists elsewhere in the dump,
confirming the `Managed` references a freed/stale adapter, not the live one.
`UrFuncPtrs` is populated only in the `adapter_impl` constructor
(`PopulateUrFuncPtrTable`) and defaults every entry to `nullptr`; an all-zero
table is the signature of a freed/never-properly-initialized adapter object.
## Who outlives the adapter
`getBuiltURProgram()` returns `ResProgram.retain()` — a `Managed` sharing the
adapter. The built program is also cached as a `Managed` in the context's
`KernelProgramCache`. Either the cached `Managed` or a discarded `retain()`
temporary can be destroyed at/after `unloadAdapters()` deletes the adapters.
### To reproduce
## Affected tests (Windows, icx, `check-sycl-unittests` / build-win)
- `SubDevices.BuildProgramForSubSubDevices` (`sycl/unittests/program_manager/SubDevices.cpp`)
- `ImageRemoval.NativePrograms` (`sycl/unittests/program_manager/Cleanup.cpp`)
Both fail only on the `SYCL Post Commit` `build-win` job, which builds with
`cxx: icx`. They pass with `cl`, on Linux, and under Linux AddressSanitizer.
## Reproduction
`SYCL Post Commit` → `build-win` (cxx=icx). Locally: build with
`-DCMAKE_CXX_COMPILER=icx` on Windows and run
`ProgramManagerTests-Non_Preview_Tests.exe --gtest_filter=SubDevices.BuildProgramForSubSubDevices`.
You may need to set these variables as well.
-DCMAKE_C_FLAGS="/fp:precise /clang:-Wno-nonportable-include-path /clang:-Wno-cast-function-type-mismatch" -DCMAKE_CXX_FLAGS="/fp:precise /clang:-Wno-nonportable-include-path /clang:-Wno-cast-function-type-mismatch" -DCMAKE_EXE_LINKER_FLAGS=/manifest:no -DCMAKE_MODULE_LINKER_FLAGS=/manifest:no -DCMAKE_SHARED_LINKER_FLAGS=/manifest:no
### Environment
You can use this [workflow](https://github.com/intel/llvm/actions/workflows/sycl-windows-build.yml) . Set the compiler to icx and add these compiler options to the extra flags field:
```
-DCMAKE_C_FLAGS="/fp:precise /clang:-Wno-nonportable-include-path /clang:-Wno-cast-function-type-mismatch" -DCMAKE_CXX_FLAGS="/fp:precise /clang:-Wno-nonportable-include-path /clang:-Wno-cast-function-type-mismatch" -DCMAKE_EXE_LINKER_FLAGS=/manifest:no -DCMAKE_MODULE_LINKER_FLAGS=/manifest:no -DCMAKE_SHARED_LINKER_FLAGS=/manifest:no
```
### Additional context
_No response_
Contributor guide
Assessment
This issue has not been assessed yet.