#!/bin/bash
# Reproducer for tmpfs leak via external file descriptor
#
# Scenario:
# 1. Create a "container" (new mount namespace) with a tmpfs
# 2. Create a file on the tmpfs
# 3. Process outside the container opens the file via /proc/<pid>/root/...
# 4. Container (mount namespace) is destroyed
# 5. tmpfs remains alive because external process holds a reference

set -e

WORKDIR="/tmp/tmpfs_leak_test_$$"
mkdir -p "$WORKDIR"

cleanup() {
    echo "[cleanup] Cleaning up..."
    # Close FD 3 if still open
    exec 3<&- 2>/dev/null || true
    jobs -p | xargs -r kill 2>/dev/null || true
    rm -rf "$WORKDIR" 2>/dev/null || true
}
trap cleanup EXIT

echo "=== tmpfs Leak Reproducer (Shell Version) ==="
echo ""
echo "This demonstrates how an external process holding a file descriptor"
echo "to a file inside a container's tmpfs can prevent container cleanup."
echo ""

# Step 1: Create a "container" process in a new mount namespace
echo "[1] Creating container with new mount namespace and tmpfs..."

# Create a pipe for synchronization
mkfifo "$WORKDIR/container_ready"
mkfifo "$WORKDIR/external_ready"

unshare --mount --fork bash -c '
    WORKDIR="'"$WORKDIR"'"
    echo "[container] PID: $$"
    # Make mount namespace private
    mount --make-rprivate /
    # Create and mount tmpfs
    mkdir -p /tmp/container_tmpfs
    mount -t tmpfs -o size=10M tmpfs_device /tmp/container_tmpfs
    # Create a file on the tmpfs
    echo "Secret container data - PID $$" > /tmp/container_tmpfs/secret_file.txt
    echo "[container] tmpfs mounted at /tmp/container_tmpfs"
    echo "[container] Created /tmp/container_tmpfs/secret_file.txt"
    # Signal ready (write to pipe)
    echo "ready" > "$WORKDIR/container_ready"
    echo "[container] Waiting for external process to grab FD..."
    # Wait for external process (read from pipe blocks until written)
    read _ < "$WORKDIR/external_ready"
    echo "[container] External process has FD. Exiting in 2 seconds..."
    sleep 2
    echo "[container] Exiting now - mount namespace will be destroyed"
' &

CONTAINER_PID=$!
echo "[main] Container shell PID: $CONTAINER_PID"

# Wait for container to be ready
echo "[2] Waiting for container to set up tmpfs..."
read _ < "$WORKDIR/container_ready"
echo "[+] Container is ready"

# Find the actual unshare child process
sleep 0.5
CHILD_PID=$(pgrep -P $CONTAINER_PID 2>/dev/null | head -1)
if [ -z "$CHILD_PID" ]; then
    CHILD_PID=$CONTAINER_PID
fi
echo "[+] Container process PID: $CHILD_PID"

# Step 2: External process opens file via /proc/<pid>/root
echo ""
echo "[3] External process opening file from container's tmpfs..."

PROC_PATH="/proc/$CHILD_PID/root/tmp/container_tmpfs/secret_file.txt"

if [ ! -f "$PROC_PATH" ]; then
    echo "[!] Cannot find file at $PROC_PATH"
    echo "[!] Trying with container shell PID..."
    PROC_PATH="/proc/$CONTAINER_PID/root/tmp/container_tmpfs/secret_file.txt"
fi

# Open the file and keep it open - save content for later
exec 3< "$PROC_PATH"
ORIGINAL_CONTENT=$(cat <&3)
echo "[external] Opened FD 3 to: $PROC_PATH"
echo "[external] File contents: $ORIGINAL_CONTENT"

# Signal container we have the FD
echo "ready" > "$WORKDIR/external_ready"
echo "[external] Signaled container"

# Wait for container to exit
echo "[external] Waiting for container to exit..."
wait $CONTAINER_PID 2>/dev/null || true

echo ""
echo "=============================================="
echo "[external] CONTAINER HAS EXITED!"
echo "[external] Mount namespace should be destroyed"
echo "=============================================="
echo ""

# Show we still have access - use /proc/self/fd/3 to read
echo "[external] But we still have FD 3 open!"
echo "[external] Reading via /proc/self/fd/3:"
cat /proc/self/fd/3 2>/dev/null || echo "(read failed - but FD still holds reference)"

echo ""
echo "[external] FD 3 details:"
ls -la /proc/self/fd/3 2>/dev/null || echo "(fd info not available)"
readlink /proc/self/fd/3 2>/dev/null || echo "(target path - note it's a deleted/orphaned file)"

echo ""
echo "[external] The tmpfs superblock remains in kernel memory!"
echo "[external] Even though the mount namespace is gone, the superblock persists"
echo "[external] because we hold a file reference."
echo ""
echo "[external] Holding FD for 10 seconds to allow inspection..."
echo ""
echo "You can check with: sudo drgn monitor_tmpfs.py"
echo ""

sleep 10

echo "[external] Closing FD 3..."
exec 3<&-

echo "[external] Done. tmpfs superblock should now be freed."
echo ""
echo "=== Reproducer Complete ==="
