Bug 510116 - KWin crashed in KWin::SurfaceExtension<KWin::ConfinedPointerV1InterfacePrivate, KWin::ConfinedPointerV1Commit>::~SurfaceExtension() after alt-tabbing into a running game
Summary: KWin crashed in KWin::SurfaceExtension<KWin::ConfinedPointerV1InterfacePrivat...
Status: RESOLVED FIXED
Alias: None
Product: kwin
Classification: Plasma
Component: generic-crash (other bugs)
Version First Reported In: 6.4.5
Platform: Arch Linux Linux
: NOR crash
Target Milestone: ---
Assignee: KWin default assignee
URL:
Keywords: drkonqi
Depends on:
Blocks:
 
Reported: 2025-10-01 09:18 UTC by Marat Bakeev
Modified: 2026-10-06 11:42 UTC (History)
3 users (show)

See Also:
Latest Commit:
Version Fixed/Implemented In: 6.7.6
Sentry Crash Report:


Attachments
New crash information added by DrKonqi (92.10 KB, text/plain)
2025-10-01 09:18 UTC, Marat Bakeev
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Marat Bakeev 2025-10-01 09:18:29 UTC
Application: kwin_wayland (6.4.5)

ApplicationNotResponding [ANR]: false
Qt Version: 6.9.2
Frameworks Version: 6.18.0
Operating System: Linux 6.16.8-zen3-1-zen x86_64
Windowing System: Wayland
Distribution: "Arch Linux"
DrKonqi: 6.4.5 [CoredumpBackend]

-- Information about the crash:
Hi team, not 100% sure if this is relevant:

I had a steam game running in proton in background, and was doing something in VS Code, alt-tabbing between the game and the editor.
After Nth alt-tab KWin just crashed.

The reporter is unsure if this crash is reproducible.

-- Backtrace (Reduced):
#5  __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44
#6  0x00007f76e6098a13 in __pthread_kill_internal (threadid=<optimized out>, signo=6) at pthread_kill.c:89
#7  0x00007f76e603e410 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#8  0x00007f76e602557a in __GI_abort () at abort.c:77
#9  0x00007f76e6026613 in __libc_message_impl (fmt=fmt@entry=0x7f76e61b4355 "%s\n") at ../sysdeps/posix/libc_fatal.c:138
#10 0x00007f76e60a2d65 in malloc_printerr (str=str@entry=0x7f76e61b75b0 "free(): invalid next size (fast)") at malloc.c:5892
#11 0x00007f76e60a5036 in _int_free_chunk (av=<optimized out>, p=<optimized out>, size=<optimized out>, have_lock=have_lock@entry=0) at malloc.c:4670
#12 0x00007f76e60a7ff4 in __GI___libc_free (mem=<optimized out>) at malloc.c:3571
#13 0x00007f76e64b062a in operator delete (ptr=<optimized out>) at /usr/src/debug/gcc/gcc/libstdc++-v3/libsupc++/del_op.cc:49
#14 0x00007f76e64b063a in operator delete (ptr=<optimized out>) at /usr/src/debug/gcc/gcc/libstdc++-v3/libsupc++/del_ops.cc:33
#15 0x00007f76e9979b81 in std::__new_allocator<std::__detail::_Hash_node<std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > >, false> >::deallocate (this=<optimized out>, __p=<optimized out>, __n=1) at /usr/include/c++/15.2.1/bits/new_allocator.h:172
#16 std::allocator<std::__detail::_Hash_node<std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > >, false> >::deallocate (this=<optimized out>, __p=<optimized out>, __n=1) at /usr/include/c++/15.2.1/bits/allocator.h:215
#17 std::allocator_traits<std::allocator<std::__detail::_Hash_node<std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > >, false> > >::deallocate (__a=<optimized out>, __p=<optimized out>, __n=1) at /usr/include/c++/15.2.1/bits/alloc_traits.h:649
#18 std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > >, false> > >::_M_deallocate_node_ptr (this=0x55f90ffa8b90, __n=<optimized out>) at /usr/include/c++/15.2.1/bits/hashtable_policy.h:1583
#19 std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > >, false> > >::_M_deallocate_node (this=0x55f90ffa8b90, __n=<optimized out>) at /usr/include/c++/15.2.1/bits/hashtable_policy.h:1573
#20 std::_Hashtable<KWin::RawSurfaceExtension*, std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > >, std::allocator<std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > > >, std::__detail::_Select1st, std::equal_to<KWin::RawSurfaceExtension*>, std::hash<KWin::RawSurfaceExtension*>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase (this=0x55f90ffa8b90, __bkt=<optimized out>, __prev_n=<optimized out>, __n=<optimized out>) at /usr/include/c++/15.2.1/bits/hashtable.h:2590


