67
Динамический анализ программ на C++ Алексей Самсонов, Google Екатеринбург, УрФУ, 29.04.2013

20130429 dynamic c_c++_program_analysis-alexey_samsonov

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Динамический анализ программ на C++Алексей Самсонов, Google

Екатеринбург, УрФУ, 29.04.2013

Page 2: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Команда (Google RU-MSK)

Константин Серебряный (TL)Дмитрий ВьюковТимур ИсходжановСергей МатвеевАлександр ПотапенкоАлексей СамсоновЕвгений Степанов

Page 3: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Содержание

Часть 1● Ошибки работы с памятью● AddressSanitizer

Часть 2● Гонки● ThreadSanitizer

Page 4: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

В мире много кода на C и C++

● Операционные системы● Браузеры (Chromium, Firefox, Safari)● Виртуальные машины (Java)● Базы данных (MySQL)● Backend веб-сервисов (поиск)● Веб-серверы (Apache, nginx)

Page 5: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Ошибки работы с памятью в C/C++

● Кодеки и рендереры (ffmpeg, pdf)● Использование освобождённой памяти

(use-after-free)● Использование неинициализированной

памяти (uninitialized-memory-read)● Утечки памяти (memory leaks)● Неверный порядок

инициализации/уничтожения глобальных объектов

Page 6: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Ошибки работы с памятью в C/C++

● Выход за границы буфера (out-of-bound access)

● Использование освобождённой память (use-after-free)

● Использование неинициализированной памяти (uninitialized-memory-read)

● Утечки памяти (memory leaks)● Неверный порядок

инициализации/уничтожения глобальных объектов

Page 7: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Выход за границы буфера (стек)void authorize() { ... char username[128]; gets(username); // 128 bytes should be // enough for everyone. ...}

Page 8: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Использование освобожденной памяти~UserTable() { ... delete all_users; LOG() << "Deleted " << all_users->size() << " users!";}

Можно ли сделать этот код ещё хуже?

Page 9: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Использование освобожденной памяти~UserTable() { ... delete all_users; RunAction(all_users->action_on_delete);}

Page 10: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Всегда в моде: racy use-after-free

Поток 1

void DeleteEntries() { have_entries = false; delete entries;}

Поток 2

Entry *GetEntry(int i) { if (have_entries) return entries[i]; return 0;}

Page 11: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Инструменты для поиска ошибок

● Valgrind/Memcheck● DynamoRIO/Dr. Memory● Purify● Insure++● Mudflap● Electric Fence / Page Heap / Guard MallocПроблемы:● Замедление программы (в 10 и более раз)● Поиск ошибок только для динамически

выделенной памяти (heap)

Page 12: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Статический анализ программ

● Формальная верификация?● Clang Static Analyzer?

Проблемы:● Баланс между эффективностью и

полезностью● Баланс между false negative и false

positive

Page 13: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Есть ли в программе ошибка?

int array[10] = {...};

int main(int argc, char *argv[]) { return array[2 + argc];}

Page 14: 20130429 dynamic c_c++_program_analysis-alexey_samsonov
Page 15: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Пример отчёта об ошибке (1)int global_array[100] = {-1};int main(int argc, char **argv) { return global_array[argc + 100];}

==10538== ERROR: AddressSanitizer global-buffer-overflowREAD of size 4 at 0x000000415354 thread T0 #0 0x402481 in main a.cc:3<...>0x000000415354 is located 4 bytes to the right of global variable 'global_array' (0x4151c0) of size 400

Page 16: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Пример отчёта об ошибке (2)int main(int argc, char **argv) { int stack_array[100]; stack_array[1] = 0; return stack_array[argc + 100];}

==10589== ERROR: AddressSanitizer stack-buffer-overflowREAD of size 4 at 0x7f5620d981b4 thread T0 #0 0x4024e8 in main a.cc:4Address 0x7f5620d981b4 is located at offset 436 in frame <main> of T0's stack:

This frame has 1 object(s): [32, 432) 'stack_array'

Page 17: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Пример отчёта об ошибке (3)int main(int argc, char **argv) { int *array = new int[100]; int res = array[argc + 100]; delete [] array; return res;}==10565== ERROR: AddressSanitizer heap-buffer-overflowREAD of size 4 at 0x7fe4b0c76214 thread T0 #0 0x40246f in main a.cc:30x7fe4b0c76214 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) #1 0x402422 in main a.cc:2

Page 18: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Пример отчёта об ошибке (4)int main(int argc, char **argv) { int *array = new int[100]; delete [] array; return array[argc];}==30226== ERROR: AddressSanitizer heap-use-after-freeREAD of size 4 at 0x7faa07fce084 thread T0 #0 0x40433c in main a.cc:40x7faa07fce084 is located 4 bytes inside of 400-byte regionfreed by thread T0 here: #0 0x4058fd in operator delete[](void*) #1 0x404303 in main a.cc:3previously allocated by thread T0 here: #0 0x405579 in operator new[](unsigned long) #1 0x4042f3 in main a.cc:2

Page 19: 20130429 dynamic c_c++_program_analysis-alexey_samsonov
Page 20: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Что такое AddressSanitizer?

● Компиляторная инструментация○ Проверка всех обращений к памяти○ Добавление редзон("redzones") вокруг

глобальных и локальных переменных● Runtime-библиотека

○ Аллокатор (выделение/освобождение памяти)○ Отслеживание всех потоков○ Печать отчётов об ошибках○ Обёртки функций из стандартной библиотеки

Page 21: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Теневой байт (shadow byte)

Если адреса всех переменных кратны 8, то у любых выровненных восьми байт есть 9 состояний:

07654321-1

Page 22: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Тень адресного пространства (*)

Приложение0xFFFFFFFF0x20000000

Тень0x1FFFFFFF0x04000000

Mprotect0x03FFFFF0x0000000

Page 23: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Инструментация доступа к памяти

int *a = ...;*a = ...;

char *shadow = a >> 3;if (*shadow != 0 && *shadow < (a&7) + 4) ReportError(a);*a = ...;

Page 24: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Инструментация стекаvoid foo() { char a[328]; <------------- CODE -------------> }

Page 25: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Инструментация стекаvoid foo() { char redzone1[32]; // 32-byte aligned char a[328]; char redzone2[24]; char redzone3[32]; int *shadow = (&rz1 >> 3); shadow[0] = 0xffffffff; // poison rz1 shadow[11] = 0xffffff00; // poison rz2 shadow[12] = 0xffffffff; // poison rz3 <------------- CODE -------------> shadow[0] = shadow[11] = shadow[12] = 0;}

Page 26: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Инструментация глобальных объектов

int global;

struct { int original; char redzone[60];} global; // 32-byte aligned

Page 27: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

LLVM спешит на помощь!

Page 28: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Интеграция в LLVM

● Clang (или другой фронтенд) генерирует промежуточное представление программы;

● ASan добавляет инструментацию;● Бэкенд оптимизирует код и генерирует

объектный файл;● Исполняемый файл линкуется с runtime-

библиотекой.

Page 29: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Runtime-библиотека

● Аллокатор○ Добавление редзон вокруг блоков динамически

выделенной памяти○ Карантин для освобождённой памяти○ Хранение стека вызовов для каждой аллокации

● Печать отчётов об ошибках● Перехват библиотечных функций char bad_string[2] = {'h', 'i'}; int len = strlen(bad_string);

Page 30: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Производительность

● Замедление программы: 2x● Использование памяти: 1,5-3x● "Just works" для большинства тестов и

реальных программ● Незначительное замедление для GUI-

программ (Chromium+ASan работает без проблем)

Page 31: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Трофеи

1000+ найденных ошибок повсюду:● Chromium, Firefox, Safari● Сервисы Google● Perl, PHP, MySQL, ffmpeg, webrtc, vim● LLVM, GCC

Google заплатил $130000+ внешним исследователям за security-ошибки, найденные с помощью AddressSanitizer.

Page 32: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Что будет дальше?

● Поиск других видов ошибок (в ближайшее время: поиск утечек памяти)

● Поиск ошибок в системных и прекомпилированных библиотеках

● AddressSanitizer для ядра Linux● AddressSanitizer для Windows (нужен

компилятор).

Page 33: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Попробуйте AddressSanitizer!

● LLVM/Clang 3.1+clang++ -fsanitize=address a.cc● GCC 4.8+g++ -fsanitize=address a.cc● Платформы:Linux, Mac OS X, Android

code.google.com/p/address-sanitizer

Page 34: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Гонки (data races)

Page 35: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Что такое гонка?

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

Если в программе на C++ могут возникнуть гонки, её поведение не определено.

Компилятор имеет право считать, что в программе нет гонок.

Page 36: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Что напечатает этот код?

Поток 1

cout << x << "\n";x = 1;cout << y << "\n";

Поток 2

cout << y << "\n";y = 1;cout << x << "\n";

int x = 0, y = 0;

Page 37: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

"Безвредные" гонки

● Синхронизация замедляет программу.● Может возникнуть желание допустить

гонки на "не очень важных" данных.● Не нужно этого делать. В C++ не может

быть "безвредных" (benign) гонок.

H-J. Boehm "How to miscompile programs with "benign" data races"D. Vyukov "Benign data races: what could possibly go wrong?"

Page 38: 20130429 dynamic c_c++_program_analysis-alexey_samsonov
Page 39: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Пример "безвредной гонки"long long get_value() { return value;}

void set_value( long long x) { value = x;}

Является ли 64-битное чтение / запись атомарной операцией?

Page 40: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Пример "безвредной гонки" (2)

...int my_counter = counter; // read globalif (my_counter > my_old_counter) { my_action = print_stats;}...

if (my_counter > my_old_counter) { run_action(my_action);}

Page 41: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Пример "безвредной гонки" (3)my_action = launch_nuclear_rocket;...int my_counter = counter; // read globalif (my_counter > my_old_counter) { my_action = print_stats;}...my_counter = counter;if (my_counter > my_old_counter) { run_action(my_action);}

Page 42: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

А как же настоящие гонки?

Page 43: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Метод-лжец// Returns the user with a given IDUser& getUser(int id) { MutexLock(&mutex); CHECK(0 <= id && id < users.size()); return users[id];}

Page 44: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Метод-лжец// Returns the user with a given IDUser& getUser(int id) { MutexLock(&mutex); CHECK(0 <= id && id < users.size()); return users[id];}

Page 45: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Что не так с этим кодом?class AbstractAction { public: AbstractAction(Executor *executor) { executor->add(this); ... } ...

}

Page 46: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Что не так с этим кодом?class AbstractAction { public: AbstractAction(Executor *executor) { executor->add(this); ... } ... virtual void Run() = 0;}

Page 47: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Гонка на vptrclass AbstractAction { public: AbstractAction(Executor *executor) { executor->add(this); // переключение контекста } ... virtual void Run() = 0;}

Pure virtual call!

Page 48: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

ThreadSanitizer спешит на помощьWARNING: ThreadSanitizer: data race on vptr (ctor/dtor vs virtual call) Read of size 8 at 0x7fff103327f0 by thread T3: #0 ExecutorThread::RunAction() executor.cc:1112 #1 ExecutorThread::Start() executor.cc:1283

Previous write of size 8 at 0x7fff103327f0 by main thread: #0 MyAction::MyAction()vptr_race.cc:48 #1 main vptr_race.cc:59

Location is stack of main thread.

Page 49: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Что такое ThreadSanitizer?

● Компиляторная инструментация○ Проверка всех обращений к памяти○ Отслеживание событий: вход/выход в функцию,

обновление vptr, атомарные операции● Runtime-библиотека

○ Аллокатор (выделение/освобождение памяти)○ Отслеживание всех потоков○ Печать отчётов об ошибках○ Обёртки функций из стандартной библиотеки○ Поддержка синхронизационных примитивов

Page 50: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Инструментация (1)int *p = ...;*p = 42;

int *p = ...;__tsan_write4(p);*p = 42;

Page 51: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Инструментация (2)void foo() { ...}

void foo() { __tsan_func_entry( __builtin_return_address(0)); ... __tsan_func_exit();}

Page 52: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Инструментация (3)A::~A() { // this->vptr = A::vptr;}

A::~A() { __tsan_vptr_update(&this->vptr, A::vptr); // this->vptr = A::vptr;}

Page 53: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Runtime-библиотека

● Проверка всех доступов в память● Выделение/освобождение памяти● Поддержка синхронизации (мьютексы,

атомарные операции)● Перехват функций работы с потоками

(libpthread)● Перехват функций из стандартной

библиотеки● Печать отчётов об ошибках

Page 54: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Абстрактное время потока

Увеличивается после каждого события:

void foo() { // time = 1 Lock(&mutex); // time = 2 x = 1; // time = 3 Unlock(&mutex); // time = 4} // time = 5

Page 55: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Векторные часы (vector clock)

VC[1..N], где VC[i] - время i-го потока.

Векторные часы есть у каждого потока:VC[1..N], где VC[i] - время i-го потока в момент последней синхронизации с ним.

Векторные часы есть у каждого мьютекса:VC[1..N], VC[i] - время i-го потока, когда он в последний раз отпустил мьютекс.

Page 56: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Обновление векторных часов

Доступ к памяти в потоке T:

T->VC[T->id]++;

Page 57: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Обновление векторных часов (2)

Взятие мьютекса M в потоке T:для всех i:T->VC[i] = max(T->VC[i], M->VC[i]);

Освобождение мьютекса M в потоке T:для всех i:M->VC[i] = max(M->VC[i], T->VC[i]);

Page 58: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Как кодировать обращение к памяти?

Обращение к выровненным 8 байтам адресного пространства кодируется через:● ID потока (TID)● Время этого потока (TS)● позиция (0..7) и размер (1,2,4,8)

обращения● является ли доступ чтением или

записью?

Вся эта информация помещается в 8 байт.

Page 59: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Пример обращений к памяти

Приложение Тень

Page 60: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Запись из потока T1

Приложение Тень

T1

TS1

W

0:2

Page 61: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Чтение из потока T2

Приложение Тень

T1

TS1

W

0:2

T2

TS2

4:4

R

Page 62: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Чтение из потока T3

Приложение Тень

T1

TS1

W

0:2

T2

TS2

4:4

R

T3

TS3

0:4

R

Page 63: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Гонка?

Тень

T1

TS1

W

0:2

T2

TS2

4:4

R

T3

TS3

0:4

R

● Доступы пересекаются?

● Разные потоки?● Хотя бы одно из

обращений - запись?

● Есть ли синхронизация?

Гонка, если T3->VC[T1] < TS1

Page 64: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Как узнать стек вызовов для каждого обращения к памяти?

● Циклическая очередь всех событий в каждом потоке:○ доступ к памяти○ вход/выход из функции○ взятие/освобождение мьютекса

● "Повторение" событий перед печатью отчёта:○ Через некоторое время события "забываются"○ Неограниченный размер стека вызовов в отчёте○ Отчёт содержит множество мьютексов, взятых в

каждом потоке.

Page 65: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Результаты

● Замедление программы: 4-10x (в 10 раз быстрее аналогов).

● Использование памяти: 5-8x

● 600+ найденных ошибок● Работает для больших серверных

приложений

Page 66: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Что будет дальше?

● Портирование на другие системы (сейчас - только 64-битный Linux).

● Поиск ошибок в системных и прекомпилированных библиотеках

● Больше оптимизаций, больше пользователей

● Поиск гонок в коде на Java

Page 67: 20130429 dynamic c_c++_program_analysis-alexey_samsonov

Попробуйте ThreadSanitizer!

● LLVM/Clang 3.2+clang++ -fsanitize=thread a.cc● GCC 4.8+g++ -fsanitize=thread a.cc● Gogo build -race

code.google.com/p/thread-sanitizer