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