From 517107d741ef77b3c9b4698437d6f892b5d2a55f Mon Sep 17 00:00:00 2001 From: Evgeny Demchenko Date: Thu, 21 May 2026 22:27:39 +0100 Subject: [PATCH] =?UTF-8?q?libcuframes:=20fix=20TOCTOU=20race=20=D0=B2=20c?= =?UTF-8?q?onsumer=20slot=20read?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: producer signals **один global** cudaEvent для всего ring (один на producer). Consumer waits этот event после slot_seq validation, но event соответствует ПОСЛЕДНЕМУ published frame, не slot[target_seq]. Если producer wrap'нет ring во время event wait (ring=6 = 240ms окно), slot содержит уже next-gen data, consumer возвращает torn/stale frame. Симптом в production: video stream показывает «back-jump на момент» periodically — camera OSD timestamp дёргается, motion machines briefly teleport назад. cluster md5 analysis НЕ ловит (содержимое frames всё ещё unique, просто из неправильной epoch). Fix: post-sync verify. После cudaStreamWaitEvent / cudaEventSynchronize re-check slots[slot_idx].seq == target_seq. Если producer перезаписал — continue outer loop с новым target_seq. Закрывает race window между slot validation и event sync return. Остаются открытыми: - downstream GPU access после frame fill (consumer-side) — producer может wrap во время этого. Mitigation: STRICT_WAIT policy в publisher + ack discipline в consumer (cuframes_release_frame ack уже works). - bigger ring size снижает wrap frequency (240ms → 1.2s при ring=30). Test: после deploy в cuda-grid-pipeline (Phase 7 single cam), camera OSD clock больше не дёргается (раньше дёргалось каждые ~16 sec). Co-Authored-By: Claude Opus 4.7 --- libcuframes/src/consumer.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libcuframes/src/consumer.c b/libcuframes/src/consumer.c index 02f46c1..595cba4 100644 --- a/libcuframes/src/consumer.c +++ b/libcuframes/src/consumer.c @@ -290,6 +290,17 @@ int cuframes_subscriber_next(cuframes_subscriber_t *sub, if (cerr != cudaSuccess) return CUFRAMES_ERR_CUDA; } + /* TOCTOU защита: producer_event signal'ит для последнего published + * frame, не per-slot. Если producer wrapped ring пока мы ждали + * event sync, slot[slot_idx] уже содержит DIFFERENT seq. + * Re-verify slot_seq — если изменился, retry с новым target_seq. */ + uint64_t verify_seq = atomic_load_explicit(&sub->hdr->slots[slot_idx].seq, + memory_order_acquire); + if (verify_seq != target_seq) { + /* Slot overwritten во время event wait — outer loop пересчитает */ + continue; + } + /* Fill frame_out */ struct cuframes_frame *f = &sub->frame_obj; f->cuda_ptr = sub->mapped_ptrs[slot_idx];