#!/usr/bin/env bats load helpers function setup() { setup_busybox } function teardown() { teardown_bundle } # is_allowed_fdtarget checks whether the target of a file descriptor symlink # conforms to the allowed whitelist. # # This whitelist reflects the set of file descriptors that runc legitimately # opens during container lifecycle operations (e.g., exec, create, and run). # If runc's internal behavior changes (e.g., new FD types are introduced), # this function MUST be updated accordingly to avoid false positives. # is_allowed_fdtarget() { local target="$1" { # pty devices for stdio grep -Ex "/dev/pts/[0-9]+" <<<"$target" || # eventfd, eventpoll, signalfd, etc. grep -Ex "anon_inode:\[.+\]" <<<"$target" || # procfs handle cache (pathrs-lite / libpathrs) grep -Ex "/(proc)?" <<<"$target" || # anonymous sockets used for IPC grep -Ex "socket:\[[0-9]+\]" <<<"$target" || # anonymous pipes used for I/O forwarding grep -Ex "pipe:\[[0-9]+\]" <<<"$target" || # "runc start" synchronisation barrier FIFO grep -Ex ".*/exec\.fifo" <<<"$target" || # temporary internal fd used in exec.fifo FIFO reopen (pathrs-lite / libpathrs) grep -Ex "(/proc)?/1/task/1/fd" <<<"$target" || # overlayfs binary reference (CVE-2019-5736) grep -Ex "/runc" <<<"$target" || # memfd cloned binary (CVE-2019-5736) grep -Fx "/memfd:runc_cloned:/proc/self/exe (deleted)" <<<"$target" || # Go 1.25+ runtime opens these cgroup v1 files (see https://go.dev/cl/670497). grep -Ex ".*/cpu.cfs_(quota|period)_us" <<<"$target" } >/dev/null return "$?" } @test "runc create[detect fd leak as comprehensively as possible]" { runc create --console-socket "$CONSOLE_SOCKET" test_busybox [ "$status" -eq 0 ] testcontainer test_busybox created pid=$(__runc state test_busybox | jq '.pid') violation_found=0 while IFS= read -rd '' link; do fd_name=$(basename "$link") # Skip . and .. if [[ "$fd_name" == "." || "$fd_name" == ".." ]]; then continue fi # Resolve symlink target (use readlink) target=$(readlink "$link" 2>/dev/null) if [[ -z "$target" ]]; then echo "Warning: Cannot read target of $link" continue fi if ! is_allowed_fdtarget "$target"; then echo "Violation: FD $fd_name -> '$target'" violation_found=1 else echo "Permitted: FD $fd_name -> '$target'" fi done < <(find "/proc/$pid/fd" -type l -print0) [ "$violation_found" -eq 0 ] } @test "runc create" { runc create --console-socket "$CONSOLE_SOCKET" test_busybox [ "$status" -eq 0 ] testcontainer test_busybox created runc start test_busybox [ "$status" -eq 0 ] testcontainer test_busybox running } @test "runc create exec" { runc create --console-socket "$CONSOLE_SOCKET" test_busybox [ "$status" -eq 0 ] testcontainer test_busybox created runc exec test_busybox true [ "$status" -eq 0 ] testcontainer test_busybox created runc start test_busybox [ "$status" -eq 0 ] testcontainer test_busybox running } @test "runc create --pid-file" { runc create --pid-file pid.txt --console-socket "$CONSOLE_SOCKET" test_busybox [ "$status" -eq 0 ] testcontainer test_busybox created [ -e pid.txt ] [[ $(cat pid.txt) = $(__runc state test_busybox | jq '.pid') ]] runc start test_busybox [ "$status" -eq 0 ] testcontainer test_busybox running } @test "runc create --pid-file with new CWD" { bundle="$(pwd)" mkdir pid_file cd pid_file runc create --pid-file pid.txt -b "$bundle" --console-socket "$CONSOLE_SOCKET" test_busybox [ "$status" -eq 0 ] testcontainer test_busybox created [ -e pid.txt ] [[ $(cat pid.txt) = $(__runc state test_busybox | jq '.pid') ]] runc start test_busybox [ "$status" -eq 0 ] testcontainer test_busybox running } # https://github.com/opencontainers/runc/issues/4394#issuecomment-2334926257 @test "runc create [shared pidns + rootless]" { # Remove pidns so it's shared with the host. update_config ' .linux.namespaces -= [{"type": "pid"}]' if [ $EUID -ne 0 ]; then if rootless_cgroup; then # Rootless containers have empty cgroup path by default. set_cgroups_path fi # Can't mount real /proc when rootless + no pidns, # so change it to a bind-mounted one from the host. update_config ' .mounts |= map((select(.type == "proc") | .type = "none" | .source = "/proc" | .options = ["rbind", "nosuid", "nodev", "noexec"] ) // .)' fi exp="Such configuration is strongly discouraged" runc create --console-socket "$CONSOLE_SOCKET" test [ "$status" -eq 0 ] if [ $EUID -ne 0 ] && ! rootless_cgroup; then [[ "$output" = *"$exp"* ]] else [[ "$output" != *"$exp"* ]] fi }