# 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:

```cpp
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

```diff
// 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*
