- PVSM.RU - https://www.pvsm.ru -

Как работает Android, часть 2

Как работает Android, часть 2 - 1 [1]

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

Статьи серии:


Говоря про Unix- и Linux-корни Android, нужно вспомнить и о других проектах операционных систем, влияние которых можно проследить в Android, хотя они и не являются его прямыми предками.

Я уже упомянул про BeOS, в наследство от которой Android достался Binder.

Plan 9 from Bell Labs

Plan 9 — потомок Unix, логическое продолжение, развитие его идей и доведение их до совершенства. Plan 9 был разработан в Bell Labs той же командой, которая создала Unix и C — над ним работали такие люди, как Ken Thompson, Rob Pike, Dennis Ritchie, Brian Kernighan, Tom Duff, Doug McIlroy, Bjarne Stroustrup, Bruce Ellis и другие.

В Plan 9 взаимодействие процессов между собой и с ядром системы реализовано не через многочисленные системные вызовы и механизмы IPC, а через виртуальные текстовые файлы и файловые системы (развитие принципа Unix «всё — это файл»). При этом каждая группа процессов «видит» файловую систему по-своему (пространства имён, namespaces), что позволяет запускать разные части системы в разном окружении.

Например, чтобы получить позицию курсора мыши, приложения читают текстовый файл /dev/mouse. Оконная система rio предоставляет каждому приложению свою версию этого файла, в которой видны только события, относящиеся к окну этого приложения, и используются локальные по отношению к окну координаты. Сама rio читает события «настоящей» мыши через такой же файл /dev/mouse — в том виде, в котором его видит она. Если она запущена напрямую, этот файл предоставляется ядром и действительно описывает движения настоящей мыши, но она может быть совершенно прозрачно запущена в качестве приложения под другой копией rio, без какой-то специальной поддержки с её стороны.

Скриншот Plan 9

Plan 9 полностью поддерживает доступ к удалённым файловым системам (используется собственный протокол 9P, кроме того, поддерживаются FTP и SFTP), что позволяет программам совершенно прозрачно получать доступ к удалённым файлам, интерфейсам и ресурсам. Такая «родная» сетевая прозрачность превращает Plan 9 в распределённую операционную систему — пользователь может физически находиться за одним компьютером, на котором запущена rio, запускать приложения на нескольких других, использовать в них файлы, хранящиеся на файловом сервере и выполнять вычисления на CPU-сервере — всё это полностью прозрачно и без специальной поддержки со стороны каждой из частей системы.

За счёт красиво спроектированной архитектуры Plan 9 значительно проще и меньше, чем Unix — на самом деле ядро Plan 9 даже в несколько раз меньше известного микроядра Mach.

Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away.

Несмотря на техническое превосходство и наличие слоя совместимости с Unix, Plan 9 не получил широкого распространения. Тем не менее, многие идеи и технологии из Plan 9 получили распространение и были реализованы в других системах. Самая известная из них — кодировка UTF-8, которая была разработана в Plan 9 для обеспечения полной поддержки Unicode при сохранении обратной совместимости с ASCII — стала общепринятым стандартом.

Больше всего идей и технологий из Plan 9 реализовано в Linux:

  • файловая система /proc (procfs)
  • системный вызов clone (аналог rfork из Plan 9)
  • поддержка пространств имён монтирования (mount namespaces)
  • поддержка файловых систем, реализованных в пользовательском пространстве (filesystem in userspace, FUSE)
  • поддержка протокола 9P

Многое из этого используется, в том числе, и в Android. Кроме того, в Android реализован механизм intent’ов, похожий на plumber из Plan 9; о нём я расскажу в следующей статье.

Про Plan 9 можно узнать подробнее на сайте plan9.bell-labs.com [3] (сохранённая копия в Wayback Machine [4]), или его зеркале 9p.io [5]

Inferno

