Browse code

Only chown network files within container metadata

If the user specifies a mountpath from the host, we should not be
attempting to chown files outside the daemon's metadata directory
(represented by `daemon.repository` at init time).

This forces users who want to use user namespaces to handle the
ownership needs of any external files mounted as network files
(/etc/resolv.conf, /etc/hosts, /etc/hostname) separately from the
daemon. In all other volume/bind mount situations we have taken this
same line--we don't chown host file content.

Docker-DCO-1.1-Signed-off-by: Phil Estes <estesp@linux.vnet.ibm.com>

Phil Estes authored on 2017/07/23 11:11:09
Showing 3 changed files
... ...
@@ -86,8 +86,13 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er
86 86
 	// remapped root (user namespaces)
87 87
 	rootIDs := daemon.idMappings.RootPair()
88 88
 	for _, mount := range netMounts {
89
-		if err := os.Chown(mount.Source, rootIDs.UID, rootIDs.GID); err != nil {
90
-			return nil, err
89
+		// we should only modify ownership of network files within our own container
90
+		// metadata repository. If the user specifies a mount path external, it is
91
+		// up to the user to make sure the file has proper ownership for userns
92
+		if strings.Index(mount.Source, daemon.repository) == 0 {
93
+			if err := os.Chown(mount.Source, rootIDs.UID, rootIDs.GID); err != nil {
94
+				return nil, err
95
+			}
91 96
 		}
92 97
 	}
93 98
 	return append(mounts, netMounts...), nil
94 99
new file mode 100644
... ...
@@ -0,0 +1,77 @@
0
+// +build !windows
1
+
2
+package main
3
+
4
+import (
5
+	"io/ioutil"
6
+	"os"
7
+	"path/filepath"
8
+
9
+	"github.com/docker/docker/api/types"
10
+	containertypes "github.com/docker/docker/api/types/container"
11
+	mounttypes "github.com/docker/docker/api/types/mount"
12
+	networktypes "github.com/docker/docker/api/types/network"
13
+	"github.com/docker/docker/client"
14
+	"github.com/docker/docker/integration-cli/checker"
15
+	"github.com/docker/docker/pkg/ioutils"
16
+	"github.com/docker/docker/pkg/system"
17
+	"github.com/go-check/check"
18
+	"github.com/stretchr/testify/assert"
19
+	"golang.org/x/net/context"
20
+)
21
+
22
+func (s *DockerSuite) TestContainersAPINetworkMountsNoChown(c *check.C) {
23
+	// chown only applies to Linux bind mounted volumes; must be same host to verify
24
+	testRequires(c, DaemonIsLinux, SameHostDaemon)
25
+
26
+	tmpDir, err := ioutils.TempDir("", "test-network-mounts")
27
+	c.Assert(err, checker.IsNil)
28
+	defer os.RemoveAll(tmpDir)
29
+
30
+	// make tmp dir readable by anyone to allow userns process to mount from
31
+	err = os.Chmod(tmpDir, 0755)
32
+	c.Assert(err, checker.IsNil)
33
+	// create temp files to use as network mounts
34
+	tmpNWFileMount := filepath.Join(tmpDir, "nwfile")
35
+
36
+	err = ioutil.WriteFile(tmpNWFileMount, []byte("network file bind mount"), 0644)
37
+	c.Assert(err, checker.IsNil)
38
+
39
+	config := containertypes.Config{
40
+		Image: "busybox",
41
+	}
42
+	hostConfig := containertypes.HostConfig{
43
+		Mounts: []mounttypes.Mount{
44
+			{
45
+				Type:   "bind",
46
+				Source: tmpNWFileMount,
47
+				Target: "/etc/resolv.conf",
48
+			},
49
+			{
50
+				Type:   "bind",
51
+				Source: tmpNWFileMount,
52
+				Target: "/etc/hostname",
53
+			},
54
+			{
55
+				Type:   "bind",
56
+				Source: tmpNWFileMount,
57
+				Target: "/etc/hosts",
58
+			},
59
+		},
60
+	}
61
+
62
+	cli, err := client.NewEnvClient()
63
+	c.Assert(err, checker.IsNil)
64
+	defer cli.Close()
65
+
66
+	ctrCreate, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "")
67
+	c.Assert(err, checker.IsNil)
68
+	// container will exit immediately because of no tty, but we only need the start sequence to test the condition
69
+	err = cli.ContainerStart(context.Background(), ctrCreate.ID, types.ContainerStartOptions{})
70
+	c.Assert(err, checker.IsNil)
71
+
72
+	// check that host-located bind mount network file did not change ownership when the container was started
73
+	statT, err := system.Stat(tmpNWFileMount)
74
+	c.Assert(err, checker.IsNil)
75
+	assert.Equal(c, uint32(0), statT.UID(), "bind mounted network file should not change ownership from root")
76
+}
... ...
@@ -3115,6 +3115,11 @@ func (s *DockerSuite) TestRunNetworkFilesBindMount(c *check.C) {
3115 3115
 	filename := createTmpFile(c, expected)
3116 3116
 	defer os.Remove(filename)
3117 3117
 
3118
+	// for user namespaced test runs, the temp file must be accessible to unprivileged root
3119
+	if err := os.Chmod(filename, 0646); err != nil {
3120
+		c.Fatalf("error modifying permissions of %s: %v", filename, err)
3121
+	}
3122
+
3118 3123
 	nwfiles := []string{"/etc/resolv.conf", "/etc/hosts", "/etc/hostname"}
3119 3124
 
3120 3125
 	for i := range nwfiles {
... ...
@@ -3132,6 +3137,11 @@ func (s *DockerSuite) TestRunNetworkFilesBindMountRO(c *check.C) {
3132 3132
 	filename := createTmpFile(c, "test123")
3133 3133
 	defer os.Remove(filename)
3134 3134
 
3135
+	// for user namespaced test runs, the temp file must be accessible to unprivileged root
3136
+	if err := os.Chmod(filename, 0646); err != nil {
3137
+		c.Fatalf("error modifying permissions of %s: %v", filename, err)
3138
+	}
3139
+
3135 3140
 	nwfiles := []string{"/etc/resolv.conf", "/etc/hosts", "/etc/hostname"}
3136 3141
 
3137 3142
 	for i := range nwfiles {
... ...
@@ -3149,6 +3159,11 @@ func (s *DockerSuite) TestRunNetworkFilesBindMountROFilesystem(c *check.C) {
3149 3149
 	filename := createTmpFile(c, "test123")
3150 3150
 	defer os.Remove(filename)
3151 3151
 
3152
+	// for user namespaced test runs, the temp file must be accessible to unprivileged root
3153
+	if err := os.Chmod(filename, 0646); err != nil {
3154
+		c.Fatalf("error modifying permissions of %s: %v", filename, err)
3155
+	}
3156
+
3152 3157
 	nwfiles := []string{"/etc/resolv.conf", "/etc/hosts", "/etc/hostname"}
3153 3158
 
3154 3159
 	for i := range nwfiles {