Security of C++ Applications: Top 10 Vulnerabilities in Practice
C++ is a powerful and high-performance language, but its flexibility requires a high degree of responsibility from the developer. Errors in memory management, thread handling, or input/output can lead to serious vulnerabilities. In this article, we will analyze the top 10 C++ vulnerabilities with practical code examples and ways to prevent them. The material will be useful for both beginners and experienced developers looking to strengthen the security of their projects.
1. Buffer Overflow
A classic vulnerability that occurs when data is written beyond the boundaries of an allocated array. In C++, it is especially dangerous when working with raw arrays and C functions.
#include <iostream>#include <cstring>
void vulnerable(char* input) { char buffer[10]; strcpy(buffer, input); // DANGEROUS: no length check std::cout << "Buffer: " << buffer << std::endl;}
int main() { char longInput[] = "AAAAAAAABBBBBBBBCCCCCCCC"; // 24 characters vulnerable(longInput); return 0;}Solution: Use std::string or strncpy with an explicit length check. In modern C++, STL containers are preferred.
void safe(const std::string& input) { std::string buffer = input.substr(0, 9); // safe copying std::cout << "Buffer: " << buffer << std::endl;}2. Memory Leaks
Leaks occur when dynamically allocated memory is not freed. In long-running applications, this leads to resource exhaustion.
void leakExample() { int* data = new int[1000]; // allocate memory // ... work with data ... // forgot delete[] data; — leak!}Solution: Use smart pointers (std::unique_ptr, std::shared_ptr) or STL containers.
void safeExample() { std::unique_ptr<int[]> data = std::make_unique<int[]>(1000); // memory is automatically freed when leaving the function}3. Null Pointer Dereference
Attempting to access memory at address nullptr causes undefined behavior and often leads to a program crash.
void process(int* ptr) { *ptr = 42; // DANGEROUS: if ptr == nullptr}
int main() { int* p = nullptr; process(p); // crash}Solution: Always check pointers before use. Use references if the value cannot be null.
void safeProcess(int* ptr) { if (ptr == nullptr) { throw std::invalid_argument("Null pointer received"); } *ptr = 42;}4. Race Condition
Occurs when multiple threads access shared data simultaneously without synchronization.
#include <thread>int counter = 0;
void increment() { for (int i = 0; i < 100000; ++i) { counter++; // non-atomic operation — race condition }}
int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "Counter: " << counter; // unpredictable result}Solution: Use mutexes or atomic operations.
#include <atomic>std::atomic<int> counter{0};
void safeIncrement() { for (int i = 0; i < 100000; ++i) { counter.fetch_add(1); // atomic operation }}5. Use of Uninitialized Variables
Uninitialized variables contain "garbage," which can lead to unpredictable behavior.
int main() { in