Сайт Алексея Муртазина (Star Cat) E-mail: starcat-rus@yandex.ru
Мои программы Новости сайта Мои идеи Мои стихи Форум Об авторе Мой ЖЖ
VB коды Статьи о VB6 API функции Самоучитель по VB.NET
Собрания сочинений Обмен ссылками Все работы с фото и видео
О моём деде Муртазине ГР Картинная галерея «Дыхание души»
Звёздный Кот

Самоучитель по VB.NET
Глава 16.

Дальнейшие перспективы

Книга называется «Переход на VB .NET: стратегии, концепции, код». Мы познакомились со стратегией перехода на VB .NET. Мы изучили ключевые концепции, которыми должен владеть каждый разработчик VB .NET. Мы рассмотрели не только изменения в синтаксисе языка VB .NET, но и примеры программного кода, демонстрирующие основные принципы использования важнейших пространств имен .NET.

Библиотека .NET Framework огромна. Хотя в книге рассматривается лишь малая ее часть, надеюсь, описанные базовые концепции помогут вам взяться за ее самостоятельное изучение.

Остается рассмотреть несколько дополнительных тем, которые являются неотъемлемой частью .NET, хотя и влияют на относительно небольшую часть процесса разработки.

Сначала мы в последний раз вернемся к «кошмару DLL», а затем обсудим проблемы безопасности в .NET. Книга завершается несколькими мелкими темами, для которых не нашлось места в предыдущих главах.

Хочу особо подчеркнуть, что эта глава чрезвычайно важна. Приведенный материал расположен в конце книги вовсе не потому, что ему уделяется второстепенное значение. Более того, контроль версии и безопасность одними из первых оказались в списке тем, когда я продумывал общую структуру книги, — без их хорошего понимания невозможно нормально программировать в .NET. Впрочем, при наличии хороших познаний в области .NET разобраться в них значительно проще — а если вы дочитали до этой страницы, считайте, что такие познания у вас уже есть.

 

Контроль версии в .NET

Из главы 11 вы узнали, что в .NET-языках проблемы с конфликтами компонентов, часто возникающие в приложениях СОМ, решаются посредством JIT-ком-пиляции. Умение JIT-компилятора связывать имена методов зависимых сборок при их построении и обнаруживать несовместимые изменения в процессе JIT-компиляции решает целый класс проблем с совместимостью DLL, которые мы ласково называем «кошмаром DLL».

Однако «кошмар DLL» — штука упрямая, и JIT-компиляция не учитывает некоторые возможные ситуации.

ВНИМАНИЕ

  В контексте этой главы все, что говорится о зависимости приложения от компонентов или сборок, относится в равной степени и к зависимости последних от других компонентов или сборок.

Кошмарные сценарии

Предположим, у вас имеется распределенное приложение А, использующее COM DLL с именем D версии 1.0 (Dvl.0). Затем появляется приложение В, которое использует новую версию COM DLL D версии 2.0 (Dv2.0).

К сожалению, хотя разработчики Dv2.0 постарались организовать контроль версии DLL при помощи режима двоичной совместимости VB6, а внутренние GUID всех методов и параметров остались полностью совместимыми, вкралась небольшая ошибка. В Dvl.0 поддерживалось свойство Value, которое в случае присваивания ему отрицательной величины автоматически обнулялось, но не возвращало информации об ошибке (поскольку данное свойство не могло принимать отрицательные значения). Было решено, что это неправильно, и в Dv2.0 ошибка была исправлена: при попытке задания отрицательного значения свойство Value инициировало ошибку.

По недосмотру программиста в приложении А свойству Value задавалось значение -1. На работу приложения это не влияло, поскольку библиотека Dvl.0 просто обнуляла свойство, и дальше все шло нормально.

Приложение В, распространяемое с Dv2.0, прекрасно работало. Но после установки в системе Dv2.0 в приложении А происходил сбой. Там, где раньше некорректное изменение свойства Value обходилось без проблем, возникала ошибка времени выполнения, которую разработчики приложения А не потрудились перехватить...

Перед вами вполне реальная история1.

Подобные проблемы возникают в Windows из-за того, что в общем случае в системе может присутствовать только одна версия компонента. После установки нового компонента в каталоге System или его регистрации (в зависимости от типа компонента — традиционная библиотека DLL, экспортирующая функции, или COM DLL, предоставляющая доступ к объектам) этот компонент будет возвращаться любому приложению, от которого поступил запрос.

1Компонент назывался «Animated Button» и входил в подмножество продукта Desaware Custom Control Factory, лицензированное компанией Microsoft для включения в Visual Basic. Приложением В был сам Visual Basic. В процессе тестирования специалисты Microsoft обнаружили ошибку со значением свойства Value и потребовали, чтобы при передаче отрицательной величины инициировалась ошибка. Я предупредил, что это вызовет серьезные проблемы с совместимостью, но они настаивали, что это явная ошибка и что все, кто использует значение -1 в своей программе, просто исправит свой код. Поскольку счета оплачивались в Microsoft, я внес изменения, и, конечно, приложение А перестало работать — в нем свойству Value элемента Animated Button по ошибке задавалось значение -1. Приложением А была одна из ранних версий Microsoft Encarta. До сих пор пакет Custom Control Factory (откуда был взят элемент Animated Button) принимает значение -1 для свойства Va I u e и спокойно обнуляет его. В компании Desawareсерьезно относятся к проблемам совместимости. Лучше добавить в элемент новое свойство, чем вносить исправления, принципиально изменяющие поведение нашего компонента.

Возникает целая серия потенциальных проблем. Приведу лишь несколько примеров.

Короче говоря, вы оказываетесь в «кошмаре DLL».

Как видите, даже при обеспечении идеальной совместимости на двоичном уровне всегда остается возможность функциональных несоответствий. Пока в системе работает единственная версия компонента, «кошмар DLL» теоретически нельзя исключить.

В Windows 98 и 2000 появились две особенности, в определенной степени исправлявшие положение дел. В этих операционных системах поддерживается так называемое параллельное выполнение (side-by-side execution) и механизм подстановки DLL, позволяющий приложению загрузить DLL из локального каталога вместо каталога System. Если создать в каталоге файл с расширением .local, приложение могло загрузить локальную копию компонента даже при наличии в системе другой версии. Параллельное выполнение требует учета специальных факторов при написании компонента.

К сожалению, на практике этим решениям присущи некоторые недостатки.

СОМ с поддержкой параллельного выполнения, поскольку эта проблема (как вы вскоре убедитесь) легко решается в .NET.

 

Параноидальные сценарии

Недавно я общался по электронной почте с одним программистом. Бедняга опасается, что кто-нибудь дизассемблирует его приложение, и хочет знать, нельзя ли зашифровать один исполняемый файл внутри другого и восстанавливать внутренний файл при запуске внешнего. При этом требуется полностью заблокировать отладку программы и даже исключить ее из списка процессов1.

1Нет, я не знаю, можно это сделать или нет.

Интересно, какая программа может требовать такого уровня защиты?

Но любой параноик способен представить себе следующий сценарий.

Приложение А использует COM DLL Dvl — очень надежный компонент, неспособный повредить системе. Некий злоумышленник вынашивает коварный план. Он создает копию Dvl и дизассемблирует ее. Ему не нужно разбираться в функциональности компонента, так как большинство функций вообще не изменяется и воссоздается повторным ассемблированием. Но в результате модификации некоторые ключевые методы начинают вытворять ужасные вещи: стирать жесткий диск, рассылать испорченную библиотеку по списку адресов электронной почты, хранящихся в системе и т. д. Затем злоумышленник строит DLL, присваивает ей то же имя (назовем ее Dvlbad) и увеличивает номер ревизии до 1.01, чтобы библиотека устанавливалась поверх любой существующей DLL с тем же именем. Dvlbad обладает теми же данными библиотеки типов, теми же свойствами версии и т. д. Наконец, злоумышленник распространяет испорченную DLL с приложением В, которое внешне выглядит совершенно невинно.

Подобный прием называется фальсификацией (spoofing). Он встречается довольно редко, но может приводить к очень серьезным последствиям — особенно если фальсифицируется системный файл.

К сожалению, в Windows не предусмотрено встроенных средств для решения подобных проблем. Обычно фальсифицированная программа рассматривается как вирус, а ее сигнатура включается в базы данных антивирусных программ. К сожалению, в системах, где эта программа была запущена, такие меры уже не помогут.

Контроль версии в .NET

В Microsoft .NET поддерживаются два разных механизма контроля версии в зависимости от того, какое имя было выбрано для сборки.

Но позвольте, как выбор имени связан с версией приложения?

Оказывается, между ними существует прямая зависимость.

В .NET сборкам могут присваиваться простые или сильные (strong) имена. Простое имя представляет собой имя сборки, указанное в параметрах проекта, и обычно совпадает с именем файла сборки. Сильное имя содержит простое имя, номер версии, культуру, открытый ключ и сигнатуру сборки.

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

Однако в большинстве сборок, предназначенных для последующего распространения, следует использовать сильные имена, что объясняется следующими причинами

Отсутствие сборки с простым именем будет обнаружено исполнительной средой .NET. При нарушении совместимости между версиями зависимой сборки с простым именем во время выполнения программы произойдет ошибка и будет инициировано исключение — по крайней мере, это не приводит к ошибкам защиты памяти. Конечно, если исключение не будет перехвачено, все кончится аварийным завершением программы.

Проекты UnsignedAppl и UnsignedDLLl написаны специально для того, чтобы вы могли поэкспериментировать с зависимостями простых сборок. Измените версию UnsignedDLLl и запустите UnsignedAppl. Также попробуйте изменить параметры функции on в UnsignedDLLl и посмотрите, что произойдет при запуске UnsignedAppl.

Другими словами, хотя при использовании простых имен .NET защитит от системных сбоев, «кошмар DLL» по-прежнему остается очень серьезной проблемой.

Существуют ли ситуации, в которых предпочтительно использовать простые имена? Я могу предложить только два варианта.

1. Если проблемы контроля версии или зависимости для вас несущественны (например, при написании примеров для книги или статьи).

2. Если вы полностью контролируете содержимое каталога. От параллельного выполнения выигрывают даже сборки с простыми именами — это естественное следствие того факта, что .NET сначала пытается загрузить сборку с простым именем из локального каталога приложения. Но даже в этом случае сильные имена не имеют никаких отрицательных сторон, если не считать некоторых дополнительных хлопот в процессе построения (почти полностью автоматизированного в Visual Studio).

Сильные имена рекомендуется использовать во всех сборках, предназначенных для практического использования (даже внутри организации).

 

Сильные имена

Попробуйте создать простое приложение VB6, в котором функция CreateObject создает объект с именем Projectl.

Если ваш компьютер ранее использовался для программирования на VB6, эта операция завершится успешно. Вы получите объект для последнего созданного ActiveX EXE или DLL.

В СОМ нет ничего, что помешало бы двум программистом создать одноименные объекты. Проблема конфликтов имен в СОМ решается при помощи GUID — глобально-уникальных идентификаторов, которые являются «истинными» именами объектов, классов и интерфейсов.

В Microsoft .NET избран другой подход: сборка идентифицируется объединением различных информационных элементов. К числу таких элементов относятся:

Сочетание этих элементов однозначно идентифицирует сборку. По отдельности эти элементы служат разным целям.

Использование сильных имен исключает опасность «кошмара DLL» для компонентов .NET1.

1Впрочем, сильные имена не препятствуют возникновению «кошмара DLL» при использовании компонентов СОМ средствами COM Interop.

Сильные имена и контроль версии

Вспомним, как приложение загружает компоненты СОМ.

1. Приложение знает имя или GUID загружаемого объекта.

2. Приложение ищет в реестре данные о местонахождении объекта.

3. Приложение загружает объект.

Поскольку в реестре для каждого компонента хранится всего одна запись, не существует механизма идентификации, поиска и загрузки разных версий одного компонента2.

Компоненты .NET вообще не используют реестр. Общие сборки хранятся в глобальном кэше сборок (global assembly cache, GAC), причем для хранения используются только длинные имена. Другими словами, GAC может содержать столько разных версий одной сборки, сколько вам захочется создать.

Процесс загрузки компонента .NET в приложении .NET называется связыванием (binding). Он принципиально отличается от аналогичного процесса СОМ3.

2Не считая редко используемого параллельного выполнения на базе {\langl033 СОМ}, о котором говорилось выше.

3Следующее описание несколько упрощено. За подробным описанием процесса загрузки обращайтесь к разделу «How the Runtime Locates Assemblies» электронной документации.

Пример использования сильных имен

Проекты StrongAppl и StrongDLLl предназначены для экспериментов с контролем версии.

Проект StrongDLLl содержит единственный класс с методом, возвращающим полное имя версии для компонента:

Imports System.Reflection Public font>

Public Function MyVersionO As String

Return Reflect!on.Assembly.GetExecutingAssembly.FullName()

End Function

End

Точный номер версии задается в файле Assemblyinfo.vb при помощи атрибута: 

<Assembly: AssemblyVersion ("1.0.0.1")>

В соответствии с конфигурацией проекта StrongDLLl при создании сильного имени используется ключевой файл, заданный в параметрах проекта (на вкладке Sharing). Ключевой файл TestKeys.snk создается утилитой sn.exe с параметром - k (хотя его также можно создать при помощи Visual Studio). Файл TestKeys.snk находится в каталоге СН16 и используется несколькими проектами этой главы. Не используйте этот файл в своих приложениях1.

Обычно доступа к ключевому файлу у вас не будет: большинство организаций предпочитает хранить свои закрытые ключи на гибком диске или защищенном сервере и не распространяет их среди разработчиков. Чтобы из-за этого у вас не возникали затруднения, установите флажок Delay Sign. При этом в сборку внедряется открытый ключ. В результате приложения могут ссылаться на сборку, а в исполняемом файле резервируется место для последующего включения полного

1 Ключевой файл был сгенерирован специально для примеров, включенных в эту главу. Не используйте его для каких-либо целей, кроме экспериментов с этими программами.

имени. Вы можете при помощи программы sn.exe извлечь открытый ключ и распространить его среди разработчиков. Перед выпуском окончательной версии программа sn.exe заново «подписывает» сборку закрытым ключом и вставляет в нее сильное имя.

Ссылка на сборку StrongDLLl содержится в проекте StrongAppl. Это консольное приложение выводит данные о версии сборки, вызывая ее открытую функцию My Version.

Imports StrongDLLl Module Modulel

Sub Main()

Dim с As New StrongDLLl.>

Console.WriteLine (c.MyVersion)

Console.ReadLineO End Sub

End Module

При запуске программы выводится следующий результат:

StrongDLLl, Version=l.0.0.0, Culture=neutral, PublicKeyToken=4139a2c451ef76d7

Поле PublicKeyToken содержит каптированную версию открытого ключа (вскоре мы вернемся к этой теме). Если дизассемблировать приложение StrongAppl, в нем обнаруживается следующий фрагмент:

.assembly extern StrongDLLl {

.publickeytoken = (41 39 A2 C4 51 EF 76 D7)

// A9..Q.v

.ver 1:0:0:0 }

Приложение будет связываться только с версией 1.0.0.0 библиотеки strongdlll с заданным открытым ключом. Таким образом, по умолчанию для работы каждой сборки с сильным именем необходимо точное совпадение версий всех зависимых сборок. Тем самым предотвращаются несовместимости, обусловленные расхождением версий.

Разумеется, подобный подход затрудняет обновление компонентов.

