September 4, 2024·6 min read·engineeringrustsecuritywindowsprocesses

Rust's batch-file fix missed Windows filename normalization

CVE-2024-43402 bypasses April's batch argument mitigation through trailing spaces or periods in a filename. The flaw lies in selecting the escaping path before Windows normalizes the name.

Escaping rules only protect an invocation when the implementation selects the right rules. Rust's April repair for Windows batch-file arguments checked whether a command name ended in .bat or .cmd. Windows can strip trailing whitespace and periods from the name before interpreting it, so a spelling that missed Rust's suffix check could still invoke a batch file. The September 4 advisory for CVE-2024-43402 describes this incomplete fix.

The Rust security working group rates the new issue low because it requires niche conditions. Its scope includes untrusted arguments passed to a Windows batch script whose name contains the relevant trailing characters. That is narrower than the original April vulnerability, which affected batch invocation escaping before Rust 1.77.2.

I find this a useful systems example because the argument encoder can be improved while the surrounding dispatch remains wrong. The type-safe interface still has to agree with the operating system about which program is being invoked. The safety of a selected parser does not establish the correctness of the selection.

The original repair had a classification step

The April advisory explained the underlying platform problem. Windows process creation supplies one command-line string, and the child decides how to split it. Most executables follow the conventional C runtime rules. cmd.exe, used for batch execution, follows its own command parsing and expansion rules.

Rust's Command implementation therefore needs special encoding when it invokes a batch file. The April fix strengthened that encoding and added InvalidInput failures for arguments it could not safely represent. Those measures depended on entering the batch-specific path.

The Rust 1.77.2 process implementation shows the dispatch explicitly. After resolving the executable, it performs a case-insensitive check of the final UTF-16 characters for a batch extension. A matching name selects the command interpreter and batch command-line builder. A nonmatching name takes the ordinary command-line construction path.

This is separate from argument escaping. The suffix test classifies the destination, then the chosen builder encodes the arguments. A mistake in the classification can therefore bypass the new builder without defeating any individual rule inside it.

The code makes this dependency easy to inspect. There is a boolean for whether the target is a batch file, followed by distinct branches. The vulnerability concerns whether that boolean reflects the target Windows will actually execute.

The two branches also choose different executable handling. Recognized batch files use the command-prompt program and a command string containing the script. Ordinary targets retain the resolved executable and ordinary argument construction. Classification therefore selects both the interpreter and the encoding arrangement, which explains why this check belongs in the security analysis of the complete spawn operation.

Windows accepts another spelling of the same suffix

The new advisory states that Windows removes trailing whitespace and periods when parsing file paths. It gives a suffix containing additional periods and a space that Windows interprets as .bat. Rust's original suffix test did not account for that transformation.

The important distinction is between the supplied spelling and the operating system's interpretation. The supplied name can fail an exact ending test while the interpreted path still names a batch script. Rust and Windows then disagree about which argument grammar applies to the invocation.

Case-insensitive matching does not solve that mismatch. It handles variations in letter case, while trailing-character removal changes where the meaningful extension ends. These are different normalization operations. The original implementation handled the former without handling the latter.

The advisory also explains why the original assumption seemed sufficient. Rust refuses to invoke batch scripts with no extension, so checking for the two batch extensions appeared to cover the permitted cases. The missing case still has an effective extension, but its supplied spelling contains characters Windows discards.

That distinction matters when understanding the repair. The new issue is not evidence that every filename must be rewritten arbitrarily or that all executable lookup has the same defect. The disclosed discrepancy involves specific trailing characters and the classification of batch invocations.

The argument boundary depends on the target boundary

An application can supply separate arguments through Command and still encounter this flaw. The name chooses the processing path, and that path determines whether untrusted argument characters remain data. The command name and argument values are different inputs, but their interpretation is coupled by the selected encoder.

This is why reasoning about the argument in isolation is insufficient. A value might be correctly encoded for a conventional executable and incorrectly encoded for cmd.exe. The same literal text does not have one universal Windows representation independent of its consumer.

The original batch encoder contains additional handling for quotes, backslashes, and percent expansion. Its 1.77.2 source also rejects line breaks that can truncate arguments. None of those precautions helps when dispatch sends the invocation through another builder.

The failure illustrates a specific ordering requirement. The implementation must account for the filename interpretation that determines the target before it can choose target-specific encoding. Applying a careful encoder to the wrong classification leaves the original semantic boundary unprotected.

I prefer this explanation to treating the second advisory as another unspecified escaping edge case. It identifies a separate component of correctness. The first repair changed how batch arguments were represented, and the new disclosure shows a way to avoid selecting that representation.

The immediate mitigation uses the fixed encoder already present

For affected users on Rust 1.77.2 or later, the September advisory recommends removing trailing ASCII spaces and periods from the batch filename. It identifies the characters as 0x20 and 0x2E. Removing those suffix characters lets the existing extension check recognize the batch file and apply April's mitigations.

The version qualification is essential. Trimming the name on an older implementation does not create the improved batch encoder delivered in 1.77.2. The mitigation depends on that encoder already being present and makes sure the invocation reaches it.

This mitigation concerns the filename, not sanitizing the untrusted arguments by deleting arbitrary characters. Its purpose is to align classification with Windows' effective filename. The argument encoder then remains responsible for its own contract and can reject values it cannot represent safely.

The advisory says Rust 1.81.0 is scheduled for September 5 and will apply the prior mitigations regardless of the trailing filename characters. As of this disclosure, that release is forthcoming. The available guidance therefore includes both the filename mitigation and the announced toolchain correction.

Severity follows the disclosed conditions

The affected-versions statement covers releases before 1.81.0 when an application or dependency invokes the specifically named batch-file case and passes untrusted arguments. The security working group notes that an automated severity score may be higher than its own rating because the score does not reflect the conditions needed to trigger the incomplete fix.

That explanation is more informative than applying a generic command-injection label to every Windows subprocess. The dangerous consequence still concerns shell interpretation of attacker-controlled argument content. The route to that consequence requires the particular filename spelling that bypasses batch detection.

A dependency can supply the invocation just as application code can. The location of the Command call does not change how Windows interprets the filename. What matters is whether that path uses the affected spelling and whether untrusted data reaches the arguments.

The advisory does not establish that an attacker must control every part of the invocation. A program might already use an unusual filename spelling while accepting external argument values. Conversely, a program with only trusted arguments does not meet the disclosed untrusted-input condition merely because its filename contains trailing punctuation.

Dispatch is part of a parser's security contract

The original API intent remains literal argument passing. April's update improved enforcement for recognized batch files, and September's disclosure identifies a classification gap around the same enforcement. The corrected direction is to account for the operating system's filename rules when deciding which argument rules apply.

This is a concrete boundary between two interpretations of one invocation. Rust sees a name before suffix normalization, while Windows acts on the normalized name. Ensuring both choose the batch case makes the existing escaping and rejection behavior relevant to the program that actually runs. That agreement is necessary before the contents of an untrusted argument can be assessed under the correct grammar.