Reported using DrKonqi
Comment 1 Marat Bakeev 2025-10-01 09:18:32 UTC
Created attachment 185414 [details]
New crash information added by DrKonqi

DrKonqi auto-attaching complete backtrace.
Comment 2 Nate Graham 2025-10-01 16:40:55 UTC
Full searchable backtrace:


Thread 1 (Thread 0x7f76e0929a80 (LWP 2951)):
[KCrash Handler]
#5  __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44
#6  0x00007f76e6098a13 in __pthread_kill_internal (threadid=<optimized out>, signo=6) at pthread_kill.c:89
#7  0x00007f76e603e410 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#8  0x00007f76e602557a in __GI_abort () at abort.c:77
#9  0x00007f76e6026613 in __libc_message_impl (fmt=fmt@entry=0x7f76e61b4355 "%s\n") at ../sysdeps/posix/libc_fatal.c:138
#10 0x00007f76e60a2d65 in malloc_printerr (str=str@entry=0x7f76e61b75b0 "free(): invalid next size (fast)") at malloc.c:5892
#11 0x00007f76e60a5036 in _int_free_chunk (av=<optimized out>, p=<optimized out>, size=<optimized out>, have_lock=have_lock@entry=0) at malloc.c:4670
#12 0x00007f76e60a7ff4 in __GI___libc_free (mem=<optimized out>) at malloc.c:3571
#13 0x00007f76e64b062a in operator delete (ptr=<optimized out>) at /usr/src/debug/gcc/gcc/libstdc++-v3/libsupc++/del_op.cc:49
#14 0x00007f76e64b063a in operator delete (ptr=<optimized out>) at /usr/src/debug/gcc/gcc/libstdc++-v3/libsupc++/del_ops.cc:33
#15 0x00007f76e9979b81 in std::__new_allocator<std::__detail::_Hash_node<std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > >, false> >::deallocate (this=<optimized out>, __p=<optimized out>, __n=1) at /usr/include/c++/15.2.1/bits/new_allocator.h:172
#16 std::allocator<std::__detail::_Hash_node<std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > >, false> >::deallocate (this=<optimized out>, __p=<optimized out>, __n=1) at /usr/include/c++/15.2.1/bits/allocator.h:215
#17 std::allocator_traits<std::allocator<std::__detail::_Hash_node<std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > >, false> > >::deallocate (__a=<optimized out>, __p=<optimized out>, __n=1) at /usr/include/c++/15.2.1/bits/alloc_traits.h:649
#18 std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > >, false> > >::_M_deallocate_node_ptr (this=0x55f90ffa8b90, __n=<optimized out>) at /usr/include/c++/15.2.1/bits/hashtable_policy.h:1583
#19 std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > >, false> > >::_M_deallocate_node (this=0x55f90ffa8b90, __n=<optimized out>) at /usr/include/c++/15.2.1/bits/hashtable_policy.h:1573
#20 std::_Hashtable<KWin::RawSurfaceExtension*, std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > >, std::allocator<std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > > >, std::__detail::_Select1st, std::equal_to<KWin::RawSurfaceExtension*>, std::hash<KWin::RawSurfaceExtension*>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase (this=0x55f90ffa8b90, __bkt=<optimized out>, __prev_n=<optimized out>, __n=<optimized out>) at /usr/include/c++/15.2.1/bits/hashtable.h:2590
#21 std::_Hashtable<KWin::RawSurfaceExtension*, std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > >, std::allocator<std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > > >, std::__detail::_Select1st, std::equal_to<KWin::RawSurfaceExtension*>, std::hash<KWin::RawSurfaceExtension*>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::erase(KWin::RawSurfaceExtension* const&) [clone .isra.0] (this=0x55f90ffa8b90, __k=@0x7fff12c5f838: 0x55f90fa00d70) at /usr/include/c++/15.2.1/bits/hashtable.h:2620
#22 0x00007f76e997372f in std::unordered_map<KWin::RawSurfaceExtension*, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> >, std::hash<KWin::RawSurfaceExtension*>, std::equal_to<KWin::RawSurfaceExtension*>, std::allocator<std::pair<KWin::RawSurfaceExtension* const, std::unique_ptr<KWin::RawSurfaceAttachedState, std::default_delete<KWin::RawSurfaceAttachedState> > > > >::erase (this=<optimized out>, __x=<optimized out>) at /usr/include/c++/15.2.1/bits/unordered_map.h:834
#23 KWin::SurfaceInterface::removeExtension (this=0x55f90f90b690, extension=<optimized out>) at /usr/src/debug/kwin/kwin-6.4.5/src/wayland/surface.cpp:504
#24 0x00007f76e9957bde in KWin::SurfaceExtension<KWin::ConfinedPointerV1InterfacePrivate, KWin::ConfinedPointerV1Commit>::~SurfaceExtension (this=0x55f90fa00d70, this=<optimized out>) at /usr/src/debug/kwin/kwin-6.4.5/src/wayland/surface.h:564
#25 KWin::ConfinedPointerV1InterfacePrivate::~ConfinedPointerV1InterfacePrivate (this=0x55f90fa00d20, this=<optimized out>) at /usr/src/debug/kwin/kwin-6.4.5/src/wayland/pointerconstraints_v1_p.h:76
#26 KWin::ConfinedPointerV1InterfacePrivate::~ConfinedPointerV1InterfacePrivate (this=0x55f90fa00d20, this=<optimized out>) at /usr/src/debug/kwin/kwin-6.4.5/src/wayland/pointerconstraints_v1_p.h:76
#27 std::default_delete<KWin::ConfinedPointerV1InterfacePrivate>::operator() (this=<optimized out>, __ptr=0x55f90fa00d20) at /usr/include/c++/15.2.1/bits/unique_ptr.h:93
#28 std::unique_ptr<KWin::ConfinedPointerV1InterfacePrivate, std::default_delete<KWin::ConfinedPointerV1InterfacePrivate> >::~unique_ptr (this=0x55f910066c60, this=<optimized out>) at /usr/include/c++/15.2.1/bits/unique_ptr.h:399
#29 KWin::ConfinedPointerV1Interface::~ConfinedPointerV1Interface (this=0x55f910066c50, this=<optimized out>) at /usr/src/debug/kwin/kwin-6.4.5/src/wayland/pointerconstraints_v1.cpp:314
#30 0x00007f76e9a4e396 in KWin::ConfinedPointerV1Interface::~ConfinedPointerV1Interface (this=0x55f910066c50, this=<optimized out>) at /usr/src/debug/kwin/kwin-6.4.5/src/wayland/pointerconstraints_v1.cpp:312
#31 KWin::ConfinedPointerV1InterfacePrivate::zwp_confined_pointer_v1_destroy_resource (this=0x55f90fa00d20, resource=0x55f91004d910) at /usr/src/debug/kwin/kwin-6.4.5/src/wayland/pointerconstraints_v1.cpp:290
#32 QtWaylandServer::zwp_confined_pointer_v1::destroy_func (client_resource=<optimized out>) at /usr/src/debug/kwin/build/src/wayland/qwayland-server-pointer-constraints-unstable-v1.cpp:716
#33 0x00007f76e837dba0 in remove_and_destroy_resource (element=0x55f90fc857b0, data=<optimized out>, flags=0) at ../wayland-1.24.0/src/wayland-server.c:796
#34 0x00007f76e5392ac6 in ffi_call_unix64 () at ../src/x86/unix64.S:104
#35 0x00007f76e538f76b in ffi_call_int (cif=cif@entry=0x7fff12c5fb20, fn=fn@entry=0x7f76e9a4c050 <QtWaylandServer::zwp_confined_pointer_v1::handle_destroy(wl_client*, wl_resource*)>, rvalue=<optimized out>, rvalue@entry=0x0, avalue=avalue@entry=0x7fff12c5fbf0, closure=closure@entry=0x0) at ../src/x86/ffi64.c:676
#36 0x00007f76e539206e in ffi_call (cif=cif@entry=0x7fff12c5fb20, fn=0x7f76e9a4c050 <QtWaylandServer::zwp_confined_pointer_v1::handle_destroy(wl_client*, wl_resource*)>, rvalue=rvalue@entry=0x0, avalue=avalue@entry=0x7fff12c5fbf0) at ../src/x86/ffi64.c:713
#37 0x00007f76e8379532 in wl_closure_invoke (closure=closure@entry=0x55f90fcfb140, target=<optimized out>, target@entry=0x55f90fc857b0, opcode=opcode@entry=0, data=<optimized out>, data@entry=0x55f90efb0b70, flags=2) at ../wayland-1.24.0/src/connection.c:1241
#38 0x00007f76e837ed30 in wl_client_connection_data (fd=<optimized out>, mask=<optimized out>, data=<optimized out>) at ../wayland-1.24.0/src/wayland-server.c:483
#39 0x00007f76e837d182 in wl_event_loop_dispatch (loop=0x55f90dcf2460, timeout=<optimized out>) at ../wayland-1.24.0/src/event-loop.c:1059
#40 0x00007f76e990b4fa in KWin::Display::dispatchEvents (this=<optimized out>) at /usr/src/debug/kwin/kwin-6.4.5/src/wayland/display.cpp:138
#41 0x00007f76e69d466f in QtPrivate::QSlotObjectBase::call (this=0x55f90ed16060, r=0x55f90dd12350, a=0x7fff12c602f0, this=<optimized out>, r=<optimized out>, a=<optimized out>) at /usr/src/debug/qt6-base/qtbase/src/corelib/kernel/qobjectdefs_impl.h:461
#42 doActivate<false> (sender=<optimized out>, signal_index=<optimized out>, argv=<optimized out>) at /usr/src/debug/qt6-base/qtbase/src/corelib/kernel/qobject.cpp:4157
#43 0x00007f76e69de9c0 in QMetaObject::activate<void, QSocketDescriptor, QSocketNotifier::Type, QSocketNotifier::QPrivateSignal> (sender=0x55f90ee1f960, mo=<optimized out>, local_signal_index=0, ret=0x0) at /usr/src/debug/qt6-base/qtbase/src/corelib/kernel/qobjectdefs.h:306
#44 QSocketNotifier::activated (this=0x55f90ee1f960, _t1=..., _t2=<optimized out>, _t3=...) at /usr/src/debug/qt6-base/build/src/corelib/Core_autogen/include/moc_qsocketnotifier.cpp:161
#45 QSocketNotifier::event (this=0x55f90ee1f960, e=<optimized out>) at /usr/src/debug/qt6-base/qtbase/src/corelib/kernel/qsocketnotifier.cpp:327
#46 0x00007f76e7d01dd0 in QApplicationPrivate::notify_helper (this=<optimized out>, receiver=0x55f90ee1f960, e=0x7fff12c60410) at /usr/src/debug/qt6-base/qtbase/src/widgets/kernel/qapplication.cpp:3300
#47 0x00007f76e6968678 in QCoreApplication::notifyInternal2 (receiver=0x55f90ee1f960, event=0x7fff12c60410) at /usr/src/debug/qt6-base/qtbase/src/corelib/kernel/qcoreapplication.cpp:1106
#48 0x00007f76e6b28f19 in QCoreApplication::sendEvent (receiver=<optimized out>, event=0x7fff12c60410) at /usr/src/debug/qt6-base/qtbase/src/corelib/kernel/qcoreapplication.cpp:1546
#49 QEventDispatcherUNIXPrivate::activateSocketNotifiers (this=this@entry=0x55f90dc3c110) at /usr/src/debug/qt6-base/qtbase/src/corelib/kernel/qeventdispatcher_unix.cpp:276
#50 0x00007f76e6b29c6b in QEventDispatcherUNIX::processEvents (this=<optimized out>, flags=..., flags@entry=...) at /usr/src/debug/qt6-base/qtbase/src/corelib/kernel/qeventdispatcher_unix.cpp:498
#51 0x00007f76e771b073 in QUnixEventDispatcherQPA::processEvents (this=<optimized out>, flags=...) at /usr/src/debug/qt6-base/qtbase/src/gui/platform/unix/qunixeventdispatcher.cpp:27
#52 0x00007f76e6974ca6 in QEventLoop::processEvents (this=0x7fff12c605e0, flags=...) at /usr/src/debug/qt6-base/qtbase/src/corelib/kernel/qeventloop.cpp:104
#53 QEventLoop::exec (this=0x7fff12c605e0, flags=...) at /usr/src/debug/qt6-base/qtbase/src/corelib/kernel/qeventloop.cpp:186
#54 0x00007f76e696cd21 in QCoreApplication::exec () at /usr/src/debug/qt6-base/qtbase/src/corelib/kernel/qcoreapplication.cpp:1449
#55 0x00007f76e7cfc5ea in QApplication::exec () at /usr/src/debug/qt6-base/qtbase/src/widgets/kernel/qapplication.cpp:2567
#56 0x000055f90115bd26 in main (argc=<optimized out>, argv=<optimized out>) at /usr/src/debug/kwin/kwin-6.4.5/src/main_wayland.cpp:635
Comment 3 Jeff Stokes 2026-07-22 00:09:27 UTC
Here's the report, ready to paste:

---

**Title:** kwin_wayland segfault in `KWin::ConfinedPointerV1Interface::region()` on alt-tab out of fullscreen game using pointer confinement

**Product:** kwin
**Component:** input
**Version:** 6.7.3
**Severity:** crash
**Platform:** Arch Linux (CachyOS)

**Summary:**
kwin_wayland segfaults and restarts when alt-tabbing out of a fullscreen game that uses pointer confinement (`zwp_pointer_constraints_v1`). The compositor crash takes down all Wayland clients (game, Steam, browser) with it. Crash is a null-pointer read (SIGSEGV, error 4) inside `ConfinedPointerV1Interface::region()`, reached via `KWin::Transaction::apply()` during Wayland event dispatch — appears to be a use-after-free / stale-pointer access as the pointer confinement is torn down on focus-out.

**Steps to reproduce:**
1. Launch a borderless-full screen game game that confines the cursor for mouselook (Wayland-native or XWayland).
2. Alt-tab out of the game to another window.
3. Sometimes kwin_wayland segfaults and auto-restarts; all graphical clients die. (chrome/discord/steam on my machine)

**Reproducibility:** intermittent

**Backtrace** (partial symbols — CachyOS build, debug info not available via debuginfod):
```
#0  KWin::ConfinedPointerV1Interface::region() const () from /usr/lib/libkwin.so.6
#1  ?? () from /usr/lib/libkwin.so.6
#3  ?? () from /usr/lib/libkwin.so.6
#4  ?? () from /usr/lib/libkwin.so.6
#5  KWin::Transaction::apply() () from /usr/lib/libkwin.so.6
#8  ffi_call () from /usr/lib/libffi.so.8
#11 wl_event_loop_dispatch () from /usr/lib/libwayland-server.so.0
#12 KWin::Display::dispatchEvents() () from /usr/lib/libkwin.so.6
#14 QSocketNotifier::event(QEvent*)
#21 QCoreApplication::exec()
```
dmesg: `segfault at 10 ... error 4 in libkwin.so.6.7.3` (read fault at offset 0x10 off a near-null pointer).

