Common Intermediate Language и системное программирование в Microsoft.Net. Макаров А.В - 81 стр.

UptoLike

4. всеми этими возможностями может воспользоваться злоумыш-
ленник для написания вредоносного кода.
Другими словами, верификатор .NET предназначен не для поиска
ошибок в программах, а для обеспечения безопасности системы.
Мы будем различать два достаточно близких понятия, относящихся к
верификации. Это верифицированный код и верифицируемый код.
Верифицируемый код – это код, для которого верификатор может
доказать, что он не разрушает память. Другими словами, для верифициру-
емого кода можно заранее, еще до запуска верификатора, сказать, что он
успешно пройдет верификацию (такой код может генерироваться компи-
лятором C#).
Верифицированный код – это код, для которого верификатор доказал,
что он не разрушает память. То есть чтобы из верифицируемого кода полу-
чить верифицированный код, нужно обязательно запустить верификатор.
4.3.3. Алгоритм верификации
В спецификации CLI предложен базовый вариант алгоритма вери-
фикации. Любая реализация CLI должна включать верификатор, верифи-
цирующий по крайней мере те программы, которые допускает базовый ал-
горитм.
Алгоритм верификации представляет собой вариант абстрактного
интерпретатора CIL-кода. Линейность алгоритма верификации достига-
ется за счет того, что он просматривает тело метода последовательно инст-
рукция за инструкцией (при этом ни одна инструкция не обрабатывается
дважды).
4.3.3.1. Совместимость типов
Пусть S и T – типы. Тогда S[] и T[] – соответствующие им массивные
типы, а S& и T& – типы соответствующих управляемых указателей.
Тот факт, что S совместим по присваиванию с T, мы будем записывать
как S := T.
Операция := рефлексивна и транзитивна.
Правила совместимости типов:
1. S := T, если S – базовый класс для T или интерфейс, реализуе-
мый T, и при этом T не является типом-значением.
2. S := T, если S и T – интерфейсы, и реализация T требует реали-
зации S.
3. S := null, если S – объектный тип или интерфейс.
4. S[] := T[], если S := T и размерности массивов совпадают.
5. S& := T&, если S := T.
Если ни одно из этих правил не выполняется, то типы S и T несовме-
стимы.
Анализ кода на CIL
149
вычислителей этот вопрос до сих пор остается открытым (ответ на него
приходится искать методом проб и ошибок). Для верификаторов ответ за-
висит от того, какой подход (первый или второй) был выбран для ограни-
чения задачи:
1. Верификаторы, предназначенные для выявления определенно-
го класса ошибок в программах, используются при разработке
программ на этапе тестирования. Тем самым, на вход таких ве-
рификаторов подаются любые программы в надежде, что в них
будут обнаружены ошибки.
Запуск такого верификатора можно рассматривать как один из
тестов, которым подвергается программа. Если в некоторой
программе верификатор не обнаружил ни одной ошибки, то
можно считать, что его запуск был напрасным.
2. Верификаторы, способные доказать, что программа, принадле-
жащая некоторому классу программ, не разрушает память, слу-
жат для обеспечения безопасности. Они гарантируют, что запуск
прошедшей верификацию программы не может привести к
сбою всей системы.
Использование таких верификаторов оправдано и удобно в слу-
чаях, если существуют специальные компиляторы, генерирую-
щие такой код, который не только заведомо не разрушает па-
мять, но и гарантированно проходит верификацию.
Тем самым ответ на вопрос о том, для каких программ верифи-
катор может доказать, что они не разрушают память, становит-
ся тривиально прост: для тех программ, которые были откомпи-
лированы специальным компилятором.
4.3.2. Особенности верификатора кода, используемого в .NET
Верификатор кода .NET относится ко второму типу. Он может дока-
зать, что сборки, генерируемые компиляторами C#, J# и Visual Basic .NET,
не разрушают память. При этом он терминируем и имеет линейную слож-
ность.
Здесь может возникнуть следующий вопрос: зачем нужен верифика-
тор, если компиляторы и так генерируют не разрушающий память код?
Чтобы ответить на этот вопрос, нужно вспомнить, что:
1. существуют компиляторы (например, Visual C++ with Managed
Extensions), которые в общем случае могут порождать код, раз-
рушающий память;
2. в языке C# предусмотрены unsafe-блоки и unsafe-методы, внутри
которых может находиться код, способный разрушить память;
3. любой CIL-код, в том числе и разрушающий память, можно
непосредственно компилировать с помощью ILASM;
148
CIL и системное программирование в Microsoft .NET
148                         CIL и системное программирование в Microsoft .NET   Анализ кода на CIL                                                    149


вычислителей этот вопрос до сих пор остается открытым (ответ на него                   4. всеми этими возможностями может воспользоваться злоумыш-
приходится искать методом проб и ошибок). Для верификаторов ответ за-                     ленник для написания вредоносного кода.
висит от того, какой подход (первый или второй) был выбран для ограни-               Другими словами, верификатор .NET предназначен не для поиска
чения задачи:                                                                   ошибок в программах, а для обеспечения безопасности системы.
       1. Верификаторы, предназначенные для выявления определенно-                   Мы будем различать два достаточно близких понятия, относящихся к
          го класса ошибок в программах, используются при разработке            верификации. Это верифицированный код и верифицируемый код.
          программ на этапе тестирования. Тем самым, на вход таких ве-               Верифицируемый код – это код, для которого верификатор может
          рификаторов подаются любые программы в надежде, что в них             доказать, что он не разрушает память. Другими словами, для верифициру-
          будут обнаружены ошибки.                                              емого кода можно заранее, еще до запуска верификатора, сказать, что он
          Запуск такого верификатора можно рассматривать как один из            успешно пройдет верификацию (такой код может генерироваться компи-
          тестов, которым подвергается программа. Если в некоторой              лятором C#).
          программе верификатор не обнаружил ни одной ошибки, то                     Верифицированный код – это код, для которого верификатор доказал,
          можно считать, что его запуск был напрасным.                          что он не разрушает память. То есть чтобы из верифицируемого кода полу-
       2. Верификаторы, способные доказать, что программа, принадле-            чить верифицированный код, нужно обязательно запустить верификатор.
          жащая некоторому классу программ, не разрушает память, слу-
          жат для обеспечения безопасности. Они гарантируют, что запуск         4.3.3. Алгоритм верификации
          прошедшей верификацию программы не может привести к                        В спецификации CLI предложен базовый вариант алгоритма вери-
          сбою всей системы.                                                    фикации. Любая реализация CLI должна включать верификатор, верифи-
          Использование таких верификаторов оправдано и удобно в слу-           цирующий по крайней мере те программы, которые допускает базовый ал-
          чаях, если существуют специальные компиляторы, генерирую-             горитм.
          щие такой код, который не только заведомо не разрушает па-                 Алгоритм верификации представляет собой вариант абстрактного
          мять, но и гарантированно проходит верификацию.                       интерпретатора CIL-кода. Линейность алгоритма верификации достига-
          Тем самым ответ на вопрос о том, для каких программ верифи-           ется за счет того, что он просматривает тело метода последовательно инст-
          катор может доказать, что они не разрушают память, становит-          рукция за инструкцией (при этом ни одна инструкция не обрабатывается
          ся тривиально прост: для тех программ, которые были откомпи-          дважды).
          лированы специальным компилятором.
                                                                                4.3.3.1. Совместимость типов
4.3.2. Особенности верификатора кода, используемого в .NET                           Пусть S и T – типы. Тогда S[] и T[] – соответствующие им массивные
      Верификатор кода .NET относится ко второму типу. Он может дока-           типы, а S& и T& – типы соответствующих управляемых указателей.
зать, что сборки, генерируемые компиляторами C#, J# и Visual Basic .NET,             Тот факт, что S совместим по присваиванию с T, мы будем записывать
не разрушают память. При этом он терминируем и имеет линейную слож-             как S := T.
ность.                                                                               Операция := рефлексивна и транзитивна.
      Здесь может возникнуть следующий вопрос: зачем нужен верифика-                 Правила совместимости типов:
тор, если компиляторы и так генерируют не разрушающий память код?                        1. S := T, если S – базовый класс для T или интерфейс, реализуе-
Чтобы ответить на этот вопрос, нужно вспомнить, что:                                        мый T, и при этом T не является типом-значением.
        1. существуют компиляторы (например, Visual C++ with Managed                     2. S := T, если S и T – интерфейсы, и реализация T требует реали-
           Extensions), которые в общем случае могут порождать код, раз-                    зации S.
           рушающий память;                                                              3. S := null, если S – объектный тип или интерфейс.
        2. в языке C# предусмотрены unsafe-блоки и unsafe-методы, внутри                 4. S[] := T[], если S := T и размерности массивов совпадают.
           которых может находиться код, способный разрушить память;                     5. S& := T&, если S := T.
        3. любой CIL-код, в том числе и разрушающий память, можно                    Если ни одно из этих правил не выполняется, то типы S и T несовме-
           непосредственно компилировать с помощью ILASM;                       стимы.