In this post, we’ll walk through some of the most common and dangerous security flaws in C and C++ programming: buffer overflows, integer overflows, format string vulnerabilities and massive 25+ point checklist of potential C/C++ vulnerabilities. Understanding these issues is the first step toward writing safer and more secure code.
- Buffer Overflows (Stack & Heap)
- Integer Overflows
- Uncontrolled Format String Vulnerabilities
- Use-After-Free
- Memory Leaks
- Double Free
- Uninitialized Memory
- Insecure Temp Files
- Race Conditions
- Command Injection
- Poor Error Handling
- Type Confusion
- Dangling Pointers
- Improper Bounds Checking
- Insecure Deserialization
- Volatile Keyword Misuse
- Signal Handler Vulnerabilities
- Type Conversion Issues
- TOCTOU
- Dangerous Macros
- Unchecked Return Values
- Thread Safety Issues
- Dangerous Functions
- Environment Variable Abuse
- Stack Exhaustion
- General Undefined Behavior
1. Buffer Overflows
What is a Buffer Overflow?
A buffer overflow happens when data exceeds the boundaries of a fixed-size buffer and overwrites adjacent memory. This can cause unexpected behavior, program crashes, and even allow attackers to execute arbitrary code.
Buffer overflows can occur in various memory regions:
- Stack (stack-based buffer overflow)
- Heap (heap-based overflow)
- Memory-mapped files
Stack-Based Buffer Overflow: A Classic Attack
Let’s consider this scenario:
If userInput is longer than 32 characters, it will overwrite memory beyond buffer, including potentially the function's return address.
Why is this dangerous?
When a function is called, the system creates a stack frame that contains local variables and the return address. If an attacker controls the overflow and knows the memory layout, they could:
- Overwrite the return address with the address of malicious code.
- Hijack the execution flow of the program.
This can result in arbitrary code execution, data leaks, system crashes, or even a full system compromise.
Heap-Based Buffer Overflow
Unlike stack overflows, heap overflows affect dynamically allocated memory. Here’s a simplified example:
If long_user_input is larger than 32 characters, it overflows into secret_password, potentially altering sensitive data.
While heap overflows are typically less dangerous in terms of executing shellcode, they can still lead to security breaches—especially if critical values like authentication tokens or pointers are overwritten.
How to Prevent Buffer Overflows:
- Always validate input sizes.
- Use safer functions like
strncpy,snprintf, or modern C++ constructs likestd::string. - Enable compiler protections like stack canaries.
- Take advantage of OS-level defenses like ASLR and non-executable stacks.
2. Integer Overflows
What is an Integer Overflow?
An integer overflow occurs when a calculation results in a number outside the range that can be represented by the data type.
Example:
The result can be surprising: instead of throwing an error, C/C++ silently wraps around the value. This leads to undefined behavior.
Why is this a problem?
- An overflowed value might be used to allocate memory:
- It might cause infinite loops, incorrect bounds checking, or even allow buffer overflows if the logic assumes the value is within bounds.
How to Prevent Integer Overflows:
- Validate arithmetic operations before executing them.
- Use safer arithmetic functions or libraries with built-in overflow checks (e.g.,
__builtin_add_overflowin GCC). - Prefer unsigned types for sizes (with caution).
- Perform careful range checking when working with user input.
3. Uncontrolled Format String Vulnerabilities
What is a Format String Vulnerability?
This happens when a program passes unchecked user input as a format string to functions like printf.
Example of unsafe code:
If an attacker enters %x %x %x, printf will print values from the stack. Even worse, using %n allows writing to arbitrary memory locations, which can be exploited to alter program behavior or gain code execution.
Safer Alternatives:
Fix the code by specifying a format string:
Use
coutinstead ofprintfwhen working in C++:
How to Avoid Format String Attacks:
- Never pass user input directly as a format string.
- Sanitize and validate input rigorously.
- Disable risky specifiers like
%nusing compiler flags or secure coding guidelines.
4. Use-After-Free
What is it?
Use-After-Free (UAF) occurs when a program continues to use a pointer after the memory it points to has been freed.
Why it matters:
- Can lead to undefined behavior, crashes, or execution of malicious code.
- Attackers may exploit UAF to control program behavior by reallocating memory in the same location.
How to prevent it:
- Always set pointers to
nullptrafter freeing. - Use smart pointers (
std::unique_ptr,std::shared_ptr) to manage memory automatically.
5. Memory Leaks
What is it?
Memory that is allocated but never deallocated leads to memory leaks.
Why it matters:
- Causes increased memory usage over time.
- In long-running systems, can lead to slowdowns or crashes due to exhaustion of system memory.
Prevention:
- Use RAII (Resource Acquisition Is Initialization) with smart pointers.
- Always pair
newwithdelete, andnew[]withdelete[]. - Use tools like Valgrind or ASan (Address Sanitizer) to detect leaks.
6. Double Free
What is it?
Calling delete or free() on the same memory block more than once.
Risks:
- May corrupt the heap.
- Can be exploited to gain control over the heap metadata in certain allocators.
Fix:
- Set pointers to
nullptrimmediately after deletion.
7. Uninitialized Memory Access
What is it?
Using variables or memory that hasn’t been initialized.
Why it matters:
- Leads to unpredictable behavior.
- Might leak sensitive data left in memory from other processes or operations.
Prevention:
- Always initialize variables before use.
- Use tools like Valgrind or compiler flags like
-Wuninitializedto detect it.
8. Insecure Temporary Files
What is it?
Creating predictable temp file names can allow attackers to create files with those names in advance (race condition attacks).
Risk:
- Attackers can create symbolic links to redirect writes to other files.
Fix:
- Use
mkstemp()instead oftmpnam()or hardcoded paths.
9. Race Conditions
What is it?
When two threads/processes access shared data simultaneously and the result depends on the sequence of access.
Risk:
- Can lead to unpredictable bugs.
- Security implications if the race condition affects authentication, permissions, or data integrity.
Example:
- Time-of-check to time-of-use (TOCTOU) vulnerabilities in file handling.
10. Command Injection via system() or popen()
What is it?
Passing unsanitized input to system-level commands.
Risk:
- Full command execution by attackers.
- Leads to privilege escalation or data destruction.
Fix:
- Avoid using
system()when possible. - Use safer APIs or at least validate/escape input properly.
11. Poor Error Handling
What is it?
Ignoring or mishandling return values from critical functions (e.g., malloc, fopen, read).
Why it matters:
- Can lead to null pointer dereferences or logic failures.
- Hides issues that attackers can exploit.
12. Type Confusion / Casting Errors
What is it?
Using incorrect type casts, especially when using void pointers or complex class hierarchies in C++.
Risk:
- Results in undefined behavior.
- Can be exploited to execute arbitrary code in complex systems.
13. Dangling Pointers
What is it?
A dangling pointer is a pointer that continues to reference memory after it has been deallocated.
Why it’s dangerous:
- Can lead to undefined behavior, crashes, or accidental corruption of unrelated memory.
- If attackers can predict or control memory reuse, they may exploit the dangling pointer to hijack execution flow.
How to prevent it:
- Set pointers to
nullptrimmediately after deletion. - Use smart pointers like
std::unique_ptrorstd::shared_ptrin C++ for automatic memory management.
14. Improper Bounds Checking
What is it?
Failing to ensure that indices or values fall within expected limits when accessing arrays, buffers, or memory.
Why it matters:
- Can cause buffer overflows, memory corruption, or logic bugs.
- Common source of CVEs in both user-space and kernel-space programs.
How to prevent it:
- Always validate indices before access.
- Prefer safer containers like
std::vectorwith.at()method in C++. - Enable compiler flags like
-fsanitize=addressor use static analysis tools.
15. Insecure Deserialization (C++)
What is it?
In insecure deserialization, untrusted data is used to rebuild objects, potentially allowing attackers to manipulate object state or execute code.
Risk:
Attackers can craft malicious serialized objects to:
- Modify program logic
- Trigger unexpected behavior
- Possibly execute arbitrary code depending on how deserialization is implemented
Prevention:
- Avoid deserializing data from untrusted sources.
- Use safe and verified serialization libraries with schema validation.
- Implement input validation and integrity checks (e.g., signatures, checksums).
16. Volatile Keyword Misuse
What is it?
Using the volatile keyword incorrectly or assuming it provides thread safety.
Why it's problematic:
volatileonly prevents compiler optimizations; it does not guarantee memory visibility across threads.- Misuse can lead to subtle multi-threading bugs.
Proper alternative:
- Use proper synchronization primitives like
std::atomic,std::mutex, orstd::condition_variablein C++. - Understand memory models and concurrency implications.
17. Signal Handler Vulnerabilities
What is it?
Improper use of signal handlers can introduce race conditions, inconsistent program state, or unsafe behavior.
Why it’s dangerous:
- Only async-signal-safe functions (like
write) can be used inside signal handlers. - Unsafe function calls (like
malloc,printf, etc.) may lead to deadlocks or crashes.
Safe signal handling:
- Minimize logic inside the handler.
- Set a flag or use
sig_atomic_tto signal the main thread to take action later.
18. Type Conversion Issues (Signed/Unsigned Mismatch)
What is it?
Mixing signed and unsigned types in expressions can lead to logic errors or unexpected behavior.
Risk:
- Leads to incorrect comparisons and logic bugs.
- Can be used by attackers to bypass input validation or overflow checks.
Prevention:
- Avoid mixing signed and unsigned types.
- Use explicit casts with care and always validate ranges.
19. Time-of-Check to Time-of-Use (TOCTOU)
What is it?
When a resource (file, permission, etc.) is checked and then used later, a window opens where an attacker can change it.
Prevention:
- Avoid using
access()andopen()separately. - Use
O_CREAT | O_EXCLor secure file APIs that combine check and use. - Use file descriptors directly when possible.
20. Improper Use of Macros
What is it?
Macros may expand in unexpected ways, leading to side effects or incorrect evaluations.
Fix:
- Use inline functions or
constexprin C++ for safe behavior.
21. Unchecked Return Values
What is it?
Ignoring return values from standard library or system calls can lead to missing failures.
Prevention:
- Always check the return values of functions that can fail, especially I/O, memory allocation, and system calls.
22. Thread Safety Issues
What is it?
Using shared data across threads without proper synchronization.
Fix:
- Use
std::mutex,std::atomic, or higher-level thread-safe structures. - Always protect shared resources in multi-threaded environments.
23. Use of Dangerous Functions
What is it?
Functions like gets(), strcpy(), sprintf() are inherently unsafe and prone to buffer overflows.
Risk:
- Many have been deprecated or removed from modern standards, but they still exist in legacy code.
Safer Alternatives:
- Use
fgets(),strncpy(),snprintf(), and consider C++ abstractions (std::string,std::stringstream).
24. Environment Variable Attacks
What is it?
Programs relying on environment variables (e.g., for paths, passwords, or config) may be tricked into unsafe behavior.
Risk:
- Attackers can manipulate the environment (especially in setuid programs) to control program behavior.
Fix:
- Validate and sanitize environment variables.
- Drop privileges when they are no longer needed.
25. Stack Exhaustion / Deep Recursion
What is it?
Unbounded recursion or large local variable allocations can overflow the stack.
Fix:
Use iteration instead of deep recursion.
Allocate large data on the heap or use
std::vector.
26. Undefined Behavior in General
C/C++ has hundreds of forms of undefined behavior (UB), and many bugs arise from:
- Dereferencing null/invalid pointers
- Modifying const objects
- Violating object lifetimes
- Using shifted values out of range (
x << 32) - Modifying and reading the same variable without a sequence point
Fix:
- Use modern compilers with flags like
-Wall,-Wextra,-Werror,-fsanitize=undefined, and-pedantic. - Use static analysis tools like Clang Static Analyzer, Coverity, or SonarQube.