Каковы последствия запроса API Reflection для перезаписывания System.String.Empty?

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

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 доступен только для чтения, поэтому его изменение должно быть невозможным, и компиляция должна заканчиваться на "Статическое поле readonly не может быть присвоено" или аналогичная ошибка.

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

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

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

Ответ 1

Статическое поле readonly нельзя присваивать

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

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

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

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

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


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

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

s == System.String.Empty

и

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

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

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

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

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

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


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

Ответ 2

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

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

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

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

Ответ 3

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

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

Другой пример: Могу ли я изменить приватное поле readonly на С#, используя отражение?


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

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

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

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


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

Ответ 4

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

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

UPDATE

Запуск скомпилированного exo файла в ОС Windows (MS Dotnet 4) создает

evil 
equal

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

Сократить длинную историю: это, по-видимому, среда выполнения.

Ответ 5

только для чтения используется только

на уровне COMPILER

поэтому

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