3.1 KiB
UNDF: UNDF-2026-000000569
vault-0001 — HashiCorp Vault: sanitizeAndUpsertGroup O(G²) group membership test
Severity: HIGH
CWE: CWE-407 (Algorithmic Complexity — Linear Membership Test in Loop)
File: vault/identity_store_util.go
Function: (*IdentityStore).sanitizeAndUpsertGroup
Defect
// identity_store_util.go:2317-2318
for _, currentMemberGroupID := range currentMemberGroupIDs { // O(G_current)
if strutil.StrListContains(memberGroupIDs, currentMemberGroupID) { // O(G_member)
continue
}
// ... remove from parent group
}
strutil.StrListContains performs a linear scan of the memberGroupIDs slice:
// sdk/helper/strutil/strutil.go
func StrListContains(haystack []string, needle string) bool {
for _, item := range haystack {
if item == needle { return true }
}
return false
}
When updating a group with G member groups, the outer loop runs G times and each iteration
scans the full memberGroupIDs slice (also up to G entries). Total: O(G²).
This function is called on every PUT /identity/group/:id request. Large Vault deployments
with enterprise customers frequently have groups with hundreds to thousands of members (LDAP
sync, external IdP groups). At G=1000 this is 1,000,000 string comparisons per update.
Fix
Pre-build a map[string]bool from memberGroupIDs before the loop for O(1) lookups:
// Build O(1) lookup set before the loop
memberGroupIDSet := make(map[string]bool, len(memberGroupIDs))
for _, id := range memberGroupIDs {
memberGroupIDSet[id] = true
}
for _, currentMemberGroupID := range currentMemberGroupIDs {
if memberGroupIDSet[currentMemberGroupID] { // O(1)
continue
}
// ... remove from parent group
}
Patch
--- a/vault/identity_store_util.go
+++ b/vault/identity_store_util.go
@@ -2301,6 +2301,12 @@ func (i *IdentityStore) sanitizeAndUpsertGroup(...) error {
memberGroupIDs = strutil.RemoveDuplicates(memberGroupIDs, false)
+ // CWE-407: pre-build a set for O(1) membership checks below.
+ memberGroupIDSet := make(map[string]bool, len(memberGroupIDs))
+ for _, id := range memberGroupIDs {
+ memberGroupIDSet[id] = true
+ }
+
// For those group member IDs that are removed from the list, remove current
// group ID as their respective ParentGroupID.
@@ -2317,7 +2323,7 @@ func (i *IdentityStore) sanitizeAndUpsertGroup(...) error {
for _, currentMemberGroupID := range currentMemberGroupIDs {
- if strutil.StrListContains(memberGroupIDs, currentMemberGroupID) {
+ if memberGroupIDSet[currentMemberGroupID] {
continue
}
Complexity
| G (member groups) | Before (comparisons) | After (comparisons) |
|---|---|---|
| 100 | 10,000 | 100 |
| 500 | 250,000 | 500 |
| 1,000 | 1,000,000 | 1,000 |
| 5,000 | 25,000,000 | 5,000 |
Reproduction
See defects/vault/unit/VaultGroupMembershipAlgorithm.java — ratio ≥ 5x at G=400 (72x at G=1000).