**Environment:**
- KWin/Plasma 6.7.3
- Qt 6 (Wayland session)
- Mesa 26.1.5, RADV
- AMD Radeon RX 9070 XT (RDNA4)
- Kernel: CachyOS (sched_ext/scx_lavd active)
Comment 4 Terry Raimondo 2026-08-29 13:17:16 UTC
Root cause analysis, with a symbolised coredump.

I hit this on Plasma 6.7.3 (kwin-6.7.3-2.fc43, Fedora Kinoite 43, Qt 6.10.3,
glibc 2.42, Mesa 25.3.6, AMD RX 7900 XTX, Wayland session). Twice, both times a
few seconds to a minute after launching a 32-bit Windows game (World of Warcraft
3.3.5a) through Steam + Proton, i.e. an Xwayland client that constantly grabs and
releases the pointer for mouselook.

The abort site is not the bug site. This is a 24-byte heap buffer overflow, and
glibc aborts at whatever free() next touches the clobbered chunk header. My two
cores abort in completely different places for that reason: one inside
SurfaceState::mergeInto, the other inside Mesa's amdgpu_bo_fence_wait. The
original report's abort inside removeExtension -> extensions.erase() is a third
detection point for the same corruption.


THE OVERFLOW
============

Stack of the crashing thread (core 1):

  malloc_printerr ("free(): invalid next size (fast)")  malloc.c:5895
  _int_free_chunk                                       malloc.c:4670
  std::default_delete<KWin::RawSurfaceAttachedState>::operator()
  std::unique_ptr<KWin::RawSurfaceAttachedState>::~unique_ptr
  std::unordered_map<KWin::RawSurfaceExtension*,
                     std::unique_ptr<KWin::RawSurfaceAttachedState>>::~unordered_map
  KWin::SurfaceState::mergeInto (this=0x558301589b30, target=0x5583014ebc60)
                                                        src/wayland/surface.cpp:667
  KWin::SurfaceInterfacePrivate::applyState
  KWin::Transaction::apply                              src/wayland/transaction.cpp:169
  QtWaylandServer::wl_surface::handle_commit  (via ffi_call/wl_closure_invoke)
  KWin::Display::dispatchEvents                         src/wayland/display.cpp:138

