Skip to content

Commit 11ef8d3

Browse files
committed
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 <[email protected]>
1 parent 4bbc52c commit 11ef8d3

2 files changed

Lines changed: 129 additions & 6 deletions

File tree

daemon/graphdriver/overlay2/overlay.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ var (
113113
useNaiveDiffLock sync.Once
114114
useNaiveDiffOnly bool
115115

116-
indexOff string
116+
indexOff string
117+
userxattr string
117118
)
118119

119120
func init() {
@@ -204,7 +205,16 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
204205
logger.Warnf("Unable to detect whether overlay kernel module supports index parameter: %s", err)
205206
}
206207

207-
logger.Debugf("backingFs=%s, projectQuotaSupported=%v, indexOff=%q", backingFs, projectQuotaSupported, indexOff)
208+
needsUserXattr, err := overlayutils.NeedsUserXAttr(home)
209+
if err != nil {
210+
logger.Warnf("Unable to detect whether overlay kernel module needs \"userxattr\" parameter: %s", err)
211+
}
212+
if needsUserXattr {
213+
userxattr = "userxattr,"
214+
}
215+
216+
logger.Debugf("backingFs=%s, projectQuotaSupported=%v, indexOff=%q, userxattr=%q",
217+
backingFs, projectQuotaSupported, indexOff, userxattr)
208218

209219
return d, nil
210220
}
@@ -257,6 +267,7 @@ func (d *Driver) Status() [][2]string {
257267
{"Backing Filesystem", backingFs},
258268
{"Supports d_type", strconv.FormatBool(d.supportsDType)},
259269
{"Native Overlay Diff", strconv.FormatBool(!useNaiveDiff(d.home))},
270+
{"userxattr", strconv.FormatBool(userxattr != "")},
260271
}
261272
}
262273

@@ -546,9 +557,9 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e
546557

547558
var opts string
548559
if readonly {
549-
opts = indexOff + "lowerdir=" + diffDir + ":" + strings.Join(absLowers, ":")
560+
opts = indexOff + userxattr + "lowerdir=" + diffDir + ":" + strings.Join(absLowers, ":")
550561
} else {
551-
opts = indexOff + "lowerdir=" + strings.Join(absLowers, ":") + ",upperdir=" + diffDir + ",workdir=" + workDir
562+
opts = indexOff + userxattr + "lowerdir=" + strings.Join(absLowers, ":") + ",upperdir=" + diffDir + ",workdir=" + workDir
552563
}
553564

554565
mountData := label.FormatMountLabel(opts, mountLabel)
@@ -571,9 +582,9 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e
571582
// smaller at the expense of requiring a fork exec to chroot.
572583
if len(mountData) > pageSize-1 {
573584
if readonly {
574-
opts = indexOff + "lowerdir=" + path.Join(id, diffDirName) + ":" + string(lowers)
585+
opts = indexOff + userxattr + "lowerdir=" + path.Join(id, diffDirName) + ":" + string(lowers)
575586
} else {
576-
opts = indexOff + "lowerdir=" + string(lowers) + ",upperdir=" + path.Join(id, diffDirName) + ",workdir=" + path.Join(id, workDirName)
587+
opts = indexOff + userxattr + "lowerdir=" + string(lowers) + ",upperdir=" + path.Join(id, diffDirName) + ",workdir=" + path.Join(id, workDirName)
577588
}
578589
mountData = label.FormatMountLabel(opts, mountLabel)
579590
if len(mountData) > pageSize-1 {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// +build linux
2+
3+
// Forked from https://github.com/containerd/containerd/blob/9ade247b38b5a685244e1391c86ff41ab109556e/snapshots/overlay/check.go
4+
/*
5+
Copyright The containerd Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
package overlayutils
21+
22+
import (
23+
"fmt"
24+
"io/ioutil"
25+
"os"
26+
"path/filepath"
27+
28+
"github.com/containerd/containerd/mount"
29+
"github.com/containerd/containerd/sys"
30+
"github.com/sirupsen/logrus"
31+
)
32+
33+
// NeedsUserXAttr returns whether overlayfs should be mounted with the "userxattr" mount option.
34+
//
35+
// The "userxattr" option is needed for mounting overlayfs inside a user namespace with kernel >= 5.11.
36+
//
37+
// The "userxattr" option is NOT needed for the initial user namespace (aka "the host").
38+
//
39+
// Also, Ubuntu (since circa 2015) and Debian (since 10) with kernel < 5.11 can mount
40+
// the overlayfs in a user namespace without the "userxattr" option.
41+
//
42+
// The corresponding kernel commit: https://github.com/torvalds/linux/commit/2d2f2d7322ff43e0fe92bf8cccdc0b09449bf2e1
43+
// > ovl: user xattr
44+
// >
45+
// > Optionally allow using "user.overlay." namespace instead of "trusted.overlay."
46+
// > ...
47+
// > Disable redirect_dir and metacopy options, because these would allow privilege escalation through direct manipulation of the
48+
// > "user.overlay.redirect" or "user.overlay.metacopy" xattrs.
49+
// > ...
50+
//
51+
// The "userxattr" support is not exposed in "/sys/module/overlay/parameters".
52+
func NeedsUserXAttr(d string) (bool, error) {
53+
if !sys.RunningInUserNS() {
54+
// we are the real root (i.e., the root in the initial user NS),
55+
// so we do never need "userxattr" opt.
56+
return false, nil
57+
}
58+
59+
// TODO: add fast path for kernel >= 5.11 .
60+
//
61+
// Keep in mind that distro vendors might be going to backport the patch to older kernels.
62+
// So we can't completely remove the check.
63+
64+
tdRoot := filepath.Join(d, "userxattr-check")
65+
if err := os.RemoveAll(tdRoot); err != nil {
66+
logrus.WithError(err).Warnf("Failed to remove check directory %v", tdRoot)
67+
}
68+
69+
if err := os.MkdirAll(tdRoot, 0700); err != nil {
70+
return false, err
71+
}
72+
73+
defer func() {
74+
if err := os.RemoveAll(tdRoot); err != nil {
75+
logrus.WithError(err).Warnf("Failed to remove check directory %v", tdRoot)
76+
}
77+
}()
78+
79+
td, err := ioutil.TempDir(tdRoot, "")
80+
if err != nil {
81+
return false, err
82+
}
83+
84+
for _, dir := range []string{"lower1", "lower2", "upper", "work", "merged"} {
85+
if err := os.Mkdir(filepath.Join(td, dir), 0755); err != nil {
86+
return false, err
87+
}
88+
}
89+
90+
opts := []string{
91+
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")),
92+
"userxattr",
93+
}
94+
95+
m := mount.Mount{
96+
Type: "overlay",
97+
Source: "overlay",
98+
Options: opts,
99+
}
100+
101+
dest := filepath.Join(td, "merged")
102+
if err := m.Mount(dest); err != nil {
103+
// Probably the host is running Ubuntu/Debian kernel (< 5.11) with the userns patch but without the userxattr patch.
104+
// Return false without error.
105+
logrus.WithError(err).Debugf("cannot mount overlay with \"userxattr\", probably the kernel does not support userxattr")
106+
return false, nil
107+
}
108+
if err := mount.UnmountAll(dest, 0); err != nil {
109+
logrus.WithError(err).Warnf("Failed to unmount check directory %v", dest)
110+
}
111+
return true, nil
112+
}

0 commit comments

Comments
 (0)