Дмитрий Радищев (dibr) wrote,
Дмитрий Радищев
dibr

защищённость без защиты

     Операционная система должна быть защищена от работающих в ней приложений, и обеспечивать защиту приложений друг от друга (привет от К.О.). Речь сейчас в основном не о вирусах/троянах, которые используют штатные возможности системы чтобы делать нехорошие вещи (здесь мы рано или поздно упрёмся в то, что отличить "хорошую" вещь от "нехорошей" не всегда просто), а о том, чтобы ошибки в обычном, не вредоносном, приложении (такие как обращение к "чужой" памяти) оставались внутри приложения и не приводили к нарушениям работы других приложений и собственно ОС. Сейчас для этого обычно используются аппаратные методы, такие как "защищённый режим" процессора: ОС выставляет процессу ограничения на используемые им ресурсы, процессор при выполнении кода аппаратно проверяет допустимость операций, и при выполнении операции, нарушающей ограничения, генерируется "исключение", обрабатываемое операционной системой ("программа выполнила и будет закрыта"), или отдаваемое приложению ("runtime error..."). И, хотя аппаратные проверки и замедляют работу (на любую проверку нужно время, кроме того - при переключении между процессами тратится время на перегрузку "описания ограничений" в процессоре), и увеличивают сложность процессора - всё равно по другому как бы вроде бы и не получится. Даже если конкретный компилятор порождает код, делающий все необходимые проверки - компиляторов много, кто-то может вообще использовать ассемблер или "писать в шестнадцатеричных кодах", а значит в выполняемом файле может оказаться инструкция, обращающаяся "не туда", эта инструкция может выполниться на реальном, аппаратном, процессоре, а при аппаратном выполнении инструкции единственный способ отследить "обращение не туда" - делать это аппаратно, на уровне процессора.

     Или нет? Например, сейчас вовсю набирают популярность языки, где возможность "обратиться не туда" исключается самим языком: достаточно отказаться от указателей внутри языка (вместо них ввести какие-нибудь высокоуровневые "ссылки"), ввести принудительную проверку индексов массивов и приравненных к ним сущностей, а чтобы не делать лишние проверки где не надо - максимально "обернуть" типовые действия над множествами объектов разнообразными "итераторами" и прочими примочками. И, собственно, всё - если язык by design не позволяет совершать такого типа ошибки, а компилятор исправен и не допускает их сам - полученный машинный код можно запускать "без защиты", ошибки в нём могут быть, но только внутренние, не мешающие остальным. Проблема получается только в том, что не весь код создаётся именно этим, "правильным", компилятором, на "правильном" языке (а возвращаясь к "вредоносным программам" - кто-то может создать "неправильный" код непосредственно в коде, минуя компилятор), а значит совсем отказаться от аппаратной защиты как бы опять же... или таки нет?

     А что если попробовать возродить что-то вроде UCSD P-System, но на новом уровне? Для начала - разрабатываем "относительно низкоуровневый" псевдокод (вроде того же UCSD P-code или Java bytecode), в который можно оттранслировать программу на любом интересующем нас языке, но при этом обладающий тем свойством, что в рамках этого псевдокода нельзя обратиться к не принадлежащему тебе объекту - просто потому что нет метода это сделать, как, например, нет метода обратиться к произвольной ячейке памяти в языках без указателей. Помнится, Великий и Ужасный dz когда-то упоминал (жаль, без подробностей) архитектуру Intel 432, где в самом машинном коде просто не было способа создать указатель из числа - только получить его извне, а значит и обратиться "не туда" программа не могла, ввиду отсутствия метода это сделать. (Как я понимаю, "метод создать указатель" там всё-таки был, но он был доступен только на определенном уровне привилегий - грубо говоря, "только ОС и доверенный код" - раз уж указатели предусмотрены архитектурой, кто-то же их должен порождать). А значит, "тьюринг-полный" псевдокод, застрахованный от ошибок вида "обратился к чужому", придумать можно.
     А дальше - очень просто. Программы распространяются не в "бинарниках" с машинным кодом, а в псевдокоде. Принесённый извне (скопированный) бинарник с машинным кодом ОС игнорируется - "это не моё, запускать не буду", при первом запуске программы в псевдокоде - из него генерируется бинарник в машинном коде (заведомо не содержащий ошибок вида "обратиться к чужому", в силу устройства псевдокода), и именно он уже запускается. Сгенерированный бинарник с машинным кодом на уровне ОС доступен пользователю только на просмотр и удаление, изменение разрешено только самой ОС - а значит хотя в машинном коде и могли бы содержаться ошибки вида "обращение к чужому", взяться им там неоткуда: ОС генерирует код без ошибок, а подменить машинный код на ошибочный так просто не получится - ОС защищает файл.
     При "добропорядочном" использовании системы этого уже достаточно: принесённые извне бинарники с машинным кодом не запускаются (а по потребности - пересоздаются из псевдокода), лежащие на локальных несменных дисках бинарники с машинным кодом защищены от изменения средствами самой ОС. На втором уровне паранойи ("а мы теперь такие загрузимся с дискетки и таки подменим бинарник с маш. кодом") можно предусмотреть, например, "подписывание" бинарников этой копией ОС, и даже их перегенерацию из псевдокода при запуске после перезагрузки системы, но это уже подробности - если не заглубляться в рассуждения, как ещё можно обмануть ОС, если поставить себе цель, то достаточную безопасность обеспечить можно, а если заглубиться - так имея физический доступ к диску всегда можно обойти любую защиту, вопрос только в сложности.

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

     ...А теперь скажите мне, какой велосипед я на этот раз изобрёл? Не верю, что с момента изобретения p-code никто не пытался сформулировать эту идею именно так :-)
Subscribe
  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 60 comments