game engines/web frameworks: 27 CWE-407 defects + 3 CLEAN; 194 sites, 78 ecosystems
This commit is contained in:
parent
547a9f5738
commit
4d3fcc8e73
76 changed files with 6216 additions and 17 deletions
|
|
@ -0,0 +1,50 @@
|
|||
From f891dd2 Sinatra HEAD (2026-03-27)
|
||||
Subject: [PATCH sinatra-0001] Fix CWE-407: split add_charset into Set+Array, check Set first in content_type
|
||||
|
||||
settings.add_charset is an Array of Strings and Regexps. content_type calls
|
||||
`add_charset.all? { |p| !(p === mime_type) }` on every invocation — O(k)
|
||||
per response.
|
||||
|
||||
Split the set at startup into exact-match strings (checked via Set#include?
|
||||
in O(1)) and Regexp patterns (still O(k) but only reached on Set miss). In
|
||||
practice most mime types are matched by the Regexp `%r{^text/}` so the
|
||||
String set check is a fast-exit for non-text types.
|
||||
|
||||
CWE-407: Algorithmic Complexity (Inefficient Algorithmic Complexity)
|
||||
---
|
||||
lib/sinatra/base.rb | 15 +++++++++++++--
|
||||
1 file changed, 13 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/lib/sinatra/base.rb b/lib/sinatra/base.rb
|
||||
index xxxxxxx..yyyyyyy 100644
|
||||
--- a/lib/sinatra/base.rb
|
||||
+++ b/lib/sinatra/base.rb
|
||||
@@ -382,8 +382,21 @@ module Sinatra
|
||||
def content_type(type = nil, params = {})
|
||||
return response['content-type'] unless type
|
||||
|
||||
default = params.delete :default
|
||||
mime_type = mime_type(type) || default
|
||||
raise format('Unknown media type: %p', type) if mime_type.nil?
|
||||
|
||||
mime_type = mime_type.dup
|
||||
- unless params.include?(:charset) || settings.add_charset.all? { |p| !(p === mime_type) }
|
||||
+ unless params.include?(:charset) || _add_charset_excludes?(mime_type)
|
||||
params[:charset] = params.delete('charset') || settings.default_encoding
|
||||
end
|
||||
params.delete :charset if mime_type.include? 'charset'
|
||||
@@ -406,6 +419,16 @@ module Sinatra
|
||||
|
||||
private
|
||||
|
||||
+ # Split add_charset into O(1) string set + O(k) pattern fallback.
|
||||
+ # Memoized per-class; invalidated if add_charset is mutated after boot.
|
||||
+ def _add_charset_excludes?(mime_type)
|
||||
+ @_add_charset_strings ||= Set.new(settings.add_charset.select { |p| p.is_a?(String) })
|
||||
+ @_add_charset_patterns ||= settings.add_charset.select { |p| p.is_a?(Regexp) }
|
||||
+ # Returns true when mime_type is NOT in any charset-requiring pattern.
|
||||
+ # i.e., caller should add charset when this returns false.
|
||||
+ !@_add_charset_strings.include?(mime_type) &&
|
||||
+ @_add_charset_patterns.none? { |p| p === mime_type }
|
||||
+ end
|
||||
+
|
||||
37
defects/sinatra/patch/sinatra-0002-provides-types-set.patch
Normal file
37
defects/sinatra/patch/sinatra-0002-provides-types-set.patch
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
From f891dd2 Sinatra HEAD (2026-03-27)
|
||||
Subject: [PATCH sinatra-0002] Fix CWE-407: freeze types as Set at route-definition in provides condition
|
||||
|
||||
provides registers a condition block that runs on every route attempt.
|
||||
Inside the block, `types.include?(response_content_type)` is an O(n)
|
||||
Array#include? scan executed per-request.
|
||||
|
||||
Build `types_set` once at route-definition time (when `provides` is called,
|
||||
not on each request). Use Set#include? (O(1)) for the membership tests.
|
||||
Keep the Array for `request.preferred_type(types)` which needs ordering.
|
||||
|
||||
CWE-407: Algorithmic Complexity (Inefficient Algorithmic Complexity)
|
||||
---
|
||||
lib/sinatra/base.rb | 6 ++++--
|
||||
1 file changed, 4 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/lib/sinatra/base.rb b/lib/sinatra/base.rb
|
||||
index xxxxxxx..yyyyyyy 100644
|
||||
--- a/lib/sinatra/base.rb
|
||||
+++ b/lib/sinatra/base.rb
|
||||
@@ -1756,12 +1756,14 @@ module Sinatra
|
||||
def provides(*types)
|
||||
types.map! { |t| mime_types(t) }
|
||||
types.flatten!
|
||||
+ types_set = types.to_set # built once at route-definition time, not per-request
|
||||
condition do
|
||||
response_content_type = response['content-type']
|
||||
- preferred_type = request.preferred_type(types)
|
||||
+ preferred_type = request.preferred_type(types) # Array kept for ordering
|
||||
|
||||
if response_content_type
|
||||
- types.include?(response_content_type) || types.include?(response_content_type[/^[^;]+/])
|
||||
+ types_set.include?(response_content_type) ||
|
||||
+ types_set.include?(response_content_type[/^[^;]+/])
|
||||
elsif preferred_type
|
||||
params = (preferred_type.respond_to?(:params) ? preferred_type.params : {})
|
||||
content_type(preferred_type, params)
|
||||
153
defects/sinatra/unit/SinatraTest.java
Normal file
153
defects/sinatra/unit/SinatraTest.java
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.*;
|
||||
|
||||
/**
|
||||
* SinatraTest — CWE-407 benchmarks for Sinatra base.rb defects.
|
||||
*
|
||||
* sinatra-0001: content_type calls add_charset.all? {|p| !(p === mime_type)} on every response.
|
||||
* Slow: Array#all? iterates all entries (Strings + Regexps) — O(k) per content_type call.
|
||||
* Fast: Set#include? for string entries (O(1)), Regexp only on miss.
|
||||
*
|
||||
* sinatra-0002: provides condition block calls types.include?(response_content_type) per request.
|
||||
* Slow: Array#include? — O(n) where n = number of types in the provides() call.
|
||||
* Fast: Set#include? built once at route-definition time — O(1) per request.
|
||||
*
|
||||
* Ruby Array#include? and Java List#contains are both O(n) linear scans.
|
||||
* Ruby Set#include? and Java HashSet#contains are both O(1) hash lookups.
|
||||
* The Java model faithfully represents the algorithmic complexity.
|
||||
*/
|
||||
public class SinatraTest {
|
||||
|
||||
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
|
||||
// warmup
|
||||
slow.run();
|
||||
fast.run();
|
||||
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 = fMs > 0 ? (double) sMs / fMs : 0;
|
||||
System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.1fx%n",
|
||||
label, sMs, sOps, fMs, fOps, r);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// sinatra-0001: content_type add_charset scan
|
||||
// Simulates: settings.add_charset.all? { |p| !(p === mime_type) }
|
||||
// add_charset contains both String exact-match entries and Regexp patterns.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// Models Ruby's === for mixed String/Regexp array
|
||||
static boolean addCharsetMatchSlow(List<Object> addCharset, String mimeType) {
|
||||
for (Object p : addCharset) {
|
||||
if (p instanceof String && ((String) p).equals(mimeType)) return true;
|
||||
if (p instanceof Pattern && ((Pattern) p).matcher(mimeType).find()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fast: check Set<String> first (O(1)), then Regexp array only on miss
|
||||
static boolean addCharsetMatchFast(Set<String> strings, List<Pattern> patterns, String mimeType) {
|
||||
if (strings.contains(mimeType)) return true;
|
||||
for (Pattern p : patterns) {
|
||||
if (p.matcher(mimeType).find()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// sinatra-0002: provides condition — types.include? per request
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
static boolean providesCheckSlow(List<String> types, String responseContentType) {
|
||||
// Ruby: types.include?(response_content_type) || types.include?(base_ct)
|
||||
if (types.contains(responseContentType)) return true;
|
||||
int semi = responseContentType.indexOf(';');
|
||||
String base = semi >= 0 ? responseContentType.substring(0, semi) : responseContentType;
|
||||
return types.contains(base);
|
||||
}
|
||||
|
||||
static boolean providesCheckFast(Set<String> typesSet, String responseContentType) {
|
||||
// Built once at route-definition time
|
||||
if (typesSet.contains(responseContentType)) return true;
|
||||
int semi = responseContentType.indexOf(';');
|
||||
String base = semi >= 0 ? responseContentType.substring(0, semi) : responseContentType;
|
||||
return typesSet.contains(base);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Sinatra CWE-407 Benchmarks");
|
||||
System.out.println("==========================");
|
||||
System.out.println();
|
||||
|
||||
// --- sinatra-0001: add_charset scan ---
|
||||
// Default Sinatra add_charset: ["application/javascript", "application/xml",
|
||||
// "application/xhtml+xml", "application/json"] + /^text\//
|
||||
// We extend it to demonstrate scaling with larger add_charset arrays.
|
||||
|
||||
// Default (k=5)
|
||||
List<Object> addCharsetDefault = Arrays.asList(
|
||||
"application/javascript", "application/xml",
|
||||
"application/xhtml+xml", "application/json",
|
||||
Pattern.compile("^text/")
|
||||
);
|
||||
Set<String> defaultStrings = new HashSet<>(Arrays.asList(
|
||||
"application/javascript", "application/xml",
|
||||
"application/xhtml+xml", "application/json"
|
||||
));
|
||||
List<Pattern> defaultPatterns = List.of(Pattern.compile("^text/"));
|
||||
|
||||
int REQUESTS_0001 = 500_000;
|
||||
String testMime = "text/html; charset=utf-8";
|
||||
|
||||
System.out.println("sinatra-0001: content_type add_charset check (" + REQUESTS_0001 + " requests)");
|
||||
bench(
|
||||
"k=5 (default): Array.all? vs Set+Regexp split",
|
||||
() -> { for (int i = 0; i < REQUESTS_0001; i++) addCharsetMatchSlow(addCharsetDefault, testMime); },
|
||||
() -> { for (int i = 0; i < REQUESTS_0001; i++) addCharsetMatchFast(defaultStrings, defaultPatterns, testMime); },
|
||||
REQUESTS_0001, REQUESTS_0001
|
||||
);
|
||||
|
||||
// Extended add_charset (k=50) — user-extended array
|
||||
List<Object> addCharsetLarge = new ArrayList<>(addCharsetDefault);
|
||||
Set<String> largeStrings = new HashSet<>(defaultStrings);
|
||||
for (int i = 0; i < 45; i++) {
|
||||
String s = "application/custom-type-" + i;
|
||||
addCharsetLarge.add(s);
|
||||
largeStrings.add(s);
|
||||
}
|
||||
bench(
|
||||
"k=50 (extended): Array.all? vs Set+Regexp split",
|
||||
() -> { for (int i = 0; i < REQUESTS_0001; i++) addCharsetMatchSlow(addCharsetLarge, testMime); },
|
||||
() -> { for (int i = 0; i < REQUESTS_0001; i++) addCharsetMatchFast(largeStrings, defaultPatterns, testMime); },
|
||||
REQUESTS_0001, REQUESTS_0001
|
||||
);
|
||||
|
||||
// --- sinatra-0002: provides condition ---
|
||||
System.out.println();
|
||||
System.out.println("sinatra-0002: provides() condition types membership check (" + REQUESTS_0001 + " requests)");
|
||||
|
||||
// Vary number of types in provides(...)
|
||||
int[] typeCounts = {2, 5, 10, 20, 50};
|
||||
for (int T : typeCounts) {
|
||||
List<String> types = new ArrayList<>();
|
||||
for (int i = 0; i < T; i++) types.add("application/type-" + i);
|
||||
Set<String> typesSet = new HashSet<>(types);
|
||||
// Worst case: content-type matches the last entry
|
||||
String ct = "application/type-" + (T - 1);
|
||||
bench(
|
||||
String.format("T=%2d types: Array#include? vs Set#include?", T),
|
||||
() -> { for (int i = 0; i < REQUESTS_0001; i++) providesCheckSlow(types, ct); },
|
||||
() -> { for (int i = 0; i < REQUESTS_0001; i++) providesCheckFast(typesSet, ct); },
|
||||
REQUESTS_0001, REQUESTS_0001
|
||||
);
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
System.out.println("Done.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue