address sanitizer или как сделать программы на c/с++ надежнее и...
TRANSCRIPT
AddressSanitizer Как сделать программы на C/С++
надежнее и безопаснее
Костя СеребряныйGoogle
План доклада
● Ошибки доступа в память (C/C++)● AddressSanitizer -- инструмент для
поиска ошибок○ Что делает○ Как работает
Почему С или С++ ?
● Всё написано на C или C++○ Даже если Вы об этом не знаете
● Виртуальные машины (Java, Perl, Python)
● Базы данных (MySQL)
● Веб серверы (Apache)
● Все остальное тоже (libpng, libz, memcached)
C++ в Google
● Всего > 100М строк кода○ Больше всего кода на С++
● Серверная часть○ Mapreduce, Bigtable, Spanner, Chubby, ...
● Chrome (> 10M строк)
Плата за эффективность
+ "Ручное" управление памятью - Ошибки использования памяти * Переполнение буфера * Использование после free
Открытые ворота для хакеров
Что же делать?
● Бинарная инструментация○ Valgrind, Dr.Memory, Intel Parallel Studio, Purify,
Bounds Checker, Insure++, ... ○ Медленно (> 20x), только heap
● Отладочный malloc○ Постраничная защита
■ electric fence, libgmalloc, Page Heap○ Магические значения○ Находят не всё, медленно, только heap
AddressSanitizer
● Инструментирующий компилятор + библиотека
● Май 2011: первая версия○ Май 2012: входит в LLVM 3.1
● clang -faddress_sanitizer a.c
Кстати: Clang, LLVM
● Компилятор C/C++ (opensource)● Независимые модули
○ C++ frontend○ Статический анализ, диагностика○ Оптимизатор○ Кодогенератор (x86, ARM, ...)
● Основной компилятор на MacOS, iOS● Активно используется в Google● Сопоставим с GCC по производительности
AddressSanitizer
● Переполнение буфера○ Динамические объекты○ Стековые объекты ○ Глобальные объекты
● Использование после free()● Двойной free, пересечение параметров
memcpy, и др.
Самое главное
● Среднее замедление: < 2x○ 3x-3.5x на очень больших бинарниках (кэш
инструкций)
● Клиентские приложения почти не замедляются
○ Chrome, Firefox
Пример global-buffer-overflowint global_array[100] = {-1};int main(int argc, char **argv) { return global_array[argc + 100]; // BOOM}% clang++ -O1 -faddress-sanitizer example_GlobalOutOfBounds.cc ; ./a.out
==10538== ERROR: AddressSanitizer global-buffer-overflow READ of size 4 at 0x000000415354 thread T0 #0 0x402481 in main example_GlobalOutOfBounds.cc:3 #1 0x7f0a1c295c4d in __libc_start_main ??:0 #2 0x402379 in _start ??:0
0x000000415354 is located 4 bytes to the right of global variable 'global_array' (0x4151c0) of size 400
Пример stack-buffer-overflowint main(int argc, char **argv) { int stack_array[100]; stack_array[1] = 0; return stack_array[argc + 100]; } // BOOM% clang++ -O1 -faddress-sanitizer example_StackOutOfBounds.cc; ./a.out
==10589== ERROR: AddressSanitizer stack-buffer-overflowREAD of size 4 at 0x7f5620d981b4 thread T0 #0 0x4024e8 in main example_StackOutOfBounds.cc:4 #1 0x7f5621db6c4d in __libc_start_main ??:0 #2 0x402349 in _start ??:0
Address 0x7f5620d981b4 is located at offset 436 in frame <main> of T0's stack: This frame has 1 object(s): [32, 432) 'stack_array'
Пример heap-buffer-overflowint main(int argc, char **argv) { int *array = new int[100]; int res = array[argc + 100]; // BOOM delete [] array; return res; }% clang++ -O1 -faddress-sanitizer example_HeapOutOfBounds.cc; ./a.out==10565== ERROR: AddressSanitizer heap-buffer-overflowREAD of size 4 at 0x7fe4b0c76214 thread T0 #0 0x40246f in main example_HeapOutOfBounds.cc:3 #1 0x7fe4b0cb4c4d in __libc_start_main ??:0
0x7fe4b0c76214 is located 4 bytes to the right of 400-byte region [0x7fe..., 0x7fe...)allocated by thread T0 here: #0 0x402c36 in operator new[](unsigned long) _asan_rtl_ #1 0x402422 in main example_HeapOutOfBounds.cc:3
Пример heap-use-after-freeint main(int argc, char **argv) { int *array = new int[100]; delete [] array; return array[argc]; } // BOOM% clang++ -O1 -faddress-sanitizer example_UseAfterFree.cc; ./a.out==30226== ERROR: AddressSanitizer heap-use-after-freeREAD of size 4 at 0x7faa07fce084 thread T0 #0 0x40433c in main example_UseAfterFree.cc:4
0x7faa07fce084 is located 4 bytes inside of 400-byte region [0x7fa...,x7fa...)freed by thread T0 here: #0 0x4058fd in operator delete[](void*) _asan_rtl_ #1 0x404303 in main example_UseAfterFree.cc:4
previously allocated by thread T0 here: #0 0x405579 in operator new[](unsigned long) _asan_rtl_ #1 0x4042f3 in main example_UseAfterFree.cc:2
КАК ЖЕ ЭТО ВСЕ РАБОТАЕТ?
Теневой (shadow) байт
0
7
6
5
4
3
2
1
-1
● У любых выравненных 8-и байт программы одно из 9-ти состояний:
○ N хороших байт○ (8 - N) плохих
Addressable
Unaddressable
Shadow
Хорошие
Плохие
Тень
Отображение память тень
Виртуальные адреса:Тень = Память / 8
0xffffffff0x20000000
0x1fffffff0x04000000
0x03ffffff0x00000000
Память
Тень
Закрыто
Или так:0xffffffff0x40000000
0x3fffffff0x28000000
0x27ffffff0x24000000
0x23ffffff0x20000000
0x1fffffff0x00000000
Тень = Память / 8 + 0x20000000
Память
Тень
Закрыто
Инструментирование: 8 байт*a = ...
char *shadow = (a>>3)+Offset;if (*shadow)
ReportError(a);*a = ...
Инструментирование: < 8 байт*a = ...
char *shadow = (a>>3)+Offset;if (*shadow && *shadow <= ((a&7)+N-1)) ReportError(a);*a = ...
Немножко ассемблераshr $0x3,%rax # shift by 3 mov $0x100000000000,%rcx or %rax,%rcx # add offsetcmpb $0x0,(%rcx) # load shadowje 1f <foo+0x1f>ud2a # generate SIGILL*
movq $0x1234,(%rdi) # original store
* May use call instead of UD2
Стековые переменныеvoid foo() { char a[328];
<------------- CODE ------------->
}
Стековые переменныеvoid foo() { char rz1[32]; // 32-byte aligned char a[328]; char rz2[24]; char rz3[32]; int *shadow = (&rz1 >> 3) + kOffset; shadow[0] = 0xffffffff; // poison rz1 shadow[11] = 0xffffff00; // poison rz2 shadow[12] = 0xffffffff; // poison rz3 <------------- CODE -------------> shadow[0] = shadow[11] = shadow[12] = 0; }
Глобальные переменные
int a;
struct { int original; char redzone[60];} a; // 32-aligned
Библиотека
● Ининциализация теневой памяти● Замена malloc/free
○ Отравленные области вокруг malloc○ Отравление памяти при free, карантин○ Сохранение стеков malloc/free
● Перехват memset, strlen, и т.п. ● Вывод сообщений
Трофеи
● Chromium (включая WebKit); в первые 10 месяцев○ heap-use-after-free: 201○ heap-buffer-overflow: 73○ global-buffer-overflow: 8○ stack-buffer-overflow: 7○ Google выплатил > $100k внешним исследователям
● Сотни ошибок в серверных приложениях Google● Firefox, FreeType, FFmpeg, WebRTC, libjpeg-turbo ● Perl, Vim, LLVM, GCC● MySQL
● А у Вас баги есть?
Есть что улучшить
● Статический анализ○ Меньше проверок
● Инструментировать всё○ Библиотеки, Ассемблер
● Адаптировать для ядра
● Портировать на Windows○ Уже работает для С
Короче...
● AddressSanitizer: ○ Находит много ошибок в коде на C/C++ ○ Очень быстрый○ Можно использовать при тестировании○ ... и в боевом режиме (осторожно)○ Работает на Linux, MacOS, Android○ Часть LLVM
○ clang.llvm.org/docs/AddressSanitizer.html
А еще у нас есть...
● ThreadSanitizer○ Находит гонки (data races)○ С++ и Go
● MemorySanitizer○ Находит использование
неинициализированных данных (С++)
Q&A
Valgrind ASan MSan
Heap out-of-bounds YES YES NO
Stack out-of-bounds NO YES NO
Global out-of-bounds NO YES NO
Use-after-free YES YES NO
Use-after-return NO Sometimes NO
Uninitialized reads YES NO YES
CPU Overhead 10x-300x 1.5x-3x 3x
ASan/MSan vs Valgrind