//go:build !windows package safeio import ( "os" "strings" "golang.org/x/sys/unix" ) type unixParentHandles struct { descriptors []int parent int target string } func (parents *unixParentHandles) Close() { closeUnixDescriptors(parents.descriptors) } func openCanonicalUnixParent(path string) (*unixParentHandles, error) { if err := ValidateCanonicalPath(path); err != nil { return nil, ErrUnsafeFile } components := strings.Split(strings.TrimPrefix(path, string(os.PathSeparator)), string(os.PathSeparator)) if len(components) == 0 || components[0] == "" { return nil, ErrUnsafeFile } directory, err := unix.Open(string(os.PathSeparator), unix.O_RDONLY|unix.O_CLOEXEC|unix.O_DIRECTORY, 0) if err != nil { return nil, ErrUnsafeFile } parents := &unixParentHandles{descriptors: []int{directory}, parent: directory, target: components[len(components)-1]} for _, component := range components[:len(components)-1] { next, err := unix.Openat(directory, component, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_DIRECTORY|unix.O_NOFOLLOW, 0) if err != nil { parents.Close() return nil, ErrUnsafeFile } directory = next parents.descriptors = append(parents.descriptors, directory) parents.parent = directory } return parents, nil } func privateUnixDirectoryStat(stat *unix.Stat_t) bool { return stat != nil && stat.Mode&unix.S_IFMT == unix.S_IFDIR && stat.Uid == uint32(os.Geteuid()) && stat.Mode&0o7777 == 0o700 } func preflightPrivateDirectory(path string) (bool, error) { parents, err := openCanonicalUnixParent(path) if err != nil { return false, ErrUnsafeFile } defer parents.Close() descriptor, err := unix.Openat(parents.parent, parents.target, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_DIRECTORY|unix.O_NOFOLLOW, 0) if err != nil { if err == unix.ENOENT { if unix.Faccessat(parents.parent, ".", unix.W_OK|unix.X_OK, unix.AT_EACCESS) != nil { return false, ErrUnsafeFile } return false, nil } return false, ErrUnsafeFile } defer unix.Close(descriptor) var stat unix.Stat_t if unix.Fstat(descriptor, &stat) != nil || !privateUnixDirectoryStat(&stat) { return false, ErrUnsafeFile } return true, nil }