From 11ef8d3ba983d6fb0b1597e715bf45e3b882a507 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Wed, 24 Feb 2021 18:36:48 +0900 Subject: [PATCH] overlay2: support "userxattr" option (kernel 5.11) The "userxattr" option is needed for mounting overlayfs inside a user namespace with kernel >= 5.11. The "userxattr" option is NOT needed for the initial user namespace (aka "the host"). Also, Ubuntu (since circa 2015) and Debian (since 10) with kernel < 5.11 can mount the overlayfs in a user namespace without the "userxattr" option. The corresponding kernel commit: 2d2f2d7322ff43e0fe92bf8cccdc0b09449bf2e1 > **ovl: user xattr** > > Optionally allow using "user.overlay." namespace instead of "trusted.overlay." > ... > Disable redirect_dir and metacopy options, because these would allow privilege escalation through direct manipulation of the > "user.overlay.redirect" or "user.overlay.metacopy" xattrs. Fix issue 42055 Related to containerd/containerd PR 5076 Signed-off-by: Akihiro Suda --- daemon/graphdriver/overlay2/overlay.go | 23 +++- daemon/graphdriver/overlayutils/userxattr.go | 112 +++++++++++++++++++ 2 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 daemon/graphdriver/overlayutils/userxattr.go diff --git a/daemon/graphdriver/overlay2/overlay.go b/daemon/graphdriver/overlay2/overlay.go index e26fa07c82..38ce92caf7 100644 --- a/daemon/graphdriver/overlay2/overlay.go +++ b/daemon/graphdriver/overlay2/overlay.go @@ -113,7 +113,8 @@ var ( useNaiveDiffLock sync.Once useNaiveDiffOnly bool - indexOff string + indexOff string + userxattr string ) func init() { @@ -204,7 +205,16 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap logger.Warnf("Unable to detect whether overlay kernel module supports index parameter: %s", err) } - logger.Debugf("backingFs=%s, projectQuotaSupported=%v, indexOff=%q", backingFs, projectQuotaSupported, indexOff) + needsUserXattr, err := overlayutils.NeedsUserXAttr(home) + if err != nil { + logger.Warnf("Unable to detect whether overlay kernel module needs \"userxattr\" parameter: %s", err) + } + if needsUserXattr { + userxattr = "userxattr," + } + + logger.Debugf("backingFs=%s, projectQuotaSupported=%v, indexOff=%q, userxattr=%q", + backingFs, projectQuotaSupported, indexOff, userxattr) return d, nil } @@ -257,6 +267,7 @@ func (d *Driver) Status() [][2]string { {"Backing Filesystem", backingFs}, {"Supports d_type", strconv.FormatBool(d.supportsDType)}, {"Native Overlay Diff", strconv.FormatBool(!useNaiveDiff(d.home))}, + {"userxattr", strconv.FormatBool(userxattr != "")}, } } @@ -546,9 +557,9 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e var opts string if readonly { - opts = indexOff + "lowerdir=" + diffDir + ":" + strings.Join(absLowers, ":") + opts = indexOff + userxattr + "lowerdir=" + diffDir + ":" + strings.Join(absLowers, ":") } else { - opts = indexOff + "lowerdir=" + strings.Join(absLowers, ":") + ",upperdir=" + diffDir + ",workdir=" + workDir + opts = indexOff + userxattr + "lowerdir=" + strings.Join(absLowers, ":") + ",upperdir=" + diffDir + ",workdir=" + workDir } mountData := label.FormatMountLabel(opts, mountLabel) @@ -571,9 +582,9 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e // smaller at the expense of requiring a fork exec to chroot. if len(mountData) > pageSize-1 { if readonly { - opts = indexOff + "lowerdir=" + path.Join(id, diffDirName) + ":" + string(lowers) + opts = indexOff + userxattr + "lowerdir=" + path.Join(id, diffDirName) + ":" + string(lowers) } else { - opts = indexOff + "lowerdir=" + string(lowers) + ",upperdir=" + path.Join(id, diffDirName) + ",workdir=" + path.Join(id, workDirName) + opts = indexOff + userxattr + "lowerdir=" + string(lowers) + ",upperdir=" + path.Join(id, diffDirName) + ",workdir=" + path.Join(id, workDirName) } mountData = label.FormatMountLabel(opts, mountLabel) if len(mountData) > pageSize-1 { diff --git a/daemon/graphdriver/overlayutils/userxattr.go b/daemon/graphdriver/overlayutils/userxattr.go new file mode 100644 index 0000000000..492560f1b5 --- /dev/null +++ b/daemon/graphdriver/overlayutils/userxattr.go @@ -0,0 +1,112 @@ +// +build linux + +// Forked from https://github.com/containerd/containerd/blob/9ade247b38b5a685244e1391c86ff41ab109556e/snapshots/overlay/check.go +/* + Copyright 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 overlayutils + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/containerd/containerd/mount" + "github.com/containerd/containerd/sys" + "github.com/sirupsen/logrus" +) + +// NeedsUserXAttr returns whether overlayfs should be mounted with the "userxattr" mount option. +// +// The "userxattr" option is needed for mounting overlayfs inside a user namespace with kernel >= 5.11. +// +// The "userxattr" option is NOT needed for the initial user namespace (aka "the host"). +// +// Also, Ubuntu (since circa 2015) and Debian (since 10) with kernel < 5.11 can mount +// the overlayfs in a user namespace without the "userxattr" option. +// +// The corresponding kernel commit: https://github.com/torvalds/linux/commit/2d2f2d7322ff43e0fe92bf8cccdc0b09449bf2e1 +// > ovl: user xattr +// > +// > Optionally allow using "user.overlay." namespace instead of "trusted.overlay." +// > ... +// > Disable redirect_dir and metacopy options, because these would allow privilege escalation through direct manipulation of the +// > "user.overlay.redirect" or "user.overlay.metacopy" xattrs. +// > ... +// +// The "userxattr" support is not exposed in "/sys/module/overlay/parameters". +func NeedsUserXAttr(d string) (bool, error) { + if !sys.RunningInUserNS() { + // we are the real root (i.e., the root in the initial user NS), + // so we do never need "userxattr" opt. + return false, nil + } + + // TODO: add fast path for kernel >= 5.11 . + // + // Keep in mind that distro vendors might be going to backport the patch to older kernels. + // So we can't completely remove the check. + + tdRoot := filepath.Join(d, "userxattr-check") + if err := os.RemoveAll(tdRoot); err != nil { + logrus.WithError(err).Warnf("Failed to remove check directory %v", tdRoot) + } + + if err := os.MkdirAll(tdRoot, 0700); err != nil { + return false, err + } + + defer func() { + if err := os.RemoveAll(tdRoot); err != nil { + logrus.WithError(err).Warnf("Failed to remove check directory %v", tdRoot) + } + }() + + td, err := ioutil.TempDir(tdRoot, "") + if err != nil { + return false, err + } + + for _, dir := range []string{"lower1", "lower2", "upper", "work", "merged"} { + if err := os.Mkdir(filepath.Join(td, dir), 0755); err != nil { + return false, err + } + } + + opts := []string{ + fmt.Sprintf("lowerdir=%s:%s,upperdir=%s,workdir=%s", filepath.Join(td, "lower2"), filepath.Join(td, "lower1"), filepath.Join(td, "upper"), filepath.Join(td, "work")), + "userxattr", + } + + m := mount.Mount{ + Type: "overlay", + Source: "overlay", + Options: opts, + } + + dest := filepath.Join(td, "merged") + if err := m.Mount(dest); err != nil { + // Probably the host is running Ubuntu/Debian kernel (< 5.11) with the userns patch but without the userxattr patch. + // Return false without error. + logrus.WithError(err).Debugf("cannot mount overlay with \"userxattr\", probably the kernel does not support userxattr") + return false, nil + } + if err := mount.UnmountAll(dest, 0); err != nil { + logrus.WithError(err).Warnf("Failed to unmount check directory %v", dest) + } + return true, nil +}