-
-
Notifications
You must be signed in to change notification settings - Fork 445
Description
Please confirm the following points:
- This report is NOT about the Android apps in the Play Store
- I have searched the project page to check if the issue was already reported
Affected Project
libprojectM (including the playlist library)
Affected Version
4.1.6
Operating Systems and Architectures
Don't know, other or not relevant
Build Tools
Compiler: GNU GCC, Build Tool: GNU Make
Additional Project, OS and Toolset Details
- Void Linux
- Kernel 6.12.66_1
- g++ (GCC) 14.2.1 20250405
- GNU Make 4.4.1
- SDL2: 2.32.10
- libpulse: 16.1
- OpenGL: 4.6 (Compatibility Profile) Mesa 25.3.3
Type of Defect
Crash (unhandled exceptions, segmentation faults)
Log Output
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00007fdb17fee750 in libprojectM::Renderer::Texture::Empty() const ()
#1 0x00007fdb17fb5c75 in libprojectM::MilkdropPreset::MilkdropShader::LoadVariables(...)
#2 0x00007fdb17fadbcd in libprojectM::MilkdropPreset::FinalComposite::Draw(...)
#3 0x00007fdb17fb07e1 in libprojectM::MilkdropPreset::MilkdropPreset::RenderFrame(...)
#4 0x00007fdb18060524 in libprojectM::ProjectM::RenderFrame()Describe the Issue
libprojectM crashes with SIGSEGV when loading a preset that contains a commented-out sampler declaration in its composite shader. The sampler declaration parser does not respect comment syntax (// or /* */), causing it to attempt loading a texture that doesn't exist.
The crash is triggered by a preset from the official presets-cream-of-the-crop repository, so this affects end users.
Steps to Reproduce
-
Download the preset: suksma - gapes to spelunkcide - dada hate art isn't art.milk
-
Load and render the preset using any libprojectM application with no random textures configured (i.e., no texture search paths set, or empty texture directories). If random textures are available, the bug may be masked because
GetRandomTexture()succeeds. -
The application crashes with SIGSEGV on the first frame render
Backtrace
#0 libprojectM::Renderer::Texture::Empty() const ()
#1 libprojectM::MilkdropPreset::MilkdropShader::LoadVariables(...)
#2 libprojectM::MilkdropPreset::FinalComposite::Draw(...)
#3 libprojectM::MilkdropPreset::MilkdropPreset::RenderFrame(...)
#4 libprojectM::ProjectM::RenderFrame()
Root Cause Analysis
The preset's composite shader contains this commented line:
comp_1=`//sampler sampler_rand00; // this will choose a random texture from disk!
Bug Location 1: Missing comment handling in sampler parsing
File: src/libprojectM/MilkdropPreset/MilkdropShader.cpp, function MilkdropShader::GetReferencedSamplers() (defined at line 487, problematic code at lines 496-512)
// Search for sampler usage
auto found = program.find("sampler_", 0);
while (found != std::string::npos)
{
found += 8;
size_t const end = program.find_first_of(" ;,\n\r)", found);
if (end != std::string::npos)
{
std::string const sampler = program.substr(...);
if (sampler != "state")
{
m_samplerNames.insert(sampler);
}
}
found = program.find("sampler_", found);
}Problem: Uses simple find("sampler_") which does not respect comments (// or /* */). A commented line like //sampler sampler_rand00; or /* sampler_rand00 */ will match and extract rand00 as a sampler name.
Bug Location 2: Null pointer dereference in Empty()
File: src/libprojectM/Renderer/TextureSamplerDescriptor.cpp, line 23
auto TextureSamplerDescriptor::Empty() const -> bool
{
return m_texture->Empty(); // <-- CRASH: m_texture can be nullptr!
}Problem: No null check before dereferencing m_texture. When GetRandomTexture() returns an empty descriptor (no textures available), m_texture is nullptr. Note that the method's docstring in the header file (lines 47-50) states it should return "false if at least one is invalid or nullptr" - the current implementation contradicts this documented contract.
Call Path Leading to Crash
- Preset contains comp_1=`//sampler sampler_rand00;
MilkdropShader::GetReferencedSamplers()(line 487) findssampler_rand00(ignoring the//comment)LoadTexturesAndCompile()seesrand00and callsGetRandomTexture("rand00")GetRandomTexture()returns empty descriptor{}(no textures found)- Empty descriptor (with
m_texture = nullptr) is added tom_textureSamplerDescriptors - During
LoadVariables(), line 318:if (desc.Empty())callsTextureSamplerDescriptor::Empty() Empty()doesm_texture->Empty()with nullm_texture-> SIGSEGV
Proof: Removing this single commented line (changing comp_1 to an empty line) completely fixes the crash.
Suggested Fixes
Fix 1 (Primary): Skip comments when parsing samplers
In MilkdropShader.cpp, modify the GetReferencedSamplers() function's sampler search loop to check for comments. The example below shows // comment handling; a complete fix should also handle /* */ block comments:
auto found = program.find("sampler_", 0);
while (found != std::string::npos)
{
// Check if this is inside a // comment
size_t lineStart = program.rfind('\n', found);
if (lineStart == std::string::npos) lineStart = 0;
else lineStart++; // Skip the newline character
size_t commentPos = program.find("//", lineStart);
if (commentPos != std::string::npos && commentPos < found)
{
// Inside a comment, skip to next occurrence
found = program.find("sampler_", found + 1);
continue;
}
// ... rest of existing sampler extraction code
}Fix 2 (Defensive): Add null check in Empty()
In TextureSamplerDescriptor.cpp, add null check:
auto TextureSamplerDescriptor::Empty() const -> bool
{
return !m_texture || m_texture->Empty();
}Both fixes should be applied - Fix 1 addresses the root cause, Fix 2 provides defensive programming against other potential null texture scenarios.
Workaround
Users can fix affected presets by removing or modifying commented sampler declarations:
- Delete the line entirely
- Rename the sampler to break the pattern (e.g., change
sampler_rand00toxsampler_rand00ordisabled_sampler_rand00)
Note: Adding a space after // (e.g., // sampler instead of //sampler) does NOT work - the parser matches sampler_rand00 regardless of preceding characters.
Additional Context
- The preset loads successfully -
projectm_load_preset_file()returns without error - The
preset_switch_failed_event_callbackis NOT triggered - The crash occurs on the first call to
projectm_opengl_render_frame() - This preset is from the official presets-cream-of-the-crop collection
- Any preset with a commented
samplerdeclaration will crash if no matching textures are available - Two code paths in
GetRandomTexture()can return an empty descriptor: (1) when no texture files are scanned at all, or (2) when a prefix filter is specified but no matching files exist. Both paths lead to the same crash. - The
Empty()method's docstring (TextureSamplerDescriptor.hpp lines 47-50) states it should return "false if at least one is invalid or nullptr" - but the implementation crashes instead of returning false, indicating the current behavior contradicts the documented intent. - The
texsize_parsing code (lines 516-529 in the same function) uses an identical pattern and has the same vulnerability - commentedtexsize_declarations would also be incorrectly parsed.
Final note:
This bug was found and the bug report written with the help of Claude Opus 4.5. I hope you all don't mind, and I hope the analysis is correct. I don't know C++ and couldn't have figured out the problem or described it adequately without Claude's help. I hope this is not a red herring and is a real bug and a useful bug report.
I wish you all the best and am really enjoying projectm!
Thank you for your hard work!