Пользователь
0,0
рейтинг
8 апреля 2014 в 23:11

Разработка → Диагностика ошибки Heartbleed в OpenSSL. (Окончательный диагноз ещё не поставлен, хотя лечение уже идёт вовсю) перевод

Предисловие переводчика
Начиная переводить данную статью, я предполагал, что её автор разобрался в проблеме.
Однако, как правильно показали некоторые пользователи Хабра (спасибо VBart), не всё так просто и упоминание автором malloc, mmap и sbrk ещё более его запутало.
В связи с эти статья представляет больше исторический интерес, нежели технический.
Update Автор обновил свой пост в том же ключе, в котором шло обсуждение в коментариях к этому переводу.


Когда я писал об ошибке в GnuTLS, я сказал, что это не последняя тяжелая ошибка в стеке TLS, которую мы увидим. Однако, я не ожидал, что всё будет так плачевно.

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

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


Баг


Исправление начинается здесь, в ssl/d1_both.c:

int            
dtls1_process_heartbeat(SSL *s)
    {          
    unsigned char *p = &s->s3->rrec.data[0], *pl;
    unsigned short hbtype;
    unsigned int payload;
    unsigned int padding = 16; /* Use minimum padding */


Так, сначала мы получаем указатель на данные в записи SSLv3, которая выглядит следующим образом:
typedef struct ssl3_record_st
    {
        int type;               /* type of record */
        unsigned int length;    /* How many bytes available */
        unsigned int off;       /* read/write offset into 'buf' */
        unsigned char *data;    /* pointer to the record data */
        unsigned char *input;   /* where the decode bytes are */
        unsigned char *comp;    /* only used with decompression - malloc()ed */
        unsigned long epoch;    /* epoch number, needed by DTLS1 */
        unsigned char seq_num[8]; /* sequence number, needed by DTLS1 */
    } SSL3_RECORD;


Структура, описывающая записи, содержит тип, длину и данные. Вернёмся к dtls1_process_heartbeat:

/* Read type and payload length first */
hbtype = *p++;
n2s(p, payload);
pl = p;

Примечание переводчика: код n2s(c, s);
#define n2s(c,s)	((s=(((unsigned int)(c[0]))<< 8)| \
			    (((unsigned int)(c[1]))    )),c+=2)



Первый байт записи SSLv3 — это тип «сердцебиения». Макрос n2s берёт два байта из p и помещает их в payload. Это на самом деле длина (length) полезных данных. Обратите внимание, что фактическая длина в записи SSLv3 не проверяется.
Затем переменная pl получает данные «сердцебиения», предоставленные запрашивающим.
Далее в функции происходит следующее:

unsigned char *buffer, *bp;
int r;

/* Allocate memory for the response, size is 1 byte
 * message type, plus 2 bytes payload length, plus
 * payload, plus padding
 */
/* Выделение памяти для ответа, размером в 
 * 1 байт под тип сообщения, плюс 2 байта - под длину полезной нагрузки,
 * плюс полезная нагрузка, плюс заполнение
 */

buffer = OPENSSL_malloc(1 + 2 + payload + padding);
bp = buffer;


Выделяется столько памяти, сколько попросил запрашивающий: до 65535+1+2+16, если быть точным.
Переменная bp — это указатель, используемый для доступа к этой памяти. Затем:

/* Enter response type, length and copy payload */
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload);

Примечание переводчика про memcpy
НАЗВАНИЕ

memcpy — копирует область памяти
СИНТАКСИС

#include <string.h>
void *memcpy(void *dest, const void *src, size_t n);

ОПИСАНИЕ

Функция memcpy() копирует n байтов из области памяти src в область памяти dest. Области памяти не могут пересекаться. Используйте memmove(3), если области памяти перекрываются.
ВОЗВРАЩАЕМЫЕ ЗНАЧЕНИЯ

Функция memcpy() возвращает указатель на dest.
СООТВЕТСТВИЕ СТАНДАРТАМ

SVID 3, BSD 4.3, ISO 9899


Макрос s2n делает обратное макросу n2s: берёт 16-разрядное значение и помещает его в два байта. Затем он устанавливает ту же самую запрошенную длину полезной нагрузки.
Примечание переводчика: код s2n(c, s);
#define s2n(s,c)	((c[0]=(unsigned char)(((s)>> 8)&0xff), \
			  c[1]=(unsigned char)(((s)    )&0xff)),c+=2)



