In order to avoid reverting our fix for mount leakage in devicemapper,
add a test which checks that devicemapper's Get() and Put() cycle can
survive having a command running in an rprivate mount propagation setup
in-between. While this is quite rudimentary, it should be sufficient.
We have to skip this test for pre-3.18 kernels.
Signed-off-by: Aleksa Sarai <asarai@suse.de>
| ... | ... |
@@ -5,12 +5,15 @@ package devmapper |
| 5 | 5 |
import ( |
| 6 | 6 |
"fmt" |
| 7 | 7 |
"os" |
| 8 |
+ "os/exec" |
|
| 8 | 9 |
"syscall" |
| 9 | 10 |
"testing" |
| 10 | 11 |
"time" |
| 11 | 12 |
|
| 12 | 13 |
"github.com/docker/docker/daemon/graphdriver" |
| 13 | 14 |
"github.com/docker/docker/daemon/graphdriver/graphtest" |
| 15 |
+ "github.com/docker/docker/pkg/parsers/kernel" |
|
| 16 |
+ "golang.org/x/sys/unix" |
|
| 14 | 17 |
) |
| 15 | 18 |
|
| 16 | 19 |
func init() {
|
| ... | ... |
@@ -150,3 +153,53 @@ func TestDevmapperLockReleasedDeviceDeletion(t *testing.T) {
|
| 150 | 150 |
case <-doneChan: |
| 151 | 151 |
} |
| 152 | 152 |
} |
| 153 |
+ |
|
| 154 |
+// Ensure that mounts aren't leakedriver. It's non-trivial for us to test the full |
|
| 155 |
+// reproducer of #34573 in a unit test, but we can at least make sure that a |
|
| 156 |
+// simple command run in a new namespace doesn't break things horribly. |
|
| 157 |
+func TestDevmapperMountLeaks(t *testing.T) {
|
|
| 158 |
+ if !kernel.CheckKernelVersion(3, 18, 0) {
|
|
| 159 |
+ t.Skipf("kernel version <3.18.0 and so is missing torvalds/linux@8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe.")
|
|
| 160 |
+ } |
|
| 161 |
+ |
|
| 162 |
+ driver := graphtest.GetDriver(t, "devicemapper", "dm.use_deferred_removal=false", "dm.use_deferred_deletion=false").(*graphtest.Driver).Driver.(*graphdriver.NaiveDiffDriver).ProtoDriver.(*Driver) |
|
| 163 |
+ defer graphtest.PutDriver(t) |
|
| 164 |
+ |
|
| 165 |
+ // We need to create a new (dummy) device. |
|
| 166 |
+ if err := driver.Create("some-layer", "", nil); err != nil {
|
|
| 167 |
+ t.Fatalf("setting up some-layer: %v", err)
|
|
| 168 |
+ } |
|
| 169 |
+ |
|
| 170 |
+ // Mount the device. |
|
| 171 |
+ _, err := driver.Get("some-layer", "")
|
|
| 172 |
+ if err != nil {
|
|
| 173 |
+ t.Fatalf("mounting some-layer: %v", err)
|
|
| 174 |
+ } |
|
| 175 |
+ |
|
| 176 |
+ // Create a new subprocess which will inherit our mountpoint, then |
|
| 177 |
+ // intentionally leak it and stick around. We can't do this entirely within |
|
| 178 |
+ // Go because forking and namespaces in Go are really not handled well at |
|
| 179 |
+ // all. |
|
| 180 |
+ cmd := exec.Cmd{
|
|
| 181 |
+ Path: "/bin/sh", |
|
| 182 |
+ Args: []string{
|
|
| 183 |
+ "/bin/sh", "-c", |
|
| 184 |
+ "mount --make-rprivate / && sleep 1000s", |
|
| 185 |
+ }, |
|
| 186 |
+ SysProcAttr: &syscall.SysProcAttr{
|
|
| 187 |
+ Unshareflags: syscall.CLONE_NEWNS, |
|
| 188 |
+ }, |
|
| 189 |
+ } |
|
| 190 |
+ if err := cmd.Start(); err != nil {
|
|
| 191 |
+ t.Fatalf("starting sub-command: %v", err)
|
|
| 192 |
+ } |
|
| 193 |
+ defer func() {
|
|
| 194 |
+ unix.Kill(cmd.Process.Pid, unix.SIGKILL) |
|
| 195 |
+ cmd.Wait() |
|
| 196 |
+ }() |
|
| 197 |
+ |
|
| 198 |
+ // Now try to "drop" the device. |
|
| 199 |
+ if err := driver.Put("some-layer"); err != nil {
|
|
| 200 |
+ t.Fatalf("unmounting some-layer: %v", err)
|
|
| 201 |
+ } |
|
| 202 |
+} |