# How fmtlib Is Fuzz-Tested for Security Using OSS-Fuzz

> Learn how fmtlib uses OSS-Fuzz to automatically fuzz-test for security. Detect memory corruption and undefined behavior in pull requests before they reach production.

- Repository: [Hello World Foundation/fmt](https://github.com/fmtlib/fmt)
- Tags: how-to-guide
- Published: 2026-09-04

---

**The fmt library protects against security regressions through automated OSS-Fuzz integration that builds and executes libFuzzer harnesses on every pull request, detecting memory corruption and undefined behavior before code reaches production.**

The [fmt](https://github.com/fmtlib/fmt) library is a modern C++ formatting library that provides a fast and safe alternative to printf and iostreams. Given its widespread use in performance-critical and security-sensitive applications, maintaining memory safety is paramount. This article examines how fmtlib is fuzz-tested for security using OSS-Fuzz through continuous integration pipelines and specialized fuzzing harnesses.

## Continuous Fuzzing via GitHub Actions

The fmt project integrates with OSS-Fuzz through a dedicated GitHub Actions workflow defined in [`.github/workflows/fuzz.yml`](https://github.com/fmtlib/fmt/blob/main/.github/workflows/fuzz.yml). This workflow triggers automatically on every pull request to ensure that proposed changes do not introduce memory safety vulnerabilities or crash conditions.

The pipeline utilizes Google's **cifuzz** (Continuous Integration Fuzzing) actions from the `google/oss-fuzz` repository. The configuration employs two primary actions:

- **`build_fuzzers`**: Compiles the fuzz targets using the OSS-Fuzz infrastructure, specifying the project name **`fmt`** and the C++ language.
- **`run_fuzzers`**: Executes the compiled fuzzers for a fixed duration of **300 seconds** (5 minutes) by default.

When the build succeeds but the fuzz run detects crashes, the workflow automatically uploads crash artifacts for analysis. This ensures that every commit is validated against real-world edge cases and malformed inputs before merging.

## Fuzz Target Implementation

The fuzzing logic resides in the `test/fuzzing` directory, with shared utilities defined in **[`test/fuzzing/fuzzer-common.h`](https://github.com/fmtlib/fmt/blob/main/test/fuzzing/fuzzer-common.h)**. These components transform raw fuzzer input into valid arguments for fmt's formatting API.

### The [`fuzzer-common.h`](https://github.com/fmtlib/fmt/blob/main/fuzzer-common.h) Utilities

The header provides several key abstractions for constructing test cases:

- **`data_to_string`**: A structure that converts raw bytes into a `fmt::string_view`, with behavior controlled by the `FMT_FUZZ_SEPARATE_ALLOCATION` preprocessor flag. When enabled, this allocates a separate `std::vector<char>` buffer to improve AddressSanitizer detection of out-of-bounds reads.
- **`assign_from_buf`**: A template function that safely copies `sizeof(Item)` bytes from the fuzzer input into a typed value using `std::memcpy`.
- **Fixed-size partitioning**: The `constexpr auto fixed_size = 16` constant ensures the fuzzer efficiently mutates data across different type interpretations.

```cpp
// From test/fuzzing/fuzzer-common.h
#define FMT_FUZZ_SEPARATE_ALLOCATION 1
constexpr auto fixed_size = 16;

template <class Item>
inline Item assign_from_buf(const std::uint8_t* data) {
  Item item{};
  std::memcpy(&item, data, sizeof(Item));
  return item;
}

struct data_to_string {
#if FMT_FUZZ_SEPARATE_ALLOCATION
  std::vector<char> buffer;
  data_to_string(const uint8_t* data, size_t size, bool add_terminator = false)
      : buffer(size + (add_terminator ? 1 : 0)) {
    if (size) std::memcpy(buffer.data(), data, size);
  }
  fmt::string_view get() const { return {buffer.data(), buffer.size()}; }
#else
  fmt::string_view sv;
  data_to_string(const uint8_t* data, size_t size, bool = false)
      : sv(reinterpret_cast<const char*>(data), size) {}
  fmt::string_view get() const { return sv; }
#endif
  const char* data() const { return get().data(); }
};

```

### Harness Execution Flow

Individual fuzz targets (such as [`fmt_fuzz.cpp`](https://github.com/fmtlib/fmt/blob/main/fmt_fuzz.cpp)) implement the `LLVMFuzzerTestOneInput` entry point required by libFuzzer. The harness interprets the first 16 bytes as a format string and subsequent bytes as arguments, invoking `fmt::format` or `fmt::vformat` within a try-catch block to isolate crashes from expected exceptions:

```cpp
#include "fuzzer-common.h"
#include <fmt/core.h>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  // Use the first fixed_size bytes as the format string
  const size_t fmt_len = std::min<size_t>(size, fixed_size);
  data_to_string fmt_str(data, fmt_len);
  
  // Remaining bytes interpreted as arguments
  const uint8_t *arg_data = data + fmt_len;
  int arg = assign_from_buf<int>(arg_data);
  
  // Execute formatting operation
  try {
    fmt::format(fmt_str.get(), arg);
  } catch (...) {
    // Expected exceptions do not indicate security issues
  }
  return 0;
}

```

## Security Detection Capabilities

By running these harnesses continuously on Google's infrastructure, fmt receives comprehensive security validation:

- **Memory-safety verification** via AddressSanitizer (detecting buffer overflows and use-after-free) and UndefinedBehaviorSanitizer (detecting integer overflow and invalid shifts).
- **Crash detection** for malformed format strings, invalid argument types, and edge-case handling paths that might trigger assertions or segmentation faults.
- **Regression protection** through automated OSS-Fuzz issue filing. When the fuzzer discovers a crash, it creates a detailed bug report linked to the specific commit and input data, ensuring developers can reproduce and fix vulnerabilities quickly.

## Summary

- **The fmt library uses [`.github/workflows/fuzz.yml`](https://github.com/fmtlib/fmt/blob/main/.github/workflows/fuzz.yml)** to trigger OSS-Fuzz builds on every pull request via Google's cifuzz actions.
- **Fuzzing harnesses reside in `test/fuzzing/`**, with [`fuzzer-common.h`](https://github.com/fmtlib/fmt/blob/main/fuzzer-common.h) providing utilities like `data_to_string` and `assign_from_buf` to safely convert raw bytes into format arguments.
- **The 300-second fuzzing duration** provides rapid feedback while catching memory safety issues through AddressSanitizer and UndefinedBehaviorSanitizer instrumentation.
- **Automated crash reporting** ensures that any regressions are immediately flagged and linked to the causing commit, maintaining fmt's security guarantees.

## Frequently Asked Questions

### How often does fmtlib run fuzz tests?

The fuzzing workflow executes automatically on every pull request through GitHub Actions. Additionally, OSS-Fuzz runs the targets continuously on Google's infrastructure, often for much longer periods than the 300-second CI limit, to discover deep bugs.

### What types of vulnerabilities can OSS-Fuzz detect in fmtlib?

The integration primarily detects **memory safety violations** (buffer overflows, out-of-bounds reads, heap corruption) through AddressSanitizer, and **undefined behavior** (integer overflow, null pointer dereference, alignment issues) through UndefinedBehaviorSanitizer. It also catches assertion failures and segmentation faults caused by malformed format strings.

### Can I run the fmtlib fuzzers locally?

Yes. Developers can build the fuzz targets locally using the same source files in `test/fuzzing/` by linking against libFuzzer and compiling with sanitizer flags (`-fsanitize=address,undefined`). The [`fuzzer-common.h`](https://github.com/fmtlib/fmt/blob/main/fuzzer-common.h) header is designed to work both in the OSS-Fuzz environment and standalone local builds.

### Where are the fuzzing configuration files located?

The CI configuration is in [`.github/workflows/fuzz.yml`](https://github.com/fmtlib/fmt/blob/main/.github/workflows/fuzz.yml), while the fuzz target implementations and shared utilities are in the `test/fuzzing/` directory, specifically [`test/fuzzing/fuzzer-common.h`](https://github.com/fmtlib/fmt/blob/main/test/fuzzing/fuzzer-common.h) and the various `*_fuzz.cpp` files.