From 00aae60378982902b871a87850e0ed427b28a347 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Mon, 14 Sep 2026 19:42:17 +0200 Subject: plan 8: trash refuses unsafe entry names and relative paths --- internal/trash/trash.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) (limited to 'internal/trash/trash.go') diff --git a/internal/trash/trash.go b/internal/trash/trash.go index cc9bc23..b96c3aa 100644 --- a/internal/trash/trash.go +++ b/internal/trash/trash.go @@ -174,7 +174,9 @@ func percentDecode(s string) string { } // Restore moves an entry back to the Path recorded in its .trashinfo and -// removes the .trashinfo. It refuses when that path already exists. +// removes the .trashinfo. It refuses when that path already exists. It also +// refuses an entry that is not a plain name inside the Trash, and a +// trashinfo whose Path is not absolute. // // Once the rename back to the original path has succeeded, removing the // .trashinfo is best-effort: that file back in place is the substantive @@ -183,6 +185,11 @@ func percentDecode(s string) string { // as an error — Restore returns (path, nil) regardless — and the // .trashinfo may survive as a stale, otherwise-harmless record. func Restore(entry string) (restored string, err error) { + // An entry comes from the log; it must name something inside the Trash + // (spec §15.1), never a path that climbs out of it. + if entry == "" || entry == "." || entry == ".." || strings.ContainsRune(entry, '/') { + return "", fmt.Errorf("trash: bad entry name %q", entry) + } infoPath := filepath.Join(infoDir(), entry+".trashinfo") b, err := os.ReadFile(infoPath) if err != nil { @@ -211,11 +218,17 @@ func Restore(entry string) (restored string, err error) { return path, nil } -// parsePath extracts and decodes the Path= line of a .trashinfo file. +// parsePath extracts and decodes the Path= line of a .trashinfo file. The +// path must be absolute: Restore must never resolve one against the +// working directory. func parsePath(info string) (string, error) { for _, line := range strings.Split(info, "\n") { if v, ok := strings.CutPrefix(line, "Path="); ok { - return percentDecode(v), nil + p := percentDecode(v) + if !filepath.IsAbs(p) { + return "", fmt.Errorf("trashinfo path %q is not absolute", p) + } + return p, nil } } return "", errors.New("trashinfo has no path") -- cgit v1.3