Обновление компонентов с сильными именами

На первый взгляд может показаться, что при любом распространении обновленных компонентов приходится заново строить и распространять все приложения, использующие данный компонент (поскольку в противном случае приложения будут ссылаться на старую версию компонента). Ликвидируя «кошмар DLL», мы создаем «кошмар с распространением».

К счастью, в .NET предусмотрен механизм управления связыванием, позволяющий загружать обновленные версии зависимых сборок. В работе этого механизма используется не один и не два, а целых три конфигурационных файла в формате XML. Конфигурационные файлы управляют безопасностью, контролем версии и удаленным доступом к приложению.

Первый из этих файлов (конфигурационный файл приложения, application configuration file) определяет стандартную конфигурацию приложения. Конфигурационный файл публикации (publisher configuration file) добавляется при обновлении приложения для переопределения стандартных параметров, в том числе и относящихся к контролю версии. Также существует и конфигурационный файл компьютера (machine configuration file), который создается системным администратором и управляет поведением всех сборок в системе (с переопределением всех параметров двух предыдущих файлов).

Имя конфигурационного файла приложения строится по правилу «имя приложения + суффикс .config» — например, Myapp.exe.config. Имя конфигурационного файла публикации строится в формате политика. осн.доп .сборка.dll, где оси и доп — основной и дополнительный номера версии сборки, к которой применяется заданная политика. Для версий 2.1.0.0-2.1.х.х сборки strongdlll политика определяется файлом с именем policy.2.1.strongdlll.dll. Файлы политики применяются только к сборкам, хранящимся в GAC. За инструкциями по созданию файлов политики обращайтесь к электронной документации. Системная конфигурация обычно задается при помощи Microsoft Management Console.

Все конфигурационные файлы заключаются в теги верхнего уровня <Configuration> (то есть файл начинается с тега <Configuration> и завершается тегом </Configuration>).

Рассмотрим пример конфигурационного файла.

<configuration> 

<runtime>

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.vl"> <dependentAssembly> 

<assemblyIdentity name="strongDLLl"

  publicKeyToken="4139a2c451ef76d7" culture="" /> 

<bindingRedirect oldVersion="1.0.0.0-1.0.0.1"

  newVersion="1.0.0.2" />

 </dependentAssembly>

 </assemblyВinding> 

</runtime>

 </configuration>

Для каждой зависимой сборки, поведение которой требуется изменить, в конфигурационном файле создается блок <dependentAssembly>. В него включается тег <assemblyldentity>, теги которого используются для идентификации сборки по сильному имени (имя, версия, локальный контекст и открытый ключ).

Тег <codebase> показывает, откуда следует загружать сборку, если она отсутствует в глобальном кэше сборок или в локальном каталоге.

Тег <bindingRedirect> предписывает исполнительной среде загрузить обновленную сборку. В данном примере приложения, построенные с версиями 1.0.0.0-1.0.0.1 сборки strongDLLl, загружают версию 1.0.0.2. Предполагается, что перед заданием этого параметра вы тщательно проверили совместимость обновленной сборки.

Тег <publisherPoliсу> (отсутствующий в приведенном примере) указывает, следует ли использовать конфигурационный файл публикации, который представляет мнение публикующей стороны о том, с какой версией следует выполнить связывание. Иначе говоря, даже если разработчик компонента выпустит

обновленную сборку с файлом политики, разрешающим автоматическое обновление, у вас есть возможность переопределить настройки этого файла и заставить приложение работать со/ старой версией компонента.

В теге <probing>, находящемся внутри блока <assemblyBinding>, перечисляются подкаталоги приложения, в которых осуществляется поиск сборки. С помощью этого тега можно организовать логическую группировку, если в проекте задействовано большое количество сборок.

 

Контроль версии исполнительной среды .NET

Рассмотрим следующий Пример конфигурационного файла:

<configuration>

<startup> <requiredRuntime version="l.0.0.0" safeMode="fal.se"/>

</startup> </configuration>

В приведенном примере указывается, что для работы приложения необходима конкретная версия исполнительной среды .NET.

Прежде я неоднократно упоминал о том, что .NET решает проблему «кошмара DLL», позволяя сборкам указывать точную версию зависимых сборок. К счастью, в Microsoft вовремя сообразили, что этот принцип относится к самой среде .NET, особенно если учесть, что Microsoft породила больше «кошмаров DLL» с несовместимыми компонентами, чем любая другая компания (не сочтите выпадом против Microsoft — это простое отражение того факта, что в Microsoft было создано больше DLL, чем где-либо еще). Более того, в системе может быть одновременно установлено несколько версий исполнительной среды .NET, которые могут одновременно работать.

У этого флага есть лишь один недостаток: он не всегда помогает с приложениями, работающими под управлением Internet Explorer (например, web-элементами), поскольку в одном процессе не могут одновременно работать две разные версии среды .NET.

 

Сильные имена и фальсификация

Итак, вы узнали, как сильные имена помогают избавиться от проблем контроля версии за счет включения номера версии в имя сборки. Теперь давайте посмотрим, какое место они занимают в борьбе с фальсификацией и модификацией сборок.

Как было показано выше, приложение StrongAppl содержит образец открытого ключа сборки StrongDLLl. Образец представляет собой хэш-код реального ключа. Идея заключается в том, что сохранять в приложении весь открытый ключ не нужно, достаточно убедиться в том, что открытый ключ, содержащийся в зависимой сборке, совпадает с ключом, указанным при построении приложения. Для этого вполне можно обойтись хэш-кодом, поскольку вероятность совпадения хэш-кодов двух разных открытых ключей близка к нулю (как и вероятность сгенерировать открытый ключ по хэш-коду).

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

Природа шифрования с открытым ключом такова, что для расшифровки информации, зашифрованной с закрытым ключом, используется только открытый ключ. При пометке сборки закрытым ключом (в процессе построения или окончательного выпуска) вся сборка хэшируется, в результате чего появляется сигнатура. Сигнатура зашифровывается закрытым ключом и расшифровывается открытым ключом.

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

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

Итак, сильные имена успешно решают проблему фальсификации сборок.

Сильные имена и пометка кода

Тем не менее, сильные имена не позволяют точно определить, откуда поступила сборка. Допустим, вы получили компонент от компании-разработчика hcwrecords.com. На этот компонент можно ссылаться и использовать его в программе, но кто может поручиться, что этот компонент действительно поступил от hcwrecords.com? Проще говоря, сильные имена анонимны, а любой хакер может присвоить своему изделию сильное имя.

На помощь приходит механизм пометки кода, хорошо знакомый авторам элементов ActiveX, «подписывавшим» свои разработки при помощи Authenticode. Некая третья сторона предоставляет вам открытый ключ разработчика компонента. Если вы доверяете третьей стороне, то можете быть уверены в том, что компонент поступил действительно от hcwrecords.com, а не от кого-то другого, маскирующегося под них1.

Сильные имена и пометка кода не исключают друг друга и могут использоваться в сборке одновременно. В этом случае сначала применяются сильные имена.

1Я не буду углубляться в подробности. Вероятно, сейчас пометка кода будет использоваться значительно реже, чем при разработке элементов ActiveX (сильные имена обеспечивают защиту от модификации, которая была одной из важнейших особенностей Authenticode).

 

Конфликты в пространствах имен

При выборе имен сборок нередко возникает путаница с пространствами имен. Обратитесь к главе 10, где эта тема рассматривается более подробно. Помните о том, что пространства имен общих сборок (тех, которые вы собираетесь установить в глобальном кэше сборок) должны быть уникальными. Для этого проще всего строить иерархию пространств имен от названия вашей компании. По этой причине названия всех пространств имен .NET Framework, созданных в Microsoft, начинаются с префиксов System и Microsoft.

 

.NET и параллельное выполнение

