# UNDF: UNDF-2026-000000850 # CWE-407: Algorithmic Complexity — deviceFolderFileDownloadState.blockIndexes linear scan # # File: lib/model/devicedownloadstate.go # Defect: blockIndexes stored as []int with slices.Contains() O(B) lookup # Called from blockAvailabilityFromTemporaryRLocked per device per block # Total complexity per file pull: O(D * B^2) where D=devices, B=blocks # Fix: Replace []int with map[int]struct{} for O(1) membership test # Severity: MEDIUM — large files (100MB+ with 128KB blocks = 800+ blocks) across # multiple devices trigger quadratic behavior in the sync hot path # Overhead: ~250x at B=500 blocks (realistic for 64MB file with 128KB block size) --- a/lib/model/devicedownloadstate.go +++ b/lib/model/devicedownloadstate.go @@ -7,14 +7,13 @@ package model import ( - "slices" "sync" "github.com/syncthing/syncthing/lib/protocol" ) // deviceFolderFileDownloadState holds current download state of a file that // a remote device has advertised. blockIndexes represents indexes within // FileInfo.Blocks that the remote device already has, and version represents // the version of the file that the remote device is downloading. type deviceFolderFileDownloadState struct { - blockIndexes []int + blockIndexes map[int]struct{} version protocol.Vector blockSize int } @@ -35,7 +34,8 @@ func (p *deviceFolderDownloadState) Has(file string, version protocol.Vector, in if !ok || !local.version.Equal(version) { return false } - return slices.Contains(local.blockIndexes, index) + _, found := local.blockIndexes[index] + return found } // Update updates internal state of what has been downloaded into the temporary @@ -50,22 +50,28 @@ func (p *deviceFolderDownloadState) Update(updates []protocol.FileDownloadProgre } else if update.UpdateType == protocol.FileDownloadProgressUpdateTypeAppend { switch { case !ok: + m := make(map[int]struct{}, len(update.BlockIndexes)) + for _, idx := range update.BlockIndexes { + m[idx] = struct{}{} + } local = deviceFolderFileDownloadState{ - blockIndexes: update.BlockIndexes, + blockIndexes: m, version: update.Version, blockSize: update.BlockSize, } case !local.version.Equal(update.Version): - local.blockIndexes = append(local.blockIndexes[:0], update.BlockIndexes...) + local.blockIndexes = make(map[int]struct{}, len(update.BlockIndexes)) + for _, idx := range update.BlockIndexes { + local.blockIndexes[idx] = struct{}{} + } local.version = update.Version local.blockSize = update.BlockSize default: - local.blockIndexes = append(local.blockIndexes, update.BlockIndexes...) + for _, idx := range update.BlockIndexes { + local.blockIndexes[idx] = struct{}{} + } } p.files[update.Name] = local } } } func (p *deviceFolderDownloadState) BytesDownloaded() int64 { p.mut.RLock() defer p.mut.RUnlock()