5.1 KiB
LÖVE2D — CWE-407 Disclosure Brief
Project: LÖVE2D Disclosure date: 2026-03-27 Severity: HIGH Speedup: 250× (love2d-0003), varies for others Status: PATCHED
Finding
Three independent defects in LÖVE2D use linear scans (std::find on std::vector) for repeated membership checks where O(1) hash structures are required. The defects span joystick event handling, display mode enumeration, and filesystem mount validation — all affecting performance in tight loops or initialization paths.
The Defect(s)
| ID | Location | Pattern | Complexity |
|---|---|---|---|
| love2d-0001 | src/modules/joystick/ |
JoystickModule::getJoystickFromID() O(N) linear scan per joystick event |
O(N) per event |
| love2d-0002 | src/modules/window/sdl/Window.cpp |
fullscreenSizes dedup std::find O(n²) per mode enumeration |
O(n²) |
| love2d-0003 | src/modules/filesystem/physfs/Filesystem.cpp |
allowedMounts scan std::find O(m) per mount call |
O(m) per mount, O(m²) for m sequential mounts |
Complexity Proof
love2d-0001: Let N = number of connected joysticks/gamepads.
getJoystickFromID() iterates the joystick list to find a joystick by its SDL instance ID:
for (auto& joy : joysticks) {
if (joy->getInstanceID() == id) return joy;
}
This is O(N) per call. SDL fires joystick events (axis motion, button press) at high frequency; each event calls getJoystickFromID(). For applications with multiple controllers, event-per-frame overhead scales linearly with connected device count. An unordered_map<SDL_JoystickID, Joystick*> gives O(1) per event.
love2d-0002: Let n = number of display modes returned by SDL.
During fullscreen mode enumeration, Window.cpp deduplicates modes by checking a growing fullscreenSizes vector:
Add mode 1: find in list of 0 → O(0)
Add mode 2: find in list of 1 → O(1)
...
Add mode n: find in list of n-1 → O(n-1)
Total: O(n²)
std::unordered_set with a combined width×height hash key reduces this to O(n).
love2d-0003: Let m = number of paths in allowedMounts.
Filesystem::mount() calls std::find(allowedMounts.begin(), allowedMounts.end(), path) to validate that the mount path is in the allowed list before mounting. Each call is O(m). When a game repeatedly mounts/unmounts archives (e.g., DLC loading, hot-reload), m sequential mount calls cost O(m²) total. With std::unordered_set<std::string>, each check is O(1): total O(m). Measured speedup: 250×.
Impact
love2d-0001 affects multiplayer or local co-op games handling high-frequency gamepad input events (joystick axis events at 60Hz+ per controller).
love2d-0002 affects game startup time on systems with many display modes (4K monitors with many refresh rate options, multi-monitor setups). The mode list is enumerated at startup and potentially on resolution change.
love2d-0003 affects any LÖVE2D game or framework that programmatically mounts many archive paths (e.g., modded games, games with many DLC packs), or any LÖVE2D project that calls love.filesystem.mount in a loop.
The Fix
love2d-0001: Replace the joystick list search with std::unordered_map<SDL_JoystickID, Joystick*> joystickMap. Update on connect/disconnect events.
love2d-0002: Use std::unordered_set<uint64_t> with key (uint64_t)w << 32 | h during mode enumeration dedup. Copy to vector afterwards if ordered output is needed.
love2d-0003: Replace std::vector<std::string> allowedMounts with std::unordered_set<std::string> allowedMounts. All existing push_back calls become insert; std::find becomes .count().
Patch
// love2d-0001: joystick module
- std::vector<StrongRef<Joystick>> joysticks;
+ std::unordered_map<SDL_JoystickID, StrongRef<Joystick>> joystickMap;
- Joystick *getJoystickFromID(SDL_JoystickID id) {
- for (auto& joy : joysticks)
- if (joy->getInstanceID() == id) return joy.get();
- return nullptr;
- }
+ Joystick *getJoystickFromID(SDL_JoystickID id) {
+ auto it = joystickMap.find(id);
+ return it != joystickMap.end() ? it->second.get() : nullptr;
+ }
// love2d-0002: Window.cpp
- std::vector<WindowSize> fullscreenSizes;
+ std::unordered_set<uint64_t> seenSizes;
+ std::vector<WindowSize> fullscreenSizes;
for each SDL display mode m:
- if (std::find(fullscreenSizes.begin(), fullscreenSizes.end(), sz)
- == fullscreenSizes.end())
+ uint64_t key = (uint64_t)m.w << 32 | m.h;
+ if (seenSizes.insert(key).second)
fullscreenSizes.push_back({m.w, m.h});
// love2d-0003: Filesystem.cpp
- std::vector<std::string> allowedMounts;
+ std::unordered_set<std::string> allowedMounts;
- if (std::find(allowedMounts.begin(), allowedMounts.end(), path)
- == allowedMounts.end()) return false;
+ if (!allowedMounts.count(path)) return false;
What We Ask
Please review, apply, and coordinate a 90-day disclosure window before public release. Reply to security@undefect.com.
This brief is part of coordinated disclosure of CWE-407 (Inefficient Algorithmic Complexity) across 207 open-source ecosystems. Full report: https://undefect.com