Skip to content

.gitattributes: blanket * text eol=crlf will silently corrupt any unlisted binary on commit #1834

Description

@hiyochi

Summary

.gitattributes enables text conversion for everything, unconditionally:

* text eol=crlf

The binary list underneath it only names media and archive formats. Nothing currently in the repo is affected — I checked, and it's genuinely fine today (details below) — but any binary whose extension isn't on that list will be silently corrupted the moment it is committed, with no warning and no way to recover it from history afterwards. .exe, .dll, .nupkg, .pdb, .snk, .msi, .msix and .winmd are all unlisted.

Filing this because a downstream fork hit exactly that: xammen/BetterTrumpet inherited this file and now has 351 corrupted PE files in master — see xammen/BetterTrumpet#54 for the full autopsy.

Why nothing here is broken yet

Attribute lines are last-match-wins, so *.png binary and friends correctly override the blanket * text. Verified on .chocolatey/logo.png (blob 6d2acd8):

size 17913   PNG magic ok: True   IHDR CRC ok: True

That file is a good demonstration of the mechanism, actually — the PNG signature is 89 50 4e 47 0d 0a 1a 0a, so it contains a literal CRLF. It survives only because *.png is listed. The four binary types tracked here (51 .png, 3 .gif, 2 .ico, 1 .pfx) are all covered.

How the corruption works, for reference

With text conversion forced on, git add runs the clean filter (CRLF → LF) over the file. In a PE image that strips CR bytes throughout, including the one in the DOS stub, which ends mode.\r\r\n$ (0d 0d 0a 24). It becomes mode.\r\n$, one byte shorter, and the e_lfanew offset stored at 0x3C no longer points at the PE\0\0 signature:

healthy    00000070  6d 6f 64 65 2e 0d 0d 0a 24 ...   mode....$
corrupted  00000070  6d 6f 64 65 2e 0d 0a 24 00 ...   mode...$
                                    ^ stripped, everything after shifts by one

The damage lands in the object store, so it is permanent: there is no revision to restore from. Checkout then re-inflates the file in the other direction (LF → CRLF), which can also make the round trip lossy, leaving git status permanently dirty on a freshly cloned file.

Suggested fix

-[core]
-* text eol=crlf
+* text=auto eol=crlf
+
+*.exe binary
+*.dll binary
+*.nupkg binary
+*.msi binary
+*.pdb binary
+*.snk binary

text=auto lets git's own NUL-byte heuristic bail out on binary content instead of forcing conversion, and the explicit entries are a deterministic backstop. Text files are unaffected by the switch as long as they're already stored LF-normalized, which is the normal case.

Two smaller notes while you're in the file:

  • The leading [core] line is a no-op. .gitattributes has no INI sections, so git reads it as a pattern — a character class matching one of c, o, r, e — with no attributes attached. It looks like it drifted in from a .gitconfig.
  • *.woff is listed but *.woff2 isn't.

Happy to send a PR if you'd like it, but since nothing is actually broken here I didn't want to push an unsolicited change to a config file.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions