November 7, 2023·4 min read·engineeringgosecuritywindowsfilesystem

Go 1.21.4 recognizes Windows paths that evade ordinary locality checks

Windows device prefixes and reserved names require more than dot-segment cleanup. Go's November fixes align filepath classification and normalization with additional Windows path forms.

A path can look relative to an application while naming something special to the operating system. Go's November security release corrects two groups of Windows path cases in path/filepath. One involves a device-path prefix that the package did not recognize. The other involves reserved device names with spelling variations that escaped IsLocal checks.

The Go 1.21.4 release history dates the release to November 7. A security announcement also releases Go 1.20.11 and describes CVE-2023-45283 and CVE-2023-45284. The fixes concern how the library classifies and transforms paths, which can affect applications that accept names from an untrusted source.

I appreciate Go's portable filesystem APIs because they let most application code express ordinary file operations directly. Portability still requires the implementation to understand the target operating system's naming rules. A path-security check cannot rely on the subset of those rules familiar to a developer using another platform.

A device prefix was missing from classification

On Windows, the announcement explains that a path beginning with \??\ is a Root Local Device path, equivalent to one beginning with \\?\. Such paths can access arbitrary locations. Its example, \??\c:\x, names the same location as the more familiar c:\x.

The old package did not treat the \??\ prefix as special. IsAbs failed to classify paths beginning with that prefix as absolute. After the correction, it does. VolumeName now also recognizes the prefix as a volume name. These functions need a consistent understanding of the same path grammar.

The classification error matters wherever an application uses absolute-versus-relative status as part of an input decision. The caller can make a reasonable policy choice while receiving an incorrect answer about a platform-specific form. Rejecting only drive-letter spellings does not address a second spelling that the operating system interprets with comparable authority.

This is an API correctness issue with security consequences. The application should not need to maintain its own incomplete catalogue of device-prefix aliases alongside filepath. The library's classification needs to match the operating system behavior that gives the string its meaning.

Normalization could create a special path

The same CVE includes Clean and Join. The announcement gives a rooted input, \a\..\??\b, that Clean could transform into the device path \??\b. After the fix, it produces .\??\b. The inserted relative form prevents cleanup from promoting the result into that device namespace.

This demonstrates why normalization deserves its own security analysis. A transformation can remove ordinary path elements and expose a special prefix at the start of the result. Inspecting only the original spelling is insufficient if the string passed to the operating system has a different interpretation after cleanup.

Join had a related problem. Combining elements representing a root separator, ?? and b could produce \??\b. The corrected result is \.\??\b. Individually unremarkable components could previously assemble into a special path, so the operation needs to preserve the intended interpretation of the combined result.

These examples are specific to Windows rules. They do not show that eliminating parent-directory segments is generally wrong. They show that a cleaner must account for additional syntax that can emerge after those segments disappear. The output's prefix has meaning independent of the developer's reason for combining the components.

Reserved device names have several accepted spellings

CVE-2023-45284 concerns IsLocal. The announcement identifies reserved names followed by spaces, such as COM1 , and COM or LPT followed by superscript one, two or three. The function now reports these cases as non-local.

A locality check that recognizes only an ordinary textual spelling can disagree with the operating system about these inputs. A trailing space may look insignificant to an application, yet the resulting name can still refer to a reserved device. Superscript characters also make a simple ASCII-only spelling rule incomplete.

The relevant property is whether the name has special platform meaning. It is not enough to ask whether a person would recognize the text as resembling an ordinary filename. The corrected classification covers the disclosed spellings so callers do not treat them as ordinary local names.

I favor using the standard library for this decision because operating-system path semantics are broader than a convenient regular expression. These fixes strengthen the shared implementation of an existing question. They do not require each caller to invent a new definition of locality.

The fixes also separate normalization from acceptance. Clean and Join must avoid accidentally creating a device path while transforming components. IsAbs and IsLocal must answer classification questions about the resulting spelling. Correct behavior in one function does not remove the need for correctness in the others, because callers compose these operations in different orders and use their results for different decisions.

Locality remains a lexical guarantee

The versioned documentation for IsLocal defines the property precisely. The function uses lexical analysis. It checks that a path stays within the subtree where it is evaluated, is nonempty and is not absolute. On Windows it also excludes reserved names.

The documented consequence is that joining a local path to a base produces a path contained within that base, and cleaning it produces an unrooted path without parent-directory elements. Those statements concern the path representation. The documentation explicitly says IsLocal does not account for symbolic links in the filesystem.

That limit remains relevant after the patch. Correctly recognizing a device name does not turn lexical analysis into a filesystem traversal check. A symbolic link can change where a path resolves, so a claim about string structure should retain its stated scope when an application uses it as part of a broader file-access policy.

For programs built with the affected Go toolchains, adopting the patched library means rebuilding with the corrected release. The resulting checks then include the newly recognized device prefixes and reserved-name variants. Callers still need to preserve the relationship between the path they validate and the path they use, because the library's guarantee applies to the actual input supplied to the operation.