Double Free and Use After Free: Common IoT Security Weaknesses

Double Free and Use After Free: Common IoT Security Weaknesses

IGAL ZEIFMAN

December 19, 2022

Embedded devices running C code are often to exposed to two common security weaknesses that can be exploited to modify the device memory, resulting in memory leaks, denial of service, unauthorized code execution, etc. These weaknesses are:

Double Free (CWE-415)—calling the free() function multiple times, resulting in a memory leak. This might allow an attacker to write values to arbitrary memory spaces and create an interactive shell with elevated privileges.

Use After Free (CWE 416)—occurs when a program continues to use a memory pointer after it has been freed. If an attacker manages to overwrite one of those pointers with an address to shellcode, they could execute arbitrary code.

In this post, I`ll do a deep dive into each of these two weaknesses and provide some hypothetical and real-life examples of how they could be exploited.

What Is the Double Free (CWE-415)? 

The free() function in the C programming languages releases memory blocks that were allocated using functions like calloc(), malloc(), or realloc(). A double free error occurs if free() is called multiple times with the same memory address.

Example of Double Free Attack Flow

Calling free() twice on the same value causes a memory leak. If a program calls free() twice with the same arguments, it corrupts the program's memory management data structures. This can have several impacts:

  • Program crashes - one of the most common impacts of a double free vulnerability is a program crash. When a block of memory is freed twice, it can cause the program to access invalid or uninitialized memory, which can result in a crash.
  • Changing flow of execution - the double free vulnerability can cause the flow of execution in a program to change unexpectedly. This can occur when the memory that is freed twice is accessed by multiple variables or pointers, which can lead to unpredictable behavior.
  • Arbitrary code execution - in some cases, a double free vulnerability can be exploited by an attacker to execute arbitrary code. This can occur when the vulnerability allows the attacker to write to memory in a way that allows them to overwrite important data or control the flow of execution.

In many cases, an attacker who manages to write values to arbitrary memory spaces can create an interactive shell with elevated privileges. This is also known as a write-what-where condition.

What happens when a buffer is “freed”?

If the free() function is called on a buffer, it reads a concatenated list of free buffers, reorders them, and combines free memory blocks so that larger buffers can be allocated in the future. Blocks are arranged in a double-linked list pointing to the previous and next blocks.

Calling free() has the effect of unlinking an unused buffer. This could allow an attacker to write arbitrary values to memory—overwriting registers and calling shellcode from their own buffer.

Double Free Examples 

Note that to illustrate the double free problem, we are showing simplified examples. In real life, double free vulnerabilities are much more complex and might be laid out across hundreds of lines of code and multiple files.

Simple Example

The following is a trivial example of a double free vulnerability.

int* pointer;
pointer = (int*)malloc(SIZE);
...
if (a > 10) {
  free(pointer);
}
...
free(pointer);

Notice here that the pointer is being freed twice. This leads to uncertainties regarding the exact code section in charge of freeing the memory and which memory space, if available, to be reused.

Complex Example with Two Functions

Let’s now look at an example where the vulnerability exists in two different functions.

#include <stdio.h>
#include <stdlib.h>
void process_arr(int** arr, int n) {
  ...
  free(*arr);
}
int main(int argc, char* argv[]) {
  int* arr = malloc(sizeof(int)) * 10);
  process_arr(&arr, 10);
  free(arr);
  return 0;
}

What Is a Use After Free (CWE 416)? 

In a C or C++ memory is allocated when program instructions are executed. The ‘heap’ is the memory space available to programmers to allocate and deallocate for program instructions. 

Referencing memory after it has been “freed” may cause a program to crash. Using memory allocated on the heap after freeing or deleting it results in undefined system behavior, most commonly a write-what-where condition (ability to write arbitrary values to memory spaces).

A use after free error occurs when the program continues to use the pointer after it has been freed. This is typically caused by error conditions at the application level, or confusion over which program elements are responsible for freeing memory (e.g. lack of coordination between different modules of the same program).

Use after free errors can have the following impacts:

  • Availability—can cause a program to crash. E.g., If the program attempts to merge blocks with previously freed data, and invalid data is used as block information, the operating system process can be disrupted.
  • Integrity—program data could be corrupted as a result of the error (more details below).
  • Code execution—the error can lead to a write-what-where condition, which can potentially be used to execute arbitrary code.

How does data corruption occur?

In a use after free error, memory might be effectively allocated to another pointer at some point after it was freed. The original pointer to the freed memory is reused and points somewhere in the new allocation. When the data changes, the validly used memory is destroyed. This causes undefined behavior in the process and can lead to data corruption.

How does arbitrary code execution occur?

When memory space is freed and then reused, if the newly allocated data might hold a class, multiple function pointers might exist within heap data. If an attacker manages to overwrite one of those pointers with an address to shellcode, they could execute arbitrary code.

Use After Free Examples 

No security concept can protect the contents of specific memory blocks and ensure that only certain pointers can use them. A program’s memory space is entirely available for all pointers anywhere within the program.

Hypothetical Example: Dangling Pointer

If a program uses a dangling pointer to freed memory blocks, it can still access everything stored in these locations. If someone subsequently reallocated the memory to another part of the program that manages sensitive data, it could accidentally change or disclose the data. 

Here is an example of C code that uses a dangling pointer:

#include <stdlib.h>
int main()
{
    int *ptr = malloc(sizeof(int));
    *ptr = 42;
    free(ptr);
    *ptr = 10;  // UAF vulnerability
    return 0;
}

In this code, the ptr pointer is dynamically allocated using the malloc function and is used to store the value 42. The memory is then freed using the free function.

However, the program continues to access the memory pointed to by ptr and assigns the value 10 to it. This opens the door for a ‘use after free’ vulnerability, as the program is attempting to use memory that has already been deallocated.

To fix this vulnerability, the program should ensure that it does not attempt to access or manipulate freed memory. One way to do this would be to set the ptr pointer to NULL after freeing the memory:

#include <stdlib.h>
int main()
{
    int *ptr = malloc(sizeof(int));
    *ptr = 42;
    free(ptr);
    ptr = NULL;  // set ptr to NULL to prevent use after free
    return 0;
}

Real-life Examples

CVE-2022-21661: WordPress SQL Injection Flaw

In January 2022, WordPress released a patch for the CVE-2022-21661 vulnerability. Attackers could exploit this use after free vulnerability to launch an SQL injection attack. 

CVE-2022-226617 Use After Free Fix In iOS 15.4

Another example was in March 2022 when Apple launched iOS 15.4—the new release included a fix for the CVE-2022-22667 vulnerability in GPU drivers. This vulnerability enabled applications to execute arbitrary code using kernel privileges.   

CVE-2022-23270: Windows Server VPN Remote Kernel Use After Free Vulnerability

This vulnerability was discovered in the Windows kernel through reverse engineering and fuzz testing of the raspptp.sys kernel driver. The vulnerability allows attackers to perform denial of service (DoS) and potentially achieve remote code execution against a target server. CVE-2022-23270 is dependent on the specific implementation of the Winsock Kernel (WSK) to be successfully triggered.

CVE-2022-37332 : Foxit Software PDF Reader

The CVE-2022-37332 use after free vulnerability was discovered in the JavaScript engine of Foxit Software's PDF Reader, version 12.0.1.12430. Attackers can create a specially-crafted PDF document, which triggers reuse previously freed memory by misusing the media player API, resulting in arbitrary code execution. However, the vulnerability requires that the user opens a malicious file, or visits a malicious site when the Fox Software browser extension is enabled.

Mitigating CWEs 415 and 416 With Sternum

The vast majority of IoT/embedded devices use C code, and are prone to related memory and code vulnerabilities, including 'double free' and 'use after free' exploit attempts.

Sternum’s patented EIV™ technology protects from these with runtime (RASP-like) protection that deterministically prevents all memory and code manipulation attempts, offering blanket protection from a broad range software weaknesses (CWEs)—including 415 and 416.

Embedding itself directly in the firmware code, EIV™ is agentless and connection agnostic. Operating at the bytecode level, it is also universally compatible with any IoT device or operating system (RTOS, Linux, OpenWrt, Zephyr, Micirum, FreeRTOS, etc.) and has low overhead of only 1-3%, even on legacy devices. 

Moreover, EIV’s runtime protection features are also augmented by (XDR-like) threat detection capabilities of our Cloud platform, leveraging its extended observability features. For a quick overview, check the video down below:

Sternum offers you :

  • Agentless security—integrates directly into firmware, making it a part of the core build. This ensures that the solution cannot be externally compromised and leveraged as a point of failure.
  • Automatic mitigation of known and zero-day threats—prevents 96.5% attacks in benchmark (RIPE) security tests. Its vulnerability-agnostic approach makes it equally effective in dealing with known and zero-day threats. This not only improves security but can also cut security patch management costs by as much as 60%.
  • Supply chain protection—relies on binary instrumentation, making it able to protect all running code. This extends to 3rd party and operating system libraries, effectively preventing all supply chain exploit attempts. 
  • Protection of isolated devices—does not rely on external communication to secure devices, making it equally effective for connected and isolated devices.
  • Live attack information with zero false positives—real-time alert system notifies about all blocked attacks, providing—for each—detailed logs and attack path analysis. The deterministic nature of EIV’s integrity checks ensures that all alerts are always valid. 
  • Advanced threat intelligence—leveraging AI anomaly detection and granular device-level understanding of user interactions, user activity, and device behavior. Can automatically spot indicators of exposure and compromise, malicious behavior, security blindspots, stealthy and sophisticated threats.
  • Streamlined compliance—helps meet the latest cyber regulations for IoT devices (IEC 62443, FDA, NIST, etc) and the most current FBI recommendations for Internet of Medical Things (IoMT) endpoint protection.

Learn more about our IoT security solutions

Better Performance with Autonomous Observability

Gain complete visibility by monitoring and analyzing events in your device or across an entire fleet, from the first line of code and until post-deployment maintenance.

Diagnose software bugs and vulnerabilities by using instrumentation, to pinpoint flaws, gain dynamic profiling and analysis of the software, including third-party code.

Learn about Sternum observability >>