Plan 9 получил продолжение в виде проекта Inferno, тоже разработанного в Bell Labs. К таким свойствам Plan 9, как простота и распределённость, Inferno добавляет переносимость. Программы для Inferno пишутся на высокоуровневом языке Limbo и выполняются — с использованием just-in-time компиляции — встроенной в ядро Inferno виртуальной машиной.

Inferno настолько переносим, что может запускаться

  • на процессорах разных архитектур: ARM, x86, IBM PowerPC, Sun SPARC, 6SGI MIPS и HP PA-RISC,
  • как самостоятельная операционная система или как программа под Plan 9, Unix, Windows 95 и Windows NT.

При этом приложениям, запущенным внутри Inferno, предоставляется совершенно одинаковое окружение.

Inferno получил ещё меньше распространения и известности, чем Plan 9. С другой стороны, Inferno во многом предвосхитил Android, самую популярную операционную систему на свете.

Danger

Компания Danger Research Inc. была сооснована Энди Рубином (Andy Rubin) в 1999 году, за 4 года до сооснования им же Android Inc. в 2004 году.

В 2002 году Danger выпустили свой смартфон Danger Hiptop. Многие из разработчиков Danger впоследствии работали над Android, поэтому неудивительно, что его операционная система была во многом похожа на Android. Например, в ней были реализованы:

  • «всегда запущенные» приложения, написанные на Java,
  • полноценный веб-браузер,
  • веб-приложения,
  • мессенджер,
  • email client,
  • облачная синхронизация,
  • магазин сторонних приложений.

Danger Hiptop [6]

Подробнее о Danger можно прочитать в статье Chris DeSalvo, одного из разработчиков, под названием The future that everyone forgot [7].

Java

Хотя использование высокоуровневых языков для серьёзной разработки сейчас уже никого не удивляет, из популярных операционных систем только у Android «родной» язык — высокоуровневая Java (с другой стороны, здесь можно вспомнить веб с его JavaScript, .NET для Windows и относительно высокоуровневый — но полностью компилируемый в нативный код и не использующий сборку мусора — Swift).

Несмотря на кажущиеся недостатки («Java сочетает в себе красоту синтаксиса C++ со скоростью выполнения питона»), Java обладает множеством преимуществ.

Во-первых, Java — самый популярный [8] (с большим отрывом) язык программирования. У Java огромная экосистема библиотек и инструментов разработки (в том числе систем сборки и IDE). Про Java написано множество статей, книг и документации. Наконец, существует множество квалифицированных Java-разработчиков.

Программы на Java, как и на многих других высокоуровневых языках, переносимы между операционными системами и архитектурами процессора («Write once, run anywhere»). Практически это проявляется, например, в том, что приложения для Android работают без перекомпиляции на устройствах любой архитектуры (Android поддерживает ARM, ARM64, x86, x86–64 и MIPS).

В отличие от низкоуровневых языков вроде C и C++, использующих ручное управление памятью, в Java память автоматически управляется средой времени выполнения (runtime environment). Программа на Java даже не имеет прямого доступа к памяти, что автоматически предотвращает несколько классов ошибок, часто приводящих к падениям и уязвимостям в программах, написанных на низкоуровневых языках — невозможны «висячие ссылки» (из-за которых происходит use-after-free), разыменование нулевого указателя (при попытке это сделать выбрасывается NullPointerException), чтение неинициализированной памяти и выход за границы массива.

Использование полноценной сборки мусора (по сравнению с automatic reference counting) избавляет программиста от всех проблем и сложностей с циклическими ссылками и позволяет реализовывать ещё более продвинутые (advanced) зависимости между объектами.

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

Running Java is ART

В отличие от большинства других высокоуровневых языков, программы на Java не распространяются в виде исходного кода, а компилируются в промежуточный формат (байткод, bytecode), который представляет собой исполняемый бинарный код для специального процессора.

Хотя делаются попытки [9] создать физический процессор, который исполнял бы Java-байткод напрямую, в подавляющем большинстве случаев в качестве такого процессора используется эмулятор — Java virtual machine (JVM). Обычно используется реализация от Oracle/OpenJDK под названием HotSpot [10].