Затем копируются payload байт из pl, предоставленных пользователем данных, во вновь выделенный массив bp. После этого все это посылается обратно пользователю.
Так где же ошибка?

Пользователь управляет полезной нагрузкой и pl



Что если запрашивающая сторона на самом деле не передаст payload-байты, как должна была бы?
Что, если pl в действительности содержит только один байт?
Тогда memcpy будет читать из памяти всё, что было неподалёку от записи SSLv3.

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

Существует два способа память выделяется динамически с помощью malloc (по крайней мере, в Linux): с помощью sbrk(2) и используя mmap(2). Если память выделяется sbrk, то используются старые правила heap-grows-up, что ограничивает то, что может быть найдено с его помощью, хотя, используя несколько запросов (особенно одновременных) можно все же найти некоторые интересные вещи. [В этом разделе первоначально содержался мой скепсис по поводу PoC из-за природы того, как куча работает через sbrk. Однако, многие читатели напомнили мне, что вместо этого в malloc может быть использован mmap , а это всё меняет. Спасибо!]

Update от автора - эта часть из оригинальной статьи убрана
Однако, если используется mmap, «Ставки сделаны!». Для mmap может быть выделена любая неиспользуемая память. Это — цель большинства атак, направленных против Heartbleed.

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

Операционные системы, которые не используют mmap для реализации malloc скорее всего, чуть менее уязвимы.


Расположение bp вообще не имеет значения, на самом деле. Расположение pl, однако, имеет огромное значение. Память под неё почти наверняка выделяется с помощью sbrk() из-за порога mmap в malloc(). Тем не менее, память под интересные материалы (например, документы или информацию пользователей), весьма вероятно, будет выделена mmap() и может быть доступна из pl. Несколько одновременных запросов также сделает доступными некоторые интересные данные.

Итак, что же это значит? Ну, модели распределения памяти для pl дикттуют нам, что мы можем прочитать. Вот что сказал об этом один из первооткрывателей уязвимости:

Модели распределения памяти в куче делают компрометацию приватного ключа маловероятной # heartbleed # dontpanic.

— Нил Мехта (@ neelmehta) 8 апреля 2014


Исправление


Наиболее важной частью исправления является эта:

/* Read type and payload length first */
if (1 + 2 + 16 > s->s3->rrec.length)
    return 0; /* silently discard */
hbtype = *p++;
n2s(p, payload);
if (1 + 2 + payload + 16 > s->s3->rrec.length)
    return 0; /* silently discard per RFC 6520 sec. 4 */
pl = p;


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

Уроки


Что мы можем извлечь из этого?

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

После Heartbleed и багом GnuTLS я думаю, мы должны сделать три вещи:

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


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

Проголосовало 245 человек. Воздержалось 211 человек.

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

