From 5e7fffe472ef281574ee5796e0b7b5bc20a0b5b0 Mon Sep 17 00:00:00 2001 From: Dome Pongmongkol Date: Wed, 20 May 2026 13:45:02 -0700 Subject: [PATCH 1/5] Add Doze network-block repro test script Automated script to reproduce the Android Doze network block that affects Broker auth triggered from background callers (FCM notifications). The script: - Verifies prerequisites (adb, broker, SilentAuthReceiver) - Forces Doze via adb - Sends a broadcast to trigger acquireTokenSilent from background context - Captures logcat and reports PASS/FAIL - Cleans up Doze state Requires MsalTestApp from msal PR #2516 (SilentAuthReceiver branch). --- scripts/doze-repro-test.sh | 215 +++++++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 scripts/doze-repro-test.sh diff --git a/scripts/doze-repro-test.sh b/scripts/doze-repro-test.sh new file mode 100644 index 00000000..bca82468 --- /dev/null +++ b/scripts/doze-repro-test.sh @@ -0,0 +1,215 @@ +#!/usr/bin/env bash +# doze-repro-test.sh — Reproduce Android Doze network block on Broker auth +# +# This script tests that the Android Broker (Authenticator/BrokerHost) fails +# with a DNS/network error when a background caller triggers silent auth +# during Doze mode. It uses the SilentAuthReceiver in MsalTestApp to invoke +# acquireTokenSilent from a BroadcastReceiver (PROCESS_STATE_RECEIVER), +# which does NOT elevate the Broker's process importance enough to get a +# dozable-allow firewall rule from NetworkPolicyManagerService. +# +# Root cause (validated via AOSP source + on-device netpolicy dump): +# When a foreground app binds to Broker via IPC, Android's +# NetworkPolicyManagerService dynamically adds a dozable-allow firewall +# rule for the Broker's UID. When the caller is in a background context +# (BroadcastReceiver handling FCM), the binding does NOT elevate the +# Broker enough — Doze firewall blocks its DNS resolution. +# +# Prerequisites (manual, one-time): +# 1. Install exactly ONE broker app (Authenticator, Company Portal, +# BrokerHost, or Link to Windows) +# 2. Install MsalTestApp (must have SilentAuthReceiver registered) +# 3. Sign in to an account in MsalTestApp via interactive auth +# 4. Connect device via USB with adb authorized +# +# Usage: +# bash scripts/doze-repro-test.sh +# +# Expected result: +# AUTH FAILED with either: +# - io_error: Doze firewall blocked Broker's DNS resolution +# - device_network_not_available_doze_mode: Broker's proactive Doze check +# TOKEN ACQUIRED if broker is battery-exempt (DeviceIdle whitelist) +# +set -euo pipefail + +RECEIVER_PKG="com.msft.identity.client.sample.local" +RECEIVER_CLASS="com.microsoft.identity.client.testapp.SilentAuthReceiver" +RECEIVER_ACTION="com.microsoft.identity.client.testapp.SILENT_AUTH" +WAIT_SECONDS=15 +TAG="SilentAuthReceiver" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo "=============================================" +echo " Doze Network Block Reproduction Test" +echo "=============================================" +echo "" + +# --- Prerequisite checks --- +echo "[1/8] Checking adb connection..." +if ! adb devices 2>/dev/null | grep -q "device$"; then + echo -e "${RED}ERROR: No adb device connected.${NC}" + echo "Connect a device via USB and authorize adb." + exit 1 +fi +SERIAL=$(adb devices | grep "device$" | head -1 | awk '{print $1}') +echo " Device: $SERIAL" + +echo "[2/8] Checking SilentAuthReceiver is registered..." +if ! adb shell "dumpsys package $RECEIVER_PKG" 2>/dev/null | grep -q "SilentAuthReceiver"; then + echo -e "${RED}ERROR: SilentAuthReceiver not found in $RECEIVER_PKG.${NC}" + echo "Install MsalTestApp." + exit 1 +fi +echo " SilentAuthReceiver: registered" + +echo "[3/8] Checking broker apps installed..." +BROKER_PKGS=( + "com.azure.authenticator" + "com.microsoft.windowsintune.companyportal" + "com.microsoft.identity.testuserapp" + "com.microsoft.appmanager" +) +BROKER_NAMES=( + "Microsoft Authenticator" + "Company Portal" + "BrokerHost (test)" + "Link to Windows" +) +INSTALLED_BROKERS=() +INSTALLED_NAMES=() +for i in "${!BROKER_PKGS[@]}"; do + if adb shell pm list packages -e 2>/dev/null | grep -q "${BROKER_PKGS[$i]}"; then + INSTALLED_BROKERS+=("${BROKER_PKGS[$i]}") + INSTALLED_NAMES+=("${BROKER_NAMES[$i]}") + fi +done +if [ ${#INSTALLED_BROKERS[@]} -eq 0 ]; then + echo -e "${RED}ERROR: No broker app installed.${NC}" + echo "Install one of: Authenticator, Company Portal, BrokerHost, or Link to Windows." + exit 1 +fi +if [ ${#INSTALLED_BROKERS[@]} -gt 1 ]; then + echo -e "${RED}ERROR: Multiple broker apps installed (${#INSTALLED_BROKERS[@]}):${NC}" + for i in "${!INSTALLED_BROKERS[@]}"; do + echo " - ${INSTALLED_NAMES[$i]} (${INSTALLED_BROKERS[$i]})" + done + echo "" + echo "Uninstall all but one broker to get a deterministic test." + echo "With multiple brokers, MSAL's broker discovery picks one at runtime" + echo "and the test can't guarantee which broker handles the request." + exit 1 +fi +BROKER="${INSTALLED_BROKERS[0]}" +BROKER_NAME="${INSTALLED_NAMES[0]}" +echo " Broker: $BROKER_NAME ($BROKER)" + +# --- Battery exemption check --- +echo "[4/8] Checking battery optimization for broker..." +BATTERY_EXEMPT=false +if adb shell dumpsys deviceidle whitelist 2>/dev/null | grep -q "$BROKER"; then + BATTERY_EXEMPT=true +fi +if [ "$BATTERY_EXEMPT" = true ]; then + echo -e "${YELLOW} WARNING: $BROKER_NAME is battery-exempt (DeviceIdle whitelist).${NC}" + echo " The broker may bypass Doze network restrictions." + echo " To remove exemption: Settings > Apps > $BROKER_NAME > Battery > Optimized" + echo " Or run: adb shell dumpsys deviceidle whitelist -$BROKER" + echo "" + echo -e " Continuing anyway — test may show UNEXPECTED SUCCESS...${NC}" +else + echo " Battery optimization: active (not exempt) — good" +fi + +# --- Force Doze --- +echo "[5/8] Forcing Doze mode..." +adb logcat -c +adb shell dumpsys battery unplug > /dev/null 2>&1 +adb shell dumpsys deviceidle force-idle > /dev/null 2>&1 + +DOZE_STATE=$(adb shell dumpsys deviceidle get deep 2>/dev/null) +IDLE_MODE=$(adb shell "dumpsys power | grep mDeviceIdleMode" 2>/dev/null | tr -d '[:space:]') +echo " Doze state: $DOZE_STATE" +echo " $IDLE_MODE" + +if [ "$DOZE_STATE" != "IDLE" ]; then + echo -e "${RED}ERROR: Failed to enter Doze. State: $DOZE_STATE${NC}" + adb shell dumpsys deviceidle unforce > /dev/null 2>&1 + adb shell dumpsys battery reset > /dev/null 2>&1 + exit 1 +fi + +# --- Send broadcast --- +echo "[6/8] Sending SILENT_AUTH broadcast (background context)..." +adb shell "am broadcast \ + -a $RECEIVER_ACTION \ + -n $RECEIVER_PKG/$RECEIVER_CLASS \ + --es scopes 'https://graph.microsoft.com/.default'" > /dev/null 2>&1 + +echo " Waiting ${WAIT_SECONDS}s for auth to complete..." +sleep "$WAIT_SECONDS" + +# --- Capture results --- +echo "[7/8] Capturing logcat results..." +LOGCAT=$(adb logcat -d -s "${TAG}:*" 2>/dev/null) + +# --- Cleanup --- +echo "[8/8] Cleaning up Doze..." +adb shell dumpsys deviceidle unforce > /dev/null 2>&1 +adb shell dumpsys battery reset > /dev/null 2>&1 +echo " Doze reset to ACTIVE" + +# --- Report results --- +echo "" +echo "=============================================" +echo " Results" +echo "=============================================" +echo "" + +if echo "$LOGCAT" | grep -q "=== SUCCESS ==="; then + echo -e "${GREEN}Result: TOKEN ACQUIRED${NC}" + echo "" + echo "The Broker had network access during Doze." + if [ "$BATTERY_EXEMPT" = true ]; then + echo " (Broker is battery-exempt — this is expected)" + fi + echo "" + echo "$LOGCAT" | grep "$TAG" +elif echo "$LOGCAT" | grep -q "=== FAILED ==="; then + ERROR_CODE=$(echo "$LOGCAT" | grep "Error code:" | sed 's/.*Error code: //') + ERROR_MSG=$(echo "$LOGCAT" | grep "Message:" | sed 's/.*Message: //') + + echo -e "${RED}Result: AUTH FAILED${NC}" + echo "" + echo " Error code: $ERROR_CODE" + echo " Message: $ERROR_MSG" + echo "" + + if echo "$ERROR_CODE" | grep -q "io_error"; then + echo " Interpretation: Doze firewall blocked Broker's network." + echo " This is the raw DNS/network failure — the Broker attempted" + echo " the eSTS call but Android's dozable chain dropped the packet." + elif echo "$ERROR_CODE" | grep -q "device_network_not_available\|power_optimization"; then + echo " Interpretation: Broker's proactive Doze check caught it." + echo " The powerOptCheck detected Doze before attempting the" + echo " network call and returned a classified error." + else + echo " Interpretation: Unexpected error code — see details above." + fi +elif echo "$LOGCAT" | grep -q "=== CANCELLED ==="; then + echo -e "${YELLOW}Result: AUTH CANCELLED${NC}" + echo "$LOGCAT" | grep "$TAG" +elif echo "$LOGCAT" | grep -q "No signed-in account\|No accounts found"; then + echo -e "${YELLOW}Result: NO ACCOUNT${NC}" + echo " Sign in to MsalTestApp first via interactive auth." +else + echo -e "${YELLOW}Result: NO OUTPUT${NC}" + echo " SilentAuthReceiver did not produce expected logs." + echo "" + echo "Raw logcat:" + echo "$LOGCAT" | head -20 +fi From fc315ac9b7ccead1273f8ecf239f6e4d2f35031f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 23:09:03 +0000 Subject: [PATCH 2/5] Fix Doze script cleanup and serial-scoped adb usage Agent-Logs-Url: https://github.com/AzureAD/android-complete/sessions/bf5089a0-3aa4-4100-b20d-04b8dfc75776 Co-authored-by: rpdome <19558668+rpdome@users.noreply.github.com> --- scripts/doze-repro-test.sh | 48 ++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/scripts/doze-repro-test.sh b/scripts/doze-repro-test.sh index bca82468..dffd180e 100644 --- a/scripts/doze-repro-test.sh +++ b/scripts/doze-repro-test.sh @@ -38,6 +38,7 @@ RECEIVER_CLASS="com.microsoft.identity.client.testapp.SilentAuthReceiver" RECEIVER_ACTION="com.microsoft.identity.client.testapp.SILENT_AUTH" WAIT_SECONDS=15 TAG="SilentAuthReceiver" +DOZE_FORCED=false RED='\033[0;31m' GREEN='\033[0;32m' @@ -58,9 +59,26 @@ if ! adb devices 2>/dev/null | grep -q "device$"; then fi SERIAL=$(adb devices | grep "device$" | head -1 | awk '{print $1}') echo " Device: $SERIAL" +ADB=(adb -s "$SERIAL") + +adb_cmd() { + "${ADB[@]}" "$@" +} + +cleanup() { + if [ "$DOZE_FORCED" = true ]; then + echo "[8/8] Cleaning up Doze..." + adb_cmd shell dumpsys deviceidle unforce > /dev/null 2>&1 || true + adb_cmd shell dumpsys battery reset > /dev/null 2>&1 || true + DOZE_FORCED=false + echo " Doze reset to ACTIVE" + fi +} + +trap cleanup EXIT echo "[2/8] Checking SilentAuthReceiver is registered..." -if ! adb shell "dumpsys package $RECEIVER_PKG" 2>/dev/null | grep -q "SilentAuthReceiver"; then +if ! adb_cmd shell "dumpsys package $RECEIVER_PKG" 2>/dev/null | grep -q "SilentAuthReceiver"; then echo -e "${RED}ERROR: SilentAuthReceiver not found in $RECEIVER_PKG.${NC}" echo "Install MsalTestApp." exit 1 @@ -83,7 +101,7 @@ BROKER_NAMES=( INSTALLED_BROKERS=() INSTALLED_NAMES=() for i in "${!BROKER_PKGS[@]}"; do - if adb shell pm list packages -e 2>/dev/null | grep -q "${BROKER_PKGS[$i]}"; then + if adb_cmd shell pm list packages -e 2>/dev/null | grep -q "${BROKER_PKGS[$i]}"; then INSTALLED_BROKERS+=("${BROKER_PKGS[$i]}") INSTALLED_NAMES+=("${BROKER_NAMES[$i]}") fi @@ -111,7 +129,7 @@ echo " Broker: $BROKER_NAME ($BROKER)" # --- Battery exemption check --- echo "[4/8] Checking battery optimization for broker..." BATTERY_EXEMPT=false -if adb shell dumpsys deviceidle whitelist 2>/dev/null | grep -q "$BROKER"; then +if adb_cmd shell dumpsys deviceidle whitelist 2>/dev/null | grep -q "$BROKER"; then BATTERY_EXEMPT=true fi if [ "$BATTERY_EXEMPT" = true ]; then @@ -127,25 +145,24 @@ fi # --- Force Doze --- echo "[5/8] Forcing Doze mode..." -adb logcat -c -adb shell dumpsys battery unplug > /dev/null 2>&1 -adb shell dumpsys deviceidle force-idle > /dev/null 2>&1 +adb_cmd logcat -c +adb_cmd shell dumpsys battery unplug > /dev/null 2>&1 +adb_cmd shell dumpsys deviceidle force-idle > /dev/null 2>&1 +DOZE_FORCED=true -DOZE_STATE=$(adb shell dumpsys deviceidle get deep 2>/dev/null) -IDLE_MODE=$(adb shell "dumpsys power | grep mDeviceIdleMode" 2>/dev/null | tr -d '[:space:]') +DOZE_STATE=$(adb_cmd shell dumpsys deviceidle get deep 2>/dev/null) +IDLE_MODE=$(adb_cmd shell "dumpsys power | grep mDeviceIdleMode" 2>/dev/null | tr -d '[:space:]') echo " Doze state: $DOZE_STATE" echo " $IDLE_MODE" if [ "$DOZE_STATE" != "IDLE" ]; then echo -e "${RED}ERROR: Failed to enter Doze. State: $DOZE_STATE${NC}" - adb shell dumpsys deviceidle unforce > /dev/null 2>&1 - adb shell dumpsys battery reset > /dev/null 2>&1 exit 1 fi # --- Send broadcast --- echo "[6/8] Sending SILENT_AUTH broadcast (background context)..." -adb shell "am broadcast \ +adb_cmd shell "am broadcast \ -a $RECEIVER_ACTION \ -n $RECEIVER_PKG/$RECEIVER_CLASS \ --es scopes 'https://graph.microsoft.com/.default'" > /dev/null 2>&1 @@ -155,13 +172,8 @@ sleep "$WAIT_SECONDS" # --- Capture results --- echo "[7/8] Capturing logcat results..." -LOGCAT=$(adb logcat -d -s "${TAG}:*" 2>/dev/null) - -# --- Cleanup --- -echo "[8/8] Cleaning up Doze..." -adb shell dumpsys deviceidle unforce > /dev/null 2>&1 -adb shell dumpsys battery reset > /dev/null 2>&1 -echo " Doze reset to ACTIVE" +LOGCAT=$(adb_cmd logcat -d -s "${TAG}:*" 2>/dev/null) +cleanup # --- Report results --- echo "" From 66f7568e29ec9b26f2ab671224bb55e6cce79a94 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 23:09:59 +0000 Subject: [PATCH 3/5] Remove redundant explicit cleanup call in doze script Agent-Logs-Url: https://github.com/AzureAD/android-complete/sessions/bf5089a0-3aa4-4100-b20d-04b8dfc75776 Co-authored-by: rpdome <19558668+rpdome@users.noreply.github.com> --- scripts/doze-repro-test.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/doze-repro-test.sh b/scripts/doze-repro-test.sh index dffd180e..8aadd5dd 100644 --- a/scripts/doze-repro-test.sh +++ b/scripts/doze-repro-test.sh @@ -173,7 +173,6 @@ sleep "$WAIT_SECONDS" # --- Capture results --- echo "[7/8] Capturing logcat results..." LOGCAT=$(adb_cmd logcat -d -s "${TAG}:*" 2>/dev/null) -cleanup # --- Report results --- echo "" From da21a41c6af3c3b177fabbdfacb0919b4f993848 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 23:10:38 +0000 Subject: [PATCH 4/5] Harden serial resolution and signal-based cleanup trap Agent-Logs-Url: https://github.com/AzureAD/android-complete/sessions/bf5089a0-3aa4-4100-b20d-04b8dfc75776 Co-authored-by: rpdome <19558668+rpdome@users.noreply.github.com> --- scripts/doze-repro-test.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/doze-repro-test.sh b/scripts/doze-repro-test.sh index 8aadd5dd..219505dc 100644 --- a/scripts/doze-repro-test.sh +++ b/scripts/doze-repro-test.sh @@ -58,6 +58,10 @@ if ! adb devices 2>/dev/null | grep -q "device$"; then exit 1 fi SERIAL=$(adb devices | grep "device$" | head -1 | awk '{print $1}') +if [ -z "$SERIAL" ]; then + echo -e "${RED}ERROR: Failed to resolve adb device serial.${NC}" + exit 1 +fi echo " Device: $SERIAL" ADB=(adb -s "$SERIAL") @@ -75,7 +79,7 @@ cleanup() { fi } -trap cleanup EXIT +trap cleanup EXIT INT TERM echo "[2/8] Checking SilentAuthReceiver is registered..." if ! adb_cmd shell "dumpsys package $RECEIVER_PKG" 2>/dev/null | grep -q "SilentAuthReceiver"; then From adc6172d59a29315b3dc3b35ebaf75564c9c450d Mon Sep 17 00:00:00 2001 From: Dome Pongmongkol Date: Wed, 20 May 2026 16:21:23 -0700 Subject: [PATCH 5/5] kill msaltestapp before launching the test --- scripts/doze-repro-test.sh | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/scripts/doze-repro-test.sh b/scripts/doze-repro-test.sh index 219505dc..70960fad 100644 --- a/scripts/doze-repro-test.sh +++ b/scripts/doze-repro-test.sh @@ -51,7 +51,7 @@ echo "=============================================" echo "" # --- Prerequisite checks --- -echo "[1/8] Checking adb connection..." +echo "[1/9] Checking adb connection..." if ! adb devices 2>/dev/null | grep -q "device$"; then echo -e "${RED}ERROR: No adb device connected.${NC}" echo "Connect a device via USB and authorize adb." @@ -71,7 +71,7 @@ adb_cmd() { cleanup() { if [ "$DOZE_FORCED" = true ]; then - echo "[8/8] Cleaning up Doze..." + echo "[9/9] Cleaning up Doze..." adb_cmd shell dumpsys deviceidle unforce > /dev/null 2>&1 || true adb_cmd shell dumpsys battery reset > /dev/null 2>&1 || true DOZE_FORCED=false @@ -81,7 +81,7 @@ cleanup() { trap cleanup EXIT INT TERM -echo "[2/8] Checking SilentAuthReceiver is registered..." +echo "[2/9] Checking SilentAuthReceiver is registered..." if ! adb_cmd shell "dumpsys package $RECEIVER_PKG" 2>/dev/null | grep -q "SilentAuthReceiver"; then echo -e "${RED}ERROR: SilentAuthReceiver not found in $RECEIVER_PKG.${NC}" echo "Install MsalTestApp." @@ -89,7 +89,7 @@ if ! adb_cmd shell "dumpsys package $RECEIVER_PKG" 2>/dev/null | grep -q "Silent fi echo " SilentAuthReceiver: registered" -echo "[3/8] Checking broker apps installed..." +echo "[3/9] Checking broker apps installed..." BROKER_PKGS=( "com.azure.authenticator" "com.microsoft.windowsintune.companyportal" @@ -131,7 +131,7 @@ BROKER_NAME="${INSTALLED_NAMES[0]}" echo " Broker: $BROKER_NAME ($BROKER)" # --- Battery exemption check --- -echo "[4/8] Checking battery optimization for broker..." +echo "[4/9] Checking battery optimization for broker..." BATTERY_EXEMPT=false if adb_cmd shell dumpsys deviceidle whitelist 2>/dev/null | grep -q "$BROKER"; then BATTERY_EXEMPT=true @@ -147,8 +147,14 @@ else echo " Battery optimization: active (not exempt) — good" fi +# --- Kill MsalTestApp to ensure cold start --- +echo "[5/9] Killing MsalTestApp (ensure cold start)..." +adb_cmd shell am force-stop "$RECEIVER_PKG" > /dev/null 2>&1 +echo " Killed $RECEIVER_PKG" +sleep 1 + # --- Force Doze --- -echo "[5/8] Forcing Doze mode..." +echo "[6/9] Forcing Doze mode..." adb_cmd logcat -c adb_cmd shell dumpsys battery unplug > /dev/null 2>&1 adb_cmd shell dumpsys deviceidle force-idle > /dev/null 2>&1 @@ -165,7 +171,7 @@ if [ "$DOZE_STATE" != "IDLE" ]; then fi # --- Send broadcast --- -echo "[6/8] Sending SILENT_AUTH broadcast (background context)..." +echo "[7/9] Sending SILENT_AUTH broadcast (background context)..." adb_cmd shell "am broadcast \ -a $RECEIVER_ACTION \ -n $RECEIVER_PKG/$RECEIVER_CLASS \ @@ -175,7 +181,7 @@ echo " Waiting ${WAIT_SECONDS}s for auth to complete..." sleep "$WAIT_SECONDS" # --- Capture results --- -echo "[7/8] Capturing logcat results..." +echo "[8/9] Capturing logcat results..." LOGCAT=$(adb_cmd logcat -d -s "${TAG}:*" 2>/dev/null) # --- Report results ---