Вводная статья серии. Мы написали ОС с нуля на Rust и запустили её на серийном планшете Samsung Galaxy Tab S6 (Snapdragon 855). Ниже — не про Rust и не про планшет. Про то, кто ещё живёт в этом устройстве, какие у каждого права, и почему наша ОС — далеко не самый привилегированный жилец. Остальные статьи серии — про то, как мы это узнавали, по одному открытию за раз.
1. Наивная картина
Когда пишешь ОС для микроконтроллера, картина простая: есть процессор, есть память, есть периферия. Ты — единственный код, который исполняется. Что ты не написал, того нет. Все привилегии — твои, вся ответственность — тоже.
Мы принесли эту картину на планшет. Загрузчик передаёт управление нашему образу на EL1, дальше — наша память, наш MMU, наши драйверы. Так казалось.
Понадобилось около десяти тихих перезагрузок всего чипа — без паники, без сообщения, без прерывания, — чтобы картина сменилась на честную: в этом устройстве живёт по меньшей мере восемь исполняющихся сущностей, семь из них написаны не нами, и почти каждая может нас убить, а мы — ни одну из них.
2. Жильцы
Вот они — от самого привилегированного к нам. Для каждого: где исполняется, чей код, что держит над нами, и как мы это узнали (обычно — сбросом).
| Жилец | Где | Чей код | Что держит над нами | Как узнали |
|---|---|---|---|---|
| TrustZone (secure world, EL3) | те же ядра, секретный режим | Qualcomm (+ Samsung) | Загружает и проверяет подписи прошивок остальных жильцов (PAS). Ставит XPU‑заборы на память: наше чтение одного байта за забор — тихий сброс. Владеет регистрами модема (QDSP6SS): читать — bus error. Решает, кому принадлежит какой регион RAM (assign_mem). Свой лог зашифрован и чистится при сбросе | сбросы №1, №2, №9; каждая передача памяти модему идёт через него |
| Гипервизор (EL2) | те же ядра, уровень выше нашего | Samsung | Владеет SMMU (qcom,skip-init — глобальная конфигурация его): stage‑2 трансляция всех DMA‑мастеров — его, нам остаётся stage‑1. Ветирует запись S2CR типа bypass — она возвращается как fault. Пересылает наши smc в TrustZone. Его память hyp_mem — no-map: спекулятивное чтение процессора в неё — сброс | попытка настроить SMMU для Wi‑Fi; сброс №10 (превентивно) |
| AOP (Always‑On Processor) | отдельное ядро Cortex‑M внутри SoC | Qualcomm | Управляет питанием и тактами через RPMh: каждая рельса — голос по имени из базы команд (cmd-db), которую он же хранит. Копия базы в его message‑RAM закрыта: чтение — остановка процессора. Без его load_state modem on модем не грузится, без такта qdss — Wi‑Fi не стартует. Наш неверный голос vote(0) за cx.lvl обесточил цифровую логику всего SoC | сброс №3, сброс №8 |
| S‑Boot (загрузчик) | до нас; остаётся в двух слотах на диске | Samsung | Решает, что вообще загрузится: boot или recovery, по байту причины перезагрузки в PMIC. Выбирает дерево устройств по msm-id, входит на EL1 «где хочет» (наш образ — PIE). Оставляет после себя голоса за рельсы от имени APPS и framebuffer с логотипом, который мы захватываем как дисплей. Умирающий recovery больше не грузит — сам | эксперимент с причиной recovery, который привёл в Download mode |
| Модем (MPSS, Hexagon) | отдельный DSP, 160 МиБ carve‑out | Qualcomm, 80 МБ подписанной прошивки | Внутри — три домена: сотовый, GNSS, WLAN. Требует от нас четыре сервера (rmtfs, tqftp, pd‑mapper, memshare), общую память SMEM, очереди GLINK/QRTR/QMI. Без EFS умирает через 40 с; на отказ открыть fsg — через 23 мс. Собственное радио, собственный uplink | сбросы №4, №5, №6, №7; вся стадия 3–4 Wi‑Fi |
| Copy engines WLAN | 12 DMA‑каналов в WCSS | исполняют дескрипторы, которые пишет прошивка модема | DMA в физическую память — поток 0x640 идёт через SMMU без stage‑1 трансляции. Единственный забор — гипервизор и XPU, не наши | анализ SMMU; docs/wifi-threat-model.md |
| Дисплейный контроллер (MDSS) | DMA‑мастер, поток 0x800 | железо + настройка загрузчика | Сканирует память framebuffer’а через ту же SMMU. Мы не поднимали дисплей — мы подменили содержимое того, что он уже показывал | milestone 0: полосы и циклы цветов, по которым угадали stride и порядок байт |
| PMIC (PM8150) по SPMI | отдельная микросхема | Qualcomm | Причины перезагрузки (PON), байт для загрузчика, и SDAM — единственная энергонезависимая память, куда мы можем записать слово перед опасным шагом и прочитать после сброса. Наш единственный свидетель | методика тихих сбросов |
| Мы (EL1) | ядра APPS, куча ~85 МиБ между образом и первым резервом | наш Rust | Голосуем за рельсы (по разрешению AOP), просим TrustZone загрузить модем, обслуживаем модем, программируем stage‑1 SMMU (пока нет), пишем слово в SDAM. Второе ядро — наш watchdog, который называет шаг, на котором повисло первое | — |
Читать таблицу нужно так: выше нас — трое (TrustZone, гипервизор, AOP), рядом — двое с DMA в нашу память (модем через copy engines, дисплей), и до нас — один, который решает, будем ли мы вообще. Все семеро — чужой код.
2a. Карта связей
Та же таблица — как граф. Это общая схема всей серии: каждая частная статья показывает свой фрагмент этой же схемы, теми же именами узлов.
Как читать: красное — чужой код; зелёное — наш; синее — память, за которую идёт спор; пунктир — «может, если захочет» (сброс, чтение, DMA мимо окна). Все стрелки из нашего блока — просьбы, голоса, ответы и трансляция; все стрелки в него — власть.
3. Что значит «владеть»
Не всякая привилегия одинакова. Разложим по тому, что каждый жилец может сделать с нами — и что мы с ним.
| Прочитать нашу память | Записать в неё | Обесточить/сбросить | Решить, загрузимся ли мы | Что можем мы | |
|---|---|---|---|---|---|
| TrustZone | всю | всю | да (XPU → сброс) | косвенно (проверяет подписи) | просить (smc) |
| Гипервизор | всю | всю | да | нет | просить (через него идут smc); принимать его вето |
| AOP | нет | нет | да (питание всего SoC) | нет | голосовать по его правилам |
| S‑Boot | до нас | до нас | нет | да | оставить байт причины в PMIC |
| Модем через CE | всю (bypass) | всю (bypass) | косвенно (сброс через TZ на отказ) | нет | обслуживать, изолируя прозрачно; запереть stage‑1 |
| Модем сам по себе | свои регионы + SMEM | то же | нет | нет | валидировать всё, что он написал |
| Дисплей | framebuffer | нет | нет | нет | рисовать в нужное место |
| PMIC | нет | нет | по команде | хранит байт для S‑Boot | писать слово‑свидетель |
Обратите внимание на асимметрию: в столбце «что можем мы» — только просить, голосовать, обслуживать, валидировать и запирать. Ни одного «запретить», ни одного «проверить их код». Это нормально для современного SoC. Ненормально — не знать об этом.
4. Три открытия, из которых выросла серия
4.1 Чип наказывает не ошибкой, а сбросом
На микроконтроллере ошибка — это HardFault с адресом в регистре. Здесь ошибка перед забором TrustZone — это перезагрузка из secure world, неотличимая от штатной SYSTEM_RESET: регистры причины в PMIC чисты, лог TrustZone зашифрован, прерывания нет. Единственный способ узнать, на каком шаге умерли, — записать слово о шаге в энергонезависимую память до шага и прочитать после. Так появился «свидетель» в SDAM, и так десяток сбросов был превращён в таблицу «симптом → причина → доказательство → фикс».
4.2 Карта памяти — это граница безопасности
Один кэшируемый мегабайт в окне, которое мы одолжили модему, стоил сброса: модем прочитал через наш кэш пустые заголовки своей файловой системы, «восстановил» её и упал. Спекулятивное чтение процессора в память гипервизора — сброс. Метаданные для TrustZone в куче, которую мы потом переиспользовали, — сброс через шесть секунд «в простое». Отсюда инварианты, проверяемые компилятором: всё разделяемое — некэшируемое, всё чужое — Device, каждое окно — на границе 2 МиБ.
→ Мегабайт, который убил модем
4.3 Радио — не периферия, а жилец
Самое большое открытие. На макетке STM32 + ESP32 Wi‑Fi был модулем за UART‑ом: 950 строк драйвера, провод, текстовый протокол. На планшете «то же самое» заняло 32 000 строк, потому что Wi‑Fi‑прошивка исполняется на процессоре сотового модема, требует от хоста файлы, диск, память и IPC, и ходит в нашу RAM по физическим адресам. Это касается не одного планшета: каждого Android на Snapdragon, ноутбуков с Wi‑Fi Qualcomm под Windows, iPhone с модемом Qualcomm.
→ Паразит в планшете — центральная статья: анатомия, модель угроз, Android/Windows/Apple/ESP32, что открывается спецслужбам, как проверить своё устройство.
Из неё выросли четыре частных:
- 23 миллисекунды, чтобы солгать — почему ответ «нет такого файла» роняет модем, а ответ «вот он, пустой» — нет; почему изоляция должна быть прозрачной: правильный по форме ответ, пустой по сути.
- Гипервизор сказал «нет» — как выглядит вето на bypass, почему «bypass» Android — это translate‑банк с выключенной трансляцией, и что нужно, чтобы запереть DMA прошивки в её окно.
- Пароль у нас, ключ у модема — что защищает supplicant на хосте (пароль сети), чего не защищает (трафик: ключ сессии всё равно уезжает в прошивку), и почему так у всех.
- Доверяй проводу? — честное состояние TLS на маленьких платах, почему «доверяй проводу» превращает радио из наблюдателя в MITM, и что нужно, чтобы это закрыть.
5. Что из этого стало правилами ОС
Каждое открытие оставило в кодовой базе правило. Не в документе — в коде, там, где его нельзя забыть.
- Ничего не читать без запроса. Первый
smcи первое чтение SMEM — только когда агент спросил. Загрузка не должна повиснуть на вопросе, который никто не задавал. - Каждый шаг, который может повиснуть или убить, — помечен. Вызов в secure world, первое чтение общей памяти, каждый шаг рукопожатия с модемом — под меткой, которую читает watchdog‑ядро или следующая загрузка из SDAM. Ответ «повисло на
scm detect» или «сброс наwlfw fw_ready wait» — вместо «умерло». - Карта памяти — константы с
const‑проверками. Окна 2 МиБ, всё разделяемое некэшируемое, всеno‑map— Device, метаданные для TrustZone — в своём окне вне кучи. - Всё, что написала другая сторона, — недоверенный ввод. База FIFO, смещение элемента, длина, адрес в QMI, адрес буфера в HTT — проверяется против окна до разыменования. Парсеры — safe Rust: баг — паника, не запись мимо буфера.
- Прозрачная изоляция. Модему отвечают на всё, что он спросит, — формально правильно, по сути пусто: тень вместо диска, scratch в RAM вместо файлов, именованные регионы вместо произвольных,
NOT_SUPPORTEDвместо молчания. - Голосовать, не приказывать. Рельсы и такты — голоса с полом, не абсолютные значения: наш голос — один из нескольких, и загрузчик уже оставил свой.
- Честный список «не можем». Модель угроз, в которой всё закрыто, — не модель угроз. Гипервизор, XPU, прошивка модема, эфир и сотовый uplink — не наши; это записано, а не спрятано.
6. Что дальше
- Stage‑1 для потока
0x640— запереть copy engines в их окне. Единственная доработка, требующая живого прогона с риском сброса; mainline Linux на этом SoC уже ходит этим путём, так что ответ известен. - TLS с pinned root — превратить радио из MITM в наблюдателя метаданных.
- Фаззинг парсеров QMI/QRTR/GLINK/HTT на детерминированных фейках, которые уже есть для каждого сервера.
- Уровневые голоса за рельсы как общая модель ядра (максимум по голосующим), а не пол в одной константе.
7. Порядок чтения
Если читать одно — Паразит в планшете. Если читать всё — в таком порядке:
- Эта статья — карта жильцов.
- Сброс без свидетелей — метод, без которого не было бы остального.
- Мегабайт, который убил модем — карта памяти как граница.
- Паразит в планшете — радио как жилец.
- 23 миллисекунды, чтобы солгать — как жилец проверяет хозяина.
- Гипервизор сказал «нет» — кто владеет SMMU.
- Пароль у нас, ключ у модема — где граница WPA.
- Доверяй проводу? — где граница конфиденциальности.
Технические первоисточники — docs/wifi-sm8150.md (железо, карта памяти, все сбросы с причинами, методика) и docs/wifi-threat-model.md (модель угроз по каналам, остаточные возможности, сравнение с Linux).
Ответ на вопрос из заголовка: компьютер — того, кто держит ключи, владеет заборами и решает, что загрузится. На планшете это TrustZone, гипервизор, AOP и загрузчик — Qualcomm и Samsung. Мы — жилец на EL1, который научился не наступать на заборы, обслуживать соседа с DMA так, чтобы он ничего не получил, и честно записывать, до чего не дотягивается. Это меньше, чем хотелось. Это больше, чем есть у любой ОС, которая об этом не знает.
Умный дом под ключ на собственной ОС: 0xd-os и 0xd-uiМы строим умные дома под ключ на компонентах собственной разработки. В основе — ОС 0xd-os, которая одинаково работает на флагманском планшете и в устройстве с 512 КБ памяти.
Зачем агенту безопасности нужна песочницаАгент, который «просто запускает инструменты» на разборе, — второй атакующий, пока у каждого вызова нет гранта. AGI Core изолирует инструменты; Forge держит список скиллов в UIDE.