Drop capabilities not needed by Gofer
PiperOrigin-RevId: 199808391 Change-Id: Ib37a4fb6193dc85c1f93bc16769d6aa41854b9d4
This commit is contained in:
parent
5c37097e34
commit
5c51bc51e4
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -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{})
|
||||
}
|
||||
|
|
|
@ -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 {
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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})
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue