Summary
Clipboard::get_text() (and friends that go through string_from_type) on macOS can SIGSEGV during the per-item stringForType: iteration when another process mutates the pasteboard at the same time. The crash lands in objc_msgSend after _updateTypeCacheIfNeeded deref's a freed Objective-C pointer.
Environment
- arboard
3.6.1 (latest)
- macOS
26.5 (build 25F5068a — beta seed)
- Apple Silicon (
Mac16,6)
- Crash thread name:
clipboard-capture (a worker thread the embedding app routes Cmd+C/X/V events to via a serial dispatch queue)
What I see
Thread 106 Crashed:: clipboard-capture
Exception: EXC_BAD_ACCESS (SIGSEGV) — KERN_INVALID_ADDRESS at 0x0071fae168e6b598
0 libobjc.A.dylib objc_msgSend + 32
1 AppKit -[NSPasteboard _updateTypeCacheIfNeeded] + 1064
2 AppKit -[NSPasteboard _typesAtIndex:combinesItems:] + 36
3 AppKit -[NSPasteboard _canRequestDataForType:index:usesPboardTypes:combinesItems:] + 156
4 AppKit -[NSPasteboard _dataForType:index:usesPboardTypes:combinesItems:securityScoped:] + 236
5 AppKit -[NSPasteboardItem __dataForType:async:completionHandler:] + 316
6 AppKit -[NSPasteboardItem stringForType:] + 28
7 app objc2_app_kit::generated::__NSPasteboardItem::NSPasteboardItem::stringForType
8 app arboard::platform::osx::Clipboard::string_from_type + 292
9 app cidre::blocks::Layout1Mut<Closure>::invoke0 + 88
10 libdispatch.dylib _dispatch_client_callout + 16
11 libdispatch.dylib _dispatch_lane_barrier_sync_invoke_and_complete + 56
The bad address 0x0071fae168e6b598 looks like a freed Objective-C reference whose memory was reused — _updateTypeCacheIfNeeded is iterating through cached type entries and one of them no longer points to a live object.
Why I think it happens
src/platform/osx.rs deliberately uses pasteboardItems() + per-item stringForType: instead of NSPasteboard.string(forType:) directly, with this comment explaining the choice:
// XXX: We explicitly use `pasteboardItems` and not `stringForType` since the latter will concat
// multiple strings, if present, into one and return it instead of reading just the first which is
// `arboard`'s historical behavior.
let contents = unsafe { self.pasteboard.pasteboardItems() }
.ok_or_else(|| Error::unknown("NSPasteboard#pasteboardItems errored"))?;
for item in contents {
if let Some(string) = unsafe { item.stringForType(type_) } {
return Ok(string.to_string());
}
}
The NSPasteboardItem instances returned by pasteboardItems() reference into AppKit's type-cache. If another app calls setData:forType: on the general pasteboard between pasteboardItems() returning and the stringForType: call hitting the bad item, the cache is invalidated mid-iteration and the now-stale item pointer crashes.
The embedding app already serializes all clipboard reads through a single GCD serial queue with an autorelease pool wrapper — that prevents intra-process races, but cannot stop external apps (system clipboard manager, paste apps, etc.) from mutating the pasteboard at the same instant.
This crash signature has been observed at least twice in the wild on macOS 26.x:
- earlier crash key
57E6EDAB-D2D1-44D3-9BD0-82DCA482DBFF
- recent crash key
4D7ABCBF-D341-CA69-AD26-30FFB67F0BBC / incident 56416840-0903-4FAB-8869-5D471B78335C
Reproduction
I don't have a reliable repro yet — both observed crashes occurred after multi-hour sessions of normal Cmd+C/X/V activity. The pattern that seems to make it more likely: macOS 26.x (especially the 26.5 beta seed) + a third-party clipboard manager running concurrently. Happy to gather more data if there's a specific instrumented build you'd want to ship.
Proposed mitigation
The NSPasteboard.string(forType:) direct API would avoid the per-item iteration entirely — AppKit handles the cache internally and atomically. The trade-off, per the existing comment, is the "first string only" semantic vs. concatenated content.
For my use case (general clipboard tracking) the concatenated content is actually preferable. Would you be open to either:
- An opt-in API like
Clipboard::get_text_concatenated() (or a GetExtApple extension) that uses string(forType:) directly, for callers who explicitly don't need the historical "first string" semantic, or
- An internal try-catch around the per-item iteration with a fallback to
string(forType:) when the items become invalid?
Happy to send a PR for either if you have a preferred direction.
Summary
Clipboard::get_text()(and friends that go throughstring_from_type) on macOS can SIGSEGV during the per-itemstringForType:iteration when another process mutates the pasteboard at the same time. The crash lands inobjc_msgSendafter_updateTypeCacheIfNeededderef's a freed Objective-C pointer.Environment
3.6.1(latest)26.5(build25F5068a— beta seed)Mac16,6)clipboard-capture(a worker thread the embedding app routes Cmd+C/X/V events to via a serial dispatch queue)What I see
The bad address
0x0071fae168e6b598looks like a freed Objective-C reference whose memory was reused —_updateTypeCacheIfNeededis iterating through cached type entries and one of them no longer points to a live object.Why I think it happens
src/platform/osx.rsdeliberately usespasteboardItems()+ per-itemstringForType:instead ofNSPasteboard.string(forType:)directly, with this comment explaining the choice:The
NSPasteboardIteminstances returned bypasteboardItems()reference into AppKit's type-cache. If another app callssetData:forType:on the general pasteboard betweenpasteboardItems()returning and thestringForType:call hitting the bad item, the cache is invalidated mid-iteration and the now-stale item pointer crashes.The embedding app already serializes all clipboard reads through a single GCD serial queue with an autorelease pool wrapper — that prevents intra-process races, but cannot stop external apps (system clipboard manager, paste apps, etc.) from mutating the pasteboard at the same instant.
This crash signature has been observed at least twice in the wild on macOS 26.x:
57E6EDAB-D2D1-44D3-9BD0-82DCA482DBFF4D7ABCBF-D341-CA69-AD26-30FFB67F0BBC/ incident56416840-0903-4FAB-8869-5D471B78335CReproduction
I don't have a reliable repro yet — both observed crashes occurred after multi-hour sessions of normal Cmd+C/X/V activity. The pattern that seems to make it more likely: macOS 26.x (especially the 26.5 beta seed) + a third-party clipboard manager running concurrently. Happy to gather more data if there's a specific instrumented build you'd want to ship.
Proposed mitigation
The
NSPasteboard.string(forType:)direct API would avoid the per-item iteration entirely — AppKit handles the cache internally and atomically. The trade-off, per the existing comment, is the "first string only" semantic vs. concatenated content.For my use case (general clipboard tracking) the concatenated content is actually preferable. Would you be open to either:
Clipboard::get_text_concatenated()(or aGetExtAppleextension) that usesstring(forType:)directly, for callers who explicitly don't need the historical "first string" semantic, orstring(forType:)when the items become invalid?Happy to send a PR for either if you have a preferred direction.