undf: assign UNDF numbers, stamp patches; 878 total

This commit is contained in:
russell@unturf.com 2026-03-30 16:46:38 -04:00
parent 8b5e3c35b1
commit 494d1c82a3
6 changed files with 216 additions and 1 deletions

View file

@ -873,5 +873,8 @@
"suitecrm-0002": "UNDF-2026-000000872",
"suitecrm-0003": "UNDF-2026-000000873",
"vllm-0001": "UNDF-2026-000000874",
"suricata-0001-0001": "UNDF-2026-000000875"
"suricata-0001-0001": "UNDF-2026-000000875",
"redmine-0001": "UNDF-2026-000000876",
"redmine-0002": "UNDF-2026-000000877",
"redmine-0003": "UNDF-2026-000000878"
}

View file

@ -1,3 +1,4 @@
# UNDF: UNDF-2026-000000876
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -1332,15 +1332,16 @@

View file

@ -1,3 +1,4 @@
# UNDF: UNDF-2026-000000877
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -1352,7 +1352,7 @@

View file

@ -0,0 +1,18 @@
# UNDF: UNDF-2026-000000878
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -700,9 +700,10 @@
members = Member.joins(:project, :member_roles).
where("#{Project.table_name}.status <> 9").
where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Member.table_name}.user_id = ?)", self.id, true, group_id).
pluck(:user_id, :role_id, :project_id)
+ project_ids_set = project_ids.to_set # O(1) lookup instead of O(P) Array#include?
hash = {}
members.each do |user_id, role_id, project_id|
# Ignore the roles of the builtin group if the user is a member of the project
- next if user_id != id && project_ids.include?(project_id)
+ next if user_id != id && project_ids_set.include?(project_id)
hash[role_id] ||= []
hash[role_id] << project_id

Binary file not shown.

View file

