View in English

  • Apple Developer
    • Get Started

    Explore Get Started

    • Overview
    • Learn
    • Apple Developer Program

    Stay Updated

    • Latest News
    • Hello Developer
    • Platforms

    Explore Platforms

    • Apple Platforms
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    Featured

    • Design
    • Distribution
    • Games
    • Accessories
    • Web
    • Home
    • CarPlay
    • Technologies

    Explore Technologies

    • Overview
    • Xcode
    • Swift
    • SwiftUI

    Featured

    • Accessibility
    • AI & Machine Learning
    • App Intents
    • Apple Intelligence
    • Games
    • Security
    • Xcode Cloud
    • Community

    Explore Community

    • Overview
    • Meet with Apple events
    • Community events
    • Developer Forums
    • Open Source

    Featured

    • WWDC
    • Swift Student Challenge
    • Developer Stories
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Centers
    • Documentation

    Explore Documentation

    • Documentation Library
    • Technology Overviews
    • Sample Code
    • Human Interface Guidelines
    • Videos

    Release Notes

    • Featured Updates
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • Downloads

    Explore Downloads

    • All Downloads
    • Operating Systems
    • Applications
    • Design Resources

    Featured

    • Xcode
    • TestFlight
    • Fonts
    • SF Symbols
    • Icon Composer
    • Support

    Explore Support

    • Overview
    • Help Guides
    • Developer Forums
    • Feedback Assistant
    • Contact Us

    Featured

    • Account Help
    • App Review Guidelines
    • App Store Connect Help
    • Upcoming Requirements
    • Agreements and Guidelines
    • System Status
  • Quick Links

    • Events
    • News
    • Forums
    • Sample Code
    • Videos
 

Videos

