Drop capabilities not needed by Gofer

PiperOrigin-RevId: 199808391
Change-Id: Ib37a4fb6193dc85c1f93bc16769d6aa41854b9d4
This commit is contained in:
Fabricio Voznika 2018-06-08 09:58:29 -07:00 committed by Shentubot
parent 5c37097e34
commit 5c51bc51e4
7 changed files with 135 additions and 71 deletions

View File

@ -5,7 +5,6 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library( go_library(
name = "boot", name = "boot",
srcs = [ srcs = [
"capability.go",
"config.go", "config.go",
"controller.go", "controller.go",
"events.go", "events.go",
@ -72,7 +71,6 @@ go_library(
"//runsc/boot/filter", "//runsc/boot/filter",
"//runsc/specutils", "//runsc/specutils",
"@com_github_opencontainers_runtime-spec//specs-go:go_default_library", "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
"@com_github_syndtr_gocapability//capability:go_default_library",
], ],
) )

View File

@ -6,6 +6,7 @@ go_library(
name = "cmd", name = "cmd",
srcs = [ srcs = [
"boot.go", "boot.go",
"capability.go",
"checkpoint.go", "checkpoint.go",
"cmd.go", "cmd.go",
"create.go", "create.go",
@ -39,6 +40,7 @@ go_library(
"//runsc/specutils", "//runsc/specutils",
"@com_github_google_subcommands//:go_default_library", "@com_github_google_subcommands//:go_default_library",
"@com_github_opencontainers_runtime-spec//specs-go: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", "@org_golang_x_sys//unix:go_default_library",
], ],
) )

View File

@ -16,7 +16,6 @@ package cmd
import ( import (
"os" "os"
"runtime"
"runtime/debug" "runtime/debug"
"strings" "strings"
"syscall" "syscall"
@ -24,7 +23,6 @@ import (
"context" "context"
"flag" "flag"
"github.com/google/subcommands" "github.com/google/subcommands"
specs "github.com/opencontainers/runtime-spec/specs-go"
"gvisor.googlesource.com/gvisor/pkg/log" "gvisor.googlesource.com/gvisor/pkg/log"
"gvisor.googlesource.com/gvisor/runsc/boot" "gvisor.googlesource.com/gvisor/runsc/boot"
"gvisor.googlesource.com/gvisor/runsc/specutils" "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) waitStatus := args[1].(*syscall.WaitStatus)
if b.applyCaps { if b.applyCaps {
setCapsAndCallSelf(conf, spec) caps := spec.Process.Capabilities
Fatalf("setCapsAndCallSelf must never return") 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. // Create the loader.
@ -130,32 +146,3 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
*waitStatus = syscall.WaitStatus(ws.Status()) *waitStatus = syscall.WaitStatus(ws.Status())
return subcommands.ExitSuccess 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{})
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package boot package cmd
import ( import (
"fmt" "fmt"
@ -20,53 +20,74 @@ import (
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/syndtr/gocapability/capability" "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. // 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()) setter, err := capability.NewPid2(os.Getpid())
if err != nil { if err != nil {
return err return err
} }
if err := setter.Load(); err != nil {
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 {
return err return err
} }
// Ptrace platform requires extra capabilities. bounding, err := trimCaps(caps.Bounding, setter)
if conf.Platform == PlatformPtrace { if err != nil {
bounding = append(bounding, capability.CAP_SYS_PTRACE) return err
effective = append(effective, capability.CAP_SYS_PTRACE)
permitted = append(permitted, capability.CAP_SYS_PTRACE)
} }
setter.Set(capability.BOUNDS, bounding...) 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...) 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...) setter.Set(capability.AMBIENT, ambient...)
return setter.Apply(capability.CAPS | capability.BOUNDS | capability.AMBS) 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) { func capsFromNames(names []string) ([]capability.Cap, error) {
var caps []capability.Cap var caps []capability.Cap
for _, name := range names { for _, name := range names {

View File

@ -18,9 +18,13 @@ package cmd
import ( import (
"fmt" "fmt"
"os" "os"
"runtime"
"strconv" "strconv"
"syscall"
specs "github.com/opencontainers/runtime-spec/specs-go"
"gvisor.googlesource.com/gvisor/pkg/log" "gvisor.googlesource.com/gvisor/pkg/log"
"gvisor.googlesource.com/gvisor/runsc/specutils"
) )
// Fatalf logs to stderr and exits with a failure status code. // 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) *i = append(*i, fd)
return nil 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")
}

View File

@ -15,11 +15,13 @@
package cmd package cmd
import ( import (
"os"
"sync" "sync"
"context" "context"
"flag" "flag"
"github.com/google/subcommands" "github.com/google/subcommands"
specs "github.com/opencontainers/runtime-spec/specs-go"
"gvisor.googlesource.com/gvisor/pkg/log" "gvisor.googlesource.com/gvisor/pkg/log"
"gvisor.googlesource.com/gvisor/pkg/p9" "gvisor.googlesource.com/gvisor/pkg/p9"
"gvisor.googlesource.com/gvisor/pkg/unet" "gvisor.googlesource.com/gvisor/pkg/unet"
@ -32,6 +34,7 @@ import (
type Gofer struct { type Gofer struct {
bundleDir string bundleDir string
ioFDs intFlags ioFDs intFlags
applyCaps bool
} }
// Name implements subcommands.Command. // Name implements subcommands.Command.
@ -53,6 +56,7 @@ func (*Gofer) Usage() string {
func (g *Gofer) SetFlags(f *flag.FlagSet) { 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.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.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. // Execute implements subcommands.Command.
@ -66,6 +70,32 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
if err != nil { if err != nil {
Fatalf("error reading spec: %v", err) 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) specutils.LogSpec(spec)
// Start with root mount, then add any other addition mount as needed. // Start with root mount, then add any other addition mount as needed.

View File

@ -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 // 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 // are virtualized inside the sandbox. Be paranoid and run inside an empty
// namespace for these. // 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{ nss := []specs.LinuxNamespace{
{Type: specs.IPCNamespace}, {Type: specs.IPCNamespace},
{Type: specs.UTSNamespace}, {Type: specs.UTSNamespace},
} }
if conf.Platform == boot.PlatformPtrace { 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. // access to other host processes.
log.Infof("Sandbox will be started in the current PID namespace") log.Infof("Sandbox will be started in the current PID namespace")
} else { } 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}) nss = append(nss, specs.LinuxNamespace{Type: specs.PIDNamespace})
} }
if conf.FileAccess == boot.FileAccessProxy { 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}) nss = append(nss, specs.LinuxNamespace{Type: specs.MountNamespace})
} else { } else {
log.Infof("Sandbox will be started in the current mount namespace") 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) log.Infof("Sandbox will be started in the container's network namespace: %+v", ns)
nss = append(nss, ns) nss = append(nss, ns)
} else { } 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}) 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") cmd.Args = append(cmd.Args, "--apply-caps=true")
} else { } 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}) nss = append(nss, specs.LinuxNamespace{Type: specs.UserNamespace})
} }