Исполнительная среда Microsoft -NET рассчитана на поддержку двух типов параллельного выполнения: на одном компьютере и в одном процессе. Впрочем, текущая версия среды не поддерживает параллельного выполнения в одном процессе. В документации сказано, что эта возможность появится в окончательной версии.

Иначе говоря, два разных приложения могут одновременно использовать две разные версии сборки, но один процесс пока не может загрузить две версии сборки. Сказанное подтверждается проектами StrongDLL2A, StrongDLL2B и StrongApp2.

StrongDLL2B ссылается на версию 1.0 сборки StrongDLL2A. StrongApp2 содержит ссылку как на версию 1.0.0.1 StrongDLL2A, так и на StrongDLL2B. Следовательно, для работы программы в память должны быть одновременно загружены версии 1.0.0.0 и 1Д0.1. Поскольку это пока невозможно, загрузка завершается неудачей, если только в файле StrongApp2.exe.config не указано, что версия 1.0.0.1 может быть принята в качестве обновления 1.0.0.0.

Параллельное выполнение должно учитываться при проектировании сборок. Например, если сборка использует жестко закодированные пути к файлам или именованные мьютексы (mutexes), следует помнить, что к этим объектам возможен одновременный доступ со стороны двух разных версий вашего кода.

 

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

В понятие «безопасность» часто вкладывается разный смысл. Под ним понимают:

  •  аутентификацию пользователей (локальную или по Интернету);
  •  защиту компьютера или сервера от разного рода внешних нападений;
  •  возможность запретить выполнение некоторых приложений;
  •  предотвращение вреда, наносимого выполнением злонамеренных программ;
  •  хранение конфиденциальной информации или поддержание защищенных каналов связи.

В общем, безопасность тоже заслуживает отдельной книги (наверное, вы уже привыкли к этой фразе). С другой стороны, каждый программист хотя бы в общих чертах разбирается в этой теме. Для начала стоит предположить, что средства безопасности, поддерживаемые в VB6 на программном уровне, могут быть реализованы и в .NET при помощи таких пространств имен, как System.Securityn System.DirectoryServices.

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

 

Прощайте, сбои, прощайте, вирусы?

Одна из целей .NET Framework заключалась — ни больше, ни меньше — в устранении неустранимых сбоев приложений и защите системы от вирусов и других злонамеренных программ.

Вижу, как вы недоверчиво качаете головой: «Да, конечно — уже слышали...» Ваш скептицизм вполне оправдан. Каждый раз, когда Microsoft объявляет о новой схеме безопасности, за этим неизбежно следует поток сообщений о дефектах защиты и поспешных исправлений. А в новых операционных системах, которые работают еще надежнее предыдущих, все равно возникают фатальные ошибки, от которых спасает только перезагрузка (хотите вы того или нет).

Пока трудно судить о том, каким будет истинный уровень защиты .NET и будет ли эта система устойчивее прежних, но после чтения этого раздела вы согласитесь с тем, что сама архитектура безопасности в .NET наглядно доказывает: в Microsoft очень серьезно относятся к этой проблеме. По крайней мере, это большой шаг, сделанный в нужном направлении.

Указатели и сбои

Каждый, кому доводилось программировать в эпоху 16-разрядных версий Windows, хорошо помнит «прелести» ошибок UAE (Unrecoverable Application Error, неустранимая ошибка приложения), позднее превратившихся в GPF (General1Protection Fault, общая ошибка защиты). В наши дни сбои приложений по-прежнему случаются, о чем обычно свидетельствует ошибка «Memory Exception» или внезапное исчезновение программы, но общесистемные сбои (полное «зависание» в Windows 98/ME или «посмертный синий экран» NT/2000) встречаются реже.

Почему возникают подобные сбои?

Потому что большинство приложений пишется на языках, использующих указатели, например на C++. При работе с указателем всегда существует вероятность того, что ему по какой-либо причине будет присвоено неправильное значение. Если вы забудете инициализировать указатель, он может ссылаться в несуществующую область памяти или в сегмент программного кода вашего приложения, что приведет к непреднамеренному стиранию кода2. Если ошибочный указатель будет ссылаться в область данных, Он может испортить структуры данных (в том числе и другие указатели). Возможны и другие варианты, например случайная запись после конца приемного буфера с порчей данных в памяти.

Да, сбои могут возникать и по другим причинам, например из-за деления на ноль. Многие приложения нередко инициируют ошибки времени выполнения (случайно или намеренно), но самые неожиданные и зловредные ошибки связаны с применением указателей.

В 32-разрядных операционных системах Microsoft защита от большинства системных сбоев обеспечивается изоляцией процессов друг от друга и от операционной системы3. Тем не менее для .NET подобной изоляции процессов недостаточно. Как было сказано в главе 10, в .NET основной единицей является домен приложения, а не процесс. Домен приложения может соответствовать отдельному процессу, но в системах типа ASP .NET один процесс может содержать сотни и даже тысячи доменов приложений.

1В оригинале — Global Protection Fault. —Примеч. перев.

2В зависимости от типа ОС и указателя попытки записи в программный код либо приводят к модификации программы, либо инициируют исключение защиты памяти.

3В Windows 95/98/ME это утверждение не совсем верно, но для наших целей сойдет.

Если бы указатель одного из этих доменов приложений мог каким-то образом испортить данные другого домена (или ASP .NET), ни о какой устойчивой работе и речи быть не могло.

