Browse code

archive: do not use overlayWhiteoutConverter for UserNS

overlay2 no longer sets `archive.OverlayWhiteoutFormat` when
running in UserNS, so we can remove the complicated logic in the
archive package.

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
(cherry picked from commit 6322dfc217a3c28ea4d7c66f43394146b3862801)
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>

Akihiro Suda authored on 2021/03/23 15:22:23
Showing 5 changed files
... ...
@@ -15,7 +15,6 @@ import (
15 15
 	"strings"
16 16
 	"sync"
17 17
 
18
-	"github.com/containerd/containerd/sys"
19 18
 	"github.com/docker/docker/daemon/graphdriver"
20 19
 	"github.com/docker/docker/daemon/graphdriver/overlayutils"
21 20
 	"github.com/docker/docker/pkg/archive"
... ...
@@ -682,6 +681,7 @@ func (d *Driver) ApplyDiff(id string, parent string, diff io.Reader) (size int64
682 682
 		return d.naiveDiff.ApplyDiff(id, parent, diff)
683 683
 	}
684 684
 
685
+	// never reach here if we are running in UserNS
685 686
 	applyDir := d.getDiffPath(id)
686 687
 
687 688
 	logger.Debugf("Applying tar in %s", applyDir)
... ...
@@ -690,7 +690,6 @@ func (d *Driver) ApplyDiff(id string, parent string, diff io.Reader) (size int64
690 690
 		UIDMaps:        d.uidMaps,
691 691
 		GIDMaps:        d.gidMaps,
692 692
 		WhiteoutFormat: archive.OverlayWhiteoutFormat,
693
-		InUserNS:       sys.RunningInUserNS(),
694 693
 	}); err != nil {
695 694
 		return 0, err
696 695
 	}
... ...
@@ -721,6 +720,7 @@ func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) {
721 721
 		return d.naiveDiff.Diff(id, parent)
722 722
 	}
723 723
 
724
+	// never reach here if we are running in UserNS
724 725
 	diffPath := d.getDiffPath(id)
725 726
 	logger.Debugf("Tar with options on %s", diffPath)
726 727
 	return archive.TarWithOptions(diffPath, &archive.TarOptions{
... ...
@@ -739,13 +739,18 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
739 739
 		return nil, err
740 740
 	}
741 741
 
742
+	whiteoutConverter, err := getWhiteoutConverter(options.WhiteoutFormat, options.InUserNS)
743
+	if err != nil {
744
+		return nil, err
745
+	}
746
+
742 747
 	go func() {
743 748
 		ta := newTarAppender(
744 749
 			idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps),
745 750
 			compressWriter,
746 751
 			options.ChownOpts,
747 752
 		)
748
-		ta.WhiteoutConverter = getWhiteoutConverter(options.WhiteoutFormat, options.InUserNS)
753
+		ta.WhiteoutConverter = whiteoutConverter
749 754
 
750 755
 		defer func() {
751 756
 			// Make sure to check the error on Close.
... ...
@@ -903,7 +908,10 @@ func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) err
903 903
 	var dirs []*tar.Header
904 904
 	idMapping := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps)
905 905
 	rootIDs := idMapping.RootPair()
906
-	whiteoutConverter := getWhiteoutConverter(options.WhiteoutFormat, options.InUserNS)
906
+	whiteoutConverter, err := getWhiteoutConverter(options.WhiteoutFormat, options.InUserNS)
907
+	if err != nil {
908
+		return err
909
+	}
907 910
 
908 911
 	// Iterate through the files in the archive.
909 912
 loop:
... ...
@@ -2,29 +2,26 @@ package archive // import "github.com/docker/docker/pkg/archive"
2 2
 
3 3
 import (
4 4
 	"archive/tar"
5
-	"fmt"
6
-	"io/ioutil"
7 5
 	"os"
8 6
 	"path/filepath"
9 7
 	"strings"
10
-	"syscall"
11 8
 
12
-	"github.com/containerd/continuity/fs"
13 9
 	"github.com/docker/docker/pkg/system"
14
-	"github.com/moby/sys/mount"
15 10
 	"github.com/pkg/errors"
16 11
 	"golang.org/x/sys/unix"
17 12
 )
18 13
 
