diff --git a/vendor.conf b/vendor.conf index 18f1a92de0..548c78907f 100644 --- a/vendor.conf +++ b/vendor.conf @@ -44,7 +44,7 @@ github.com/gotestyourself/gotestyourself 44dbf532bbf5767611f6f2a61bded572e337010 github.com/google/go-cmp v0.1.0 # cri dependencies -github.com/containerd/cri v1.0.5 +github.com/containerd/cri f117382467baf182382c44332bfbf488effc34bb # release/1.0 github.com/containerd/go-cni f2d7272f12d045b16ed924f50e91f9f9cecc55a7 github.com/blang/semver v3.1.0 github.com/containernetworking/cni v0.6.0 @@ -61,7 +61,7 @@ github.com/hashicorp/errwrap 7554cd9344cec97297fa6649b055a8c98c2a1e55 github.com/hashicorp/go-multierror ed905158d87462226a13fe39ddf685ea65f1c11f github.com/json-iterator/go 1.0.4 github.com/opencontainers/runtime-tools 6073aff4ac61897f75895123f7e24135204a404d -github.com/opencontainers/selinux 4a2974bf1ee960774ffd517717f1f45325af0206 +github.com/opencontainers/selinux b6fa367ed7f534f9ba25391cc2d467085dbb445a github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0 github.com/spf13/pflag v1.0.0 github.com/tchap/go-patricia 5ad6cdb7538b0097d5598c7e57f0a24072adf7dc diff --git a/vendor/github.com/containerd/cri/pkg/containerd/opts/spec.go b/vendor/github.com/containerd/cri/pkg/containerd/opts/spec.go new file mode 100644 index 0000000000..3ac100139a --- /dev/null +++ b/vendor/github.com/containerd/cri/pkg/containerd/opts/spec.go @@ -0,0 +1,51 @@ +/* +Copyright 2018 The containerd Authors. + +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 opts + +import ( + "context" + + "github.com/containerd/containerd/containers" + "github.com/containerd/containerd/oci" + runtimespec "github.com/opencontainers/runtime-spec/specs-go" +) + +// WithAdditionalGIDs adds any additional groups listed for a particular user in the +// /etc/groups file of the image's root filesystem to the OCI spec's additionalGids array. +func WithAdditionalGIDs(userstr string) oci.SpecOpts { + return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) (err error) { + gids := s.Process.User.AdditionalGids + if err := oci.WithAdditionalGIDs(userstr)(ctx, client, c, s); err != nil { + return err + } + // Merge existing gids and new gids. + s.Process.User.AdditionalGids = mergeGids(s.Process.User.AdditionalGids, gids) + return nil + } +} + +func mergeGids(gids1, gids2 []uint32) []uint32 { + for _, gid1 := range gids1 { + for i, gid2 := range gids2 { + if gid1 == gid2 { + gids2 = append(gids2[:i], gids2[i+1:]...) + break + } + } + } + return append(gids1, gids2...) +} diff --git a/vendor/github.com/containerd/cri/pkg/os/os.go b/vendor/github.com/containerd/cri/pkg/os/os.go index f0e7a3271b..ef0edc6297 100644 --- a/vendor/github.com/containerd/cri/pkg/os/os.go +++ b/vendor/github.com/containerd/cri/pkg/os/os.go @@ -44,6 +44,7 @@ type OS interface { Mount(source string, target string, fstype string, flags uintptr, data string) error Unmount(target string, flags int) error LookupMount(path string) (containerdmount.Info, error) + Hostname() (string, error) } // RealOS is used to dispatch the real system level operations. @@ -132,3 +133,8 @@ func Unmount(target string, flags int) error { } return unix.Unmount(target, flags) } + +// Hostname will call os.Hostname to get the hostname of the host. +func (RealOS) Hostname() (string, error) { + return os.Hostname() +} diff --git a/vendor/github.com/containerd/cri/pkg/server/container_create.go b/vendor/github.com/containerd/cri/pkg/server/container_create.go index cb6ef5f844..50a373a8bd 100644 --- a/vendor/github.com/containerd/cri/pkg/server/container_create.go +++ b/vendor/github.com/containerd/cri/pkg/server/container_create.go @@ -19,6 +19,7 @@ package server import ( "os" "path/filepath" + "sort" "strconv" "strings" "time" @@ -232,6 +233,15 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta specOpts = append(specOpts, oci.WithUser(userstr)) } + if securityContext.GetRunAsUsername() != "" { + userstr = securityContext.GetRunAsUsername() + } else { + // Even if RunAsUser is not set, we still call `GetValue` to get uid 0. + // Because it is still useful to get additional gids for uid 0. + userstr = strconv.FormatInt(securityContext.GetRunAsUser().GetValue(), 10) + } + specOpts = append(specOpts, customopts.WithAdditionalGIDs(userstr)) + apparmorSpecOpts, err := generateApparmorSpecOpts( securityContext.GetApparmorProfile(), securityContext.GetPrivileged(), @@ -333,6 +343,17 @@ func (c *criService) generateContainerSpec(id string, sandboxID string, sandboxP g.AddProcessEnv("TERM", "xterm") } + // Add HOSTNAME env. + hostname := sandboxConfig.GetHostname() + if sandboxConfig.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtime.NamespaceMode_NODE && + hostname == "" { + hostname, err = c.os.Hostname() + if err != nil { + return nil, err + } + } + g.AddProcessEnv(hostnameEnv, hostname) + // Apply envs from image config first, so that envs from container config // can override them. if err := addImageEnvs(&g, imageConfig.Env); err != nil { @@ -349,8 +370,8 @@ func (c *criService) generateContainerSpec(id string, sandboxID string, sandboxP return nil, errors.Wrapf(err, "failed to init selinux options %+v", securityContext.GetSelinuxOptions()) } - // Add extra mounts first so that CRI specified mounts can override. - mounts := append(extraMounts, config.GetMounts()...) + // Merge extra mounts and CRI mounts. + mounts := mergeMounts(config.GetMounts(), extraMounts) if err := c.addOCIBindMounts(&g, mounts, mountLabel); err != nil { return nil, errors.Wrapf(err, "failed to set OCI bind mounts %+v", mounts) } @@ -596,8 +617,35 @@ func setOCIDevicesPrivileged(g *generate.Generator) error { // addOCIBindMounts adds bind mounts. func (c *criService) addOCIBindMounts(g *generate.Generator, mounts []*runtime.Mount, mountLabel string) error { + // Sort mounts in number of parts. This ensures that high level mounts don't + // shadow other mounts. + sort.Sort(orderedMounts(mounts)) + // Mount cgroup into the container as readonly, which inherits docker's behavior. g.AddCgroupsMount("ro") // nolint: errcheck + + // Copy all mounts from default mounts, except for + // - mounts overriden by supplied mount; + // - all mounts under /dev if a supplied /dev is present. + mountSet := make(map[string]struct{}) + for _, m := range mounts { + mountSet[filepath.Clean(m.ContainerPath)] = struct{}{} + } + defaultMounts := g.Spec().Mounts + g.Spec().Mounts = nil + for _, m := range defaultMounts { + dst := filepath.Clean(m.Destination) + if _, ok := mountSet[dst]; ok { + // filter out mount overridden by a supplied mount + continue + } + if _, mountDev := mountSet["/dev"]; mountDev && strings.HasPrefix(dst, "/dev/") { + // filter out everything under /dev if /dev is a supplied mount + continue + } + g.Spec().Mounts = append(g.Spec().Mounts, m) + } + for _, mount := range mounts { dst := mount.GetContainerPath() src := mount.GetHostPath() @@ -777,10 +825,6 @@ func defaultRuntimeSpec(id string) (*runtimespec.Spec, error) { if mount.Destination == "/run" { continue } - // CRI plugin handles `/dev/shm` itself. - if mount.Destination == "/dev/shm" { - continue - } mounts = append(mounts, mount) } spec.Mounts = mounts @@ -924,3 +968,25 @@ func generateUserString(username string, uid, gid *runtime.Int64Value) (string, } return userstr, nil } + +// mergeMounts merge CRI mounts with extra mounts. If a mount destination +// is mounted by both a CRI mount and an extra mount, the CRI mount will +// be kept. +func mergeMounts(criMounts, extraMounts []*runtime.Mount) []*runtime.Mount { + var mounts []*runtime.Mount + mounts = append(mounts, criMounts...) + // Copy all mounts from extra mounts, except for mounts overriden by CRI. + for _, e := range extraMounts { + found := false + for _, c := range criMounts { + if filepath.Clean(e.ContainerPath) == filepath.Clean(c.ContainerPath) { + found = true + break + } + } + if !found { + mounts = append(mounts, e) + } + } + return mounts +} diff --git a/vendor/github.com/containerd/cri/pkg/server/container_stop.go b/vendor/github.com/containerd/cri/pkg/server/container_stop.go index b8f980a428..ca1e430d98 100644 --- a/vendor/github.com/containerd/cri/pkg/server/container_stop.go +++ b/vendor/github.com/containerd/cri/pkg/server/container_stop.go @@ -33,7 +33,10 @@ import ( // killContainerTimeout is the timeout that we wait for the container to // be SIGKILLed. -const killContainerTimeout = 2 * time.Minute +// The timeout is set to 1 min, because the default CRI operation timeout +// for StopContainer is (2 min + stop timeout). Set to 1 min, so that we +// have enough time for kill(all=true) and kill(all=false). +const killContainerTimeout = 1 * time.Minute // StopContainer stops a running container with a grace period (i.e., timeout). func (c *criService) StopContainer(ctx context.Context, r *runtime.StopContainerRequest) (*runtime.StopContainerResponse, error) { @@ -63,6 +66,16 @@ func (c *criService) stopContainer(ctx context.Context, container containerstore return nil } + task, err := container.Container.Task(ctx, nil) + if err != nil { + if !errdefs.IsNotFound(err) { + return errors.Wrapf(err, "failed to stop container, task not found for container %q", id) + } + return nil + } + + // We only need to kill the task. The event handler will Delete the + // task from containerd after it handles the Exited event. if timeout > 0 { stopSignal := unix.SIGTERM image, err := c.imageStore.Get(container.ImageRef) @@ -81,52 +94,47 @@ func (c *criService) stopContainer(ctx context.Context, container containerstore } } logrus.Infof("Stop container %q with signal %v", id, stopSignal) - task, err := container.Container.Task(ctx, nil) - if err != nil { - if !errdefs.IsNotFound(err) { - return errors.Wrapf(err, "failed to stop container, task not found for container %q", id) - } - return nil - } - if task != nil { - if err = task.Kill(ctx, stopSignal); err != nil { - if !errdefs.IsNotFound(err) { - return errors.Wrapf(err, "failed to stop container %q", id) - } - // Move on to make sure container status is updated. - } + if err = task.Kill(ctx, stopSignal); err != nil && !errdefs.IsNotFound(err) { + return errors.Wrapf(err, "failed to stop container %q", id) } - err = c.waitContainerStop(ctx, container, timeout) - if err == nil { + if err = c.waitContainerStop(ctx, container, timeout); err == nil { return nil } - logrus.WithError(err).Errorf("Stop container %q timed out", id) + logrus.WithError(err).Errorf("An error occurs during waiting for container %q to be stopped", id) } - task, err := container.Container.Task(ctx, nil) - if err != nil { - if !errdefs.IsNotFound(err) { - return errors.Wrapf(err, "failed to stop container, task not found for container %q", id) - } - return nil - } - // Event handler will Delete the container from containerd after it handles the Exited event. logrus.Infof("Kill container %q", id) - if task != nil { - if err = task.Kill(ctx, unix.SIGKILL, containerd.WithKillAll); err != nil { - if !errdefs.IsNotFound(err) { - return errors.Wrapf(err, "failed to kill container %q", id) - } - // Move on to make sure container status is updated. - } + if err = task.Kill(ctx, unix.SIGKILL, containerd.WithKillAll); err != nil && !errdefs.IsNotFound(err) { + return errors.Wrapf(err, "failed to kill container %q", id) } // Wait for a fixed timeout until container stop is observed by event monitor. - if err := c.waitContainerStop(ctx, container, killContainerTimeout); err != nil { - return errors.Wrapf(err, "an error occurs during waiting for container %q to stop", id) + if err = c.waitContainerStop(ctx, container, killContainerTimeout); err == nil { + return nil } - return nil + logrus.WithError(err).Errorf("An error occurs during waiting for container %q to be killed", id) + + // This is a fix for `runc`, and should not break other runtimes. With + // containerd.WithKillAll, `runc` will get all processes from the container + // cgroups, and kill them. However, sometimes the processes may be moved + // out from the container cgroup, e.g. users manually move them by mistake, + // or systemd.Delegate=true is not set. + // In these cases, we should try our best to do cleanup, kill the container + // without containerd.WithKillAll, so that runc can kill the container init + // process directly. + // NOTE(random-liu): If pid namespace is shared inside the pod, non-init processes + // of this container will be left running until the pause container is stopped. + logrus.Infof("Kill container %q init process", id) + if err = task.Kill(ctx, unix.SIGKILL); err != nil && !errdefs.IsNotFound(err) { + return errors.Wrapf(err, "failed to kill container %q init process", id) + } + + // Wait for a fixed timeout until container stop is observed by event monitor. + if err = c.waitContainerStop(ctx, container, killContainerTimeout); err == nil { + return nil + } + return errors.Wrapf(err, "an error occurs during waiting for container %q init process to be killed", id) } // waitContainerStop waits for container to be stopped until timeout exceeds or context is cancelled. diff --git a/vendor/github.com/containerd/cri/pkg/server/helpers.go b/vendor/github.com/containerd/cri/pkg/server/helpers.go index b09f281519..9cf86baec2 100644 --- a/vendor/github.com/containerd/cri/pkg/server/helpers.go +++ b/vendor/github.com/containerd/cri/pkg/server/helpers.go @@ -19,8 +19,10 @@ package server import ( "encoding/json" "fmt" + "os" "path" "path/filepath" + "regexp" "strconv" "strings" @@ -95,6 +97,8 @@ const ( etcHosts = "/etc/hosts" // resolvConfPath is the abs path of resolv.conf on host or container. resolvConfPath = "/etc/resolv.conf" + // hostnameEnv is the key for HOSTNAME env. + hostnameEnv = "HOSTNAME" ) const ( @@ -370,11 +374,16 @@ func initSelinuxOpts(selinuxOpt *runtime.SELinuxOption) (string, string, error) // Should ignored selinuxOpts if they are incomplete. if selinuxOpt.GetUser() == "" || selinuxOpt.GetRole() == "" || - selinuxOpt.GetType() == "" || - selinuxOpt.GetLevel() == "" { + selinuxOpt.GetType() == "" { return "", "", nil } + // make sure the format of "level" is correct. + ok, err := checkSelinuxLevel(selinuxOpt.GetLevel()) + if err != nil || !ok { + return "", "", err + } + labelOpts := fmt.Sprintf("%s:%s:%s:%s", selinuxOpt.GetUser(), selinuxOpt.GetRole(), @@ -383,6 +392,18 @@ func initSelinuxOpts(selinuxOpt *runtime.SELinuxOption) (string, string, error) return label.InitLabels(selinux.DupSecOpt(labelOpts)) } +func checkSelinuxLevel(level string) (bool, error) { + if len(level) == 0 { + return true, nil + } + + matched, err := regexp.MatchString(`^s\d(-s\d)??(:c\d{1,4}((.c\d{1,4})?,c\d{1,4})*(.c\d{1,4})?(,c\d{1,4}(.c\d{1,4})?)*)?$`, level) + if err != nil || !matched { + return false, fmt.Errorf("the format of 'level' %q is not correct: %v", level, err) + } + return true, nil +} + // isInCRIMounts checks whether a destination is in CRI mount list. func isInCRIMounts(dst string, mounts []*runtime.Mount) bool { for _, m := range mounts { @@ -444,3 +465,30 @@ func getRuntimeConfigFromContainerInfo(c containers.Container) (criconfig.Runtim r.Root = runtimeOpts.RuntimeRoot return r, nil } + +// mounts defines how to sort runtime.Mount. +// This is the same with the Docker implementation: +// https://github.com/moby/moby/blob/17.05.x/daemon/volumes.go#L26 +type orderedMounts []*runtime.Mount + +// Len returns the number of mounts. Used in sorting. +func (m orderedMounts) Len() int { + return len(m) +} + +// Less returns true if the number of parts (a/b/c would be 3 parts) in the +// mount indexed by parameter 1 is less than that of the mount indexed by +// parameter 2. Used in sorting. +func (m orderedMounts) Less(i, j int) bool { + return m.parts(i) < m.parts(j) +} + +// Swap swaps two items in an array of mounts. Used in sorting +func (m orderedMounts) Swap(i, j int) { + m[i], m[j] = m[j], m[i] +} + +// parts returns the number of parts in the destination of a mount. Used in sorting. +func (m orderedMounts) parts(i int) int { + return strings.Count(filepath.Clean(m[i].ContainerPath), string(os.PathSeparator)) +} diff --git a/vendor/github.com/containerd/cri/pkg/server/sandbox_stop.go b/vendor/github.com/containerd/cri/pkg/server/sandbox_stop.go index 9def535765..2d5b3ca338 100644 --- a/vendor/github.com/containerd/cri/pkg/server/sandbox_stop.go +++ b/vendor/github.com/containerd/cri/pkg/server/sandbox_stop.go @@ -109,11 +109,20 @@ func (c *criService) stopSandboxContainer(ctx context.Context, sandbox sandboxst } // Kill the sandbox container. - err = task.Kill(ctx, unix.SIGKILL, containerd.WithKillAll) - if err != nil && !errdefs.IsNotFound(err) { + if err = task.Kill(ctx, unix.SIGKILL, containerd.WithKillAll); err != nil && !errdefs.IsNotFound(err) { return errors.Wrap(err, "failed to kill sandbox container") } + if err = c.waitSandboxStop(ctx, sandbox, killContainerTimeout); err == nil { + return nil + } + logrus.WithError(err).Errorf("An error occurs during waiting for sandbox %q to be killed", sandbox.ID) + + // Kill the sandbox container init process. + if err = task.Kill(ctx, unix.SIGKILL); err != nil && !errdefs.IsNotFound(err) { + return errors.Wrap(err, "failed to kill sandbox container init process") + } + return c.waitSandboxStop(ctx, sandbox, killContainerTimeout) } diff --git a/vendor/github.com/containerd/cri/vendor.conf b/vendor/github.com/containerd/cri/vendor.conf index 437f2a3b3a..381bcbddf1 100644 --- a/vendor/github.com/containerd/cri/vendor.conf +++ b/vendor/github.com/containerd/cri/vendor.conf @@ -3,9 +3,9 @@ github.com/blang/semver v3.1.0 github.com/boltdb/bolt e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895 github.com/containerd/cgroups fe281dd265766145e943a034aa41086474ea6130 -github.com/containerd/console cb7008ab3d8359b78c5f464cb7cf160107ad5925 -github.com/containerd/containerd 01a0741488487779a95035cd85504589034d2e91 -github.com/containerd/continuity 3e8f2ea4b190484acb976a5b378d373429639a1a +github.com/containerd/console 4d8a41f4ce5b9bae77c41786ea2458330f43f081 +github.com/containerd/containerd 57508dcb0b5776efaacd0828ed42f819fab5ba07 +github.com/containerd/continuity a60600ad77f38aaa70165825f61e2ea72e51c9b1 github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c github.com/containerd/go-cni f2d7272f12d045b16ed924f50e91f9f9cecc55a7 github.com/containerd/go-runc bcb223a061a3dd7de1a89c0b402a60f4dd9bd307 @@ -40,7 +40,7 @@ github.com/opencontainers/image-spec v1.0.1 github.com/opencontainers/runc 69663f0bd4b60df09991c08812a60108003fa340 github.com/opencontainers/runtime-spec v1.0.1 github.com/opencontainers/runtime-tools 6073aff4ac61897f75895123f7e24135204a404d -github.com/opencontainers/selinux 4a2974bf1ee960774ffd517717f1f45325af0206 +github.com/opencontainers/selinux b6fa367ed7f534f9ba25391cc2d467085dbb445a github.com/pkg/errors v0.8.0 github.com/pmezard/go-difflib v1.0.0 github.com/prometheus/client_golang f4fb1b73fb099f396a7f0036bf86aa8def4ed823 diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go index 569dcf0841..f0a055b87e 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go @@ -49,8 +49,10 @@ func InitLabels(options []string) (string, string, error) { mcon[con[0]] = con[1] } } + _ = ReleaseLabel(processLabel) processLabel = pcon.Get() mountLabel = mcon.Get() + _ = ReserveLabel(processLabel) } return processLabel, mountLabel, nil } @@ -85,9 +87,6 @@ func FormatMountLabel(src, mountLabel string) string { // SetProcessLabel takes a process label and tells the kernel to assign the // label to the next program executed by the current process. func SetProcessLabel(processLabel string) error { - if processLabel == "" { - return nil - } return selinux.SetExecLabel(processLabel) } @@ -131,7 +130,7 @@ func Relabel(path string, fileLabel string, shared bool) error { return nil } - exclude_paths := map[string]bool{"/": true, "/usr": true, "/etc": true} + exclude_paths := map[string]bool{"/": true, "/usr": true, "/etc": true, "/tmp": true, "/home": true, "/run": true, "/var": true, "/root": true} if exclude_paths[path] { return fmt.Errorf("SELinux relabeling of %s is not allowed", path) } diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go similarity index 69% rename from vendor/github.com/opencontainers/selinux/go-selinux/selinux.go rename to vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go index de9316c2e2..5dc09a51eb 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go @@ -1,13 +1,16 @@ -// +build linux +// +build selinux,linux package selinux import ( "bufio" + "bytes" "crypto/rand" "encoding/binary" + "errors" "fmt" "io" + "io/ioutil" "os" "path/filepath" "regexp" @@ -23,14 +26,16 @@ const ( // Permissive constant to indicate SELinux is in permissive mode Permissive = 0 // Disabled constant to indicate SELinux is disabled - Disabled = -1 + Disabled = -1 + selinuxDir = "/etc/selinux/" selinuxConfig = selinuxDir + "config" + selinuxfsMount = "/sys/fs/selinux" selinuxTypeTag = "SELINUXTYPE" selinuxTag = "SELINUX" - selinuxPath = "/sys/fs/selinux" xattrNameSelinux = "security.selinux" stRdOnly = 0x01 + selinuxfsMagic = 0xf97cff8c ) type selinuxState struct { @@ -43,7 +48,13 @@ type selinuxState struct { } var ( + // ErrMCSAlreadyExists is returned when trying to allocate a duplicate MCS. + ErrMCSAlreadyExists = errors.New("MCS label already exists") + // ErrEmptyPath is returned when an empty path has been specified. + ErrEmptyPath = errors.New("empty path") + assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`) + roFileLabel string state = selinuxState{ mcsList: make(map[string]bool), } @@ -91,6 +102,83 @@ func (s *selinuxState) setSELinuxfs(selinuxfs string) string { return s.selinuxfs } +func verifySELinuxfsMount(mnt string) bool { + var buf syscall.Statfs_t + for { + err := syscall.Statfs(mnt, &buf) + if err == nil { + break + } + if err == syscall.EAGAIN { + continue + } + return false + } + if uint32(buf.Type) != uint32(selinuxfsMagic) { + return false + } + if (buf.Flags & stRdOnly) != 0 { + return false + } + + return true +} + +func findSELinuxfs() string { + // fast path: check the default mount first + if verifySELinuxfsMount(selinuxfsMount) { + return selinuxfsMount + } + + // check if selinuxfs is available before going the slow path + fs, err := ioutil.ReadFile("/proc/filesystems") + if err != nil { + return "" + } + if !bytes.Contains(fs, []byte("\tselinuxfs\n")) { + return "" + } + + // slow path: try to find among the mounts + f, err := os.Open("/proc/self/mountinfo") + if err != nil { + return "" + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for { + mnt := findSELinuxfsMount(scanner) + if mnt == "" { // error or not found + return "" + } + if verifySELinuxfsMount(mnt) { + return mnt + } + } +} + +// findSELinuxfsMount returns a next selinuxfs mount point found, +// if there is one, or an empty string in case of EOF or error. +func findSELinuxfsMount(s *bufio.Scanner) string { + for s.Scan() { + txt := s.Text() + // The first field after - is fs type. + // Safe as spaces in mountpoints are encoded as \040 + if !strings.Contains(txt, " - selinuxfs ") { + continue + } + const mPos = 5 // mount point is 5th field + fields := strings.SplitN(txt, " ", mPos+1) + if len(fields) < mPos+1 { + continue + } + return fields[mPos-1] + } + + return "" +} + func (s *selinuxState) getSELinuxfs() string { s.Lock() selinuxfs := s.selinuxfs @@ -100,40 +188,7 @@ func (s *selinuxState) getSELinuxfs() string { return selinuxfs } - selinuxfs = "" - f, err := os.Open("/proc/self/mountinfo") - if err != nil { - return selinuxfs - } - defer f.Close() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - txt := scanner.Text() - // Safe as mountinfo encodes mountpoints with spaces as \040. - sepIdx := strings.Index(txt, " - ") - if sepIdx == -1 { - continue - } - if !strings.Contains(txt[sepIdx:], "selinuxfs") { - continue - } - fields := strings.Split(txt, " ") - if len(fields) < 5 { - continue - } - selinuxfs = fields[4] - break - } - - if selinuxfs != "" { - var buf syscall.Statfs_t - syscall.Statfs(selinuxfs, &buf) - if (buf.Flags & stRdOnly) == 1 { - selinuxfs = "" - } - } - return s.setSELinuxfs(selinuxfs) + return s.setSELinuxfs(findSELinuxfs()) } // getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs @@ -150,7 +205,7 @@ func GetEnabled() bool { return state.getEnabled() } -func readConfig(target string) (value string) { +func readConfig(target string) string { var ( val, key string bufin *bufio.Reader @@ -192,30 +247,42 @@ func readConfig(target string) (value string) { } func getSELinuxPolicyRoot() string { - return selinuxDir + readConfig(selinuxTypeTag) + return filepath.Join(selinuxDir, readConfig(selinuxTypeTag)) } -func readCon(name string) (string, error) { - var val string +func readCon(fpath string) (string, error) { + if fpath == "" { + return "", ErrEmptyPath + } - in, err := os.Open(name) + in, err := os.Open(fpath) if err != nil { return "", err } defer in.Close() - _, err = fmt.Fscanf(in, "%s", &val) - return val, err + var retval string + if _, err := fmt.Fscanf(in, "%s", &retval); err != nil { + return "", err + } + return strings.Trim(retval, "\x00"), nil } // SetFileLabel sets the SELinux label for this path or returns an error. -func SetFileLabel(path string, label string) error { - return lsetxattr(path, xattrNameSelinux, []byte(label), 0) +func SetFileLabel(fpath string, label string) error { + if fpath == "" { + return ErrEmptyPath + } + return lsetxattr(fpath, xattrNameSelinux, []byte(label), 0) } // FileLabel returns the SELinux label for this path or returns an error. -func FileLabel(path string) (string, error) { - label, err := lgetxattr(path, xattrNameSelinux) +func FileLabel(fpath string) (string, error) { + if fpath == "" { + return "", ErrEmptyPath + } + + label, err := lgetxattr(fpath, xattrNameSelinux) if err != nil { return "", err } @@ -260,8 +327,12 @@ func ExecLabel() (string, error) { return readCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid())) } -func writeCon(name string, val string) error { - out, err := os.OpenFile(name, os.O_WRONLY, 0) +func writeCon(fpath string, val string) error { + if fpath == "" { + return ErrEmptyPath + } + + out, err := os.OpenFile(fpath, os.O_WRONLY, 0) if err != nil { return err } @@ -275,6 +346,37 @@ func writeCon(name string, val string) error { return err } +/* +CanonicalizeContext takes a context string and writes it to the kernel +the function then returns the context that the kernel will use. This function +can be used to see if two contexts are equivalent +*/ +func CanonicalizeContext(val string) (string, error) { + return readWriteCon(filepath.Join(getSelinuxMountPoint(), "context"), val) +} + +func readWriteCon(fpath string, val string) (string, error) { + if fpath == "" { + return "", ErrEmptyPath + } + f, err := os.OpenFile(fpath, os.O_RDWR, 0) + if err != nil { + return "", err + } + defer f.Close() + + _, err = f.Write([]byte(val)) + if err != nil { + return "", err + } + + var retval string + if _, err := fmt.Fscanf(f, "%s", &retval); err != nil { + return "", err + } + return strings.Trim(retval, "\x00"), nil +} + /* SetExecLabel sets the SELinux label that the kernel will use for any programs that are executed by the current process thread, or an error. @@ -285,7 +387,10 @@ func SetExecLabel(label string) error { // Get returns the Context as a string func (c Context) Get() string { - return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"]) + if c["level"] != "" { + return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"]) + } + return fmt.Sprintf("%s:%s:%s", c["user"], c["role"], c["type"]) } // NewContext creates a new Context struct from the specified label @@ -297,7 +402,9 @@ func NewContext(label string) Context { c["user"] = con[0] c["role"] = con[1] c["type"] = con[2] - c["level"] = con[3] + if len(con) > 3 { + c["level"] = con[3] + } } return c } @@ -306,12 +413,14 @@ func NewContext(label string) Context { func ReserveLabel(label string) { if len(label) != 0 { con := strings.SplitN(label, ":", 4) - mcsAdd(con[3]) + if len(con) > 3 { + mcsAdd(con[3]) + } } } func selinuxEnforcePath() string { - return fmt.Sprintf("%s/enforce", selinuxPath) + return fmt.Sprintf("%s/enforce", getSelinuxMountPoint()) } // EnforceMode returns the current SELinux mode Enforcing, Permissive, Disabled @@ -354,16 +463,22 @@ func DefaultEnforceMode() int { } func mcsAdd(mcs string) error { + if mcs == "" { + return nil + } state.Lock() defer state.Unlock() if state.mcsList[mcs] { - return fmt.Errorf("MCS Label already exists") + return ErrMCSAlreadyExists } state.mcsList[mcs] = true return nil } func mcsDelete(mcs string) { + if mcs == "" { + return + } state.Lock() defer state.Unlock() state.mcsList[mcs] = false @@ -424,14 +539,14 @@ Allowing it to be used by another process. func ReleaseLabel(label string) { if len(label) != 0 { con := strings.SplitN(label, ":", 4) - mcsDelete(con[3]) + if len(con) > 3 { + mcsDelete(con[3]) + } } } -var roFileLabel string - // ROFileLabel returns the specified SELinux readonly file label -func ROFileLabel() (fileLabel string) { +func ROFileLabel() string { return roFileLabel } @@ -497,23 +612,25 @@ func ContainerLabels() (processLabel string, fileLabel string) { roFileLabel = fileLabel } exit: - mcs := uniqMcs(1024) scon := NewContext(processLabel) - scon["level"] = mcs - processLabel = scon.Get() - scon = NewContext(fileLabel) - scon["level"] = mcs - fileLabel = scon.Get() + if scon["level"] != "" { + mcs := uniqMcs(1024) + scon["level"] = mcs + processLabel = scon.Get() + scon = NewContext(fileLabel) + scon["level"] = mcs + fileLabel = scon.Get() + } return processLabel, fileLabel } // SecurityCheckContext validates that the SELinux label is understood by the kernel func SecurityCheckContext(val string) error { - return writeCon(fmt.Sprintf("%s.context", selinuxPath), val) + return writeCon(fmt.Sprintf("%s/context", getSelinuxMountPoint()), val) } /* -CopyLevel returns a label with the MLS/MCS level from src label replaces on +CopyLevel returns a label with the MLS/MCS level from src label replaced on the dest label. */ func CopyLevel(src, dest string) (string, error) { @@ -536,20 +653,26 @@ func CopyLevel(src, dest string) (string, error) { // Prevent users from relabing system files func badPrefix(fpath string) error { - var badprefixes = []string{"/usr"} + if fpath == "" { + return ErrEmptyPath + } - for _, prefix := range badprefixes { - if fpath == prefix || strings.HasPrefix(fpath, fmt.Sprintf("%s/", prefix)) { + badPrefixes := []string{"/usr"} + for _, prefix := range badPrefixes { + if strings.HasPrefix(fpath, prefix) { return fmt.Errorf("relabeling content in %s is not allowed", prefix) } } return nil } -// Chcon changes the fpath file object to the SELinux label label. -// If the fpath is a directory and recurse is true Chcon will walk the -// directory tree setting the label +// Chcon changes the `fpath` file object to the SELinux label `label`. +// If `fpath` is a directory and `recurse`` is true, Chcon will walk the +// directory tree setting the label. func Chcon(fpath string, label string, recurse bool) error { + if fpath == "" { + return ErrEmptyPath + } if label == "" { return nil } @@ -568,7 +691,7 @@ func Chcon(fpath string, label string, recurse bool) error { } // DupSecOpt takes an SELinux process label and returns security options that -// can will set the SELinux Type and Level for future container processes +// can be used to set the SELinux Type and Level for future container processes. func DupSecOpt(src string) []string { if src == "" { return nil @@ -576,18 +699,23 @@ func DupSecOpt(src string) []string { con := NewContext(src) if con["user"] == "" || con["role"] == "" || - con["type"] == "" || - con["level"] == "" { + con["type"] == "" { return nil } - return []string{"user:" + con["user"], + dup := []string{"user:" + con["user"], "role:" + con["role"], "type:" + con["type"], - "level:" + con["level"]} + } + + if con["level"] != "" { + dup = append(dup, "level:"+con["level"]) + } + + return dup } -// DisableSecOpt returns a security opt that can be used to disabling SELinux -// labeling support for future container processes +// DisableSecOpt returns a security opt that can be used to disable SELinux +// labeling support for future container processes. func DisableSecOpt() []string { return []string{"disable"} } diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go new file mode 100644 index 0000000000..4dbfd83eda --- /dev/null +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go @@ -0,0 +1,188 @@ +// +build !selinux + +package selinux + +import ( + "errors" +) + +const ( + // Enforcing constant indicate SELinux is in enforcing mode + Enforcing = 1 + // Permissive constant to indicate SELinux is in permissive mode + Permissive = 0 + // Disabled constant to indicate SELinux is disabled + Disabled = -1 +) + +var ( + // ErrMCSAlreadyExists is returned when trying to allocate a duplicate MCS. + ErrMCSAlreadyExists = errors.New("MCS label already exists") + // ErrEmptyPath is returned when an empty path has been specified. + ErrEmptyPath = errors.New("empty path") +) + +// Context is a representation of the SELinux label broken into 4 parts +type Context map[string]string + +// SetDisabled disables selinux support for the package +func SetDisabled() { + return +} + +// GetEnabled returns whether selinux is currently enabled. +func GetEnabled() bool { + return false +} + +// SetFileLabel sets the SELinux label for this path or returns an error. +func SetFileLabel(fpath string, label string) error { + return nil +} + +// FileLabel returns the SELinux label for this path or returns an error. +func FileLabel(fpath string) (string, error) { + return "", nil +} + +/* +SetFSCreateLabel tells kernel the label to create all file system objects +created by this task. Setting label="" to return to default. +*/ +func SetFSCreateLabel(label string) error { + return nil +} + +/* +FSCreateLabel returns the default label the kernel which the kernel is using +for file system objects created by this task. "" indicates default. +*/ +func FSCreateLabel() (string, error) { + return "", nil +} + +// CurrentLabel returns the SELinux label of the current process thread, or an error. +func CurrentLabel() (string, error) { + return "", nil +} + +// PidLabel returns the SELinux label of the given pid, or an error. +func PidLabel(pid int) (string, error) { + return "", nil +} + +/* +ExecLabel returns the SELinux label that the kernel will use for any programs +that are executed by the current process thread, or an error. +*/ +func ExecLabel() (string, error) { + return "", nil +} + +/* +CanonicalizeContext takes a context string and writes it to the kernel +the function then returns the context that the kernel will use. This function +can be used to see if two contexts are equivalent +*/ +func CanonicalizeContext(val string) (string, error) { + return "", nil +} + +/* +SetExecLabel sets the SELinux label that the kernel will use for any programs +that are executed by the current process thread, or an error. +*/ +func SetExecLabel(label string) error { + return nil +} + +// Get returns the Context as a string +func (c Context) Get() string { + return "" +} + +// NewContext creates a new Context struct from the specified label +func NewContext(label string) Context { + c := make(Context) + return c +} + +// ReserveLabel reserves the MLS/MCS level component of the specified label +func ReserveLabel(label string) { + return +} + +// EnforceMode returns the current SELinux mode Enforcing, Permissive, Disabled +func EnforceMode() int { + return Disabled +} + +/* +SetEnforceMode sets the current SELinux mode Enforcing, Permissive. +Disabled is not valid, since this needs to be set at boot time. +*/ +func SetEnforceMode(mode int) error { + return nil +} + +/* +DefaultEnforceMode returns the systems default SELinux mode Enforcing, +Permissive or Disabled. Note this is is just the default at boot time. +EnforceMode tells you the systems current mode. +*/ +func DefaultEnforceMode() int { + return Disabled +} + +/* +ReleaseLabel will unreserve the MLS/MCS Level field of the specified label. +Allowing it to be used by another process. +*/ +func ReleaseLabel(label string) { + return +} + +// ROFileLabel returns the specified SELinux readonly file label +func ROFileLabel() string { + return "" +} + +/* +ContainerLabels returns an allocated processLabel and fileLabel to be used for +container labeling by the calling process. +*/ +func ContainerLabels() (processLabel string, fileLabel string) { + return "", "" +} + +// SecurityCheckContext validates that the SELinux label is understood by the kernel +func SecurityCheckContext(val string) error { + return nil +} + +/* +CopyLevel returns a label with the MLS/MCS level from src label replaced on +the dest label. +*/ +func CopyLevel(src, dest string) (string, error) { + return "", nil +} + +// Chcon changes the `fpath` file object to the SELinux label `label`. +// If `fpath` is a directory and `recurse`` is true, Chcon will walk the +// directory tree setting the label. +func Chcon(fpath string, label string, recurse bool) error { + return nil +} + +// DupSecOpt takes an SELinux process label and returns security options that +// can be used to set the SELinux Type and Level for future container processes. +func DupSecOpt(src string) []string { + return nil +} + +// DisableSecOpt returns a security opt that can be used to disable SELinux +// labeling support for future container processes. +func DisableSecOpt() []string { + return []string{"disable"} +} diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go b/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go index 7f2ef85049..67a9d8ee85 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go @@ -1,4 +1,4 @@ -// +build linux +// +build selinux,linux package selinux