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

     1  #!/usr/bin/env bats
     2  
     3  load helpers
     4  
     5  # Support for seccomp notify requires Linux > 5.6 because
     6  # runc uses the pidfd_getfd system call to fetch the seccomp fd.
     7  # https://github.com/torvalds/linux/commit/8649c322f75c96e7ced2fec201e123b2b073bf09
     8  # We also require arch x86_64, to not make this fail when people run tests
     9  # locally on other archs.
    10  function setup() {
    11  	requires_kernel 5.6
    12  	requires arch_x86_64
    13  
    14  	setup_seccompagent
    15  	setup_busybox
    16  }
    17  
    18  function teardown() {
    19  	teardown_seccompagent
    20  	teardown_bundle
    21  }
    22  
    23  # Create config.json template with SCMP_ACT_NOTIFY actions
    24  # $1: command to run
    25  # $2: noNewPrivileges (false/true)
    26  # $3: list of syscalls
    27  function scmp_act_notify_template() {
    28  	# The agent intercepts mkdir syscalls and creates the folder appending
    29  	# "-bar" (listenerMetadata below) to the name.
    30  	update_config '   .process.args = ["/bin/sh", "-c", "'"$1"'"]
    31  			| .process.noNewPrivileges = '"$2"'
    32  			| .linux.seccomp = {
    33  				"defaultAction":"SCMP_ACT_ALLOW",
    34  				"listenerPath": "'"$SECCCOMP_AGENT_SOCKET"'",
    35  				"listenerMetadata": "bar",
    36  				"architectures": [ "SCMP_ARCH_X86","SCMP_ARCH_X32", "SCMP_ARCH_X86_64" ],
    37  				"syscalls": [{ "names": ['"$3"'], "action": "SCMP_ACT_NOTIFY" }]
    38  			}'
    39  }
    40  
    41  # The call to seccomp is done at different places according to the value of
    42  # noNewPrivileges, for this reason many of the following cases are tested with
    43  # both values.
    44  
    45  # Test basic actions handled by the agent work fine. noNewPrivileges FALSE.
    46  @test "runc run [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges false)" {
    47  	scmp_act_notify_template "mkdir /dev/shm/foo && stat /dev/shm/foo-bar" false '"mkdir"'
    48  
    49  	runc run test_busybox
    50  	[ "$status" -eq 0 ]
    51  }
    52  
    53  # Test basic actions handled by the agent work fine. noNewPrivileges TRUE.
    54  @test "runc run [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges true)" {
    55  	scmp_act_notify_template "mkdir /dev/shm/foo && stat /dev/shm/foo-bar" true '"mkdir"'
    56  
    57  	runc run test_busybox
    58  	[ "$status" -eq 0 ]
    59  }
    60  
    61  # Test actions not-handled by the agent work fine. noNewPrivileges FALSE.
    62  @test "runc exec [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges false)" {
    63  	requires root
    64  
    65  	scmp_act_notify_template "sleep infinity" false '"mkdir"'
    66  
    67  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
    68  	[ "$status" -eq 0 ]
    69  
    70  	runc exec test_busybox /bin/sh -c "mkdir /dev/shm/foo && stat /dev/shm/foo-bar"
    71  	[ "$status" -eq 0 ]
    72  }
    73  
    74  # Test actions not-handled by the agent work fine. noNewPrivileges TRUE.
    75  @test "runc exec [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges true)" {
    76  	requires root
    77  
    78  	scmp_act_notify_template "sleep infinity" true '"mkdir"'
    79  
    80  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
    81  	runc exec test_busybox /bin/sh -c "mkdir /dev/shm/foo && stat /dev/shm/foo-bar"
    82  	[ "$status" -eq 0 ]
    83  }
    84  
    85  # Test important syscalls (some might be executed by runc) work fine when handled by the agent. noNewPrivileges FALSE.
    86  @test "runc run [seccomp] (SCMP_ACT_NOTIFY important syscalls noNewPrivileges false)" {
    87  	scmp_act_notify_template "/bin/true" false '"execve","openat","open","read","close"'
    88  
    89  	runc run test_busybox
    90  	[ "$status" -eq 0 ]
    91  }
    92  
    93  # Test important syscalls (some might be executed by runc) work fine when handled by the agent. noNewPrivileges TRUE.
    94  @test "runc run [seccomp] (SCMP_ACT_NOTIFY important syscalls noNewPrivileges true)" {
    95  	scmp_act_notify_template "/bin/true" true '"execve","openat","open","read","close"'
    96  
    97  	runc run test_busybox
    98  	[ "$status" -eq 0 ]
    99  }
   100  
   101  # Ignore listenerPath if the profile doesn't use seccomp notify actions.
   102  @test "runc run [seccomp] (ignore listener path if no notify act)" {
   103  	update_config '   .process.args = ["/bin/sh", "-c", "mkdir /dev/shm/foo && stat /dev/shm/foo"]
   104  			| .linux.seccomp = {
   105  				"defaultAction":"SCMP_ACT_ALLOW",
   106  				"listenerPath": "'"$SECCCOMP_AGENT_SOCKET"'",
   107  				"listenerMetadata": "bar",
   108  			}'
   109  
   110  	runc run test_busybox
   111  	[ "$status" -eq 0 ]
   112  }
   113  
   114  # Ensure listenerPath is present if the profile uses seccomp notify actions.
   115  @test "runc run [seccomp] (SCMP_ACT_NOTIFY empty listener path and notify act)" {
   116  	scmp_act_notify_template "/bin/true" false '"mkdir"'
   117  	update_config '.linux.seccomp.listenerPath = ""'
   118  
   119  	runc run test_busybox
   120  	[ "$status" -ne 0 ]
   121  }
   122  
   123  # Test using an invalid socket (none listening) as listenerPath fails.
   124  @test "runc run [seccomp] (SCMP_ACT_NOTIFY wrong listener path)" {
   125  	scmp_act_notify_template "/bin/true" false '"mkdir"'
   126  	update_config '.linux.seccomp.listenerPath = "/some-non-existing-listener-path.sock"'
   127  
   128  	runc run test_busybox
   129  	[ "$status" -ne 0 ]
   130  }
   131  
   132  # Test using an invalid abstract socket as listenerPath fails.
   133  @test "runc run [seccomp] (SCMP_ACT_NOTIFY wrong abstract listener path)" {
   134  	scmp_act_notify_template "/bin/true" false '"mkdir"'
   135  	update_config '.linux.seccomp.listenerPath = "@mysocketishere"'
   136  
   137  	runc run test_busybox
   138  	[ "$status" -ne 0 ]
   139  }
   140  
   141  # Check that killing the seccompagent doesn't block syscalls in
   142  # the container. They should return ENOSYS instead.
   143  @test "runc run [seccomp] (SCMP_ACT_NOTIFY kill seccompagent)" {
   144  	scmp_act_notify_template "sleep 4 && mkdir /dev/shm/foo" false '"mkdir"'
   145  
   146  	sleep 2 && teardown_seccompagent &
   147  	runc run test_busybox
   148  	[ "$status" -ne 0 ]
   149  	[[ "$output" == *"mkdir:"*"/dev/shm/foo"*"Function not implemented"* ]]
   150  }
   151  
   152  # Check that starting with no seccomp agent running fails with a clear error.
   153  @test "runc run [seccomp] (SCMP_ACT_NOTIFY no seccompagent)" {
   154  	teardown_seccompagent
   155  
   156  	scmp_act_notify_template "/bin/true" false '"mkdir"'
   157  
   158  	runc run test_busybox
   159  	[ "$status" -ne 0 ]
   160  	[[ "$output" == *"failed to connect with seccomp agent"* ]]
   161  }
   162  
   163  # Check that agent-returned error for the syscall works.
   164  @test "runc run [seccomp] (SCMP_ACT_NOTIFY error chmod)" {
   165  	scmp_act_notify_template "touch /dev/shm/foo && chmod 777 /dev/shm/foo" false '"chmod", "fchmod", "fchmodat"'
   166  
   167  	runc run test_busybox
   168  	[ "$status" -ne 0 ]
   169  	[[ "$output" == *"chmod:"*"/dev/shm/foo"*"No medium found"* ]]
   170  }
   171  
   172  # check that trying to use SCMP_ACT_NOTIFY with write() gives a meaningful error.
   173  @test "runc run [seccomp] (SCMP_ACT_NOTIFY write)" {
   174  	scmp_act_notify_template "/bin/true" false '"write"'
   175  
   176  	runc run test_busybox
   177  	[ "$status" -ne 0 ]
   178  	[[ "$output" == *"SCMP_ACT_NOTIFY cannot be used for the write syscall"* ]]
   179  }
   180  
   181  # check that a startContainer hook doesn't get any extra file descriptor.
   182  @test "runc run [seccomp] (SCMP_ACT_NOTIFY startContainer hook)" {
   183  	# shellcheck disable=SC2016
   184  	# We use single quotes to properly delimit the $1 param to
   185  	# update_config(), but this shellshcheck is quite silly and fails if the
   186  	# multi-line string includes some $var (even when it is properly outside of the
   187  	# single quotes) or when we use this syntax to execute commands in the
   188  	# string: $(command).
   189  	# So, just disable this check for our usage of update_config().
   190  	update_config '   .process.args = ["/bin/true"]
   191  			| .linux.seccomp = {
   192  				"defaultAction":"SCMP_ACT_ALLOW",
   193  				"listenerPath": "'"$SECCCOMP_AGENT_SOCKET"'",
   194  				"architectures": [ "SCMP_ARCH_X86", "SCMP_ARCH_X32", "SCMP_ARCH_X86_64" ],
   195  				"syscalls":[{ "names": [ "mkdir" ], "action": "SCMP_ACT_NOTIFY" }]
   196  			}
   197  			|.hooks = {
   198  				"startContainer": [ {
   199  						"path": "/bin/sh",
   200  						"args": [
   201  							"sh",
   202  							"-c",
   203  							"if [ $(ls /proc/self/fd/ | wc -l) -ne 4 ]; then echo \"File descriptors is not 4\". && ls /proc/self/fd/ | wc -l && exit 1; fi"
   204  						],
   205  				} ]
   206  			}'
   207  
   208  	runc run test_busybox
   209  	[ "$status" -eq 0 ]
   210  }
   211  
   212  # Check that example config in the seccomp agent dir works.
   213  @test "runc run [seccomp] (SCMP_ACT_NOTIFY example config)" {
   214  	# Run the script used in the seccomp agent example.
   215  	# This takes a bare config.json and modifies it to run an example.
   216  	"${INTEGRATION_ROOT}/../../contrib/cmd/seccompagent/gen-seccomp-example-cfg.sh"
   217  
   218  	# The listenerPath the previous command uses is the default used by the
   219  	# seccomp agent. However, inside bats the socket is in a bats tmp dir.
   220  	update_config '.linux.seccomp.listenerPath = "'"$SECCCOMP_AGENT_SOCKET"'"'
   221  
   222  	runc run test_busybox
   223  
   224  	[ "$status" -eq 0 ]
   225  	[[ "$output" == *"chmod:"*"test-file"*"No medium found"* ]]
   226  }