Make container shm parent unbindable
| ... | ... |
@@ -1022,14 +1022,23 @@ func (container *Container) InitializeStdio(iop *cio.DirectIO) (cio.IO, error) {
|
| 1022 | 1022 |
return &rio{IO: iop, sc: container.StreamConfig}, nil
|
| 1023 | 1023 |
} |
| 1024 | 1024 |
|
| 1025 |
+// MountsResourcePath returns the path where mounts are stored for the given mount |
|
| 1026 |
+func (container *Container) MountsResourcePath(mount string) (string, error) {
|
|
| 1027 |
+ return container.GetRootResourcePath(filepath.Join("mounts", mount))
|
|
| 1028 |
+} |
|
| 1029 |
+ |
|
| 1025 | 1030 |
// SecretMountPath returns the path of the secret mount for the container |
| 1026 |
-func (container *Container) SecretMountPath() string {
|
|
| 1027 |
- return filepath.Join(container.Root, "secrets") |
|
| 1031 |
+func (container *Container) SecretMountPath() (string, error) {
|
|
| 1032 |
+ return container.MountsResourcePath("secrets")
|
|
| 1028 | 1033 |
} |
| 1029 | 1034 |
|
| 1030 | 1035 |
// SecretFilePath returns the path to the location of a secret on the host. |
| 1031 |
-func (container *Container) SecretFilePath(secretRef swarmtypes.SecretReference) string {
|
|
| 1032 |
- return filepath.Join(container.SecretMountPath(), secretRef.SecretID) |
|
| 1036 |
+func (container *Container) SecretFilePath(secretRef swarmtypes.SecretReference) (string, error) {
|
|
| 1037 |
+ secrets, err := container.SecretMountPath() |
|
| 1038 |
+ if err != nil {
|
|
| 1039 |
+ return "", err |
|
| 1040 |
+ } |
|
| 1041 |
+ return filepath.Join(secrets, secretRef.SecretID), nil |
|
| 1033 | 1042 |
} |
| 1034 | 1043 |
|
| 1035 | 1044 |
func getSecretTargetPath(r *swarmtypes.SecretReference) string {
|
| ... | ... |
@@ -1042,13 +1051,17 @@ func getSecretTargetPath(r *swarmtypes.SecretReference) string {
|
| 1042 | 1042 |
|
| 1043 | 1043 |
// ConfigsDirPath returns the path to the directory where configs are stored on |
| 1044 | 1044 |
// disk. |
| 1045 |
-func (container *Container) ConfigsDirPath() string {
|
|
| 1046 |
- return filepath.Join(container.Root, "configs") |
|
| 1045 |
+func (container *Container) ConfigsDirPath() (string, error) {
|
|
| 1046 |
+ return container.GetRootResourcePath("configs")
|
|
| 1047 | 1047 |
} |
| 1048 | 1048 |
|
| 1049 | 1049 |
// ConfigFilePath returns the path to the on-disk location of a config. |
| 1050 |
-func (container *Container) ConfigFilePath(configRef swarmtypes.ConfigReference) string {
|
|
| 1051 |
- return filepath.Join(container.ConfigsDirPath(), configRef.ConfigID) |
|
| 1050 |
+func (container *Container) ConfigFilePath(configRef swarmtypes.ConfigReference) (string, error) {
|
|
| 1051 |
+ configs, err := container.ConfigsDirPath() |
|
| 1052 |
+ if err != nil {
|
|
| 1053 |
+ return "", err |
|
| 1054 |
+ } |
|
| 1055 |
+ return filepath.Join(configs, configRef.ConfigID), nil |
|
| 1052 | 1056 |
} |
| 1053 | 1057 |
|
| 1054 | 1058 |
// CreateDaemonEnvironment creates a new environment variable slice for this container. |
| ... | ... |
@@ -151,7 +151,7 @@ func (container *Container) CopyImagePathContent(v volume.Volume, destination st |
| 151 | 151 |
|
| 152 | 152 |
// ShmResourcePath returns path to shm |
| 153 | 153 |
func (container *Container) ShmResourcePath() (string, error) {
|
| 154 |
- return container.GetRootResourcePath("shm")
|
|
| 154 |
+ return container.MountsResourcePath("shm")
|
|
| 155 | 155 |
} |
| 156 | 156 |
|
| 157 | 157 |
// HasMountFor checks if path is a mountpoint |
| ... | ... |
@@ -218,49 +218,61 @@ func (container *Container) IpcMounts() []Mount {
|
| 218 | 218 |
} |
| 219 | 219 |
|
| 220 | 220 |
// SecretMounts returns the mounts for the secret path. |
| 221 |
-func (container *Container) SecretMounts() []Mount {
|
|
| 221 |
+func (container *Container) SecretMounts() ([]Mount, error) {
|
|
| 222 | 222 |
var mounts []Mount |
| 223 | 223 |
for _, r := range container.SecretReferences {
|
| 224 | 224 |
if r.File == nil {
|
| 225 | 225 |
continue |
| 226 | 226 |
} |
| 227 |
+ src, err := container.SecretFilePath(*r) |
|
| 228 |
+ if err != nil {
|
|
| 229 |
+ return nil, err |
|
| 230 |
+ } |
|
| 227 | 231 |
mounts = append(mounts, Mount{
|
| 228 |
- Source: container.SecretFilePath(*r), |
|
| 232 |
+ Source: src, |
|
| 229 | 233 |
Destination: getSecretTargetPath(r), |
| 230 | 234 |
Writable: false, |
| 231 | 235 |
}) |
| 232 | 236 |
} |
| 233 | 237 |
|
| 234 |
- return mounts |
|
| 238 |
+ return mounts, nil |
|
| 235 | 239 |
} |
| 236 | 240 |
|
| 237 | 241 |
// UnmountSecrets unmounts the local tmpfs for secrets |
| 238 | 242 |
func (container *Container) UnmountSecrets() error {
|
| 239 |
- if _, err := os.Stat(container.SecretMountPath()); err != nil {
|
|
| 243 |
+ p, err := container.SecretMountPath() |
|
| 244 |
+ if err != nil {
|
|
| 245 |
+ return err |
|
| 246 |
+ } |
|
| 247 |
+ if _, err := os.Stat(p); err != nil {
|
|
| 240 | 248 |
if os.IsNotExist(err) {
|
| 241 | 249 |
return nil |
| 242 | 250 |
} |
| 243 | 251 |
return err |
| 244 | 252 |
} |
| 245 | 253 |
|
| 246 |
- return detachMounted(container.SecretMountPath()) |
|
| 254 |
+ return mount.RecursiveUnmount(p) |
|
| 247 | 255 |
} |
| 248 | 256 |
|
| 249 | 257 |
// ConfigMounts returns the mounts for configs. |
| 250 |
-func (container *Container) ConfigMounts() []Mount {
|
|
| 258 |
+func (container *Container) ConfigMounts() ([]Mount, error) {
|
|
| 251 | 259 |
var mounts []Mount |
| 252 | 260 |
for _, configRef := range container.ConfigReferences {
|
| 253 | 261 |
if configRef.File == nil {
|
| 254 | 262 |
continue |
| 255 | 263 |
} |
| 264 |
+ src, err := container.ConfigFilePath(*configRef) |
|
| 265 |
+ if err != nil {
|
|
| 266 |
+ return nil, err |
|
| 267 |
+ } |
|
| 256 | 268 |
mounts = append(mounts, Mount{
|
| 257 |
- Source: container.ConfigFilePath(*configRef), |
|
| 269 |
+ Source: src, |
|
| 258 | 270 |
Destination: configRef.File.Name, |
| 259 | 271 |
Writable: false, |
| 260 | 272 |
}) |
| 261 | 273 |
} |
| 262 | 274 |
|
| 263 |
- return mounts |
|
| 275 |
+ return mounts, nil |
|
| 264 | 276 |
} |
| 265 | 277 |
|
| 266 | 278 |
type conflictingUpdateOptions string |
| ... | ... |
@@ -54,22 +54,30 @@ func (container *Container) CreateSecretSymlinks() error {
|
| 54 | 54 |
// SecretMounts returns the mount for the secret path. |
| 55 | 55 |
// All secrets are stored in a single mount on Windows. Target symlinks are |
| 56 | 56 |
// created for each secret, pointing to the files in this mount. |
| 57 |
-func (container *Container) SecretMounts() []Mount {
|
|
| 57 |
+func (container *Container) SecretMounts() ([]Mount, error) {
|
|
| 58 | 58 |
var mounts []Mount |
| 59 | 59 |
if len(container.SecretReferences) > 0 {
|
| 60 |
+ src, err := container.SecretMountPath() |
|
| 61 |
+ if err != nil {
|
|
| 62 |
+ return nil, err |
|
| 63 |
+ } |
|
| 60 | 64 |
mounts = append(mounts, Mount{
|
| 61 |
- Source: container.SecretMountPath(), |
|
| 65 |
+ Source: src, |
|
| 62 | 66 |
Destination: containerInternalSecretMountPath, |
| 63 | 67 |
Writable: false, |
| 64 | 68 |
}) |
| 65 | 69 |
} |
| 66 | 70 |
|
| 67 |
- return mounts |
|
| 71 |
+ return mounts, nil |
|
| 68 | 72 |
} |
| 69 | 73 |
|
| 70 | 74 |
// UnmountSecrets unmounts the fs for secrets |
| 71 | 75 |
func (container *Container) UnmountSecrets() error {
|
| 72 |
- return os.RemoveAll(container.SecretMountPath()) |
|
| 76 |
+ p, err := container.SecretMountPath() |
|
| 77 |
+ if err != nil {
|
|
| 78 |
+ return err |
|
| 79 |
+ } |
|
| 80 |
+ return os.RemoveAll(p) |
|
| 73 | 81 |
} |
| 74 | 82 |
|
| 75 | 83 |
// CreateConfigSymlinks creates symlinks to files in the config mount. |
| ... | ... |
@@ -96,17 +104,21 @@ func (container *Container) CreateConfigSymlinks() error {
|
| 96 | 96 |
// ConfigMounts returns the mount for configs. |
| 97 | 97 |
// All configs are stored in a single mount on Windows. Target symlinks are |
| 98 | 98 |
// created for each config, pointing to the files in this mount. |
| 99 |
-func (container *Container) ConfigMounts() []Mount {
|
|
| 99 |
+func (container *Container) ConfigMounts() ([]Mount, error) {
|
|
| 100 | 100 |
var mounts []Mount |
| 101 | 101 |
if len(container.ConfigReferences) > 0 {
|
| 102 |
+ src, err := container.ConfigsDirPath() |
|
| 103 |
+ if err != nil {
|
|
| 104 |
+ return nil, err |
|
| 105 |
+ } |
|
| 102 | 106 |
mounts = append(mounts, Mount{
|
| 103 |
- Source: container.ConfigsDirPath(), |
|
| 107 |
+ Source: src, |
|
| 104 | 108 |
Destination: containerInternalConfigsDirPath, |
| 105 | 109 |
Writable: false, |
| 106 | 110 |
}) |
| 107 | 111 |
} |
| 108 | 112 |
|
| 109 |
- return mounts |
|
| 113 |
+ return mounts, nil |
|
| 110 | 114 |
} |
| 111 | 115 |
|
| 112 | 116 |
// DetachAndUnmount unmounts all volumes. |
| ... | ... |
@@ -165,7 +165,10 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
|
| 165 | 165 |
return nil |
| 166 | 166 |
} |
| 167 | 167 |
|
| 168 |
- localMountPath := c.SecretMountPath() |
|
| 168 |
+ localMountPath, err := c.SecretMountPath() |
|
| 169 |
+ if err != nil {
|
|
| 170 |
+ return errors.Wrap(err, "error getting secrets mount dir") |
|
| 171 |
+ } |
|
| 169 | 172 |
logrus.Debugf("secrets: setting up secret dir: %s", localMountPath)
|
| 170 | 173 |
|
| 171 | 174 |
// retrieve possible remapped range start for root UID, GID |
| ... | ... |
@@ -204,7 +207,10 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
|
| 204 | 204 |
|
| 205 | 205 |
// secrets are created in the SecretMountPath on the host, at a |
| 206 | 206 |
// single level |
| 207 |
- fPath := c.SecretFilePath(*s) |
|
| 207 |
+ fPath, err := c.SecretFilePath(*s) |
|
| 208 |
+ if err != nil {
|
|
| 209 |
+ return errors.Wrap(err, "error getting secret file path") |
|
| 210 |
+ } |
|
| 208 | 211 |
if err := idtools.MkdirAllAndChown(filepath.Dir(fPath), 0700, rootIDs); err != nil {
|
| 209 | 212 |
return errors.Wrap(err, "error creating secret mount path") |
| 210 | 213 |
} |
| ... | ... |
@@ -250,7 +256,10 @@ func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) {
|
| 250 | 250 |
return nil |
| 251 | 251 |
} |
| 252 | 252 |
|
| 253 |
- localPath := c.ConfigsDirPath() |
|
| 253 |
+ localPath, err := c.ConfigsDirPath() |
|
| 254 |
+ if err != nil {
|
|
| 255 |
+ return err |
|
| 256 |
+ } |
|
| 254 | 257 |
logrus.Debugf("configs: setting up config dir: %s", localPath)
|
| 255 | 258 |
|
| 256 | 259 |
// retrieve possible remapped range start for root UID, GID |
| ... | ... |
@@ -279,7 +288,10 @@ func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) {
|
| 279 | 279 |
continue |
| 280 | 280 |
} |
| 281 | 281 |
|
| 282 |
- fPath := c.ConfigFilePath(*configRef) |
|
| 282 |
+ fPath, err := c.ConfigFilePath(*configRef) |
|
| 283 |
+ if err != nil {
|
|
| 284 |
+ return err |
|
| 285 |
+ } |
|
| 283 | 286 |
|
| 284 | 287 |
log := logrus.WithFields(logrus.Fields{"name": configRef.File.Name, "path": fPath})
|
| 285 | 288 |
|
| ... | ... |
@@ -379,3 +391,22 @@ func (daemon *Daemon) initializeNetworkingPaths(container *container.Container, |
| 379 | 379 |
container.ResolvConfPath = nc.ResolvConfPath |
| 380 | 380 |
return nil |
| 381 | 381 |
} |
| 382 |
+ |
|
| 383 |
+func (daemon *Daemon) setupContainerMountsRoot(c *container.Container) error {
|
|
| 384 |
+ // get the root mount path so we can make it unbindable |
|
| 385 |
+ p, err := c.MountsResourcePath("")
|
|
| 386 |
+ if err != nil {
|
|
| 387 |
+ return err |
|
| 388 |
+ } |
|
| 389 |
+ |
|
| 390 |
+ if err := idtools.MkdirAllAndChown(p, 0700, daemon.idMappings.RootPair()); err != nil {
|
|
| 391 |
+ return err |
|
| 392 |
+ } |
|
| 393 |
+ |
|
| 394 |
+ if err := mount.MakeUnbindable(p); err != nil {
|
|
| 395 |
+ // Setting unbindable is a precaution and is not neccessary for correct operation. |
|
| 396 |
+ // Do not error out if this fails. |
|
| 397 |
+ logrus.WithError(err).WithField("resource", p).WithField("container", c.ID).Warn("Error setting container resource mounts to unbindable, this may cause mount leakages, preventing removal of this container.")
|
|
| 398 |
+ } |
|
| 399 |
+ return nil |
|
| 400 |
+} |
| ... | ... |
@@ -21,7 +21,10 @@ func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) {
|
| 21 | 21 |
return nil |
| 22 | 22 |
} |
| 23 | 23 |
|
| 24 |
- localPath := c.ConfigsDirPath() |
|
| 24 |
+ localPath, err := c.ConfigsDirPath() |
|
| 25 |
+ if err != nil {
|
|
| 26 |
+ return err |
|
| 27 |
+ } |
|
| 25 | 28 |
logrus.Debugf("configs: setting up config dir: %s", localPath)
|
| 26 | 29 |
|
| 27 | 30 |
// create local config root |
| ... | ... |
@@ -48,7 +51,10 @@ func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) {
|
| 48 | 48 |
continue |
| 49 | 49 |
} |
| 50 | 50 |
|
| 51 |
- fPath := c.ConfigFilePath(*configRef) |
|
| 51 |
+ fPath, err := c.ConfigFilePath(*configRef) |
|
| 52 |
+ if err != nil {
|
|
| 53 |
+ return err |
|
| 54 |
+ } |
|
| 52 | 55 |
|
| 53 | 56 |
log := logrus.WithFields(logrus.Fields{"name": configRef.File.Name, "path": fPath})
|
| 54 | 57 |
|
| ... | ... |
@@ -94,7 +100,10 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
|
| 94 | 94 |
return nil |
| 95 | 95 |
} |
| 96 | 96 |
|
| 97 |
- localMountPath := c.SecretMountPath() |
|
| 97 |
+ localMountPath, err := c.SecretMountPath() |
|
| 98 |
+ if err != nil {
|
|
| 99 |
+ return err |
|
| 100 |
+ } |
|
| 98 | 101 |
logrus.Debugf("secrets: setting up secret dir: %s", localMountPath)
|
| 99 | 102 |
|
| 100 | 103 |
// create local secret root |
| ... | ... |
@@ -123,7 +132,10 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
|
| 123 | 123 |
|
| 124 | 124 |
// secrets are created in the SecretMountPath on the host, at a |
| 125 | 125 |
// single level |
| 126 |
- fPath := c.SecretFilePath(*s) |
|
| 126 |
+ fPath, err := c.SecretFilePath(*s) |
|
| 127 |
+ if err != nil {
|
|
| 128 |
+ return err |
|
| 129 |
+ } |
|
| 127 | 130 |
logrus.WithFields(logrus.Fields{
|
| 128 | 131 |
"name": s.File.Name, |
| 129 | 132 |
"path": fPath, |
| ... | ... |
@@ -812,6 +812,10 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
| 812 | 812 |
return nil, fmt.Errorf("linux seccomp: %v", err)
|
| 813 | 813 |
} |
| 814 | 814 |
|
| 815 |
+ if err := daemon.setupContainerMountsRoot(c); err != nil {
|
|
| 816 |
+ return nil, err |
|
| 817 |
+ } |
|
| 818 |
+ |
|
| 815 | 819 |
if err := daemon.setupIpcDirs(c); err != nil {
|
| 816 | 820 |
return nil, err |
| 817 | 821 |
} |
| ... | ... |
@@ -839,11 +843,17 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
| 839 | 839 |
} |
| 840 | 840 |
ms = append(ms, tmpfsMounts...) |
| 841 | 841 |
|
| 842 |
- if m := c.SecretMounts(); m != nil {
|
|
| 843 |
- ms = append(ms, m...) |
|
| 842 |
+ secretMounts, err := c.SecretMounts() |
|
| 843 |
+ if err != nil {
|
|
| 844 |
+ return nil, err |
|
| 844 | 845 |
} |
| 846 |
+ ms = append(ms, secretMounts...) |
|
| 845 | 847 |
|
| 846 |
- ms = append(ms, c.ConfigMounts()...) |
|
| 848 |
+ configMounts, err := c.ConfigMounts() |
|
| 849 |
+ if err != nil {
|
|
| 850 |
+ return nil, err |
|
| 851 |
+ } |
|
| 852 |
+ ms = append(ms, configMounts...) |
|
| 847 | 853 |
|
| 848 | 854 |
sort.Sort(mounts(ms)) |
| 849 | 855 |
if err := setMounts(daemon, &s, c, ms); err != nil {
|
| ... | ... |
@@ -93,12 +93,20 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
| 93 | 93 |
} |
| 94 | 94 |
} |
| 95 | 95 |
|
| 96 |
- if m := c.SecretMounts(); m != nil {
|
|
| 97 |
- mounts = append(mounts, m...) |
|
| 96 |
+ secretMounts, err := c.SecretMounts() |
|
| 97 |
+ if err != nil {
|
|
| 98 |
+ return nil, err |
|
| 99 |
+ } |
|
| 100 |
+ if secretMounts != nil {
|
|
| 101 |
+ mounts = append(mounts, secretMounts...) |
|
| 98 | 102 |
} |
| 99 | 103 |
|
| 100 |
- if m := c.ConfigMounts(); m != nil {
|
|
| 101 |
- mounts = append(mounts, m...) |
|
| 104 |
+ configMounts, err := c.ConfigMounts() |
|
| 105 |
+ if err != nil {
|
|
| 106 |
+ return nil, err |
|
| 107 |
+ } |
|
| 108 |
+ if configMounts != nil {
|
|
| 109 |
+ mounts = append(mounts, configMounts...) |
|
| 102 | 110 |
} |
| 103 | 111 |
|
| 104 | 112 |
for _, mount := range mounts {
|
| ... | ... |
@@ -9,6 +9,7 @@ import ( |
| 9 | 9 |
containertypes "github.com/docker/docker/api/types/container" |
| 10 | 10 |
"github.com/docker/docker/container" |
| 11 | 11 |
"github.com/docker/docker/errdefs" |
| 12 |
+ "github.com/docker/docker/pkg/mount" |
|
| 12 | 13 |
"github.com/pkg/errors" |
| 13 | 14 |
"github.com/sirupsen/logrus" |
| 14 | 15 |
) |
| ... | ... |
@@ -231,6 +232,10 @@ func (daemon *Daemon) Cleanup(container *container.Container) {
|
| 231 | 231 |
logrus.Warnf("%s cleanup: failed to unmount secrets: %s", container.ID, err)
|
| 232 | 232 |
} |
| 233 | 233 |
|
| 234 |
+ if err := mount.RecursiveUnmount(container.Root); err != nil {
|
|
| 235 |
+ logrus.WithError(err).WithField("container", container.ID).Warn("Error while cleaning up container resource mounts.")
|
|
| 236 |
+ } |
|
| 237 |
+ |
|
| 234 | 238 |
for _, eConfig := range container.ExecCommands.Commands() {
|
| 235 | 239 |
daemon.unregisterExecCommand(container, eConfig) |
| 236 | 240 |
} |
| 237 | 241 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,84 @@ |
| 0 |
+package container |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "context" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "testing" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/api/types" |
|
| 9 |
+ "github.com/docker/docker/api/types/container" |
|
| 10 |
+ "github.com/docker/docker/api/types/mount" |
|
| 11 |
+ "github.com/docker/docker/integration-cli/daemon" |
|
| 12 |
+ "github.com/docker/docker/pkg/stdcopy" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+func TestContainerShmNoLeak(t *testing.T) {
|
|
| 16 |
+ t.Parallel() |
|
| 17 |
+ d := daemon.New(t, "docker", "dockerd", daemon.Config{})
|
|
| 18 |
+ client, err := d.NewClient() |
|
| 19 |
+ if err != nil {
|
|
| 20 |
+ t.Fatal(err) |
|
| 21 |
+ } |
|
| 22 |
+ d.StartWithBusybox(t) |
|
| 23 |
+ defer d.Stop(t) |
|
| 24 |
+ |
|
| 25 |
+ ctx := context.Background() |
|
| 26 |
+ cfg := container.Config{
|
|
| 27 |
+ Image: "busybox", |
|
| 28 |
+ Cmd: []string{"top"},
|
|
| 29 |
+ } |
|
| 30 |
+ |
|
| 31 |
+ ctr, err := client.ContainerCreate(ctx, &cfg, nil, nil, "") |
|
| 32 |
+ if err != nil {
|
|
| 33 |
+ t.Fatal(err) |
|
| 34 |
+ } |
|
| 35 |
+ defer client.ContainerRemove(ctx, ctr.ID, types.ContainerRemoveOptions{Force: true})
|
|
| 36 |
+ |
|
| 37 |
+ if err := client.ContainerStart(ctx, ctr.ID, types.ContainerStartOptions{}); err != nil {
|
|
| 38 |
+ t.Fatal(err) |
|
| 39 |
+ } |
|
| 40 |
+ |
|
| 41 |
+ // this should recursively bind mount everything in the test daemons root |
|
| 42 |
+ // except of course we are hoping that the previous containers /dev/shm mount did not leak into this new container |
|
| 43 |
+ hc := container.HostConfig{
|
|
| 44 |
+ Mounts: []mount.Mount{
|
|
| 45 |
+ {
|
|
| 46 |
+ Type: mount.TypeBind, |
|
| 47 |
+ Source: d.Root, |
|
| 48 |
+ Target: "/testdaemonroot", |
|
| 49 |
+ BindOptions: &mount.BindOptions{Propagation: mount.PropagationRPrivate}},
|
|
| 50 |
+ }, |
|
| 51 |
+ } |
|
| 52 |
+ cfg.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("mount | grep testdaemonroot | grep containers | grep %s", ctr.ID)}
|
|
| 53 |
+ cfg.AttachStdout = true |
|
| 54 |
+ cfg.AttachStderr = true |
|
| 55 |
+ ctrLeak, err := client.ContainerCreate(ctx, &cfg, &hc, nil, "") |
|
| 56 |
+ if err != nil {
|
|
| 57 |
+ t.Fatal(err) |
|
| 58 |
+ } |
|
| 59 |
+ |
|
| 60 |
+ attach, err := client.ContainerAttach(ctx, ctrLeak.ID, types.ContainerAttachOptions{
|
|
| 61 |
+ Stream: true, |
|
| 62 |
+ Stdout: true, |
|
| 63 |
+ Stderr: true, |
|
| 64 |
+ }) |
|
| 65 |
+ if err != nil {
|
|
| 66 |
+ t.Fatal(err) |
|
| 67 |
+ } |
|
| 68 |
+ |
|
| 69 |
+ if err := client.ContainerStart(ctx, ctrLeak.ID, types.ContainerStartOptions{}); err != nil {
|
|
| 70 |
+ t.Fatal(err) |
|
| 71 |
+ } |
|
| 72 |
+ |
|
| 73 |
+ buf := bytes.NewBuffer(nil) |
|
| 74 |
+ |
|
| 75 |
+ if _, err := stdcopy.StdCopy(buf, buf, attach.Reader); err != nil {
|
|
| 76 |
+ t.Fatal(err) |
|
| 77 |
+ } |
|
| 78 |
+ |
|
| 79 |
+ out := bytes.TrimSpace(buf.Bytes()) |
|
| 80 |
+ if !bytes.Equal(out, []byte{}) {
|
|
| 81 |
+ t.Fatalf("mount leaked: %s", string(out))
|
|
| 82 |
+ } |
|
| 83 |
+} |