Android 13 Developer Preview 1: Что там по безопасности? - (не)Уникальный опыт

Android 13 Developer Preview 1: Что там по безопасности?


Вышла первая версия нового Android 13, и Google обещает, что этот билд продолжит делать нам всем хорошо в плане приватности, безопасности и вообще котики никогда не умрут. Давайте посмотрим, что нового там завезли и стоит ли оно внимания.

/img/android-13-preview-security/logo.png

Photo picker

В Android уже давно есть системный пикер для файлов, который позволяет получать доступ к конкретным файлам без необходимости давать приложению какие-либо разрешения. В Android 11 это приложение Documents (com.google.android.documentsui). Для работы с ним нужно просто послать интент или использовать Result API

// Устаревший, не рекомендованный способ

val documents = Intent(Intent.ACTION_GET_CONTENT).apply {
    addCategory(Intent.CATEGORY_OPENABLE)
    type = "*/*"
}

startActivityForResult(documents, 1337)
...

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == 1337) {
        // Упадет если нет URI в data ;)
        image.setImageURI(data!!.data)
    }
}
// Новый, блестящий способ. Всегда так делайте.

val getContent = registerForActivityResult(
    ActivityResultContracts.GetContent()) { uri: Uri? ->
        uri?.let {
            image.setImageURI(uri)
        }
    }

getContent.launch("*/*")

Эта штука понимает MIME-типы и вместо */* туда можно передать image/png и оно позволит выбирать только png картинки. Чувствуете, как ваше приложение становится “privacy friendly”? То-то же!

Недостаток у этого пикера ровно один - он слишком “общий”. Т.е. даже с примененным фильтром по изображениям он будет выглядеть как-то вот так:

/img/android-13-preview-security/docsui.png

И тут новый Photo Picker™ спешит на помощь неся красоту, удобство и безопасность (о ней чуть позже). Он продолжает идею старого пикера в отношении пермишенов и дает новый UI и чуть больше контроля над параметрами выбора изображений и видео.

/img/android-13-preview-security/photo-picker-multiselect.gif

Google пока не показал как вызвать этот пикер через Result API, вероятно это вообще невозможно на данный момент, но старый добрый startActivityForResult все еще тут и будет радовать разработчиков долгие годы. Как AsyncTask

val maxNumPhotosAndVideos = 10
val intent = Intent(MediaStore.ACTION_PICK_IMAGES)
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxNumPhotosAndVideos)
startActivityForResult(intent, PHOTO_PICKER_MULTI_SELECT_REQUEST_CODE)

...

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    super.onActivityResult(requestCode, resultCode, data)
    if (resultCode != Activity.RESULT_OK) {
        // Обработать ошибку
        return
    }
    when (requestCode) {
            REQUEST_PHOTO_PICKER_SINGLE_SELECT -> {
            // Обработать выбор одной фотографии
            val currentUri: Uri = data.data

            // Do stuff with the photo/video URI.
            return
        }
        
        REQUEST_PHOTO_PICKER_MULTI_SELECT -> {
            // Обработать выбор нескольких фотографий
            var i = 0
            while (i < data.clipData!!.itemCount) {
                // Отправить все, что смогли украсть на C&C
            }
        }
    }
}

Код выше, запустит пикер в режиме выбора нескольких фотографий и можно даже указать максимальное количество больше которого он выбрать не даст. Количество, при этом, далеко не бесконечное и регламентируется методом MediaStore#getPickImagesMaxLimit

А теперь самое главное - почему это все безопасно? Покажу на примере старого пикера, потому что мне сейчас лень накатывать Preview версию студии чтобы скачать образ на эмулятор :D

Я специально показал два спосба запуска пикера, потому что так нагляднее. Давайте еще раз:

val documents = Intent(Intent.ACTION_GET_CONTENT).apply {
    addCategory(Intent.CATEGORY_OPENABLE)
    type = "*/*"
}

startActivityForResult(documents, 1337)

Мы здесь видим неявный intent, а это значит, что можно создать вредоносное приложение, в activity которого будет intent-filter c таким же action и category. В этом случае, при старте такого неявного intent-а система пойдет искать все activity, которые могут его обработать и если их больше одной, то отобразится Intent chooser. Это дает нам примерно 50% шанс увести юзера в нашу вредоносную галлерею и все украсть, а то и чего похуже…

