aboutsummaryrefslogtreecommitdiffstats
path: root/internal/notify
diff options
context:
space:
mode:
Diffstat (limited to 'internal/notify')
-rw-r--r--internal/notify/image.go18
-rw-r--r--internal/notify/image_test.go17
2 files changed, 31 insertions, 4 deletions
diff --git a/internal/notify/image.go b/internal/notify/image.go
index 9dd750e..575f52b 100644
--- a/internal/notify/image.go
+++ b/internal/notify/image.go
@@ -109,10 +109,14 @@ func asInt(v any) (int, bool) {
}
// 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.
+// icon is small.
const maxImageDimension = 1 << 16
+// maxRowStrideSlack bounds how far RowStride may exceed the tight packing of
+// Width*Channels. Real strides are the tight width or that plus a small
+// alignment pad.
+const maxRowStrideSlack = 4096
+
// 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 {
@@ -124,12 +128,18 @@ func (r *RawImage) PNG() ([]byte, error) {
if r.BitsPerSample != 8 || (r.Channels != 3 && r.Channels != 4) {
return nil, fmt.Errorf("notifyd: image bits=%d channels=%d", r.BitsPerSample, r.Channels)
}
+ // RowStride is client-supplied and may arrive as int64. A negative or
+ // absurd stride would wrap both the length guard and the y*stride slice
+ // index, so bound it before use.
stride := r.RowStride
+ if stride < 0 || stride > r.Width*r.Channels+maxRowStrideSlack {
+ return nil, fmt.Errorf("notifyd: image rowstride %d for %dx%d channels=%d", stride, r.Width, r.Height, r.Channels)
+ }
if stride < r.Width*r.Channels {
stride = r.Width * r.Channels
}
- // int64 keeps a hostile RowStride from wrapping the length comparison
- // negative and passing the guard.
+ // Width/Height are capped and stride is bounded, so this product and the
+ // y*stride indexing below cannot overflow.
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))
diff --git a/internal/notify/image_test.go b/internal/notify/image_test.go
index 00dfb33..4f8c7ce 100644
--- a/internal/notify/image_test.go
+++ b/internal/notify/image_test.go
@@ -16,6 +16,7 @@ import (
"image"
"image/color"
"image/png"
+ "math"
"testing"
"github.com/godbus/dbus/v5"
@@ -161,4 +162,20 @@ func TestRawImagePNGRejectsBadData(t *testing.T) {
}).PNG(); err == nil {
t.Fatal("oversized dimensions must error")
}
+ // Small dimensions but RowStride at MaxInt: need wraps negative and the
+ // y*stride slice at y=1 panics. Must error, not panic.
+ if _, err := (&RawImage{
+ Width: 2, Height: 2, RowStride: math.MaxInt,
+ BitsPerSample: 8, Channels: 4,
+ Data: []byte{0, 0, 0, 255, 0, 0, 0, 255},
+ }).PNG(); err == nil {
+ t.Fatal("oversized rowstride must error")
+ }
+ if _, err := (&RawImage{
+ Width: 2, Height: 1, RowStride: -1,
+ BitsPerSample: 8, Channels: 4,
+ Data: []byte{0, 0, 0, 255, 0, 0, 0, 255},
+ }).PNG(); err == nil {
+ t.Fatal("negative rowstride must error")
+ }
}