amarok, arrow, audacity, cargo, clementine, composer, dask, deluge, dosbox-x, dragonfly. All CWE-407.
4.5 KiB
Clementine — CWE-407 Disclosure Brief
2026-04-13 · Patch available — awaiting upstream merge
Finding
Two O(n²) defects in Clementine's library scanner and network remote song sender. Both patched. Patches ready for upstream review. The first defect site contains a // TODO: Make this faster comment acknowledging the performance problem.
The Defects
clementine-0001 (PATCHED — HIGH): src/library/librarywatcher.cpp:358
// In ScanSubdirectory() — fires during every library scan:
// TODO: Make this faster
Song matching_song;
if (FindSongByPath(songs_in_db, file, &matching_song)) { ... }
// FindSongByPath is O(S) linear scan per file
// Also at line 437:
if (!files_on_disk.contains(song.url().toLocalFile())) { ... }
// QStringList::contains() is O(F) per song
Two O(N²) patterns in the same function: FindSongByPath linearly scans songs_in_db for every file on disk — O(F×S). Then files_on_disk.contains() linearly scans the QStringList for every song in the database — O(S×F). Both fire during every library scan.
clementine-0002 (PATCHED — MEDIUM): src/networkremote/songsender.cpp:321
// In SendAlbum() — fires when remote client requests album download:
for (Song s : album) {
DownloadItem item(s, album.indexOf(s) + 1, album.size()); // O(N) per song
download_queue_.append(item);
}
QList::indexOf(s) is O(N) per song in three methods: SendAlbum(), SendPlaylist(), and SendUrls(). SendPlaylist() also calls requested_ids.contains(s.id()) with O(R) per song. Total cost across all three: O(N²).
Complexity Proof
clementine-0001: At F=S=500 (typical music directory):
- Defective: 500 × 500 = 250,000 comparisons (FindSongByPath) + 500 × 500 = 250,000 (contains)
- Fixed: 500 × O(1) = 500 lookups each (QHash + QSet)
- ~250× op reduction per library scan.
clementine-0002: At N=500 songs:
- Defective: 500 × 250 (avg) = 125,000 comparisons per send operation
- Fixed: 500 × O(1) = 500 (integer counter replaces indexOf)
- ~250× op reduction per remote download.
Impact
Clementine is a popular cross-platform music player forked from Amarok, with a large user base on Linux, macOS, and Windows. clementine-0001 fires on every library scan — initial imports, directory watches, and manual rescans all trigger ScanSubdirectory(). Users with large music collections (thousands of files per directory) experience slow scans. The source code itself acknowledges the problem with a TODO comment.
clementine-0002 fires when Clementine's Android remote client requests album, playlist, or URL downloads. Large playlists trigger quadratic indexOf lookups for every song sent.
The Fix
clementine-0001: Build QHash<QString, Song> for path lookup and QSet<QString> for file-on-disk membership:
// Before
if (FindSongByPath(songs_in_db, file, &matching_song)) { ... }
if (!files_on_disk.contains(song.url().toLocalFile())) { ... }
// After
// CWE-407 fix: QHash for O(1) song-by-path lookup (was O(S) linear scan per file).
QHash<QString, Song> songs_by_path;
for (const Song& song : songs_in_db) songs_by_path.insert(song.url().toLocalFile(), song);
auto it = songs_by_path.find(file);
// CWE-407 fix: QSet for O(1) contains instead of O(F) QStringList scan.
QSet<QString> files_on_disk_set;
if (!files_on_disk_set.contains(song.url().toLocalFile())) { ... }
clementine-0002: Replace indexOf with integer counter; convert requested_ids to QSet:
// Before
DownloadItem item(s, album.indexOf(s) + 1, album.size());
// After
// CWE-407 fix: counter replaces O(N) indexOf.
int pos = 0;
for (const Song& s : album) {
DownloadItem item(s, ++pos, album.size());
}
Patch
Fix available: defects/clementine/patch/clementine-0001-librarywatcher-scan-quadratic.patch and defects/clementine/patch/clementine-0002-songsender-indexof-quadratic.patch
Two-file patch across librarywatcher.cpp and songsender.cpp.
clementine-0001: ~250× speedup at F=S=500. clementine-0002: ~250× speedup at N=500.
What We Ask
A patch is ready for review.
- Confirm receipt and assign an issue reference (clementine-player/Clementine).
- Assess severity — clementine-0001 fires on every library scan and the source code acknowledges the performance problem with a TODO comment.
- Coordinate a disclosure date — we are targeting 90 days from first contact.
- We will credit the Clementine team in the public disclosure. Preferred acknowledgment format welcome.
Contact: see cover email. This brief is confidential until coordinated disclosure.