diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-19 10:08:07 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-19 10:08:07 +0200 |
| commit | 2c305b35c9cdae62fa7f0b719085f7d835fefeb7 (patch) | |
| tree | 47db9d7fb25d1d6d322b22ab7c65b855d59bd2a1 | |
| parent | 47c2469aeb0e8f3ed40b995a560ab95ca1eb1074 (diff) | |
| download | notifyd-master.tar.gz notifyd-master.zip | |
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 <noreply@anthropic.com>
| -rw-r--r-- | internal/notify/sounds.go | 20 | ||||
| -rw-r--r-- | internal/notify/sounds_test.go | 8 |
2 files changed, 23 insertions, 5 deletions
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 { diff --git a/internal/notify/sounds_test.go b/internal/notify/sounds_test.go index 060bbbe..060b927 100644 --- a/internal/notify/sounds_test.go +++ b/internal/notify/sounds_test.go @@ -14,6 +14,7 @@ package notify import ( "os" "path/filepath" + "syscall" "testing" "github.com/godbus/dbus/v5" @@ -88,6 +89,10 @@ func TestSoundFromHints(t *testing.T) { if err := os.WriteFile(file, []byte("x"), 0o644); err != nil { t.Fatal(err) } + fifo := filepath.Join(t.TempDir(), "pipe") + if err := syscall.Mkfifo(fifo, 0o600); err != nil { + t.Fatal(err) + } v := dbus.MakeVariant cases := []struct { name string @@ -102,6 +107,9 @@ func TestSoundFromHints(t *testing.T) { {"missing file rejected", map[string]dbus.Variant{"sound-file": v(file + ".gone")}, "", false}, {"bad file falls to name", map[string]dbus.Variant{"sound-file": v("x"), "sound-name": v("bell")}, root + "/freedesktop/stereo/bell.oga", false}, {"suppress", map[string]dbus.Variant{"suppress-sound": v(true), "sound-name": v("bell")}, root + "/freedesktop/stereo/bell.oga", true}, + {"fifo rejected", map[string]dbus.Variant{"sound-file": v(fifo)}, "", false}, + {"device rejected", map[string]dbus.Variant{"sound-file": v("/dev/null")}, "", false}, + {"name with a path rejected", map[string]dbus.Variant{"sound-name": v("../freedesktop/stereo/bell")}, "", false}, {"wrong types", map[string]dbus.Variant{"suppress-sound": v("yes"), "sound-name": v(3)}, "", false}, } for _, c := range cases { |
