Hi,
I would like to report a heap buffer overrun in klippy/chelper/stepcompress.c, reproducible on unmodified upstream master (v0.13.0-820-g0cee841e3) in batch mode.
Background
We build the RF50, a Klipper-based 3D printer, and test configuration changes by running full prints through batch mode in CI. A configuration with an extruder stepper synced to the extruder started crashing with SIGSEGV mid-print. I reproduced it on plain upstream with a standard [extruder_stepper]; the attached files come from that reproduction.
The bug
In queue_append_far(), when the step is not far enough out for stepcompress_flush_far(), it is stored with sc->queue_next->clock32 = step_clock; sc->queue_next++; and there is no queue_next >= queue_end check. queue_append() tests for a far step before it tests for a full queue, so a full queue plus a far step writes one slot past the end of the buffer.
In batch mode, _flush_handler_debug() can make a single step generation call cover tens of seconds (25.5 s in this reproduction). Once a stepper has CLOCK_DIFF_MAX of pending steps, every append takes the far path, which never grows the queue, until it overflows. I have not seen this outside batch mode.
Steps to reproduce (files attached)
cp test/configs/stm32h723.config .config && make olddefconfig && makevalgrind --error-limit=no python klippy/klippy.py repro2.cfg -i repro2.gcode -o out.serial -v -d out/klipper.dict -l klippy.log
repro2.cfg is a cartesian printer with an extruder, a synced [extruder_stepper gear] and a [gcode_macro CUBE] holding about 980 moves from a small cube; repro2.gcode is G28, M83, CUBE. The run exits 0, but valgrind reports:
Invalid write of size 4
at 0x8F597A7: UnknownInlinedFun (stepcompress.c:441)
by 0x8F597A7: queue_append (stepcompress.c:495)
by 0x8F59C31: UnknownInlinedFun (stepcompress.c:526)
by 0x8F59C31: itersolve_gen_steps_range (itersolve.c:108)
...
Address 0x88d9770 is 0 bytes after a block of size 4,096 alloc'd
at 0x4EDE250: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
by 0x8F598C0: UnknownInlinedFun (stepcompress.c:472)
stepcompress.c:441 is the store in queue_append_far() and :472 is the realloc() in queue_append_extend(). ERROR SUMMARY: 3 errors from 2 contexts.
Possible fix
Fall back to queue_append_extend() when the queue is full (valgrind reports 0 errors with this applied):
+static int queue_append_extend(struct stepcompress *sc);
+
// Slow path for queue_append() - handle next step far in future
static int
queue_append_far(struct stepcompress *sc)
@@
if (step_clock >= sc->last_step_clock + CLOCK_DIFF_MAX)
return stepcompress_flush_far(sc, step_clock);
+ if (sc->queue_next >= sc->queue_end) {
+ // Queue full: grow/shuffle it instead of writing past queue_end
+ sc->next_step_clock = step_clock;
+ return queue_append_extend(sc);
+ }
sc->queue_next->clock32 = step_clock;
sc->queue_next++;
return 0;
I am happy to submit this as a pull request.
Disclosure:
I used an AI coding assistant (Claude Code) to help investigate and draft this post. The attached klippy.log and valgrind output are unedited output from real runs on unmodified upstream.
klipper-queue_append_far-report.zip (11.6 KB)
Attached: klippy.log, repro2.cfg, repro2.gcode, valgrind output, patch.
Best Regards.
Guilherme Costa
Blocks Software Developer