An account could replace ~/.claude with a link to /etc and have the node-engine chown, chmod or write through it on the next apply. Below a person's or an agent's home every component is now opened without following a link, and a link refuses the resource in words. The root judge also reads doas and polkit rules, the container runtimes' sockets with their ACLs, ACLs on the secrets, and setuid-root programs no package owns, in the C locale; Judged and NotJudged write down exactly what it covers (hq ADR 0266 review).
179 lines
5.4 KiB
Go
179 lines
5.4 KiB
Go
//go:build linux
|
|
|
|
package apply
|
|
|
|
// The descriptor half of home_links.go: below a home, every component is opened from the one above it with
|
|
// O_NOFOLLOW, so a link is refused by the kernel at the moment of use, not only at the check before it.
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// openDirUnder opens the directory rel names below home, following no link below the home.
|
|
func openDirUnder(home, dir string) (int, error) {
|
|
rel, err := filepath.Rel(home, filepath.Clean(dir))
|
|
if err != nil || strings.HasPrefix(rel, "..") {
|
|
return -1, fmt.Errorf("%s is not below %s", dir, home)
|
|
}
|
|
fd, err := unix.Open(home, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
|
if err != nil {
|
|
return -1, &os.PathError{Op: "open", Path: home, Err: err}
|
|
}
|
|
if rel == "." {
|
|
return fd, nil
|
|
}
|
|
at := home
|
|
for _, part := range strings.Split(rel, string(os.PathSeparator)) {
|
|
at = filepath.Join(at, part)
|
|
next, err := unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
|
unix.Close(fd)
|
|
if err != nil {
|
|
return -1, linkOr(at, home, dir, "open", err)
|
|
}
|
|
fd = next
|
|
}
|
|
return fd, nil
|
|
}
|
|
|
|
// linkOr says a refused link in the mesh's words, and any other failure as the system's.
|
|
func linkOr(at, home, path, op string, err error) error {
|
|
if errors.Is(err, unix.ELOOP) || errors.Is(err, unix.ENOTDIR) {
|
|
if info, lerr := os.Lstat(at); lerr == nil && info.Mode()&os.ModeSymlink != 0 {
|
|
return &LinkUnderHomeError{Path: path, Link: at, Home: home}
|
|
}
|
|
}
|
|
return &os.PathError{Op: op, Path: at, Err: err}
|
|
}
|
|
|
|
// openUnder opens the file or directory at path below home, following no link, for its metadata.
|
|
func openUnder(home, path string) (int, error) {
|
|
dir, err := openDirUnder(home, filepath.Dir(path))
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
defer unix.Close(dir)
|
|
fd, err := unix.Openat(dir, filepath.Base(path), unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK|unix.O_CLOEXEC, 0)
|
|
if err != nil {
|
|
return -1, linkOr(path, home, path, "open", err)
|
|
}
|
|
return fd, nil
|
|
}
|
|
|
|
func chmodUnder(home, path string, mode os.FileMode) error {
|
|
fd, err := openUnder(home, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer unix.Close(fd)
|
|
if err := unix.Fchmod(fd, uint32(mode.Perm())); err != nil {
|
|
return &os.PathError{Op: "chmod", Path: path, Err: err}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func chownUnder(home, path string, uid, gid int) error {
|
|
fd, err := openUnder(home, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer unix.Close(fd)
|
|
if err := unix.Fchown(fd, uid, gid); err != nil {
|
|
return &os.PathError{Op: "chown", Path: path, Err: err}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readUnder(home, path string) ([]byte, error) {
|
|
fd, err := openUnder(home, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
f := os.NewFile(uintptr(fd), path)
|
|
defer f.Close()
|
|
var st unix.Stat_t
|
|
if err := unix.Fstat(fd, &st); err == nil && st.Mode&unix.S_IFMT != unix.S_IFREG {
|
|
return nil, fmt.Errorf("%s is not a regular file", path)
|
|
}
|
|
return io.ReadAll(f)
|
|
}
|
|
|
|
// mkdirAllUnder makes the directories missing below home down to dir, each made in its parent's descriptor
|
|
// and opened without following a link; answers those it made, deepest first, as makeDirsSaying does.
|
|
func mkdirAllUnder(home, dir string, mode os.FileMode) ([]string, error) {
|
|
rel, err := filepath.Rel(home, filepath.Clean(dir))
|
|
if err != nil || strings.HasPrefix(rel, "..") {
|
|
return nil, fmt.Errorf("%s is not below %s", dir, home)
|
|
}
|
|
fd, err := unix.Open(home, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
|
if err != nil {
|
|
return nil, &os.PathError{Op: "open", Path: home, Err: err}
|
|
}
|
|
defer func() { unix.Close(fd) }()
|
|
var made []string
|
|
if rel == "." {
|
|
return nil, nil
|
|
}
|
|
at := home
|
|
for _, part := range strings.Split(rel, string(os.PathSeparator)) {
|
|
at = filepath.Join(at, part)
|
|
if err := unix.Mkdirat(fd, part, uint32(mode.Perm())); err == nil {
|
|
made = append([]string{at}, made...)
|
|
} else if !errors.Is(err, unix.EEXIST) {
|
|
return made, &os.PathError{Op: "mkdir", Path: at, Err: err}
|
|
}
|
|
next, err := unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
|
if err != nil {
|
|
return made, linkOr(at, home, dir, "open", err)
|
|
}
|
|
unix.Close(fd)
|
|
fd = next
|
|
}
|
|
return made, nil
|
|
}
|
|
|
|
// writeUnder writes a file below home atomically, through its directory's descriptor: made O_EXCL|O_NOFOLLOW
|
|
// under a name of its own, given its mode, and renamed over the file within that directory.
|
|
func writeUnder(home, path string, content []byte, mode os.FileMode) error {
|
|
dir, err := openDirUnder(home, filepath.Dir(path))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer unix.Close(dir)
|
|
name := ".mesh-host-" + strconv.FormatInt(time.Now().UnixNano(), 36) + "-" + strconv.Itoa(os.Getpid())
|
|
fd, err := unix.Openat(dir, name, unix.O_WRONLY|unix.O_CREAT|unix.O_EXCL|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0o600)
|
|
if err != nil {
|
|
return &os.PathError{Op: "create", Path: filepath.Join(filepath.Dir(path), name), Err: err}
|
|
}
|
|
f := os.NewFile(uintptr(fd), name)
|
|
_, werr := f.Write(content)
|
|
if werr == nil {
|
|
werr = f.Sync()
|
|
}
|
|
if werr == nil {
|
|
if err := unix.Fchmod(fd, uint32(mode.Perm())); err != nil {
|
|
werr = &os.PathError{Op: "chmod", Path: path, Err: err}
|
|
}
|
|
}
|
|
if cerr := f.Close(); werr == nil {
|
|
werr = cerr
|
|
}
|
|
if werr == nil {
|
|
if err := unix.Renameat(dir, name, dir, filepath.Base(path)); err != nil {
|
|
werr = &os.PathError{Op: "rename", Path: path, Err: err}
|
|
}
|
|
}
|
|
if werr != nil {
|
|
_ = unix.Unlinkat(dir, name, 0)
|
|
}
|
|
return werr
|
|
}
|