Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом пошаговом руководстве создайте проверенные сборки, которые находят и сообщают об ошибках безопасности памяти.
Ошибки безопасности памяти, такие как внеграничные операции чтения и записи памяти, использование памяти после освобождения, NULL
разыменовки указателя и т. д., являются основной проблемой для кода C/C++. Санитизатор адресов (ASAN) — это технология компилятора и среды выполнения, которая предоставляет такие типы ошибок, которые трудно найти, и делает это с нулевыми ложными срабатываниями. Общие сведения об ASAN см. в разделе AddressSanitizer.
Продолжить ошибку (COE) — это новая функция ASAN, которая автоматически диагностировать и сообщает об ошибках безопасности памяти при запуске приложения. Когда программа завершает работу, сводка ошибок безопасности памяти выводится в stdout
файл stderr
журнала или в файл журнала. При создании стандартной сборки -fsanitizer=address
C++ с помощью вызовов распределителей, сделок, таких как free
, memcpy
memset
и т. д., пересылаются в среду выполнения ASAN. Среда выполнения ASAN предоставляет ту же семантику для этих функций, но отслеживает, что происходит с памятью. ASAN диагностирует и сообщает скрытые ошибки безопасности памяти с нуля ложных срабатываний при запуске приложения.
Значительное преимущество COE заключается в том, что, в отличие от предыдущего поведения ASAN, программа не перестает работать при обнаружении первой ошибки памяти. Вместо этого ASAN заметит ошибку и приложение продолжает работать. После выхода приложения сводка всех проблем с памятью выводится.
Рекомендуется создать проверенную сборку приложения C или C++ с включенной функцией ASAN, а затем запустить приложение в тестовом ремень. При выполнении тестов пути кода в приложении, который ищет ошибки, вы также узнаете, будут ли эти пути кода портить проблемы безопасности памяти без вмешательства в тесты.
По завершении приложения вы получите сводку о проблемах с памятью. С помощью COE можно скомпилировать и развернуть существующее приложение в ограниченной рабочей среде, чтобы найти проблемы с безопасностью памяти. Для полного выполнения кода можно запустить установленную сборку в течение нескольких дней, хотя приложение будет работать медленнее из-за инструментирования ASAN.
Эту функцию можно использовать для создания нового шлюза доставки. Если все существующие тесты проходят, но COE сообщает об ошибке безопасности памяти или утечке, не отправляет новый код или не интегрируйте его в родительскую ветвь.
Не развертывайте сборку с поддержкой COE в рабочей среде! COE предназначен для использования только в средах тестирования и разработки. Не следует использовать сборку ASAN в рабочей среде из-за влияния производительности инструментирования, добавленного для обнаружения ошибок памяти, риска предоставления внутренней реализации при обнаружении ошибок, а также для предотвращения увеличения области возможных эксплойтов безопасности путем доставки функций библиотеки, которые ASAN заменяет выделение памяти, освобождение, и т. д.
В следующих примерах вы создаете проверенные сборки и задаете переменную среды для вывода сведений о дезинфикаторе адресов, чтобы stdout
увидеть ошибки безопасности памяти, которые сообщает ASAN.
Необходимые компоненты
Для выполнения этого пошагового руководства вам потребуется Visual Studio 2022 17.6 или более поздней версии с установленной рабочей нагрузкой C++.
Двойной бесплатный пример
В этом примере вы создадите сборку с поддержкой ASAN, чтобы проверить, что происходит при двойном освобождении памяти. ASAN обнаруживает эту ошибку и сообщает об этом. В этом примере программа продолжает работать после обнаружения ошибки, что приводит к второй ошибке с использованием памяти, которая была освобождена. Сводка ошибок выводится stdout
при выходе программы.
Создайте пример:
Откройте командную строку разработчика: откройте меню "Пуск ", введите "Разработчик" и выберите последнюю командную строку, например командную строку разработчика для VS 2022 в списке совпадений.
Создайте каталог на компьютере, чтобы запустить этот пример. Например,
%USERPROFILE%\Desktop\COE
.В этом каталоге создайте пустой исходный файл. Например:
doublefree.cpp
Вставьте в файл приведенный ниже код.
#include <stdio.h> #include <stdlib.h> void BadFunction(int *pointer) { free(pointer); free(pointer); // double-free! } int main(int argc, const char *argv[]) { int *pointer = static_cast<int *>(malloc(4)); BadFunction(pointer); // Normally we'd crash before this, but with COE we can see heap-use-after-free error as well printf("\n\n******* Pointer value: %d\n", *pointer); return 1; }
В предыдущем коде pointer
освобождается дважды. Это готовый пример, но двойные освобождения являются легкой ошибкой, чтобы сделать в более сложном коде C++.
Создайте сборку предыдущего кода с включенным COE, выполнив следующие действия:
- Скомпилируйте код в командной строке разработчика, открывшейся ранее:
cl -fsanitize=address -Zi doublefree.cpp
Переключатель-fsanitize=address
включает ASAN и-Zi
создает отдельный PDB-файл, который использует санитизатор адресов для отображения сведений о расположении ошибки памяти. - Отправьте выходные данные
stdout
ASAN, задавASAN_OPTIONS
переменную среды в командной строке разработчика следующим образом:set ASAN_OPTIONS=continue_on_error=1
- Запустите тестовый код с помощью:
doublefree.exe
В выходных данных показано, что произошла двойная бесплатная ошибка и стек вызовов. Отчет начинается с стека вызовов, в который показано, что ошибка произошла в BadFunction
:
==22976==ERROR: AddressSanitizer: attempting double-free on 0x01e03550 in thread T0:
#0 free D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_malloc_win_thunk.cpp(69)
#1 BadFunction C:\Users\xxx\Desktop\COE\doublefree.cpp(8)
#2 main C:\Users\xxx\Desktop\COE\doublefree.cpp(14)
#3 __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(288)
#4 BaseThreadInitThunk Windows
#5 RtlInitializeExceptionChain Windows
Далее есть сведения о освобожденной памяти и стеке вызовов, для которого выделена память:
0x01e03550 is located 0 bytes inside of 4-byte region [0x01e03550,0x01e03554)
freed by thread T0 here:
#0 free D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_malloc_win_thunk.cpp(69)
#1 BadFunction C:\Users\xxx\Desktop\COE\doublefree.cpp(7)
#2 main C:\Users\xxx\Desktop\COE\doublefree.cpp(14)
#3 __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(288)
#4 BaseThreadInitThunk Windows
#5 RtlInitializeExceptionChain Windows
previously allocated by thread T0 here:
#0 malloc D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_malloc_win_thunk.cpp(85)
#1 main C:\Users\xxx\Desktop\COE\doublefree.cpp(13)
#2 __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(288)
#3 BaseThreadInitThunk Windows
#4 RtlInitializeExceptionChain Windows
Затем есть сведения об ошибке после использования кучи. Это относится к использованию *pointer
в вызове printf()
, так как память pointer
относится к освобожденной ранее. Стек вызовов, в котором отображается ошибка, как и стеки вызовов, в которых выделена и освобождена эта память:
==35680==ERROR: AddressSanitizer: heap-use-after-free on address 0x02a03550 at pc 0x00e91097 bp 0x012ffc64 sp 0x012ffc58READ of size 4 at 0x02a03550 thread T0
#0 main C:\Users\xxx\Desktop\Projects\ASAN\doublefree.cpp(18)
#1 __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(288)
#2 BaseThreadInitThunk Windows
#3 RtlInitializeExceptionChain Windows
0x02a03550 is located 0 bytes inside of 4-byte region [0x02a03550,0x02a03554)
freed by thread T0 here:
#0 free D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_malloc_win_thunk.cpp(69)
#1 BadFunction C:\Users\xxx\Desktop\Projects\ASAN\doublefree.cpp(7)
#2 main C:\Users\xxx\Desktop\Projects\ASAN\doublefree.cpp(14)
#3 __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(288)
#4 BaseThreadInitThunk Windows
#5 RtlInitializeExceptionChain Windows
previously allocated by thread T0 here:
#0 malloc D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_malloc_win_thunk.cpp(85)
#1 main C:\Users\xxx\Desktop\Projects\ASAN\doublefree.cpp(13)
#2 __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(288)
#3 BaseThreadInitThunk Windows
#4 RtlInitializeExceptionChain Windows
Далее есть сведения о теневых байтах в окрестностях переполнения буфера. Дополнительные сведения о теневых байтах см. в статье AddressSanitizer теневого байта.
После теневых байтовых данных вы увидите выходные данные программы, которые указывают на то, что он продолжал работать после того, как ASAN обнаружил ошибку:
******* Pointer value: xxx
Затем есть сводка исходных файлов, в которых произошла ошибка памяти. Он сортируется по уникальным стекам вызовов для ошибок памяти в этом файле. Уникальный стек вызовов определяется типом ошибки и стеком вызовов, в котором произошла ошибка.
Эта сортировка определяет проблемы безопасности памяти, которые могут быть наиболее актуальными. Например, пять уникальных стеков вызовов, приводящих к разным ошибкам безопасности памяти в одном файле, потенциально более тревожным, чем одна ошибка, которая возникает много раз. Сводка выглядит следующим образом:
=== Files in priority order ===
File: D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_malloc_win_thunk.cpp Unique call stacks: 1
File: C:\Users\xxx\Desktop\COE\doublefree.cpp Unique call stacks: 1
Наконец, отчет содержит сводку о том, где произошли ошибки памяти:
=== Source Code Details: Unique errors caught at instruction offset from source line number, in functions, in the same file. ===
File: D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_malloc_win_thunk.cpp
Func: free()
Line: 69 Unique call stacks (paths) leading to error at line 69 : 1
Bug: double-free at instr 19 bytes from start of line
File: C:\Users\xxx\Desktop\COE\doublefree.cpp
Func: main()
Line: 18 Unique call stacks (paths) leading to error at line 18 : 1
Bug: heap-use-after-free at instr 55 bytes from start of line
>>>Total: 2 Unique Memory Safety Issues (based on call stacks not source position) <<<
#0 C:\Users\xxx\Desktop\COE\doublefree.cpp Function: main(Line:18)
Raw HitCnt: 1 On Reference: 4-byte-read-heap-use-after-free
#1 D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_malloc_win_thunk.cpp Function: free(Line:69)
Raw HitCnt: 1
Пример доступа к памяти вне границ
В этом примере вы создадите сборку с поддержкой ASAN, чтобы проверить, что происходит при доступе к памяти приложения, которая является внеграничной. ASAN обнаруживает эту ошибку и сообщает сводку об ошибках stdout
при выходе программы.
Создайте пример:
Откройте командную строку разработчика: откройте меню "Пуск ", введите "Разработчик" и выберите последнюю командную строку, например командную строку разработчика для VS 2022 в списке совпадений.
Создайте каталог на компьютере, чтобы запустить этот пример. Например,
%USERPROFILE%\Desktop\COE
.В этом каталоге создайте исходный файл, например,
coe.cpp
и вставьте следующий код:#include <stdlib.h> char* func(char* buf, size_t sz) { char* local = (char*)malloc(sz); for (auto ii = 0; ii <= sz; ii++) // bad loop exit test { local[ii] = ~buf[ii]; // Two memory safety errors } return local; } char buffer[10] = {0,1,2,3,4,5,6,7,8,9}; int main() { char* inverted_buf= func(buffer, 10); }
В предыдущем коде параметр sz
равен 10, а исходный буфер равен 10 байтам. Существует две ошибки безопасности памяти:
- Внеграничная загрузка из
buf
for
цикла - внеграничное хранилище
local
в циклеfor
Переполнение буфера связано с тестом <=sz
выхода цикла. При выполнении этого примера это безопасно по совпадению. Это связано с чрезмерной выделением и выравниванием, выполняемой большинством реализаций среды выполнения C++. Когда sz % 16 == 0
последняя запись local[ii]
в память повреждена. Другие случаи только для чтения и записи в "malloc slop", который является дополнительной памятью, выделенной из-за того, как панели выполнения C (CRT) выделяются на границу 0 мода 16.
Ошибки могут наблюдаться только в том случае, если страница после выделения не сопоставлена или при использовании поврежденных данных. Все остальные случаи молчат в этом примере. При использовании ошибки "Продолжить по ошибке" ошибки отображаются в сводке после завершения программы.
Создайте сборку предыдущего кода с включенным COE:
- Скомпилируйте код с
cl -fsanitize=address -Zi coe.cpp
помощью . Переключатель-fsanitize=address
включает ASAN и-Zi
создает отдельный PDB-файл, который использует санитизатор адресов для отображения сведений о расположении ошибки памяти. - Отправьте выходные данные
stdout
ASAN, задавASAN_OPTIONS
переменную среды в командной строке разработчика следующим образом:set ASAN_OPTIONS=continue_on_error=1
- Запустите тестовый код с помощью:
coe.exe
Выходные данные показывают, что были две ошибки переполнения буфера памяти и предоставляют стек вызовов для места их возникновения. Отчет начинается следующим образом:
==9776==ERROR: AddressSanitizer: global-buffer-overflow on address 0x0047b08a at pc 0x003c121b bp 0x012ffaec sp 0x012ffae0
READ of size 1 at 0x0047b08a thread T0
#0 func C:\Users\xxx\Desktop\COE\coe.cpp(8)
#1 main C:\Users\xxx\Desktop\COE\coe.cpp(18)
#2 __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(288)
#3 BaseThreadInitThunk Windows
#4 RtlInitializeExceptionChain Windows
Далее есть сведения о теневых байтах в окрестностях переполнения буфера. Дополнительные сведения о теневых байтах см. в статье AddressSanitizer теневого байта.
После отчета теневого байта содержится сводка исходных файлов, в которых произошли ошибки памяти. Он сортируется по уникальным стекам вызовов для ошибок памяти в этом файле. Уникальный стек вызовов определяется типом ошибки и стеком вызовов, в котором произошла ошибка.
Эта сортировка определяет проблемы безопасности памяти, которые могут быть наиболее актуальными. Например, пять уникальных стеков вызовов, приводящих к разным ошибкам безопасности памяти в одном файле, потенциально более тревожным, чем одна ошибка, которая возникает много раз.
Сводка выглядит следующим образом:
=== Files in priority order ===
File: C:\Users\xxx\Desktop\COE\coe.cpp Unique call stacks: 2
Наконец, отчет содержит сводку о том, где произошли ошибки памяти. Продолжить при ошибке сообщает о двух разных ошибках, возникающих в одной исходной строке. Первая ошибка считывает память по глобальному адресу в .data
разделе, а другая запись в память, выделенную из кучи.
Отчет выглядит следующим образом:
=== Source Code Details: Unique errors caught at instruction offset from source line number, in functions, in the same file. ===
File: C:\Users\xxx\Desktop\COE\coe.cpp
Func: func()
Line: 8 Unique call stacks (paths) leading to error at line 8 : 2
Bug: heap-buffer-overflow at instr 124 bytes from start of line
>>>Total: 2 Unique Memory Safety Issues (based on call stacks not source position) <<<
#0 C:\Users\xxx\Desktop\COE\coe.cpp Function: func(Line:8)
Raw HitCnt: 1 On Reference: 1-byte-read-global-buffer-overflow
#1 C:\Users\xxx\Desktop\COE\coe.cpp Function: func(Line:8)
Raw HitCnt: 1 On Reference: 1-byte-write-heap-buffer-overflow
Поведение среды выполнения Sanitizer по умолчанию завершает работу приложения после сообщения о первой ошибке, обнаруженной. Это не позволяет выполнять инструкцию "плохого" компьютера. Новая среда выполнения Sanitizer адреса обнаруживает и сообщает об ошибках, но затем выполняет последующие инструкции.
COE пытается автоматически вернуть элемент управления в приложение после создания отчетов о каждой ошибке безопасности памяти. Существуют ситуации, когда это невозможно, например при нарушении доступа к памяти (AV) или сбое выделения памяти. COE не продолжается после нарушений доступа, которые структурированная обработка исключений программы не перехватывает. Если COE не может вернуть выполнение в приложение, CONTINUE CANCELLED - Deadly Signal. Shutting down.
сообщение выводится.
Выберите место отправки выходных данных ASAN
Используйте переменную среды, чтобы определить, куда отправлять выходные ASAN_OPTIONS
данные ASAN следующим образом:
- Выходные данные в stdout:
set ASAN_OPTIONS=continue_on_error=1
- Выходные данные в stderr:
set ASAN_OPTIONS=continue_on_error=2
- Выходные данные в файл журнала по вашему выбору:
set COE_LOG_FILE=yourfile.log
Обработка неопределенного поведения
Среда выполнения ASAN не имитирует все неопределенное поведение функций выделения и распределения на C++. В следующем примере показано, как версия ASAN _alloca отличается от версии среды выполнения C:
#include <cstdio>
#include <cstring>
#include <malloc.h>
#include <excpt.h>
#include <windows.h>
#define RET_FINISH 0
#define RET_STACK_EXCEPTION 1
#define RET_OTHER_EXCEPTION 2
int foo_redundant(unsigned long arg_var)
{
char *a;
int ret = -1;
__try
{
if ((arg_var+3) > arg_var)
{
// Call to _alloca using parameter from main
a = (char *) _alloca(arg_var);
memset(a, 0, 10);
}
ret = RET_FINISH;
}
__except(1)
{
ret = RET_OTHER_EXCEPTION;
int i = GetExceptionCode();
if (i == EXCEPTION_STACK_OVERFLOW)
{
ret = RET_STACK_EXCEPTION;
}
}
return ret;
}
int main()
{
int cnt = 0;
if (foo_redundant(0xfffffff0) == RET_STACK_EXCEPTION)
{
cnt++;
}
if (cnt == 1)
{
printf("pass\n");
}
else
{
printf("fail\n");
}
}
В main()
большом количестве передается, в которое в конечном итоге передаетсяfoo_redundant
_alloca()
, что приводит _alloca()
к сбою.
В этом примере выходные pass
данные при компиляции без ASAN (т. е. без -fsanitize=address
коммутатора), но выходные данные fail
при компиляции с включенным ASAN (то есть с параметром -fsanitize=address
). Это связано с тем, что без ASAN код исключения соответствует RET_STACK_EXCEPTION
cnt
1. Он ведет себя по-разному при компиляции с помощью ASAN, так как возникающее исключение является ошибкой Sanitizer address вместо этого: dynamic-stack-buffer-overflow. Это означает, что вместо этого код возвращает RET_OTHER_EXCEPTION
RET_STACK_EXCEPTION
cnt
значение 1.
Другие преимущества
При использовании новой среды выполнения ASAN дополнительные двоичные файлы не нужно развертывать с помощью приложения. Это упрощает использование ASAN в обычном тестовом использовании, так как вам не нужно управлять дополнительными двоичными файлами.
См. также
АдресSanitizer Continue on Error blog post
Примеры ошибок безопасности памяти
Флаг компилятора -Zi
-fsanitize=адрес компилятора
Топ-25 самых опасных уязвимостей программного обеспечения