Home
Unchained
Security Blog

Mitigating a rsync Vulnerability: A Lesson in Compiler Hardening

Mark Esler, Senior Product Security Engineer

Chainguard’s defense-in-depth security strategy protected against the latest rsync vulnerability, even before it was publicly disclosed, showing the clear benefit of enabling C/C++ compiler hardening flags by default to prevent 0-day attacks.


Let’s first address the crustaceans in the room. In CISA's recent Product Security Bad Practices publication:


The development of new product lines for use in service of critical infrastructure or NCFs [national critical functions] in a memory-unsafe language (e.g., C or C++) where readily available alternative memory-safe languages could be used is dangerous and significantly elevates risk to national security, national economic security, and national public health and safety.


The C/C++ language is notoriously unsafe, but it is also here to stay. Large portions of the Linux Kernel, such as filesystems, and core software, like systemd, may not be replaced with memory safe languages anytime soon. In the meantime, we must meet in the middle. We can work towards hardening existing C/C++ projects while suitable memory safe alternatives are developed. Linux distributions and developers must opt into C/C++ compiler hardening flags to make our software safer. The OpenSSF recommends these flags in their Compiler Options Hardening Guide for C and C++:


-O2 -Wall -Wformat -Wformat=2 -Wconversion -Wimplicit-fallthrough \
-Werror=format-security \
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 \
-D_GLIBCXX_ASSERTIONS \
-fstrict-flex-arrays=3 \
-fstack-clash-protection -fstack-protector-strong \
-Wl,-z,nodlopen -Wl,-z,noexecstack \
-Wl,-z,relro -Wl,-z,now \
-Wl,--as-needed -Wl,--no-copy-dt-needed-entries

Chainguard implements most recommendations, and takes them a step further by also opting into:


  • Position-independence (-fPIE and -fPIC for executables and shared libraries, respectively.)

  • Code reuse attack protections (-fcf-protection=full and -mshstk for Intel Control-flow Enforcement Technology x86_64 and -mbranch-protection=standard for branch protection on arm64.)

  • Auto variable initialization (-ftrivial-auto-var-init=zero, which this article will discuss.)

  • Additional checks (-fno-delete-null-pointer-checks and on LLVM-16 -D_LIBCXX_ENABLE_ASSERTIONS)

  • Define behavior (-fno-strict-overflow and -fno-strict-aliasing)

  • Stack trace improvements common to the cloud (-fno-omit-frame-pointer and -mno-omit-leaf-frame-pointer)

  • Hardening flags upstream GCC recommends (We are proud to be the first distro to adopt -fhardened, so that we always apply the latest additions.)


Each hardening flag adds a layer of defense which guards against a different type of threat. Additional layers of defense-in-depth mitigates more undisclosed 0-day vulnerabilities and attacks.


To see a comparison of how Linux distributions use C/C++ compiler hardening flags in their packages, check out Julien Voisin’s spreadsheet.


Most Linux distributions only enable compiler flags when building their packages. Chainguard enables compiler hardening flags in our compilers directly, so that developers using our images also build their software with a hardened compiler by default.


And that’s not to mention that most Linux distributions only increase hardening for their future releases, without backporting to the current production releases. Chainguard images provide these hardening improvements for our production tier images, including all stable supported versions of all software components. This ensures that the benefits of the latest toolchain hardening are deployed in production quicker, without requiring customers to upgrade dependencies to the new major versions.


Some of these flags can address entire classes of C/C++ vulnerabilities. Lets take a look at a flag which almost no other distros have enabled: -ftrivial-auto-var-init=zero. This flag was implemented in Clang by JF Bastien, and implemented in GCC by Qing Zhao. Its use in the Linux kernel was driven by Kees Cook as part of the Kernel Self Protection Project. Kees describes this flag as:


Universally wiping the variables eliminates nearly the entire class of uninitialized stack variable use (https://cwe.mitre.org/data/definitions/457.html) with nearly no overhead.


-ftrivial-auto-var-init controls how automatic (stack allocated) variables are initialized. The flag has the options uninitialized, pattern, or zero. Unless otherwise set, uninitialized is the default. uninitialized means that stack variables could contain stale (or secret) data. It is a misunderstanding to consider this as "uninitialized" – it was initialized – just not by the function using the variable, and the contents could be externally controlled. Relying on "uninitialized data" leads to undefined behavior and could be leveraged into an attack. pattern and zero set stack variables to given values. This adds a tiny amount of memory writing overhead, but prevents the exposure and misuse of prior stack memory contents.


According to proposal P2723R1 Zero-initialize objects of automatic storage duration by The C++ Standards Committee:


Adopting this change would mitigate or extinguish around 10% of exploits against security-relevant [C++] codebase.


Here is an example of a CWE-457: Use of Uninitialized Variable type of attack. We have a loop that can only run twice. Each time the loop runs, a local variable named secret is defined. These variables go out of scope at the end of each loop. On the first pass of the loop, we set the value of our local variable to a string. On the second pass, our brand new local variable is not given a value, but we ask to print its contents.


#include <stdio.h>
#include <string.h>

int main()
{
	for (int i = 0; i < 2; i++) {
		char secret[18];
		if (i == 0) {
			memcpy(secret, "supersecretsecret", 18);
			printf("Copied secret\n");
		} else {
			printf("stack contents: [%s]\n", secret);
		}
	}
}

Let’s save this code to trivial-0.c and see how -ftrivial-auto-var-init=zero affects the binaries:


[linky@chainguard ~] gcc ./trivial-0.c

[linky@chainguard ~] ./a.out
Copied secret
stack contents: [supersecretsecret]

[linky@chainguard ~] gcc -ftrivial-auto-var-init=zero ./trivial-0.c

[linky@chainguard ~] ./a.out
Copied secret
stack contents: []

The recent rsync server vulnerabilities discovered by Google researchers Pedro Gallegos, Simon Scannell, and Jasiel Spelman demonstrate the real-world value of a defense-in-depth approach to compiler flags. The Google researchers discovered two vulnerabilities in the rsync daemon. The first of the vulnerabilities (CVE-2024-12084) is a buffer overflow, and the second (CVE-2024-12085) is a memory contents exposure or “info leak.” The Google Researchers were able to demonstrate an attack which used their memory exposure to reliably trigger the buffer overflow and achieve remote code execution (RCE) as an anonymous guest to an rsync server. Even with ASLR. Modern attacks like this often rely on a sequence of multiple vulnerabilities and are called an “attack chain”. Defenders only need to break a single link in that chain to prevent the attack chain. In this case, CVE-2024-12085 is the very type of vulnerability that -ftrivial-auto-var-init=zero prevents. By mitigating the memory exposure the attack chain was also mitigated.


Chainguard prevented the 0-day rsync attack chain on October 18th, 2024 with Rsync glow up #31186 which opted into OpenSSF recommended and other compiler hardening flags. Google Researchers discovered the vulnerabilities on October 29th, 2024. Security fixes for rsync were publicly released on January 14, 2025. Linux distributions need to step up and own defense-in-depth for our systems and users. We can lift the security tide and protect infrastructure by making C/C++ compilers use hardening flags by default.


Learn more about Using Compiler Flags to Secure Your Code, and check out our demo below to see them in action.



Cheers to Simon and Kees for their support and efforts to improve open source security.

Share

Ready to Lock Down Your Supply Chain?

Talk to our customer obsessed, community-driven team.

Talk to an expert