P.S recently signed up for a competition of the Open Atomic Fund, so the official account has not been updated, and after the 15th final, it began to continue to update the "Rust Research: Rust and LLM Series", and it just so happened that this competition also used Rust to implement an AI Agent to solve specific problems, and I can share my experience when the time comes.
Recently, it was the xz backdoor incident, the discovery of Windows platform vulnerabilities in the Rust standard library, and the discovery that some friends may have misunderstood Rust's security promises, so I planned to write an article to talk about Rust and security.
directory
- Classification of security: Safety and Security
- Rust's security promise
- But Rust doesn't guarantee 100% security
- The efforts of the Rust Foundation and the Rust Security Working Group
- Memory Safety
- Typical Rust Security: "BatBadBut" critical security vulnerability
- xz Backdoor Apocalypse
- Rust security policy prevention
- Rust supply chain security solution: cargo vet
- Secure development strategy: Reduce dependencies
- postscript
Classification of security: Safety and Security
In the world of technology and engineering, "Safety" and "Security" are two key concepts that, while sounding similar, represent different concerns. Especially in the Chinese translation, both English words are translated as the word "security", so it can cause some confusion for some people.
In fact, these two terms have different connotations.
- Safety:
- Safety refers specifically to functional safety. Typically refers to the ability to protect systems, devices, or programs from unexpected, unintentional errors and malfunctions. This includes preventing injury or damage caused by system failures, operational errors, or external events. For example, in programming, functional safety is concerned with how to avoid program crashes, data corruption, or unexpected behaviors such as buffer overflows, null pointer access, data contention, out-of-bounds writes, and so on. Because these vulnerabilities can directly affect the system function itself.
- Security:
- Security refers specifically to information security, which is a kind of security guarantee. Focus on protecting systems from malicious attacks and threats. This involves preventing unauthorized access, data breaches, system intrusions, injections, DDOS, and other forms of malicious behavior. In the same programming scenario, security safeguards may include the use of cryptography, the implementation of complex user authentication mechanisms, the defense against cyberattacks, and so on. Compared with Safety, Security pays more attention to the security protection and assurance on the outside of the system.
In summary, the improvement of safety is usually achieved by enhancing the robustness and error handling ability of the system, while security needs to take proactive defense measures considering potential malicious behavior.
In practice, Safety and Security go hand in hand. For example, a memory safety vulnerability (Safety Issue) could be exploited to execute malicious code (Security Issue). Therefore, writing safe code requires not only the stability and error-proofing of the code itself (improving safety), but also the potential security threats and protective measures (enhanced security).
Rust's security promise
A lot of people only hear about Rust security, but don't know what Rust's security promises are, and they don't understand where Rust's security boundaries are. So much so that when I see CVE exposed the Rust language, I will say, "The supposedly safe Rust language is not safe again" and other "nonsense".
When using the Rust language, it is important to understand the security promises of the Rust language. The Rust programming language is designed to provide a secure and efficient way to write system-level software. Its security commitment revolves around the following areas:
Memory Safety
One of the most well-known features of Rust is its memory safety. Rust prevents common memory errors such as null pointer exceptions and data contention through the concepts of ownership, borrowing, and lifetime. This mechanism ensures that potential memory errors are caught at compile time, greatly improving the reliability and security of the software.
- Ownership system: In Rust, each value has a variable called its "owner". A value can have only one owner at any one time. This mechanism also prevents the risk of memory leaks, because when the owner variable leaves the scope, the value and the memory it occupies are automatically cleaned up.
- Borrowing rules: Rust allows the borrowing of values, but there are strict rules: either there can only be one mutable borrowing (which can change the data) or multiple immutable borrowing (read-only access), both of which cannot exist at the same time. This avoids data contention and ensures thread safety.
- Lifecycle annotation: Rust requires developers to indicate the age of memory data (lifecycle) in certain situations, which helps the compiler understand when a reference is still valid and when it may cause a hanging reference.
The above memory safety rules are guaranteed by the well-designed type system of the Rust language, which is checked by the compiler according to the type system during compilation to achieve the goal of memory safety.
But Rust doesn't guarantee 100% security
Understanding Rust's security promise also means recognizing its boundaries.
While Rust offers strong security guarantees, it doesn't claim to be 100% secure. Security still depends on the developer using the features provided by the language correctly. For example, Rust doesn't automatically protect against logic errors or algorithm flaws, and developers need to thoroughly test and review their code logic.
In addition, if a developer uses Unsafe Rust, the obligation and responsibility for security will also fall on every developer, not only the Unsafe Rust code writers, but also the Unsafe Rust code callers. With the unsafe block, developers have the option to bypass Rust's security checks and manipulate memory directly. This opens up possibilities for advanced optimization, but it also introduces risks.
Although there is no official Unsafe Rust coding specification, there is still a set of conventional Unsafe Rust security abstract specifications in the industry. In this regard, please refer to the internal implementations of Google Android/ Rust for Linux/ Rust std.
I've summarized this in the Unsafe Rust section of the Rust Coding Specs[1] for your reference.
Also, while Rust strives to provide memory safety, it doesn't directly deal with other types of security issues such as network security or user authentication. Because it's part of Security. Security is a problem that many languages face.
A few typical security issues are:
- DDoS issues. For example, if you use the wrong hash algorithm, this problem may be caused.
- Character class issues. For example, in 2021, the Rust compiler also issued a security advisory (CVE-2021-42574), exposing a Unicode vulnerability attack method "Troy Source". This official account has also introduced this vulnerability: Sources of Troy | An invisible vulnerability hidden in Rust code
There are also a number of security issues introduced in the historical articles of this official account. Includes a code interpretation of cve-rs that explains how to construct security issues in Safe Rust.
You see, Rust is not a panacea, it's not a silver bullet for security, it's just a progressive solution for software security. Security matters, in particular, can't just rely on language.
The efforts of the Rust Foundation and the Rust Security Working Group
The official Rust Security Working Group is working to expand the security features of Rust by promoting safer programming practices and improving existing tool support. For example, they have tools like cargo-audit to help developers detect known dependency library vulnerabilities and patch them in a timely manner. It also maintains a https://rustsec.org/[2] to track CVEs and software bugs found in Rust and its ecosystem.
The Rust Foundation is also working hard to further expand Rust security. This includes hiring security experts, doing threat intelligence analysis on the Rust ecosystem, and more.
Through these mechanisms and the community's continued efforts, Rust hopes to provide a safe and efficient option in the system programming space, reducing common security vulnerabilities while improving development efficiency and program performance.
Typical Rust Security: "BatBadBut" critical security vulnerability
In the past two days, a critical security vulnerability called "BatBadBut" has been discovered in the Rust standard library, affecting all versions on Windows prior to version 1.77.2. The vulnerability, identified as CVE-2024-24576 with a CVSS score of 10.0, allows an attacker to execute arbitrary shell commands by bypassing the escape mechanism when calling batch files.
Recently, the Rust Security Response Working Group was notified that the Rust Standard Library did not escape parameters correctly when using Command to call batch files (with bat and cmd extensions) on Windows prior to version 1.77.2.
An attacker with the ability to control the parameters passed to the generated process could execute arbitrary shell commands by bypassing escaping.
The severity of this vulnerability is critical for someone who calls batch files with untrusted parameters on Windows. Other platforms or uses are not affected.
The API for Command::arg and Command::args declares in the documentation that the argument will be passed to the generated process as-is regardless of its contents, and will not be evaluated by the shell. This means that untrusted input can be safely passed as arguments.
This function doesn't fall under Rust's memory-safe promise, so naming it unsafe doesn't help.
On Windows, this implementation is more complex than on other platforms, because the Windows API only provides a string with all the parameters, and the resulting process splits them. Most programs use the standard C runtime argv, which actually results in the same way that arguments are split. The one exception is cmd.exe (which is used to perform other tasks such as batch files), which has its own parameter splitting logic. This forces the standard library to implement custom escaping for the parameters passed to the batch file.
As a result, it has been reported that Rust's escape logic is not rigorous enough and can lead to arbitrary shell execution by passing malicious arguments. Due to the complexity of cmd.exe, the Rust team also failed to find a solution that properly escaped the parameters in all cases. In order to maintain the API guarantee of the standard library, the official team improved the robustness of the escape code and changed the Command API to return an InvalidInput error when the escape argument cannot be safely escaped. This error is emitted when the process is built.
In fact, this kind of problem is common in many languages, but because the Rust language is security-oriented, it is a good idea. People who don't know why are attacking Rust's security. The "BatBadBut" vulnerability was discovered by security researcher RyotaK, who was responsible for making responsible disclosures to the Rust security team.
While the initial focus was on the Rust programming language, it has now been discovered that the "BatBadBut" vulnerability is not limited to just a CVE identifier. The vulnerability affects multiple programming languages and tools, each assigned a different CVE ID, depending on the implementation and impact.
In addition to CVE-2024-24576 related to the Rust standard library, "BatBadBut" includes CVE-2024-1874, CVE-2024-22423 (affecting yt-dlp with a risk score of 8.3), and CVE-2024-3566 (affecting Haskell, Node.js, Rust, PHP, and yt-dlp). This highlights the widespread nature of the vulnerability and the need for developers to evaluate applications and dependencies across various programming languages and tools.
In a responsible manner, the official Rust team fixed this issue in Rust 1.77.2 (other languages don't necessarily fix it for you). Note that the new escape logic for batch files is a bit more conservative and may reject valid arguments. Those who implement escaping themselves or only handle trusted Windows input can also use the CommandExt::raw_arg method to bypass the escaping logic of the standard library.
xz Backdoor Apocalypse
The xz backdoor incident[3], officially labeled CVE-2024-3094, exposed the potential harm of planting malicious code in widely used open-source libraries. Through careful planning and execution, the attacker gradually gained the right to maintain the project, and finally introduced a backdoor in the XZ/Liblzma library.
This attack not only affects multiple distributions of Linux, but can also have an indirect impact on applications that use these libraries. In this incident in particular, the backdoor is designed to be difficult to detect, it does not directly modify the files of the codebase, but is implanted in the released archive, which allows it to spread without arousing immediate suspicion.
Rust security policy prevention
Guillaume Endignoux of the Rust community[4] analyzes the role of the Rust community's security strategy in preventing xz backdoor incidents from the perspective of his own lzma-rs project, a pure Rust-implemented XZ compression format library.
- The RustSec advisory[5] security policy has a strict definition for marking Rust crates as unmaintained, emphasizing transparency about the maintenance status of software components in open source projects. This process involves submitting a pull request with specific details that helps users of the library make informed decisions about the use of outdated or unmaintained components that may have security vulnerabilities.
- crates.io also strictly restricts the identity of the owner of the crate, which is bound by the token and personal mailbox.
This strategy has the potential to help defend against man-made attacks like the XZ backdoor. By making the maintenance status of components publicly and in a timely manner, you can increase community vigilance, thereby reducing security risks due to the use of insecure or deprecated dependencies. This helps to identify and replace components that may be planted with malicious code in a timely manner.
However, for social engineering attacks such as XZ backdoors, security strategies are of limited use.
Rust supply chain security solution: cargo vet
Rust has another solution for providing supply chain security.
In response to these supply chain issues, Mozilla developed the cargo vet tool two years ago to help developers audit their project's dependencies. This tool examines the security records of dependencies to help identify and protect against possible security threats. While this does not guarantee complete protection against all supply chain attacks, it provides a mechanism to reduce risk by increasing the transparency of code and dependencies. This tool has also been adopted internally by Google, which has published a list of Rust crates that have been audited by their team[6].
Cargo vet motives
The main idea of cargo-vet is to ensure that the project's third-party dependencies have been audited by a trusted entity, and that it is lightweight and easy to integrate.
At runtime, cargo-vet matches all third-party dependencies of a project with a series of audits conducted by the project author or an entity they trust. If there are any gaps, the tool assists in performing and documenting audits.
The main reason people don't usually audit open source dependencies is that it's too much work. There are several key ways in which cargo-vet aims to reduce the developer's work to a manageable level.
- Share. Public crates are often used by many projects that can share their findings to avoid duplication of effort.
- Relative audits. Different versions of the same crate are often very similar. Developers can check the differences between the two versions and record that if the first version was reviewed, the second version could also be considered audited.
- Delayed audits. It's not always practical to achieve full coverage. Dependencies can be added to an exception list that can be narrowed down over time. This makes it trivial to introduce genuine audits in a new project and protect against future vulnerabilities, while reviewing pre-existing code incrementally as time allows.
Reducing the security risks of third-party code in Rust code is easier than in other languages, and Rust has two unique features that create the conditions for system analysis:
- First, it's relatively easy to audit Rust code. Unlike C/C++, Rust code is memory-safe by default, and unlike JavaScript, there is no highly dynamic shared global environment. This means that developers can often infer the scope of a module's potential behavior at a high level without having to scrutinize all of its internal invariants. For example, a complex string parser with proper interfaces, no Unsafe code, and no robust import has limited means of harming the rest of the program. This also makes it easier to conclude that the new version is safe based on differences from the previous trusted version.
- Second, almost everyone in the Rust ecosystem relies on the same set of basic tools - Cargo and crates.io - to import and manage third-party components, and there is a high degree of overlap in dependency sets.
Cargo-vet working mechanism
Most developers are busy people with limited energy to work on supply chain integrity. Therefore, the driving principle behind cargo-vet is to minimize friction and do the right thing as easily as possible. It's designed to simplify setup, unobtrusively fit into existing workflows, walk people through every step, and allow the entire ecosystem to share the work of auditing widely used software packages.
The specific workflow is as follows:
- Initial setup: Cargo-vet can enable cargo vet init by adding the tool as a linter and running, which creates some metadata in the repository. This takes about five minutes and, crucially, doesn't require an audit of existing dependencies. These are automatically added to the list of exemptions.
- Add a new third-party crate. After some time, the developers try to pull new third-party code into the project. This could be a new dependency, or an update to an existing dependency. As part of the continuous integration, cargo-vet analyzes the updated building diagram to verify that the new code has been audited by a trusted organization. If not, the patch will be rejected.
- In the event of a rejection, cargo-vet will help the developer resolve the issue:
a. First, it scans the registry to see if any reputable organizations have previously audited the package.
b. If there is a match, cargo-vet notifies the developer and provides the option to add the organization to the project's trusted import.
c. Approvals for import and audit submissions automatically fall into the hands of the code owner of the supply-chain/directory, which should be made up of project leaders or a dedicated security team.
d. If there is no match, the developer can audit it themselves. cargo-vet simplifies this process. Someone has often reviewed different versions of the same crate, in which case cargo-vet calculates the relevant differences and determines the smallest differences1[7]. After guiding the developer through the process of determining what to audit, it renders the relevant artifacts locally or on Sourcegraph[8] for inspection.
Share audit results. Cargo-vet's sharing and discovery mechanism is built on this decentralized storage. Import is achieved by pointing directly to audit files in external repositories, and the registry is simply an index of such files from well-known organizations. It also means that attackers don't have a central infrastructure to breach.
Cargo-vet simplifies the complex audit work through statistical analysis and clever process design, allowing developers to "lazy" do the job step by step.
Cargo-vet has a number of advanced features – it supports custom audit criteria, configurable policies for building different subtrees in a graph, and filtering platform-specific code.
Secure development strategy: Reduce dependencies
In practice, reducing unnecessary external dependencies is one of the effective strategies to reduce the risk of being attacked. For example, the sudo-rs project reduces potential security threats by streamlining its dependent libraries [9]. This approach helps to control the attack surface of the project, which improves overall security. This strategy emphasizes the importance of dependency management in software development, especially at a time when supply chain attacks are increasing, and controlling and auditing dependencies can significantly improve the security of a project.
However, no matter how strict the security policies adopted by the Rust community, there is no way to avoid the risk of the underlying Linux library being attacked. As I wrote in Rust Takes over the C Language: The Technology Changes Happening in Rust for Linux:
As mentioned above, on the networking side, Rust developers have had to ask network maintainers to slow down the rate of merging Rust code[3]. Specifically, Tomonori Fujita is currently in the process of adding some Rust abstractions to the Physical Layer (PHY) driver. A large number of reviews have been conducted, and the patch set has been frequently reformulated based on these reviews. Unfortunately, Rust-for-Linux developers are having trouble keeping up with this speed. There seems to be some disconnect between the development practices of the two communities. Andrew Lunn, the reviewer of the patch, pointed out that network patches do not need to be reviewed before they can be merged, "and if there is no feedback within three days and it passes the CI (continuous integration) test, then it is likely to be merged." But Ojeda (core developer of Rust for Linux) said that CI testing can't determine if the abstraction is well-designed and sensible, which is a key property required for Rust abstraction (with a focus on security abstraction), and he wants someone to be involved. Lunn replied that ultimately it's the people who decide whether or not to merge the code, but that API issues are just like any other bug that can be fixed later if a problem is found.
I agree with Ojeda. Because Rust abstractions, especially Unsafe Rust security abstractions need to be designed, but there is a misunderstanding that some modules in Linux are developed and merged too quickly, are severely understaffed, and that people on the C side think that the API can be modified later. In fact, it is too late to modify the Rust security abstraction later, and it must be done in the early stage.
However, Jakub Kicinski, a network maintainer, said, "Longer review cycles will make tracking patches and discussions difficult to manage. He wondered if the Rust-for-Linux project would reduce its involvement in patch reviews after the initial phase. Ojeda agrees that this is the goal, but the initial set of abstractions will require more review time.
Therefore, I feel that the rapid release cadence of Linux is fragile for the security of the supply chain. Therefore, supply chain security still has a long way to go. As a developer, it's impossible to review all the dependencies in your code, and maybe AI can make some breakthroughs in supply chain security in the future.
postscript
I hope this article will help readers and friends to establish a "systematic and healthy" understanding of Rust and security.
Thanks for reading.
Resources
[1] Unsafe Rust section of the Rust Coding Specs: https://rust-coding-guidelines.github.io/rust-coding-guidelines-zh/
[2] https://rustsec.org/: https://rustsec.org/
[3] XZ Backdoor Incident: https://www.wired.com/story/xz-backdoor-everything-you-need-to-know/
[4] Guillaume Endignoux: https://gendignoux.com/blog/2024/04/08/xz-backdoor.html
[5] RustSec advisory: https://rustsec.org/
[6] Google has also published a list of Rust crates that have been audited by their team: https://opensource.googleblog.com/2023/05/open-sourcing-our-rust-crate-audits.html
[7] 1: https://mozilla.github.io/cargo-vet/how-it-works.html#1
[8] Sourcegraph: https://sourcegraph.com/
[9] The sudo-rs project mitigates potential security threats by streamlining its dependencies: https://www.memorysafety.org/blog/reducing-dependencies-in-sudo/