Перевод: Sean Cassidy (ex509)
Роман @Noospheratu
карма
11,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

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

  • +1
    И самое главное: чем больше ваш запрашиваемый блок, тем больше вероятность, что он будет обслужен mmap, а не sbrk.
    Только непонятно, каким образом размер запрашиваемого блока связан с размером блока памяти, в котором собственно лежит запрос и, собственно, за границы которого происходит чтение.

    Как выделена память тут: buffer = OPENSSL_malloc(1 + 2 + payload + padding); — значение не имеет.
    • 0
      Кажется, автор статьи недостаточно прояснил вопрос, каким образом утекают данные.

      Из кода я вижу, что запрашивающей стороне отправляется содержимое необнулённого массива buffer, выделенного malloc'ом в куче.
      Посмотрев код, никакой связи адреса расположения buffer'a с адресом структуры SSLv3, об окрестностях которой говорит автор статьи, не обнаружил.
      Если бы OPENSSL_malloc обнулял содержимое выделяемой памяти, уязвимость не имела бы таких последствий.
      Но, к сожалению, OPENSSL_malloc это простой malloc с дополнительными проверками, но без обнуления.
      • +1
        Дело не в том, что делается с отправляемым буфером. Дело в том, что в него копируется. А в него копируется существенно больше данных, чем прислал клиент. В этом и уязвимость, что в него попадают данные расположенные за пределами присланного клиентом объема.
        • 0
          Автор тоже думал всё это время и поправил оригинальный пост:
          The allocations for bp don't matter at all, actually. The allocation for pl, however, matters a great deal. It's almost certainly allocated with sbrk because of the mmap threshold in malloc. However, interesting stuff (like documents or user info), is very likely to be allocated with mmap and might be reachable from pl. Multiple simultaneous requests will also make some interesting data available.
          • 0
            Наиболее интересный момент, каким образом «interesting stuff» оказывается «reachable from pl» — так и остался за кадром.
            • 0
              «Продолжение — в следующей серии» (С)
              Ждём апдейтов от автора. Я его пост уже в WebSite Watcher добавил.
              Будут новости — опубликую.
              • 0
                Это как раз очевидно — класика Use-After-Free:

                //юзер 1 пришел
                a = malloc(size)
                write(a,SECRET_FROM_USER_1);
                free(a)
                //… юзер 1 ушел. пришел второй запрос в тот же процесс, но уже от второго юзера.
                b= malloc(size)
                read(b,size)

                А теперь Вы спрашиваете почему в b будет SECRET_FROM_USER_1
                • 0
                  Классика*
                • 0
                  Да, это не UAF, я погорячился, так как указатель новый, а не старый. Но суть от этого не меняется.
  • НЛО прилетело и опубликовало эту надпись здесь
    • +1
      Библиотека распространяется в объектном виде, и не имеет значения, на каком языке она написана, если язык компилируется.
      • НЛО прилетело и опубликовало эту надпись здесь
        • +2
          1. Я знаю несколько (Lisp, Ada, OCaml, Haskell), но ведь для наших целей достаточно одного, верно? Любой из них обеспечит большую безопасность, поскольку нигде не нужно выделять память руками.

          2. Не очень понятно. Вот вы написали на C, а вот вы написали на хаскеле, gcc -c вызвать, или ghc -c, разницы нет. Платформ в природе не так уж много разных, и популярные среды несомненно поддерживают все.

          3. Функция, которая в объектном файле, уже не сишная, как бы. Она уже скомпилирована. Про С++ судить не могу, но нет никаких проблем вызывать хаскелл из си. Любой более-менее развитый компилируемый язык предоставит байндинги и сгенерит хедеры для линкования с си.
          • +1
            В рассылке rust-dev обсуждали уже что rust был бы хорошим выбором для нового libssl, не считая того что написать качественную криптографическую либу с нуля очень сложно и надо бы компилятор постабильнее, перед тем как думать о таких объемах.
          • НЛО прилетело и опубликовало эту надпись здесь
  • +20
    Просто отличное голосование:

    Я бы не пожалел для этого усилий. А вы?
    * Да, я не пожалел бы.
    * Нет, я не пожалел бы.
  • 0
    "Без использования какой-либо конфиденциальной информации или учетных данных мы смогли украсть у себя
    секретные ключи, используемые для наших сертификатов X.509,
    имена пользователей и пароли,
    мгновенные сообщения,
    электронную почту и
    важные деловые документы и общения.
    "

    И всё это хранилось в куче?! Даже без наложенной маски?!

    А можно узнать, каким ПО пользовались эксперты?
    Чтобы избегать таких неправильно написанных программ?
    • +6
      Эксперты использовали специально модицифированный веб сервер, которй при старте считывает все секретные ключи, все пароли всех пользователей, расшифровывает их, архив мновенных сообщений и почты, а также важные деловые документы, и держит их в специально выделенной памяти ;)
  • +2
    При наличии времени, я выбрал бы
    Начать писать альтернативные реализации на более безопасных языках.
    В крайнем случае, можно попытаться выделить из Си «множество безопасных команд» и начать переписывать код по этому множеству.
    В случае использования потенциально опасных команд и невозможности от них отказаться — аудит этих участков кода. Можно и независимый, и платный.
    В общем, я фанат и последователь Н.Вирта.
  • 0
    Языком программирования надо правильно пользоваться. В том же C относительно легко можно создать разумные и безопасные абстракции и пользоваться ими. Проблема, имхо, больше в желании нагородить низкоуровневых велосипедов, когда а) свою абстракцию писать лень и б) использовать чужую абстракцию тоже не хочется.

    Менять язык просто для того чтобы помешать разработчикам городить свои велосипеды и навязать какие-то конкретные абстракции — имхо глупо. Все потенциально опасные конструкции все равно не убрать не удастся, зато появится ложное чувство безопасности. Там где заранее понятно что все проблемы все равно выловить не удастся и всех это устраивает ибо оптимизирует соотношение цены и качества, подобный подход разумен — всех проблем не повылавливаем, но хоть их общее число за счет смены языка сократим. А для продуктов уровня OpenSSL где даже одна ошибка это катастрофа это бессмысленно.
  • 0
    Можно начать следовать стандартам и перестать говнокодить, для начала.

    Думаю, от С не откажутся, хотя-бы из-за скорости. Но то что надо заставлять следовать хорошим практикам — факт.
    • 0
      Можно начать следовать стандартам и перестать говнокодить, для начала.
      Вы тут немного тёплое с мягким перепутали. Беда с TLS — в ДНК. Overengineering — это ещё мягко сказано. Проблема в том, что в последнее время модно складывать два и два с привлечением стандартов для описания которых требуются тысячи страниц. Тут на каком языке ни напиши — ничего хорошего не выйдет. Потому что добавлять уровни индирекции куда проще, чем реально решать проблемы (RFC 1925). Если мы хотим получить хоть какую-то безопасность, то нужно перестать городить вавилонские башни из кубиков когда «hello, world» занимает мегабайты и включает в себя поддержку приготовления яичницы на Луне, но кто ж на это пойдёт? Пока не будут введены санкции (и серъёзные санкции) за выпуск (и, главное, использование) небезопасного софта ничего не изменится. Та же история, что и, например, с карточками: в Америке никто и не собирается особо переходить на чипованные карточки, потому что для разрабочиков гораздо дешевле использовать нечипованные, а случаи мошенничества всё равно расследуются полицией и, соответственно, напрямую на затраты не ложатся (ну там есть, конечно, какие-то затраты, но они несоизмерыми с теми, которые бы банкам и платёжным системам пришлось бы нести, если бы они напрямую содержали полицию). В Европе — переходят, но не потому что там банки сознательнее, а потому что законы этого требуют.

      Увы и ах, но безопасность — это то, чего рыночная экономика обеспечить не может. Она поощряет эффективность, «срезание углов» и всемерную экономию средств — а это ну никак не может привести к безопасности. Потому армии даже в самых супер-пупер прогрессивных странах содержит не рыночная экономика, а государство.
      • +1
        Ну на самом деле OpenSSL ещё тот говнокод, так что в какой-то степени тут одно накладывается на другое.
      • +3
        Концентрация безапелляционных утверждений зашкаливает.

        Беда с TLS — в ДНК. Overengineering — это ещё мягко сказано. Проблема в том, что в последнее время модно складывать два и два с привлечением стандартов для описания которых требуются тысячи страниц

        Это точно про TLS? И где там overengineering, не благоволите пояснить?
        Вот стандарт, кстати, 104 страницы

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

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

        Пока не будут введены санкции… ничего не изменится

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

        безопасность — это то, чего рыночная экономика обеспечить не может

        Абсолютную безопасность обеспечить не сможет никто, а пока большинство современных алгоритмов, стандартов и софта — продукт рыночной экономики. Некоторые компании сделали миллиарды на безопасноcти: Verisign, RSA, не считая еще кучи компаний, специализирующихся на корпоративной безопасности. И они ее неплохо обеспечивают, пока как раз государство не сует свой нос, куда не следует (привет NSA и RSA).

        Она поощряет эффективность ...

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

        Потому армии даже в самых супер-пупер прогрессивных странах содержит не рыночная экономика, а государство

        Ох… Ну для начала, а кто финансирует государство?
        Далее, как вы себе представляете зарабатывание денег армией вообще? Корованы пусть грабят?
        Кстати, это сейчас смешно, а в 19-м веке британская армия была по большей части частной и сама себя окупала захватническими войнами в колониях. Весьма, между прочим, эффективно, в отличии от
  • +1
    Всем, кто спрашивал меня про PVS-Studio: Скучная статья про проверку OpenSSL.

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