https://bugs.documentfoundation.org/show_bug.cgi?id=174257

            Bug ID: 174257
           Summary: Skia: VulkanWindowContext::initializeContext frees
                    uninitialized pNext chain when Vulkan backend creation
                    fails (no Vulkan ICD) → crash on first start
           Product: LibreOffice
           Version: 25.8.7.3 release
          Hardware: x86-64 (AMD64)
                OS: Windows (All)
            Status: UNCONFIRMED
          Severity: normal
          Priority: medium
         Component: LibreOffice
          Assignee: [email protected]
          Reporter: [email protected]

Description:
On Windows with no Vulkan driver (ICD) installed, a first start with a fresh
user profile tries Skia/Vulkan. `vulkan-1.dll` loads, but Vulkan backend
creation fails because no driver is available. On that failure path,
`skwindow::internal::VulkanWindowContext::initializeContext` frees a Vulkan
features `pNext` chain whose root is a stack local that is never initialized on
this path. `sk_free()` is called on whatever value is left in that stack slot.

What happens next depends on the leftover stack contents:
- If the value is null or harmless, startup falls back to Skia/Raster. This is
what we usually see on a plain Windows install.
- If the value is a live non-heap pointer, the free crashes. We hit this every
time when soffice runs under an application-virtualization layer that hooks
heap APIs, because the hook frames leave the process heap handle in that slot.
`RtlFreeHeap` faults and the crash guard sets `ForceSkiaRaster=true`, so the
second start works.

The bug is the uninitialized read and free in LibreOffice; the virtualization
layer only makes it show up reliably. Other injected DLLs (AV hooks, overlays,
compatibility shims) could cause the same crash.


Steps to Reproduce:
1. Use a Windows x64 machine without a Vulkan ICD, for example a VM without GPU
passthrough. `vulkan-1.dll` is present, `HKLM\SOFTWARE\Khronos\Vulkan\Drivers`
is absent, and no display adapter has a `VulkanDriverName` value.
2. Delete the user profile (`%APPDATA%\LibreOffice`) or start with
`-env:UserInstallation=file:///C:/Temp/lo-fresh`.
3. Start `soffice.exe`.

To check that the bad path runs, set a breakpoint on the free helper called at
`skialo!...initializeContext+0x5ad` and look at `[rcx+8]`. Its value is
whatever the stack slot held before. Under Application Verifier, or with code
that leaves a non-null pointer at that stack depth, it crashes.


Actual Results:
`sk_free()` is called on an uninitialized pointer, which can crash and abort
startup.

Expected Results:
Vulkan backend creation fails cleanly, and LibreOffice falls back to
Skia/Raster.


Reproducible: Always


User Profile Reset: Yes

Additional Info:
## Crash details

Build ID `30742500f2d3eb4366ac312fa33d3dcabdb3eba5`. Offsets are from the
release `skialo.dll`; we didn't have LibreOffice symbols.

Original exception (nested under the crash handler's `Application::Abort`):
```
ExceptionCode: c0000005 (Access violation) reading 0x000001ce7027ffff
ntdll!RtlFreeHeap+0x7d
(heap hook)
ucrtbase!free_base+0x1b
skialo!<pNext chain free helper>            ; loops: next = node->pNext;
sk_free(node)
skialo!skwindow::internal::VulkanWindowContext::initializeContext+0x5b2
skialo!skwindow::internal::VulkanWindowContext::VulkanWindowContext+0x161
skialo!skwindow::MakeVulkanForWin+0xf0
vclplug_winlo!...
mergedlo!SkiaHelper::renderMethodToUse+0x3cf
mergedlo!SkiaHelper::isVCLSkiaEnabled+0x56
...
mergedlo!WorkWindow::ImplInit+0xe1
mergedlo!desktop_SplashScreen_get_implementation+0x92
...
mergedlo!soffice_main+0x2e5
```
The freed "node" is `0x1ce70280000`, which is the process heap handle
(`PEB.ProcessHeap`), not a heap block.

## Analysis (from disassembly of initializeContext)

- `+0x71..+0x11d`: allocates the shared context (`new` 0x538, `memset` 0) and
stores it in `fGlobalShared`.
- `+0x18b`: calls the Vulkan backend-context creation function. Its arguments
are the shared context's backend context, extensions at `[rbp+138h]`, and
features at `shared+0x50`. None of them point to `[rbp+10h]`.
- `+0x192`: `test al,al / je +0x57d` takes the failure path.
- `+0x57d`: frees the features chain at `shared+0x50`. This is fine because the
object was zeroed.
- `+0x585..+0x5a7`: resets `fGlobalShared`.
- `+0x5a9`: `lea rcx,[rbp+10h]; call <pNext chain free helper>`. This local is
never written on this path, so `[rbp+18h]` (`pNext`) holds stack garbage.

On the crashing run, the stack around `[rbp+18h]` still held values left by an
earlier `RtlAllocateHeap(ProcessHeap, …, 0x168)` call at the same depth. That
explains the heap handle value.

## Suggested fix

Value-initialize the local `VkPhysicalDeviceFeatures2`, or whichever struct
owns that `pNext` chain, at its declaration (`= {}`, or at least `pNext =
nullptr`). Alternatively, only free its chain on paths where it was filled in.
Check the other early returns in `initializeContext` too. This code may be
LibreOffice-specific (in `external/skia` patches); compare it with upstream
Skia's `tools/window/VulkanWindowContext.cpp`.

## Workaround

Set `SAL_SKIA=raster`, or `ForceSkiaRaster=true` / disable Skia in Tools →
Options → View, before the first start.

## Additional info

- With the workaround, or on the second start after the crash guard fires,
About shows `UI render: Skia/Raster`.
- A full-memory minidump (~560 MB) is available on request.

-- 
You are receiving this mail because:
You are the assignee for the bug.

Reply via email to