Describe the bug
Summary
sycl::detail::Managed<ur_program_handle_t> / Managed<ur_kernel_handle_t>
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 94a600c). That PR is not the cause — it only re-ordered the
include graph of adapter_impl.hpp (added #include <detail/backend_impl.hpp>
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<ur_program_handle_t>::~Managed adapter_impl.hpp
sycl::detail::adapter_impl::call<urProgramRelease> 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 . 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
Describe the bug
Summary
sycl::detail::Managed<ur_program_handle_t>/Managed<ur_kernel_handle_t>hold a raw
adapter_impl *and, in~Managed, callurProgramRelease/urKernelReleasethrough it. Adapters are owned byGlobalHandleras rawnew/deleteand are destroyed inGlobalHandler::unloadAdapters(). AManagedthat outlives adapter teardown therefore releases its UR resourcethrough 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 backall-zero, so
call_nocheckcalls a null function pointer and the processtakes an access violation jumping to address
0x0(
SEH exception with code 0x3221225477==0xC0000005). On otherconfigurations (MSVC
cl, Linux/gcc) the freed memory stays usable, so thebug is latent and the same tests pass.
When it started
Bisected to #22047 ("[SYCL] Remove iostream from the SYCL header path",
commit 94a600c). That PR is not the cause — it only re-ordered the
include graph of
adapter_impl.hpp(added#include <detail/backend_impl.hpp>because the moved
iostream_proxy.hppno longer pulled it transitively). There-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-winwas 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 referenceand a separate real heap-overflow in theBuildLogtest 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](executefault) — a call through a null function pointer.
adapter_impltheManagedpoints at has:MBackendandadapterReleasedcarrying0xBAADF00D(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
Managedreferences a freed/stale adapter, not the live one.UrFuncPtrsis populated only in theadapter_implconstructor(
PopulateUrFuncPtrTable) and defaults every entry tonullptr; an all-zerotable is the signature of a freed/never-properly-initialized adapter object.
Who outlives the adapter
getBuiltURProgram()returnsResProgram.retain()— aManagedsharing theadapter. The built program is also cached as a
Managedin the context'sKernelProgramCache. Either the cachedManagedor a discardedretain()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 Commitbuild-winjob, which builds withcxx: icx. They pass withcl, on Linux, and under Linux AddressSanitizer.Reproduction
SYCL Post Commit→build-win(cxx=icx). Locally: build with-DCMAKE_CXX_COMPILER=icxon Windows and runProgramManagerTests-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 . Set the compiler to icx and add these compiler options to the extra flags field:
Additional context
No response