scieee AI-readable full text Open interactive document viewer

Dynamic Detection of Vulnerable DMA Race Conditions

Johannesmeyer, Brian; Giuffrida, Cristiano; Bos, Herbert

Abstract

The drivers of modern operating systems use Direct Memory Access (DMA) to efficiently communicate with peripheral devices. Since the memory accessed by DMA is a shared resource between driver and device, it is a possible source of race conditions. Peripheral devices are also often untrusted, so these race conditions open up a new potential attack vector against a trusted OS kernel. In this paper, we present DMARacer, a dynamic detector called for these DMA-based race conditions in kernel code. DMARacer tracks memory accesses to DMA memory throughout the kernel's lifetime and analyses them for various indicators of race conditions. Additionally, upon detecting a race condition, DMARacer uses taint tracking to trace its impact and identify any potential vulnerabilities it may trigger, such as memory corruption or denial-of-service. We used DMARacer to search the drivers of the Linux kernel for DMA-based errors and find that DMA-based race conditions are a systemic issue in driver code. In total, DMARacer was able to detect 817 problematic memory accesses and 344 vulnerable operations in the scanned Linux kernel drivers.

Full text

Dynamic Detection of Vulnerable DMA Race Conditions Brian Johannesmeyer∗† Qualcomm Technologies, Inc. San Diego, United States of America [email protected] Raphael Isemann∗ Vrije Universiteit Amsterdam Amsterdam, The Netherlands r[email protected] Cristiano Giuffrida Vrije Universiteit Amsterdam Amsterdam, The Netherlands [email protected] Herbert Bos Vrije Universiteit Amsterdam Amsterdam, The Netherlands [email protected] Abstract The drivers of modern operating systems use Direct Memory Access (DMA) to efficiently communicate with peripheral devices. Since the memory accessed by DMA is a shared resource between driver and device, it is a possible source of race conditions. Peripheral devices are also often untrusted, so these race conditions open up a new potential attack vector against a trusted OS kernel. In this paper, we present DMARacer, a dynamic detector called for these DMA-based race conditions in kernel code. DMARacer tracks memory accesses to DMA memory throughout the kernel’s lifetime and analyses them for various indicators of race conditions. Additionally, upon detecting a race condition, DMARacer uses taint tracking to trace its impact and identify any potential vulnerabilities it may trigger, such as memory corruption or denial-of-service. We used DMARacer to search the drivers of the Linux kernel for DMAbased errors and find that DMA-based race conditions are a systemic issue in driver code. In total, DMARacer was able to detect 817 problematic memory accesses and 344 vulnerable operations in the scanned Linux kernel drivers. CCS Concepts •Security and privacy → Operating systems security;•Software and its engineering → Dynamic analysis;•Hardware → Communication hardware, interfaces and storage. Keywords Direct Memory Access, Race Condition Vulnerabilities, Dynamic Taint Analysis, Linux Kernel ACM Reference Format: Brian Johannesmeyer, Raphael Isemann, Cristiano Giuffrida, and Herbert Bos. 2025. Dynamic Detection of Vulnerable DMA Race Conditions. In ∗Both authors contributed equally to this research. †Work done while at Vrije Universiteit Amsterdam. Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and the full citation on the first page. Copyrights for components of this work owned by others than the author(s) must be honored. Abstracting with credit is permitted. To copy otherwise, or republish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. Request permissions from [email protected]. CCS ’25, Taipei, Taiwan ©2025 Copyright held by the owner/author(s). Publication rights licensed to ACM. ACM ISBN 979-8-4007-1525-9/2025/10 https://doi.org/10.1145/3719027.3765126 Proceedings of the 2025 ACM SIGSAC Conference on Computer and Communications Security (CCS ’25), October 13–17, 2025, Taipei, Taiwan. ACM, New York, NY, USA, 15 pages. https://doi.org/10.1145/3719027.3765126 1 Introduction “We trust the hardware pretty implicitly. There are bits and pieces of the kernel that are starting to nibble away at the “do we trust this?” portion, but for the most part, this is not how Linux was designed at all.” — Greg Kroah-Hartman, Linux kernel maintainer (in response to our disclosure) Where OS developers in the past limited their security concerns to malicious user programs [ 43 ], operating system kernels today have to include peripheral devices in their threat model [ 7 ]. Indeed, most general-purpose computers now come equipped with IOMMUs [ 7 ]—mirroring established protection mechanisms that prevent unauthorized memory access by user programs, by means of MMUs [ 36 ], to also isolate memory for external devices. However, there is also memory that the kernel intentionally shares to communicate with either user space or peripherals—and doing so introduces the risk of race conditions. For example, the shared memory used to transfer data from a user program to the kernel in a system call may easily become a source of race conditions. Moreover, the kernel and user/device have different privilege levels, putting them out of reach of standard synchronization approaches and verification tools [ 6 , 16 , 17 , 39 , 44 , 48 ] that rely on all processes synchronizing voluntarily. Previous work extensively studied race conditions, such as double fetches, at the system call interface [11, 15, 22, 28, 29, 46, 53, 54, 58]. However, the problem also exists for drivers at the boundary between kernel and untrusted devices. As with the system call interface, the kernel and device both access shared memory to communicate. Using Direct Memory Access (DMA), a malicious device may surrepitiously modify data that the kernel assumes is valid. Recent work studied DMA-based communication between driver and device and highlighted that standard security practices (e.g., input validation and IOMMU protection) are not yet consistently adopted [ 8 , 10 , 14 , 18 , 20 , 25 , 30 , 31 , 33 , 37 , 40 , 49 , 50 , 52 , 57 , 59 , 61 ]. Despite these findings, the research community has largely ignored the issue of DMA-based race conditions. The shared memory and the different privilege levels mirror the conditions that lead to race conditions and double fetch bugs on the user-kernel interface. However, at the device-kernel interface, they may lead not just to “classic” TOCTOU-like conditions, but also other, less-known CCS ’25, October 13–17, 2025, Taipei, Taiwan Brian Johannesmeyer, Raphael Isemann, Cristiano Giuffrida, and Herbert Bos but equally dangerous, ones. Examples include races in streaming DMA, as well as cases where attackers corrupt kernel-initialized data. The latter category is of particular concern as they are often exploitable [ 8 , 31 ] and we find that such vulnerabilities are much more common than TOCTOU ones. This raises the question of how to detect such conditions in the various device drivers found in modern kernels? In this work, we fill this gap by identifying DMA-based race conditions using insights from prior research on user-to-kernel race conditions. Unfortunately, applying these insights to the deviceto-kernel domain poses unique challenges. For instance, few (if any) of the interactions across this boundary are standardized and interposition is hard: devices may interact with the kernel through custom driver interrupts, and the kernel may access DMA through ordinary memory operations. Thus, we cannot incorporate the methods from previous studies of unsafe DMA accesses directly and must instead adapt them specifically to DMA race conditions. In particular, unlike unsafe DMA accesses, DMA race conditions require analyzing memory accesses collectively, as well as tracking of long-lived state. For this purpose, we use dynamic taint analysis (DTA) [ 35 ] to track such complex state across complex interactions. We present DMARacer, a dynamic DMA race condition detector for the Linux kernel. DMARacer monitors runtime invariants to determine whether DMA accesses are race-free. When DMARacer identifies a violation—a race condition—it reports the access as an errant access. If the errant access involves attacker-controllable data (e.g., loading from coherent DMA), we apply taint to the data to track it throughout the kernel’s execution. Finally, if the tainted data reaches a security-sensitive operation (e.g., a memory write pointer), DMARacer flags it as a vulnerable operation. Like other sanitizers, DMARacer instruments the kernel to detect these issues during runtime. Specifically, it (i) tracks DMA state by hooking into every DMA operation, (ii) identifies errant accesses by monitoring all memory accesses, (iii) identifies vulnerable operations through DTA policies, and (iv) covers DMA race conditions by executing a variety of device-to-kernel interactions. In combiation, it enables DMARacer to detect DMA race conditions. By applying race condition detection to the novel domain of DMA data, DMARacer identifies 817 errant accesses and 344 vulnerable operations across the kernel—with false positive rates of 0% and 9%, respectively. Moreover, our case studies show that these race conditions are not easily eliminated: many are deeply embedded in the semantics of existing drivers and APIs. Contributions. We make the following contributions: • We present a new dynamic analysis approach for detecting DMA race conditions and vulnerable device-to-kernel interactions. • We develop DMARacer, an open-source 1 DMA race condition detector for the Linux kernel. • We evaluate DMARacer on a recent kernel to identify hundreds of errant accesses and vulnerable operations, and present case studies that highlight that the issues are difficult to mitigate. 1DMARacer is available at https://github.com/vusec/dmaracer. 2 Background Race conditions. Race conditions are non-deterministic errors that occur when several processes access a shared resource without synchronization[ 34 , 44 ]. A common synchronization mechanism are locks that grant exclusive access to the variable for a limited time. Many synchronization primitives require the cooperation of all involved processes to work correctly. They do not work if one of the processes is malicious and ignores the synchronization primitive. TOCTOU bugs. A special kind of bug caused by race conditions are time-of-check to time-of-use (TOCTOU) errors. Here, a piece of code first checks whether a certain property holds for a shared resource. The rest of the code then incorrectly assumes this property still holds [ 41 ], even though there is no mechanism in place that ensures the immutability of the shared resource. An unprivileged attacker can abuse this behavior to potentially exploit the code that relies on the checked property. In a concrete attack, the attacker would set the shared resource to a ’good’ state to satisfy the check, and then modify it before the first to a ’bad’ state that causes unintended behavior. Double fetch bugs. A common attack scenario involving TOCTOU errors are kernel system call handlers where they are also referred to as double-fetch bugs [ 22 , 28 , 46 , 53 , 54 , 58 ]. In this scenario, the attacker performs a system call as an unprivileged user and the shared resource is the memory containing the system call arguments. This memory is accessible by both user and kernel while the system call is being performed. The attacker can therefore modify an argument between the time the kernel checks it for validity (1st fetch) and the actual processing of the arguments (2nd fetch). If the attacker is successful and is able to bypass a critical check such as a buffer size check, the consequences of such an attack can result in information leakage or privilege escalations [53]. Direct memory access. Direct memory access (DMA) is a hardware feature that reduces the CPU workload when communicating with peripheral hardware. In a system with DMA, a dedicated DMA controller is responsible for moving data between system memory and hardware. The CPU itself is only responsible for initiating the data transfers and can be utilized for other tasks while the data is being moved. Kernel drivers utilize DMA by allocating a special DMA buffer that is bound to a device’s registers or memory. The buffer itself is used like any other plain memory buffer in C and can be directly written to and read from without invoking special functions. The driver can decide between two kinds of DMA buffers that differ in when they synchronize their contents with the device. (1) Streaming (or asynchronous) DMA buffers are used for single DMA-based data transfers. The transfer to and from the device is explicitly initialized by the driver. As the assumption is that driver and device do not access the same memory region at the same time, there are generally no strong guarantees with respect to cache coherence. (2) Coherent (or synchronous) DMA buffers are implicitly synchronized between device and system memory. These buffers are set up in cache-coherent memory, therefore any memory writes Dynamic Detection of Vulnerable DMA Race Conditions CCS ’25, October 13–17, 2025, Taipei, Taiwan done by either device or CPU are immediately made visible to the other. Coherent buffers are used when the driver and the device require concurrent write access. DMA errors. The two kinds of DMA buffers impose each a very different challenge for a driver that uses them. For streaming DMA buffers, the main challenge is the correct timing of the synchronization request. All data that needs to be transferred must have been written to the buffer before it is synchronized with the device. Coherent buffers do not impose this requirement, but instead come with different security implications. Because their contents can be concurrently accessed by an untrusted device and the kernel, they can be a potential source of TOCTOU bugs. The possible attack scenario using these bugs is very similar to the TOCTOU attacks on kernel system call handlers (see the previous section). Instead of the user switching out system call arguments, a device could change the contents of a DMA buffer after the driver performed the necessary sanity checks on it. 3 Threat Model We adopt the threat model of previous work [ 10 ], which considers a local attacker who controls a peripheral device (e.g., a USB keyboard) and its workload. The kernel and its drivers are trusted and not compromised. The attacker’s goal is to cause a denial of service (e.g., trigger a kernel panic) or achieve privilege escalation (e.g., corrupt kernel memory). The ability to control the peripheral device allows the attacker to observe all DMA accesses performed by the kernel driver and control the values of the accessed DMA memory. E.g., the attacker can observe that a certain part of the DMA memory is accessed twice within a short time span and change the memory contents so that two different values are read by each access. The system memory is protected by an idealized IOMMU that can protect each individual byte of memory against unintentional writes from an external device. This means the system is invulnerable against accidental exposure of memory caused by too coarse-grained IOMMU protection capabilities [31]. 4 Defining DMA Race Conditions In this section, we will define three possible race conditions that can arise out of improper DMA usage. 4.1 Coherent DMA-based Race Conditions Coherent DMA is simultaneously accessible to both the kernel and a malicious device, similar to how userspace data is simultaneously accessible to both the kernel and a malicious program. Hence, we can gain insights from userspace-based race conditions when defining coherent DMA-based race conditions. TOCTOU bugs. Previous work on user-to-kernel TOCTOU bugs [ 22 ] holds that the kernel should not load the same user data twice within a single execution context (i.e., an interrupt, system call, or new kernel thread). We apply this same rule to coherent DMA: Invariant 1. Avoiding TOCTOU bugs. The kernel should not read coherent DMA data more than once within an execution context. If this invariant is violated, the kernel unsafely races against a malicious device corrupting the previously kernel-loaded data. Although drivers may sometimes busy-poll MMIO/PMIO status registers for ultra-low-latency I/O [ 27 ], polling a DMA buffer itself is uncommon—completion is normally reported via interrupts or other asynchronous mechanisms [ 13 ]. Thus, our invariant holds in practice (see Section 8). TOITOU bugs. Recent DMA exploitation techniques [ 8 , 31 ] target bugs that involve the corruption of kernel-initialized DMA data. We term such cases TOITOU, time-of-initialization to time-of-use, bugs. A TOITOU bug is defined as a race condition with the following three phases: (1) the victim stores valid data in a shared resource, (ii) the attacker overwrites this data after it was stored, and (iii) the victim later loads the data and assumes it is still valid, thus omitting any validity checks. The difference to a TOCTOU bug is that instead of a check being skipped, the user’s attempt to satisfy a check by modifying a shared resource is disabled. Unlike the user-to-kernel domain, where the kernel only rarely initializes data in userspace for later use (e.g., when signal handling [ 12 ]), kernel drivers will oftentimes initialize entire data structures in DMA. These drivers also expect the device to play nice and only access the parts that it is supposed to. Typically, the kernel initializes such data early on (e.g., during boot time), then uses it much later on (i.e., after boot time), all the while, expecting it to remain unchanged. Hence, we can derive a second invariant: Invariant 2. Avoiding TOITOU bugs. The kernel should not read coherent DMA data if it previously initialized it. If this invariant is violated, the kernel unsafely races against a malicious device corrupting the previously kernel-initialized data. 4.2 Streaming DMA-based Race Conditions In contrast to coherent DMA, streaming DMA is only accessible to either the kernel or the device—but not both. The kernel governs this accessibility by invoking synchronization operations that transfer access rights between the kernel and the device. Inconsistent access bugs. Previous work on identifying unsafe DMA accesses notes that the kernel should not access a streaming DMA region when it is synchronized for device access [ 10 ]. Doing so results in an inconsistent DMA access bug because the data accessed by the kernel (either in the CPU cache or a bounce buffer) may not be consistent with the actual data in the (device-accessible) DMA buffer [13]. Hence, we apply this same invariant: CCS ’25, October 13–17, 2025, Taipei, Taiwan Brian Johannesmeyer, Raphael Isemann, Cristiano Giuffrida, and Herbert Bos Invariant 3. Avoiding inconsistent access bugs. The kernel should not access streaming DMA data if the region is synchronized with the device. If this invariant is violated, the kernel unsafely races against the CPU cache or bounce buffer inadvertently corrupting the data. Notably, this bug is very difficult for a malicious device to exploit, so we consider such exploitation out of scope. Instead, it primarily poses a reliability issue when the kernel later uses the corrupted data. Moreover, because synchronization operations are non-blocking, the manifestation of this bug is nondeterministic: the race condition may only occur when the synchronization commits, making it difficult to consistently reproduce and diagnose. 5 Overview Having defined the various DMA race conditions, we now present the main design challenges and how we addressed them to detect these conditions. To detect DMA race conditions, DMARacer has to track all memory operations and determine which are accessing DMA memory. DMARacer must then identify which parts of the kernel are potentially affected by attacker-controlled data from these errant accesses. Figure 1 presents an example of how DMARacer detects vulnerable code. Although this example features a hypothetical TOCTOU bug for illustrative purposes (given its similarity to well-studied double-fetch bugs), it is important to note that, as discussed in Section 8.3.1, most vulnerabilities in practice stem from TOITOU bugs instead. A Tracking DMA regions. DMA memory regions are dynamically created and then accessed like any other buffer via C pointers in the rest of the kernel code base. We therefore cannot hook a standardized access method (e.g., copy_from_user() on the kerneluser barrier) to detect DMA memory accesses. Instead, DMARacer hooks all relevant DMA methods and maintains its own metadata for the location and state of all DMA buffer. In Section 6.1, we detail how we hook the variety of DMA APIs within the kernel. B Identifying errant accesses. DMARacer has to determine all DMA memory operations and identify any unsafe DMA accesses among them. As DMA is accessed via normal store and load instructions, DMARacer uses its collected DMA metadata to determine which memory operations access DMA buffers. For errors like TOCTOU that involve several memory operations, DMARacer also has to reason about the relationship between different memory accesses. We support this post-hoc analysis of several DMA operations by additionally maintaining a list of all DMA memory accesses. In Section 6.2, we describe how our monitor checks whether DMA is accessed, and if so, whether the access violates an invariant. C Identifying vulnerable operations. Once DMARacer detects an errant DMA access, it is still unclear whether the attacker-controlled from the access is actually vulnerable. DMARacer searches for potentially exploitable code by applying taint to attacker-controlled DMA data and tracking it through the kernel’s execution. This taint spreads across the kernel during execution until it reaches Table 1: DMA operations hooked by DMARacer to track DMA state at runtime. DMA Op. Function Hooked DMARacer Handler Map dma_alloc_attrs() Track a coherent DMA buffer dma_map_single_attrs() Track a streaming DMA buffer __dma_map_sg_attrs() Track a streaming DMA SG list Unmap dma_free_attrs() Untrack a coherent DMA buffer dma_unmap_page_attrs() Untrack a streaming DMA buffer dma_unmap_sg_attrs() Untrack a streaming DMA SG list Sync dma_sync_single_for_cpu() CPU-sync a DMA region dma_sync_single_for_device() Device-sync a DMA region operations that can enable exploits if certain operands are attackercontrolled (e.g., the address of a store operation). In Section 6.3, we describe our taint policies, and the subtleties in tracking DMA data. D Covering DMA race conditions. Unlike the user-kernel boundary with syzkaller [ 1 ], there is no production-ready tooling for testing the device-kernel boundary. Given that DMARacer isadynamic analysis tool, we therefore also need a way to run drivers and give them meaningful workloads that utilize DMA operations and spread them across the kernel. To overcome this challenge, we leverage the flexibility of a virtual environment to invoke device-to-kernel interactions. This allows us to easily configure the kernel to support a variety of drivers, attach multiple devices, and run diverse devicespecific workloads. In Section 8.1, we explain this pipeline, and how future work could build upon our flexible tooling. 6 Detecting DMA Race Conditions In this section, we detail each component of our approach to detecting DMA race conditions. 6.1 DMA Operation Hooks Table 1 presents our DMA operation hooks, which allow us to track DMA state at runtime. For both coherent and streaming DMA, we begin tracking a region at a map (or allocate) operation, and stop tracking it at an unmap (or free) operation. When streaming DMA is mapped, we designate it is as device-accessible; for every synchronization operation thereafter, we designate it as either CPUor device-accessible. Moreover, because the kernel may synchronize DMA partially—i.e., only a subset of the region—we track synchronization state at a per-byte granularity. Applicability to various DMA APIs. The kernel offers a variety of APIs to map, unmap, and synchronize DMA. However, because we hook the lowest-level DMA operations, which are called by these APIs, we therefore also hook into these various APIs. For example, the various interfaces for allocating coherent DMA—e.g., the generic ( dma_alloc_coherent() ), managed ( dmam_alloc_coherent() ), pool ( dma_pool_alloc() ), and driver-specific (e.g., hcd_buffer_alloc() ) APIs—all eventually call dma_alloc_attrs() . Therefore, by hooking dma_alloc_attrs(), we also hook such allocation interfaces. Dynamic Detection of Vulnerable DMA Race Conditions CCS ’25, October 13–17, 2025, Taipei, Taiwan USB keyboard & audio vmxnet3 NIC e100 NIC Overview void driver_func() { dma_ptr = dma_alloc_attrs(...); if (*dma_ptr > 10) return; x = *dma_ptr; arr[x] = 0xdeadbeef; dma_free_attrs(..., dma_ptr, ...); } ❷ Run various devicespecific workloads. Ⓑ Monitor memory accesses to identify errant DMA accesses. Ⓐ Hook DMA operations to track DMA regions. Ⓒ Use taint policies to identify vulnerable operations. Ⓓ Invoke device-to-kernel interactions to cover DMA race conditions. ❸ Track the coherent DMA region at address dma_ptr. ❹ Record the first load from dma_ptr. ❺ Report the second load from dma_ptr as an errant access. ❽ Stop tracking the coherent DMA region at address dma_ptr. ❼ Report an attacker-controllable memory write as a vulnerable operation. ❻ Add an attacker taint label to attacker-controllable data. Play audio file Interact with keyboard Handle network request What we do… - Take a generic/virtual/device-agnostic approach? Why we do it… - Maximizing: - Number of drivers supported - Number of devices attached - Device-to-kernel coverage //Run device-heavy workloads to invoke device-to-kernel interactions. Invoke device-to-kernel interactions to cover DMA race conditions. ❶ Attach a variety of devices. Figure 1: The components ( A – D ) used by our approach and the steps ( 1–8) they take to identify an example TOCTOU bug and a dependent attacker-controllable memory write. Design Load from coherent_dma_ptr Load from coherent_dma_ptr (Later in the same execution context) TOCTOU bug! Store to coherent_dma_ptr Map streaming_dma_ptr Synchronize streaming_dma_ptr as CPU-accessible Synchronize streaming_dma_ptr as device-accessible Load from or store to streaming_dma_ptr Load from or store to streaming_dma_ptr Inconsistent access bug! Load from coherent_dma_ptr TOITOU bug! Map coherent_dma_ptr Map coherent_dma_ptr (a) Invariant 1 violation. Design Load from coherent_dma_ptr Load from coherent_dma_ptr (Later in the same execution context) TOCTOU bug! Store to coherent_dma_ptr Map streaming_dma_ptr Synchronize streaming_dma_ptr as CPU-accessible Synchronize streaming_dma_ptr as device-accessible Load from or store to streaming_dma_ptr Load from or store to streaming_dma_ptr Inconsistent access bug! Load from coherent_dma_ptr TOITOU bug! Map coherent_dma_ptr Map coherent_dma_ptr (b) Invariant 2 violation. Design Load from coherent_dma_ptr Load from coherent_dma_ptr (Later in the same execution context) TOCTOU bug! Store to coherent_dma_ptr Map streaming_dma_ptr Synchronize streaming_dma_ptr as CPU-accessible Synchronize streaming_dma_ptr as device-accessible Load from or store to streaming_dma_ptr Load from or store to streaming_dma_ptr Inconsistent access bug! Load from coherent_dma_ptr TOITOU bug! Map coherent_dma_ptr Map coherent_dma_ptr (c) Invariant 3 violation. Figure 2: Policies of the memory access monitor in determining whether an access violates an invariant. 6.2 Memory Access Monitor To monitor every memory access, we insert callbacks to our DMARacer runtime library at memory access instructions. From our callback, we first check whether the access is to a DMA region, and if so, whether it violates an invariant, as outlined in Figure 2. 6.2.1 Coherent DMA Accesses. For loads from coherent DMA data, we check whether the data was previously accessed. Specifically, whether it was previously: (i) loaded from within the same execution context, which indicates a TOCTOU error (Invariant 1), or (ii) stored to within the lifetime of the kernel, which indicates a TOITOU error (Invariant 2). For this purpose, we record every access into one of two structures: (i) if an access loads from DMA, we record it into a local access map, which is local to a particular execution context, and is cleared when the execution context exits; or (ii) if an access stores to DMA, we record it into a global access map, which is global to the entire kernel, and persists through the kernel’s lifetime. If a load from DMA has a preceding load in the local access map, then we report aTOCTOU bug (as in Figure 2a). Otherwise, if it has a preceding store in the global access map, then we report a TOITOU bug (as in Figure 2b). Storing kernel pointers to coherent DMA. We observe one concerning pattern in DMA usage: the kernel frequently storing pointers to DMA. Typically, this may be because it maintains a data structure (e.g., a linked list) in DMA, and expects the device to only access certain parts of the structure (e.g., the “data” of a linked list, but not the pointers). However, because devices generally do not have access to the same address space as the kernel, the only practical reason that the kernel would store a pointer to DMA is if it will use it later. Hence, such an operation is either bad practice (at best), or the beginning of a vulnerability 2 (at worst). If it uses the pointer later, we would report it as a TOITOU bug. Therefore, if the kernel stores a pointer to DMA, we report it as an errant access, because we expect the kernel to use it later, which would then be a TOITOU bug. By reporting the initial pointer store as an errant access, we mitigate the case where our dynamic analysis’ imperfect coverage fails to cover the subsequent pointer load. 6.2.2 Streaming DMA Accesses. For accesses to streaming DMA, we check whether any part of the accessed region is devicesynchronized. If yes, the access violates Invariant 3 and demonstrates an invalid DMA memory access. Figure 2c outlines this process: The kernel may access streaming DMA data if it is immediately preceded by a CPU-synchronization operation. However, the kernel may not access it if it is immediately preceded by a mapping or device-synchronization operation; otherwise, we report the access as an inconsistent access bug. 6.3 Taint Policies Table 2 summarizes our taint policies, which track attacker data from errant accesses to dependent vulnerable operations. 6.3.1 Taint Sources. From our memory access callbacks (Section 6.2), we add a unique taint label to the output of attackercontrollable errant accesses (i.e., TOCTOU or TOITOU bugs). Hence, as is typical of DTA systems [ 35 ], we are able to track the flow of attacker data at runtime by checking its taint label: untainted data is 2 This may also be considered information leakage (e.g., breaking KASLR). However, we focus on the risk of race conditions in this work. CCS ’25, October 13–17, 2025, Taipei, Taiwan Brian Johannesmeyer, Raphael Isemann, Cristiano Giuffrida, and Herbert Bos Table 2: Taint policies to track attacker data to dependent vulnerable operations, while avoiding taint explosion. Taint Policy Type Operate On Justification Source: Track attacker data Output of a TOCTOU/TOITOU bug Controllable by a malicious device Sink: Identify vulnerable operations Pointer of a memory write Enables an attacker-controllable memory corruption primitive Condition of a loop/assertion Enables an attacker-controllable denial-of-service primitive Clear: Avoid taint explosion AND instruction Instruction used to access specific bits of DMA data Kernel hot paths Execution may spill taint into frequently accessed global data Execution context entry Cross-execution context dataflows are challenging to reproduce not attacker data, and tainted data is attacker data. In our case, our taint label identifies the specific errant access (i.e., the backtrace) that loaded the attacker data. 6.3.2 Taint Sinks. We track the flow of attacker data to certain security-sensitive operations. Following previous work [ 10 ], we target operations leading to memory corruption and denial-of-service (DoS) vulnerabilities. Specifically, our security-sensitive operations are: (i) A tainted pointer of a store instruction, because an attacker may redirect it to corrupt memory; hence, we report it as a vulnerable write. (ii) A tainted loop condition, because an attacker may corrupt it to loop indefinitely, leading to a DoS or buffer overflow; hence we we report it as a vulnerable loop. (iii) A tainted assertion condition, because an attacker may corrupt it to fail the assertion, leading to a DoS, so we report it as a vulnerable assert. We note that this is not an exhaustive list of all possible securitysensitive operations; rather, it is a set to demonstrate the feasibility of our approach. DMARacer can be easily extended to hook into more security-sensitive operations. 6.3.3 Taint Clears. A known challenge of DTA is avoiding taint explosion—i.e., the accidental spilling of taint into data that is not intended to be tainted. If unaddressed, we may incorrectly report certain non-vulnerable operations as vulnerable. The ideal solution for avoiding taint explosion is to perfectly model all possible dataflows in the program. However, doing so for every possible operation in a program is a difficult task [60]. Instead, we follow an approach used by other DTA systems [ 21 , 38 , 45 ] and clear taint at various operations to mitigate this issue. In general, our taint policies aim to be conservative to reduce false positives. That is, we clear taint unless there is a likely dataflow for a certain operation or code pattern. Bit-level accesses. It is not uncommon for the kernel to access specific bits of DMA data (e.g., a 1-bit flag). In such a case, the kernel may e.g., load a one-byte word from DMA, then perform a bitwise AND to isolate the particular bit. Unfortunately, bit-level taint analysis—which could be used to accurately model such a dataflow—is known to be difficult [60]. Hence, to avoid false positives arising from such a dataflow, we instead clear its taint. Specifically, we modify the taint analysis’ AND instruction callback—which normally propagates the taint from its inputs to its output—to instead clear the taint of its output. Flows to frequently-accessed global objects. Certain parts of the kernel (e.g., the timer subsystem and the slab allocator) are called frequently throughout the kernel’s execution and handle globally-accessible data structures. Hence, if taint passes into one of these kernel “hot paths”, it soon spills into unrelated operations throughout the kernel. In principle, a heavyweight constraint solver could address this (e.g., by only propagating verifiably controllable dataflows to these hot paths). However, in practice, such approaches do not scale to the size and complexity of the Linux kernel. Therefore, to avoid false positives arising from such dataflows, we adopt an approach from an existing kernel DTA system [ 38 ]. Specifically, we clear taint for all operations and accessed memory within various kernel hot paths3. Flows across execution contexts. The kernel maintains various data structures (e.g., sockets and file descriptors) that persist state from one execution context to another. If taint flows into one of these structures in one execution context (e.g., a timer interrupt), and is used in another execution context (e.g., some syscall), it is often difficult to reproduce the dataflow, as it may rely on nondeterministic (e.g., timing) constraints. Hence, we maintain a notion of taint validity, which enforces same-domain dataflows using the following semantics: (i) An execution context begins with all taint labels marked as invalid. (ii) If a taint source (i.e., an errant access) is covered, its taint label is marked as valid. (iii) A taint sink only reports vulnerable operations if its taint label is valid. As a result, we ensure that the vulnerable operations we identify are accurate and easily-reproducible. 7 Implementation Figure 3 presents the workflow of DMARacer, which: (i) takes the Linux kernel and our runtime library, (ii) builds them with our LLVM instrumentation, and (iii) runs the instrumented kernel as a VM in our custom QEMU hypervisor, where it identifies vulnerable DMA race conditions at runtime. Runtime library. Our runtime library consists of: (i) the KernelDataFlowSanitizer (KDFSAN) [ 21 ] runtime library, which provides support for DTA (e.g., by creating taint labels, combining labels, etc.); and (ii) the handlers for DMARacer’s various instruction-level and function-level callbacks, which collectively identify vulnerable DMA race conditions. Specifically, the DMARacer runtime library handles the following function-level callbacks: First, for DMA operations, it tracks the state of DMA regions. Second, for execution context entries and exits 3 The semantics are similar to those of KMSAN’s __no_kmsan_checks function attribute; indeed, we clear taint in many of the same functions where it is applied. Dynamic Detection of Vulnerable DMA Race Conditions CCS ’25, October 13–17, 2025, Taipei, Taiwan Workflow Run in custom QEMU with various target devices attached Build with modified KDFSAN pass DMARACER runtime library DMARACERinstrumented kernel Linux kernel Execution logs DMA race condition statistics Parse and analyze Figure 3: DMARacer’s workflow: Take the Linux kernel, identify DMA race conditions, and present statistics to aid mitigation. (e.g., __enter_from_user_mode() , irq_enter_rcu() ), it: (i) clears the local access map and (ii) marks taint labels as invalid. Third, for assertions (e.g., BUG_ON()), it performs the taint sink for vulnerable asserts. Furthermore, it handles the following instruction-level callbacks: First, for memory accesses, it: (i) records DMA accesses, (ii) performs the taint source for errant accesses, and (iii) performs the taint sink for vulnerable writes. Second, for backward conditional branches, it performs the taint sink for vulnerable loops. Third, for AND operations, it clears the output’s taint. LLVM instrumentation. We build the kernel with the KDFSAN pass, which we modified to add the above instruction-level callbacks. First, to hook memory accesses, we reuse KDFSAN’s callbacks for LOAD , STORE , and MEMTRANSFER instructions. Second, to hook backward conditional branches, we modify KDFSAN’s conditional BranchInst visitor to check its direction (via the PostDominatorTree API), and if it is backwards, to add our callback. Third, to hook AND operations, we modify KDFSAN’s BinaryOperator visitor to add our callback for AND instructions. 8 Evaluation In this section, we evaluate DMARacer’s ability to detect DMA race conditions. 8.1 Setup Environment. We perform our evaluation on a host machine with an AMD Ryzen 9 3950X CPU and 128GB of RAM, running Ubuntu 22.04.4 LTS (kernel v6.8). We instrument the kernel (based on Linux v6.5.8) with a modified KDFSAN pass (based on LLVM v11.0.1), and run the instrumented kernel as a guest VM in QEMU. Device emulation with QEMU. Dynamic error detectors require that the code is executed to be analyzed. In the case of DMARacer, this means that a driver needs to allocate a DMA buffer and then violate one of the invariants. Additionally, the detection of vulnerable operations relies on tainted data from DMA to spread across the kernel. Device driver code can only be successfully executed if their respective device is connected. Because we do not have access to the multitude of physical devices supported by the Linux Kernel, we instead decided to use the virtual device emulation feature of QEMU for our evaluation. This emulation feature allows QEMU to mimic a connected peripheral which includes the kernel-device communication via DMA. This emulation is only available for QEMU devices for which the QEMU developers implemented a backend. The backend is responsible for communicating with the running kernel in the same way as the real hardware would. Table 4 shows a summary of the 147 QEMU devices we used in our evaluation. These 147 devices include all QEMU devices except CPUs and various test devices (e.g., devices that are only used for educational purposes). Evaluation workloads. For each emulated QEMU device we decided to evaluate, we also needed a way to exercise the DMA communication of the device and spread the data from DMA across the kernel. Unfortunately, standard kernel fuzzing techniques such as syzkaller are not suitable for this purpose for two reasons. First, they focus on general code coverage which is not directly relevant for DMARacer. For example, just covering a corner case in a system call handler will never lead to a report from DMARacer unless the code accesses DMA memory or handles tainted data from DMA. Second, some driver logic depends heavily on input from DMA devices itself (e.g. drivers for input devices), and fuzzers like syzkaller focus on the unrelated user-kernel interface. To overcome this, we instead decided to manually create workloads that are tailored for each device. Table 4 provides a summary of each workload. In general, we mostly use shell scripts that randomly invoke shell commands specific to the device for about 5 minutes per device. For example, the workload for storage devices consists of a shell script that randomly manipulates files, file contents and folders on the mounted storage device. The workload for human interface devices was implemented by using a custom version of QEMU that produces random mouse, touchpad and keyboard presses. We again use QEMU’s virtual device emulation feature for this and emit the same internal input events that QEMU creates when being used with a graphical frontend. The emulation backend then translates these generic QEMU input events into device-specific device-to-kernel communication. For the network devices, we start an HTTP server on the host machine and then send random HTTP requests from the QEMU guest. 8.2 Overall Results Table 3 presents the race conditions found by DMARacer, categorized by: (i) the number of DMA regions affected by errant accesses, (ii) the number of errant accesses, and (iii) the number of vulnerable operations. We group DMA regions based on the calltrace of their mapping operation, as this reflects the number of unique objects a developer may need to address. This grouping is independent of any intermediate DMA mapping APIs (e.g., dma_pool_alloc() ), which may ultimately perform a single low-level mapping operation (e.g., via dma_alloc_attrs()). Conversely, we group errant accesses and vulnerable operations based on the offending instruction, representing the number of unique lines of code that a developer would need to fix. It should be noted that the data flow from errant accesses can span multiple files, which means that the vulnerable operations in one row are not necessarily caused by the errant accesses in the same row (see Section 8.3.2). CCS ’25, October 13–17, 2025, Taipei, Taiwan Brian Johannesmeyer, Raphael Isemann, Cristiano Giuffrida, and Herbert Bos Table 3: DMA race conditions found. Kernel Source Streaming DMA Coherent DMA Aff. Regions Errant Accesses Aff. Regions Errant Accesses Vuln. Writes Vuln. Loops Vuln. Asserts block/* - - - - 32 2 1 drivers/ata/* 3 - 1 2 49 2 - drivers/hid/hid-core.c - - - 1 - - - drivers/hid/usbhid/hid-core.c - - - - 1 - - drivers/net/ethernet/amd/pcnet32.c 2 - 3 10 - - - drivers/net/ethernet/dec/tulip/* 3 - 1 8 - - - drivers/net/ethernet/intel/e100.c 2 10 2 35 37 1 - drivers/net/ethernet/intel/e1000/e1000_main.c 3 - 2 7 - - - drivers/net/ethernet/intel/e1000e/netdev.c - - 2 8 - - - drivers/net/ethernet/realtek/8139cp.c - - 1 10 - - - drivers/net/vmxnet3/* 1 455 5 30 - - - drivers/pci/msi/msi.c - 4 - - - - - drivers/scsi/* - - - - 46 - 1 drivers/tty/serial/serial_core.c - - - - - 1 - drivers/usb/core/* 28 - 3 - - - - drivers/usb/host/* - 8 16 119 18 - - fs/ext4/* - 19 - - - - - fs/fs-writeback.c - - - - 2 - - fs/kernfs/dir.c - - - - - 1 - kernel/dma/* - 4 - - 16 3 5 kernel/printk/printk.c - - - - 2 - - kernel/sched/* - - - - 34 5 - kernel/workqueue.c - - - - 4 - - lib/* - 8 - - 58 6 2 mm/dmapool.c - - - 7 - - - net/core/* - 68 - - 1 - - net/ipv4/* - - - - 9 - 1 net/ipv6/addrconf.c - - - - 1 - - security/keys/key.c - - - - - 1 - sound/core/* - - 1 - 2 - - sound/hda/hdac_stream.c - - - 1 - - - sound/pci/hda/hda_controller.c - - - 2 - - - sound/usb/pcm.c - - - 1 - - - Total 42 576 37 241 312 22 10 Table 4: Tested devices and workloads. Device Kind # Devices Workload Audio Card 13 Playing/Recording audio files GPU 12 Running OpenGL/GPU test software Mouse/Keyboard 36 Random input events Network Adapter 61 Handling random HTTP requests Storage Device 25 Random file system operations Our first observation is that the sheer number of errant accesses (817) and vulnerable operations (344) highlights that DMA race conditions pose a significant threat. However, this alone does not fully convey the mitigation effort required: if errant accesses are confined toasingle DMA region, the fix is relatively straightforward; but if they occur across multiple distinct DMA regions, remediation becomes more challenging. By examining the number of DMA regions affected (79), we see that the problem is indeed widespread, indicating that mitigation may be difficult. Our second observation is that the largest count of taint sinks for TOCTOU/TOITOU errors are vulnerable writes. That is, the attacker has at least partial control over the address of a store instruction. As DMARacer is a dynamic analysis tool, it cannot determine all constraints enforced for each write. However, any of these found vulnerable writes is a potential arbitrary write primitive which can corrupt kernel memory and lead to privilege escalation. In Sections 8.3 and 8.4, we draw more insights by breaking down the numbers based on the source file and type of DMA. 8.3 Coherent DMA-based Race Conditions Our analysis of coherent DMA-based race conditions reveals two notable findings: (i) most vulnerabilities stem from drivers managing complex data structures within DMA regions, which unfortunately makes exploitation easier and mitigation more challenging; and (ii) most dataflows to vulnerable operations occur across kernel subsystems, making them difficult to track without DMARacer’s DTA-based approach. To illustrate each finding, we present case studies that share a common theme: a vulnerable high-level API, which implies that any driver that uses the API may also be vulnerable. 8.3.1 TOITOU-vulnerable data structures. Our analysis reveals that although just over half (57%) of the identified errant accesses are TOITOU bugs—as opposed to TOCTOU bugs—the vast majority of the resulting vulnerabilities (99%) depend on TOITOU-loaded data. Upon manual examination, we attribute this disparity to the Dynamic Detection of Vulnerable DMA Race Conditions CCS ’25, October 13–17, 2025, Taipei, Taiwan fact that TOITOU bugs typically stem from the kernel managing critical data structures within DMA regions. Consequently, the kernel performs complex operations on these data structures, many of which can be exploited. For example, it is common to find entire linked lists—with pointers and all—managed in DMA. We observe such DMA-resident lists throughout the kernel code, e.g.: the UHCI driver maintains a list of “queue headers”; the E100 driver maintains a list of “callbacks”; and the DMA pool API maintains a list of “blocks” (which we will discuss below). Whenever the kernel loads a pointer from these linked lists, it loads an attacker-controllable pointer. Thus, when the kernel dereferences it (e.g., to traverse the list), it performs an attacker-controllable memory access. In contrast, TOCTOU-based bugs are typically more limited, as they involve simpler, less consequential data structures. For example, a common source of a TOCTOU bug is reading a status flag multiple times. If an attacker corrupts such a status flag, the impact is limited because it usually does not allow control over critical operations, such as the pointer used in a memory write. Furthermore, we find that TOITOU bugs are more concerning than TOCTOU bugs for two main reasons. First, they are easier to exploit, as they involve the corruption of long-lived data. Conversely, exploiting TOCTOU bugs requires corrupting short-lived data within a narrow time window, forcing attackers to employ synchronization tricks to precisely time the corruption [ 2 , 55 , 56 ]. Second, they are more difficult to mitigate, as they affect critical data structures, and may therefore require extensive algorithmic rewrites of driver code. Conversely, TOCTOU bugs can often be resolved with minor, localized changes (e.g., combining two reads into a single read). Case study: DMA pool API. A striking example of a TOITOUvulnerable linked list is in the widely used DMA pool API. A DMA pool aims to reduce the overhead of coherent DMA allocations, which are resource-intensive operations. It achieves this by creating a large coherent DMA buffer—the “pool”—from which smaller buffers are allocated as needed. Fundamentally, a DMA pool functions like a heap: it is a structure composed of linked memory “blocks”, which, in this context, are DMA buffers. When a driver employs a DMA pool, it grants the device access not only to these blocks but also to the pointers linking them. Consequently, similar to traditional heap corruption vulnerabilities—where a malicious program corrupts heap metadata to e.g., hijack control flow [ 9 , 23 ]—a TOITOU bug allows a malicious device to corrupt DMA pool metadata, which can trivially lead to arbitrary kernel memory corruption from any driver that uses it, as illustrated by Figure 4. Unfortunately, because the DMA pool API is extensively used, this vulnerability is not confined to a single instance. In fact, every usage of the DMA pool API is potentially vulnerable. Each of these instances could lead to arbitrary memory corruption, highlighting the critical importance of addressing this issue. 8.3.2 Cross-subsystem dataflows. Coherent DMA-based race conditions frequently involve dataflows across different kernel subsystems, making them challenging to detect. Typically, both the mapping of a coherent DMA region and any errant accesses to it occur within the same file, usually within the driver. However, the Example: DMA pool *next_block; ------------- ------------- *next_block; ------------- ------------- *next_block; ------------- ------------- pool->next_block ------------- --(in use)-- ------------- *next_block; ------------- ------------- *next_block; ------------- ------------- pool->next_block kernel_addr *next_block; ------------- ------------- *next_block; ------------- ------------- *next_block; ------------- ------------- pool->next_block kernel_addr --sensitive-- -kernel-data- -to-corruptpool->next_block kernel_addr ... ... ... ❶ The DMA pool initializes its list of blocks, then points to the first block. ❹ The driver again calls dma_pool_alloc(), which incorrectly returns kernel_addr. Therefore, anytime the driver writes to this “block”, it corrupts sensitive kernel data. ❸ The driver makes its first call to dma_pool_alloc(), after which, the pool should point to the second block. However, it instead points to kernel_addr. ❷ The malicious device overwrites the first block's next_block pointer to an arbitrary kernel address, kernel_addr. Figure 4: DMA pool exploitation: The DMA pool API initializes a linked list in coherent DMA, which a device can exploit to corrupt arbitrary kernel memory. data loaded via DMA often propagates throughout the kernel, and any vulnerable operations that depend on this data may occur in separate files or entirely different subsystems. This is understandable because drivers manage DMA, but the data they load can flow into other kernel components. Tracking such dataflows between disparate components is difficult for heavyweight analyses (e.g., symbolic execution) because it requires interprocedural and cross-module analysis. In contrast, DMARacer’s lightweight DTA engine effectively tracks these dataflows, allowing us to identify many vulnerable operations that might otherwise be missed. Case study: Driver-to-swiotlb dataflow. An illustrative example of a cross-subsystem dataflow occurs when a driver improperly saves the bus address of a streaming DMA mapping into a coherent DMA region—thereby making it attacker-controllable—then uses it in an unmapping operation. We observed this scenario in several drivers (e.g., the Intel E100 NIC driver, RealTek 8139C+ NIC driver), and we present one such occurrence in Figure 5. This vulnerability is particularly challenging to detect with static analysis due to several factors. First, the three interactions with the coherent DMA region—i.e., the allocation, initialization, and usage—occur in separate syscalls and interrupts, which complicates