@ -0,0 +1,192 @@
import java.util.*;
/**
* CWE-407 unit tests for Redmine defects.
*
* redmine-0001: issue.rb blocks?() BFS uses Array for 'all' visited set O(V^2) subtraction
* redmine-0002: issue.rb would_reschedule?() same BFS Array pattern O(V^2)
* redmine-0003: user.rb project_ids_by_role project_ids.include? in members.each O(M*P)
*/
public class RedmineCWE407Test {
// ---- redmine-0001: blocks? BFS with Array visited set ----
/** Unpatched: Array 'all' grows, Array subtraction O(C*A) per BFS layer */
static boolean blocksBfsUnpatched(int startId, int targetId, Map<Integer, List<Integer>> graph) {
List<Integer> all = new ArrayList<>();
all.add(startId);
List<Integer> last = new ArrayList<>();
last.add(startId);
while (!last.isEmpty()) {
Set<Integer> currentSet = new LinkedHashSet<>();
for (int node : last) {
List<Integer> neighbors = graph.getOrDefault(node, Collections.emptyList());
currentSet.addAll(neighbors);
}
List<Integer> current = new ArrayList<>(currentSet);
// Array subtraction: current -= last, current -= all (both O(N) per element)
current.removeAll(last);
Iterator<Integer> it = current.iterator();
while (it.hasNext()) {
if (all.contains(it.next())) { // O(A) per element
it.remove();
}
}
if (current.contains(targetId)) return true;
last = current;
all.addAll(last);
}
return false;
}
/** Patched: Set 'all' for O(1) membership — O(V+E) total */
static boolean blocksBfsPatched(int startId, int targetId, Map<Integer, List<Integer>> graph) {
Set<Integer> all = new HashSet<>();
all.add(startId);
List<Integer> last = new ArrayList<>();
last.add(startId);
while (!last.isEmpty()) {
Set<Integer> currentSet = new LinkedHashSet<>();
for (int node : last) {
List<Integer> neighbors = graph.getOrDefault(node, Collections.emptyList());
currentSet.addAll(neighbors);
}
List<Integer> current = new ArrayList<>(currentSet);
// Set-based removal: O(1) per element
current.removeIf(all::contains);
if (current.contains(targetId)) return true;
last = current;
all.addAll(last);
}
return false;
}
// ---- redmine-0003: user project_ids membership ----
/** Unpatched: project_ids.include? in members.each — O(M*P) */
static Map<Integer, List<Integer>> projectIdsByRoleUnpatched(
int userId, List<int[]> members, List<Integer> projectIds) {
Map<Integer, List<Integer>> hash = new HashMap<>();
for (int[] member : members) {
int memberUserId = member[0];
int roleId = member[1];
int projectId = member[2];
// Skip builtin group roles if user is a member of this project
if (memberUserId != userId && projectIds.contains(projectId)) { // O(P) per member
continue;
}
hash.computeIfAbsent(roleId, k -> new ArrayList<>()).add(projectId);
}
return hash;
}
/** Patched: project_ids.to_set for O(1) lookup — O(M+P) */
static Map<Integer, List<Integer>> projectIdsByRolePatched(
int userId, List<int[]> members, List<Integer> projectIds) {
Set<Integer> projectIdsSet = new HashSet<>(projectIds); // O(P) once
Map<Integer, List<Integer>> hash = new HashMap<>();
for (int[] member : members) {
int memberUserId = member[0];
int roleId = member[1];
int projectId = member[2];
if (memberUserId != userId && projectIdsSet.contains(projectId)) { // O(1) per member
continue;
}
hash.computeIfAbsent(roleId, k -> new ArrayList<>()).add(projectId);
}
return hash;
}
// ---- Test harness ----
static void testRedmine0001and0002() {
System.out.println("=== redmine-0001/0002: issue BFS blocks?/would_reschedule? ===");
// Build a chain graph: 0 -> 1 -> 2 -> ... -> V-1
int V = 20000;
Map<Integer, List<Integer>> graph = new HashMap<>();
for (int i = 0; i < V - 1; i++) {
graph.put(i, List.of(i + 1));
}
int targetId = V - 1;
// Warmup
blocksBfsPatched(0, targetId, graph);
long t0 = System.nanoTime();
boolean r1 = blocksBfsUnpatched(0, targetId, graph);
long unpatched = System.nanoTime() - t0;
t0 = System.nanoTime();
boolean r2 = blocksBfsPatched(0, targetId, graph);
long patched = System.nanoTime() - t0;
assert r1 == r2 : "Result mismatch";
assert r1 == true : "Expected true";
double ratio = (double) unpatched / patched;
System.out.printf(" V=%d unpatched=%dms patched=%dms ratio=%.1fx%n",
V, unpatched / 1_000_000, patched / 1_000_000, ratio);
assert ratio > 3.0 : "Expected >3x ratio, got " + ratio;
System.out.println(" PASS (covers both blocks? and would_reschedule?)");
}
static void testRedmine0003() {
System.out.println("=== redmine-0003: user project_ids_by_role membership ===");
int M = 50000; // membership rows
int P = 5000; // user's projects
int userId = 1;
List<Integer> projectIds = new ArrayList<>(P);
for (int i = 0; i < P; i++) {
projectIds.add(i);
}
// Generate member rows: mix of user's own and builtin group entries
List<int[]> members = new ArrayList<>(M);
Random rng = new Random(42);
for (int i = 0; i < M; i++) {
int uid = rng.nextBoolean() ? userId : 999; // 50% user, 50% builtin group
int roleId = rng.nextInt(10);
int projectId = rng.nextInt(P * 2); // some in user's projects, some not
members.add(new int[]{uid, roleId, projectId});
}
// Warmup
projectIdsByRolePatched(userId, members, projectIds);
long t0 = System.nanoTime();
Map<Integer, List<Integer>> r1 = projectIdsByRoleUnpatched(userId, members, projectIds);
long unpatched = System.nanoTime() - t0;
t0 = System.nanoTime();
Map<Integer, List<Integer>> r2 = projectIdsByRolePatched(userId, members, projectIds);
long patched = System.nanoTime() - t0;
// Verify same results
int totalR1 = r1.values().stream().mapToInt(List::size).sum();
int totalR2 = r2.values().stream().mapToInt(List::size).sum();
assert totalR1 == totalR2 : "Result mismatch: " + totalR1 + " vs " + totalR2;
double ratio = (double) unpatched / patched;
System.out.printf(" M=%d P=%d unpatched=%dms patched=%dms ratio=%.1fx%n",
M, P, unpatched / 1_000_000, patched / 1_000_000, ratio);
assert ratio > 3.0 : "Expected >3x ratio, got " + ratio;
System.out.println(" PASS");
}
public static void main(String[] args) {
testRedmine0001and0002();
testRedmine0003();
System.out.println("\nAll 3 Redmine CWE-407 tests PASSED.");
}
}