Напрашивается предположение, что проблема решается исключением указателей из языков .NET (таких, как VB .NET и С#), но как было показано в главе 15, даже VB .NET может использовать переменные-указатели Int32Ptr для работы с неуправляемым кодом.

Решение проблемы лежит в области безопасности типов — вспомните, что говорилось о неуправляемом коде в главе 15. Обмен данными с неуправляемым кодом осуществляется через объект Marshal, благодаря чему данные отправляются и принимаются в виде четко определенных структур данных. Другими словами, даже копирование данных в памяти выполняется с точным указанием расположения этих данных в памяти и сильной типизацией переменных. При правильном объявлении функций или структур CLR гарантирует, что данные будут переданы правильно, без загадочных переполнений или порчи данных.

Что касается других обращений к управляемой памяти, поскольку весь доступ осуществляется через объекты с сильной типизацией (см. главу 10), вы можете обеспечить проверяемую (verifiable) изоляцию доменов приложений. Говорят, что приложение обладает проверяемой безопасностью типов. В CLR предусмотрены средства для анализа кода и проверки безопасности типов. Не каждый язык .NET создает код с проверяемой безопасностью типов (программа может быть безопасной по отношению к типам, но не проверяемой — такой код генерируется в C++), но код VB .NET всегда должен удовлетворять этому условию. Безопасность типов сборки можно проверить при помощи утилиты Preverify.exe. В С# у вас есть возможность выбора, но ненадежный код не дает почти никаких (или просто никаких) преимуществ.

Зато у ненадежного кода есть один серьезный недостаток: для его выполнения требуется более высокая степень доверия. Вскоре я объясню, что это значит.

Злонамеренный код и вирусы

Программный код поступает в вашу систему из разных источников. Конечно, в первую очередь это программы, устанавливаемые с компакт-дисков или гибких дисков. Когда-то этим и ограничивались все ваши хлопоты, но в наши дни появились элементы и компоненты, загружаемые с web-страниц; сценарии, работающие на web-страницах; программы, принятые с web- и FTP-сайтов; файлы, присоединенные к сообщениям электронной почты, и даже макросы, скрытые в документах текстовых редакторов и электронных таблиц.

И любой фрагмент программного кода может вызвать полный хаос в вашей системе.

Почему? Потому что любая программа (кроме некоторых сценарных языков) при получении управления могла вытворять в вашей системе все, что угодно.

Операционные системы Windows NT/2000 обладают чрезвычайно ограниченными средствами безопасности на уровне приложений. Система может запретить пользователю или группе пользователей запускать некоторое приложение, но если приложение все же будет запущено, оно может делать практически все. Некоторые сценарные языки работают в так называемой «песочнице» (sandbox) —

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

В .NET Framework принят принципиально иной подход к безопасности на уровне приложений. В дополнение к хорошо знакомой вам ролевой безопасности (распределению прав в зависимости от идентификации пользователя), в .NET появилась так называемая безопасность программного доступа1.

Общая идея заключается в следующем: каждая сборка работает в собственном контексте безопасности, определяемом информацией о сборке и политике безопасности, установленной в системе.

Если сборка обладает проверяемой безопасностью типов, CLR может следить за тем, какой код работает и что он делает (невозможно передать управление таким образом, чтобы об этом не стало известно CLR). Таким образом, CLR точно знает, когда программа вызывает тот или иной метод пространства имен. В свою очередь, это означает, что среда может установить надежные ограничения доступа на уровне объектов или методов.

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

Краткий обзор подходит к концу. Давайте поближе познакомимся с безопасностью .NET и возможностями ее практического использования.

1Ролевая безопасность в .NET реализована весьма гибко. Помимо идентификации на основании учетных записей пользователей и групп вы можете определять нестандартные роли. Например, можно построить базу данных пользователей и ролей, независимую от системных данных, и использовать ее для принятия решений из области безопасности. Тем не менее общие принципы ролевой безопасности не изменились, поэтому мы не будем подробно рассматривать эту тему.

 

Сборки и политики безопасности

Первое, что необходимо знать о безопасности программного доступа: все решения из области безопасности в .NET принимаются на основании информации о сборке, имеющейся у среды.

Информация о сборке

А что .NET знает о сборке? Точнее, какая информация о сборке может храниться в системе?

  •  Издатель сборки (если последняя обладает цифровым сертификатом).
  •  Web-сайт, пытающийся выполнить сборку (при вызове через обращение к web-странице).
  •  Сильное имя сборки (обеспечивает уникальную идентификацию сборки).
  •  URL (web-сайт или FTP-сайт), с которого была принята сборка.
  • Зона Интернета, из которой была принята сборка. Зоны определяются в настройках Internet Explorer — Интернет, местная интрасеть, надежные и ограниченные узлы, а также локальный компьютер.
  •  Признак хранения сборки в одном каталоге с запустившим ее приложением (или в одном из его подкаталогов). Рассмотрим некоторые возможные ситуации.
  • Проверка компонентов ASP может быть основана на сайте, с которого поступил вызов.
  •  CLR точно знает, откуда поступает весь принятый код. Допустим, вы не сомневаетесь в надежности сборок, полученных из местной интрасети и от Microsoft.com. Сборки, полученные из этих источников, объявляются доверенными.
  •  Привилегии можно регулировать избирательно, например запретить всему 1 коду с указанного web-сайта модификацию реестра или запись на диск.
  •  При установке сборки в системе .NET сохраняет все доступные сведения о ней и использует их при определении политики безопасности для данной сборки.

Привилегии

Объекты пространства имен System.Security.Permissions определяют уровень привилегий, то есть разрешений на доступ к защищенным ресурсам или выполнение привилегированных операций. Вы можете определить собственный объект привилегий, но эта тема выходит за рамки данной книги.

Таблица 15.1. Классы привилегий

Объект

Операция

DirecttoryServicesPermission

Объекты пространства имен System. Directory Services

DnsPermission

Операции DNS

EnvironmentPermission

Переменные окружения

EventLogPermission

Журнал событий

FileDialogPermission

Выбор файлов в стандартном диалоговом окне File Open

FilelOPermission

Доступ к каталогам и/файлам

IsolatedStorageFilePermission

Закрытые виртуальные файловые системы

Isolated Storage Permission

Изолированное хранилище — новая разновидность хранения данных в .NET, ассоциируемая с конкретным кодом

MessageQueiiePermission

Microsoft Message Queue (MSMQ)

OleDb Permission

Операции OLE DB

PerformanceCounterPermission

Счетчики быстродействия

PrintingPermission

Операции с принтером

ReflectionPermission

Операции рефлексии в .NET

Registry Permission

Операции с системным реестром

Security Permission

Различные привилегии, связанные с безопасностью программного доступа

ServiceControllerPermission

Службы Windows

SocketPermission

Операции с сонетами

SQLClientPermission

Операции с базами данных SQL

U I Permission

Функции пользовательского интерфейса

WebPermission

Web-операции (прием или отправка)

Для управления безопасностью операций используются методы соответствующих объектов привилегий. Эта тема более подробно рассматривается ниже.

Наборы привилегий

Совокупность привилегий называется набором (set). Скажем, набор привилегий Интернета определяет привилегии, типично используемые для программного кода, принятого из Интернета, а набор привилегий «с полным доверием» (full trust) позволяет программе выполнять любые операции.

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

Политики

Весь код в .NET относится к одной или нескольким кодовым группам (code groups) на основании сведений, доступных для данного кода. Например, весь код относится к группе «All code», а код с цифровой сигнатурой Microsoft принадлежит к группе «Published by Microsoft». Кодовая группа даже может состоять из одной сборки, заданной по сильному имени. Кодовые группы определяются в виде иерархии, возглавляемой группой «All code».

В каждой системе могут действовать до трех политик: для всей организации, для данного компьютера и для конкретного пользователя.

Допустим, сборка, работающая в браузере, была принята с сайта BadCodeSite.bad. На основании имеющихся сведений она принадлежит к двум кодовым группам, All Code (Весь код) и Internet Zone (Зона Интернета).

В соответствии с политикой компьютера для зоны Интернета, скорее всего, будет запрещена запись на диск, обращения к локальным папкам электронной почты, обращение к реестру и выполнение Интернет-операций. Любые попытки выполнения опасных операций сборкой приведут к инициированию исключения.

Но что, если код заверен сигнатурой доверенного сайта HCWrecords.com? В этом случае он может принадлежать к группе HCWRecords. Если привилегии этой группы разрешают запись на диск или выполнение других защищенных операций, эти права будут предоставлены сборке.

Другими словами, на уровне политики используется правило «минимального ограничения». Если принадлежность кода к какой-либо кодовой группе приводит к предоставлению некоторой привилегии, данная привилегия предоставляется всей сборке. Кроме того, возможна настройка, обеспечивающая прекращение поиска на конкретной кодовой группе или уровне политики.

Фактически предоставляемые полномочия определяются минимальным уровнем по трем политикам. Например, если бы в приведенном примере политика организации запрещала бы некоторую привилегию кодовой группе HCWRecords, запрет действовал бы даже в том случае, если бы эта привилегия разрешалась в соответствии с политиками компьютера и пользователя.

Политики применяются в следующем порядке: сначала политика организации, затем политика пользователя, после чего политика компьютера.

В дополнение к этим трем группам каждая сборка также проверяется на соответствие параметрам безопасности домена приложения.

Вероятно, мои объяснения могут показаться невразумительными. Почитайте документацию Microsoft — загляните в .NET Framework SDK, раздел «Programming with the .NET Framework\Securing your Application». Там все объясняется подробнее, но ясность изложения, мягко говоря, оставляет желать лучшего. Надеюсь, из чтения этой главы вместе с документацией вы получите нормальное представление о безопасности в .NET.

А пока давайте рассмотрим конкретный пример, который покажет, как механизмы безопасности работают на практике.

 

Безопасность в примерах

Ничто не проясняет сложную тему так, как практический пример. Надеюсь, последующие примеры покажутся вам достаточно интересными.

Конфигурация и трассировка стека

Решение CallUnmanagedl содержит два проекта, CallUnmanagedl и Unmanagedение класса Unmanaged так:

Public font>

Private Denction GetTickCount Lib "kernel32" () As Integer

Public Function Ticks() As Integer Return GetTickCount()

End Function End

Перед нами простой пример вызова функции API через объект-обертку. Мы знаем, что функция совершенно безвредна, но CLR этого не знает. Поскольку функция API может выполнить много опасных операций в обход .NET Framework (а следовательно, и стандартных объектов привилегий, о которых говорилось выше), выполнение неуправляемого кода требует высокого доверия.

Например, привилегию выполнения неуправляемого кода никогда не следует предоставлять программам, принятым из Интернета, если только у вас нет очень веских оснований доверять им.

В этом заключается одна из главных причин, по которым использовать функции Win32 API в VB .NET не рекомендуется: они значительно снижают мобильность ваших программ. Например, функции API не могут использоваться в элементах, которые должны запускаться в браузерах по Интернету. Скорее всего, такие попытки завершатся неудачей.

Итак, наша конкретная сборка была успешно создана и установлена в системе, а поскольку она принадлежит кодовой группе локальной системы, пользуется полным доверием. Следовательно, при вызове из приложения Windows она будет нормально работать. Но что произойдет, если вызвать ее из сборки, принятой из Интернета?

Открытая функция Ticks сама по себе не использует неуправляемого кода. Сможет ли сборка, не обладающая привилегий выполнения неуправляемого кода, воспользоваться функцией Ticks и обмануть класс Unmanaged/font> заставив его вызвать функцию API?

Давайте посмотрим.

Консольное приложение CallUnmanagedl создает объект класса и вызывает метод Тiсks:

' Вызов метода класса с неуправляемым кодом

' Copyright ©2001 by Desaware Inc. All Rights Reserved

Imports Unmanaged

Module Modulel

Sub MainQ

Dim с As New

Dim x As Integer

For x = 0 To 100 Console.WriteLine (c.Ticks)

Next

Console.ReadLine()

 End Sub

End Module

Если построить приложение CallsUnmanagedl и запустить его, оно будет работать, поскольку это приложение тоже принадлежит к кодовой группе локальной системы. Чтобы проверить систему безопасности, необходимо изменить набор привилегий для этого приложения. Запустите программу mmc.exe (Microsoft Management Console) и добавьте в нее модуль конфигурации .NET командой Add/Remove snap-in1.

На рис. 16.1 показано окно настройки системы безопасности с развернутой политикой компьютера (Machine). Из рисунка видно, что в политике уровня компьютера определено несколько кодовых групп и наборов привилегий, присваиваемых кодовым группам. На рисунке перечислен состав набора привилегий для Интернета.

Наша задача — включить приложение CallsUnmanagedl в новую кодовую группу. Для этого следует щелкнуть правой кнопкой мыши в группе Му_ Computer_Zone (поскольку сборка является подмножеством этой группы) и определить в ней новую подгруппу.

Мастер предлагает ввести имя и описание кодовой группы. Введите любое имя по своему желанию.

1Настройка системы безопасности также выполняется утилитой caspol. Более того, системные конфигурационные файлы можно редактировать вручную в текстовом редакторе — они хранятся в простом формате XML.

Далее запрашивается условие принадлежности к этой группе. Здесь указывается тип сведений, используемых для идентификации сборок, входящих в данную группу. Укажите, идентификацию по сильному имени (Strong-Name).

Затем вам будет предложено ввести открытый ключ, имя и сведения о версии для идентификации сборки. Для этого проще всего щелкнуть на кнопке Import и импортировать нужную информацию из исполняемого файла CallsUnmanagedl.exe. Не забудьте установить флажки Name и Version, иначе вы измените параметры безопасности для всех сборок, использующих ваш открытый ключ!

Рис, 16.1. Настройка политики безопасности в окне ММС

Далее мастер запрашивает набор привилегий, назначаемый данной группе. Выберите набор Internet.

На следующем этапе вам предоставляется возможность установить флажки Exclusive и Final level. Флажок Exclusive сообщает системе безопасности о том, что указанный набор привилегий должен применяться ко всем сборкам кодовой группы независимо от того, принадлежат ли они при этом другим группам или нет. Флажок Final level указывает на то, что проверка должна завершаться на этом уровне.

В приведенном примере установка этого флажка приведет к тому, что поиск на уровне User производиться не будет.

Установите флажок Exclusive. В данном случае это существенно, поскольку локальный код может присутствовать и в других группах, а в пределах уровня действует правило «минимального ограничения».

Щелкните правой кнопкой мыши на узле Runtime Security Policy и выберите команду Save All, чтобы сохранить внесенные изменения.

Если все было сделано верно, то с точки зрения CLR приложение CallsUnmanagedl обладает привилегиями, типичными для приложений, принятых из Интернета.

Что же произойдет при попытке запуска этого приложения?

При запуске из командной строки будет выведена следующая информация1:

Unhandled Exception:

System.Security.SecurityException:

Request for permission of type "System. Security.Permissions.

SecurityPermission, mscorlib, Version=l.0.?.?, Culture=neutral,

PublicKeyToken=b77a5c561934e089" failed, at

System.Securi ty.CodeAccessSecurityEngine.CheckHelper(PermissionSet

grantedSet, PermissionSet deniedSet, CodeAccessPermission demand,

PermissionToken permToken) at UnmanagedetTickCount() at

Unmanaged-icksO in

D:\CPBknet\Srcl\CH16\Unmanagedb:line 4 at

CallsUnmanagedl.Module 1.Main() in

D:\CPBknet\Srcl\CH16\CallsUnmanagedl\Modulel.vb:line 10

The state of the failed permission was:

<IPermission stem.Security.Permissions.SecurityPermission,

mscorlib, Version=1.0.?.?, Culture=neutral, PublicKeyToken=b77a5c561934e089"

version = "1"

Flags = "UnmanagedCode"/>

Возникает вопрос: настройка системы безопасности должна была запретить вызов неуправляемого кода из консольного приложения CallsUnmanagedl. Но вызов GetTickCount поступил из сборки UnmanagedCall.dll.

Как же CLR узнает о том, что выполнение кода следует запретить?

Ответ прост. При каждом выполнении привилегированной операции CLR просматривает содержимое стека и проверяет его на соответствие параметрам безопасности каждой функции. Перед вызовом функции API GetTi ckCount CLR видит, что на текущем уровне этот вызов разрешен, поскольку сборка входит в локальную группу (My_Computer_Zone). Далее следует метод Ticks, который тоже проходит проверку, поскольку он входит в ту же сборку. Следующая проверка относится к процедуре Main приложения CallsUnmanagedl. На этот раз результат оказывается отрицательным, поскольку сборка не обладает привилегиями на выполнение неуправляемого кода

1Номера версии, образцы ключей и некоторые второстепенные подробности могут отличаться..

Как видите, CLR отлично справляется с идентификацией программного кода и поддержанием защиты. Параметры безопасности кода могут выбираться в зависимости от степени доверия. Более того, с точки зрения программиста появляется возможность корректного сокращения функциональности кода за счет перехвата ошибок безопасности и отключением некоторых возможностей в менее защищенных средах.

А как быть в случаях, когда при выполнении заведомо безопасной операции требуется, чтобы операция могла выполняться менее защищенными сборками (при условии, что ваш компонент пользуется доверием)?

Методы Demand и Assert

В решение CallUnmanaged2 входят два проекта: CallUnmanaged2 и Unmanaged

Прежде чем переходить к экспериментам, постройте оба проекта и при помощи конфигурационной программы ММС настройте приложение CallsUnmanaged2 на использование привилегий Интернета, как это было сделано ранее для программы CallsUnmanagedl.

Класс Unmanagedопределяет метод GetTickCount, как и в предыдущей версии, но новый вариант функции Ticks выглядит несколько иначе.

Imports System.Security.Permissions

Imports System.Security.Principal

Imports System.Security

Public bsp;

Private Dection GetTickCount Lib "kernel32" () As Integer

<SecurityPermission(SecurityAction.Demand,

Flags:=SecurityPermissionFlag.UnmanagedCode)> 

Public Function Ticks() As Integer

Return GetTickCount()

 End Function

Атрибут SecurityPermission с командой Demand сообщает CLR, что для выполнения кода необходимы привилегии выполнения неуправляемого кода. Установка подобных требований удобна тем, что вам уже не придется перехватывать отдельные ошибки времени выполнения внутри метода. Вызывающая сторона должна обладать необходимыми привилегиями даже для простого вызова метода. Применение подобных атрибутов обеспечивает так называемую «декларативную безопасность», поскольку требования декларируются при объявлении метода.

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

<Assembly: SecurityPermission(SecurityAction.RequestMinimum, Flags:=SecurityPermissionFlag.UnmanagedCode)>

Сборка UnmanagedCIass2 содержит несколько тестовых функций, вызываемых -из программы CallsUnmanaged2. Функции замеряют время выполнения 50 000 вызовов Ticks и возвращают результат в виде объекта TimeSpan. Значение в миллисекундах выводится в консольном окне.

Функция Ticks2, приведенная в листинге 16.1, показывает, как создать объект SecurityPermission для конкретной привилегии на стадии выполнения.

Листинг 16.1. Метод UnmanagedTicks2'1

Public Function Ticks2() As TimeSpan 

Dim x As Integer 

Dim 1 As Long

 Dim t As New DateTimeQ() 

Dim ts As TimeSpan

Dim sec As New SecurityPermission(SecurityPermissionFlag.UnmanagedCode) sec.Assert()

t = DateTime.Now For x = 0 To 50000

1 += TicksO Next

ts = DateTime.Now.Subtract(t)

  CodeAccessPermission.RevertAssert()

Try

TicksO Catch e As Security.SecurityException

MsgBox ("Caught the security exception after revert!") 

End Try 

Return ts 

End Function

Вызов Assert сообщает CLR о том, что данному коду разрешается выполнять неуправляемый код. Поскольку сборка пользуется полным доверием, ей позволено отдавать подобные указания. В процессе проверки стека CLR достигает блока, в котором вызывается метод Assert, прекращает дальнейшую проверку и разрешает выполнение кода.

Разумеется, к вызову метода Assert необходимо подходить очень осторожно. Используйте его лишь в том случае, если вы абсолютно уверены в невозможности злонамеренного использования вашего кода.

Последствия вызова Assert отменяются немедленно после возвращения из функции. Все вызовы Assert, действующие для функции, можно отменить при помощи общего метода CodeAccessSecurity.RevertAssert.

Приведенная выше функция также показывает, как перехватывать ошибки безопасности. Попытка вызова Ticks завершается неудачей, поскольку в объявлении метода указан атрибут безопасности Demand (а последствия предыдущего вызова Assert были отменены).

ПРИМЕЧАНИЕ

 В зависимости от быстродействия компьютера сообщение может появиться через одну-две минуты

Поскольку функция API GetTickCount всегда безопасна, запись можно сократить прямым включением вызова Assert в объявление функции API:

<SecurityPermission(SecurityAction.Assert, _ Flags:=SecurityPermissionFlag.UnmanagedCode)> _

Private Denction GetTickCountSpecial Lib "kerne!32" _ 

Alias "GetTickCount" () As Integer

1Все исходные тексты можно найти на сайте издательства «Питер» www.piter.com. —Примеч. ред.

Данное объявление следует использовать лишь для вызова абсолютно безопасных функций API, а также для закрытых функций, которые вызываются абсолютно безопасным способом.

В методе Ticks3 (листинг 6.2) продемонстрирован распространенный способ применения метода Assert. Вместо того чтобы вызвать Assert, вы сначала используете Demand для проверки другой привилегии, обеспечивающей по возможности более жесткие ограничения. В данном примере вместо безопасности программного доступа используется ролевая безопасность. Вызов Demand гарантирует, что неуправляемый код будет вызываться лишь членами локальной административной группы.

Листинг 16.2. Метод UnmanagedTicks3

Public Function Ticks3()

 As TimeSpan 

Dim x As Integer 

Dim I As Long 

Dim t As 

New DateTime() Dim ts As TimeSpan

Dim sec2 As New _

SecurityPermission(SecurityPeriinssionFlag.ControlPrincipal) sec2.Assert()

 AppDomain.CurrentDomain.SetPrincipalPolicy _

(PrincipalPolicy.WindowsPrincipal)

 Dim roleSec As New 'PrincipalPermission(Nothing, _

"BUILTINVAdminis-trators")

 roleSec.Demand()

