# Patch Analysis: CVE-2026-4480 Fix (commit b80131fcf582)

## What the fix changes

### Files modified
- `source3/printing/print_generic.c` (94 insertions, 13 deletions)

### Functions changed
1. **New function: `replace_print_cmd_J()`** — Pre-substitutes `%J` in the print command with a masked, single-quoted version of the jobname (or a fixed fallback string for mixed-quoting configurations).

2. **Modified function: `generic_job_submit()`** — Now calls `replace_print_cmd_J()` to pre-substitute `%J` before passing the command to `print_run_command()`. Removes the `"%J", jobname` variadic argument from the `print_run_command()` call. Also adds `log_escape()` for safe logging of the jobname.

3. **New utility functions (in `lib/util/substitute.c`):**
   - `talloc_string_sub_unsafe()` — Generic function for safe substitution of `%X` variables in shell commands. Masks unsafe characters, wraps in single quotes, or falls back to a fixed value.
   - `talloc_string_sub_mixed_quoting()` — Detects if a print command uses mixed single/double quoting around `%J`, which prevents safe single-quote wrapping.

4. **New header: `lib/util/util_str_escape.h`** — Included for `STRING_SUB_UNSAFE_CHARACTERS` definition.

### Logic changes

**Before the fix (vulnerable 4.22.9):**
```c
jobname = talloc_strdup(ctx, pjob->jobname);
jobname = talloc_string_sub(ctx, jobname, "'", "_");  // only replaces ' → _
ret = print_run_command(snum, ..., lp_print_command(snum), NULL,
    "%s", p, "%J", jobname, "%f", p, ...);
```
- `talloc_string_sub()` with `remove_unsafe_characters=true` only masks `$ \` " ' ; % \r \n`
- Missing: `& | < > ( ) # ! ~ * ?` and other shell metacharacters
- `&` (and `&&`) survives → command injection

**After the fix (4.22.10):**
```c
print_cmd = lp_print_command(snum);
if (talloc_string_sub_mixed_quoting(print_cmd, 'J')) {
    jobname = "__CVE-2026-4480_FallbackJobname__";  // fallback for mixed quoting
}
print_cmd = replace_print_cmd_J(ctx, print_cmd, jobname, invalid_jobname);
ret = print_run_command(snum, ..., print_cmd, NULL,
    "%s", p, "%f", p, ...);  // NO "%J" — already pre-substituted
```
- `replace_print_cmd_J()` masks ALL unsafe characters: `$ \` " ' ; % | & < > / \` + control chars → `_`
- Wraps `%J` substitution in single quotes: `'%J'` → `'<masked_value>'`
- Falls back to fixed string for mixed-quoting configurations
- Pre-substitutes `%J` so `print_run_command()` never sees raw `%J`

## What assumptions the fix makes

1. **Sink-level coverage assumption:** The fix assumes that ALL paths to shell execution in the printing subsystem go through `generic_job_submit()` → `print_run_command()`. This assumption is **correct** — a thorough search of all `print_run_command()` callers confirms that only `generic_job_submit()` passes `%J`.

2. **Single-quote safety assumption:** The fix assumes that wrapping the masked jobname in single quotes prevents shell interpretation. This is **correct** for POSIX shells — inside single quotes, no character has special meaning. The only way to break out is a literal `'` character, which is masked to `_`.

3. **Mixed-quoting detection assumption:** The fix assumes that if the print command has both single and double quotes around `%J`, it cannot safely wrap in single quotes. In this case, it falls back to a fixed string. This is a **conservative** assumption — it errs on the side of safety.

4. **Non-CUPS/non-iPrint scope:** The fix only applies to printing backends that use `generic_job_submit()` (sysv, lprng, etc.). CUPS and iPrint backends use their own APIs and don't execute `print command` via shell. This is **correct** — the vulnerability only exists in the generic printing path.

## What code paths/inputs the fix does NOT cover

### Paths that DO NOT need coverage (correctly not covered):
- **CUPS backend** (`cups_printif`): Uses CUPS API directly, no `print command` execution
- **iPrint backend** (`iprint_printif`): Uses iPrint API directly, no `print command` execution
- **Other print commands** (`lprm`, `lppause`, `lpresume`, `lpq`, `queuepause`, `queueresume`): These pass only `%j` (numeric job ID), `%p` (printer name from config), or no substitutions — none are client-controlled injectable values

### Paths that ARE covered (confirmed by this variant analysis):
- **SMB file open on printable share** (original repro): ✅ Covered — reaches `generic_job_submit()`
- **spoolss RPC StartDocPrinter** (this variant): ✅ Covered — reaches `generic_job_submit()`
- **SMB2 Create on printable share**: ✅ Covered — reaches `generic_job_submit()`
- **SMB1 Open Print File**: ✅ Covered (but not injectable — uses hardcoded docname)

## Whether the fix is complete or leaves gaps

**The fix is COMPLETE.** No gaps were found.

The fix's key design decision — placing the sanitization at the **sink** (`generic_job_submit()`) rather than at any specific entry point — ensures that ALL paths to the vulnerable `%J` substitution are covered. This variant analysis confirmed this by:

1. Testing the spoolss RPC `StartDocPrinter(pDocName)` path — a materially different entry point with different input constraints
2. Confirming RCE on the vulnerable version (4.22.9)
3. Confirming the fix blocks the same attack on the fixed version (4.22.10)

## Comparison of behavior before and after the fix

### Before (vulnerable 4.22.9):
- Input: `pDocName = 'PWN&&id&&touch spoolss_marker&&END'`
- Executed command: `(echo PWN&&id&&touch spoolss_marker&&END) > /tmp/samba_printlog 2>&1`
- Result: `id` executes (uid=65534), `spoolss_marker` file created — **RCE**

### After (fixed 4.22.10):
- Input: `pDocName = 'PWN&&id&&touch spoolss_marker&&END'`
- Executed command: `(echo 'PWN__id__touch spoolss_marker__END') > /tmp/samba_printlog 2>&1`
- Result: Literal string printed, no command execution — **blocked**

The difference: `&` is masked to `_`, and the jobname is wrapped in single quotes, preventing shell interpretation.

## Target's threat model scope

Samba's printing subsystem is designed to execute admin-configured `print command` strings via shell. The vulnerability is that client-controlled data (job name) reaches this shell execution without proper escaping. The fix correctly addresses this by sanitizing at the point of substitution.

The spoolss RPC path is within Samba's threat model — it's a network-accessible RPC interface that accepts unauthenticated (guest) connections when `map to guest = Bad User` is configured. The document name field is explicitly client-controlled per the MS-RPRN specification.
