//go:build !windows package main // The kernel's answer to "who is on the other end of this socket", on unix. // // linux SO_PEERCRED, filled by the kernel at connect() time. It cannot be set // by the caller and it is not re-read later, so it is the identity of // the process that connected even if that process is gone by now. // darwin/bsd getpeereid, the same guarantee through a different call. // // Neither reads anything the client sent. That is the whole reason this file // exists rather than a username field in the request. import ( "errors" "net" "os" "os/user" "path/filepath" "strconv" "syscall" ) // contextSockListen binds the socket and sets its mode. // // 0666 ON THE SOCKET, DELIBERATELY, and it is the one line here worth arguing // with. The daemon's config dir is 0700, so a socket inside it is unreachable // by anyone else — which would make this whole path work only for the user who // already did not need it. The socket is therefore created in a directory of // its own at 0711: traversable by anyone, listable by nobody, so a local user // can connect to a path they cannot enumerate. // // Connecting is not authority. Every connection is identified by the kernel // and the ask still lands inert for a person on the host to approve, so what an // unprivileged local user gains by reaching this socket is the ability to ASK // in their own name — which is precisely the feature. func contextSockListen(path string) (net.Listener, error) { dir := filepath.Dir(path) // 0755: traversable AND listable by everyone, because the path is fixed and // public anyway — hiding it bought nothing and the 0711 version could not be // traversed at all from inside a 0700 config dir, which is how the first // version of this shipped dead. if err := os.MkdirAll(dir, 0o755); err != nil { return nil, err } if err := os.Chmod(dir, 0o755); err != nil { return nil, err } sock := path _ = os.Remove(sock) // a stale socket from a previous run refuses to bind ln, err := net.Listen("unix", sock) if err != nil { return nil, err } if err := os.Chmod(sock, 0o666); err != nil { _ = ln.Close() return nil, err } return ln, nil } // contextPeerUser reads the connecting process's user from the kernel. func contextPeerUser(c net.Conn) (string, error) { _, who, err := contextPeerIdentity(c) return who, err } // contextPeerIdentity is the connecting process's uid from the kernel, and // the account's name for messages (or "uid N" when it has none). func contextPeerIdentity(c net.Conn) (uint32, string, error) { uc, ok := c.(*net.UnixConn) if !ok { return 0, "", errors.New("not a unix socket") } raw, err := uc.SyscallConn() if err != nil { return 0, "", err } var uid uint32 var serr error err = raw.Control(func(fd uintptr) { uid, serr = contextPeerUID(fd) }) if err != nil { return 0, "", err } if serr != nil { return 0, "", serr } name := "" if u, err := user.LookupId(strconv.FormatUint(uint64(uid), 10)); err == nil { name = u.Username } return uid, contextSockFmtUser(uid, name), nil } // contextSockDir is machine-wide and root-owned. See contextSockPath. const contextSockDir = "/var/run/privatae-bastion" // contextSockDialable refuses a socket that is not root's. // // The daemon that can forward a draft runs as root on the machines where this // path matters. A socket at this address owned by anyone else is not that // daemon, and handing it a folder path would be handing it to whoever made it. func contextSockDialable(p string) string { fi, err := os.Stat(p) if err != nil { return "" // absent: not an impostor, just not there — caller falls back } st, ok := fi.Sys().(*syscall.Stat_t) if !ok { return "" } if st.Uid != 0 { return "the bastion's local socket at " + p + " is owned by uid " + strconv.FormatUint(uint64(st.Uid), 10) + ", not by root. That is not this " + "machine's bastion, so nothing was sent to it." } return "" }