wave14a/c/d: 524/239 — rails-0018/grape-0001..3/django-0005..6/sqlalchemy-0003/go-stdlib-0001/vault-0001/tf-aws-0001
This commit is contained in:
parent
8f0bc73afa
commit
ac0e1e091e
20 changed files with 1948 additions and 21 deletions
40
defects/grape/patch/grape-0001-values-validator-set.patch
Normal file
40
defects/grape/patch/grape-0001-values-validator-set.patch
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
Fixes grape-0001: Grape::Validations::Validators::ValuesValidator#check_values? — values Array#include? inside param_array.all? O(P×V).
|
||||
|
||||
--- a/lib/grape/validations/validators/values_validator.rb
|
||||
+++ b/lib/grape/validations/validators/values_validator.rb
|
||||
|
||||
@@ DEFECT grape-0001: lines 36-39 check_values?
|
||||
|
||||
def check_values?(val, attr_name)
|
||||
values = @values.is_a?(Proc) && @values.arity.zero? ? @values.call : @values
|
||||
return true if values.nil?
|
||||
|
||||
param_array = val.nil? ? [nil] : Array.wrap(val)
|
||||
- return param_array.all? { |param| values.include?(param) } unless values.is_a?(Proc)
|
||||
+ unless values.is_a?(Proc) # FIX: build Set once for O(1) lookup
|
||||
+ values_set = values.is_a?(Set) ? values : values.to_set
|
||||
+ return param_array.all? { |param| values_set.include?(param) }
|
||||
+ end
|
||||
|
||||
begin
|
||||
param_array.all? { |param| values.call(param) }
|
||||
rescue StandardError => e
|
||||
warn "Error '#{e}' raised while validating attribute '#{attr_name}'"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# BEFORE: values is a plain Array (the allowlist defined in params do ... values: [...] end)
|
||||
# param_array.all? { |param| values.include?(param) }
|
||||
# For a multi-value param (e.g., tags[]=a&tags[]=b&...): P params × V values = O(P×V)
|
||||
# Every validated request incurs this cost.
|
||||
#
|
||||
# AFTER: values_set = values.to_set — O(V) once (or reuse if already a Set)
|
||||
# values_set.include? is O(1) per param
|
||||
# Total: O(P + V) per validation call
|
||||
#
|
||||
# Severity: HIGH — executed on every API request that includes a multi-value param
|
||||
# with a large allowlist (e.g., enums with 100+ values, multi-select filters)
|
||||
# At P=50 submitted params, V=200 allowed values: 10,000 → 250 ops per request — 40x
|
||||
#
|
||||
# Speedup: ~V× where V = allowlist size
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
Fixes grape-0002: Grape::Validations::Validators::ExceptValuesValidator#validate_param! — excepts Array#include? inside param_array.any? O(P×E).
|
||||
|
||||
--- a/lib/grape/validations/validators/except_values_validator.rb
|
||||
+++ b/lib/grape/validations/validators/except_values_validator.rb
|
||||
|
||||
@@ DEFECT grape-0002: line 19 validate_param!
|
||||
|
||||
def validate_param!(attr_name, params)
|
||||
return unless params.respond_to?(:key?) && params.key?(attr_name)
|
||||
|
||||
excepts = @except.is_a?(Proc) ? @except.call : @except
|
||||
return if excepts.nil?
|
||||
|
||||
param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name])
|
||||
- raise Grape::Exceptions::Validation.new(...) if param_array.any? { |param| excepts.include?(param) }
|
||||
+ excepts_set = excepts.is_a?(Set) ? excepts : excepts.to_set # FIX: O(1) lookup
|
||||
+ raise Grape::Exceptions::Validation.new(...) if param_array.any? { |param| excepts_set.include?(param) }
|
||||
end
|
||||
|
||||
# BEFORE: excepts is a plain Array (the blocklist defined in params do ... except_values: [...] end)
|
||||
# param_array.any? { |param| excepts.include?(param) }
|
||||
# For a multi-value param: P submitted values × E excluded values = O(P×E) per request
|
||||
#
|
||||
# AFTER: excepts_set = excepts.to_set — O(E) once
|
||||
# excepts_set.include? is O(1) per submitted param
|
||||
# Total: O(P + E) per validation call
|
||||
#
|
||||
# Severity: MEDIUM — executed on every API request containing a multi-value param
|
||||
# with except_values configured as a long blocklist
|
||||
# At P=30 submitted, E=150 excluded: 4,500 → 180 ops per request — 25x improvement
|
||||
#
|
||||
# Note: grape-0001 (ValuesValidator) and grape-0002 (ExceptValuesValidator) are companion defects —
|
||||
# same pattern, opposite semantic (allowlist vs blocklist)
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
Fixes grape-0003: Grape::DSL::Routing#route — endpoints Array#any? duplicate-check on every route registration O(N²).
|
||||
|
||||
--- a/lib/grape/dsl/routing.rb
|
||||
+++ b/lib/grape/dsl/routing.rb
|
||||
|
||||
@@ DEFECT grape-0003: line 176 route method
|
||||
|
||||
def route(methods, paths = ['/'], route_options = {}, &)
|
||||
...
|
||||
new_endpoint = Grape::Endpoint.new(...)
|
||||
- endpoints << new_endpoint unless endpoints.any? { |e| e.equals?(new_endpoint) }
|
||||
+ endpoints << new_endpoint unless endpoints_set.include_equivalent?(new_endpoint)
|
||||
...
|
||||
end
|
||||
|
||||
# Simpler fix — use a separate tracking Hash keyed by endpoint identity:
|
||||
|
||||
@@ Preferred fix: track endpoint identity in a Hash alongside the Array
|
||||
|
||||
def reset_endpoints!
|
||||
@endpoints = []
|
||||
+ @endpoints_seen = {} # FIX: identity map for O(1) duplicate detection
|
||||
end
|
||||
|
||||
def route(methods, paths = ['/'], route_options = {}, &)
|
||||
...
|
||||
new_endpoint = Grape::Endpoint.new(...)
|
||||
+ key = new_endpoint.identity_key # endpoint must expose a stable identity string
|
||||
- endpoints << new_endpoint unless endpoints.any? { |e| e.equals?(new_endpoint) }
|
||||
+ unless @endpoints_seen.key?(key)
|
||||
+ @endpoints_seen[key] = true
|
||||
+ endpoints << new_endpoint
|
||||
+ end
|
||||
...
|
||||
end
|
||||
|
||||
# BEFORE: endpoints is Array (initialized as @endpoints = [] in reset_endpoints!)
|
||||
# On each call to route(), endpoints.any? { |e| e.equals?(new_endpoint) } scans all
|
||||
# existing endpoints. With N routes registered: 1+2+3+...+N = O(N²/2) total checks.
|
||||
#
|
||||
# AFTER: Track seen endpoints in a Hash; Hash#key? is O(1)
|
||||
# Total registration cost: O(N)
|
||||
#
|
||||
# Severity: HIGH — triggered at application startup for every route defined in a Grape API
|
||||
# A large Grape API with 500 routes: 125,000 → 500 ops during boot
|
||||
# Directly slows cold-start / server reload time proportionally to route count²
|
||||
#
|
||||
# Speedup: ~N/2 × where N = total routes
|
||||
# At N=500 routes: ~250x reduction in duplicate-check ops
|
||||
175
defects/grape/unit/GrapeAlgorithm.java
Normal file
175
defects/grape/unit/GrapeAlgorithm.java
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* GrapeAlgorithm — grape-0001..0003
|
||||
*
|
||||
* Proves CWE-407 in Grape (Ruby API framework):
|
||||
* grape-0001: ValuesValidator — values Array#include? in param_array.all? O(P×V)
|
||||
* grape-0002: ExceptValuesValidator — excepts Array#include? in param_array.any? O(P×E)
|
||||
* grape-0003: DSL::Routing#route — endpoints Array#any? duplicate check O(N²)
|
||||
*
|
||||
* Run: javac -d . GrapeAlgorithm.java && java -ea unit.GrapeAlgorithm
|
||||
*/
|
||||
public class GrapeAlgorithm {
|
||||
|
||||
// ── grape-0001: ValuesValidator values.include? ───────────────────────────
|
||||
|
||||
/** SLOW: values is Array — Array#include? per param O(P×V) */
|
||||
static long valuesValidatorSlow(int paramCount, int valuesCount) {
|
||||
// Build allowlist array (e.g., permitted tag values)
|
||||
List<String> valuesArr = new ArrayList<>();
|
||||
for (int i = 0; i < valuesCount; i++) valuesArr.add("value_" + i);
|
||||
|
||||
long ops = 0;
|
||||
// Simulate P submitted params, each validated against V-element array
|
||||
// param_array.all? { |param| values.include?(param) }
|
||||
for (int p = 0; p < paramCount; p++) {
|
||||
String param = "value_" + (p % valuesCount);
|
||||
for (String v : valuesArr) {
|
||||
ops++;
|
||||
if (v.equals(param)) break; // include? short-circuits but worst case scans all
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
/** FAST: values is Set — Set#include? per param O(P+V) */
|
||||
static long valuesValidatorFast(int paramCount, int valuesCount) {
|
||||
// values_set = values.to_set
|
||||
Set<String> valuesSet = new HashSet<>();
|
||||
for (int i = 0; i < valuesCount; i++) valuesSet.add("value_" + i);
|
||||
|
||||
long ops = 0;
|
||||
for (int p = 0; p < paramCount; p++) {
|
||||
String param = "value_" + (p % valuesCount);
|
||||
ops++; // O(1) Set#include?
|
||||
valuesSet.contains(param);
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
// ── grape-0002: ExceptValuesValidator excepts.include? ───────────────────
|
||||
|
||||
/** SLOW: excepts is Array — Array#include? per param O(P×E) */
|
||||
static long exceptValuesValidatorSlow(int paramCount, int exceptsCount) {
|
||||
// Build blocklist array (e.g., reserved words that cannot be submitted)
|
||||
List<String> exceptsArr = new ArrayList<>();
|
||||
for (int i = 0; i < exceptsCount; i++) exceptsArr.add("except_" + i);
|
||||
|
||||
long ops = 0;
|
||||
// Simulate P submitted params; raise if any is in blocklist
|
||||
// param_array.any? { |param| excepts.include?(param) }
|
||||
for (int p = 0; p < paramCount; p++) {
|
||||
String param = "safe_" + p; // none match, forces full scan each time
|
||||
for (String e : exceptsArr) {
|
||||
ops++;
|
||||
if (e.equals(param)) break;
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
/** FAST: excepts is Set — Set#include? per param O(P+E) */
|
||||
static long exceptValuesValidatorFast(int paramCount, int exceptsCount) {
|
||||
Set<String> exceptsSet = new HashSet<>();
|
||||
for (int i = 0; i < exceptsCount; i++) exceptsSet.add("except_" + i);
|
||||
|
||||
long ops = 0;
|
||||
for (int p = 0; p < paramCount; p++) {
|
||||
String param = "safe_" + p;
|
||||
ops++; // O(1) Set#include?
|
||||
exceptsSet.contains(param);
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
// ── grape-0003: DSL::Routing endpoints duplicate check ───────────────────
|
||||
|
||||
/**
|
||||
* SLOW: endpoints is Array — endpoints.any? { |e| e.equals?(new_endpoint) }
|
||||
* O(N²) total across N route registrations
|
||||
*/
|
||||
static long routingEndpointsSlow(int routeCount) {
|
||||
List<String> endpoints = new ArrayList<>();
|
||||
long ops = 0;
|
||||
for (int i = 0; i < routeCount; i++) {
|
||||
String newEndpoint = "GET:/path/" + i;
|
||||
// endpoints.any? { |e| e.equals?(new_endpoint) }
|
||||
boolean found = false;
|
||||
for (String e : endpoints) {
|
||||
ops++;
|
||||
if (e.equals(newEndpoint)) { found = true; break; }
|
||||
}
|
||||
if (!found) endpoints.add(newEndpoint);
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
/**
|
||||
* FAST: endpoints seen in Hash — Hash#key? per registration O(N)
|
||||
*/
|
||||
static long routingEndpointsFast(int routeCount) {
|
||||
List<String> endpoints = new ArrayList<>();
|
||||
Map<String, Boolean> endpointsSeen = new HashMap<>();
|
||||
long ops = 0;
|
||||
for (int i = 0; i < routeCount; i++) {
|
||||
String newEndpoint = "GET:/path/" + i;
|
||||
ops++; // O(1) Hash#key?
|
||||
if (!endpointsSeen.containsKey(newEndpoint)) {
|
||||
endpointsSeen.put(newEndpoint, true);
|
||||
endpoints.add(newEndpoint);
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
// ── bench harness ─────────────────────────────────────────────────────────
|
||||
|
||||
interface Bench { long run(); }
|
||||
|
||||
static void bench(String label, Bench slow, Bench fast, long sOps, long fOps) {
|
||||
long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime()-t0)/1_000_000;
|
||||
long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime()-t1)/1_000_000;
|
||||
double r = fOps > 0 ? (double)sOps/fOps : 0;
|
||||
System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
|
||||
label, sMs, sOps, fMs, fOps, r);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== UNIT grape-0001..0003: Grape CWE-407 ===");
|
||||
System.out.println();
|
||||
|
||||
final int PARAMS = 100, VALUES = 300; // grape-0001: 100 submitted, 300 allowed
|
||||
final int PARAMS2 = 80, EXCEPTS = 200; // grape-0002: 80 submitted, 200 blocklisted
|
||||
final int ROUTES = 600; // grape-0003: 600 route registrations
|
||||
|
||||
long s0 = valuesValidatorSlow(PARAMS, VALUES);
|
||||
long f0 = valuesValidatorFast(PARAMS, VALUES);
|
||||
bench("grape-0001 ValuesValidator values.include?",
|
||||
() -> valuesValidatorSlow(PARAMS, VALUES),
|
||||
() -> valuesValidatorFast(PARAMS, VALUES), s0, f0);
|
||||
|
||||
long s1 = exceptValuesValidatorSlow(PARAMS2, EXCEPTS);
|
||||
long f1 = exceptValuesValidatorFast(PARAMS2, EXCEPTS);
|
||||
bench("grape-0002 ExceptValuesValidator excepts.include?",
|
||||
() -> exceptValuesValidatorSlow(PARAMS2, EXCEPTS),
|
||||
() -> exceptValuesValidatorFast(PARAMS2, EXCEPTS), s1, f1);
|
||||
|
||||
long s2 = routingEndpointsSlow(ROUTES);
|
||||
long f2 = routingEndpointsFast(ROUTES);
|
||||
bench("grape-0003 DSL::Routing endpoints.any? dup check",
|
||||
() -> routingEndpointsSlow(ROUTES),
|
||||
() -> routingEndpointsFast(ROUTES), s2, f2);
|
||||
|
||||
System.out.println();
|
||||
int pass = 0;
|
||||
assert s0 > f0 * 5 : "grape-0001 expected >5x speedup; got slow=" + s0 + " fast=" + f0; pass++;
|
||||
assert s1 > f1 * 5 : "grape-0002 expected >5x speedup; got slow=" + s1 + " fast=" + f1; pass++;
|
||||
assert s2 > f2 * 5 : "grape-0003 expected >5x speedup; got slow=" + s2 + " fast=" + f2; pass++;
|
||||
|
||||
System.out.printf("%d/3 PASS — grape-0001..0003: CWE-407 in ValuesValidator/ExceptValuesValidator/DSL::Routing%n", pass);
|
||||
System.out.printf("Hotpaths: per-request param validation (grape-0001/0002), app boot route registration (grape-0003)%n");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue