/// <summary> /// The method that performs the actual validation. /// More information about checksum algorithm: /// https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PE-COFF.md#portable-pdb-checksum /// </summary> /// <param name="targetDirectory">The directory used during the current validation.</param> /// <param name="packageId">Package Id.</param> /// <param name="packageNormalizedVersion">PackageNormalized version.</param> /// <returns></returns> public virtual INuGetValidationResponse ValidateSymbolMatching(string targetDirectory, string packageId, string packageNormalizedVersion) { foreach (string extension in PEExtensionsPatterns) { foreach (string peFile in Directory.GetFiles(targetDirectory, extension, SearchOption.AllDirectories)) { INuGetValidationResponse validationResponse; if (!IsPortable(GetSymbolPath(peFile))) { _telemetryService.TrackSymbolsValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Failed); return(NuGetValidationResponse.FailedWithIssues(ValidationIssue.SymbolErrorCode_PdbIsNotPortable)); } if (!IsChecksumMatch(peFile, packageId, packageNormalizedVersion, out validationResponse)) { _telemetryService.TrackSymbolsValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Failed); return(validationResponse); } } } _telemetryService.TrackSymbolsValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Succeeded); return(NuGetValidationResponse.Succeeded); }
public async Task IncompleteStateIsProcessedAndSavedOnFailed() { // Arrange ValidatorStatus status = new ValidatorStatus() { State = ValidationStatus.Incomplete }; _validatorStateService.Setup(s => s.GetStatusAsync(It.IsAny <Guid>())).ReturnsAsync(status); _validatorStateService.Setup(s => s.SaveStatusAsync(It.IsAny <ValidatorStatus>())).ReturnsAsync(SaveStatusResult.Success); _symbolService .Setup(s => s.ValidateSymbolsAsync(It.IsAny <SymbolsValidatorMessage>(), It.IsAny <CancellationToken>())) .ReturnsAsync(NuGetValidationResponse.FailedWithIssues(ValidationIssue.Unknown)); // Act var result = await Target.HandleAsync(_message); // Assert Assert.True(result); Assert.Equal(1, status.ValidatorIssues.Count); _validatorStateService.Verify(ss => ss.SaveStatusAsync(It.IsAny <ValidatorStatus>()), Times.Once); }
public async Task <INuGetValidationResponse> ValidateSymbolsAsync(SymbolsValidatorMessage message, CancellationToken token) { _logger.LogInformation("{ValidatorName} :Start ValidateSymbolsAsync. PackageId: {packageId} PackageNormalizedVersion: {packageNormalizedVersion}", ValidatorName.SymbolsValidator, message.PackageId, message.PackageNormalizedVersion); try { using (Stream snupkgstream = await _symbolFileService.DownloadSnupkgFileAsync(message.SnupkgUrl, token)) { if (!await _zipArchiveService.ValidateZipAsync(snupkgstream, message.SnupkgUrl, token)) { return(NuGetValidationResponse.FailedWithIssues(ValidationIssue.SymbolErrorCode_SnupkgContainsEntriesNotSafeForExtraction)); } try { using (Stream nupkgstream = await _symbolFileService.DownloadNupkgFileAsync(message.PackageId, message.PackageNormalizedVersion, token)) { var pdbs = _zipArchiveService.ReadFilesFromZipStream(snupkgstream, SymbolExtension); var pes = _zipArchiveService.ReadFilesFromZipStream(nupkgstream, PEExtensions); if (pdbs.Count == 0) { return(NuGetValidationResponse.FailedWithIssues(ValidationIssue.SymbolErrorCode_SnupkgDoesNotContainSymbols)); } using (_telemetryService.TrackSymbolValidationDurationEvent(message.PackageId, message.PackageNormalizedVersion, pdbs.Count)) { List <string> orphanSymbolFiles; if (!SymbolsHaveMatchingPEFiles(pdbs, pes, out orphanSymbolFiles)) { orphanSymbolFiles.ForEach((symbol) => { _telemetryService.TrackSymbolsAssemblyValidationResultEvent(message.PackageId, message.PackageNormalizedVersion, ValidationStatus.Failed, nameof(ValidationIssue.SymbolErrorCode_MatchingAssemblyNotFound), assemblyName: symbol); }); _telemetryService.TrackSymbolsValidationResultEvent(message.PackageId, message.PackageNormalizedVersion, ValidationStatus.Failed); return(NuGetValidationResponse.FailedWithIssues(ValidationIssue.SymbolErrorCode_MatchingAssemblyNotFound)); } var targetDirectory = Settings.GetWorkingDirectory(); try { _logger.LogInformation("Extracting symbols to {TargetDirectory}", targetDirectory); var symbolFiles = _zipArchiveService.ExtractFilesFromZipStream(snupkgstream, targetDirectory, SymbolExtension); _logger.LogInformation("Extracting dlls to {TargetDirectory}", targetDirectory); _zipArchiveService.ExtractFilesFromZipStream(nupkgstream, targetDirectory, PEExtensions, symbolFiles); var status = ValidateSymbolMatching(targetDirectory, message.PackageId, message.PackageNormalizedVersion); return(status); } finally { TryCleanWorkingDirectoryForSeconds(targetDirectory, message.PackageId, message.PackageNormalizedVersion, _cleanWorkingDirectoryTimeSpan); } } } } catch (FileNotFoundException) { _telemetryService.TrackPackageNotFoundEvent(message.PackageId, message.PackageNormalizedVersion); return(NuGetValidationResponse.Failed); } } } catch (InvalidOperationException) { _telemetryService.TrackSymbolsPackageNotFoundEvent(message.PackageId, message.PackageNormalizedVersion); return(NuGetValidationResponse.Failed); } }
private bool IsChecksumMatch(string peFilePath, string packageId, string packageNormalizedVersion, out INuGetValidationResponse validationResponse) { validationResponse = NuGetValidationResponse.Succeeded; using (var peStream = File.OpenRead(peFilePath)) using (var peReader = new PEReader(peStream)) { // This checks if portable PDB is associated with the PE file and opens it for reading. // It also validates that it matches the PE file. // It does not validate that the checksum matches, so we need to do that in the following block. if (peReader.TryOpenAssociatedPortablePdb(peFilePath, File.OpenRead, out var pdbReaderProvider, out var pdbPath) && // No need to validate embedded PDB (pdbPath == null for embedded) pdbPath != null) { // Get all checksum entries. There can be more than one. At least one must match the PDB. var checksumRecords = peReader.ReadDebugDirectory().Where(entry => entry.Type == DebugDirectoryEntryType.PdbChecksum) .Select(e => peReader.ReadPdbChecksumDebugDirectoryData(e)) .ToArray(); if (checksumRecords.Length == 0) { _telemetryService.TrackSymbolsAssemblyValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Failed, nameof(ValidationIssue.SymbolErrorCode_ChecksumDoesNotMatch), assemblyName: Path.GetFileName(peFilePath)); validationResponse = NuGetValidationResponse.FailedWithIssues(ValidationIssue.SymbolErrorCode_ChecksumDoesNotMatch); return(false); } var pdbBytes = File.ReadAllBytes(pdbPath); var hashes = new Dictionary <string, byte[]>(); using (pdbReaderProvider) { var pdbReader = pdbReaderProvider.GetMetadataReader(); int idOffset = pdbReader.DebugMetadataHeader.IdStartOffset; foreach (var checksumRecord in checksumRecords) { if (!hashes.TryGetValue(checksumRecord.AlgorithmName, out var hash)) { HashAlgorithmName han = new HashAlgorithmName(checksumRecord.AlgorithmName); using (var hashAlg = IncrementalHash.CreateHash(han)) { hashAlg.AppendData(pdbBytes, 0, idOffset); hashAlg.AppendData(new byte[20]); int offset = idOffset + 20; int count = pdbBytes.Length - offset; hashAlg.AppendData(pdbBytes, offset, count); hash = hashAlg.GetHashAndReset(); } hashes.Add(checksumRecord.AlgorithmName, hash); } if (checksumRecord.Checksum.ToArray().SequenceEqual(hash)) { // found the right checksum _telemetryService.TrackSymbolsAssemblyValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Succeeded, issue: "", assemblyName: Path.GetFileName(peFilePath)); return(true); } } // Not found any checksum record that matches the PDB. _telemetryService.TrackSymbolsAssemblyValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Failed, nameof(ValidationIssue.SymbolErrorCode_ChecksumDoesNotMatch), assemblyName: Path.GetFileName(peFilePath)); validationResponse = NuGetValidationResponse.FailedWithIssues(ValidationIssue.SymbolErrorCode_ChecksumDoesNotMatch); return(false); } } } _telemetryService.TrackSymbolsAssemblyValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Failed, nameof(ValidationIssue.SymbolErrorCode_MatchingAssemblyNotFound), assemblyName: Path.GetFileName(peFilePath)); validationResponse = NuGetValidationResponse.FailedWithIssues(ValidationIssue.SymbolErrorCode_MatchingAssemblyNotFound); return(false); }