Line 667 is the closing brace of SurfaceState::mergeInto, so this is the scope
exit that destroys the local `previousExtensions` map created on line 652.

The node being destroyed has a type-mismatched key and value:

  key   0x558301564600 -> vtable for
        KWin::SurfaceExtension<KWin::LockedPointerV1InterfacePrivate,
                               KWin::LockedPointerV1Commit>
  value 0x55830156a130 -> vtable for KWin::ConfinedPointerV1Commit

The value object's memory shows the overflow directly:

  0x55830156a120:  <malloc chunk header>  size = 0x51  -> 72 usable bytes
  0x55830156a130:  vtable ConfinedPointerV1Commit
  0x55830156a138 .. 0x55830156a170: zeros (in bounds; last in-bounds word 0x...170)
  0x55830156a178:  0x40932c0000000000   = double 1226.0   <- offset 72, first OOB word
  0x55830156a180:  0x4083780000000000   = double  623.0
  0x55830156a188:  0x0000000000000001

and gdb confirms the size delta:

  (gdb) print sizeof(KWin::ConfinedPointerV1Commit)   ->  72
  (gdb) print sizeof(KWin::LockedPointerV1Commit)     ->  96

LockedPointerV1Commit adds std::optional<QPointF> hint at offset 72.
ConfinedPointerV1Commit ends at 72 and its 0x50 chunk has exactly 72 usable
bytes, zero slack. So writing a LockedPointerV1Commit into a
ConfinedPointerV1Commit puts the cursor position hint straight onto the next
chunk's size field. Those two doubles are a set_cursor_position_hint(1226, 623).
0x50 is fastbin-sized, which is where the "(fast)" in the glibc message comes from.


HOW THE TYPES GET CONFUSED
==========================

1. SurfaceInterface::removeExtension() (src/wayland/surface.cpp:501) erases the
   extension from d->pending->extensions, from d->subsurface.transaction, and
   from the d->firstTransaction chain. It never erases it from
   d->current->extensions. So after an extension is destroyed, `current` keeps
   {dangling RawSurfaceExtension* -> stale state} until the next mergeInto
   happens to prune it.

2. The map is keyed by raw RawSurfaceExtension*. Once the extension object is
   freed, a different extension can be allocated at the same address.

3. SurfaceState::mergeInto (surface.cpp:652-664) looks the key up with no type
   check:

       auto previousExtensions = std::exchange(target->extensions, {});
       for (const auto &[extension, sourceState] : extensions) {
           std::unique_ptr<RawSurfaceAttachedState> targetState;
           if (auto it = previousExtensions.find(extension); it != previousExtensions.end()) {
               targetState = std::move(it->second);
           } else {
               targetState = extension->createState();
           }
           sourceState->mergeInto(targetState.get());
           target->extensions[extension] = std::move(targetState);
       }

   With a recycled address, find() hits the stale entry, so targetState is the
   old state object of the wrong type.

4. SurfaceAttachedState<Self>::mergeInto (src/wayland/surface.h:507) then does an
   unchecked downcast and a full-object assignment:

       auto self  = static_cast<Self *>(this);
       auto other = static_cast<Self *>(target);
       *other = std::exchange(*self, Self{});

   sizeof(Self) bytes are written into an allocation sized for a different type.

LockedPointerV1InterfacePrivate and ConfinedPointerV1InterfacePrivate are the
pair that makes this reachable in practice: they are the only two SurfaceExtension
users that are created and destroyed repeatedly over a single live surface, and
their Commit types differ in size by exactly the 24 bytes seen above. An Xwayland
client doing mouselook churns lock/confine constraints continuously on one
surface, which is why this reproduces with Proton games and not with ordinary
applications.

Two details in the dump corroborate the ordering. Implicit assignment does not
copy the vptr, so the victim keeps its ConfinedPointerV1Commit vtable while
holding LockedPointer data. And ~SurfaceExtension resets the base subobject's
vptr to the template's own vtable before the memory is freed, which is why the
dangling key resolves to "vtable for SurfaceExtension<LockedPointerV1InterfacePrivate,
LockedPointerV1Commit>" rather than to "vtable for LockedPointerV1InterfacePrivate + N".
That symbol is the fingerprint of freed memory. Consistent with:

  a) a confined-pointer extension at address A registers a ConfinedPointerV1Commit
     in current->extensions[A];
  b) it is destroyed; removeExtension leaves {A -> state} in current;
  c) a locked-pointer extension is allocated at A;
  d) the next commit merges a LockedPointerV1Commit into the 72-byte confined
     state -> 24-byte overflow;
  e) some later free() touching that region aborts.


SECOND DEFECT IN THE SAME AREA
==============================

SurfaceInterfacePrivate::applyState iterates current->extensions and calls
extension->applyState(state) on the key. After step (b) above that key is
dangling, so this is a use-after-free on the next commit. This looks like what
comment #3 hit: SIGSEGV in KWin::ConfinedPointerV1Interface::region() reached via
KWin::Transaction::apply().


FIX
===

The minimal fix is for removeExtension() to also drop the entry from the current
state:

    void SurfaceInterface::removeExtension(RawSurfaceExtension *extension)
    {
        d->current->extensions.erase(extension);      // <-- missing
        d->pending->extensions.erase(extension);
        ...
    }

That closes both the type confusion and the applyState use-after-free.

Note that MR !9606 / commit 8f0882b2 ("pointer_input: rework pointer constraints",
milestone 6.8) removes LockedPointerV1Commit and ConfinedPointerV1Commit
entirely and moves pointer-constraint state into SurfaceState. That makes this
crash unreachable on master, but only incidentally: the underlying
SurfaceExtension key-lifetime defect is still there on master, and the six
remaining SurfaceExtension users (XdgToplevel, XdgPopup, XwaylandSurfaceV1,
LayerSurfaceV1, PlasmaShellSurface, XXPipV1) are only safer because they are not
created and destroyed repeatedly on a live surface.

The defect was introduced by 192cbfdc ("wayland: Rework surface attached state",
MR !7426), first released in Plasma 6.4.0. Plasma/6.7 HEAD still has the
incomplete removeExtension and still declares both Commit types, so 6.4.0 through
6.7.x are all affected and !9606 was not backported.

