darktable-org / darktable-org/darktable
zonesystem can paint a Cairo surface larger than the buffer behind it
@kofa73 is already working on this.
Since Aug 19, 2026.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.