Data integrity audit using AI (Claude Fable 5 + /effort max + workflow) #75
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Аудит Vitastor: баги, способные привести к повреждению или потере данных
Версия: commit
c16f955a(3.0.13+2), чистое рабочее дерево.Область: весь C/C++ код (
src/,node-binding/), кроме вендоренного кода (json11,cpp-btree,src/liburing,src/util/robin_hood.h) и rust-крейта. JS-монитор (mon/) не рассматривался.Методология: код прочитан целиком пофайлово; каждый кандидат проверялся несколькими независимыми «адверсариальными» проходами, плюс ручная перепроверка по исходникам всех критических и части высоких находок. Отсев ложных срабатываний — приоритет.
Помечено
[проверено вручную]— то, что я лично подтвердил по коду. Помечено[требует подтверждения мейнтейнера]— реальный по механике дефект, но триггер узкий/сложный и я не смог довести трассу до 100%.? Критические (тихая потеря/порча зафиксированных данных)
1.
vitastor-cli modify --resize <меньший размер>удаляет ВСЕ данные образа, а не «хвост»[проверено вручную]Файл:
src/cmd/cli_modify.cpp:110-116→src/cmd/cli_rm_data.cpp:331-334При сжатии образа
image_changer_t::loopвызываетstart_rm_data, передавая ключи через дефис:А
start_rm_dataчитает ключи через подчёркивание:cfg["min_offset"](строка 333),cfg["down_ok"](331). Нормализация-→_(cli_tool_t::parse_config,cli_common.cpp:104-116) применяется только к argv верхнего уровня вcli.cpp:331и не применяется к этому внутреннему JSON. В результатеmin_offsetотсутствует → json11 возвращает null →uint64_value() == 0.При
min_offset==0 && max_offset==0кодrm_inode_tсчитает это «без границы» и удаляет все объекты инода (cli_rm_data.cpp:70-73и условие на:97stripe >= 0 && (!0 || …)всегда истинно). После этогоcfg.sizeуменьшается, транзакция в etcd коммитится, печатается «Image … modified».Триггер: документированная операция —
vitastor-cli modify myimage --resize 500Gна образе 1 ТБ после уменьшения ФС внутри. Также достижимо черезvitastor-nfsblock-mode SETATTR-truncate (nfs_block.cpp:192-199) иvitastor-cli dd conv=truncна существующий бо́льший образ.Последствие: полная тихая потеря всех данных образа при «безопасной» операции сжатия; команда рапортует успех.
Фикс: передавать
min_offset/down_ok(подчёркивание) — как это уже корректно делаетnfs_kv_setattr.cpp:133.2. Удалённое неаутентифицированное переполнение кучи в OSD через
OSD_OP_SHOW_CONFIG[проверено вручную]Файл:
src/client/msgr_receive.cpp:318-327,handle_op_hdrjson_len— 64-битное поле с провода (osd_ops.h:183), не валидируется. Приjson_len = 0xFFFFFFFFFFFFFFFF:json_len+1переполняется в 0 →malloc_or_die(0)отдаёт крошечный буфер; вrecv_listкладётся iovec длиной2^64-1.read_remaining—int(messenger.h:79), становится-1, поэтому op помечается «готовым» через веткуelse, не сбрасываяrecv_list(он был сброшен вhandle_finished_read:217до вызова). На следующей итерацииhandle_read_bufferостаток принятых байт (до ~64 КБ за один recv)memcpy-ится в этот гигантский iovec поверхmalloc(0)-буфера (msgr_receive.cpp:185).Проверка магии в
handle_finished_read:222(SECONDARY_OSD_OP_MAGIC) не требует аутентификации;accept_connectionsпринимает любого TCP-пира. В вашей модели угроз (tenant-VM имеют сетевой доступ к data-портам OSD через QEMU/NBD-драйвер) это критично.Последствие: удалённое переполнение кучи на единственном event-loop OSD → краш/DoS, порча соседних буферов (в т.ч. данных конкурентных записей до сброса на диск), потенциально RCE.
Фикс: валидировать
json_len(и вообще все длины с провода) разумным верхним пределом до аллокации.3. ublk в режиме образа (
--image, дефолт и дефолт CSI): fsync гостя не доходит до OSD[проверено вручную]Файл:
src/client/ublk_server.cpp:285В режиме образа
inodeвсегда0(ublk_server.cpp:196-201), реальный инод — вwatch->cfg.num.get_immediate_commit(0)из-за раннегоif (!pool_id) return true;(cluster_client.cpp:631-632) возвращаетtrue→writeback=false→ вadd_deviceне выставляетсяUBLK_ATTR_VOLATILE_CACHE(ublk_server.cpp:318). Ядро считает устройство write-through и не шлёт FLUSH, поэтому обработчикUBLK_IO_OP_FLUSH → OSD_OP_SYNC(ublk_server.cpp:772-775) никогда не вызывается. То, что соседняя строка 286 уже использует fallbackinode ? inode : watch->cfg.num, доказывает: автор знал проinode==0и забыл применить fallback в строке 285 (предыдущий фикс85c7e3bdчинил полярность этой строки, но пропустил случай образа).Триггер: пул с
immediate_commit=none/small(документированная норма для HDD и SSD без конденсаторов) + том примонтированvitastor-ublk map --image …(или через Kubernetes CSI, где ublk теперь дефолт). Гость делаетfsync()→ возвращает успех, данные не синкнуты на OSD → потеря питания/краш OSD → подтверждённые fsync-ом данные теряются.Последствие: тихая потеря данных, подтверждённых
fsync(), на любом пуле кромеimmediate_commit=all; повреждение журналов гостевых ФС.Фикс:
cli->get_immediate_commit(inode ? inode : watch->cfg.num).4.
vitastor-disk resizeтихо рушит метаданные heap-OSD при области метаданных > 4 ГиБ[проверено вручную]Файл:
src/disk_tool/disk_tool_resize.cpp:565,594,598,616+src/blockstore/blockstore_heap.h:364new_meta_pos—uint64_t(строка 565), ноblockstore_heap_t::fill_block_empty_space(uint8_t*, uint32_t pos)принимаетuint32_t. На вызовах 594/598/616 позиция ≥ 4 ГиБ тихо усекается по модулю 2³². Внутри (blockstore_heap.cpp:2367-2384) функция пишет 4-байтовый маркер конца блока{size, BS_HEAP_FREE_SPACE}поbuffer + (pos % meta_block_size), продвинувbufferна усечённый индекс блока. То есть маркер «дальше блок свободен» для позиции ≥ 2³² попадает в низкий блок метаданных, где уже лежат реальные записи (а при ровно 2³² — в суперблок). Копии самих записей (memcpyна строке 624) используют корректную 64-битную арифметику, поэтому портятся именно маркеры — загрузчик молча считает заполненные низкие блоки пустыми.Триггер: heap-формат (Vitastor 3.0),
meta_area_size > 4 ГиБ— штатно для OSD ≥ 4 ТБ со 128К блоками и crc32c (min_meta_len ~5.8 ГиБ, аvitastor-disk prepareрезервирует 2× минимума). Админ запускаетvitastor-disk resize/raw-resize.Последствие: массовая тихая потеря зафиксированных объектов на resized-OSD (часть блоков загрузчик видит пустыми, без ошибки; либо «Metadata is corrupt»).
Фикс: объявить
posкакuint64_t/size_tвfill_block_empty_space.5. Повторная отправка устаревшего writeback-флаша перезаписывает уже подтверждённые fsync-ом данные
[требует подтверждения мейнтейнера]Файл:
src/client/cluster_client_wb.cpp:300-364,src/client/cluster_client.cpp:196-211Подтверждённая механика: в
calc_waitдва флаш-op (OP_FLUSH_BUFFER) не упорядочиваются друг относительно друга — условие на строке 204 требует!(op->flags & OP_FLUSH_BUFFER). При сбое флашаF1(рестарт OSD,-EPIPE) он паркуется наclient_retry_interval(cluster_client.cpp:1600-1606), при этом захваченный им буфер со старыми данными жив (refcnt вflushed_buffers,op->iovуказывает на него). Свежий перекрывающий флашF2имеетretry_after=0и отправляется первым; после его коммита запоздавшийF1повторно пишет старые данные тем же диапазоном без версии/CAS.Триггер (узкое окно):
client_enable_writeback=true(QEMUcache=writeback, NBD, ublk, NFS); флаш по лимиту буфера без хвостового SYNC; рестарт primary OSD; перезапись того же горячего блока +fsyncв окне ретрая.Последствие: старая версия блока становится durable после успешного
fsyncновой версии (например, commit-блок журнала гостевой ФС) → тихая порча, видимая при последующих чтениях.Фикс: упорядочить повторную отправку флаша относительно более новых перекрывающихся флашей того же диапазона.
? Высокие
Хранилище / OSD
src/blockstore/blockstore_disk.cpp:543-556—trim_dataдискардит ЖИВЫЕ блоки[проверено вручную]. После выравнивания поdiscard_granularityпустой диапазон делаетcontinue, пропускаяj = i+1(строка 556).jостаётся на старом начале пробега, и следующийBLKDISCARDнакрывает промежуточный используемый блок → физическое уничтожение зафиксированных данных (на чтении — нули; с checksum — EIO). Достижимо черезvitastor-disk trimиdiscard_on_start. Фикс: продвигатьjдаже при пустом выровненном диапазоне.src/osd/osd_rmw.cpp:1211-1218— переполнение кучи в скрабе degraded EC-объекта.ec_check_combinationиндексируетtmp_bufпо роли[0, pg_size), ноec_find_good(:1341) выделяетtmp_bufразмеромstripe_count*(chunk_size+bitmap_size), гдеstripe_count= число присутствующих чанков< pg_size. Для EC с ≥2 parity и объектом, потерявшим 1..(parity-1) чанков, восстановление пишетchunk_size(≥128К) байт за пределы аллокации. Последствие: краш OSD при скрабе любого degraded EC-объекта; возможна тихая порча соседней кучи.src/osd/osd_scrub.cpp:630-635— скраб снимаетLOC_CORRUPTEDс всё ещё битых чанков. Массивstripesиндексируется поchunk.roleвместоi, плюс нет проверки!read_error→ для EC ещё и OOB-чтение. Последствие: скраб может пометить реально повреждённый чанк как хороший / неверно «починить».src/osd/osd_primary_chain.cpp:131-148— неверная реконструкция bitmap при цепочечном (snapshot) чтении EC. В качестве источников декодирования используются никогда не запрошенные нулевые bitmap недоступных/незагруженных чанков → неверная карта присутствия данных → неверные результаты чтения снапшот-цепочек.src/blockstore/blockstore_sync.cpp:30-88— sync может быть подтверждён без нужного fsync[требует подтверждения мейнтейнера]. Дедупликация fsync по совпадению fd: при раскладкеdata_fd == journal_fd != meta_fdи наличии незасинканных data-записей без buffer-записей ни data-, ни journal-fsync не выполняется (data-fsync пропущен поdata_fd==journal_fd, journal-fsync — поunsynced_buffer_write_count==0). Последствие: в нестандартной раскладке дисков метаданные становятся durable, ссылаясь на несинхронизированные данные.src/osd/osd_peering_pg.cpp:415-449— эпоха PG может регрессировать[требует подтверждения мейнтейнера].calc_object_statesсбрасываетepoch = 0и пересчитывает только из версий объектов в текущих листингах; персистентнаяreported_epochиз etcd не используется как нижняя граница. Если объекты с наивысшей эпохой пропали из листингов, эпоха падает ниже ранее использованной → теоретически возможна реанимация устаревшей реплики (с более высокой эпохой) поверх подтверждённой записи. Функция помечена самим авторомFIXME: Write at least some tests.src/blockstore/v1/read.cpp:810-817— абсолютный индекс csum-блока в относительный массив (legacy store,csum_block_size > bitmap_granularity). При верификации чтения ещё не сброшенной big-write используется абсолютный индекс блока для доступа к per-write относительному массиву контрольных сумм → ложная «порча» или пропуск реальной порчи.src/blockstore/v1/init.cpp:953-956иsrc/blockstore/blockstore_heap.cpp:584-588— потеря новых записей при torn-write в журнале. (init) Рваная small-write в начале блока журнала оставляет занулённый блок в середине журнала; следующий replay считает это концом журнала и тихо отбрасывает более новые зафиксированные записи. (heap) Стартовая перепроверка torn-write смотрит только на голову цепочки — несинхронизированные small-write под головой ROLLBACK/COMMIT не проверяются.src/blockstore/multilist.cpp:185-197— assert проверяет полный размер области вместо остатка вmultilist_alloc_t::use(), позволяя диапазону зайти в следующую (занятую) область и испортить граничные теги аллокатора.src/osd/osd_primary_sync.cpp:260— очисткаdirty_pgsклиента на завершении sync стирает маркеры записей, подтверждённых уже после снапшота sync, ослабляя механизм drop/replay при репире (потенциальная потеря подтверждённой записи)[требует подтверждения мейнтейнера].Офлайн-инструменты диска (
vitastor-disk) — порча при восстановлении/миграцииsrc/disk_tool/disk_tool_meta.cpp:578-580— roundtripdump-meta | write-metaзануляет ВСЕ контрольные суммы данных[проверено вручную].dump-metaпишет ключ"block_csums"(:500), аwrite-metaчитаетe["data_csum"](:578) — такого ключа нет ни в одном выводе. Per-entry CRC пересчитывается по занулённым байтам, поэтому метаданные «валидны», но затем каждое чтение данных на этом OSD валится по проверке checksum. На OSD с единственной копией (типичная причина ручной хирургии) — данные становятся нечитаемыми.src/disk_tool/disk_tool_journal.cpp:531-582—write-journalсоздаёт невалидный журнал на csum-сторе[частично проверено вручную]. Неверная формула числа csum-блоков(offset+len)/bs - len/bs(верная — в этом же файле на:236-238) + переставлены местами bitmap и контрольные суммы относительно on-disk формата. На старте OSD усекает журнал на первой small-write (теряя все последующие зафиксированные записи), а big-write протаскивают занулённые csum в метаданные.src/disk_tool/disk_tool_resize.cpp:215-217—free_lastокругляет вниз: при сжатии до невыровненногоnew_data_lenпоследний data-блок ссылается наblock_num == block_count(вне диапазона).src/disk_tool/disk_tool_resize_auto.cpp:216-220—resize --journal-sizeна журнале в разделе молча заменяет запрошенный размер на весь раздел; при общем разделе journal+meta (--force) запись журнала затирает область метаданных.Память (удалённо/локально)
src/nfs/nfs_portmap.cpp:134— удалённое переполнение кучи в NFS-портмаппере[проверено вручную].malloc_or_die(sizeof(pmap3_mapping_list*) * N)выделяет 8 байт на элемент вместоsizeof(pmap3_mapping_list)(~56-64), а цикл пишет полные структуры. Достижимо неаутентифицированно (rpcinfo -p, prog=100000/vers=3/proc=4). v2-обработчик (:67) использует корректный sizeof — это опечаткаsizeof(T*)вместоsizeof(T). Фикс:sizeof(pmap3_mapping_list).src/nfs/proto/xdr_impl_inline.h:142-148 и :329-331— обход проверок длины через 32-битное переполнение вxdr_bytes/xdr_array(длина с провода).entry_size*(*len)и4 + ((len+3)&~3)переполняются в 32 бита → недо-аллокация с последующей записью сегментов → переполнение кучи в NFS/NFS-RDMA.src/nfs/nfs_kv_write.cpp:421— over-read кучи при redirect-записи за пределы размера shared-файла: дыра заполняется мусором из-за чтения за концом буфера.src/client/qemu_driver.c:1041-1062— use-after-free вblock_status[требует подтверждения мейнтейнера].client->last_bitmapзаимствуется через coroutine-yield; конкурентный write/block_status освобождает его → UAF (multi-threaded AioContext QEMU).Логика / CLI
src/cmd/cli_rm_data.cpp:64-69— use-after-move: финальный SYNC после удаления никогда не отправляется[проверено вручную]. В агрегатной инициализации.objects = std::move(objects)(член 2) выполняется до.synced = !objects.size() || …(член 8), читающего уже опустошённый set →syncedвсегдаtrue,get_immediate_commitне вызывается. На пулахimmediate_commit != allудаления не стабилизируются; черезvitastor-nfs(truncate/remove/rename отвечают OK) краш OSD в окне autosync откатывает удаления → объекты воскресают, при последующем расширении файла видны старые данные вместо нулей.src/cmd/cli_merge.cpp:253-257—continue_mergeигнорирует ошибки валидацииstart_merge. Ошибки («Layer not found» и т.п.) ставятstate=100, но вызывающий перетирает его наstate=1, и при пустомsourcesмашина состояний доходит до «Done … merged» сerr=0. Последствие: во время долгогоvitastor-cli rmконкурентное изменение цепочки (например, переименование базового слоя) делает merge ребёнка тихим no-op «успехом», после чегоrmребейзит ребёнка и удаляет данные слоёв → безвозвратная потеря блоков, существовавших только в этих слоях.src/cmd/cli_rm.cpp:159-165 и :189-191— порядок удаления вrm. (159) Inverse-child workflow уничтожает данные ребёнка до etcd-rename — прерывание/сбой транзакции оставляет ребёнка тихо читающим устаревшие данные родителя. (189) На non-immediate_commitпулах удаление source-слоя не синкается перед удалением, и потеря питания может зафиксировать удаления при откате слитой копии.src/blockstore/v1/impl.cpp:439-474— чанкованный online-решардинг оставляетclean_db_shardsпустым для пула между паузами event-loop, пока флашер продолжает его мутировать → сброшенные обновления теряются/затеняются[требует подтверждения мейнтейнера].src/client/cluster_client_wb.cpp:127-134— потерянный dirty-run при рантайм-включении writeback: расщепление CACHE_DIRTY-пробега не-DIRTY записью инкрементитwriteback_queue_sizeбез добавления якоря → осиротевший dirty-run никогда не флашится.src/client/nbd_proxy.cpp:1117-1121—NBD_CMD_DISCделаетexit(0)безcli->flush()(в отличие от ветки EPOLLRDHUP), теряя writeback-кэш при штатном отключении клиента.? Средние (краш/DoS/восстановимые рассинхроны) — кратко
Достойны починки, но не дают тихой потери зафиксированных данных. Большинство — краш/abort OSD или утечки.
disk_tool_journal.cpp:510lenдо инициализации записи → переполнениеnew_journal_bufна oversized-дампеblockstore_sync.cpp:108assert(!lsn)рушит OSD при ретрае do_sync после WAIT_SQE (assert включён в release)osd_primary_chain.cpp:64assert(op_data->st < base_state)blockstore_stable.cpp:55,70blockstore_write.cpp:330blockstore_heap.cpp:2007to_compact_countпосле рестарта (декремент при GC без инкремента при загрузке)blockstore_heap.cpp:666,1047,1766,2570v1/read.cpp:367,493,688,100osd_peering_pg.cpp:162,osd_primary.cpp:5081 << roleломает EC сpg_size > 31(UB-сдвиг) → healthy-объекты помечаются incompleteosd_secondary.cpp:131,etcd_state_client.cpp:1208,osd.cpp:340,http_client.cpp:423osd_flush.cpp:95PG_REPEERINGublk_server.cpp:739,nfs_proxy_rdma.cpp:589get_sqe()без NULL-проверки (SIGSEGV при queue_depth≥1024); бесконечный цикл post_sendcli_dd.cpp:536,921cli_merge.cpp:641delete_sourceне учтён вin_flight,snap_merger_tосвобождается до колбэкаcluster_client.cpp:349continue_ops()разыменовывает освобождённый op при синхронном завершении следующего SYNCkv_db.cpp:236read_string→ ~4ГБ OOB-чтение на битом KV-блокеblockstore_disk.cpp:246,blockstore_init.cpp:101,v1/init.cpp:348,disk_tool_resize_auto.cpp:119meta_area_size/частичный блок → abort на старте; суперблок пишется до инициализации меты; underflowblock_count-done_cnt; устаревшийdata_sizeпосле resizenbd_proxy.cpp:623--readonlyпарсится, но не применяется — NBD всегда RWnode-binding/client.cc:61,1119-1120uv_poll_tбезuv_close; listing без Ref во время in-flightlist_nextrdma_alloc.cpp:141Низкоприоритетные находки (43 шт., в основном недостижимые на практике переполнения, проблемы сообщения об ошибках, мелкие утечки) опущены ради читаемости — могу выгрузить отдельным списком при необходимости.
Примечания
-vs_во внутренних JSON, №1 иcli_rm.cpp:681): стоит провести аудит всех внутренних вызововstart_*, передающих JSON, мимоparse_config.dump|write-инструментов: roundtripvitastor-diskдля метаданных и журнала на csum-сторах сейчас не сохраняет данные верно — это пути аварийного восстановления, где порча наиболее болезненна.[требует подтверждения мейнтейнера]опираются на подтверждённую механику, но с узким окном срабатывания — их стоит проверить отдельно (желательно тестом), а не принимать на веру.Смешной однако баг. Исправил, добавил тест
Ладно, уже десятый раз про это все нейронки пишут, перенёс фиксы этого в 3.0 из ветки openssl
Поправил
Поправил
TODO Надо допроверить
Реально, поправил, но это было только если передать параметр --discard_granularity > 4096.
Тоже уже десятый раз нейронки ругаются на эти скрабы - поправил в репорте от GPT 5.5 от Лисы, тут то же самое, регрессионный тест добавил.
Аналогично, GPT-5-5 от Лисы тоже это нашёл, поправлено и добавлен регресс-тест
Годно, поправил, добавил регресс-тест
GPT-5-5 на это тоже ругнулся, я поправил, но фактически это не могло триггерить баг т.к. это могло сработать только при data_fd == journal_fd != meta_fd. А так в жизни не бывает
TODO Тут надо допроверить, возможно и есть смысл поправить
Мало деталей, но уже в процессе дебага нашёл сам, добавил тестов (первые тесты на старый стор), поправил
Журнал специально так сделан, тут это не баг.
Формально это конечно был тоже баг, в use(), но фактически он не мог триггериться в жизни, т.к. в жизни use() идёт по оффсету полученному через find_free. А там бага нет. Но поправил и тоже нагенерил тест.
TODO Надо допроверить, пока не могу ничего сказать.
Дальше тоже пока не посмотрел, смотрим дальше)
Дальше:
Эти два замечания - это было даже ещё не всё. Добавил тест dump|load для обоих сторов и поправил в итоге 4 бага.
Поправил, тоже добавил тест. Ну кейс узкий, но действительно разница не-кратных размеру блока размеров области данных в части последнего блока обрабатывалась некорректно.
Тут всё же не баг, auto-resize это упрощённая команда с ограниченными возможностями, там можно только либо переместить журнал/мету на отдельные девайсы или вернуть обратно на общий с данными, либо уменьшить/увеличить журнал, либо данные, на общем устройстве данных. Переместить на один и тот же девайс журнал и мету, ещё и задав вручную их размер, там нельзя и не должно быть "льзя". Также заставить его сделать пересекающиеся журнал и мету невозможно даже с --force.
Память (удалённо/локально)
Поправил
Поправил, ну это конечно кейс взлома нфс сервера :)
Попытался добавить тест, но сходу не смог триггернуть из внешнего мира чтобы буфер забился мусором. Но баг был, поправил
Тут кажется бага нет, т.к. драйвер не многопоточный.
Логика / CLI
Поправил
Тоже смешной баг, поправил
Тут как бы корректно, конкурентных операций одновременно с rm inverse-child не должно быть. Ну то есть логично, если ты решил что-то нехорошее сделать с парентом, пока его переименовываешь в чайлда, ничего хорошего не будет.
При простых прерываниях тоже ничего плохого не будет - т.к. сначала мержится дочерний в родительский, потом удаляются данные дочернего, потом переименовывается родительский в дочерний. Писателей в этот момент быть не должно (механизма жёстко запретить это нет, но идея такая).
ГМ, однако палево А как бы это и для нового стора тоже не было актуально... Надо поправить для обоих. TODO
UPD Отключил этот chunked resharding вообще нафиг пока что. Нужно сначала поправить, тестов написать, потом включать обратно.
Ну в рантайме всё-таки не предполагается включение writeback. Но нужно подумать, может стоит предусмотреть на всякий случай.
UPD В итоге поправил, регресс тест добавил.
Это поправил.
Так, отпишусь и потом коммент буду править, а то много всего
"Средние (краш/DoS/восстановимые рассинхроны) — кратко"
Поправил
Поправил
Поправил с регресс тестом )
Поправил путём удаления сомнительной логики, но без теста
Поправил с двумя регресс тестами! :) первый чтобы батчевание fsync правильно работало, второе чтобы не-батчевание fsync правильно работало.
Поправил с тестом
Тут три штуки (1047, 1766 и 2570) неправда. Первое типа правда, но относится к воркараунду для проблемы с double-claim и поломать по факту что-то может в очень редких случаях, но в общем так или иначе поправил.
Поправил чтобы она была 64-битной плюс просто запретил пока в osd старт пулов с pg_size > 64. Всё равно кому это нужно, pg_size 64.
Поправил с регресс тестом, точнее снова двумя :) тут интересно то, что это идёт как followup к проблеме которую я в 3.0.13 классифицировал как опасный проезд по памяти, а по факту после написания теста уже не уверен, что там этот проезд мог быть. Возможно его и не могло быть...
Логика так или иначе, правда, была не очень корректная - так что с тестом теперь будет всё как надо. Теоретически это может быть даже исправлением древней редкой проблемы с зависанием EC PG в repeering.
добавил in_flight++/--
когда смотришь свежим взглядом, замечание правда выглядит потенциально корректным - модель продолжения операций там как будто кривая, в том плане что связный список операций может модифицироваться и из continue_ops, и из всяких других мест, и выглядит, как будто должно быть можно подобрать ситуацию, когда это приведёт к UAF.
с другой стороны, ни клод, ни deepseek пока не осилили написать тест, который бы это реально воспроизводил. :-)
кроме того - вроде не было ни одного живого багрепорта, где падал бы клиент. а UAF к этому с большой вероятностью бы приводил...
ну и в целом, сходу переделывать это стрёмно, т.к. изменение получается немаленькое и можно наоборот что-то ещё им поломать.
так что править наверное пока не буду, но отложу на будущее, нужно там подразобраться и в порядок привести. при этом нужно не потерять текущие характеристики - в частности то, что при завершении операции нужно проходиться по следующим и продолжать их, если они от неё зависят, т.к. это влияет на перформанс - я эти связные списки туда внедрял ради перформанса.
UPD попробовал тоже воспроизвести, короче с SYNCами это точно нереально, т.к. это нужно чтобы колбэк завершения, причём успешного завершения, части операции вызвался внутри continue_ops, а это невозможно, он из eventloop (из мессенджера) вызывается...
может быть это как-то возможно с writeback-ами, но там сильно потрахаться надо, чтобы это воспроизвести. но на будущее прикопаем.
поправил, хотя это типа только для обработки некорректных данных на диске
действительно, добавил обработку опции
хм ну да, я делал uv_poll_stop(), а наверное надо uv_close(). поменял.нет не поменял, close делает нода на своём event loop-е. а я только poll останавливаю, так что всё правильно было там.Написал тест на этот аллокатор, в итоге поправил условно или 2 или 3 бага, включая этот.