github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/tests/integration/userns.bats (about)

     1  #!/usr/bin/env bats
     2  
     3  load helpers
     4  
     5  function setup() {
     6  	setup_busybox
     7  
     8  	# Prepare source folders for bind mount
     9  	mkdir -p source-{accessible,inaccessible-1,inaccessible-2}/dir
    10  	touch source-{accessible,inaccessible-1,inaccessible-2}/dir/foo.txt
    11  
    12  	# Permissions only to the owner, it is inaccessible to group/others
    13  	chmod 700 source-inaccessible-{1,2}
    14  
    15  	mkdir -p rootfs/tmp/mount-{1,2}
    16  
    17  	to_umount_list="$(mktemp "$BATS_RUN_TMPDIR/userns-mounts.XXXXXX")"
    18  	if [ $EUID -eq 0 ]; then
    19  		update_config ' .linux.namespaces += [{"type": "user"}]
    20  			| .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}]
    21  			| .linux.gidMappings += [{"hostID": 200000, "containerID": 0, "size": 65534}] '
    22  		remap_rootfs
    23  	fi
    24  }
    25  
    26  function teardown() {
    27  	teardown_bundle
    28  
    29  	if [ -v to_umount_list ]; then
    30  		while read -r mount_path; do
    31  			umount -l "$mount_path" || :
    32  			rm -f "$mount_path"
    33  		done <"$to_umount_list"
    34  		rm -f "$to_umount_list"
    35  		unset to_umount_list
    36  	fi
    37  }
    38  
    39  @test "userns with simple mount" {
    40  	update_config ' .process.args += ["-c", "stat /tmp/mount-1/foo.txt"]
    41  		| .mounts += [{"source": "source-accessible/dir", "destination": "/tmp/mount-1", "options": ["bind"]}] '
    42  
    43  	runc run test_busybox
    44  	[ "$status" -eq 0 ]
    45  }
    46  
    47  # We had bugs where 1 mount worked but not 2+, test with 2 as it is a more
    48  # general case.
    49  @test "userns with 2 inaccessible mounts" {
    50  	update_config '   .process.args += ["-c", "stat /tmp/mount-1/foo.txt /tmp/mount-2/foo.txt"]
    51  			| .mounts += [	{ "source": "source-inaccessible-1/dir", "destination": "/tmp/mount-1", "options": ["bind"] },
    52  			                { "source": "source-inaccessible-2/dir", "destination": "/tmp/mount-2", "options": ["bind"] }
    53  			           ]'
    54  
    55  	# When not running rootless, this should work: while
    56  	# "source-inaccessible-1" can't be read by the uid in the userns, the fd
    57  	# is opened before changing to the userns and sent over via SCM_RIGHTs
    58  	# (with env var _LIBCONTAINER_MOUNT_FDS). Idem for
    59  	# source-inaccessible-2.
    60  	# On rootless, the owner is the same so it is accessible.
    61  	runc run test_busybox
    62  	[ "$status" -eq 0 ]
    63  }
    64  
    65  # exec + bindmounts + user ns is a special case in the code. Test that it works.
    66  @test "userns with inaccessible mount + exec" {
    67  	update_config ' .mounts += [ 	{ "source": "source-inaccessible-1/dir", "destination": "/tmp/mount-1", "options": ["bind"] },
    68  					{ "source": "source-inaccessible-2/dir", "destination": "/tmp/mount-2", "options": ["bind"] }
    69  			         ]'
    70  
    71  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
    72  	[ "$status" -eq 0 ]
    73  
    74  	runc exec test_busybox stat /tmp/mount-1/foo.txt /tmp/mount-2/foo.txt
    75  	[ "$status" -eq 0 ]
    76  }
    77  
    78  # Issue fixed by https://github.com/opencontainers/runc/pull/3510.
    79  @test "userns with bind mount before a cgroupfs mount" {
    80  	# This can only be reproduced on cgroup v1 (and no cgroupns) due to the
    81  	# way it is mounted in such case (a bunch of of bind mounts).
    82  	requires cgroups_v1
    83  
    84  	# Add a bind mount right before the /sys/fs/cgroup mount,
    85  	# and make sure cgroupns is not enabled.
    86  	update_config '	  .mounts |= map(if .destination == "/sys/fs/cgroup" then ({"source": "source-accessible/dir", "destination": "/tmp/mount-1", "options": ["bind"]}, .) else . end)
    87  			| .linux.namespaces -= [{"type": "cgroup"}]'
    88  
    89  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
    90  	[ "$status" -eq 0 ]
    91  
    92  	# Make sure this is real cgroupfs.
    93  	runc exec test_busybox cat /sys/fs/cgroup/{pids,memory}/tasks
    94  	[ "$status" -eq 0 ]
    95  }
    96  
    97  @test "userns join other container userns" {
    98  	# Create a detached container with the id-mapping we want.
    99  	update_config '.process.args = ["sleep", "infinity"]'
   100  	runc run -d --console-socket "$CONSOLE_SOCKET" target_userns
   101  	[ "$status" -eq 0 ]
   102  
   103  	# Configure our container to attach to the first container's userns.
   104  	target_pid="$(__runc state target_userns | jq .pid)"
   105  	update_config '.linux.namespaces |= map(if .type == "user" then (.path = "/proc/'"$target_pid"'/ns/" + .type) else . end)
   106  		| del(.linux.uidMappings)
   107  		| del(.linux.gidMappings)'
   108  	runc run -d --console-socket "$CONSOLE_SOCKET" in_userns
   109  	[ "$status" -eq 0 ]
   110  
   111  	runc exec in_userns cat /proc/self/uid_map
   112  	[ "$status" -eq 0 ]
   113  	if [ $EUID -eq 0 ]; then
   114  		grep -E '^\s+0\s+100000\s+65534$' <<<"$output"
   115  	else
   116  		grep -E '^\s+0\s+'$EUID'\s+1$' <<<"$output"
   117  	fi
   118  
   119  	runc exec in_userns cat /proc/self/gid_map
   120  	[ "$status" -eq 0 ]
   121  	if [ $EUID -eq 0 ]; then
   122  		grep -E '^\s+0\s+200000\s+65534$' <<<"$output"
   123  	else
   124  		grep -E '^\s+0\s+'$EUID'\s+1$' <<<"$output"
   125  	fi
   126  }
   127  
   128  @test "userns join other container userns [bind-mounted nsfd]" {
   129  	requires root
   130  
   131  	# Create a detached container with the id-mapping we want.
   132  	update_config '.process.args = ["sleep", "infinity"]'
   133  	runc run -d --console-socket "$CONSOLE_SOCKET" target_userns
   134  	[ "$status" -eq 0 ]
   135  
   136  	# Bind-mount the first containers userns nsfd to a different path, to
   137  	# exercise the non-fast-path (where runc has to join the userns to get the
   138  	# mappings).
   139  	target_pid="$(__runc state target_userns | jq .pid)"
   140  	userns_path=$(mktemp "$BATS_RUN_TMPDIR/userns.XXXXXX")
   141  	mount --bind "/proc/$target_pid/ns/user" "$userns_path"
   142  	echo "$userns_path" >>"$to_umount_list"
   143  
   144  	# Configure our container to attach to the first container's userns.
   145  	update_config '.linux.namespaces |= map(if .type == "user" then (.path = "'"$userns_path"'") else . end)
   146  		| del(.linux.uidMappings)
   147  		| del(.linux.gidMappings)'
   148  	runc run -d --console-socket "$CONSOLE_SOCKET" in_userns
   149  	[ "$status" -eq 0 ]
   150  
   151  	runc exec in_userns cat /proc/self/uid_map
   152  	[ "$status" -eq 0 ]
   153  	if [ $EUID -eq 0 ]; then
   154  		grep -E '^\s+0\s+100000\s+65534$' <<<"$output"
   155  	else
   156  		grep -E '^\s+0\s+'$EUID'\s+1$' <<<"$output"
   157  	fi
   158  
   159  	runc exec in_userns cat /proc/self/gid_map
   160  	[ "$status" -eq 0 ]
   161  	if [ $EUID -eq 0 ]; then
   162  		grep -E '^\s+0\s+200000\s+65534$' <<<"$output"
   163  	else
   164  		grep -E '^\s+0\s+'$EUID'\s+1$' <<<"$output"
   165  	fi
   166  }