darktable-org / darktable-org/darktable
`exposure`: `deflicker_histogram` is freed by the GTK thread while a pipe thread walks it
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 13.1k
- Forks
- 1.4k
- Avg merge
- 22h 14m
- Merged PRs (30d)
- 198
Description
Found by bots via code analysis, 2026-08-24.
**Severity: a freed pointer and a freed buffer
Summary
dt_iop_exposure_gui_data_t caches the source image's raw histogram for deflicker mode:
uint32_t *deflicker_histogram; // src/iop/exposure.c:85
dt_dev_histogram_stats_t deflicker_histogram_stats; // :86
The GTK thread frees and reallocates it. A pipe thread reads the pointer, the buffer
contents and the stats tuple. Neither side takes a lock.
The two sides
GTK thread — frees and reallocates:
gui_update()—dt_free_align+NULL(:724-725), then
_deflicker_prepare_histogram()(:737)gui_changed()on a mode switch —:998-999, then:1015gui_cleanup()—:1362
Pipe thread — reads: _process_common_setup() → _compute_correction()
(:486-493), which dereferences the buffer in its percentile scan and reads
histogram_stats->pixels and ->bins_count.
gui_changed() frees before dt_dev_add_history_item() invalidates anything, so a pipe
already inside process() with d->deflicker == 1 can be walking the buffer as it is
freed.
Why a lock around the pointer load is not enough
Taking gui_lock only to load the pointer leaves the buffer free to be released while the
reader walks it. This is the Short Is Not The Same As Correct case in
dev-doc/GUI_Threading.md. deflicker_histogram_stats is the matching tuple problem:
bins_count and pixels are read while the GTK thread rewrites them, so they can be
mutually inconsistent even if each is individually intact.
Suggested fix
Needs a design choice, not a one-liner. Options, roughly in order of preference:
- Move the histogram out of
gui_dataentirely — it is source-image data, not GUI
state. The non-GUI branch of_process_common_setup()(:495-502) already builds and
frees its own copy; making the GUI path do the same removes the sharing. Costs a
rebuild per pipe run, which is why it was cached in the first place — measure before
assuming that matters. - Refcount or version the allocation, so a reader holds it alive for the duration of
_compute_correction(). - Hold
gui_lockacross the whole of_compute_correction()— correct, but it
blocks the GTK thread for the length of a 65536-bin scan.
Interacts with defect 2 in #21974 (the NULL
histogram producing a black frame): the transient half of that defect's reachability is
this race. Fix them in the same change if convenient; the black-frame fix is independent
and can land first.
Reachability
Deflicker mode only — raw image, buf_dsc.channels == 1, datatype == TYPE_UINT16
(commit_params(), src/iop/exposure.c:646-650). Reached through the "magic lantern
defaults" preset (:312-322) or by switching the mode combo manually.
Environment
Present in 56898c27b8. Static analysis only — nothing verified at runtime.
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.
Research direction
Read dev-doc/GUI_Threading.md and inspect src/iop/exposure.c at gui_update(), gui_changed(), gui_cleanup(), _process_common_setup(), and _compute_correction(). Trace deflicker_histogram and deflicker_histogram_stats across GTK and pipe threads, then verify the chosen design removes use-after-free and inconsistent stats access in deflicker mode.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100