1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//go:build linux
package nsexec
import (
"fmt"
"os"
"os/exec"
"syscall"
"golang.org/x/sys/unix"
)
func Enter(c Config, self []string) error {
if err := c.Validate(); err != nil {
return err
}
unshare, err := exec.LookPath(Unshare)
if err != nil {
return fmt.Errorf("nsexec: a private hostname needs util-linux %s on this host: %w", Unshare, err)
}
inner := append(append([]string(nil), self...), "--inside")
argv := append([]string{unshare, "--user", "--map-current-user", "--keep-caps", "--uts", "--mount",
"--propagation", "private", "--"}, Wrap(inner, c.Hostname, c.Binds, c.Argv)...)
return syscall.Exec(unshare, argv, os.Environ())
}
func Inside(c Config) error {
if err := c.Validate(); err != nil {
return err
}
if err := unix.Sethostname([]byte(c.Hostname)); err != nil {
return fmt.Errorf("nsexec: sethostname %q: %w", c.Hostname, err)
}
for _, b := range c.Binds {
st, err := os.Stat(b.Target)
if err != nil || !st.IsDir() {
continue
}
if err := os.MkdirAll(b.Source, 0o700); err != nil {
return err
}
if err := unix.Mount(b.Source, b.Target, "", unix.MS_BIND|unix.MS_REC, ""); err != nil {
return fmt.Errorf("nsexec: bind %s over %s: %w", b.Source, b.Target, err)
}
}
if err := unix.Prctl(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); err != nil {
return fmt.Errorf("nsexec: clearing the ambient capabilities before exec: %w", err)
}
exe, err := exec.LookPath(c.Argv[0])
if err != nil {
return err
}
return syscall.Exec(exe, c.Argv, os.Environ())
}