Data integrity review using AI (Claude 4.7) v2 #67
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
Дата аудита: 2026-05-12
Версия кода: master @
4b7d0ce2("Add data device fsyncs during compaction")Контекст: Аудит проведён в связи с использованием Vitastor в качестве слоя хранения для публичного коммерческого облака. Цель — выявить дефекты, способные привести к потере или повреждению клиентских данных в продакшене.
Метод: 11 параллельных агентов, каждый аудитировал свою подсистему путём полного построчного чтения исходников. Найденные критические дефекты затем повторно верифицированы прямым чтением кода.
TL;DR — самые опасные находки
src/util/crc32c.c:413left_pad>0вместоright_pad>0. CRC может быть рассчитан без учёта правого паддинга → ложные/пропущенные ошибки целостности при чтении.src/blockstore/blockstore_heap.cpp:1727-1742punch_holesмодифицирует bitmap/checksums существующей entry, но не пересчитываетwr->crc32c. После перезапуска OSD entry помечается как corrupt → потеря BIG_WRITE'а → потеря пользовательских данных.src/osd/osd_primary_subops.cpp:391-397OSD_OP_SEC_WRITE_STABLE, но проверкаread_errorловит толькоOSD_OP_SEC_WRITE. Partial-write на 2/3 репликах остаётся незамеченным, клиент тихо получает старые данные.src/osd/osd_primary_subops.cpp:167+osd_primary.cpp:441-450stripe_num=0, поэтомуstripes[0].osd_numперезаписывается на каждой реплике в цикле. Даже если #3 исправить, mark_partial_write пометит максимум 1 из N расходящихся реплик.src/disk_tool/disk_tool_resize.cpp:114raw_resize()всегдаreturn 0— все ошибки шагов resize маскируются. Caller начинает затирать старые суперблоки → необратимая потеря OSD.src/client/cluster_client_wb.cpp:226 / 447writeback_bytesпри flush'е серии DELETE:+=не считает DELETE-буферы,-=считает. Assert / hot loop в release-сборке → крах клиента и потеря writeback.src/client/cluster_client.cpp:1174-1180src/client/cluster_client.cpp:1467-1483dirty_osdsотключены. Данные ещё CACHE_REPEATING, при крахе клиента теряются.src/osd/osd_scrub.cpp:488-522src/osd/osd_scrub.cpp:630op_data->stripes[chunk.role]при EC-объектах с OUTDATED-чанками: размер массиваstripe_count, но индексируетchunk.role ∈ [0, pg_size).src/osd/osd_peering_pg.cpp:387-402+osd_primary.cpp:676-678OBJ_CORRUPTED-only попадает вmisplaced_objectsчерез else-ветку, аremove_object_from_stateне имеет ветки для CORRUPTED-only →runtime_error→ крах OSD при обычной write/delete операции.src/blockstore/blockstore_init.cpp:310-352fsync(meta_fd)(как только что был добавлен для compaction в4b7d0ce2). При сбое питания после старта но до флэша — потеря очистки garbage, повторные double-claim.mon/mon.js:304, 802save_last_clean()иupdate_total_stats()пишут в etcd без проверкиLEASEна/mon/master. Старый монитор после потери lease перезаписывает данные нового → split-brain.1. Критические баги в blockstore (хранилище)
1.1. CRITICAL —
crc32c_pad: опечаткаleft_padвместоright_padФайл:
src/util/crc32c.c:413Код (подтверждён):
Описание: При
left_pad == 0остатокright_pad % 4096не учитывается в CRC. Также возможна обратная асимметрия: приleft_pad > 0 && right_pad == 0crc32c(r, zero_page, 0)— безвредно, но семантически тоже неверно.Сценарии воздействия:
bitmap_granularity < csum_block_size: hint-биты в bitmap указывают, что часть csum-блока «нулевая». При расчёте csum черезcrc32c_padправый «нулевой» паддинг должен войти в CRC, но из-за бага не входит.crc32c_pad, баги взаимокомпенсируются → проверка проходит. Однако:crc32c_iterвblockstore_heap.cpp:1161— иной кодовый путь, который правильно учитывает паддинги) — заново вычисленная CRC не совпадёт с записанной.Воспроизведение:
csum_block_size > bitmap_granularity, частичная запись через blockstore v1 (src/blockstore/v1/write.cpp:184-200), либо любая ситуация, в которойcrc32c_padвызывается сleft_pad=0, right_pad>0, right_pad<4096.Рекомендация (1 символ): заменить на
if (right_pad > 0).1.2. CRITICAL —
punch_holesне обновляетwr->crc32cв heap blockstoreФайл:
src/blockstore/blockstore_heap.cpp:1727-1742Код (подтверждён):
Проверка
grepпоказывает:calc_crc32cвызывается только вadd_entry:1548. Ниpunch_holes, ниadd_compact(1700-1725) не пересчитывают crc32c существующей entry, в которой меняется содержимое.Что произойдёт:
perfect_csum_update(требуетcsum_block_size > bitmap_granularity) bitmap и checksums существующего BIG_WRITE entry модифицируются.blockstore_flush.cpp:385).read_blocks(blockstore_heap.cpp:383-389) проверяетwr->crc32c != calc_crc32c()→ mismatch → entry classified as corrupt.skip_corrupted_meta_entries = false(по умолчанию) → OSD отказывается стартовать.skip_corrupted_meta_entries = true→ BIG_WRITE пропускается = полная потеря пользовательского блока данных (данные на disk-данных есть, но ссылки в метаданных нет → расцениваются как «свободное место» и могут быть перезаписаны).Рекомендация: добавить
wr->crc32c = wr->calc_crc32c();в концеpunch_holes. Также аудитировать любые in-place изменения существующих entries.1.3. CRITICAL — Забыт
fsync(meta_fd)после recheck-перезаписи метаблоков в initФайл:
src/blockstore/blockstore_init.cpp:310-345Описание: После
finish_recheckблокстор записывает «исправленные» метаблоки (с удалённым garbage) черезio_uring_prep_writev. Никакогоio_uring_prep_fsync(meta_fd, IORING_FSYNC_DATASYNC)после цикла нет.initialized = 10выставляется немедленно.Параллель: только что закрыт аналогичный баг для compaction — коммит
4b7d0ce2 "Add data device fsyncs during compaction (forgotten during initial heap store dev)". Здесь тот же класс ошибки в init-пути.Последствия:
caa70317), которая теперь срабатывает на разных entries иinit_erase_double_claimначинает удалять валидные объекты.disable_meta_fsync=false(это режим по умолчанию) такого допускать нельзя.Рекомендация: добавить fsync meta после цикла
recheck_modified_blocks, аналогично compaction-fix.1.4. HIGH — Use-after-free в
recheck_buffer::free_entryФайл:
src/blockstore/blockstore_heap.cpp:864-892C++ UB. На практике glibc обычно не переиспользует адрес немедленно, но в комбинации с jemalloc/tcmalloc/thread-local arenas возможна ложная коллизия → пропуск удаления entry из
inf.entriesили падениеassert(found).Рекомендация: сохранить копию pointer для сравнения до
free(li), либоfree(li)послеmodify_alloc.1.5. HIGH —
free_buffer_area(_, 0, 0)падает на zero-length small_write в init-путиФайл:
src/blockstore/blockstore_heap.cpp:712-722(нет проверкиif (wr->small().len > 0)вinit_free_bad_entry, хотя такая проверка есть в аналогичных местах на строках 589 и 2001).Последствие:
multilist_alloc_t::do_free(0)падает наassert(!nexts[pos])→ крах OSD в init. OSD не может стартовать. Условие триггерится приinit_erase_double_claimдля EC-пула с zero-length small_writes (bump-версии без данных).1.6. HIGH — Хрупкий инвариант
assert(!inf.mod_lsn_to || inf.mod_lsn_to == next_lsn)Файл:
src/blockstore/blockstore_heap.cpp:1376, 1469Если между двумя
allocate_entryв один и тот же meta-block произошёлallocate_entryв другой block,next_lsnинкрементировался для чужого блока. Затем при возврате к исходному блокуinf.mod_lsn_to == old_value != next_lsn→ ассерт. Падение OSD под обычной mixed-нагрузкой (big + small + intent).1.7. MEDIUM —
iterate_with_stable/iterate_compactionне skip garbage entriesФайл:
src/blockstore/blockstore_heap.cpp:2030-2069, 2080-2159В отличие от
validate_object:464иmark_garbage_up_to:1983, эти итераторы не пропускаютis_garbage()entries. Reader черезiterate_with_stableможет выбрать garbage entry с location, уже переиспользованным новой записью → возврат wrong data клиенту.1.8. CRITICAL —
intent_write_allowedбез проверкиuse_atomic_flagФайл:
src/blockstore/blockstore_write.cpp:55-110 / 459-462Сценарий:
disable_meta_fsync=true,atomic_write_size=16384,use_atomic_flag=false.intent_write_allowedпропускает запись (len <= atomic_write_size), но вresume_10RWF_ATOMIC не ставится.disable_meta_fsync=trueзапись метаданных считается durable сразу →trim_lsnсжигает recheck-возможность.Рекомендация: в
intent_write_allowedдобавитьif (!dsk.use_atomic_flag) return false;.2. Критические баги в OSD primary (репликация)
2.1. CRITICAL —
mark_partial_writeсломан для replicated пуловФайл:
src/osd/osd_primary_subops.cpp:391-397(подтверждено)Код:
Replicated primary использует
OSD_OP_SEC_WRITE_STABLE(см.osd_primary_subops.cpp:204, 236), но проверка ловит толькоOSD_OP_SEC_WRITE. Поэтомуread_errorдля replicated никогда не устанавливается, а значитmark_partial_write(osd_primary.cpp:432-453) никаких чанков какLOC_OUTDATEDне пометит.Сценарий потери данных (реалистичный):
read_errorдля 3-й не выставлен →mark_partial_writeне помечает 3-ю как OUTDATED.misplaced_objects/incomplete_objects.zero_read(свой OSD или первая ненулевая). Если выбрана отставшая реплика → клиент тихо получает старые данные.fact_ver.target_ver = old+1= уже записанная новая версия на других → EEXIST → drop соединений → re-peering → возможна более тяжёлая инконсистентность.Рекомендация: добавить
|| opcode == OSD_OP_SEC_WRITE_STABLEв проверку.2.2. CRITICAL —
stripes[0]перезаписывается для каждой репликиФайл:
src/osd/osd_primary_subops.cpp:167,src/osd/osd_primary.cpp:441-450submit_primary_subop_batchдля replicated используетstripe_num = rep ? 0 : role, т.е.si = &stripes[0]для всех реплик.si->osd_numперезаписывается на каждой итерации цикла. После завершенияstripes[0].osd_numхранит только последнюю реплику.Даже если исправить #2.1,
mark_partial_writeсможет пометить максимум одну реплику из множества расходящихся.Рекомендация: для replicated завести per-replica структуру ошибок (например, массив параллельный
stripes), а не делить общийstripes[0].2.3. CRITICAL — Отсутствует
if (n_subops > 0)обёртка вcontinue_primary_syncдляcopies_to_deleteФайл:
src/osd/osd_primary_sync.cpp:195-198Коммит
5100f822обернул аналогичные места, но пропустил именно этот путь:Если
submit_primary_del_batchне создаст ни одного subop (рассогласование счётчиковcopies_to_delete_after_sync_countи фактического содержимого), SYNC висит навечно. Последующие SYNC-ы блокируются друг другом → OSD выпадает из обслуживания записи.2.4. HIGH — UB в
free(op_data->snapshot_bitmaps)Файл:
src/osd/osd_primary_chain.cpp:193op_data->snapshot_bitmapsуказывает на офсет внутри одной большойcalloc_or_die-аллокацииop_data(osd_primary.cpp:113-129).free()на не-начале аллокации — UB; на glibc обычноabort("invalid pointer").Триггер: chained-read для EC, когда
extend_missing_stripesвозвращает -1 (недостаточно живых OSD для восстановления). OSD аварийно завершается.2.5. HIGH —
pg_state_bitsперезаписывается, теряетсяPG_HAS_CORRUPTEDФайл:
src/osd/osd_primary.cpp:521-538pg.stateне получаетPG_HAS_CORRUPTEDдля первого corrupted объекта в degraded PG — оператор не видит сигнал, monitoring молчит.2.6. HIGH — Утечка
cur_op->bufпри retry-чтения послеmark_object_corruptedФайл:
src/osd/osd_primary.cpp:272, 297-303При
goto resume_0старыйcur_op->bufтеряется безfree(). Вosd_primary_chain.cpp:59это сделано корректно. Здесь — забыто. При множественных корраптингах → OOM OSD.2.7. MEDIUM — Гонка между PG_REPEERING и in-flight WRITE
Файл:
src/osd/osd_primary_write.cpp:225-232PG-state проверяется один раз перед
submit_primary_subops(SUBMIT_WRITE). Если PG переходит вPG_STOPPING|PG_REPEERINGпосле запуска subops, операция продолжается. При быстрой смене primary возможна ситуация двух «пишущих» primary одновременно (split-brain в окне re-peering).2.8. CRITICAL — Объект OBJ_CORRUPTED-only → крах OSD при write/delete
Файл:
src/osd/osd_peering_pg.cpp:387-402+osd_primary.cpp:676-678Объект со state
OBJ_CORRUPTED(без других флагов) попадает вmisplaced_objectsчерез else-ветку.remove_object_from_stateиспользует цепочкуelse ifдля INCONSISTENT/INCOMPLETE/DEGRADED/MISPLACED, а для CORRUPTED-only-state выкидываетthrow runtime_error("BUG: Invalid object state: ...").Триггер:
add_object_to_setдаётobj_state = OBJ_CORRUPTED-only.misplaced_objects.remove_object_from_state→ throw → крах OSD.3. Критические баги в EC / RMW
3.1. CRITICAL —
ec_check_combinationмодифицирует оригинальныеbmp_bufстриповФайл:
src/osd/osd_rmw.cpp:1198-1216(внутриec_find_good)Функция использует
bitmap_setнепосредственно на оригинальныхstripes[i].bmp_buf. После возврата вызывающий получает повреждённые bitmaps. При scrub сbitmap_size > 0возможна потеря информации о sparse regions.3.2. CRITICAL —
ec_find_goodне ловит ambiguity при «меньшем найденном первым»Файл:
src/osd/osd_rmw.cpp:1361-1378Этот код ловит ambiguity ТОЛЬКО когда новый valid set ≤ предыдущего. Если первая комбинация дала меньший valid set, а вторая — больший, ambiguity молчаливо игнорируется И больший набор отбрасывается. При
scrub_find_best = true(default) → scrub может принять неверный набор данных как «good» и репарировать остальные на его основе.3.3. CRITICAL — Majority voting репарирует «правильную» копию из 2 испорченных
Файл:
src/osd/osd_scrub.cpp:493-538+ документация наscrub_find_bestЕсли 3 реплики и 2 одинаково испорчены (тот же дефект на 2 HDD с одинаковой прошивкой) — majority выбирает «плохую» версию как эталон, помечает хорошую копию как corrupted, и recovery затем перезаписывает хорошую копию плохими данными. Все 3 реплики становятся битыми.
Документация (
docs/config/osd.en.md) явно признаёт сценарий, ноscrub_find_best=trueостаётся default. Для коммерческого облака рекомендуется выставлятьscrub_find_best=falseглобально.3.4. CRITICAL — OOB-доступ
op_data->stripes[chunk.role]для EC с OUTDATED-чанкамиФайл:
src/osd/osd_scrub.cpp:630Массив
stripesимеет размерstripe_count(= число не-OUTDATED чанков), аchunk.roleможет быть[0, pg_size). Для EC объектов с OUTDATED-чанками возникает OOB-чтение → ложное снятие/установкаLOC_CORRUPTEDна основе случайных байт.3.5. HIGH —
reconstruct_stripes_ecбез assert на корректное число чанковФайл:
src/osd/osd_rmw.cpp:410-419Если функция вызвана при количестве доступных чанков <
pg_minsize(логическая ошибка выше по стеку), цикл сбораdata_ptrsне заполнит все позиции, и ISA-L использует неинициализированные указатели → чтение мусора → реконструкция из мусора → записанные мусорные данные при recovery.3.6. HIGH — jerasure unaligned branch расширяет цикл за
pg_minsizeФайл:
src/osd/osd_rmw.cpp:556-565for (role = 0; role < pg_size; role++)отличается от aligned-ветки (role < pg_minsize). При missing parity сread_end != 0используетсяdecoding_matrix + (role*pg_minsize)за пределами выделенного массива → потенциальный OOB read и неверная реконструкция.4. Критические баги в client / write-back cache
4.1. CRITICAL — Underflow
writeback_bytesпри flush последовательных DELETEФайл:
src/client/cluster_client_wb.cpp:226, 447(подтверждено)При series соседних
OSD_OP_DELETE(которые группируются в одну batch)writeback_bytesуходит в underflowuint64_t. В debug-сборке падает assert → потеря всех unflushed данных. В release (assert disabled)while (wb->writeback_bytes > client_max_buffered_bytes ...)вexecute_internal:784превращается в бесконечный цикл на следующей операции записи.4.2. CRITICAL — Heap overflow в
copy_writeпри некорректном iovФайл:
src/client/cluster_client_wb.cpp:247-258bufвыделен размеромop->len. Если caller передалop->iovс суммарной длиной >op->len, в release-сборке (assert исчез)memcpyпишет за границей буфера → heap corruption.4.3. CRITICAL — Фантомный read данных несостоявшегося write
Файл:
src/client/cluster_client.cpp:1174-1180При fatal-error пути (
-EROFS,-EACCES,-EINVALи т.п.)mark_flush_writtenНЕ вызывается, и CACHE_REPEATING-буфер остаётся вdirty_buffersнавсегда. Последующий read того же региона в той же сессии копирует данные из этого буфера черезread_from_cache:515. Application получает данные write'а, который вернул ошибку.Это нарушает базовую семантику и в коммерческом облаке потенциально приводит к скрытому повреждению на уровне приложения клиента (например, файловые системы поверх RBD).
4.4. HIGH/CRITICAL — SYNC возвращает успех при disconnected OSD
Файл:
src/client/cluster_client.cpp:1467-1483При disconnect OSD между write OK и client SYNC:
repeat_ops_forпосле disconnect).dirty_osds(он не вmsgr.osd_peers).dirty_osdsстановится пустым → SYNC возвращает 0 мгновенно.Нарушение семантики fsync.
4.5. HIGH — TOCTOU в
send_syncФайл:
src/client/cluster_client.cpp:1536msgr.osd_peers.at(part->osd_num)бросает исключение, если в callback'ах внутри цикла отправки произошёлstop_client. → необработанный exception → крах клиента с возможным неконсистентным writeback.4.6. HIGH — Reentrant
stop_clientвhandle_op_partФайл:
src/client/cluster_client.cpp:1611-1614Авторами помечено FIXME (
// FIXME postpone such things to set_immediate here to avoid bugs). При нескольких parts на одного primary с -EPIPE возможен двойной вызовcontinue_sync/rwна одну op и нарушение инвариантаinflight_count.4.7. HIGH —
bitmap_bufне обнуляется при resliceФайл:
src/client/cluster_client.cpp:1263-1269Обнуление выполняется только если
bitmap_mem > bitmap_buf_size. При reslice без расширения областьpart_bitmapsхранит биты прошлого slice'а. В сочетании с server'ом, который возвращает короткий bitmap, → ложные «1» в bitmap для snapshot/chain reads → пропуск обращения к parent inode → incorrect read.5. Критические баги в scrub
5.1. CRITICAL — Replicated scrub помечает только канонического члена минорной группы
Файл:
src/osd/osd_scrub.cpp:488-522Цикл
votes[eq_to[role]]заполняет только канонические индексы каждого класса эквивалентности; цикл маркировки итерирует поroleи видитvotes[role]==0для не-канонических → они не помечаются как LOC_CORRUPTED.Эффект: в группе из 2 «плохих» копий с одинаковыми данными после scrub'а одна (каноническая) помечена, вторая — нет. При следующем read primary может выбрать неотмеченную плохую копию (по
zero_read) → клиент получает битые данные. При recoveryread_targetвключает плохую копию → распространение порчи.5.2. CRITICAL — OOB stripes[chunk.role] при OUTDATED-чанках
См. §3.4.
5.3. CRITICAL — Majority voting с
scrub_find_best=trueСм. §3.3.
5.4. HIGH — Scrub отключён для clean-объектов в degraded PG
Файл:
src/osd/osd_scrub.cpp:384-393, 426Для clean-объекта stripes создаются из
cur_set, в котором может бытьosd_num=0(отключённый OSD).submit_primary_subopдля osd_num=0 немедленно возвращает -EPIPE.errcode != EIO/EDOM→continue_primary_scrub(строки 663-665) делаетfinish_opбезscrub_check_results. Scrub фактически не работает ни для одного clean-объекта в любом PG с хоть одним выпавшим OSD.В реальном кластере это окно может длиться часами/днями (recovery). Silent corruption за это время не выявляется.
5.5. HIGH —
get_object_osd_setигнорируетPG_HAS_CORRUPTED/INCONSISTENTФайл:
src/osd/osd_primary.cpp:184-211Не проверяет
PG_HAS_INCONSISTENT/CORRUPTED— если PG содержит только corrupted/inconsistent объекты (без degraded/misplaced/incomplete), возвращаетсяcur_set(включая corrupted-OSD), а неread_target→ re-scrub читает с corrupted-источника.5.6. LOW — Hang scrub при stripe_count == 0
Файл:
src/osd/osd_scrub.cpp:426-433Если все чанки объекта помечены LOC_OUTDATED,
n_subops=0, ноop_data->st = 1уже выставлен. Никто не разбудит. Слот вscrub_queue_depthблокируется навсегда.6. Критические баги в peering/cluster
6.1. CRITICAL — OBJ_CORRUPTED-only объект → крах OSD
См. §2.8.
6.2. HIGH — Memory leak в
remove_object_from_staterecheck pathФайл:
src/osd/osd_primary.cpp:613-622При
recheck_state == NULLи(*object_state)->ref_countпосле декремента == 0 entry вstate_dictне очищается.6.3. MEDIUM —
drop_dirty_pg_connectionsне вызывается в else-веткеrepeer_pgФайл:
src/osd/osd_peering.cpp:175-193При переходе PG_ACTIVE → PG_REPEERING без
start_pg_peering, dirty_pg_connections не сбрасываются. Клиенты с unsynced writes получают задержки/таймауты.7. Критические баги в monitor / etcd
7.1. CRITICAL —
save_last_clean()иupdate_total_stats()без проверки LEASEФайл:
mon/mon.js:304, 802В отличие от
save_pg_config():681, эти транзакции не проверяютLEASEна/mon/master. Старый монитор после потери lease может перезаписатьlast_clean_pgsустаревшими данными, что повредит логикуgenerate_pool_pgsнового мастера → лишние data movements, потенциально incorrect placement.7.2. CRITICAL —
antietcd_adapter.become_master()без CASФайл:
mon/antietcd_adapter.js:126-148В отличие от
etcd_adapter.become_master, проверкаCREATE 0отсутствует. Безусловная запись/mon/master. При кратковременном split-brain в antietcd raft два «лидера» могут оба перезаписать ключ.7.3. HIGH —
schedule_next_recheck_at(): таймер срабатывает мгновенноФайл:
mon/mon.js:722now - next_recheck_atотрицательно/нулевое (next в будущем) → таймер срабатывает сразу, ещё и без* 1000. Политикаosd_out_time(выкидывание OSD из дерева через 600 сек) фактически не работает корректно. PG висят в degraded дольше → при следующей потере OSD возможен переход в incomplete.7.4. HIGH — Use-after-free в etcd retry-таймерах
Файл:
src/client/etcd_state_client.cpp:151-155и аналогичные вosd_cluster.cppЛямбды захватывают
this. Деструкторetcd_state_client_tне отменяет произвольные retry-таймеры → при разрушении объекта (NFS proxy, dynamic reconfiguration) таймер триггерится с уже удалённымthis.7.5. HIGH —
etcd_watch_revision_*устаревает приetcd_watches_initialised < ETCD_TOTAL_WATCHESФайл:
src/client/etcd_state_client.cpp:418-442События watch обрабатываются сразу, но
etcd_watch_revision_*обновляется только после полной инициализации всех watches. При обрыве/reconnect возможны дублирующие обработки событий, в частности пропускon_change_pg_state_hookдля зависших операций.7.6. HIGH —
clean_nonexistent_pgsне убирает удалённые inode'ыФайл:
src/client/etcd_state_client.cpp:736 / 770-822При reload через compaction inode_config не очищается. Клиент пишет в удалённый inode → -EPIPE/-ENOENT. Не data corruption, но liveness-проблема.
7.7. MEDIUM —
reshard_continue()UB на пустом вектореФайл:
src/osd/osd_cluster.cpp:803-810reshard_pools[0]без проверки.empty()после goto-цикла.8. Критические баги в messenger (TCP / RDMA)
8.1. HIGH — Утечка
zc_free_listдля партий безMSGR_SENDP_FREEФайл:
src/client/msgr_send.cpp:295-340Если партия (типичная для клиента) не содержит FREE-операций, NULL-маркер всё равно добавляется. Второе уведомление (
prev=true) удаляет операции только приi > 0— NULL-маркер не убирается, и последующие операции с FREE «застревают» за чужими NULL-маркерами. Долгоживущее соединение OSD → OOM → крах OSD.8.2. MEDIUM — Отсутствие валидации длин в
handle_op_hdrФайл:
src/client/msgr_receive.cpp:264-346sec_rw.len,attr_len,sec_stab.len,json_lenпринимаются без проверки и сразу передаются вmalloc_or_die. ПриUINT32_MAXпроцесс падает черезexit(1). DoS, но не data corruption. В trusted cluster не критично; при компрометации хотя бы одного OSD-узла — критично.8.3. MEDIUM —
bitmap_lenкороче ожидаемого → mixed-state вop->bitmapФайл:
src/client/msgr_receive.cpp:366-380Если OSD возвращает bitmap короче
op->bitmap_len, вop->bitmapостаются неинициализированные/устаревшие байты. При EC-recovery это приведёт к неверной интерпретации stripes → клиент получает corrupted data. Требует ошибки сервера, защиты в клиенте нет.8.4. LOW — Ping-FIXME при RDMA-transition
Файл:
src/client/msgr_receive.cpp:78-81(FIXME: Ignore pings during RDMA state transition).PING-reply пропускается → keepalive таймаут → ложный дисконнект и retry.
9. Критические баги в disk_tool
9.1. CRITICAL —
raw_resize()всегда возвращает 0Файл:
src/disk_tool/disk_tool_resize.cpp:114(подтверждено)Вызывающий код (
disk_tool_resize_auto.cpp:111,disk_tool_upgrade.cpp:134) при «успехе» начинает:clear_osd_superblock).При реальной ошибке resize данные могут быть частично перемещены / журнал и метаданные побиты, но старый суперблок уже стёрт. OSD не сможет стартовать; восстановление требует bare-metal вмешательства.
Рекомендация (1 символ):
return r;.9.2. CRITICAL —
write_osd_superblock/clear_osd_superblock/write_zeroбез fsyncФайл:
src/disk_tool/disk_tool_udev.cpp:141, 423-427,src/disk_tool/disk_tool_utils.cpp:278-300После записи суперблока просто
close(fd)безfsync. При write-back кэше диска (норма!) и потере питания:prepare: суперблок «записан», но не на пластинах → OSD не стартует после reboot.resize_data: новые суперблоки могут пропасть, но физическое перемещение данных уже произошло.purge: суперблоки «очищены», но кэш может их вернуть — старый OSD может попытаться зарегистрироваться обратно.9.3. CRITICAL — Не сбрасывается состояние KV-БД при close/open
Файл:
src/kv/kv_db.cpp:680-699, 506-546close()сбрасывает толькоblock_cache.clear()иknown_versions.clear(). Остаютсяblock_levels,allocating_blocks,new_versionsи т.д. При reopen на другой inode_idconfirmed=trueallocating blocks содержат смещения от предыдущего inode → новые записи в новый inode перепишут существующие KV-блоки на старте.9.4. HIGH — Опечатка
allocating_block_posвместоnextвalloc_blockФайл:
src/kv/kv_db.cpp:712Должно быть
allocating_blocks[next].offset. Лишние allocating blocks создаются, фрагментация растёт.9.5. HIGH — Double-close при общем fd для journal и meta
Файл:
src/disk_tool/disk_tool_meta.cpp:229-239После
close(meta_fd); meta_fd = -1;проверкаjournal_fd != meta_fdвсегда true → double-close. В однопоточной утилите безвредно (EBADF), но при добавлении многопоточности → race с чужим fd.9.6. HIGH — Орфанные KV-блоки при split-сбое root
Файл:
src/kv/kv_db.cpp:1856-1922clear_blockиспользуетversion=0, что в CAS возвращает -EEXIST для уже-аллоцированных inode_block. Орфанные блоки никогда не очищаются → утечка пространства KV-индекса.10. Util / Misc
10.1. HIGH — SQE128 partial init в ringloop
Файл:
src/util/ringloop.cpp:93, 184-200*sqe = { 0 };обнуляет 64 байта, реальный SQE128 — 128 байт.restore()иspace_left()используют>> sqe_shift, ноsqe_tailинкрементируется на 1. Сейчас активируется только в ublk_server control path → не corruption, но мина при будущем использовании SQE128 в data path.10.2. MEDIUM —
readv_blockinginfinite loop при EOFФайл:
src/util/rw_blocking.cpp:60-93r == 0от readv не обрабатывается → бесконечный цикл. Используется вfio_sec_osd.cpp(тестовая утилита), не в OSD.10.3. LOW —
free()C++ объектов в~ring_loop_t()Не вызывает деструкторы
std::functioncallbacks → потенциальный memory leak.11. Что НЕ найдено / выглядит корректно
Аудит подтвердил корректность:
submit_fsyncsвыбор FD.completed_lsnв суперблоке черезRWF_DSYNC.mark_completed_lsnsдля out-of-order meta-блоков.sec_check_pg_lock+ lease.write_queue(для не-scrub операций).Эти места выглядят надёжно реализованными.
12. Приоритизированный список действий
Срочно исправить перед запуском в продакшен
src/util/crc32c.c:413— заменитьif (left_pad > 0)наif (right_pad > 0).src/blockstore/blockstore_heap.cpp:1727-1742— добавитьwr->crc32c = wr->calc_crc32c();в концеpunch_holes. Дополнительно: проверить, что аналогичная пересчёт делается вadd_overwrite_in_placeи других in-place модификаторах.src/blockstore/blockstore_init.cpp:310-352— добавитьio_uring_prep_fsync(meta_fd, IORING_FSYNC_DATASYNC)после цикла перезаписиrecheck_modified_blocks(если!disable_meta_fsync && !readonly).src/osd/osd_primary_subops.cpp:391-397— добавитьOSD_OP_SEC_WRITE_STABLEв проверкуread_error.src/osd/osd_primary_subops.cpp:167+osd_primary.cpp:441-450— переработатьmark_partial_writeдля replicated: хранить per-replica статус ошибки отдельно, а не sharedstripes[0].src/osd/osd_primary_sync.cpp:195-198— обернуть путьcopies_to_deleteвif (n_subops > 0).src/osd/osd_primary_chain.cpp:193— убратьfree(op_data->snapshot_bitmaps)(память освобождается сop_data).src/osd/osd_peering_pg.cpp:387-402— добавить ветку дляOBJ_CORRUPTED-only состояния, либо обработать его вremove_object_from_state.src/osd/osd_primary.cpp:521-538— заменить=на|=для всех ветокpg_state_bits(теряетсяPG_HAS_CORRUPTED).src/osd/osd_scrub.cpp:488-522— итерировать маркировку поeq_to[role]-классу, помечая все копии в классе.src/osd/osd_scrub.cpp:630— индексировать массивstripesпо позиции в нём, а не поchunk.role(или через maprole → stripe index).src/osd/osd_scrub.cpp:384-393— пропускатьosd_num=0чанки в submit_scrub_subops для clean-объектов (либо помечать ихnot_exists=trueбез отправки).src/disk_tool/disk_tool_resize.cpp:114— заменитьreturn 0;наreturn r;.src/disk_tool/disk_tool_udev.cpp:141, 423+disk_tool_utils.cpp:278— добавитьfsync(fd)передclose(fd)для всех записей суперблока иwrite_zero.src/client/cluster_client_wb.cpp:447—writeback_bytes -=должно учитывать только не-DELETE буферы; либо+=на 226 должен+= op->lenнезависимо от is_del.src/client/cluster_client.cpp:1174-1180— при fatal-error вызыватьwb->mark_flush_written(op->inode, op->offset, op->len, op->flush_id)доerase_op.src/client/cluster_client.cpp:1475-1483— при удалении OSD изdirty_osdsпроверять наличие CACHE_REPEATING-буферов на тот же OSD; не возвращать SYNC OK, пока re-flush не завершится.mon/mon.js:304, 802— добавитьcompare: [{ target:'LEASE', lease: etcd_lease_id, key: ... '/mon/master' }]в обе транзакции.mon/antietcd_adapter.js:141— добавитьcompare: [{ target:'CREATE', create_revision:0, key:'/mon/master' }].mon/mon.js:722— заменитьsetTimeout(cb, now - next)наsetTimeout(cb, (next - now) * 1000).Важно — в ближайшем релизе
intent_write_allowed(blockstore_write.cpp:55-110) — добавитьif (!dsk.use_atomic_flag) return false;.iterate_with_stable/iterate_compaction— пропускатьis_garbage()entries.recheck_buffer::free_entry— переставить порядокfree(li)иmodify_alloc.init_free_bad_entry— проверкаif (wr->small().len > 0)передfree_buffer_area.osd_primary.cpp:303—free(cur_op->buf); cur_op->buf = NULL;передgoto resume_0.scrub_find_best = falseдля коммерческого окружения (см. §3.3).Защита-в-глубину
stop_clientвhandle_op_part(FIXME уже стоит).handle_op_hdr— отвергать с -EINVAL вместоexit(1).OBJ_CORRUPTEDвget_object_osd_set(включить в проверку маски).13. Замечания об устройстве кода и о методе аудита
assert(res==0),assert(li_entry_type == cwr_entry_type)и т.п.) дают защиту fail-stop, но иногда срабатывают на сценариях, которые могли бы возникнуть, если бы вышестоящий слой OSD когда-нибудь нарушил инвариант. В долгоживущем продакшене такие assert'ы — мины: их активация = полный downtime OSD без возможности recovery.4b7d0ce2,2a6ce356,f6e07f57,04fe7eb2,21f712ad,f271c845,fe8b1fe0,1ec963e4,5100f822,87a52307,0c5e6d43,b2780874,caa70317) — большой поток bugfix'ов конкретно в новом heap-блокстoре и в путях fsync/durability. Это сигнал: код только что стабилизируется. Для коммерческого облака с гарантиями данных рекомендуется:perfect_csum_update=trueдо исправления #1.2.false, чтобы исключить риск «починки» хорошей копии 2 одинаково-битыми (см. §3.3).14. Приложение: что было проверено
Аудит охватил полное чтение и анализ следующих файлов (всего ≈25.5 KLOC C++ кода + JS-монитор):
src/blockstore/:blockstore.cpp/h,blockstore_disk.cpp/h,blockstore_flush.cpp/h,blockstore_heap.cpp/h,blockstore_impl.cpp/h,blockstore_init.cpp/h,blockstore_open.cpp,blockstore_read.cpp,blockstore_stable.cpp,blockstore_sync.cpp,blockstore_write.cpp,multilist.cpp/h,ondisk_formats.hsrc/osd/:osd.cpp/h,osd_cluster.cpp,osd_flush.cpp,osd_peering.cpp,osd_peering_pg.cpp/h,osd_primary.cpp/h,osd_primary_chain.cpp,osd_primary_describe.cpp,osd_primary_subops.cpp,osd_primary_sync.cpp,osd_primary_write.cpp,osd_rmw.cpp/h,osd_rmw_test.cpp,osd_scrub.cpp,osd_secondary.cppsrc/client/:cluster_client.cpp/h,cluster_client_impl.h,cluster_client_list.cpp,cluster_client_wb.cpp,etcd_state_client.cpp/h,http_client.cpp/h,messenger.cpp/h,msgr_op.cpp/h,msgr_receive.cpp,msgr_send.cpp,msgr_stop.cpp,msgr_rdma.cpp/h,msgr_rdmacm.cpp,osd_ops.cpp/h,pg_states.cpp/hsrc/util/: crc32c, ringloop, timerfd_manager, allocator, rw_blocking, epoll_manager, sha256, addr_utilsrc/disk_tool/: prepare, resize, resize_auto, journal, meta, udev, upgrade, utils, discard, simple_offsetssrc/kv/: kv_dbmon/: mon.js, antietcd_adapter.js, etcd_adapter.js, pg_gen.js, pg_utils.jsПодтверждены прямым повторным чтением:
crc32c_padleft_pad/right_pad баг (CRITICAL #1)punch_holesбез crc32c (CRITICAL #2)mark_partial_writeдля SEC_WRITE_STABLE (CRITICAL #3)raw_resizeвсегда 0 (CRITICAL #5)writeback_bytesunderflow для DELETE (CRITICAL #6)Все приведённые номера строк актуальны для коммита
4b7d0ce2.Норм, продолжаем дебаг )))
Спасибо за быстрые фиксы. Проверил все 13 коммитов между
4b7d0ce2..6d7a7ae8— все корректны. Прогнали ещё один раунд на коммите6d7a7ae8, нашли новые места. Ниже только то, что ещё не обсуждали.⚠️ Возражение по поводу OBJ_CORRUPTED-only (#11 в прошлом списке)
Ты ответил: «не бывает объектов в стейте только OBJ_CORRUPTED, оно всегда с MISPLACED/DEGRADED/INCOMPLETE/INCONSISTENT».
Перепроверили — это утверждение неверно для EC. В
osd_primary.cpp:514-544:Сценарий: EC pg_size=3, pg_data_size=2. Объект имеет все 3 chunks на cur_set. Scrub помечает parity (role=2) как corrupted:
n_corrupted = 1n_roles = 2(роли 0 и 1 целые) — НЕ меньше pg_data_size (2)n_misplaced = 0(corrupted чанки не считаются как misplaced)n_outdated = 0,n_inconsistent = 0,n_invalid = 0→
obj_state == OBJ_CORRUPTED(только этот флаг).Затем
remove_object_from_state(osd_primary.cpp:631-685) проходитcorrupted_objects--в первом блоке, потомelse if (INCONSISTENT/INCOMPLETE/DEGRADED/MISPLACED)все false → финальныйelse throw runtime_error("BUG: Invalid object state: ...")→ краш OSD при следующем write/delete на этот объект.Воспроизводимо в EC-пуле при partial corruption на parity-чанке.
Новые критические находки (HEAD
6d7a7ae8)1. CRITICAL — UBLK: инвертированный
UBLK_ATTR_VOLATILE_CACHE→ потеря данных при IMMEDIATE_NONEФайл:
src/client/ublk_server.cpp:285, 318get_immediate_commit()возвращаетtrueкогда писания немедленно персистентны (нет кеша).UBLK_ATTR_VOLATILE_CACHEдолжен значить есть волатильный кеш (требует FLUSH). Семантика инвертирована.Должно быть
writeback = !cli->get_immediate_commit(inode).Эффект: При
immediate_commit=none/small(типичная конфигурация)VOLATILE_CACHE=0— ядро считает устройство немедленно-персистентным, не шлёт FLUSH. Записи в журнал OSD не синкаются. Потеря данных при крахе OSD/питания.Бонус: на строке 285 в режиме image
inodeещё равен 0 (INODE_POOL(0)=0→get_immediate_commitвозвращает true черезif (!pool_id) return true;).2. CRITICAL — Use-after-free в
handle_flush_opчерез stop_clientФайл:
src/osd/osd_flush.cpp:67-103stop_clientсинхронно вызываетmsgr_stop.cpp:123 → repeer_pgs → repeer_pg → reset_pg(osd_peering.cpp:154-158), который делаетdelete pg.flush_batch; pg.flush_batch = NULL;. После возвратаhandle_flush_opобращается кfb(= delete'd).Триггер: любая ошибка от peer-OSD во время flush (EIO/EROFS/EPIPE).
Фикс: отложить stop_client через
ringloop->set_immediate(...)или завершить flush_done-проверку до stop_client.3. HIGH —
submit_pg_flush_opsпреждевременно завершает batch при отсутствии peerФайл:
src/osd/osd_flush.cpp:47-65,158-228fb->flush_ops++инкрементируется передsubmit_flush_op. Вsubmit_flush_opпри отсутствии peer:Если это первая op в цикле,
flush_done == flush_opsсразу → batch «завершён» черезdelete fb. После возвратаsubmit_pg_flush_opsвидитreturn falseи прекращает отправку остальных rollback/stable_lists.Аналогичная проблема была закрыта в
submit_primary_subop:284иsubmit_primary_del_batch:630черезringloop->set_immediate(...). Здесь забыто.4. HIGH —
submit_primary_sync_subopshung forever если все peers недоступныФайл:
src/osd/osd_primary_subops.cpp:636-687Если ВСЕ sync_osd недоступны,
done == n_subopsсразу. Ноcontinue_primary_syncresume_3 видитn_subops > 0, ставитst = 3иreturn. Никакой callback не разбудит — sync застрял навсегда. Все последующие syncs блокируются черезsyncs_in_progress.Сравните с
submit_primary_stab_subops(стр. 743-748), где правильно вызываетсяringloop->set_immediate(...)для отсутствующих peers.5. HIGH —
pick_next_recoveryне проверяетscrub_opsиwrite_queueФайл:
src/osd/osd_flush.cpp:233-289Сравните с
osd_scrub.cpp:144-146, где корректно проверяются три:recovery_ops,scrub_ops,write_queue. Recovery может подобрать объект, который сейчас scrubbing → одновременные scrub-read и recovery-write на один oid → scrub может ложно пометить объект corrupted.6. HIGH —
pgs.at()throw при stop_pg во время syncФайл:
src/osd/osd_primary_sync.cpp:178-188Если PG исчезла во время sync (stop_pg, reshard),
pgs.atбросит исключение → крах OSD.7. CRITICAL — NFS-KV setattr (truncate) orphan data → чтение старых данных
Файл:
src/nfs/nfs_kv_setattr.cpp:125-145Алгоритм:
rm_dataдля удаления данных за пределами нового размера.Если шаг 2 падает (некоторые PG недоступны), inode говорит
size=N, но объекты в диапазоне(N, M]остаются на дисках.При последующем расширении файла через
setattr size=M' (M'>N), чтение в[N, M']вернёт старые данные вместо нулей. Нарушение POSIX-семантики truncate, утечка данных предыдущего пользователя.8. HIGH — Нет обработки SIGTERM в NFS/NBD/UBLK демонах
Файлы:
nfs_proxy.cpp:318-322,nbd_proxy.cpp:695-707,ublk_server.cpp:347-356Ни один демон не устанавливает signal handler. При
systemctl stop/ k8s SIGTERM процесс умирает мгновенно безcli->flush(). Writeback-кеш с грязными данными теряется.С учётом UBLK #1 эффект ещё хуже — ядро не шлёт FLUSH, считая кеш отсутствующим.
9. HIGH — QEMU
vitastor_co_truncatesilent no-opФайл:
src/client/qemu_driver.c:772-799qemu-img resizeили TRIM из VM возвращают success, но реальный inode в Vitastor не изменяется. Локальныйclient->sizeобновляется только в текущей сессии.При уменьшении размера через QEMU данные за пределами нового size остаются и могут быть прочитаны через другой клиент (NBD, vitastor-cli).
10. MEDIUM — QEMU
vitastor_co_block_statusнекорректный bmp_endФайл:
src/client/qemu_driver.c:1050-1067bmp_pos— абсолютный индекс,bmp_end— после вычитанияbmp_posэто длина. Сравнениеbmp_pos < bmp_endневерно когдаbmp_pos != 0.Пример:
offset=5000, task.offset=0, granularity=4096→bmp_pos=1, bmp_end=2. Цикл выполнится 1 раз вместо 2.Эффект:
qemu-img convertс sparse-копированием через QEMU API может пропустить allocated extents → данные не экспортируются.11. HIGH —
vitastor_c_destroy()без flush() теряет writebackФайл:
src/client/vitastor_c.cpp:218-228delete client->cliвызывает~cluster_client_t(), который не делает flush. C-API клиенты, не вызывающиеvitastor_c_sync()перед destroy, теряют writeback-данные.QEMU защищён через
bdrv_co_flush. fio_cluster.cpp:347-363 — не делает flush перед destroy.12. HIGH — Monitor: shared-ref mutation в
state.pg.historyчерезsave_new_pgs_txnФайлы:
mon/mon.js:625-631,mon/pg_gen.js:111-119В
apply_pool_pgsсобираетсяpg_historyс shared-ref на in-memory state:В
save_new_pgs_txn:Аналог фикса
36d2b562("Deep copy merged_history"), но вsave_new_pgs_txn. При CAS-конфликте retry может встретить уже мутированную history → накопление osd_sets / рассогласование с etcd.Фикс: deep copy при формировании
pg_history.13. HIGH — Monitor:
scale_pg_countshared refs + push вgenerate_pool_pgs→ broken prev_pgsФайлы:
mon/pg_utils.js:98-112,mon/pg_gen.js:222-223Пример:
prev_pgs=[[1,2,3]], scale до 3 PG →[[1,2,3],[1,2,3],[1,2,3]](все три = одна ссылка). При pg_size=5 push добавит 2 нуля три раза в один массив →[1,2,3,0,0,0,0,0,0](6 нулей) вместо ожидаемых 2.LP-оптимизатор получает мусорные prev_pgs при scale-up.
Фикс:
prev_pgs[i] = [...prev_pgs[i % prev_pgs.length]];(копия).14. MEDIUM-HIGH — Monitor:
orderedне передан в combinator для EC poolsФайл:
mon/pg_gen.js:199-208Конструкторы Combinator принимают
orderedпараметром, но он не передаётся (undefined → falsy). Внутриrandom_custom_combinationsэто даёт отсортированные комбинации, без ротаций ролей. Для EC pools LP-оптимизатор получает уменьшенное число valid combinations → возможна неравномерность EC ролей по OSD.В тестах (
test-optimize-fold.js:92)ordered: falseдля EC передаётся явно. В прод-коде забыто.15. HIGH — Monitor:
max_txn_ops=128лимит etcd блокирует крупные пулыФайл:
mon/pg_gen.js:121-148(FIXME уже в коде)При >60 PG в пуле сумма ops в
save_pg_configпревышает etcdmax_txn_ops(default 128). Транзакция отвергается → recheck бесконечно retry → PG конфигурация не сохраняется → пул stuck.Видимо, нужно разбить на несколько txn.
16. HIGH — Blockstore heap: assert в
recheck_bufferпри mixed STABLE/UNSTABLE chainФайл:
src/blockstore/blockstore_heap.cpp:909add_small_write(blockstore_heap.cpp:1554-1584) разрешает добавлять UNSTABLE small_write поверх STABLE small_write — отбрасывает только обратное ((type & STABLE) && !(obj->entry_type & STABLE)→ EINVAL).Если на рестарте checksum mismatch на нижнем STABLE entry,
recheck_bufferобходит head (UNSTABLE) → assert падает. OSD не стартует.17. MEDIUM — Blockstore heap: ENOENT из
add_commitприводит к assertФайл:
src/blockstore/blockstore_stable.cpp:31-59add_commit(blockstore_heap.cpp:1798-1800) возвращает ENOENT если запрашиваемая version не найдена. dequeue_stable обрабатывает только EBUSY/ENOSPC. Bad client request → assert → крах OSD.18. HIGH — Cluster client:
repeat_ops_forне разделяет WRITE/DELETE буферы (latent)Файл:
src/client/cluster_client_wb.cpp:281-283При флэше группы из WRITE и
OSD_OP_DELETEсlen>0в одной flush-операцииadd_iovупадёт на assert. Сегодня недостижимо (len=0для DELETE), но мина при первой же реализации TRIM/punch-hole на DELETE — а ты сам отмечал, что хочешь буферизовать удаления.Blockstore v1 (legacy) — критические баги
В issue #65 упоминались два, всё ещё не исправлены:
v1-1. CRITICAL — Нет проверки CRC chain первой entry журнала
Файл:
src/blockstore/v1/init.cpp:777-778started=falseдля первой записи.crc32_prevпервой entry не валидируется → можно принять остатки старого журнала после неполного wraparound.v1-2. CRITICAL — Нет per-block recheck BIG_WRITE при старте v1
Файл:
src/blockstore/v1/init.cpp:1010-1112JE_BIG_WRITE принимается без чтения и валидации данных на data-устройстве. Для small_write валидация есть; для big_write нет. Heap-blockstore делает
recheck_small_writes(). При расщеплении data fsync vs journal fsync или bit-rot — silent corruption.v1-3. CRITICAL — SIGSEGV при corrupted
je->size < 4Файл:
src/blockstore/v1/init.cpp:777+ondisk_formats.h:141-145je->sizeuint32 — при значении <4 wraparound к ~4 GiB → segfault/OOM. Heap-store проверяет минимальный размер, v1 — нет.v1-4. HIGH —
cfg_journal_sizeигнорируется в v1Файл:
src/blockstore/v1/open.cpp:147v1 вызывает только
dsk.calc_lengths(), но неdsk.check_lengths()(heap вызывает оба).cfg_journal_size > 0не подрезаетjournal_len, проверкаMIN_JOURNAL_SIZEпропущена. Приinmemory_journal=true→ huge allocation.v1-5. HIGH —
cancel_all_writes→ assert при ENOSPC raceФайл:
src/blockstore/v1/write.cpp:217-259dirty_db.find(...)→assert(... != dirty_db.end())падает раньше проверки cancellation markerreal_version == UINT64_MAX. Крах OSD при cancel-сценарии.v1-bonus —
disk_tool_resize.cpp:592при HEAP→V1 migrationfill_old_journal_entryжёстко проставляетje->type = JE_SMALL_WRITE_INSTANT/JE_SMALL_WRITEдаже для big_write entries. После миграции HEAP→V1 recovery поломан.Полный отчёт раунда 2 с дополнительными MEDIUM/LOW находками — могу выложить отдельно, если интересно.
А degraded почему пропускаем. Он тут будет)
Точно, исправил
Да, исправил, правда не так как описано, правильно просто выйти
Тут всё правильно, при return false оно сразу дёрнет неудачный колбэк, остановит и дальше по пункту 2
Ну, блин, а это между прочим после предыдущего исправления по отчёту AI) аккуратненько надо однако с вашими отчётами. Поправил.
Тут проблем нет, recovery само встаёт в write_queue, а scrub допускает параллельную работу с записью и ничего не промаркирует - сверяет версии
Не, там есть защита через pg.inflight. Во время sync pg не может stop.
Ну такое. Думаю оставлю как есть, не кажется критичным. Файл же самому пользователю и принадлежит, условно говоря.
Ну наверное стоит добавить. Пока не добавлял.
Так и задумано.
Вот это кстати важнее чем предыдущие. Поправил.
Ну возможно и стоит добавить flush в деструктор для всех клиентов. Хотя именно касаемо fio пофигу, оно для бенчей и там есть опция -end_fsync, если надо.
Хз влияет ли на что-то но добавил deep copy, хуже не будет.
Ну вот здесь точно пофиг, нулей там лишних точно не появится. Но с другой стороны почему бы и нет, добавил [...]. :)
Тут надо ещё проверить, есть вариант что это специально. Но это не точно :)
С дефолтным лимитом vitastor просто и не работает, это нормально.
Ничего особо не затрагивает, но для порядка поправил.
Вроде разумно, добавил обработку. Ненормальная ситуация (значит версия которую пытаемся коммитить не существует), но лучше в ней не падать.
Сейчас неактуально в силу того что неактуальна буферизация удалений, да.
Не должна она проверяться, у неё crc chain всегда указывает на несуществующую запись. Так и должно быть.
Не, по алгоритму старого стора и не должно быть никакого recheck big_write. Там строго доверяем ордерингу fsync-ов.
Добавил проверочку, для обработки корапченного диска разумно.
Для меты разница в вызовах тут точно специально, поэтому просто добавлять check_lengths точно нельзя. Так что наверное трабл только в том что не проверится слишком маленький размер журнала... но это в общем пофигу. Просто стор потом подвиснет быстро, писать журнал некуда будет.
Что-то вообще не понял о чём речь, проверка real_version точно идёт до cancel_all_writes.
Вообще выпилил эту конверсию из нового стора в старый, она не тестировалась и не используется. Легче если понадобится из истории вернуть :)
Интересно, в старом сторе нашло мало багов - это потому что плохо искало или потому что он правда отдебаженный?
Короче тут осталось
Data integrity review using AI (Claude) v2to Data integrity review using AI (Claude 4.7) v2