Files
mapsize/internal/platform/trash_unix.go
T
Andy DixonandClaude Opus 5.5 3ec5bd7e65 AI codebase review
fix: pin parent directory and verify owner/type before trashing

Trash renamed the item by path after the confirmation dialog, so a
parent directory swapped for a symlink since the scan (e.g. by another
user, while an administrator cleans their home) redirected the move to
an unrelated file. The parent is now opened once and the item is checked
(owner and file type as scanned) and renamed relative to that fd.
macOS volume trash directories now get the same ownership/symlink checks
as freedesktop.org ones on Linux.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:58:38 +01:00

60 lines
2.0 KiB
Go

//go:build linux || darwin
package platform
import (
"errors"
"io/fs"
"os"
"path/filepath"
"syscall"
"golang.org/x/sys/unix"
)
// renameChecked moves path to dst if it still matches want. The parent
// directory is opened once and both the check and the rename are relative to
// that descriptor, so a parent swapped for a symlink since the scan (or
// between check and rename) cannot redirect the move to another user's file.
func renameChecked(path, dst string, want Expect) error {
dir, base := filepath.Dir(path), filepath.Base(path)
dfd, err := unix.Open(dir, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
if err != nil {
return &os.PathError{Op: "open", Path: dir, Err: err}
}
defer unix.Close(dfd)
var st unix.Stat_t
if err := unix.Fstatat(dfd, base, &st, unix.AT_SYMLINK_NOFOLLOW); err != nil {
return &os.PathError{Op: "stat", Path: path, Err: err}
}
if st.Uid != want.UID || modeFromUnix(uint32(st.Mode))&fs.ModeType != want.Mode&fs.ModeType {
return errors.New("item changed since the scan (owner or type differs); rescan first")
}
return unix.Renameat(dfd, base, unix.AT_FDCWD, dst)
}
// ensurePrivateDir creates dir (mode 0700) if missing; an existing entry must
// be a real directory (not a symlink) owned by us. With strict (trash on a
// shared filesystem) it must also be inaccessible to others, as the
// freedesktop.org spec requires; home trash directories made by desktop
// environments are sometimes 0755, which is harmless there.
func ensurePrivateDir(dir string, strict bool) error {
if err := os.Mkdir(dir, 0o700); err != nil && !errors.Is(err, os.ErrExist) {
return err
}
fi, err := os.Lstat(dir)
if err != nil {
return err
}
st := fi.Sys().(*syscall.Stat_t)
switch {
case !fi.IsDir():
return errors.New("not a directory (symlink?)")
case int(st.Uid) != os.Getuid():
return errors.New("owned by another user")
case strict && fi.Mode().Perm()&0o077 != 0:
return errors.New("accessible to other users")
}
return nil
}