FlawOpen

flawopen.com/Teardowns/cve-2021-3156-sudo-baron-samedit

● CVE-2021-3156 · CVSS 7.8 · High
FlawOpen Security Research

CVE-2021-3156: Sudo Baron Samedit Heap Overflow

CVE-2021-3156 was a heap buffer overflow in sudo set_cmnd(): a trailing backslash could make its unescaper step past an argument terminator and copy beyond a buffer sized from the arguments. Qualys reported affected legacy versions 1.8.2–1.8.31p2 and stable versions 1.9.0–1.9.5p1 in the default configuration.[3]

💡 Plain English Explainer (ELI5)

A form-processing clerk treats a backslash as an instruction to skip the next character. If the backslash is the last character, the clerk copies the form’s end marker as if it were ordinary text, then keeps reading beyond the form and writing into a tray sized only for its actual text. The fix checks that a real character follows before skipping and checks that each write fits.[3]

Core Concepts & Subsystem Terms

Baron Samedit
CVE-2021-3156 is a heap-based buffer overflow in sudo. Qualys reported that the affected parsing path had existed since July 2011.[3]
Heap Overflow
Writing past the boundary of dynamically allocated heap memory, corrupting control structures.[3], [2]
Bounded parser
A parser that carries an explicit input length and checks every read and output write against that length and capacity.[4]

Framework mappings

Root Cause Analysis

Reachability depended on different mode checks: parse_args() escaped metacharacters only in command-run plus shell mode, while set_cmnd() entered the unescape block for shell or login-shell mode without requiring command-run mode. This mismatch could let an argument reach the unescaper without the preceding escape transformation. In set_cmnd(), the unescape loop copied a backslash and then read the following byte. When an argument ended with a backslash, the loop advanced past the terminating NUL and continued copying beyond the source argument into user_args, whose size was calculated from the original arguments. The affected path required local access and the vulnerable default configuration; Qualys reported local privilege escalation to root on affected systems. The fix checks that a byte follows the backslash before skipping it and verifies remaining destination capacity before each write.[3], [4]

Step-by-Step Attack Flow

Step 1

Mode-check mismatch

Qualys traced CVE-2021-3156 to a mode-check mismatch: parse_args() escaped metacharacters only for command-run plus shell mode, but set_cmnd() entered its unescape block for shell or login-shell mode without requiring command-run mode. This could expose the unescaper to an argument that had not passed through the matching escape stage. Local access and the vulnerable default configuration were required.[3], [4]

Step 2

Destination sized from arguments

set_cmnd() allocated user_args from the lengths of the command-line arguments, including space for their terminators and separators.[3]

Step 3

Terminator crossed

In the unescape loop, a trailing backslash passed the non-whitespace test because the following NUL byte is not whitespace. Advancing over the backslash left the source pointer at NUL; copying and advancing again moved it past the argument.[3]

Step 4

Heap boundary exceeded

The loop could then read past the argument and write bytes not included in the destination-size calculation, causing a heap buffer overflow. Qualys verified local privilege escalation to root on affected default configurations.[3]

Step 5

Upstream fix

The upstream patch avoids this unescape path outside command-run mode, refuses to skip a backslash before NUL, and checks remaining destination capacity before writes. Apply the security update supplied by the operating-system vendor.[4]

Source Code: Flaw vs. Secure Implementation

The hardened excerpt also adds MODE_RUN to align the unescape block with the mode that performs the preceding escape transformation. These focused excerpts show sudo set_cmnd() argument-unescaping logic and the corresponding upstream fix; they are not the complete function or an exploit. The vulnerable loop checks whether the next byte is whitespace but not whether it is NUL before advancing over a backslash. The fix rejects that end-of-argument case and checks remaining destination capacity before writes. Apply the security update provided by the operating-system vendor.[3], [4]

UNPATCHED FLAW
size_t size = 0;
for (char **av = NewArgv + 1; *av; av++)
    size += strlen(*av) + 1;
char *user_args = malloc(size);

if (ISSET(sudo_mode, MODE_SHELL | MODE_LOGIN_SHELL)) {
    for (char *to = user_args, **av = NewArgv + 1, *from; (from = *av); av++) {
        while (*from) {
            if (from[0] == '\\' && !isspace((unsigned char)from[1]))
                from++;
            *to++ = *from++;
        }
        *to++ = ' ';
    }
    *--to = '\0';
}
HARDENED SECURE PATCH
if (ISSET(sudo_mode, MODE_SHELL | MODE_LOGIN_SHELL) &&
    ISSET(sudo_mode, MODE_RUN)) {
    for (char *to = user_args, **av = NewArgv + 1, *from;
         (from = *av); av++) {
        while (*from) {
            if (from[0] == '\\' && from[1] != '\0' &&
                !isspace((unsigned char)from[1])) {
                from++;
            }
            if (size - (size_t)(to - user_args) < 1)
                debug_return_int(NOT_FOUND_ERROR);
            *to++ = *from++;
        }
        if (size - (size_t)(to - user_args) < 1)
            debug_return_int(NOT_FOUND_ERROR);
        *to++ = ' ';
    }
    *--to = '\0';
}

Incident Timeline

July 2011

Qualys reported that the affected code path was introduced in July 2011.[3]

13–26 January 2021

Qualys reported disclosure to sudo's maintainer on 13 January, distribution contacts on 19 January, and coordinated public disclosure on 26 January.[3], [5]

Engineering & System Hardening Checklist

Sources