CVE-2021-43816: Improper Preservation of Permissions (CWE-281)
value: containerd is an open source container runtime. On installations using SELinux, such as EL8 (CentOS, RHEL), Fedora, or SUSE MicroOS, with containerd since v1.5.0-beta.0 as the backing container runtime interface (CRI), an unprivileged pod scheduled to the node may bind mount, via hostPath volume, any privileged, regular file on disk for complete read/write access (sans delete). Such is achieved by placing the in-container location of the hostPath volume mount at either `/etc/hosts`, `/etc/hostname`, or `/etc/resolv.conf`. These locations are being relabeled indiscriminately to match the container process-label which effectively elevates permissions for savvy containers that would not normally be able to access privileged host files. This issue has been resolved in version 1.5.9. Users are advised to upgrade as soon as possible.
CWE description: The product does not preserve permissions or incorrectly preserves permissions when copying, restoring, or sharing objects, which can cause them to have less restrictive permissions than intended.
CVSS v3 base score: 9.1
Published: 2022-01-05T19:15Z
Repository: https://github.com/containerd/containerd
Language: Go
Files changed: pkg/cri/opts/spec_linux.go, pkg/cri/server/container_create_linux.go
Commit date: 2021-01-19 13:42:09 -0500
Commit message: [cri] label etc files for selinux containers

Signed-off-by: Michael Crosby <michael@thepasture.io>

Diff: diff --git a/pkg/cri/opts/spec_linux.go b/pkg/cri/opts/spec_linux.go
index c54fe20d2..8730d8482 100644
--- a/pkg/cri/opts/spec_linux.go
+++ b/pkg/cri/opts/spec_linux.go
@@ -240,10 +240,34 @@ func WithMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*ru
 		}
 		return nil
 	}
 }
 
+const (
+	etcHosts       = "/etc/hosts"
+	etcHostname    = "/etc/hostname"
+	resolvConfPath = "/etc/resolv.conf"
+)
+
+// WithRelabeledContainerMounts relabels the default container mounts for files in /etc
+func WithRelabeledContainerMounts(mountLabel string) oci.SpecOpts {
+	return func(ctx context.Context, client oci.Client, _ *containers.Container, s *runtimespec.Spec) (err error) {
+		if mountLabel == "" {
+			return nil
+		}
+		for _, m := range s.Mounts {
+			switch m.Destination {
+			case etcHosts, etcHostname, resolvConfPath:
+				if err := label.Relabel(m.Source, mountLabel, false); err != nil {
+					return err
+				}
+			}
+		}
+		return nil
+	}
+}
+
 // Ensure mount point on which path is mounted, is shared.
 func ensureShared(path string, lookupMount func(string) (mount.Info, error)) error {
 	mountInfo, err := lookupMount(path)
 	if err != nil {
 		return err
diff --git a/pkg/cri/server/container_create_linux.go b/pkg/cri/server/container_create_linux.go
index 61208145a..199586998 100644
--- a/pkg/cri/server/container_create_linux.go
+++ b/pkg/cri/server/container_create_linux.go
@@ -181,11 +181,11 @@ func (c *criService) containerSpec(id string, sandboxID string, sandboxPid uint3
 		if retErr != nil {
 			_ = label.ReleaseLabel(processLabel)
 		}
 	}()
 
-	specOpts = append(specOpts, customopts.WithMounts(c.os, config, extraMounts, mountLabel))
+	specOpts = append(specOpts, customopts.WithMounts(c.os, config, extraMounts, mountLabel), customopts.WithRelabeledContainerMounts(mountLabel))
 
 	if !c.config.DisableProcMount {
 		// Apply masked paths if specified.
 		// If the container is privileged, this will be cleared later on.
 		if maskedPaths := securityContext.GetMaskedPaths(); maskedPaths != nil {

Vulnerable code: 	specOpts = append(specOpts, customopts.WithMounts(c.os, config, extraMounts, mountLabel))
Fixed code: const (
	etcHosts       = "/etc/hosts"
	etcHostname    = "/etc/hostname"
	resolvConfPath = "/etc/resolv.conf"
)

// WithRelabeledContainerMounts relabels the default container mounts for files in /etc
func WithRelabeledContainerMounts(mountLabel string) oci.SpecOpts {
	return func(ctx context.Context, client oci.Client, _ *containers.Container, s *runtimespec.Spec) (err error) {
		if mountLabel == "" {
			return nil
		}
		for _, m := range s.Mounts {
			switch m.Destination {
			case etcHosts, etcHostname, resolvConfPath:
				if err := label.Relabel(m.Source, mountLabel, false); err != nil {
					return err
				}
			}
		}
		return nil
	}
}

	specOpts = append(specOpts, customopts.WithMounts(c.os, config, extraMounts, mountLabel), customopts.WithRelabeledContainerMounts(mountLabel))