intel / intel/llvm

Use-after-free: `Managed<>` releases UR resource through a freed `adapter_impl` (Windows/icx SEH 0xC0000005)

Open
#22,367 3 comments 0 reactions 0 assignees View on GitHub
bug unified-runtime
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.