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.
Environment
ultralight-sdk-latest-linux-x64.7zfrom the dev CDN, dated 2024-10-26)ulViewConfigSetIsAccelerated(false)), renderer-only C API (no AppCore window)Summary
Loading an SVG image (via
<img>) that internally references adata:URI raster (<image xlink:href="data:image/png;base64,...">) causes a hard SIGSEGV insideWebResourceLoadScheduler::scheduleLoadinlibUltralight.so.WebCore renders SVG-as-image inside an isolated utility
Page(SVGImage). External subresource references in such images are blocked, butdata:URIs are allowed and do reach the load scheduler — synchronously, while the outer document'sCachedImage::finishLoadingis parsing the SVG.scheduleLoadthen walks from the loader's frame to the owningultralight::ViewImpl(for theMaybeHandleURLSchemecustom-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:image.svg(any validdata:PNG works; this is a 1x1 transparent pixel):repro.c(pure C API):Steps:
SIGSEGV within ~1–2 seconds, while
SVGImage::dataChangedis parsing the SVG.Notes:
data:URI in<img src>does not crash (different loader path).data:case reaches the broken path.Backtrace
Disassembly analysis (the actual bug)
Faulting instruction:
mov 0x18(%rbx),%raxwith rbx = 0 (SEGV_MAPERR, address0x18). Function prologue ofscheduleLoad(SDK binary,libUltralight.so, symbol at0x54f50):The
test/jeat54f74proves the code knows this pointer can be null — but the taken branch lands exactly on the unguarded0x18(%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/MaybeHandleURLSchemelookup 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 noViewImpl(SVGImage here; likely anyPagebuilt 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.