5.1 KiB
OpenTofu — CWE-407 Disclosure Brief
2026-03-27 · Patch available — awaiting upstream merge
Finding
Two O(N²) defects in OpenTofu's configuration parser and plan file snapshot reader. Both use slice-based membership tests inside loops over the same collection. Both patched. Speedups measured at 50×–250×.
The Defects
opentofu-0001 (PATCHED — HIGH): internal/configs/parser_config_dir.go:295
// filterTfPathsWithTofuAlternatives() — called during config directory parsing:
for _, p := range paths { // O(N) outer
parallelTofuPath := toTofuPath(p)
if slices.Contains(paths, parallelTofuPath) { // O(N) inner scan
// this path has a .tofu alternative — skip
}
}
// O(N²) total — N = number of .tf/.tofu files in config directory
filterTfPathsWithTofuAlternatives() calls slices.Contains(paths, parallelTofuPath) inside a range over paths. Both the outer loop and the inner slices.Contains scan are O(N) over the same slice. Total: O(N²). Fix: pre-build map[string]bool from paths before the loop. Measured ratio: 250×.
opentofu-0002 (PATCHED — HIGH): internal/plans/planfile/config_snapshot.go:135
// readConfigSnapshot() — plan file deserialization:
for k := range snap.Modules { // O(M) outer
found := false
for _, record := range manifest { // O(M) inner
if record.Key == k { // O(M²) total
found = true
break
}
}
if !found { return error }
}
// O(M²) — M = number of snapshot module entries
readConfigSnapshot() validates that every module key in snap.Modules exists in the manifest using a nested loop with string comparison. For M module entries: O(M²). Fix: pre-build map[string]bool from manifest keys before the outer loop. Measured ratio: 50×.
Complexity Proof
opentofu-0001: Let N = number of .tf and .tofu files in the configuration directory being parsed.
- Defective: for each of N paths,
slices.Containsscans all N paths → N² string comparisons. - Fixed:
pathSet := make(map[string]bool, len(paths))before loop; each lookup O(1). - Total work: O(N) map build + O(N) lookups = O(N) vs O(N²).
- At N=250 files (large modular config): defective=62,500 comparisons, fixed=250. 250× measured ratio.
opentofu-0002: Let M = number of module entries in the plan file config snapshot.
- Defective: for each of M module keys, scan all M manifest records → M² comparisons.
- Fixed:
manifestKeys := make(map[string]bool, len(manifest))before outer loop; each check O(1). - Total work: O(M) map build + O(M) lookups = O(M) vs O(M²).
- At M=50 modules: defective=2,500 comparisons, fixed=50. 50× measured ratio.
Impact
opentofu-0001 fires on every tofu init, tofu plan, and tofu apply invocation when the working directory contains both .tf and .tofu files — which is the expected migration state for any project transitioning from Terraform to OpenTofu. Large module directories with many .tf files hit worst case on every run.
opentofu-0002 fires on every tofu show and plan file deserialization operation. Plan files from large infrastructure configurations with many modules (common in monorepo patterns) hit worst case on every read.
OpenTofu is the open-source fork of Terraform adopted as the CNCF-hosted infrastructure-as-code standard following the HashiCorp license change.
The Fix
opentofu-0001: Pre-build a set from paths before the filter loop:
// Before
for _, p := range paths {
parallelTofuPath := toTofuPath(p)
if slices.Contains(paths, parallelTofuPath) { ... } // O(N) per iteration
}
// After
// CWE-407 fix: map[string]bool for O(1) membership test instead of O(N) slices.Contains.
pathSet := make(map[string]bool, len(paths))
for _, p := range paths {
pathSet[p] = true
}
for _, p := range paths {
parallelTofuPath := toTofuPath(p)
if pathSet[parallelTofuPath] { ... } // O(1)
}
opentofu-0002: Pre-build a set from manifest keys before the validation loop:
// Before
for k := range snap.Modules {
for _, record := range manifest {
if record.Key == k { found = true; break } // O(M) per module key
}
}
// After
// CWE-407 fix: map[string]bool for O(1) key validation instead of O(M) scan per key.
manifestKeys := make(map[string]bool, len(manifest))
for _, record := range manifest {
manifestKeys[record.Key] = true
}
for k := range snap.Modules {
if !manifestKeys[k] { return error } // O(1)
}
Patch
defects/opentofu/patch/opentofu-0001-0002-parser-snapshot-map.patch
What We Ask
- Confirm receipt and assign a GitHub Security Advisory or issue reference (opentofu/opentofu).
- Validate the patch against the config parser and planfile test suites.
- Assess CVE eligibility — opentofu-0001 measured at 250× and fires on every invocation with mixed
.tf/.tofudirectories. - Coordinate a disclosure date — we are targeting 90 days from first contact.
Contact: see cover email. This brief is confidential until coordinated disclosure.