github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/integration/seccomp_test.go (about) 1 //go:build linux && cgo && seccomp 2 // +build linux,cgo,seccomp 3 4 package integration 5 6 import ( 7 "strings" 8 "syscall" 9 "testing" 10 11 "github.com/opencontainers/runc/libcontainer" 12 "github.com/opencontainers/runc/libcontainer/configs" 13 libseccomp "github.com/seccomp/libseccomp-golang" 14 ) 15 16 func TestSeccompDenySyslogWithErrno(t *testing.T) { 17 if testing.Short() { 18 return 19 } 20 21 errnoRet := uint(syscall.ESRCH) 22 23 config := newTemplateConfig(t, nil) 24 config.Seccomp = &configs.Seccomp{ 25 DefaultAction: configs.Allow, 26 Syscalls: []*configs.Syscall{ 27 { 28 Name: "syslog", 29 Action: configs.Errno, 30 ErrnoRet: &errnoRet, 31 }, 32 }, 33 } 34 35 container, err := newContainer(t, config) 36 ok(t, err) 37 defer container.Destroy() //nolint:errcheck 38 39 buffers := newStdBuffers() 40 pwd := &libcontainer.Process{ 41 Cwd: "/", 42 Args: []string{"dmesg"}, 43 Env: standardEnvironment, 44 Stdin: buffers.Stdin, 45 Stdout: buffers.Stdout, 46 Stderr: buffers.Stderr, 47 Init: true, 48 } 49 50 err = container.Run(pwd) 51 ok(t, err) 52 ps, err := pwd.Wait() 53 if err == nil { 54 t.Fatal("Expecting error (negative return code); instead exited cleanly!") 55 } 56 57 var exitCode int 58 status := ps.Sys().(syscall.WaitStatus) 59 if status.Exited() { 60 exitCode = status.ExitStatus() 61 } else if status.Signaled() { 62 exitCode = -int(status.Signal()) 63 } else { 64 t.Fatalf("Unrecognized exit reason!") 65 } 66 67 if exitCode == 0 { 68 t.Fatalf("dmesg should fail with negative exit code, instead got %d!", exitCode) 69 } 70 71 expected := "dmesg: klogctl: No such process" 72 actual := strings.Trim(buffers.Stderr.String(), "\n") 73 if actual != expected { 74 t.Fatalf("Expected output %s but got %s\n", expected, actual) 75 } 76 } 77 78 func TestSeccompDenySyslog(t *testing.T) { 79 if testing.Short() { 80 return 81 } 82 83 config := newTemplateConfig(t, nil) 84 config.Seccomp = &configs.Seccomp{ 85 DefaultAction: configs.Allow, 86 Syscalls: []*configs.Syscall{ 87 { 88 Name: "syslog", 89 Action: configs.Errno, 90 }, 91 }, 92 } 93 94 container, err := newContainer(t, config) 95 ok(t, err) 96 defer container.Destroy() //nolint:errcheck 97 98 buffers := newStdBuffers() 99 pwd := &libcontainer.Process{ 100 Cwd: "/", 101 Args: []string{"dmesg"}, 102 Env: standardEnvironment, 103 Stdin: buffers.Stdin, 104 Stdout: buffers.Stdout, 105 Stderr: buffers.Stderr, 106 Init: true, 107 } 108 109 err = container.Run(pwd) 110 ok(t, err) 111 ps, err := pwd.Wait() 112 if err == nil { 113 t.Fatal("Expecting error (negative return code); instead exited cleanly!") 114 } 115 116 var exitCode int 117 status := ps.Sys().(syscall.WaitStatus) 118 if status.Exited() { 119 exitCode = status.ExitStatus() 120 } else if status.Signaled() { 121 exitCode = -int(status.Signal()) 122 } else { 123 t.Fatalf("Unrecognized exit reason!") 124 } 125 126 if exitCode == 0 { 127 t.Fatalf("dmesg should fail with negative exit code, instead got %d!", exitCode) 128 } 129 130 expected := "dmesg: klogctl: Operation not permitted" 131 actual := strings.Trim(buffers.Stderr.String(), "\n") 132 if actual != expected { 133 t.Fatalf("Expected output %s but got %s\n", expected, actual) 134 } 135 } 136 137 func TestSeccompPermitWriteConditional(t *testing.T) { 138 if testing.Short() { 139 return 140 } 141 142 config := newTemplateConfig(t, nil) 143 config.Seccomp = &configs.Seccomp{ 144 DefaultAction: configs.Allow, 145 Syscalls: []*configs.Syscall{ 146 { 147 Name: "write", 148 Action: configs.Errno, 149 Args: []*configs.Arg{ 150 { 151 Index: 0, 152 Value: 2, 153 Op: configs.EqualTo, 154 }, 155 }, 156 }, 157 }, 158 } 159 160 container, err := newContainer(t, config) 161 ok(t, err) 162 defer container.Destroy() //nolint:errcheck 163 164 buffers := newStdBuffers() 165 dmesg := &libcontainer.Process{ 166 Cwd: "/", 167 Args: []string{"busybox", "ls", "/"}, 168 Env: standardEnvironment, 169 Stdin: buffers.Stdin, 170 Stdout: buffers.Stdout, 171 Stderr: buffers.Stderr, 172 Init: true, 173 } 174 175 err = container.Run(dmesg) 176 ok(t, err) 177 if _, err := dmesg.Wait(); err != nil { 178 t.Fatalf("%s: %s", err, buffers.Stderr) 179 } 180 } 181 182 func TestSeccompDenyWriteConditional(t *testing.T) { 183 if testing.Short() { 184 return 185 } 186 187 // Only test if library version is v2.2.1 or higher 188 // Conditional filtering will always error in v2.2.0 and lower 189 major, minor, micro := libseccomp.GetLibraryVersion() 190 if (major == 2 && minor < 2) || (major == 2 && minor == 2 && micro < 1) { 191 return 192 } 193 194 config := newTemplateConfig(t, nil) 195 config.Seccomp = &configs.Seccomp{ 196 DefaultAction: configs.Allow, 197 Syscalls: []*configs.Syscall{ 198 { 199 Name: "write", 200 Action: configs.Errno, 201 Args: []*configs.Arg{ 202 { 203 Index: 0, 204 Value: 2, 205 Op: configs.EqualTo, 206 }, 207 }, 208 }, 209 }, 210 } 211 212 container, err := newContainer(t, config) 213 ok(t, err) 214 defer container.Destroy() //nolint:errcheck 215 216 buffers := newStdBuffers() 217 dmesg := &libcontainer.Process{ 218 Cwd: "/", 219 Args: []string{"busybox", "ls", "does_not_exist"}, 220 Env: standardEnvironment, 221 Stdin: buffers.Stdin, 222 Stdout: buffers.Stdout, 223 Stderr: buffers.Stderr, 224 Init: true, 225 } 226 227 err = container.Run(dmesg) 228 ok(t, err) 229 230 ps, err := dmesg.Wait() 231 if err == nil { 232 t.Fatal("Expecting negative return, instead got 0!") 233 } 234 235 var exitCode int 236 status := ps.Sys().(syscall.WaitStatus) 237 if status.Exited() { 238 exitCode = status.ExitStatus() 239 } else if status.Signaled() { 240 exitCode = -int(status.Signal()) 241 } else { 242 t.Fatalf("Unrecognized exit reason!") 243 } 244 245 if exitCode == 0 { 246 t.Fatalf("Busybox should fail with negative exit code, instead got %d!", exitCode) 247 } 248 249 // We're denying write to stderr, so we expect an empty buffer 250 expected := "" 251 actual := strings.Trim(buffers.Stderr.String(), "\n") 252 if actual != expected { 253 t.Fatalf("Expected output %s but got %s\n", expected, actual) 254 } 255 } 256 257 func TestSeccompPermitWriteMultipleConditions(t *testing.T) { 258 if testing.Short() { 259 return 260 } 261 262 config := newTemplateConfig(t, nil) 263 config.Seccomp = &configs.Seccomp{ 264 DefaultAction: configs.Allow, 265 Syscalls: []*configs.Syscall{ 266 { 267 Name: "write", 268 Action: configs.Errno, 269 Args: []*configs.Arg{ 270 { 271 Index: 0, 272 Value: 2, 273 Op: configs.EqualTo, 274 }, 275 { 276 Index: 2, 277 Value: 0, 278 Op: configs.NotEqualTo, 279 }, 280 }, 281 }, 282 }, 283 } 284 285 buffers := runContainerOk(t, config, "ls", "/") 286 // We don't need to verify the actual thing printed 287 // Just that something was written to stdout 288 if len(buffers.Stdout.String()) == 0 { 289 t.Fatalf("Nothing was written to stdout, write call failed!\n") 290 } 291 } 292 293 func TestSeccompDenyWriteMultipleConditions(t *testing.T) { 294 if testing.Short() { 295 return 296 } 297 298 // Only test if library version is v2.2.1 or higher 299 // Conditional filtering will always error in v2.2.0 and lower 300 major, minor, micro := libseccomp.GetLibraryVersion() 301 if (major == 2 && minor < 2) || (major == 2 && minor == 2 && micro < 1) { 302 return 303 } 304 305 config := newTemplateConfig(t, nil) 306 config.Seccomp = &configs.Seccomp{ 307 DefaultAction: configs.Allow, 308 Syscalls: []*configs.Syscall{ 309 { 310 Name: "write", 311 Action: configs.Errno, 312 Args: []*configs.Arg{ 313 { 314 Index: 0, 315 Value: 2, 316 Op: configs.EqualTo, 317 }, 318 { 319 Index: 2, 320 Value: 0, 321 Op: configs.NotEqualTo, 322 }, 323 }, 324 }, 325 }, 326 } 327 328 buffers, exitCode, err := runContainer(t, config, "ls", "/does_not_exist") 329 if err == nil { 330 t.Fatalf("Expecting error return, instead got 0") 331 } 332 if exitCode == 0 { 333 t.Fatalf("Busybox should fail with negative exit code, instead got %d!", exitCode) 334 } 335 336 expected := "" 337 actual := strings.Trim(buffers.Stderr.String(), "\n") 338 if actual != expected { 339 t.Fatalf("Expected output %s but got %s\n", expected, actual) 340 } 341 } 342 343 func TestSeccompMultipleConditionSameArgDeniesStdout(t *testing.T) { 344 if testing.Short() { 345 return 346 } 347 348 // Prevent writing to both stdout and stderr. 349 config := newTemplateConfig(t, nil) 350 config.Seccomp = &configs.Seccomp{ 351 DefaultAction: configs.Allow, 352 Syscalls: []*configs.Syscall{ 353 { 354 Name: "write", 355 Action: configs.Errno, 356 Args: []*configs.Arg{ 357 { 358 Index: 0, 359 Value: 1, 360 Op: configs.EqualTo, 361 }, 362 { 363 Index: 0, 364 Value: 2, 365 Op: configs.EqualTo, 366 }, 367 }, 368 }, 369 }, 370 } 371 372 buffers := runContainerOk(t, config, "ls", "/") 373 // Verify that nothing was printed 374 if len(buffers.Stdout.String()) != 0 { 375 t.Fatalf("Something was written to stdout, write call succeeded!\n") 376 } 377 } 378 379 func TestSeccompMultipleConditionSameArgDeniesStderr(t *testing.T) { 380 if testing.Short() { 381 return 382 } 383 384 // Prevent writing to both stdout and stderr. 385 config := newTemplateConfig(t, nil) 386 config.Seccomp = &configs.Seccomp{ 387 DefaultAction: configs.Allow, 388 Syscalls: []*configs.Syscall{ 389 { 390 Name: "write", 391 Action: configs.Errno, 392 Args: []*configs.Arg{ 393 { 394 Index: 0, 395 Value: 1, 396 Op: configs.EqualTo, 397 }, 398 { 399 Index: 0, 400 Value: 2, 401 Op: configs.EqualTo, 402 }, 403 }, 404 }, 405 }, 406 } 407 408 buffers, exitCode, err := runContainer(t, config, "ls", "/does_not_exist") 409 if err == nil { 410 t.Fatalf("Expecting error return, instead got 0") 411 } 412 if exitCode == 0 { 413 t.Fatalf("Busybox should fail with negative exit code, instead got %d!", exitCode) 414 } 415 // Verify nothing was printed 416 if len(buffers.Stderr.String()) != 0 { 417 t.Fatalf("Something was written to stderr, write call succeeded!\n") 418 } 419 }