Skip to content

bug: all uploaded objects are byte-for-byte identical — minio/pkg/v3/rng.ResetSize() always produces same output due to zero subxor keys #471

Description

@russfellows

Summary

Every object warp uploads during a benchmark is byte-for-byte identical to every other object of the same size. The root cause is a one-character typo in github.com/minio/pkg/v3/rng (the upstream library warp uses for data generation): sub-keys are derived from a zero-filled local variable instead of the struct field that was just populated with random bytes. Because ResetSize resets the stream offset to 0 and (incorrectly) reuses the same zero sub-keys, every reset produces the exact same byte stream.

Warp calls ResetSize once per object via randomSrc.Object() in pkg/generator/random.go.

Warp's own code is correct. The bug is entirely in the upstream minio/pkg/v3/rng library. This report is filed here because minio/pkg has no public issue tracker.


Root Cause (in minio/pkg/v3/rng/reader.go)

func (r *Reader) init() error {
        _, err := io.ReadFull(r.o.rng, r.buf[:])   // ✅ base buffer seeded correctly
        ...
    }
    // Always reset subkeys.
    var tmp [32]byte                                  // ❌ local variable, zero-initialized
    _, err := io.ReadFull(r.o.rng, r.tmp[:])         // ✅ fills r.tmp (struct field) correctly
    ...
    r.subxor[1] = binary.LittleEndian.Uint64(tmp[8:16])  // ❌ always 0
    r.subxor[2] = binary.LittleEndian.Uint64(tmp[16:24]) // ❌ always 0
    r.subxor[3] = binary.LittleEndian.Uint64(tmp[24:32]) // ❌ always 0
    r.offset = 0
    return nil
}

Random bytes are correctly read into r.tmp (struct field), but r.subxor is derived from a separate uninitialized local tmp — always zeros.

The fix is four characters: change tmp[ → r.tmp[ on lines 87–90.

r.subxor[0] = binary.LittleEndian.Uint64(r.tmp[:8])
r.subxor[1] = binary.LittleEndian.Uint64(r.tmp[8:16])
r.subxor[2] = binary.LittleEndian.Uint64(r.tmp[16:24])
r.subxor[3] = binary.LittleEndian.Uint64(r.tmp[24:32])

Reproduction

A minimal test that demonstrates the bug in minio/pkg v3.6.1:

func TestResetSizeProducesUniqueOutput(t *testing.T) {
    const size = 64 << 20 // 64 MiB
    r, _ := rng.NewReader(rng.WithSize(size))

    out1 := make([]byte, size)
    io.ReadFull(r, out1)

    r.ResetSize(size)
    out2 := make([]byte, size)
    io.ReadFull(r, out2)

    if bytes.Equal(out1, out2) {
        t.Fatal("ResetSize produced identical output: subxor keys are not being randomized")
    }
}

This test fails — out1 and out2 are byte-for-byte identical.


Impact Measurement

Generated two 10 GB files using the warp generator (one per run), then analyzed them:

Compression (zstd -3 and -5) — both files:
Data is correctly incompressible — the XOR scrambling looks uniform to any compressor.

Input Compressed Ratio
10,737,418,240 B 10,737,664,018 B 1.000023× (0% compression)

Block-level deduplication at 4 KB granularity:

Metric Result
Total 4 KB blocks 2,621,440
Unique blocks 16,384 (= exactly one 64 MiB write chunk)
Duplicate blocks 2,605,056
Dedup savings 99.375%

Chunk-level (64 MiB = one warp object):
Every chunk across 10 GB has the same MD5 hash — 160 objects, 1 unique content.

Practical consequence

Any storage system with block-level deduplication reduces a warp-generated 10 GB dataset to 64 MB on disk. Benchmarks run against such systems will report:

  • Artificially high write throughput (dedup hit on every block after the first object)
  • Artificially low storage consumption
  • Zero meaningful data for evaluating random-data performance

Environment

  • github.com/minio/pkg v3.6.1
  • github.com/minio/warp (current master, Apr 2026)
  • Go 1.26.1, linux/amd64
  • Intel Xeon Platinum 8280L @ 2.70GHz

Activity

  1. russfellows commented on Apr 2, 2026

    @russfellows
    Author

    Minio: This is kind of a big deal, and is a VERY simple fix. I cannot PR the fix because the code lives in a repo you no longer maintain. Can you all kindly review and fix? Happy to discuss as well.

  2. added a commit that references this issue on Apr 6, 2026
  3. russfellows commented on Apr 7, 2026

    @russfellows
    Author

    So, apparently MinIO doesn't look at Open issues ???

    No, apparently I didn't read.

  4. klauspost commented on Apr 7, 2026

    @klauspost
    Collaborator

    @russfellows Look above your post 🙄

  5. russfellows commented on Apr 7, 2026

    @russfellows
    Author

    Yeah! OK, my bad, I missed the comment that you had reviewed. I just wanted to make sure someone reviewed and either agreed, or decided it wasn't a problem.

    Thanks.

    Also, I just added another issue, and submitted a PR. It appears that the latest 1.4.0 version no longer outputs tsv formatted result files, but instead creates json format instead. These are much less efficient to parse as you all know, and your parser isn't happy with them either. The PR #474 just makes the default behavior the same as the old, that is create tsv format by default with a new option name to create json instead if desired.

  6. added a commit that references this issue on Apr 9, 2026
    da66ac5
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