Основы безопасности операционной системы Android. Native user space, ч.1
В этой статье я попробую рассмотреть безопасность чуть-чуть повыше ядра, а именно: как работает безопасность в Native user space. Мы коснемся темы процесса загрузки операционной системы и рассмотрим структуру файловой системы Android. Как я уже говорил, я не очень силен в Linux, поэтому если заметите неточности, то исправляйте — меня научите и статью улучшите. Так как эта тема довольно обширная, я решил разбить её на две части. В первой части мы рассмотрим процесс загрузки операционной системы и особенности файловой системы. Всем кому интересно, добро пожаловать!
Список статей
Что подразумевается под Native user space
Под Native user space подразумеваются все компоненты пространства пользователя, которые выполняются вне Dalvik Virtual Machine, и которые не являются частью Linux kernel.
Файловая система Android
Для начала давайте рассмотрим структуру файловой системы Android. Хотя Android и базируется на Linux kernel, привычную нашему глазу структуру файловой системы мы здесь не увидим. Давайте запустим эмулятор и посмотрим, что у нас есть. Для этого выполним комманду:
В моем терминале для эмулятора на Android 4.2 я вижу следующий результат:
drwxr-xr-x root root 2013-04-10 08:13 acct drwxrwx--- system cache 2013-04-10 08:13 cache dr-x------ root root 2013-04-10 08:13 config lrwxrwxrwx root root 2013-04-10 08:13 d -> /sys/kernel/debug drwxrwx--x system system 2013-04-10 08:14 data -rw-r--r-- root root 116 1970-01-01 00:00 default.prop drwxr-xr-x root root 2013-04-10 08:13 dev lrwxrwxrwx root root 2013-04-10 08:13 etc -> /system/etc -rwxr-x--- root root 244536 1970-01-01 00:00 init -rwxr-x--- root root 2487 1970-01-01 00:00 init.goldfish.rc -rwxr-x--- root root 18247 1970-01-01 00:00 init.rc -rwxr-x--- root root 1795 1970-01-01 00:00 init.trace.rc -rwxr-x--- root root 3915 1970-01-01 00:00 init.usb.rc drwxrwxr-x root system 2013-04-10 08:13 mnt dr-xr-xr-x root root 2013-04-10 08:13 proc drwx------ root root 2012-11-15 05:31 root drwxr-x--- root root 1970-01-01 00:00 sbin lrwxrwxrwx root root 2013-04-10 08:13 sdcard -> /mnt/sdcard d---r-x--- root sdcard_r 2013-04-10 08:13 storage drwxr-xr-x root root 2013-04-10 08:13 sys drwxr-xr-x root root 2012-12-31 03:20 system -rw-r--r-- root root 272 1970-01-01 00:00 ueventd.goldfish.rc -rw-r--r-- root root 4024 1970-01-01 00:00 ueventd.rc lrwxrwxrwx root root 2013-04-10 08:13 vendor -> /system/vendor
Я отмечу здесь только главные директории и те, которые нам пригодятся в будущем. В Интернете можно найти описание и предназаначение других директорий. Можно заметить, что некоторые директории такие же, как и в Linux, например, /dev, /proc, /sys, /mnt, /etc И их предназначение в основном такое же, как и в Linux. Кстати, отметьте, что мы не видим /bin и /lib директорий. Где они скрылись, я расскажу чуть позже.
C другой стороны можно заметить директории, которых в Linux вообще нет. Среди них нас интересуют /data, /system, /cache, /init, /init.rc Давайте рассмотрим их назначение поподробнее.
/system Это главная директория, где хранятся неизменяемые компоненты Android системы. Если проводить аналогию, то эта папка похожа на папку C:\windows\, доступную только для чтения. Т.е. изменять данные в этой директории мы не можем. Как раз здесь можно найти директории /bin и /lib, где хранятся различные исполняемые файлы и shared libraries. Кроме того, здесь же лежат системные приложения, которые встроены в операционку и которые, по умолчанию, нельзя удалить. Содержимое этой директории формируется во время компиляции операционной системы.
/data Т.к. /system у нас доступна только для чтения, то должна быть директория где хранятся изменяемые данные. /data как раз ею и является. Например, в эту директорию в /data/app сохраняются apk файлы устанавливаемых приложений, а в /data/data хранятся их данные (эту директорию мы подробно рассматривали в прошлой статье).
/cache Это просто временное хранилище. Также в эту директорию сохраняются, а потом из неё запускаются системные обновления.
Чтобы понять, что такое /init файл и для чего нужны непонятные файлы с расширением *.rc, рассмотрим процесс загрузки системы.
Процесс загрузки Android
Давайте рассмотрим несколько шагов процесса загрузки операционной системы Android. Эта картинка взята из книги «Embedded Android», там же можно найти и более детальное описание. Хотя в целом я и понимаю процесс, но для меня это больше магия 🙂
CPU. Когда вы нажимаете на кнопку включения, на процессор вашего устройства начинает подаваться напряжение. Так как до этого момента процессор был выключен, и так как он не способен сохранять свое состояние без подачи напряжения, то сразу после старта он находится в некотором неинициализированном состоянии. В данном случае процессор считывает из своего специального регистра некоторый жестко зашитый адрес и начинает выполнять инструкции начиная с него. Чаще всего, этот адрес указывает на чип, в который зашит bootloader (загрузчик).
Bootloader. Bootloader инициализирует RAM и загружает в неё Linux kernel. Кроме того Bootloader создает RAMdisk.
Linux kernel. Ядро инициализирует различные подсистемы, встроенные драйвера и монтирует root filesystem (корневую файловую систему). После этого ядро может запускать первую программу.
На этом магия заканчивается и дальше всё становится более-менее понятно.
Init
Первой программой в случае Android является init. Исполняемый файл находится в корневой директории (/init). Именно эту программу стартует ядро после своей загрузки. Её исходники находятся в папке system/core/init/ Давайте в них слегка покопаемся. Нас интересует system/core/init/init.c:
. int main(int argc, char **argv) < . /* clear the umask */ umask(0); /* Get the basic filesystem setup we need put * together in the initramdisk on / and then we will * let the rc file figure out the rest. */ mkdir("/dev", 0755); mkdir("/proc", 0755); mkdir("/sys", 0755); mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts", 0, NULL); mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL); . init_parse_config_file("/init.rc"); . >
Вначале мы создаем и монтируем некоторые необходимые для работы директории, а потом парсим файл /init.rc и выполняем то, что распарсили. Формат /init.rc файла очень хорошо описан в readme, там же можно найти и пример. Если кратко, то этот файл представляет собой набор actions (секций — именнованная последовательность комманд). Каждая последовательность команд срабатывает по определенному trigger (триггеру). Например, следующая последовательно — это action, в которой trigger — это fs, а последовательность команд — это набор mount команд:
on fs # mount mtd partitions # Mount /system rw first to give the filesystem a chance to save a checkpoint mount yaffs2 mtd@system /system mount yaffs2 mtd@system /system ro remount mount yaffs2 mtd@userdata /data nosuid nodev mount yaffs2 mtd@cache /cache nosuid nodev
Исходный файл /init.rc находится в system/core/rootdir/init.rc Давайте рассмотрим некоторые основные его части, хотя я вам очень советую просмотреть его полность. После этого многие вещи вам должны стать понятны. Итак, начинается наш файл следующими строками:
import /init.usb.rc import /init.$.rc import /init.trace.rc
Они означают, что кроме init.rc файла нужно также импортировать настройки из файлов init.usb.rc, init.trace.rc и из файла с непонятным именем init.$.rc Впрочем, $ — это просто переменная, значение которая определяет тип железа. В случае эмулятора, её значение, например, — goldfish. Далее определяются переменные окружения:
. on init . # setup the global environment export PATH /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin export LD_LIBRARY_PATH /vendor/lib:/system/lib export ANDROID_BOOTLOGO 1 export ANDROID_ROOT /system export ANDROID_ASSETS /system/app export ANDROID_DATA /data export ANDROID_STORAGE /storage export ASEC_MOUNTPOINT /mnt/asec export LOOP_MOUNTPOINT /mnt/obb export BOOTCLASSPATH /system/framework/core.jar:/system/framework/okhttp.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/telephony-common.jar:/system/framework/mms-common.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar .
После этого происходит инициализация переменных, необходимых для работы устройства. Если вас заинтересует эта тема, то вы легко найдете информацию о той или иной комманде. Давайте подробно рассмотрим следующий блок (который я уже приводил в этой статье):
on fs # mount mtd partitions # Mount /system rw first to give the filesystem a chance to save a checkpoint mount yaffs2 mtd@system /system mount yaffs2 mtd@system /system ro remount mount yaffs2 mtd@userdata /data nosuid nodev mount yaffs2 mtd@cache /cache nosuid nodev
MTD — Memory Technology Devices. Если в общих чертах, то MTD — это специальный чип с энергонезависимой (т.е. данные на этом чипе сохраняются после перезагрузки или выключения) flash-памятью (типа NOR или NAND), на который сохраняются образы дисков. В этой статье более подробно рассказывается об этом типе устройств, а также об ограничениях. Специально для этих разновидностей flash-памяти были разработаны специальные файловые системы, например, YAFFS. Одно из самых важных ограничений этих типов памяти заключается в том, что для того чтобы записать данные в сектор, куда уже записаны какие-то данные, вам надо полностью сначала стереть весь сектор. Поэтому производители стали переходить на новый тип блочной flash-памяти (eMMC), на которые можно поставить обычную ext4 файловую систему и избавиться от указанного ограничения. Т.к. я показываю пример init.rc файла для эмулятора, где вся работа эмулируется, то в нем по умолчанию используется файловая система YAFFS2 (думаю, что это пережитки прошлого, т.к. YAFFS2 использовалась для всех устройств до Android 2.2). В реальном устройстве (это как раз один из примеров, когда необходимо использовать init.rc файл для определенного железа) эти комманды будут перезаписаны. Например, в случае устройства herring (Google Nexus S), в файле init.herring.rc эта секция выглядит следующим образом:
on fs mkdir /efs 0775 radio radio mount yaffs2 mtd@efs /efs noatime nosuid nodev chmod 770 /efs/bluetooth chmod 770 /efs/imei mount_all /fstab.herring .
Где fstab.herring — это файл, содержимое которого выглядит следующим образом:
. /dev/block/platform/s3c-sdhci.0/by-name/system /system ext4 ro wait /dev/block/platform/s3c-sdhci.0/by-name/userdata /data ext4 noatime,nosuid,nodev,nomblk_io_submit,errors=panic wait,encryptable=/efs/userdata_footer
Как вы могли заметить, /system, /data, /cache — это просто mounting points (точки монтирования файловой системы), которые указывают либо на MTD устройства (в случае эмулятора), либо на блочные устройства (в случае настоящего устройства), куда записаны соответствующие дисковые образы (system.img, userdata.img и cache.img). Я не уверен, но думаю, что внутри смартфона находится один единственный чип с flash-памятью, разделенный на partitions (тома), в каждый из которых записан соответствующий образ. Этот чип с flash-памятью — то, что мы знаем под именем Internal storage (внутренняя память), объем которой — один из основных параметров смартфона.
Следует заметить, что /system смонтирован read-only (только для чтения). Это означает, что содержимое данного раздела не изменяется в процессе работы устройства, а только когда вы, например, обновляете систему на вашем устройстве (используя системные обновления).
Продолжим рассматривать наш init.rc. По триггеру post-fs-data формируется базовая структура файловой системы /data раздела. Там, в общем всё понятно — набор mkdir, chown, chmod команд.
Далее init.rc запускает несколько демонов. Если вернуться к рисунку в начале статьи, то они перечислены в блоке Native daemons. На этом мы пока остановимся. Как вы могли заметить из рисунка, я не полностью рассмотрел процесс загрузки операционной системы. Некоторые непокрытые этапы я рассмотрю в следующих статья.
Заключение
В следующей части я расскажу, откуда берутся образы system.img, userdata.img и cache.img и рассмотрю безопасность на уровне Native user space. Как всегда приветствуются исправления, дополнения, а так же предложения, о чем написать. И хотя у меня уже есть некоторый план, о чем писать в следующих статья, я готов его подкорректировать.
Ссылки
- Комментарий от пользователя bmx666 про различные варианты размещения загузчика на MTD устройствах.
- Комментарий от пользователя SamOwaR про инициализацию CPU на разных SoC