Каковы последствия запроса API-интерфейса Reflection для перезаписи System.String.Empty? - PullRequest
61 голосов
/ 09 июня 2011

Я наткнулся на этот код:

static void Main()
{
    typeof(string).GetField("Empty").SetValue(null, "evil");//from DailyWTF

    Console.WriteLine(String.Empty);//check

    //how does it behave?
    if ("evil" == String.Empty) Console.WriteLine("equal"); 

    //output: 
    //evil 
    //equal

 }

и мне интересно, как вообще возможно скомпилировать этот кусок кода. Я рассуждаю так:

Согласно MSDN String.Empty только для чтения, поэтому его изменение должно быть невозможным, а компиляция должна заканчиваться «Статическое поле только для чтения не может быть назначено» или аналогичная ошибка.

Я думал, что сборки Библиотеки базовых классов каким-то образом защищены и подписаны, и это не может предотвратить именно такую ​​атаку. В следующий раз кто-то может изменить System.Security.Cryptography или другой критический класс.

Я думал, что сборки библиотеки базовых классов компилируются NGEN после установки .NET, поэтому изменение полей класса String требует расширенного взлома и будет намного сложнее.

И все же этот код компилируется и работает. Может кто-нибудь объяснить, что не так с моими рассуждениями?

Ответы [ 5 ]

52 голосов
/ 09 июня 2011

Статическое поле только для чтения не может быть присвоено

Вы не назначаете его.Вы вызываете публичные функции в пространстве имен System.Reflection.У компилятора нет причин жаловаться на это.

Кроме того, typeof(string).GetField("Empty") может вместо этого использовать переменные, введенные пользователем, у компилятора нет уверенного способа во всех случаях указать, является ли аргумент GetFieldв итоге получится "Empty".

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

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

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


Также обратите внимание, что значения полей initonly внутри mscorlib.dll являются глобальными инвариантами среды выполнения .NET.После их разрушения вы даже не сможете надежно проверить, был ли нарушен инвариант, потому что код для проверки текущего значения System.String.Empty также сломался, потому что вы нарушили его инварианты.Начните нарушать системные инварианты, и на них нельзя полагаться.

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

s == System.String.Empty

и

(s != null) && (s.Length == 0)

эквивалентны, но последний намного быстрее (условно говоря).

Также компилятор может определитьчто

if (int.Parse(s) > int.MaxValue)

никогда не имеет значения true и генерирует безусловный переход к блоку else (ему все равно приходится вызывать Int32.Parse, чтобы иметь такое же поведение исключения, но сравнение можно удалить).

System.String.Empty также широко используется в реализациях BCL.Если вы перезаписаете его, могут произойти все виды сумасшедших вещей, включая ущерб, который просачивается за пределы вашей программы (например, вы можете записать файл, имя которого построено с использованием манипуляций со строками ... при разрыве строки выможет перезаписать не тот файл)


И поведение может легко отличаться в разных версиях .NET.Обычно, когда обнаруживаются новые возможности оптимизации, они не переносятся обратно в предыдущие версии JIT-компилятора (и даже если бы они были, могут быть установки до того, как был создан обратный порт).Особенно.String.Empty связанные оптимизации заметно различаются между .NET 2.x и Mono и .NET 4.5 +.

42 голосов
/ 09 июня 2011

Код компилируется, потому что каждая строка кода является абсолютно допустимой в C #.Какую конкретную строку вы считаете синтаксической ошибкой?Там нет строки кода, которая присваивает поле только для чтения.Есть строка кода, которая вызывает метод в Reflection, который присваивает поле только для чтения, но это уже скомпилировано, и в конечном итоге, что нарушает безопасность, даже не было написано на C #, это было написано на C ++.Это часть самого движка.

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

Если вы попытаетесь запустить свой код в частично доверенной среде, то увидите, что Reflection выдает исключение «вам не разрешено делать это».

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

20 голосов
/ 09 июня 2011

Отражение позволяет вам игнорировать законы физики делать что угодно.Вы даже можете установить значение закрытых членов.

Отражение не следует правилам, вы можете прочитать об этом в MSDN .

Другой пример: Могу ли яизменить частное поле только для чтения в C # с помощью отражения?


Если вы находитесь в веб-приложении, вы можете установить уровень доверия вашего приложения.

level="[Full|High|Medium|Low|Minimal]"

Это ограничение уровня доверия, согласно MSDN , при среднем доверии вы ограничиваете доступ к отражению.

Редактировать: НЕ Запустите веб-приложение, отличное от Full Trust, это прямая рекомендация команды ASP.NET.Чтобы защитить ваше приложение, создайте один пул приложений для каждого веб-сайта.


Кроме того, не рекомендуется безумно использовать отражение для всего.Он имеет правильное место и время для использования.

5 голосов
/ 09 июня 2011

Точка, о которой никто не упоминал ранее: этот фрагмент кода вызывает различное поведение на разных реализациях / платформах .net. На самом деле на Mono он вообще ничего не возвращает: см. IDE One (Mono 2.8) , мой локальный Mono 2.6.7 (linux) выдает тот же "вывод".

Я еще не рассматривал код низкого уровня, но я полагаю, что он специфичен для компилятора, как упомянуто PRASHANT P или средой выполнения.

UPDATE

Запуск Mono-скомпилированного exe-файла в Windows (MS Dotnet 4) дает

evil 
equal

Запуск скомпилированного exe-файла Windows в Linux был невозможен (Dotnet 4 ...), поэтому я перекомпилировал с Dotnet 2 (он по-прежнему говорит, что evil и равно в Windows). Нет выхода. Конечно, должно быть по крайней мере "\n" от первого WriteLine, на самом деле он есть. Я передал вывод в файл и запустил свой гекседитор, чтобы посмотреть на один символ 0x0A.

Короче говоря: кажется, что среда выполнения специфическая.

0 голосов
/ 09 июня 2011

только для чтения применяется

на уровне КОМПИЛЕРА

, поэтому

может быть изменено при токе ниже уровня

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