Aussie AI Blog

Use-After-Free Memory Safety in C++

  • November 2nd, 2024
  • by David Spuler, Ph.D.

Use-After-Free Memory Safety in C++

Use-after-free errors arise when heap memory is de-allocated, but there is still a pointer to that address. This becomes a "dangling pointer" (or "dangling reference") and any use of that memory via the pointer is a "use-after-free" error. Note that the word "free" means any memory deallocation primitive, such as the free function or the delete operator.

There are several problems with use-after-free errors:

  • Crashes
  • Insidious program errors
  • Portability issues
  • Security exploits

Programs with use-after-free errors often exhibit unpredictable behavior with intermittent failures. They may also work fine on one platform, but crash when ported to a different platform, or when the optimizer level is turned up.

Use-After-Free Security Vulnerabilities

Surprisingly, use-after-free errors in heap memory are a very common security vulnerability, second only to buffer overflow attacks on stack memory. The attack involves these steps:

    (a) Intentionally triggering a problematic free to gain a dangling pointer,

    (b) Waiting for something important to get allocated into the previously-freed memory, and

    (c) Accessing or modifying the important data (e.g., Unix suid bits) via the dangling pointer.

This sounds very complicated and unwieldy, but it's been a very successful method of targeting vulnerabilities in C++ software.

Detecting Use-After-Free

The methods to detect use-after-free errors include:

  • Memory sanitizer runtime tools
  • Memory tagging
  • Memory poisoning (magic bytes)
  • Hardware-assisted memory block exceptions

The main way to detect these sorts of errors is to use memory sanitizers, such as Valgrind or AddressSanitizer. These tools are very good at this stuff, and you should be running them in your nightly builds with a full regression test suite. It's also useful to run these tools when using "fuzzing" (testing with many large random inputs), as a way to detect these memory errors on unexpected inputs.

Some of the ways to reduce these errors, or to mitigate them as a security attack vector, include:

  • Never-free policies (where possible).
  • Delayed-free policies (with various configurations).
  • Random delayed free (less predictable delayed-free sequences).

Note that if you change the memory deallocation policy, you need to do it at a low level, such as in your own custom memory allocators, or in debug wrappers for allocation functions. You can't just comment out all the delete statements in destructors, becauses it's sometimes important that the destructors for these sub-objects can still run.

The idea of never deallocating any memory is horror-inspiring for most programmers. However, it's a plausible idea for short batch programs that aren't hanging around long enough for the leaks to matter.

Also, one particular case is that you can disable memory deallocation whenever the program is shutting down, whether it's a batch program or a long-running service. Program termination commonly triggers a huge volume of deallocation requests in destructors for stack, heap, and global objects, making it a fertile field for memory deallocation errors, not to mention that it also causes slow program exits! Plenty of inadequately tested programs will crash on exit due to earlier heap corruptions. And yet, these deallocations don't actually matter because the operating system will reclaim all the memory once the program shuts down.

Double Deallocation Errors

One special case of the use-after-free error is a double-free or a double-delete. A program crash is likely from this:

    char *s = (char*)malloc(100);
    free(s);
    free(s);  // Boom!

One minor mitigation is to clear the pointer to null whenever using any deallocation:

    free(s);
    s = NULL;  // safety

Hence, the second call will do free(NULL), which is not a crash, and supposedly harmless according to the standards.

You can do self-referential macro tricks with the comma operator:

    #define free(s)  ( free((s)), (s) = NULL )

Another way is that you can define wrapper functions with reference paramters:

    #define free free_wrapper

    inline void free_wrapper(void *&v)
    {
        free(v);
        v = NULL;  // change reference parameter
    }

However, it's harder to do these types of tricks for the delete operator because its syntax is not function-like. If only C++ had a more powerful preprocessor mode!

Related Memory Safety Blog Articles

See also these articles:

Safe C++ Book



Safe C++: Fixing Memory Safety Issues The new Safe C++ coding book by David Spuler:
  • Memory Safety
  • Rust versus C++
  • The Safe C++ Standard
  • Pragmatic Memory Safety

Get your copy from Amazon: Safe C++: Fixing Memory Safety Issues

Aussie AI Advanced C++ Coding Books



C++ AVX Optimization C++ AVX Optimization: CPU SIMD Vectorization:
  • Introduction to AVX SIMD intrinsics
  • Vectorization and horizontal reductions
  • Low latency tricks and branchless programming
  • Instruction-level parallelism and out-of-order execution
  • Loop unrolling & double loop unrolling

Get your copy from Amazon: C++ AVX Optimization: CPU SIMD Vectorization



C++ Ultra-Low Latency C++ Ultra-Low Latency: Multithreading and Low-Level Optimizations:
  • Low-level C++ efficiency techniques
  • C++ multithreading optimizations
  • AI LLM inference backend speedups
  • Low latency data structures
  • Multithreading optimizations
  • General C++ optimizations

Get your copy from Amazon: C++ Ultra-Low Latency



Advanced C++ Memory Techniques Advanced C++ Memory Techniques: Efficiency & Safety:
  • Memory optimization techniques
  • Memory-efficient data structures
  • DIY memory safety techniques
  • Intercepting memory primitives
  • Preventive memory safety
  • Memory reduction optimizations

Get your copy from Amazon: Advanced C++ Memory Techniques



Safe C++ Safe C++: Fixing Memory Safety Issues:
  • The memory safety debate
  • Memory and non-memory safety
  • Pragmatic approach to safe C++
  • Rust versus C++
  • DIY memory safety methods
  • Safe standard C++ library

Get it from Amazon: Safe C++: Fixing Memory Safety Issues



Efficient C++ Multithreading Efficient C++ Multithreading: Modern Concurrency Optimization:
  • Multithreading optimization techniques
  • Reduce synchronization overhead
  • Standard container multithreading
  • Multithreaded data structures
  • Memory access optimizations
  • Sequential code optimizations

Get your copy from Amazon: Efficient C++ Multithreading



Efficient Mordern C++ Data Structures Efficient Modern C++ Data Structures:
  • Data structures overview
  • Modern C++ container efficiency
  • Time & space optimizations
  • Contiguous data structures
  • Multidimensional data structures

Get your copy from Amazon: Efficient C++ Data Structures



Low Latency C++: Multithreading and Hotpath Optimizations Low Latency C++: Multithreading and Hotpath Optimizations: advanced coding book:
  • Low Latency for AI and other applications
  • C++ multithreading optimizations
  • Efficient C++ coding
  • Time and space efficiency
  • C++ slug catalog

Get your copy from Amazon: Low Latency C++



CUDA C++ Optimization CUDA C++ Optimization book:
  • Faster CUDA C++ kernels
  • Optimization tools & techniques
  • Compute optimization
  • Memory optimization

Get your copy from Amazon: CUDA C++ Optimization



CUDA C++ Optimization CUDA C++ Debugging book:
  • Debugging CUDA C++ kernels
  • Tools & techniques
  • Self-testing & reliability
  • Common GPU kernel bugs

Get your copy from Amazon: CUDA C++ Debugging

More AI Research Topics

Read more about: