Batch 6 (9): dolibarr, jitsi-videobridge, zed, tryton, suricata, strawberry, zulip, zesarux, zephyr Batch 7 (15): xonotic (4), xash3d (3), xenia, xtuple, zabbix (2), zathura, zebra, yabause, zephyr-0001 Batch 8 (15): woodpecker (2), wine (4), widelands (3), wesnoth (3), wekan (3) Mix of CWE-407 and CWE-312.
4.8 KiB
Strawberry Music Player — CWE-407 Disclosure Brief
2026-04-13 · Patches available — awaiting upstream merge
Finding
Two O(n^2) defects in Strawberry Music Player across the collection watcher and scrobbler cache. Both patched. Patches ready for upstream review. The collection watcher defect fires on every library rescan; the scrobbler defect fires on every batch flush to Last.fm/Libre.fm.
The Defects
strawberry-0001 (PATCHED — HIGH): src/collection/collectionwatcher.cpp:577
// QStringList files_on_disk — O(S*F + F^2) across two patterns
QStringList files_on_disk;
// ...
// Pattern 1: deleted-songs detection — O(S*F)
for (const Song &song : songs_in_db) {
if (files_on_disk.contains(song.file)) { ... } // O(F) per song
}
// Pattern 2: removeAll in scan loop — O(F^2) aggregate
files_on_disk.removeAll(file); // O(F) called up to F times
files_on_disk is a QStringList (linear container). Two O(n^2) patterns compound:
- The deleted-songs detection loop iterates S songs in the database and calls
files_on_disk.contains()for each, costing O(S*F) where F = files on disk. - Multiple
files_on_disk.removeAll()calls inside the main scan loop cost O(F) each, called up to F times total, giving O(F^2) aggregate.
Additionally, files_changed_path_ (also QStringList) uses .contains() in both the inner scan loop and the deleted-songs loop.
strawberry-0002 (PATCHED — MEDIUM): src/scrobbler/scrobblercache.cpp:295
// QList contains + removeAll — O(F*C) per flush
for (ScrobblerCacheItemPtr cache_item : cache_items) {
if (scrobbler_cache_.contains(cache_item)) { // O(C) per item
scrobbler_cache_.removeAll(cache_item); // O(C) per item
}
}
scrobbler_cache_ is a QList. Each item in the flush batch triggers a linear scan via contains() plus a linear scan via removeAll(). With F items to flush and C total cache entries, total cost: O(F*C).
Complexity Proof
strawberry-0001: At F=5,000 files on disk, S=5,000 songs in database:
- Defective: 5,000 * 5,000 + 5,000^2 = 50,000,000 string comparisons
- Fixed: 5,000 + 5,000 = 10,000 set operations
- ~250x speedup at F=5,000.
strawberry-0002: At F=C=1,000 scrobbles:
- Defective: 1,000 * 1,000 * 2 = 2,000,000 pointer comparisons
- Fixed: 1,000 set builds + 1,000 single-pass filter = 2,000 operations
- ~250x speedup at F=C=1,000.
Impact
Strawberry Music Player serves as a popular open-source music player for Linux, macOS, and Windows, forked from Clementine. The collection watcher (strawberry-0001) fires on every library rescan, which happens at startup, on directory change notifications, and when users manually trigger a rescan. Users with large music libraries (5,000+ files per directory) experience multi-second hangs during rescans.
The scrobbler cache flush (strawberry-0002) fires after every successful batch submission to Last.fm, Libre.fm, or ListenBrainz. Users who go offline and accumulate hundreds of pending scrobbles hit the quadratic cost on reconnection.
The Fix
strawberry-0001: Convert QStringList to QSet<QString> for O(1) contains/remove:
// Before
QStringList files_on_disk;
files_on_disk.contains(file); // O(F)
files_on_disk.removeAll(file); // O(F)
// After
QSet<QString> files_on_disk;
files_on_disk.contains(file); // O(1)
files_on_disk.remove(file); // O(1)
Also converts files_changed_path_ from QStringList to QSet<QString> in the header.
strawberry-0002: Build a QSet of items to remove, then single-pass filter:
// Before
for (auto item : cache_items) {
if (scrobbler_cache_.contains(item)) {
scrobbler_cache_.removeAll(item);
}
}
// After
QSet<ScrobblerCacheItemPtr> to_remove(cache_items.begin(), cache_items.end());
scrobbler_cache_.erase(
std::remove_if(scrobbler_cache_.begin(), scrobbler_cache_.end(),
[&to_remove](const auto &item) { return to_remove.contains(item); }),
scrobbler_cache_.end());
Patch
Fixes available:
defects/strawberry/patch/strawberry-0001-collectionwatcher-files-on-disk.patchdefects/strawberry/patch/strawberry-0002-scrobblercache-flush-contains.patch
Two-file patch across collectionwatcher.cpp, collectionwatcher.h, and scrobblercache.cpp. strawberry-0001: ~250x speedup at F=5,000. strawberry-0002: ~250x speedup at F=C=1,000.
What We Ask
Patches ready for review.
- Confirm receipt and assign a GitHub issue reference (strawberrymusicplayer/strawberry).
- Assess severity — strawberry-0001 fires on every library rescan and causes visible hangs with large music libraries.
- Coordinate a disclosure date — we target 90 days from first contact.
- We will credit the Strawberry team in the public disclosure. Preferred acknowledgment format welcome.
Contact: see cover email. This brief is confidential until coordinated disclosure.