38 lines
1.2 KiB
Go
38 lines
1.2 KiB
Go
//go:build linux
|
|
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"net"
|
|
"strconv"
|
|
"time"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// serviceDialSocket connects to EXACTLY the socket inode at sock, never to
|
|
// whatever a name resolves to at dial time. The container owns the directory
|
|
// the socket lives in, and a plain Lstat-then-Dial leaves a window in which it
|
|
// can rename a link (to a host socket owned by the same uid — a rootless
|
|
// container runtime's, say) into place. So: open the name with O_PATH|O_NOFOLLOW
|
|
// (a symlink fails to open, it is never followed), confirm on the FD that it is
|
|
// a socket, and connect through /proc/self/fd/N — the kernel resolves that to
|
|
// the inode already held, whatever happens to the name afterwards. The peer's
|
|
// uid is still checked after connect (spliceUnix) as a second wall.
|
|
func serviceDialSocket(sock string, timeout time.Duration) (net.Conn, error) {
|
|
fd, err := unix.Open(sock, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer unix.Close(fd)
|
|
var st unix.Stat_t
|
|
if err := unix.Fstat(fd, &st); err != nil {
|
|
return nil, err
|
|
}
|
|
if st.Mode&unix.S_IFMT != unix.S_IFSOCK {
|
|
return nil, errors.New("not a socket")
|
|
}
|
|
return net.DialTimeout("unix", "/proc/self/fd/"+strconv.Itoa(fd), timeout)
|
|
}
|