Post

Rustifying Image Codecs in Chromium

Rustifying Image Codecs in Chromium
A cross-team effort to make browser image decoding safer without slowing the web down.

Introduction

Image decoding is on the hot path in every modern browser. Page loads, <img> tags, and favicons all require parsing bytes straight from the untrusted web. Media codecs have been a steady source of security bugs, especially in large, highly optimized C++ codebases where memory safety is difficult to guarantee. Recent examples include libtheora (CVE-2024-56431) and AV1 (CVE-2024-0444).

Over the last few years, Chromium has been steadily shifting critical components toward memory-safe languages as part of its broader security strategy. One of the earliest successes of this approach was the Rust-based PNG decoder (“Rust PNG”), which became an important proof point for introducing memory-safe image decoding into Chromium.

Microsoft joined this initiative after Rust PNG established the first production proof point. Our contribution was turning that single-codec success into a repeatable rustification model: extracting common integration pieces, closing upstream crate gaps, and applying the approach to additional attacker-facing formats such as BMP and ICO. That shifted the work from proving one Rust codec could ship to accelerating how quickly a family of image decoders can move onto a safer foundation.

Why image codecs are a good fit for Rust

Codecs sit at a particularly sensitive boundary: they parse complex binary formats, and their inputs are fully attacker-controlled. In browser security reports, image decoders repeatedly appear as a source of memory-corruption vulnerabilities.

At the same time, codec boundaries tend to be relatively clean: stable APIs, well-scoped responsibilities, and extensive existing test and fuzz coverage. This makes them good candidates for incremental replacement, where Rust implementations can be introduced behind existing interfaces without destabilizing the rest of the system.

Our guiding approach

We framed the work around a security-first question: where can memory safety reduce meaningful browser risk without disrupting performance, compatibility, or Chromium’s existing architecture?

  • Focus on codec boundaries where inputs are attacker-controlled, responsibilities are well scoped, and memory safety can directly reduce risk.
  • Treat Rust as the implementation strategy for those high-value boundaries, not the goal in itself: the headline is safer image decoding.
  • Integrate through the Skia layer so Chromium-based browsers can share the same implementation behind a stable abstraction.
  • Prefer existing Rust crates over rewriting everything from scratch, contributing upstream when needed and sharing responsibility for dependencies.
  • Measure, don’t guess: rely on experiments and field trials to validate security, performance, stability, and compatibility before broader rollout.

This translates into a repeatable decision flow:

A diagram showing the repeatable decision flow

Clarifying scope and contributions

The contribution map below distinguishes the original Rust PNG work from Microsoft’s later role in accelerating the broader rustification effort:

  • Rust PNG: The initial Rust PNG decoder was developed, experimented with, and shipped before Microsoft joined the effort. Microsoft did not contribute to the original Rust PNG implementation.
  • PNG follow-up work: After joining, we contributed architectural refactoring and integration improvements that turned the Rust PNG path into reusable infrastructure rather than a one-off integration. This included extracting common build and FFI patterns, separating codec-specific logic from shared infrastructure, and lowering the engineering cost for subsequent codecs to adopt the same model.
  • Rust BMP: Our next major contribution was defining and implementing the Rust-based BMP decoder using BmpDecoder from the image crate. BMP became the turning point where the approach stopped being a PNG-specific success and became a repeatable production pattern. The work required upstream contributions to support Chromium’s incremental/resumable decoding model on partially received streams, validated the shared rustification framework in a second production codec, and moved from implementation start to shipping readiness in months rather than becoming another multi-year one-off effort. Rust BMP is already enabled in your Chromium-based browser.
  • Rust ICO and JPEG: Microsoft’s next codec contributions are focused on ICO and JPEG. At the time of writing, both implementations are work in progress, but they are already benefiting from the shared architecture, validation approach, and upstream collaboration pattern established through PNG and BMP.
  • Codec requirements for Chromium: Enabling a Rust-based codec requires feature parity with the existing decoder and support for incremental/resumable decoding, so the browser can begin decoding before the full image stream has arrived and continue as more data becomes available.
  • Revisiting legacy implementations: Rustification also gave us a forcing function to revisit old compatibility assumptions. One example was support for embedded JPEG/PNG images inside BMP files, which Chromium had kept for an obsolete Windows printing scenario where printers could decode those formats directly in hardware. Keeping that path alive added attack surface without meaningful modern value, so we removed it from Chromium.

Rust crates used in the implementation

For PNG, the work builds on the existing Rust image-png crate from the image-rs/image-png project.

For BMP and ICO, we rely on the image crate image-rs/image project, also from the image-rs project, which provides a clean, idiomatic Rust abstraction over multiple image formats and naturally models ICO as a container format.

For color management across codecs, we use the moxcms crate awxkee/moxcms project, which allows safe handling of ICC profiles without introducing large unsafe dependencies.

Finally, for JPEG we will rely on zune-jpeg etemesi254/zune-image project for decoding and jpeg-encoder vstroebel/jpeg-encoder project for encoding.

From experimentation to shipping

Rust PNG was a critical proof point for introducing memory-safe image decoding into Chromium without regressing performance or compatibility. Beyond demonstrating functional correctness, the Rust implementation delivered measurable improvements in both performance characteristics and reliability signals during experimentation and early production rollout.

The bigger impact of the Microsoft-led BMP work was momentum. Rust PNG proved that a memory-safe image decoder could meet Chromium’s bar, but it also required a long path of implementation, integration, experimentation, and rollout. For BMP, we used that learning to define a repeatable rustification framework: shared Rust integration infrastructure, reusable Skia/Chromium glue, upstream crate collaboration, feature-parity validation, fuzzing and test alignment, and a field-trial-driven rollout path. As a result, BMP moved from implementation start to ship phase in roughly six months, demonstrating that the approach can convert a successful prototype pattern into a scalable migration engine for additional codecs.

The performance punchline is not that Rust is automatically faster than C++. The gains came from using rustification to revisit old assumptions in legacy decoders and adopt newer, cleaner codec implementations from the Rust ecosystem. In PNG and BMP, the Rust paths benefited from simpler decode pipelines, less accumulated legacy special-case behavior, and modern crate-level optimizations while still integrating through Chromium’s performance-critical abstractions. The result was better hot-path behavior: in some scenarios, the safer implementation was not merely performance-neutral, but faster.

The reliability punchline is direct: Rust reduces the spray from memory corruption. In a C++ decoder, one memory-safety issue can surface as many small, noisy crash buckets because corrupted memory may fail later, in different places, or under different timing. With Rust, the decoder is much less likely to chase invalid pointers or continue after memory has been corrupted, so corruption paths are prevented rather than redistributed across telemetry. That gives us a cleaner reliability signal: fewer diffuse crash signatures, fewer unknown corruption-style failures, and more confidence that remaining issues are ordinary logic or compatibility bugs rather than latent memory-safety defects.

A diagram showing the performance improvement One visible example was the Blink performance web test for decode-png-palette.html, which showed execution time reduction after RustyPNG enablement.

The results were not anecdotal: they were validated through controlled experiments, performance benchmarks, production telemetry, and crash analysis. Field trials on Rust BMP showed a reduction in average image decoding time in the range from 20% to 50%, depending on platform. For reliability, the useful signal is not only the raw crash rate, but the shape of the crash data: memory-corruption-heavy components tend to produce scattered buckets, while the Rust transition should reduce that spray by preventing corruption paths in the decoder.

The reusable playbook is the core contribution: each new codec no longer starts from a blank page. Chromium now has a practical model for evaluating candidates, adapting upstream Rust crates to browser streaming requirements, preserving compatibility, measuring performance and stability through experiments, and retiring obsolete attack surface where legacy behavior no longer justifies the risk. The work is valuable beyond BMP because it increases Chromium’s capacity to move more image-decoding attack surface into memory-safe code with less bespoke engineering.

For ICO and JPEG, the next milestone follows the same path: experimentation via field trials to gather equivalent performance, stability, and compatibility signals. These results will inform rollout strategy and any targeted optimizations before broader enablement.

Lessons learned

One consistent lesson across this effort is that rustification is as much about collaboration as it is about language choice. Early engagement with maintainers of open source projects, shared experiments, and incremental integration were key to building confidence, both technically and organizationally. The reusable methodology mattered because it converted individual engineering wins into a program model: each codec can now reuse prior integration work, validation strategy, and rollout evidence instead of restarting the argument from scratch.

Next steps

The next phase is to continue applying the rustification playbook across selected high-value image codecs in Chromium, prioritizing formats where memory safety can reduce meaningful browser risk without compromising compatibility, performance, or maintainability.

Image CodecStatus
PNGShipped
BMPShipped
ICOIn progress
JPEGIn progress
WebPTo do
TIFFTo do
JPEG 2000To do
SVGTo do

Note: JPEG XL is also supported by Chromium, but its initial implementation was already in Rust; GIF is already implemented in a memory-safe language (Wuffs).

The main takeaway is that this work moved Rust image decoding from an isolated proof point to a scalable migration model. PNG proved the path, BMP demonstrated that the path could accelerate, and the shared playbook now gives Chromium a practical way to retire more memory-unsafe image-decoding surface with measured performance, compatibility, and reliability confidence.

Acknowledgements

This work builds on collaboration across the Rust image ecosystem and multiple organizations. Special thanks to maintainers and community from image, image-png, moxcms, zune-jpeg, and jpeg-encoder crates; and our collaborators in the Chromium and Skia teams at Google, as well as Microsoft’s Rust and browser security teams.

This post is licensed under CC BY 4.0 by the author.