76 lines
2.1 KiB
Go
76 lines
2.1 KiB
Go
//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
|
|
}
|