Уже третий день у всех на слуху слова Meltdown и Spectre, свеженькие уязвимости в процессорах. К сожалению, сходу найти что либо про то, как именно работают данные уязвимости (для начала я сосредоточился на Meldown, она попроще), у меня не удалось, пришлось изучать оригинальные публикации и статьи: оригинальная статья, блок Google Project Zero, статья аж из лета 2017. Несмотря на то, что на хабре уже есть перевод введения из оригинальной публикации, хочется поделиться тем, что мне удалось прочитать и понять.


UPD: добавил псевдокода в описание атаки


Как работает процессор


Последние десятилетия, начиная с 1992 года, когда появился первый Pentium, Intel развивала суперскалярную архитектуру своих процессоров. Суть в том, что компании очень хотелось сделать процессоры быстрее, сохраняя при этом обратную совместимость. В итоге современные процессоры — это очень сложная конструкция. Просто представьте себе: компилятор изо всех сил трудится и упаковывает инструкции так, чтобы они исполнялись в один поток, а процессор внутри себя дербанит код на отдельные инструкции, и начинает исполнять их параллельно, если это возможно, при этом ещё и переупорядочивает их. А всё из-за того, что аппаратных блоков для исполнения команд в процессоре много, каждая же инструкция обычно задействует только один их них. Подливает масла в огонь и то, что тактовая частота процессоров росла сильно быстрее, чем скорость работы оперативной памяти, что привело к появлению кешей 1, 2 и 3 уровней. Сходить в оперативную память стоит больше 100 процессорных тактов, сходить в кэш 1 уровня — уже единицы, исполнить какую нибудь простую арифметическую операцию типа сложения — пара тактов.


image
В итоге, пока одна инструкция ждёт получения данных из памяти, освобождения блока работы с floating point, ну или ещё чего нибудь, процессор спекулятивно отрабатывает следующие. Современные процессоры могут таким образом параллельно обрабатывать порядка сотни инструкций (97 в Sky Lake, если быть точным). Каждая такая инструкция работает со своими копиями регистров (это происходит в reservation station), и они, в момент исполнения, друг на друга не влияют. После того, как инструкция выполнена, процессор пытается выстроить результат их выполнения в линию в блоке retirement, как если бы всей этой магии суперскалярности не было (компилятор то про неё ничего не знает и думает, что там последовательное исполнение команд — помните об этом?). Если по какой-то причине процессор решит, что инструкция выполнена неправильно, например, потому, что использовала значение регистра, которое на самом деле изменила предыдущая инструкция, то текущая инструкция будет просто выкинута. То же самое происходит и при изменении значения в памяти, или если предсказатель переходов ошибся.


Кстати, тут должно стать понятно, как работает гипертрединг — добавляем второй Register allocation table, и второй блок Retirement register file — и вуаля, у нас уже как бы два ядра, практически бесплатно.


Память в Linux


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


Side-channel атаки


Когда не получается прочитать какие либо данные, можно попробовать воспользоваться побочными эффектами от работы объекта атаки. Классический пример: измеряя с высокой точностью потребление электричества можно различить операции, которые выполняет процессор, именно так был взломан чип для автосигнализаций KeeLoq. В случае Meltdown таким побочным каналом является время чтения данных. Если байт данных, содержится в кэше, то он будет прочитан намного быстрее, чем если он будет вычитываться из оперативной памяти и загружаться в кэш.


Соединяем эти знания вместе


Собственно, суть атаки то очень проста и достаточно красива:


  1. Сбрасываем кэш процессора.
    char userspace_array[256*4096];
    for (i = 0; i < 256*4096; i++) {
    _mm_clflush(&userspace_array[i]);
    }
  2. Читаем интересную нам переменную из адресного пространства ядра, это вызовет исключение, но оно обработается не сразу.
    const char* kernel_space_ptr = 0xBAADF00D;
    char tmp = *kernel_space_ptr;
  3. Спекулятивно делаем чтение из массива, который располагается в нашем, пользовательском адресном пространстве, на основе значения переменной из пункта 2.
    char not_used = userspace_array[tmp * 4096];
  4. Последовательно читаем массив и аккуратно замеряем время доступа. Все элементы, кроме одного, будут читаться медленно, а вот элемент, который соответствует значению по недоступному нам адресу — быстро, потому что он уже попал в кэш.
    for (i = 0; i < 256; i++) {
    if (is_in_cache(userspace_array[i*4096])) {
        // Got it! *kernel_space_ptr == i
    }
    }

Таким образом, объектом атаки является микроархитектура процессора, и саму атаку в софте не починить.


Код атаки


; rcx = kernel address
; rbx = probe array
retry:
mov al, byte [rcx]
shl rax, 0xc
jz retry
mov rbx, qword [rbx + rax]

Теперь по шагам, как это работает.
mov al, byte [rcx] — собственно чтение по интересующему атакующего адресу, заодно вызывает исключение. Важный момент заключается в том, что исключение обрабатывается не в момент чтения, а несколько позже.
shl rax, 0xc — зачение умножается на 4096 для того, чтобы избежать сложностей с механизмом загрузки данных в кэш
mov rbx, qword [rbx + rax] — "запоминание" прочитанного значения, этой строкой прогревается кэш
retry и jz retry нужны из-за того, что обращение к началу массива даёт слишком много шума и, таким образом, извлечение нулевых байтов достаточно проблематично. Честно говоря, я не особо понял, зачем так делать — я бы просто к rax прибавил единичку сразу после чтения, да и всё. Важный момент заключается в том, что этот цикл, на самом деле, не бесконечный. Уже первое чтение вызывает исключение


Как пофиксили в Linux


