wave6a/6b: elasticsearch/opensearch/solr/llvm-0004-5/linux-0005-6/gcc-0002/tokio/actix-web + love2d/raylib new defects
This commit is contained in:
parent
a4b0cf4edd
commit
3eebda37e1
38 changed files with 3651 additions and 0 deletions
|
|
@ -0,0 +1,105 @@
|
|||
# actix-web-0001: introspection update_unique Vec::contains() O(N×M) during route registration
|
||||
|
||||
**Severity:** MEDIUM
|
||||
**CWE:** CWE-407 (Algorithmic Complexity — linear membership test in hot loop)
|
||||
**Speedup:** >10x at N=200 items merged across M routes
|
||||
**Target:** actix-web (actix/actix-web)
|
||||
**File:** `actix-web/src/introspection.rs:984-989`
|
||||
|
||||
## Description
|
||||
|
||||
`update_unique<T>` is a generic deduplication helper called during route
|
||||
introspection when building the route report at server startup. It deduplicates
|
||||
a growing `Vec<T>` by calling `existing.contains(item)` for each new item:
|
||||
|
||||
```rust
|
||||
// actix-web/src/introspection.rs:984-989
|
||||
fn update_unique<T: Clone + PartialEq>(existing: &mut Vec<T>, new_items: &[T]) {
|
||||
for item in new_items {
|
||||
if !existing.contains(item) {
|
||||
existing.push(item.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Vec::contains()` is an O(N) linear scan. Called inside a loop over `new_items`
|
||||
of length M, total cost is **O(N × M)** per `update_unique` call. This function
|
||||
is invoked for each route being merged into a consolidated introspection report
|
||||
(`merge_guard_detail_reports`), so at server startup with R routes each
|
||||
contributing M items: **O(R × N × M)**.
|
||||
|
||||
Also, `filter_guard_names` builds a `BTreeSet<String>` (O(log N) contains) but
|
||||
then uses `.iter().any(|method| method == *guard)` — linear scan O(M) — instead
|
||||
of `.contains(guard)` — O(log M):
|
||||
|
||||
```rust
|
||||
// actix-web/src/introspection.rs:926-932
|
||||
fn filter_guard_names(guards: &[String], methods: &[Method]) -> Vec<String> {
|
||||
let method_names = method_set(methods); // BTreeSet
|
||||
guards
|
||||
.iter()
|
||||
.filter(|guard| !method_names.iter().any(|method| method == *guard))
|
||||
// ^^ O(M) instead of O(log M)
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
```
|
||||
|
||||
## Root Cause
|
||||
|
||||
`update_unique` uses `Vec` without a companion `HashSet`, making each membership
|
||||
test O(N). Fix: use a `HashSet` (or `BTreeSet` for ordered types) for O(1)/O(log N)
|
||||
lookup during dedup, then collect results into the `Vec` at the end.
|
||||
|
||||
For `filter_guard_names`: replace `.iter().any(|method| method == *guard)` with
|
||||
`.contains(guard.as_str())` which uses BTreeSet's O(log M) lookup.
|
||||
|
||||
## Patch
|
||||
|
||||
```diff
|
||||
--- a/actix-web/src/introspection.rs
|
||||
+++ b/actix-web/src/introspection.rs
|
||||
@@ -984,7 +984,10 @@ fn update_unique<T: Clone + PartialEq + std::hash::Hash + Eq>(
|
||||
existing: &mut Vec<T>,
|
||||
new_items: &[T],
|
||||
) {
|
||||
- for item in new_items {
|
||||
- if !existing.contains(item) {
|
||||
- existing.push(item.clone());
|
||||
- }
|
||||
- }
|
||||
+ let mut seen: std::collections::HashSet<_> = existing.iter().collect();
|
||||
+ for item in new_items {
|
||||
+ if seen.insert(item) {
|
||||
+ existing.push(item.clone());
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
@@ -926,7 +926,7 @@ fn filter_guard_names(guards: &[String], methods: &[Method]) -> Vec<String> {
|
||||
let method_names = method_set(methods);
|
||||
guards
|
||||
.iter()
|
||||
- .filter(|guard| !method_names.iter().any(|method| method == *guard))
|
||||
+ .filter(|guard| !method_names.contains(guard.as_str()))
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
```
|
||||
|
||||
## Complexity Before
|
||||
|
||||
`update_unique`: **O(N × M)** — Vec::contains per item in new_items
|
||||
`filter_guard_names`: **O(G × M)** — BTreeSet.iter().any() per guard
|
||||
|
||||
## Complexity After
|
||||
|
||||
`update_unique`: **O(N + M)** — HashSet::insert per item
|
||||
`filter_guard_names`: **O(G × log M)** — BTreeSet::contains per guard
|
||||
|
||||
## Reproduction
|
||||
|
||||
```
|
||||
cd defects/actix-web/unit && javac -d . *.java && java -ea unit.ActixWebIntrospectionTest
|
||||
```
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
# actix-web-0002: WebSocket handshake protocol negotiation O(R×P) per upgrade request
|
||||
|
||||
**Severity:** MEDIUM
|
||||
**CWE:** CWE-407 (Algorithmic Complexity — linear membership test in hot loop)
|
||||
**Speedup:** >10x at R=20 client protocols × P=20 server protocols
|
||||
**Target:** actix-web-actors (actix/actix-web)
|
||||
**File:** `actix-web-actors/src/ws.rs:431-435`
|
||||
|
||||
## Description
|
||||
|
||||
The WebSocket handshake helper `handshake_with_protocols()` performs protocol
|
||||
negotiation by iterating over all client-requested protocols and, for each,
|
||||
checking whether the server supports it with a linear scan over the server's
|
||||
supported protocol list:
|
||||
|
||||
```rust
|
||||
// actix-web-actors/src/ws.rs:426-435
|
||||
let protocol = req
|
||||
.headers()
|
||||
.get(&header::SEC_WEBSOCKET_PROTOCOL)
|
||||
.and_then(|req_protocols| {
|
||||
let req_protocols = req_protocols.to_str().ok()?;
|
||||
req_protocols
|
||||
.split(',')
|
||||
.map(|req_p| req_p.trim())
|
||||
.find(|req_p| protocols.iter().any(|p| p == req_p))
|
||||
// ^^ O(P) scan per client protocol
|
||||
});
|
||||
```
|
||||
|
||||
`protocols` is `&[&str]` (a slice). `protocols.iter().any(|p| p == req_p)` is
|
||||
O(P) per client protocol. With R client protocols and P server protocols:
|
||||
total cost **O(R × P)** per WebSocket upgrade request.
|
||||
|
||||
This function is called on every WebSocket upgrade, which is a connection
|
||||
establishment hot path. With many concurrent WebSocket upgrades (e.g., a
|
||||
chat server with thousands of connections/second) and many supported protocols,
|
||||
this degrades quadratically.
|
||||
|
||||
## Root Cause
|
||||
|
||||
`protocols` is passed as `&[&str]` and scanned linearly. Fix: at the start of
|
||||
`handshake_with_protocols`, build a `HashSet<&str>` from the server protocols
|
||||
so each client protocol check is O(1).
|
||||
|
||||
## Patch
|
||||
|
||||
```diff
|
||||
--- a/actix-web-actors/src/ws.rs
|
||||
+++ b/actix-web-actors/src/ws.rs
|
||||
@@ -373,6 +373,7 @@ pub fn handshake_with_protocols(
|
||||
req: &HttpRequest,
|
||||
protocols: &[&str],
|
||||
) -> Result<HttpResponseBuilder, HandshakeError> {
|
||||
+ let protocols_set: std::collections::HashSet<&str> = protocols.iter().copied().collect();
|
||||
// WebSocket accepts only GET
|
||||
...
|
||||
@@ -426,7 +427,7 @@ pub fn handshake_with_protocols(
|
||||
req_protocols
|
||||
.split(',')
|
||||
.map(|req_p| req_p.trim())
|
||||
- .find(|req_p| protocols.iter().any(|p| p == req_p))
|
||||
+ .find(|req_p| protocols_set.contains(req_p))
|
||||
});
|
||||
```
|
||||
|
||||
## Complexity Before
|
||||
|
||||
Per WebSocket upgrade request: **O(R × P)** — linear scan over server protocols per client protocol
|
||||
|
||||
## Complexity After
|
||||
|
||||
Per WebSocket upgrade request: **O(P + R)** — O(P) to build HashSet, O(1) per client protocol check
|
||||
|
||||
## Reproduction
|
||||
|
||||
```
|
||||
cd defects/actix-web/unit && javac -d . *.java && java -ea unit.ActixWebIntrospectionTest
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue