@@ -426,6 +426,70 @@ func TestContainerCopyLeaksMounts(t *testing.T) {
426426 assert .Equal (t , mountsBefore , mountsAfter )
427427}
428428
429+ func TestContainerBindMountReadOnlyDefault (t * testing.T ) {
430+ skip .If (t , testEnv .IsRemoteDaemon )
431+ skip .If (t , ! isRROSupported (), "requires recursive read-only mounts" )
432+
433+ ctx := setupTest (t )
434+
435+ // The test will run a container with a simple readonly /dev bind mount (-v /dev:/dev:ro)
436+ // It will then check /proc/self/mountinfo for the mount type of /dev/shm (submount of /dev)
437+ // If /dev/shm is rw, that will mean that the read-only mounts are NOT recursive by default.
438+ const nonRecursive = " /dev/shm rw,"
439+ // If /dev/shm is ro, that will mean that the read-only mounts ARE recursive by default.
440+ const recursive = " /dev/shm ro,"
441+
442+ for _ , tc := range []struct {
443+ version string
444+ expectedOut string
445+ name string
446+ }{
447+ {version : "" , expectedOut : recursive , name : "latest should be the same as 1.44" },
448+ {version : "1.44" , expectedOut : recursive , name : "submount should be recursive by default on 1.44" },
449+
450+ {version : "1.43" , expectedOut : nonRecursive , name : "older should be non-recursive by default" },
451+ } {
452+ t .Run (tc .name , func (t * testing.T ) {
453+ apiClient := testEnv .APIClient ()
454+
455+ if tc .version != "" {
456+ skip .If (t , versions .LessThan (testEnv .DaemonAPIVersion (), tc .version ), "requires API v" + tc .version )
457+ c , err := client .NewClientWithOpts (client .FromEnv , client .WithVersion (tc .version ))
458+ assert .NilError (t , err , "failed to create client with version v%s" , tc .version )
459+ apiClient = c
460+ }
461+
462+ for _ , tc2 := range []struct {
463+ subname string
464+ mountOpt func (* container.TestContainerConfig )
465+ }{
466+ {"mount" , container .WithMount (mounttypes.Mount {
467+ Type : mounttypes .TypeBind ,
468+ Source : "/dev" ,
469+ Target : "/dev" ,
470+ ReadOnly : true ,
471+ })},
472+ {"bind mount" , container .WithBindRaw ("/dev:/dev:ro" )},
473+ } {
474+ t .Run (tc2 .subname , func (t * testing.T ) {
475+ cid := container .Run (ctx , t , apiClient , tc2 .mountOpt ,
476+ container .WithCmd ("sh" , "-c" , "grep /dev/shm /proc/self/mountinfo" ),
477+ )
478+ out , err := container .Output (ctx , apiClient , cid )
479+ assert .NilError (t , err )
480+
481+ assert .Check (t , is .Equal (out .Stderr , "" ))
482+ // Output should be either:
483+ // 545 526 0:160 / /dev/shm ro,nosuid,nodev,noexec,relatime shared:90 - tmpfs shm rw,size=65536k
484+ // or
485+ // 545 526 0:160 / /dev/shm rw,nosuid,nodev,noexec,relatime shared:90 - tmpfs shm rw,size=65536k
486+ assert .Check (t , is .Contains (out .Stdout , tc .expectedOut ))
487+ })
488+ }
489+ })
490+ }
491+ }
492+
429493func TestContainerBindMountRecursivelyReadOnly (t * testing.T ) {
430494 skip .If (t , testEnv .IsRemoteDaemon )
431495 skip .If (t , versions .LessThan (testEnv .DaemonAPIVersion (), "1.44" ), "requires API v1.44" )
@@ -450,7 +514,7 @@ func TestContainerBindMountRecursivelyReadOnly(t *testing.T) {
450514 }
451515 }()
452516
453- rroSupported := kernel . CheckKernelVersion ( 5 , 12 , 0 )
517+ rroSupported := isRROSupported ( )
454518
455519 nonRecursiveVerifier := []string {`/bin/sh` , `-xc` , `touch /foo/mnt/file; [ $? = 0 ]` }
456520 forceRecursiveVerifier := []string {`/bin/sh` , `-xc` , `touch /foo/mnt/file; [ $? != 0 ]` }
@@ -504,3 +568,7 @@ func TestContainerBindMountRecursivelyReadOnly(t *testing.T) {
504568 poll .WaitOn (t , container .IsSuccessful (ctx , apiClient , c ), poll .WithDelay (100 * time .Millisecond ))
505569 }
506570}
571+
572+ func isRROSupported () bool {
573+ return kernel .CheckKernelVersion (5 , 12 , 0 )
574+ }
0 commit comments