Table of Contents

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)

public static string ResolveOrThrow(string rootDirectory, string relativePath)

Parameters

rootDirectory string
relativePath string

Returns

string

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

rootDirectory string

The install root. Must be an existing, rooted directory.

relativePath string

The manifest-supplied path, which is untrusted.

fullPath string

The resolved absolute path when the call returns true.

rejectionReason string

Why the path was refused, when the call returns false.

Returns

bool

True when the path is safe to act on.