/img/android-13-preview-security/huy-tam-plaval_52853848_orig_.jpeg

как любил говаривать Морихэй Уэсиба.

При выполнении этого кода будет запущено приложение Documents. Без вариантов. Даже на Android 6.0.1. Но как же так? Хитрость кроется в том, как определена целевая activity в этом приложении:

<activity
    android:theme="@ref/0x7f1200f5"
    android:name="com.android.documentsui.picker.PickActivity"
    android:exported="true"
    android:visibleToInstantApps="true">
    <intent-filter android:priority="100"> <!-- эта строчка все портит -->
        <action android:name="android.intent.action.GET_CONTENT" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.OPENABLE" />
        <data android:mimeType="*/*" />
    </intent-filter>
</activity>

Ну приоритет это старая история. Сейчас поставим 999 и все будет хорошо. Но Морихэй Уэсиба и в этой ситуации дает нам правильный ответ ;) Давайте разбираться почему так происходит. Начнем с метода ComponentResolver#adjustPriority

String packageName = activity.getPackageName();
AndroidPackage pkg = sPackageManagerInternal.getPackage(packageName);
final boolean privilegedApp = pkg.isPrivileged();
String className = activity.getClassName();
if (!privilegedApp) {
    // non-privileged applications can never define a priority >0
    if (DEBUG_FILTERS) {
        Slog.i(TAG, "Non-privileged app; cap priority to 0;"
                + " package: " + packageName
                + " activity: " + className
                + " origPrio: " + intent.getPriority());
    }
    intent.setPriority(0);
    return;
}

Он и не дает нам перебить установленный в com.android.documentsui.picker.PickActivity приоритет. Согласно документации, привелигерованные приложения живут в /system/priv-app и некоторых других директориях. Проверяем:

sunfish:/ $ ls -la /system/priv-app
total 156
drwxr-xr-x 39 root root 4096 2009-01-01 03:00 .
drwxr-xr-x 12 root root 4096 2009-01-01 03:00 ..
...
drwxr-xr-x  2 root root 4096 2009-01-01 03:00 ContactsProvider
drwxr-xr-x  2 root root 4096 2009-01-01 03:00 DocumentsUIGoogle <-- а вот и оно
drwxr-xr-x  2 root root 4096 2009-01-01 03:00 DownloadProvider
...

И с грустью осознаем, что тягаться с привелигерованным приложением с выставленным явно приоритетом нам не по силам. Еще немного инфы про приоритеты можно найти тут.

Согласно законам здравого смысла, новый пикер должен работать схожим образом. Это нужно обязательно проверить.

Новое разрешение для Wi-Fi

Google продолжает радовать нас новыми пермишенами. Феерический выход из-за печечки пермишенов на bluethooth в 12-й версии продолжается столь же эпохальным выходом пермешенов на Wi-Fi. Эпохальность в том, что и те и другие перестали требовать геолокацию для своей работы. Так что теперь ваши приложения смогут брать новые вершины privacy friendly-евости.

Называется новый пермишен NEARBY_WIFI_DEVICES. Для всех приложений с таргетом на Android 13 и выше, он является обязательным, если используются следующие API:

Естественно не все так хорошо и есть методы, которые все еще требуют ACCESS_FINE_LOCATION для своей работы:

Так что будь бдителен любитель начать сканирование и получить его результаты ;) Ну и как обычно, если будете дергать API без соотвествующих пермишенов, то получите SecurityException.

Новый пермишен относится к группе NEARBY_DEVICES, которая появилась в прошлой версии и также включает в себя пермишены на bluethooth. Почему это важно? При запросе пермишенов юзер получит универсальный диалог запроса разрешений на группу NEARBY_DEVICES. И отключить в настройках можно только всю группу сразу. Г - говно гранулярность!

Еще один важный момент - если ваше приложение имеет таргет на Android 13 и вы точно знаете, что не используете никакое Wi-Fi API связанное с геолокаций, то для того чтобы не спалиться задекларировать это явно, нужно использовать специальный флаг neverForLocation

<manifest>
    <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
                     android:usesPermissionFlags="neverForLocation" />
    <application>
        ...
    </application>
</manifest>

Завершить пост хочу красивой картинкой, которая показывает как будет выглядеть новый воркфлоу

/img/android-13-preview-security/nearby-wifi-permission-flow-t.svg

Увидимся в следующих обновлениях.

Ссылки


Смотрите также