В Android используется собственная реализация под названием Android Runtime (ART), специально оптимизированная для работы на мобильных устройствах. В старых версиях Android (до 5.0 Lollipop) вместо ART использовалась другая реализация под названием Dalvik.

И в Dalvik, и в ART используется собственный формат байткода [11] и собственный формат файлов, в которых хранится байткод — DEX (Dalvik executable [12]). В отличие от class-файлов в «обычной джаве», весь Java-код приложения обычно [13] компилируется в один DEX-файл classes.dex. При сборке Android-приложения Java-код сначала компилируется обычным компилятором Java в class-файлы, а потом конвертируется в DEX-файл специальной утилитой [14] (возможно и обратное преобразование [15]).

И HotSpot, и Dalvik, и ART дополнительно оптимизируют выполняемый код. Все три используют just-in-time compilation [16] (JIT), то есть во время выполнения компилируют байткод в куски полностью нативного кода, который выполняется напрямую. Кроме очевидного выигрыша в скорости, это позволяет оптимизировать код для выполнения на конкретном процессоре, не отказываясь от полной переносимости байткода.

Кроме того, ART может компилировать байткод в нативный код заранее, а не во время выполнения (ahead-of-time compilation [17]) — причём система автоматически планирует эту компиляцию на то время, когда устройство не используется и подключено к зарядке (например, ночью). При этом ART учитывает данные, собранные профилировщиком во время предыдущих запусков этого кода (profile-guided optimization [18]). Такой подход позволяет дополнительно оптимизировать код под специфику работы конкретного приложения и даже под особенности использования этого приложения именно этим пользователем.

В результате всех этих оптимизаций производительность Java-кода на Android не сильно уступает производительности низкоуровневого кода (на C/C++), а в некоторых случаях и превосходит её [19].

Java-байткод, в отличие от обычного исполняемого кода, использует объектную модель Java — то есть в байткоде явно записываются такие вещи, как классы, методы и сигнатуры. Это делает возможным компиляцию других языков в Java-байткод, что позволяет написанным на них программам исполняться на виртуальной машине Java и быть в той или иной степени совместимыми (interoperable) с Java.

Существуют как JVM-реализации независимых языков — например, Jython [20] для Python, JRuby [21] для Ruby, Rhino [22] для JavaScript и диалект Lisp Clojure [23] — так и языки, исходно разработанные для компиляции в Java-байткод и выполнения на JVM, самые известные из которых — Groovy [24], Scala [25] и Kotlin [26].

Самый новый из них, Kotlin, специально разработанный для идеальной совместимости с Java и обладающий гораздо более приятным синтаксисом (похожим на Swift), поддерживается [27] Google как официальный язык разработки под Android наравне с Java.

Kotlin on Android logo

Несмотря на все преимущества Java, в некоторых случаях всё-таки желательно использовать низкоуровневый язык — например, для реализации критичного по производительности компонента, такого как браузерный движок, или чтобы использовать существующую нативную библиотеку. Java позволяет вызывать нативный код через Java Native Interface [28] (JNI), и Android предоставляет специальные средства для нативной разработки — Native Development Kit [29] (NDK), в который входят в том числе заголовочные файлы, компилятор (Clang), отладчик (LLDB) и система сборки.

Хотя NDK в основном ориентирован на использование C/C++, с его помощью можно писать под Android и на других языках — в том числе Rust [30], Swift [31], Python [32], JavaScript [33] и даже Haskell [34]. Больше того, есть даже возможность [35] портировать iOS-приложения (написанные на Objective-C или Swift) на Android практически без изменений.

О безопасности

Классический Unix

Модель безопасности в классическом Unix основана на системе UID/GID — специальных номеров, которые ядро хранит для каждого процесса. Процессам с одинаковым UID разрешён доступ друг к другу, процессы с разным UID защищены друг от друга. Аналогично ограничивается доступ к файлам.