19
-func getWhiteoutConverter(format WhiteoutFormat, inUserNS bool) tarWhiteoutConverter {
14
+func getWhiteoutConverter(format WhiteoutFormat, inUserNS bool) (tarWhiteoutConverter, error) {
20 15
 	if format == OverlayWhiteoutFormat {
21
-		return overlayWhiteoutConverter{inUserNS: inUserNS}
16
+		if inUserNS {
17
+			return nil, errors.New("specifying OverlayWhiteoutFormat is not allowed in userns")
18
+		}
19
+		return overlayWhiteoutConverter{}, nil
22 20
 	}
23
-	return nil
21
+	return nil, nil
24 22
 }
25 23
 
26 24
 type overlayWhiteoutConverter struct {
27
-	inUserNS bool
28 25
 }
29 26
 
30 27
 func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, err error) {
... ...
@@ -77,13 +74,7 @@ func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (boo
77 77
 	if base == WhiteoutOpaqueDir {
78 78
 		err := unix.Setxattr(dir, "trusted.overlay.opaque", []byte{'y'}, 0)
79 79
 		if err != nil {
80
-			if c.inUserNS {
81
-				if err = replaceDirWithOverlayOpaque(dir); err != nil {
82
-					return false, errors.Wrapf(err, "replaceDirWithOverlayOpaque(%q) failed", dir)
83
-				}
84
-			} else {
85
-				return false, errors.Wrapf(err, "setxattr(%q, trusted.overlay.opaque=y)", dir)
86
-			}
80
+			return false, errors.Wrapf(err, "setxattr(%q, trusted.overlay.opaque=y)", dir)
87 81
 		}
88 82
 		// don't write the file itself
89 83
 		return false, err
... ...
@@ -95,19 +86,7 @@ func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (boo
95 95
 		originalPath := filepath.Join(dir, originalBase)
96 96
 
97 97
 		if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil {
98
-			if c.inUserNS {
99
-				// Ubuntu and a few distros support overlayfs in userns.
100
-				//
101
-				// Although we can't call mknod directly in userns (at least on bionic kernel 4.15),
102
-				// we can still create 0,0 char device using mknodChar0Overlay().
103
-				//
104
-				// NOTE: we don't need this hack for the containerd snapshotter+unpack model.
105
-				if err := mknodChar0Overlay(originalPath); err != nil {
106
-					return false, errors.Wrapf(err, "failed to mknodChar0UserNS(%q)", originalPath)
107
-				}
108
-			} else {
109
-				return false, errors.Wrapf(err, "failed to mknod(%q, S_IFCHR, 0)", originalPath)
110
-			}
98
+			return false, errors.Wrapf(err, "failed to mknod(%q, S_IFCHR, 0)", originalPath)
111 99
 		}
112 100
 		if err := os.Chown(originalPath, hdr.Uid, hdr.Gid); err != nil {
113 101
 			return false, err
... ...
@@ -119,146 +98,3 @@ func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (boo
119 119
 
120 120
 	return true, nil
121 121
 }
122
-
123
-// mknodChar0Overlay creates 0,0 char device by mounting overlayfs and unlinking.
124
-// This function can be used for creating 0,0 char device in userns on Ubuntu.
125
-//
126
-// Steps:
127
-// * Mkdir lower,upper,merged,work
128
-// * Create lower/dummy
129
-// * Mount overlayfs
130
-// * Unlink merged/dummy
131
-// * Unmount overlayfs
132
-// * Make sure a 0,0 char device is created as upper/dummy
133
-// * Rename upper/dummy to cleansedOriginalPath
134
-func mknodChar0Overlay(cleansedOriginalPath string) error {
135
-	dir := filepath.Dir(cleansedOriginalPath)
136
-	tmp, err := ioutil.TempDir(dir, "mc0o")
137
-	if err != nil {
138
-		return errors.Wrapf(err, "failed to create a tmp directory under %s", dir)
139
-	}
140
-	defer os.RemoveAll(tmp)
141
-	lower := filepath.Join(tmp, "l")
142
-	upper := filepath.Join(tmp, "u")
143
-	work := filepath.Join(tmp, "w")
144
-	merged := filepath.Join(tmp, "m")
145
-	for _, s := range []string{lower, upper, work, merged} {
146
-		if err := os.MkdirAll(s, 0700); err != nil {
147
-			return errors.Wrapf(err, "failed to mkdir %s", s)
148
-		}
149
-	}
150
-	dummyBase := "d"
151
-	lowerDummy := filepath.Join(lower, dummyBase)
152
-	if err := ioutil.WriteFile(lowerDummy, []byte{}, 0600); err != nil {
153
-		return errors.Wrapf(err, "failed to create a dummy lower file %s", lowerDummy)
154
-	}
155
-	// lowerdir needs ":" to be escaped: https://github.com/moby/moby/issues/40939#issuecomment-627098286
156
-	lowerEscaped := strings.ReplaceAll(lower, ":", "\\:")
157
-	mOpts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lowerEscaped, upper, work)
158
-	if err := mount.Mount("overlay", merged, "overlay", mOpts); err != nil {
159
-		return err
160
-	}
161
-	mergedDummy := filepath.Join(merged, dummyBase)
162
-	if err := os.Remove(mergedDummy); err != nil {
163
-		syscall.Unmount(merged, 0)
164
-		return errors.Wrapf(err, "failed to unlink %s", mergedDummy)
165
-	}
166
-	if err := syscall.Unmount(merged, 0); err != nil {
167
-		return errors.Wrapf(err, "failed to unmount %s", merged)
168
-	}
169
-	upperDummy := filepath.Join(upper, dummyBase)
170
-	if err := isChar0(upperDummy); err != nil {
171
-		return err
172
-	}
173
-	if err := os.Rename(upperDummy, cleansedOriginalPath); err != nil {
174
-		return errors.Wrapf(err, "failed to rename %s to %s", upperDummy, cleansedOriginalPath)
175
-	}
176
-	return nil
177
-}
178
-
179
-func isChar0(path string) error {
180
-	osStat, err := os.Stat(path)
181
-	if err != nil {
182
-		return errors.Wrapf(err, "failed to stat %s", path)
183
-	}
184
-	st, ok := osStat.Sys().(*syscall.Stat_t)
185
-	if !ok {
186
-		return errors.Errorf("got unsupported stat for %s", path)
187
-	}
188
-	if os.FileMode(st.Mode)&syscall.S_IFMT != syscall.S_IFCHR {
189
-		return errors.Errorf("%s is not a character device, got mode=%d", path, st.Mode)
190
-	}
191
-	if st.Rdev != 0 {
192
-		return errors.Errorf("%s is not a 0,0 character device, got Rdev=%d", path, st.Rdev)
193
-	}
194
-	return nil
195
-}
196
-
197
-// replaceDirWithOverlayOpaque replaces path with a new directory with trusted.overlay.opaque
198
-// xattr. The contents of the directory are preserved.
199
-func replaceDirWithOverlayOpaque(path string) error {
200
-	if path == "/" {
201
-		return errors.New("replaceDirWithOverlayOpaque: path must not be \"/\"")
202
-	}
203
-	dir := filepath.Dir(path)
204
-	tmp, err := ioutil.TempDir(dir, "rdwoo")
205
-	if err != nil {
206
-		return errors.Wrapf(err, "failed to create a tmp directory under %s", dir)
207
-	}
208
-	defer os.RemoveAll(tmp)
209
-	// newPath is a new empty directory crafted with trusted.overlay.opaque xattr.
210
-	// we copy the content of path into newPath, remove path, and rename newPath to path.
211
-	newPath, err := createDirWithOverlayOpaque(tmp)
212
-	if err != nil {
213
-		return errors.Wrapf(err, "createDirWithOverlayOpaque(%q) failed", tmp)
214
-	}
215
-	if err := fs.CopyDir(newPath, path); err != nil {
216
-		return errors.Wrapf(err, "CopyDir(%q, %q) failed", newPath, path)
217
-	}
218
-	if err := os.RemoveAll(path); err != nil {
219
-		return err
220
-	}
221
-	return os.Rename(newPath, path)
222
-}
223
-
224
-// createDirWithOverlayOpaque creates a directory with trusted.overlay.opaque xattr,
225
-// without calling setxattr, so as to allow creating opaque dir in userns on Ubuntu.
226
-func createDirWithOverlayOpaque(tmp string) (string, error) {
227
-	lower := filepath.Join(tmp, "l")
228
-	upper := filepath.Join(tmp, "u")
229
-	work := filepath.Join(tmp, "w")
230
-	merged := filepath.Join(tmp, "m")
231
-	for _, s := range []string{lower, upper, work, merged} {
232
-		if err := os.MkdirAll(s, 0700); err != nil {
233
-			return "", errors.Wrapf(err, "failed to mkdir %s", s)
234
-		}
235
-	}
236
-	dummyBase := "d"
237
-	lowerDummy := filepath.Join(lower, dummyBase)
238
-	if err := os.MkdirAll(lowerDummy, 0700); err != nil {
239
-		return "", errors.Wrapf(err, "failed to create a dummy lower directory %s", lowerDummy)
240
-	}
241
-	// lowerdir needs ":" to be escaped: https://github.com/moby/moby/issues/40939#issuecomment-627098286
242
-	lowerEscaped := strings.ReplaceAll(lower, ":", "\\:")
243
-	mOpts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lowerEscaped, upper, work)
244
-	if err := mount.Mount("overlay", merged, "overlay", mOpts); err != nil {
245
-		return "", err
246
-	}
247
-	mergedDummy := filepath.Join(merged, dummyBase)
248
-	if err := os.Remove(mergedDummy); err != nil {
249
-		syscall.Unmount(merged, 0)
250
-		return "", errors.Wrapf(err, "failed to rmdir %s", mergedDummy)
251
-	}
252
-	// upperDummy becomes a 0,0-char device file here
253
-	if err := os.Mkdir(mergedDummy, 0700); err != nil {
254
-		syscall.Unmount(merged, 0)
255
-		return "", errors.Wrapf(err, "failed to mkdir %s", mergedDummy)
256
-	}
257
-	// upperDummy becomes a directory with trusted.overlay.opaque xattr
258
-	// (but can't be verified in userns)
259
-	if err := syscall.Unmount(merged, 0); err != nil {
260
-		return "", errors.Wrapf(err, "failed to unmount %s", merged)
261
-	}
262
-	upperDummy := filepath.Join(upper, dummyBase)
263
-	return upperDummy, nil
264
-}
... ...
@@ -1,19 +1,14 @@
1 1
 package archive // import "github.com/docker/docker/pkg/archive"
2 2
 
3 3
 import (
4
-	"fmt"
5 4
 	"io/ioutil"
6 5
 	"os"
7
-	"os/exec"
8 6
 	"path/filepath"
9 7
 	"syscall"
10 8
 	"testing"
11 9
 
12 10
 	"github.com/containerd/containerd/sys"
13
-	"github.com/docker/docker/pkg/reexec"
14 11
 	"github.com/docker/docker/pkg/system"
15
-	"github.com/moby/sys/mount"
16
-	"github.com/pkg/errors"
17 12
 	"golang.org/x/sys/unix"
18 13
 	"gotest.tools/v3/assert"
19 14
 	"gotest.tools/v3/skip"
... ...
@@ -167,129 +162,3 @@ func TestOverlayTarAUFSUntar(t *testing.T) {
167 167
 	checkFileMode(t, filepath.Join(dst, "d2", "f1"), 0660)
168 168
 	checkFileMode(t, filepath.Join(dst, "d3", WhiteoutPrefix+"f1"), 0600)
169 169
 }
170
-
171
-func unshareCmd(cmd *exec.Cmd) {
172
-	cmd.SysProcAttr = &syscall.SysProcAttr{
173
-		Cloneflags: syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS,
174
-		UidMappings: []syscall.SysProcIDMap{
175
-			{
176
-				ContainerID: 0,
177
-				HostID:      os.Geteuid(),
178
-				Size:        1,
179
-			},
180
-		},
181
-		GidMappings: []syscall.SysProcIDMap{
182
-			{
183
-				ContainerID: 0,
184
-				HostID:      os.Getegid(),
185
-				Size:        1,
186
-			},
187
-		},
188
-	}
189
-}
190
-
191
-const (
192
-	reexecSupportsUserNSOverlay = "docker-test-supports-userns-overlay"
193
-	reexecMknodChar0            = "docker-test-userns-mknod-char0"
194
-	reexecSetOpaque             = "docker-test-userns-set-opaque"
195
-)
196
-
197
-func supportsOverlay(dir string) error {
198
-	lower := filepath.Join(dir, "l")
199
-	upper := filepath.Join(dir, "u")
200
-	work := filepath.Join(dir, "w")
201
-	merged := filepath.Join(dir, "m")
202
-	for _, s := range []string{lower, upper, work, merged} {
203
-		if err := os.MkdirAll(s, 0700); err != nil {
204
-			return err
205
-		}
206
-	}
207
-	mOpts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower, upper, work)
208
-	if err := mount.Mount("overlay", merged, "overlay", mOpts); err != nil {
209
-		return err
210
-	}
211
-	if err := mount.Unmount(merged); err != nil {
212
-		return err
213
-	}
214
-	return nil
215
-}
216
-
217
-// supportsUserNSOverlay returns nil error if overlay is supported in userns.
218
-// Only Ubuntu and a few distros support overlay in userns (by patching the kernel).
219
-// https://lists.ubuntu.com/archives/kernel-team/2014-February/038091.html
220
-// As of kernel 4.19, the patch is not merged to the upstream.
221
-func supportsUserNSOverlay() error {
222
-	tmp, err := ioutil.TempDir("", "docker-test-supports-userns-overlay")
223
-	if err != nil {
224
-		return err
225
-	}
226
-	defer os.RemoveAll(tmp)
227
-	cmd := reexec.Command(reexecSupportsUserNSOverlay, tmp)
228
-	unshareCmd(cmd)
229
-	out, err := cmd.CombinedOutput()
230
-	if err != nil {
231
-		return errors.Wrapf(err, "output: %q", string(out))
232
-	}
233
-	return nil
234
-}
235
-
236
-// isOpaque returns nil error if the dir has trusted.overlay.opaque=y.
237
-// isOpaque needs to be called in the initial userns.
238
-func isOpaque(dir string) error {
239
-	xattrOpaque, err := system.Lgetxattr(dir, "trusted.overlay.opaque")
240
-	if err != nil {
241
-		return errors.Wrapf(err, "failed to read opaque flag of %s", dir)
242
-	}
243
-	if string(xattrOpaque) != "y" {
244
-		return errors.Errorf("expected \"y\", got %q", string(xattrOpaque))
245
-	}
246
-	return nil
247
-}
248
-
249
-func TestReexecUserNSOverlayWhiteoutConverter(t *testing.T) {
250
-	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
251
-	skip.If(t, sys.RunningInUserNS(), "skipping test that requires initial userns")
252
-	if err := supportsUserNSOverlay(); err != nil {
253
-		t.Skipf("skipping test that requires kernel support for overlay-in-userns: %v", err)
254
-	}
255
-	tmp, err := ioutil.TempDir("", "docker-test-userns-overlay")
256
-	assert.NilError(t, err)
257
-	defer os.RemoveAll(tmp)
258
-
259
-	char0 := filepath.Join(tmp, "char0")
260
-	cmd := reexec.Command(reexecMknodChar0, char0)
261
-	unshareCmd(cmd)
262
-	out, err := cmd.CombinedOutput()
263
-	assert.NilError(t, err, string(out))
264
-	assert.NilError(t, isChar0(char0))
265
-
266
-	opaqueDir := filepath.Join(tmp, "opaquedir")
267
-	err = os.MkdirAll(opaqueDir, 0755)
268
-	assert.NilError(t, err, string(out))
269
-	cmd = reexec.Command(reexecSetOpaque, opaqueDir)
270
-	unshareCmd(cmd)
271
-	out, err = cmd.CombinedOutput()
272
-	assert.NilError(t, err, string(out))
273
-	assert.NilError(t, isOpaque(opaqueDir))
274
-}
275
-
276
-func init() {
277
-	reexec.Register(reexecSupportsUserNSOverlay, func() {
278
-		if err := supportsOverlay(os.Args[1]); err != nil {
279
-			panic(err)
280
-		}
281
-	})
282
-	reexec.Register(reexecMknodChar0, func() {
283
-		if err := mknodChar0Overlay(os.Args[1]); err != nil {
284
-			panic(err)
285
-		}
286
-	})
287
-	reexec.Register(reexecSetOpaque, func() {
288
-		if err := replaceDirWithOverlayOpaque(os.Args[1]); err != nil {
289
-			panic(err)
290
-		}
291
-	})
292
-	if reexec.Init() {
293
-		os.Exit(0)
294
-	}
295
-}
... ...
@@ -2,6 +2,6 @@
2 2
 
3 3
 package archive // import "github.com/docker/docker/pkg/archive"
4 4
 
5
-func getWhiteoutConverter(format WhiteoutFormat, inUserNS bool) tarWhiteoutConverter {
6
-	return nil
5
+func getWhiteoutConverter(format WhiteoutFormat, inUserNS bool) (tarWhiteoutConverter, error) {
6
+	return nil, nil
7 7
 }