Class PathContainment
- Namespace
- FishMMO.Patcher
- Assembly
- Updater.dll
Resolves a manifest-supplied relative path against the install root and proves the result stays inside it.
public static class PathContainment
- Inheritance
-
PathContainment
- Inherited Members
Remarks
This is the fix for the archive-extraction traversal ("zip slip") class of bug. Every
path in a patch manifest is attacker-controlled the moment the patch host is spoofed or
compromised, and Combine(string, string) is not a security
boundary: it happily returns the second argument outright when that argument is rooted,
and it does not collapse .. at all. So
Path.Combine(installRoot, "../../.bashrc") and
Path.Combine(installRoot, "/etc/cron.d/x") both escape, and the updater writes,
overwrites, or DELETES whatever they name — with whatever privileges the player's
install runs under.
The delete path matters as much as the write path. An unchecked delete is arbitrary file deletion, which needs no code execution to be destructive, so every site that turns a manifest string into a filesystem path goes through here — new files, patched files, deletions, and the directory pre-creation pass that runs ahead of all three.
The checks are deliberately layered and deliberately strict. Each one alone has a known
bypass: a textual .. scan misses C:\, a prefix comparison against the
resolved path misses a symlinked subdirectory, and rejecting rooted paths misses
..\ on a manifest authored on Windows and applied on Linux (where \ is a
legal filename character rather than a separator, so Path would
not split on it at all). Together they leave no gap, and the cost is a few string
operations per manifest entry.
Methods
ResolveOrThrow(string, string)
TryResolve(string, string, out string, out string), throwing PathContainmentException on refusal.
public static string ResolveOrThrow(string rootDirectory, string relativePath)
Parameters
Returns
Remarks
The throwing form is what the patch phases use: it fails the whole patch and triggers the existing rollback, rather than skipping the offending entry and committing a partially-applied update the manifest author chose the shape of.
TryResolve(string, string, out string, out string)
Resolves relativePath under rootDirectory.
public static bool TryResolve(string rootDirectory, string relativePath, out string fullPath, out string rejectionReason)
Parameters
rootDirectorystringThe install root. Must be an existing, rooted directory.
relativePathstringThe manifest-supplied path, which is untrusted.
fullPathstringThe resolved absolute path when the call returns true.
rejectionReasonstringWhy the path was refused, when the call returns false.
Returns
- bool
True when the path is safe to act on.