По смыслу каждый UID (user ID) соответствует своему пользователю — во времена создания Unix была нормальной ситуация, когда один компьютер одновременно использовался множеством людей. Таким образом, в Unix процессы и файлы разных людей были защищены друг от друга. Чтобы разрешить общий доступ к некоторым файлам, пользователи объединялись в группы, которым и соответствовал GID (group ID).

При этом всем программам, запускаемым пользователем, даётся полный доступ ко всему, к чему есть доступ у этого пользователя. Собственно, поскольку пользователь не может общаться с ядром напрямую, а взаимодействует с компьютером через shell и другие процессы — права пользователя и есть права программ, запущенных от его имени.

Такая модель подразумевает, что пользователь полностью доверяет всем программам, которые использует. В то время это было логично, потому что программы чаще всего либо были частью системы, либо создавались (писались и компилировались) самим пользователем.

В Unix есть и исключение из ограничений доступа — UID 0, который принято называть root. У него есть доступ ко всему в системе, и никакие ограничения на него не распространяются. Этот аккаунт использовался системным администратором; кроме того, под UID 0 запускаются многие системные сервисы.

В современном Linux эта модель была значительно расширена и обобщена, в том числе появились capabilities, позволяющие «получить часть root-прав», и реализующая мандатное управление доступом (mandatory access control, MAC) подсистема SELinux, которая позволяет дополнительно ограничить права (в том числе права root-процессов).

Всё изменилось

За несколько десятков лет, прошедших с создания Unix до создания Android, практика использования компьютеров («вычислителей») значительно изменилась.

Вместо машин, рассчитанных на параллельное использование многими пользователями (через терминалы — то, что сейчас эмулируют эмуляторы терминалов), появились персональные компьютеры, рассчитанные на использование одним человеком. Компьютеры перестали быть лишь рабочим инструментом и стали центром нашей цифровой жизни. С появлением мобильных устройств — сначала КПК, потом смартфонов, планшетов, умных часов и т.п. — эта тенденция только усилилась (потому что заниматься рабочими вопросами на мобильных устройствах относительно неудобно).

На таких устройствах хранятся гигабайты персональной информации, доступ к которой должен быть защищён и ограничен. В то же время расцвёл рынок сторонних приложений, которым у пользователя нет никаких оснований доверять.

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

Android

Хотя часть Android-приложений поставляется с системой — например, такие стандартные приложения, как Калькулятор, Часы и Камера — большинство приложений пользователи устанавливают из сторонних источников. Самый известный из них — Google Play Store [36], но есть и другие, например, F-Droid [37], Amazon Appstore [38], Яндекс.Store [39], китайские Baidu App Store [40], Xiaomi App Store [41], Huawei App Store [42] и т.д. Кроме того, Android позволяет вручную устанавливать произвольные приложения из APK-файлов (это называют sideloading).

Как и другие Unix-подобные системы, Android использует для ограничения доступа существующий механизм UID/GID. При этом — в отличие от традиционного использования, когда UID соответствуют пользователям — в Android разные UID соответствуют разным приложениям. Поскольку процессы разных приложений запускаются с разными UID, уже на уровне ядра приложения защищены и изолированы друг от друга и не имеют доступа к системе и данным пользователя. Это образует песочницу (Application Sandbox) и позволяет пользователю устанавливать любые приложения без необходимости доверять им.

Чтобы всё-таки получить доступ к пользовательским данным, камере, совершению звонков и т.п., приложение должно получить от пользователя разрешение (permission). Некоторые из разрешений [43] существуют в виде GID, в которые приложение добавляется, когда получает это разрешение — например, получение разрешения ACCESS_FM_RADIO помещает приложение в группу media, что позволяет ему получить доступ к файлу /dev/fm. Остальные существуют только на более высоком уровне (в виде записей в файле packages.xml) и проверяются другими компонентами системы при обращении к высокоуровневому API через Binder.

Как работает Android, часть 2 - 5

