game engines/web frameworks: 27 CWE-407 defects + 3 CLEAN; 194 sites, 78 ecosystems

This commit is contained in:
russell@unturf.com 2026-03-27 14:14:49 -04:00
parent 547a9f5738
commit 4d3fcc8e73
76 changed files with 6216 additions and 17 deletions

View file

@ -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
+

View 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)

View 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.");
}
}