Video hosted by Apple at devstreaming-cdn.apple.com

Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: meet-with-apple-282
$eventId
ID of event: meet-with-apple
$eventContentId
ID of session without event part: 282
$eventShortId
Shortened ID of event: meet-with-apple
$year
Year of session: 2026
$extension
Extension of original filename: mp4
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

Meet with Apple • Session 282

Find security bugs with Sanitizers

2026-03-06 • 13:44

Discover how Address Sanitizer and Thread Sanitizer can help you catch hidden memory corruption and concurrency issues. Learn how Address Sanitizer detects buffer overflows and use-after-free errors across Swift, C, C++, and Objective-C with byte-level precision. Explore how Thread Sanitizer uncovers elusive data races, and how to configure both tools in your automated test plans.

This session was originally presented as part of the Meet with Apple event “Fortify your app: Essential strategies to strengthen security.” Watch the full video for more insights and related sessions.

Speaker: Dan Blackwell

Open in Apple Developer site

Part of: Fortify your app: Essential strategies to strengthen security

Transcript

Hi, I’m Dan and I’m a software engineer here at Apple. I’m on the security tools team and I work on the sanitizers. I’m here today to talk about finding security bugs in your existing code with the sanitizers. In this presentation, I’m going to introduce the concept of sanitizers. And then I’m going to talk about two of them address sanitizer and thread sanitizer, which are the two most powerful sanitizers. To start off, I’ll introduce the general concept.

Sanitizers are bug finding tools. They compile extra extra bookkeeping and runtime checks into your code because of the strict runtime checks. They can help you discover bugs that you didn’t know you have before your customers are affected. They can also be invaluable for finding the root cause of those seemingly impossible crash reports.

Now that I’ve defined what sanitizers are, I’ll talk about address sanitizer at a high level. Address sanitizer finds and throws an error when your program makes invalid uses of memory. Unlike tools such as guard malloc, which only detect issues in heap memory, address sanitizer can also detect issues in stack and global variable regions. It also offers byte level precision to its checks, meaning that even the slightest out-of-bounds access will be caught. I’m going to go through a selection of the types of bugs that address sanitizer can find.

In this code snippet, I’m planning to make a raw copy of this Nsstring variable original. It might not be obvious, but it has an out-of-bounds memory access bug. I’ll explain why. Here I grab a pointer to the underlying bytes of original shown here on the right. And then I allocate original dot length bytes for the copy. And this is where things start to go wrong.

Original dot length does not give me the number of bytes. It actually gives me the number of UTF 16 code units, and the waving hand emoji is made up of two of these. So raw copy now points to an allocation of five bytes. Next, I copy across the bytes from raw original into raw copy, but the last two bytes are going to be out of bounds of the allocation. This is exactly the kind of tricky bug that would be easy to miss and could be exploited. Fortunately, Address Sanitizer detects and warns me about the buffer overflow here.

Another bug type detected by address sanitizer is use after free. In this C++ example, I keep a standard vector of tabs and keep a pointer to the current active tab. On the right, I can see there is space for one element reserved in the vector, but it hasn’t been populated yet.

I opened my first tab mail and set it as the active tab. I then open another tab. News. This forces the vector to grow its capacity, which in turn means creating a new allocation and moving the elements there. Unfortunately, active did not get updated when this happened and now points at Deallocated memory. When I try to reload the tab pointer that by active address sanitizer throws an error indicating the use of Deallocated memory.

As shown in the examples. C, C++, and Objective-C are all supported for Objective-C. This includes manual and automatic reference counting. Finally, so is Swift. Although Pure Swift is unlikely to have any invalid uses of memory. Importantly, though, Address Sanitizer also works across languages. Here I have a Swift array numbers. In order to use this array from a C function, I’m going to take an unsafe buffer pointer. Notice that the pointer nums points to the first element of the array.

Now I’ll call the sum function. To do so, I need to pass in a pointer to the start of the array and its length. The sum function will iterate over each element of the array and sum them up. But I’ve made a classic error here. Rather than iterating while less than Len, I’ve used less than or equal to. As a result, the final iteration is going to go out of bounds.

Address sanitizer catches this and throws an error about the buffer overflow. It’s worth noting that this is the kind of security bug that using the band safety extension for C solves. Here’s what you need to know about address sanitizer. Firstly, it’s only for use during development, not for runtime hardening. For a tool this powerful. It has low memory and runtime overheads approximately 2 to 3 times for each.

It’s very good at finding bugs that occur during testing before they get to your customers, so it’s important to do as much testing as possible. With it enabled, Enable it for automated tests such as Unit and UI tests, enable it when doing manual testing during development, and to get the most out of the tool. It’s really important to trigger edge cases and rare paths.

To enable address sanitizer during development. First, navigate to the Scheme Editor by opening the product menu, then opening the scheme submenu and selecting Edit Scheme. From here, navigate to the Run Scheme. Then under the diagnostics tab, check the Address Sanitizer checkbox to enable Address Sanitizer for a test plan. Open the product menu, then test plan and edit test plan. Open the configurations tab and scroll to the Runtime Sanitization section. Then select the Address Sanitizer Row and set it to on. I’m now going to demonstrate how to use address sanitizer for testing.

Okay, so here I have my code from the mixed languages example slide. I take my Swift array numbers. I’m then going to take an unsafe buffer pointer to this and call my C function sum in there. I’m going to iterate over each element, summing them up, and return the result back to Swift.

And then I’m going to print out the array and the result. So let’s run this now. Firstly, I can see that my program ran successfully. But definitely this value here is not correct. So I’m now going to enable address sanitizer under product scheme edit scheme. And by checking the Address Sanitizer checkbox.

So clicking the run button rebuilds with sanitization and straight away I can see that it’s caught something a heap buffer overflow on this line. On the left, I can see the call stack that led to this, as well as being able to see that this is immediately after a 64 byte heap allocation. In this side menu, I can see where the allocation was, and unsurprisingly, it’s when I constructed that Swift array.

Back in the stack frame, I’m in a live debugging session so I can see the values of the variables. Currently I can see that I is the same as Len, which tells me there’s something wrong with my loop bound condition, as discussed in my slides. That’s because I’ve got this extra equal sign here. So removing that and rebuilding my application. I’ll now see that it ran successfully. And what’s more, I’m now getting the correct value out of sum. Okay, I’ll now go back to my slides.

The next sanitizer I’ll go over is thread Sanitizer. Thread sanitizer detects data races and other concurrency issues. A data race occurs when one thread writes to a memory location, and a different thread accesses the same memory location without synchronizing. In the code example on the right, I take the head from the queue pending, send it, free it, and then increment head.

This works fine until there’s another thread doing the same thing. Now I have two worker threads and here is a possible interleaving of operations thread one reads head with value zero. Fred two then also reads head with value zero. Fred one sends the pending message and frees the object. It then increments the value of head to one.

Here’s the data race. Depending on which order the read on Fred two and the write on Fred one occur. The value of index on Fred two can differ in this case, Fred two having the stale value of head leads to it reading pending zero, which has just been freed by Fred one address sanitizer detects the use after free here when it occurs, and in this execution the use after free has occurred.

This is the same code running on the same two threads, but this time there’s no interleaving operations between them. Address sanitizer cannot detect an issue in this execution. But great news. Even though I observed the expected outcome, thread sanitizer still detects there’s an issue here and warns me about it. This read on thread two is part of a data race, and Thread Sanitizer also shows me the memory access that it’s racing with. This right on thread one. This is why thread sanitizer can be extremely useful for finding the root cause of those seemingly unreproducible bug reports.

To fix this, I use a serial dispatch queue to ensure that each of these sections runs atomically and that the read and writes ahead are synchronized on the right. I’ve wrapped these in dispatch async, and they’ll run on a dedicated serial queue. Using Swift concurrency prevents any data races.

Thread sanitizer is enabled in the same way as address sanitizer. Note that the two are mutually exclusive, so you can only run with one enabled at a time. For automated testing, you can create two different configurations for your test plan, one with address sanitizer enabled and another with thread sanitizer enabled.

Sanitizers play an important role in adopting memory integrity enforcement. Address sanitizer helps you find and fix invalid memory accesses. These will cause crashes when Emmy is enabled. Thread sanitizer helps you find data races, which often lead to use after frees. Fortunately, Emmy will cause a crash when a use after free occurs, which prevents exploitation, but will be a cause of frustration to your customers. Finding and fixing the bug with thread sanitizer will keep your customers safe and happy.

Here’s what you need to do next. First, go add address sanitizer to the config on your test plans, especially if you’re using any C, C++, or Objective-C in your code base. Then go add Fred Sanitizer to your test plans if you’re using any C or pre concurrency. Swift.

Adopt Swift concurrency to prevent data races in your Swift code. And finally, the next time you get a crash report that you can’t explain or reproduce, reach for the sanitizers and see if they detect any issues. Maybe they can provide a hint as to how that bad state was reached. Thank you. And over to Kurt.