aboutsummaryrefslogtreecommitdiffstats
path: root/internal/notify/image.go
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-15 18:09:26 +0200
committerDanilo M. <danix@danix.xyz>2026-09-15 18:09:26 +0200
commit2a3916ce192d73bdd6f621cc3842782814351227 (patch)
tree4c275a9cd02cad501a77925cbdd0d18807a15c88 /internal/notify/image.go
parent6ad6f122eb2ec9cd8a55c042dffe38db41ed1959 (diff)
downloadnotifyd-2a3916ce192d73bdd6f621cc3842782814351227.tar.gz
notifyd-2a3916ce192d73bdd6f621cc3842782814351227.zip
fix(notify): cap image dimensions and rank icon_data last
A notification is untrusted input. RawImage.PNG computed stride*(Height-1)+Width*Channels in int, so Width=Height=2^31-1 with RowStride=0 wrapped the length expression negative, slipped past the guard, and reached image.NewRGBA, which panics. The D-Bus call path has no recover, so one malformed Notify killed the daemon. Cap Width/Height at 1<<16 before any multiplication and compute the required byte count in int64. image-data, image-path and the deprecated icon_data were read as one tier, with icon_data ahead of image-path, inverting the spec's order. Split the deprecated key into IconDataFromHints and apply tier 1 (image-data/image_data), then image-path, then icon_data. Raw handling is unchanged: encode, hold pendingImage, WriteImage after Add, SetImage.
Diffstat (limited to 'internal/notify/image.go')
-rw-r--r--internal/notify/image.go29
1 files changed, 25 insertions, 4 deletions
diff --git a/internal/notify/image.go b/internal/notify/image.go
index 4a0f6df..9dd750e 100644
--- a/internal/notify/image.go
+++ b/internal/notify/image.go
@@ -34,10 +34,10 @@ type RawImage struct {
Data []byte
}
-// ImageDataFromHints reads the raw image struct, preferring the spec key then
-// the deprecated icon_data, then the underscore alias older libnotify sent.
+// ImageDataFromHints reads the spec's top-priority raw image key, image-data,
+// or the underscore alias older libnotify sent.
func ImageDataFromHints(hints map[string]dbus.Variant) (*RawImage, bool) {
- for _, key := range []string{"image-data", "icon_data", "image_data"} {
+ for _, key := range []string{"image-data", "image_data"} {
v, ok := hints[key]
if !ok {
continue
@@ -49,6 +49,16 @@ func ImageDataFromHints(hints map[string]dbus.Variant) (*RawImage, bool) {
return nil, false
}
+// IconDataFromHints reads the deprecated icon_data raw image, the lowest
+// priority source, below image-data and the image-path URI.
+func IconDataFromHints(hints map[string]dbus.Variant) (*RawImage, bool) {
+ v, ok := hints["icon_data"]
+ if !ok {
+ return nil, false
+ }
+ return rawImageFromVariant(v)
+}
+
// ImagePathFromHints reads image-path, a URI, a path, or a theme icon name.
func ImagePathFromHints(hints map[string]dbus.Variant) (string, bool) {
v, ok := hints["image-path"]
@@ -98,11 +108,19 @@ func asInt(v any) (int, bool) {
return 0, false
}
+// maxImageDimension caps a client-supplied width or height. A notification
+// icon is small, and the cap keeps every later width*channels and y*stride
+// product far from overflow.
+const maxImageDimension = 1 << 16
+
// PNG encodes the raw pixels as a PNG the renderer can load.
func (r *RawImage) PNG() ([]byte, error) {
if r.Width <= 0 || r.Height <= 0 {
return nil, fmt.Errorf("notifyd: image %dx%d", r.Width, r.Height)
}
+ if r.Width > maxImageDimension || r.Height > maxImageDimension {
+ return nil, fmt.Errorf("notifyd: image %dx%d larger than %d", r.Width, r.Height, maxImageDimension)
+ }
if r.BitsPerSample != 8 || (r.Channels != 3 && r.Channels != 4) {
return nil, fmt.Errorf("notifyd: image bits=%d channels=%d", r.BitsPerSample, r.Channels)
}
@@ -110,7 +128,10 @@ func (r *RawImage) PNG() ([]byte, error) {
if stride < r.Width*r.Channels {
stride = r.Width * r.Channels
}
- if len(r.Data) < stride*(r.Height-1)+r.Width*r.Channels {
+ // int64 keeps a hostile RowStride from wrapping the length comparison
+ // negative and passing the guard.
+ need := int64(stride)*int64(r.Height-1) + int64(r.Width)*int64(r.Channels)
+ if int64(len(r.Data)) < need {
return nil, fmt.Errorf("notifyd: image data short: %d bytes", len(r.Data))
}
img := image.NewRGBA(image.Rect(0, 0, r.Width, r.Height))