Skip to content

macOS: use-after-free crash when calling set_menu while menu is displayed #328

@louis030195

Description

@louis030195

Summary

Calling set_menu() on a tray icon (via tray-icon) while the menu is currently displayed causes a use-after-free. If the user then clicks a menu item, the app crashes inside fire_menu_item_click with a panic in to_png() (ZeroWidth) or a segfault — depending on what the freed memory looks like.

Root cause

MudaMenuItem stores a raw *const MenuChild pointer as an ivar (mod.rs:947):

// FIXME: Use `Rc` or something else to access the MenuChild.
#[ivars = Cell<*const MenuChild>]
struct MenuItem;

When set_menu(new_menu) is called:

  1. The old Box<dyn ContextMenu> is dropped → MenuChild items are freed
  2. But old NSMenuItems survive (retained by the still-visible NSMenu)
  3. User clicks a stale menu item → fire_menu_item_click dereferences the freed pointer
  4. Use-after-free → reads garbage → crash

Crash we hit

PANIC on thread 'main' at muda-0.17.1/src/platform_impl/macos/icon.rs:34:53:
called `Result::unwrap()` on an `Err` value: Format(FormatError { inner: ZeroWidth })

The freed memory was reinterpreted as a MenuChild with an Icon containing width=0. to_png() encodes it → png crate rejects zero width → .unwrap() panics. Since this runs inside an extern "C" Obj-C callback (fireMenuItemAction:), the panic crosses a nounwind boundary → immediate abort. catch_unwind cannot help.

Secondary issue: to_png() uses .unwrap()

Even without the use-after-free, PlatformIcon::to_png() (icon.rs:34) should return Result instead of using .unwrap(). The tray-icon crate's version of the same function already does this. And Icon::from_rgba(vec![], 0, 0) currently succeeds (the validation allows zero dimensions), so a zero-dimension Icon can be constructed legally.

Reproduction

This triggers in any app that periodically calls tray.set_menu(Some(new_menu)) to update the tray menu while the user has the menu open. Related to #128, #202, #233 — all are manifestations of the dangling *const MenuChild.

Workaround

Keep the previous Menu alive (e.g. via menu.clone() into a static) so the Rc<RefCell<MenuChild>> ref count stays > 0 until the next update cycle.

Suggested fix

Replace Cell<*const MenuChild> with Rc<MenuChild> (as the FIXME already suggests). This would make the MenuChild lifetime tied to the NSMenuItem, preventing the use-after-free entirely.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions