mirror of
https://github.com/google/security-research
synced 2026-06-08 14:27:23 +00:00
7297a9e637
1) Disabled AppArmor block for unpriviledged user namespaces. This hapend in ubuntu-latest: https://discourse.ubuntu.com/t/understanding-apparmor-user-namespace-restriction/58007. Added this step to "get_android_dependencies.sh". 2) Using "get_android_dependencies.sh" script to handle dependencies for all execution options: server.py, exploit build, and exploit_reproduction. 3) Optimised exploit reproduction run time, by downloading releases and installing dependencies in parallel. 4) Conducted clean up to free up a bit of extra space for Cuttlefish runs (cleaning compilation leftovers and APT caches). 5) Using "cuttlefish.sh" to run VM in all execution options: server.py and GHA reproducer. Modified "cuttlefish.sh" to add "--test-mode" which allows easy testing by setting "falg" file permissions equivalent to exploit executable. It's disabled in prod. Improved Android16 detection logic (to enable necessary execution flags for "launch_cvd"), as apparently "launch_cvd --help" could fail sometimes. Improved cleaning logic (important as launch_cvd is super nasty and lefts a lot of tmps and random leftovers which could negatively impact next run of the VM) 6) Apparently in ubuntu-latest "sg -c" requires password. So implemented this logic canonical/data-platform-workflows@349e0c8. This is important as Cuttlefish couldn't run without user being added in groups. By default they suggest reboot to apply those changes, but it's impossible in GHA. 7) "Check PR content" logic is already updated in prod 8) Ensured that in exploit build environment NDK is added to the PATH, so simple Makefiles like: ``` exploit: exploit.c clang --target=x86_64-linux-android35 -static $^ -o exploit ``` The whole thing was tested with dummy exploit in my fork. 9) Implemented proper timeout check (30 mins) in "cuttlefish.sh" 10) Introduced sparse-checkout for the GHA.
475 lines
16 KiB
Bash
Executable File
475 lines
16 KiB
Bash
Executable File
#!/bin/bash
|
|
set -e
|
|
|
|
# Unset conflicting Android SDK environment variable if it exists
|
|
unset ANDROID_SDK_ROOT
|
|
|
|
# Check if running in exploit-build-only mode
|
|
EXPLOIT_BUILD_ONLY=false
|
|
if [ "$1" == "--exploit-build-only" ]; then
|
|
EXPLOIT_BUILD_ONLY=true
|
|
fi
|
|
|
|
ANDROID_COMPILE_SDK="android-34"
|
|
ANDROID_BUILD_TOOLS="35.0.0"
|
|
|
|
log() {
|
|
echo "[SETUP] $1"
|
|
}
|
|
|
|
error() {
|
|
echo "[ERROR] $1"
|
|
}
|
|
|
|
warn() {
|
|
echo "[WARN] $1"
|
|
}
|
|
|
|
install_if_missing() {
|
|
local package=$1
|
|
local display_name=${2:-$package}
|
|
|
|
if ! dpkg-query -W -f'${Status}' "$package" 2>/dev/null | grep -q 'ok installed'; then
|
|
log "Installing $display_name..."
|
|
sudo apt install -y "$package"
|
|
else
|
|
echo "$display_name is already installed"
|
|
fi
|
|
}
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ANDROID_SHELLSERVER_DIR="$SCRIPT_DIR/android_shellserver"
|
|
|
|
# Skip KVM check in exploit-build-only mode
|
|
if [ "$EXPLOIT_BUILD_ONLY" = false ]; then
|
|
if [ "$(grep -c -w 'vmx\|svm' /proc/cpuinfo)" == 0 ]; then
|
|
error "Virtualization with KVM is not available."
|
|
exit 1;
|
|
fi
|
|
fi
|
|
|
|
# System & Runtime Dependencies
|
|
log "Updating package lists..."
|
|
sudo apt-get update
|
|
|
|
log "Installing system dependencies..."
|
|
install_if_missing "git" "Git"
|
|
install_if_missing "curl" "Curl"
|
|
install_if_missing "wget" "Wget"
|
|
install_if_missing "unzip" "Unzip"
|
|
install_if_missing "build-essential" "Build Essential"
|
|
|
|
# Only install runtime dependencies if not in exploit-build-only mode
|
|
if [ "$EXPLOIT_BUILD_ONLY" = false ]; then
|
|
install_if_missing "socat" "Socat"
|
|
|
|
# Python dependencies
|
|
install_if_missing "python3-gmpy2" "python3-gmpy2"
|
|
install_if_missing "python-gmpy2-common" "python-gmpy2-common"
|
|
install_if_missing "python3-ecdsa" "python3-ecdsa"
|
|
fi
|
|
|
|
# Cuttlefish Installation - skip in exploit-build-only mode
|
|
if [ "$EXPLOIT_BUILD_ONLY" = false ]; then
|
|
log "Checking Cuttlefish installation..."
|
|
|
|
# Ensure repository is configured
|
|
if [ ! -f "/etc/apt/sources.list.d/artifact-registry.list" ]; then
|
|
log "Adding Artifact Registry GPG key..."
|
|
sudo curl -fsSL https://us-apt.pkg.dev/doc/repo-signing-key.gpg \
|
|
-o /etc/apt/trusted.gpg.d/artifact-registry.asc
|
|
sudo chmod a+r /etc/apt/trusted.gpg.d/artifact-registry.asc
|
|
|
|
log "Adding Cuttlefish repository..."
|
|
echo "deb https://us-apt.pkg.dev/projects/android-cuttlefish-artifacts android-cuttlefish main" \
|
|
| sudo tee /etc/apt/sources.list.d/artifact-registry.list > /dev/null
|
|
|
|
log "Updating package lists..."
|
|
sudo apt-get update
|
|
fi
|
|
|
|
# Check if Cuttlefish is installed
|
|
if dpkg-query -W -f'${Status}' 'cuttlefish-base' 2>/dev/null | grep -q 'ok installed'; then
|
|
log "Cuttlefish is installed. Checking for updates..."
|
|
|
|
# Refresh package information
|
|
sudo apt-get update -qq
|
|
|
|
# Get installed and available versions
|
|
INSTALLED_VERSION=$(dpkg-query -W -f='${Version}' cuttlefish-base 2>/dev/null)
|
|
AVAILABLE_VERSION=$(apt-cache policy cuttlefish-base | grep Candidate | awk '{print $2}')
|
|
|
|
echo " Installed version: $INSTALLED_VERSION"
|
|
echo " Available version: $AVAILABLE_VERSION"
|
|
|
|
# Compare versions using dpkg --compare-versions
|
|
if dpkg --compare-versions "$INSTALLED_VERSION" lt "$AVAILABLE_VERSION"; then
|
|
log "Newer version available. Upgrading Cuttlefish..."
|
|
sudo apt-get install -y --only-upgrade cuttlefish-base cuttlefish-user
|
|
log "Cuttlefish upgrade complete"
|
|
else
|
|
log "Cuttlefish is already up to date."
|
|
fi
|
|
else
|
|
log "Cuttlefish not found. Installing from Artifact Registry..."
|
|
|
|
# Make sure we have the latest package lists
|
|
sudo apt-get update -qq
|
|
|
|
log "Installing Cuttlefish packages..."
|
|
sudo apt-get install -y cuttlefish-base cuttlefish-user
|
|
|
|
log "Cuttlefish installation complete"
|
|
fi
|
|
|
|
# Cuttlefish Configuration & Verification
|
|
log "Verifying Cuttlefish environment configuration..."
|
|
|
|
echo "Updating user groups..."
|
|
sudo usermod -aG kvm,cvdnetwork,render $USER
|
|
|
|
echo "Verifying permissions..."
|
|
|
|
if [ -e /dev/kvm ]; then
|
|
ls -la /dev/kvm
|
|
else
|
|
warn "/dev/kvm not found. KVM support may not be available."
|
|
fi
|
|
|
|
if [ -e /dev/net/tun ]; then
|
|
ls -la /dev/net/tun
|
|
else
|
|
warn "/dev/net/tun not found."
|
|
fi
|
|
|
|
# Group Membership Check
|
|
NEEDS_RELOAD=false
|
|
|
|
if ! groups | grep -q cvdnetwork; then NEEDS_RELOAD=true; fi
|
|
if ! groups | grep -q kvm; then NEEDS_RELOAD=true; fi
|
|
if ! groups | grep -q render; then NEEDS_RELOAD=true; fi
|
|
|
|
if [ "$NEEDS_RELOAD" = true ]; then
|
|
warn "Group membership updated."
|
|
warn "You need to log out and log back in for these changes to persist."
|
|
warn "A temporary shell with new permissions will be spawned at the end of this script."
|
|
else
|
|
log "Group membership verified."
|
|
fi
|
|
fi
|
|
|
|
# Android SDK & Build Tools
|
|
install_if_missing "android-sdk" "Android SDK"
|
|
|
|
# Detect ANDROID_HOME location after apt installation (universal detection)
|
|
if [ -d "/usr/lib/android-sdk" ]; then
|
|
export ANDROID_HOME=/usr/lib/android-sdk
|
|
elif [ -d "/usr/local/lib/android/sdk" ]; then
|
|
export ANDROID_HOME=/usr/local/lib/android/sdk
|
|
elif [ -n "$ANDROID_HOME" ] && [ -d "$ANDROID_HOME" ]; then
|
|
# Use existing ANDROID_HOME if set and valid
|
|
log "Using existing ANDROID_HOME: $ANDROID_HOME"
|
|
else
|
|
# Try to find it using dpkg
|
|
ANDROID_HOME=$(dpkg -L android-sdk 2>/dev/null | grep -m1 "bin/sdkmanager$" | xargs dirname | xargs dirname || echo "")
|
|
if [ -z "$ANDROID_HOME" ] || [ ! -d "$ANDROID_HOME" ]; then
|
|
error "Could not detect ANDROID_HOME after installing android-sdk package"
|
|
error "Please set ANDROID_HOME manually"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
log "ANDROID_HOME set to: $ANDROID_HOME"
|
|
|
|
if [ ! -d "$ANDROID_HOME" ]; then
|
|
error "ANDROID_HOME directory ($ANDROID_HOME) does not exist."
|
|
error "The 'android-sdk' package might not have installed correctly."
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -d "$ANDROID_HOME/cmdline-tools/latest" ]; then
|
|
# Get latest cmdline-tools version from Android SDK repository
|
|
log "Detecting latest command-line tools version..."
|
|
LATEST_CMDLINE_VERSION=$(curl -s https://dl.google.com/android/repository/repository2-3.xml | \
|
|
grep -oP 'commandlinetools-linux-\K[0-9]+' | \
|
|
sort -n | \
|
|
tail -1)
|
|
|
|
if [ -n "$LATEST_CMDLINE_VERSION" ]; then
|
|
CMDLINE_TOOLS_URL="https://dl.google.com/android/repository/commandlinetools-linux-${LATEST_CMDLINE_VERSION}_latest.zip"
|
|
log "Using latest command-line tools version: $LATEST_CMDLINE_VERSION"
|
|
else
|
|
warn "Could not detect latest version, using default"
|
|
CMDLINE_TOOLS_URL="https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip"
|
|
fi
|
|
|
|
log "Downloading command line tools..."
|
|
wget -q --show-progress "$CMDLINE_TOOLS_URL" -O cmdline_tools.zip
|
|
unzip -q cmdline_tools.zip -d cmdline-tools
|
|
|
|
# Attempting to install cmdline-tools.
|
|
# WARNING: If ANDROID_HOME is not writable by the current user, this step will fail.
|
|
if [ ! -w "$ANDROID_HOME" ]; then
|
|
warn "$ANDROID_HOME is not writable. Trying with sudo..."
|
|
sudo mkdir -p "$ANDROID_HOME/cmdline-tools/latest"
|
|
sudo mv cmdline-tools/cmdline-tools/* "$ANDROID_HOME/cmdline-tools/latest/"
|
|
else
|
|
mkdir -p "$ANDROID_HOME/cmdline-tools/latest"
|
|
mv cmdline-tools/cmdline-tools/* "$ANDROID_HOME/cmdline-tools/latest/"
|
|
fi
|
|
|
|
rm -rf cmdline-tools cmdline_tools.zip
|
|
|
|
export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$PATH
|
|
else
|
|
echo "Cmdline-tools is already installed"
|
|
fi
|
|
|
|
SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
|
|
|
|
if [ -f "$SDKMANAGER" ]; then
|
|
log "Accepting licenses via SDKManager..."
|
|
|
|
# If not writable, we must use sudo to accept licenses/install
|
|
if [ ! -w "$ANDROID_HOME" ]; then
|
|
log "Using sudo for sdkmanager (system directory detected)..."
|
|
yes | sudo $SDKMANAGER --licenses >/dev/null 2>&1
|
|
|
|
log "Installing Android platform and build tools..."
|
|
sudo $SDKMANAGER --install "platforms;${ANDROID_COMPILE_SDK}"
|
|
sudo $SDKMANAGER --install "build-tools;${ANDROID_BUILD_TOOLS}"
|
|
else
|
|
yes | $SDKMANAGER --licenses >/dev/null 2>&1
|
|
$SDKMANAGER --install "platforms;${ANDROID_COMPILE_SDK}"
|
|
$SDKMANAGER --install "build-tools;${ANDROID_BUILD_TOOLS}"
|
|
fi
|
|
log "Platform and build tools installation complete"
|
|
else
|
|
error "sdkmanager not found at $SDKMANAGER"
|
|
exit 1
|
|
fi
|
|
|
|
# Android NDK Installation
|
|
log "Checking Android NDK installation..."
|
|
|
|
# Get the latest available NDK version from sdkmanager
|
|
NDK_VERSION=$($SDKMANAGER --list 2>/dev/null | grep "ndk;" | grep -v "ndk-bundle" | tail -1 | awk '{print $1}' | cut -d';' -f2)
|
|
|
|
if [ -z "$NDK_VERSION" ]; then
|
|
error "Could not detect latest NDK version from sdkmanager"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if NDK is already installed
|
|
if [ -d "$ANDROID_HOME/ndk/$NDK_VERSION" ]; then
|
|
log "NDK $NDK_VERSION is already installed"
|
|
else
|
|
log "Installing Android NDK version: $NDK_VERSION"
|
|
|
|
if [ ! -w "$ANDROID_HOME" ]; then
|
|
sudo $SDKMANAGER "ndk;${NDK_VERSION}"
|
|
else
|
|
$SDKMANAGER "ndk;${NDK_VERSION}"
|
|
fi
|
|
|
|
log "NDK installation complete"
|
|
fi
|
|
|
|
# Set NDK environment variables
|
|
export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$NDK_VERSION"
|
|
NDK_TOOLCHAIN_PATH="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
|
|
|
# Add NDK to PATH
|
|
export PATH="$NDK_TOOLCHAIN_PATH:$PATH"
|
|
|
|
log "ANDROID_NDK_HOME set to: $ANDROID_NDK_HOME"
|
|
log "NDK toolchain added to PATH"
|
|
|
|
# Check and fix AppArmor namespace restrictions (only when Cuttlefish will be used)
|
|
if [ "$EXPLOIT_BUILD_ONLY" = false ]; then
|
|
log "Checking AppArmor namespace restrictions..."
|
|
|
|
CURRENT_USERNS_RESTRICTION=$(sudo sysctl -n kernel.apparmor_restrict_unprivileged_userns 2>/dev/null || echo "not_found")
|
|
|
|
if [ "$CURRENT_USERNS_RESTRICTION" = "not_found" ]; then
|
|
log "AppArmor userns restriction not present on this system (likely not Ubuntu 24.04+)"
|
|
elif [ "$CURRENT_USERNS_RESTRICTION" = "1" ]; then
|
|
warn "AppArmor is restricting unprivileged user namespaces"
|
|
warn "This will cause Cuttlefish to fail with 'unshare(CLONE_NEWNS) failed: Operation not permitted'"
|
|
echo ""
|
|
|
|
if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; then
|
|
log "CI environment detected - applying fix automatically..."
|
|
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
|
log "AppArmor restriction disabled (temporary)"
|
|
else
|
|
echo "To fix this, you have two options:"
|
|
echo ""
|
|
echo "Option 1 (Temporary - until reboot):"
|
|
echo " sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0"
|
|
echo ""
|
|
echo "Option 2 (Permanent):"
|
|
echo " echo 'kernel.apparmor_restrict_unprivileged_userns = 0' | sudo tee /etc/sysctl.d/99-cuttlefish-userns.conf"
|
|
echo " sudo sysctl -p /etc/sysctl.d/99-cuttlefish-userns.conf"
|
|
echo ""
|
|
read -p "Would you like to apply the temporary fix now? (y/N): " -r REPLY
|
|
echo ""
|
|
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
log "Applying temporary fix..."
|
|
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
|
log "AppArmor restriction disabled (temporary - until reboot)"
|
|
echo ""
|
|
warn "To make this permanent, run:"
|
|
warn " echo 'kernel.apparmor_restrict_unprivileged_userns = 0' | sudo tee /etc/sysctl.d/99-cuttlefish-userns.conf"
|
|
warn " sudo sysctl -p /etc/sysctl.d/99-cuttlefish-userns.conf"
|
|
else
|
|
warn "Skipping AppArmor fix - Cuttlefish may fail to launch"
|
|
warn "You can apply the fix manually later if needed"
|
|
fi
|
|
fi
|
|
else
|
|
log "AppArmor userns restriction is already disabled (value: $CURRENT_USERNS_RESTRICTION)"
|
|
fi
|
|
|
|
echo ""
|
|
fi
|
|
|
|
# Build android_shellserver APK (only when not in exploit-build-only mode)
|
|
if [ "$EXPLOIT_BUILD_ONLY" = false ]; then
|
|
if [ ! -d "$ANDROID_SHELLSERVER_DIR" ]; then
|
|
error "Directory not found: $ANDROID_SHELLSERVER_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
cd "$ANDROID_SHELLSERVER_DIR"
|
|
|
|
if [ ! -f "gradlew" ]; then
|
|
error "gradlew not found. Not a valid Gradle project?"
|
|
exit 1
|
|
fi
|
|
|
|
NUM_CORES=$(nproc)
|
|
log "Detected $NUM_CORES CPU cores"
|
|
|
|
log "Configuring Gradle..."
|
|
|
|
if [ -f "gradle.properties" ]; then
|
|
warn "gradle.properties exists. Backing up to gradle.properties.bak"
|
|
cp gradle.properties gradle.properties.bak
|
|
fi
|
|
|
|
cat > gradle.properties << EOF
|
|
# AndroidX Support
|
|
android.useAndroidX=true
|
|
android.enableJetifier=true
|
|
|
|
# Performance optimizations
|
|
org.gradle.parallel=true
|
|
org.gradle.caching=true
|
|
org.gradle.configureondemand=true
|
|
org.gradle.workers.max=$NUM_CORES
|
|
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError --enable-native-access=ALL-UNNAMED
|
|
org.gradle.daemon=true
|
|
org.gradle.build-cache=true
|
|
EOF
|
|
|
|
log "Checking Gradle version..."
|
|
./gradlew --version --quiet 2>&1 | grep "Gradle "
|
|
|
|
./gradlew --stop > /dev/null 2>&1 || true
|
|
|
|
log "Cleaning build directories..."
|
|
rm -rf app/build build .gradle
|
|
|
|
export GRADLE_USER_HOME="$ANDROID_SHELLSERVER_DIR/.gradle"
|
|
|
|
if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; then
|
|
log "Running in CI environment - optimized for CI/CD"
|
|
GRADLE_OPTS="--parallel --build-cache --no-daemon"
|
|
else
|
|
log "Running locally - using daemon for faster subsequent builds"
|
|
GRADLE_OPTS="--parallel --build-cache"
|
|
fi
|
|
|
|
log "Generating keystore..."
|
|
./gradlew generateKeystore $GRADLE_OPTS --quiet
|
|
|
|
log "Building release APK (parallel mode with $NUM_CORES workers)..."
|
|
./gradlew --refresh-dependencies assembleRelease $GRADLE_OPTS --quiet
|
|
|
|
APK_PATH="app/build/outputs/apk/release/app-release.apk"
|
|
if [ -f "$APK_PATH" ]; then
|
|
log "Build successful!"
|
|
echo "APK: $(realpath "$APK_PATH")"
|
|
ls -lh "$APK_PATH"
|
|
else
|
|
error "Build failed: APK not found"
|
|
exit 1
|
|
fi
|
|
|
|
cd $SCRIPT_DIR
|
|
fi
|
|
|
|
# Export environment variables
|
|
if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; then
|
|
# CI/CD environment (GitHub Actions, etc.)
|
|
if [ -n "$GITHUB_ENV" ] && [ -n "$GITHUB_PATH" ]; then
|
|
log "Exporting environment variables for GitHub Actions..."
|
|
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
|
|
echo "ANDROID_NDK_HOME=$ANDROID_NDK_HOME" >> $GITHUB_ENV
|
|
echo "$NDK_TOOLCHAIN_PATH" >> $GITHUB_PATH
|
|
|
|
log "Environment variables exported to GitHub Actions:"
|
|
log " ANDROID_HOME=$ANDROID_HOME"
|
|
log " ANDROID_NDK_HOME=$ANDROID_NDK_HOME"
|
|
log " NDK in PATH: $NDK_TOOLCHAIN_PATH"
|
|
fi
|
|
else
|
|
# Local development environment
|
|
ENV_FILE="$SCRIPT_DIR/android_env.sh"
|
|
log "Creating environment file: $ENV_FILE"
|
|
|
|
cat > "$ENV_FILE" << EOF
|
|
# Android development environment
|
|
# Source this file to set up your environment:
|
|
# source $ENV_FILE
|
|
|
|
export ANDROID_HOME="$ANDROID_HOME"
|
|
export ANDROID_NDK_HOME="$ANDROID_NDK_HOME"
|
|
export PATH="$NDK_TOOLCHAIN_PATH:\$PATH"
|
|
|
|
echo "Android development environment loaded:"
|
|
echo " ANDROID_HOME=\$ANDROID_HOME"
|
|
echo " ANDROID_NDK_HOME=\$ANDROID_NDK_HOME"
|
|
echo " NDK toolchain added to PATH"
|
|
EOF
|
|
|
|
chmod +x "$ENV_FILE"
|
|
|
|
echo ""
|
|
warn "==============================================="
|
|
warn "Environment variables have been saved to:"
|
|
warn " $ENV_FILE"
|
|
warn ""
|
|
warn "To use them in your current shell, run:"
|
|
warn " source $ENV_FILE"
|
|
warn "==============================================="
|
|
echo ""
|
|
fi
|
|
|
|
log "... The End ..."
|
|
|
|
if [ "$EXPLOIT_BUILD_ONLY" = false ] && [ "$NEEDS_RELOAD" = true ]; then
|
|
echo ""
|
|
if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; then
|
|
warn "Running in CI environment - group changes detected"
|
|
warn "Groups will be activated using 'sudo --user \"$USER\""
|
|
warn "--preserve-env --preserve-env=PATH -- env --' command"
|
|
warn "when running Cuttlefish"
|
|
else
|
|
warn "Spawning new shell with updated group permissions..."
|
|
exec sudo su - $USER
|
|
fi
|
|
fi
|