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 linux
2
3package host
4
5import (
6 "fmt"
7 "os"
8 "os/exec"
9 "os/user"
10 "runtime"
11 "strconv"
12 "syscall"
13
14 "golang.org/x/sys/unix"
15)
16
17var registryExecutable, registryExecutableError = os.Open("/proc/self/exe")
18
19func registryChildExec(path string, uid, gid int) (*exec.Cmd, error) {
20 if registryExecutableError != nil {
21 return nil, registryExecutableError
22 }
23 cmd := exec.Command("/proc/self/fd/3", registryChildCommand, path, strconv.Itoa(uid), strconv.Itoa(gid))
24 cmd.ExtraFiles = []*os.File{registryExecutable}
25 return cmd, nil
26}
27
28func registryChildCloseExec() error {
29 // This marks descriptors in the parent too; Go opens its own descriptors
30 // close-on-exec, and ExtraFiles explicitly preserves the pinned executable.
31 return unix.CloseRange(3, ^uint(0), unix.CLOSE_RANGE_CLOEXEC)
32}
33
34func registryChildStart(cmd *exec.Cmd) error {
35 started := make(chan error, 1)
36 go func() {
37 runtime.LockOSThread()
38 // Never unlock: exiting this goroutine retires this OS thread. The
39 // parent keeps its privileges; every thread in the exec'd child inherits
40 // no-new-privileges, including threads created by cgo or the Go runtime.
41 if err := unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil {
42 started <- fmt.Errorf("registry: no new privileges: %w", err)
43 return
44 }
45 started <- cmd.Start()
46 }()
47 return <-started
48}
49
50func registryChildHarden() error {
51 // Exec as root keeps runtime startup inaccessible to the service user.
52 // Resolve the fixed service identity without inspecting child arguments.
53 // syscall's credential setters update every thread, including with cgo.
54 if err := syscall.Setgroups(nil); err != nil {
55 return err
56 }
57 service, err := user.Lookup(serviceUser)
58 if err != nil {
59 return err
60 }
61 uid, err := strconv.Atoi(service.Uid)
62 if err != nil {
63 return err
64 }
65 gid, err := strconv.Atoi(service.Gid)
66 if err != nil {
67 return err
68 }
69 if uid <= 0 || gid < 0 {
70 return fmt.Errorf("registry: invalid service credentials")
71 }
72 if err := syscall.Setresgid(gid, gid, gid); err != nil {
73 return err
74 }
75 if err := syscall.Setresuid(uid, uid, uid); err != nil {
76 return err
77 }
78 // Changing credentials resets dumpability to suid_dumpable. Explicitly
79 // require zero too, before reading any registry arguments or pipe input.
80 if err := unix.Prctl(unix.PR_SET_DUMPABLE, 0, 0, 0, 0); err != nil {
81 return fmt.Errorf("registry: disable dumpability: %w", err)
82 }
83 dumpable, err := unix.PrctlRetInt(unix.PR_GET_DUMPABLE, 0, 0, 0, 0)
84 if err != nil {
85 return err
86 }
87 ruid, euid, suid := unix.Getresuid()
88 rgid, egid, sgid := unix.Getresgid()
89 groups, err := os.Getgroups()
90 if err != nil {
91 return err
92 }
93 if dumpable != 0 || ruid != uid || euid != uid || suid != uid || rgid != gid || egid != gid || sgid != gid || len(groups) != 0 {
94 return fmt.Errorf("registry: child credentials were not dropped")
95 }
96 return nil
97}
98
99func closeRegistryExecutable() {
100 // fd 3 is the parent's pinned executable, passed only for exec.
101 unix.Close(3)
102 if registryExecutable != nil {
103 registryExecutable.Close()
104 }
105}
106