Browse code

pkg/archive: add TestReexecUserNSOverlayWhiteoutConverter

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>

Akihiro Suda authored on 2018/11/29 18:25:17
Showing 1 changed files
... ...
@@ -1,14 +1,18 @@
1 1
 package archive // import "github.com/docker/docker/pkg/archive"
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"io/ioutil"
5 6
 	"os"
7
+	"os/exec"
6 8
 	"path/filepath"
7 9
 	"syscall"
8 10
 	"testing"
9 11
 
12
+	"github.com/docker/docker/pkg/reexec"
10 13
 	"github.com/docker/docker/pkg/system"
11 14
 	rsystem "github.com/opencontainers/runc/libcontainer/system"
15
+	"github.com/pkg/errors"
12 16
 	"golang.org/x/sys/unix"
13 17
 	"gotest.tools/assert"
14 18
 	"gotest.tools/skip"
... ...
@@ -162,3 +166,129 @@ func TestOverlayTarAUFSUntar(t *testing.T) {
162 162
 	checkFileMode(t, filepath.Join(dst, "d2", "f1"), 0660)
163 163
 	checkFileMode(t, filepath.Join(dst, "d3", WhiteoutPrefix+"f1"), 0600)
164 164
 }
165
+
166
+func unshareCmd(cmd *exec.Cmd) {
167
+	cmd.SysProcAttr = &syscall.SysProcAttr{
168
+		Cloneflags: syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS,
169
+		UidMappings: []syscall.SysProcIDMap{
170
+			{
171
+				ContainerID: 0,
172
+				HostID:      os.Geteuid(),
173
+				Size:        1,
174
+			},
175
+		},
176
+		GidMappings: []syscall.SysProcIDMap{
177
+			{
178
+				ContainerID: 0,
179
+				HostID:      os.Getegid(),
180
+				Size:        1,
181
+			},
182
+		},
183
+	}
184
+}
185
+
186
+const (
187
+	reexecSupportsUserNSOverlay = "docker-test-supports-userns-overlay"
188
+	reexecMknodChar0            = "docker-test-userns-mknod-char0"
189
+	reexecSetOpaque             = "docker-test-userns-set-opaque"
190
+)
191
+
192
+func supportsOverlay(dir string) error {
193
+	lower := filepath.Join(dir, "l")
194
+	upper := filepath.Join(dir, "u")
195
+	work := filepath.Join(dir, "w")
196
+	merged := filepath.Join(dir, "m")
197
+	for _, s := range []string{lower, upper, work, merged} {
198
+		if err := os.MkdirAll(s, 0700); err != nil {
199
+			return err
200
+		}
201
+	}
202
+	mOpts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower, upper, work)
203
+	if err := syscall.Mount("overlay", merged, "overlay", uintptr(0), mOpts); err != nil {
204
+		return errors.Wrapf(err, "failed to mount overlay (%s) on %s", mOpts, merged)
205
+	}
206
+	if err := syscall.Unmount(merged, 0); err != nil {
207
+		return errors.Wrapf(err, "failed to unmount %s", merged)
208
+	}
209
+	return nil
210
+}
211
+
212
+// supportsUserNSOverlay returns nil error if overlay is supported in userns.
213
+// Only Ubuntu and a few distros support overlay in userns (by patching the kernel).
214
+// https://lists.ubuntu.com/archives/kernel-team/2014-February/038091.html
215
+// As of kernel 4.19, the patch is not merged to the upstream.
216
+func supportsUserNSOverlay() error {
217
+	tmp, err := ioutil.TempDir("", "docker-test-supports-userns-overlay")
218
+	if err != nil {
219
+		return err
220
+	}
221
+	defer os.RemoveAll(tmp)
222
+	cmd := reexec.Command(reexecSupportsUserNSOverlay, tmp)
223
+	unshareCmd(cmd)
224
+	out, err := cmd.CombinedOutput()
225
+	if err != nil {
226
+		return errors.Wrapf(err, "output: %q", string(out))
227
+	}
228
+	return nil
229
+}
230
+
231
+// isOpaque returns nil error if the dir has trusted.overlay.opaque=y.
232
+// isOpaque needs to be called in the initial userns.
233
+func isOpaque(dir string) error {
234
+	xattrOpaque, err := system.Lgetxattr(dir, "trusted.overlay.opaque")
235
+	if err != nil {
236
+		return errors.Wrapf(err, "failed to read opaque flag of %s", dir)
237
+	}
238
+	if string(xattrOpaque) != "y" {
239
+		return errors.Errorf("expected \"y\", got %q", string(xattrOpaque))
240
+	}
241
+	return nil
242
+}
243
+
244
+func TestReexecUserNSOverlayWhiteoutConverter(t *testing.T) {
245
+	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
246
+	skip.If(t, rsystem.RunningInUserNS(), "skipping test that requires initial userns")
247
+	if err := supportsUserNSOverlay(); err != nil {
248
+		t.Skipf("skipping test that requires kernel support for overlay-in-userns: %v", err)
249
+	}
250
+	tmp, err := ioutil.TempDir("", "docker-test-userns-overlay")
251
+	assert.NilError(t, err)
252
+	defer os.RemoveAll(tmp)
253
+
254
+	char0 := filepath.Join(tmp, "char0")
255
+	cmd := reexec.Command(reexecMknodChar0, char0)
256
+	unshareCmd(cmd)
257
+	out, err := cmd.CombinedOutput()
258
+	assert.NilError(t, err, string(out))
259
+	assert.NilError(t, isChar0(char0))
260
+
261
+	opaqueDir := filepath.Join(tmp, "opaquedir")
262
+	err = os.MkdirAll(opaqueDir, 0755)
263
+	assert.NilError(t, err, string(out))
264
+	cmd = reexec.Command(reexecSetOpaque, opaqueDir)
265
+	unshareCmd(cmd)
266
+	out, err = cmd.CombinedOutput()
267
+	assert.NilError(t, err, string(out))
268
+	assert.NilError(t, isOpaque(opaqueDir))
269
+}
270
+
271
+func init() {
272
+	reexec.Register(reexecSupportsUserNSOverlay, func() {
273
+		if err := supportsOverlay(os.Args[1]); err != nil {
274
+			panic(err)
275
+		}
276
+	})
277
+	reexec.Register(reexecMknodChar0, func() {
278
+		if err := mknodChar0Overlay(os.Args[1]); err != nil {
279
+			panic(err)
280
+		}
281
+	})
282
+	reexec.Register(reexecSetOpaque, func() {
283
+		if err := replaceDirWithOverlayOpaque(os.Args[1]); err != nil {
284
+			panic(err)
285
+		}
286
+	})
287
+	if reexec.Init() {
288
+		os.Exit(0)
289
+	}
290
+}