1#!/bin/bash
2#===- lib/asan/scripts/asan_device_setup -----------------------------------===#
3#
4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5# See https://llvm.org/LICENSE.txt for license information.
6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7#
8# Prepare Android device to run ASan applications.
9#
10#===------------------------------------------------------------------------===#
11
12set -e
13
14HERE="$(cd "$(dirname "$0")" && pwd)"
15
16revert=no
17extra_options=
18device=
19lib=
20use_su=0
21
22function usage {
23    echo "usage: $0 [--revert] [--device device-id] [--lib path] [--extra-options options]"
24    echo "  --revert: Uninstall ASan from the device."
25    echo "  --lib: Path to ASan runtime library."
26    echo "  --extra-options: Extra ASAN_OPTIONS."
27    echo "  --device: Install to the given device. Use 'adb devices' to find"
28    echo "            device-id."
29    echo "  --use-su: Use 'su -c' prefix for every adb command instead of using"
30    echo "            'adb root' once."
31    echo
32    exit 1
33}
34
35function adb_push {
36  if [ $use_su -eq 0 ]; then
37    $ADB push "$1" "$2"
38  else
39    local FILENAME=$(basename $1)
40    $ADB push "$1" "/data/local/tmp/$FILENAME"
41    $ADB shell su -c "rm \\\"$2/$FILENAME\\\"" >&/dev/null
42    $ADB shell su -c "cat \\\"/data/local/tmp/$FILENAME\\\" > \\\"$2/$FILENAME\\\""
43    $ADB shell su -c "rm \\\"/data/local/tmp/$FILENAME\\\""
44  fi
45}
46
47function adb_remount {
48  if [ $use_su -eq 0 ]; then
49    $ADB remount
50  else
51    local STORAGE=`$ADB shell mount | grep /system | cut -d ' ' -f1`
52    if [ "$STORAGE" != "" ]; then
53      echo Remounting $STORAGE at /system
54      $ADB shell su -c "mount -o rw,remount $STORAGE /system"
55    else
56      echo Failed to get storage device name for "/system" mount point
57    fi
58  fi
59}
60
61function adb_shell {
62  if [ $use_su -eq 0 ]; then
63    $ADB shell $@
64  else
65    $ADB shell su -c "$*"
66  fi
67}
68
69function adb_root {
70  if [ $use_su -eq 0 ]; then
71    $ADB root
72  fi
73}
74
75function adb_wait_for_device {
76  $ADB wait-for-device
77}
78
79function adb_pull {
80  if [ $use_su -eq 0 ]; then
81    $ADB pull "$1" "$2"
82  else
83    local FILENAME=$(basename $1)
84    $ADB shell rm "/data/local/tmp/$FILENAME" >&/dev/null
85    $ADB shell su -c "[ -f \\\"$1\\\" ] && cat \\\"$1\\\" > \\\"/data/local/tmp/$FILENAME\\\" && chown root.shell \\\"/data/local/tmp/$FILENAME\\\" && chmod 755 \\\"/data/local/tmp/$FILENAME\\\"" &&
86    $ADB pull "/data/local/tmp/$FILENAME" "$2" >&/dev/null && $ADB shell "rm \"/data/local/tmp/$FILENAME\""
87  fi
88}
89
90function get_device_arch { # OUT OUT64
91    local _outvar=$1
92    local _outvar64=$2
93    local _ABI=$(adb_shell getprop ro.product.cpu.abi)
94    local _ARCH=
95    local _ARCH64=
96    if [[ $_ABI == x86* ]]; then
97        _ARCH=i386
98    elif [[ $_ABI == armeabi* ]]; then
99        _ARCH=arm
100    elif [[ $_ABI == arm64-v8a* ]]; then
101        _ARCH=arm
102        _ARCH64=aarch64
103    else
104        echo "Unrecognized device ABI: $_ABI"
105        exit 1
106    fi
107    eval $_outvar=\$_ARCH
108    eval $_outvar64=\$_ARCH64
109}
110
111while [[ $# > 0 ]]; do
112  case $1 in
113    --revert)
114      revert=yes
115      ;;
116    --extra-options)
117      shift
118      if [[ $# == 0 ]]; then
119        echo "--extra-options requires an argument."
120        exit 1
121      fi
122      extra_options="$1"
123      ;;
124    --lib)
125      shift
126      if [[ $# == 0 ]]; then
127        echo "--lib requires an argument."
128        exit 1
129      fi
130      lib="$1"
131      ;;
132    --device)
133      shift
134      if [[ $# == 0 ]]; then
135        echo "--device requires an argument."
136        exit 1
137      fi
138      device="$1"
139      ;;
140    --use-su)
141      use_su=1
142      ;;
143    *)
144      usage
145      ;;
146  esac
147  shift
148done
149
150ADB=${ADB:-adb}
151if [[ x$device != x ]]; then
152    ADB="$ADB -s $device"
153fi
154
155if [ $use_su -eq 1 ]; then
156  # Test if 'su' is present on the device
157  SU_TEST_OUT=`$ADB shell su -c "echo foo" 2>&1 | sed 's/\r$//'`
158  if [ $? != 0 -o "$SU_TEST_OUT" != "foo" ]; then
159    echo "ERROR: Cannot use 'su -c':"
160    echo "$ adb shell su -c \"echo foo\""
161    echo $SU_TEST_OUT
162    echo "Check that 'su' binary is correctly installed on the device or omit"
163    echo "            --use-su flag"
164    exit 1
165  fi
166fi
167
168echo '>> Remounting /system rw'
169adb_wait_for_device
170adb_root
171adb_wait_for_device
172adb_remount
173adb_wait_for_device
174
175get_device_arch ARCH ARCH64
176echo "Target architecture: $ARCH"
177ASAN_RT="libclang_rt.asan-$ARCH-android.so"
178if [[ -n $ARCH64 ]]; then
179  echo "Target architecture: $ARCH64"
180  ASAN_RT64="libclang_rt.asan-$ARCH64-android.so"
181fi
182
183RELEASE=$(adb_shell getprop ro.build.version.release)
184PRE_L=0
185if echo "$RELEASE" | grep '^4\.' >&/dev/null; then
186    PRE_L=1
187fi
188ANDROID_O=0
189if echo "$RELEASE" | grep '^8\.0\.' >&/dev/null; then
190    # 8.0.x is for Android O
191    ANDROID_O=1
192fi
193
194if [[ x$revert == xyes ]]; then
195    echo '>> Uninstalling ASan'
196
197    if ! adb_shell ls -l /system/bin/app_process | grep -o '\->.*app_process' >&/dev/null; then
198      echo '>> Pre-L device detected.'
199      adb_shell mv /system/bin/app_process.real /system/bin/app_process
200      adb_shell rm /system/bin/asanwrapper
201    elif ! adb_shell ls -l /system/bin/app_process64.real | grep -o 'No such file or directory' >&/dev/null; then
202      # 64-bit installation.
203      adb_shell mv /system/bin/app_process32.real /system/bin/app_process32
204      adb_shell mv /system/bin/app_process64.real /system/bin/app_process64
205      adb_shell rm /system/bin/asanwrapper
206      adb_shell rm /system/bin/asanwrapper64
207    else
208      # 32-bit installation.
209      adb_shell rm /system/bin/app_process.wrap
210      adb_shell rm /system/bin/asanwrapper
211      adb_shell rm /system/bin/app_process
212      adb_shell ln -s /system/bin/app_process32 /system/bin/app_process
213    fi
214
215    if [[ ANDROID_O -eq 1 ]]; then
216      adb_shell mv /system/etc/ld.config.txt.saved /system/etc/ld.config.txt
217    fi
218
219    echo '>> Restarting shell'
220    adb_shell stop
221    adb_shell start
222
223    # Remove the library on the last step to give a chance to the 'su' binary to
224    # be executed without problem.
225    adb_shell rm /system/lib/$ASAN_RT
226
227    echo '>> Done'
228    exit 0
229fi
230
231if [[ -d "$lib" ]]; then
232    ASAN_RT_PATH="$lib"
233elif [[ -f "$lib" && "$lib" == *"$ASAN_RT" ]]; then
234    ASAN_RT_PATH=$(dirname "$lib")
235elif [[ -f "$HERE/$ASAN_RT" ]]; then
236    ASAN_RT_PATH="$HERE"
237elif [[ $(basename "$HERE") == "bin" ]]; then
238    # We could be in the toolchain's base directory.
239    # Consider ../lib, ../lib/asan, ../lib/linux,
240    # ../lib/clang/$VERSION/lib/linux, and ../lib64/clang/$VERSION/lib/linux.
241    P=$(ls "$HERE"/../lib/"$ASAN_RT" \
242           "$HERE"/../lib/asan/"$ASAN_RT" \
243           "$HERE"/../lib/linux/"$ASAN_RT" \
244           "$HERE"/../lib/clang/*/lib/linux/"$ASAN_RT" \
245           "$HERE"/../lib64/clang/*/lib/linux/"$ASAN_RT" 2>/dev/null | sort | tail -1)
246    if [[ -n "$P" ]]; then
247        ASAN_RT_PATH="$(dirname "$P")"
248    fi
249fi
250
251if [[ -z "$ASAN_RT_PATH" || ! -f "$ASAN_RT_PATH/$ASAN_RT" ]]; then
252    echo ">> ASan runtime library not found"
253    exit 1
254fi
255
256if [[ -n "$ASAN_RT64" ]]; then
257  if [[ -z "$ASAN_RT_PATH" || ! -f "$ASAN_RT_PATH/$ASAN_RT64" ]]; then
258    echo ">> ASan runtime library not found"
259    exit 1
260  fi
261fi
262
263TMPDIRBASE=$(mktemp -d)
264TMPDIROLD="$TMPDIRBASE/old"
265TMPDIR="$TMPDIRBASE/new"
266mkdir "$TMPDIROLD"
267
268if ! adb_shell ls -l /system/bin/app_process | grep -o '\->.*app_process' >&/dev/null; then
269
270    if adb_pull /system/bin/app_process.real /dev/null >&/dev/null; then
271        echo '>> Old-style ASan installation detected. Reverting.'
272        adb_shell mv /system/bin/app_process.real /system/bin/app_process
273    fi
274
275    echo '>> Pre-L device detected. Setting up app_process symlink.'
276    adb_shell mv /system/bin/app_process /system/bin/app_process32
277    adb_shell ln -s /system/bin/app_process32 /system/bin/app_process
278fi
279
280echo '>> Copying files from the device'
281if [[ -n "$ASAN_RT64" ]]; then
282  adb_pull /system/lib/"$ASAN_RT" "$TMPDIROLD" || true
283  adb_pull /system/lib64/"$ASAN_RT64" "$TMPDIROLD" || true
284  adb_pull /system/bin/app_process32 "$TMPDIROLD" || true
285  adb_pull /system/bin/app_process32.real "$TMPDIROLD" || true
286  adb_pull /system/bin/app_process64 "$TMPDIROLD" || true
287  adb_pull /system/bin/app_process64.real "$TMPDIROLD" || true
288  adb_pull /system/bin/asanwrapper "$TMPDIROLD" || true
289  adb_pull /system/bin/asanwrapper64 "$TMPDIROLD" || true
290else
291  adb_pull /system/lib/"$ASAN_RT" "$TMPDIROLD" || true
292  adb_pull /system/bin/app_process32 "$TMPDIROLD" || true
293  adb_pull /system/bin/app_process.wrap "$TMPDIROLD" || true
294  adb_pull /system/bin/asanwrapper "$TMPDIROLD" || true
295fi
296cp -r "$TMPDIROLD" "$TMPDIR"
297
298if [[ -f "$TMPDIR/app_process.wrap" || -f "$TMPDIR/app_process64.real" ]]; then
299    echo ">> Previous installation detected"
300else
301    echo ">> New installation"
302fi
303
304echo '>> Generating wrappers'
305
306cp "$ASAN_RT_PATH/$ASAN_RT" "$TMPDIR/"
307if [[ -n "$ASAN_RT64" ]]; then
308  cp "$ASAN_RT_PATH/$ASAN_RT64" "$TMPDIR/"
309fi
310
311ASAN_OPTIONS=start_deactivated=1
312
313# The name of a symlink to libclang_rt.asan-$ARCH-android.so used in LD_PRELOAD.
314# The idea is to have the same name in lib and lib64 to keep it from falling
315# apart when a 64-bit process spawns a 32-bit one, inheriting the environment.
316ASAN_RT_SYMLINK=symlink-to-libclang_rt.asan
317
318function generate_zygote_wrapper { # from, to
319  local _from=$1
320  local _to=$2
321  if [[ PRE_L -eq 0 ]]; then
322    # LD_PRELOAD parsing is broken in N if it starts with ":". Luckily, it is
323    # unset in the system environment since L.
324    local _ld_preload=$ASAN_RT_SYMLINK
325  else
326    local _ld_preload=\$LD_PRELOAD:$ASAN_RT_SYMLINK
327  fi
328  cat <<EOF >"$TMPDIR/$_from"
329#!/system/bin/sh-from-zygote
330ASAN_OPTIONS=$ASAN_OPTIONS \\
331ASAN_ACTIVATION_OPTIONS=include_if_exists=/data/local/tmp/asan.options.%b \\
332LD_PRELOAD=$_ld_preload \\
333exec $_to "\$@"
334
335EOF
336}
337
338# On Android-L not allowing user segv handler breaks some applications.
339# Since ~May 2017 this is the default setting; included for compatibility with
340# older library versions.
341if [[ PRE_L -eq 0 ]]; then
342    ASAN_OPTIONS="$ASAN_OPTIONS,allow_user_segv_handler=1"
343fi
344
345if [[ x$extra_options != x ]] ; then
346    ASAN_OPTIONS="$ASAN_OPTIONS,$extra_options"
347fi
348
349# Zygote wrapper.
350if [[ -f "$TMPDIR/app_process64" ]]; then
351  # A 64-bit device.
352  if [[ ! -f "$TMPDIR/app_process64.real" ]]; then
353    # New installation.
354    mv "$TMPDIR/app_process32" "$TMPDIR/app_process32.real"
355    mv "$TMPDIR/app_process64" "$TMPDIR/app_process64.real"
356  fi
357  generate_zygote_wrapper "app_process32" "/system/bin/app_process32.real"
358  generate_zygote_wrapper "app_process64" "/system/bin/app_process64.real"
359else
360  # A 32-bit device.
361  generate_zygote_wrapper "app_process.wrap" "/system/bin/app_process32"
362fi
363
364# General command-line tool wrapper (use for anything that's not started as
365# zygote).
366cat <<EOF >"$TMPDIR/asanwrapper"
367#!/system/bin/sh
368LD_PRELOAD=$ASAN_RT_SYMLINK \\
369exec \$@
370
371EOF
372
373if [[ -n "$ASAN_RT64" ]]; then
374  cat <<EOF >"$TMPDIR/asanwrapper64"
375#!/system/bin/sh
376LD_PRELOAD=$ASAN_RT_SYMLINK \\
377exec \$@
378
379EOF
380fi
381
382function install { # from, to, chmod, chcon
383  local _from=$1
384  local _to=$2
385  local _mode=$3
386  local _context=$4
387  local _basename="$(basename "$_from")"
388  echo "Installing $_to/$_basename $_mode $_context"
389  adb_push "$_from" "$_to/$_basename"
390  adb_shell chown root.shell "$_to/$_basename"
391  if [[ -n "$_mode" ]]; then
392    adb_shell chmod "$_mode" "$_to/$_basename"
393  fi
394  if [[ -n "$_context" ]]; then
395    adb_shell chcon "$_context" "$_to/$_basename"
396  fi
397}
398
399if ! ( cd "$TMPDIRBASE" && diff -qr old/ new/ ) ; then
400    # Make SELinux happy by keeping app_process wrapper and the shell
401    # it runs on in zygote domain.
402    ENFORCING=0
403    if adb_shell getenforce | grep Enforcing >/dev/null; then
404        # Sometimes shell is not allowed to change file contexts.
405        # Temporarily switch to permissive.
406        ENFORCING=1
407        adb_shell setenforce 0
408    fi
409
410    if [[ PRE_L -eq 1 ]]; then
411        CTX=u:object_r:system_file:s0
412    else
413        CTX=u:object_r:zygote_exec:s0
414    fi
415
416    echo '>> Pushing files to the device'
417
418    if [[ -n "$ASAN_RT64" ]]; then
419      install "$TMPDIR/$ASAN_RT" /system/lib 644
420      install "$TMPDIR/$ASAN_RT64" /system/lib64 644
421      install "$TMPDIR/app_process32" /system/bin 755 $CTX
422      install "$TMPDIR/app_process32.real" /system/bin 755 $CTX
423      install "$TMPDIR/app_process64" /system/bin 755 $CTX
424      install "$TMPDIR/app_process64.real" /system/bin 755 $CTX
425      install "$TMPDIR/asanwrapper" /system/bin 755
426      install "$TMPDIR/asanwrapper64" /system/bin 755
427
428      adb_shell rm -f /system/lib/$ASAN_RT_SYMLINK
429      adb_shell ln -s $ASAN_RT /system/lib/$ASAN_RT_SYMLINK
430      adb_shell rm -f /system/lib64/$ASAN_RT_SYMLINK
431      adb_shell ln -s $ASAN_RT64 /system/lib64/$ASAN_RT_SYMLINK
432    else
433      install "$TMPDIR/$ASAN_RT" /system/lib 644
434      install "$TMPDIR/app_process32" /system/bin 755 $CTX
435      install "$TMPDIR/app_process.wrap" /system/bin 755 $CTX
436      install "$TMPDIR/asanwrapper" /system/bin 755 $CTX
437
438      adb_shell rm -f /system/lib/$ASAN_RT_SYMLINK
439      adb_shell ln -s $ASAN_RT /system/lib/$ASAN_RT_SYMLINK
440
441      adb_shell rm /system/bin/app_process
442      adb_shell ln -s /system/bin/app_process.wrap /system/bin/app_process
443    fi
444
445    adb_shell cp /system/bin/sh /system/bin/sh-from-zygote
446    adb_shell chcon $CTX /system/bin/sh-from-zygote
447
448    if [[ ANDROID_O -eq 1 ]]; then
449      # For Android O, the linker namespace is temporarily disabled.
450      adb_shell mv /system/etc/ld.config.txt /system/etc/ld.config.txt.saved
451    fi
452
453    if [ $ENFORCING == 1 ]; then
454        adb_shell setenforce 1
455    fi
456
457    echo '>> Restarting shell (asynchronous)'
458    adb_shell stop
459    adb_shell start
460
461    echo '>> Please wait until the device restarts'
462else
463    echo '>> Device is up to date'
464fi
465
466rm -r "$TMPDIRBASE"
467