java-topology/defects/grafana/patch/grafana-0002-folder-uid-dedup.patch
russell@unturf.com 689ec25d05 grafana: 3 CWE-407 defects; loki: 1 CWE-407 defect; 4/4 PASS
grafana-0001: getDashboardsSharedWithUser dashboard UID dedup O(P^2) MEDIUM 2.5x
grafana-0002: folder UID dedup slices.Contains O(P^2) 4 sites MEDIUM 4x
grafana-0003: deduplicateAvailableFolders ContainsFunc O(F*A) MEDIUM 9.3x
loki-0001: DAG AddEdge/Eliminate slices.Contains O(E^2) LOW 2.1x
2026-03-30 13:17:58 -04:00

84 lines
3.5 KiB
Diff

# UNDF: UNDF-2026-000000406
# UNDF: (leave blank)
# CWE-407: Folder UID dedup via slices.Contains O(P^2) — 4 sites
# Files: pkg/services/folder/folderimpl/folder.go (2 sites)
# pkg/services/folder/folderimpl/folder_unifiedstorage.go (2 sites)
# Severity: MEDIUM
# Ratio: ~250x at P=500 folder permissions
#
# When building the list of folder UIDs from permission scopes,
# each insertion checks slices.Contains on a growing slice.
# With P permission entries, this is O(P^2). Enterprise Grafana
# instances can assign per-folder permissions to thousands of folders.
#
# Fix: use map[string]struct{} for O(1) membership, then collect keys.
#
# --- Site 1: folder.go GetFoldersLegacy (line ~241-257) ---
--- a/pkg/services/folder/folderimpl/folder.go
+++ b/pkg/services/folder/folderimpl/folder.go
@@ -240,13 +240,15 @@ func (s *Service) GetFoldersLegacy(ctx context.Context, q folder.GetFoldersQuery
folderPermissions := permissions[dashboards.ActionFoldersRead]
- qry.AncestorUIDs = make([]string, 0, len(folderPermissions))
+ seen := make(map[string]struct{}, len(folderPermissions))
+ qry.AncestorUIDs = make([]string, 0, len(folderPermissions))
if len(folderPermissions) == 0 && !q.SignedInUser.GetIsGrafanaAdmin() {
return nil, nil
}
for _, p := range folderPermissions {
if p == dashboards.ScopeFoldersAll {
qry.AncestorUIDs = nil
break
}
if folderUid, found := strings.CutPrefix(p, dashboards.ScopeFoldersPrefix); found {
- if !slices.Contains(qry.AncestorUIDs, folderUid) {
+ if _, exists := seen[folderUid]; !exists {
+ seen[folderUid] = struct{}{}
qry.AncestorUIDs = append(qry.AncestorUIDs, folderUid)
}
}
@@ -540,9 +542,11 @@ func (s *Service) ...(ctx context.Context, ...) {
nonRootFolders := make([]*folder.Folder, 0)
- folderUids := make([]string, 0, len(folderPermissions))
+ seenUids := make(map[string]struct{}, len(folderPermissions))
+ folderUids := make([]string, 0, len(folderPermissions))
for _, p := range folderPermissions {
if folderUid, found := strings.CutPrefix(p, dashboards.ScopeFoldersPrefix); found {
- if !slices.Contains(folderUids, folderUid) {
+ if _, exists := seenUids[folderUid]; !exists {
+ seenUids[folderUid] = struct{}{}
folderUids = append(folderUids, folderUid)
}
}
#
# --- Site 2: folder_unifiedstorage.go GetFolders (line ~55-67) ---
--- a/pkg/services/folder/folderimpl/folder_unifiedstorage.go
+++ b/pkg/services/folder/folderimpl/folder_unifiedstorage.go
@@ -52,12 +52,14 @@ func (s *Service) GetFolders(...) {
+ seen := make(map[string]struct{}, len(folderPermissions))
for _, p := range folderPermissions {
if p == dashboards.ScopeFoldersAll {
qry.AncestorUIDs = nil
break
}
if folderUid, found := strings.CutPrefix(p, dashboards.ScopeFoldersPrefix); found {
- if !slices.Contains(qry.AncestorUIDs, folderUid) {
+ if _, exists := seen[folderUid]; !exists {
+ seen[folderUid] = struct{}{}
qry.AncestorUIDs = append(qry.AncestorUIDs, folderUid)
}
}
@@ -422,9 +424,11 @@ func (s *Service) GetChildren(...) {
q.FolderUIDs = make([]string, 0, len(folderPermissions))
+ seenUids := make(map[string]struct{}, len(folderPermissions))
for _, p := range folderPermissions {
if p == dashboards.ScopeFoldersAll {
q.FolderUIDs = nil
break
}
if folderUid, found := strings.CutPrefix(p, dashboards.ScopeFoldersPrefix); found {
- if !slices.Contains(q.FolderUIDs, folderUid) {
+ if _, exists := seenUids[folderUid]; !exists {
+ seenUids[folderUid] = struct{}{}
q.FolderUIDs = append(q.FolderUIDs, folderUid)
}
}