From 5c51bc51e43a0f1d1f06ae490b0d352d1b483766 Mon Sep 17 00:00:00 2001 From: Fabricio Voznika Date: Fri, 8 Jun 2018 09:58:29 -0700 Subject: [PATCH] Drop capabilities not needed by Gofer PiperOrigin-RevId: 199808391 Change-Id: Ib37a4fb6193dc85c1f93bc16769d6aa41854b9d4 --- runsc/boot/BUILD | 2 - runsc/cmd/BUILD | 2 + runsc/cmd/boot.go | 53 ++++++++------------ runsc/{boot => cmd}/capability.go | 81 +++++++++++++++++++------------ runsc/cmd/cmd.go | 26 ++++++++++ runsc/cmd/gofer.go | 30 ++++++++++++ runsc/sandbox/sandbox.go | 12 ++--- 7 files changed, 135 insertions(+), 71 deletions(-) rename runsc/{boot => cmd}/capability.go (76%) diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD index 1746df988..73893d699 100644 --- a/runsc/boot/BUILD +++ b/runsc/boot/BUILD @@ -5,7 +5,6 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "boot", srcs = [ - "capability.go", "config.go", "controller.go", "events.go", @@ -72,7 +71,6 @@ go_library( "//runsc/boot/filter", "//runsc/specutils", "@com_github_opencontainers_runtime-spec//specs-go:go_default_library", - "@com_github_syndtr_gocapability//capability:go_default_library", ], ) diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD index a8c84a6a3..63d8036bd 100644 --- a/runsc/cmd/BUILD +++ b/runsc/cmd/BUILD @@ -6,6 +6,7 @@ go_library( name = "cmd", srcs = [ "boot.go", + "capability.go", "checkpoint.go", "cmd.go", "create.go", @@ -39,6 +40,7 @@ go_library( "//runsc/specutils", "@com_github_google_subcommands//:go_default_library", "@com_github_opencontainers_runtime-spec//specs-go:go_default_library", + "@com_github_syndtr_gocapability//capability:go_default_library", "@org_golang_x_sys//unix:go_default_library", ], ) diff --git a/runsc/cmd/boot.go b/runsc/cmd/boot.go index 3bdc2ced0..34dd8b3c0 100644 --- a/runsc/cmd/boot.go +++ b/runsc/cmd/boot.go @@ -16,7 +16,6 @@ package cmd import ( "os" - "runtime" "runtime/debug" "strings" "syscall" @@ -24,7 +23,6 @@ import ( "context" "flag" "github.com/google/subcommands" - specs "github.com/opencontainers/runtime-spec/specs-go" "gvisor.googlesource.com/gvisor/pkg/log" "gvisor.googlesource.com/gvisor/runsc/boot" "gvisor.googlesource.com/gvisor/runsc/specutils" @@ -106,8 +104,26 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) waitStatus := args[1].(*syscall.WaitStatus) if b.applyCaps { - setCapsAndCallSelf(conf, spec) - Fatalf("setCapsAndCallSelf must never return") + caps := spec.Process.Capabilities + if conf.Platform == boot.PlatformPtrace { + // Ptrace platform requires extra capabilities. + const c = "CAP_SYS_PTRACE" + caps.Bounding = append(caps.Bounding, c) + caps.Effective = append(caps.Effective, c) + caps.Permitted = append(caps.Permitted, c) + } + + // Remove --apply-caps arg to call myself. + var args []string + for _, arg := range os.Args { + if !strings.Contains(arg, "apply-caps") { + args = append(args, arg) + } + } + if err := setCapsAndCallSelf(spec, args, caps); err != nil { + Fatalf("%v", err) + } + panic("setCapsAndCallSelf must never return success") } // Create the loader. @@ -130,32 +146,3 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) *waitStatus = syscall.WaitStatus(ws.Status()) return subcommands.ExitSuccess } - -// setCapsAndCallSelf sets capabilities to the current thread and then execve's -// itself again with the same arguments except '--apply-caps' to restart the -// whole process with the desired capabilities. -func setCapsAndCallSelf(conf *boot.Config, spec *specs.Spec) { - // Keep thread locked while capabilities are changed. - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - if err := boot.ApplyCaps(conf, spec.Process.Capabilities); err != nil { - Fatalf("ApplyCaps, err: %v", err) - } - binPath, err := specutils.BinPath() - if err != nil { - Fatalf("%v", err) - } - - // Remove --apply-caps arg to call myself. - var args []string - for _, arg := range os.Args { - if !strings.Contains(arg, "apply-caps") { - args = append(args, arg) - } - } - - log.Infof("Execve 'boot' again, bye!") - log.Infof("%s %v", binPath, args) - syscall.Exec(binPath, args, []string{}) -} diff --git a/runsc/boot/capability.go b/runsc/cmd/capability.go similarity index 76% rename from runsc/boot/capability.go rename to runsc/cmd/capability.go index efa28fb97..0209feb1b 100644 --- a/runsc/boot/capability.go +++ b/runsc/cmd/capability.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package boot +package cmd import ( "fmt" @@ -20,53 +20,74 @@ import ( specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/syndtr/gocapability/capability" + "gvisor.googlesource.com/gvisor/pkg/log" ) -// ApplyCaps applies the capabilities in the spec to the current thread. +// applyCaps applies the capabilities in the spec to the current thread. // // Note that it must be called with current thread locked. -func ApplyCaps(conf *Config, caps *specs.LinuxCapabilities) error { +func applyCaps(caps *specs.LinuxCapabilities) error { setter, err := capability.NewPid2(os.Getpid()) if err != nil { return err } - - bounding, err := capsFromNames(caps.Bounding) - if err != nil { - return err - } - effective, err := capsFromNames(caps.Effective) - if err != nil { - return err - } - permitted, err := capsFromNames(caps.Permitted) - if err != nil { - return err - } - inheritable, err := capsFromNames(caps.Inheritable) - if err != nil { - return err - } - ambient, err := capsFromNames(caps.Ambient) - if err != nil { + if err := setter.Load(); err != nil { return err } - // Ptrace platform requires extra capabilities. - if conf.Platform == PlatformPtrace { - bounding = append(bounding, capability.CAP_SYS_PTRACE) - effective = append(effective, capability.CAP_SYS_PTRACE) - permitted = append(permitted, capability.CAP_SYS_PTRACE) + bounding, err := trimCaps(caps.Bounding, setter) + if err != nil { + return err } - setter.Set(capability.BOUNDS, bounding...) - setter.Set(capability.PERMITTED, permitted...) - setter.Set(capability.INHERITABLE, inheritable...) + + effective, err := trimCaps(caps.Effective, setter) + if err != nil { + return err + } setter.Set(capability.EFFECTIVE, effective...) + + permitted, err := trimCaps(caps.Permitted, setter) + if err != nil { + return err + } + setter.Set(capability.PERMITTED, permitted...) + + inheritable, err := trimCaps(caps.Inheritable, setter) + if err != nil { + return err + } + setter.Set(capability.INHERITABLE, inheritable...) + + ambient, err := trimCaps(caps.Ambient, setter) + if err != nil { + return err + } setter.Set(capability.AMBIENT, ambient...) + return setter.Apply(capability.CAPS | capability.BOUNDS | capability.AMBS) } +func trimCaps(names []string, setter capability.Capabilities) ([]capability.Cap, error) { + wantedCaps, err := capsFromNames(names) + if err != nil { + return nil, err + } + + // Trim down capabilities that aren't possible to acquire. + var caps []capability.Cap + for _, c := range wantedCaps { + // Capability rules are more complicated than this, but this catches most + // problems with tests running with non-priviledged user. + if setter.Get(capability.PERMITTED, c) { + caps = append(caps, c) + } else { + log.Warningf("Capability %q is not permitted, dropping it.", c) + } + } + return caps, nil +} + func capsFromNames(names []string) ([]capability.Cap, error) { var caps []capability.Cap for _, name := range names { diff --git a/runsc/cmd/cmd.go b/runsc/cmd/cmd.go index 9f7fd6e25..940c8cd14 100644 --- a/runsc/cmd/cmd.go +++ b/runsc/cmd/cmd.go @@ -18,9 +18,13 @@ package cmd import ( "fmt" "os" + "runtime" "strconv" + "syscall" + specs "github.com/opencontainers/runtime-spec/specs-go" "gvisor.googlesource.com/gvisor/pkg/log" + "gvisor.googlesource.com/gvisor/runsc/specutils" ) // Fatalf logs to stderr and exits with a failure status code. @@ -64,3 +68,25 @@ func (i *intFlags) Set(s string) error { *i = append(*i, fd) return nil } + +// setCapsAndCallSelf sets capabilities to the current thread and then execve's +// itself again with the arguments specified in 'args' to restart the process +// with the desired capabilities. +func setCapsAndCallSelf(spec *specs.Spec, args []string, caps *specs.LinuxCapabilities) error { + // Keep thread locked while capabilities are changed. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if err := applyCaps(caps); err != nil { + return fmt.Errorf("applyCaps() failed: %v", err) + } + binPath, err := specutils.BinPath() + if err != nil { + return err + } + + log.Infof("Capabilities applied: %+v", caps) + log.Infof("Execve %q again, bye!", binPath) + syscall.Exec(binPath, args, []string{}) + panic("unreachable") +} diff --git a/runsc/cmd/gofer.go b/runsc/cmd/gofer.go index 844e16dbf..39803bde5 100644 --- a/runsc/cmd/gofer.go +++ b/runsc/cmd/gofer.go @@ -15,11 +15,13 @@ package cmd import ( + "os" "sync" "context" "flag" "github.com/google/subcommands" + specs "github.com/opencontainers/runtime-spec/specs-go" "gvisor.googlesource.com/gvisor/pkg/log" "gvisor.googlesource.com/gvisor/pkg/p9" "gvisor.googlesource.com/gvisor/pkg/unet" @@ -32,6 +34,7 @@ import ( type Gofer struct { bundleDir string ioFDs intFlags + applyCaps bool } // Name implements subcommands.Command. @@ -53,6 +56,7 @@ func (*Gofer) Usage() string { func (g *Gofer) SetFlags(f *flag.FlagSet) { f.StringVar(&g.bundleDir, "bundle", "", "path to the root of the bundle directory, defaults to the current directory") f.Var(&g.ioFDs, "io-fds", "list of FDs to connect 9P servers. They must follow this order: root first, then mounts as defined in the spec") + f.BoolVar(&g.applyCaps, "apply-caps", true, "if true, apply capabilities to restrict what the Gofer process can do") } // Execute implements subcommands.Command. @@ -66,6 +70,32 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) if err != nil { Fatalf("error reading spec: %v", err) } + + if g.applyCaps { + // Minimal set of capabilities needed by the Gofer to operate on files. + caps := []string{ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_DAC_READ_SEARCH", + "CAP_FOWNER", + "CAP_FSETID", + } + lc := &specs.LinuxCapabilities{ + Bounding: caps, + Effective: caps, + Permitted: caps, + } + + // Disable caps when calling myself again. + // Note: minimal argument handling for the default case to keep it simple. + args := os.Args + args = append(args, "--apply-caps=false") + if err := setCapsAndCallSelf(spec, args, lc); err != nil { + Fatalf("Unable to apply caps: %v", err) + } + panic("unreachable") + } + specutils.LogSpec(spec) // Start with root mount, then add any other addition mount as needed. diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index 2a434cfb7..48388aa7f 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -295,23 +295,23 @@ func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bund // process. IPC and UTS namespaces from the host are not used as they // are virtualized inside the sandbox. Be paranoid and run inside an empty // namespace for these. - log.Infof("Sandbox will be started in empty IPC and UTS namespaces") + log.Infof("Sandbox will be started in new IPC and UTS namespaces") nss := []specs.LinuxNamespace{ {Type: specs.IPCNamespace}, {Type: specs.UTSNamespace}, } if conf.Platform == boot.PlatformPtrace { - // TODO: Also set an empty PID namespace so that we limit + // TODO: Also set a new PID namespace so that we limit // access to other host processes. log.Infof("Sandbox will be started in the current PID namespace") } else { - log.Infof("Sandbox will be started in empty PID namespace") + log.Infof("Sandbox will be started in a new PID namespace") nss = append(nss, specs.LinuxNamespace{Type: specs.PIDNamespace}) } if conf.FileAccess == boot.FileAccessProxy { - log.Infof("Sandbox will be started in empty mount namespace") + log.Infof("Sandbox will be started in new mount namespace") nss = append(nss, specs.LinuxNamespace{Type: specs.MountNamespace}) } else { log.Infof("Sandbox will be started in the current mount namespace") @@ -324,7 +324,7 @@ func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bund log.Infof("Sandbox will be started in the container's network namespace: %+v", ns) nss = append(nss, ns) } else { - log.Infof("Sandbox will be started in empty network namespace") + log.Infof("Sandbox will be started in new network namespace") nss = append(nss, specs.LinuxNamespace{Type: specs.NetworkNamespace}) } @@ -347,7 +347,7 @@ func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bund cmd.Args = append(cmd.Args, "--apply-caps=true") } else { - log.Infof("Sandbox will be started in empty user namespace") + log.Infof("Sandbox will be started in new user namespace") nss = append(nss, specs.LinuxNamespace{Type: specs.UserNamespace}) }