Небольшая часть системных сервисов в Android запускается под UID 0, то есть root, но большинство используют специально выделенные номера UID, повышая при необходимости свои права с помощью Linux capabilities. Кроме того, Android использует SELinux — использование SELinux в Android называют SEAndroid  —  для ещё большего ограничения того, какие действия разрешено выполнять приложениям и системным сервисам.

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


В следующей статье (которая выходит уже через неделю) я расскажу о компонентах, из которых состоят приложения под Android, и об идеях, которые стоят за этой архитектурой.

Автор: Сергей Бугаев

Источник [44]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/java/264120

Ссылки в тексте:

[1] Image: https://habrastorage.org/web/f93/913/fda/f93913fda77445249c0d6e8f3445d314.png

[2] Как работает Android, часть 1: https://habrahabr.ru/company/solarsecurity/blog/334796/

[3] plan9.bell-labs.com: http://plan9.bell-labs.com/

[4] сохранённая копия в Wayback Machine: https://web.archive.org/web/20160114184818/http://plan9.bell-labs.com/plan9/

[5] 9p.io: https://9p.io/plan9/

[6] Image: https://habrastorage.org/web/341/9ed/14b/3419ed14bc3942099205711ccf704d9d.jpeg

[7] The future that everyone forgot: https://medium.com/@chrisdesalvo/the-future-that-everyone-forgot-d823af31f7c

[8] самый популярный: https://www.tiobe.com/tiobe-index/

[9] делаются попытки: https://www.wikiwand.com/en/Java_processor

[10] HotSpot: https://www.wikiwand.com/en/HotSpot

[11] формат байткода: https://source.android.com/devices/tech/dalvik/dalvik-bytecode

[12] Dalvik executable: https://source.android.com/devices/tech/dalvik/dex-format

[13] обычно: https://developer.android.com/studio/build/multidex.html

[14] специальной утилитой: https://r8.googlesource.com/r8

[15] обратное преобразование: https://github.com/pxb1988/dex2jar

[16] just-in-time compilation: https://source.android.com/devices/tech/dalvik/jit-compiler

[17] ahead-of-time compilation: https://www.wikiwand.com/en/Ahead-of-time_compilation

[18] profile-guided optimization: https://www.wikiwand.com/en/Profile-guided_optimization

[19] превосходит её: http://www.androidauthority.com/java-vs-c-app-performance-689081/

[20] Jython: http://www.jython.org/

[21] JRuby: http://jruby.org/

[22] Rhino: https://developer.mozilla.org/docs/Mozilla/Projects/Rhino

[23] Clojure: https://clojure.org/

[24] Groovy: http://groovy-lang.org/

[25] Scala: https://www.scala-lang.org/

[26] Kotlin: https://kotlinlang.org/

[27] поддерживается: https://android-developers.googleblog.com/2017/05/android-announces-support-for-kotlin.html

[28] Java Native Interface: https://www.wikiwand.com/en/Java_Native_Interface

[29] Native Development Kit: https://developer.android.com/ndk

[30] Rust: https://github.com/tomaka/android-rs-glue

[31] Swift: https://github.com/apple/swift/blob/master/docs/Android.md

[32] Python: https://python-for-android.readthedocs.io/en/latest/

[33] JavaScript: https://www.nativescript.org/

[34] Haskell: https://medium.com/@zw3rk/a-haskell-cross-compiler-for-android-8e297cb74e8a

[35] возможность: http://www.apportable.com/

[36] Google Play Store: https://play.google.com/store/apps

[37] F-Droid: https://f-droid.org/

[38] Amazon Appstore: https://amazon.com/appstore

[39] Яндекс.Store: https://store.yandex.ru/

[40] Baidu App Store: http://shouji.baidu.com/

[41] Xiaomi App Store: http://app.mi.com

[42] Huawei App Store: http://appstore.huawei.com/

[43] Некоторые из разрешений: https://android.googlesource.com/platform/frameworks/base/+/master/data/etc/platform.xml

[44] Источник: https://habrahabr.ru/post/338292/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best