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

     1  #!/usr/bin/env bats
     2  
     3  load helpers
     4  
     5  function setup() {
     6  	setup_busybox
     7  	update_config '.process.args = ["/bin/echo", "Hello World"]'
     8  }
     9  
    10  function teardown() {
    11  	teardown_bundle
    12  }
    13  
    14  @test "runc run" {
    15  	runc run test_hello
    16  	[ "$status" -eq 0 ]
    17  
    18  	runc state test_hello
    19  	[ "$status" -ne 0 ]
    20  }
    21  
    22  @test "runc run --keep" {
    23  	runc run --keep test_run_keep
    24  	[ "$status" -eq 0 ]
    25  
    26  	testcontainer test_run_keep stopped
    27  
    28  	runc state test_run_keep
    29  	[ "$status" -eq 0 ]
    30  
    31  	runc delete test_run_keep
    32  
    33  	runc state test_run_keep
    34  	[ "$status" -ne 0 ]
    35  }
    36  
    37  @test "runc run --keep (check cgroup exists)" {
    38  	# for systemd driver, the unit's cgroup path will be auto removed if container's all processes exited
    39  	requires no_systemd
    40  	[ $EUID -ne 0 ] && requires rootless_cgroup
    41  
    42  	set_cgroups_path
    43  
    44  	runc run --keep test_run_keep
    45  	[ "$status" -eq 0 ]
    46  
    47  	testcontainer test_run_keep stopped
    48  
    49  	runc state test_run_keep
    50  	[ "$status" -eq 0 ]
    51  
    52  	# check that cgroup exists
    53  	check_cgroup_value "pids.max" "max"
    54  
    55  	runc delete test_run_keep
    56  
    57  	runc state test_run_keep
    58  	[ "$status" -ne 0 ]
    59  }
    60  
    61  @test "runc run [hostname domainname]" {
    62  	update_config ' .process.args |= ["sh"]
    63  			| .hostname = "myhostname"
    64  			| .domainname= "mydomainname"'
    65  
    66  	runc run -d --console-socket "$CONSOLE_SOCKET" test_utc
    67  	[ "$status" -eq 0 ]
    68  
    69  	# test hostname
    70  	runc exec test_utc hostname
    71  	[ "$status" -eq 0 ]
    72  	[[ "${lines[0]}" == *'myhostname'* ]]
    73  
    74  	# test domainname
    75  	runc exec test_utc cat /proc/sys/kernel/domainname
    76  	[ "$status" -eq 0 ]
    77  	[[ "${lines[0]}" == *'mydomainname'* ]]
    78  }
    79  
    80  # https://github.com/opencontainers/runc/issues/3952
    81  @test "runc run with tmpfs" {
    82  	requires root
    83  
    84  	chmod 'a=rwx,ug+s,+t' rootfs/tmp # set all bits
    85  	mode=$(stat -c %A rootfs/tmp)
    86  
    87  	# shellcheck disable=SC2016
    88  	update_config '.process.args = ["sh", "-c", "stat -c %A /tmp"]'
    89  	update_config '.mounts += [{"destination": "/tmp", "type": "tmpfs", "source": "tmpfs", "options":["noexec","nosuid","nodev","rprivate"]}]'
    90  
    91  	runc run test_tmpfs
    92  	[ "$status" -eq 0 ]
    93  	[ "${lines[0]}" = "$mode" ]
    94  }
    95  
    96  @test "runc run with tmpfs perms" {
    97  	# shellcheck disable=SC2016
    98  	update_config '.process.args = ["sh", "-c", "stat -c %a /tmp/test"]'
    99  	update_config '.mounts += [{"destination": "/tmp/test", "type": "tmpfs", "source": "tmpfs", "options": ["mode=0444"]}]'
   100  
   101  	# Directory is to be created by runc.
   102  	runc run test_tmpfs
   103  	[ "$status" -eq 0 ]
   104  	[ "${lines[0]}" = "444" ]
   105  
   106  	# Run a 2nd time with the pre-existing directory.
   107  	# Ref: https://github.com/opencontainers/runc/issues/3911
   108  	runc run test_tmpfs
   109  	[ "$status" -eq 0 ]
   110  	[ "${lines[0]}" = "444" ]
   111  
   112  	# Existing directory, custom perms, no mode on the mount,
   113  	# so it should use the directory's perms.
   114  	update_config '.mounts[-1].options = []'
   115  	chmod 0710 rootfs/tmp/test
   116  	# shellcheck disable=SC2016
   117  	runc run test_tmpfs
   118  	[ "$status" -eq 0 ]
   119  	[ "${lines[0]}" = "710" ]
   120  
   121  	# Add back the mode on the mount, and it should use that instead.
   122  	# Just for fun, use different perms than was used earlier.
   123  	# shellcheck disable=SC2016
   124  	update_config '.mounts[-1].options = ["mode=0410"]'
   125  	runc run test_tmpfs
   126  	[ "$status" -eq 0 ]
   127  	[ "${lines[0]}" = "410" ]
   128  }
   129  
   130  @test "RUNC_DMZ=true runc run [runc-dmz]" {
   131  	# centos-7 has an outdated container-selinux (<2.224.0) which means
   132  	# runc-dmz won't work.
   133  	exclude_os centos-7
   134  
   135  	RUNC_DMZ=true runc --debug run test_hello
   136  	[ "$status" -eq 0 ]
   137  	[[ "$output" = *"Hello World"* ]]
   138  	# We use runc-dmz if we can.
   139  	[[ "$output" = *"runc-dmz: using runc-dmz"* ]]
   140  }
   141  
   142  @test "RUNC_DMZ=true runc run [cap_sys_ptrace -> /proc/self/exe clone]" {
   143  	# centos-7 has an outdated container-selinux (<2.224.0) which means
   144  	# runc-dmz won't work.
   145  	exclude_os centos-7
   146  
   147  	# Add CAP_SYS_PTRACE to the bounding set, the minimum needed to indicate a
   148  	# container process _could_ get CAP_SYS_PTRACE.
   149  	update_config '.process.capabilities.bounding += ["CAP_SYS_PTRACE"]'
   150  
   151  	RUNC_DMZ=true runc --debug run test_hello
   152  	[ "$status" -eq 0 ]
   153  	[[ "$output" = *"Hello World"* ]]
   154  	if [ "$EUID" -ne 0 ] && is_kernel_gte 4.10; then
   155  		# For Linux 4.10 and later, rootless containers will use runc-dmz
   156  		# because they are running in a user namespace. See isDmzBinarySafe().
   157  		[[ "$output" = *"runc-dmz: using runc-dmz"* ]]
   158  	else
   159  		# If the container has CAP_SYS_PTRACE and is not rootless, we use
   160  		# /proc/self/exe cloning.
   161  		[[ "$output" = *"runc-dmz: using /proc/self/exe clone"* ]]
   162  	fi
   163  }
   164  
   165  @test "runc run [/proc/self/exe clone]" {
   166  	runc --debug run test_hello
   167  	[ "$status" -eq 0 ]
   168  	[[ "$output" = *"Hello World"* ]]
   169  	[[ "$output" = *"runc-dmz: using /proc/self/exe clone"* ]]
   170  }
   171  
   172  @test "runc run [joining existing container namespaces]" {
   173  	requires timens
   174  
   175  	# Create a detached container with the namespaces we want. We notably want
   176  	# to include both userns and timens, which require config-related
   177  	# configuration.
   178  	if [ $EUID -eq 0 ]; then
   179  		update_config '.linux.namespaces += [{"type": "user"}]
   180  			| .linux.uidMappings += [{"containerID": 0, "hostID": 100000, "size": 100}]
   181  			| .linux.gidMappings += [{"containerID": 0, "hostID": 200000, "size": 200}]'
   182  		remap_rootfs
   183  	fi
   184  	update_config '.linux.namespaces += [{"type": "time"}]
   185  		| .linux.timeOffsets = {
   186  			"monotonic": { "secs": 7881, "nanosecs": 2718281 },
   187  			"boottime": { "secs": 1337, "nanosecs": 3141519 }
   188  		}'
   189  	update_config '.process.args = ["sleep", "infinity"]'
   190  
   191  	runc run -d --console-socket "$CONSOLE_SOCKET" target_ctr
   192  	[ "$status" -eq 0 ]
   193  
   194  	# Modify our container's configuration such that it is just going to
   195  	# inherit all of the namespaces of the target container.
   196  	#
   197  	# NOTE: We cannot join the mount namespace of another container because of
   198  	# some quirks of the runtime-spec. In particular, we MUST pivot_root into
   199  	# root.path and root.path MUST be set in the config, so runc cannot just
   200  	# ignore root.path when joining namespaces (and root.path doesn't exist
   201  	# inside root.path, for obvious reasons).
   202  	#
   203  	# We could hack around this (create a copy of the rootfs inside the rootfs,
   204  	# or use a simpler mount namespace target), but those wouldn't be similar
   205  	# tests to the other namespace joining tests.
   206  	target_pid="$(__runc state target_ctr | jq .pid)"
   207  	update_config '.linux.namespaces |= map_values(.path = if .type == "mount" then "" else "/proc/'"$target_pid"'/ns/" + ({"network": "net", "mount": "mnt"}[.type] // .type) end)'
   208  	# Remove the userns and timens configuration (they cannot be changed).
   209  	update_config '.linux |= (del(.uidMappings) | del(.gidMappings) | del(.timeOffsets))'
   210  
   211  	runc run -d --console-socket "$CONSOLE_SOCKET" attached_ctr
   212  	[ "$status" -eq 0 ]
   213  
   214  	# Make sure there are two sleep processes in our container.
   215  	runc exec attached_ctr ps aux
   216  	[ "$status" -eq 0 ]
   217  	run -0 grep "sleep infinity" <<<"$output"
   218  	[ "${#lines[@]}" -eq 2 ]
   219  
   220  	# ... that the userns mappings are the same...
   221  	runc exec attached_ctr cat /proc/self/uid_map
   222  	[ "$status" -eq 0 ]
   223  	if [ $EUID -eq 0 ]; then
   224  		grep -E '^\s+0\s+100000\s+100$' <<<"$output"
   225  	else
   226  		grep -E '^\s+0\s+'$EUID'\s+1$' <<<"$output"
   227  	fi
   228  	runc exec attached_ctr cat /proc/self/gid_map
   229  	[ "$status" -eq 0 ]
   230  	if [ $EUID -eq 0 ]; then
   231  		grep -E '^\s+0\s+200000\s+200$' <<<"$output"
   232  	else
   233  		grep -E '^\s+0\s+'$EUID'\s+1$' <<<"$output"
   234  	fi
   235  
   236  	# ... as well as the timens offsets.
   237  	runc exec attached_ctr cat /proc/self/timens_offsets
   238  	grep -E '^monotonic\s+7881\s+2718281$' <<<"$output"
   239  	grep -E '^boottime\s+1337\s+3141519$' <<<"$output"
   240  }
   241  
   242  @test "RUNC_DMZ=true runc run [exec error]" {
   243  	# centos-7 has an outdated container-selinux (<2.224.0) which means
   244  	# runc-dmz won't work.
   245  	exclude_os centos-7
   246  
   247  	cat <<EOF >rootfs/run.sh
   248  #!/mmnnttbb foo bar
   249  sh
   250  EOF
   251  	chmod +x rootfs/run.sh
   252  	update_config '.process.args = [ "/run.sh" ]'
   253  	RUNC_DMZ=true runc run test_hello
   254  
   255  	# Ensure that the output contains the right error message. For runc-dmz, both
   256  	# nolibc and libc have the same formatting string (but libc will print the
   257  	# errno description rather than just the number), and for runc_nodmz the error
   258  	# message from Go starts with the same string.
   259  	[ "$status" -ne 0 ]
   260  	[[ "$output" = *"exec /run.sh: "* ]]
   261  }
   262  
   263  @test "runc run [execve error]" {
   264  	cat <<EOF >rootfs/run.sh
   265  #!/mmnnttbb foo bar
   266  sh
   267  EOF
   268  	chmod +x rootfs/run.sh
   269  	update_config '.process.args = [ "/run.sh" ]'
   270  	runc run test_hello
   271  	[ "$status" -ne 0 ]
   272  
   273  	# After the sync socket closed, we should not send error to parent
   274  	# process, or else we will get a unnecessary error log(#4171).
   275  	[ ${#lines[@]} -eq 1 ]
   276  	[[ ${lines[0]} = "exec /run.sh: no such file or directory" ]]
   277  }