add mount builder & fix rlimits unit

This commit is contained in:
criyle 2019-09-18 01:42:10 -07:00
parent 2b11c31b60
commit c8d682f4b6
17 changed files with 194 additions and 202 deletions

View File

@ -12,6 +12,7 @@ import (
"github.com/criyle/go-sandbox/daemon"
"github.com/criyle/go-sandbox/pkg/cgroup"
"github.com/criyle/go-sandbox/pkg/memfd"
"github.com/criyle/go-sandbox/pkg/mount"
"github.com/criyle/go-sandbox/pkg/rlimit"
"github.com/criyle/go-sandbox/pkg/seccomp"
"github.com/criyle/go-sandbox/pkg/seccomp/libseccomp"
@ -205,8 +206,8 @@ func start() (*types.Result, error) {
rlims := rlimit.RLimits{
CPU: timeLimit,
CPUHard: realTimeLimit,
FileSize: outputLimit,
Stack: stackLimit,
FileSize: outputLimit << 20,
Stack: stackLimit << 20,
}
actionDefault := seccomp.ActionKill
@ -255,26 +256,24 @@ func start() (*types.Result, error) {
return nil, fmt.Errorf("cannot make temp root for new namespace")
}
defer os.RemoveAll(root)
mounts, err := mount.NewBuilder().WithMounts(unshare.DefaultMounts).WithBind(root, "w", true).Build(true)
if err != nil {
return nil, fmt.Errorf("cannot make rootfs mounts")
}
runner = &unshare.Runner{
Args: args,
Env: []string{pathEnv},
ExecFile: execFile,
WorkDir: "/w",
Files: fds,
RLimits: rlims,
RLimits: rlims.PrepareRLimit(),
Limit: types.Limit{
TimeLimit: timeLimit * 1e3,
MemoryLimit: memoryLimit << 10,
},
Seccomp: filter,
Root: root,
Mounts: unshare.GetDefaultMounts(root, []unshare.AddBind{
{
Source: workPath,
Target: "w",
},
}),
Seccomp: filter,
Root: root,
Mounts: mounts,
ShowDetails: showDetails,
SyncFunc: syncFunc,
HostName: "run_program",
@ -295,7 +294,7 @@ func start() (*types.Result, error) {
Env: []string{pathEnv},
ExecFile: execFile,
WorkDir: workPath,
RLimits: rlims,
RLimits: rlims.PrepareRLimit(),
Limit: types.Limit{
TimeLimit: timeLimit * 1e3,
MemoryLimit: memoryLimit << 10,

View File

@ -1,15 +1,12 @@
package daemon
import (
"os"
"github.com/criyle/go-sandbox/pkg/mount"
"golang.org/x/sys/unix"
)
const (
bind = unix.MS_BIND | unix.MS_NOSUID | unix.MS_PRIVATE
roBind = bind | unix.MS_RDONLY
roBind = unix.MS_BIND | unix.MS_NOSUID | unix.MS_PRIVATE | unix.MS_RDONLY
mFlag = unix.MS_NOSUID | unix.MS_NOATIME | unix.MS_NODEV
)
@ -19,7 +16,7 @@ var (
DefaultPath = "PATH=/usr/local/bin:/usr/bin:/bin"
// rootfs created by bind mounting
DefaultMounts = []*mount.Mount{
DefaultMounts = []mount.Mount{
{
Source: "/usr",
Target: "usr",
@ -56,18 +53,3 @@ var (
},
}
)
func init() {
// check if bind mount source exists, e.g. /lib64 does not exists on arm
mounts := make([]*mount.Mount, 0, len(DefaultMounts))
for _, m := range DefaultMounts {
if m.Source != "tmpfs" {
if _, err := os.Stat(m.Source); !os.IsNotExist(err) {
mounts = append(mounts, m)
}
} else {
mounts = append(mounts, m)
}
}
DefaultMounts = mounts
}

View File

@ -6,6 +6,7 @@ import (
"github.com/criyle/go-sandbox/pkg/forkexec"
"github.com/criyle/go-sandbox/pkg/memfd"
"github.com/criyle/go-sandbox/pkg/mount"
"github.com/criyle/go-sandbox/pkg/unixsocket"
"golang.org/x/sys/unix"
)
@ -43,23 +44,28 @@ func New(root string) (*Master, error) {
if err != nil {
return nil, fmt.Errorf("daemon: failed to create socket(%v)", err)
}
defer outs.Conn.Close()
outf, err := outs.Conn.File()
if err != nil {
ins.Conn.Close()
outs.Conn.Close()
return nil, fmt.Errorf("daemon: failed to dup file outs(%v)", err)
}
defer outf.Close()
if err = ins.SetPassCred(1); err != nil {
ins.Conn.Close()
outs.Conn.Close()
return nil, fmt.Errorf("daemon: failed to set pass_cred ins(%v)", err)
}
if err = outs.SetPassCred(1); err != nil {
ins.Conn.Close()
outs.Conn.Close()
return nil, fmt.Errorf("daemon: failed to set pass_cred outs(%v)", err)
}
mounts, err := mount.NewBuilder().WithMounts(DefaultMounts).Build(true)
if err != nil {
ins.Conn.Close()
return nil, fmt.Errorf("daemon: failed to build rootfs mount %v", err)
}
r := &forkexec.Runner{
Args: []string{os.Args[0], initArg},
@ -68,7 +74,7 @@ func New(root string) (*Master, error) {
Files: []uintptr{fnull.Fd(), fnull.Fd(), fnull.Fd(), uintptr(outf.Fd())},
WorkDir: "/w",
UnshareFlags: forkexec.UnshareFlags,
Mounts: DefaultMounts,
Mounts: mounts,
HostName: "daemon",
DomainName: "daemon",
PivotRoot: root,
@ -76,11 +82,8 @@ func New(root string) (*Master, error) {
pid, err := r.Start()
if err != nil {
ins.Conn.Close()
outs.Conn.Close()
return nil, fmt.Errorf("daemon: failed to execve(%v)", err)
}
outs.Conn.Close()
return &Master{pid, ins}, nil
}

2
go.mod
View File

@ -4,5 +4,5 @@ go 1.12
require (
github.com/seccomp/libseccomp-golang v0.9.1
golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3
)

4
go.sum
View File

@ -1,4 +1,4 @@
github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7 h1:wYqz/tQaWUgGKyx+B/rssSE6wkIKdY5Ee6ryOmzarIg=
golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -58,12 +58,6 @@ func (r *Runner) Start() (int, error) {
return 0, err
}
// prepare mount param
mountParams, dirsToMake, err := prepareMounts(r.Mounts)
if err != nil {
return 0, nil
}
// socketpair p used to notify child the uid / gid mapping have been setup
// socketpair p is also used to sync with parent before final execve
// p[0] is used by parent and p[1] is used by child
@ -277,9 +271,9 @@ func (r *Runner) Start() (int, error) {
}
// performing mounts
for i, m := range mountParams {
for _, m := range r.Mounts {
// mkdirs(target)
for _, p := range dirsToMake[i] {
for _, p := range m.Prefixes {
_, _, err1 = syscall.RawSyscall(syscall.SYS_MKDIRAT, uintptr(_AT_FDCWD), uintptr(unsafe.Pointer(p)), 0755)
if err1 != 0 && err1 != syscall.EEXIST {
goto childerror

View File

@ -2,8 +2,6 @@ package forkexec
import (
"syscall"
"github.com/criyle/go-sandbox/pkg/mount"
)
// prepareExec prepares execve parameters
@ -63,47 +61,3 @@ func preparePivotRoot(r string) (*byte, *byte, error) {
}
return root, oldRoot, nil
}
// prepareMounts prepare mkdir and mount syscall params
func prepareMounts(ms []*mount.Mount) ([]*mount.SyscallParams, [][]*byte, error) {
params, err := mount.ToSyscalls(ms)
if err != nil {
return nil, nil, err
}
// evaluate paths that need to be created
pathsToCreate := make([][]*byte, 0, len(ms))
for _, m := range ms {
prefix := pathPrefix(m.Target)
paths, err := arrayPtrFromStrings(prefix)
if err != nil {
return nil, nil, err
}
pathsToCreate = append(pathsToCreate, paths)
}
return params, pathsToCreate, nil
}
// pathPrefix get all components from path
func pathPrefix(path string) []string {
ret := make([]string, 0)
for i := 1; i < len(path); i++ {
if path[i] == '/' {
ret = append(ret, path[:i])
}
}
ret = append(ret, path)
return ret
}
// arrayPtrFromStrings convers srings to c style strings
func arrayPtrFromStrings(strs []string) ([]*byte, error) {
bytes := make([]*byte, 0, len(strs))
for _, s := range strs {
b, err := syscall.BytePtrFromString(s)
if err != nil {
return nil, err
}
bytes = append(bytes, b)
}
return bytes, nil
}

View File

@ -56,17 +56,14 @@ type Runner struct {
// need CAP_SYS_ADMIN inside the namespace (e.g. unshare user namespace)
// if pivot root is provided, relative target is better for chdir-mount meta
// and pivot root will mount as tmpfs before any mount
Mounts []*mount.Mount
// HostName and DomainName to be set after unshare UTS & user (CAP_SYS_ADMIN)
HostName, DomainName string
Mounts []mount.SyscallParams
// pivot_root defines a readonly new root after unshare mount namespace
// it should be a directory in absolute path
// Before mounts, tt will call:
// it should be a directory in absolute path and should used with mounts
// Call path:
// mount("tmpfs", root, "tmpfs", 0, nil)
// chdir(root)
// After mounts, it will call:
// [do mounts]
// mkdir("old_root")
// pivot_root(root, "old_root")
// umount("old_root", MNT_DETACH)
@ -74,6 +71,9 @@ type Runner struct {
// mount("tmpfs", "/", "tmpfs", MS_BIND | MS_REMOUNT | MS_RDONLY | MS_NOATIME | MS_NOSUID, nil)
PivotRoot string
// HostName and DomainName to be set after unshare UTS & user (CAP_SYS_ADMIN)
HostName, DomainName string
// drop_caps calls cap_set(self, 0) to drop all capabilities
// from effective, permitted, inheritable capability sets before execve
// it should avoid calls to set ambient capabilities

90
pkg/mount/builder.go Normal file
View File

@ -0,0 +1,90 @@
package mount
import (
"os"
"golang.org/x/sys/unix"
)
const (
bind = unix.MS_BIND | unix.MS_NOSUID | unix.MS_NOATIME | unix.MS_PRIVATE
roBind = bind | unix.MS_RDONLY
mFlag = unix.MS_NOSUID | unix.MS_NOATIME | unix.MS_NODEV | unix.MS_PRIVATE
)
// Builder builds fork_exec friendly mount syscall format
type Builder struct {
Mounts []Mount
}
// NewBuilder creates new mount builder instance
func NewBuilder() *Builder {
return &Builder{}
}
// Build creates sequence of syscalls for fork_exec
// skipNotExists skips bind mounts that source not exitst
func (b *Builder) Build(skipNotExists bool) ([]SyscallParams, error) {
ret := make([]SyscallParams, 0, len(b.Mounts))
for _, m := range b.Mounts {
if err := isBindMountNotExists(m); err != nil {
if skipNotExists {
continue
}
return nil, err
}
sp, err := m.ToSyscall()
if err != nil {
return nil, err
}
ret = append(ret, *sp)
}
return ret, nil
}
func isBindMountNotExists(m Mount) error {
if m.Flags&unix.MS_BIND == unix.MS_BIND {
if _, err := os.Stat(m.Source); os.IsNotExist(err) {
return err
}
}
return nil
}
// WithMounts add mounts to builder
func (b *Builder) WithMounts(m []Mount) *Builder {
b.Mounts = append(b.Mounts, m...)
return b
}
// WithMount add single mount to builder
func (b *Builder) WithMount(m Mount) *Builder {
b.Mounts = append(b.Mounts, m)
return b
}
// WithBind adds a bind mount to builder
func (b *Builder) WithBind(source, target string, readonly bool) *Builder {
var flags uintptr = bind
if readonly {
flags = roBind
}
b.Mounts = append(b.Mounts, Mount{
Source: source,
Target: target,
Flags: flags,
})
return b
}
// WithTmpfs add a tmpfs mount to builder
func (b *Builder) WithTmpfs(target, data string) *Builder {
b.Mounts = append(b.Mounts, Mount{
Source: "tmpfs",
Target: target,
FsType: "tmpfs",
Flags: mFlag,
Data: data,
})
return b
}

View File

@ -1,7 +1,6 @@
package mount
import (
"os"
"syscall"
)
@ -15,6 +14,7 @@ type Mount struct {
type SyscallParams struct {
Source, Target, FsType, Data *byte
Flags uintptr
Prefixes []*byte
}
// ToSyscall convert Mount to SyscallPrams
@ -38,53 +38,42 @@ func (m *Mount) ToSyscall() (*SyscallParams, error) {
return nil, err
}
}
prefix := pathPrefix(m.Target)
paths, err := arrayPtrFromStrings(prefix)
if err != nil {
return nil, err
}
return &SyscallParams{
Source: source,
Target: target,
FsType: fsType,
Flags: m.Flags,
Data: data,
Source: source,
Target: target,
FsType: fsType,
Flags: m.Flags,
Data: data,
Prefixes: paths,
}, nil
}
// Mount calls mount syscall
func (m *Mount) Mount() error {
if err := os.MkdirAll(m.Target, 0755); err != nil {
return err
}
if err := syscall.Mount(m.Source, m.Target, m.FsType, m.Flags, m.Data); err != nil {
return err
}
// Read-only bind mount need to be remounted
const bindRo = syscall.MS_BIND | syscall.MS_RDONLY
if m.Flags&bindRo == bindRo {
if err := syscall.Mount("", m.Target, m.FsType, m.Flags|syscall.MS_REMOUNT, m.Data); err != nil {
return err
// pathPrefix get all components from path
func pathPrefix(path string) []string {
ret := make([]string, 0)
for i := 1; i < len(path); i++ {
if path[i] == '/' {
ret = append(ret, path[:i])
}
}
return nil
ret = append(ret, path)
return ret
}
// ToSyscalls converts arrays of Mounts into SyscallParams
func ToSyscalls(ms []*Mount) ([]*SyscallParams, error) {
ret := make([]*SyscallParams, 0, len(ms))
for _, m := range ms {
sp, err := m.ToSyscall()
// arrayPtrFromStrings converts srings to c style strings
func arrayPtrFromStrings(strs []string) ([]*byte, error) {
bytes := make([]*byte, 0, len(strs))
for _, s := range strs {
b, err := syscall.BytePtrFromString(s)
if err != nil {
return nil, err
}
ret = append(ret, sp)
bytes = append(bytes, b)
}
return ret, nil
}
// Mounts calls multiple mount syscalls
func Mounts(ms []*Mount) error {
for _, m := range ms {
err := m.Mount()
if err != nil {
return err
}
}
return nil
return bytes, nil
}

24
pkg/mount/mount_linux.go Normal file
View File

@ -0,0 +1,24 @@
package mount
import (
"os"
"syscall"
)
// Mount calls mount syscall
func (m *Mount) Mount() error {
if err := os.MkdirAll(m.Target, 0755); err != nil {
return err
}
if err := syscall.Mount(m.Source, m.Target, m.FsType, m.Flags, m.Data); err != nil {
return err
}
// Read-only bind mount need to be remounted
const bindRo = syscall.MS_BIND | syscall.MS_RDONLY
if m.Flags&bindRo == bindRo {
if err := syscall.Mount("", m.Target, m.FsType, m.Flags|syscall.MS_REMOUNT, m.Data); err != nil {
return err
}
}
return nil
}

View File

@ -8,10 +8,10 @@ import (
type RLimits struct {
CPU uint64 // in s
CPUHard uint64 // in s
Data uint64 // in kb
FileSize uint64 // in kb
Stack uint64 // in kb
AddressSpace uint64 // in kb
Data uint64 // in bytes
FileSize uint64 // in bytes
Stack uint64 // in bytes
AddressSpace uint64 // in bytes
}
// RLimit is the resource limits defined by Linux setrlimit
@ -23,7 +23,7 @@ type RLimit struct {
}
func getRlimit(cur, max uint64) syscall.Rlimit {
return syscall.Rlimit{Cur: uint64(cur), Max: uint64(max)}
return syscall.Rlimit{Cur: cur, Max: max}
}
// PrepareRLimit creates rlimit structures for tracee
@ -44,25 +44,25 @@ func (r *RLimits) PrepareRLimit() []RLimit {
if r.Data > 0 {
ret = append(ret, RLimit{
Res: syscall.RLIMIT_DATA,
Rlim: getRlimit(r.Data<<10, r.Data<<10),
Rlim: getRlimit(r.Data, r.Data),
})
}
if r.FileSize > 0 {
ret = append(ret, RLimit{
Res: syscall.RLIMIT_FSIZE,
Rlim: getRlimit(r.FileSize<<10, r.FileSize<<10),
Rlim: getRlimit(r.FileSize, r.FileSize),
})
}
if r.Stack > 0 {
ret = append(ret, RLimit{
Res: syscall.RLIMIT_STACK,
Rlim: getRlimit(r.Stack<<10, r.Stack<<10),
Rlim: getRlimit(r.Stack, r.Stack),
})
}
if r.AddressSpace > 0 {
ret = append(ret, RLimit{
Res: syscall.RLIMIT_AS,
Rlim: getRlimit(r.AddressSpace<<10, r.AddressSpace<<10),
Rlim: getRlimit(r.AddressSpace, r.AddressSpace),
})
}
return ret

View File

@ -12,7 +12,7 @@ func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) {
Args: r.Args,
Env: r.Env,
ExecFile: r.ExecFile,
RLimits: r.RLimits.PrepareRLimit(),
RLimits: r.RLimits,
Files: r.Files,
WorkDir: r.WorkDir,
Seccomp: r.Seccomp.SockFprog(),

View File

@ -24,7 +24,7 @@ type Runner struct {
Files []uintptr
// Resource limit set by set rlimit
RLimits rlimit.RLimits
RLimits []rlimit.RLimit
// Res limit enforced by tracer
Limit types.Limit

View File

@ -1,36 +1,16 @@
package unshare
import (
"os"
"golang.org/x/sys/unix"
"github.com/criyle/go-sandbox/pkg/mount"
)
// AddBind is the additional bind mounts besides the default one
type AddBind struct {
Source, Target string
ReadOnly bool
}
const roBind = unix.MS_BIND | unix.MS_NOSUID | unix.MS_PRIVATE | unix.MS_RDONLY
const (
bind2 = unix.MS_BIND | unix.MS_NOSUID | unix.MS_NOATIME | unix.MS_NODEV | unix.MS_NODIRATIME
bind = unix.MS_BIND | unix.MS_NOSUID | unix.MS_PRIVATE
roBind = bind | unix.MS_RDONLY
noExecRoBind = roBind | unix.MS_NOEXEC
remountRo = unix.MS_REMOUNT | unix.MS_RDONLY
)
// default parameters. I was tend to reuse the configs but it is hard since there are some
// cross device symblics
var (
DefaultMounts = []*mount.Mount{
{
Source: "/usr/lib/locale",
Target: "usr/lib/locale",
Flags: roBind,
},
// DefaultMounts is a example of minimal rootfs
DefaultMounts = []mount.Mount{
{
Source: "/usr",
Target: "usr",
@ -53,26 +33,3 @@ var (
},
}
)
// GetDefaultMounts returns default mount parameters for given root
func GetDefaultMounts(root string, add []AddBind) []*mount.Mount {
mounts := make([]*mount.Mount, 0, len(DefaultMounts)+len(add))
// check if bind mount source exists, e.g. /lib64 does not exists on arm
for _, m := range DefaultMounts {
if _, err := os.Stat(m.Source); !os.IsNotExist(err) {
mounts = append(mounts, m)
}
}
for _, m := range add {
flags := bind
if m.ReadOnly {
flags = roBind
}
mounts = append(mounts, &mount.Mount{
Source: m.Source,
Target: m.Target,
Flags: uintptr(flags),
})
}
return mounts
}

View File

@ -23,7 +23,7 @@ func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) {
Args: r.Args,
Env: r.Env,
ExecFile: r.ExecFile,
RLimits: r.RLimits.PrepareRLimit(),
RLimits: r.RLimits,
Files: r.Files,
WorkDir: r.WorkDir,
Seccomp: r.Seccomp.SockFprog(),

View File

@ -23,7 +23,7 @@ type Runner struct {
Files []uintptr
// Resource limit set by set rlimit
RLimits rlimit.RLimits
RLimits []rlimit.RLimit
// Resource limit enforced by tracer
Limit types.Limit
@ -35,7 +35,7 @@ type Runner struct {
Root string
// Mount syscalls
Mounts []*mount.Mount
Mounts []mount.SyscallParams
// hostname & domainname
HostName, DomainName string