mirror of
https://github.com/google/security-research
synced 2026-06-08 14:27:23 +00:00
477 lines
16 KiB
Bash
Executable File
477 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"
|
|
|
|
install_if_missing "libguestfs-tools" "libguestfs-tools"
|
|
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
|