Shopify / Shopify/react-native-skia
use-after-free in RNSkPictureRenderer::performDraw / SkPicture ref-counting (SIGSEGV drawPicture + SIGTRAP SkRefCntBase::ref)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 8.6k
- Forks
- 647
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 35
Description
Description
We are seeing sporadic native crashes in the picture rendering path. There
are two distinct signatures, both on the main thread inside
RNSkPictureRenderer::performDraw, and both consistent with the renderer
touching an SkPicture whose refcount has already dropped to zero (i.e. the
JS side replaced/released the picture while a redraw was in flight):
- SIGTRAP —
sk_abort_no_printfired fromSkRefCntBase::ref()while
performDrawcopies itssk_sp<SkPicture>(resurrecting a dead object). - SIGSEGV / EXC_BAD_ACCESS —
SkCanvas::drawPicturedereferencing a
garbage pointer (one report flags "possible pointer authentication
failure", consistent with a freed object).
We have collected 8 crash reports since 2026-07-01 with these signatures.
The crashes are sporadic and include occurrences while the app is idle on a
screen with a continuously animating canvas.
Environment
@shopify/react-native-skia: 2.6.4 (also inspected 2.6.5–2.6.9
changelogs/commits — no picture-lifecycle changes found in that range)- React Native: 0.86.0, New Architecture enabled (Fabric), Hermes
- iOS — reports below are from the iOS Simulator (Xcode 26.3 / 17C529,
macOS 26.1 25B78, arm64); the app is also exercised on physical devices - Rendering: default Metal canvas provider (
RNSkMetalCanvasProvider) - react-native-reanimated: not installed (JS-driven rendering only)
Stack signature 1 — SIGTRAP, refcount assert on a dead SkPicture
AayuPlus-2026-07-01-213531.ips (EXC_BREAKPOINT / SIGTRAP, main thread):
0 sk_abort_no_print()
1 SkRefCntBase::ref() const::'lambda'()::operator()() const
2 SkRefCntBase::ref() const
3 SkPicture* SkSafeRef<SkPicture>(SkPicture*)
4 sk_sp<SkPicture>::sk_sp(sk_sp<SkPicture> const&)
5 sk_sp<SkPicture>::sk_sp(sk_sp<SkPicture> const&)
6 RNSkia::RNSkPictureRenderer::performDraw(std::shared_ptr<RNSkia::RNSkCanvasProvider>)
7 RNSkia::RNSkPictureRenderer::renderImmediate(std::shared_ptr<RNSkia::RNSkCanvasProvider>)
8 RNSkia::RNSkView::requestRedraw()::'lambda'()::operator()() const
… (std::function plumbing)
Reading of this stack: requestRedraw → renderImmediate → performDraw
copies the current picture sk_sp — and the copy constructor's SkSafeRef
hits Skia's "ref on 0-refcount object" abort, i.e. the picture was already
destroyed when the redraw ran.
Stack signature 2 — SIGSEGV inside drawPicture
AayuPlus-2026-07-08-121149.ips (EXC_BAD_ACCESS / SIGSEGV,
KERN_INVALID_ADDRESS at 0x0000beadd8b87e08 — possible pointer authentication failure, main thread):
0 SkCanvas::drawPicture(SkPicture const*, SkMatrix const*, SkPaint const*)
1 SkCanvas::drawPicture(SkPicture const*)
2 SkCanvas::drawPicture(sk_sp<SkPicture> const&)
3 RNSkia::RNSkPictureRenderer::performDraw(...)::'lambda'(SkCanvas*)::operator()(SkCanvas*) const
… (std::function plumbing)
11 RNSkMetalCanvasProvider::renderToCanvas(std::function<void (SkCanvas*)> const&)
12 RNSkia::RNSkPictureRenderer::performDraw(std::shared_ptr<RNSkia::RNSkCanvasProvider>)
13 RNSkia::RNSkPictureRenderer::renderImmediate(std::shared_ptr<RNSkia::RNSkCanvasProvider>)
Same entry path, but here the dead picture survives the copy and crashes
inside drawPicture when its contents are dereferenced.
Reproduction context
We don't have a minimal reproducer yet (it's a sporadic race), but the app
context is:
- Multiple
<Canvas>components on screen, each rendering an imperative
SkPicture:createPicture(cb)is called every frame from a
requestAnimationFrame→setStateloop (~60fps), and the result is
rendered through a single<Picture picture={p} />element per canvas.
Each frame therefore replaces the previous picture, which becomes
garbage-collectable immediately. - Crashes occur sporadically, including while the app idles on the screen
with the animating canvases (nothing else happening). - Frequency increased when we added a canvas with higher picture churn
(more draws per picture, still one picture/frame) — but the SIGTRAP
signature predates that change on the same 2.6.4, when our only Skia
usage was declarative JSX canvases re-rendered viarequestAnimationFrame- state (so the picture in question was the renderer's internal one).
- 8 reports between 2026-07-01 and 2026-07-08 across normal dev usage.
Mitigations we applied (reduce frequency — may help locate the window)
- Two-frame picture retention on the JS side: keeping the last two
SkPictureobjects referenced in a ref so GC cannot finalize a picture
that a not-yet-completed redraw may still read. This points at the
window being between the JS-side release/GC of the previous picture and
performDrawconsuming it. - Pausing the rAF loop when the screen is not focused (less churn,
fewer chances to race).
Both reduce, not eliminate, the exposure — the race looks like it needs a
fix at the RNSkPictureRenderer level (e.g. taking a strong ref under the
same lock/thread that swaps the picture, or deferring picture destruction
to the render thread).
Possibly related open issues
- #3589 — "iOS - EXC_BAD_ACCESS - Attempted to dereference garbage pointer"
- #3744 — "fix: prevent EXC_BAD_ACCESS in Promise destructor after runtime
teardown"
Crash reports
The two full (redacted) .ips crash reports exceed the issue body size
limit, so they are attached as a gist:
https://gist.github.com/iamdiwakar/69bbcef50c7ceb0f62f78493b4c76d6f
AayuPlus-2026-07-01-213531.ips— SIGTRAP signatureAayuPlus-2026-07-08-121149.ips— SIGSEGV signature
Redactions are limited to incident_id/crashReporterKey/modelCode;
all thread stacks, binary images (incl. UUIDs), and exception details
are intact. Happy to test candidate patches.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at RNSkPictureRenderer::performDraw and follow the requestRedraw → renderImmediate path, then inspect how the current SkPicture is swapped and retained; no source files or tests are named. Reproduce with multiple Canvas components creating a picture every animation frame through RNSkMetalCanvasProvider, and consider the work done when the reported SIGTRAP and SIGSEGV no longer occur during sustained churn.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, react-native
- Domain
- computer-graphics, mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100