Open Menu Close Menu
  • Collections
  • All Videos
  • About
  • About
  • Transcript
  • Fortify your app: Essential strategies to strengthen security

    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.

    Resources

      • HD Video
      • SD Video
  • Search this video…

    Hello and welcome to the Apple Developer Center here in Cupertino. I'm Kurt 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 Sur. 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. Well, 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 web 25, we've. 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. 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 livestream.

    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 Kurt.

    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 these 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. Our North Star has remained 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 including 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, Emmi is a major breakthrough that combines the unique strengths 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're 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 Kurt to get us started.

    Thank you.

    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, 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.

    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.

    Thank you.

    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.

    The careful layering and combination of the strategies that Devin 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, Devin 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 if 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.

    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 copy 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 has 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 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'll 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 we'll use that as a step, 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. It's gone too far.

    And so at Apple, we rely on multiple layers of security engineering that layer and complement one another. There'll be deep, 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 will be required to use them in your applications. I'm going to talk about five strategies.

    I'll cover how to make memory safety vulnerabilities impossible in your code by using a memory safe language such as Swift.

    How to make vulnerabilities unreachable by reducing the available attack surface, making vulnerabilities unexploited 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 code bases that are critical to security, such as WebKit, complex parsing, and even embedded environments like the Secure Enclave processes 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 band safety and its annotations for C and C++ standard library hardening to help you reduce the risk of band 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.

    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 guarantees type safety by requiring that casts are checked at runtime.

    Swift guarantees initialization by requiring you to initialize your variables before they are used. And with swift concurrency, it provides thread safety by preventing data races.

    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 code bases 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 conditional 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.

    Another place your app might be unintentionally exposing attack 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 Webviews, and these modes allow you to control your app, 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, Maximize 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 apps webviews 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 webviews throughout the OS, including for mail. Quick look. Captive portal shortcuts and Apple ads in iOS 26.4. And that's a really good time for you to try them out to.

    Attack surface 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. Excuse me. 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 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. 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 sessions. That 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 me 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 defense is known as containment.

    Consider the chain 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 apps 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. And 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 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 it 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 and messages transcodes it from a potentially complex type into 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.

    So Blast Door 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.

    For that reason, the most important thing about this architecture is that message is 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 the transcript.

    That's just one example of how Apple uses containment in our most security sensitive code bases.

    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 to 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 exploitable 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.

    Thanks, Mark. Hi, I'm Devon Coughlan. 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 expanding 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 codesign 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.

    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 code bases, 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.

    Mark ordered them from highest protection to lowest for new code bases. 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. 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 practice, 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.

    Address 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.

    Thread sanitizer 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. 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.

    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 freeze 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 retreating. Attacker. 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 in 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.

    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 and heap allocated memory.

    It's a CPU feature designed to compose with a 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 based Macs 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 and 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 the 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 ten S 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.

    It requires building a universal binary, one that has both arm64 and arm64 e 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 for attackers if they're able to compromise the dynamic loaders 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 De Wilde and Objective-C runtime metadata. Don't modify them directly.

    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.

    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 attack surface written in C based languages. After adopting Holap, 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 code bases use the standard library for container classes and other widely used abstractions.

    C++ Standard library hardening protects against out-of-bounds accesses and classes like standard span and vector. It safely traps rather than allowing for memory corruption for code bases 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, and 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 pointers 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 see.

    Xcode C and C++ bounds 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 code bases. These are strong protections, but full memory safety requires a memory safe language. Swift is memory safe and takes advantage of the platform security protections at the OS hardware and language level.

    Swift six two 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 span is also fast. It's great if you need to guarantee zero overhead runtime overhead for lifetime safety and highly optimized bounds checks.

    With Swift Span, the combination of compile time checks for lifetime safety and runtime checks for bound safety provides both security and low overhead.

    There are two strategies to secure your app, with Swift adopting a new code and writing rewriting existing security sensitive code. Use both strategies.

    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, life cycle 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 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.

    I hope you 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.

    Thank you.

    Hello. Welcome.

    Welcome to the first deep dive of the day. My name is Henry Capell. I'm a security engineer and I'll be later joined on stage by my colleagues, Filippo 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. Then 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. Remember 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 and exploitable. That's huge. They no longer are a security problem for your application.

    We are releasing memory integrity enforcement the very same way we use it for ourselves. It does not hold him back here. And that's because we strongly believe the 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. We 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 end to end 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 also attach 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 our application, interact with its security centerpiece, the type aware secure memory allocator that leverages memory tagging extensions.

    MTV 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 of the 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. High bits, 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 tag seven, 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, these do not affect software that much. In fact, in every memory, integrity enforcement is extremely straightforward. Just a few clicks of Xcode. Contrary to other technologies that require non-trivial adoption effort and my 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 use them here in order of likelihood.

    The first one is allocated wrappers. Allocated wrappers are interfaces that wrap the system allocated 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 reinforce the idea. Here we define a function named Allocate memory, 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 the stage in a short and expand on it.

    The second direct memory match 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 rare 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 tagging 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 spend a lot of time making it scalable and fast for the vast majority of users, and it was reimplemented from scratch over the last three years. So if you took performance numbers more than three years ago, please reevaluate them. You might be pleasantly surprised. And we rewrote it so that it shines for integrity enforcement. Now, we do understand that some abstractions 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 security bar that our allocator meets. And 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 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 to some memory that was incorrectly modified or incorrectly used, then 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 modifying. And to put it in a more scientific way. We take an approach that is a little unique in the industry, and it's 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. refers to the intrinsic characteristics of the memory location. It could be a data structure. It could be a string. It could be a collection of records. Then we have the victim type. That's some 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. There could be a patient, 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 that 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, allocation is done via malloc. Just a bag of bytes doesn't know much more than the size that was requested. It doesn't know about the intent of the allocation. Our secure allocators instead understand types, and this huge leverage is achieved with both manual typing, mostly used in the kernel and compile time automated typing, which is what we use in userspace. Once we have type information, we can start playing. Our allocators can start making 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 is we collect together those that have similar characteristics. We randomize those 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 ran on a device.

    Much in the same vein, we randomize the placement in memory of these type regions. Once again, we do that at 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 various type classes. We assign different tags at each location and Freecycle, and ensure an even distribution of tags across the whole region. We also enforce the no. Two adjacent objects have or 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 managed patterns that I discussed earlier. So they don't hamper the security of your application.

    Over to you.

    Thank you. My name is Filippo and I'm a security engineer. Is here 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 abstraction across all kinds of code bases, allocator wrappers.

    And 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 code base as an abstraction layer to interact with the allocator.

    So all of this seems fairly innocuous, 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. And in fact, let's focus on the call sides to the system allocator interfaces. This is where the foundations of type isolation are. And it's actually the compiler, the one who's doing all the work for you.

    When you enable support for the type 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 code sites and rewrite each call into a type aware one, passing the type 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 to locations based on their types by implementing a style bucketing policies.

    When you implement a wrapper, this callsites 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 a 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 of 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 underscore malloc type macro by specifying the corresponding type 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's actually using 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 life cycle 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 light 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 retagging, 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 our recycling policy in our packet processing code. When we need to allocate a packet, we first try to extract it from a threadlocal queue that we maintain and that we refill in the release bucket 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 MTA 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 a threadlocal 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 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 MI has to offer.

    And now I'd like to spend a moment talking about custom allocators. 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 Ma, 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.

    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 building blocks that you need to to implement support for MT in your custom allocator. 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 building blocks in your use case.

    The first thing you need to do is to check whether empty 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 empty enabled on the devices that support it, but the same code will still need to run on devices that do not support empty. All the operations that rely on the empty instruction set architecture should only be performed after checking that empty 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, bypassing the VM flags. empty flag 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 retag each location when it is freed back to our allocator.

    There are two parts when it comes to tagging and allocation. Choosing a target, 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 target 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. Empty. Generate random tag will give back to us a pointer pointing to the same memory, but with a different random tag in the high bits.

    Last but not least, you want to use empty store tag 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 allocator.

    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 Milan. 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 Typekit 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 code base and to identify them, and so you can better evaluate your exposure.

    Whenever you identify one of these. The prefer. A 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'll 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 and tooling, 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. M I 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 provides your application with control flow integrity. That means even if an attacker can bypass me 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 attackers 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 Miis. 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 API for almost a decade, and we've been using it as a tool to predict 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 pointer authentication. 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 is only rarely visible to functions that are written in assembly.

    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 there are all sorts of 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.

    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 methods in Swift virtual methods 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. Indirection is attractive to attackers because each layer of these can be attacked independently.

    And that's why in Swift, C++, and Objective-C were 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.

    And 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.

    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 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.

    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.

    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.

    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. And when you do this, Xcode will automatically configure your project to build a universal binary that contains the familiar arm 64 slice and an additional arm 64 slice that is used by the hardware that supports pointer 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 pointer again, we've designed all of the pointer 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.

    The underlying cause of these incompatibilities is when you are unintentionally invoking undefined behavior.

    As these operations overlap 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 and your container types that do perform these operations. And that is where we have seen these failures. The fix for these errors is to either adopt higher level language features and data structures, or to adopt the library functions for object copy 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.

    These are the most common patterns that we have seen in code adopting pointer authentication, but they are still very rare, even in extremely large code bases.

    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.

    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 bit of the faulting address to provide a similar diagnostic.

    Now, 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 is 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 of being that are being set, as that can vary across different hardware generations.

    And 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 and 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.

    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 the copy with a virtual clone method, or to adopt manual type aware logic to perform these copies. So that would be you check a type. 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 use the 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 pointer 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 in 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.

    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? 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.

    And 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 Kurt.

    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 145 Pacific Time for three more great talks.

    I hope you enjoyed lunch or for folks online. 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 the owl.

    Thank you. Thank you.

    Hi. I'm your security tools team, and I'll be joined by my colleague Louis. 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 bond safety matters with a real vulnerability as a motivation.

    Then how it works. Why you should adopt bond safety and how to adopt it in practice.

    I'll also talk about efforts to build a broader bond 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 bond safety technologies made this entire class of bugs. An exploitable. And today, you get the tools to build that exact same armor into your apps.

    Memory safety is still the number one security challenge in systems programming. You're probably already working hard to find and fix bugs using static analysis, sanitizers, and code reviews. But here's the problem attackers keep finding new bugs. Bug finding tools are valuable, but they are not enough on their own to really shift the attacker model. Entire classes of vulnerabilities need to be eliminated so that even when bug exists, they cannot be exploited.

    The complete solution to memory safety is to use memory safe languages like Swift, and that's absolutely the right choice for new code.

    But existing code bases are a different story. Sometimes hundreds of millions of lines of C and C++ complete rewrites would take decades, but people need protection now. A practical path forward. Existing code is essential.

    What's needed are technologies that eliminate entire vulnerability classes, not just find individual bugs.

    These technologies need to be easier to adopt than a complete rewrite.

    And critically, they need to be incrementally adaptable. So you can start protecting your code today without waiting for a massive migration project.

    Earlier today, Mark gave a great overview of five different categories of memory safety bugs. These are all important problems, but each needs different solutions.

    And this talk focuses on balance safety, which is one of the two biggest problems along with lifetime safety. Remember the audio codec vulnerability earlier that was about safety issue. And it represents about half of all memory safety problems.

    That means tackling balance. Safety issues will make half of the memory safety issues go away, and I'll show you how you can protect your apps using Apple's found safety technology for SI.

    Imagine you are building an image filter for your app. It takes a buffer, a size and loop through the pixels.

    But notice the loop condition here. It uses less than or equal to size. Introducing a classic of by one error on the very last iteration, it writes one element past the end of the buffer.

    While this is a simplified example, it represents the exact kind of out of bounds error that when combined with attacker control input, becomes the root cause of countless real world exploits.

    Here's how the bounce safety 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 nonzero index.

    This error guides you to the necessary bounce annotation. Telling the compiler how many elements image points to.

    That's where counted by comes in counted by size, tells the compiler that this pointer points to size element. Now the compiler knows the bounds.

    It automatically inserts bounds checks before every memory access.

    If the bounds safety bug triggers the program traps before writing out of bounds.

    The bug still exists in the code, but is no longer exploitable.

    C programs use pointers in different ways, so the bound safety extension provides a set of bounds. Annotations. Let me show you a common pointer patterns and which annotation to use for each cases.

    I'll start with pointers to a single element.

    When examining bound safety box in C and C++. There's a clear pattern. Most problems come from array indexing and pointer arithmetic. In this diagram, here's an array of four integers. The green boxes are the valid elements indices zero through three. The red boxes are out of bounds. Memory. Taking a pointer to arrays of zero points at the first element. And that is safe.

    But with array indexing or pointer arithmetic, it's easy to go beyond the array bounds and access that red region.

    Interestingly, most pointers in C don't actually need pointer arithmetic. They just point to a single object like this message t.

    Incrementing it or indexing it like an array pushes you out of bounds, is unsafe and unnecessary.

    Dereferencing is what you need. Using the arrow to access a member or the star operator to dereference directly.

    And that's exactly what the single annotation captures. It says this pointer points to exactly one element or is null with single. The compiler prevents pointer arithmetic and array indexing. You cannot accidentally move the pointer beyond that single object because these bugs caught at compile time. It is safe by default.

    Okay, so single works great for most 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 path sizes with pointers.

    struck. Store them side by side. The information is already there. It just needs a way to be explicitly expressed.

    That's where counted by comes in. It lets you say these pointers bounds are determined by this other variable. You are making explicit. What was already there in the code counted by is the most common annotation. But there are others that capture different bounds patterns.

    I'll show you another one with a slightly modified example. Imagine the function now takes a void pointer to process opaque serialized data. The type is unknown just how many bytes are available. Same thing in the struct. Imagine now takes a serialized data, which is a void pointer. The structure is unknown, but data size tracks how many bytes it contains.

    This is where you use size by annotation. Instead of counting elements like counted by, it counts bytes.

    Now a different example. The allocate pixels function allocates image as an array of pixels. It either returns a pointer to n elements of pixels or null if the allocation fails.

    Using counted by n elements for the return type would be wrong unless n is zero, because when it returns null, the pointer doesn't point to n elements, it points to nothing.

    This is where you use counted by or no. It says either this points to N elements or it's no. Use this annotation when the pointer can be null while the count isn't zero.

    I covered the most common ones, but there are more annotations for other parents like size five or null and Devi and others.

    You can find the full list in the documentation on bound safety extensions.

    Now you know how the bound safety extensions works, and here are the reasons why you should adopt it.

    Two reasons it provides strong safety guarantees and it's practical to adopt.

    First, strong safety guarantees mean the compiler always inserts the necessary balance checks.

    For years, developers have relied on opportunistic balance checking tools like unison and Fortify source. These are. These tools are valuable, but they catch bugs on a best effort basis. It's like applying a never ending game of whack a mole attacker. 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 bounce results in a compiler error in the first function. There is no bounce notation for image, so the compiler rejects the array access in. The second function counted by provides the bounce. So code compiles and compiler inserts bounce checks.

    This means if code compiles, bounds are checked.

    Right. This is one of my favorite features. How many times have you updated a buffer but forgot to update the size? With this extension, the compiler actually understands the relationship between your pointer and your size variable, and enforces at compile time at runtime, meaning you cannot accidentally break bounds. Correctness.

    Take this example up top. The data pointer is tied to data size down below. The function allocates memory for data but forgets to update data size. This classic mistake usually leaves a stale size value leading to out of bounds errors. But now the compiler catches it immediately and refuses to compile.

    To fix it, you simply update the size alongside the pointer.

    Beyond compile time checks. The compiler also inserts runtime checks because the size field is 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 adapt this incrementally while preserving Abi compatibility. You can convert one file at a time without breaking compatibility with the rest of your code base.

    I'll start with smart defaults. Going back to the message process example, this function takes a pointer to a single message t object, and single was added to express that. Now, because this pattern is so common, the bound safety extension makes single the default for pointers at Abi boundaries. Function parameters. Struct fields, global variables, and nested pointers.

    So you don't need to annotate this parameter. It's already single by default. You only need to annotate when you want something different, like count it by.

    Now you might be thinking, hey, do I really have to annotate every single local pointer in my millions of lines of code? The answer is absolutely not because local variables aren't exposed at the Abi boundary. The compiler does something brilliant. It automatically promotes them into wide pointers behind the scenes. You get full pointer arithmetic, you get automatic runtime checking, and you get all of this safety without adding an annotation to your local code. It just works.

    Here's an example. The process image function uses local variables to track the current position and and to mark the end of the array. Both are automatically wide pointers. No annotations needed.

    When you increment PTR, the compiler carries the bounds along.

    When you dereference. TR is automatically bounce checked all the safety without adding annotations.

    And here's the most practical reason to adopt this. It is designed for incremental adoption in the real world. Pausing development to rewrite a massive SQL base overnight just isn't feasible because these annotations don't change pointer representation at the Abi boundary. A full rewrite is not necessary. You can take a single highly secured critical file, enable bond safety today, and link it right back into an existing Unannotated project.

    There will be practical scenarios where Unannotated APIs like system headers are required by default. Pointers from these headers are treated as unsafe. No bounce information. No. No bounce checks. The compiler cannot verify them, but this allows bounce safe code to still compile and interoperate with older libraries.

    Here's an example. Open file handle calls apply 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. Save f, an explicit conversion is required.

    To do so. Use the unsafe for single macro. It takes the base pointer type And the unsafe pointer. This says I know these points. 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 adapt bound safety in your project.

    Here's the process. You start by annotating headers. Then work through your files one by one. Enable bound safety for the file. Adapt it. Test and debug. Once you've finished all files enable bound safety globally in Xcode.

    I'll walk through each step.

    First. Adapt bounce annotations in headers where API contracts are defined.

    Take this header first. Include Peter check dot h. This provides the bounce safety annotations and macros.

    Then use Peter. Check Abi or some single. When you bend your headers. This macro ensures consumers automatically treat your Abi pointers as single, not unsafe.

    Finally, add counted by or other bounce annotations as needed.

    That's it. This header is now bounce safe. If another bounce safe code calls this function, the compiler inserts bounce checks at the course site.

    Next, you pick a single source file. Enable the extension.

    To do so, you add the compiler flag dash f. Found safety in build phases to enable the feature for the source file you adopted.

    Then compile and follow compiler diagnostics. The compiler will immediately flag missing or mismatched annotations.

    Once enabling bounce safety for the process image. Example two diagnostics appear.

    The first one says the function definition must match the annotations in the header declaration.

    The second one says the parameter image doesn't have a bounce annotation, so indexing into it isn't allowed.

    Adding counted by size to the parameter. Fixes both diagnostics.

    Now run your test and debug any runtime checks.

    If you hit a trap, the compiler just called an incorrect annotation or a real bug like this off by one error.

    Xcode immediately stops execution and tells us exactly what happened. The referencing above bounds.

    You fix the logic here, and the code runs cleanly.

    Once your tests pass. That file is protected.

    You can adopt remaining files over time.

    When your whole project is ready, enable the Bounce Safety extension in Xcode build settings.

    To do so. Go to your project's build settings and find the security section set. Enable language extension for C language extension for bounce safety in C to yes. From that point on, all C code in your target is protected and new code must follow 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 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 secure, sensitive systems.

    Apple relies on this to protect billions of users every single day.

    Now it's your turn. Take this and keep your users safe.

    Of course, if you write C, you care about performance. You're probably wondering what the catch is. So performance was measured across the kernel's networking stack where every microsecond counts. And to be clear, during this test, down safety was fully active across every single control path.

    With bound safety enabled, 93% of the tests show an overhead that falls entirely within the margin of measurement noise. In the few places where difference is actually measurable, it stays under 2%. The bottom line is you're getting the holy grail of bound safety with highly practical performance.

    Bound safety is a huge win for your code base, but it becomes a total game changer when it's available everywhere. Here is how we are building a border bound, safe ecosystem.

    The goal is simple you should be able to protect users on any platform, not just apples, to make that happen. Bounce safety is already open source today in swift fork of clang. It's also being actively upstream to mainline llvM so everyone can use it.

    But it goes even further. Apple is collaborating directly with the C Standard Committee to standardize bond safety right into the C language itself.

    Ultimately, this will bring 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.

    Thank you. Hi folks, my name is Louis and I work on the C++ standard library here at Apple. It's great to be here. Let's dive into the C++ side of things now.

    C++ is different from C in that it already provides many abstractions that have sufficient information to provide safety. For example, standard span contains a pointer and a size so it knows what bounds are valid when it is accessed.

    The same is true for many other components of the standard library like standard vector, standard string, standard array, and so on.

    However, the C++ standard does not require enforcing safety in these APIs, and indeed C++ standard libraries have not historically done that.

    So, for example, the code on the right is a direct C++ translation of the C process image function presented by UL earlier. It uses standard span instead of a raw pointer. However, despite using standard span, this code does not enforce 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 balance 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, right? Debugging features are extremely helpful to boost your productivity, but they are not sufficient to make your code truly safer when running. Running on devices in production. Indeed, code that uses the hardened standard library is intended to ship to production, and hardening in the standard library was designed very carefully for that to be valuable.

    This ensures that your code can be made safer in production when it runs on your users devices and deals with real data, which is where safety truly matters.

    When using a 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. They're back pub back their front methods. Those are all hardened.

    Accessing an optional value is also hardened.

    In general, you can rely on any checked precondition in an ISO C++ 26 hardened implementation to be checked.

    Now there is also some cases where the standard library already provides checking by default. For example, invoking a standard function when the function is empty already throws an exception. Hardening does not affect these APIs.

    Other APIs that would be easy to harden, but that would provide limited security value are also not hardened. In order to minimize the performance impact of hardening on your app, checks that actually provide security value are prioritized.

    For example, constructing a string from a null pointer is technically not correct. However, in practice, it already results in a segmentation fault which terminates the program. Because of that, there is limited security value in adding an explicit check for that case.

    Finally, a notable exception to hardening is iterators. Iterators do not generally store sufficient information to perform bounds checking. Adding that information would require changing the Abi, and that would make adoption a lot more difficult. For that reason, iterator accesses are not hardened.

    Now that you have a good understanding of what APIs are hardened, I'll discuss the experience that you can expect when developing a hardened application.

    So when a hardening check fails, the program will get terminated inside Xcode. You'll be taken to the place where the hardening assertion failed, which is in the standard library, and you can then use the stack frame selector on the left to navigate to your own code.

    The debugging console will display an error message providing additional context for the failure. And you can then use your normal debugging workflow in Xcode to understand and resolve the issue.

    In shipping apps, things are a bit different. Since there is no debugger to attach, the program will be terminated by a trap, just like what happens for bound safety extensions in C in C++. Just like in C, this mechanism is the quickest and safest way to terminate your program, since it provides few options for a potentially malicious attacker to divert the control flow of your program after the assertion has failed.

    In particular, this means that no message is logged to the console. This prevents leaking information about your program, and it also ensures that binaries stay as small as possible by not storing diagnostic strings in the executable.

    But that's not all. Xcode even provides additional modes of checking, which provide different performance trade offs.

    The hardened mode that I've been referring to until now is in fact called the fast mode. On top of the fast mode, Xcode also provides an extensive mode which adds checks that are cheap but not necessarily security critical.

    This mode is great to ensure rigorous programming without paying too much of a performance cost.

    Then, on top of the extensive mode, Xcode also provides a debug mode. The debug mode performs more exhaustive checking in exchange for a larger performance impact. For example, under the debug mode, the C++ library can check that a comparator provided to standard sort has the properties required for sorting. This is extremely helpful to catch subtle semantic semantic bugs in your app.

    Now the fast and the extensive modes can be used in production, while the debug mode should only be used during development.

    It's also possible to mix different modes in different files. This can be used to incrementally adopt hardening in your code base or for precise performance tweaking. For instance, you could enable extensive or fast checking throughout your code base, but disable hardening in a single source file that is very performance sensitive.

    And since hardening does not affect the Abi, all of this works seamlessly.

    I'm really quite excited for these features to now be generally available in Xcode. Indeed, Apple has been developing this technology for quite some time, and it's been adopted internally to get it to where it is today. In particular, this technology was adopted in all of WebKit and in multiple parts of the operating system, including parts of the kernel.

    No significant performance impact was observed, and multiple bugs were found, including some that were very elusive.

    In addition to Apple's own experience, there are many accounts of this technology being used across the industry.

    For example, Google documented their deployment across their whole server fleet. This corresponds to millions of lines of performance sensitive code.

    They observed as low as a 0.3% performance impact after some tweaking, and they reported finding over 1000 bugs, including security critical bugs.

    Another example of industry adoption is C++ 26, which adopted the notion of a hardened standard library based on the fast mode.

    So overall, the experience has been overwhelmingly positive, and I'm really excited for this to now be generally available in Xcode.

    Okay, so we went over the tools that Xcode now provides to help eliminate important vulnerability classes in both C and in C++ for you. This means that you can now start adopting bound safety extensions in your C code on a file by file basis. You should start with the most security sensitive parts of your code, and then work your way towards the rest of your code when you're ready to do so. Enable it on your whole project for your C++ code. Enable the fast mode immediately to make your application safer without requiring code changes. You should also enable the debug mode during testing and development. This will find subtle bugs that you would otherwise miss to catch places in your code where you are accessing buffers without using the idiomatic abstractions of the standard library. Enable the new Xcode diagnostics and. If you want to learn more about what you'll. And I presented today, extensive documentation is available online. Remember that people depend on your code being safe and that every step counts. So start today and give these features a try. With that, I'll hand it back to Kurt.

    Thanks, Louis. And now we'll take a short break before resuming our last talks of the day at 255 Pacific time. Thanks. Welcome back. Moving on from C and C++, please welcome Doug to tell us about writing security sensitive code in Swift. Doug.

    Thank you.

    So I'm Doug from the Swift Language team, and I'm here with my colleague Felix to talk about memory, safety and Swift.

    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 a way memory safety issues in the programming language itself. And so we're going to 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.

    Now, first of all, memory safety isn't some blanket notion about preventing all errors. So good language design can define a way 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. 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.

    And safety is the easiest one. So this is checking to ensure that access is into a block of memory. Don't go outside that block of memory. As we've seen in C, you can adjust the 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 a 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, right? The same automatic lifetime management is used in Swift 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 unique owned resource. There's a trade off here, which is that non-compatible 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 C unions and typecasting, but both of them are designed to be safe by construction. Swift enums are discriminating. 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 these are the. 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 its own other method. Pretty classical object oriented programming. Here we're going to 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 happen even if your language is safe in every other axis. So in this example, here's a shared mutable resource. The function replace resource 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 use resource 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 six 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 use resource and replace resource 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 is 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 width lock 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 six two 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 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 size 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. So you can know. And it also ensures that the data isn't going to go away or be modified while this function is running, even in a multithreaded 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 decode function we've been working on. Because our 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 our 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 code bases, 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.

    Okay, 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 decode function. Read code 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 an inescapable 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 non escapable 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 going to 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 caller 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 then get used after your function has returned and the local variables gone away. Swift makes those restrictions part of the language, so you can't make a mistake that compromises memory safety.

    Spanned 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 immutable 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 immutable 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 inout 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. Okay, so now it's time to adopt a mutable span in the run length decoding 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 caller. So it actually previously relied on the fact that early 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 our LED code 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 heap memory allocation.

    The image decode operation takes a mutable reference to the frame buffer was given again using Inout. It then gets immutable span into those pixels and passes that down to our LED decode. See what's happening here? The decode is going directly into the frame. No. 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 Code bases. 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 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, 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 low 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 re-introduce 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 bites. 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 migrations 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. 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 memcpy 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.

    This is resolved by adding the unsafe keyword in front of the call.

    I'll take a minute to expand on the unsafe 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 Memcpy example, the code is correct because both arrays contain four bytes. However, the compiler does not know that this is a precondition for the call. The unsafe keyword is a reminder to verify that Memcpy 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, there are actually two facets of the same little unsafe gem.

    Writing new code in safe languages has many benefits, but an unsafe implementation 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 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, a 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 a 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 users of the Swift interface, because it will pass spans, and spans are safe. There is 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 red code gets a safe implementation. This risk is eliminated entirely.

    Now, on the side of implementing safe primitives. Something else that might happen when wrapping unsafe code is that an external library could be vending. Some kind of resource that must be created and destroyed manually.

    Here's the decode function again. I modified it so that instead of a single stateless decode function, now you need to create a decoder object with RL in it, and then you need to destroy it with RL destroy.

    And the RL decode function is mostly the same as before, but now it takes a decoder as a first argument.

    This pattern needs an explicit DNN 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 RL init function and then the initializer calls the 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 malloc and 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 six, you can use non Copyable structs for these use cases.

    Structs don't need a dynamic allocation. So that's one source of overhead down. And they have course 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. References from C. And another reason is that most languages don't understand each other very well. For instance, in many languages that can call in to C, interop needs glucose. That describes the C header file because the targets language compiler can't read C headers, writing that code is tedious.

    But thankfully, Swift was designed for interop 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 early 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 returned pixels in completely different ways. First one that returns an array, and second one that returns its output by reference into a mode X frame 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 yet. One last good look at it because I am 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 is ready code that takes an input pointer, an account, an output pointer, an account, and then it creates a span over the input pointer span over the output pointer. And it calls the common ready code 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 at c attribute to the function.

    When using at C, Swift translates its types to reasonable corresponding C types. For instance, the Swift Int32 type translates to int32t. I'll see basic integer typedefs translate to their expected counterpart. See start to chart. See. Short to short. See end to int, etc. mind that int translates to pointer div t. And. This is a reason that our code accepts and returns pointer div t instead of size t when implementing. When the function to implement is already visible in a bridging header. There is a second option using the at implementation at C combination, like when using Objective-C methods with at implementation instead of emitting a declaration in your generated header. This asks the compiler to find an existing declaration for the decode 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're not compatible.

    When using just at C, Swift has to pick argument types, but when using at implementation at 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 is 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 decode function to decompress the image. And lastly, it identifies it calls identify cats and dogs to find where cats and dogs are in the picture.

    Before this would call the C implementation of read code on the right that dogs showed earlier.

    But by using at implementation at C without having to change anything in a scholar, The cats and dogs function now uses the safe red code implementation, and this worked seamlessly from Swift because the Swift compiler is able to match the decode implementation with the same C function prototype that clang sees for red code.

    And that's all that's needed at C and at implementation are great tools to start migrating a C codebase to Swift and write new features in safe languages when they need to be used by C callers.

    Now that you've learned about span move only types and interact 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 code bases. 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 Unsafety is today's biggest source of security bugs, and I'm looking forward to building a safer world 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 stage.

    Hi, I'm Dan and I'm a software engineer here at Apple. I'm on the security tools team and I work on the sanitizers. I'm here today to talk about finding security bugs in your existing code with the sanitizers.

    In this presentation, I'm going to introduce the concept of sanitizers. And then I'm going to talk about two of them address sanitizer and thread sanitizer, which are the two most powerful sanitizers.

    To start off, I'll introduce the general concept.

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

    Now that I've defined what sanitizers are, I'll talk about address sanitizer. At a high level, address sanitizer finds and throws an error when your program makes invalid uses of memory.

    Unlike tools such as guard malloc, which only detect issues in heap memory, address sanitizer can also detect issues in stack and global variable regions. It also offers byte level precision to its checks, meaning that even the slightest out-of-bounds access will be caught. I'm going to go through a selection of the types of bugs that address sanitizer can find.

    In this code snippet. I'm planning to make a raw copy of this Nsstring variable original. It might not be obvious, but it has an out-of-bounds memory access bug.

    I'll explain why here. I grab a pointer to the underlying bytes of original shown here on the right.

    And then I allocate original dot length bytes for the copy. And this is where things start to go wrong.

    Original dot length does not give me the number of bytes. It actually gives me the number of UTF 16 code units, and the waving hand emoji is made up of two of these.

    So raw copy now points to an allocation of five bytes.

    Next, I copy across the bytes from raw original into raw copy, but the last two bytes are going to be out of bounds of the allocation. This is exactly the kind of tricky bug that would be easy to miss and could be exploited. Fortunately, Address Sanitizer detects and warns me about the buffer overflow here.

    Another bug type detected by address sanitizer is use after free.

    In this C++ example. I keep a standard vector of tabs and keep a pointer to the current active tab on the right. I can see there is space for one element reserved in the vector, but it hasn't been populated yet.

    I opened my first tab mail and set it as the active tab.

    I then open another tab. News. This forces the vector to grow its capacity, which in turn means creating a new allocation and moving the elements there.

    Unfortunately, active did not get updated when this happened and now points at Deallocated memory.

    When I try to reload the tab pointer that by active address sanitizer throws an error indicating the use of Deallocated memory.

    As shown in the examples. C, C++, and Objective-C are all supported for Objective-C. This includes manual and automatic reference counting.

    Finally, so is Swift. Although Pure Swift is unlikely to have any invalid uses of memory.

    Importantly, though, Address Sanitizer also works across languages. Here I have a Swift array numbers.

    In order to use this array from a C function, I'm going to take an unsafe buffer pointer. Notice that the pointer nums points to the first element of the array.

    Now I'll call the sum function. To do so, I need to pass in a pointer to the start of the array and its length.

    The sum function will iterate over each element of the array and sum them up but I've made a classic error here. Rather than iterating while less than Len, I've used less than or equal to. As a result, the final iteration is going to go out of bounds.

    Address sanitizer catches this and throws an error about the buffer overflow. It's worth noting that this is the kind of security bug that using the bound safety extension for C solves.

    Here's what you need to know about address sanitizer. Firstly, it's only for use during development, not for runtime hardening.

    For a tool this powerful. It has low memory and runtime overheads approximately 2 to 3 times for each.

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

    To enable address sanitizer during development. First, navigate to the Schema Editor by opening the product menu, then opening the scheme submenu and selecting Edit Scheme.

    From here, navigate to the Run Scheme. Then under the diagnostics tab, check the Address Sanitizer checkbox.

    To enable Address Sanitizer for a test plan. Open the product menu, then test plan and edit test plan.

    Open the configurations tab and scroll to the Runtime Sanitization section. Then select the Address Sanitizer Row and set it to on.

    I'm now going to demonstrate how to use address sanitizer for testing.

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

    And then I'm going to print out the array and the result. So let's run this now.

    Firstly, I can see that my program ran successfully, but definitely this value here is not correct.

    So I'm now going to enable Address Sanitizer under Product Scheme Edit scheme and by checking the Address Sanitizer checkbox.

    So clicking the run button rebuilds with sanitization and straight away I can see that it's caught something. A heap buffer overflow on this line.

    On the left, I can see the call stack that led to this, as well as being able to see that this is immediately after a 64 byte heap allocation.

    In this slide menu, I can see where allocation was, and unsurprisingly, it's when I constructed that Swift array.

    Back in the stack frame. I'm in a live debugging session so I can see the values of the variables currently. I can see that I is the same as Len, which tells me there's something wrong with my loop bound condition, as discussed in my slides. That's because I've got this extra equal sign here. So removing that and rebuilding my application.

    I'll now see that it ran successfully. And what's more, I'm now getting the correct value out of sum.

    Okay. I'll now go back to my slides.

    The next sanitizer I'll go over is 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. Fred one reads head with value zero. Fred two then also reads head with value zero.

    Fred one sends the pending message and frees the object.

    It then increments the value of head to one.

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

    This is the same code running on the same two threads, but this time there's no interleaving operations between them. Address Sanitizer cannot detect an issue in this execution.

    But great news. Even though I observed the expected outcome. Thread sanitizer still detects there's an issue here and warns me about it. This read on thread two is part of a data race, and Fred Sanitizer also shows me the memory access that it's racing with.

    This write on thread one. This is why thread sanitizer can be extremely useful for finding the root cause of those seemingly unreproducible bug reports.

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

    Thread sanitizer is enabled in the same way as address sanitizer.

    Note that the two are mutually exclusive, so you can only run with one enabled at a time.

    For automated testing, you can create two different configurations for your test plan, one with address sanitizer enabled and another with thread sanitizer enabled.

    Sanitizers play an important role in adopting memory integrity enforcement.

    Address sanitizer helps you find and fix invalid memory accesses. These will cause crashes when Emmy is enabled.

    Thread sanitizer helps you find data races, which often lead to use after frees. Fortunately, Emmy will cause a crash when a use after free occurs, which prevents exploitation but will be a cause of frustration to your customers.

    Finding and fixing the bug with thread sanitizer will keep your customers safe and happy.

    Here's what you need to do next. First, go at Address Sanitizer to the config on your test plans, especially if you're using any C, C++, or Objective-C in your codebase.

    Then go at Thread Sanitizer to your test plans if you're using any C or pre concurrency. Swift.

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

    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.

Developer Footer

  • Videos
  • Meet with Apple
  • Fortify your app: Essential strategies to strengthen security
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • AI & Machine Learning
    • Apple Intelligence
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Downloads
    • Sample Code
    • Videos
    • Documentation Archive
    Open Menu Close Menu
    • Help Guides & Articles
    • Contact Us
    • Forums
    • Feedback & Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • Mini Apps Partner Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Read the latest news.
    Get the Apple Developer app.
    Follow us on bilibili, LinkedIn, WeChat, and YouTube.
    Copyright © 2026 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines