carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build unix
2
3package host
4
5import (
6 "errors"
7 "fmt"
8 "os"
9 "path/filepath"
10 "strings"
11
12 "golang.org/x/sys/unix"
13 "syscall"
14)
15
16// errRegistryFileShape refuses a registry path that is not a plain,
17// single-link regular file. /data belongs to the service user, so anything
18// there may have been planted: a symlink or a hard link to a root-owned file
19// would otherwise hand that file to the service user when root chowns it.
20var errRegistryFileShape = errors.New("not a plain single-link regular file")
21
22// ownRegistryFile gives path to uid:gid without following a symlink and
23// without touching any file reachable by another name. It opens the path
24// O_NOFOLLOW, checks the opened file itself, and changes ownership through
25// that descriptor, so a swap between the check and the chown cannot redirect
26// it. A missing file reports os.ErrNotExist; changed is false when the file
27// was already owned as wanted.
28func ownRegistryFile(path string, uid, gid int) (changed bool, err error) {
29 parent, name, err := openRegistryParent(path)
30 if err != nil {
31 return false, err
32 }
33 defer parent.Close()
34 fd, err := openRegistryDescriptor(int(parent.Fd()), name)
35 var f *os.File
36 if err == nil {
37 f = os.NewFile(uintptr(fd), path)
38 }
39 if err != nil {
40 if errors.Is(err, syscall.ELOOP) {
41 return false, fmt.Errorf("%s: %w", path, errRegistryFileShape)
42 }
43 return false, err
44 }
45 defer f.Close()
46 fi, err := f.Stat()
47 if err != nil {
48 return false, err
49 }
50 st, ok := fi.Sys().(*syscall.Stat_t)
51 if !ok || !fi.Mode().IsRegular() || st.Nlink != 1 {
52 return false, fmt.Errorf("%s: %w", path, errRegistryFileShape)
53 }
54 if int(st.Uid) == uid && int(st.Gid) == gid {
55 return false, nil
56 }
57 if err := chownRegistryDescriptor(f, uid, gid); err != nil {
58 return false, err
59 }
60 return true, nil
61}
62
63// Walk each parent without following symlinks, keeping the final directory pinned.
64func openRegistryParent(path string) (*os.File, string, error) {
65 absolute, err := filepath.Abs(path)
66 if err != nil {
67 return nil, "", err
68 }
69 parent, err := os.Open("/")
70 if err != nil {
71 return nil, "", err
72 }
73 for _, part := range strings.Split(strings.TrimPrefix(filepath.Dir(absolute), "/"), "/") {
74 if part == "" {
75 continue
76 }
77 fd, err := unix.Openat(int(parent.Fd()), part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
78 parent.Close()
79 if err != nil {
80 return nil, "", err
81 }
82 parent = os.NewFile(uintptr(fd), part)
83 }
84 return parent, filepath.Base(absolute), nil
85}
86