Breaking

Wednesday, May 7, 2025

In this post, we’ll walk through some of the most common and dangerous security flaws in C and C++ programming: buffer overflows, integer overflowsformat 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.


Common-Security-Flaws-in-C-and-C++


  • 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?

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:

void smash_stack(char *userInput) {
    char buffer[32];
strcpy(buffer, userInput); // No bounds checking!
}

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:

char* buffer = new char[32];
char* secret_password = new char[32];
strcpy(secret_password, "password");
strcpy(buffer, long_user_input);

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 strncpysnprintf, or modern C++ constructs like std::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:

    int c = INT_MAX;
    c = c + 1; // Wraps around to INT_MIN

    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:
        int size = get_user_input();
        char* buffer = new char[size];

    •  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_overflow in 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:

    void vulnerable_function(char *userInput) {
        printf(userInput); // DANGER: userInput could contain format specifiers
    }

    If an attacker enters %x %x %xprintf 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:

      printf("%s", userInput); // Safe
    • Use cout instead of printf when working in C++:

      std::cout << userInput;

    How to Avoid Format String Attacks:

    • Never pass user input directly as a format string.
    • Sanitize and validate input rigorously.
    • Disable risky specifiers like %n using 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.

    char* buffer = new char[100];
    delete[] buffer;
    strcpy(buffer, "This is unsafe!"); // Using freed memory

    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 nullptr after freeing.
    • Use smart pointers (std::unique_ptrstd::shared_ptr) to manage memory automatically.


    5. Memory Leaks

    What is it?

    Memory that is allocated but never deallocated leads to memory leaks.

    char* buffer = new char[256];
    // Forgot to call delete[]

    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 new with delete, and new[] with delete[].
    • 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.


    char* data = new char[50];
    delete[] data;
    delete[] data; // Dangerous!

    Risks:

    • May corrupt the heap.
    • Can be exploited to gain control over the heap metadata in certain allocators.

    Fix:

    • Set pointers to nullptr immediately after deletion.


    7. Uninitialized Memory Access

    What is it?

    Using variables or memory that hasn’t been initialized.

    int a;
    cout << a; // Value is undefined

    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 -Wuninitialized to 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).

    FILE* f = fopen("/tmp/mytempfile", "w");

    Risk:

    • Attackers can create symbolic links to redirect writes to other files.

    Fix:

    • Use mkstemp() instead of tmpnam() 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.

    system(userInput); // BAD if userInput = "rm -rf /"

    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., mallocfopenread).

    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.

    In addition to secure coding, code reviewsstatic analysis tools (like CppcheckClang-Tidy, or Coverity), and dynamic testing tools (like ValgrindASanFuzzers) are critical in identifying and mitigating these issues before they make it into production.


    13. Dangling Pointers

    What is it?

    dangling pointer is a pointer that continues to reference memory after it has been deallocated.

    int* ptr = new int(5);
    delete ptr;
    *ptr = 10; // Dangling: accessing freed memory

    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 nullptr immediately after deletion.
    • Use smart pointers like std::unique_ptr or std::shared_ptr in 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.

    int arr[5];
    arr[5] = 10; // Out-of-bounds write

    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::vector with .at() method in C++.
    • Enable compiler flags like -fsanitize=address or 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.

    // Example pseudocode
    MyClass obj;
    ifstream file("data.ser");
    file >> obj; // Deserializing untrusted input

    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.

    volatile bool done = false;
    void worker() {
    while (!done) {
    // do something
    }
    }

    Why it's problematic:

    • volatile only 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::atomicstd::mutex, or std::condition_variable in 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.

    void handler(int sig) {
        printf("Signal received\n"); // Not safe in signal context
    }
    signal(SIGINT, handler);

    Why it’s dangerous:

    • Only async-signal-safe functions (like write) can be used inside signal handlers.
    • Unsafe function calls (like mallocprintf, etc.) may lead to deadlocks or crashes.

    Safe signal handling:

    • Minimize logic inside the handler.
    • Set a flag or use sig_atomic_t to 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.

    unsigned int a = 1;
    int b = -2;
    if (a > b) {
    // True? Yes, due to implicit conversion!
    }

    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.

    if (access("file.txt", R_OK) == 0) {
        // Attacker swaps file before we open it
    int fd = open("file.txt", O_RDONLY);
    }

    Prevention:

    • Avoid using access() and open() separately.
    • Use O_CREAT | O_EXCL or 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.

    #define SQUARE(x) x * x
    int result = SQUARE(1 + 2); // Expands to 1 + 2 * 1 + 2

    Fix:

    • Use inline functions or constexpr in 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.

    fwrite(data, sizeof(data), 1, file); // But did it succeed?

    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.

    int shared = 0;
    void thread_func() {
    shared++; // Race condition!
    }

    Fix:

    • Use std::mutexstd::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::stringstd::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.

    char* path = getenv("PATH");

    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.

    void recurse_forever() {
        recurse_forever(); // Boom!
    }

    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 AnalyzerCoverity, or SonarQube.



    close