diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-03-17 02:55:36 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-03-17 02:55:36 +0900 |
| commit | 24618ab9a1524e8b8986a9bf67667288e642fcf1 (patch) | |
| tree | b3f2a71a2c9bedf937d0fec00092ad9133cb3ec9 /internal/sandbox/container.go | |
| parent | 9ce4706a0766880c072cccd2643d66f614a6a16b (diff) | |
sandbox: move out of internal
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/sandbox/container.go')
| -rw-r--r-- | internal/sandbox/container.go | 232 |
1 files changed, 0 insertions, 232 deletions
diff --git a/internal/sandbox/container.go b/internal/sandbox/container.go deleted file mode 100644 index afbcc4fd..00000000 --- a/internal/sandbox/container.go +++ /dev/null @@ -1,232 +0,0 @@ -package sandbox - -import ( - "context" - "encoding/gob" - "errors" - "fmt" - "io" - "os" - "os/exec" - "path" - "strconv" - "syscall" - "time" - - "git.gensokyo.uk/security/fortify/seccomp" -) - -type HardeningFlags uintptr - -const ( - FSyscallCompat HardeningFlags = 1 << iota - FAllowDevel - FAllowUserns - FAllowTTY - FAllowNet -) - -func (flags HardeningFlags) seccomp(opts seccomp.SyscallOpts) seccomp.SyscallOpts { - if flags&FSyscallCompat == 0 { - opts |= seccomp.FlagExt - } - if flags&FAllowDevel == 0 { - opts |= seccomp.FlagDenyDevel - } - if flags&FAllowUserns == 0 { - opts |= seccomp.FlagDenyNS - } - if flags&FAllowTTY == 0 { - opts |= seccomp.FlagDenyTTY - } - return opts -} - -type ( - // Container represents a container environment being prepared or run. - // None of [Container] methods are safe for concurrent use. - Container struct { - // Name of initial process in the container. - name string - // Cgroup fd, nil to disable. - Cgroup *int - // ExtraFiles passed through to initial process in the container, - // with behaviour identical to its [exec.Cmd] counterpart. - ExtraFiles []*os.File - - InitParams - // Custom [exec.Cmd] initialisation function. - CommandContext func(ctx context.Context) (cmd *exec.Cmd) - - // param encoder for shim and init - setup *gob.Encoder - // cancels cmd - cancel context.CancelFunc - - Stdin io.Reader - Stdout io.Writer - Stderr io.Writer - - Cancel func() error - WaitDelay time.Duration - - cmd *exec.Cmd - ctx context.Context - } - - InitParams struct { - // Working directory in the container. - Dir string - // Initial process environment. - Env []string - // Absolute path of initial process in the container. Overrides name. - Path string - // Initial process argv. - Args []string - - // Mapped Uid in user namespace. - Uid int - // Mapped Gid in user namespace. - Gid int - // Hostname value in UTS namespace. - Hostname string - // Sequential container setup ops. - *Ops - // Extra seccomp options. - Seccomp seccomp.SyscallOpts - - Flags HardeningFlags - } - - Ops []Op - Op interface { - apply(params *InitParams) error - - Is(op Op) bool - fmt.Stringer - } -) - -func (p *Container) Start() error { - if p.cmd != nil { - return errors.New("sandbox: already started") - } - if p.Ops == nil || len(*p.Ops) == 0 { - return errors.New("sandbox: starting an empty container") - } - - ctx, cancel := context.WithCancel(p.ctx) - p.cancel = cancel - - var cloneFlags uintptr = syscall.CLONE_NEWIPC | - syscall.CLONE_NEWUTS | - syscall.CLONE_NEWCGROUP - if p.Flags&FAllowNet == 0 { - cloneFlags |= syscall.CLONE_NEWNET - } - - // map to overflow id to work around ownership checks - if p.Uid < 1 { - p.Uid = OverflowUid() - } - if p.Gid < 1 { - p.Gid = OverflowGid() - } - - if p.CommandContext != nil { - p.cmd = p.CommandContext(ctx) - } else { - p.cmd = exec.CommandContext(ctx, MustExecutable()) - p.cmd.Args = []string{"init"} - } - - p.cmd.Stdin, p.cmd.Stdout, p.cmd.Stderr = p.Stdin, p.Stdout, p.Stderr - p.cmd.Cancel, p.cmd.WaitDelay = p.Cancel, p.WaitDelay - p.cmd.Dir = "/" - p.cmd.SysProcAttr = &syscall.SysProcAttr{ - Setsid: p.Flags&FAllowTTY == 0, - Pdeathsig: syscall.SIGKILL, - - Cloneflags: cloneFlags | - syscall.CLONE_NEWUSER | - syscall.CLONE_NEWPID | - syscall.CLONE_NEWNS, - - // remain privileged for setup - AmbientCaps: []uintptr{CAP_SYS_ADMIN}, - - UseCgroupFD: p.Cgroup != nil, - } - if p.cmd.SysProcAttr.UseCgroupFD { - p.cmd.SysProcAttr.CgroupFD = *p.Cgroup - } - - // place setup pipe before user supplied extra files, this is later restored by init - if fd, e, err := Setup(&p.cmd.ExtraFiles); err != nil { - return wrapErrSuffix(err, - "cannot create shim setup pipe:") - } else { - p.setup = e - p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)} - } - p.cmd.ExtraFiles = append(p.cmd.ExtraFiles, p.ExtraFiles...) - - msg.Verbose("starting container init") - if err := p.cmd.Start(); err != nil { - return msg.WrapErr(err, err.Error()) - } - return nil -} - -func (p *Container) Serve() error { - if p.setup == nil { - panic("invalid serve") - } - - if p.Path != "" && !path.IsAbs(p.Path) { - return msg.WrapErr(syscall.EINVAL, - fmt.Sprintf("invalid executable path %q", p.Path)) - } - - if p.Path == "" { - if p.name == "" { - p.Path = os.Getenv("SHELL") - if !path.IsAbs(p.Path) { - return msg.WrapErr(syscall.EBADE, - "no command specified and $SHELL is invalid") - } - p.name = path.Base(p.Path) - } else if path.IsAbs(p.name) { - p.Path = p.name - } else if v, err := exec.LookPath(p.name); err != nil { - return msg.WrapErr(err, err.Error()) - } else { - p.Path = v - } - } - - setup := p.setup - p.setup = nil - return setup.Encode( - &initParams{ - p.InitParams, - syscall.Getuid(), - syscall.Getgid(), - len(p.ExtraFiles), - msg.IsVerbose(), - }, - ) -} - -func (p *Container) Wait() error { defer p.cancel(); return p.cmd.Wait() } - -func (p *Container) String() string { - return fmt.Sprintf("argv: %q, flags: %#x, seccomp: %#x", - p.Args, p.Flags, int(p.Flags.seccomp(p.Seccomp))) -} - -func New(ctx context.Context, name string, args ...string) *Container { - return &Container{name: name, ctx: ctx, - InitParams: InitParams{Args: append([]string{name}, args...), Dir: "/", Ops: new(Ops)}, - } -} |
