undf: assign 847-849; stamp freecad/retroarch patches

This commit is contained in:
russell@unturf.com 2026-03-30 14:53:28 -04:00
parent e06902c99a
commit 4cc47349a5
26 changed files with 931 additions and 1 deletions

View file

@ -0,0 +1,81 @@
# UNDF: UNDF-2026-000000850
# UNDF: (leave blank)
# 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()

Binary file not shown.

View file

@ -0,0 +1,140 @@
import java.util.*;
/**
* CWE-407 unit test for syncthing-0001: deviceFolderFileDownloadState.blockIndexes
* linear scan O(B) inside per-device per-block availability check.
*
* Defect: blockIndexes stored as ArrayList<Integer> (simulating Go []int) with
* contains() O(B) lookup called from blockAvailabilityFromTemporary
* per device per block. Total: O(D * B^2).
* Fix: Replace with HashSet<Integer> for O(1) membership test.
*
* File: lib/model/devicedownloadstate.go
*/
public class Syncthing0001Test {
// --- DEFECTIVE: []int with slices.Contains (linear scan) ---
static class DefectiveDownloadState {
private final List<Integer> blockIndexes = new ArrayList<>();
void addBlocks(List<Integer> indexes) {
blockIndexes.addAll(indexes);
}
boolean has(int index) {
return blockIndexes.contains(index); // O(B) linear scan
}
}
// --- FIXED: map[int]struct{} (hash set) ---
static class FixedDownloadState {
private final Set<Integer> blockIndexes = new HashSet<>();
void addBlocks(List<Integer> indexes) {
blockIndexes.addAll(indexes);
}
boolean has(int index) {
return blockIndexes.contains(index); // O(1) hash lookup
}
}
/**
* Simulate blockAvailabilityFromTemporaryRLocked: for each device,
* check if a block index exists in the download state.
* With B blocks and D devices, defective = O(D * B^2), fixed = O(D * B).
*/
static long benchmarkAvailability(int numBlocks, int numDevices, boolean useFixed) {
// Build block indexes (simulating progressive download)
List<Integer> indexes = new ArrayList<>(numBlocks);
for (int i = 0; i < numBlocks; i++) {
indexes.add(i);
}
// Create per-device download states
Object[] states;
if (useFixed) {
FixedDownloadState[] fs = new FixedDownloadState[numDevices];
for (int d = 0; d < numDevices; d++) {
fs[d] = new FixedDownloadState();
fs[d].addBlocks(indexes);
}
states = fs;
} else {
DefectiveDownloadState[] ds = new DefectiveDownloadState[numDevices];
for (int d = 0; d < numDevices; d++) {
ds[d] = new DefectiveDownloadState();
ds[d].addBlocks(indexes);
}
states = ds;
}
// Simulate: for each block we want to pull, check availability across all devices
long ops = 0;
long start = System.nanoTime();
for (int blockIdx = 0; blockIdx < numBlocks; blockIdx++) {
for (int d = 0; d < numDevices; d++) {
boolean found;
if (useFixed) {
found = ((FixedDownloadState) states[d]).has(blockIdx);
} else {
found = ((DefectiveDownloadState) states[d]).has(blockIdx);
}
if (found) ops++;
}
}
long elapsed = System.nanoTime() - start;
return elapsed;
}
public static void main(String[] args) {
int B = 1000; // blocks (realistic: 128MB file / 128KB block size)
int D = 5; // devices sharing the folder
// Warmup
for (int i = 0; i < 3; i++) {
benchmarkAvailability(B, D, false);
benchmarkAvailability(B, D, true);
}
// Measure
long defectiveNs = benchmarkAvailability(B, D, false);
long fixedNs = benchmarkAvailability(B, D, true);
double ratio = (double) defectiveNs / fixedNs;
System.out.printf("syncthing-0001: deviceDownloadState blockIndexes linear scan%n");
System.out.printf(" B=%d blocks, D=%d devices%n", B, D);
System.out.printf(" Defective (ArrayList.contains): %,d ns%n", defectiveNs);
System.out.printf(" Fixed (HashSet.contains): %,d ns%n", fixedNs);
System.out.printf(" Ratio: %.1fx%n", ratio);
// Correctness check
DefectiveDownloadState ds = new DefectiveDownloadState();
FixedDownloadState fs = new FixedDownloadState();
List<Integer> testIndexes = Arrays.asList(0, 5, 10, 15, 20);
ds.addBlocks(testIndexes);
fs.addBlocks(testIndexes);
boolean pass = true;
for (int idx : new int[]{0, 5, 10, 15, 20}) {
if (!ds.has(idx) || !fs.has(idx)) { pass = false; break; }
}
for (int idx : new int[]{1, 6, 11, 16, 21}) {
if (ds.has(idx) || fs.has(idx)) { pass = false; break; }
}
if (!pass) {
System.out.println("FAIL: correctness check failed");
System.exit(1);
}
if (ratio < 2.0) {
System.out.println("FAIL: expected ratio >= 2.0, got " + ratio);
System.exit(1);
}
System.out.println("PASS");
}
}