Заметки с bring‑up‑а собственной ОС на Samsung Galaxy Tab S6 (SM‑T865, Snapdragon 855 / SM8150). Продолжение статьи о «паразите» (карта всех жильцов устройства — во вводной серии): там мы выяснили, что Wi‑Fi‑прошивка на этом SoC DMA‑ит в нашу память по физическим адресам. Здесь — почему так вышло на уровне регистров, кто нам это разрешил, кто запретил сделать иначе, и как выглядит путь наружу.
Проблема
Записали в регистр SMMU тип bypass для DMA-потока Wi-Fi — прочитали обратно fault. Кто-то между нашей инструкцией и регистром посмотрел на значение и заменил его на противоположное. Это гипервизор Samsung на EL2.
Что сделали
Разобрали три уровня владения SMMU: stage 2 и глобальные регистры — гипервизора, содержимое банка stage 1 — наше. Повторили обход стокового Android: translate-банк с выключенной трансляцией. Тем же путём подняли USB, UFS и дисплей.
Что получилось
«Bypass» Android — это bypass, оформленный как трансляция: поток Wi-Fi-прошивки сегодня адресует физическую память напрямую. Из mainline известно, что stage 1 для этого чипа возможен — план записан, честно помечен как «не сделано».
0. Одна запись, которая не легла
История короткая. Прошивка WLAN живёт на Hexagon модема, её двенадцать copy engines ходят в RAM через apps_smmu (0x1500_0000) потоком 0x640 (в дереве устройств — iommus = <&apps_smmu 0x640 0x1>: два id, 0x640 и 0x641). Чтобы кольца DMA заработали, потоку нужно разрешение пройти. Самый простой способ на SMMUv2 — записать в регистр S2CR этого потока тип bypass: «не транслировать, пропускать как есть». Так мы и сделали.
Запись не легла. Регистр читался обратно с типом fault. Не «остался прежним» — переписан в fault. Кто‑то между нашей инструкцией и регистром SMMU посмотрел на значение и заменил его на противоположное.
Этот «кто‑то» — гипервизор на EL2. На Snapdragon он есть в каждом телефоне, и в стоковом дереве устройств у apps_smmu стоит свойство qcom,skip-init: «не инициализируй глобальную конфигурацию, она не твоя».
1. SMMUv2 за пять минут
ARM SMMUv2 (MMU‑500, как его ставит Qualcomm) — это MMU перед DMA‑мастерами. Транзакция приходит с stream id — номером, который NoC приписывает источнику. Дальше по порядку:
- SMR (Stream Match Register). Массив пар «id, маска»; поток совпал с записью
i, если(id ^ sid) & !mask == 0. Маска — это0x1изiommus: одна запись покрывает0x640и0x641. Бит валидности — либоSMR.VALID, либо, при 16‑битных id (IDR0.EXIDS),S2CR.EXIDVALID. - S2CR (Stream‑to‑Context Register). Что делать с совпавшим потоком. Тип в битах 16–17:
0— translate через контекст‑банк с номером в младших битах;1— bypass;2/3— fault. - Контекст‑банк (CB). Своя страница регистров во второй половине окна SMMU.
CBAR— тип банка (stage 1, stage 2, оба) и атрибуты для непереведённого трафика;CBA2R— 64‑битный формат таблиц или нет;SCTLR— бит 0Mвключает трансляцию, бит 7CFCFG— останавливать ли транзакцию на fault;TTBR0/TCR/MAIR— таблицы страниц, как у процессора;FSR— что банк записал о faults. - Stage 1 и stage 2. Как у процессора: stage 1 — таблицы ОС, stage 2 — таблицы гипервизора. Мастер видит IOVA; куда это попадёт в DRAM, решают оба уровня по очереди.
sCR0. Глобальный регистр:CLIENTPD(бит 0) выключает клиентский порт целиком — всё идёт мимо;USFCFG(бит 10) решает судьбу потока, не совпавшего ни с одним SMR: пропустить или fault.
Путь одной транзакции и кто что в нём пишет:
copy engine +----------------- apps_smmu (0x1500_0000) -----------------+
(Hexagon, WLAN) | |
---- sid 0x640 ----> | SMR[i] ----> S2CR[i] ----> CB n: CBAR CBA2R SCTLR | ----> stage 2 ----> DRAM
| id/mask type,cb TTBR0 TCR MAIR (stage 1) | (EL2) PA
+------|--------------|-------------------|----------------+ |
| | | |
кто пишет: мы (EL1) мы (EL1), мы (EL1): гипервизор
запись но под вето EL2: SCTLR.M = 0 сегодня, (Samsung, EL2):
остаётся BYPASS -> FAULT M = 1 -- план таблиц не видим
поток без SMR -----> sCR0.USFCFG (глобальный регистр: гипервизора) -----> bypass | faultНам принадлежит середина — SMR, S2CR (с оговоркой) и содержимое банка. Глобальные регистры и stage 2 — нет.
2. Что значит «гипервизор владеет глобальной конфигурацией»
qcom,skip-init в дереве — инструкция драйверу: не сбрасывать SMMU, не трогать sCR0, не считать себя первым. Кто‑то настроил устройство до EL1 и продолжает за ним следить. На планшете это гипервизор Samsung в hyp_mem (резерв no-map с 0x8570_0000).
Три уровня на практике:
- Stage 2 — его. Таблицы, которые решают, какие физические страницы вообще существуют для гостя (нас) и его мастеров. Прочитать их мы не можем: EL2 из EL1 недоступен, а
hyp_memу нас отображён как Device именно чтобы ни одно спекулятивное чтение не пересекло чужой забор. - Глобальные регистры — его.
sCR0, распределение банков. Наши записи вS2CRдо регистра доходят не всегда — отсюда вето; каким механизмом он их перехватывает, нам не видно. - Stage 1 — наш. Содержимое контекст‑банка:
SCTLR,TTBR0,TCR,MAIR. Единственный слой, где ОС решает, что видит мастер.
Стоковый Android живёт в тех же условиях — с той же строкой в дереве и тем же гипервизором.
Та же картина на уровень выше — не регистры, а жильцы: кто какой слой apps_smmu держит и куда через него доходит поток 0x640. Красное — чужое, зелёное — наше; сплошная стрелка — механизм, наблюдавшийся живьём, пунктир — «может, если захочет» и то, что помечено как план. Это фрагмент общей карты устройства из вводной статьи (§2a), теми же именами узлов.
3. Вето
Наблюдаемый факт: запись S2CR.TYPE = BYPASS возвращается как FAULT. Гипервизор не разрешает EL1 объявить поток «без трансляции» напрямую. Почему именно — политика «каждый поток через банк» или защита от гостя, который случайно откроет мастеру всё, — мы не знаем. Знаем, что на этом SoC так не пишется.
Драйвер различает две неудачи. Если банк‑кандидат был, а проверка после записи показывает не то, что писали, — SmmuError::Vetoed: записи не легли. Если свободных банков не нашлось вовсе — NoRoom.
4. Обход: pass‑through в маске трансляции
Что делает стоковый Android для мастера без stage‑1 таблиц (icnss со свойством qcom,smmu-s1-bypass) и что повторяет наш map_passthrough_group:
- Найти SMR, уже называющий
sid, иначе свободный; записать0x640с маской0x1. - Найти контекст‑банк, на который не указывает ни один валидный
S2CRтипа translate — не занятый ни гипервизором, ни другим мастером. Кандидатов перебирается не больше восьми. - В
CBAR— «stage 1 с обходом stage 2» (гипервизор свой stage 2 подставит сам), атрибуты non‑shareable, write‑back; вCBA2R— 64‑битный формат. - В
SCTLR— ноль.M = 0: трансляция выключена, атрибуты входящие. Банк есть, таблиц нет, адрес проходит как пришёл. - В
S2CR— тип translate с номером этого банка. - Прочитать
disposition(sid)и убедиться, что поток теперьTranslate { cb: наш, sctlr.M = 0 }. Нет — следующий кандидат; кончились —Vetoed.
Гипервизор это пропускает: с его точки зрения поток идёт через банк, по правилам. С точки зрения мастера адрес после stage 1 не изменился. Это bypass, оформленный как трансляция.
Тем же путём у нас идут все DMA‑мастера: USB (0x140), UFS (0x300), QUP/GPI на шинах I²C. Часть банков достаётся от загрузчика: UFS живьём читался как Translate { smr 2, cb 1, sctlr 0xe0, fsr 0x400 } — банк с M = 0, уже pass‑through; 0x400 — бит 10 FORMAT, признак формата, а не fault: банк ничего не записал. Такой банк map_passthrough_group оставляет и лишь чистит FSR. Дисплей (0x800) на этой загрузке — тоже pass‑through с SCTLR.M = 0.
В таблице ресурсов платформы это одна строка — smmu:wlan, «wlan firmware dma: apps smmu stream 0x640/1», переключатель Switch::Stream. «Включено» для неё — Bypass или Translate с M = 0; «выключить» — пустая операция, включённое остаётся включённым. Голосует wifi.power до старта модема: первое обращение прошивки в память через несконфигурированный поток под гипервизором — не ошибка, а тихий сброс всего SoC (на CAL_REPORT).
5. Что защищает stage 2 гипервизора — и чего не защищает
От чего защищает. От гостя и его мастеров — память гипервизора, secure world, регионы других процессоров, всё, чего в его таблицах для нас нет. Что именно там есть, мы не знаем и проверить не можем.
От чего не защищает по построению. Stage 2 отображает память гостя. Наше ядро, куча, стор с паролями сетей и токенами, кольца DMA в LENT_WLAN — для гипервизора это одна сущность, «RAM гостя»; он не отличает наш код от нашего DMA‑буфера. Если бы отличал — наш собственный pass‑through не работал бы. Значит, всё, куда может писать наш процессор, может писать и мастер за банком с M = 0.
Отсюда формулировка модели угроз: по нашей части системы поток 0x640 сегодня адресует физическую память. Адрес в SR_BASE_LO/HI и DR_BASE_LO/HI колец — физический адрес LENT_WLAN (0x84e0_0000, 4 МиБ некэшируемой RAM ниже hyp_mem). Честная прошивка пишет туда. Скомпрометированная положит в дескриптор любой другой адрес — и на уровне apps_smmu её ничто не остановит. Оставшиеся заборы — stage 2 Samsung и XPU Qualcomm — не наши, мы не видим их содержимого и не можем считать их своей границей.
6. Линза, а не драйвер
Поскольку SMMU не наш, Smmu намеренно почти ничего не делает. Это инструмент наблюдения плюс одна операция:
disposition(sid)— что случится с потоком:Unmatched { usf_faults }(ни один SMR не назвал его; решаетsCR0.USFCFG),Bypass { smr },Fault { smr }илиTranslate { smr, cb, sctlr, fsr }. Ответ на «сматчен? в какой банк? транслирует, пропускает или роняет? были faults?» — до того, как мастера выпустят в память.faults()—FSRбез битаFORMAT:MULTI,SS,UUT,ASF,TLBLKF,TLBMCF,EF,PF,AFF,TF. Ноль — банк чист.clear_faults(cb)— сбросить записанное (write‑1‑to‑clear), вернуть, что было.map_passthrough_group(sid, mask)— та единственная операция из §4.
Никаких «сбросить SMMU», «включить порт», «выставить USFCFG». Глобальное состояние (sCR0, IDR0/1, sGFSR) читается, не пишется. Каждый мастер при подъёме сообщает свою disposition до и после — в логе bring‑up‑а, в фактах hw.ufs и hw/panel. Так «кто владеет чем» не теряется между запусками.
Про USFCFG одно замечание: поток без SMR при взведённом бите роняется, и на этом SoC такой fault для процессора — не событие, а зависание. Поэтому «просто не трогать SMMU» для нового мастера — не вариант; каждому нужен явный SMR и банк.
Отступление: питание единиц трансляции
SMMU — не один блок. Единицы трансляции (TBU) сидят на NoC рядом с мастерами, и у них свои домены питания в GCC: tbu1_gdsc и tbu2_gdsc (hlos1_vote_aggre_noc_mmu_tbu*_gdsc; TBU2 — потоки 0x400..0x800 на aggregate NoC, TBU1 — ниже). Загрузчик оставляет их выключенными. Симптом — не fault: первое обращение мастера в память ждёт вечно, и процессор, написавший ему doorbell, ждёт вместе с ним. В таблице ресурсов оба домена vital — на них держится консоль по USB, и выключить их сверху HAL отказывается. «Работает ли SMMU» на этом чипе — вопрос не только конфигурации, но и питания.
7. Дальше: настоящий stage 1 для 0x640
Это план. Живьём не сделано.
Идея прямая: тот же банк, тот же S2CR типа translate — но SCTLR.M = 1 и заполненные TTBR0/TCR/MAIR. Таблицы отображают только окно LENT_WLAN, identity внутри (IOVA = PA) и ничего снаружи. Кольца программируются теми же адресами — для честной прошивки ничего не меняется. DMA за пределы окна — fault банка (CFCFG: terminate, не stall), а не запись в память. disposition(0x640) должен показать Translate { sctlr.M = 1 }, clear_faults в честной работе — ноль.
В коде: Smmu::map_translate(sid, mask, ttbr0, tcr, mair) рядом с map_passthrough_group; таблицы stage 1 на окно; Switch::TranslatedStream вместо Switch::Stream для строки smmu:wlan. Затем prove‑run: wifi.power on до ready, скан, вход в сеть.
Риски названы честно. Гипервизор может ветировать банк с M = 1 так же, как ветирует bypass. Адреса, которые прошивка вычисляет сама (память по SERVICE_READY), могут оказаться вне отображённого — тогда либо другой IOVA‑layout, либо честная запись, что путь на этой плате закрыт. Любое отклонение здесь, как и весь bring‑up, — тихий сброс.
Почему мы считаем, что это должно работать — вывод из mainline, не наше наблюдение. В mainline Linux sm8150.dtsi даёт wifi@18800000 iommus = <&apps_smmu 0x0640 0x1>. Драйвер arm-smmu-qcom.c держит список клиентов, которым выдаётся identity‑домен — mdss, adreno, *-mss-pil; qcom,wcn3990-wifi в нём нет. Значит, DMA API для этого устройства идёт через dma-iommu, и copy engines адресуют IOVA. HDK SM8150 с той же прошивкой WLAN.HL.3.2.0 поднимает Wi‑Fi в mainline. Это говорит, что stage 1 для 0x640 на этом SoC с этой прошивкой возможен и гипервизор референсной платы его не запрещает. Наш планшет — Samsung со своим гипервизором; совпадает ли политика, покажет только прогон.
И чтобы не переоценить результат: stage 1 запирает copy engines — путь через apps_smmu. Регионы, отданные модему через memshare (MSA, буфер rmtfs), он пишет своим путём — Q6 через собственную MMU и XPU, минуя apps_smmu. Там лечение другое: минимальные гранты, а забор — XPU от TrustZone.
8. Факт, план, вывод
- Наблюдалось живьём:
S2CRтипа bypass возвращается как fault; банк типа translate сSCTLR.M = 0гипервизор пропускает; поток0x640сегодня идёт через такой банк; банки загрузчика (UFS, дисплей) — того же вида; TBU без питания — вечное ожидание, не fault. - План, не сделано:
SCTLR.M = 1для0x640с таблицами на одно окноLENT_WLAN;map_translate;Switch::TranslatedStream; prove‑run. - Вывод из mainline, не наше наблюдение: на SM8150 stage 1 для этого потока работает под
dma-iommu; гипервизор референсной платы его не ветирует. Про гипервизор Samsung на планшете это гипотеза. - Не знаем и не притворяемся: что в таблицах stage 2; почему запрещён bypass; через какую TBU физически идёт
0x640.
SMMU в вашем телефоне — не ваш. Вам дают один слой из трёх и следят, чтобы вы не выключили его совсем. Пока этот слой у нас пустой, единственный забор между Wi‑Fi‑прошивкой и памятью ядра — чужой. Заполнить его — один банк, одна таблица страниц и один прогон, который либо подтвердит вывод из mainline, либо добавит строку в список того, что этот гипервизор не разрешает.
Источники. Драйвер‑линза: crates/qcom/src/smmu.rs — Disposition, map_passthrough_group, SmmuError::{Vetoed, NoRoom}, clear_faults, FSR_FAULTS, смещения регистров. Платформа: platforms/sdm855/src/soc.rs — APPS_SMMU, WLAN_SID, MDSS_SID, LENT_WLAN, TBU1_GDSC/TBU2_GDSC; platforms/sdm855/src/resources.rs — строка smmu:wlan; crates/qcom/src/resource.rs — семантика Switch::Stream; platforms/sdm855/src/storage.rs — живое чтение банка UFS; crates/qcom/src/i2c.rs — USFCFG как зависание. Документы: docs/wifi-sm8150.md (таблица фактов, §8 «чего с AP не видно»), docs/wifi-threat-model.md §4, §5 (уровень 2), §6, §7, docs/wifi-parasite-article.md §2.2, §8.
SMMU в вашем телефоне — не ваш: вам дают один слой из трёх и следят, чтобы вы не выключили его совсем. Пока этот слой пуст, единственный забор между Wi-Fi-прошивкой и памятью ядра — чужой. Мы умеем читать эти заборы на любом устройстве, а в своих ставим собственный.Продукты из статьи
Устройство, где хозяин — вы
Мы строим умные дома на 0xd-os — и проводим аудит чужих устройств и прошивок с письменным scope. Напишите, что у вас.
Умный дом под ключ на собственной ОС: 0xd-os и 0xd-uiМы строим умные дома под ключ на компонентах собственной разработки. В основе — ОС 0xd-os, которая одинаково работает на флагманском планшете и в устройстве с 512 КБ памяти.
Зачем агенту безопасности нужна песочницаАгент, который «просто запускает инструменты» на разборе, — второй атакующий, пока у каждого вызова нет гранта. AGI Core изолирует инструменты; Forge держит список скиллов в UIDE.