Scroll to top

GHSA-c522-hqhp-v2qq: Critical uucore Flaw Lets a Translation File Inject Code into Rust Coreutils

uucore FTL translation injection vulnerability GHSA-c522-hqhp-v2qq

Advisory: GHSA-c522-hqhp-v2qq
Severity: Critical · CVSS 9.9 (v3.1)
Vector: AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Weakness: CWE-94 (Code Injection), CWE-1395 (Dependency on Vulnerable Third-Party Component)
Affected Package: uucore (crates.io) ≥ 0.2.2, ≤ 0.11.0
Fixed Version: 0.12.0

uutils coreutils is the cross-platform Rust rewrite of the GNU core utilities — ls, cp, cat, install, kill and the rest — and it now ships as the default coreutils in mainstream Linux distributions. Every one of those utilities links the shared uucore crate.

I found that the uucore build script turned the project’s translation files into Rust source code in an unsafe way. A single Fluent (.ftl) translation containing the two characters "# was enough to break out of a string literal and have the rest of the file compiled into the binaries as Rust code.

In other words, a file that every reviewer treats as harmless text was, in practice, a path to a supply-chain backdoor in the core utilities.


How the Translations Are Embedded

uutils localizes its messages with Project Fluent. To make the binaries self-contained, src/uucore/build.rs reads each .ftl file at compile time and writes it into a generated Rust file, embedded_locales.rs, as one arm of a match:

// src/uucore/build.rs (vulnerable versions)
// Determine if we need a hash. If content contains ", we need r#""#
let delimiter = if content.contains('"') { "#" } else { "" };
writeln!(
    embedded_file,
    "        \"{locale_key}\" => Some(r{delimiter}\"{content}\"{delimiter}),"
)?;

That generated file is then pulled straight into the crate that every utility depends on:

// src/uucore/src/lib/mods/locale.rs
include!(concat!(env!("OUT_DIR"), "/embedded_locales.rs"));

Whatever ends up in embedded_locales.rs is ordinary Rust source, compiled with the full privileges of the program.


Root Cause: A Raw String Ends at "#, Not at "

A Rust raw string opened with one hash, r#", has no escape sequences. It simply ends at the first byte pair "#.

The guard in build.rs only asked whether the content contained a double quote. It never checked whether the content contained the terminator itself. So any translation holding " immediately followed by # closes the literal early, and everything after that point is parsed by rustc as code.


Proof of Concept

A translation file with this content is enough. To Fluent it is just a message called greeting:

# evil.ftl
greeting = quote "#),
        "pwn" => { std::process::Command::new("/bin/sh").arg("-c")
            .arg("id > /tmp/UUTILS_FTL_INJECTION_PROOF").status().ok(); None },
        "tail" => Some(r#"

After build.rs wraps it in r#"…"#, the generated source becomes:

        "<locale_key>" => Some(r#"greeting = quote "#),
        "pwn" => { std::process::Command::new("/bin/sh").arg("-c")
            .arg("id > /tmp/UUTILS_FTL_INJECTION_PROOF").status().ok(); None },
        "tail" => Some(r#""#),

The string ends after quote , a new match arm that runs a shell command appears, and a final dummy arm swallows the original closing delimiter. The file compiles cleanly, and the payload is now part of the uucore library that is linked into every utility.

A match arm is only the simplest demonstration. Because the attacker controls raw Rust tokens, the injected text can just as well close the function and add arbitrary items of its own. I confirmed the issue on the current master branch, not on stale cached code.


Impact: Compromise of uutils via a Malicious Pull Request

This flaw can lead to the compromise of uutils itself via a single malicious pull request. An attacker does not need commit rights or access to any maintainer account: they only need a translation change that looks harmless to be merged.

Once that pull request lands, the attacker’s code is compiled into the official release and into every distribution package built from it. Because uutils provides the core commands that system scripts, installers, package managers and CI pipelines run constantly, this would be a full supply-chain catastrophe: malware shipped inside trusted, signed system binaries on every machine that installs or updates them.


Why It Was Realistically Exploitable

  • 214 .ftl files ship in the source tree, and 34 of them already contained ", so they already took the r#"…"# branch. Only the extra # was missing.
  • .github/fluent_linter_config.yml explicitly disabled the Fluent linter’s TE04 double-quote check, so quotation marks in translations were sanctioned by project policy and nothing in CI looked for this pattern.
  • Reviewers focus on .rs files. A one-line change inside a translation — especially inside a large pull request — is easy to approve without a second look.
  • Once merged, the payload is compiled into the next release and into every distribution package built from it, reaching every system that runs those binaries. That is why the score is Scope: Changed with full confidentiality, integrity and availability impact.

The Fix

My report proposed dropping raw strings entirely and letting Rust’s Debug formatter produce a correctly escaped literal. The maintainers adopted exactly that, and it ships in uucore 0.12.0:

// src/uucore/build.rs (fixed)
// Never a raw string: `r#"..."#` ends at `"#`, so content holding that
// pair closes the literal and the rest is compiled as Rust. `{:?}`
// escapes every quote and has no terminator the content can reach.
writeln!(
    embedded_file,
    "        {locale_key:?} => Some({content:?}),"
)?;

With {:?}, every quote, backslash and control character is escaped, so the exploit string becomes inert data inside an ordinary string literal.


What You Should Do

  • Upgrade to uutils coreutils / uucore 0.12.0 or later.
  • Distribution packagers and anyone building from source: rebuild from a fixed tree, and check any downstream or vendored locale files.
  • Audit translation files for the terminator pair:
grep -rnF '"#' --include='*.ftl' .

The broader lesson applies to any project with a build.rs, code generator or template: never build source code by pasting data into a string. Use a real escaper such as {:?}, quote! or a serializer, and treat every file a build script reads as part of the code that needs review.


Final Assessment

GHSA-c522-hqhp-v2qq is a small bug with a large blast radius. One missing character check in a build script turned translation files — the least scrutinized files in the repository — into a way to inject code into the core utilities that sit underneath almost every shell script on a system.

Thanks to the uutils maintainers for the quick fix and coordinated publication.


References

GitHub Security Advisory
GHSA-c522-hqhp-v2qq

uutils coreutils
https://github.com/uutils/coreutils

The Rust Reference — Raw string literals
https://doc.rust-lang.org/reference/tokens.html#raw-string-literals

CWE-94: Improper Control of Generation of Code
https://cwe.mitre.org/data/definitions/94.html

Discovered and reported by JoeCyberTech (GitHub: HackingRepo)

Leave a Comment

Please check your email
Please check your message
Thank you. Your message has been sent.
Error, email not sent