diff --git a/defects/traefik/patch/traefik-0001-ip-checker-map.patch b/defects/traefik/patch/traefik-0001-ip-checker-map.patch new file mode 100644 index 000000000..e611ad05f --- /dev/null +++ b/defects/traefik/patch/traefik-0001-ip-checker-map.patch @@ -0,0 +1,37 @@ +--- a/pkg/ip/checker.go ++++ b/pkg/ip/checker.go +@@ -11,8 +11,9 @@ import ( + // Checker allows to check that addresses are in a trusted IPs. + type Checker struct { +- authorizedIPs []*net.IP ++ authorizedIPs map[[16]byte]struct{} // O(1) exact-IP lookup; was O(T) slice scan + authorizedIPsNet []*net.IPNet + } + +@@ -23,8 +23,8 @@ func NewChecker(trustedIPs []string) (*Checker, error) { + } + +- checker := &Checker{} ++ checker := &Checker{authorizedIPs: make(map[[16]byte]struct{})} + + for _, ipMask := range trustedIPs { + if ipAddr := net.ParseIP(ipMask); ipAddr != nil { +- checker.authorizedIPs = append(checker.authorizedIPs, &ipAddr) ++ var key [16]byte ++ copy(key[:], ipAddr.To16()) ++ checker.authorizedIPs[key] = struct{}{} + continue + } + +@@ -78,11 +78,11 @@ func (ip *Checker) ContainsIP(addr net.IP) bool { +- for _, authorizedIP := range ip.authorizedIPs { +- if authorizedIP.Equal(addr) { +- return true +- } ++ var key [16]byte ++ copy(key[:], addr.To16()) ++ if _, ok := ip.authorizedIPs[key]; ok { /* O(1) map lookup; was O(T) slice scan */ ++ return true + } + + for _, authorizedNet := range ip.authorizedIPsNet { diff --git a/defects/traefik/patch/traefik-0002-cors-origin-map.patch b/defects/traefik/patch/traefik-0002-cors-origin-map.patch new file mode 100644 index 000000000..550b78dc9 --- /dev/null +++ b/defects/traefik/patch/traefik-0002-cors-origin-map.patch @@ -0,0 +1,40 @@ +--- a/pkg/middlewares/headers/header.go ++++ b/pkg/middlewares/headers/header.go +@@ -18,6 +18,7 @@ type Header struct { + next http.Handler + hasCustomHeaders bool + hasCorsHeaders bool + headers *dynamic.Headers ++ allowOriginSet map[string]struct{} // O(1) exact-origin lookup; built at init from AccessControlAllowOriginList + allowOriginRegexes []*regexp.Regexp + } + +@@ -40,6 +41,12 @@ func NewHeader(next http.Handler, cfg dynamic.Headers) (*Header, error) { ++ originSet := make(map[string]struct{}, len(cfg.AccessControlAllowOriginList)) ++ for _, o := range cfg.AccessControlAllowOriginList { ++ originSet[o] = struct{}{} ++ } ++ + return &Header{ + next: next, + headers: &cfg, + hasCustomHeaders: hasCustomHeaders, + hasCorsHeaders: hasCorsHeaders, ++ allowOriginSet: originSet, + allowOriginRegexes: regexes, + }, nil + } + +@@ -178,10 +185,11 @@ func (s *Header) isOriginAllowed(origin string) (bool, string) { +- for _, item := range s.headers.AccessControlAllowOriginList { +- if item == "*" || item == origin { +- return true, item +- } ++ if _, ok := s.allowOriginSet["*"]; ok { ++ return true, "*" ++ } ++ if _, ok := s.allowOriginSet[origin]; ok { /* O(1) map lookup; was O(O) slice scan */ ++ return true, origin + } + + for _, regex := range s.allowOriginRegexes { diff --git a/defects/traefik/unit/TraefikTest.java b/defects/traefik/unit/TraefikTest.java new file mode 100644 index 000000000..ef1667949 --- /dev/null +++ b/defects/traefik/unit/TraefikTest.java @@ -0,0 +1,119 @@ +import java.util.*; + +/** + * CWE-407 unit tests for Traefik defects. + * + * traefik-0001: pkg/ip/checker.go ContainsIP() + * authorizedIPs is []*net.IP — linear scan with Equal() per request. + * O(T) where T = trusted IP count. Hot path: every HTTP request through + * ipallowlist/ipwhitelist/forwardedheaders middleware. + * Fix: map[[16]byte]struct{} for O(1) exact-IP lookup. + * + * traefik-0002: pkg/middlewares/headers/header.go isOriginAllowed() + * AccessControlAllowOriginList is []string — linear scan per CORS request. + * O(O) where O = allowed origin count. Hot path: every CORS response. + * Fix: map[string]struct{} built at init time for O(1) lookup. + */ +public class TraefikTest { + + // --- traefik-0001 --- + + static boolean containsIP_slice(List authorizedIPs, String addr) { + for (String ip : authorizedIPs) { // O(T) — defect + if (ip.equals(addr)) return true; + } + return false; + } + + static boolean containsIP_map(Set authorizedMap, String addr) { + return authorizedMap.contains(addr); // O(1) — fix + } + + static void testTraefik0001() throws Exception { + int T = 500; // trusted IPs (Cloudflare + enterprise egress IPs) + List ipSlice = new ArrayList<>(T); + Set ipMap = new HashSet<>(T); + for (int i = 0; i < T; i++) { + String ip = "10." + (i / 256) + "." + (i % 256) + ".1"; + ipSlice.add(ip); + ipMap.add(ip); + } + + // target near end (worst case for linear scan) + String target = "10." + ((T - 1) / 256) + "." + ((T - 1) % 256) + ".1"; + String unknown = "1.2.3.4"; + + // correctness + assert containsIP_slice(ipSlice, target) == containsIP_map(ipMap, target); + assert !containsIP_slice(ipSlice, unknown) && !containsIP_map(ipMap, unknown); + + // performance: simulate R HTTP requests + int R = 200_000; + long t0 = System.nanoTime(); + for (int r = 0; r < R; r++) containsIP_slice(ipSlice, target); + long tSlice = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int r = 0; r < R; r++) containsIP_map(ipMap, target); + long tMap = System.nanoTime() - t0; + + double ratio = (double) tSlice / tMap; + System.out.printf("traefik-0001: slice=%.3fs map=%.3fs ratio=%.1f×%n", + tSlice / 1e9, tMap / 1e9, ratio); + assert ratio > 20 : "Expected >20× speedup, got " + ratio; + System.out.println("PASS traefik-0001"); + } + + // --- traefik-0002 --- + + static boolean isOriginAllowed_list(List allowList, String origin) { + for (String item : allowList) { // O(O) — defect + if (item.equals("*") || item.equals(origin)) return true; + } + return false; + } + + static boolean isOriginAllowed_map(Set allowSet, String origin) { + return allowSet.contains("*") || allowSet.contains(origin); // O(1) — fix + } + + static void testTraefik0002() throws Exception { + int O = 200; // allowed origins (multi-tenant SaaS: many partner domains) + List allowList = new ArrayList<>(O); + Set allowSet = new HashSet<>(O); + for (int i = 0; i < O; i++) { + String origin = "https://partner-" + i + ".example.com"; + allowList.add(origin); + allowSet.add(origin); + } + + // target near end (worst case) + String target = "https://partner-" + (O - 1) + ".example.com"; + String blocked = "https://evil.com"; + + // correctness + assert isOriginAllowed_list(allowList, target) == isOriginAllowed_map(allowSet, target); + assert !isOriginAllowed_list(allowList, blocked) && !isOriginAllowed_map(allowSet, blocked); + + int R = 200_000; + long t0 = System.nanoTime(); + for (int r = 0; r < R; r++) isOriginAllowed_list(allowList, target); + long tList = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int r = 0; r < R; r++) isOriginAllowed_map(allowSet, target); + long tMap = System.nanoTime() - t0; + + double ratio = (double) tList / tMap; + System.out.printf("traefik-0002: list=%.3fs map=%.3fs ratio=%.1f×%n", + tList / 1e9, tMap / 1e9, ratio); + assert ratio > 10 : "Expected >10× speedup, got " + ratio; + System.out.println("PASS traefik-0002"); + } + + public static void main(String[] args) throws Exception { + testTraefik0001(); + testTraefik0002(); + System.out.println("ALL PASS"); + } +}