Memory security vulnerabilities continue to pose a prevalent danger to software security. At Google, we are convinced that the key to wiping out this type of vulnerabilities on a large scale and constructing high-assurance software lies in Safe Coding, an approach designed with security in mind that gives priority to transitioning to languages that ensure memory safety.
In this article, we illustrate why concentrating on Safe Coding for new code swiftly and unexpectedly reduces the overall security risk of a codebase, ultimately overcoming the persistently high level of memory safety vulnerabilities and initiating a steep decline, all while remaining scalable and cost-efficient.
We will also present updated data on how the proportion of memory safety vulnerabilities in Android decreased from 76% to 24% over a period of 6 years as development shifted to memory secure languages.
Unforeseen Outcomes
Contemplate a growing codebase predominantly coded in languages that are not memory-safe, encountering a continual stream of memory safety vulnerabilities. What occurs when we gradually move towards memory-safe languages for new functionalities, while keeping existing code mostly intact except for bug fixes?
We can model the outcomes. After several years, the composition of the codebase looks like this1 as the growth of new memory-unsafe development slows down and new memory-safe development starts to dominate:
In the final year of our simulation, even though the amount of memory-unsafe code expanded, the quantity of memory safety vulnerabilities decreased drastically, a seemingly unexpected outcome not observed with other tactics:
This reduction may seem contradictory: how is this possible when the number of new memory-unsafe code actually rose?
The Mathematics
The explanation lies in a crucial observation: vulnerabilities decline exponentially. They exhibit decay rates. The lifespan distribution of vulnerabilities follows an exponential distribution given an average vulnerability lifespan λ:
A comprehensive examination of vulnerability lifetimes2 presented in Usenix Security in 2022 established this occurrence. Researchers discovered that the majority of vulnerabilities exist in new or recently altered code:
This reaffirms and generalizes our assessment, published in 2021, that the prevalence of memory safety bugs in Android decreased as the code aged, mainly concentrated in recent modifications.
This leads to two key points to remember:
- The issue predominantly lies with new code, demanding a fundamental shift in our code development practices.
- Code matures and becomes more secure over time, exponentially, resulting in diminishing returns on endeavors such as rewrites as the code ages.
For instance, based on the average vulnerability lifetimes, code that is 5 years old has vulnerability density between 3.4x (using the lifetimes from the study) and 7.4x (using lifetimes observed in Android and Chromium) lower than that of new code.
In reality, as demonstrated in our simulation, once we begin to prioritize prevention, the situation starts to improve rapidly.
In Action on Android
Around 2019, the Android team commenced giving preference to transitioning new development to languages that ensure memory safety. This choice was influenced by the growing cost and complexity of managing memory safety vulnerabilities. There is still much left to accomplish, but the outcomes have already been beneficial. Here’s the overview in 2024, looking at the entire codebase:
Despite the fact that the majority of the code is still not secure (but, critically, is gradually becomingIn the older stages of development, there has been a significant and sustained decrease in memory security vulnerabilities. The findings are in line with our previous simulations, and they are even more promising, possibly due to our simultaneous endeavors to enhance the security of our memory-unsafe code. The initial mention of this decline was made in 2022, and we are still observing a continuous reduction in the overall count of memory safety vulnerabilities, which is represented as 36 for 2024 but currently stands at 27 after the September security bulletin.
The proportion of vulnerabilities arising from memory safety issues remains closely linked to the programming language utilized for new code. Memory safety concerns, which constituted 76% of Android vulnerabilities in 2019, have now decreased to 24% in 2024, well below the industry standard of 70%, and are on a declining trajectory.
As discussed in a prior publication, memory safety vulnerabilities tend to be notably more severe, more prone to remote exploitation, more adaptable, and more predisposed to malicious exploitation compared to other vulnerability types. With the reduction in memory safety vulnerabilities, the overall security risk has also diminished accordingly.
### Evolution of Strategies Focused on Memory Safety
Throughout the past decades, the sector has pioneered significant advancements to combat memory safety vulnerabilities, with each progression offering valuable tools and techniques that have effectively enhanced software security. However, looking back, it is apparent that a scalable and sustainable solution achieving an acceptable level of risk is yet to be accomplished.
**1st generation: reactive patching**
In the initial phase, the primary emphasis was on reactively fixing vulnerabilities. For prevalent issues like memory safety, this incurs ongoing costs on businesses and their user base. Software developers need to allocate substantial resources to address frequent incidents, resulting in a cycle of continuous security updates leaving users exposed to unknown vulnerabilities and frequently temporarily vulnerable to known issues that are rapidly exploited.
**2nd generation: proactive mitigating**
The subsequent approach involved proactively reducing risk in vulnerable software by implementing a series of exploit mitigation strategies that elevated the difficulty of crafting exploits. Nonetheless, these mitigations, such as stack canaries and control-flow integrity, often pose a recurring cost on products and development teams, creating potential conflicts between security and other product requirements.
– They often lead to performance degradation impacting execution speed, battery life, tail latencies, and memory consumption, occasionally hindering their deployment.
– Attackers demonstrate persistent creativity, initiating a continual struggle with defenders. Moreover, the threshold for developing and weaponizing an exploit continues to decrease due to improved tooling and other innovations.
**3rd generation: proactive vulnerability discovery**
The subsequent generation concentrated on identifying vulnerabilities through tools like sanitizers, often paired with fuzzing techniques. While beneficial, these methods primarily address the symptoms of memory unsafety rather than eliminating the root cause. They typically demand continual pressure to prompt teams to fuzz, triage, and resolve their findings, resulting in limited coverage. Even with comprehensive implementation, fuzzing does not offer high assurance, as evidenced by vulnerabilities discovered in extensively fuzzed code.
Products across the industry have significantly benefited from these strategies, and we are dedicated to addressing, mitigating, and actively searching for vulnerabilities. Nevertheless, it is becoming increasingly apparent that these approaches are insufficient in attaining an acceptable risk level in the memory safety domain and incur escalating costs for developers, users, businesses, and products. As emphasized by various government bodies, including CISA, in their secure-by-design report, “only by integrating secure by design practices will we break the cycle of constantly identifying and applying fixes”.
### The Fourth Generation: Assurance-Centric Prevention
The transition towards memory-safe languages signifies more than a technological shift; it epitomizes a fundamental change in security paradigms. This shift is not unprecedented but a substantial extension of a proven methodology that has already exhibited remarkable success in eradicating other vulnerability categories like XSS.
The cornerstone of this shift lies in Safe Coding, enforcing security invariants directly within the development platform through language capabilities, static analysis, and API structure. The outcome is a secure ecosystem by design that offers ongoing assurance at scale, impervious to the inadvertent introduction of vulnerabilities.
Moving from previous generations to Safe Coding manifests in the determinability of assertions made during code development. Rather than focusing on applied interventions (mitigations, fuzzing) or relying on past expereinces to forecast future security, Safe Coding empowers us to make solid assertions about the code’s attributes and the potential outcomes based on those attributes.
The scalability of Safe Coding is evident in its capacity to reduce expenses by:
- Disrupting the arms race: Instead of perpetuating a never-ending arms race where defenders try to increase attackers’ costs by also escalating their own, Safe Coding utilizes our control of developer ecosystems to disrupt this pattern by focusing on actively constructing secure software from the outset.
- Standardizing high assurance memory safety: Rather than customizing interventions for each asset’s evaluated risk, while also managing the expense and burden of reevaluating evolving risks and implementing diverse interventions, Safe Coding establishes a strong foundation of commoditized security, like memory-safe languages, that economically reduces vulnerability density universally. Contemporary memory-safe languages (particularly Rust) expand these principles beyond memory safety to other types of bugs.
- Enhancing efficiency: Safe Coding enhances code accuracy and developer efficiency by moving bug detection earlier in the process, even before the code is committed. This shift is reflected in critical metrics such as rollback frequencies (urgent code reversions due to unforeseen bugs). The Android team has observed that the rollback rate of Rust modifications is less than half of that for C++.
Translating Insights into Action
Interoperability as the fresh approach
Based on our insights, it has become evident that there is no requirement to discard or rewrite all existing memory-unsafe code. Instead, Android is concentrating on making interoperability safe and convenient as a key capability in our memory safety journey. Interoperability presents a pragmatic and gradual method of embracing memory-safe languages, enabling organizations to capitalize on existing code and systems investments while expediting the development of new functionalities.
We suggest emphasizing enhancements in interoperability, similar to our efforts with Rust ↔︎ C++ and Rust ↔︎ Kotlin. Consequently, earlier this year, Google awarded a $1,000,000 grant to the Rust Foundation and developed interoperability tools like Crubit and autocxx.
Role of preceding generations
As Safe Coding continues to diminish risks, what will be the role of mitigations and proactive detection? While we lack definitive answers in Android, we anticipate the following trends:
- More targeted proactive mitigations: We foresee reduced dependence on exploit mitigations as we transition to memory-safe code, leading to not only safer software but also more streamlined software. For example, subsequent to eliminating the now redundant sandbox, Chromium’s Rust QR code generator is 95% faster.
- Diminished usage, yet heightened efficacy of proactive detection: We predict decreased reliance on proactive detection methods like fuzzing but enhanced effectiveness, as achieving comprehensive coverage over small, well-contained code segments becomes more achievable.
Closing Remarks
Struggling against the mathematics of vulnerability durations has been a challenging endeavor. Embracing Safe Coding in new code presents a shift in mindset, allowing us to exploit the innate decline of vulnerabilities to our advantage, even within extensive existing systems. The principle is straightforward: once we halt the influx of new vulnerabilities, they decrease exponentially, enhancing the safety of all our code, amplifying the efficacy of security design, and mitigating the scalability obstacles linked with existing memory safety strategies, allowing for more targeted and effective application.
This strategy has proven successful in eradicating entire classes of vulnerabilities, and its efficiency in addressing memory safety is increasingly apparent based on over five years of consistent outcomes in Android.
More insights into our secure-by-design initiatives will be shared in the upcoming months.
Acknowledgments
Special thanks to Alice Ryhl for engineering the simulation. Gratitude to Emilia Kasper, Adrian Taylor, Manish Goregaokar, Christoph Kern, and Lars Bergstrom for their valuable input on this article.
Notes
-
The simulation was based on data resembling Android and other Google projects. The codebase doubles every 6 years. The average lifespan of vulnerabilities is 2.5 years. It requires 10 years to transition to memory-safe languages for new code, and we utilize a sigmoid function to represent this transition. Note that the utilization of the sigmoid function is why the second graph doesn’t appear initially exponential. ↩
-
Alexopoulos et al. “How Long Do Vulnerabilities Live in the Code? A Large-Scale Empirical Measurement Study on FOSS Vulnerability Lifetimes”. USENIX Security 22. ↩
-
Unlike our simulation, these vulnerabilities stem from a genuine codebase, which entails higher variance, evident in the slight rise in 2023. Reports of vulnerabilities were unusually high that year, consistent with expectations considering code growth. Thus, while the percentage of memory safety vulnerabilities continued to decline, the absolute number saw a slight increase. ↩





