diff --git a/AsarSharp/AsarExtractor.cs b/AsarSharp/AsarExtractor.cs index 6a9475e..96aca5d 100644 --- a/AsarSharp/AsarExtractor.cs +++ b/AsarSharp/AsarExtractor.cs @@ -41,9 +41,12 @@ namespace AsarSharp var destFilename = Path.Combine(dest, filename); var file = filesystem.GetFile(filename, followLinks); - // Path-traversal guard. - string relativePath = Extensions.GetRelativePath(dest, destFilename); - if (relativePath.StartsWith("..")) + // Path-traversal (zip-slip) guard. Uses the normalising + // containment check: GetRelativePath's fast path strips the + // 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( $"{fullPath}: file \"{destFilename}\" writes out of the package"); @@ -164,7 +167,7 @@ namespace AsarSharp var linkTo = Path.Combine(relativeLinkPath, Path.GetFileName(file.Link)); - if (Extensions.GetRelativePath(dest, linkSrcPath).StartsWith("..")) + if (!Extensions.IsPathInside(dest, linkSrcPath)) { throw new InvalidOperationException( $"{fullPath}: file \"{file.Link}\" links out of the package to \"{linkSrcPath}\""); diff --git a/AsarSharp/PickleTools/Pickle.cs b/AsarSharp/PickleTools/Pickle.cs index 169d15d..c192a8f 100644 --- a/AsarSharp/PickleTools/Pickle.cs +++ b/AsarSharp/PickleTools/Pickle.cs @@ -231,8 +231,14 @@ namespace AsarSharp.PickleTools private void Resize(int newCapacity) { newCapacity = AlignInt(newCapacity, PAYLOAD_UNIT); - byte[] newHeader = new byte[_header.Length + newCapacity]; - Buffer.BlockCopy(_header, 0, newHeader, 0, _header.Length); + // The backing array must hold the header plus the full advertised + // 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; _capacityAfterHeader = newCapacity; } diff --git a/AsarSharp/Utils/Extensions.cs b/AsarSharp/Utils/Extensions.cs index feeb7e0..3abaf5f 100644 --- a/AsarSharp/Utils/Extensions.cs +++ b/AsarSharp/Utils/Extensions.cs @@ -97,6 +97,27 @@ namespace AsarSharp.Utils private static bool IsSeparator(char c) => c == '/' || c == '\\'; + /// + /// Security check for archive extraction: returns true only when + /// resolves to a location inside + /// . Both paths are fully normalised first, so + /// embedded ".." segments cannot escape the root (zip-slip). The + /// fast path must not be used here because + /// it strips the prefix literally without resolving "..". + /// + 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) { if (string.IsNullOrEmpty(path))