java-topology/whitepaper/outreach/minio.md
russell@unturf.com d1f82fd8e3 feat: add 8 outreach docs (26 defects) for batch 3
rpcs3 (4, C++), ppsspp (4, C++), spring-framework (3, Java),
nats-server (3, Go), minio (3, Go), gimp (3, C), cockroach (3, Go),
superset (3, Python). Note: rpcs3-0004 is CWE-312, rest are CWE-407.
2026-04-13 15:35:53 -04:00

4.8 KiB
Raw Blame History

MinIO — CWE-407 Disclosure Brief

2026-04-13 · Patches available — awaiting upstream merge

Finding

Three O(n²) defects in MinIO across disk healing, pool decommissioning, and site replication. All patched. Patches ready for upstream review. Two defects fire during bucket-level healing/decommission loops; one fires during cross-site replication heal comparisons.

The Defects

minio-0001 (PATCHED — MEDIUM): cmd/background-newdisks-heal-ops.go

// In healingTracker.isHealed() — fires per bucket during heal loop:
func (h *healingTracker) isHealed(bucket string) bool {
    h.mu.RLock()
    defer h.mu.RUnlock()
    return slices.Contains(h.HealedBuckets, bucket)  // O(H) linear scan
}

HealedBuckets grows as healing progresses. The heal loop iterates all buckets (B) and calls isHealed() for each, which scans HealedBuckets (H) with slices.Contains. As healing progresses, H grows toward B, making total cost O(B²). Ironically, setQueuedBuckets() already builds a set.CreateStringSet(HealedBuckets...) for the same data, but isHealed() does not use it.

minio-0002 (PATCHED — MEDIUM): cmd/erasure-server-pool-decom.go

// In isBucketDecommissioned() — fires per bucket during decommission:
func (pd *PoolDecommissionInfo) isBucketDecommissioned(bucket string) bool {
    return slices.Contains(pd.DecommissionedBuckets, bucket)  // O(D) linear scan
}

Same pattern as minio-0001 in the decommission path. decommissionInBackground() iterates pending buckets (P), each calling isBucketDecommissioned which scans DecommissionedBuckets (D). Total: O(P×D). As decommission progresses, D grows toward total buckets.

minio-0003 (PATCHED — MEDIUM): cmd/site-replication.go:5677,5698

// In isGroupDescEqual() / isUserInfoEqual() — fires per group/user during site-replication heal:
for _, v1 := range g1.Members {
    if slices.Contains(g2.Members, v1) {
        found = true
    }
    if !found {
        return false
    }
}

Both isGroupDescEqual() and isUserInfoEqual() compare two string slices for set equality by iterating one and calling slices.Contains on the other — O(M²) where M = members or groups. In LDAP-backed deployments, groups can have 1,000+ members, producing 1M+ operations per comparison. Called per-user and per-group across deployment sites during site replication healing.

Complexity Proof

minio-0001/minio-0002: At B=500 buckets:

  • Defective: 500 × (500 / 2) = ~125,000 comparisons
  • Fixed: 500 × 1 = 500 map lookups
  • 250× op reduction. At B=10,000 (large deployments): 5,000× op reduction.

minio-0003: At M=500 members per group:

  • Defective: 500 × 500 = 250,000 comparisons per group comparison
  • Fixed: 500 + 500 = 1,000 operations (map build + probe)
  • 250× op reduction.

Impact

MinIO powers object storage for thousands of organizations — from on-premise Kubernetes deployments to large-scale data lakes. minio-0001 fires during disk healing after a drive replacement or new node addition — a routine cluster maintenance operation. At 10,000+ buckets (common in multi-tenant deployments), the O(B²) scan produces visible healing delays. minio-0002 fires during pool decommissioning, another routine capacity management operation. minio-0003 fires during site replication healing across geographically distributed MinIO clusters — LDAP-backed deployments with large groups compound the quadratic cost per user and per group comparison.

The Fix

minio-0001: Add a healedSet map[string]struct{} field to healingTracker, maintained alongside HealedBuckets. Replace slices.Contains with map lookup.

minio-0002: Add a decomBucketSet map[string]struct{} field to PoolDecommissionInfo, maintained alongside DecommissionedBuckets. Lazy-rebuild the set on first call after deserialization.

minio-0003: Pre-build a map[string]struct{} from one slice, then probe with the other. Applied to both isGroupDescEqual() and isUserInfoEqual().

Patch

Fixes available:

  • defects/minio/patch/minio-0001-heal-tracker-isHealed-linear-scan.patch
  • defects/minio/patch/minio-0002-decom-isBucketDecommissioned-linear-scan.patch
  • defects/minio/patch/minio-0003-site-replication-set-equality-linear-scan.patch

Three patches across background-newdisks-heal-ops.go, erasure-server-pool-decom.go, and site-replication.go.

What We Ask

Patches ready for review.

  1. Confirm receipt and assign a GitHub issue reference (minio/minio).
  2. Assess severity — minio-0001 fires during disk healing; minio-0002 fires during pool decommission; minio-0003 fires during site replication heal.
  3. Coordinate a disclosure date — we target 90 days from first contact.
  4. We will credit the MinIO team in the public disclosure. Preferred acknowledgment format welcome.

Contact: see cover email. This brief is confidential until coordinated disclosure.