88 "testing"
99 "time"
1010
11+ "github.com/docker/docker/api"
1112 containertypes "github.com/docker/docker/api/types/container"
1213 mounttypes "github.com/docker/docker/api/types/mount"
1314 "github.com/docker/docker/api/types/network"
@@ -426,6 +427,78 @@ func TestContainerCopyLeaksMounts(t *testing.T) {
426427 assert .Equal (t , mountsBefore , mountsAfter )
427428}
428429
430+ func TestContainerBindMountReadOnlyDefault (t * testing.T ) {
431+ skip .If (t , testEnv .IsRemoteDaemon )
432+ skip .If (t , ! isRROSupported (), "requires recursive read-only mounts" )
433+
434+ ctx := setupTest (t )
435+
436+ // The test will run a container with a simple readonly /dev bind mount (-v /dev:/dev:ro)
437+ // It will then check /proc/self/mountinfo for the mount type of /dev/shm (submount of /dev)
438+ // If /dev/shm is rw, that will mean that the read-only mounts are NOT recursive by default.
439+ const nonRecursive = " /dev/shm rw,"
440+ // If /dev/shm is ro, that will mean that the read-only mounts ARE recursive by default.
441+ const recursive = " /dev/shm ro,"
442+
443+ for _ , tc := range []struct {
444+ clientVersion string
445+ expectedOut string
446+ name string
447+ }{
448+ {clientVersion : "" , expectedOut : recursive , name : "latest should be the same as 1.44" },
449+ {clientVersion : "1.44" , expectedOut : recursive , name : "submount should be recursive by default on 1.44" },
450+
451+ {clientVersion : "1.43" , expectedOut : nonRecursive , name : "older than 1.44 should be non-recursive by default" },
452+
453+ // TODO: Remove when MinSupportedAPIVersion >= 1.44
454+ {clientVersion : api .MinSupportedAPIVersion , expectedOut : nonRecursive , name : "minimum API should be non-recursive by default" },
455+ } {
456+ t .Run (tc .name , func (t * testing.T ) {
457+ apiClient := testEnv .APIClient ()
458+
459+ minDaemonVersion := tc .clientVersion
460+ if minDaemonVersion == "" {
461+ minDaemonVersion = "1.44"
462+ }
463+ skip .If (t , versions .LessThan (testEnv .DaemonAPIVersion (), minDaemonVersion ), "requires API v" + minDaemonVersion )
464+
465+ if tc .clientVersion != "" {
466+ c , err := client .NewClientWithOpts (client .FromEnv , client .WithVersion (tc .clientVersion ))
467+ assert .NilError (t , err , "failed to create client with version v%s" , tc .clientVersion )
468+ apiClient = c
469+ }
470+
471+ for _ , tc2 := range []struct {
472+ subname string
473+ mountOpt func (* container.TestContainerConfig )
474+ }{
475+ {"mount" , container .WithMount (mounttypes.Mount {
476+ Type : mounttypes .TypeBind ,
477+ Source : "/dev" ,
478+ Target : "/dev" ,
479+ ReadOnly : true ,
480+ })},
481+ {"bind mount" , container .WithBindRaw ("/dev:/dev:ro" )},
482+ } {
483+ t .Run (tc2 .subname , func (t * testing.T ) {
484+ cid := container .Run (ctx , t , apiClient , tc2 .mountOpt ,
485+ container .WithCmd ("sh" , "-c" , "grep /dev/shm /proc/self/mountinfo" ),
486+ )
487+ out , err := container .Output (ctx , apiClient , cid )
488+ assert .NilError (t , err )
489+
490+ assert .Check (t , is .Equal (out .Stderr , "" ))
491+ // Output should be either:
492+ // 545 526 0:160 / /dev/shm ro,nosuid,nodev,noexec,relatime shared:90 - tmpfs shm rw,size=65536k
493+ // or
494+ // 545 526 0:160 / /dev/shm rw,nosuid,nodev,noexec,relatime shared:90 - tmpfs shm rw,size=65536k
495+ assert .Check (t , is .Contains (out .Stdout , tc .expectedOut ))
496+ })
497+ }
498+ })
499+ }
500+ }
501+
429502func TestContainerBindMountRecursivelyReadOnly (t * testing.T ) {
430503 skip .If (t , testEnv .IsRemoteDaemon )
431504 skip .If (t , versions .LessThan (testEnv .DaemonAPIVersion (), "1.44" ), "requires API v1.44" )
@@ -450,7 +523,7 @@ func TestContainerBindMountRecursivelyReadOnly(t *testing.T) {
450523 }
451524 }()
452525
453- rroSupported := kernel . CheckKernelVersion ( 5 , 12 , 0 )
526+ rroSupported := isRROSupported ( )
454527
455528 nonRecursiveVerifier := []string {`/bin/sh` , `-xc` , `touch /foo/mnt/file; [ $? = 0 ]` }
456529 forceRecursiveVerifier := []string {`/bin/sh` , `-xc` , `touch /foo/mnt/file; [ $? != 0 ]` }
@@ -504,3 +577,7 @@ func TestContainerBindMountRecursivelyReadOnly(t *testing.T) {
504577 poll .WaitOn (t , container .IsSuccessful (ctx , apiClient , c ), poll .WithDelay (100 * time .Millisecond ))
505578 }
506579}
580+
581+ func isRROSupported () bool {
582+ return kernel .CheckKernelVersion (5 , 12 , 0 )
583+ }
0 commit comments