AppDomain.CurrentDomain.SetPrincipalPol icy _ (PrincipalPolicy.UnauthenticatedPrincipal) CodeAccessPermission.RevertAssert()

t = DateTime.Now 

For x = 0 To 50600

1 += GetTickCountSpecial() 

Next ts = DateTime.Now.-Subtract(t)

Try

GetTickCountSpecialO

 Catch e As Security.SecurityException

MsgBox ("Caught the security exception after revert!") 

End Try 

Return ts 

End Function

Сначала функция методом Assert разрешает доступ к параметрам роли пользователя для домена приложения, включая идентификационные данные, сведения о принадлежности к группам и т. д. По умолчанию эти данные недоступны, поскольку с их помощью программа может идентифицировать пользователя, выполняющего код, что создает потенциальную брешь в системе безопасности.

После предоставления этих привилегий для текущего домена приложения создается объект PrincipalPermission для встроенной административной группы Administrators. Метод Demand этого объекта инициирует ошибку времени выполнения в том случае, если текущий пользователь не является администратором.

Далее функция отменяет последствия вызова Assert и восстанавливает параметры домена приложения в более защищенном состоянии.

Наконец, функция 50 000 раз вызывает функцию GetTickCountSpecial. Как говорилось выше, эта функция содержит собственный вызов Assert.

