traefik-0001/0002: ip.Checker []net.IP→map O(1); CORS origin []string→map O(1)
This commit is contained in:
parent
9f9acfd5f5
commit
76d1e3cc28
3 changed files with 196 additions and 0 deletions
37
defects/traefik/patch/traefik-0001-ip-checker-map.patch
Normal file
37
defects/traefik/patch/traefik-0001-ip-checker-map.patch
Normal file
|
|
@ -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 {
|
||||
40
defects/traefik/patch/traefik-0002-cors-origin-map.patch
Normal file
40
defects/traefik/patch/traefik-0002-cors-origin-map.patch
Normal file
|
|
@ -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 {
|
||||
119
defects/traefik/unit/TraefikTest.java
Normal file
119
defects/traefik/unit/TraefikTest.java
Normal file
|
|
@ -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<String> authorizedIPs, String addr) {
|
||||
for (String ip : authorizedIPs) { // O(T) — defect
|
||||
if (ip.equals(addr)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static boolean containsIP_map(Set<String> 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<String> ipSlice = new ArrayList<>(T);
|
||||
Set<String> 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<String> 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<String> 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<String> allowList = new ArrayList<>(O);
|
||||
Set<String> 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");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue