2026-03-06 • 43:12
Discover how bounds safety technologies in Xcode eliminate entire classes of out-of-bounds vulnerabilities in your existing C and C++ codebases. Learn how to use the C bounds safety extension with annotations like __counted_by to enforce bounds checking without breaking ABI compatibility. And explore how to enable C++ Safe Buffers and the hardened C++ Standard Library to protect production apps with minimal performance overhead.
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.
Speakers: Yeoul Na, Louis Dionne
Part of: Fortify your app: Essential strategies to strengthen security
Downloads from Apple
Transcript
Hi. I’m Yeoul on the security tools team, and I’ll be joined by my colleague Louis. Today’s session is about safety, a technology that eliminates an entire class of memory safety vulnerabilities from your C and C++ code. Here is what this session covers. First, why bound safety matters with a real vulnerability as a motivation. Then how it works. Why you should adopt bound safety and how to adopt it in practice. I’ll also talk about efforts to build a broader bound, safe ecosystem. Finally, the approach for C++.
Here’s a real example. Recently, a zero click vulnerability hit a widely used audio library. Zero click means an attacker can silently execute arbitrary code without the user doing anything. On most platforms, this allows attackers to silently take over a device. But on Apple platforms, the exploit simply fails. Because bound safety technologies made this entire class of bugs an exploitable. And today you get the tools to build that exact same armor into your apps.
Memory safety is still the number one security challenge in systems programming. You’re probably already working hard to find and fix bugs using static analysis, sanitizers, and code reviews. But here’s the problem. Attackers keep finding new bugs. Bug finding tools are valuable, but they are not enough on their own to really shift the attacker model. Entire classes of vulnerabilities need to be eliminated so that even when bug exists, they cannot be exploited.
The complete solution to memory safety is to use memory safe languages like Swift. And that’s absolutely the right choice for new code. But existing code bases are a different story. Sometimes hundreds of millions of lines of C and C++. Complete rewrites would take decades. But people need protection now.
A practical path forward. Existing code is essential. What’s needed are technologies that eliminate entire vulnerability classes, not just find individual bugs. These technologies need to be easier to adopt than a complete rewrite. And critically, they need to be incrementally adaptable. So you can start protecting your code today without waiting for a massive migration project.
Earlier today, Mark gave a great overview of five different categories of memory safety bugs. These are all important problems, but each needs different solutions. And this talk focuses on bound safety, which is one of the two biggest problems along with lifetime safety. Remember the audio codec vulnerability earlier. That was a safety issue. And it represents about half of all memory safety problems.
That means tackling balance. Safety issues will make half of the memory safety issues go away. And I’ll show you how you can protect your apps using Apple’s found safety technology for C. Imagine you are building an image filter for your app. It takes a buffer, a size and loop through the pixels.
But notice the loop condition here. It uses less than or equal to size. Introducing a classic of by one error on the very last iteration, it writes one element past the end of the buffer. While this is a simplified example, it represents the exact kind of out of bounds error that, when combined with attacker control input, becomes the root cause of countless real world exploits.
Here’s how the bounds safety extension for C solves this problem with bounds safety. Indexing requires bounce information. The compiler won’t let you index into a pointer without knowing its bounds. Examine the error message here. The compiler is telling you that image doesn’t have bounds information, so you cannot index into it with a non-zero index. This error guides you to the necessary bounds annotation. Telling the compiler how many elements image points to.
That’s where counted by comes in. Counted by size. Tells the compiler that this pointer points to size element. Now the compiler knows the bounds. It automatically inserts bound checks before every memory access. If the bounds safety bug triggers the program traps before writing out of bounds. The bug still exists in the code, but is no longer exploitable. C programs use pointers in different ways, so the bound safety extension provides a set of bounds. Annotations. Let me show you a common pointer patterns and which annotation to use for each cases. I’ll start with pointers to a single element.
When examining bound safety bugs in C and C++. There’s a clear pattern. Most problems come from array indexing and pointer arithmetic. In this diagram, here’s an array of four integers. The green boxes are the valid elements indices zero through three. The red boxes are out of bounds. Memory. Taking a pointer to arrays of zero points at the first element. And that is safe. But with array indexing or pointer arithmetic, it’s easy to go beyond the array bounds and access that red region.
Interestingly, most pointers in C don’t actually need pointer arithmetic. They just point to a single object like this message T. Incrementing it or indexing it like an array pushes you out of bounds is unsafe and unnecessary. Dereferencing is what you need using the arrow to access a member or the star operator to dereference directly.
And that’s exactly what the single annotation captures. It says this pointer points to exactly one element or is null with single, the compiler prevents pointer arithmetic and array indexing. You cannot accidentally move the pointer beyond that single object because these bugs caught at compile time, it is safe by default.
Okay, so single works great for most printers. The ones that just point to a single object, but for pointers to multiple elements where you do actually need indexing and pointer arithmetic, you need bounce information. You need to know how many elements you can safely access. What’s the valid range?
That bounce information has to come from somewhere. And fortunately most code already has it. Consider process image. The pointer is passed right alongside its size. Or consider message T. The data pointer is sitting right next to data size. This pattern is everywhere in C functions. Path sizes with pointers structs store them side by side. The information is already there. It just needs a way to be explicitly expressed.
That’s where Counted by comes in. It lets you say these pointers bounds are determined by this other variable. You are making explicit. What was already there in the code counted by is the most common annotation. But there are others that capture different bounds patterns. I’ll show you another one with a slightly modified example. Imagine the function now takes a void pointer to process opaque serialized data.
The type is unknown just how many bytes are available. Same thing in the struct. Imagine now takes a serialized data, which is a void pointer. The structure is unknown, but data size tracks how many bytes it contains. This is where you use size by annotation. Instead of counting elements like counted by, it counts bytes.
Now a different example. The allocate pixels function allocates image as an array of pixels. It either returns a pointer to n elements of pixels or null. If the allocation fails, using counted by n elements for the return type would be wrong unless n is zero, because when it returns null, the pointer doesn’t point to n elements, it points to nothing.
This is where you use counted by or null. It says either this points to n elements or it’s no. Use this annotation when the pointer can be null while the count isn’t zero. I covered the most common ones, but there are more annotations for other parents like size Y or null and Devi and others. You can find the full list in the documentation on bound safety extensions.
Now you know how the bound safety extensions works, and here are the reasons why you should adopt it. Two reasons it provides strong safety guarantees and it’s practical to adopt. First, strong safety guarantees mean the compiler always inserts the necessary balance checks. For years, developers have relied on opportunistic balance checking tools like unison and Fortify source.
These are. These tools are valuable, but they catch bugs on a best effort basis. It’s like playing a never ending game of whack a mole. Attackers just find gaps that we missed. Bond safety completely changes the game. It gives you guaranteed checking. The compiler acts as your safety net, ensuring balance information always exists and is always correct. This structurally Eliminates this vulnerability class.
First, the compiler ensures bounce information is always available, so trying to index a pointer without bounce results in a compiler error in the first function. There is no bounce or notation for image, so the compiler rejects the array access in. The second function counted by provides the bounce. So code compiles and compiler inserts bounce checks. This means if code compiles, bounds are checked.
Right. This is one of my favorite features. How many times have you updated a buffer but forgot to update the size? With this extension, the compiler actually understands the relationship between your pointer and your size variable and enforces at compile time at runtime, meaning you cannot accidentally break bounds. Correctness.
Take this example up top. The data pointer is tied to data size down below. The function allocates memory for data but forgets to update data size. This classic mistake usually leaves a stale size value leading to out of bounds errors. But now the compiler catches it immediately and refuses to compile. To fix it, you simply update the size alongside the pointer. Beyond compile time checks. The compiler also inserts runtime checks because the size field is hardcoded to 100. Here, the compiler automatically ensures 100 actually fits inside that original allocation.
With these safety guarantees, the bound safety extension is practical to adopt. First of all, most pointers don’t need annotation. Smart defaults handle the common cases automatically, and you can adapt this incrementally while preserving Abi compatibility. You can convert one file at a time without breaking compatibility with the rest of your code base.
I’ll start with smart defaults. Going back to the message process example. This function takes a pointer to a single message T object, and single was added to express that. Now, because this pattern is so common, the bound safety extension makes single the default for pointers at Abi boundaries. Function parameters. Struct fields, global variables, and nested pointers. So you don’t need to annotate this parameter. It’s already single by default. You only need to annotate when you want something different, like count it by.
Now you might be thinking, hey, do I really have to annotate every single local pointer in my millions of lines of code? The answer is absolutely not because local variables aren’t exposed at the Abi boundary. The compiler does something brilliant. It automatically promotes them into wide pointers behind the scenes. You get full pointer arithmetic, you get automatic runtime checking, and you get all of this safety without adding an annotation to your local code. It just works.
Here’s an example. The process image function uses local variables to track the current position and and to mark the end of the array. Both are automatically wide pointers. No annotations needed. When you increment PTR, the compiler carries the bounds along. When you dereference TR, it’s automatically bounce checked all the safety without adding annotations.
And here’s the most practical reason to adopt this. It is designed for incremental adoption in the real world. Pausing development to rewrite a massive SQL base overnight just isn’t feasible because these annotations don’t change pointer representation at the Abi boundary. A full rewrite is not necessary. You can take a single highly secured critical file, enable bounce safety today, and link it right back into an existing Unannotated project.
There will be practical scenarios where Unannotated APIs, like system headers are required By default, pointers from these headers are treated as unsafe. No bounce information. No. No bounce checks. The compiler cannot verify them, but this allows bounce safe code to still compile and interoperate with older libraries. Here’s an example. Open file handle calls open to get a file pointer. Because file comes from an annotated system header, the compiler treats it as an unsafe pointer to assign it to the safe variable. Safe F an explicit conversion is required.
To do so. Use the unsafe for single macro. It takes the base pointer type and the unsafe pointer. This says, I know these points, these points to a single file object, and I’m taking responsibility for that. The goal is to audit and remove these unsafe constructs over time as proper annotations are added upstream.
All right. That was enough of theory right? Now for the fun part. Here is exactly how to adapt bound safety in your project. Here’s the process. You start by annotating headers. Then work through your files one by one. Enable bound safety for the file adapted test and debug. Once you’ve finished all files, enable bound safety globally in Xcode. I’ll walk through each step.
First, a double bounce annotations in headers where API contracts are defined. Take this header. First, include to check that H. This provides the bounce safety annotations and macros, then use to check Abi or some single. When you bend your headers. This macro ensures consumers automatically treat your Abi pointers as single, not unsafe.
Finally, add counted by or other bounce annotations as needed. That’s it. This header is now bounce safe. If another bounce safe code calls this function, the compiler inserts bounce checks at the call site. Next, you pick a single source file. Enable the extension. To do so, you add the compiler flag. Dash found safety in build phases to enable the feature for the source file you adopted.
Then compile and follow compiler diagnostics. The compiler will immediately flag missing or mismatched annotations. Once enabling bounce safety for the process. Image example two diagnostics appear. The first one says the function definition must match the annotations in the header declaration. The second one says the parameter image doesn’t have a bounce annotation, so indexing into it isn’t allowed.
Counted by size to the parameter fixes both diagnostics. Now run your test and debug any runtime checks. If you hit a trap, the compiler just called an incorrect annotation or a real bug like this. Off by one error. Xcode immediately stops execution and tells us exactly what happened. The referencing above bounds.
You fix the logic here, and the code runs cleanly. Once your tests pass, that file is protected. You can adopt remaining files over time. When your whole project is ready, enable the bounce safety extension in Xcode build settings. To do so. Go to your project’s build setting and find the security section set enable language extension for C. Let me get the extension for bounce safety in C to yes. From that point on, all C code in your target is protected and new code must follow bounce safety rules.
Now I want to be clear this isn’t just a research project. This is proven at scale. Apple is already using this to protect millions of lines of production code. It’s in the networking stack of the kernel that powers Apple platforms. It’s in built in audio and image codecs. Secure boot libraries. firmware of the N1 networking chip and more. It is shipping to customers right now in highly security sensitive systems. Apple relies on this to protect billions of users every single day. Now it’s your turn. Take this and keep your users safe.
Of course, if you write C, you care about performance. You’re probably wondering what the catch is. So performance was measured across the kernel’s networking stack where every microsecond counts. And to be clear, during this test, down safety was fully active across every single control path. With bound safety enabled, 93% of the tests show an overhead that falls entirely within the margin of measurement noise. In the few places where difference is actually measurable. It stays under 2%. The bottom line is you’re getting the holy grail of bounce FC with highly practical performance.
Bounce safety is a huge win for your code base, but it becomes a total game changer when it’s available everywhere. Here is how we are building a broader bounce safe ecosystem. The goal is simple you should be able to protect users on any platform, not just apples, to make that happen. Bounce safety is already open source today in swift fork of clang. is also being actively upstream to mainline llvM so everyone can use it, but it goes even further.
Apple is collaborating directly with the C Standards Committee to standardize biosafety right into the C language itself. Ultimately, this will bring biosafety to every C developer out there, no matter what platform they are building for. Okay, I’ll now hand it over to Louis to talk about the C++ approach.
Thank you. Hi folks. My name is Louis and I work on the C++ standard library here at Apple. It’s great to be here. Let’s dive into the C++ side of things now. C++ is different from C in that it already provides many abstractions that have sufficient information to provide safety. For example, standard span contains a pointer and a size, so it knows what bounds are valid when it is accessed. The same is true for many other components of the standard library like standard vector, standard string, standard array, and so on.
However, the C++ standard does not require enforcing safety in these APIs, and indeed C++ standard libraries have not historically done that. So, for example, the code on the right is a direct C++ translation of the C process image function presented by UL earlier. It uses standard span instead of a raw pointer. However, despite using standard span, This code does not enforce bounce safety by default. Xcode now makes it possible to change that.
This is achieved using a two pronged approach called C++ save buffers. First, Xcode provides tools to help ensure that your C++ code is using the idiomatic abstractions provided by the standard library when accessing buffers. Second, it now also comes with a hardened C++ standard library that can be enabled to catch misuse in many of its APIs.
Now, to help you adopt safer abstractions in your code, Xcode now provides diagnostics that can flag places in your code where you’re accessing buffers in a non idiomatic way. For example, this code is a version of the process image function written using a raw pointer. In this case, the new Xcode diagnostics would flag this code because it indexes using a raw pointer, which is unsafe. So this allows you to spot places in your code that are not using idiomatic constructs for accessing buffers and to remediate to the problem. In this case, one approach would be to rewrite the code using standard span instead, and the error would go away.
Now, to make code that uses idiomatic abstractions actually safer, Xcode also provides a hardened C++ standard library. When enabled in your Xcode settings, the standard library uses the existing bounds information it already contains to check your usage of certain APIs. In this example, Indexing the standard span with hardening enabled would use the existing bounce information it already has to ensure that the access is valid. Now, if the index is not valid, the hardened standard libraries guarantee that your program will terminate. This means that it cannot keep running and potentially corrupt or compromise your app.
It’s also worth noting that the APIs do not change in usage or in contract. A hardened standard library is still just a conforming ISO C++ library. The Abi also does not change when the hardened library is enabled. All of this means that no code changes are required to take advantage of the added safety. This makes it easy to adopt hardening. Now.
To enable both the new Xcode diagnostics and standard library hardening. Navigate to your build settings under security and enable Enforce Bound save buffer usage in C++. In some cases, you may have existing code that uses unsafe constructs, but you cannot update it to use modern idioms yet to still take advantage of the hardened standard library immediately without introducing new errors in your code. You can enable just the hardened standard library by navigating to your build settings under Apple Clang language C++, and selecting enable C++ Standard Library hardening.
It’s also important to note that this is not just a debugging feature, right? Debugging features are extremely helpful to boost your productivity, but they are not sufficient to. Make your code truly safer when running. Running on devices in production. Indeed, code that uses the hardened standard library is intended to ship to production, and hardening in the standard library was designed very carefully for that to be viable. This ensures that your code can be made safer in production when it runs on your users devices and deals with real data, which is where safety truly matters.
When using a hardened standard library, many APIs provide additional safety. Now, the general mental model is that container access APIs and container modification APIs that have direct size requirements are hardened. Indeed, those APIs already have the necessary information to check their bounds, and detecting misuse in these APIs provides direct security value. For example, the indexing operator of containers. They’re back pub back their front methods. Those are all hardened.
Accessing an optional value is also hardened. In general, you can rely on any checked precondition in an ISO C++ 26 hardened implementation to be checked. Now there is also some cases where the standard library already provides checking by default. For example, invoking a standard function when the function is empty already throws an exception. Hardening does not affect these APIs. Other APIs that would be easy to harden, but that would provide limited security value, are also not hardened. In order to minimize the performance impact of hardening on your app, checks that actually provide security value are prioritized.
For example, constructing a string from a null pointer is technically not correct. However, in practice, it already results in a segmentation fault which terminates the program. Because of that, there is limited security value in adding an explicit check for that case. Finally, a notable exception to hardening is iterators. Iterators do not generally store sufficient information to perform bounds checking.
Adding that information would require changing the Abi, and that would make adoption a lot more difficult. For that reason, iterator accesses are not hardened. Now that you have a good understanding of what APIs are hardened, I’ll discuss the experience that you can expect when developing a hardened application.
So when a hardening check fails, the program will get terminated inside Xcode. You’ll be taken to the place where the hardening assertion failed, which is in the standard library, and you can then use the stack frame selector on the left to navigate to your own code. The debugging console will display an error message providing additional context for the failure, and you can then use your normal debugging workflow in Xcode to understand and resolve the issue.
In shipping apps, things are a bit different since there is no debugger to attach, the program will be terminated by a trap, just like what happens for bound safety extensions in C in C++, just like in C. This mechanism is the quickest and safest way to terminate your program, since it provides few options for a potentially malicious attacker to divert the control flow of your program after the assertion has failed. In particular, this means that no message is logged to the console. This prevents leaking information about your program, and it also ensures that binaries stay as small as possible by not storing diagnostic strings in the executable.
But that’s not all. Xcode even provides additional modes of checking which provide different performance trade offs. The hardened mode that I’ve been referring to until now is in fact called the Fast mode. On top of the fast mode, Xcode also provides an extensive mode which adds checks that are cheap but not necessarily security critical. This mode is great to ensure rigorous programming without paying too much of a performance cost.
Then, on top of the extensive mode, Xcode also provides a debug mode. The debug mode performs more exhaustive checking in exchange for a larger performance impact. For example, under the debug mode, the C++ library can check that a comparator provided to standard sort has the properties required for sorting. This is extremely helpful to catch subtle semantic semantic bugs in your app. Now the fast and the extensive modes can be used in production, while the debug mode should only be used during development.
It’s also possible to mix different modes in different files. This can be used to incrementally adopt hardening in your code base or for precise performance tweaking. For instance, you could enable extensive or fast checking throughout your code base, but disable hardening in a single source file that is very performance sensitive. And since hardening does not affect the Abi, all of this works seamlessly.
I’m really quite excited for these features to now be generally available in Xcode. Indeed, Apple has been developing this technology for quite some time, and it’s been adopted internally to get it to where it is today. In particular, this technology was adopted in all of WebKit and in multiple parts of the operating system, including parts of the kernel. No significant performance impact was observed and multiple bugs were found, including some that were very elusive.
In addition to Apple’s own experience, there are many accounts of this technology being used across the industry. For example, Google documented their deployment across their whole server fleet. This corresponds to millions of lines of performance sensitive code. They observed as low as a 0.3% performance impact. After some tweaking, and they reported finding over 1000 bugs, including security critical bugs. Another example of industry adoption is C++ 26, which adopted the notion of a hardened standard library based on the fast mode. So overall, the experience has been overwhelmingly positive, and I’m really excited for this to now be generally available in Xcode.
Okay. So we went over the tools that Xcode now provides to help eliminate important vulnerability classes in both C and in C++ for you. This means that you can now start adopting bound safety extensions in your C code on a file by file basis. You should start with the most security sensitive parts of your code, and then work your way towards the rest of your code when you’re ready to do so. Enable it on your whole project for your C++ code. Enable the fast mode immediately to make your application safer without requiring code changes.
You should also enable the debug mode during testing and development. This will find subtle bugs that you would otherwise miss to catch places in your code where you are accessing buffers without using the idiomatic abstractions of the standard library. Enable the new Xcode diagnostics and. If you want to learn more about what you’ll. And I presented today, extensive documentation is available online. Remember that people depend on your code being safe and that every step counts. So start today and give these features a try. With that, I’ll hand it back to Kurt.