My First Go Contribution
How a scary GIF bug report turned into documentation in the Go standard library, and what I learned shipping CL 774640.
Go is my favorite language. I have been writing it for years, but until now I had not really thought about contributing to the Go language itself. That felt like a different league.
I was going through some of the outstanding issues on the Go repo and found issue #79063. It looked like a good place to start.
TL;DR
A 34-byte GIF with a huge logical screen can make Go allocate gigabytes before decode fails. The Go team wanted better docs, not a behavior change. I shipped CL 774640 with warnings on Decode/DecodeConfig and a runnable DecodeConfig then validate then Decode example. My first contribution to the Go standard library.
The scary GIF
Reading through the thread, one report seemed pretty wild. Someone crafted a 34-byte GIF that claimed its logical screen was 65535 by 65535 pixels. When you called gif.Decode or gif.DecodeAll on it, Go would try to allocate a paletted buffer for width times height before it had enough pixel data to finish decoding.
On a 64-bit machine that multiplication is legal. No overflow panic. Just a 4 GiB allocation in about 2.5 ms, then an error like gif: not enough image data. The damage was already done.
Any service that decodes untrusted images with image.Decode (including apps that only blank-import _ "image/gif") could get OOM-killed from a tiny upload.
What the Go team actually wanted
I am not going to pretend I understood the full politics of the thread on day one. The issue title started as a security-style report. Then Alan Donovan (who knows this codebase better than I ever will) reframed it.
His point was simple. If you do not want to decode a huge image, do not decode it. The standard library already documents a safe pattern in the Security Considerations section of package image. Call image.DecodeConfig first, check width and height against your limits, then call Decode.
The issue became a documentation task. Help people discover that pattern before they learn the hard way from a malicious header.
The original reporter, jayantkamble10000, was super constructive. They suggested two concrete things:
- Warnings on
image.Decodeandgif.Decode(not only the package overview) that allocation can happen from header dimensions early. - A small runnable example showing
DecodeConfig→ validate →Decode.
The issue had the help wanted label, so I decided to give it a shot.
What I shipped
I opened golang/go#79221 and sent CL 774640 on Gerrit on May 6. No behavior change. Just docs and one example, which is exactly what maintainers asked for.
On image.Decode I documented that decoding may allocate memory proportional to width and height from the header before all pixel data is consumed, and pointed readers at DecodeConfig plus the security section.
On image.DecodeConfig I clarified that it reads headers only and does not allocate a full pixel buffer, so you can reject oversized images cheaply.
In image/gif I added package-level guidance and matching notes on gif.Decode, gif.DecodeAll, and gif.DecodeConfig. DecodeAll deserved an extra sentence because it keeps every frame in memory, not just the first one.
The example I added is ExampleDecode_untrusted in src/image/decode_example_test.go. It decodes a tiny valid 1x1 GIF in two steps. First DecodeConfig, then reject if width times height exceeds a limit (using int64 so 32-bit platforms are safe), then Decode. That pattern is the whole story in code.
The merged commit is c203e4ecb94df984c337e49a7a626620e176811a.
Code review (my first time on Gerrit)
If you have only ever used GitHub PRs, Gerrit is a culture shock. Patch sets. Labels. TryBots.
I got review comments, fixed them, and uploaded patch set 2. What I did not know yet is that on Gerrit you are supposed to hit Reply when you are done addressing feedback. That tells the reviewer the change is ready for another look. I thought uploading the new patch set was enough.
A few days later I checked Gerrit again and saw that Alan Donovan had already replied on his side. I hit Reply on mine, things moved faster from there, TryBots ran on May 13, and the change merged on May 14 as commit c203e4e. About a week from first upload to landing on master.
Gerrit also left me a message I will remember:
Congratulations on opening your first change. Thank you for your contribution!
Alan Donovan and Dmitri Shuralyov reviewed the change. The issue is tied to the Go 1.27 milestone now.
After merge, jayantkamble came back on the issue thread and said the CL covered both of their earlier suggestions, including the runnable example. That felt really good.
What's next
Go is a stable language and while this isn't an impressive change by any means, it is still cool to say I have contributed to the Go language. I contributed to a language co-authored by Ken Thompson.
This is not the end for me with Go. I will definitely be doing more of this, hopfully in bigger areas like the standard library or the runtime.
Thanks for reading.
