Skip to content

Concurrent NSFont creation crashes inside libfontconfig via the backend #239

Open

Description

@danjboyd

Summary

Creating NSFont instances concurrently from several threads crashes inside libfontconfig, reached from libgnustep-back. A short program whose worker threads do nothing but call +[NSFont fontWithName:size:] crashes on every run.

On macOS, NSFont instances are immutable and font lookup is safe from any thread, so code written against Cocoa crashes under GNUstep.

Environment

  • GNUstep Base 1.31, GUI 0.32, Back 0.32 (X11 backend)
  • libs-back at 45eeba4, libs-gui at f03c34410
  • clang / libobjc2 / libdispatch
  • Debian 13, kernel 6.12.101, fontconfig 2.15.0-2.3, x86_64

Reproducer

// clang `gnustep-config --objc-flags` fontrace.m -o fontrace \
//       `gnustep-config --gui-libs` -ldispatch
#import <AppKit/AppKit.h>
#import <dispatch/dispatch.h>
#include <stdio.h>

int main(void)
{
    [NSAutoreleasePool new];
    [NSApplication sharedApplication];   // bring the backend up

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    int task = 0;
    for (; task < 200; task++) {
        dispatch_group_async(group, queue, ^{
            @autoreleasepool {
                int i = 0;
                for (; i < 60; i++) {
                    NSFont *font = [NSFont fontWithName: @"Helvetica"
                                                   size: 8.0 + (CGFloat)(i % 24)];
                    (void)[font pointSize];
                }
            }
        });
    }

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    printf("completed without crashing\n");
    return 0;
}

10 crashes in 10 runs, split between SIGSEGV and SIGABRT with glibc reporting malloc_consolidate(): unaligned fastbin chunk detected.

Backtrace

Thread 18 "oot.default-qos" received signal SIGSEGV, Segmentation fault.
#0  libfontconfig.so.1
#1  libfontconfig.so.1
#2  libgnustep-back-032.bundle
#3  libgnustep-back-032.bundle
#4  libgnustep-back-032.bundle
#5  libgnustep-back-032.bundle

Narrowing

Two control variants isolate which path is unsafe:

Variant Result
As above (concurrent create and release) 10 / 10 crashed
Same, with @synchronized ([NSFont class]) around the font work 0 / 10 crashed
All 24 fonts created and retained on the main thread first, so workers only take the cache-hit path 0 / 10 crashed

The read path is fine. Concurrent font construction is what reaches the backend and crashes.

Impact

Any GNUstep application that measures or renders text off the main thread can crash, and because the failure mode is heap corruption it often surfaces far from its cause — in our case as unrelated crashes elsewhere in a test suite. Rendering a document off the main thread is a natural design: it is CPU-bound, per-document, and touches no UI.

Note

Companion issue: gnustep/libs-gui#932, for the unsynchronized globalFontMap cache in NSFont.m, which shows up as NSMapRemove crashes in the same workload. The two look related but are separately fixable, and locking on the gui side alone might only serialise enough to hide this one rather than fix it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions