aboutsummaryrefslogtreecommitdiffhomepage
path: root/container/container.go
diff options
context:
space:
mode:
Diffstat (limited to 'container/container.go')
-rw-r--r--container/container.go104
1 files changed, 77 insertions, 27 deletions
diff --git a/container/container.go b/container/container.go
index 2a6363a5..a6457714 100644
--- a/container/container.go
+++ b/container/container.go
@@ -1,4 +1,5 @@
-// Package container implements unprivileged Linux containers with built-in support for syscall filtering.
+// Package container implements unprivileged Linux containers with built-in
+// support for syscall filtering.
package container
import (
@@ -42,22 +43,25 @@ type (
SchedPolicy int
// 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 passed through to initial process in the container, with
+ // behaviour identical to its [exec.Cmd] counterpart.
ExtraFiles []*os.File
- // param pipe for shim and init
+ // Write end of a pipe connected to the init to deliver [Params].
setup *os.File
- // cancels cmd
+ // Cancels the context passed to the underlying cmd.
cancel context.CancelFunc
- // closed after Wait returns
+ // Closed after Wait returns. Keeps the spawning thread alive.
wait chan struct{}
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
- Cancel func(cmd *exec.Cmd) error
+ // Custom cancellation behaviour for the underlying [exec.Cmd]. Must
+ // deliver [CancelSignal] before returning.
+ Cancel func(cmd *exec.Cmd) error
+ // Copied to the underlying [exec.Cmd].
WaitDelay time.Duration
cmd *exec.Cmd
@@ -286,7 +290,11 @@ func (p *Container) Start() error {
// place setup pipe before user supplied extra files, this is later restored by init
if fd, f, err := Setup(&p.cmd.ExtraFiles); err != nil {
- return &StartError{true, "set up params stream", err, false, false}
+ return &StartError{
+ Fatal: true,
+ Step: "set up params stream",
+ Err: err,
+ }
} else {
p.setup = f
p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)}
@@ -298,10 +306,16 @@ func (p *Container) Start() error {
runtime.LockOSThread()
p.wait = make(chan struct{})
- done <- func() error { // setup depending on per-thread state must happen here
- // PR_SET_NO_NEW_PRIVS: depends on per-thread state but acts on all processes created from that thread
+ // setup depending on per-thread state must happen here
+ done <- func() error {
+ // PR_SET_NO_NEW_PRIVS: thread-directed but acts on all processes
+ // created from the calling thread
if err := SetNoNewPrivs(); err != nil {
- return &StartError{true, "prctl(PR_SET_NO_NEW_PRIVS)", err, false, false}
+ return &StartError{
+ Fatal: true,
+ Step: "prctl(PR_SET_NO_NEW_PRIVS)",
+ Err: err,
+ }
}
// landlock: depends on per-thread state but acts on a process group
@@ -313,28 +327,40 @@ func (p *Container) Start() error {
if abi, err := LandlockGetABI(); err != nil {
if p.HostAbstract {
- // landlock can be skipped here as it restricts access to resources
- // already covered by namespaces (pid)
+ // landlock can be skipped here as it restricts access
+ // to resources already covered by namespaces (pid)
goto landlockOut
}
- return &StartError{false, "get landlock ABI", err, false, false}
+ return &StartError{Step: "get landlock ABI", Err: err}
} else if abi < 6 {
if p.HostAbstract {
// see above comment
goto landlockOut
}
- return &StartError{false, "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", ENOSYS, true, false}
+ return &StartError{
+ Step: "kernel too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET",
+ Err: ENOSYS,
+ Origin: true,
+ }
} else {
p.msg.Verbosef("landlock abi version %d", abi)
}
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
- return &StartError{true, "create landlock ruleset", err, false, false}
+ return &StartError{
+ Fatal: true,
+ Step: "create landlock ruleset",
+ Err: err,
+ }
} else {
p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
if err = LandlockRestrictSelf(rulesetFd, 0); err != nil {
_ = Close(rulesetFd)
- return &StartError{true, "enforce landlock ruleset", err, false, false}
+ return &StartError{
+ Fatal: true,
+ Step: "enforce landlock ruleset",
+ Err: err,
+ }
}
if err = Close(rulesetFd); err != nil {
p.msg.Verbosef("cannot close landlock ruleset: %v", err)
@@ -346,7 +372,7 @@ func (p *Container) Start() error {
}
// sched_setscheduler: thread-directed but acts on all processes
- // created from that thread
+ // created from the calling thread
if p.SchedPolicy > 0 {
p.msg.Verbosef("setting scheduling policy %d", p.SchedPolicy)
if err := schedSetscheduler(
@@ -364,7 +390,11 @@ func (p *Container) Start() error {
p.msg.Verbose("starting container init")
if err := p.cmd.Start(); err != nil {
- return &StartError{false, "start container init", err, false, true}
+ return &StartError{
+ Step: "start container init",
+ Err: err,
+ Passthrough: true,
+ }
}
return nil
}()
@@ -376,6 +406,7 @@ func (p *Container) Start() error {
}
// Serve serves [Container.Params] to the container init.
+//
// Serve must only be called once.
func (p *Container) Serve() error {
if p.setup == nil {
@@ -385,12 +416,21 @@ func (p *Container) Serve() error {
setup := p.setup
p.setup = nil
if err := setup.SetDeadline(time.Now().Add(initSetupTimeout)); err != nil {
- return &StartError{true, "set init pipe deadline", err, false, true}
+ return &StartError{
+ Fatal: true,
+ Step: "set init pipe deadline",
+ Err: err,
+ Passthrough: true,
+ }
}
if p.Path == nil {
p.cancel()
- return &StartError{false, "invalid executable pathname", EINVAL, true, false}
+ return &StartError{
+ Step: "invalid executable pathname",
+ Err: EINVAL,
+ Origin: true,
+ }
}
// do not transmit nil
@@ -415,7 +455,8 @@ func (p *Container) Serve() error {
return err
}
-// Wait waits for the container init process to exit and releases any resources associated with the [Container].
+// Wait blocks until the container init process to exit and releases any
+// resources associated with the [Container].
func (p *Container) Wait() error {
if p.cmd == nil || p.cmd.Process == nil {
return EINVAL
@@ -460,11 +501,13 @@ func (p *Container) StderrPipe() (r io.ReadCloser, err error) {
}
func (p *Container) String() string {
- return fmt.Sprintf("argv: %q, filter: %v, rules: %d, flags: %#x, presets: %#x",
- p.Args, !p.SeccompDisable, len(p.SeccompRules), int(p.SeccompFlags), int(p.SeccompPresets))
+ return fmt.Sprintf(
+ "argv: %q, filter: %v, rules: %d, flags: %#x, presets: %#x",
+ p.Args, !p.SeccompDisable, len(p.SeccompRules), int(p.SeccompFlags), int(p.SeccompPresets),
+ )
}
-// ProcessState returns the address to os.ProcessState held by the underlying [exec.Cmd].
+// ProcessState returns the address of os.ProcessState held by the underlying [exec.Cmd].
func (p *Container) ProcessState() *os.ProcessState {
if p.cmd == nil {
return nil
@@ -472,7 +515,8 @@ func (p *Container) ProcessState() *os.ProcessState {
return p.cmd.ProcessState
}
-// New returns the address to a new instance of [Container] that requires further initialisation before use.
+// New returns the address to a new instance of [Container]. This value requires
+// further initialisation before use.
func New(ctx context.Context, msg message.Msg) *Container {
if msg == nil {
msg = message.New(nil)
@@ -486,7 +530,13 @@ func New(ctx context.Context, msg message.Msg) *Container {
}
// NewCommand calls [New] and initialises the [Params.Path] and [Params.Args] fields.
-func NewCommand(ctx context.Context, msg message.Msg, pathname *check.Absolute, name string, args ...string) *Container {
+func NewCommand(
+ ctx context.Context,
+ msg message.Msg,
+ pathname *check.Absolute,
+ name string,
+ args ...string,
+) *Container {
z := New(ctx, msg)
z.Path = pathname
z.Args = append([]string{name}, args...)