mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
fixed unshared user namespace
This commit is contained in:
parent
4a120f9a3d
commit
d260bf4657
@ -35,9 +35,11 @@ Packages:
|
|||||||
|
|
||||||
- seccomp: provides utility function that wrappers libseccomp
|
- seccomp: provides utility function that wrappers libseccomp
|
||||||
- mount: provides utility function that wrappers mount syscall
|
- mount: provides utility function that wrappers mount syscall
|
||||||
- forkexec: fork-exec provides unshare, ptrace, seccomp before exec
|
- rlimit: provides utility function that defines rlimit syscall
|
||||||
|
- forkexec: fork-exec provides mount, unshare, ptrace, seccomp, capset before exec
|
||||||
- tracer: ptrace tracer and provides syscall trap filter context
|
- tracer: ptrace tracer and provides syscall trap filter context
|
||||||
- runprogram: wrapper to call forkexec and trecer
|
- runprogram: wrapper to call forkexec and trecer
|
||||||
|
- rununshared: wrapper to call forkexec and unshared namespaces
|
||||||
- runconfig: defines arch & language specified trace condition
|
- runconfig: defines arch & language specified trace condition
|
||||||
|
|
||||||
Executable:
|
Executable:
|
||||||
|
|||||||
@ -24,8 +24,10 @@ func afterForkInChild()
|
|||||||
//go:norace
|
//go:norace
|
||||||
func (r *Runner) Start() (int, error) {
|
func (r *Runner) Start() (int, error) {
|
||||||
var (
|
var (
|
||||||
err1 syscall.Errno
|
err1, err2 syscall.Errno
|
||||||
r1 uintptr
|
r1 uintptr
|
||||||
|
p [2]int
|
||||||
|
unshareUser = r.UnshareFlags&unix.CLONE_NEWUSER == unix.CLONE_NEWUSER
|
||||||
)
|
)
|
||||||
|
|
||||||
argv0, argv, envv, err := prepareExec(r.Args, r.Env)
|
argv0, argv, envv, err := prepareExec(r.Args, r.Env)
|
||||||
@ -51,8 +53,12 @@ func (r *Runner) Start() (int, error) {
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare set uid / gid map files
|
if unshareUser {
|
||||||
files := prepareIDMap(r.UnshareFlags&unix.CLONE_NEWUSER == unix.CLONE_NEWUSER)
|
err = unix.Pipe2(p[:], unix.O_CLOEXEC)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// similar to exec_linux, avoid side effect by shuffling around
|
// similar to exec_linux, avoid side effect by shuffling around
|
||||||
fd, nextfd := prepareFds(r.Files)
|
fd, nextfd := prepareFds(r.Files)
|
||||||
@ -71,8 +77,19 @@ func (r *Runner) Start() (int, error) {
|
|||||||
if err1 != 0 || pid != 0 {
|
if err1 != 0 || pid != 0 {
|
||||||
// restore all signals
|
// restore all signals
|
||||||
afterFork()
|
afterFork()
|
||||||
syscall.ForkLock.Unlock()
|
|
||||||
|
|
||||||
|
// synchronize with child for uid / gid map
|
||||||
|
if unshareUser {
|
||||||
|
unix.Close(p[0])
|
||||||
|
err = writeIDMaps(int(pid))
|
||||||
|
if err != nil {
|
||||||
|
err2 = err.(syscall.Errno)
|
||||||
|
}
|
||||||
|
syscall.RawSyscall(syscall.SYS_WRITE, uintptr(p[1]), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
||||||
|
unix.Close(p[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
syscall.ForkLock.Unlock()
|
||||||
if err1 != 0 {
|
if err1 != 0 {
|
||||||
return int(pid), syscall.Errno(err1)
|
return int(pid), syscall.Errno(err1)
|
||||||
}
|
}
|
||||||
@ -83,6 +100,32 @@ func (r *Runner) Start() (int, error) {
|
|||||||
afterForkInChild()
|
afterForkInChild()
|
||||||
// Notice: cannot call any GO functions beyond this point
|
// Notice: cannot call any GO functions beyond this point
|
||||||
|
|
||||||
|
// If usernamespace is unshared, uid map and gid map is required to create folders
|
||||||
|
// and files
|
||||||
|
// We need parent to setup uid_map / gid_map for us since we do not have capabilities
|
||||||
|
// in the original namespace
|
||||||
|
// At the same time, socket pair / pipe sychronization is required as well
|
||||||
|
if unshareUser {
|
||||||
|
if _, _, err1 = syscall.RawSyscall(syscall.SYS_CLOSE, uintptr(p[1]), 0, 0); err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
r1, _, err1 = syscall.RawSyscall(syscall.SYS_READ, uintptr(p[0]), uintptr(unsafe.Pointer(&err2)), unsafe.Sizeof(err2))
|
||||||
|
if err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
if r1 != unsafe.Sizeof(err2) {
|
||||||
|
err1 = syscall.EINVAL
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
if err2 != 0 {
|
||||||
|
err1 = err2
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
if _, _, err1 = syscall.RawSyscall(syscall.SYS_CLOSE, uintptr(p[0]), 0, 0); err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get pid of child
|
// Get pid of child
|
||||||
pid, _, err1 = syscall.RawSyscall(syscall.SYS_GETPID, 0, 0, 0)
|
pid, _, err1 = syscall.RawSyscall(syscall.SYS_GETPID, 0, 0, 0)
|
||||||
if err1 != 0 {
|
if err1 != 0 {
|
||||||
@ -141,31 +184,6 @@ func (r *Runner) Start() (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If usernamespace is unshared, uid map and gid map is required to create folders
|
|
||||||
// and files
|
|
||||||
// Notice: This is not working right now since unshare user namespace drops all
|
|
||||||
// capabilities, thus this operation will fail to do this
|
|
||||||
// Thus, we need parent to setup uid_map / gid_map for us
|
|
||||||
// At the same time, socket pair / pipe sychronization is required as well
|
|
||||||
for _, f := range files {
|
|
||||||
r1, _, err1 = syscall.RawSyscall6(syscall.SYS_OPENAT, uintptr(_AT_FDCWD),
|
|
||||||
uintptr(unsafe.Pointer(f.fileName)), uintptr(fileOption), uintptr(filePerm), 0, 0)
|
|
||||||
if err1 == syscall.ENOENT { // Kernel > 3.19 for setgroups
|
|
||||||
continue
|
|
||||||
} else if err1 != 0 {
|
|
||||||
goto childerror
|
|
||||||
}
|
|
||||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_WRITE, r1, uintptr(unsafe.Pointer(&f.fileContent[0])),
|
|
||||||
uintptr(len(f.fileContent)))
|
|
||||||
if err1 != 0 {
|
|
||||||
goto childerror
|
|
||||||
}
|
|
||||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_CLOSE, r1, 0, 0)
|
|
||||||
if err1 != 0 {
|
|
||||||
goto childerror
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mount tmpfs & chdir to new root before performing mounts
|
// mount tmpfs & chdir to new root before performing mounts
|
||||||
if pivotRoot != nil {
|
if pivotRoot != nil {
|
||||||
// mount("tmpfs", root, "tmpfs", 0, "")
|
// mount("tmpfs", root, "tmpfs", 0, "")
|
||||||
@ -301,14 +319,6 @@ func (r *Runner) Start() (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop before execve syscall
|
|
||||||
if r.StopBeforeExec {
|
|
||||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_KILL, pid, uintptr(syscall.SIGSTOP), 0)
|
|
||||||
if err1 != 0 {
|
|
||||||
goto childerror
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// at this point, runner is successfully attached for seccomp trap filter
|
// at this point, runner is successfully attached for seccomp trap filter
|
||||||
// or execve traped without seccomp filter
|
// or execve traped without seccomp filter
|
||||||
// time to exec
|
// time to exec
|
||||||
|
|||||||
@ -41,13 +41,9 @@ type Runner struct {
|
|||||||
// right before the calls to seccomp. It is automatically enabled when seccomp
|
// right before the calls to seccomp. It is automatically enabled when seccomp
|
||||||
// filter and ptrace are provided since kill might not be avaliable after
|
// filter and ptrace are provided since kill might not be avaliable after
|
||||||
// seccomp and execve might be traced by ptrace
|
// seccomp and execve might be traced by ptrace
|
||||||
|
// cannot stop after seccomp since kill might not be allowed by seccomp filter
|
||||||
StopBeforeSeccomp bool
|
StopBeforeSeccomp bool
|
||||||
|
|
||||||
// stop before exec calls kill(getpid(), SIGSTOP) to wait for tracer to continue
|
|
||||||
// right before the final calls to execve. It acts as a synchronize
|
|
||||||
// for parent process
|
|
||||||
StopBeforeExec bool
|
|
||||||
|
|
||||||
// unshare flag to create linux namespace, effective when clone child
|
// unshare flag to create linux namespace, effective when clone child
|
||||||
// since unshare syscall does not join the new pid group
|
// since unshare syscall does not join the new pid group
|
||||||
UnshareFlags uintptr
|
UnshareFlags uintptr
|
||||||
|
|||||||
@ -6,37 +6,36 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// writeUidGidMappings writes User ID and Group ID mappings for user namespaces
|
||||||
fileOption = unix.O_RDWR
|
// for a process and it is called from the parent process.
|
||||||
filePerm = 0755
|
func writeIDMaps(pid int) error {
|
||||||
)
|
pidStr := strconv.Itoa(pid)
|
||||||
|
uidStr := strconv.Itoa(unix.Geteuid())
|
||||||
var (
|
gidStr := strconv.Itoa(unix.Getegid())
|
||||||
uidMap = [...]byte{'/', 'p', 'r', 'o', 'c', '/', 's', 'e', 'l', 'f', '/', 'u', 'i', 'd', '_', 'm', 'a', 'p', 0}
|
if err := writeFile("/proc/"+pidStr+"/uid_map", []byte("0 "+uidStr+" 1")); err != nil {
|
||||||
gidMap = [...]byte{'/', 'p', 'r', 'o', 'c', '/', 's', 'e', 'l', 'f', '/', 'g', 'i', 'd', '_', 'm', 'a', 'p', 0}
|
return err
|
||||||
setGroups = [...]byte{'/', 'p', 'r', 'o', 'c', '/', 's', 'e', 'l', 'f', '/', 's', 'e', 't', 'g', 'r', 'o', 'u', 'p', 's', 0}
|
|
||||||
)
|
|
||||||
|
|
||||||
type fileWriteSyscall struct {
|
|
||||||
fileName *byte
|
|
||||||
fileContent []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareIDMap(userNs bool) []fileWriteSyscall {
|
|
||||||
ret := make([]fileWriteSyscall, 0, 3)
|
|
||||||
if userNs {
|
|
||||||
ret = append(ret, fileWriteSyscall{
|
|
||||||
fileName: &uidMap[0],
|
|
||||||
fileContent: []byte("0 " + strconv.Itoa(unix.Geteuid()) + " 1"),
|
|
||||||
})
|
|
||||||
ret = append(ret, fileWriteSyscall{
|
|
||||||
fileName: &gidMap[0],
|
|
||||||
fileContent: []byte("0 " + strconv.Itoa(unix.Getegid()) + " 1"),
|
|
||||||
})
|
|
||||||
ret = append(ret, fileWriteSyscall{
|
|
||||||
fileName: &setGroups[0],
|
|
||||||
fileContent: []byte("deny"),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return ret
|
if err := writeFile("/proc/"+pidStr+"/setgroups", []byte("deny")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := writeFile("/proc/"+pidStr+"/gid_map", []byte("0 "+gidStr+" 1")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeFile writes file
|
||||||
|
func writeFile(path string, content []byte) error {
|
||||||
|
fd, err := unix.Open(path, unix.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := unix.Write(fd, content); err != nil {
|
||||||
|
unix.Close(fd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := unix.Close(fd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,18 +35,18 @@ func (r *RunUnshared) Start() (rt tracer.TraceResult, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ch := &forkexec.Runner{
|
ch := &forkexec.Runner{
|
||||||
Args: r.Args,
|
Args: r.Args,
|
||||||
Env: r.Env,
|
Env: r.Env,
|
||||||
RLimits: r.RLimits.PrepareRLimit(),
|
RLimits: r.RLimits.PrepareRLimit(),
|
||||||
Files: r.Files,
|
Files: r.Files,
|
||||||
WorkDir: r.WorkDir,
|
WorkDir: r.WorkDir,
|
||||||
Seccomp: bpf,
|
Seccomp: bpf,
|
||||||
NoNewPrivs: true,
|
NoNewPrivs: true,
|
||||||
StopBeforeExec: true,
|
StopBeforeSeccomp: true,
|
||||||
UnshareFlags: UnshareFlags,
|
UnshareFlags: UnshareFlags,
|
||||||
Mounts: r.Mounts,
|
Mounts: r.Mounts,
|
||||||
PivotRoot: r.Root,
|
PivotRoot: r.Root,
|
||||||
DropCaps: true,
|
DropCaps: true,
|
||||||
}
|
}
|
||||||
return r.Trace(ch)
|
return r.Trace(ch)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user