From a86a765544b40dc620df5a21b984aa8ea521667b Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 31 Mar 2026 10:10:03 -0400 Subject: [PATCH] minetest (Luanti): 5 CWE-407 defects, all 5 MOADs scanned minetest-0001: mg_ore.cpp c_wherein vector CONTAINS in voxel inner loop O(V*W) HIGH 3.3x minetest-0002: mg_decoration.cpp c_place_on/c_spawnby vector CONTAINS O(S*P) MEDIUM 1.8x minetest-0003: l_env.cpp find_node_near/find_nodes_in_area filter CONTAINS O(V*F) MEDIUM 1.6x minetest-0004: nodedef.cpp nodeboxConnects sorted vector linear scan O(N) MEDIUM 2.1x minetest-0005: blockmodifier.cpp ABM neighbor check sorted vector O(N) LOW-MEDIUM 1.5x MOAD-0002: g_settings global singleton (architectural, not patchable) MOAD-0003: thread_local log streams (properly scoped, not leaked context) MOAD-0004: CLEAN (no credential logging found) MOAD-0005: CLEAN (no unsynchronized cache patterns found) --- .../minetest-0001/patch/minetest-0001.patch | 80 ++++++++++++++ .../minetest-0001/test/minetest-0001-test.cpp | 101 ++++++++++++++++++ defects/minetest-0001/test/test | Bin 0 -> 18032 bytes .../minetest-0002/patch/minetest-0002.patch | 62 +++++++++++ .../minetest-0002/test/minetest-0002-test.cpp | 92 ++++++++++++++++ defects/minetest-0002/test/test | Bin 0 -> 18016 bytes .../minetest-0003/patch/minetest-0003.patch | 56 ++++++++++ .../minetest-0003/test/minetest-0003-test.cpp | 92 ++++++++++++++++ defects/minetest-0003/test/test | Bin 0 -> 18016 bytes .../minetest-0004/patch/minetest-0004.patch | 22 ++++ .../minetest-0004/test/minetest-0004-test.cpp | 83 ++++++++++++++ defects/minetest-0004/test/test | Bin 0 -> 17504 bytes .../minetest-0005/patch/minetest-0005.patch | 23 ++++ .../minetest-0005/test/minetest-0005-test.cpp | 81 ++++++++++++++ defects/minetest-0005/test/test | Bin 0 -> 17504 bytes 15 files changed, 692 insertions(+) create mode 100644 defects/minetest-0001/patch/minetest-0001.patch create mode 100644 defects/minetest-0001/test/minetest-0001-test.cpp create mode 100755 defects/minetest-0001/test/test create mode 100644 defects/minetest-0002/patch/minetest-0002.patch create mode 100644 defects/minetest-0002/test/minetest-0002-test.cpp create mode 100755 defects/minetest-0002/test/test create mode 100644 defects/minetest-0003/patch/minetest-0003.patch create mode 100644 defects/minetest-0003/test/minetest-0003-test.cpp create mode 100755 defects/minetest-0003/test/test create mode 100644 defects/minetest-0004/patch/minetest-0004.patch create mode 100644 defects/minetest-0004/test/minetest-0004-test.cpp create mode 100755 defects/minetest-0004/test/test create mode 100644 defects/minetest-0005/patch/minetest-0005.patch create mode 100644 defects/minetest-0005/test/minetest-0005-test.cpp create mode 100755 defects/minetest-0005/test/test diff --git a/defects/minetest-0001/patch/minetest-0001.patch b/defects/minetest-0001/patch/minetest-0001.patch new file mode 100644 index 000000000..94ef19155 --- /dev/null +++ b/defects/minetest-0001/patch/minetest-0001.patch @@ -0,0 +1,80 @@ +# UNDF: UNDF-2026-000000938 +--- a/src/mapgen/mg_ore.h ++++ b/src/mapgen/mg_ore.h +@@ -1,6 +1,7 @@ + #pragma once + + #include ++#include + #include "objdef.h" + #include "noise.h" + #include "nodedef.h" +@@ -40,7 +41,7 @@ class Ore : public ObjDef, public NodeResolver { + + content_t c_ore; // the node to place +- std::vector c_wherein; // the nodes to be placed in ++ std::unordered_set c_wherein; // the nodes to be placed in + u32 clust_scarcity; // ore cluster has a 1-in-clust_scarcity chance of appearing at a node + s16 clust_num_ores; // how many ore nodes are in a chunk + s16 clust_size; // how large (in nodes) a chunk of ore is +--- a/src/mapgen/mg_ore.cpp ++++ b/src/mapgen/mg_ore.cpp +@@ -75,7 +75,10 @@ Ore::~Ore() + + void Ore::resolveNodeNames() + { +- getIdsFromNrBacklog(&c_wherein); ++ std::vector c_wherein_vec; ++ getIdsFromNrBacklog(&c_wherein_vec); ++ c_wherein.clear(); ++ c_wherein.insert(c_wherein_vec.begin(), c_wherein_vec.end()); + getIdFromNrBacklog(&c_ore, "", CONTENT_AIR); + } + +@@ -102,7 +105,7 @@ void Ore::cloneTo(Ore *def) const + + // All six ore generate() methods: replace CONTAINS(c_wherein, ...) with +-// c_wherein.count(...) for O(1) lookup instead of O(N) linear scan. ++// c_wherein.count(...) for O(1) lookup instead of O(N) linear scan: + + // OreScatter::generate +@@ -165,7 +168,7 @@ void OreScatter::generate(...) + u32 i = vm->m_area.index(x0 + x1, y0 + y1, z0 + z1); +- if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) ++ if (c_wherein.count(vm->m_data[i].getContent()) == 0) + continue; + + // OreSheet::generate +@@ -234,7 +237,7 @@ void OreSheet::generate(...) + u32 i = vm->m_area.index(x, y, z); +- if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) ++ if (c_wherein.count(vm->m_data[i].getContent()) == 0) + continue; + + // OrePuff::generate +@@ -328,7 +331,7 @@ void OrePuff::generate(...) + u32 i = vm->m_area.index(x, y, z); +- if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) ++ if (c_wherein.count(vm->m_data[i].getContent()) == 0) + continue; + + // OreBlob::generate +@@ -384,7 +387,7 @@ void OreBlob::generate(...) + u32 i = vm->m_area.index(x0 + x1, y0 + y1, z0 + z1); +- if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) ++ if (c_wherein.count(vm->m_data[i].getContent()) == 0) + continue; + + // OreVein::generate +@@ -463,7 +466,7 @@ void OreVein::generate(...) + u32 i = vm->m_area.index(x, y, z); +- if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) ++ if (c_wherein.count(vm->m_data[i].getContent()) == 0) + continue; + + // OreStratum::generate +@@ -574,7 +577,7 @@ void OreStratum::generate(...) + u32 i = vm->m_area.index(x, y, z); +- if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) ++ if (c_wherein.count(vm->m_data[i].getContent()) == 0) + continue; diff --git a/defects/minetest-0001/test/minetest-0001-test.cpp b/defects/minetest-0001/test/minetest-0001-test.cpp new file mode 100644 index 000000000..6b4717bfa --- /dev/null +++ b/defects/minetest-0001/test/minetest-0001-test.cpp @@ -0,0 +1,101 @@ +// minetest-0001-test: CWE-407 ore generation c_wherein linear scan +// Defect: CONTAINS(c_wherein, content) uses std::find O(N) per voxel node +// in ore generation inner loop, iterating 512K+ nodes per mapchunk. +// Fix: std::unordered_set for O(1) lookup. +// +// Severity: HIGH +// Location: src/mapgen/mg_ore.cpp, all 6 ore type generate() methods +// Pattern: CONTAINS(c_wherein, vm->m_data[i].getContent()) + +#include +#include +#include +#include +#include +#include +#include + +using content_t = uint16_t; + +#define CONTAINS(c, v) (std::find((c).begin(), (c).end(), (v)) != (c).end()) + +// Simulate ore generation inner loop: for each voxel, check membership +// in the c_wherein collection. + +static long long benchmark_vector(const std::vector &c_wherein, + const std::vector &voxel_data) { + auto start = std::chrono::high_resolution_clock::now(); + int matches = 0; + for (content_t c : voxel_data) { + if (CONTAINS(c_wherein, c)) + matches++; + } + auto end = std::chrono::high_resolution_clock::now(); + auto ns = std::chrono::duration_cast(end - start).count(); + // prevent optimization + assert(matches >= 0); + return ns; +} + +static long long benchmark_unordered_set(const std::unordered_set &c_wherein, + const std::vector &voxel_data) { + auto start = std::chrono::high_resolution_clock::now(); + int matches = 0; + for (content_t c : voxel_data) { + if (c_wherein.count(c) > 0) + matches++; + } + auto end = std::chrono::high_resolution_clock::now(); + auto ns = std::chrono::duration_cast(end - start).count(); + assert(matches >= 0); + return ns; +} + +int main() { + // Heavy modded game: ore placeable in many node types via groups. + // "group:stone" can resolve to 100+ content IDs in modpacks. + const int WHEREIN_SIZE = 100; + // Mapchunk volume: 80x80x80 = 512000 nodes + const int VOLUME = 512000; + + std::vector wherein_vec; + std::unordered_set wherein_set; + for (int i = 0; i < WHEREIN_SIZE; i++) { + content_t id = 100 + i * 7; // spread out IDs + wherein_vec.push_back(id); + wherein_set.insert(id); + } + + // Generate voxel data: mostly non-matching (air, water, etc.) + // with ~10% matching (stone variants) + std::vector voxel_data(VOLUME); + for (int i = 0; i < VOLUME; i++) { + if (i % 10 == 0) + voxel_data[i] = wherein_vec[i % WHEREIN_SIZE]; // match + else + voxel_data[i] = 1 + (i % 50); // non-match, common nodes + } + + // Warmup + benchmark_vector(wherein_vec, voxel_data); + benchmark_unordered_set(wherein_set, voxel_data); + + // Benchmark + long long vec_ns = benchmark_vector(wherein_vec, voxel_data); + long long set_ns = benchmark_unordered_set(wherein_set, voxel_data); + + double ratio = (double)vec_ns / (double)set_ns; + + printf("=== minetest-0001: ore c_wherein membership test ===\n"); + printf("c_wherein size: %d, volume: %d nodes, ops: %lld\n", + WHEREIN_SIZE, VOLUME, (long long)WHEREIN_SIZE * VOLUME); + printf("vector (std::find): %lld ns\n", vec_ns); + printf("unordered_set (count): %lld ns\n", set_ns); + printf("ratio: %.1fx\n", ratio); + fflush(stdout); + + // PASS criteria: unordered_set must be faster + assert(ratio >= 1.5 && "FAIL: unordered_set should be at least 1.5x faster"); + printf("PASS\n"); + return 0; +} diff --git a/defects/minetest-0001/test/test b/defects/minetest-0001/test/test new file mode 100755 index 0000000000000000000000000000000000000000..6935f345b7da9d108fe8a2f5df76b3ae5923c0af GIT binary patch literal 18032 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 c_place_on; ++ std::unordered_set c_place_on; + int check_offset = 0; + s16 sidelen = 2; + s16 y_min; + s16 y_max; +@@ -58,7 +58,7 @@ class Decoration : public ObjDef, public NodeResolver { + float fill_ratio = 0.0f; + NoiseParams np; +- std::vector c_spawnby; ++ std::unordered_set c_spawnby; + s16 nspawnby = -1; + s16 place_offset_y = 0; + std::unordered_set biomes; +--- a/src/mapgen/mg_decoration.cpp ++++ b/src/mapgen/mg_decoration.cpp +@@ -60,8 +60,14 @@ void Decoration::resolveNodeNames() + { +- getIdsFromNrBacklog(&c_place_on); +- getIdsFromNrBacklog(&c_spawnby); ++ std::vector place_on_vec; ++ getIdsFromNrBacklog(&place_on_vec); ++ c_place_on.clear(); ++ c_place_on.insert(place_on_vec.begin(), place_on_vec.end()); ++ ++ std::vector spawnby_vec; ++ getIdsFromNrBacklog(&spawnby_vec); ++ c_spawnby.clear(); ++ c_spawnby.insert(spawnby_vec.begin(), spawnby_vec.end()); + } + + bool Decoration::canPlaceDecoration(MMVManip *vm, v3s16 p) +@@ -72,7 +78,7 @@ bool Decoration::canPlaceDecoration(MMVManip *vm, v3s16 p) + // Check if the decoration can be placed on this node + u32 vi = vm->m_area.index(p); +- if (!CONTAINS(c_place_on, vm->m_data[vi].getContent())) ++ if (c_place_on.count(vm->m_data[vi].getContent()) == 0) + return false; + +@@ -98,7 +104,7 @@ bool Decoration::canPlaceDecoration(MMVManip *vm, v3s16 p) + if (!vm->m_area.contains(index)) + continue; + +- if (CONTAINS(c_spawnby, vm->m_data[index].getContent())) ++ if (c_spawnby.count(vm->m_data[index].getContent()) > 0) + nneighs++; + } + +@@ -110,7 +116,7 @@ bool Decoration::canPlaceDecoration(MMVManip *vm, v3s16 p) + if (!vm->m_area.contains(index)) + continue; + +- if (CONTAINS(c_spawnby, vm->m_data[index].getContent())) ++ if (c_spawnby.count(vm->m_data[index].getContent()) > 0) + nneighs++; + } diff --git a/defects/minetest-0002/test/minetest-0002-test.cpp b/defects/minetest-0002/test/minetest-0002-test.cpp new file mode 100644 index 000000000..19eba52f7 --- /dev/null +++ b/defects/minetest-0002/test/minetest-0002-test.cpp @@ -0,0 +1,92 @@ +// minetest-0002-test: CWE-407 decoration c_place_on/c_spawnby linear scan +// Defect: CONTAINS(c_place_on, content) and CONTAINS(c_spawnby, content) +// use std::find O(N) per decoration placement candidate. +// Fix: std::unordered_set for O(1) lookup. +// +// Severity: MEDIUM +// Location: src/mapgen/mg_decoration.cpp, canPlaceDecoration() +// Pattern: CONTAINS(c_place_on, ...) and CONTAINS(c_spawnby, ...) + +#include +#include +#include +#include +#include +#include +#include + +using content_t = uint16_t; + +#define CONTAINS(c, v) (std::find((c).begin(), (c).end(), (v)) != (c).end()) + +static long long bench_vector(const std::vector &place_on, + const std::vector &nodes, int reps) { + auto start = std::chrono::high_resolution_clock::now(); + int matches = 0; + for (int r = 0; r < reps; r++) { + for (content_t c : nodes) { + if (CONTAINS(place_on, c)) + matches++; + } + } + auto end = std::chrono::high_resolution_clock::now(); + assert(matches >= 0); + return std::chrono::duration_cast(end - start).count(); +} + +static long long bench_set(const std::unordered_set &place_on, + const std::vector &nodes, int reps) { + auto start = std::chrono::high_resolution_clock::now(); + int matches = 0; + for (int r = 0; r < reps; r++) { + for (content_t c : nodes) { + if (place_on.count(c) > 0) + matches++; + } + } + auto end = std::chrono::high_resolution_clock::now(); + assert(matches >= 0); + return std::chrono::duration_cast(end - start).count(); +} + +int main() { + // Modded game: 50 place_on node types (group:soil etc.), + // 6400 surface nodes per mapchunk (80x80), 50 repetitions (50 decos) + const int PLACE_ON_SIZE = 50; + const int SURFACE_NODES = 6400; + const int REPS = 50; + + std::vector place_on_vec; + std::unordered_set place_on_set; + for (int i = 0; i < PLACE_ON_SIZE; i++) { + content_t id = 50 + i * 5; + place_on_vec.push_back(id); + place_on_set.insert(id); + } + + std::vector surface(SURFACE_NODES); + for (int i = 0; i < SURFACE_NODES; i++) { + if (i % 5 == 0) + surface[i] = place_on_vec[i % PLACE_ON_SIZE]; + else + surface[i] = 1 + (i % 30); + } + + bench_vector(place_on_vec, surface, 1); + bench_set(place_on_set, surface, 1); + + long long vec_ns = bench_vector(place_on_vec, surface, REPS); + long long set_ns = bench_set(place_on_set, surface, REPS); + double ratio = (double)vec_ns / (double)set_ns; + + printf("=== minetest-0002: decoration c_place_on membership ===\n"); + printf("place_on size: %d, surface nodes: %d\n", PLACE_ON_SIZE, SURFACE_NODES); + printf("vector (std::find): %lld ns\n", vec_ns); + printf("unordered_set (count): %lld ns\n", set_ns); + printf("ratio: %.1fx\n", ratio); + + fflush(stdout); + assert(ratio >= 1.5 && "FAIL: unordered_set should be at least 1.5x faster"); + printf("PASS\n"); + return 0; +} diff --git a/defects/minetest-0002/test/test b/defects/minetest-0002/test/test new file mode 100755 index 0000000000000000000000000000000000000000..db1207398edfa86d6febcdecf96dd141bca8b1cc GIT binary patch literal 18016 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 Rv3^stAwjo3%)!F4{{w1*d&2+# literal 0 HcmV?d00001 diff --git a/defects/minetest-0003/patch/minetest-0003.patch b/defects/minetest-0003/patch/minetest-0003.patch new file mode 100644 index 000000000..32d5fb9bf --- /dev/null +++ b/defects/minetest-0003/patch/minetest-0003.patch @@ -0,0 +1,56 @@ +# UNDF: UNDF-2026-000000940 +--- a/src/script/lua_api/l_env.cpp ++++ b/src/script/lua_api/l_env.cpp +@@ -809,11 +809,12 @@ template + int ModApiEnvBase::findNodeNear(lua_State *L, v3s16 pos, int radius, +- const std::vector &filter, int start_radius, F &&getNode) ++ const std::vector &filter_vec, int start_radius, F &&getNode) + { ++ std::unordered_set filter(filter_vec.begin(), filter_vec.end()); + for (int d = start_radius; d <= radius; d++) { + const std::vector &list = FacePositionCache::getFacePositions(d); + for (const v3s16 &i : list) { + v3s16 p = pos + i; + content_t c = getNode(p).getContent(); +- if (CONTAINS(filter, c)) { ++ if (filter.count(c) > 0) { + push_v3s16(L, p); + return 1; +@@ -884,7 +885,8 @@ int ModApiEnvBase::findNodesInArea(lua_State *L, const NodeDefManager *ndef, + + iterate([&](v3s16 p, MapNode n) -> bool { + content_t c = n.getContent(); +- +- auto it = std::find(filter.begin(), filter.end(), c); +- if (it != filter.end()) { ++ // Build a hash map for O(1) lookup; also need index, so use ++ // unordered_map built once before the iterate. ++ auto it = filter_map.find(c); ++ if (it != filter_map.end()) { + // Calculate index of the table and append the position +- u32 filt_index = it - filter.begin(); ++ u32 filt_index = it->second; + + // Similarly for the non-grouped branch: +@@ -918,8 +920,8 @@ int ModApiEnvBase::findNodesInArea(...) + iterate([&](v3s16 p, MapNode n) -> bool { + content_t c = n.getContent(); +- +- auto it = std::find(filter.begin(), filter.end(), c); +- if (it != filter.end()) { ++ auto it = filter_map.find(c); ++ if (it != filter_map.end()) { + push_v3s16(L, p); + lua_rawseti(L, -2, ++i); +- u32 filt_index = it - filter.begin(); ++ u32 filt_index = it->second; + + // findNodesInAreaUnderAir: same pattern +@@ -989,7 +991,7 @@ int ModApiEnvBase::findNodesInAreaUnderAir(...) ++ std::unordered_set filter_set(filter.begin(), filter.end()); + for (p.X = minp.X; p.X <= maxp.X; p.X++) + for (p.Z = minp.Z; p.Z <= maxp.Z; p.Z++) { + ... + if (c != CONTENT_AIR && csurf == CONTENT_AIR && +- CONTAINS(filter, c)) { ++ filter_set.count(c) > 0) { diff --git a/defects/minetest-0003/test/minetest-0003-test.cpp b/defects/minetest-0003/test/minetest-0003-test.cpp new file mode 100644 index 000000000..da74eeb73 --- /dev/null +++ b/defects/minetest-0003/test/minetest-0003-test.cpp @@ -0,0 +1,92 @@ +// minetest-0003-test: CWE-407 Lua API find_node_near/find_nodes_in_area +// Defect: CONTAINS(filter, c) / std::find(filter...) O(F) per node +// in search volumes up to 4M nodes. +// Fix: std::unordered_set / std::unordered_map for O(1). +// +// Severity: MEDIUM +// Location: src/script/lua_api/l_env.cpp +// findNodeNear (line 818), findNodesInArea (lines 887, 921), +// findNodesInAreaUnderAir (line 989) + +#include +#include +#include +#include +#include +#include +#include +#include + +using content_t = uint16_t; + +#define CONTAINS(c, v) (std::find((c).begin(), (c).end(), (v)) != (c).end()) + +// Simulate find_nodes_in_area: iterate volume, check filter membership +static long long bench_vector(const std::vector &filter, + const std::vector &volume) { + auto start = std::chrono::high_resolution_clock::now(); + int matches = 0; + for (content_t c : volume) { + auto it = std::find(filter.begin(), filter.end(), c); + if (it != filter.end()) + matches++; + } + auto end = std::chrono::high_resolution_clock::now(); + assert(matches >= 0); + return std::chrono::duration_cast(end - start).count(); +} + +static long long bench_set(const std::unordered_set &filter, + const std::vector &volume) { + auto start = std::chrono::high_resolution_clock::now(); + int matches = 0; + for (content_t c : volume) { + if (filter.count(c) > 0) + matches++; + } + auto end = std::chrono::high_resolution_clock::now(); + assert(matches >= 0); + return std::chrono::duration_cast(end - start).count(); +} + +int main() { + // Lua mod: find_nodes_in_area with 50 node types (group expansions) + // over a 40x40x40 volume = 64000 nodes + const int FILTER_SIZE = 50; + const int VOLUME = 64000; + + std::vector filter_vec; + std::unordered_set filter_set; + for (int i = 0; i < FILTER_SIZE; i++) { + content_t id = 200 + i * 3; + filter_vec.push_back(id); + filter_set.insert(id); + } + + std::vector volume(VOLUME); + for (int i = 0; i < VOLUME; i++) { + if (i % 8 == 0) + volume[i] = filter_vec[i % FILTER_SIZE]; + else + volume[i] = 1 + (i % 40); + } + + // Warmup + bench_vector(filter_vec, volume); + bench_set(filter_set, volume); + + long long vec_ns = bench_vector(filter_vec, volume); + long long set_ns = bench_set(filter_set, volume); + double ratio = (double)vec_ns / (double)set_ns; + + printf("=== minetest-0003: Lua API node search filter ===\n"); + printf("filter size: %d, volume: %d nodes\n", FILTER_SIZE, VOLUME); + printf("vector (std::find): %lld ns\n", vec_ns); + printf("unordered_set (count): %lld ns\n", set_ns); + printf("ratio: %.1fx\n", ratio); + + fflush(stdout); + assert(ratio >= 1.5 && "FAIL: unordered_set should be at least 1.5x faster"); + printf("PASS\n"); + return 0; +} diff --git a/defects/minetest-0003/test/test b/defects/minetest-0003/test/test new file mode 100755 index 0000000000000000000000000000000000000000..b5adbac258d5bf4b0029135278663683dc1a0d92 GIT binary patch literal 18016 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 literal 0 HcmV?d00001 diff --git a/defects/minetest-0004/patch/minetest-0004.patch b/defects/minetest-0004/patch/minetest-0004.patch new file mode 100644 index 000000000..558c40719 --- /dev/null +++ b/defects/minetest-0004/patch/minetest-0004.patch @@ -0,0 +1,22 @@ +# UNDF: UNDF-2026-000000946 +--- a/src/nodedef.cpp ++++ b/src/nodedef.cpp +@@ -1285,10 +1285,12 @@ bool NodeDefManager::nodeboxConnects(MapNode from, MapNode to, + if ((f1.drawtype != NDT_NODEBOX) || (f1.node_box.type != NODEBOX_CONNECTED)) + return false; + +- // lookup target in connected set +- if (!CONTAINS(f1.connects_to_ids, to.param0)) ++ // lookup target in connected set (vector is SORT_AND_UNIQUE'd, ++ // use binary_search O(log N) instead of linear std::find O(N)) ++ if (!std::binary_search(f1.connects_to_ids.begin(), ++ f1.connects_to_ids.end(), to.param0)) + return false; + + const ContentFeatures &f2 = get(to); + + if ((f2.drawtype == NDT_NODEBOX) && (f2.node_box.type == NODEBOX_CONNECTED)) +- // ignores actually looking if back connection exists +- return CONTAINS(f2.connects_to_ids, from.param0); ++ return std::binary_search(f2.connects_to_ids.begin(), ++ f2.connects_to_ids.end(), from.param0); diff --git a/defects/minetest-0004/test/minetest-0004-test.cpp b/defects/minetest-0004/test/minetest-0004-test.cpp new file mode 100644 index 000000000..63ef1248b --- /dev/null +++ b/defects/minetest-0004/test/minetest-0004-test.cpp @@ -0,0 +1,83 @@ +// minetest-0004-test: CWE-407 nodeboxConnects sorted vector linear scan +// Defect: CONTAINS() on SORT_AND_UNIQUE'd connects_to_ids uses std::find O(N) +// instead of std::binary_search O(log N). Called 6x per connected +// nodebox node during mesh generation. +// Fix: std::binary_search on the already-sorted vector. +// +// Severity: MEDIUM +// Location: src/nodedef.cpp:1288,1295 + +#include +#include +#include +#include +#include +#include + +using content_t = uint16_t; + +#define CONTAINS(c, v) (std::find((c).begin(), (c).end(), (v)) != (c).end()) + +static long long bench_linear(const std::vector &ids, + const std::vector &queries) { + auto start = std::chrono::high_resolution_clock::now(); + int found = 0; + for (content_t q : queries) { + if (CONTAINS(ids, q)) + found++; + } + auto end = std::chrono::high_resolution_clock::now(); + assert(found >= 0); + return std::chrono::duration_cast(end - start).count(); +} + +static long long bench_binary(const std::vector &ids, + const std::vector &queries) { + auto start = std::chrono::high_resolution_clock::now(); + int found = 0; + for (content_t q : queries) { + if (std::binary_search(ids.begin(), ids.end(), q)) + found++; + } + auto end = std::chrono::high_resolution_clock::now(); + assert(found >= 0); + return std::chrono::duration_cast(end - start).count(); +} + +int main() { + // Modded game: connected nodebox with 30 connection targets + // Mesh generation: 6 faces * ~1000 connected nodes per mapblock = 6000 lookups + const int IDS_SIZE = 30; + const int LOOKUPS = 6000; + + std::vector ids; + for (int i = 0; i < IDS_SIZE; i++) + ids.push_back(10 + i * 4); + std::sort(ids.begin(), ids.end()); // SORT_AND_UNIQUE already applied + + std::vector queries(LOOKUPS); + for (int i = 0; i < LOOKUPS; i++) { + if (i % 3 == 0) + queries[i] = ids[i % IDS_SIZE]; // hit + else + queries[i] = 5 + (i % 200); // likely miss + } + + // Warmup + bench_linear(ids, queries); + bench_binary(ids, queries); + + long long lin_ns = bench_linear(ids, queries); + long long bin_ns = bench_binary(ids, queries); + double ratio = (double)lin_ns / (double)bin_ns; + + printf("=== minetest-0004: nodeboxConnects sorted vector scan ===\n"); + printf("connects_to_ids size: %d, lookups: %d\n", IDS_SIZE, LOOKUPS); + printf("linear (std::find): %lld ns\n", lin_ns); + printf("binary (binary_search): %lld ns\n", bin_ns); + printf("ratio: %.1fx\n", ratio); + + assert(ratio >= 1.5 && "FAIL: binary_search should be faster on sorted data"); + printf("PASS\n"); + return 0; +} diff --git a/defects/minetest-0004/test/test b/defects/minetest-0004/test/test new file mode 100755 index 0000000000000000000000000000000000000000..8e579b21590ecf8ea5cbfd43e87cb0fdd2fa34aa GIT binary patch literal 17504 zcmeHP3v^S*nI6dk%p;PaBymETC`4p!2*To3^ag z7D!|`+!|$R`pRb8JxSZ$&;QT=|1+O3S-N;ZG&nyRiKRN`cPv{vf9YapGVWZc zl~cRy_)+(-S=Y>sXuhQP0x*eJqGfOF^Zx^=&Os+%g}(a^t@1n z|E(hQON;3Fmm>VZBJ{_L(Em_`{^cTc^1mHFWBNV_W7&)5kI8>s5jut1*+%7r%kt*7 zcuY5n&_;htG^Ce}UGkYGWnq;pE3Jw6HaQxK zZB|<4P$CgeG_DOW`7^<2=jQAwK|h{gk37RBP*>bFvR zIy&U`P$C(R`J)kKyS%julCf=&Z;ow?#DemMP%_nq=1`*pTjW6N7CG#XL_ugzL}E&q zC_wzlWC%8CMQx$Bw)j?(!TlBB*0%X0F;D^>emNYW%I*j;FeXC^fk69q2Iiy^49uHH zH{eo2HwPe1n2WH0PG7TXmAuGV#a3v5YN@Q@;-C2StV8P!+HVL^>HW~RTxI?GnXF{G5<>sUB#rSHxcKcKY zj(QqETDgT?$;%so|M~}85L%|N22R`F{{!%svP~RUD(^uEpTI&Kj|_bSp`nC*4mc$f z%C6^SgwKpbFvCq|yEyHuU;aA6$0YV8j{ii%C$g_`Jau_wGR-LWb6mNMa5MWpa7to6 z6Z6PfzFuhL^Oc|<;PlcOO$-)r$nob_>Nv$R5yOabvVcxyB4rEcFesnS7SL^ZDq}+h zbn?AOWi@(^%$X*LfV(*VB;>iNTIt>bdSN`?UqBbJp9JnLpz9+*Q^W2rp!21JR`5^( zo$40pKmlF&j>;Y>prd2*sk?yAmqJ=uZvhmBREZv15b|NG%*hRXauJTap2n{I8B5D_l)2);Sbz3g42XJkQl*fA{=NL!D#{< z*f4_A#5Yiz$8&SeI2jAuuBu~N`*U;l81Nkie5(PE8St{y0$W*CM{X@1FmS(?$i|s1SKF`fz+gsh9X3xfY z*K5s9kCQvl$2CEzOBa(=C~g%>ji^G?RI_%Of>KxkoXmGaXr?5x8!r@IHHm46+ zd}`298ou*`yO>f%^_PX!TOD?<`*7+7pL)utzCn$96`er5U4l3D8yYWC*A)(*`&C=# zSI~$C_4P+;0KJv}2|=mi(I!ckkZ8xoD4M`@wT+kJNL@vE{^-mHkqn-*#|j z6QpL=N$GBrw4(h|#+12{zK6f}z3<)melwE# zctpy;*Kn|;{w}*7h69qjgScmS)}5z%)uY+9D1cEeiUEzeJ%=eH+wkK;;Uu2Q}&` zP?r=?9{?39V}~`iM<^(O<2&HU?%_qh3ELK2+z(1ZM$rQW!a;D*SdXI9HTwvT9_xRD zs8r+X{@dF;w|Z{#+XM1OHr*|1$DgJae7)s{OZ1>MK(EwX==5iMm}+L742zUHtG|#{L*~itz<{ zKPXaW!qWuYL(0VnrFT;vp>%TYr0uF*XaYOLA?eZhm)u97a#&K&N0IwY0udXPLb3jzRVTda8u3-yI)9_s)!OV!pyMh zTQH2MvGbXR-RG69#4V}kq@ID5lIfh(|DMm?y>sqhwQg@%T_|O40z;qFlLbSc)ITJt zPfBX9*WLTY&q-=O>R69j*Pxz7;X3#6#IbOlIwLWjY0Q&ReU^Nu#eZj4!}^0$)d_t|&eLle@6 zD8RHapZgu9-ga;vY^z7-z7TdFNgS~qY#Np_s}SU#OeEvC!AJK)t9kH8BQ^oI41RkQK5_6Ka4Q*NrA((L^Rui8wGDn1 zfdbng`QUc~YYW_?uK;Arz(BKTv!q@i579a)`^=vZX}U_tOSnZE+`YEWz3`H+>o%;R z+L-I52K7a6<*5esS#SS^@P72*GD$rH$Lw(X)a>A;Vq8}CN|}TibCL;xV#g=7vE3u3 zUm2z`X?i>Cu1>s!(T>8MuX-^YCWf!SA^lR$3nLobJ*fw?wP*;Obuq2ge3d8ljy{x~ z2lC(`thx`YsK@4^X$XHGQZ&u1m_}ArgQ37b2tSoWQhEr}>`8=d8o|%%gF;f@ZPJ#8 zyHD6UZ$TMCgcq}s8LMtbq`LO$V@>ZHhVW)n-VBA+}U58uz z9Omz&Ps*(MP>g>`{e|sdnV8zoN$v~DfSks<~Hm;tu%Vo?7({{ggvmL8Zm+_ zOI{wXSEo5}`!)`1HR7T;%mZ_%+@mh1k{LQp6gLhJOrpTkKjh1_Vj?(%5i@8ZcdkN{ zqY^@edWIHwx4fUrq3YCY5~7zLAECqIkkmn1{_NNJ+R^ zcK(`%2p=DZM!G(~9+tW$(PV{rXImNkDODaFsDGDMlXaN4#^Cqd;#GeuJ%*-JTIso2 zyGdxQluKRlQeW2sNgeQ24$$yL)OPpTI;$~!B&;LU4-h300KM+hwoV!p2)~If((b2h zyDtF4de!w5Oq=A()J-@0D&K9$EUyTwmgy1#OH|r@dIzEmR-EUx!7|N}{o)@fW=(UD zcLI_+)u(pyx zZcKEm{*(sdcpCS>$MArOzg@)JrOkHjf;TtzmQXCvidQtTkUvooh{uwO9q(VNtG9*% zN<6VV*&0tM_Eanx*&GW6?U1QfEZC0?d+ODNJ(P+>fOLBEgx7A*uLpcdY2U>~Oz?bkVk+}Va z6?T_%iT&DZ?Xy>VyuNDtC^0+wBL(d(A$!~6DUEzgv!wc7VteAbFR6Y8v0>51ltrdTUHR(q|r zyw+M)Yb}+gnj1$`AdV@R{Re*c!Z$wzWNS%ZJ?X0_eIC;1(e!=EpwBaPjkWa9(PO_n z4zcU-bD%wC@U5NNg*x|{>#XH_O3;S1#eB~cYq>{jM;*N%T87_7;zJ)i2SEH8Gupb3 z`1e@qtQF}AYphk~ucOvFtKB24@JwaP!A`#aE@aPHt#C|Czpi}N{*T|dq-rc zwfZ~KKF9^p-&a%JND_Xpfwl{@J4q+}`uFDb*7CbcYOVH+rPf-pkNmJ_qQ|-{J*m#x zXx=x)x(pO}2poAYubn#bP0<~VK5ev2`p3z$7@H-=4EdbK$4}nG>IUP~BJ1_tZN?DE zP<{Kze(ra#wY}u9d5X1a)#%RiOtnKSir;eRnGa~Gz{T30p15biK1-(LZa#EbE@!%^ z;~CR~6WI?fU8WyTB!cCd!ncEv8&BgN827-q2gW@x?tyU+{GaoH`2I3bzq`VEMN6d|Ue2NT{)*PTltlTv!?`%& z2k2cal2~2rLkyzW5a{Fk#ObHv2 zTEhD#EtT<)mjrzoZ?I@L?LbrFY)sTo@2V&XKD_hL>_c@L3A;QR-pUnY7e&LldF(95 zYj`;xB}SF&+W+US;`?o*#wz|$QNv*)ho9xJox@!m?&t7+4i9kH&0!yh#(Mr5URpDn z1D{e>+bfz|QZXfEcP+xVj#cxQrZjZnj)jYzRg0ajN?tHpax_NWZ(-+X+iFyz{-kds z_wf#~^U_ZMj>SJ0bTfl-m*|YzvlX%rae56OP}(yRXjh|Rjh_z~?U@I3FQ?o24W&KX zfKJ~{YW)00(wxc{j}zl@1EA^4AhKGyfuuNR?ThH+EIF3qOhBPcqw zgMT{JVApZ5X=wrIvw6M{+z9#%)X>NKot^_Jt=H&!zVN!e2%X0Fbo1rRcz^vv5&j>8 zel_^T_o*Vj{R;HiH2!&DznWK`B|YZp`FTRL=}Zy*Q(^d6e*PrrW9dJo@t5V#18I9G zsAOpoJyH?+ZAIuEL^oshGsLYOpp)Ok_!oA433O^_759U-7l6XoK&SCw?DumTeHOFV z2n{+s&*?_}X3U3U`R9@%^rfKNN2}Jb4Mq4PMff|4&{pNVZlI2)D(dLiJ5o{6qv5)I*RB#SJ-`evk zO@6OZj}%qbHC4$=WI5iRbS;s+V8%&;WKt8Wuiv^+-dH6!dE|{QdE-La8A&Gm_~c%z zeW}cM1tWBl0o$n`s{wzl`X)EEV5DN7YhmGj^@g>7W>nK69H{tAo=@LAEy@QsYKAC@ zIN2a$gZE>4@}pS-M+#$T&(#@-M}rK#o>WqJSs!2{r$A&yJEIW`B{3X=ad~qz-h%!N zqA!!OKh?nk@wWCT_PB%8BV%#U0gH(2PbB=?q((c7!4FY{#cL>;S`>08O2t2i!%`lJBu8kp`$mWZ@D@b9k-#5fl@lm zQgAFLuNX&A^s_K|3eKnKeluuWNZRO}j?T%CyYRC=H6ur3a302Zawo4Fs;T$EfP+V( zzqM0CqboyDF}@J+-bhho>tByNF1fzGu6_MR8W+@+y4C2{78VktmukeBA*lSb^H*b@v0~2T_BO@e0;nW(*eY^(ZxTwhGiNNWgq#Sx z&h|tcN2rwT1*Dc#1g8okK~BO!rTG-qHTG72vXwc5+heFghe|>(*@}Z$h^nIy8D)u3 z)K3CD*B(`vQ|me>vd+zMpp;350?es|I)G{e&6$X6w~sT_%J2EsAhZb#)!=+ps3Q<+ zS1{+$p)*aZ9u9OJB9F@bZ4s!~6{6!9LRecH!pj)*;(wP!&$%Z2%v{iTURXTm3cbWz zn63drk9b19*tu9CBi362Uuuvyo{x11Mh>y87?r%h3hNioI|ABAanm!!=yVHEy#9)d z_KW9t0Y&}d`Q6xlKji7zUC4{)djSt{hfy0TE9Avj3^L?XAusk91Qh!VM*j)> zzrp2exk9mDA)wgbAbEPE6!i=F2S6jA2>tnWE?@78eH^f8$=Ltj1hr7!&gBH`*2VHy zv5Xe*L4*8BULc_06AA<_-~ogDSzaI@l@}%^)pw6o`5SPTL=2$uGQK=eW)wU_jY_ E0iE0>=>Px# literal 0 HcmV?d00001 diff --git a/defects/minetest-0005/patch/minetest-0005.patch b/defects/minetest-0005/patch/minetest-0005.patch new file mode 100644 index 000000000..472e641e0 --- /dev/null +++ b/defects/minetest-0005/patch/minetest-0005.patch @@ -0,0 +1,23 @@ +# UNDF: UNDF-2026-000000947 +--- a/src/server/blockmodifier.cpp ++++ b/src/server/blockmodifier.cpp +@@ -218,11 +218,13 @@ void ABMHandler::apply(MapBlock *block, ...) + c = n.getContent(); + } ++ // required_neighbors and without_neighbors are SORT_AND_UNIQUE'd, ++ // use binary_search O(log N) instead of CONTAINS/std::find O(N) + if (check_required_neighbors && !have_required) { +- if (CONTAINS(aabm.required_neighbors, c)) { ++ if (std::binary_search(aabm.required_neighbors.begin(), ++ aabm.required_neighbors.end(), c)) { + if (!check_without_neighbors) + goto neighbor_found; + have_required = true; + } + } + if (check_without_neighbors) { +- if (CONTAINS(aabm.without_neighbors, c)) ++ if (std::binary_search(aabm.without_neighbors.begin(), ++ aabm.without_neighbors.end(), c)) + goto neighbor_invalid; + } diff --git a/defects/minetest-0005/test/minetest-0005-test.cpp b/defects/minetest-0005/test/minetest-0005-test.cpp new file mode 100644 index 000000000..cbe45b41d --- /dev/null +++ b/defects/minetest-0005/test/minetest-0005-test.cpp @@ -0,0 +1,81 @@ +// minetest-0005-test: CWE-407 ABM neighbor check sorted vector linear scan +// Defect: CONTAINS() on SORT_AND_UNIQUE'd required_neighbors/without_neighbors +// uses std::find O(N) in ABM inner loop (up to 26 neighbors per node). +// Fix: std::binary_search on the already-sorted vector. +// +// Severity: LOW-MEDIUM +// Location: src/server/blockmodifier.cpp:221,228 + +#include +#include +#include +#include +#include +#include + +using content_t = uint16_t; + +#define CONTAINS(c, v) (std::find((c).begin(), (c).end(), (v)) != (c).end()) + +static long long bench_linear(const std::vector &neighbors, + const std::vector &queries) { + auto start = std::chrono::high_resolution_clock::now(); + int found = 0; + for (content_t q : queries) { + if (CONTAINS(neighbors, q)) + found++; + } + auto end = std::chrono::high_resolution_clock::now(); + assert(found >= 0); + return std::chrono::duration_cast(end - start).count(); +} + +static long long bench_binary(const std::vector &neighbors, + const std::vector &queries) { + auto start = std::chrono::high_resolution_clock::now(); + int found = 0; + for (content_t q : queries) { + if (std::binary_search(neighbors.begin(), neighbors.end(), q)) + found++; + } + auto end = std::chrono::high_resolution_clock::now(); + assert(found >= 0); + return std::chrono::duration_cast(end - start).count(); +} + +int main() { + // Modded game: ABM with 15 required_neighbors types + // 4096 nodes per mapblock face, 26 neighbors each = 106K lookups + const int NEIGHBOR_TYPES = 15; + const int LOOKUPS = 106000; + + std::vector neighbors; + for (int i = 0; i < NEIGHBOR_TYPES; i++) + neighbors.push_back(20 + i * 6); + std::sort(neighbors.begin(), neighbors.end()); + + std::vector queries(LOOKUPS); + for (int i = 0; i < LOOKUPS; i++) { + if (i % 4 == 0) + queries[i] = neighbors[i % NEIGHBOR_TYPES]; + else + queries[i] = 3 + (i % 80); + } + + bench_linear(neighbors, queries); + bench_binary(neighbors, queries); + + long long lin_ns = bench_linear(neighbors, queries); + long long bin_ns = bench_binary(neighbors, queries); + double ratio = (double)lin_ns / (double)bin_ns; + + printf("=== minetest-0005: ABM neighbor check sorted vector ===\n"); + printf("neighbor types: %d, lookups: %d\n", NEIGHBOR_TYPES, LOOKUPS); + printf("linear (std::find): %lld ns\n", lin_ns); + printf("binary (binary_search): %lld ns\n", bin_ns); + printf("ratio: %.1fx\n", ratio); + + assert(ratio >= 1.3 && "FAIL: binary_search should be faster"); + printf("PASS\n"); + return 0; +} diff --git a/defects/minetest-0005/test/test b/defects/minetest-0005/test/test new file mode 100755 index 0000000000000000000000000000000000000000..38539fa7dca6ef77fb8286b6da675b7a63c11a06 GIT binary patch literal 17504 zcmeHP4Rlk-l^)5$I6x#rN#cb3C`4rKBnZoZiyR1+vEeDm;9^K3{a;q}e?U=d?XxyFE1_*~Z2|orDscZqg)lTauDgjstd zEWcW9HfQ&oJ$pPpdUx)fd+*G>Gw;s4d2hU}$+z5WGBJ@#*d2_p@+)*oJump|Vx9r0 zXN%b7xZ2qbtQ5FKqv_@KIzXwOUNnz|Wt?6DNOBdF83$gglbL{`g#<~iSgM$!mk7wp zb)4iTQI_LI*mt?kC!i?L*JszunE_@rrHlz!C`Z1L=IV0NlCkiF!7oCN$RxL(%dO{f z0+w?*0fj%wC-hsy{b_{ND5E5xQLc^48DXO?#ss7~D7C((1V2uFEnKdCwywtr6`jlk z6!m=ya^#nfnnX9R;q?{khXdT70#bidniq|1s9i8G8k`%A#8RDeI~Uc?tzF3*WqD(J zJSHaG#=PAKNjE8w3*t| z*(rB~63KYXAB`wo^5$wt#vuN z0pd?4L$FCJY7e!y$2XG8=8LVLaYbKo_x}1in*1*GGV+hCNh3=Sv5zU|#{9 z>K18#0bTfx$_^CJ(J}edTR`VaA+2nnfR2HbPbUiK);x70GvR>=4@`Jq!UO-`J@Ah0 z`uC;un`Kh>q*vx)THo2Lm~$tj^x?98&Gg*DJ3-FP@!@K_p`He;qc~0YLth!iX+j)IjN&vA4s964X#yNt zGm6v1H`I{Fb92r*84KHPXk=RZb93%D;9Cv&W&<8G;9&!Pp8>zifUh#(%MJJ)2D~

tTg{Z}ocCdDb?$ zPOocyoSr+;$Mr#}TNjg5C~@^l>T9+ti^G?RI_%QV8y(vEX!u6WPhpRGt;3r>Y)+rdMXWQ|0 zG@@C3?La-Cx9UG3DGk1_OqOi_ekgrz=|NhCXsUi&bw~<4Cz+nh`qAy)^d-CPf$11f zzRV^&oZ(Z?W_MxyG`n9@mcd?+t*4nT>qfkprjf9F(3XA&!x5G5fJS807PF)twe_@& zNa?H@42d_r=_BT_YK4kaFL~-o`{EL2o8IWC%q|(p<-F=)a**A1;o$Ws@MZQlfO82?AXuLUAuM-ySUx9M_-Wo-ndB}?0dyhHK-n{ zdKzZgdSd{7Dc7lo_LV5lO14LT1%aD0)Ap)|NapGEp_1X}qzrrw z2TSU2vMsP1klY=@EyJ^Qd8$`En)RUoHa$glNtuO{utt7cvfa|_RgXMLx23E1ZLjT? z<8RyU8wh)CTaRUjKg{JGqlO%$Wiv!(Km-;MbM`n2ed^nwIzZ(LvIjKk8=%$}P#*>r zDr1K=wgVIs!0}CRWdD{I{YTg~?}|g9BxDqQy+Akw4jSt1=yT0Jf@6pJUm+;fyL$in zUe7(Adp-B{>!T(61vGT17Tt!CF*F-6`(4tNxx5PM53bT%aRy6#DYN8JfNUL#DQcmc zhrVBs-;2B+&s=A`>fn8n`l^&ZeXa#JQLpPMNPiCuxhsBf3uAu>JH_|{eGn8WGwEpp z?u*J52&E5D9-;KI+$q})+tCKr&mrm2_!mA%plU=?&q;k}mr8vXOC-}#X>dfD2?h6Z z1!cKY`gjuZ)bj01sFfN%%CjpWM!Jvy`&aM_OcJ)gdo~_bkaiLrh6fqubK*1|oW^y?;X-H)?;2Ny=DeNST@6mNFeVThA0pJ>tt;y9GDptg!o~WSRPsPkk9?hE?CZ5rmCx z&ouA6pll{?Nj)$14K0;S=cU2-eD2gW&AUiF>q`HN^l_k3UX$};Lg_i@{f7t!u!+=gY|?!^w*NuSy$ zRlVm`pO(_ET=e$6j}iMG_p`0%N=d!gyz{wj_TiNnZ{gcp%*qB_ElL#EQuKGaw@h*m zD09QNm#0plvu)q(wf(f$)Gwuvn!WDxsVA~=v|#7S&ZtzCrFPS}ZC3leRsG)d2Nq=+ zuBFNXT+OLxNXgOR$M!(0dHBbpHUYN`KRgC69o`FWB}1%~*`dk&ENen-!{0!pz%~dw zcpXedQ0m9p0{7@I0kY*_pjmXCq+TKq(K;#n%pVbIx=YAQxJ8=X1GXKzAnNPB7i*|? z<~rG|zU-}fqgj34J9sI)2R*n*QqRIMTirf2JA9QGmsJB&CSk^$WI~|WdYv}5`=s=% zBQz#WZ-?D=iIW)ZDBSitFNQ;D`1U)}Cv#pH(d_O^J(6ueL*T3{Xsza}I;D4XfA&_8 zhlgR+gIGl!)Ph51;Ta02nI)G1vUOl6@DIXI)kP_N5!37`#B3VD&+CIiQr~UWmWDf@ zv+cMWWe5>oOh;zKDhyLw%;LZpl}2z&nVK^*8(}JL-XpyxTu!jJZ5;!zsD0`SVfRH_ z`UG5roAg=RSI+6V0ozxOQO;xAI?VyemJy%&b4h(M>^`2n2mMZcq~9+X_tje0;TAuS z`8(;8GAlk5<6lyLZtE!*Q~PW87=W?hzby`C7(&Hm^SR9f%Ov@kF1z#27+wZkfwF&E;m$A0W-UulPH_NtP z(h%X}qkpvP^Xp-$dooQ{n0L06!=F;s(V?byX*Jo1d21Yg&)r`2SJFW=ozhazUD{1T zW2Hjsj+gnm=Sk|2uWE>fFQT^lgl$J1hL41Wg!%!ZBm$tVPvguoJ&cAnXaD1#;E1#PfQb7a5t2Z~wK9ORvV zq)zpzU%-+$fB|v1LWDk#TwsLv3G^JLZR9X@rXzc&reY?X*@t;<_{+L~CG}a&`4&g^ z)5L(txAV+4kJOzk_t9!>n!{IhHhVpT#w^P|@eUc_bCeDLi*#UAs86!381Q8rmkq505w~s>tGuiXW_XF_aP7uK zx9U%6AWozS4}1&{nE2a8yj|L8*DiQ-W8V;p1={e6CKmE1Dg*IYQnBOxOI_XOP(X<% z7AM={3B{g@B_kVSp`aZScieoN{iE?!>5Uw(z72T!lj!>2sc*@WCH8jMqlA*m-0JG; zg>`mML$f^=ifnA#5Kq_xZK1#>dlGiRAl*zmWTpt0<{6c)j!?4BJ|}n^+!f!H>d=rp zpYAhzrRFZY%?ei4*|jt$8V%ZGNx`VwYOmCxoP?tTZNzKfOZb&Y+klZ@$?+ zd%4H!tFw<0v!g#!(7qvL5BuqD;cPa>)VXA|Gtkk&!tqorNZP8IrWkfOUE$6tj5?iO z!&O$X7Ef#I6sEhNu1;>26Cr;TF9zjEjNapMhtLD>v15N&YMNDYJJIm4#ZH~c<@NzS zbT*fx*uEby3;5Z$a=B^P1KI<47)#+3Z|8D*K<}k+M$d_uY+3+&0lx!y>Ow9zKsZ(^ z7V!6CY1{~S4sbm!rIBb)gOcgoi_#Hrf zi%>sXZY_VnELkfuB`dA=-4>s<^8QJU*6MVrdEXRkrN?S-uvRo!%Nwj^(o}QHSPH~3 z<+5Mnw-3JgS3tIs^fi&bCer62eI8BUR}K0+Q&(8a`i~y_#c_z;hMxoNv15j^Q@c>- zZgZox;{Fn}A#E}5ono!toDqh!CJYS{BVD%$GRvzxzXBU-aW;-2o!h-9CHU*-TQVgN@S)3cIn(7^j+wq+%Jy5jO+PLr zg5{>dw}FtGNE05I@W6xzCOk0Vfe8=%pZ9?H{z-iAL`xP*^i3Bffzvl&g*bhqMQNH2 z^u-YJw4SE)6WW(Vx>^VM@2;?Z(NY8hAK2^#`tGl92@=awT*vIB-T1*+&)@AQ^F3UmagKkoPWF|=!o%lfO3Ln< zk8d5T=hmh)v}S9~0%!FCr>lw=jFlXVQTJQedD^xblc+!G+sNI#L+rft6M*CK-vYXs z!MH1RM(tS(*?pW|&j*zD%mmtvs959Y14erW0^Q5$c78)?&pM#fcas`Fzmc@(7|_ph z`a}HVL|WHVx(NDs?JU8-Cq4Cs8)OovcNpjuoG#*quAMO_=q5IcRn+SgRHvmnjeZpq z_cKp#*48ybIk{ydPjr-Djqi|CPx(C;lm?#LwsJFB@Lw7mfoo(7%9gR$SwYxG&nUN1E0@B*hB^_wvtj_02% zi_mL9w~tk=VQY%;M~d)w7NN7&Ktgdj;w?)A@0DY&=tF-#PN)u94S=CxCFO=nY zN7A)W_JSEF36e=otf^`9T6t}?-0G3ny5zMrvNMuQ`0>fTR(q|?Hwhzjk^y_EAFBa> zsrptowP3VjpR1;Dzk1EeKQXFxJ`PlTCeNpDo)+bU8#P0eM4W7pvCaE2J^9frfg^=6 zv<2%7#G^rmUQa41ysQtf(NiF@qMgx*g_0N!!MMCJ8sC8a45BZSvOm?y0`c~aDE7F6 z)Fb0@&;g5x>`x^8U2-UkNo!Zb(nf$o}GKE#}11MuOz@y2y z+~$u3=@3rgxt1|(jdwc}p|G>a0UA1bGxnBibkT7e>KG`c!z=~IV)BY{1Vuj!lc(T( zitaaqwi?n#=X7*Ve%yth{iz>48iVsN#*;gF-B3-v4+b1O68){68X8*}f{O9|fcH*{ zBAfqmXQ9Y!Ix>`R8};RQzAZ5A&!3(eb9$*> zoEd`3KRJIj?inlQOm?*^{tbXiLWgZ4hu0{fL98{W1QC(wi^C#PwGuRbF6*^QBddX%S#6naZgUBdLgra^D;JJ>d!kk*yIgxd4 zj02@iG8AA=CDaL28)(i%T)Taop*DWcw*{e1V5kP?t3sWDP=|s!hYp=-TJ>kxTV z?r)Dky{-@)#}LBW+Yw&InHT@NBzn#@;b-Q8#`D7BIalZ<-okVf5PHND^2N@@3K_B9 z5_qja-grLNAs9KtvSLi~0xPUvJnsl-AHz-06l2rfK=C>(F4`}i-vt!)i|2P^`~8ro zXLlhlp6>q^yt^&)E^su$0Wpi~Rxt#r^;${yuiitynrY4YBe8IJ4O3BnPre6p9*=gzaXI4UoiSl*#B)V-@p}${R#oa{szg@ zBc-Tc$bSzs@`=!&U+41muGq%`iG{!h%{U;6b2e`a|+NYtqO6_W_|22cWGDl|? zQ0xl|71Y+k^hY38{OI1GB+hM$^YF&{1TJ6+6xtvsI~k9ptY=SE7jA4;}} p_6WOZpG~wK*?f9vp`P8#