Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/backend/wayland/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,18 @@ use std::ptr::NonNull;
use crate::backend::wayland::compositor::WaylandState;

pub struct WaylandBackend {
state: RefCell<NonNull<WaylandState>>,
state: RefCell<Option<NonNull<WaylandState>>>,
}

impl WaylandBackend {
pub fn new() -> Self {
Self {
state: RefCell::new(NonNull::dangling()),
state: RefCell::new(None),
}
}

pub fn attach_state(&self, state: &mut WaylandState) {
*self.state.borrow_mut() = NonNull::from(state);
*self.state.borrow_mut() = Some(NonNull::from(state));
}

pub fn close_window(&self, window: WindowId) -> bool {
Expand Down Expand Up @@ -120,8 +120,8 @@ impl WaylandBackend {
}

pub(crate) fn with_state<T>(&self, f: impl FnOnce(&mut WaylandState) -> T) -> Option<T> {
let mut ptr = *self.state.borrow();
Some(unsafe { f(ptr.as_mut()) })
let ptr = *self.state.borrow();
ptr.map(|mut p| unsafe { f(p.as_mut()) })
}
}

Expand Down
42 changes: 27 additions & 15 deletions src/wayland/runtime/drm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,13 @@ pub fn run() -> ! {
log::info!("Starting DRM/KMS backend");
ensure_dbus_session();

let mut wm = Wm::new(WmBackend::new_wayland(WaylandBackend::new()));
if let Some(wayland) = wm.backend.wayland_data_mut() {
init_wayland_globals(&mut wm.g, wayland);
let wm_cell = Rc::new(RefCell::new(Wm::new(WmBackend::new_wayland(WaylandBackend::new()))));
{
let mut wm = wm_cell.borrow_mut();
let wm_ref = &mut *wm;
if let Some(wayland) = wm_ref.backend.wayland_data_mut() {
init_wayland_globals(&mut wm_ref.g, wayland);
}
}

let event_loop: EventLoop<WaylandState> = EventLoop::try_new().expect("event loop");
Expand All @@ -56,12 +60,19 @@ pub fn run() -> ! {

let display: Display<WaylandState> = Display::new().expect("wayland display");
let mut state = WaylandState::new(display, &loop_handle);
state.attach_globals(&mut wm.g);
if let WmBackend::Wayland(data) = &mut wm.backend {

// Unsafe: Obtain a pointer to the inner Globals and extend its lifetime.
// This is safe because the `Wm` instance is allocated on the heap inside `Rc<RefCell<Wm>>`
// and will not move.
let globals_ptr = &mut wm_cell.borrow_mut().g as *mut _;
state.attach_globals(unsafe { &mut *globals_ptr });
Comment on lines +64 to +68
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The raw pointer/lifetime extension for wm.g can violate aliasing and RefCell borrowing guarantees.

This pattern takes a &mut Globals out of RefCell<Wm>’s borrow tracking and Rust’s aliasing model. If attach_globals stores that &mut Globals for later use (e.g., across event-loop iterations), later wm_cell.borrow_mut() calls can overlap mutable access to wm.g (and other fields) while the stored &mut is still live, which is UB even if RefCell doesn’t panic.

Prefer instead:

  • having WaylandState hold an Rc<RefCell<Globals>>/Rc<Wm> and borrow when needed, or
  • changing attach_globals to take a callback/handle instead of a long-lived &mut Globals.

If attach_globals uses the reference only within the call and does not store it, you don’t need the raw pointer or unsafe; just call state.attach_globals(&mut wm_cell.borrow_mut().g); in a small scope so the borrow ends promptly.

Comment on lines +64 to +68
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In Rust, is it sound to take &mut fieldfromRefCell::borrow_mut(), cast it to *mut T, store it as a long-lived &'static mut T, and later borrow the parent RefCell mutably again?

💡 Result:

No, it is not sound. Taking &mut field from RefCell::borrow_mut, casting it to *mut T, and storing it as a long-lived &'static mut T (via &mut *ptr as &'static mut T or similar) bypasses RefCell's runtime borrow tracking. The RefMut guard drops and clears the mutable borrow flag, allowing subsequent borrow_mut calls on the RefCell to succeed at runtime without panicking. However, dereferencing the long-lived &'static mut T creates a mutable reference while another mutable borrow (from the new borrow_mut) is active, violating Rust's aliasing rules and causing undefined behavior.

Citations:


🏁 Script executed:

# First, let's find the WaylandState struct and its attach_globals method
fd -t f "\.rs$" src/wayland | head -20

Repository: instantOS/instantWM

Length of output: 634


🏁 Script executed:

# Search for attach_globals method definition
rg -A 10 "fn attach_globals" --type rust

Repository: instantOS/instantWM

Length of output: 1127


🏁 Script executed:

# Find the run_event_loop function
rg -A 15 "fn run_event_loop" --type rust

Repository: instantOS/instantWM

Length of output: 1111


🏁 Script executed:

# Check winit.rs for similar patterns
fd -t f "winit\.rs$"

Repository: instantOS/instantWM

Length of output: 145


🏁 Script executed:

# Look at the full drm.rs file context to understand the issue
wc -l src/wayland/runtime/drm.rs

Repository: instantOS/instantWM

Length of output: 93


🏁 Script executed:

# Get the full WaylandState struct definition
rg -B 5 -A 30 "struct WaylandState" src/backend/wayland/compositor/state.rs

Repository: instantOS/instantWM

Length of output: 1505


🏁 Script executed:

# Check the globals field type and how it's accessed
rg -A 5 "fn globals_mut" src/backend/wayland/compositor/state.rs

Repository: instantOS/instantWM

Length of output: 275


🏁 Script executed:

# Look at the context around run_event_loop to see borrow patterns
rg -A 50 "fn run_event_loop" src/wayland/runtime/drm.rs | head -80

Repository: instantOS/instantWM

Length of output: 1928


🏁 Script executed:

# Check winit.rs runtime for similar patterns
rg -B 5 -A 15 "attach_globals\|run_event_loop" src/wayland/runtime/winit.rs

Repository: instantOS/instantWM

Length of output: 45


🏁 Script executed:

# Find the globals field definition in WaylandState
rg "globals:" src/backend/wayland/compositor/state.rs

Repository: instantOS/instantWM

Length of output: 223


🏁 Script executed:

# Look for all usages of globals_mut in the codebase
rg "globals_mut()" src/wayland/ -A 3

Repository: instantOS/instantWM

Length of output: 2181


🏁 Script executed:

# Check the full WaylandState struct to find globals field
sed -n '/^pub struct WaylandState/,/^}/p' src/backend/wayland/compositor/state.rs | grep -A 2 -B 2 "globals"

Repository: instantOS/instantWM

Length of output: 302


🏁 Script executed:

# Look at winit.rs runtime initialization to see if it has the same pattern
cat -n src/wayland/runtime/winit.rs | head -100

Repository: instantOS/instantWM

Length of output: 4469


🏁 Script executed:

# Check if arrange_layout_if_dirty or other event loop functions call globals_mut
rg "globals_mut()" src/wayland/runtime/common.rs -B 5 -A 5

Repository: instantOS/instantWM

Length of output: 447


🏁 Script executed:

# Look at the event loop closure more carefully - see all calls
sed -n '28,100p' src/wayland/runtime/drm.rs

Repository: instantOS/instantWM

Length of output: 2634


🏁 Script executed:

# Check if ctx operations during event loop could call globals_mut
rg "arrange_layout_if_dirty|process_ipc|process_animations" src/wayland/runtime/drm.rs -A 3

Repository: instantOS/instantWM

Length of output: 1011


🏁 Script executed:

# Verify that ctx.core_mut() or similar gives access to globals_mut
rg "fn core_mut|fn globals_mut" src/ -B 2 -A 5

Repository: instantOS/instantWM

Length of output: 1248


🏁 Script executed:

# Check if winit.rs really has the same issue - look for Rc<RefCell> or attach_globals patterns
rg "Rc<RefCell<Wm>>|attach_globals" src/wayland/runtime/winit.rs

Repository: instantOS/instantWM

Length of output: 99


🏁 Script executed:

# Also check winit init path more carefully
cat -n src/wayland/runtime/winit.rs | sed -n '30,50p'

Repository: instantOS/instantWM

Length of output: 1006


🏁 Script executed:

# Verify the event loop in winit and see if it keeps wm alive
rg "event_loop.run|run_app" src/wayland/runtime/winit.rs -A 20

Repository: instantOS/instantWM

Length of output: 45


🏁 Script executed:

# Check the full winit event loop to see if wm is kept alive during event processing
sed -n '100,200p' src/wayland/runtime/winit.rs

Repository: instantOS/instantWM

Length of output: 3305


🏁 Script executed:

# Look for the event_loop.run closure in winit
rg -A 30 "event_loop.run\|run_app" src/wayland/runtime/winit.rs

Repository: instantOS/instantWM

Length of output: 45


attach_globals is creating undefined behavior through aliased mutable references.

WaylandState::attach_globals() uses transmute to store the reference as &'static mut Globals, but the actual Globals lives within Rc<RefCell<Wm>>. During run_event_loop(), the event closure keeps let mut wm = wm_cell.borrow_mut() alive while calling arrange_layout_if_dirty(), which internally calls state.globals_mut() to dereference the stored 'static reference. This creates overlapping mutable references to wm.g — one from the active borrow_mut() and one from dereferencing the transmuted pointer — violating Rust's aliasing rules.

The fix requires changing how WaylandState stores Globals (e.g., using interior mutability or a different ownership model) rather than just adjusting how the pointer is obtained. This same storage pattern affects src/wayland/runtime/winit.rs as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/wayland/runtime/drm.rs` around lines 64 - 68, attach_globals currently
stores a transmuted &'static mut Globals, causing UB from overlapping mutable
borrows when run_event_loop holds wm_cell.borrow_mut() while code calls
state.globals_mut(); instead, change WaylandState's storage for Globals to avoid
creating a &'static mutable reference (e.g., store a raw pointer wrapped in
Cell/UnsafeCell or an Arc<Mutex/RefCell<Globals>> or store a
Weak<Rc<RefCell<Globals>>> and upgrade when needed) and update attach_globals,
globals_mut, and any callers (including run_event_loop, arrange_layout_if_dirty
and code in winit.rs) to access Globals through that safe
interior-mutability/ownership API rather than via transmute to &'static mut.
Ensure attach_globals no longer transmutes to a static lifetime and that
globals_mut returns a borrow or lock guard tied to the chosen interior-mutable
container to prevent overlapping mutable borrows.


if let WmBackend::Wayland(data) = &mut wm_cell.borrow_mut().backend {
data.backend.attach_state(&mut state);
}

{
let mut wm = wm_cell.borrow_mut();
let mut ctx = wm.ctx();
crate::keyboard_layout::init_keyboard_layout(&mut ctx);
}
Expand Down Expand Up @@ -94,10 +105,10 @@ pub fn run() -> ! {

let (total_width, total_height) = compute_total_dimensions(&output_surfaces);

crate::wayland::render::drm::sync_monitors_from_outputs_vec(&mut wm.g, &output_surfaces);
crate::wayland::render::drm::sync_monitors_from_outputs_vec(&mut wm_cell.borrow_mut().g, &output_surfaces);
{
use crate::monitor::update_geom;
update_geom(&mut wm.ctx());
update_geom(&mut wm_cell.borrow_mut().ctx());
}

let shared = init_shared_state(&output_surfaces, total_width, total_height);
Expand All @@ -106,11 +117,10 @@ pub fn run() -> ! {
spawn_xwayland(&state, &loop_handle);

// Initialize Wayland systray runtime - only applicable for Wayland backend
if let WmBackend::Wayland(data) = &mut wm.backend {
if let WmBackend::Wayland(data) = &mut wm_cell.borrow_mut().backend {
data.wayland_systray_runtime = crate::systray::wayland::WaylandSystrayRuntime::start();
}

let wm_cell = Rc::new(RefCell::new(wm));
let wm_cell_for_closure = Rc::clone(&wm_cell);

let mut libinput_context =
Expand Down Expand Up @@ -173,7 +183,7 @@ pub fn run() -> ! {

run_event_loop(
event_loop,
&mut wm_cell.borrow_mut(),
Rc::clone(&wm_cell),
&mut state,
&shared,
&mut output_surfaces,
Expand Down Expand Up @@ -316,7 +326,7 @@ fn setup_drm_vblank_handler(
#[allow(clippy::too_many_arguments)]
fn run_event_loop(
mut event_loop: EventLoop<WaylandState>,
wm: &mut Wm,
wm_cell: Rc<RefCell<Wm>>,
state: &mut WaylandState,
shared: &Arc<Mutex<SharedDrmState>>,
output_surfaces: &mut [OutputSurfaceEntry],
Expand All @@ -332,11 +342,13 @@ fn run_event_loop(

event_loop
.run(Duration::from_millis(16), state, move |state| {
let mut wm = wm_cell.borrow_mut();

process_completed_crtcs(state, shared, output_surfaces);

super::common::arrange_layout_if_dirty(wm, state);
super::common::arrange_layout_if_dirty(&mut wm, state);

process_ipc(ipc_server, wm, shared);
process_ipc(ipc_server, &mut wm, shared);

if wm.g.dirty.input_config {
wm.g.dirty.input_config = false;
Expand All @@ -356,12 +368,12 @@ fn run_event_loop(
}
}

process_animations(wm, state, shared);
process_animations(&mut wm, state, shared);

process_cursor_warp(state, &pointer_handle, shared);

render_outputs(
wm,
&mut wm,
state,
renderer,
output_surfaces,
Expand Down
2 changes: 1 addition & 1 deletion src/wayland/runtime/winit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::wm::Wm;
/// Run the winit (nested) Wayland compositor.
pub fn run() -> ! {
ensure_dbus_session();
let mut wm = Wm::new(WmBackend::new_wayland(WaylandBackend::new()));
let mut wm = Box::new(Wm::new(WmBackend::new_wayland(WaylandBackend::new())));
if let Some(wayland) = wm.backend.wayland_data_mut() {
init_wayland_globals(&mut wm.g, wayland);
}
Expand Down
Loading