Нетрудно заметить, что эта функция работает значительно быстрее предыдущей. Чем раньше в цепочке вызовов выполняется Assert, тем меньше времени тратится на перебор стека.

В класс также включен метод Ticks4, объявленный без Assert. Попытка его вызова из приложения CallUnmanaged2 завершилась бы неудачей. Метод Ticks4 должен вызываться из проекта CallsUnmanaged2b, практически идентичного CallsUnmanaged2 за одним исключением: ему оставлены параметры безопасности по умолчанию (полное доверие). Воспользуйтесь этим проектом, чтобы получить представление о том, как проверки безопасности влияют на скорость работы приложения.

 

Дополнительные средства безопасности

Существует ряд дополнительных средств, используемых при обеспечении безопасности. Ниже перечислены важнейшие примеры.

  • Помимо метода Assert существует и метод Deny. Используется в тех ситуациях, когда вы уверены в абсолютной безопасности своего кода и хотите гарантировать, что в нем заведомо не могут выполняться некоторые операции.
  •  Ограничение привилегий определенным набором даже в том случае, если уровень привилегий сборки, от которой поступило обращение, превышает необходимый минимум.
  •  Запрос привилегий в процессе загрузки с разграничением минимальных привилегий, необходимых для работы сборки, и необязательных привилегий, используемых в случае их доступности. Привилегии даже могут отклоняться, что помешает предоставить их вашей сборке.

Я хочу особо выделить некоторые ключевые моменты. Пожалуйста, отнеситесь к ним серьезно.

Применение Assert связано с риском!

Не используйте Assert, если у вас нет полной уверенности в том, что компонент не может использоваться некорректно.

Не используйте Assert без абсолютной необходимости. Если такая необходимость существует, сведите предоставление привилегий к минимуму при помощи Demand.

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

Используйте минимальное количество привилегий в своих программах, чтобы повысить мобильность кода и упростить его распространение.

 

Разное

В этом разделе собраны все второстепенные темы, которые мне не удалось пристроить в других главах.

Дизассемблирование

Представьте, что один из ваших программистов создал изощренное решение некой задачи и включил в свой код информацию, которую вы считаете своей интеллектуальной собственностью. Программа выходит в свет, и в один прекрасный день вы получаете по электронной почте следующее сообщение.

Привет, я немножко покопался в сборке, распространяемой с вашим суперприложением Emailer 2000. Результаты весьма любопытные. Например, взглянем на алгоритм VerifyEmail из следующего листинга, полученного дезассемблированием вашего продукта:

// Microsoft (R) .NET Framework IL Disassembler. Version 1.0.2914.16 

// Copyright (C) Microsoft Corp. 1998-2001. 

All rights reserved.

.namespace Disasm {

.02*/ public auto ansi Emailer

extends [mscorlib/*230000ei*/]System.Object/* 01000001 */

 {

method /*06000002*/ public instance bool

 VerifyEmaiKstring Email) cil managed {

// Code size 20 (0x14)

.maxstack 3

.locals init (bool V_0)

IL_0000: ldarg.1

IL_0001: Idstr "@" /* 70000001 */

IL_0006: Idc.i4.0

IL_0007: call 1nt32[M1crosoft.VisualBasic/* 23000002

*/]Microsoft.VisualBasic.Strings/* 01000002 */::

InStr(string,string,valuetype[Microsoft.VisualBasic

/* 230000002 */]Microsoft.VisualBasic.CompareMethod

/* 01000003 */) /* 0A000002 */

IL_000c: ldc.14.0

IL_000d: ble.S IL_0012

IL_000f: Idc.i4.1 IL_0010: br.s IL_0013

IL_0012: ldloc.0 IL_0013: ret }

 // end of method Emailer::VerifyEmail

Вполне очевидно, что вся проверка сводится к проверке наличия символа @ в адресе электронной почты. Наверное, вы не читали собственной рекламы с утверждениями о том, что ваши программисты запатентовали новый алгоритм для надежной проверки адресов?

А ваша надежная функция проверки хоста

.method /* 06000003 */public instance string GetMyHostO cil managed

{

// Code size 16 (0x10)

.maxstack 1

.locals init (string V_0) ~

IL_0000: ldarg.0

IL_0001: callvirt instance int32 Disasm.Emailer

/* 02000002 */::Delay()/* 06000004 */

IL_0006: pop

IL_0007: call string [System/* 23000003 */]System.Net.Dns

/* 01000004 */::GetHostName()/* 0A000003 */

IL_000c: br.s IL_000f

IL_000e: ldloc.0 IL_000f: ret } 

// end of method Emailer::GetMyHost

всего лишь возвращает значение, полученное функцией .NET Framework System.Net. Dns. GetHostName.

Кроме того, я обнаружил в вашей программе функцию Delay:

.method /*06000004*/private instance int32

DelayO cil managed

{

// Code size 48 (0x30)

.maxstack 2

.locals init (int32 V_0, int64 V_l, int64 V_2)

IL_0000: Idc.iS 0x1

IL_0009: stloc.l

IL_000a: ldloc.2

IL_000b: Idc.iS 0x1

IL_0014: add.ovf

IL_0015: stloc.2

IL_0016: ldloc.1

IL_0017: Idc.iS 0x1

IL_0020: add.ovf

IL_0021: stloc.l

IL_0022: ldloc.1

IL_0023: Idc.iS 0xf4240

IL_002c: ble.s IL_000a

IL_002e: ldloc.0 IL_002f: ret } // end of method Emailer::Delay

// end of } 

// end of namespace Disasm

//*********** DISASSEMBLY COMPLETE *********************** 

// WARNING: Created Win32 resource file D:\MovingToVBNet\Source\CH16\Disasm\Disasm.res

Цикл? Вы организуете задержку при помощи цикла? Неужели ваши программисты никогда не слышали о таймерах с ожиданием? Честно говоря, я поражен. Пожалуй, я выложу результаты в Интернете и посмотрю, что об этом скажут другие.

Ужас.

На эту тему шли бурные дебаты. Некоторые разработчики критиковали .NET за простоту дизассемблирования (использованный в приведенном примере дизассемблер ildasm.exe входит в пакет .NET SoftwareDevelopment Kit). Разработчики коммерческих компонентов требовали, чтобы компания Microsoft по крайней мере выпустила утилиту, которая бы переименовывала переменные, параметры, методы и т. д., затрудняя тем самым дизассемблирование и анализ сборок.

Я тоже считаю, что Microsoft следует выпустить такую утилиту, но прежде чем впадать в панику, следует учесть некоторые обстоятельства.

Данная проблема присуща не только .NET, она встречается и на других платформах, построенных на базе виртуальных машин.

Сведения, используемые 1NET для предотвращения конфликтов версий, представляют интерес для охотников за информацией. JIT-компилятор должен располагать полной информацией о классах, методах и параметрах.

Когда-то я работал в компании, занимавшейся анализом сбоев в полупроводниках. Некоторые клиенты пользовались нашими услугами, чтобы узнать, как работают микросхемы конкурентов. Если не жалеть денег, можно восстановить исходную схему любого устройства или программы.

На ранних порах существования Visual Basic было много разговоров об одной немецкой компании, разработавшей дизассемблер VB. В итоге эта тема забылась. Откровенно говоря, большинство компаний не боится дизассемблирования. Дело в том, что никто не сможет дизассемблировать ваш код, собрать его заново под исходным именем и заменить ранее существовавший модуль: этому помешают сильные имена, о которых говорилось выше.

Думаю, относительная простота дизассемблирования приложений .NET не вызовет особых проблем, по крайней мере у подавляющего большинства разработчиков. Я упоминаю об этом лишь для того, чтобы вы учитывали такую потенциальную возможность (и еще потому, что это мой последний шанс высказаться на спорную тему).

 

Установка

Время и место не позволяют мне рассмотреть проблемы установки в этой книге. Вряд ли кто-нибудь возьмется за отдельную книгу по этой теме, но она несомненно заслуживает отдельной главы — которую я, к сожалению, уже не напишу (по крайней мере в этом издании).

Впрочем, не могу не поделиться одной мыслью.

За годы эволюции система Windows прошла большой путь, становясь все более и более сложной. Программы установки тоже становились все более сложными и интеллектуальными.

С появлением .NET система Windows достигла такого совершенства, что она позволяет установить целое приложение простым копированием программы и всех используемых файлов в каталог простой командой Сору или ХСору.

Точно так же, как это было в MS-DOS...

 

Итоги

Глава посвящена двум важнейшим областям, в которых должен разбираться каждый программист VB .NET. Сначала мы рассмотрели контроль версий и применение сильных имен для создания сборок, привязанных к конкретным версиям зависимых компонентов, и борьбы с фальсификацией и модификацией кода. Вы узнали, как при помощи конфигурационных файлов разрешить обновление некоторых сборок.

Далее рассматривалась тема безопасности в .NET и безопасность программного доступа — весьма важное нововведение, повышающее мобильность кода без снижения уровня защиты. Вы научились пользоваться средствами системы безопасности для выполнения привилегированных операций (если ваша сборка обладает достаточными правами для подобных запросов) и корректного сокращения функциональности в ситуациях, когда программа не имеет прав на выполнение некоторых операций, используемых в работе сборки.

Однако не стоит забывать, что возможности, предоставляемые .NET для управления безопасностью на уровне методов (и даже внутри методов), могут применяться и в области ролевой безопасности и что вы можете определять роли по своему усмотрению, не ограничиваясь стандартными учетными записями пользователей и групп, определяемыми на уровне операционной системы.

Глава завершается кратким упоминанием проблем дизассемблирования и установки программ.

Назад

 


Инфо
Сайт создан: 20 июня 2015 г.
Рейтинг@Mail.ru
Главная страница