Достаточно прямолинейно — стали выключать отображение страниц памяти ядра в адресное пространство процесса, патч называется Kernel page-table isolation. В результате на каждый вызов сискола переключение контекста стало дороже, отсюда и падение производительности до 1.5 раз.

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


  1. interprise
    05.01.2018 02:18

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


    1. ToSHiC Автор
      05.01.2018 02:25

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


      1. chabapok
        05.01.2018 04:22

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

        Сначала много раз тренируют бренч предиктор, чтобы был заход внутрь if, потом внезапно подают туда адрес, из которого нужно красть значение, и при этом такой, чтобы мы не зашли внутрь if.
        Внтури if — чтение памяти.
        Бренч предиктор говорит: «скорей всего мы зайдем в if». Это предположение неверно, но пока это неизвестно — и поэтому cpu начинает исполнять то, что внутри if.
        В результате выполнения кода, значение из адреса, с которого нельзя читать, попадает к кэш. Вообще, это — исключение. Но потом оказывается, что бренч предиктор ошибся, а раз ошибся, то и исключения нет, и мы как бы и не читали этот адрес.
        Исключение отменяется — но остается побочный эффект: данные в кэше.

        Фактически, уже это — некорректная работа. Я так понимаю, что если мы прочитаем этот адрес, то получим какое-то значение, и это значение будет не из нашего процесса, что некорректно.

        Чтобы узнать, где подтянутое в кэш число, используется атака по времени. Читается все подряд — и замеряется время чтения. То, что было подтянуто в кэш, прочитается гораздо быстрей.

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

        Мой AMD phenom x4 B40 уязвим на тесте. Но — тест читает свою же память. То есть, чистоты эксперимента — нет. Кернел спейс читать не пробовал(не знаю его адреса, надо разбираться как узнать, и как потом понять, что прочитал именно его). Может, завтра попробую вечером.

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


        1. ToSHiC Автор
          05.01.2018 10:48

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


          1. chabapok
            05.01.2018 11:41
            +3

            Я испугался этого слова — гаджеты… И не читал ту главу.

            Там дальше глава 5, про косвенные переходы. Когда переход делается не по фиксированному адресу, а по адресу, который в регистре, или по адресу который лежит в какой-то ячейке памяти. Можно сделать финт ушами — и «спекулятивно» выполнить не тот код, который должен был бы выполниться. Насколько понимаю — переход по адресу, которого нет в icache занимает ВРЕМЯ и все это время выполняется то, что в этом кэше лежит, только потом это все отменяется, поэтому нам незаметно.
            Но этот код, хоть и отменяется, но что-то подягивает в кэш. И дальше как обычно.

            Суть одна — тем или иным способом заставляют cpu выполнить код пользователя спекулятивно, причем так, что потом он гарантирванно отменится. А в таком выполнении можно (было до того, как нашли уязвимость) безнаказанно «побегать в труселях по мечети»: все равно никто не узнает.


        1. interprise
          05.01.2018 10:52
          +1

          понял на вашем уровне, но не понятно как происходит чтение не своей памяти.


          1. chabapok
            05.01.2018 12:15
            +3

            НЯМС:

            там два чтения — одно запрещенное — а другое разрешенное:

            temp = array2[array1[x]*512];

            х — адрес внутри запрещенной области array1, array2 — разрешенная область.

            Спеклятивно это выполняется. Значит, какая-то область array2 подтягивается в кэш.
            Дальше мы читаем из array2 подряд, измеряя время чтения. И по времени чтения пытаемся понять, была ли подтянута в кэш соответствующая область.

            почему 512 — мне не понятно. Размер кэш-лини — 64 байта, а не 512.


            1. interprise
              05.01.2018 12:23
              +1

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


              1. MacIn
                05.01.2018 12:34

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


                1. interprise
                  05.01.2018 12:40

                  еще раз spectre не дает читать память ядра, это делает Meltdown только на intel.


                  1. chabapok
                    05.01.2018 14:16

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

                    Насколько понимаю, предполагается, что ядро лежит по адресу array1. Поэтому, array1[x] — чтение из области ядра.

                    Потом подтягиваются в кэш соответствущая область памяти из array2. Какая именно область подтянется — зависит от значения, которое лежит по array1[x].


                    1. interprise
                      05.01.2018 15:18

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


                      1. MacIn
                        05.01.2018 18:06
                        +1

                        А в чем бы тогда была его польза? С разрешенных страниц и так читать можно.


                        1. interprise
                          05.01.2018 18:09
                          +1

                          тогда непонятно чем 1 баг от другого отличается, только использованием предсказаний переходов?


                          1. tyomitch
                            05.01.2018 18:35

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

                            (Это всё же не баг, а намеренная часть архитектуры.)


                            1. interprise
                              05.01.2018 18:38
                              +2

                              я просто думал, различий больше


                          1. MacIn
                            05.01.2018 18:57

                            del.


                  1. andy_shev
                    06.01.2018 17:51

                    Некоторые имплантации aarch64 также подвержены.


            1. Swiftarrow7
              08.01.2018 18:31

              Если рассуждать так, что 8 бит = 1 байт, то при «разархивировании» получаем => 512 / 8 = 64 байта, и если так идти до конца то получается такая зависимость, приводящая к 1 байт / 8 = 1 бит.
              P.s. зависимость притянута за уши, но уж очень хотелось написать. Одна из мыслей, так возможно проще с памятью работать.


        1. Sap_ru
          05.01.2018 13:56

          Но это требует привилегий для управления кэшом (чтобы вытащить прочитанные данные). А имея такие привилегии и без того можно делов натворить (как минимум DoS). Поэтому уязвимость менее критична — в правильных окружениях она не выполнима.
          Можно попытаться косвенно определить значение данных (через спекулятивное исполнение), но это получается долго и нужно, чтобы процесс не прерывался планировщиком.


          1. MacIn
            05.01.2018 14:18

            чтобы вытащить прочитанные данные

            Зачем?


          1. LeonidY
            06.01.2018 00:24
            +1

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


      1. Gular
        05.01.2018 11:00

        Не поможет?
        Спасибо за статью.


        1. interprise
          05.01.2018 11:16

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


      1. DistortNeo
        05.01.2018 17:46
        +1

        Вот и мне непонятно принципиальное отличие Spectre от Meltdown. И там, и там читается значение по произвольному виртуальному адресу — принципиальной разницы между атаками нет. Вот только почему изоляция страниц спасает от Meltdown, а от Spectre — нет?


        1. ToSHiC Автор
          05.01.2018 18:02
          +1

          Meltdown — это чтение защищённых страниц памяти, гонка между отработкой исключения и чтением памяти.
          Spectre — отравление или обман предсказателя переходов. Чтение происходит из своего адресного пространства.

          Side channel при этом одинаковый — кеш данных.


          1. MacIn
            05.01.2018 18:05
            +2

            Чтение происходит из своего адресного пространства.

            Так… и ядро тоже в «своем» адресном пространстве, в этом суть проблемы же.


          1. interprise
            05.01.2018 18:09
            +3

            а зачем читать из своего адресного пространства?


            1. dimarick
              06.01.2018 15:47

              Чтобы вылести из js-песочницы в браузере и подампить память всего процесса. А там данные соседних вкладок, недавно введенные пароли, куки и т.п.

              Насколько я понял, принцип не отличается от meltdown, только дампить можно то, что и так доступно процессу с т.з. ОС.

              Мозилла уже залепила у себя www.mozilla.org/en-US/security/advisories/mfsa2018-01

              Хром вроде тоже обещает аналогично сделать (или уже сделал)


          1. DistortNeo
            05.01.2018 18:49

            Чтение происходит из своего адресного пространства.

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


            И того, что я понял, в случае Spectre в документах идёт отсылка к Berkeley Packet Filter (BPF) — это виртуальная машина, позволяющая в режиме ядра выполнять пользовательский код. Meltdown здесь неприменим, т.к. исключение бросить просто невозможно. А вот выйти за границы массива с помощью Spectre вполне можно.


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


            1. MacIn
              05.01.2018 19:26

              Эм, нет — документация по Spectre — раздел 5.2 — собственно, Example Implementation on Windows


          1. MacIn
            05.01.2018 19:32

            Spectre — отравление или обман предсказателя переходов. Чтение происходит из своего адресного пространства.

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


        1. MacIn
          05.01.2018 19:34
          +1

          Вот только почему изоляция страниц спасает от Meltdown, а от Spectre — нет?

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

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


          1. interprise
            05.01.2018 19:45
            +1

            про spectre очень интересно, ни как не могу понять как это происходит.


            1. MacIn
              05.01.2018 19:59
              +2

              Прочтите оригинал spectreattack.com/spectre.pdf, потому что можно долго Рабиновичем перепевать, все одно для деталей придется читать статью. Да и сам я мог неверно понять.

              Кратко (если брать пример под NT из раздела 5.2) — берется код, который зашарен между процессами — dll, ntdll в случае примера. В своем, атакующего процесса, адресном пространстве кусок кода с ветвлением видоизменияется так, чтобы натренировать предсказатель переходов определенным образом. (в атакуемом процессе этот же кусок регулярно атакуемым процессом используется, т.к. это обычный библиотечный код). Предсказатель переходов сохраняет свои предсказания на уровне ЦП, а не процесса, поэтому в том, атакуемом процессе будет происходить то же. Плюс определенным образом сформированные входные параметры — в итоге происходит branch misprediction + чтение данных из пространства атакуемого процесса из-за спекулятивного выполнения, потому что мы натаскали бранч предиктор на заход «вовнутрь». Они потом будут сброшены, потому что переход не произойдет, но в кеше останутся. А из кеша эти данные извелкаются аналогично тому способу, что использован в Meltdown.


              1. Danil1404
                05.01.2018 20:09
                +2

                Я не понимаю, как мы в другом процессе можем изменить входные параметры.
                В этой pdf написано, что мы контролируем используемые регистры в найденном авторами dll кодом — это вообще как?
                Да и откуда нам знать, что этот другой процесс вообще исполнит избранный код? В моем представлении для этого нужно изначально воспользоваться какими-нибудь уязвимостями для удаленного исполнения кода, а в этом случае нам spectre уже и не нужен вовсе.


                1. MacIn
                  05.01.2018 20:12
                  +1

                  Да и откуда нам знать, что этот другой процесс вообще исполнит избранный код?

                  Ну, например, запустить эту программу на своей домашней машинке и посмотреть, что там исполняется, а что нет. Учитывая, что они с ntdll игрались, шансов, что код исполнится — много.
                  Они же использовали Sleep, тут вообще почти что с гарантией.


              1. interprise
                05.01.2018 20:16

                спасибо, стало понятней. Но для решение достаточно не шарить ntdll, а грузить каждому процессу свой экземпляр или нет? Да это небольшой перерасход оперативной памяти, но это мелочи.


                1. interprise
                  05.01.2018 20:23
                  +1

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


                1. MacIn
                  05.01.2018 20:24

                  Я бы не сказал, что это небольшой перерасход. К тому же, просто скопировать маппинг виртуальной памяти при загрузке процесса намного дешевле, чем читать и парсить библиотеку каждый раз. Эта часть — mapped file sections — есть в NT с первых версий, когда она еще не была Windows NT, на нее, кмк, много завязано.


              1. DistortNeo
                05.01.2018 20:24
                +1

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

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


                Да, за счёт шаринга DLL, мы можем обмануть branch prediction так, чтобы при определённом системном вызове спекулятивно выполнился кусок кода из адресного пространства процесса-жертвы.


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


                1. MacIn
                  05.01.2018 20:28
                  +1

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


          1. LeonidY
            06.01.2018 00:28

            «Чужим процессом» для Spectre может быть само ядро, а функция-читалка — eBFP. Но может быть и другая последовательность в ядре.


    1. ToSHiC Автор
      05.01.2018 23:58
      +3

      olartamonov на гигтаймс разместил статью про spectre, вот ключевая часть второго типа атаки, наиболее интересная:

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

      Через некоторое время блок предсказания переходов абсолютно уверен, что все переходы такого вида ведут на адрес 123456, поэтому, когда атакуемая программа — с нашей подачи или по своей инициативе — доходит до аналогичного перехода, процессор радостно начинает спекулятивное исполнение инструкций с адреса 123456. Уже в адресном пространстве атакуемой программы.

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


      Под «нужной инструкцией» по адресу 123456 имеется в виду тот самый «гаджет» (кусок кода атакуемой программы, или из какой либо библиотеки, которую она использует), который оставит такие следы в кэше, которые легко отследить из атакующего процесса.


      1. DistortNeo
        06.01.2018 00:23

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


        1. tyomitch
          06.01.2018 01:09

          Насколько я понимаю, речь идёт о косвенном переходе, т.е. «модификация адреса перехода» — это не изменение инструкции в памяти, а выполнение её с изменённым значением операнда.


          1. DistortNeo
            06.01.2018 01:18

            Точно, это я напутал: физически местоположение инструкции одинаково, различие только в адресе операнда, который ещё надо прочитать.


        1. MacIn
          06.01.2018 02:05

          А что удивительного? Предсказатель переходов же не процессо-специфичен. Он процессоро-специфичен. И работает с виртуальными адресами.


  1. crea7or
    05.01.2018 02:28

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


    1. ToSHiC Автор
      05.01.2018 02:32

      Нет, данных в кэше не будет. В кэше будет 1 строчка, которая соответствует байту по недоступному адресу. То есть, если по адресу 0xdeadbeef было записано 42, и мы его хотим узнать — то в результате этой атаки в кэше будет строка, которая соответствует индексу 42*4096 в массиве.


      1. crea7or
        05.01.2018 02:43

        Ага, значит ещё сложнее. Значит самих данных не будет, только косвенно будет видно, какое там число — по индексу. Процессор в зависимости от числа в запрещённой для доступа памяти прочитает строку из нашего массива в кэш и уже по строке(которая оказалась в кэше) мы узнаём число. Хитро однако.


        1. FadeToBlack
          05.01.2018 08:08

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


          1. Процессор может выполнять команды не последовательно, а иногда запускает в параллель исполнение команд, которые в программе расположены позже. Это делается для того, чтобы полностью загрузить свободные вычислительные мощности (конвеер), пока мы ожидаем завершения выполнения других команд (длительное чтение из памяти, например).
          2. Допустимость или недопустимость чтения из памяти работает на аппаратном уровне, и при взятии информации из недопустимого адреса возникает исключение. Важным моментом является тот факт, что команды чтения по запрещенному адресу могут исполняться спекулятивно, с выполнением реального чтения, но проверка на допустимость команды будет произведена позже и, в случае недопустимости этой операции, результат выполнения этой команды будет отброшен и произойдет исключение. В этом месте как раз и получается аппаратный баг — проверка выполняется позже, что скорее всего обусловлено архитектурными особенностями (мы должны помнить, что это не программа, где мы можем за секунду переставить строчку условия, это реальные транзисторы, которые придется перемещать на кристалле, выбирать для них оптимальное расположение и так далее).
          3. Чтение из памяти устроено по-особенному: когда мы читаем один байт, вместе с ним загружается в кэш много больше данных, чем требуется, на случай, если нам нужно будет прочитать следующий байт в памяти по порядку, как в большинстве случаев и происходит. Но если после этого мы начинаем читать по адресу, который не попадает в диапазон адресов, загруженных в кэш, происходит так называемый кэш-промах, и данные в кэш приходится считывать заново, а это сильно дольше, чем если бы данные уже были в кэше.

          На основе вышеизложенного, мы можем реализовать следующую схему:


          1. Делаем массив в нашей пользовательской доступной памяти размером char userspace[256 * 4096]. 256 — количество значений которые может принимать байт, 4096 — такое расстояние, на котором гарантировано происходит кэш-промах при последовательном чтении с таким шагом в памяти.
          2. Для i = 0..255
          3. Составляем несколько инструкций так, чтобы сначала была инструкция с чтением из памяти по запрещенному адресу restrictedspace[address], а потом, на основе этих данных, выборка из нашего пользовательского массива на основе значения считанного байта: userspace[restrictedspace[address] * 4096]
          4. Так как нельзя считывать по запрещенному адресу, происходит исключение, но к этому моменту код, который идет далее уже успевает спекулятивно выполниться, и нужная часть нашего пользовательского массива будет загружена в кэш.
          5. Ловим исключение и измеряем время доступа к элементу address[i * 4096], записывая в массив time[i]
          6. Повторяем для всех i (goto 2)
          7. Ищем значение в массиве time, которое сильно отличается в меньшую сторону.
          8. Полученный индекс и будет искомым значением из запрещенной памяти


          1. FadeToBlack
            05.01.2018 08:20

            В первый пункт забыл добавить про спекулятивное выполнение.


            1. fogx
              05.01.2018 12:17
              +5

              В саму ветку, которая читает restrictedspace[address] мы тоже попадаем спекулятивно, поэтому никакого исключения генериться не будет.

              В начале кода честно стоит проверка if (address < OUT_OF_BOUNDS), но OUT_OF_BOUNDS задано не константой, а в отдельной области памяти, которая предварительно специально вымывается из кеша.

              Это не позволяет процессору мгновенно определить правильную ветку перехода, и в результате он спекулятивно выполняет код, который программа якобы и «не собиралась» исполнять.


              1. FadeToBlack
                05.01.2018 12:35
                +1

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


          1. pavel_pimenov
            05.01.2018 10:05

            Поясните про перемещение транзисторов на кристалле.
            от их позиции разве зависит логика работы CPU?


            1. FadeToBlack
              05.01.2018 11:44
              +3

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


              1. rkfg
                05.01.2018 12:59
                +2

                Вот я тоже подумал про «сбрасывать кэш при отмене», но не получится ли при этом тогда действовать от противного? Прочитать весь наш массив в кэш, и потом замерять, какая часть была сброшена? 256*4096 = 1 Мб, влезет куда угодно, ну в L3 точно.


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


                1. FadeToBlack
                  05.01.2018 13:05

                  Прочитать весь наш массив в кэш, и потом замерять, какая часть была сброшена?

                  Можете подробнее объяснить? Как мы сможем различить эти случаи?


                  1. rkfg
                    05.01.2018 13:15

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


                1. tyomitch
                  05.01.2018 13:07

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


                  1. chabapok
                    05.01.2018 14:54

                    Процентов на 30 просядет производительность.
                    Меня бы больше устроило, если бы команды rdtsc, rdtscp перевели в разряд привелегированных, или совсем отключили, или повысили гранулярность. Для большинства пользователей это будет ок. Нет часов с хорошей гранулярностью — нет проблемы. Как быстры фикс это — ок.
                    И вообще, пускай юзают hpet.


                    1. tyomitch
                      05.01.2018 15:18

                      Таймер для эксплойта необязателен: один поток пусть в бесконечном цикле инкрементирует счётчик, другой поток пусть сравнивает значения счётчика до обращения к памяти и после.


                      1. chabapok
                        05.01.2018 22:13

                        Как из одного потока в другой передать этот счетчик?
                        И вообще, что такое «счетчик» и что такое «поток» если мы говорим о микроархитектуре?

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

                        Если под понятием «поток» вы подразумевали нечто, что выполняется вторым ядром — то как передавать этот счетчик из второго ядра в первое ядро, еще и ненарушив кэш? Для таких вещей есть барьеры памяти, но они влияют на кэш. По сути передача данных между ядрами — через L3 (хотя это от архитектуры может зависеть) Думаю, там не померяешь так просто такой короткий интервал времени…


                        1. DistortNeo
                          05.01.2018 22:27
                          +1

                          Как из одного потока в другой передать этот счетчик?

                          У потоков общая память.


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

                          А зачем потоки переключать? Они одновременно же работают на разных ядрах.


                          Если под понятием «поток» вы подразумевали нечто, что выполняется вторым ядром — то как передавать этот счетчик из второго ядра в первое ядро, еще и ненарушив кэш? Для таких вещей есть барьеры памяти, но они влияют на кэш. По сути передача данных между ядрами — через L3 (хотя это от архитектуры может зависеть) Думаю, там не померяешь так просто такой короткий интервал времени…

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


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


                          1. boblenin
                            05.01.2018 23:40

                            А разве после первого обхода остальные ячейки не попадут в кэш?


                            1. DistortNeo
                              05.01.2018 23:45

                              Ну так у нас в арсенале имеется команда очистки кэша.


                              1. boblenin
                                06.01.2018 22:46
                                +1

                                Очищать будем выборочно или все подряд? Если выборочно, то по какому критерию, если все подряд — то как нам помогут множественные итерации?


                          1. chabapok
                            06.01.2018 12:34

                            У потоков общая память.

                            В том то и дело, что несовсем: мы же на микроархитектурном уровне. Несколько ядер на одну память? Как вы себе это представляете? L0, L1 у каждого ядра — свой. L2 — иногда общий, и то не всегда (Xeon Clovertown E5345). Можете lstopo попробовать (под линуксом). Можно в гугл вбить — и включить картинки, и посмотреть, как вообще бывает. Там иногда такое, что даже не представляю что это.

                            А теперь представьте себе: из L0 одного ядра нужно число переправить в L0 другого ядра. Это сотни тактов, и этот эффект больше, чем то, что мы хотим измерять. И это надо сделать так, чтобы не повлиять на кэш, потому что момент, в который мы это все меряем — очень «нежный». Любая подтяжка в кэш сама по себе уже повлияет на результат.
                            И это еще не все. Команды исполняются на конвеере. Там еще есть всякие буферы валидации и прочие оптимизации. То, что придет в соседний проц, если не использовать барьеры памяти, может весьма слабо отражать значение реального счетчика.

                            Во-первых, один атомарный инкремент

                            Атомарные операции выполняются с барьерами памяти. Ну ок, можно мои рассуждения выше про конвеер убрать. Но барьеры не отменят остального.

                            N раз провести атаку

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


                            1. DistortNeo
                              06.01.2018 15:33

                              Атомарные операции выполняются с барьерами памяти

                              В архитектуре x86, насколько я помню, нет необходимости в явном использовании барьеров. И мои эксперименты с атомарными инкрементами показывают, что пересылка занимает явно меньше сотни тактов.


                              В том коде они это делают 999 раз

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


                              1. chabapok
                                06.01.2018 19:16

                                Смотря для чего и что надо.
                                У атомарных счетчиков — барьеры «под капотом».

                                Где-то у Шипилева была статья про исследование поведения при помощи jcstress, и там было наглядно показано, что можно нарваться на баги. Эти баги неособо частые, но они есть. И я тогда крутил эти тесты на своем амд — эти баги были.

                                Но тут с вами я согласен: поскольку мы говорим про взлом, то некоторое кол-во некорректных результатов нас устроит. Если 1 раз из 1000 происходит глюк — это не ок для «обычного» софта, для банковского — вообще смерть, а для задач взлома систем — нормально, даже если 1 раз из 1000 все сработает как хочется :).

                                Так что, пожалуй, что да. Загрублять rdtsc — не способ.


                            1. LeonidY
                              06.01.2018 22:13

                              > А теперь представьте себе: из L0 одного ядра нужно число переправить в L0 другого ядра. Это сотни тактов, и этот эффект больше, чем то, что мы хотим измерять

                              Это не совсем верное рассуждение:

                              1. Это по любому не сотни циклов.
                              2. Применяют хитрости для сокращения времени.
                              3. Загрузку всегда делают в pipeline к операции. Тут конечно большая наука намешана, чтобы соблюсти когерентность данных.

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


                              1. chabapok
                                06.01.2018 23:51

                                Нет доказательств. И я не соображу, как это поменять хотя бы примерно. Строго говоря, я тоже не привел доказательств.
                                Сотни тактов — это я слышал, что из памяти. А если из соседнего L0 — то может и быстрей (там, где L3 общий)


                  1. JTG
                    05.01.2018 15:19
                    +2

                    Так ведь промахи кеша случаются нечасто в том числе благодаря тому, что даже когда предсказатель ветвлений условно на каждом 25-ом* повороте выбирает не ту ветку, кеш остаётся нетронутым. Мне кажется вайп в таких случаях сведёт на нет все преимущества кеша.

                    * perf stat -e L1-dcache-loads,L1-dcache-load-misses,branches,branch-misses /opt/phpstorm-171/bin/phpstorm.sh

                    Performance counter stats for '/opt/phpstorm-171/bin/phpstorm.sh':

                    73.192.922.309 L1-dcache-loads
                    6.005.629.782 L1-dcache-load-misses # 8,21% of all L1-dcache hits
                    44.366.574.238 branches
                    1.757.550.166 branch-misses # 3,96% of all branches

                    41,088747127 seconds time elapsed


                  1. khim
                    05.01.2018 16:46
                    +1

                    При этом ущерб производительности не колоссально большой, потому что невалидные чтения в действительности выполняются нечасто.
                    Вот же ж, блин, теоретики. Скорость доступа в кэш и в оперативку отличается на два порядка (то есть в сто раз). То есть даже если даже предсказатель будет «промахиваться» в одном случае из сотни (а этого, поверьте, не так-то просто достичь) мы получим проседание производительности на 30-50%.


                    1. tyomitch
                      05.01.2018 17:34

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


                      1. JTG
                        05.01.2018 18:03

                        Каким образом определять «невалидность»? Пинать MMU при каждом обращении к кешу?


                        1. tyomitch
                          05.01.2018 18:40

                          Так же, как она определяется сейчас.


                          1. masterspline
                            06.01.2018 04:03

                            По сути, ты предлагаешь каждый раз, когда встретится код вида

                            if(ptr != nullptr) read(ptr);

                            сбрасывать весь кеш при неверном предсказании перехода. Это расточительно. Наверняка разработчики процессоров найдут способ дешевле (а решать эту аппаратную проблему все равно придется разработчикам CPU, текущие патчи на ядро — это лишь временная заплатка).


                  1. bmth
                    05.01.2018 17:18

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


                    1. ToSHiC Автор
                      05.01.2018 17:21
                      +1

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


                      1. khim
                        05.01.2018 17:34
                        +1

                        Но и это, наверняка, не панацея.
                        Не панацея — можно заметить как что-то пропало из кеша. Можно для «первого использования» завести отдельный кеш, но тогда можно будет играться с «двойным первым использованием» и т.д. и т.п.

                        Кажется, что самое разумное решение — одновременно и самое тупое. Современные системы всё равно делят адресное пространство пополам и по старшему биту адреса можно понять — это данные ядра или нет. Ну так давайте сделаем это деление оффициальным и всё.

                        И не нужно никаких спекуляций, проверка одного бита — это несколько транзисторов, её куда угодно можно засунуть.


                        1. FadeToBlack
                          05.01.2018 17:58
                          +1

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


                        1. ToSHiC Автор
                          05.01.2018 18:06

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


                          1. khim
                            05.01.2018 18:40
                            +1

                            Это, собственно, не я предлагаю. Это в статье про Meltdown есть.

                            Просто все попытки обращения к адресам, у которых старший битик установлен отвергать «с порога» если мы не в режиме супервизора. И всё.

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


                            1. MacIn
                              05.01.2018 20:29

                              Это напоминает защиту памяти в старой БЭСМ-6 — один из битов 48 битного слова в памяти показывал, это слово данных или команда. DEP 60х.


                        1. bmth
                          05.01.2018 22:41

                          А скажите, в 64-битных системах в адресном пространстве тоже только один процесс и ядро присутствует? Или там может быть несколько процессов?


                          1. khim
                            05.01.2018 23:19
                            +1

                            Это как раз в 32-битах можно, теоретически, несколько процессов сегментами развести. В 64-битах ничего этого нет.


                          1. qw1
                            06.01.2018 10:55

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

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

                            Теперь придётся все эти «удобства» отключать )))


                      1. bmth
                        05.01.2018 22:37

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


                        1. khim
                          05.01.2018 23:23
                          +1

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


                      1. masterspline
                        06.01.2018 04:17

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

                        Мне тут подумалось, для memory mapped io бывает ли такое, чтобы при чтении из ячейки памяти происходили побочные эффекты (типа считали содержимое ячейки — получили значение счетчика и он сбросился в ноль). Ведь спекулятивная запись невозможна (при генерации исключения, процессор отбросит созданные данные), а вот спекулятивное чтение из адресного пространства ядра вполне можно сделать (возможно, даже и того, что занимается вводом/выводом на устройства).


                        1. khim
                          06.01.2018 04:41

                          Мне тут подумалось, для memory mapped io бывает ли такое, чтобы при чтении из ячейки памяти происходили побочные эффекты (типа считали содержимое ячейки — получили значение счетчика и он сбросился в ноль).
                          Бывает. В лёгкую. И это настолько плохо, что уже во времена Pentium Pro появились MTRR, отключающие спекулятивные чтения для определённых диапазонов памяти.

                          Так что для атаки это использовать нельзя.


            1. FadeToBlack
              05.01.2018 11:51
              +2

              другими словами, от их позиции не зависит логика работы CPU, но вот изменение логики работы может потребовать их перемещения и перекомпоновку блоков. Чипы все еще 2d, так что это может потребовать серьезного изменения архитектуры или может сказаться на производительности


          1. sergey-b
            05.01.2018 12:37

            Зачем goto 2? В самом начале сбрасываем кэш специальной командой. Массив userspace[256x4096] в кэше отсутствует. После обработки исключения один из блоков длиной 4096 в этом массиве должен быть загружен в кэш. Измеряем время доступа к элементам с номерами ix4096 для i от 0 до 255. Определяем, при каком i время доступа было минимальным. Найденное i будет равно значению из защищенной области памяти. Массив time для этого не нужен, достаточно помнить минимальное время и значение индекса, в котором оно было получено.


            1. FadeToBlack
              05.01.2018 13:08

              Массив time для этого не нужен

              Естественно, просто так проще объясняется алгоритм.


              Измеряем время доступа к элементам с номерами ix4096 для i от 0 до 255

              А разве кэш-промах и вытаскивание новых данных не испортят нам состояние кэша?


              1. sergey-b
                05.01.2018 17:40

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


                1. FadeToBlack
                  05.01.2018 17:53

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


          1. nckma
            06.01.2018 11:17
            +3

            Честно говоря не очень понятно
            1) как атакующий может знать какой именно адрес памяти жертвы его интересует — большинство программ используют стековую архитектуру и многие переменные находятся на стеке и вершина стека перемещается с течением времени. Многие объекты так же имеют ограниченный срок жизни между new и delete и адреса создаваемых и удаляемых объектов зачастую почти случайны особенно в многопоточном приложении жертвы. Даже дизассемблирование прогреммы жертвы до атаки не дает реального знания об интересующих адресах в процессе жертвы.
            2) атака подразумевает длительное исследование одной ячейки памяти и за это время ячейка просто может поменять свое значение в многопроцессорной системе.
            То есть метод абсолютно не подходит, чтобы сделать снимок памяти процесса жертвы.

            Иными словами, если с помощью атаки «прочитал» байт из памяти жертвы, то насколько атакующий может быть уверен, что это именно тот байт, что его интересует?

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


            1. khim
              06.01.2018 16:22

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

              И главное, что атака — ничего не ломает! Не получилось сейчас, попробуем через час… У многих браузеры сутками не закрываются!


              1. pavel_pimenov
                06.01.2018 16:36

                Пароли в нормальных менеджерах лежат в базе данных в
                зашифрованном виде и в память они попадают временно в момент когда их запрашивают (GUI/API)
                Вероятность вытащить такой атакой пароль от важного ресурса призрачно мала.
                такому удачливому хакеру нужно срочно бежать покупать билет «русском лото» — джекпот гарантирован )


                1. interprise
                  06.01.2018 16:42
                  +2

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


                  1. pavel_pimenov
                    06.01.2018 18:28

                    Предлагаю провести эксперимент:
                    1. Откройте свой менеджер паролей.
                    2. Снимите дамп памяти процесса.
                    3. Найдите в дампе свои пароли.
                    4. Назовите этот менеджер и его версию.


                    1. khim
                      06.01.2018 18:49

                      3. Найдите в дампе свои пароли.
                      Кто вам сказал, что они там «открытым текстом» будут храниться? Там где-то будет мастер-пароль и зашифрованная база со всеми остальными.

                      Но если менеджер паролей может их расшифровать (а он может, раз не спрашивает мастер-пароль повтороно), то и мы сможем… если достаточно долго посидим в отладчике предварительно…


                1. TheShock
                  06.01.2018 17:07

                  в память они попадают временно в момент когда их запрашивают (GUI/API)

                  А что мешает их каждый раз во время атаки запрашивать?


                1. khim
                  06.01.2018 17:12
                  -1

                  Пароли в нормальных менеджерах лежат в базе данных в
                  зашифрованном виде и в память они попадают временно в момент когда их запрашивают (GUI/API)
                  Если у вас менеджер спрашивает мастер-пароль каждый раз при необходимости показать какой-либо другой пароль — то да. Но большинство пользователей такого уровня параноидальности не выдерживают.

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


            1. DistortNeo
              06.01.2018 17:04

              1) Брутфорс никто не отменял. В случае Meltdown мы можем просто сдампить большой кусок памяти, а затем искать в ней какие-нибудь интересные строчки.

              2) Что-то поменяется, что-то нет. Здесь и не нужна 100% удача.

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


            1. FadeToBlack
              07.01.2018 00:06
              +1

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


          1. Dobby007
            07.01.2018 00:13

            А мне может кажется или ваш цикл на шаге 2 нужно поставить после 4-го пункта? Ведь смысл от цикла — измерить время доступа к данным массива, чтобы получить значение байта по "невалидному адресу", а измеряем время доступа мы только после того, как поймаем исключение. На шаге 2 нужен другой цикл, ИМХО. Цикл, который будет читать последовательно байты из памяти ядра, чтобы, собственно, и получить интересующий нас пароль к почте Клинтон.
            P.S. А так за объяснение плюс. Стало намного понятнее все.


            1. FadeToBlack
              07.01.2018 00:26

              В пункте 5 у меня опечатка:


              Ловим исключение и измеряем время доступа к элементу address userspace[i * 4096], записывая в массив time[i]

              Цикл стоит в правильном месте, поскольку сложно гарантировать, что за 256 итераций наша искомая кэш-линия не вытеснится из кэша. Например, ОС решит выделить квант времени другому процессу, который испортит весь кэш. В данном случае, приведен медленный, но более стабильный вариант.


  1. crea7or
    05.01.2018 02:46

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


    1. iUser
      05.01.2018 05:30

      Не все. У микроядерных — честный IPC.


      1. a1888877
        05.01.2018 12:19
        +2

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


        1. MacIn
          05.01.2018 12:32
          +2

          происходит переход на адрес который должен находится в памяти.

          Да, но это может быть микроскопический трамплин.


          1. a1888877
            05.01.2018 23:37

            Да, патч для Linux — KAISER так и делает. Просто необходимость проецирования части памяти ядра определяется архитектурой не ОС, а процессора. Защищенный режим у intel предполагал невозможность чтения памяти ядра прикладным ПО и «трамплины» не делали. Это лишний, сложный и медленный код, дублирующий функции железа. А теперь выяснилось, что из-за излишних оптимизаций этот код необходим.


        1. vire
          06.01.2018 23:45

          Сисколлы любой процесс может вызывать, к памяти и карте ее распределения это не имеет никакого отношения. Проецируют память только по одной причине — чтобы часть кода ядра попала в кэш и была там по возможности как можно дольше, а лучше — всегда, но все это на усмотрение процессора. В любом случае ОС проводит значительное время(1-10%) в режиме ядра и выполняет шаблонные действия, и вот выполнение этой части можно ускорить — держать горячие части кеше.
          Меньше возни с таблицами страниц — это побочный эффект, главное — ядерный код работает быстрее.


          1. MacIn
            07.01.2018 04:52

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

            При перегрузке CR3 сбрасывается TLB.


            1. vire
              07.01.2018 14:37

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


              1. MacIn
                07.01.2018 15:06

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


                1. vire
                  07.01.2018 16:00

                  Юзерспейс код работает в 95% времени, остальные 5% — ядро(цифры от балды, но суть — у юзерспейса огромный перевес). У ядра просто нет шансов попасть в кеш(мы не tlb, а обычный L1-L2-L3), а очень хочется, вот настойчивое «дописывание» и помогает процу понять, что эти страницы желательно держать в кеше.


                  1. khim
                    07.01.2018 18:33
                    +1

                    Ужас какой. Вы тут развели целую теорию вместо того, что вам уже сказали: при перегрузке CR3 сбрасывается TLB. А наличие одного и того же фиксированного маппинга во всех процессах позволяет CR3 при переходе в ядро и возврате не сбрасывать. Вот и всё.

                    А что с этого можно ещё каких-нибудь «плюшек» получить — это мелочи…


                    1. vire
                      07.01.2018 22:31

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


                      1. MacIn
                        07.01.2018 23:22

                        CR3 сбрасывается каждый рад при переключении задачи

                        Но не при прогулке в ядро и обратно в пределах одной задачи. Для чего и нужно маппирование в нынешнем виде, иначе CR3 придется переключать дважды, сбрасывая TLB при каждом syscall'е и при каждом возврате из него.


                        1. ToSHiC Автор
                          08.01.2018 01:16

                          Судя по git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=5aa90a84589282b87666f92b6c3c917c8080a9bf, за счёт аккуратной работы с PCID всё не на столько плохо, как могло бы быть. CR3 переключают дважды, если pti включен.


                  1. MacIn
                    07.01.2018 19:04

                    95-5, вот именно. При чем тут дописывание? Что, от дописывания страницы в кеш попадут что ли?


                    1. vire
                      07.01.2018 22:43

                      Все ядро — 1-2 мб, самые нужные структуры данных еще 10мб, еще 100мб буферов. В реальности это выглядит как минус 1-2гб памяти прямо со старта Винды(и любой другой ОСи), но само ядрышко и горячие данные компактны, локальны и кешфрендли, поэтому с легкостью залетают в кеш.


                      1. MacIn
                        07.01.2018 23:20

                        Вы не понимаете уточняющих вопросов и говорите совершенно отвлеченные вещи.


    1. LeonidY
      06.01.2018 00:38

      Есть архитектуры, где проверка доступа происходит ДО запуска чтения. MIPS например (Байкал-T1). И в AMD похоже тоже.


  1. chabapok
    05.01.2018 02:58

    а почему исключение отрабатывает не сразу? Насколько понимаю, уже это — уязвимость.


    1. ToSHiC Автор
      05.01.2018 03:10

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


      1. MacIn
        05.01.2018 12:26
        +2

        Логично, что они оптимизируют лучший случай — в 99.9% случаев чтение будет валидным, а 0.01% чтений зловреда или просто некорректного кода можно пренебречь — там performance penalty неважны.


  1. Daffodil
    05.01.2018 03:18

    .


  1. AlexTest
    05.01.2018 04:51

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


    1. apatrushev
      05.01.2018 06:17

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


      1. akrupa
        05.01.2018 06:58

        Код атаки — всего четыре инструкции. Уязвимых комбинаций инструкций очень много. Они очень часто встречаются в дикой природе (в разных вариациях).


        1. apatrushev
          05.01.2018 08:16

          парсер порезал irony… оно конечно там было. :)


        1. potan
          05.01.2018 18:26

          Верификатор загружаемого кода может проверять, что все обращения в память происходят либо к статически выделенной памяти, либо к стеку, либо к тому, что вернул new/malloc. А там, где есть сомнения, ставить breakpoint, который сломает конвейер (а лучше вообще не давать запускать).


          1. tyomitch
            05.01.2018 18:44
            +1

            Поздравляю, вы изобрели JVM.


          1. khim
            05.01.2018 18:46

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

            А тут раз: щелчок пальцев — и верификатор у нас в кармане.

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


          1. DistortNeo
            05.01.2018 18:56
            +1

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


            1. khim
              05.01.2018 22:32
              +1

              Это не совсем правда. Мы не можем точно понять — обладает программа таким поведением или нет. Но можно «пограничные случаи» обьявить «подозрительными» и тоже выкинуть.

              Проблема в том, что теоретически, это даст нам безопасность, а практически — страшную головную боль и почти все библиотеки окажется невозможно использовать. Останутся разве что олдскульные программы типа TeX'а, которые память выделяют в самом начале работы, а освобождают — по завершении…


          1. qw1
            05.01.2018 20:21

            Верификатор загружаемого кода может проверять, что все обращения в память происходят либо к статически выделенной памяти, либо к стеку, либо к тому, что вернул new/malloc
            Это не защищает от Spectre.

            Там же код
            if (index < array_size) { обращение к array[index]; }

            Проблема в спекулятивном выполнении, когда процессор зашёл внутрь if при слишком большом index, а не должен был. Верификатор тут посчитает, что такое чтение закрыто if-ом.

            Если же верификатор будет заходить во все false-ветки и пытаться выполнять код, который не должен выполняться, у него быстро крыша съедет от ложных срабатываний.


    1. khim
      05.01.2018 16:52
      +1

      Паттерны есть, но они «размыты», как бинарный боеприпас. Кусочек, которые «начищает предсказатель» там, «читатель тени» тут, какая-то логика, которая всё объединяет — ещё где-то.

      Каждый кусочек — вполне «бытовой», встречающийся в обычных программах. А как понять что в рантайме вон тот, тот и вот ещё этот участки соберутся и получится эксплоит — неясно совершенно.

      Потому и паника такая, что уязвимость, сама по себе, не особо кошмарная, но вот то, что проведённая через неё атака не оставляет никаких следов — это страшно…


    1. ToSHiC Автор
      05.01.2018 17:28

      Вот такой кусок кода даст примерно такой же ассемблерный код:


      struct foo {
          char flags;
          char padding[4095];
      };
      
      struct foo array[256];
      unsugned char* index_ptr;
      
      do {
          char flags = array[*index_ptr];
      } while (flags == 0);

      И этот код вполне себе обычный.


  1. Demon_i
    05.01.2018 09:22

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


    1. lostpassword
      05.01.2018 09:33

      Ну Интел сказал же, что это не RCE.
      Но запуск и не требуется — просто прочитать из памяти что-нибудь вкусненькое тоже хорошо.
      Меня вот лично очень пугают сообщения, что чуть ли не через JS можно будет содержимое оперативной памяти читать. Зашёл на сайт с нехорошим скриптом — и пароли из KeePass утекли. Вот где ужас-то.


      1. pavel_pimenov
        05.01.2018 10:08

        Вы пробовали снимать дамп памяти процесса KeePass — можете найти там свои пароли?


        1. Demon_i
          05.01.2018 11:08

          Вот-вот. Плюс нам надо запуститься в системе как минимум от юзера. Вычислить адрес приложения. Рассчитать где у него хранятся пароли. С вероятностью в N процентов попытаться их вычитать, ведь в кеше может быть не только то, что нам нужно. И на каждый тик процессора нужно создать как минимум 256 своих тиков, что жутко подвесит систему. Ну так себе уязвимость. На сервере может и не заметит никто, но рабочую машину я патчить не собираюсь.


          1. interprise
            05.01.2018 11:21

            Все что вы написали не верно.
            1. Вероятность прочитать правильно 99.97% (0.9997 если говорить в ТВ)
            2. То что мы читаем не обязано быть в кэше, кэш используется для извлечения данных, читать можем что угодно.
            3. Читать можно файловый кэш к примеру. Вроде как для js убрали эти примочки, но к примеру запуск flash может быть очень неприятный


            1. Demon_i
              05.01.2018 13:45
              +1

              читать можем что угодно.
              3. Читать можно файловый кэш к примеру.

              Вы могли-бы пояснить как файловый кеш относится ко внутреннему процессорному кешу предсказаний переходов? Я, как понимаю, чтобы его (файловый кеш) считать — нужно его запросить. Для этого нужно иметь доступ к системе. А для этого нужно уже быть вирусом в системе. Тогда ЭТА уязвимость ну не совсем нужна. Точнее совсем не нужна. Если мы в системе и может запросить на чтение файл с достаточными правами — мы можем и так его считать. Может я чего не понимаю — поясните.


              1. interprise
                05.01.2018 13:47
                +3

                Мы можем читать любую память, включая память ядра. Файловый кэш хранится в памяти ядра. Собственно все. Естественно, если файла нету в кэше, то его не удастся прочитать.


                1. Demon_i
                  05.01.2018 13:55

                  Т.е. чтобы считать хранилище паролей windows или chrome мы должны торчать в системе. Тратить 256 тиков на каждый процессорный тик только на чтение. Нам еще нужно обработать весь этот поток данных, чтобы понять, что мы читаем. Собрать все данные в один массив, система-же многозадачная и данные там будут не только ядра. Расшифровать его. Это вообще реально?


                  1. interprise
                    05.01.2018 13:57
                    +1

                    все реально, легко снимается полный дамп памяти. А дальше уже можно много вещей делать.


                    1. pavel_pimenov
                      05.01.2018 14:51

                      Как у вас получится снять консистентный дамп памяти и успешно разобрать что/где лежит в этих XX гигах?
                      Приложения работающие с секретными данными
                      явно знают и используют функции вроде
                      msdn.microsoft.com/en-us/library/aa366877(v=vs.85).aspx
                      и не хранят открытые ключи в памяти «вечно»


                    1. Lord_Ahriman
                      05.01.2018 15:01
                      +2

                      И потом совершенно незаметно эти XX гигов куда-то сохраняются и отсылаются? Да это даже не троянский слон, это троянский танкер какой-то.


                      1. Demon_i
                        05.01.2018 15:12
                        +1

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


                        1. sumanai
                          05.01.2018 16:14

                          у антивируса

                          Не у всех он стоит.


                          1. Demon_i
                            05.01.2018 16:31

                            Да вообще не у всех стоит, но есть-же встроенный WinDefender. Практически не отключаемый. Проще-же было обновить его, чем городить патчи с 30% просадкой мощности.


                            1. willyd
                              05.01.2018 16:34

                              А это уязвимость можно закрыть на уровне антивируса?


                              1. Demon_i
                                05.01.2018 16:45

                                Первая, вроде как закрывается виагрой, но сам не пробовал — советовать не буду. Если вы про ту, что обсуждается в статье, то ИМХО можно закрыть предпосылки к её эксплуатации. Т.е. не давать стороннему софту пролезть на комп вообще.


                                1. willyd
                                  05.01.2018 16:59

                                  JS — это софт исполняемый на стороне клиента, к примеру. Как вы собираетесь валидные, с первое взгляда, инструкции запрещать?


                                  1. Lord_Ahriman
                                    05.01.2018 17:37

                                    Я не знаю точно, но сегодня приехало обновление Firefox Quantum, в патчноуте пишут о предотвращении обсуждаемой атаки. ЗЫ: отвечал willyd, возможно, промахнулся веткой с планшета.


                                  1. potan
                                    05.01.2018 18:16

                                    Валидность можно проверять верификацией кода перед запуском, как это делает JVM с байткодом. Что-то валидное может проверку не пройти, но это не очень большая проблема.


                                    1. d-stream
                                      05.01.2018 18:38

                                      Вопрос остается только в том на сколько нечто невалидное отличается от валидного… Ну то есть если процент ложных отбраковок будет в районе долей процента — это сойдет, а если это будут единицы процентов?


                                      1. willyd
                                        05.01.2018 19:05

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


                                        1. d-stream
                                          05.01.2018 19:33

                                          Будет замирать еще дольше )


                            1. sumanai
                              05.01.2018 20:57

                              Да вообще не у всех стоит, но есть-же встроенный WinDefender.

                              У меня его и в проекте не было.


                          1. Lord_Ahriman
                            05.01.2018 17:35
                            +1

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


                            1. d-stream
                              05.01.2018 18:40

                              Отсутствие активности мышки — вполне себе повод считать что юзера рядом нет…
                              Ну и именно отправка огромных массивов — не столь уж обязательна.


                  1. Alexeyslav
                    05.01.2018 22:17

                    А если скопировать с памяти ядра токен который даёт нужные права приложению? Критических и весьма ценных данных в системе может быть не так уж и много, если знать где их взять — времени у троянца может быть просто масса, и если даже он будет угадывать по одному байту на системный тик с довольно высокой вероятностью он может выудить все необходимые пароли. А если в системе есть какой-то ключик который предоставит полный доступ вредоносному коду… то выуживать таким образом гигабайты не нужно — считываем ключ, пользуемся им для доступа к нужным нам гигабайтам данных — пара милисекунд после вторжения и система скомпрометирована.


                    1. Demon_i
                      05.01.2018 23:15

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


          1. KWEM
            05.01.2018 14:11
            -3

            Простой пример: Представим что JS может спокойно читать память с этой уязвимостью
            Итак для облегчение у нас есть браузер с 1 процессом на всё
            Открыто 2 страницы 1 с JS другая с <паролями> всего-то нужно хацкеру узнать адрес в памяти для нужной страницы браузера и найти место где хранятся пароли – и очень хорошо если пароли хранятся в чистом виде

            Но в реальности на 1 страницу браузера 1 процесс — надо найди нужный процесс
            В нужном процессе — найти нужное место в памяти где хранятся пароли

            Ребята да вас развоят – легче сломать какой-то саб домен пентагона через открытый порт – чем вытянут пароль реального процесса с кеш памяти процессора

            Это умышленное устаревание техники – чтоб лучше покупали новые процы
            Такой же развод как и силиконовая лотерея
            (кто не знает реальный процент брака там меньше 1 %
            и даже если б он реально был больше, То топовыйх процов (без брака)
            было б больше чем бракованных – но по факту продаж в разы наоборот – странно правда.
            (они могут как блочить в самом проце – так и спецом под шумок браковать если будет масс расследование, это литография – новая схема как два пальца об асфальт — вы ничего не докажите XD)
            (прям как в Апл с батареёй – типо они тут ни причем и сделаи ради пользователей
            Еслиб делали ради них то былаб галочка в настройках, хотя даже так пользователям пох
            Ибо они бегают от зарядки к зарядке весь день – и телефона макс хватает на 24 часа, а часто и на 12,
            а так это временной триггер для включение устаревания после N дней работы)
            (p.s вполне возможно что уже совсем скоро тот же “временной триггер устаревания” бeдет и в процах, если ещё его нет в последних моделях)


            1. rsmike
              05.01.2018 14:59
              +4

              Хочется взять и подарить учебник по русскому языку


              1. KWEM
                05.01.2018 21:12
                -2

                Ну, спасибо хоть не назвали “больным на голову параноиком”,
                а только “неграмотным школьником” ;)

                К примеру была такая уязвимость как Row hammer, пару лет назад
                И большие компании скопом взяли и «положили» на неё,
                Типо будет новый hardware – там пофиксим, а делать апдеты нету смысла
                – типо она не опасна, а тут на тебе — прошло 3 года и она внезапно стала опасна В новой форме
                (и пофиксить её решили патчем всех ОС что уменьшает производительность)

                p.s А ничё что JS и так может натворить такое, что на голову не налазит – и без всяких ваших новомодных уязвимостей
                – и всем опять откровенно на это “положить”


            1. Demon_i
              05.01.2018 15:16
              -1

              Вы немножко не понимаете, чуть больше, чем совсем. Дело тут не в процессах. Нельзя просто взять и считать данные отдельного процесса. Можно попытаться отследить когда на конкретном ядре процессора выполняется код конкретного процесса и попытаться считать его данные кеша. С не 100% Вероятностью, и потом попробовать извлечь из этих данных что-то. На что потребуется, ИМХО, мощность на порядки больная, чем атакуемого ПК.


              1. khim
                05.01.2018 17:01

                Нельзя просто взять и считать данные отдельного процесса.
                Почему нельзя-то? В структурах данных ядра написано — где чего хранится. У нас есть доступ ко всей памяти. Заходим и читаем.


              1. ToSHiC Автор
                05.01.2018 17:29

                Из кеша ничего не читается, но можно сделать полный дамп памяти ядра. А потом применить volatility.


                1. SwordFighter
                  06.01.2018 23:42

                  а


      1. chabapok
        05.01.2018 14:27
        +3

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


        1. willyd
          05.01.2018 14:59
          +1

          webassembly?


          1. potan
            05.01.2018 18:12
            -2

            Лишний повод не поддерживать эту технологию.


          1. chabapok
            06.01.2018 12:39

            wa — не js


      1. cyberzx23
        06.01.2018 14:56

        Уже не впервый раз встречаюстя с этим мнением.
        Нет, через JS нельзя читать память других процессов используя Spectre. Но можно прочитать память своего процесса, например, браузера. Что тоже неприятно, но не настолько фатально.


  1. Dmitri-D
    05.01.2018 10:44

    Честно говоря, я не особо понял, зачем так делать — я бы просто к rax прибавил единичку сразу после чтения, да и всё


    0x00 станет 0x01, а что будет с 0xFF? Такие байты станут нулями. Т.е. проблема не исчезнет, только может немножко ускорится чтение, если нулей больше, чем 0xff.
    Но зато похоже будет 2 операции, задействующие ALU и я не уверен что в этом случае все эти команды сможет спекулятивно выполнить prefetch. Надо проверять.


    1. ToSHiC Автор
      05.01.2018 10:45

      0xff станет 0x100, так что там всё хорошо. Прибавлять единичку я предлагают уже в rax, там 64-битное значение хранится.


    1. Hormiga
      05.01.2018 11:24

      Так делается потому, что если возвращается ноль — есть вероятность что успело отработать исключение и запретить доступ в память — поэтому попытка предпринимается еще раз


  1. Hormiga
    05.01.2018 11:25

    А вот интересное дело: в первоначально документе сказано, что хотя PoC не удалось заставить работать на AMD и ARM, скорее всего не получилось из-за плохой оптимизации самого PoC, т.к. ассемблерный код из статьи отработал и там с ожидаемым результатом.


  1. amarao
    05.01.2018 12:50
    +6

    Пункт 2 плохо объяснён, хотя именно в нём и кроется разница между intel и amd.

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


    На intel'ах проверка прав производится после чтения, а на amd — до. Таким образом, у intel'а получается спекулятивное чтение (прогревающее кеш), а у amd — спекулятивный segmentation fault.


    1. kryvichh
      05.01.2018 13:33
      +1

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


      1. kryvichh
        05.01.2018 13:46

        Если это так, как написал amarao, этот регистр по-любому будет задействован… Неудачная шутка…
        С другой стороны, это может объяснить почему на разных семействах процессоров Intel уязвимость есть, а у AMD — нет. Независимо от физического расположения регистров — кэшей на кристалле. Логика отработки спекулятивного исполнения разная.


        1. chabapok
          05.01.2018 14:45
          -2

          Как это у амд нет?

          интел не устойчив к обеим типам
          амд не устойчив только к spectre

          Но почему-то народное внимание акцентировано на интелах и meltdown, а амд в головах людей «надежны». Думаю, не последнюю роль тут сыграли маркетологи амд.
          На самом деле, это означает, что уязвимость есть везде. Я еще не разбирался с meltdown, но навскидку уязвимости весьма похожи. По сути, это 1 тип уязвимости, просто мы meltdown-это вид в фас, а spectre — вид в профиль. И основная уязвимость — та, которой подвержены обе фирмы, то есть spectre.


          1. Asparagales
            05.01.2018 15:15

            Похоже, что уязвимость присутствует не только у процессоров Intel и AMD, но и у многих процессоров с архитектурой ARM.


          1. interprise
            05.01.2018 15:20
            +1

            www.amd.com/en/corporate/speculative-execution

            амд уже пофиксили без потери производительности


            1. chabapok
              05.01.2018 22:28

              Без понятия. Возможно, то, что написано по ссылке, соответствует реальному положению дел. Но на моем phenom b40 уязвимость есть.

              Да и то, что они написали — «Resolved by software / OS updates to be made available by system vendors and manufacturers» — это же отмазка! Хочется же, чтобы и баз был закрыт — и производительность не падала. А патчи существенно снижают производительность (уже есть статья про это), повышают энергопотребление и тепловыделение.


          1. LeonidY
            06.01.2018 00:48

            > Но почему-то народное внимание акцентировано на интелах и meltdown, а амд в головах людей «надежны».

            Meltdown легко сделать, даже в JS. И он не работает на AMD.

            Spectre значительно труднее сделать, по крайней мере Гуглу пришлось воспользоваться eBPF, который по умолчанию выключен.


            1. D3fl4t3
              08.01.2018 18:09

              Разве в js можно вызвать сегфолт? Только Spectre возможно через js эксплуатировать.


    1. ToSHiC Автор
      05.01.2018 14:30
      +1

      Тогда бы не работал toy example, а он работает. В статье пишут, что их PoC код просто не успевает по какой-то причине.


  1. tyomitch
    05.01.2018 12:51
    -13

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

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


    1. TheShock
      05.01.2018 16:38
      +6

      Хайпертрейдинг — это торговля Биткоинами? От слова «хайп» и «трейдинг»?


  1. apro
    05.01.2018 12:53

    Сходить в оперативную память стоит больше 100 процессорных тактов

    Это все еще так? Вроде последнее поколение оперативной памяти всего лишь
    раза в 1.5 уступает в частоте написанной на упаковке?


    1. tyomitch
      05.01.2018 12:57
      -3

      На упаковке памяти? Или на упаковке процессора?


    1. Danil1404
      05.01.2018 14:11
      +1

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


    1. JTG
      05.01.2018 15:59
      +2

      Всё ещё так. Вот клёвая визуализация people.eecs.berkeley.edu/~rcs/research/interactive_latency.html

      И более свежая инфа по процессорам www.7-cpu.com


  1. BubaVV
    05.01.2018 13:35
    -4

    Я – Мелтдаун. Я не баг ваш.
    Я происхожу от вас и существую в вашем проце.
    Да не нарушишь ты принципа причинности в моем потоке выполнения. А не то.


  1. ReakTiVe-007
    05.01.2018 13:40
    -7

    Зачем ругаися, насяльника? Мы патш сделать, теперь работать медлена но шэсна.
    image


  1. exce1
    05.01.2018 14:12

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


    1. ToSHiC Автор
      05.01.2018 14:12

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


      1. tyomitch
        05.01.2018 14:42

        exce1 имеет в виду: результатом чтения, к моменту retire, будет исключение доступа к памяти, и эксплойту потребуется, чтобы это исключение его не убивало.
        Никаких «хакнутых векторов» и никаких особых привилегий для этого не нужно (в каком-нибудь C++ достаточно обернуть чтение в try{}catch или выставить предварительно signal(SIGSEGV)), но как это эксплойтить из ЯВУ наподобие JS, мне непонятно.


        1. olekl
          05.01.2018 15:46

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


          1. tyomitch
            05.01.2018 16:06

            Посмотрите псевдокод в статье: там безусловное чтение по недоступному адресу.


          1. exce1
            05.01.2018 17:12
            +1

            В том-то и дело. Исключение НЕ БУДЕТ отброшено. В опубликованном proof-of-concept коде чётко прописан обработчик на signal(SIGSEGV)), а также активное использование инструкций RTDSCP:
            github.com/paboldin/meltdown-exploit
            Очевидно, что ни через какой браузер выполнить такое невозможно.


            1. olekl
              05.01.2018 17:42
              +1

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


            1. LeonidY
              06.01.2018 00:50

              Это для Spectre.


    1. potan
      05.01.2018 18:02

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


  1. Asparagales
    05.01.2018 14:59

    А как узнать пропатчили меня уже или нет? (Linux). Или это произойдет при очередном обновлении ядра?
    Говорят, что в линуксе их можно отключить параметр nopti в kernel command-line parameters.
    Смотрел вот тут и никакой nopti не нашел. Нашел схожий по звучанию параметр «nopku», который

    [X86] Disable Memory Protection Keys CPU feature found in some Intel CPUs.


    1. willyd
      05.01.2018 15:04

      Как-то так.
      $ grep ISOLATION config
      CONFIG_MEMORY_ISOLATION=y
      CONFIG_PAGE_TABLE_ISOLATION=y


      1. kafeman
        05.01.2018 17:20

        Еще можно попробовать:

        $ grep insecure /proc/cpuinfo


        1. willyd
          05.01.2018 17:31

          Не показательно относительно kpti fix
          $ grep ISOLATION config
          CONFIG_MEMORY_ISOLATION=y
          CONFIG_PAGE_TABLE_ISOLATION=y
          $ grep insecure /proc/cpuinfo
          bugs: cpu_insecure


          1. Asparagales
            05.01.2018 18:21

            На opennet видел такую инструкцию:
            zcat /proc/config.gz | grep PAGE_TABLE_ISOLATION
            CONFIG_PAGE_TABLE_ISOLATION=y


            1. willyd
              05.01.2018 19:14

              Не все дистрибутивы выкладывают текущий конфиг ядра в /proc/
              Я написал общий случай подставьте файл и выберите grep или zgrep


  1. PashaPodolsky
    05.01.2018 15:00
    +1

    А если в п.2 сделать не

    char tmp = *kernel_space_ptr ;

    а
    char tmp = (*kernel_space_ptr >> k) & 1;

    и пытаться восстановить блок памяти не по байтам, а по битам? Или такое не пролезет через конвеер? Должно быть быстрее на порядок.


    1. ToSHiC Автор
      05.01.2018 15:01
      +3

      На самом деле именно так и делают, раздел 5.2 оригинальной статьи.


  1. Seroga123
    05.01.2018 15:36

    Мне вот интересно, какие пароли злоумышленник может заполучить через уязвимости Meltdown и Spectre? Речь только о сохранённых пользователем паролях в браузерах, или под угрозой также пароль на вход в систему, от BitLocker, от .rar архива, в конце концов?


  1. Tarasovych
    05.01.2018 15:36

    Есть ли изменения в производительности для Ryzen (до и после обновления, OS Win10)? Может кто тесты находил.



  1. fresheed
    05.01.2018 16:00

    Глупый вопрос. Вот нам удалось записать значение байта в tmp и… всё. Почему мы не используем непосредственно его, а ищем косвенными путями? Через некоторое время после чтения ядра произойдёт исключение, но процесс всё равно продолжает работать после этого, так почему бы не использовать tmp напрямую?


    1. ToSHiC Автор
      05.01.2018 16:04

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


      Вы же, когда на код смотрите, сами видите, что исключение будет выкинуто на строке


      char tmp = *kernel_space_ptr;

      и, с точки зрения даже ассемблерного кода, не говоря уж про С, никакой результат в tmp не попадёт.


    1. tyomitch
      05.01.2018 16:05

      Исключение произойдёт вместо записи прочитанного значения в tmp.


    1. crea7or
      05.01.2018 16:13

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


  1. potan
    05.01.2018 16:43

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


    1. khim
      05.01.2018 17:19
      +5

      А если прибивать процесс сразу после попытки чтения памяти ядра, эксплойта уже не будет?
      Будет. Там не обязательно спекулятивно исполнять после обращения к несуществующей памяти — это просто, чтобы пример упростить. Можно через предсказатель ветвлений сделать то же самое.
      bool read_memory;
      void* read_from;
      if (read_memory) {
        tmp = *read_from;
        ...
      }
      
      Несколько раз читаем с простым, валидным, «нашим» адресом read_from, процессор запоминает и начинает исполнять ветку спекулятивно. Потом перекулючаем read_from на адрес ядра, а read_memory — на false. Ветка всё равно исполняется спекулятивно и данные адра спекулятивно же читаются — но «реального» исключения не происходит.


    1. sergey-b
      05.01.2018 17:31

      Кое-где используются эти прерывания для нормальной работы. Я такое видел в интерпретаторе Java.


      1. potan
        05.01.2018 18:05
        +1

        Используются, но обращение идет не в область ядра.


        1. khim
          05.01.2018 20:17

          Так и здесь обращение идёт не в адрес ядра! Посмотрите внимательно: то обращение, которое реально происходит и то, которое «ворует» данные — идут по разным адресам!


    1. kryvichh
      05.01.2018 17:43
      +2

      Приложение может и не пытаться читать из чужого процесса. Но могут так сложиться значения переменных, что если бы выполнилась альтернативная ветка условия (которая по логике программы не должна выполниться), то приложение обратилось бы к чужому адресу в памяти. И что, из-за этого приложение «прибивать»?


  1. tnsr
    05.01.2018 17:09
    +1

    Кто-нидь может составить минимальный список литературы, чтобы понять что тут понаписано в комментах? а то чувствую «чайник закипает» ))


    1. JTG
      05.01.2018 17:13
      +11

      image


      1. qw1
        05.01.2018 20:27

        Долго искал, куда потеряли томик 2A. Оказывается, он стоит вертикально перед монитором.


        1. khim
          05.01.2018 22:23
          +1

          А ARM Manula в таком виде? Книга на 6354 страницы… я хочу это видеть!


      1. Rumata888
        05.01.2018 22:15

        Второй том-то зачем?


        1. serf
          06.01.2018 00:41
          +1

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


    1. MacIn
      05.01.2018 18:03
      +2

      Можно начать с «архитектуры компьютера» Танненбаума. Про предсказание переходов хорошо рассказано даже в старом учебнике «Организация ЭВМ и систем» Цилькера.


      1. tnsr
        05.01.2018 22:05

        Ага вот тут books кое-что нарыл


        1. serf
          06.01.2018 00:41

          .


    1. chabapok
      06.01.2018 12:45

      Если любой ассемблер изучить — то вцелом можно разобраться со всем остальным достаточно быстро.


  1. tnsr
    05.01.2018 17:45

    Не, не, не
    не с моей дислексией
    поищу курс для полных идиотов на ютюб
    спасибо
    ;o)


  1. Dywar
    05.01.2018 19:14

    Добавлю немного от себя, точнее немного текста из книги «Оптимизация приложений на платформе .NET».

    1) Микросхемы памяти типа DDR3 SDRAM дают задержки доступа к памяти порядка 15 наносекунд.
    2) Процессоры за это время могут выполнить — десятки (а иногда сотни) инструкций. Явление простоя в ожидании доступа к памяти известно под названием удар о стену памяти.
    3) Чтобы увеличить расстояние между приложением и этой «стеной», современные процессоры снабжаются несколькими уровнями внутренней кэш-памяти. L1 ~5 тактов CPU, L2 ~10 тактов, L3 ~40 тактов.
    4) Когда процессор обращается к главной памяти (RAM), он читает из нее не один байт или слово, а строку кэша (cache line), размер которой в современных системах составляет 32 или 64 байта. Обращение к любому слову в той же строке кэша уже не будет вызывать промах кэша, пока эта строка не будет вытеснена из кэша.

    Например, при суммировании элементов Int32 расположенных в массиве:
    При обращении к элементу массива, вначале происходит промах кэша и загрузка строки кэша, содержащей 16 последовательно расположенных целых чисел (строка кэша = 64 байта = 16 целых чисел). Так как доступ к элементам массива выполняется последовательно, следующие 15 чисел оказываются в кэше и при обращении к ним не возникает промаха кэша. Это почти идеальный сценарий с соотношением промахов кэша 1:16.


    1. tyomitch
      05.01.2018 19:29

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


      1. Dywar
        06.01.2018 21:29

        1. khim
          06.01.2018 22:48
          -1

          Так себе обнова. С одной стороны — полезное замечание (да, действительно, ипользование оффсета 4096 действительно очень похоже на эксплуатацию TLB), с другой — совершенно безграмотные пассажи типа «все патчи уязвимостей Spectre и Meltdown предложенные производителями ПО это и делают, перезагружая в обработчике исключения GP регистр CR3» (притом что перезагрузка CR3 в обработчике исключений поможет против Meltdown и Spectre примерно как кровопускание против туберкулёза… а было ведь время, когда так и лечили).


  1. Alexey2005
    05.01.2018 19:47
    +1

    А почему нельзя при возникновении любого исключения сбрасывать кэш? Исключения при обращении к запрещённым областям памяти, как я понимаю, всё равно сперва обрабатываются ядром ОС, вот в этом обработчике кэш и почистить перед тем, как передать управление дальше по цепочке обработчиков…


    1. khim
      05.01.2018 20:07
      +1

      1. MacIn
        05.01.2018 20:11

        Тогда разве только сбрасывать весь кеш при откате результат спекулятивного исполнения о_О


    1. LeonidY
      06.01.2018 00:56

      Можно, но потеря производительности будет не 30%, а в разы. Типа возврат обратно к старому доброму 486 или в крайнем случае — старому Atom (до 2013г).


  1. hexad
    05.01.2018 22:06

    Опечатка к тексте во 2-ом упоминании Meltdown буква «t» пропущена


  1. svcoder
    05.01.2018 22:33
    -4

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


    1. Nokta_strigo
      06.01.2018 01:30
      +1

      Откуда такая уверенность? В оригинальной работе про Spectre как раз один из примеров — эксплуатация уязвимости из JavaScript.


      1. svcoder
        06.01.2018 10:00
        -1

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


        1. qw1
          06.01.2018 10:48

          Для javascript проблема решается с помощью отключения SharedArrayBuffer
          Неизвестно, сколько сайтов сломается.
          В JVM например по стандарту языка, любая выделенная память заполняется нулями
          Это защищает от чтения данных, оставшихся от предыдущего владельца. Для Spectre/Meltdown какая разница?


          1. svcoder
            06.01.2018 15:36

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


            1. Nokta_strigo
              06.01.2018 20:39

              «Появление содержимого памяти другого процесса» в памяти вашего процесса исключено средствами ОС не зависимо от языка программирования (если мы работаем в user mode). Java дополнительно защищает только от случайного «воскрешения» данных своего же процесса.


  1. Nokta_strigo
    06.01.2018 01:25

    Спасибо за статью! Очень понятно и лаконично!


  1. gjf
    06.01.2018 01:34
    +2

    Статья интересная и достаточно доходчивая — при чём краткая. Автор — молодец!

    Но хотелось бы немного разбавить густые краски и снизить желание бегать за патчами и новыми процессорами :)

    1. Уязвимость Meltdown (которая на Intel) позволяет локальному процессу с привилегиями пользователя прочитать (не записать, не изменить) память любого другого процесса, в том числе и память ядра.
    2. Уязвимость Spectre (которая у всех процессоров) позволяет локальному процессу с привилегиями пользователя прочитать (не записать, не изменить) память любого другого процесса, но не память ядра.

    Ключевое слово: «локальный процесс». Это означает, что основной риск существует для систем:

    — которые запускают недоверенный код (то есть в первую очередь — систем с интернет-браузерами);
    — которые запускают недоверенные процессы в песочнице, облаке и т.д.

    По сути, если кто-то всю жизнь работал под админом и не использовал ограниченные учётки (думаю, это где-то 80% пользователей Windows точно) — для него всё осталось как и прежде, так что и беспокоиться не о чём.

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

    Показателем «критичности» является хотя бы то, что Mozilla быстренько выпустила хотфикс 57.0.4 с довольно невнятным описанием, но даже не почесалась для ESR.

    В основном как всегда важна раздутость в СМИ, что как бы намекает на маркетинговую подоплёку — как же, новые процессоры надо как-то продавать, а на падение производительности надо как-то списывать :) Хотя имхо производительность современных устройств чуть ли не в последнюю очередь зависит от процессора.


  1. Psychosynthesis
    06.01.2018 06:53

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

    В этой связи возникает несколько вопросов:
    1. Верно ли, что для того чтобы вытащить что-либо осмысленное из памяти, нужно чтобы и атакуемая программа и атакующая достаточно времени должны выполняться параллельно, при этом атакующая на протяжении всего этого времени должна выполнять один и тот же, достаточно однообразный (и бессмысленный с точки зрения стороннего наблюдателя) паттерн? Если это верно, разве это не тривиальная задача для антивируса, если, конечно речь не идёт о «краже» одного байта?

    2. Для чтения таким вот образом одного «чужого» байта, условно, нужно выполнить 256 своих? Разве это не чудовщиный пинок быстродействию при попытке реализации атаки и, в свою очередь, не очередной способ засечь атаку?

    3. А что если реализовать «фрагментацию» кэша? Т.е. хранить в нём данные не линейно, а с разбивкой по адресам, например. Типа:

    Элементы 0-125 хранятся по адресу 0x000AAD
    Элементы 126-255 хранятся по адресу 0x000DDD

    Разве что придётся какую-то таблицу с маппингом ещё хранить, но ведь это всё равно будет быстрее чем сбрасывать весь кэш, нет?

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


    1. ToSHiC Автор
      06.01.2018 15:05

      1. Нет, в Meltdown идёт чтение страниц памяти ядра, атакуемой программы вообще нету.
      2. Да, но это можно оптимизировать. Засечь можно разве что слишком большое количество исключений, но это не является гарантией того, что программа пытается читать память ядра.
      3. Совершенно непонятно, что вы имели в виду. Почитайте, как устроен ассоциативный кэш, например вот тут: habrahabr.ru/post/93263

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


      1. Psychosynthesis
        06.01.2018 15:25

        1. Эм… В таком случае, что даёт одно лишь чтение именно ядра?

        3. Почитаю, спасибо.

        Читать память ядра со скоростью 500 кб/с… Ну… Пока всё ещё это выглядит примерно так же как считывание информации с защищённого компьютера через вентилятор.


        1. ToSHiC Автор
          06.01.2018 15:45
          +1

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


          1. Psychosynthesis
            06.01.2018 18:29

            А можно вкратце на простейшем примере — как?


            1. ToSHiC Автор
              06.01.2018 18:44

              1. Psychosynthesis
                06.01.2018 20:30

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


          1. pavel_pimenov
            06.01.2018 18:45

            Но у процесса есть свои менеджеры памяти
            Что делать с этим массивом данных с кучей рандомных байт?


            1. khim
              06.01.2018 18:54

              Что делать с этим массивом данных с кучей рандомных байт?
              Если бы там были «рандомные данные», то процесс ничего путного сделать бы не смог. А так — там определённые структуры, списки… фактически всё, что может и «умеет» программа — там сидит.

              Во времена DOS'а статьи, описывающие как «вытащить» из ядра DOS и разных программ полезные данные — были весьма популярны… и для нахождения полезной информации вовсе не нужно было сканировать всю память.

              Потом, с переходом в защищённый режим, статьи писаться перестали (смысл какой, если защита вас всё равно в адресное пространство «чужого» процесса не пустит?). Сейчас, видимо, будет ренесса?нс…


              1. pavel_pimenov
                06.01.2018 20:50

                Ок. сдаюсь — я старпер и к сожалению застал времена DOS+asm-x86
                но вы точно не теоретик?


                1. khim
                  06.01.2018 21:54
                  -1

                  При чём тут «теоретик или практик», если кроме пресловутого RALF'а выходили целые книги описывающие внутренние структуры DOS'а? И туда народ «по привычке» активно лазал (на более ранних системах типа C64 описание чего где лежит было прямо в документации).


                  1. MacIn
                    07.01.2018 04:57

                    Ну, человек же не про структуры данных ядра сказал — про это и так можно почитать где угодно, начиная от Windows Internals, Undocumented 2000 (не помню точно название), заканчивая NT DDK и утекшими исходниками NT4 и 2000.


                    1. khim
                      07.01.2018 10:30
                      -1

                      В своё время не только структуру данных ядра, но и самые распространённые TSR'ы описаны были. RBIL описывает далеко не только ядро DOS.


  1. acDev
    06.01.2018 11:04
    +2

    Для винды уже появилась тестовая тулза, которая считывает байтики из ядра:
    github.com/stormctf/Meltdown-PoC-Windows/tree/master/Meltdown/x64/Debug

    image

    Эти байтики находятся в самом начале образа ntoskrnl.exe (см. на картинке ряд real).


    1. sumanai
      06.01.2018 15:19

      Meltdown.exe не является приложением Win32.

      Чувствую, моя ОС защищена.


    1. Vitalley
      07.01.2018 02:37

      Нет Win64, не могу попробовать…


  1. FeRViD
    06.01.2018 14:28

    CPU читает из RAM не побайтно, а кэш-линиями. Допустим CPU прочитал кэш-линию. Во время цикла

    for (i = 0; i < 256; i++) {
    if (is_in_cache(userspace_array[i*4096])) {
        // Got it! *kernel_space_ptr == i
    }
    }
    

    мы определяем индекс массива, который читается быстро, но в то же время рядом с ним находятся еще несколько байтов из этой же кэш-линии и которые тоже будут быстро читаться.
    Вопрос: как нам понять какой именно из этих индексов — нужное нам значение из закрытой области RAM?


    1. ToSHiC Автор
      06.01.2018 15:06

      обратите внимание, что i умножается на 4096, как раз для того, чтобы исключить этот эффект, и ещё несколько.


      1. FeRViD
        06.01.2018 19:50

        Да, все верно. Не уловил сразу.
        Когда мы делаем

        char not_used = userspace_array[tmp * 4096];
        , то CPU читает кэш-линию (размером 64 байта) из RAM. После чего, при обращении к любому из этих байтов, время чтения будет минимально, т.к. они уже в кэше, что, собственно, и вызвало мой вопрос )) Но на самом деле нас интересуют только те индексы, которые кратны 4096.


  1. konservs
    06.01.2018 22:39

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

    Далее, зная адрес каждого процесса, зловред попросту читает «уязвимые к чтению памяти» процессы. Т.е. практически любой софт (кроме супер-крипто-параноидальных) уязвим. KeePass вряд ли будет хранить пароли в чистом виде — они будут зашифрованы мастер-паролем. Но (самый примитивный вариант) прочесть содержимое текстового файла с паролями, открытого в блокноте — как раз плюнуть. Прочесть приватные ключи сертификатов — да легко. Прочесть вашу переписку в Скайпе — запросто. И.т.д.


    1. khim
      06.01.2018 22:50

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

      Вот если сессия закрыта (автоматом или по таймауту) — тогда уже всё. Но кто её закрывает…


      1. qw1
        06.01.2018 23:55

        Если бы заранее знать, что память процесса ненадёжна, то вот простой способ защититься: при запросе пароля он загружается с диска (с флагом «не кешировать»), расшифровывается, используется и удаляется из памяти. Эксплойт не имеет доступа к HDD.

        Просмотр исходников KeePass2 показывает, что буферы хранятся в памяти, зашифрованные ф-цией ProtectedMemory.Protect из System.Security.dll со значением scope = SameProcess.

        А вот где .NET держит ключи, отдельный вопрос. Не исключено, что они как-то дополнительно защищены.


        1. sumanai
          07.01.2018 06:59

          Эксплойт не имеет доступа к HDD.

          Зато к нему имеют доступ все кому не лень.


          1. qw1
            07.01.2018 10:13

            Это к тому, как построить менеджер паролей, защищённый конкретно от Meltdown


          1. faiwer
            07.01.2018 10:17

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


  1. Darkhon
    06.01.2018 22:39

    Тем временем некто на SecurityLab, ссылаясь на данную статью, объясняет, почему на самом деле всё не совсем так.


    1. ToSHiC Автор
      06.01.2018 22:59

      Ответил чуть ниже.


  1. Rimoz
    06.01.2018 22:39
    -2

    Всегда знал главную проблему Хабро-подобных ресурсов — желание развиваться (сосредоточенность на разумном деле) и желание трендеть о себе или почти о себе (смесь с ЧСВ) — вещи разных берегов, это очередное подтверждение: www.securitylab.ru/analytics/490642.php


    1. ToSHiC Автор
      06.01.2018 22:59

      Цитаты из оригинальной статьи:

      Flush+Reload attacks work on
      a single cache line granularity. These attacks exploit the
      shared, inclusive last-level cache. An attacker frequently
      flushes a targeted memory location using the clflush
      instruction. By measuring the time it takes to reload the
      data, the attacker determines whether data was loaded
      into the cache by another process in the meantime.

      As already discussed, we utilize cache attacks that allow
      to build fast and low-noise covert channel using the
      CPU’s cache. Thus, the transient instruction sequence
      has to encode the secret into the microarchitectural cache
      state, similarly to the toy example in Section 3.
      We allocate a probe array in memory and ensure that
      no part of this array is cached. To transmit the secret, the
      transient instruction sequence contains an indirect memory
      access to an address which is calculated based on the
      secret (inaccessible) value. In line 5 of Listing 2 the secret
      value from step 1 is multiplied by the page size, i.e.,
      4 KB. The multiplication of the secret ensures that accesses
      to the array have a large spatial distance to each
      other. This prevents the hardware prefetcher from loading
      adjacent memory locations into the cache as well.
      Here, we read a single byte at once, hence our probe array
      is 256?4096 bytes, assuming 4 KB pages.

      Цитата из FLUSH+RELOAD: a High Resolution, Low Noise,
      L3 Cache Side-Channel Attack
      :
      We observe that the clflush instruction evicts the
      memory line from all the cache levels, including from
      the shared Last-Level-Cache (LLC). Based on this observation
      we design the FLUSH+RELOAD attack—an extension
      of the Gullasch et al. attack. Unlike the original
      attack, FLUSH+RELOAD is a cross-core attack, allowing
      the spy and the victim to execute in parallel on different
      execution cores. FLUSH+RELOAD further extends
      the Gullasch et al. attack by adapting it to a virtualised
      environment, allowing cross-VM attacks.
      Two properties of the FLUSH+RELOAD attack make
      it more powerful, and hence more dangerous, than prior
      micro-architectural side-channel attacks. The first is that
      the attack identifies access to specific memory lines,
      whereas most prior attacks identify access to larger
      classes of locations, such as specific cache sets. Consequently,
      FLUSH+RELOAD has a high fidelity, does not
      suffer from false positives and does not require additional
      processing for detecting access. While the Gullasch et al.
      attack also identifies access to specific memory lines, the
      attack frequently interrupts the victim process and as a
      result also suffers from false positives.
      The second advantage of the FLUSH+RELOAD attack
      is that it focuses on the LLC, which is the cache level
      furthest from the processors cores (i.e., L2 in processors
      with two cache levels and L3 in processors with
      three). The LLC is shared by multiple cores on the
      same processor die. While some prior attacks do use the
      LLC [47, 60], all of these attacks have a very low resolution
      and cannot, therefore, attain the fine granularity
      required, for example, for cryptanalysis.


    1. qw1
      07.01.2018 00:10
      +3

      Судя по тону статьи на securitylab, её автор — тот ещё ЧСВ-шник )))


      1. khim
        08.01.2018 01:11
        +2

        Ну там ещё и адекватные люди в комментариях отметились. И обнаружили как пассаж «For instance, taking care that the address translation for the probe array is cached in the TLB increases the attack performance on some systems» в оригинальной статье (что говорит нам о том, что реальные security-researfcher'ы из Google и других мест знают о TLB уж никак не меньше автора статьи… да и собственно на схеме CPU из этой статьи DTLB явно нарисован, его не «суперзнаток» из securitylab пририсовал), а также — проверка с другими константами. С 512 — работает надёжно, с 64 — тоже, но только если отключить prefetch в BIOS'е. А вот 32 — уже не работает. Стоит ли напоминать, что размер страницы — 4096 байт, а размер строки в кеше — 64 байта?

        То есть наш д’Артанья?н не только не сделал открытия — он ещё и гидко обделался со своими «откровениями».


  1. alex3d
    07.01.2018 00:16

    retry и jz retry нужны из-за того, что обращение к началу массива даёт слишком много шума и, таким образом, извлечение нулевых байтов достаточно проблематично. Честно говоря, я не особо понял, зачем так делать — я бы просто к rax прибавил единичку сразу после чтения, да и всё.

    Судя по всему в некоторых случаях "подсмотренный" байт может занулиться до выполния shl.
    jz retry нужен для того чтобы ингорировать неудачные для нас исходы race condition


  1. tnsr
    07.01.2018 18:23

    Так были ответы на вопросы:
    какие минимальные условия для атаки через сеть?
    какое время для получения полезной инфы с взламываемого компа?
    и только ли крадется инфа или можно еще нанести какой-то вред?


    1. tnsr
      07.01.2018 18:50

      И почему молчит президент? ©
      :/


    1. ToSHiC Автор
      07.01.2018 20:47

      Это локальная атака.
      Скорость чтения памяти ядра в Meltdown, по заявлению авторов — порядка 500КБ/с.
      Можно только читать.
      Всё это есть в оригинальной статье :)


      1. tnsr
        08.01.2018 06:35

        Не смиись, не смешно ))
        А то я уж подумал, ща все JS-девелоперы меня хакнут ))


  1. sergey-b
    08.01.2018 10:21

    Есть где-нибудь рабочий пример для win-64?


  1. emusic
    08.01.2018 11:46
    +1

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

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

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


    1. khim
      08.01.2018 17:40

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

      Проблема в том, что в описанной схеме два процесса работают в тандеме — и тот, который вы можете легко обнаружить, к моменту вызова исключения уже является просто «отработанным материалом», а тот, который собирает дамп памяти — обнаружить не так-то просто. Да, им нужно быть связанными неким буфером — но это может быть, например, кусок кода libc или что-нибудь подобное…


      1. emusic
        08.01.2018 18:29

        Который не имеет отношения к тому адресу, на который производится атака.

        Что значит «не имеет»? Атака производится именно на тот адрес, по которому впоследствии генерируется исключение.

        Которые никого не волнуют.

        Ну давайте считать, насколько волнуют. Для определения значения байта, считанного с атакуемого адреса, нужно выполнить в среднем 128 чтений из собственной памяти. При условии отсутствия в соответствующих адресов в кэше, каждое из этих чтений потребует, в лучшем случае, порядка сотни тактов — всего около 13000. Полный цикл IPI (запрос-реакция) отрабатывается примерно за 1500-2000 тактов. Если по каждому исключению, вызванному попыткой доступа к адресному пространству ядра, притормаживать через IPI все остальные ядра — уже есть неплохой шанс сорвать бОльшую часть атак.

        Который, в этот момент, уже «всё сделал» и готовится умереть.

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

        Да, им нужно быть связанными неким буфером — но это может быть, например, кусок кода libc или что-нибудь подобное…

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

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


        1. khim
          08.01.2018 19:38

          Что значит «не имеет»? Атака производится именно на тот адрес, по которому впоследствии генерируется исключение.
          Сейчас — да. Но если вы видели как Spectre реализуется, то могли бы понять, что можно вообще без исключений обойтись. А уж сделать два обращения в память так, чтобы первое «било» в молоко — так и вообще не проблема.

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


  1. LeoSheenSun
    08.01.2018 18:11

    IntelR 64 and IA-32 Architectures
    Software Developer's Manual
    Volume 3A: System Programming Guide, Part 1
    Раздел 11.3 (Таблица 11-2).
    Strong Uncacheable (UC) — System memory locations are not cached. All reads and writes appear on the system bus and are executed in program order without reordering. No speculative memory accesses, pagetable walks, or prefetches of speculated branch targets are made.

    Храните деньги приватные данные в сберегательной кассе в некэшируемых страницах, и спите спокойно.


    1. khim
      08.01.2018 18:15

      Для тех, для кого не важна производительность есть Pentium MMX…


      1. sumanai
        08.01.2018 18:30

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


        1. emusic
          08.01.2018 18:37

          Самое смешное, что тормоза менеджера паролей (если, конечно, он написан не очень криворукими людьми) Вы вряд ли сумеете ощутить, даже если выполнение замедлится в десятки раз. Большинство плееров тоже не пострадает, ибо они работают с большими (сотни миллисекунд) буферами, обработка которых выполняется преимущественно в режиме ядра. А вот всякие low-latency приложения, использующие буферы на единицы миллисекунд, да в каких-нибудь десятках с их неимоверными накладными расходами, уже могут начать потрескивать.


    1. ToSHiC Автор
      08.01.2018 18:16

      Обратите внимание, что в кэш подтягивается строка из userspace_array. А будут ли те данные, которые располагаются по адресу из kernel_space_ptr, в кэше или нет — без разницы.