Skip to content

[1.4.0b] SIGSEGV in WebResourceLoadScheduler::scheduleLoad when an <img> SVG references a data: URI subresource #549

Description

@postadelmaga

Environment

  • SDK: 1.4.0b.081c48b (ultralight-sdk-latest-linux-x64.7z from the dev CDN, dated 2024-10-26)
  • OS: Arch Linux x86_64
  • Renderer: CPU (ulViewConfigSetIsAccelerated(false)), renderer-only C API (no AppCore window)
  • Reproduces independently of the embedder — minimal repro below uses only the SDK.

Summary

Loading an SVG image (via <img>) that internally references a data: URI raster (<image xlink:href="data:image/png;base64,...">) causes a hard SIGSEGV inside WebResourceLoadScheduler::scheduleLoad in libUltralight.so.

WebCore renders SVG-as-image inside an isolated utility Page (SVGImage). External subresource references in such images are blocked, but data: URIs are allowed and do reach the load scheduler — synchronously, while the outer document's CachedImage::finishLoading is parsing the SVG. scheduleLoad then walks from the loader's frame to the owning ultralight::ViewImpl (for the MaybeHandleURLScheme custom-scheme hook), but the SVGImage's utility page has no ViewImpl attached, and the lookup dereferences a null/foreign pointer.

This fires on real-world sites (SVG logos/icons that embed raster fallbacks as data: URIs are common), taking the whole renderer process down.

Minimal reproduction

page.html:

<html><body><h1>svg crash test</h1><img src="image.svg"></body></html>

image.svg (any valid data: PNG works; this is a 1x1 transparent pixel):

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100"><image xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" width="100" height="100"/></svg>

repro.c (pure C API):

#include <stdio.h>
#include <unistd.h>
#include <Ultralight/CAPI.h>
#include <AppCore/CAPI.h>

int main(int argc, char **argv) {
  ulEnablePlatformFontLoader();
  ULString base = ulCreateString(".");
  ulEnablePlatformFileSystem(base); /* run from the SDK root: ./resources */
  ulDestroyString(base);
  ULConfig cfg = ulCreateConfig();
  ULRenderer ren = ulCreateRenderer(cfg);
  ulDestroyConfig(cfg);
  ULViewConfig vc = ulCreateViewConfig();
  ulViewConfigSetIsAccelerated(vc, false);
  ULView view = ulCreateView(ren, 800, 600, vc, NULL);
  ulDestroyViewConfig(vc);
  ULString url = ulCreateString(argc > 1 ? argv[1] : "http://127.0.0.1:8000/page.html");
  ulViewLoadURL(view, url);
  ulDestroyString(url);
  for (int i = 0; i < 600; i++) { ulUpdate(ren); ulRender(ren); usleep(10000); }
  puts("no crash");
  return 0;
}

Steps:

gcc repro.c -I <sdk>/include -L <sdk>/bin -lUltralight -lUltralightCore -lWebCore -lAppCore -Wl,-rpath,<sdk>/bin -o repro
python3 -m http.server 8000 &      # in the dir with page.html + image.svg
cd <sdk> && ./repro http://127.0.0.1:8000/page.html

SIGSEGV within ~1–2 seconds, while SVGImage::dataChanged is parsing the SVG.

Notes:

  • Serving the SVG over http(s) is required; the same SVG inlined as a data: URI in <img src> does not crash (different loader path).
  • An SVG referencing a relative/external raster does not crash either (blocked before reaching the scheduler, as expected). Only the allowed data: case reaches the broken path.

Backtrace

#0  WebResourceLoadScheduler::scheduleLoad(WebCore::ResourceLoader*)   (libUltralight.so + 0x54f82)
#1  WebResourceLoadScheduler::loadResource(...) lambda                 (libUltralight.so + 0x5826c)
#2  WebCore::SubresourceLoader::create(...) lambda                     (libWebCore.so)
#3  WebCore::SubresourceLoader::init(...) lambda
#4  WebCore::ResourceLoader::init(...) lambda
#5  WebCore::SubresourceLoader::willSendRequestInternal(...) lambda
#6  WebCore::ResourceLoader::willSendRequestInternal(...)
#8  WebCore::SubresourceLoader::willSendRequestInternal(...)
#9  WebCore::ResourceLoader::init(...)
#10 WebCore::SubresourceLoader::create(...)
#11 WebResourceLoadScheduler::loadResource(...)                        (libUltralight.so + 0x54e08)
#12 WebCore::CachedResource::load(WebCore::CachedResourceLoader&)
#13 WebCore::CachedResourceLoader::requestResource(...)
#14 WebCore::CachedResourceLoader::requestImage(...)
#15 WebCore::ImageLoader::updateFromElement(...)
#16 WebCore::Element::parserSetAttributes(...)
#17 WebCore::XMLDocumentParser::startElementNs(...)
#20 xmlParseChunk
#22 WebCore::XMLDocumentParser::append(...)
#23 WebCore::DecodedDataDocumentParser::appendBytes(...)
#25 WebCore::SVGImage::dataChanged(bool)                                ← isolated SVG image page
#26 WebCore::CachedImage::finishLoading(...)
#27 WebCore::SubresourceLoader::didFinishLoading(...)
#28 WebCore::CurlRequest::callClient(...) lambda
#29 WTF::RunLoop::performWork()
#32 ultralight::RendererImpl::Update()
#33 ulUpdate

Disassembly analysis (the actual bug)

Faulting instruction: mov 0x18(%rbx),%rax with rbx = 0 (SEGV_MAPERR, address 0x18). Function prologue of scheduleLoad (SDK binary, libUltralight.so, symbol at 0x54f50):

54f5e: mov  %rsi,%r13              ; r13 = resourceLoader
54f64: mov  %rsi,%rdi
54f67: call ResourceLoader::frameLoader()
54f6c: mov  0x8(%rax),%rax         ; frameLoader → +8
54f70: mov  0x8(%rax),%rbx         ; → +8 = per-page Ultralight object
54f74: test %rbx,%rbx              ; null check…
54f77: je   54f82                  ; …but the NULL branch jumps *into* the deref!
54f79: mov  (%rbx),%rax
54f7c: mov  %rbx,%rdi
54f7f: call *0x10(%rax)            ; virtual call on the page object
54f82: mov  0x18(%rbx),%rax        ; ← FAULT: executed with rbx == NULL
54f86: mov  (%rax),%r12            ; r12 = ultralight::ViewImpl*
...
54f9e: call ultralight::ViewImpl::MaybeHandleURLScheme(WTF::URL const&)

The test/je at 54f74 proves the code knows this pointer can be null — but the taken branch lands exactly on the unguarded 0x18(%rbx) dereference, so the null path crashes unconditionally. (When the pointer is instead a foreign/empty-client object — as happens for the SVGImage utility page in some runs — it crashes a few instructions earlier on the virtual call.)

Suggested fix

In WebResourceLoadScheduler::scheduleLoad, guard the entire ViewImpl/MaybeHandleURLScheme lookup on the page object being present (and being an Ultralight-owned page), falling through to normal scheduling when it isn't — matching stock WebKitLegacy behavior. Loads can legitimately originate from utility pages with no ViewImpl (SVGImage here; likely any Page built with empty clients).

Happy to test a nightly with the fix — this is 100% deterministic on our side. Found while building a browser on the 1.4 SDK; our workaround for now is supervising and auto-respawning the renderer process.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions