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-265
$eventId
ID of event: meet-with-apple
$eventContentId
ID of session without event part: 265
$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 265

Fortify your app: Essential strategies to strengthen security

2026-03-05 • iOS, iPadOS, macOS, visionOS, watchOS

Join us to bolster your app’s security and help protect user data in this all-day activity at the Apple Developer Center Cupertino. Whether you’re hardening an existing app or starting a new project, you’ll learn directly from Apple engineers about how to reinforce your app from the ground up.

Learn about the security challenges facing modern apps, and explore a comprehensive suite of tools and technologies designed to help safeguard user data. Discover how you can protect your C and C++ codebases with powerful features like Memory Integrity Enforcement, pointer authentication, and memory bounds safety. Find out how to adopt Swift for your most security-sensitive components, taking advantage of its inherent safety and modern abstractions to write secure, high-performance code. And get guidance on how to build a clear security roadmap for new and existing projects, from high-level strategy to hands-on implementation.

Speakers: Curt Clifton, Pierre-Olivier Martel, Mark Mitchell, Devin Coughlin, Enrico Perla, Filippo Bigarella, Oliver Hunt, Yeoul Na, Louis Dionne, Doug Gregor, Félix Cloutier, Dan Blackwell

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it may have transcription errors.

Hello, and welcome to the Apple Developer Center here in Cupertino. I'm Curt, and I'm a technology evangelist on the Worldwide Developer Relations team. Today, my colleagues and I are excited to share techniques to improve the security of your app by reducing memory safety vulnerabilities. There's a lot to cover. But first, I want to share more about our venue, the Apple Developer Center.

The Developer Center is part of Apple Park. It's a great venue for today's event with areas for connection and collaboration. This room is big, sir. It's a cool space with a great setup. I just love this screen. It's designed to support a wide range of activities, including in-person presentations like this one, studio recordings and live broadcasts, which we're also on right now. Hello, world.

There are also labs, briefing rooms, and conference rooms that allow us to host activities of various sizes. This is one of four developer centers around the world where Apple hosts designers and developers for sessions, labs, workshops, and more. Has anyone here previously been to a developer center? Great.

Whether you're new or returning, we're so happy to welcome you. Our team loves connecting with developers. Our goal is to help you make the best apps for Apple's platforms. Since WW25, we've hosted over 300 activities globally, including labs, workshops, and presentations. Next week, my colleagues and I are leading a workshop in New York City on the new design. Attendees of the workshop will get hands-on experience with Liquid Glass as they update their code and designs. To learn more about upcoming events near you or online, check out developer.apple.com/events.

Before I get started with today's agenda, some tips for people in the room. To stay connected throughout the day, use the Apple Wi-Fi network. It's open to you and there's no password. Thank you. If you need to recharge at any point, there's power available at every seat. You'll find it on the front of the armrest.

For everyone tuning in today, these presentations will be recorded and available online after the event. There's no need to record video or to live stream. However, if you'd like to take photos, you are totally welcome to do that. After the event, we'll also send out follow-up information with links to any resources mentioned in the presentations, so you won't miss a thing. With the preliminaries out of the way, I'm excited to share more about today's event.

Today's topic is memory security vulnerabilities and how to address them. People's devices are an integral part of their lives. Their devices contain so much of their personal data and private details. As a result, they are a prime target for attackers seeking to access this information. Today's presentations will cover how to protect your apps from attackers by finding memory bugs and fixing them, applying general protections, and even writing memory-safe code.

For those of you online, you can use Slido to send in questions for Apple engineers. We have a team answering questions and they're eager and ready to support you. I ask you to stay focused on today's topic. The team is looking forward to answering questions about the examples in the presentations and your general app security issues.

On Slido, you'll be able to browse questions asked by others, upvote your favorites, and read the team's answers. This is a great resource, and it's driven by your questions, so please do ask. Before we kick off the technical program, I'd like to welcome a special guest to the Big Sur stage. Please join me in welcoming Apple's head of platform security, Pierre Olivier Martel. Pierre? Thank you, Curt.

Welcome

Good morning, everyone. My name is Pierre-Olivier Martel. I'm the head of platform security in the Security Engineering and Architecture group. First of all, I want to thank you all for coming today and joining us online. It's really great to see so many of you here. Now, we all know how critical our devices have become to the people who use them. And protecting those devices and the data that they hold, whether it be communications, intimate moments, health or financial information, is the reason that we build privacy and security into our products from the start. Apple achieves the best security by deeply integrating hardware, software and services. Over the past 15 years, we've built many industry-leading security defenses into our product. These range from Secure Boot, which provides software integrity from the very first instruction, to authentication technologies like Touch ID, Face ID, and Optic ID, to the end-to-end encryption protocol that power iMessage or iCloud Keychain. On-off stars remain the same. We think that security should be built-in, on by default, and easy to use. And the result of this work is that security researchers agree that iPhone is the safest and most secure consumer mobile device. Now, the common denominator behind many exploits, including those targeting other platforms in the industry, is that they exploit memory safety vulnerabilities. Here at Apple, we've been working hard to improve memory safety. For example, by developing with memory-safe languages like Swift and deploying brand new mitigations at scale, some of which you'll hear about today.

Most recently, we introduced memory integrity enforcement with our latest iPhone and Mac models. Now, MIE is a major breakthrough that combines the unique strength of Apple Silicon hardware with our advanced operating system security to provide the industry-first, always-on memory safety protection across our devices without compromising on device performance.

But this chapter does not end with us shipping a secure device to a user. Because the security of Apple devices is also rooted in the strength of the ecosystem around them. So our focus on a secure ecosystem extends through the third-party software ecosystem, where we work with you, our developer community, to provide safe apps. And that takes the App Store and platform security features working together. And here's why this partnership matters so much.

Attackers don't distinguish between the platform and the apps that are running on it. They look for the weakest link. So as we systematically raise the bar on platform security, we need to raise it everywhere. In fact, we must raise it everywhere. And that means working together to get these technologies adopted as broadly as possible. So we are thrilled to share the work we've been doing and to make it possible for you to adopt in your apps many of the same advanced defenses that we use to help protect all our users. So I want to thank you again for being with us today. Welcome. And with that, I'll hand it back to Curt to get us started.

Thanks, Pierre-Olivier. Here's an overview of the agenda for the rest of the day. Our technical program begins with a practical guide to the same memory safety strategies Apple uses to protect its own apps, including when and how to apply each layer of defense, from quick wins like the typed allocator to longer term investments like swift rewrites and enhanced security extensions. Then we'll take a short break to stretch and perhaps enjoy a caffeinated beverage.

After that is a deep dive into memory integrity enforcement and pointer authentication, two powerful hardware-software security features covering how they protect against memory corruption, exploits, and how to ensure your code doesn't undermine their protections. We'll take an 80-minute break for lunch, then return here for three more presentations, starting with a practical guide to adopting bounds safety in C and C++, covering compiler-enforced bounds annotations in C and hardened standard library checks in C++.

Another short break takes us to the home stretch, where we'll learn how Swift's built-in memory safety guarantees compare to C and C++, how to use new low-overhead span types for performance-critical code, and how to incrementally migrate unsafe code bases to Swift using strict memory safety mode and C interop features. Then, in our last session of the day, learn how to use address sanitizer and thread sanitizer in Xcode to proactively hunt down buffer overflows, use after free bugs, and data races in your code base, including how the sanitizers complement memory integrity enforcement. them.

Finally, we'll wrap up the day for those of you here in the Developer Center by gathering in the lobby for a mixer, where you can chat with Apple engineers and other fellow developers. Now, to kick off our technical program, I'd like to welcome Mark to the Big Sur stage. Mark?

Protect your app with Enhanced Security

Good morning. I'm Mark Mitchell from Apple's Security Engineering and Architecture team. Along with my colleague Devin, I'm going to describe today a framework for how you can protect your apps from memory safety vulnerabilities. abilities. The careful layering and combination of the strategies that Devon and I will talk about today is what has earned iPhone its reputation as the most secure consumer device available. Throughout today's sessions, we'll talk about what these features are, what they're for, and give you examples of how Apple uses them to protect our apps, the OS, and customers. With Xcode, you can now use the same technologies that Apple employs to protect users of your apps.

Apps touch many parts of our lives. They're essential tools that everyone trusts with their private details. Location, browsing history, photos, messages, finances. And at the same time, these apps and users are connected to the internet. And so security vulnerabilities in them can open users up to attack. The cost of these attacks range from fraud and identity theft through to blackmail, and even in rare circumstances, threats in the physical world. People expect their data to be kept private and secure, and it's a violation of trust if that promise isn't upheld. Security is the technical foundation that enables privacy.

I'll begin with an overview of what memory safety means and the different types of vulnerabilities it can lead to. Next, I'll provide a high level overview of some security engineering strategies that you can use to protect your apps, and describe how we've successfully employed them in ours. And finally, Devon will show you how you can put these strategies into practice with Xcode using the enhanced security capability.

So what do we mean by memory safety? Well, memory safety bugs are one of the most common classes of vulnerability in software, where the attacker relies on memory corruption to change or subvert program behavior. At Apple, security engineers think about memory safety in terms of intended and unintended behavior in the program.

For typical usage, the program works the way the developer intends and only performs actions that make sense. So you can think of the flow through a program kind of like a maze. The path through the maze is the code the developer intends to be used for some operation, and the other routes represent code paths that aren't used or reachable. And the unreachable paths could be in frameworks that you're not using that do things like turn on the camera or send an email.

Attackers can take advantage of those memory safety vulnerabilities to try to trick the program into an unexpected state that lets them perform very different actions than the developer actually intended. In this example, a memory safety vulnerability anywhere along the intended path leads to whole new ways to solve the maze or run code that aren't intentional by making or taking new paths. And that could mean calling that code to force the app to turn on the camera or send an email.

Memory safety is foundational for security, and without it, we can't guarantee the higher-level security properties. For example, it doesn't matter how strong your password hashing scheme is if an attacker can just gain access to the system by taking advantage of a memory safety vulnerability. See? Here's an example. It's a simple login function. It takes a copy of the password in a text field from the user, checks if it's a debug build, and skips authentication, so maybe the developer can test more quickly at their desk, and calls a function to compare a copy of the password against some hard-coded secret and returns the result of the login.

The particularly observant ones amongst you might have noticed that giant red X. There is obviously a memory safety vulnerability in this code, and you really shouldn't copy it. Here, the developer is allocating space for 32 characters on the stack to hold a copy of the password. But the Stricopy API doesn't know how much space is available, and it'll copy as many characters as there are in that text field into the buffer that it's given. And the result is a stack buffer overflow.

Behind the scenes in memory, this is roughly what's happening. The local password storage is right before the memory used to hold the debug login flag. If the password is, say, 18 characters, of course it safely fits. But what if the attacker sends more than 32 characters? Well, in that case, because this program was written in a language that's not memory safe, it will start to overwrite adjacent memory.

And here, that's that memory used to store the debug login flag. And the result is that regardless of what the developer intended or passed into this function, when the program hits this if statement, if the value's been overwritten or changed by an attacker, they can force it to skip authentication and return true anyway.

But actually, there's an even more serious problem in that code. Overwriting the debug flag can let the attacker log in without authentication. But in the memory following the debug login flag is the address of the function pointer that the program will call to do its comparison. If an attacker supplies an even longer password, they can overflow out of the local password buffer, over the debug login flag, and change that function pointer to anything they want.

And that means that when the program tries to call its compare function, it will actually be comparing, it will actually be calling any function of an attacker's choosing. And now the attacker has full control over this process to reach any of these other code paths and potentially steal users' data.

So that example is a buffer overflow, which is a violation of one of the properties of memory safety. But there are actually five axes. Bound safety ensures that all accesses to memory happen within the bounds of a memory allocation. Lifetime safety ensures that memory is only used whilst it's valid and hasn't been reused for something else. Type safety ensures that programmers can't accidentally access memory through a different type than was intended. Guaranteed initialization ensures that memory is initialized before it's used. And finally, thread safety ensures that different threads can't stomp on each other's memory.

So here's what an attack tends to look like. Generally, an attacker targeting any app will be looking to not only access data from that app or compromise and access data from that app, but will use that as the first step in a chain of bugs that goes on to attack other system components, even elevate privileges to the kernel in order to undermine the platform security model. Because they're looking to not only access files from across the system, but also other assets, like location data, photos, contacts, microphone. So apps are often the front door to the rest of the larger attack.

At the same time, modern applications offer so much functionality that many of them have been granted access to most of these assets in normal use. As the OS continues to evolve its security posture, it's reasonable for us to expect that in the future, attackers might be content to just exploit your apps and collect the data that they've got access to from that environment and not need to move on to the rest of the platform. And that's why it's really important that we're now all pushing forward together with security at the same time. Now I'm going to talk through the strategies in use at Apple to protect applications and services like messages, Safari and mail from memory safety vulnerabilities. In any sufficiently complicated code base, it's accepted there will always be vulnerabilities.

And so at Apple, we rely on multiple layers of security engineering that layer and complement one another. There'll be deeper dives into many of the technologies that I'll introduce throughout today, but I'm here to introduce the concepts, how Apple thinks about applying them, and some ways that you can assess their efficacy and the effort that'll be required to use them in your applications. I'm gonna talk about five strategies.

I'll cover how to make memory safety vulnerabilities impossible in your code by using a memory-safed language such as Swift. I'm sorry. how to make vulnerabilities unreachable by reducing the available attack surface. making vulnerabilities unexploitable with mitigations, containing the damage of an exploit if one happens, and finally some techniques for finding and eliminating vulnerabilities from your code.

The most comprehensive approach to defending from memory safety vulnerabilities is to use a language that makes them almost impossible to create in the first place. Apple has invested heavily in Swift, both as a secure and performant language, and it offers memory safety out of the box. It powers not only many of your applications, but also parts of our operating systems. And that includes more and more codebases that are critical to security, such as WebKit, complex parsing, and even embedded environments like the secure Enclave processors firmware.

Of course, many apps have large existing code bases written in the C family of languages. They are not memory safe. Enhanced Security does provide tools such as F-bound safety and its annotations for C, and C++ standard library hardening to help you reduce the risk of bound safety errors. But think of them as a stepping stone on the way towards memory safety. They're not the final goal because they don't address those other four axes. ease.

Comparing C-based languages and Swift across those five axes, the C-based languages offer none of these protections. Whereas Swift provides bound safety through the use of bounds checked arrays and other Swift standard library abstractions. It provides lifetime safety with automated reference counting and ownership. it. It guarantees type safety by requiring that casts are checked at runtime. Swift guarantees initialization by requiring you to initialize your variables before they're used. And with Swift concurrency, it provides thread safety by preventing data erasers.

So that's a really quick tour on how to make memory safety vulnerabilities impossible. Check out the writing security sensitive code in Swift session later for more information. The second strategy that Apple employs heavily in security-critical codebases is known as attack surface reduction. And the theory here is that as developers and security engineers, you can't know where all of the vulnerabilities in your code and the frameworks that you rely on might be. But you can limit the amount of code that an attacker can potentially reach to the minimum set required for your functionality. And that greatly reduces the chances that a vulnerability exists in the code that's actually used in your app.

Apple uses this technique throughout the system in order to reduce the space an attacker has available to work in. And that ranges from technologies which restrict which complex document formats can be parsed, to conditionalizing functionality based on if a counterparty is trusted, through to disabling entire features in lockdown mode.

As an example of format restrictions, if you know that your app is only ever going to send and receive JPEG images, then allowing your receiver side code to handle images of any other format exposes millions of lines of parsing code for an attacker to look for vulnerabilities in that it just doesn't need to.

So in that case, the best way to have an immediate impact and improve security would be checking for a well-formed JPEG or configuring core graphics to only parse that format for your process and just drop the traffic if it doesn't match what you expect. Thank you. Another place your app might be unintentionally exposing a tax surface that isn't strictly necessary is when rendering web content through web views that you use directly for things like in-app browsing and through frameworks that you might import from third parties, like advertising SDKs. Starting in iOS 26.4, there are two powerful new options to WebKit called Enhanced Security Web Views. And these modes allow you to control the way that your app handles content from the web with some additional security hardening.

By default, WKWebView offers all the power, functionality, and performance of WebKit right inside your app, while still providing all of Safari's security mitigations and architecture. The first new mode, Restriction Mode Maximized Compatibility, maintains the full web compatibility that WebKit currently supports, whilst removing a large percentage of the complex framework code and increasing the use of the latest hardware security mitigations.

The second of these new modes, restriction mode lockdown, goes even further, and it gives you the ability to opt your app's WK web views into the same extreme level of protection as lockdown mode, and it removes rarely used web technologies and further reduces options for an attacker. At Apple, we've begun adopting these enhanced security web views throughout the OS, including for Mail, Quick Look, Captive Portal, Shortcuts, and Apple Ads in iOS 26.4. And that was a really good time for you to try them out too.

A tax office reduction offers a huge return for security investment, but the assumption must still be that memory safety vulnerabilities will exist in any remaining code written in an unsafe language. In those cases, the next most effective defensive strategy is to impact the ability for a malicious actor to exploit those vulnerabilities.

We do that using a combination of technologies known as security mitigations. And mitigations as a concept aren't new, they've evolved and existed over the last 30 or 40 years. And Apple is constantly working to invent and provide new ones. And many have been available to your apps, and when we're confident that they're safe to use, even defaulted on in many cases, without you having to do anything.

Mitigations range from early defenses against buffer overflows like Clang's Stack Protector, to non-executable stacks and ASLR, right through to modern hardware-assisted technologies like kernel integrity protection and fast permission restrictions. In Xcode 26, there are new powerful software and hardware assisted mitigations that you can use to protect your apps.

Pointer authentication is a hardware technology assisted by the compiler. It can detect and prevent entire classes of vulnerabilities from being exploited, and provides enforcement that even if an exploit does occur, your app's code flow integrity is maintained. Memory integrity enforcement, which launched with iPhone 17 and iPhone 17 Pro, is the result of five years of research, modeling, and engineering, and is built on the robust foundations provided by the secure allocators, coupled with enhanced memory tagging extensions, and security policies known as tag confidentiality enforcement. That's a lot, and there will be a lot more on those mitigations in the adopt memory integrity enforcement and pointer auth session later. and you can read all about MIE on the security.apple.com blog. Apple believe that memory integrity enforcement represents the most significant upgrade to memory safety in the history of consumer operating systems.

In rare circumstances though, sophisticated attackers might still be able to find a vulnerability that can be reached and exploited despite our mitigations. In those cases, our final line of defence is known as containment. Consider the change from earlier. In the scenario where the attacker has found and exploited a memory safety vulnerability in the app, they now have full control of it. The ideal goal would be to trap the attacker in an environment like this, so they can't touch app data or the rest of the platform.

Now, since the very early days of iPhone, apps have run in a sandbox that helps to prevent malicious access and protect user data. The app, of course, can still access its own data and use system services that provide-- that power the frameworks that the app's using. Against a highly sophisticated attacker though, an even more extreme level of isolation is needed. It wouldn't be possible to just disconnect a full app from the system like this. They require too much access to do things like draw the UI or access resources to function in that kind of environment. So a new approach is necessary. In security-critical apps like Messages and Safari, Apple has designed a multi-process architecture that moves processing of complex attacker-supplied data into a heavily sandboxed environment with almost no access to system resources, such as other services, or even the kernel.

And the result of that is that we can move the risk of unknown vulnerabilities into a place that, even in the event of a successful compromise, traps the attacker in an environment that's devoid of assets like messages or cookies. The environment also has very few opportunities for finding options to elevate privileges and access user data. With enhanced security in Xcode 26, your apps can also make use of these heavily constrained secure environments. And they're called enhanced security extensions. I'll go over how Messages uses this architecture when it receives a photo.

So Messages calls its security extension the blast door. And the security policy is simple. Treat all assets that are received as malicious until they've been parsed and broken down into primitive types or detonated inside the blast door. And this policy means that the Messages app, which is higher privileged, only has to validate the simple types that it receives back from blast door. And the complex, risky parsing happens at the lowest privilege. So message arrives in over the internet into the Messages app, which without attempting to do any parsing or processing on it, sends it directly to Blast Door.

Inside that secure blast door process, it's time to begin to process and parse the image, which remember might have been sent by an attacker. Obviously in the vast majority of cases, the image is valid. The messages transcodes it from a potentially complex type into a much simpler format that can be validated and used to display to the user. In the case where the image is invalid, a failure occurs, a message just drops the traffic.

If an attacker is able to successfully exploit a vulnerability in Blast Door, though, they're contained within that second process, which has no access to the messages database or the rest of the system. Thank you. So Blastall now needs to return its image to messages. And the important thing to remember here is that we should consider that security containment process might be compromised. And so any data leaving it and being returned to messages isn't trustworthy. It could be the image that was successfully transcoded, or it could be entirely malicious data under the control of an attacker.

And for that reason, the most important thing about this architecture is that Messages safely and securely validates that the data it receives is a well-formed image and of the format that we expect. And that combines the previous strategies of both memory-safe Swift and attack surface reduction to do that validation safely and expose the minimum amount of code that's necessary. Once the image has been validated, it's safe to go ahead and show it in a transcript.

That's just one example of how Apple uses containment in our most security-sensitive codebases. Finally, the fifth option available is to find and eliminate as many vulnerabilities from code as possible, whilst pursuing those other strategies. On its own, that's not sufficient to prevent attacks, but it can be effective at finding the weakest spots and identifying the lowest hanging fruit like attackers do.

At Apple, we employ a combination of manual and automated techniques to help us find vulnerabilities in our code. We conduct audits of code during development and retrospectively, and use the Clang static analyzer to both aid human review and in continuous integration workflows. We make use of fuzzing, a technique where tools automatically generate many, many inputs to a program in an attempt to stumble across vulnerabilities. And tools like address sanitizer and thread sanitizer can help with not only debugging code, but also to proactively find vulnerabilities, both during development and in conjunction with techniques like fuzzing.

So there's a framework of how Apple thinks about layering approaches to memory safe code. And you can use all of the tools that I've just talked about too. Thank you. Ideally, make the bugs impossible by using a memory-safe language like Swift. Make vulnerabilities unreachable by reducing the attack surface that's available. Make vulnerabilities unexploitable by applying mitigations to your code. Contain the damage of an exploit if one happens by using an enhanced security extension to do your complex parsing. And finally, identify and eliminate as many of the bugs in your code as possible whilst you pursue those other strategies.

Now I'll hand over to Devon, who leads Apple's development of languages and tools for security. Thank you. Thank you. Thanks, Mark. Hi, I'm Devin Coughlin. I lead the Developer Security Tools group at Apple. Xcode provides a carefully selected array of protections to give your app state-of-the-art security. I'll detail those individual protections, how to enable them, and describe how to combine them together to provide maximum protection.

Security is about navigating trade-offs. For memory safety, the most critical trade-offs are the security benefit and the engineering effort, such as code changes and testing. think about the trade-off as a graph, with benefit on the vertical axis and ease of adoption on the horizontal. Some protections are easy to apply but provide less benefit. Others are harder but give more security. Navigating the trade-offs is about expending the least amount of effort while gaining the most benefit. That's the sweet spot in the upper right-hand corner.

At Apple, our goal is to tightly co-design the hardware, the operating system, and programming languages to provide protections near that sweet spot, all while maintaining good performance. For example, memory integrity enforcement has strong security at vastly lower adoption costs and higher performance than software-only protections. Thank you. I'll describe four different classes of protections. Bug finding tools that help you find security bugs before they even ship and prepare the way for stronger security adoptions. Whole app protections, which increase the security of your entire app, often with just the click of a button.

Code hardening for C and C++, which will help you add bound safety to your existing unsafe codebases. And Swift, which provides full memory safety. These are the same strategies that Mark described, but I'll go into the actual technologies that you can use to run them. Thank you. Mark ordered them from highest protection to lowest. For new codebases, that's where to start. Highest protection for the entire app from the beginning.

But when retrofitting security onto an existing app that's already under attack, it's important to be strategic about which protections you can apply now and which will take some time. Thank you. I will talk about them in order of adoption, from quick security wins that are easier to deploy to incredibly strong protections that will take longer to put into practice. I'm here to help you navigate these different technologies so you can make your app as secure as possible.

I'll start with bug-finding tools. These aren't active protections running in your released app. Instead, they're used at build and debugging time to help you find bugs before they ship. They also pave the way for stronger adoption of technologies by finding bugs early. These tools lie in the lower right quadrant. They're easy to use. Just run them and start fixing the problems. They find actionable bugs, but they cannot find all of them. So although they're an excellent preventative praxis, it's critical to adopt the other protections I'll talk about later. The good news is that running these tools makes it easier to do that.

The first bug-finding tools I'll go over are the sanitizers. They recompile your app with extra instrumentation that helps find bugs as the app runs. They work for C-based languages and Swift. And what's great about the sanitizers is that they have very few false positives. If the sanitizers report a bug, there is a bug.

A dress sanitizer finds memory safety bugs by tracking which memory locations are valid and invalid. It's great at finding the buffer overflows of the kind that Mark showed earlier. And use after free bugs, where the programmer frees memory, but accidentally leaves a dangling pointer. It finds memory corruption on the heap and stack, as well as in globals. It also provides backtraces where the memory was allocated and deallocated to help you fix the bug quickly. ThreadSanitizer finds data races, which happen when one thread interferes with memory accessed by another thread, without synchronization between the two. This can cause memory corruption, even under automated reference counting.

Next is the Clang Static Analyzer. It finds bugs without even needing to run your app. Instead, it simulates possible paths through the program. This means you don't need to have test coverage to find a bug. But the trade-off is that the tool may have false positives. That is, it may report a bug when there isn't one. The analyzer supports C, C++, and Objective-C. it finds many security-sensitive bugs, including buffer overflows, use after freeze, use of uninitialized memory, and insecure API use. Enable these checks in your target's build settings, and then run the analyzer by choosing Analyze from the product menu.

When the analyzer finds a bug, it will display the issue and the control flow path along which it manifests. The arrows show each step in the program path leading up to the bug. And notes describe key events along the path. For example, in a use after free, the path shows where the memory is allocated, freed, and later used. This makes it easy to understand and fix the vulnerability.

Address Sanitizer, Thread Sanitizer, and the Clang Static Analyzer are key bug-finding tools to help you develop your code. Use them at build and test time. They'll find some, but not all, of your bugs. Thanks. Running these tools are a great first step to make it easier to adopt stronger protections in your app.

And to be clear, more needs to be done. But they do pave the way to adding additional essential protections. I'm going to go ahead and Next, I'll cover whole app protections. These provide an excellent baseline level of hardening for your entire app, including your code, libraries, and system frameworks. They're easy to add and provide a very strong boost to security. I'll cover five different whole app protections: the typed allocator, hardware memory tagging, pointer authentication, read-only memory, and enhanced security extensions.

The first whole app protection is the typed allocator. It's a new system allocator that protects against use after free attacks. It's fantastic, because it lies near the sweet spot in the upper right corner. It has good protection and is extremely easy to adopt. Use-after-free bugs are a form of memory corruption where the program allocates memory and then frees it, but accidentally leaves a dangling pointer behind. The type of that freed memory is called the victim.

An attacker exploits the dangling pointer by manipulating the program to allocate another type, the aggressor, in that same location, and then causing the victim pointer to access data from the aggressor as if it were of the victim type. This is type confusion. If the aggressor is able to trick the victim into treating attacker-controlled data as a pointer, they can modify unintended memory or even call unexpected code.

With a typed allocator, the compiler and operating system work together to prevent type confusion by probabilistically allocating different types in different buckets of memory. The victim and aggressor allocations will likely be placed in different buckets, so attackers can't rely on type confusion. there is an excellent post on the Apple Security Research blog towards the next generation of XNU memory safety that goes into great detail about applying this approach to the kernel. Thank you. To enable the typed allocator, go to the Signing and Capabilities Editor, add the Enhanced Security Capability, and enable the Build Settings. Turn it on today. It provides very strong protection against use after free vulnerabilities. It's as easy as checking a checkbox and recompiling your app.

The second type of whole app protections is hardware memory tagging. It's extremely powerful against buffer overflows and use after free bugs in heap allocated memory. It's a CPU feature designed to compose with the typed allocator and the kernel's tag confidentiality to provide memory integrity enforcement. It's available on iPhone 17, iPhone Air, as well as M5, Base Max, and Vision Pro. Hardware memory tagging lies very close to that sweet spot in the top right. It has high protection and is easy to adopt, all while maintaining low performance overhead.

Here's how it protects against memory corruption. The typed allocator associates a tag with each pointer and each allocation. The CPU then ensures that the tag in the pointer and the tag in the memory match. If not, that indicates a use-after-free bug or a buffer overrun, so the hardware safely traps rather than allowing memory corruption.

Here's an example with a buffer overflow. The victim pointer has a tag that matches the memory it points to, and the aggressor pointer does as well. With a buffer overrun, the attacker overflows via the aggressor pointer into the victim memory. But since the aggressor pointer has one tag and the victim memory has another, the CPU detects the mismatch rather than allowing the attack to continue.

Memory tag mismatches crash your app. So before enabling the protection, test with memory tagging diagnostics enabled. And also under address sanitizer and thread sanitizer. Then fix any memory corruption. To learn more about how to enable hardware memory tagging, watch "Secure Your App with Memory Integrity Enforcement" on developer.apple.com.

Now, even with the secure allocator and hardware memory tagging enabled, some memory corruption bugs will still be exploitable. The third type of whole app protection is pointer authentication. With pointer authentication, the hardware, compiler, and operating system all work together to provide in-depth defense by enforcing the integrity of control in the app. It's available on iPhone XS and newer, and in all Apple Silicon Macs.

In a control flow integrity attack, attackers hijack control of the app by taking advantage of memory corruption. This puts the program in an unexpected state the developer never intended. Recall the stack buffer overflow Mark described earlier, where the attacker overflowed a password buffer to overwrite a nearby function pointer.

By supplying an extra long password, the attacker could change the value of the function pointer to anything that they wanted. They could then call any function in the program, leading to an unexpected state. With pointer authentication, the CPU cryptographically signs pointers and authenticates them before they're used. If the signature of the pointer doesn't match what's expected, the hardware will safely trap rather than calling unexpected code. In the buffer overflow example, this will prevent attackers from calling an arbitrary function.

Pointer authentication composes nicely with memory integrity enforcement as a backstop of protection. It sits solidly in the center of the benefit adoption chart. It has good protection, but may require some work to adopt, especially in complex C++ code bases. So test your app thoroughly after enabling to make sure you don't have crashes.

And since it's most effective when used in combination with a typed allocator and hardware memory tagging, adopt those first before tackling pointer authentication. nation. It requires building a universal binary, one that has both ARM64 and ARM64e slices, so that your app can run on older devices without hardware support. This means that all libraries in your app must be universal as well. So if you depend on a binary library or framework from a vendor, you'll need to work with them to get a universal version of the dependency.

The fourth protection is read-only platform memory. The dynamic loader is responsible for loading code in your app and its libraries. It's a very attractive target to attackers. If they're able to compromise the dynamic loader's metadata, they can use it to fully control your application. Read-only platform memory prevents attackers from modifying that data, so only the dynamic loader itself can change it. It provides valuable security protection and is extremely easy to adopt. Most apps will not need to make changes to be compatible.

To ensure compatibility, use system APIs to modify DYLD and Objective-C runtime metadata. Don't modify them directly. Thank you. The last type of whole app protection is the out-of-process enhanced security extension. As Mark described, this approach is a form of containment. It provides a very strong security benefit, although it requires some investment to implement. apply it by moving code that processes untrusted data into the extension. Then use secure system XPC APIs to shuttle data from your main app. process that untrusted data in the extension, pass the result back to the main app, and make sure to validate it.

Now, containment assumes that the attacker is able to take advantage of memory corruption to compromise the extension. The goal is to contain that attacker to the process so they will not get access to data in the main application. Thank you. To adopt, assess which parts of your app process untrusted data.

Refactor your code base to separate those parts into a different process. And validate the response from the extension. Don't trust it. Now, this is a complement to other protection strategies. It's not a substitute for them. So adopt after enabling memory integrity enforcement for your whole app. And since factoring out may take some time, pursue it in parallel with pointer authentication.

Whole app protections are just a few of the steps, or just take a few steps to adopt in Xcode. Enable the typed allocator, hardware memory tagging, pointer authentication, and read-only platform memory by adopting the enhanced security capability. Then create an enhanced security extension. adopt these whole app protections to provide a strong baseline level of security for your entire app.

Those protections are great. But you need more protection if your app has a tax surface written in C-based languages. After adopting whole app protections, apply even stronger approaches for unsafe code bases that process untrusted input. Xcode provides protections for both C and C++. I'll start with C++. Most codebases use the standard library for container classes and other widely used abstractions. C++ standard library hardening protects against out-of-bounds accesses in classes like standard span and vector. It safely traps rather than allowing for memory corruption.

For codebases extensively using the standard library, hardening lies in the lower right quadrant of the security adoption trade-off. It's very easy to adopt and provides solid protection. For even more protection, Xcode's Bound Safe Buffer usage in C++ option provides a stronger guarantee. In this mode, the compiler rejects unsafe raw pointer arithmetic. Instead, it requires the use of idiomatic library abstractions like standard span, string, and vector.

In this way, the combination of compile time checks to prevent raw pointer arithmetic and runtime protections in the hardened C++ standard library bring bound safety to the language. Now, unlike C++, C doesn't have the high-level language features, such as operator overloading, for easy-to-use library abstractions for bound-safe pointer arithmetic. To fill this gap, Apple created a new language extension for C that bakes in bound safety directly and is pursuing incorporating this extension into the language standard.

Here's how it works. Safely indexing into a pointer requires information about the bounds of the memory pointing to. So to ensure safety, the compiler prevents indexing into that with an error when it can't determine the pointer's bounds. You can then add bounds to annotations telling the compiler how to determine the bounds. And it will insert bounds checks to safely trap at runtime on an out-of-bounds access. In this way, the combination of programmer-provided annotations and compiler-generated runtime bounds checks brings bound safety to C.

Xcode's C and C++ bound safety features reside in the upper left corner of the quadrant. They provide strong guarantees, eliminating an entire class of vulnerabilities, but they do require code changes to the source. apply them after you've already adopted whole app protections, like memory integrity enforcement, to layer in even more security for your most sensitive C and C++ code.

Now, up until this point, I've talked about tools that are designed to retrofit protections onto your unsafe C, C++, and Objective-C codebases. These are strong protections, but full memory safety requires a memory-safe language. Swift is memory safe and takes advantage of the platform's security protections at the OS, hardware, and language level.

Swift 6.2 provides a new lightweight family of library abstractions designed for low-level use in parsers and other cases where security and performance are critical. For example, the new span family of types allows access to continuous unowned memory. SPAN is fully memory safe. It uses advanced features and the Swift standard library to guarantee both lifetime and bound safety. Thank you. SPAN is also fast. It's great if you need to guarantee zero overhead, runtime overhead for lifetime safety and highly optimized balance checks. Thanks.

With SwiftSpan, the combination of compile time checks for lifetime safety and runtime checks for bound safety provides both security and low overhead. Amen. there are two strategies to secure your app with Swift. Adopting a new code and rewriting existing security-sensitive code. Use both strategies. Peace. Swift makes it easy to write memory-safe code. If you're not already writing new code in Swift, start now.

The code you write today will become what attackers target in the future. So write new features in Swift. And when you're refactoring or modernizing existing subsystems, take that opportunity to write those in Swift as well. For your security surface, don't wait for an opportunistic refactor. Instead, proactively rewrite those components in Swift. The most critical areas to focus on are parsers, especially for media and protocols, complex state machines controlling object lifecycle, and anything that processes untrusted input.

Now you know how to protect your app from malicious attackers. These are the same protections Apple uses for its own apps. They're carefully crafted. When layered and applied at key places in your code base, they'll give you best-in-class security. To adopt them, enable enhanced security in Xcode, including memory integrity enforcement and pointer authentication, to provide a baseline level of protection for your whole app. contain processing of untrusted input and an enhanced security extension, and protect your unsafe C and C++ code bases with bound safety extensions.

and use Swift, which is fully memory safe for all new code and targeted rewrites of Security Surface. Security is more important now than ever. Take these steps today to protect your app, your users, and their sensitive data. Thank you. Thanks, Devin and Mark, for that great overview. Now we'll take a short break. Please join us back here and online in Big Sur at 1125 Pacific Time.

had a good break and I hope those of you here in person had a chance to chat with your fellow developers. The next presentation covers a unique combination of hardware and software security. I find this work amazing, and I think you will too. Please join me in welcoming Enrico.

Adopt Memory Integrity Enforcement and Pointer Authentication

Welcome to the first deep dive of the day. My name is Enrico Perla. I'm a security engineer, and I'll be later joined on stage by my colleagues, Filippo Bigarella and Oliver Hunt. We're going to look at two of our most exciting security features, memory integrity enforcement and pointer authentication.

This is going to be an advanced talk. We are going to talk about memory allocators, pointers, and security models. I will start by giving you an understanding of the length by which memory integrity enforcement goes to protect your application. And Filippo will show you how to address specific custom memory management implementation that can hamper the security application or just flat out don't work with memory integrity enforcement. We will then switch topic. And Oliver will take the stage to cover another angle of our security strategy, how we use pointer authentication to prevent attackers from achieving arbitrary code execution.

But enough with introductions. Let's start with memory integrity enforcement. We mentioned earlier how the majority of our security issues are memory corruption bugs. With memory integrity enforcement, our mission is to make a large swath of these unexploitable. That's huge. They no longer are a security problem for your application.

We're releasing memory integrity enforcement the very same way we use it for ourselves. There's no holding back here. And that's because we strongly believe that first-party app and third-party app are equally critical to protect our users' safety. That's why we put together some great resources to start with memory integrity enforcement. Release a blog that describes all the security axes upon which memory integrity enforcement operates.

We have a tech talk that gives you step-by-step on how to integrate memory integrity enforcement in your application. And we also put together some great hand-to-hand documentation much around the same topic. There is no need to take notes. We'll follow up with links with all of these via email. And if you're watching later, they're also attached to the online session. So just be sure to check them out. They're great. I am not going to give today a full introduction to memory integrity enforcement. And instead, I will just laser focus on how application interact with its security centerpiece, the type-aware secure memory allocator that leverages memory tagging extensions.

MTE is a hardware technology and one of the biggest investments behind memory integrity enforcement. And it's also arguably the one with the most visible impact over application. So let's take a quick look at it. It's a lock and key system. Locks are assigned to memory at a 16-byte granularity. That's a much lower granularity than a page, and it's kind of ideal for dynamic allocations. Keys are instead stored in the pointer hybrids. Lock and keys are generally referred to as tags, hence the name memory tagging. Software controls the assignment of tags. That's what we call the tagging policy. In the picture, it is a software decision that the yellow buffer tags 7, and so do the two pointers on the left.

Hardware, instead, implements what we call the checking policy and only allows accesses when the lock and key match. Now, even if pointers now have tags, this do not affect software that much. In fact, enabling memory integrity enforcement is extremely straightforward, just a few clicks of Xcode. Contrary to other technologies that require a non-trivial adoption effort, MIE works largely over unmodified software, unless-- I mean, there has to be a catch or we wouldn't be here today-- unless your application implements custom memory management. I've danced around this topic a few times, so let's go over what that means. There are three patterns that can be found in application which lead to custom memory management. I'm going to introduce them here in order of likelihood. The first one is allocator wrappers. Allocator wrappers are interfaces that wrap the system allocator API and do that usually for either portability reasons or you have to implement some additional logic around memory operations.

they are by far the most common found construct. And so, opposite to the other two cases that I'll talk about in a moment, I'm going to give you here a quick example to enforce the idea. Here, we define a function named allocateMemory, which is one that actually calls malloc. The rest of the code that you see calls malloc directory, but instead always goes to this interface. Let's put a pin on this example. Filippo will come to stage in a short and expand on it.

Second, direct memory managed pattern are pooling and caching strategies. The goal of these strategies is to improve performance by recycling frequently used objects so that you avoid a round trip to the system allocator. They're also somewhat common, but not nearly as much as allocator wrappers. And lastly, something that is extremely very in application, thankfully, but is much more, a little more prevalent in frameworks, which are fully custom memory allocators. In this case, we have a drop-in replacement, which entirely bypasses the system default one.

Intuitively, all these three patterns are detrimental to the security of memory integrity enforcement, as they interfere or entirely bypass the system allocator. And so, unsurprisingly, here comes the key recommendation of the day: Use the default system allocator. I'm going to stay a little while on this slide. Use the default system allocator with no wrappers, no caches, or even custom implementations. We spent a lot of time making it scalable and fast for the vast majority of users. And it was re-implemented from scratch over the last three years. So if you took performance numbers more than three years ago, please re-evaluate them. You might be pleasantly surprised. And we rewrote it so that it shines for mega integrity enforcement. Now, we do understand the SAM obstructions have a valid reason to exist. Maybe you just have some legacy code that limps around and you have to carry it forward. That's fine. That's why in the rest of this talk, we're going to see how you can maintain them. But to do that, you have to meet the extremely high the security bar that our locator meets. To do that, you need to understand why we built it that way, which means that we should have an understanding of the security science behind it. So let me start with what attackers do, which is exploiting systems. And with something that hopefully will resonate and actually demystify this topic a little bit for you all.

Writing exploits is the inverse of debugging. Whenever you debug a memory corruption issue, you start from a crime scene. There's some memory that was incorrectly modified or incorrectly used that led the program to misbehave. You try to piece together the parts to figure out what was the cause. What was the bug? What was the piece of memory where the corruption originated from?

In exploiting, it's exactly the other way around. You start from some memory, and that is incorrectly handled, and you try to find some memory that is worth modifying. And to put it in a more scientific way, we take an approach that is a little unique in the industry and is actually attacker-centric.

we have one piece of memory that we call the aggressor type. This is the one that is generating the corruption. Now, the term type here does a lot of work. It refers to the intrinsic characteristics of the memory location. Could be a data structure, could be a string, it could be a collection of records. Then we have the victim type. That's a memory that is valuable to the attacker to modify. That's the target of the memory corruption. The memory might contain a password, a function pointer, or something that later will give the attacker the ability to perform arbitrary code execution.

These two types are somewhere in memory, so there is a specific distance between them. They could be adiabatic, in which case the distance is one, or they could even overlap, in which case the distance is zero. We assume that an attacker can observe the system and perform any arbitrary number of operations. They can talk to the kernel, look around the file system, anything their privileges allow them to do. We don't rely on putting a cap on attacker actions.

The attacker wins if they control and predict the distance from the aggressor to the victim. That's it. That's the whole essence of writing memory corruption exploits. And this might seem simple, and well, good science usually is, but it's actually very profound. It took a long time for us to get there. And it is important because it highlights what are our options when it comes to defend against memory corruption. And what it shows is that we really have only two levers that we can pull, types and distance. From a defender perspective, The whole game is to make type selection and distance impossible or extremely hard for an attacker to predict and control. And this is why our secure memory allocators all implement four key security properties.

First of all, they are type aware. Traditionally, allocations done by a malloc are just bag of bytes. The locator doesn't know much more than the size that was requested. It doesn't know about the intent of the allocation. Our secure locators instead understand types. And this huge leverage is achieved with both manual typing, mostly using the kernel, and compile time automated typing, which is what we use in user space.

Once we have type information, we can start playing. Our locators can start making it hard for an attacker to control them. We can separate type into different regions in memory. And because we have a lot of types, we can't really have one region per type, which would be ideal. So what we do, we collect together those that have similar characteristics. We randomize this collection at boot time so that the groups will be different among different devices, which forces attackers to find a way to fine tune their exploit for each different combination they might run on a device.

Much on the same vein, we randomize the placement in memory of these type regions. Once again, we do that a boot. So then we introduce further variance across devices. This once again frustrates attempts by the attacker to predict the distance among different type classes. And last but not least, we leverage memory tagging extension to stop attacks within the virus type classes. We assign different tags at each allocation and free cycle, and ensure an even distribution of tags across the whole region. We also enforce that no two adjacent objects ever have the same tag, making any distance between one and small sizes impossible to exploit.

This is how our security allocator operates to stop memory corruption bugs. I will now hand the stage to Filippo, who will cover what you can do to prevent the three common custom memory management patterns that I discussed earlier, so they don't hamper the security of your application. Over to you. Thank you. Thank you. My name is Filippo and I'm a security engineer in CIR. Now, as Enrico said, in most cases, adopting memory integrity enforcement does not require any code change on your side.

However, there are some scenarios where we do need some extra care. And these are exactly what I'll be walking through right now. We will go over how each one of them impacts memory integrity enforcement and its security model and explain what the best resolution approaches are. So let's start by looking at what is perhaps the most commonly found obstruction across all kinds of code bases, allocator wrappers.

Let's do that by picking up the example that Enrico has shown a moment ago, the allocate memory function, as it gives us the chance to define a set of features that you should look for to identify allocator wrappers in your code base. First of all, wrappers are functions that add logic around the system allocator API. They are generic in the sense that they allow requesting memory that is used to store different types. And they are used throughout the codebase as an abstraction layer to interact with the allocator. So all of this seems fairly no-cause, right? Well, let's go over how type isolation actually works in practice to understand the security implications of allocator wrappers.

Here we have a bunch of code that's implementing some packet processing logic. Don't worry, we don't need to read through all of this. In fact, let's focus on the call sites to the system allocator interfaces. This is where the foundations of type isolation are. It's actually the compiler, the one who's doing all the work for you.

When you enable support for the typed allocator in Xcode, we turn on a compiler feature called type memory operations. With this feature, under the hood, the compiler will automatically infer a type for each of your allocation call sites and rewrite each call into a type-aware one, passing the typed information as an extra argument. You can picture the compiler assigning different shapes to each allocation, and these shapes represent exactly the information that is then passed down to the system allocator, which uses it to isolate allocations based on their types by implementing its type bucketing policies.

When you implement a wrapper, these call sites become opaque to the compiler, which will only be able to see a single shape. And now all your allocations will be bucketed together by the system allocator, as it will only see the type information produced at the call site within the wrapper.

So let's see what we can do to address this problem. Of course, if the logic implemented by the wrappers is not critical to the functionality of your code, the simplest solution is to just remove the wrappers altogether and call the system allocator interfaces directly. If you do need to keep the wrappers, you can implement type-aware variants that will properly forward the type information down to the system allocator by adopting type memory operations, the same compiler technology that is used throughout the operating system. So let's walk through that process step by step.

You start by declaring a type-aware variant of your wrapper, which takes an additional type-id argument right after the size argument. then you want to annotate your untyped variant using the _malloc_type macro. By specifying the corresponding type-aware variant and the position of the size argument, we are instructing the compiler to transform all calls to the untyped variant into calls to the type-aware one, and also to synthesize a type descriptor at each call site. Finally, you need to implement the type-aware variant. But this is very easy. You can keep all your additional logic intact. The only change required is calling the appropriate type-aware malloc interface by forwarding the type descriptor value that you get as an argument.

With this approach, you do not need to make any change to the code that you're actually using in the wrapper. The compiler will automatically provide the type information at each call site for you. So this is just a brief overview and for a lot more in-depth information, I encourage you to take a look at the documentation that covers this approach.

All right, now we understand how to deal with allocator wrappers. Let's now move to pools and caches. When we talk about pools, we refer to all those approaches that implement some form of recycling of objects of the same type, in order to avoid a round trip to the system allocator every time one of such objects is disposed and then brought back into use. Caching strategies also fit into this category.

In this context, the key notion to understand is that these abstractions change the lifecycle of the objects that are being recycled. And this has serious security implications with regards to the protections offered by memory tagging. So let me go over what those implications are by walking through an example.

Here we have a pool of objects, each of which is backed by an allocation that is tagged using memory tagging. When we extract an object from the pool, we normally get a pointer to it, which will have the same tag as the memory associated with the allocation. When we are done with the object, we put it back into the pool. Now consider the scenario where there is a bug in our code and we keep around a dangling pointer to that object. This is exactly what could lead to a use after free vulnerability that can be exploited by an attacker.

If we simply recycle the object, this means that both the dangling pointer and the new live one will still have a valid tag. An attacker that has control over the dangling pointer will be able to modify our object after it has been recycled, leading to memory corruption that can change the logic of your app.

In order for the allocation to be protected from such vulnerabilities, the tag should be updated before the object is recycled. After re-tagging, any access performed using the pointers that have the old stale tag will fail, safely terminating your app and preventing exploitation. So let me cover how we can achieve that in practice.

We have implemented a recycling policy in our packet processing code. When we need to allocate a packet, we first try to extract it from a threadLocalQueue that we maintain and that we refill in the releasePacket function when we're disposing of objects. As you can tell, this approach is subject to the exact problem that we have just seen. So what can we do to maintain the protections that MTE provides? Of course, you might have guessed it by now, the best solution is once again to use the system allocator directly. This ensures that each and every allocation is properly retagged, providing you the protections that you expect from memory integrity enforcement.

We spent years designing and implementing a system allocator that is both secure and that outperforms the old allocator in all scenarios. And indeed, with this thread-local caching, our system allocator is also optimized for scenarios such as this one. In the rare cases where this approach might not be suitable for you, we encourage you to profile your code and explore different strategies that do not change the life cycle of the objects. For example, by pre-allocating the objects that you will use in batches.

All right, so this approach makes it extremely easy to adapt your pooling strategies to preserve the security properties that MIE has to offer. And now I'd like to spend a moment talking about customer locators. This might not be necessarily implemented in your application code, but they might be part of any of the external dependencies that you're using. Libraries have historically implemented them either for performance reasons or for portability across platforms.

In the presence of a custom allocator, all bets are off. It is very important to understand that the code that's using a custom allocator is not getting the benefits of MAE. And in particular, your app cannot benefit from the protections of type isolation and memory tagging. In these scenarios, you should really evaluate the security implications of keeping such implementations around. So, I'm guessing you know what our suggestion is going to be. - No. transition that code to using the system allocator directly. We truly believe it is a foundational block for the security of your app. However, we are aware that there are cases where you cannot just move away from a custom allocator.

And this is why, as of 26.1 across all our platforms, we have included in the SDK all the bidding blocks that you need to implement support for MTE in your customer locator. We will now go through them, but given that each allocator implementation has its own peculiarities, it is up to you to understand how to use these bidding blocks in your use case.

The first thing you need to do is to check whether MTE is enabled at runtime. And you can do so using the OS Security Config API, as shown here. After enabling hardware memory tagging, your app will run with MTE enabled on the devices that support it. But the same code will still need to run on devices that do not support MTE. All the operations that rely on the MTE instruction set architecture should only be performed after checking that MTE is actually enabled in the process.

Next, when you allocate pages of memory that are used by your allocator, you should ask the VM to provide you pages that support memory tagging, by passing the VM_FLUG in your call to VM_ALLOCATE. At this stage, the kernel will provide you with a page whose associated tags will all be zero.

And that's why, finally and most importantly, you want to tag your locations. There are several different possibilities when it comes to choosing a tagging scheme, and there will be a lot of nuances to consider when implementing each one of these. To give you a brief overview of the available API, let's just consider a simple example where we re-tag each allocation when it is freed back to our allocator.

There are two parts when it comes to tagging an allocation: choosing a tag and including it in the pointer, and then storing the tag to the tag storage memory. To choose a tag, the first step is to generate an exclusion mask that accounts for the tag that is currently associated with the allocation. This then allows you to ask the hardware to generate a new random tag by excluding the previously used tag. MTD generate random tag will give back to us a pointer pointing to the same memory, but with a different random tag in the high-biz.

Last but not least, you want to use mteStoreTag to ask the hardware to store the newly generated tag to the tag storage memory, by passing in the pointer with the new tag and the size of the underlying memory block that needs to be tagged. And this is really all you need to tag memory in your locator.

And these were all the basic tools that you need to implement support for memory tagging in your allocator. which actually brings us to the end of our journey in MIE land. But before we conclude, let me reiterate the key takeaways from this section and what you can do to make the best of memory integrity enforcement.

you should enable hardware memory tagging and support for the type allocator in order to provide a very concrete protection to your users using best-in-class technologies when it comes to mitigating memory corruption vulnerabilities. It's really easy to adopt, especially because in the large majority of scenarios, you can get all the security benefits that these technologies can offer without making any change to your code.

However, as we have seen in this talk, there are certain patterns that reduce the efficacy of the protections provided by memory integrity enforcement. We encourage you to audit your codebase and to identify them, so you can better evaluate your exposure. Whenever you identify one of these, the preferred solution approach should be to transition to using the system allocator directly. This really gets you the best of all possible worlds.

However, if at times this is not feasible, we have provided you with the tools and the understanding that you need to adapt such abstractions to support memory integrity enforcement and preserve the security properties that are deeply rooted into the operating system. And now I invite Oliver to the stage to talk about control flow integrity with pointer authentication.

Thanks, Filippo. Hi, everyone. I'm Oliver Hunt. I'm an engineer working on security tooling and compilers here at Apple. At this point in the session, you've learned about how you can protect your code against memory safety errors with memory integrity enforcement. MIE makes exploiting memory safety bugs incredibly difficult, but it cannot protect against every possible attack. I'm going to be showing you how you can adopt hardware-based pointer authentication that That provides your application with control flow integrity. That means even if an attacker can bypass MIE and corrupt arbitrary memory, they still don't have the ability to control the code that your application will execute. With pointer authentication, the hardware, the compiler, and the operating system all work together to ensure the validity of pointers that attack its target when trying to hijack your application. Here's how this works under the hood.

With pointer authentication, the CPU and operating system create cryptographic signatures of pointers, and then they embed those signatures into the pointers themselves. These signatures allow the hardware to check the validity of a pointer before it is used, which gives your code a chain of trust linking the value of a pointer at the time it is used all the way back to its original value.

And pointer authentication continues to protect these pointers in applications that are also using MIE's memory tagging extensions by transparently adjusting the signing and authentication operations to accommodate the existing tag, the additional tags. With all of this in place, when an attacker attempts to corrupt a pointer, the signature is no longer valid and the chain of trust is broken. Now, when your code later attempts to use that pointer, the hardware detects this and safely halts your application. The attacker has now been stopped before they can execute any malicious code.

At Apple, we've been developing the pointer authentication ABI for almost a decade, and we've been using it as a tool to protect our platforms for all of that time. And the design is now stable and robust enough for you to be able to adopt it and employ the exact same protections in your code that we use to protect our own software. While pointer authentication does result in significant changes to code generation, we've made sure that there are only a few places that your application is likely to see any differences. So let's have a look at the big ones.

Return addresses have long been targeted by attackers, and a wide array of mitigations have been deployed over the years to protect them. With Pointerall's indication, this is taken to the next level. Whenever you make a call, you have a return address. With pointer authentication, we combine both the return address itself as well as information about the current call frame into the signature that's embedded into that return address. This information is then also used when authenticating the return address, before it can be followed.

This preserves the chain of trust from the moment the return address is first recorded to the point where it's used, and even prevents an attacker from reusing a validly signed pointer from a prior call. And all of this happens as part of the basic calling convention. And it's only rarely visible to functions that are written in assembly. WE WILL SEE HOW THAT GOES ON Now, if your code does interact with the call stack beyond the basics of just calling functions, we've also made sure that all of the APIs and the compiler features that you're using to do this continue to work seamlessly.

Now, let's go through the features that you're explicitly using for the dynamic control flow of your application. We're going to start with function pointers, because these are the most basic form of dynamic control flow in your application. And they're also the most unrestricted form of indirect code execution in languages like C and C++. So we make sure that they are always signed. This is important, as a common idiom in C and C++ is to cast function pointers into integers or opaque pointers. And we know that you cannot always avoid this. Thank you.

That's why we've made sure that the pointer authentication model maintains the embedded signatures throughout all of these operations without you needing to change your code. This means that once again, the chain of trust is maintained, and there is no point at which an attacker can modify these pointers, even though the compiler may no longer even know that they are a function pointer.

But we know in your code, you don't want to do everything using function pointers. And so you're making extensive use of language supported dynamic dispatch. And that's when you're calling non-final message in Swift, virtual message in C++, or sending messages in Objective-C. In all languages, dynamic dispatch is built on multiple layers of indirection.

In its simplest form, each object instance has a pointer to some kind of type information or a method table. Then, that data structure has another pointer that goes to the actual implementation of each method. This interaction is attractive to attackers because each layer of these can be attacked independently.

That's why in Swift, C++ and Objective-C we've protected each step in this chain. When we create the signature for each pointer in this chain, we include information about the type of the object, the identity or the location of the object, and even the type of the target method. Then, when you make a dynamic call, each step is authenticated using the same information.

By doing all of this work, we've made sure that your application is protected not just from memory corruptions, but even lifetime and type confusion attacks. But this level of protection is one of the few reasons that pointer authentication may require you to change your code. To see why, let's focus on that first authentication step. Thank you. By incorporating the object location into each signature, we've made sure that the signature is only ever valid at this location in memory.

That prevents an attacker from using a memory safety error to copy one object over another. But when you use low-level functions like memcpy to copy these objects, it's fundamentally no different than what an attacker is doing. And the result will be the same. You'll get an authentication failure when you later try to use the object.

This is one of the cases where pointer authentication changes existing code from being undefined behavior that appears to work to undefined behavior that fails at runtime. time. So we've covered just the highest level of protection that you get with pointer authentication. It's a much deeper array, but that's not something that you'll ever see. I'M SORRY, I'M NOT SURE HOW We've designed this implementation so that it will work with your existing code. So I'm sure that you're excited to adopt it in your own applications.

So let me take you through the steps that you need to do to do so. Thank you. Well, before you start adoption in your own application, you'll need to ensure that all of the libraries that you embed or link to support pointer authentication. If you're the author of these libraries, you'll be doing that work yourself. But if you are using externally developed libraries, you will need to reach out to your vendors to have them adopt pointer authentication and provide you with universal binaries.

Once that's done, you can start the adoption effort in your own application. And you do that by selecting the Enable Enhanced Security option in your Xcode project's build settings. This will enable memory integrity enforcement and pointer authentication. education. When you do this, Xcode will automatically configure your project to build a universal binary that contains the familiar ARM64 slice and an additional ARM64e slice that is used by the hardware that supports point authentication. If you want to focus on just adopting one thing at a time, you can focus on pointer authentication alone by just using the Enable Pointer Authentication option instead.

We've designed all of the point-of-authentication-based protections to work with your existing code, and most of your applications will build and run correctly with all of these protections in place. But of course, that's not a guarantee. So the next step is for you to extensively test your application. Now, some of the first bugs that you encounter may be the result of existing bugs in your code that are now caught by authentication failures. This is an expected result, and now that you know of these bugs, you'll be able to fix them.

But it is possible that you do have code that is written in ways that are not compatible with pointer authentication. And for those, you'll need to make some changes. me. The underlying cause of these incompatibilities is when you are unintentionally invoking undefined behavior. As these operations often overlap the bugs that are exploited by attackers, they get stopped by those same protections. In practice, most code does not hit these problems, but let me quickly cover a couple of the most frequent sources of compatibility bugs that we've encountered.

The most common pattern that we've seen comes from the unsafe use of functions like memcpy to copy polymorphic objects. We already talked about why it's not safe to use memcpy or similar functions when you're doing this. But while you may not make these calls directly, there can be code in your container types that do perform these operations. And that is where we have seen these failures. Thank you.

The fix for these errors is to either adopt higher level language features and data structures, or to adopt the library functions for object copies and initialization. Because these are built into your language, they know about the types of your objects, and they'll do all of the work required to ensure the correct semantics as efficiently as possible.

Even less common is storing extra data in the high bits of function pointers. If your code does this, that storage will corrupt the signature. To fix this, you need to either move this data to the bottom bits of the pointers, or simply move that data outside of the pointers entirely. Thank you.

These are the most common patterns that we have seen in code adopting point authentication. But they are still very rare, even in extremely large codebases. Thank you. But if you do know of these patterns in your application, you'll need to address them as part of your own adoption effort.

Now, after making any of the changes you needed and your testing has shown that your application is working as expected, you're going to deploy it to your users. Thank you. And as with any release, you might get reports of new crashes, and you want to know if these are authentication failures.

To help you diagnose this, when your program is terminated, the generated crash log will include a diagnostic message if the fault may be due to an authentication failure. If you're performing your own crash logging though, you can examine the high bits of the faulting address to provide a similar diagnostic. Simply knowing that a crash is due to an authentication failure isn't enough on its own. So let's take a look at an example of an authentication failure so that you can see how they will appear and how you might fix them.

So here we have a very simple program that has hit an authentication failure. When you've caught a failure in Xcode's debugger, it will initially look like any other crash. But traps resulting from authentication failures will always set the high bits in the faulting address. And that's what you see here. Now, don't focus on the exact bits that are being set, as that can vary across different hardware generations. questions.

In our little test program, the failure is occurring immediately after this call to the copy object function. And we've learned earlier that it is possible that my existing code may be copying data in ways that can result in invalid objects. So let's take a look at that function.

Perhaps unsurprisingly, as this is a demo of something going wrong, the function is using memcpy to copy a polymorphic object, because this used to work. While this example makes it very obvious, if this does happen in your code, it is again only likely to occur inside custom or special case containers and data structures. Happily, the compiler already helps you find these problems early by issuing warnings for these unsafe operations, even when pointer authentication is not enabled. If your code base does permit it, you should configure these warnings to be errors.

Now, you've found the problem. You need to decide on how you want to fix it. And there are a number of options available to you, so let's take a look at a few of them. The most simple fix is to move from untyped memory access functions-- that's your mem copies, your mem moves-- to the standard library functions provided for moving, copying, and initializing objects.

These functions will ensure that any required work to ensure your objects are correctly initialized will be done. And they will use functions like memcpy if it's safe for them to do so. Thank you. But given you're already moving away from these low-level functions, you should consider adopting higher-level language features directly. For example, a safe equivalent to these copy operations would just be to use something like placement new.

The most difficult case that we've seen, that is very hard for you to fix, is if you're using memcpy because you're trying to maintain the dynamic type of an object. Fixing this may require you to restructure your code to use something like language level polymorphism, for instance in this example we've replaced a copy with a virtual clone method, or to adopt manual type-aware logic to perform these copies. So that would be ucheckrtype.

Obviously, again, this is a very basic demonstration program just to show you what an authentication failure will look like and ways that you may approach fixing it. But almost all authentication failures in all languages are fixed in the same way. You need to migrate away from low-level operations. And instead, you see support provided by the language you're using, its runtime, and its libraries.

I've talked a lot about how pointer authentication protects your code, and we've even looked at an example written in C++. But you may not think that you need to adopt point-of-authentication because you've already adopted or are in the process of adopting a safe language like Swift. But that is not sufficient to protect your code. To understand why, we need to have a look at how attackers are going to target your code. Fundamentally, attackers need two things to gain control of your application. First, they need a bug that they can use to corrupt your application state. In this example, the use of the unsafe scanf function allows an attacker to overflow the result buffer.

Second, they need code that is operating on the result of that corrupted state. here, they can use that buffer overflow to overwrite the contents of the error handling function and the calling, the error handler in the calling function. When that error handler is then called, it executes the instructions that have been chosen by the attacker. They have now taken over the control flow of your application and are able to execute any code that they want. Thank you. So when you think about software exploitation, you need to think about more than just the original bug. You need to think about what the attacker is trying to do with that bug. So let's look at that caller.

Right now, it's some unsafe C or C++ code like you've seen so many times before. But what does it look like in a safe language? Thank you. Well, I've rewritten this function in Swift, and it is nearly identical to the C version. In fact, the exact same exploit that was used to overwrite the error handler in the C function can replace it in the Swift version as well.

Again, we're using very simple examples so you can follow it. But the outcome is the same no matter what language you're using, no matter what the original bug was, and no matter how complex it was to exploit. And that's because when a user is running your app, they're not just running your own code. They're running code from all of the libraries that are running alongside your code. So no matter what language you're using, you need to protect your code from any bugs that might exist in any of the unsafe code that is also running in your process.

and by adopting pointer authentication. You'll have done that. You've made it much harder for attackers to compromise your application, even those that do manage to circumvent other protections like memory integrity enforcement. And you got this protection with few, if any, changes to your existing code. And so that's how you can use pointer authentication to prevent attackers from hijacking your app.

With that, I'd like to thank you for joining Enrico, Filippo and myself for this overview of how Memory Integrity Enforcement and Pointer Authentication can protect your users by having the hardware, the compiler, the operating system and your apps all working together. I'm sure that you'll discover that adopting and working with these tools is practical. And it requires little, if any, code changes. And it provides a significant benefit to the integrity and overall safety of your app. So thank you. And now back to Curt. Thank you. Thanks, Oliver and Filippo and Enrico. Really amazing stuff.

Next up, for those here at the Developer Center, we've allocated lunch in the break area. Join us back here at 1.45 Pacific time for three more great talks. 1 I hope you enjoyed breakfast, lunch, dinner, or a spot of tea, whatever was appropriate. Before starting our afternoon talks, I wanted to remind everyone that the engineering team is still taking questions online. So the afternoon program starts with a couple of programming languages. To share a practical guide to adopting bound safety in C and C++, please welcome Yeoul.

Eliminate Bounds safety vulnerabilities in C and C++

Hi, I'm Yeoul on the Security Tools team. And I'll be joined by my colleague, Louie. Today's session is about bound 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. We don't view vulnerability as a motivation. then how it works, why you should adopt bound safety, and how to adapt 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 unexploitable. And today, you get the tools to build that exact same armor into your apps.

Memory safety is still the number one secret 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 bugs exist, 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 adapt 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 correct vulnerability earlier? That was a bound safety issue, and it represents about half of all memory safety problems.

That means tackling bound safety issues will make half of the memory safety issues go away. And I will show you how you can protect your apps using Apple's Bound Safety Technology for C. Imagine you are building an image filter for your app. It takes a buffer, a size, and loops 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 bound-sexy extension for C solves this problem. With bounce safety, indexing requires bounce information. The compiler won't let you index into a pointer without knowing its bounce. Examine the error message here. The compiler is telling you that image doesn't have bounce information. So you cannot index into it with a non-zero index. This error guides you to the necessary bounce annotation, telling the compiler how many elements the 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 bounce checks before every memory access. If the bounce safety bug triggers, the program traps before riding out of bounds. The bug still exists in the code, but it's no longer exploitable. C programs use pointers in different ways. So the Bound Safety extension provides a set of bounce annotations. Let me show you 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 0 through 3. The red boxes are out-of-bounds memory. Taking a pointer to array sub-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. It's unsafe and unnecessary. necessary. Dereferencing is what you need. Using the arrow to access a member or the start operator to dereference directly.

And that's exactly what the single annotation captures. It says this pointer points to exactly one element, or it's null. With single, the compiler prevents pointer arithmetic and array indexing. You cannot accidentally move the pointer beyond that single object. Because these bugs cut at compile time, it is saved by default.

Okay, so single works great for most pointers, 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 pass 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 countedBy 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 bounce patterns. I'll show you another one with a slightly modified example.

Imagine the function now takes a void pointer to process OPEX 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 sizeby annotation. Instead of counting elements like counted by, it counts bytes.

Now a different example. The AllocatePixels 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 0, because when it returns null, the pointer doesn't point to n elements. It points to nothing.

This is where you use count it by or no. It says either this points to n elements or is no. Use this annotation when the pointer can be no while the count is in zero. I covered the most common ones, but there are more annotations for other patterns, like sizeby or no, endedby, and others. You can find the full list in the CLANG 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 is practical to adopt. First, strong safety guarantees mean the compiler always inserts the necessary balance checks. For years, developers have relied on opportunistic bounce checking tools like Ubisan and Fortify Source.

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. Bounce safety completely changes the game. It gives you guaranteed checking. The compiler acts as your safety net, ensuring bounce 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 bounds results in a compiler error. In the first function, there is no bounce on the tension for image. So the compiler rejects the array access. In the second function, countedBy provides the bounds. So code compiles, and compiler inserts bound 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 hard-coded 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 adopt this incrementally while preserving ABI compatibility. You can convert one file at a time without breaking compatibility with the rest of your codebase.

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, shrug fields, global variables, and nested pointers. years. 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 counted 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, ptr to track the current position, and end 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 PTR, it's automatically bounce checked. All the safety without any 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 C code base overnight just isn't feasible. Because these annotations don't change point of representation at the ABI boundary, a full rewrite is not necessary. You can take a single, highly secure critical file, enable bound 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 bounce checks. The compiler cannot verify them. But this allows bound safe code to still compile and interoperate with older libraries. Here's an example. Open file handle calls appd 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, safef, an explicit conversion is required.

To do so, use the unsafe_forge single macro. It takes the base pointer type and the unsafe pointer. This says, I know this 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 adopt bow 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, adapt it, test, and debug. Once you've finished all files, enable bound safety globally in Xcode. I'll walk through each step.

First, adopt bounds annotations in headers where API contracts are defined. Take this header. First, include ptrcheck.h. This provides the bound safety annotations and macros. then use PTR check ABI assume 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 bounds or notations as needed. That's it. This header is now bound safe. If another bound safe code calls this function, the compiler inserts bound checks at the call site. Next, you pick a single source file. Enable the extension. To do so, you add the compiler flag -f Boundsafety in build phases to enable the feature for the source file you adapted.

then compile and follow compiler diagnostics. The compiler will immediately flag missing or mismatched annotations. Once enabling bound safety for the processed 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.

Adding counted by size to the parameter fixes both diagnostics. Now, run your test and debug any runtime traps. If you hit a trap, the compiler just caught an incorrect annotation or a real bug, like this 0x1 error. Xcode immediately stops execution and tells us exactly what happened. Dereferencing above bounds.

You fix the logic here, and the code runs cleanly. Once your test passes, that file is protected. you can adapt remaining files over time. When your whole project is ready, enable the Bound 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 bound safety in C" to "Yes". From that point on, all C code in your target is protected, and new code must follow bound 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 a difference is actually measurable, it stays under 2%. The bottom line is, you're getting the holy grail of Bound Safety with highly practical performance.

Bounce safety is a huge win for your codebase, 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 Apple's. To make that happen, DAO safety is already open source today in Swift for Cloud Clang. is also being actively upstream to mainline LIVN, so everyone can use it.

But it goes even further. Apple is collaborating directly with the C standard committee to standardize bound safety right into the C language itself. Ultimately, this will bring bound safety 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.

Thanks, you all. 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 Yule earlier. It uses standard span instead of a raw pointer. However, despite using standard span, this code does not enforce bound 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 will use the existing bounds 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 Safe 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. Debugging features are extremely helpful to boost your productivity, but they are not sufficient to make your code truly safer when 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 the 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, their back, pubback, 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's 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 RDNA 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 shuttle 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 adapt hardening in your codebase, or for precise performance tweaking. For instance, you could enable extensive or fast checking throughout your old codebase, 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 1,000 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. Thank you. OK, 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 Yul 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 Curt. Thanks, Louis and Yeoul. Now we'll take a short break before resuming our last talks of the day at 2:55 Pacific time. Thanks. C and C++, please welcome Doug to tell us about writing security sensitive code in Swift. Doug?

Write security-sensitive code in Swift

I'm Doug from the Swift language team, and I'm here with my colleague Felix to talk about memory safety and Swift. Thank you. So memory safety is a major security challenge. Now other presentations have talked about mitigations that help find memory safety bugs in C and C++ code bases or prevent them from becoming security problems. But none of these is as comprehensive or effective as just defining away memory safety issues in the programming language itself. And so we're gonna start by reviewing how the design of Swift addresses memory safety.

Then we're going to talk about performance and how to use recently introduced Swift features to get the best performance out of a low-level code without compromising on safety. Now this transition to memory-safe languages is going to take a very long time, so we're going to talk through best practices for encapsulating unsafe code and how to incrementally migrate from the C family of languages toward Swift.

First of all, memory safety isn't some blanket notion about preventing all errors. So good language design can define away some classes of errors or make them harder to introduce in your code. But the reality is programming errors do happen. So memory safety is about making sure that the program behaves as it was written. Now that sounds ridiculous on its face. Of course the program behaves as it was written, but as we've talked about earlier, when an attacker exploits a memory safety bug, they can force the program to do something that wasn't written in the code. That's why memory safety is such a big part of the security story. Because a memory safety bug anywhere, say in your C and C++ code, can make the program do almost anything.

Now, a memory-safe language protects against this kind of absurdity. Now, it can do so in either two ways. So it can refuse to compile any code that might contain a memory safety problem, or it can insert checking at runtime to detect when something has gone wrong. You can combine these strategies. And in fact, Swift uses a mixture of compile time and runtime checking that I'll talk about in a few moments. There's only one rule here, which is that if a potential memory safety problem occurs, the program must not continue. you. Trying to continue with corrupted state is exactly how a programming error becomes a memory safety vulnerability.

We've talked about these before and we often break memory safety down into five different axes that a programming language needs to address. So these are bound safety, lifetime safety, type safety, initialization safety, and thread safety. The C family of languages doesn't provide safety for any of these axes, although there are mitigations that can help with some of them. But let's dig into Swift and how it addresses each of these different areas.

Bound safety is the easiest one. So this is checking to ensure that accesses into a block of memory don't go outside that block of memory. As we've seen in C, you can adjust a pointer past the end of an allocated block, and then go read or modify some other unrelated value causing a memory safety issue.

Swift code here tries to create the same vulnerability. There's an array with 12 elements. We try to access the 15th element. Swift is going to do runtime bounds checking to ensure that this immediately traps, similar to what we talked about with bound safety. That's the easy one. It gets more interesting after this. So lifetime safety is what ensures that when you access memory, that memory is still valid. Now in C, it's fairly easy to write code that violates lifetime safety, with something like a use after free, where the code frees some allocated memory and then tries to access it through this stale pointer, which could now refer to something completely different.

Swift eliminates use after free issues by taking away the free. So if you have some code here, you can allocate an instance of my class, put it in the local variable. You can use it to call a method on it. And note there's no free anywhere. Instead, the compiler is going to insert the destruction when that instance is no longer used. And the compiler will always do so in the correct manner.

This same automatic lifetime management is used in Swift's collection classes. These are things like array and dictionary. Now here, the array is created on the first line, and it'll be deallocated automatically once it's no longer used. Swift uses a technique called automatic reference counting. So we chose this over other more traditional garbage collection techniques because it has really good engineering trade-offs. When you have automatic reference counting, you can basically program in Swift without thinking about the memory management.

All the patterns seem to work, and you're not considering the lifetime most of the time. Now, on the other hand, automatic reference counting, it's very fast. The overhead is very low, both in performance and in memory usage. And it doesn't have the kinds of pauses that you might see with a more traditional garbage collection scheme.

However, there are times when you need to ensure that there is zero runtime overhead to maintain lifetime safety. And so Swift also provides non-copyable types. These describe ownership over a uniquely owned resource. There's a tradeoff here, which is that non-copyable types have a more restrictive programming model. You have to start thinking about ownership more, but for that you get the zero overhead. We're going to come back to non-copyable types later on in the talk.

Type safety is what prevents accessing memory with the wrong type. Again, C allows you to easily create type safety problems in a couple of different ways. So say we write an integer into this cell in memory. Now later on, we could go through a union member or maybe use an explicit cast and read it back as a file, causing a type confusion.

Swift provides equivalence to these features of cunions and typecasting, but both of them are designed to be safe by construction. Swift enums are discriminated unions, meaning that they encode which of the options is currently active. So here we have the resource enum. It can either store a file or it can store an integer identifier.

To access a resource, you pattern match using a switch statement, which ensures that you can never access the wrong member, because this is a paired access. Swift's typecasting is similar. So the Swift code here defines a class and one subclass. That subclass overrides a method and introduces it to another method. Pretty classical object-oriented programming. Here we're gonna try a downcast. Now the as question here will downcast the given object to my subclass.

It produces an optional value, which will either contain the instance as a my subclass, or it will be empty if the downcast fails. So if it's an instance of the subclass, the code will run inside the body of the if so we can call the other method, that's safe. If the downcast fails, the optional is empty, the body of the if is not executed. So this eliminates any potential memory safety holes from casting as well as helping avoid programming errors due to incorrect casts.

Initialization safety is another fairly direct one. So this is what prevents memory from being read before it is initialized. Again, C doesn't require it, so you can construct a local variable and then just use it directly before it's been initialized. If you try to do the same thing in Swift, the Swift compiler will reject this attempt. So it notes that you have not assigned or initialized the variable and will reject it at compile time, so there can't be a memory safety issue.

The last and trickiest one is thread safety. So a violation of thread safety means that having a data race anywhere in your program could introduce a memory safety problem. And this can even happen even if your language is safe in every other axis. So in this example, here's a shared mutable resource. The function replaceResource is going to change the value of that resource, including setting its type to this identifier.

Imagine this is happening in one thread. Now in another thread, there's this useResource function that is switching on that same shared resource. If you had a data race here, it could mean that one thread sees it as a file at the same time that the other thread is overwriting the integer, overwriting with an integer the actual file instance.

The Swift 6 concurrency model prevents these data races by ensuring that there aren't concurrent accesses to shared mutable state. Now, along with that, Swift provides safe ways to deal with concurrency that don't introduce data races. At the high level, one of these is actors. So actors are types that encapsulate their state and protect it from concurrent modification. The resource variable here now is part of the actor's state.

So Swift ensures that useResource and replaceResource that can touch that state will never run concurrently. This is enforced consistently using Swift's async await model. So a call to a method on an actor is always asynchronous, because the caller might have to wait until the actor's done executing code on some other thread before it is safe to make that call.

Swift also provides low-level primitives for data race safety. The mutex type here describes something that can only be accessed by a single thread at a time. Every access to the data stored in a mutex occurs through this withLock function that provides temporary access to that data inside the closure. The mutex ensures that only a single thread will be able to run its closure at the same time.

So with Swift, memory safety is woven into the design of the language. There's a benefit here that's really hard to convey until you've experienced it for yourself. Because when you're using a memory-safe language, it's not about just not having to worry about, am I introducing a memory safety vulnerability or look for them? You just stop thinking about it entirely. And so you can focus all of your attention on the correctness of the code and other concerns that aren't related to memory safety.

Now, if you're coming from the C family of languages, you're probably wondering whether this memory safety comes at a cost of performance. So at a high level, Swift is a natively compiled language, it provides efficient implementations for its memory safety model, and for many programs, that's good enough. But for very low level code that has to match the performance of unsafe C, Swift 6.2 introduced a couple of safe abstractions to help.

Let's take an example in C. This is a decoder for an image that uses some run length encoding. So in the loop, it's reading four bytes at a time from the input buffer. So this is the count and the pixel data. And then it's writing the result out through an output buffer. Along the way, you can see it's manually performing the bounds checking. There's also a lot of assumptions this C code makes that aren't actually verified. So we're assuming that the count parameters correctly describe the sizes of the corresponding buffers. We're assuming the input and output buffers don't alias in some strange way and won't end up getting modified or freed by another thread while this program runs. Here's the translation of that same code into Swift. So the input buffer is an array. That encapsulates both the data and the count. And it also ensures that the data isn't going to go away or be modified while this function is running, even in a multi-threaded program.

Now array accesses are bounds checked, so an error will trap at runtime rather than become a memory safety vulnerability. But I said I'd talk about performance, so let's look at that. So with correct bounds checking in the loop, a compiler can often optimize away the bounds checks entirely when it's proven that it's safe to do so.

Now, Swift's copy-on-write arrays use reference counting in their implementation. Again, the compiler can often optimize away all the reference counting traffic, which it does in this example. However, this append operation is going to add a new element to an array. If the array doesn't have enough space in it already, it will have to allocate new memory with more storage and then copy all the existing elements over to it. That's going to be slower than the C implementation we had before. It was just writing pixels out through a pointer.

There's a second issue here, which is that this design forces clients to themselves have arrays. We can require them to introduce copies of their own. Here's an example. We have some data in an array, but it has this simple header consisting of a width and height of the actual output image, followed by the image data we actually want to decode.

Now the main problem here is that we need to make a copy of all of this run length encoded pixel data just to call the RLE decode function we've been working on. Because RLE decode wants to read the entire pixel array. That's an extra heap allocation and a copy of all the image data, which we absolutely do not want.

There's a related issue here, which is that RLE decode is going to allocate the result array on the heap all the time. Now this particular image decode operation might be okay with that, but you may have some other caller that wants you to put the pixels in some particular place, maybe into a fixed size frame buffer that's already been allocated. And to do that, you would have to copy the results yet again. So this kind of problem can come up a lot in low-level, very performance-sensitive codebases. You can partially address it using array slices or generic collections, but those can be kind of hard to work with.

So this is where the new span family of types come in. So spans provide safe, low overhead access to contiguous memory. If you've used the unsafe buffer pointer types in Swift, you can think of spans as the safe counterparts to those. Now the key idea behind span is that it references contiguous memory that it does not own. You can think of it just like a pointer plus length because that's exactly how it's represented in memory.

Now, span provides full memory safety. It provides lifetime safety in a manner that is checked by the compiler with no runtime overhead whatsoever. It provides bound safety by bounds checking all accesses. But most importantly, it doesn't own the storage it references. Instead, it interoperates with the various types that do own storage. You can get a span referencing into a copy on write array or the fixed length inline array. It also interoperates with the unsafe pointer types, which is important when interacting with unsafe languages or code that predates spans.

The span family of types was introduced in Swift 6.2. However, we think span is so important that we made these types back deploy to much older versions of Apple's operating systems. That way, you can adopt them now without having to raise your deployment target. OK, it's time to adopt span for the input to the run length decoder.

This didn't require a lot. We just changed the parameter type from an array to a span. This works because span provides the same APIs for accessing elements that arrays do. And because this code is just reading through the data, it's not trying to modify it, it's not returning copies, nothing else actually had to change in this function. Now what did change is the API contract. Callers now need to provide a span instead of an array. So let's go back to our caller's code to see how to do that.

Now first of all, every array has a span property that produces a span. So we can make the code compile like this, just adding the.span at the end. That does work. However, it's not going to improve performance. Because we're still creating this new array copying all the data before we take the span into it. We can do better. So instead what we're going to do is take a span from the original array and then pass just the slice of it that we care about down to the RLE decode function. Our lead decode receives just the span that's relevant to it, but there are no extra allocations and no copies, while still maintaining memory safety throughout.

This example does make it look like span is just a drop-in replacement for array. It's not. So it's a reference to storage owned by someone else. And so to make this safe without any runtime overhead, the span type itself has some necessary restrictions. So span is what we call a non-escapable type. It's written with this tilde-escapable syntax shown above. You can use this same syntax to define your own non-escapable types that behave like span does.

Now with a non-escapable type, you can always pass it down as an argument to a function, like we did earlier. However, you can't return any span out of a function, because you can only return a value of nonescapable type if its lifetime is tied to one of the parameters.

Let's dig into the body of the first run function to see what this means. So the code here is finding a run of elements that match the first element. And the loop inside is gonna break out when it finds the first mismatch. The critical part here isn't that logic, but the return at the end.

Now here, what do we return? We return the span extracted directly from the data parameter that we got. It's just the relevant portion of it. And that's okay. It means that the color that provided us with the span will keep that resulting span alive long enough. However, imagine that the code instead made a copy of the data into a new array. This array is stored in a local variable. And then it tries to return a span from that copy out. The compiler is going to produce an error here.

The reasoning is that this newly created array is stored in a local variable. That's what's keeping it alive. We can't return a span into a local variable up to our caller because the local variable is going to go away as soon as we exit. So if you think in terms of C, the restrictions I'm talking about might sound familiar. Imagine you take the address of a local variable. You have to be very careful with that pointer to make sure it doesn't get saved somewhere and doesn't get used after your function has returned and the local variable's gone away. Swift makes those restrictions part of the language, so you can't make a mistake that compromises memory safety.

Span by itself provides read-only access to memory. There's another type, mutable span, that provides mutating access, so you can both write as well as read. You can use the mutable span property from any of the contiguous collections I talked about earlier to get a mutable span into its storage and then modify elements there using subscripting just as you would expect.

Now a crucial part of the safety model here is that mutable span requires exclusive access to the underlying memory. That means if you have a mutable span referring to some block of memory, nobody else has access to that same memory, not for writing and also not for reading. And so in our code, if we were to create a second span that accesses inside that same array, and then try to go do a mutation, the compiler is going to produce an error here to prevent any accesses to the storage while there's still an active mutation.

Now, this exclusivity model is fundamental to Swift. It's actually been in place since the beginning. It's what ensures that memory safety when using things like mutating methods and in-out parameters. Most Swift programmers don't even know it exists because it's very rare to hit these situations where it'll actually trigger a memory safety violation, but it's there as a backstop to provide memory safety. Thank you. Okay, so now it's time to adopt mutable span in the runLengthDecoding function instead of returning a heap allocated array. This one's going to require a little bit more work than span did, but again, we're going to start with the function signature.

The key here is that instead of creating new storage that's returned to the caller, the caller is going to tell us where to put the results via this output parameter. It's passed in out because when you're modifying a mutable span, that's where the results go. And note that instead of appending to an output array, the pixels are now written directly into their final place through this out parameter. This means there's no longer any memory allocation in this function. It's all up to the caller to set up the read and write buffers, just like we did in C.

Speaking of the color, so it actually previously relied on the fact that RLE decode returned an array. So let's go back and look at that. Now what this function is going to need to do is allocate its own array of pixels. And then it's going to pass down a mutable span into that array to RLE decode to fill in the resulting pixels. This caller of course is choosing to do the heap allocation, but a different caller could make a completely different choice.

Here's an example. This structure here is representing a 320 by 240 frame buffer of pixels. It's represented as an inline array to avoid heat memory allocation. The image decode operation takes a mutable reference to the frame buffer it was given, again using in-out. It then gets a mutable span into those pixels and passes that down to our lead decode. See what's happening here. The decode is going directly into the frame. No extra copies, no memory allocation.

Adopting SPAN is a significant win for both performance and memory safety. We encourage you to adopt it in your Swift codebases. If you're looking for the places where you might need to adopt SPAN, there are two starting points. From a performance perspective, look for performance-sensitive code that's using the array or the data types. If you notice extra copies or heap allocations, use span instead as we did with the RLE decode operation.

From a memory safety perspective, start by replacing uses of unsafe buffer pointer types in your code. As their name implies, the unsafe pointer types do not maintain memory safety and should be used rarely. SPAN is a safe alternative for most uses of unsafe pointers, although it may take you some refactoring to get there. Now I'll hand things over to my colleague Felix to talk more about how to deal with unsafe constructs in Swift without compromising on memory safety.

Thanks, Doug. Hi, Curt. My name is Felix, and I'm from the security engineering and architecture team. The next topic on the agenda is safely using unsafe code. Now, memory-safe languages are the future of programming. As Doug explained, by using overhead primitives like SPAN, safe code is very fast. And safe code cannot introduce memory safety bugs. At the same time, unsafe code is everywhere today, literally.

And even as safe languages are picking up in areas where they were hard to use before, there are still billions of lines of unsafe code to account for. This has been known for decades. Unfortunately, it's common engineering wisdom that grounds-up rewrites are risky. A new implementation can introduce or reintroduce bugs, and when the existing implementation continues to evolve at the same time as the unsafe implementation, it competes with it.

This is why it's important to make plans to migrate incrementally from unsafe code. By taking smaller rewrite bytes, the risk becomes much easier to manage, and the chances of success improve massively. - And the reason this matters today is that Swift has unique capabilities that were designed so that incremental migration from C, C++, and Objective-C are possible.

The first of these tools is strict memory safety. There is no safer way to work with unsafe code than by enabling strict memory safety. This is very important. Strict memory safety reveals all uses of unsafe code. This is very useful because while Swift often puts unsafe in the name of unsafe things, some operations are implicitly unsafe.

For instance, in this code, the copy over array function declares two array variables, x and y, and it uses memcopy to copy one to the other. The code doesn't say that memcpy does anything unsafe. But memcpy is a C function, so it accepts unsafe pointers. X and Y are converted to unsafe pointers implicitly. This could cause a memory safety bug with no clear indication.

Once you enable strict memory safety, the compiler will warn for this and all other uses of unsafe code, with expression uses unsafe constructs, but is not marked with unsafe. Thank you. This is resolved by adding the unsaved keyword in front of the call. I'll take a minute to expand on the unsaved keyword. Outside of addressing the compiler warning, it has two main functions. First, it's a warning for security reviewers. When auditing a code base, the unsafe keyword is an indication that something potentially dangerous is happening.

And second, and even more importantly, it's a reminder that you must verify something that the compiler is unable to verify. Bringing back the memcopy example, the code is correct because both arrays contain 4 bytes. However, the compiler does not know that this is a precondition for the call. The unsafe keyword is a reminder to verify that memcopy is used correctly. To enable it in Xcode projects, look for the strict memory safety setting under Swift language options. In a Swift package, strict memory safety is enabled by adding the strict memory safety setting to the package description.

Now, even though strict memory safety is enabled and will ensure that unsafe code is visible, it's still best to avoid using unsafe functionality entirely. There are only two cases where unsafe code is truly necessary. First, to interoperate with unsafe libraries. and second, to implement safe primitives. In many cases, these are actually two facets of the same little unsafe gem.

Writing new code in safe languages has many benefits, but an unsafe implementation may still be the best tool for some tasks. This is because unsafe libraries are common, and security aside, may still be the most mature. When it's possible to rewrite an unsafe library in a safe language, that's always preferable. But it's not always possible on an arbitrary time frame. Safe rewrites take time and expertise. Until the time and expertise are available, Swift can help ensure that the library is used safely.

Doug introduced the RLE decode function earlier. Say that it's implemented in an external C library that can't be rewritten easily. When Swift sees the header, it will expose the function as this Swift unsafe interface. It takes the same parameters: a source pointer, source size, destination pointer, and a destination count. And it's not ideal because it still uses unsafe values, but it can still be called from Swift. However, with strict memory safety enabled, the compiler emits the same unsafe constructs warning.

It's better to write the safe wrapper of the unsafe implementation. Like Doug showed, this wrapper takes a span as input, mutable span as output. That wrapper will have a lot of mentions of unsafe in it. This is because each operation that takes a pointer out of a span or uses those pointers needs to be marked unsafe. However, it's easy to audit for security.

The code takes an unsafe pointer out of each span, and it passes it along with the corresponding counts to the C implementation. It's not necessary to audit uses of the Swift interface because it will pass spans, and spans are safe. There's no mistake here, but if there was one, it would be in this implementation. There could also be a memory safety bug in the C implementation. However, the calling Swift module is cleared of wrongdoing. When RLE decode gets a safe implementation, this risk is eliminated entirely.

Now, on the side of implementing safe primitives, something else that might happen when wrapping on safe code is that an external library could be vending some kind of resource that must be created and destroyed manually. Here's the RLE decode function again. I modified it so that instead of a single stateless decode function, now you need to create a decoder object with RLE in it, and then you need to destroy it with RLE destroy. And the RLE decode function is mostly the same as before, but now it takes a decoder as a first argument.

This pattern needs an explicit D in it. Since copyable structs can't have one, this was always implemented with a class. A lot of code that encapsulates unsafe resources looks like this today. The initializer calls the RLE_INIT function. And then the de-initializer calls the RLE_DESTROY function. And the decode function is going to be vastly the same as it was from before. This works, but it's not as efficient as it could be.

First, class instances are allocated dynamically. This is usually not a problem for long-lived objects. But creating and destroying instances repeatedly leads to avoidable calls to mallocan-free. And it's even more expensive to use a dynamic allocation to wrap another dynamic allocation. Second, class instances are reference counted. This allows complex memory management situations to remain safe. But objects can end up paying for reference count traffic, even if they have very simple lifetimes.

And last, all fields of a class instance have fine-grained exclusivity checks at runtime. And this allows more flexible aliasing operations than you can do on structs. But again, types with simple operations may not benefit at all and still pay the cost. -- -- Class instances are very versatile, but they can be more bulky than necessary. These are small overhead costs, but when you compare to an unsafe implementation that does no checking at all, they add up quickly.

Since Swift 6, you can use non-copyable structs for these use cases. Structs don't need that dynamic allocation, so that's one source of overhead down. and they have coarse exclusivity checks that are mostly verifiable at compile time, so the program has fewer of them to begin with and they are easier to optimize away.

However, sharing a non-copyable struct is much more constrained than a class or a copyable struct. Thanks to the flexibility of reference types in the decode one function, there is no problem having multiple references to the same decoder and calling the decode method through either of them. However, it would be an error to do the same thing with a non-copyable struct. The compiler would immediately diagnose that the object A is moved into B and then used again. So in short, for simple object management, there are lots of performance benefits to non-copyable structs over classes. You may not always be able to use them because they are less flexible, it's more specific, but in exchange, you get more predictable performance.

The last thing I want to talk about is calling Swift from C. All major platforms currently have an unsafe C or C++ core, and all safe languages must call into that core somehow. Mixing with C is a normal and expected capability of safe languages. Regardless, it is frequently difficult.

One reason it can be difficult is that different languages have different expectations, and when one language calls into another one, the expectations of both languages have to hold. For instance, in a language that uses garbage collection, passing an object pointer to C may require special collaboration with the garbage collector to prevent it from moving while the object is still referenced from C.

And another reason is that most languages don't understand each other very well. For instance, in many languages that can call into C, interrupt needs glue code that describes the C header file, because the target's language compiler can't read C headers. Writing that code is tedious. But thankfully, Swift was designed for interrupt with unsafe languages.

Swift eliminates most of that complexity for developers by embedding an entire C compiler. It can interpret and make arbitrary declarations available from header files, including inline functions, by parsing them from the bridging header. Projects can include arbitrary headers from their bridging headers. and Swift can make declarations available to C compilers by generating a header of its own.

I am bringing back Doug's RLE decode example to the screen. Doug showed how an implementation using span is the most flexible option. That implementation was used as a unique backend for two other functions that return pixels in completely different ways. First one that returns an array, and second one that returns its output by reference into a modeXFrame structure. Starting with Swift 6.3, the span implementation can also be the backend for a C function, such as the one that Doug started with.

This is a C implementation that Doug introduced. Get one last good look at it, because I'm going to delete it to replace it with a Swift implementation. To do this, first, there needs to be a Swift function that has a prototype that's compatible with the target C function.

Here, there's RLE decode that takes an input pointer in account, an output pointer in account. And then it creates a span over the input pointer, span over the output pointer, and it calls the common RLE decode Swift backend. Next, the function needs to be exposed to C. There are two ways to do it. The first one is to add the @C attribute to the function.

When using @c, Swift translates its types to reasonable corresponding c types. For instance, the Swift int32 type translates to int32t. All c basic integer type defs translate to their expected counterpart, c char to char, c short to short, c int to int, et cetera. Mind that int translates to pointer diff t. And this is a reason that RLD code accepts and returns pointer diff t instead of size t. When the function to implement is already visible in the bridging header, there is a second option.

using the @implementation, @c combination. Like when using Objective-C methods with @implementation, instead of emitting a declaration in your generated header, this asks the compiler to find an existing declaration for the RLED code function from your bridging header. When using this method, the compiler verifies that the Swift prototype can match the C prototype, and it generates an error if they are not compatible. When using just @c, Swift has to pick argument types, but when using @implementation @c, it's able to adapt to some other conversions. For instance, if the C prototype uses size_t, Swift will also accept an implementation that receives int instead of uint.

Now, this is a great opportunity to look back at what just happened. On my right, there's a C function called cats and dogs that identifies pets in pictures. It takes a pointer to an image structure, and a pointer to a buffer of pet records. It manages pixel memory for decompressing the image, and it calls the RLE decode function to decompress the image. And lastly, it calls identify cats and dogs to find where the cats and dogs are in the picture.

Before, this would call the C implementation of RLE decode on the right that Doug showed earlier. But by using @implementation@c without having to change anything in a C caller, the cats and dogs function now uses the safe RLE decode implementation. And this worked seamlessly from Swift, because the Swift compiler is able to match the RLE decode implementation with the same C function prototype that Clang sees for RLE decode.

And that's all that's needed. @C and @Implementation are great tools to start migrating a C code base to Swift and write new features and safe languages when they need to be used by C callers. years. Now that you've learned about span, move-only types, and interrupt between C and Swift, here's what you need to do next. First, use strict memory safety in your code.

This will enable you to account for unsafe operations in your codebases. Next, start using SPAN to access contiguous memory safely. Start with replacing uses of unsafe buffer pointer. Then, create safe interfaces by encapsulating your unsafe resources, when possible with move-only types, classes otherwise. And finally, start gradually migrating your unsafe code to Swift using its interop features. Thank you for your attention. Memory on safety is today's biggest source of security bugs, and I'm looking forward to building a safer world together. together. And that brings us to our last presentation of the day, a short practical overview of using sanitizers in Xcode to chase down bugs in your code. Please join me in welcoming Dan to the Big Sur stage.

Find security bugs with Sanitizers

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: a dress sanitizer and a 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 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 GuardMalloc, which only detect issues in heap memory, AddressVanitizer 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 a dress 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.length bytes for the copy. And this is where things start to go wrong.

Original.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, a dress sanitizer detects and warns me about the buffer overflow here.

Another bug type detected by a dress 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 open 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 relay the tab pointed out by active, address sanitizer, frozen error, indicating the use of the allocated 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, AddressSanitizer 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. 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 bound safety extension for C solves. Here's what you need to know about a dress 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 two to three 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 ACT-BEST testing.

Here I have my code from the mixed languages example slide. Take my Swift array numbers, and then I'm 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, 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 by checking the address sanitizer checkbox.

So clicking the Run button rebuilds with sanitization, and straightaway, I can see that it's caught something, a heat 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 that allocation was. And unsurprisingly, it's when I constructed that Swift array.

Back in the active stack frame, I'm in a live debugging session, so I can see the values of the variables currently. You 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. 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 FRED sanitizer. FRED 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.

FRED1 reads head with value 0. FRED2 then also reads head with value 0. FRED1 sends the pending message and frees the object. It then increments the value of head to 1. Here's the data race. Depending on which order the read on thread two and the write on thread one occur, the value of index on thread two can differ. In this case, FRED2 having the stale value of head leads to it reading pending 0, which has just been freed by FRED1. AddressSanitizer detects the use after free here when it occurs. 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, FRED Sanitizer still detects there's an issue here and warns me about it. This read on FRED2 is part of a data race. And FRED Sanitizer also shows me the memory access that it's racing with. This right on Fred 1. This is why Fred 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 write 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 a Dress 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. A dress sanitizer helps you find and fix invalid memory accesses. These will cause crashes when MIE is enabled. FRED Sanitizer helps you find data races, which often lead to use after freeze. Fortunately, MIE 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 sanitizers 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 Curt.

Thanks, Dan. And that ties a bow on our presentations today. A big thank you to all the presenters. To supplement what was covered today, one of the best ways to learn more about developing for Apple platforms is through videos from WWDC and other Meet with Apple events. There are hundreds of videos, and you can watch them on the Apple Developer website or in the Developer app. You might even notice some familiar faces. Thank you all for joining us, both here in the Developer Center and online. Now I'll say goodbye to you online. Bye. and invite those of you here to join us in the lobby for light refreshments and refreshing conversation. Thank you.