Summary
RgbaIcon::from_rgba validates only that the buffer length matches the stated
dimensions, so a 0x0 icon constructs successfully and panics later at render
time. Still present at muda-v0.19.3:
// src/icon.rs:82
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
if rgba.len() % PIXEL_SIZE != 0 { /* ByteCountNotDivisibleBy4 */ }
let pixel_count = rgba.len() / PIXEL_SIZE;
if pixel_count != (width * height) as usize {
Err(BadIcon::DimensionsVsPixelCount { .. })
} else {
Ok(RgbaIcon { rgba, width, height })
}
}
from_rgba(vec![], 0, 0) satisfies this: 0 == 0. Rendering it then panics:
// src/platform_impl/macos/icon.rs:34
let mut writer = encoder.write_header().unwrap();
called `Result::unwrap()` on an `Err` value: Format(FormatError { inner: ZeroWidth })
A zero-sized icon is therefore legal to construct and fatal to render, and
the failure surfaces far from the call that created it — in a menu-item action
handler, on the main thread, inside an Objective-C callback where unwinding is
not an option.
Reproduction
let icon = muda::Icon::from_rgba(Vec::new(), 0, 0).unwrap(); // Ok
let item = muda::IconMenuItem::new("x", true, Some(icon), None); // panics on render
Any path reaching PlatformIcon::to_nsimage will do: IconMenuItem::new,
MenuChild::set_icon, or AboutMetadata { icon: Some(..), .. }.
Why this is worth tightening
This is the reason #328 presented the way it did, and it cost real debugging
time. We hit the same ZeroWidth panic through a different route — a window
menu bar via dioxus-desktop, not a tray menu — without our application ever
constructing an icon. The freed MenuChild read back as
Some(RgbaIcon { rgba: [], width: 0, height: 0 }), a shape this validation
accepts, so instead of faulting at the corruption the process aborted inside
the PNG encoder with a message pointing squarely at icons. We spent a while
looking for code that built an icon; there wasn't any.
Rejecting zero dimensions would not have prevented that use-after-free, but it
would have turned a misleading abort into either an honest fault or an error at
the boundary. It also closes the "valid to construct, invalid to use" gap for
legitimate callers, who today get no signal until render time.
Suggested fix
Reject zero dimensions in the constructor, where the caller can still handle it:
if width == 0 || height == 0 {
return Err(BadIcon::ZeroSized { width, height });
}
A new BadIcon variant is a breaking change to that enum; if that is unwelcome
before the next major, DimensionsVsPixelCount reads acceptably for this case.
Secondarily, the two unwraps in to_png run on the main thread inside an ObjC
callback. Even with a stricter constructor, propagating rather than unwrapping
would stop malformed image data from aborting the host application.
Environment
Reproduced against muda 0.17.2 (via dioxus-desktop 0.7.10) on macOS 26.0 and
27.0, Apple silicon. Validation and to_png are unchanged at muda-v0.19.3.
Summary
RgbaIcon::from_rgbavalidates only that the buffer length matches the stateddimensions, so a 0x0 icon constructs successfully and panics later at render
time. Still present at
muda-v0.19.3:from_rgba(vec![], 0, 0)satisfies this:0 == 0. Rendering it then panics:A zero-sized icon is therefore legal to construct and fatal to render, and
the failure surfaces far from the call that created it — in a menu-item action
handler, on the main thread, inside an Objective-C callback where unwinding is
not an option.
Reproduction
Any path reaching
PlatformIcon::to_nsimagewill do:IconMenuItem::new,MenuChild::set_icon, orAboutMetadata { icon: Some(..), .. }.Why this is worth tightening
This is the reason #328 presented the way it did, and it cost real debugging
time. We hit the same
ZeroWidthpanic through a different route — a windowmenu bar via
dioxus-desktop, not a tray menu — without our application everconstructing an icon. The freed
MenuChildread back asSome(RgbaIcon { rgba: [], width: 0, height: 0 }), a shape this validationaccepts, so instead of faulting at the corruption the process aborted inside
the PNG encoder with a message pointing squarely at icons. We spent a while
looking for code that built an icon; there wasn't any.
Rejecting zero dimensions would not have prevented that use-after-free, but it
would have turned a misleading abort into either an honest fault or an error at
the boundary. It also closes the "valid to construct, invalid to use" gap for
legitimate callers, who today get no signal until render time.
Suggested fix
Reject zero dimensions in the constructor, where the caller can still handle it:
A new
BadIconvariant is a breaking change to that enum; if that is unwelcomebefore the next major,
DimensionsVsPixelCountreads acceptably for this case.Secondarily, the two
unwraps into_pngrun on the main thread inside an ObjCcallback. Even with a stricter constructor, propagating rather than unwrapping
would stop malformed image data from aborting the host application.
Environment
Reproduced against muda 0.17.2 (via
dioxus-desktop0.7.10) on macOS 26.0 and27.0, Apple silicon. Validation and
to_pngare unchanged atmuda-v0.19.3.