- Регистрация
- 21.07.20
- Сообщения
- 40.408
- Реакции
- 1
- Репутация
- 0
“C позволяет легко выстрелить себе в ногу. На C++ это сделать сложнее, но ногу оторвёт целиком” — Бьёрн Страуструп, создатель C++.
В этой статье мы покажем, как писать стабильный, безопасный и надежный код и насколько легко на самом деле его совершенно непреднамеренно поломать. Для этого мы постарались собрать максимально полезный и увлекательный материал.
Мы в SimbirSoft тесно сотрудничаем с проектом
You must be registered for see links
, обучая других разработчиков создавать безопасные решения. Специально для Хабра мы перевели
You must be registered for see links
, написанную нашим автором для портала CodeProject.com.Итак, к коду!
Здесь представлен небольшой фрагмент абстрактного кода на C++. Этот код был специально написан с целью демонстрации всевозможных проблем и уязвимостей, которые потенциально можно встретить на вполне реальных проектах. Как вы можете заметить, это код из Windows DLL (это важный момент). Предположим, что кто-то собирается использовать этот код в некоем (безопасном, разумеется) решении.
Приглядитесь к коду. Что, на ваш взгляд, в нём может пойти не так?
Код
class Finalizer
{
struct Data
{
int i = 0;
char* c = nullptr;
union U
{
long double d;
int i[sizeof(d) / sizeof(int)];
char c [sizeof(i)];
} u = {};
time_t time;
};
struct DataNew;
DataNew* data2 = nullptr;
typedef DataNew* (*SpawnDataNewFunc)();
SpawnDataNewFunc spawnDataNewFunc = nullptr;
typedef Data* (*Func)();
Func func = nullptr;
Finalizer()
{
func = GetProcAddress(OTHER_LIB, "func")
auto data = func();
auto str = data->c;
memset(str, 0, sizeof(str));
data->u.d = 123456.789;
const int i0 = data->u.i[sizeof(long double) - 1U];
spawnDataNewFunc = GetProcAddress(OTHER_LIB, "SpawnDataNewFunc")
data2 = spawnDataNewFunc();
}
~Finalizer()
{
auto data = func();
delete[] data2;
}
};
Finalizer FINALIZER;
HMODULE OTHER_LIB;
std::vector* INTEGERS;
DWORD WINAPI Init(LPVOID lpParam)
{
OleInitialize(nullptr);
ExitThread(0U);
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
static std::vector THREADS;
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
srand(time(nullptr));
OTHER_LIB = LoadLibrary("B.dll");
if (OTHER_LIB = nullptr)
return FALSE;
CreateThread(nullptr, 0U, &Init, nullptr, 0U, nullptr);
break;
case DLL_PROCESS_DETACH:
CoUninitialize();
OleUninitialize();
{
free(INTEGERS);
const BOOL result = FreeLibrary(OTHER_LIB);
if (!result)
throw new std::runtime_error("Required module was not loaded");
return result;
}
break;
case DLL_THREAD_ATTACH:
THREADS.push_back(std::this_thread::get_id());
break;
case DLL_THREAD_DETACH:
THREADS.pop_back();
break;
}
return TRUE;
}
__declspec(dllexport) int Initialize(std::vector integers, int& c) throw()
{
for (int i : integers)
i *= c;
INTEGERS = new std::vector(integers);
}
int Random()
{
return rand() + rand();
}
__declspec(dllexport) long long int __cdecl _GetInt(int a)
{
return 100 / a code>
Возможно, вы сочли этот код простым, очевидным и достаточно безопасным? Или, может быть, вы нашли в нем некоторые проблемы? А может быть, даже дюжину или две?
Что ж, на самом деле в этом фрагменте более 43 потенциальных угроз различной степени значимости!
На что стоит всё же обратить внимание
1) sizeof(d) (где d — это long double) не обязательно кратен sizeof(int)
int i[sizeof(d) / sizeof(int)];
Такая ситуация не проверяется и не обрабатывается здесь. Например, размер long double может быть 10 байт на некоторых платформах (что неверно для компилятора MS VS, но верно для RAD Studio, в прошлом известного как C++ Builder).
You must be registered for see links
(приведенный выше код предназначен для Windows, поэтому применительно именно к этой текущей ситуации проблема несколько надуманная, но для переносимого кода такая проблема весьма актуальна).Все это может стать проблемой, если мы хотим использовать так называемый
You must be registered for see links
. К слову, он
You must be registered for see links
согласно стандарту языка C++. Впрочем, использование каламбура типизации является
You must be registered for see links
, поскольку
You must be registered for see links
(как, например, это делает
You must be registered for see links
).
You must be registered for see links
Между прочим, в отличие от C++,
You must be registered for see links
(вы же понимаете, что
You must be registered for see links
, и вы не должны ожидать, что будете знать C, если вы знаете C++, и наоборот, не так ли?)Решение: используйте
You must be registered for see links
для контроля всех подобных предположений во время компиляции. Он предупредит вас, если вдруг что-то с размерами типов пойдет не так:static_assert(0U == (sizeof(d) % sizeof(int)), “Houston, we have a problem”);
2) time_t — это макрос, в Visual Studio он может ссылаться на 32-битный (старый) или 64-битный (новый) целочисленный тип
time_t time;
Доступ к переменной этого типа из разных исполняемых модулей (например, исполняемого файла и библиотеки DLL, которую он загружает) может привести к чтению/записи за границами объекта, в случае если эти два двоичных модуля скомпилированы с разным физическим представлением этого типа. Что, в свою очередь, приведёт к повреждению памяти или считыванию мусора.
Решение: убедитесь, что для обмена данными между всеми модулями используются одинаковые типы строго определенного размера:
int64_t time;
3) B.dll (хэндл которой хранит переменная OTHER_LIB) еще не загружена в момент, когда мы обращаемся к указанной выше переменной, поэтому мы не сможем получить адреса функций данной библиотеки
4) проблема с порядком инициализации статических объектов (
You must be registered for see links
): (объект OTHER_LIB в коде используется раньше, чем он был инициализирован) func = GetProcAddress(OTHER_LIB, "func");
FINALIZER — это
You must be registered for see links
объект, который создается перед вызовом функции DllMain. В его конструкторе мы пытаемся использовать библиотеку, которая еще не загружена. Проблема усугубляется тем, что статический объект OTHER_LIB, который используется статическим объектом FINALIZER, размещается в единице трансляции ниже по коду. Это означает, что инициализирован (обнулен) он также будет позже. Т. е. на момент, когда к нему будут обращаться, он будет содержать некоторый
You must be registered for see links
мусор. WinAPI в целом должен нормально отреагировать на это, потому что с высокой степенью вероятности загруженного модуля с таким дескриптором просто не будет вовсе. И даже если произойдет совершенно невероятное совпадение и он всё таки будет — вряд ли в нём будет присутствовать функция по имени «Func».Решение: общий совет — избегать использования глобальных объектов, особенно сложных, особенно если они зависят друг от друга, особенно в DLL. Однако, если они все же нужны вам по какой-либо причине, будьте предельно внимательны и осторожны с порядком их инициализации. Чтобы
You must be registered for see links
, поместите все экземпляры (определения) глобальных объектов в одну
You must be registered for see links
в нужном порядке, чтобы обеспечить их корректную инициализацию.5) возвращенный ранее результат не проверяется перед использованием
auto data = func();
func — это
You must be registered for see links
. И указывать он должен на функцию из B.dll. Однако, поскольку мы полностью провалили все действия на предыдущем шаге, это будет nullptr. Таким образом, пытаясь разыменовать его, вместо ожидаемого вызова функции мы получим ошибку нарушения прав доступа (access violation) или ошибку защиты памяти (general protection fault) или что-то в этом духе.Решение: при работе с внешним кодом (в нашем случае с WinAPI) всегда проверяйте результат возврата вызываемых функций. Для надежных и отказоустойчивых систем это правило распространяется даже на функции, для которых существует строгий договор [о том, что и в каком случае они должны возвращать].
6) считывание/запись мусора при обмене данными между модулями, скомпилированными с разными alignment/padding настройками
auto str = data->c;
Если структура Data (которая используется для обмена информацией между взаимодействующими модулями) имеет в этих самых модулях различное физическое представление, все выльется в ранее упомянутое
You must be registered for see links
,
You must be registered for see links
,
You must be registered for see links
,
You must be registered for see links
и т.д. Или же мы просто будем считывать мусор. Точный результат будет зависеть от реального сценария использования этой памяти. Все это может произойти из-за того, что для самой структуры отсутствуют явные настройки
You must be registered for see links
. Поэтому в случае, если эти глобальные настройки в момент компиляции были разными для взаимодействующих модулей, у нас появятся проблемы.Решение: убедиться, что все совместно используемые структуры данных имеют строгое, явно определенное и очевидное физическое представление (используют типы с фиксированным размером, явно указанное выравнивание и т.д.) и/или двоичные модули, взаимодействующие между собой, были скомпилированы с одинаковыми глобальными настройками выравнивания/заполнения.
Смотрите также
You must be registered for see links
You must be registered for see links
You must be registered for see links
7) использование размера указателя на массив вместо размера самого массива
memset(str, 0, sizeof(str));
Обычно такое является результатом банальной опечатки. Но также эта проблема потенциально может возникнуть, когда приходится иметь дело со
You must be registered for see links
или при бездумном использовании ключевого слова auto (
You must be registered for see links
). Очень хочется надеяться, впрочем, что современные компиляторы достаточно умны, чтобы обнаруживать такие проблемы на этапе компиляции, используя возможности
You must be registered for see links
.Решение:
— никогда не путайте sizeof () и sizeof ( );
—
You must be registered for see links
;— вы также можете использовать немного шаблонной магии С++, комбинируя typeid, constexpr и
You must be registered for see links
, чтобы гарантировать правильность типов на этапе компиляции (здесь ещё могут быть полезны
You must be registered for see links
, в частности,
You must be registered for see links
).8) неопределенное поведение при попытке читать иное поле объединения, нежели то, что ранее использовалось для установки значения
9) возможна попытка чтения за пределами допустимой области памяти, если размер long double различается между двоичными модулями
const int i0 = data->u.i[sizeof(long double) - 1U];
Это уже было упомянуто ранее, поэтому здесь мы просто получили еще одну точку присутствия ранее упомянутой проблемы.
Решение: не обращаться к другому полю, кроме того, которое было установлено ранее, если вы не уверены, что ваш компилятор обрабатывает это правильно. Убедитесь, что размеры типов общих объектов одинаковы во всех взаимодействующих модулях.
Смотрите также
You must be registered for see links
You must be registered for see links
10) даже если B.dll была правильно загружена и функция «func» правильно экспортирована и импортирована, B.dll все равно уже выгружена из памяти к данному моменту (т. к. ранее была вызвана системная функции FreeLibrary в секции DLL_PROCESS_DETACH функции обратного вызова DllMain)
auto data = func();
Вызов виртуальной функции у уже разрушенного ранее объекта полиморфного типа, равно как и вызов функции уже выгруженной динамической библиотеки, вероятно, приведет к
You must be registered for see links
.Решение: внедрить в приложение корректную процедуру финализации, гарантирующую, что все динамические библиотеки завершат свою работу/будут выгружены в правильном порядке. Избегайте использования статических объектов со сложной логикой в DLL. Избегайте выполнения каких-либо операций внутри библиотеки после вызова DllMain/DLL_PROCESS_DETACH (когда библиотека перейдёт к своему последнему этапу жизненного цикла — фазе разрушения своих статических объектов).
Необходимо понимать что из себя представляет жизненный цикл DLL:
А) Сторонний модуль вызывает LoadLibrary с целью загрузить библиотеку
- происходит инициализация статических объектов библиотеки (этот этап должен содержать только очень простую логику, вызывается автоматически)
- происходит вызов DllMain -> DLL_PROCESS_ATTACH (секция должна содержать только очень простую логику, вызывается автоматически)
- теперь другие потоки приложения могут начать [параллельно] вызывать DllMain -> DLL_THREAD_ATTACH / DLL_THREAD_DETACH (вызывается автоматически, но см. далее примечания в пункте 30).
- эти секции, возможно, могут содержать некоторую сложную логику (например, индивидуальную инициализацию генератора псевдослучайных чисел для каждого потока), но будьте аккуратны
- происходит вызов экспортируемой разработчиком библиотеки функции инициализации библиотеки (содержит в себе всю сложную/тяжелую работу по инициализации, вызывается вручную тем, кто загружает библиотеку)
- непосредственно работа приложения с библиотекой, для чего она (библиотека) и создавалась
- происходит вызов экспортируемой разработчиком библиотеки функции ДЕинициализации библиотеки (содержит в себе всю сложную/тяжелую работу по ДЕинициализации, вызывается вручную тем, кто выгружает библиотеку)
- После этой точки избегайте каких-либо действий в библиотеке: все ранее запущенные потоки библиотеки должны быть завершены, прежде чем произойдет возврат из этой функции
В) Другой модуль вызывает FreeLibrary
- происходит вызов DllMain -> DLL_PROCESS_DETACH (секция должна содержать только очень простую логику, вызывается автоматически)
- происходит уничтожение статических объектов библиотеки (должен содержать только очень простую логику, вызываемую автоматически)
11)
You must be registered for see links
(
You must be registered for see links
, чтобы вызвать деструктор, поэтому удаление объекта с помощью opaque pointer может привести к утечке памяти и другим проблемам) 12) если деструктор DataNew является виртуальным,
You must be registered for see links
и получении полной информации о нем, все равно вызов его деструктора на этом этапе является проблемой — это, вероятно, приведет к
You must be registered for see links
(так как тип DataNew импортируется из уже выгруженного файла B.dll). Эта проблема возможна, даже если деструктор не является виртуальным.13) если класс DataNew является
You must be registered for see links
, а его базовый класс имеет чистый виртуальный деструктор без тела, в любом случае
You must be registered for see links
чистый вызов виртуальной функции.14) неопределенное поведение, если выделять память через new и удалять, используя delete[]
delete[] data2;
В целом, вы всегда должны быть
You must be registered for see links
при освобождении объектов, полученных от внешних модулей.Также хорошей практикой является
You must be registered for see links
на разрушенные объекты.Решение:
— при удалении объекта должен быть известен его полный тип
— все деструкторы должны иметь тело
— библиотека, из которой экспортируется код, не должна выгружаться слишком рано
— всегда правильно использовать различные формы new и delete, не путать их
— указатель на удаленный объект должен обнуляться.
Также обратите внимание на следующее:
— вызов оператора delete для указателя на void
You must be registered for see links
к неопределенному поведениючисто виртуальные функции
You must be registered for see links
вызываться из конструктора— вызов виртуальной функции в конструкторе
You must be registered for see links
виртуальным— старайтесь избегать
You must be registered for see links
— вместо этого используйте
You must be registered for see links
,
You must be registered for see links
и
You must be registered for see links
Смотрите также
You must be registered for see links
15) ExitThread — предпочтительный метод выхода из потока в C. В C++ при вызове этой функции поток завершится до вызова деструкторов локальных объектов (и любой другой автоматической очистки), поэтому завершение потока в C++ следует выполнять просто путем возврата из функции потока
ExitThread(0U);
Решение: никогда не используйте вручную эту функцию в C++ коде.
16) в теле DllMain вызов любых стандартных функций, для которых требуются системные DLL, отличные от Kernel32.dll, может привести к различным трудно диагностируемым проблемам
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
Решение в DllMain:
— избегайте любой сложной (де)инициализации
— избегайте вызова функций из других библиотек (или, по крайней мере, будьте предельно аккуратны с этим)
17) некорректная инициализация генератора псевдослучайных чисел в многопоточной среде
18) т. к. время, возвращаемое функцией
You must be registered for see links
, имеет разрешение 1 сек., любой поток в программе, который вызывает эту функцию в течение этого периода времени, получит на выходе одинаковое значение. Использование этого числа для инициализации ГПСЧ может привести к возникновению коллизий (например, генерации одинаковых псевдослучайных имён для временных файлов, одинаковых номеров портов и т.д.). Одно из возможных решений — смешать (
You must be registered for see links
) полученный результат с каким-то
You must be registered for see links
, таким как адрес любого стэка или объекта в куче, более точным временем и т.д.srand(time(nullptr));
Решение:
You must be registered for see links
. Кроме того,
You must be registered for see links
, предпочтительнее использовать
You must be registered for see links
.Смотрите также
You must be registered for see links
You must be registered for see links
You must be registered for see links
[C#]19) может вызвать тупик или сбой (или создать циклы зависимости в порядке загрузки DLL)
OTHER_LIB = LoadLibrary("B.dll");
Решение:
You must be registered for see links
. Любая сложная (де)инициализация должна выполняться в определенных экспортируемых разработчиком DLL функциях, таких как, например, «Init» и «Deint». Библиотека предоставляет эти функции пользователю, а пользователь должен их корректно в нужный момент вызвать. Обе стороны должны строго соблюдать этот контракт.20) опечатка (условие всегда ложно), неправильная логика программы и возможная утечка ресурсов (поскольку OTHER_LIB никогда не выгружается при успешной загрузке)
if (OTHER_LIB = nullptr)
return FALSE;
You must be registered for see links
возвращает ссылку левого типа, т.е. if проверит значение OTHER_LIB (которое будет nullptr) и nullptr будет интерпретировано как false.Решение: всегда используйте обратную форму, чтобы избежать таких опечаток:
if/while ( == expression>)
21) рекомендуется использовать системную функцию _beginthread для создания нового потока в приложении (особенно если приложение было слинковано со статической версией библиотеки времени выполнения C) в противном случае могут возникнуть утечки памяти при вызове ExitThread, DisableThreadLibraryCalls
22) все внешние вызовы DllMain сериализуются, поэтому в теле этой функции не должны предприниматься попытки создавать потоки/процессы или осуществлять с ними какое-либо взаимодействие, иначе возможно возникновение взаимных блокировок
CreateThread(nullptr, 0U, &Init, nullptr, 0U, nullptr);
23) вызов функций COM во время завершения работы DLL может привести к некорректному доступу к памяти, поскольку соответствующий компонент уже может быть выгружен
CoUninitialize();
24) не существует способа контролировать порядок загрузки и выгрузки внутрипроцессных сервисов COM/OLE, поэтому не вызывайте OleInitialize или OleUninitialize из функции DllMain
OleUninitialize();
Смотрите также
You must be registered for see links
You must be registered for see links
25) вызов free для блока памяти, выделенного с помощью new
26) если процесс приложения находится в стадии завершения своей работы (на что указывает ненулевое значение параметра lpvReserved), все потоки в процессе, кроме текущего, либо уже завершены, либо были принудительно остановлены при вызове функции ExitProcess, которая может оставить некоторые ресурсы процесса, такие как куча, в неконсистентном состоянии. В результате очищать ресурсы небезопасно для DLL. Вместо этого DLL должна позволять операционной системе восстанавливать память
free(INTEGERS);
Решение: убедитесь, что старый стиль C ручного выделения памяти не смешан с “новым” стилем, принятым в C++. Будьте предельно осторожны при управлении ресурсами в функции
You must be registered for see links
.27) может привести к тому, что DLL будет использоваться даже после того, как система выполнила свой код завершения
const BOOL result = FreeLibrary(OTHER_LIB);
Решение: не вызывать
You must be registered for see links
в точке входа DllMain.28) произойдет сбой текущего (возможно, основного) потока
throw new std::runtime_error("Необходимый модуль не был загружен");
Решение: избегайте выбрасывания исключений в функции DllMain. Если DLL не может быть корректно загружена по какой-либо причине, функция должна просто вернуть FALSE. Выбрасывать исключения из секции DLL_PROCESS_DETACH также не следует.
Всегда будьте осторожны, выбрасывая исключения за пределы DLL. Любые сложные объекты (например, классы
You must be registered for see links
) могут иметь различное физическое представление (и даже логику работы) в различных исполняемых модулях в случае, если они скомпилированы с разными (несовместимыми) версиями
You must be registered for see links
.Постарайтесь обмениваться между модулями только
You must be registered for see links
(с фиксированным размером и четко определенным бинарным представлением).Помните, что завершение основного потока автоматически завершит все остальные потоки (которые не завершатся правильно и из-за этого могут повредить память, оставив примитивы синхронизации и другие объекты в непредсказуемом и некорректном состоянии. Кроме того, эти потоки уже перестанут существовать в тот момент, когда статические объекты начнут свою собственную деконструкцию, поэтому не пытайтесь в деструкторах статических объектов ожидать завершения каких-либо потоков).
Смотрите также
You must be registered for see links
29)
You must be registered for see links
(например, std::bad_alloc), которое здесь не перехватывается THREADS.push_back(std::this_thread::get_id());
Поскольку секция DLL_THREAD_ATTACH вызывается из какого-то неизвестного внешнего кода, не особо рассчитывайте увидеть здесь корректное поведение.
Решение: приложите с помощью команды try/catch те инструкции, которые могут выбрасывать исключения, которые вероятнее всего не могут быть обработаны правильно (особенно если они выходят из библиотеки DLL).
Смотрите также
You must be registered for see links
30) UB, если до загрузки этой DLL были представлены потоки
THREADS.pop_back();
Уже существующие на момент загрузки DLL потоки (включая тот, который непосредственно загружает DLL) не вызывают функцию точки входа загружаемой DLL (поэтому они не регистрируются в векторе THREADS во время события DLL_THREAD_ATTACH), в то время как они по-прежнему вызывают его с событием DLL_THREAD_DETACH по завершении.
Это означает, что количество обращений к секциям DLL_THREAD_ATTACH и DLL_THREAD_DETACH функции DllMain будет разным.
31) лучше использовать
You must be registered for see links
32) передача сложного объекта между модулями может вызвать сбой, если они скомпилированы с разными настройками и флагами линковки и компиляции (разные версии библиотеки времени выполнения и т. д.)
33) доступ к объекту c по его виртуальному адресу (который является общим для модулей) может вызвать проблемы, если указатели по-разному обрабатываются в этих модулях (например, если модули связаны с различными параметрами
You must be registered for see links
) __declspec(dllexport) int Initialize(std::vector integers, int& c) throw()
Смотрите также
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
[C#]
You must be registered for see links
[Ru]
You must be registered for see links
[Ru]А также...
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
Вряд ли приведенный выше список является полным, поэтому вы наверняка сможете добавить что-нибудь важное в комментариях.
Работа с указателями на самом деле существенно сложнее, чем о ней обычно думают. Вне всяких сомнений, матерые разработчики смогут вспомнить и другие существующие нюансы и тонкости (например, что-то о разнице между
You must be registered for see links
, из-за чего, возможно,
You must be registered for see links
, и т.д.).34) внутри функции
You must be registered for see links
You must be registered for see links
:INTEGERS = new std::vector(integers);
при этом спецификация throw() этой функции пуста:
__declspec(dllexport) int Initialize(std::vector integers, int& c) throw()
You must be registered for see links
при нарушении спецификации исключения: исключение выбрасывается из функции, спецификация исключения которой запрещает исключения этого типа.Решение: используйте try / catch (особенно при выделении ресурсов, особенно в DLL) или nothrow форму оператора new. В любом случае,
You must be registered for see links
.Смотрите также
You must be registered for see links
You must be registered for see links
You must be registered for see links
Проблема 1: формирование такого «более случайного» значения некорректно. Как утверждает
You must be registered for see links
, сумма независимых случайных величин стремится к
You must be registered for see links
, а не к равномерному (даже если сами исходные величины распределены равномерно).Проблема 2: возможное переполнение целочисленного типа (что является
You must be registered for see links
)return rand() + rand();
Работая с генераторами псевдослучайных чисел, шифрованием и тому подобными вещами, всегда остерегайтесь использования самодельных «решений». Если у вас нет специализированного образования и опыта работы с этими крайне специфическими областями, очень высоки шансы, что вы просто перехитрите сами себя и лишь усугубите ситуацию.
35) имя экспортируемой функции будет декорировано (изменено), чтобы предотвратить это использование extern «C»
36) имена, начинающиеся с '_', неявно запрещены для C++, так как этот стиль именования зарезервирован для STL
__declspec(dllexport) long long int __cdecl _GetInt(int a)
Несколько проблем (и их возможные решения):
37)
You must be registered for see links
не является потокобезопасным, вместо него нужно использовать
You must be registered for see links
/
You must be registered for see links
38) rand устарел,
You must be registered for see links
современный C++11
39) не факт, что функция rand была инициализирована конкретно для текущего потока (MS VS требует инициализации этой функции для каждого потока, где она будет вызываться)
40)
You must be registered for see links
, и в устойчивых ко взлому решениях
You must be registered for see links
(подойдут переносимые решения вроде
You must be registered for see links
,
You must be registered for see links
и т.д.)41) потенциальное деление на ноль: может вызвать аварийное завершение текущего потока
42) в одном ряду использованы
You must be registered for see links
, что вносит хаос в порядок вычисления – применяйте скобки и/или
You must be registered for see links
для задания очевидной последовательности вычисления43) потенциальное
You must be registered for see links
return 100 / a code>
Смотрите также
You must be registered for see links
А также...
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
И это еще не всё!
Представьте, что у вас есть какой-то важный контент в памяти (например, пароль пользователя). Конечно же,
You must be registered for see links
дольше, чем это реально необходимо, увеличивая таким образом вероятность того, что кто-то
You must be registered for see links
.
You must be registered for see links
будет выглядеть примерно так:bool login(char* const userNameBuf, const size_t userNameBufSize,
char* const pwdBuf, const size_t pwdBufSize) throw()
{
if (nullptr == userNameBuf || '\0' == *userNameBuf || nullptr == pwdBuf)
return false;
// Here some actual implementation, which does not checks params
// nor does it care of the 'userNameBuf' or 'pwdBuf' lifetime,
// while both of them obviously contains private information
const bool result = doLoginInternall(userNameBuf, pwdBuf);
// We want to minimize the time this private information is stored within the memory
memset(userNameBuf, 0, userNameBufSize);
memset(pwdBuf, 0, pwdBufSize);
}
И это, конечно же,
You must be registered for see links
так, как бы нам хотелось. Что же тогда делать? Неправильное «решение» №1: если memset не работает, давайте сделаем это вручную!
void clearMemory(char* const memBuf, const size_t memBufSize) throw()
{
if (!memBuf || memBufSize < 1U)
return;
for (size_t idx = 0U; idx < memBufSize; ++idx)
memBuf[idx] = '\0';
}
Почему и это нам тоже не подходит? Дело в том, что в этом коде нет никаких ограничений, которые бы не позволили современному компилятору
You must be registered for see links
(кстати, функция
You must be registered for see links
, если она всё же будет использоваться, скорее всего, будет
You must be registered for see links
).Смотрите также
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
Неправильное «решение» #2: попытаться «улучшить» предыдущее «решение», поигравшись с ключевым словом
You must be registered for see links
void clearMemory(volatile char* const volatile memBuf, const volatile size_t memBufSize) throw()
{
if (!memBuf || memBufSize < 1U)
return;
for (volatile size_t idx = 0U; idx < memBufSize; ++idx)
memBuf[idx] = '\0';
*(volatile char*)memBuf = *(volatile char*)memBuf;
// There is also possibility for someone to remove this "useless" code in the future
}
Будет ли это работать? Возможно. Например, такой подход используется в
You must be registered for see links
(в чём вы можете убедиться самостоятельно, посмотрев фактическую реализацию этой функции в исходниках Windows
You must be registered for see links
).Однако, такой прием будет ожидаемо работать
You must be registered for see links
.Смотрите также
You must be registered for see links
Неправильное «решение» #3: использовать неподходящую функцию API ОС (например, RtlZeroMemory) или STL (например, std::fill, std::for_each)
RtlZeroMemory(memBuf, memBufSize);
Другие примеры попыток решения данной проблемы
You must be registered for see links
.И как же всё-таки правильно??
● использовать корректную функцию API ОС, например, RtlSecureZeroMemory для Windows
● использовать функцию C11
You must be registered for see links
:Кроме того, мы можем помешать компилятору оптимизировать код путем вывода (в файл, консоль или другой поток) значения переменной, но этот способ, очевидно, не очень полезен.
Смотрите также
You must be registered for see links
Подводя итоги
Это, конечно, не полный список всех возможных проблем, нюансов и тонкостей с которыми вы можете столкнуться при написании приложений на C/C++.
Есть также и такие замечательные вещи, как:
-
You must be registered for see links;
-
You must be registered for see links(например, вызванные неправильной реализациейYou must be registered for see links,You must be registered for see links, не потокобезопасными реализациямиYou must be registered for see links, неправильными реализациямиYou must be registered for see links);
-
You must be registered for see links;
-
You must be registered for see links(из-за округления или числовой нестабильности алгоритмов, например, суммирование массива чисел с плавающей запятой без предварительной их сортировки);
- проблемы взаимодействия
You must be registered for see links
- путаница, непонимание и неправильное использование
You must be registered for see linksпеременных;
- некорректное использование целочисленных литералов (например, 0603 вместо 603);
- проблема рассинхронизации проверки/доступа (
You must be registered for see links);
- лямбда-выражения, которые живут дольше своих захваченных по ссылкам объектов;
-
You must be registered for see links;
- неправильный обмен данными между двумя устройствами с
You must be registered for see links(например, через сеть) и т. д. и т. п.;
И многое другое
Есть что добавить? Поделитесь своими интересным опытом в комментариях!
P.S. Хотите узнать больше?
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links