summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorFabricio Voznika <fvoznika@google.com>2018-09-19 17:14:20 -0700
committerShentubot <shentubot@google.com>2018-09-19 17:15:14 -0700
commite3952733011df912ecaa48974832a054a45c345a (patch)
treefccc5d39886cfa3d881d86504df06e5b0aed4118
parent2ad3228cd0f226804cfc7ae3ae7fff561caa2eda (diff)
Fix sandbox and gofer capabilities
Capabilities.Set() adds capabilities, but doesn't remove existing ones that might have been loaded. Fixed the code and added tests. PiperOrigin-RevId: 213726369 Change-Id: Id7fa6fce53abf26c29b13b9157bb4c6616986fba
-rw-r--r--runsc/boot/fs.go13
-rw-r--r--runsc/cmd/BUILD9
-rw-r--r--runsc/cmd/capability.go63
-rw-r--r--runsc/cmd/capability_test.go121
-rw-r--r--runsc/cmd/gofer.go33
-rw-r--r--runsc/specutils/specutils.go10
6 files changed, 197 insertions, 52 deletions
diff --git a/runsc/boot/fs.go b/runsc/boot/fs.go
index 420e57022..59ae5faae 100644
--- a/runsc/boot/fs.go
+++ b/runsc/boot/fs.go
@@ -428,13 +428,13 @@ func parseAndFilterOptions(opts []string, allowedKeys ...string) ([]string, erro
kv := strings.Split(o, "=")
switch len(kv) {
case 1:
- if contains(allowedKeys, o) {
+ if specutils.ContainsStr(allowedKeys, o) {
out = append(out, o)
continue
}
log.Warningf("ignoring unsupported key %q", kv)
case 2:
- if contains(allowedKeys, kv[0]) {
+ if specutils.ContainsStr(allowedKeys, kv[0]) {
out = append(out, o)
continue
}
@@ -540,15 +540,6 @@ func mountFlags(opts []string) fs.MountSourceFlags {
return mf
}
-func contains(strs []string, str string) bool {
- for _, s := range strs {
- if s == str {
- return true
- }
- }
- return false
-}
-
func mustFindFilesystem(name string) fs.Filesystem {
fs, ok := fs.FindFilesystem(name)
if !ok {
diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD
index f9c091ba2..7c90ff2c5 100644
--- a/runsc/cmd/BUILD
+++ b/runsc/cmd/BUILD
@@ -55,18 +55,27 @@ go_test(
name = "cmd_test",
size = "small",
srcs = [
+ "capability_test.go",
"delete_test.go",
"exec_test.go",
],
+ data = [
+ "//runsc",
+ ],
embed = [":cmd"],
deps = [
"//pkg/abi/linux",
+ "//pkg/log",
"//pkg/sentry/control",
"//pkg/sentry/kernel/auth",
"//pkg/urpc",
"//runsc/boot",
+ "//runsc/container",
+ "//runsc/specutils",
+ "//runsc/test/testutil",
"@com_github_google_go-cmp//cmp:go_default_library",
"@com_github_google_go-cmp//cmp/cmpopts:go_default_library",
"@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
+ "@com_github_syndtr_gocapability//capability:go_default_library",
],
)
diff --git a/runsc/cmd/capability.go b/runsc/cmd/capability.go
index e2410d4ad..affbb7ce3 100644
--- a/runsc/cmd/capability.go
+++ b/runsc/cmd/capability.go
@@ -16,56 +16,67 @@ package cmd
import (
"fmt"
- "os"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/syndtr/gocapability/capability"
"gvisor.googlesource.com/gvisor/pkg/log"
)
+var allCapTypes = []capability.CapType{
+ capability.BOUNDS,
+ capability.EFFECTIVE,
+ capability.PERMITTED,
+ capability.INHERITABLE,
+ capability.AMBIENT,
+}
+
// applyCaps applies the capabilities in the spec to the current thread.
//
// Note that it must be called with current thread locked.
func applyCaps(caps *specs.LinuxCapabilities) error {
- setter, err := capability.NewPid2(os.Getpid())
+ // Load current capabilities to trim the ones not permitted.
+ curCaps, err := capability.NewPid2(0)
if err != nil {
return err
}
- if err := setter.Load(); err != nil {
+ if err := curCaps.Load(); err != nil {
return err
}
- bounding, err := trimCaps(caps.Bounding, setter)
+ // Create an empty capability set to populate.
+ newCaps, err := capability.NewPid2(0)
if err != nil {
return err
}
- setter.Set(capability.BOUNDS, bounding...)
- 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
+ for _, c := range allCapTypes {
+ if !newCaps.Empty(c) {
+ panic("unloaded capabilities must be empty")
+ }
+ set, err := trimCaps(getCaps(c, caps), curCaps)
+ if err != nil {
+ return err
+ }
+ newCaps.Set(c, set...)
}
- setter.Set(capability.PERMITTED, permitted...)
- inheritable, err := trimCaps(caps.Inheritable, setter)
- if err != nil {
- return err
- }
- setter.Set(capability.INHERITABLE, inheritable...)
+ return newCaps.Apply(capability.CAPS | capability.BOUNDS | capability.AMBS)
+}
- ambient, err := trimCaps(caps.Ambient, setter)
- if err != nil {
- return err
+func getCaps(which capability.CapType, caps *specs.LinuxCapabilities) []string {
+ switch which {
+ case capability.BOUNDS:
+ return caps.Bounding
+ case capability.EFFECTIVE:
+ return caps.Effective
+ case capability.PERMITTED:
+ return caps.Permitted
+ case capability.INHERITABLE:
+ return caps.Inheritable
+ case capability.AMBIENT:
+ return caps.Ambient
}
- setter.Set(capability.AMBIENT, ambient...)
-
- return setter.Apply(capability.CAPS | capability.BOUNDS | capability.AMBS)
+ panic(fmt.Sprint("invalid capability type:", which))
}
func trimCaps(names []string, setter capability.Capabilities) ([]capability.Cap, error) {
diff --git a/runsc/cmd/capability_test.go b/runsc/cmd/capability_test.go
new file mode 100644
index 000000000..be9ef2e7b
--- /dev/null
+++ b/runsc/cmd/capability_test.go
@@ -0,0 +1,121 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+ "fmt"
+ "os"
+ "testing"
+
+ specs "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/syndtr/gocapability/capability"
+ "gvisor.googlesource.com/gvisor/pkg/log"
+ "gvisor.googlesource.com/gvisor/runsc/boot"
+ "gvisor.googlesource.com/gvisor/runsc/container"
+ "gvisor.googlesource.com/gvisor/runsc/specutils"
+ "gvisor.googlesource.com/gvisor/runsc/test/testutil"
+)
+
+func init() {
+ log.SetLevel(log.Debug)
+ if err := testutil.ConfigureExePath(); err != nil {
+ panic(err.Error())
+ }
+}
+
+func checkProcessCaps(pid int, wantCaps *specs.LinuxCapabilities) error {
+ curCaps, err := capability.NewPid2(pid)
+ if err != nil {
+ return fmt.Errorf("capability.NewPid2(%d) failed: %v", pid, err)
+ }
+ if err := curCaps.Load(); err != nil {
+ return fmt.Errorf("unable to load capabilities: %v", err)
+ }
+ fmt.Printf("Capabilities (PID: %d): %v\n", pid, curCaps)
+
+ for _, c := range allCapTypes {
+ if err := checkCaps(c, curCaps, wantCaps); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func checkCaps(which capability.CapType, curCaps capability.Capabilities, wantCaps *specs.LinuxCapabilities) error {
+ wantNames := getCaps(which, wantCaps)
+ for name, c := range capFromName {
+ want := specutils.ContainsStr(wantNames, name)
+ got := curCaps.Get(which, c)
+ if want != got {
+ if want {
+ return fmt.Errorf("capability %v:%s should be set", which, name)
+ }
+ return fmt.Errorf("capability %v:%s should NOT be set", which, name)
+ }
+ }
+ return nil
+}
+
+func TestCapabilities(t *testing.T) {
+ stop := testutil.StartReaper()
+ defer stop()
+
+ spec := testutil.NewSpecWithArgs("/bin/sleep", "10000")
+ caps := []string{
+ "CAP_CHOWN",
+ "CAP_SYS_PTRACE", // ptrace is added due to the platform choice.
+ }
+ spec.Process.Capabilities = &specs.LinuxCapabilities{
+ Permitted: caps,
+ Bounding: caps,
+ Effective: caps,
+ Inheritable: caps,
+ }
+
+ conf := testutil.TestConfig()
+
+ // Use --network=host to make sandbox use spec's capabilities.
+ conf.Network = boot.NetworkHost
+
+ rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer os.RemoveAll(rootDir)
+ defer os.RemoveAll(bundleDir)
+
+ // Create and start the container.
+ c, err := container.Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ if err != nil {
+ t.Fatalf("error creating container: %v", err)
+ }
+ defer c.Destroy()
+ if err := c.Start(conf); err != nil {
+ t.Fatalf("error starting container: %v", err)
+ }
+
+ // Check that sandbox and gofer have the proper capabilities.
+ if err := checkProcessCaps(c.Sandbox.Pid, spec.Process.Capabilities); err != nil {
+ t.Error(err)
+ }
+ if err := checkProcessCaps(c.GoferPid, goferCaps); err != nil {
+ t.Error(err)
+ }
+}
+
+func TestMain(m *testing.M) {
+ testutil.RunAsRoot()
+ os.Exit(m.Run())
+}
diff --git a/runsc/cmd/gofer.go b/runsc/cmd/gofer.go
index 95926f5f9..fd4eee546 100644
--- a/runsc/cmd/gofer.go
+++ b/runsc/cmd/gofer.go
@@ -31,6 +31,23 @@ import (
"gvisor.googlesource.com/gvisor/runsc/specutils"
)
+var caps = []string{
+ "CAP_CHOWN",
+ "CAP_DAC_OVERRIDE",
+ "CAP_DAC_READ_SEARCH",
+ "CAP_FOWNER",
+ "CAP_FSETID",
+ "CAP_SYS_CHROOT",
+}
+
+// goferCaps is the minimal set of capabilities needed by the Gofer to operate
+// on files.
+var goferCaps = &specs.LinuxCapabilities{
+ Bounding: caps,
+ Effective: caps,
+ Permitted: caps,
+}
+
// Gofer implements subcommands.Command for the "gofer" command, which starts a
// filesystem gofer. This command should not be called directly.
type Gofer struct {
@@ -72,25 +89,11 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
}
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(args, lc); err != nil {
+ if err := setCapsAndCallSelf(args, goferCaps); err != nil {
Fatalf("Unable to apply caps: %v", err)
}
panic("unreachable")
diff --git a/runsc/specutils/specutils.go b/runsc/specutils/specutils.go
index fdc9007e0..daf10b875 100644
--- a/runsc/specutils/specutils.go
+++ b/runsc/specutils/specutils.go
@@ -392,3 +392,13 @@ func Mount(src, dst, typ string, flags uint32) error {
}
return nil
}
+
+// ContainsStr returns true if 'str' is inside 'strs'.
+func ContainsStr(strs []string, str string) bool {
+ for _, s := range strs {
+ if s == str {
+ return true
+ }
+ }
+ return false
+}