Начало истории: 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(×tampBegin, NULL);
int x[sizeOfArray];
for(int i = 0; i < sizeOfArray; i++ ) {
x[i] = i;
}
struct timeval timestampEnd;
gettimeofday(×tampEnd, 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 байт.
В следующей части будем разбираться, насколько защищённый режим влияет на скорость работы.