49 lines
1.2 KiB
Go
49 lines
1.2 KiB
Go
//go:build linux
|
|
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// dcnNoLinkStat describes the directory at p (absolute, clean) reached one
|
|
// component at a time with O_NOFOLLOW, so a link anywhere in the path fails
|
|
// instead of being followed. O_PATH: a component the bastion may traverse but
|
|
// not list (a home at 0711) still opens.
|
|
func dcnNoLinkStat(p string) (os.FileInfo, error) {
|
|
if !strings.HasPrefix(p, "/") {
|
|
return nil, errors.New("not an absolute path")
|
|
}
|
|
fd, err := unix.Open("/", unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, c := range strings.Split(p, "/") {
|
|
if c == "" {
|
|
continue
|
|
}
|
|
if c == "." || c == ".." {
|
|
_ = unix.Close(fd)
|
|
return nil, errors.New("not a clean path")
|
|
}
|
|
next, err := unix.Openat(fd, c, unix.O_PATH|unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
|
_ = unix.Close(fd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var st unix.Stat_t
|
|
if err := unix.Fstat(next, &st); err != nil || st.Mode&unix.S_IFMT != unix.S_IFDIR {
|
|
_ = unix.Close(next)
|
|
return nil, errors.New("a component is not a directory")
|
|
}
|
|
fd = next
|
|
}
|
|
f := os.NewFile(uintptr(fd), p)
|
|
defer f.Close()
|
|
return f.Stat()
|
|
}
|