Fixed the heap-use-after-free, root cause: system fallback loading published a font under desc.family, replacing and destroying an existing same-family font while its getGlyph() call was still executing.

This commit is contained in:
Martín Lucas Golini
2026-07-30 11:21:09 -03:00
parent 1dc8195187
commit 9f65377c50
3 changed files with 35 additions and 4 deletions
+6 -1
View File
@@ -147,8 +147,13 @@ FontTrueType* FontService::getOrLoadSystemFallbackFont( const FontDesc& desc ) {
return ttf;
}
// System fallbacks are implementation resources, not family-name bindings. Publishing one
// under desc.family could replace the font whose getGlyph() call requested the fallback and
// destroy that font while it is still executing.
std::string resourceName =
"@system-fallback/" + desc.path + "#" + std::to_string( desc.faceIndex );
FontTrueTypePtr ttf =
FontTrueType::New( desc.family, desc.path, desc.faceIndex, mResourceScope );
FontTrueType::New( resourceName, desc.path, desc.faceIndex, mResourceScope );
if ( !ttf || !ttf->loaded() ) {
if ( ttf )
mResourceScope.eraseLocalFont( ttf.get() );
@@ -19,6 +19,7 @@
#include <eepp/graphics/shaderprogramregistry.hpp>
#include <eepp/graphics/sprite.hpp>
#include <eepp/graphics/statelistdrawable.hpp>
#include <eepp/graphics/systemfontresolver.hpp>
#include <eepp/graphics/textlayout.hpp>
#include <eepp/graphics/textureatlas.hpp>
#include <eepp/graphics/textureatlasloader.hpp>
@@ -664,6 +665,30 @@ UTEST( ResourcePrerequisites, distinctFallbackResourcesRemainInFallbackChain ) {
Engine::destroySingleton();
}
UTEST( ResourcePrerequisites, systemFallbackDoesNotReplaceMatchingFamilyBinding ) {
EE::Window::Window* window = createLifecycleTestWindow( "System fallback ownership test" );
ResourceScopePtr scope = ResourceScope::New();
const std::string fontPath = Sys::getProcessPath() + "assets/fonts/NotoNaskhArabic-Regular.ttf";
FontTrueTypePtr familyFont = FontTrueType::New( "Noto Naskh Arabic", fontPath, *scope );
ASSERT_TRUE( familyFont && familyFont->loaded() );
FontTrueTypeWeakPtr familyFontWeak = familyFont;
FontDesc desc;
desc.family = "Noto Naskh Arabic";
desc.path = fontPath;
FontTrueType* fallback = scope->getFontService().getOrLoadSystemFallbackFont( desc );
ASSERT_TRUE( fallback != nullptr );
EXPECT_NE( familyFont.get(), fallback );
EXPECT_EQ( familyFont.get(), scope->findFont( desc.family ).get() );
familyFont.reset();
EXPECT_FALSE( familyFontWeak.expired() );
scope.reset();
EXPECT_TRUE( familyFontWeak.expired() );
window->display( false );
Engine::destroySingleton();
}
UTEST( ResourcePrerequisites, fontFactoriesPublishIntoExplicitScope ) {
EE::Window::Window* window = createLifecycleTestWindow( "Scoped font factories test" );
auto scope = ResourceScope::New();