private static PoisonedFileEntry ExtractAndCheckZipFileOnly(IEnumerable <CatalogPackageEntry> catalogedPackages, string zipToCheck, string markerFileName, string tempDir, Queue <string> futureFilesToCheck) { var poisonEntry = new PoisonedFileEntry(); poisonEntry.Path = zipToCheck; using (var sha = SHA256.Create()) using (var stream = File.OpenRead(zipToCheck)) { poisonEntry.Hash = sha.ComputeHash(stream); } // first check for a matching poisoned or non-poisoned hash match: // - non-poisoned is a potential error where the package was redownloaded. // - poisoned is a use of a local package we were not expecting. foreach (var matchingCatalogedPackage in catalogedPackages.Where(c => c.OriginalHash.SequenceEqual(poisonEntry.Hash) || (c.PoisonedHash?.SequenceEqual(poisonEntry.Hash) ?? false))) { poisonEntry.Type |= PoisonType.Hash; var match = new PoisonMatch { Package = matchingCatalogedPackage.Path, PackageId = matchingCatalogedPackage.Id, PackageVersion = matchingCatalogedPackage.Version, }; poisonEntry.Matches.Add(match); } // now extract and look for the marker file if (ZipFileExtensions.Any(e => zipToCheck.ToLowerInvariant().EndsWith(e))) { ZipFile.ExtractToDirectory(zipToCheck, tempDir); } else if (TarFileExtensions.Any(e => zipToCheck.ToLowerInvariant().EndsWith(e))) { Directory.CreateDirectory(tempDir); var psi = new ProcessStartInfo("tar", $"xf {zipToCheck} -C {tempDir}"); Process.Start(psi).WaitForExit(); } else if (TarGzFileExtensions.Any(e => zipToCheck.ToLowerInvariant().EndsWith(e))) { Directory.CreateDirectory(tempDir); var psi = new ProcessStartInfo("tar", $"xzf {zipToCheck} -C {tempDir}"); Process.Start(psi).WaitForExit(); } else { throw new ArgumentOutOfRangeException($"Don't know how to decompress {zipToCheck}"); } if (!string.IsNullOrWhiteSpace(markerFileName) && File.Exists(Path.Combine(tempDir, markerFileName))) { poisonEntry.Type |= PoisonType.NupkgFile; } foreach (var child in Directory.EnumerateFiles(tempDir, "*", SearchOption.AllDirectories)) { // also add anything in this zip/package for checking futureFilesToCheck.Enqueue(child); } return(poisonEntry.Type != PoisonType.None ? poisonEntry : null); }
private static PoisonedFileEntry CheckSingleFile(IEnumerable <CatalogPackageEntry> catalogedPackages, string rootPath, string fileToCheck) { // skip some common files that get copied verbatim from nupkgs - LICENSE, _._, etc as well as // file types that we never care about - text files, .gitconfig, etc. if (FileNamesToSkip.Any(f => Path.GetFileName(fileToCheck).ToLowerInvariant() == f.ToLowerInvariant()) || FileExtensionsToSkip.Any(e => Path.GetExtension(fileToCheck).ToLowerInvariant() == e.ToLowerInvariant())) { return(null); } var poisonEntry = new PoisonedFileEntry(); poisonEntry.Path = Utility.MakeRelativePath(fileToCheck, rootPath); // There seems to be some weird issues with using file streams both for hashing and assembly loading. // Copy everything into a memory stream to avoid these problems. var memStream = new MemoryStream(); using (var stream = File.OpenRead(fileToCheck)) { stream.CopyTo(memStream); } memStream.Seek(0, SeekOrigin.Begin); using (var sha = SHA256.Create()) { poisonEntry.Hash = sha.ComputeHash(memStream); } foreach (var p in catalogedPackages) { // This hash can match either the original hash (we couldn't poison the file, or redownloaded it) or // the poisoned hash (the obvious failure case of a poisoned file leaked). foreach (var matchingCatalogedFile in p.Files.Where(f => f.OriginalHash.SequenceEqual(poisonEntry.Hash) || (f.PoisonedHash?.SequenceEqual(poisonEntry.Hash) ?? false))) { poisonEntry.Type |= PoisonType.Hash; var match = new PoisonMatch { File = matchingCatalogedFile.Path, Package = p.Path, PackageId = p.Id, PackageVersion = p.Version, }; poisonEntry.Matches.Add(match); } } try { memStream.Seek(0, SeekOrigin.Begin); using (var asm = AssemblyDefinition.ReadAssembly(memStream)) { foreach (var a in asm.CustomAttributes) { foreach (var ca in a.ConstructorArguments) { if (ca.Type.Name == asm.MainModule.TypeSystem.String.Name) { if (ca.Value.ToString().Contains(PoisonMarker)) { poisonEntry.Type |= PoisonType.AssemblyAttribute; } } } } } } catch { // this is fine, it's just not an assembly. } return(poisonEntry.Type != PoisonType.None ? poisonEntry : null); }