java-topology/defects/tomcat/patch/tomcat-0002-tribes-arrays-merge.md

3.4 KiB
Raw Blame History

tomcat-0002 — CWE-407: Arrays.merge() ArrayList.contains() O(M×N) in Tribes member deduplication

File: java/org/apache/catalina/tribes/util/Arrays.java Method: merge(Member[] m1, Member[] m2) Severity: MEDIUM CWE: CWE-407 (Inefficient Algorithmic Complexity)

Defect

Arrays.merge() builds a union of two Member[] arrays by adding elements of m2 into an ArrayList seeded with m1, guarding duplicates with list.contains(member). ArrayList.contains() is O(N) — it calls member.equals() against every element in the list. The loop runs |m2| times, giving total complexity O(|m1| × |m2|).

// DEFECTIVE — java/org/apache/catalina/tribes/util/Arrays.java:151-162
public static Member[] merge(Member[] m1, Member[] m2) {
    AbsoluteOrder.absoluteOrder(m1);
    AbsoluteOrder.absoluteOrder(m2);
    ArrayList<Member> list = new ArrayList<>(java.util.Arrays.asList(m1));
    for (Member member : m2) {
        if (!list.contains(member)) {   // O(N) scan per iteration → O(M×N) total
            list.add(member);
        }
    }
    Member[] result = list.toArray(new Member[0]);
    AbsoluteOrder.absoluteOrder(result);
    return result;
}

MemberImpl implements hashCode() (host byte sum) and equals() (host+port+uniqueId), so membership in a hash-based set is safe and correct.

Fix

Replace ArrayList with LinkedHashSet to make the dedup guard O(1). Insertion order is not required (the result is re-sorted by AbsoluteOrder.absoluteOrder before return), so LinkedHashSet is a safe drop-in.

// FIXED
public static Member[] merge(Member[] m1, Member[] m2) {
    AbsoluteOrder.absoluteOrder(m1);
    AbsoluteOrder.absoluteOrder(m2);
    LinkedHashSet<Member> set = new LinkedHashSet<>(java.util.Arrays.asList(m1));
    for (Member member : m2) {
        set.add(member);   // O(1) — LinkedHashSet.add() is idempotent
    }
    Member[] result = set.toArray(new Member[0]);
    AbsoluteOrder.absoluteOrder(result);
    return result;
}

Import to add: java.util.LinkedHashSet

Complexity

Before After
merge() O( m1

Impact

merge() is called in the Tribes clustering layer to union member lists during cluster topology updates. With N cluster nodes, repeated merges degrade to O(N²) equality checks. At N=200 nodes (large WildFly/EAP cluster) this produces a 200× overhead per topology update.

Patch

--- a/java/org/apache/catalina/tribes/util/Arrays.java
+++ b/java/org/apache/catalina/tribes/util/Arrays.java
@@ -19,6 +19,7 @@ package org.apache.catalina.tribes.util;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.StringTokenizer;

@@ -151,9 +152,8 @@ public class Arrays {
     public static Member[] merge(Member[] m1, Member[] m2) {
         AbsoluteOrder.absoluteOrder(m1);
         AbsoluteOrder.absoluteOrder(m2);
-        ArrayList<Member> list = new ArrayList<>(java.util.Arrays.asList(m1));
+        LinkedHashSet<Member> set = new LinkedHashSet<>(java.util.Arrays.asList(m1));
         for (Member member : m2) {
-            if (!list.contains(member)) {
-                list.add(member);
-            }
+            set.add(member);
         }
-        Member[] result = list.toArray(new Member[0]);
+        Member[] result = set.toArray(new Member[0]);
         AbsoluteOrder.absoluteOrder(result);
         return result;
     }