flawopen.com/Incidents/Baron Samedit

Baron Samedit: ten years of root hidden in a backslash

High — Local privilege escalation CWE-787: Out-of-bounds Write Disclosed 26 January 2021
ELI5

A form-processing clerk has a rule: a backslash means "treat the next character literally". Someone hands in a form ending in a single backslash, so the clerk reaches for the next character — but there isn't one, because the form ended. Rather than stopping, they keep reading past the edge of the page and copying whatever they find onto the next form in the pile, overwriting it. Arrange the pile carefully and you can dictate exactly what the clerk writes.

Key terms on this page
setuid binary
A program that runs with its owner's privileges rather than the caller's. sudo is owned by root and setuid, so any memory-corruption bug in it is a direct route to root.
heap buffer overflow
Writing past the end of a heap allocation, corrupting adjacent structures. Unlike an over-read, it lets an attacker change memory, which is the path to code execution.
local privilege escalation
Turning any level of access on a machine into full administrative control. It is the second half of most real intrusions.

What happened

In January 2021 Qualys disclosed CVE-2021-3156, nicknamed "Baron Samedit", a heap buffer overflow in sudo. It allowed any local user to become root — with no password, and regardless of whether that user appeared in the sudoers file at all. Exploitation worked in the default configuration.

Qualys demonstrated working root exploits on Ubuntu 20.04, Debian 10 and Fedora 33. Because sudo is installed essentially everywhere on Unix-like systems, the affected population was close to the entire Linux and macOS server estate.

The bug was introduced in a commit in July 2011 and went unnoticed for nearly a decade, affecting legacy versions 1.8.2 through 1.8.31p2 and stable versions 1.9.0 through 1.9.5p1.

The technical root cause

sudo supports escaping special characters in command arguments with backslashes, and unescapes them later. Normally, when sudo runs a command in "shell" mode it escapes the arguments on the way in, so the unescaping logic never encounters a malformed string.

A code path that skipped the escaping but kept the unescaping

Invoking sudo through sudoedit -s reached the unescaping loop without the corresponding escaping step having run. That made it possible to pass an argument ending in a lone trailing backslash — a state the unescaping code had been written to assume could never occur.

Reading past the terminator

On encountering a backslash, the loop consumed the following character. With the backslash as the final byte, the "following character" was the string's null terminator — so the loop stepped over the end of the string and carried on reading and copying out-of-bounds data into a heap buffer sized for the original input. The attacker controlled both the length and the content of the overflow.

THE SHAPE OF THE BUG
/* Simplified illustration */
while (*from != '\0') {
    if (from[0] == '\\' && from[1] != '\0')
        from++;
    *to++ = *from++;
}

/* In the vulnerable path the guard on
   from[1] was not effective, so a
   trailing "\" consumed the NUL and
   the loop ran past the buffer end. */
THE PRINCIPLE
/* 1. Never advance past a terminator.
      Check the next byte exists before
      consuming it.                     */

/* 2. Do not rely on an invariant
      established elsewhere. The
      unescaper assumed a caller had
      escaped the input. One code path
      did not, and the assumption was
      never checked locally.            */

That second point is the durable engineering lesson. The unescaping function was correct given its assumed precondition. The vulnerability lived in the gap between a documented assumption and an unusual call path that violated it. Preconditions enforced by convention rather than by code fail silently, and they fail years later, when whoever knew the convention has moved on.

Why a local bug matters as much as a remote one

Privilege escalation flaws are sometimes discounted because they require existing access. That reasoning misreads how intrusions work. Attackers routinely obtain low-privilege access first — a compromised web application process, a stolen SSH key for an unprivileged account, a container breakout, a malicious dependency running in CI. A reliable local root exploit converts that foothold into total control of the host.

In shared environments the effect compounds: on a multi-user build server or a shell host, this bug meant every user was effectively root.

The lessons that actually transfer

FAQ

Did I need to be in the sudoers file?

No. That is what made this unusually severe — the overflow occurred during argument processing, before any authorisation decision. Users with no sudo rights whatsoever could exploit it.

Would a memory-safe language have prevented it?

This specific failure mode, yes — a bounds-checked language would panic or throw rather than write out of bounds, turning a root exploit into a crash. The logic error of mishandling a trailing escape could still exist; its consequences would be far less severe.

Related reading

Sources