Всем привет! На связи Юрий Шабалин, ведущий архитектор Swordfish Security и генеральный директор Стингрей Технолоджиз. Эта статья написана в соавторстве с Android-разработчиком Веселиной Зацепиной (@VeselinaZatsepina). В материале мы поговорим про один из ключевых механизмов в Android, а именно про разрешения. Разберем, что это такое, как с ними работать, а главное, какие ошибки могут возникнуть и как их не допустить. Будет интересно, поехали!

Введение

Разрешения, или Permissions, в официальной документации – это механизмы разграничения доступа к различным функциям на устройстве. Они, по сути, служат для обеспечения конфиденциальности пользователей, чтобы последние понимали, к каким данным и действиям имеет доступ приложение и что оно может делать. 

Разрешения защищают доступ к:

  • данным с ограниченным правом на использование, таким как состояние системы и контактная информация пользователей;

  • запрещенным действиям, среди которых – подключение к сопряженному устройству и запись звука.

А чтобы узнать, что из себя представляют разрешения на более низком уровне, в самом Android, достаточно покопаться в исходниках.

Многие механизмы безопасности Android основаны на принципах классических Linux-систем, и способы работы с разрешениями - не исключение. Как известно, каждое приложение, установленное на устройстве, имеет своего собственного пользователя и группу в Linux. На этой особенности создан механизм песочницы. То есть никто, кроме этого пользователя, не имеет права доступа к директории приложения.

На этой же основе построены и разрешения. В файле  frameworks/base/data/etc/platform.xml имена разрешений, которые есть в Android, сопоставлены с наименованиями групп в Linux. 

<permissions>
    <!-- ================================================================== -->
    <!-- ================================================================== -->
    <!-- ================================================================== -->
    <!-- The following tags are associating low-level group IDs with
         permission names.  By specifying such a mapping, you are saying
         that any application process granted the given permission will
         also be running with the given group ID attached to its process,
         so it can perform any filesystem (read, write, execute) operations
         allowed for that group. -->
    ...
    <permission name="android.permission.INTERNET" >
        <group gid="inet" />
    </permission>
    <permission name="android.permission.READ_LOGS" >
        <group gid="log" />
    </permission>
    <permission name="android.permission.BLUETOOTH" >
        <group gid="net_bt" />
    </permission>
    ...
</permissions>

Если пойти чуть глубже и посмотреть, что представляют из себя группы и как они работают внутри системы, то в файле system/core/include/private/android_filesystem_config.h можно увидеть сопоставление групп с их числовыми идентификаторами:

...
#define AID_ROOT             0  /* traditional unix root user */
#define AID_SYSTEM        1000  /* system server */
#define AID_RADIO         1001  /* telephony subsystem, RIL */
#define AID_BLUETOOTH     1002  /* bluetooth subsystem */
#define AID_CAMERA        1006  /* camera devices */

...

static const struct android_id_info android_ids[] = {
    { "root",      AID_ROOT, },
    { "system",    AID_SYSTEM, },
    { "radio",     AID_RADIO, },
    { "bluetooth", AID_BLUETOOTH, },
    { "camera",    AID_CAMERA, },
 ...

И на самом деле, когда приложение запрашивает какие-то привилегии в системе, используя механизм разрешений, всё, что происходит под капотом – это добавление пользователя в определенную группу. Соответственно, когда приложение пробует воспользоваться, например камерой, на уровне системы происходит проверка присутствия клиента в нужной группе. Всё гениальное просто и отлично работает!

Общие положения Android-разрешений

Вернемся на уровень выше и рассмотрим типы разрешений:

  1. Install-time permissions – предоставляются автоматически при установке приложения. Разрешения во время загрузки дают приложению неполный доступ к данным или позволяют ему выполнять ограниченные действия, которые минимально влияют на систему или другие программные продукты. Android включает в себя несколько install-time permissions: normal permissions и signature permissions, которые отличаются уровнем защиты (normal и signature);

Список разрешений во время установки приложения, который отображается в магазине приложений.
Список разрешений во время установки приложения, который отображается в магазине приложений.
  1. Runtime permissions (dangerous permissions) – требуют, чтобы приложение запросило разрешение во время выполнения. Предоставляет продукту дополнительный доступ к данным с ограниченными правами или разрешает ему выполнять некоторые действия, которые существенно влияют на систему и другие приложения. Этим permissions система присваивает dangerous уровень защиты;

Запрос разрешения во время выполнения. 
Запрос разрешения во время выполнения. 
  1. Special permissions – соответствуют конкретным операциям приложения. Только Android-платформа и OEM (оригинальный производитель оборудования) могут определять специальные разрешения в тех случаях, когда хотят защитить доступ к особенно мощным действиям, таким как отрисовка поверх других приложений. Этим разрешениям система присваивает appop уровень защиты. В отличие от runtime permissions, здесь пользователь должен предоставить специальные разрешения на странице «Доступ к специальному приложению» в системных настройках. Приложения могут отправлять туда пользователей с помощью Intent, который приостанавливает ПО и запускает соответствующую страницу настроек для данного специального разрешения. После того как пользователь вернется в приложение, оно может проверить, предоставлено ли разрешение в функции onResume().  

Специальные разрешения для прилоеджни
Специальные разрешения для прилоеджни

Группы разрешений

Разрешения могут принадлежать группам, которые состоят из набора логически связанных permissions. Например, разрешения на отправку и получение SMS могут входить в одну и ту же группу, поскольку они оба относятся к взаимодействию приложения с сообщениями.

Группы разрешений помогают системе свести к минимуму количество диалогов, которые отображаются пользователю, когда приложение запрашивает тесно связанные permissions. Когда пользователю предлагается предоставить разрешения для приложения, в том же интерфейсе размещаются остальные permissions, входящие в группу. 

<permission-group android:name="android.permission-group.MESSAGES"
        android:label="@string/permgrouplab_messages"
        android:icon="@drawable/perm_group_messages"
        android:description="@string/permgroupdesc_messages"
        android:permissionGroupFlags="personalInfo"
        android:priority="360"/>

    <!-- Allows an application to monitor incoming SMS messages, to record
       or perform processing on them. -->
    <permission android:name="android.permission.RECEIVE_SMS"
        android:permissionGroup="android.permission-group.MESSAGES"
        android:protectionLevel="dangerous"
        android:label="@string/permlab_receiveSms"
        android:description="@string/permdesc_receiveSms" />

    <!-- Allows an application to send SMS messages. -->
    <permission android:name="android.permission.SEND_SMS"
        android:permissionGroup="android.permission-group.MESSAGES"
        android:protectionLevel="dangerous"
        android:permissionFlags="costsMoney"
        android:label="@string/permlab_sendSms"
        android:description="@string/permdesc_sendSms" />

Разрешения используются для того, чтобы понять, имеет ли приложение права доступа к чувствительным данным или право на выполнение определенных операций (действий). Например, если программный продукт хочет получить доступ к контактам, то разработчику необходимо объявить разрешение в файле AndroidManifest.xml: 

<uses-permission android:name="android.permission.READ_CONTACTS" />

Это встроенное разрешение, которое имеет уровень защиты (protectionLevel) – dangerous. При установке приложения с данным разрешением Android ОС спросит пользователя о том, хочет ли он предоставить доступ к данным своих контактов.

Разработчики могут создать свои разрешения. Для этого их нужно прописать в файле AndroidManifest.xml: 

    <permission android:name="com.mycoolcam.USE_COOL_CAMERA"
        android:protectionLevel="dangerous" />

    <activity android:name=".CoolCamActivity" android:exported="true" android:permission="com.mycoolcam.USE_COOL_CAMERA">
        <intent-filter>
            <action android:name="com.mycoolcam.LAUNCH_COOL_CAM" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>

Если какое-то стороннее приложение захочет использовать данные Activity, то разрешения необходимо также прописать в файле AndroidManifest.xml этого продукта: 

<uses-permission android:name="com.mycoolcam.USE_COOL_CAMERA" />

И тогда при установке приложения ОС Android спросит пользователя, давать ли доступ приложению к камере или нет.

Уровни разрешений

  1. normal – пользователь не знает, что приложение запрашивает доступ к этому ресурсу, потому что права предоставляются автоматически при установке;

  2. dangerous – этот уровень требует, чтобы пользователь подтвердил, может ли приложение получить доступ к определенному ресурсу;

  3. signature – приложение, использующее этот уровень разрешений, должно быть подписано тем же сертификатом, что и продукт, объявивший его. Это не позволяет злоумышленнику прописать <uses-permission> с этим разрешением в файле AndroidManifest.xml, потому что Android не даст установить приложение;

  4. Есть несколько других уровней, таких как system, installer, privileged, appop и т.  д., которые используются системными приложениями. Их следует рассматривать как уровень signature с точки зрения злоумышленника, т. е. разрешения, использующие этот уровень защиты, не могут быть объявлены в <uses-permission>.

Некоторые разрешения, такие как CAMERA, позволяют вашему приложению получать доступ к аппаратным частям, которые есть только у некоторых устройств Android. Если ваше приложение объявляет одно из этих hardware-associated permissions, подумайте, сможет ли оно работать на устройстве, не имеющем этого оборудования. В большинстве случаев оборудование является необязательным, поэтому лучше и назвать его таковым, задав для android:required значение false в объявлении <uses-feature>, как показано в следующем фрагменте кода из файла AndroidManifest.xml:

    <uses-feature android:name="android.hardware.camera"
        android:required="false" />

Если вы не установите для android:required значение false в объявлении <uses-feature>, Android предположит, что для запуска вашего приложения требуется аппаратное обеспечение. Система запретит некоторым устройствам устанавливать ваше приложение.

Рекомендации при работе с разрешениями

  1. При объявлении «своих» (кастомных) разрешений не забывайте об уровне защиты. Если мы объявим свое разрешение не укажем protectionLevel, то по умолчанию оно примет уровень normal и его можно будет спокойно использовать. Аналогичную ошибку допустили разработчики встроенного приложения Samsung, это привело к тому, что другие приложения на телефоне могли читать историю звонков, SMS-сообщения и т. д.

  2. Будьте внимательны при работе с разрешениями, если у вас есть «экосистема» приложений. Предположим, ваша экосистема состоит из двух продуктов: My Cool Cam и My Cool Reader. Первый для чтения использует функциональные возможности второго.

Манифест приложения камеры:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mycoolcam">
        <permission android:name="com.mycoolcam.USE_COOL_CAMERA" android:protectionLevel="signature" />
        <uses-permission android:name="com.mycoolcam.USE_COOL_CAMERA" />

        <application android:label="My Cool Cam">
            <activity android:name=".CoolCamActivity" android:exported="true" android:permission="com.mycoolcam.USE_COOL_CAMERA">
                <intent-filter>
                    <action android:name="com.mycoolcam.LAUNCH_COOL_CAM" />
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
            </activity>
            <!-- ... -->
        </application>
    </manifest>

Манифест приложения для чтения:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mycoolreader">
        <uses-permission android:name="com.mycoolcam.USE_COOL_CAMERA" />

        <application android:label="My Cool Reader">
            <provider android:name=".AllUserNotesContentProvider" android:authorities="com.mycoolreader.notes_provider" android:exported="true" android:permission="com.mycoolcam.USE_COOL_CAMERA" />
            <!-- ... -->
        </application>
    </manifest>

На первый взгляд может показаться, что всё хорошо и безопасно, поскольку только приложения из экосистемы могут получить доступ к конфиденциальной информации, хранящейся в AllUserNotesContentProvider. Но что произойдет, если на устройстве жертвы установлено только приложение для чтения? В этом случае система Android ничего не будет знать об объявлении разрешений com.mycoolcam.USE_COOL_CAMERA, и поэтому уровень по умолчанию будет помечен как обычный.

Чтобы предотвратить это, убедитесь, что каждое разрешение из экосистемы нескольких приложений объявляется индивидуально во всех продуктах.

  1. Избегайте опечаток в названии разрешений.

    <permission android:name="com.mycoolcam.USE_COOL_CAMERA"
        android:protectionLevel="signature" />

    <activity android:name=".CoolCamActivity" android:exported="true" android:permission="com.mycoolcam.USE_COOL_CAM">
        <intent-filter>
            <action android:name="com.mycoolcam.LAUNCH_COOL_CAM" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>

Разрешение для com.mycoolcam.USE_COOL_CAMERA будет иметь protectionLevel = signature, а для com.mycoolcam.USE_COOL_CAM – значение normal. Это позволяет любому приложению прописать в манифесте  <uses-permission> разрешения на использование с помощью com.mycoolcam.USE_COOL_CAM, что, в свою очередь, предоставит доступ к действию CoolCamActivity.

    <permission android:name="com.mycoolcam.USE_COOL_CAMERA"
        android:protectionLevel="signature" />

    <activity android:name=".CoolCamActivity" android:exported="true"
        android:uses-permission="com.mycoolcam.USE_COOL_CAMERA">
        <intent-filter>
            <action android:name="com.mycoolcam.LAUNCH_COOL_CAM" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>
  1. Избегайте опечаток в названии атрибутов.

В приведенном выше примере вместо атрибута android:permission (который устанавливает уровень доступа к компоненту), указано android:uses-permission. Это значит, что компонент не имеет уровня защиты, поэтому любое стороннее приложение сможет получить к нему доступ.

  1. «Защищайте» используемые разрешения.
    Файл AndroidManifest.xml:

    <uses-permission android:name="android.permission.READ_CONTACTS" />
    ...
    <provider android:name=".ContactsProvider"
        android:authorities="com.exampleapp.contacts"
        android:exported="true" />

Файл ContactsProvider.java:

    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        return getContext().getContentResolver()
                    .query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                            projection,
                            selection,
                            selectionArgs,
                            sortOrder);
    }

В этом случае для доступа к uri ContactsContract.CommonDataKinds.Phone требуется разрешение android.permission.READ_CONTACTS. Однако для доступа к content: //com.exampleapp.contacts не запрашиваются никакие права.

  1. Защищайте свои Content Providers.
    Используйте атрибут android:permission тега <provider>, чтобы ограничить доступ других приложений к данным в ContentProvider. В отличие от остальных компонентов, для ContentProvider можно установить два отдельных атрибута разрешений: android:readPermission (ограничивает права сторонних приложений на чтение данных) и android:writePermission (доступ на запись). Обратите внимание, если провайдер защищен разрешениями и на чтение, и на запись, наличие только разрешения на запись не позволит приложению читать данные.

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.test.appwithpermission">
    
        <!-- Провайдер имеет одинаковые разрешения на чтение и запись - это плохо -->
        <provider android:name=".MyProvider"
            android:authorities="contacts;com.android.contacts"
            android:readPermission="android.permission.READ_CONTACTS"
            android:writePermission="android.permission.READ_CONTACTS"
            android:exported="true"/>
    
        <!-- Провайдер имеет разные разрешения на чтение и запись - это хорошо -->
        <provider android:name=".MyProvider"
            android:authorities="contacts;com.android.contacts"
            android:readPermission="android.permission.READ_CONTACTS"
            android:writePermission="android.permission.WRITE_CONTACTS"
            android:exported="true"/>
    
    </manifest>

    Разрешения проверяются при первом обращении к ContentProvider и в тот момент, когда приложение выполняет операции с данными. Если запрашивающее приложение не имеет разрешения, возникает исключение SecurityException. Использование ContentResolver.query() требует разрешения на чтение; применение ContentResolver.insert(), ContentResolver.update() или ContentResolver.delete() требует разрешения на запись. Во всех этих случаях отсутствие необходимого permission приводит к SecurityException.

    6.1. Предоставляйте доступ на основе каждого URI.

    Система предоставляет нам дополнительный детальный контроль, как другие приложения могут получить доступ к ContentProvider вашего приложения. В частности, ContentProvider может защитить себя с помощью разрешений на чтение и запись, при этом позволяя своим прямым клиентам совместно использовать определенные URI с другими приложениями. Чтобы заявить о поддержке вашим продуктом этой модели, применяйте атрибут android:grantUriPermissions или элемент <grant-uri-permission>. Последний позволяет совместно использовать определенные пути.

    Также возможно предоставлять разрешения для каждого URI. Для этого необходимо при запуске Activity или возврате результата в Activity установить флагIntent.FLAG_GRANT_READ_URI_PERMISSION или Intent.FLAG_GRANT_WRITE_URI_PERMISSION (или оба этих флага). Это даст другим приложениям разрешения на чтение, запись или чтение и запись для определенного URI, включенного в Intent).

    Предположим, что пользователь использует приложение для просмотра электронного письма с вложенным изображением. Другие приложения не должны иметь доступ к содержимому электронной почты вообще, но они могут быть заинтересованы в просмотре изображения. Ваше приложение может использовать Intent и флаг Intent.FLAG_GRANT_READ_URI_PERMISSION, чтобы приложение для просмотра смогло получит доступ к файлу.

  2. Проверяйте uri, указанные в атрибутах <grant-uri-permission …/> у Content Provider.

    <!-- good: -->
    <grant-uri-permission android:pathPrefix="/all_downloads/"/>

    <!-- bad: -->
    <grant-uri-permission android:path="/"/>
    <grant-uri-permission android:pathPrefix="/"/>
    <grant-uri-permission android:pathPattern=".*"/> // соответствует любой последовательности от нуля до набора символов.

В случае с «bad» ContentProvider разрешит доступ ко всем своим данным.

  1. Избегайте определения пользовательских разрешений в пространстве имен «android.permission».
    Определение кастомного разрешения в пространстве имен android.permission может привести к неожиданному поведению, если более новая версия Android добавит разрешение с тем же именем. Для кастомных permissions рекомендуется использовать пространство имен, специфичное для приложения.

  2. Проверяйте опечатки в значениях атрибутов android:permission.

    <activity
    . . .
    android:exported="true"
    android:permission="" >

    android:permission="TODO" >
    android:permission="[TODO]" >
  1. Явным образом предоставляйте другому приложению разрешение на доступ к компоненту приложения через уровень защиты signature|knownSigner.

<permission android:name="com.example.app.permission.MY_PERMISSION"
        android:protectionLevel="signature|knownSigner"
        android:knownCerts="3a71b28418ed0ad6280d3e6639d29dcd654a42e441e60585607391ccdf1f25c8"/>
  1. Будьте внимательны с разрешениями, которые запрашивают сторонние библиотеки в вашем проекте.

    При подключении внешней библиотеки, вы также наследуете ее требования к разрешениям. Помните о разрешениях, которые нужны для каждой зависимости, и о том, для чего они используются. В Android Studio, при просмотре файла AndroidManifest.xml, снизу есть вкладка Merged Manifest. В ней – итоговый файл манифеста с учетом всех подключенных библиотек и т. д. То есть в этом файле отображаются все разрешения, запрашиваемые сторонними библиотеками, а также их компоненты.

Заключение

Наша статья вдохновлена материалом одной известной компании и призвана лишний раз подчеркнуть, что даже в таком привычном и обыденном функционале, как механизм разрешений в Android, могут быть свои особенности и нюансы. И знание всех этих тонкостей поможет избежать ошибок. Надеемся, статья пригодится как начинающим Android-разработчикам, так и их старшим коллегам – для повторения материала. 

И напоследок еще одна рекомендация – старайтесь регулярно проверять защищенность своих приложений. Все вместе мы сделаем нашу цифровую среду более безопасной!

Спасибо за внимание, с вами были Юрий Шабалин и Веселина Зацепина!

До новых встреч!

Полезные ссылки

1. Permissions on Android

2. Распространенные ошибки при использовании разрешений в Android

3. Content Provider шарит все свои данные

4. Детекты линтера проблем в разрешениях

5. Описание флагов для открытия доступа к определенному Uri (например, в Content Provider)

Комментарии (2)


  1. Rusrst
    15.06.2023 19:59
    +1

    Веселина спасибо :)

    мы все ждем приложу и ее исходники ;)


  1. dmt_ovs
    15.06.2023 19:59
    +1

    Отличная статья, спасибо авторам!