From 2c305b35c9cdae62fa7f0b719085f7d835fefeb7 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 19 Oct 2026 10:08:07 +0200 Subject: fix(notify): harden sound hints from the final review A sound-file must be a regular file: a FIFO blocked the player on open and a device such as /dev/urandom played noise. A sound-name containing a path separator or .. is refused instead of being cleaned into a lookup outside the theme. The search roots are read once per lookup. Co-Authored-By: Claude Opus 5.5 --- internal/notify/sounds.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'internal/notify/sounds.go') diff --git a/internal/notify/sounds.go b/internal/notify/sounds.go index 85f1ecc..15ce3b9 100644 --- a/internal/notify/sounds.go +++ b/internal/notify/sounds.go @@ -13,6 +13,7 @@ package notify import ( "log" + "os" "path/filepath" "strings" @@ -36,13 +37,20 @@ var soundExts = []string{".oga", ".ogg", ".wav"} // full name first and then with trailing -components dropped, as the spec's // fallback (message-new-email, message-new, message). "" when nothing matches. func LookupSound(name, theme string) string { + // A name is a name, never a path: refuse what would walk out of the theme. + if strings.ContainsAny(name, "/\\") || strings.Contains(name, "..") { + return "" + } + roots := XDGSoundDirs() var chain []string seen := map[string]bool{} for t := theme; t != "" && !seen[t]; { seen[t] = true chain = append(chain, t) next := "" - for _, root := range XDGSoundDirs() { + // ponytail: only the first Inherits entry, as for icons; walk every + // parent if a multi-parent sound theme ever turns up. + for _, root := range roots { if v := inheritsOf(filepath.Join(root, t)); v != "" { next = v break @@ -55,7 +63,7 @@ func LookupSound(name, theme string) string { } for n := name; n != ""; { for _, t := range chain { - for _, root := range XDGSoundDirs() { + for _, root := range roots { for _, ext := range soundExts { p := filepath.Join(root, t, "stereo", n+ext) if fileExists(p) { @@ -74,7 +82,7 @@ func LookupSound(name, theme string) string { } // SoundFromHints reads the three freedesktop sound hints. sound-file wins over -// sound-name, but only as an absolute path to an existing file: anything else +// sound-name, but only as an absolute path to a regular file: anything else // is logged and treated as absent, so the name still gets its turn. silent is // suppress-sound, published as is; the renderer decides what it means. func SoundFromHints(hints map[string]dbus.Variant, theme string) (sound string, silent bool) { @@ -83,10 +91,12 @@ func SoundFromHints(hints map[string]dbus.Variant, theme string) (sound string, } if v, ok := hints["sound-file"]; ok { if p, _ := v.Value().(string); p != "" { - if filepath.IsAbs(p) && fileExists(p) { + // Regular only: a FIFO would block the player on open and a + // device like /dev/urandom would play noise. + if info, err := os.Stat(p); err == nil && filepath.IsAbs(p) && info.Mode().IsRegular() { return p, silent } - log.Printf("notifyd: sound-file %q is not an absolute path to a file", p) + log.Printf("notifyd: sound-file %q is not an absolute path to a regular file", p) } } if v, ok := hints["sound-name"]; ok { -- cgit v1.2.3