dotnet / dotnet/runtime

Official package SourceLink checksums require CR/LF normalization

Open
#129,023 6 comments 0 reactions 1 assignee Claimed by @steveisok View on GitHub
area-Infrastructure
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Description

Several official .NET package PDBs appear to contain source document checksums computed over CRLF-normalized source files, while SourceLink points at GitHub raw URLs that serve LF-normalized blobs.

This means strict SourceLink/PDB byte verification fails, but the same files verify after CR/LF normalization. This does **not** look like source-content drift; it looks like line-ending drift between official build inputs and the Git blob bytes addressed by SourceLink.

This was found with `dotnet-inspect` 0.9.0, which reports both strict content mismatches and CR/LF-normalized matches.

### Repro

Install/use dotnet-inspect 0.9.0 or newer:

```bash
dotnet tool install -g dotnet-inspect --version 0.9.0
```

Run SourceLink Integrity on a runtime-owned package:

```bash
dotnet-inspect library System.Text.Json.dll --package System.Text.Json -S "SourceLink Integrity"
```

Observed output shape:

```md
## SourceLink Integrity

| Field | Value |
| ----- | ----- |
| Mismatched | 0 |
| CR/LF Mismatch | 338 normalized |
| Status | Partial |
| Unverifiable | 2 |
| Verified | 338 |
```

A package with no unverifiable source documents shows the same line-ending pattern more cleanly:

```bash
dotnet-inspect library System.IO.Pipelines.dll --package System.IO.Pipelines -S "SourceLink Integrity"
```

```md
| Mismatched | 0 |
| CR/LF Mismatch | 30 normalized |
| Status | Verified |
| Unverifiable | 0 |
| Verified | 30 |
```

### Expected behavior

For SourceLink URLs pinned to immutable Git commits, the source bytes fetched from SourceLink should match the checksums recorded in the portable PDB without line-ending normalization.

### Actual behavior

For every sampled source document in the affected packages, strict hash comparison fails, but comparison succeeds after normalizing LF/CRLF. For example, for `System.Text.Json`, all 338 verified files required CR/LF normalization.

### Package sample

Representative packages tested with `dotnet-inspect 0.9.0`:

| Package | Source repo | Status | Verified | CR/LF normalized | Mismatched | Unverifiable |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| [System.Text.Json](https://www.nuget.org/packages/System.Text.Json) | runtime | Partial | 338 | 338 | 0 | 2 |
| [System.Text.Encodings.Web](https://www.nuget.org/packages/System.Text.Encodings.Web) | runtime | Partial | 27 | 27 | 0 | 2 |
| [System.IO.Pipelines](https://www.nuget.org/packages/System.IO.Pipelines) | runtime | Verified | 30 | 30 | 0 | 0 |
| [System.Collections.Immutable](https://www.nuget.org/packages/System.Collections.Immutable) | runtime | Verified | 130 | 130 | 0 | 0 |
| [System.Memory](https://www.nuget.org/packages/System.Memory) | runtime | Verified | 101 | 101 | 0 | 0 |
| [System.Buffers](https://www.nuget.org/packages/System.Buffers) | runtime | Verified | 6 | 6 | 0 | 0 |
| [System.Diagnostics.DiagnosticSource](https://www.nuget.org/packages/System.Diagnostics.DiagnosticSource) | runtime | Partial | 76 | 76 | 0 | 2 |
| [System.Formats.Asn1](https://www.nuget.org/packages/System.Formats.Asn1) | runtime | Partial | 42 | 42 | 0 | 2 |
| [Microsoft.Bcl.AsyncInterfaces](https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces) | runtime | Verified | 4 | 4 | 0 | 0 |
| [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection) | runtime | Verified | 42 | 42 | 0 | 0 |
| [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging) | runtime | Verified | 21 | 21 | 0 | 0 |
| [Microsoft.AspNetCore.OpenApi](https://www.nuget.org/packages/Microsoft.AspNetCore.OpenApi) | aspnetcore | Verified | 48 | 48 | 0 | 0 |
| [Microsoft.AspNetCore.Components](https://www.nuget.org/packages/Microsoft.AspNetCore.Components) | aspnetcore | Verified | 217 | 217 | 0 | 0 |
| [Microsoft.AspNetCore.Components.Web](https://www.nuget.org/packages/Microsoft.AspNetCore.Components.Web) | aspnetcore | Verified | 116 | 116 | 0 | 0 |
| [Microsoft.CodeAnalysis.CSharp](https://www.nuget.org/packages/Microsoft.CodeAnalysis.CSharp) | roslyn | Verified | 1149 | 1149 | 0 | 0 |
| [Microsoft.CodeAnalysis.Common](https://www.nuget.org/packages/Microsoft.CodeAnalysis.Common) | roslyn | Verified | 1042 | 1042 | 0 | 0 |
| [Microsoft.CodeAnalysis.Workspaces.Common](https://www.nuget.org/packages/Microsoft.CodeAnalysis.Workspaces.Common) | roslyn | Verified | 1567 | 1567 | 0 | 0 |
| [Microsoft.Extensions.AI.OpenAI](https://www.nuget.org/packages/Microsoft.Extensions.AI.OpenAI) | extensions | Verified | 24 | 24 | 0 | 0 |
| [Microsoft.Extensions.AI](https://www.nuget.org/packages/Microsoft.Extensions.AI) | extensions | Verified | 103 | 103 | 0 | 0 |
| [Microsoft.Extensions.Caching.Hybrid](https://www.nuget.org/packages/Microsoft.Extensions.Caching.Hybrid) | extensions | Verified | 30 | 30 | 0 | 0 |

This appears to affect multiple .NET repos, not just `dotnet/runtime` or `dotnet/extensions`.

### Control packages

This does not appear to be a dotnet-inspect artifact. Some packages verify with strict byte-for-byte SourceLink/PDB checksums without CR/LF normalization:

| Package | Group | Status | Verified | CR/LF normalized | Mismatched | Unverifiable |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| [Markout](https://www.nuget.org/packages/Markout) | third-party | Verified | 70 | 0 | 0 | 0 |
| [MarkdownTable.Formatting](https://www.nuget.org/packages/MarkdownTable.Formatting) | third-party | Verified | 22 | 0 | 0 | 0 |
| [NuGetFetch](https://www.nuget.org/packages/NuGetFetch) | third-party | Verified | 12 | 0 | 0 | 0 |

For comparison, `Newtonsoft.Json` also shows the CR/LF-normalized pattern (`240/240` verified after normalization), so the issue is not unique to .NET-owned packages. The .NET-owned sample above is included because it demonstrates the same pattern across `runtime`, `aspnetcore`, `roslyn`, and `extensions` packages.

### Why this likely happens

The repo settings generally normalize text into Git but do not force `*.cs` working-tree files to LF:

- `dotnet/runtime`: `*.cs text diff=csharp`, no `eol=lf`
- `dotnet/extensions`: `*.cs text=auto diff=csharp`, no `eol=lf`
- `dotnet/aspnetcore`: `* text=auto`, no `*.cs eol=lf`
- `dotnet/roslyn`: `*.cs diff=csharp text`, no `eol=lf`

If official builds run from a Windows checkout or another CRLF working tree, the compiler records CRLF-based source checksums in the PDB. SourceLink then points to GitHub raw URLs for commit-pinned blobs, which are served as LF bytes.

### Impact

Tools performing strict SourceLink/PDB source verification will report checksum mismatches for official .NET packages, even though the logical source content matches after line-ending normalization.

`dotnet-inspect 0.9.0` now reports this explicitly as `CR/LF Mismatch`, but ideally official package PDB checksums would match the exact SourceLink bytes without normalization.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.