[Bug] Overlapping memcpy in juce::EdgeTable on Linux

Hi!

When I’m running my JUCE app on Linux with AddressSanitizer, I sporadically get a memory error due to an overlapping memcpy in juce::EdgeTable. I’m using JUCE v7.0.5, but it looks like this hasn’t been resolved in later JUCE versions.

Unfortunately I’m unable to provide steps for reproducing this bug, I can’t find a clear pattern of when this happens. However, I do have a backtrace:

==26402==ERROR: AddressSanitizer: memcpy-param-overlap: memory ranges [0x62a0005f9548,0x62a0005f9640) and [0x62a0005f9454, 0x62a0005f954c) overlap
    #0 0x7ffff7848d29 in __interceptor_memcpy (/usr/lib/libasan.so.8+0x48d29)
    #1 0x555556660d9e in juce::EdgeTable::intersectWithEdgeTableLine(int, int const*) /home/void/plugdata/Libraries/JUCE/modules/juce_graphics/geometry/juce_EdgeTable.cpp:612
    #2 0x555556663445 in juce::EdgeTable::clipLineToMask(int, int, unsigned char const*, int, int) /home/void/plugdata/Libraries/JUCE/modules/juce_graphics/geometry/juce_EdgeTable.cpp:816
    #3 0x5555567f892b in juce::RenderingHelpers::EdgeTableFillers::ImageFill<juce::PixelAlpha, juce::PixelAlpha, false>::clipEdgeTableLine(juce::EdgeTable&, int, int, int) /home/void/plugdata/Libraries/JUCE/modules/juce_graphics/native/juce_RenderingHelpers.h:885
    #4 0x5555567f892b in void juce::RenderingHelpers::ClipRegions<juce::RenderingHelpers::SoftwareRendererSavedState>::EdgeTableRegion::straightClipImage<juce::PixelAlpha>(juce::Image::BitmapData const&, int, int, juce::PixelAlpha const*) /home/void/plugdata/Libraries/JUCE/modules/juce_graphics/native/juce_RenderingHelpers.h:1804
    #5 0x5555567f892b in juce::RenderingHelpers::ClipRegions<juce::RenderingHelpers::SoftwareRendererSavedState>::EdgeTableRegion::clipToImageAlpha(juce::Image const&, juce::AffineTransform const&, juce::Graphics::ResamplingQuality) /home/void/plugdata/Libraries/JUCE/modules/juce_graphics/native/juce_RenderingHelpers.h:1694
    #6 0x5555567ae3ae in juce::RenderingHelpers::ClipRegions<juce::RenderingHelpers::SoftwareRendererSavedState>::RectangleListRegion::clipToImageAlpha(juce::Image const&, juce::AffineTransform const&, juce::Graphics::ResamplingQuality) /home/void/plugdata/Libraries/JUCE/modules/juce_graphics/native/juce_RenderingHelpers.h:1844
    #7 0x5555567d1c6d in juce::RenderingHelpers::SavedStateBase<juce::RenderingHelpers::SoftwareRendererSavedState>::clipToImageAlpha(juce::Image const&, juce::AffineTransform const&) /home/void/plugdata/Libraries/JUCE/modules/juce_graphics/native/juce_RenderingHelpers.h:2188
    #8 0x5555567d1c6d in juce::RenderingHelpers::StackBasedLowLevelGraphicsContext<juce::RenderingHelpers::SoftwareRendererSavedState>::clipToImageAlpha(juce::Image const&, juce::AffineTransform const&) /home/void/plugdata/Libraries/JUCE/modules/juce_graphics/native/juce_RenderingHelpers.h:2712
    #9 0x55555669000e in juce::Graphics::drawImageTransformed(juce::Image const&, juce::AffineTransform const&, bool) const /home/void/plugdata/Libraries/JUCE/modules/juce_graphics/contexts/juce_GraphicsContext.cpp:845
    #10 0x55555669000e in juce::Graphics::drawImageTransformed(juce::Image const&, juce::AffineTransform const&, bool) const /home/void/plugdata/Libraries/JUCE/modules/juce_graphics/contexts/juce_GraphicsContext.cpp:836
    #11 0x55555669040b in juce::Graphics::drawImageAt(juce::Image const&, int, int, bool) const /home/void/plugdata/Libraries/JUCE/modules/juce_graphics/contexts/juce_GraphicsContext.cpp:803
    #12 0x555555e52d4a in StackShadow::renderDropShadow(juce::Graphics&, juce::Path const&, juce::Colour, int, juce::Point<int>, int, float) /home/void/plugdata/Source/Utility/StackShadow.h:735
    #13 0x555555e632e0 in PlugDataWindow::paint(juce::Graphics&) /home/void/plugdata/Source/Standalone/PlugDataWindow.h:649

I actually have a whole bunch of backtraces from Linux users, all crashing at EdgeTable::intersectWithEdgeTableLine but coming from completely different parts of my code. If I am making a mistake that is causing this crash, I’d be interested to know too, of course.

The crash happens in the memcpy at line 612 in juce::EdgeTable. This issue should be easy to resolve by replacing memcpy with memmove or std::copy_backward, which should both handle overlapping regions correctly.

1 Like

Thanks for your patience. This has been nagging at me for months, but I finally found some time to investigate.

It looks like the overlapping ranges that are flagged only overlap by 4 bytes, or the size of an int, so I think this is caused by an off-by-one error when keeping track of line lengths in the edge table.

I’ve attempted a fix on the JUCE8 branch:

Please try out this version and let us know if you encounter any new issues.

2 Likes

Thank you for looking into this! I never found a reliable way to reproduce it myself, all I had was the asan reports from a frequent contributor to my open source project. I will keep an eye out to see if this will ever be reported from now on, and I’ll use asan as much as possible myself when testing on Linux. Also, very positively surprised you managed to look into this, since I couldn’t provide steps to reproduce.

The final memcpy call in this function requires there to be
"srcNum1 * 2" valid entries after the current "src1" ptr, and none of
those entries may overlap with the area starting at "temp".

On inspection, I think that the memory region being read is too large.
At the point of the call, src1 will point to a LineItem::level, not
LineItem::x, so there will actually be (srcNum1 * 2 - 1) valid items
following it.

This sounds like a plausible cause to me, and the provided solution also makes the code a bit more readable too.

I think the overlapping memcpy by itself has a low chance of causing a crash directly, but it does indicate that the code wasn’t doing what it was supposed to do. So that makes your solution better than what I suggested (using memmove), since that would just make it not crash even though the underlying logic is incorrect.