darktable-org / darktable-org/darktable

zonesystem can paint a Cairo surface larger than the buffer behind it

Open
#21,916 0 comments 0 reactions 1 assignee View on GitHub

@kofa73 is already working on this.

Since Aug 19, 2026.

scope: UI
Dominant language
C
Stars
13.1k
Forks
1.4k
Avg merge
22h 14m
Merged PRs (30d)
198

Description

Is there an existing issue for this?
  • I checked and did not find my issue in the already reported ones
Describe the bug

Found by Claude + Codex via code analysis.

Description

The zone-preview drawing callback of zonesystem runs on the GTK thread and builds a
small image from the preview buffers that the pixelpipe fills. It allocates that image
inside a critical section, using g->preview_width and g->preview_height, then leaves
the critical section and reads the same two fields again to tell Cairo how large the
image is. Between the two reads the preview pipe can run with a different output size,
free both buffers and store new, larger dimensions. Cairo is then given a size that does
not match the allocation it was handed, and reads past the end of it while painting. The
consequence is an out-of-bounds read: garbage in the preview widget at best, a crash if
the memory after the allocation is not mapped.

Evidence

All code is in src/iop/zonesystem.c.

The pipe side, in process_common_setup(), replaces the buffers and the dimensions
together, under the lock:

    dt_iop_gui_enter_critical_section(self);
    if(g->in_preview_buffer == NULL || g->out_preview_buffer == NULL || g->preview_width != width
       || g->preview_height != height)
    {
      g_free(g->in_preview_buffer);
      g_free(g->out_preview_buffer);
      g->in_preview_buffer = g_malloc_n((size_t)width * height, sizeof(guchar));
      g->out_preview_buffer = g_malloc_n((size_t)width * height, sizeof(guchar));
      g->preview_width = width;
      g->preview_height = height;
    }
    dt_iop_gui_leave_critical_section(self);

The GTK side, in dt_iop_zonesystem_preview_draw(), reads them twice, and the second
read is outside the lock:

  dt_iop_gui_enter_critical_section(self);
  if(g->in_preview_buffer && g->out_preview_buffer && self->enabled)
  {
    ...
    guchar *image = g_malloc_n((size_t)4 * g->preview_width * g->preview_height, sizeof(guchar));
    ...
    dt_iop_gui_leave_critical_section(self);

    const int wd = g->preview_width, ht = g->preview_height;   // re-read, no lock
    ...
    cairo_surface_t *surface = cairo_image_surface_create_for_data(image, CAIRO_FORMAT_RGB24, wd, ht, stride);
    ...
    cairo_fill_preserve(cr);                                   // reads past `image`

image is sized from the values seen inside the lock; wd, ht and stride describe
the surface from the values seen after it. If the preview run landed in between and the
new size is larger, the surface is larger than image.

Suggested fix

Read the two dimensions into local variables inside the critical section that is already
there, and use only those locals afterwards:

    const int wd = g->preview_width, ht = g->preview_height;
    guchar *image = g_malloc_n((size_t)4 * wd * ht, sizeof(guchar));
    ...
    dt_iop_gui_leave_critical_section(self);

The pixel loop above the unlock should use the same locals, so that the allocation, the
loop and the Cairo surface all describe one consistent size. No new lock is needed, and
the critical section does not have to grow.

Steps to reproduce

no known crash reported so far, but:
"The trigger is an ordinary one: anything that changes the preview output size while the
module's preview widget is on screen, such as resizing the darkroom window or changing
the zoom."

Expected behavior

No response

Logfile | Screenshot | Screencast

No response

Commit

No response

Where did you obtain darktable from?

self compiled

darktable version

6783f7c4

What OS are you using?

Linux

What is the version of your OS?

Ubuntu 26.04

Describe your system

No response

Are you using OpenCL GPU in darktable?

None

If yes, what is the GPU card and driver?

No response

Please provide additional context if applicable. You can attach files too, but might need to rename to .txt or .zip

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.