fixed unshared user namespace

This commit is contained in:
criyle 2019-07-14 16:45:50 -07:00
parent 4a120f9a3d
commit d260bf4657
5 changed files with 95 additions and 88 deletions

View File

@ -35,9 +35,11 @@ Packages:
- seccomp: provides utility function that wrappers libseccomp
- 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
- runprogram: wrapper to call forkexec and trecer
- rununshared: wrapper to call forkexec and unshared namespaces
- runconfig: defines arch & language specified trace condition
Executable:

View File

@ -24,8 +24,10 @@ func afterForkInChild()
//go:norace
func (r *Runner) Start() (int, error) {
var (
err1 syscall.Errno
err1, err2 syscall.Errno
r1 uintptr
p [2]int
unshareUser = r.UnshareFlags&unix.CLONE_NEWUSER == unix.CLONE_NEWUSER
)
argv0, argv, envv, err := prepareExec(r.Args, r.Env)
@ -51,8 +53,12 @@ func (r *Runner) Start() (int, error) {
return 0, nil
}
// prepare set uid / gid map files
files := prepareIDMap(r.UnshareFlags&unix.CLONE_NEWUSER == unix.CLONE_NEWUSER)
if unshareUser {
err = unix.Pipe2(p[:], unix.O_CLOEXEC)
if err != nil {
return 0, err
}
}
// similar to exec_linux, avoid side effect by shuffling around
fd, nextfd := prepareFds(r.Files)
@ -71,8 +77,19 @@ func (r *Runner) Start() (int, error) {
if err1 != 0 || pid != 0 {
// restore all signals
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 {
return int(pid), syscall.Errno(err1)
}
@ -83,6 +100,32 @@ func (r *Runner) Start() (int, error) {
afterForkInChild()
// 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
pid, _, err1 = syscall.RawSyscall(syscall.SYS_GETPID, 0, 0, 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
if pivotRoot != nil {
// 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
// or execve traped without seccomp filter
// time to exec

View File

@ -41,13 +41,9 @@ type Runner struct {
// right before the calls to seccomp. It is automatically enabled when seccomp
// filter and ptrace are provided since kill might not be avaliable after
// seccomp and execve might be traced by ptrace
// cannot stop after seccomp since kill might not be allowed by seccomp filter
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
// since unshare syscall does not join the new pid group
UnshareFlags uintptr

View File

@ -6,37 +6,36 @@ import (
"golang.org/x/sys/unix"
)
const (
fileOption = unix.O_RDWR
filePerm = 0755
)
var (
uidMap = [...]byte{'/', 'p', 'r', 'o', 'c', '/', 's', 'e', 'l', 'f', '/', 'u', 'i', 'd', '_', 'm', 'a', 'p', 0}
gidMap = [...]byte{'/', 'p', 'r', 'o', 'c', '/', 's', 'e', 'l', 'f', '/', 'g', 'i', 'd', '_', 'm', 'a', 'p', 0}
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
// writeUidGidMappings writes User ID and Group ID mappings for user namespaces
// for a process and it is called from the parent process.
func writeIDMaps(pid int) error {
pidStr := strconv.Itoa(pid)
uidStr := strconv.Itoa(unix.Geteuid())
gidStr := strconv.Itoa(unix.Getegid())
if err := writeFile("/proc/"+pidStr+"/uid_map", []byte("0 "+uidStr+" 1")); err != nil {
return err
}
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
}
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"),
})
// writeFile writes file
func writeFile(path string, content []byte) error {
fd, err := unix.Open(path, unix.O_RDWR, 0)
if err != nil {
return err
}
return ret
if _, err := unix.Write(fd, content); err != nil {
unix.Close(fd)
return err
}
if err := unix.Close(fd); err != nil {
return err
}
return nil
}

View File

@ -42,7 +42,7 @@ func (r *RunUnshared) Start() (rt tracer.TraceResult, err error) {
WorkDir: r.WorkDir,
Seccomp: bpf,
NoNewPrivs: true,
StopBeforeExec: true,
StopBeforeSeccomp: true,
UnshareFlags: UnshareFlags,
Mounts: r.Mounts,
PivotRoot: r.Root,