Продолжаем исправлять coreutils в защищённом режиме E2k

Тест date-debug не срабатывает в ЗР из-за какой-то проблемы внутри gnulib:

./date --debug -d 'TZ="America/Edmonton" 2006-04-02 02:30:00'

date: parsed date part: (Y-M-D) 2006-04-02
date: parsed time part: 02:30:00
date: input timezone: TZ="America/Edmonton" in date string
date: using specified time as starting value: '02:30:00'
date: error: invalid date/time value:
Недопустимая инструкция

Оставим эту проблему на потом.

Программа pr валится с недопустимой инструкцией на:

pr --date-format="-- Date/Time --" -h x -b -3 './tests/pr/0Ft'

По поводу патча для randread.c мне сказали, что в курсе о использовании неинициализированной памяти, и это нормально. Поэтому патч пришлось переделать на e2k-специфический:

diff --git a/gl/lib/randread.c b/gl/lib/randread.c
index 8e6b1c5b8..ccad16bc3 100644
--- a/gl/lib/randread.c
+++ b/gl/lib/randread.c
@@ -132,7 +132,13 @@ static struct randread_source *
 simple_new (FILE *source, void const *handler_arg)
 {
   struct randread_source *s = xmalloc (sizeof *s);
+  #ifdef __e2k__
+  if (sizeof(void*) == 16)
+  {
+    // In E2K protected mode uninitialized memory can't be used
+    memset(s, 0, sizeof *s);
+  }
+  #endif
   s->source = source;
   s->handler = randread_error;
   s->handler_arg = handler_arg;

Тестирование coreutils в защищённом режиме (обновляется)

Скомпилировать coreutils в защищённом режиме несложно:

./configure CFLAGS='-Wno-error=suggest-attribute=const -m128 -Wno-error=suggest-attribute=pure -Wno-error=suggest-attribute=noreturn -Wno-error=suggest-attribute=format -Wno-error=suggest-attribute=cold -Wno-error=suggest-attribute=malloc -Wno-error=bad-macro-redef -Wno-error=type-limits -Wno-error=shadow=global' LDFLAGS='-m128'

Изначально не проходит достаточно много тестов:

FAIL: tests/misc/help-version.sh
FAIL: tests/pr/pr-tests.pl
FAIL: tests/misc/date-debug.sh
FAIL: tests/misc/sort.pl
FAIL: tests/misc/sort-discrim.sh
FAIL: tests/misc/sort-month.sh
FAIL: tests/misc/sort-rand.sh
FAIL: tests/misc/yes.sh
FAIL: tests/du/long-from-unreadable.sh
FAIL: tests/rmdir/symlink-errors.sh

В тесте help-version.sh на самом деле вся проблема в программе shuf - она попросту не запускается.

Проблема - в создании неинициализированного блока памяти в lib/randread.c

static struct randread_source *
simple_new (FILE *source, void const *handler_arg)
{
  struct randread_source *s = xmalloc (sizeof *s);
  s->source = source;
  s->handler = randread_error;
  s->handler_arg = handler_arg;
  return s;
}

(дальше s->buf используется в lib/rand-isaac.c, ISAAC_MIX как переменная seed):

/* The basic ISAAC initialization pass.  */
#define ISAAC_MIX(s, a, b, c, d, e, f, g, h, seed) \
  {                                                \
    int i;                                         \
                                                   \
    for (i = 0; i < ISAAC_WORDS; i += 8)           \
      {                                            \
        a += seed[i];                              \
        b += seed[i + 1];                          \
        c += seed[i + 2];                          \
        d += seed[i + 3];                          \
        e += seed[i + 4];                          \
        f += seed[i + 5];                          \
        g += seed[i + 6];                          \
        h += seed[i + 7];                          \
        mix (a, b, c, d, e, f, g, h);              \
        s->m[i] = a;                               \
        s->m[i + 1] = b;                           \
        s->m[i + 2] = c;                           \
        s->m[i + 3] = d;                           \
        s->m[i + 4] = e;                           \
        s->m[i + 5] = f;                           \
        s->m[i + 6] = g;                           \
        s->m[i + 7] = h;                           \
      }                                            \
  }

Решение очевидно и корректно - очищаем память после создания.

static struct randread_source *
simple_new (FILE *source, void const *handler_arg)
{
  struct randread_source *s = xmalloc (sizeof *s);
  memset(s, 0, sizeof *s);
  s->source = source;
  s->handler = randread_error;
  s->handler_arg = handler_arg;
  return s;
}

Программа shuf теперь работает. Новый список невыполненных тестов стал существенно короче:

FAIL: tests/pr/pr-tests.pl
FAIL: tests/misc/date-debug.sh
FAIL: tests/misc/tac.pl
FAIL: tests/misc/yes.sh
FAIL: tests/du/long-from-unreadable.sh
FAIL: tests/rmdir/symlink-errors.sh

Следующей исправим программу уes

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

do
    {
      size_t operand_len = strlen (*operandp);
      bufalloc += operand_len + 1;
      if (operandp + 1 < operand_lim
          && *operandp + operand_len + 1 != operandp[1])
        reuse_operand_strings = false;
    }
  while (++operandp < operand_lim);

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

Добавляем специальный код для защищённого режима E2K

#ifdef __e2k__
  if (sizeof(void*) == 16) 
    { // in protected mode, pointers are 128-bit
      reuse_operand_strings = false;
    }
  #endif

Новый список невыполненных тестов:

FAIL: tests/pr/pr-tests.pl
FAIL: tests/misc/date-debug.sh
FAIL: tests/misc/tac.pl
FAIL: tests/misc/tac-2-nonseekable.sh
FAIL: tests/du/long-from-unreadable.sh
FAIL: tests/rmdir/symlink-errors.sh

Теперь появился новый сбойнувший тест tac, который, видимо, зависел от работы yes.

Как Эльбрус помог исправить уязвимость в tar

Начало истории: https://blog.handydev.com/admin/edit-content/compiling-apps-in-protected-mode

Эльбрус в защищённом режиме всё-таки выявил настоящую уязвимость в программе tar c доступом в неинициализированную область памяти! Поиск и исправление занял не очень много времени, т.к. сбойнул лишь один тест, а программа компактная.

Не проходил тест, добавленный этим комитом: https://git.savannah.gnu.org/cgit/tar.git/commit/?id=336519aa4f81415a34064d3342ce1d984be5f290

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

Как несложно убедиться в файле buffer.c, метод gnu_flush_read читает входной файл блоками размером с record_size, но не переживает, если прочитано на самом деле меньше. Ну а далее, разумеется, все эти 10240 байт случайной памяти попадают на вход других методов, понятно, что это плохо.

Исправление было несложным.

if(status < record_size) 
        {
          //We didn't read the entire buffer, so we need to fill it with zeroes
          size_t unreadSize = record_size - status;
          memset(record_start->buffer + status, 0, unreadSize);
        }

После строки 1522 закрывают уязвимость. Теперь пропатченный tar проходит все тесты в защищённом режиме!

## ------------- ##
## Test results. ##
## ------------- ##

219 tests were successful.
19 tests were skipped.

Патч отправлен: https://savannah.gnu.org/patch/index.php?10081

Компиляция приложений Эльбрус в защищённом режиме

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

Многие опробованные программы "с лёту" скомпилировать в защищённом режиме не удалось, т.к. они имеют зависимости от 64-битных библиотек, т.е., нужно по цепочке компилировать всё окружение.

Из полезных приложений, легко оказалось скомпилировать tar:

./configure CFLAGS='-m128' LDFLAGS='-m128'
make

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

188: updating short archives                         FAILED (shortupd.at:34)

Кажется, тут стоит копнуть поглубже - возможно, защищённый режим выявил баги в tar.

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

Интересно, что tar в -m128 получился 2.6 Мб, а в обычном режиме - 3.8 Мб.

Скорость работы защищённого режима Эльбрус. Наивный тест.

Начнём с наивного сравнения скорости заполнения массива целых чисел размером в 1 миллион ячеек.

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

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

protected_utils.h

#define E2K_PROTECTED_MODE sizeof(void*) == 16

protected_benchmark.c

#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <stdint.h>
#include "protected_utils.h"

void main() {
    int sizeOfArray = 1000000;
    int numberOfRuns = 50;

    unsigned long accumulatedDuration = 0;

    for(int retry = 0; retry < numberOfRuns; retry++) {
        struct timeval timestampBegin;
        gettimeofday(&timestampBegin, NULL);

        int x[sizeOfArray];
        for(int i = 0; i < sizeOfArray; i++ ) {
            x[i] = i;
        }

        struct timeval timestampEnd;
        gettimeofday(&timestampEnd, NULL);

        unsigned long timeInterval = 1000000 * (timestampEnd.tv_sec - timestampBegin.tv_sec) +
        timestampEnd.tv_usec - timestampBegin.tv_usec;
        accumulatedDuration += timeInterval;
    }

    printf("Array assignment benchmark executed in: %lu\n", accumulatedDuration / (unsigned long)numberOfRuns);
    if(E2K_PROTECTED_MODE) {
        printf("Benchmark executed in protected mode.\n");
    } else {
        printf("Benchmark executed in non-protected mode.\n");
    }
}

Обычный режим:

Array assignment benchmark executed in: 10071
Benchmark executed in non-protected mode.

Что интересно, в обычном режиме многократный запуск теста практически не влияет на результаты, разброс в пределах 0.6%

Защищённый режим:

Array assignment benchmark executed in: 19899
Benchmark executed in protected mode.

Однако, многократный запуск даёт разброс вплоть до 23057, что составляет разницу в целых 15%.

Если сравнивать минимальные результаты, то на таком наивном тесте без оптимизаций защищенный режим медленнее обычного на 98%.

Скомпилируем наш пример с оптимизацией О3.

Обычный режим.

Array assignment benchmark executed in: 507
Benchmark executed in non-protected mode.

Защищённый режим.

Array assignment benchmark executed in: 5322
Benchmark executed in protected mode.

При этом в защищённом режиме практически пропал разброс результатов между запусками, но он стал на 950% медленнее обычного.

Пока сложно сказать, что именно вызывает такую разницу - плохие оптимизации lcc в режиме m128 или физические ограничения защищённого режима на уровне процессора.

Защищённый режим Эльбрус. Введение.

Здесь и далее работаем на E8C-SWTX с 4 восьмиядерными процессорами.

Для понимания того, что такое защищённый режим, нужно обязательно прочитать: http://ftp.altlinux.org/pub/people/mike/elbrus/docs/elbrus_prog/html/chapter11.html

Поэкспериментируем с возможностями защищённого режима.

Сначала - простейший пример.

#include <stdio.h>

void main() {
        int testArray[14];
        testArray[15] = 0;
        printf("We should have crashed before!\n");
}

В обычном режиме:

We should have crashed before!

(То есть, здесь и далее - мы успешно вылезли за законную выделенную память). В защищённом режиме:

Ошибка сегментирования

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

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

#include <stdio.h>
#include <stdlib.h>

void main() {
        int arraySize;
        printf("Enter array size\n");
        scanf("%d", &arraySize);
        int *testArray = (int*)malloc(arraySize * sizeof(int));
        testArray[arraySize] = 0;
        printf("We should have crashed before!\n");
}

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

Enter array size
10
We should have crashed before!

В защищённом режиме:

Enter array size
10
Ошибка сегментирования

Поэкспериментируем с неинициализированными данными.

#include <stdio.h>
#include <stdlib.h>

void main() {
        int arraySize;
        printf("Enter array size\n");
        scanf("%d", &arraySize);
        int *testArray = (int*)malloc(arraySize * sizeof(int));
        printf("We shouldn't output this uninitalized data: %d\n", testArray[0]);
}

В обычном режиме мы можем читать неинициализированные данные:

Enter array size
10
We shouldn't output this uninitalized data: 0

В защищённом режиме:

Enter array size
10
Недопустимая инструкция

Функции защищённого режиме поддерживаются как в С, так и в С++.

Проверить выполняемый файл на предмет: обычный он или защищённый, легко командой ldd

Обычное приложение:

libc.so.6 => /lib64/libc.so.6 (0x000046382dade000)
/lib64/ld-linux.so.2 (0x000046382da2b000)

Защищённое приложение:

libc.so.6 => /lib128/libc.so.6 (0x0000000050ea5000)
/lib128/ld-linux.so.2 (0x0000000050180000)

Различие в размере для простейшей программы (пример 3) без оптимизации:

7992/16336 байт (обычный/защищённый режим).

С оптимизацией O3:

7984/16336 байт.

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