I can provide the coredumps or run further gdb queries against them on request.
Comment 5 Bug Janitor Service 2026-09-15 11:07:20 UTC
A possibly relevant merge request was started @ https://invent.kde.org/plasma/kwin/-/merge_requests/9928
Comment 6 Bug Janitor Service 2026-09-15 12:07:12 UTC
A possibly relevant merge request was started @ https://invent.kde.org/plasma/kwin/-/merge_requests/9929
Comment 7 Bug Janitor Service 2026-09-15 12:09:46 UTC
A possibly relevant merge request was started @ https://invent.kde.org/plasma/kwin/-/merge_requests/9930
Comment 8 Vlad Zahorodnii 2026-09-15 12:13:50 UTC
Git commit 8931793d02ea497d610bd6cb4ccb864ccdfc82b7 by Vlad Zahorodnii.
Committed on 15/09/2026 at 11:03.
Pushed by vladz into branch 'master'.

wayland: Remove current extension state in SurfaceInterface::removeExtension as well

The idea was to preserve the current state, that the destructor request
doesn't affect the current state. On the other hand, the relevant
extension object will be gone, so that state cannot be accessed.

It should also fix SurfaceState::mergeInto() creating an incompatible
state object if a destroyed and a newly created RawSurfaceExtension
objects have the same address.

M  +1    -0    src/wayland/surface.cpp

https://invent.kde.org/plasma/kwin/-/commit/8931793d02ea497d610bd6cb4ccb864ccdfc82b7
Comment 9 Vlad Zahorodnii 2026-09-15 12:49:55 UTC
Git commit 6570182f279269b472aa831dbc35054489fe3066 by Vlad Zahorodnii.
Committed on 15/09/2026 at 12:06.
Pushed by vladz into branch 'Plasma/6.8'.

wayland: Remove current extension state in SurfaceInterface::removeExtension as well

The idea was to preserve the current state, that the destructor request
doesn't affect the current state. On the other hand, the relevant
extension object will be gone, so that state cannot be accessed.

It should also fix SurfaceState::mergeInto() creating an incompatible
state object if a destroyed and a newly created RawSurfaceExtension
objects have the same address.


(cherry picked from commit 8931793d02ea497d610bd6cb4ccb864ccdfc82b7)

Co-authored-by: Vlad Zahorodnii <vlad.zahorodnii@kde.org>

M  +1    -0    src/wayland/surface.cpp

https://invent.kde.org/plasma/kwin/-/commit/6570182f279269b472aa831dbc35054489fe3066
Comment 10 Vlad Zahorodnii 2026-09-15 12:54:36 UTC
Git commit 5f587fb72b5dceaef911aa1ae848afb24f90be6a by Vlad Zahorodnii.
Committed on 15/09/2026 at 12:09.
Pushed by vladz into branch 'Plasma/6.7'.

wayland: Remove current extension state in SurfaceInterface::removeExtension as well

The idea was to preserve the current state, that the destructor request
doesn't affect the current state. On the other hand, the relevant
extension object will be gone, so that state cannot be accessed.

It should also fix SurfaceState::mergeInto() creating an incompatible
state object if a destroyed and a newly created RawSurfaceExtension
objects have the same address.


(cherry picked from commit 8931793d02ea497d610bd6cb4ccb864ccdfc82b7)

Co-authored-by: Vlad Zahorodnii <vlad.zahorodnii@kde.org>

M  +1    -0    src/wayland/surface.cpp

https://invent.kde.org/plasma/kwin/-/commit/5f587fb72b5dceaef911aa1ae848afb24f90be6a
Comment 11 Vlad Zahorodnii 2026-10-06 11:42:42 UTC
Git commit 7f58ab953f05260b8b7258a16975fd7d52377335 by Vlad Zahorodnii.
Committed on 06/10/2026 at 11:28.
Pushed by vladz into branch 'Plasma/6.6'.

wayland: Remove current extension state in SurfaceInterface::removeExtension as well

The idea was to preserve the current state, that the destructor request
doesn't affect the current state. On the other hand, the relevant
extension object will be gone, so that state cannot be accessed.

It should also fix SurfaceState::mergeInto() creating an incompatible
state object if a destroyed and a newly created RawSurfaceExtension
objects have the same address.


(cherry picked from commit 8931793d02ea497d610bd6cb4ccb864ccdfc82b7)

Co-authored-by: Vlad Zahorodnii <vlad.zahorodnii@kde.org>

M  +1    -0    src/wayland/surface.cpp

https://invent.kde.org/plasma/kwin/-/commit/7f58ab953f05260b8b7258a16975fd7d52377335