Merge pull request #143 from divya0795/fix/asar-extraction-safety-and-process-kill

fix(asar): path traversal (zip-slip) on extract + Pickle buffer overrun
This commit is contained in:
k1tbyte
2026-07-21 23:22:42 +03:00
committed by GitHub
3 changed files with 36 additions and 6 deletions
+7 -4
View File
@@ -41,9 +41,12 @@ namespace AsarSharp
var destFilename = Path.Combine(dest, filename); var destFilename = Path.Combine(dest, filename);
var file = filesystem.GetFile(filename, followLinks); var file = filesystem.GetFile(filename, followLinks);
// Path-traversal guard. // Path-traversal (zip-slip) guard. Uses the normalising
string relativePath = Extensions.GetRelativePath(dest, destFilename); // containment check: GetRelativePath's fast path strips the
if (relativePath.StartsWith("..")) // prefix literally without resolving "..", so a crafted entry
// such as "a/../../evil" would otherwise pass this check and be
// written outside "dest".
if (!Extensions.IsPathInside(dest, destFilename))
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
$"{fullPath}: file \"{destFilename}\" writes out of the package"); $"{fullPath}: file \"{destFilename}\" writes out of the package");
@@ -164,7 +167,7 @@ namespace AsarSharp
var linkTo = Path.Combine(relativeLinkPath, Path.GetFileName(file.Link)); var linkTo = Path.Combine(relativeLinkPath, Path.GetFileName(file.Link));
if (Extensions.GetRelativePath(dest, linkSrcPath).StartsWith("..")) if (!Extensions.IsPathInside(dest, linkSrcPath))
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
$"{fullPath}: file \"{file.Link}\" links out of the package to \"{linkSrcPath}\""); $"{fullPath}: file \"{file.Link}\" links out of the package to \"{linkSrcPath}\"");
+8 -2
View File
@@ -231,8 +231,14 @@ namespace AsarSharp.PickleTools
private void Resize(int newCapacity) private void Resize(int newCapacity)
{ {
newCapacity = AlignInt(newCapacity, PAYLOAD_UNIT); newCapacity = AlignInt(newCapacity, PAYLOAD_UNIT);
byte[] newHeader = new byte[_header.Length + newCapacity]; // The backing array must hold the header plus the full advertised
Buffer.BlockCopy(_header, 0, newHeader, 0, _header.Length); // payload capacity (matches Chromium's realloc(header_size_ + new_capacity)).
// Sizing it from _header.Length under-allocates by _headerSize on the
// first growth (when _header is still empty), leaving the payload region
// _headerSize bytes short of _capacityAfterHeader and overrunning the
// buffer when a write fills the payload.
byte[] newHeader = new byte[_headerSize + newCapacity];
Buffer.BlockCopy(_header, 0, newHeader, 0, Math.Min(_header.Length, newHeader.Length));
_header = newHeader; _header = newHeader;
_capacityAfterHeader = newCapacity; _capacityAfterHeader = newCapacity;
} }
+21
View File
@@ -97,6 +97,27 @@ namespace AsarSharp.Utils
private static bool IsSeparator(char c) => c == '/' || c == '\\'; private static bool IsSeparator(char c) => c == '/' || c == '\\';
/// <summary>
/// Security check for archive extraction: returns true only when
/// <paramref name="candidate"/> resolves to a location inside
/// <paramref name="root"/>. Both paths are fully normalised first, so
/// embedded ".." segments cannot escape the root (zip-slip). The
/// <see cref="GetRelativePath"/> fast path must not be used here because
/// it strips the prefix literally without resolving "..".
/// </summary>
public static bool IsPathInside(string root, string candidate)
{
string fullRoot = TrimTrailingSeparators(Path.GetFullPath(root));
string fullCandidate = TrimTrailingSeparators(Path.GetFullPath(candidate));
if (string.Equals(fullRoot, fullCandidate, StringComparison.OrdinalIgnoreCase))
return true;
return fullCandidate.Length > fullRoot.Length
&& fullCandidate.StartsWith(fullRoot, StringComparison.OrdinalIgnoreCase)
&& IsSeparator(fullCandidate[fullRoot.Length]);
}
public static string GetDirectoryName(string path) public static string GetDirectoryName(string path)
{ {
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))