Безопасность

Issue #381 · недоверенные входные данные, верификаторы и найденные и исправленные уязвимости

Это отчёт по issue #381: исходники эмулятора были проверены на уязвимости, достижимые из загружаемых данных, все подтверждённые ошибки исправлены, а отсутствовавшие проверки собраны в небольшой слой с тестами — src/verify.h. Аудит и исправления сделаны только по этому дереву исходников: в другие эмуляторы не подглядывали.

Полные таблицы по каждому артефакту приведены на английской странице отчёта; здесь — суть, модель угроз и перечень исправленного по подсистемам.

Модель угроз

Эмулятор — это программа, которая разбирает файлы. Эмулятор GameCube разбирает файлы, сделанные третьими лицами (образы игр, homebrew, сохранения), исполняет написанный этими лицами код и работает с правами пользователя. Реальный атакующий — это:

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

Что эмулятор берёт извне

АртефактКто читаетТочка входа
JSON настроек (Data/DefaultSettings*.json, Data/Settings*.json)src/config.cppsrc/json.cppпервый GetConfig*, то есть запуск
Образ Bootrom (Data/bootrom.bin)src/bootrtc.cppBootROM() при каждой загрузке
DSP IROM/DROM (Data/dsp_irom.bin, Data/dsp_drom.bin)src/flipper.cppsrc/dspcore.cppFlipper::Flipper()
Исполняемые файлы (.dol, .elf)src/main.cppLoadFile(): командная строка, селектор, load
Образы дисков (.iso, .gcm, .rvz)src/dvd.cpp, src/rvz.cpp, src/dvddebug.cppDVD::MountFile(), dvd_fs_init()
Сохранения карт памятиsrc/memcard.cppMCConnect() при запуске, обмены EXI от гостя
Командная строкаsrc/main.cppEMUParseCmdLine()
Память IPL ROM и регистры устройств гостяsrc/pi.cpp, src/mem.cpp, src/exi.cpp, src/bootrtc.cpp, src/dsparam.cpp, src/dspdma.cpp, src/cp.cppловушки памяти, FIFO CP и все движки DMA
Консольные сценарии (autoexec.cmd, любые script <файл>.cmd)src/debug.cppCallJdi("script autoexec.cmd") при каждой загрузке
Таблицы символов (*.map, Data/makemap.dat)src/sym.cppAutoloadMap() при каждой загрузке

Метод

Верификаторы

Правила живут в src/verify.h, и все входные пути ими пользуются:

ВерификаторЧто проверяет
Verify::Range(offset, size, limit)единственное место, где складываются два недоверенных значения; вычитание вместо сложения — ни одна пара 32-битных полей не может обойти проверку переполнением
Verify::MainMemory(phys, size, ramSize)окно в основной памяти с маской адреса, как её декодирует MI (маска допускает 64 МБ, а выделено 24 или 48 МБ)
Verify::ImageSection(...)секция исполняемого образа: есть в файле и помещается в основную память
Verify::DiscRead(position, length, imageSize)чтение с диска со знаковым смещением, которым управляет гость
Verify::FstRoot / FstEntry / FstNameтаблица файловой системы диска, записи которой берутся прямо из образа
Verify::MemcardWindow(cardSize, offset, length)обмен с картой памяти, включая саму длину
Verify::ScriptLine / ScriptTrimбезопасное чтение строк autoexec.cmd и прочих консольных сценариев

У интерфейса памяти появились аксессоры, знающие длину (MIGetMemoryPointerForIO(phys, size), ...ForDSP, ...ForPI, ...ForDebug, MIGetMemorySize()), поэтому копирование блока больше не может начаться внутри RAM, а закончиться за её пределами.

Что найдено и исправлено

Ниже — по подсистемам; в скобках указано, сколько записей в полной таблице на английской странице.

JSON настроек (src/json.cpp, src/config.cpp) — 13

Исполняемые файлы и командная строка (src/main.cpp, src/utils.cpp) — 12

Образы дисков (src/dvd.cpp, src/rvz.cpp, src/dvddebug.cpp) — 9

Карты памяти и шина EXI (src/memcard.cpp, src/bootrtc.cpp) — 12

Движки DMA и командный процессор (dsparam.cpp, dspdma.cpp, pi.cpp, cp.cpp) — 6

Консольные сценарии и команды отладчика (debug.cpp, jdiserver.cpp, gekkodebug.cpp) — 11

Таблицы символов и текст баннеров (sym.cpp, ui.cpp, uisdl.cpp) — 10

Как теперь ловятся падения при запуске

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

Для модульных тестов vstest.console <тест.dll> /Blame называет тест, который упал или завис, вместо запуска без итогов.

Тесты

testing/security_test.cpp входит в обычный набор модульных тестов (scripts/VS2026/pureikyubu_test.slnx) и покрывает: сами верификаторы (включая свойство-тест, сравнивающий Verify::Range с арифметикой по переносу на 200 000 случайных входов), чтение сценариев (в том числе свойство-тест на 3000 случайных бинарных буферов с канарейками вокруг буфера строки), разбор настроек (все испорченные документы, которые раньше переполняли буфер, зацикливались или исчерпывали стек, должны отклоняться) и поставляемые спецификации JDI (все одиннадцать разбираются ужесточённым парсером, чтобы слишком строгое ограничение не сломало отладочный интерфейс).

Что осталось за рамками

Как воспроизвести проверку