Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
btrfs_fixes — Пользовательские инструменты восстановления BTRFS для серьезного повреждения дерева extent, когда btrfs check --repair завершается неудачей (ошибка сегментации, зацикливание или взаимная блокировка) | Kitploit
Инструменты/GitHubGitHub/msedek/btrfs_fixes
Анализ уязвимостейФорензикаВосстановление ДанныхСтатьи и ИсследованияОбучение и Образование
GitHubmsedek/btrfs_fixes

btrfs_fixes

Пользовательские инструменты восстановления BTRFS для серьезного повреждения дерева extent, когда btrfs check --repair завершается неудачей (ошибка сегментации, зацикливание или взаимная блокировка)

Репозиторий
94 месяцев назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

btrfs_fixes: Пользовательские инструменты восстановления BTRFS

Пользовательские инструменты, написанные в процессе восстановления 12-ТБ пула BTRFS на нескольких устройствах с серьёзным повреждением дерева экстентов, которое не смогли исправить стандартные команды (btrfs check --repair, --init-extent-tree и т.д.).

См. INCIDENT-ANALYSIS.md для структурированного описания случая восстановления, классификации первопричин и набора конструктивных предложений по улучшению btrfs-progs, которые позволили бы избежать необходимости в большинстве этих инструментов.

Когда использовать эти инструменты

Используйте эти инструменты ТОЛЬКО если btrfs check --repair вызывает segfault, входит в бесконечный цикл или оставляет файловую систему в худшем состоянии, чем до него.

Документированные случаи, в которых они помогают:

  • btrfs check --repair вызывает segfault на [3/8] checking extents (Issue #525)
  • btrfs check --init-extent-tree вызывает взаимоблокировку
  • btrfs check --repair входит в бесконечный цикл, повторяя одни и те же исправления
  • Дерево экстентов с тысячами METADATA_ITEM, несущих неверные owner/level/generation
  • FS_TREE с устаревшими дочерними указателями, ссылающимися на блоки, повторно используемые другими деревьями
  • Пул монтируется только с rescue=all,ro, не удаётся смонтировать RW

Эти инструменты НЕ для лёгких повреждений. При обычных повреждениях сначала попробуйте btrfs check --repair.

Предупреждения

  • СДЕЛАЙТЕ РЕЗЕРВНУЮ КОПИЮ метаданных ПЕРЕД запуском любого инструмента с --write:
    root@kitploit:~
    for DEV in sda1 sdb1 sdc1; do
      sudo dd if=/dev/$DEV of=sb_${DEV}.bin bs=4096 count=1 skip=16
    done
    
  • Эти инструменты вносят необратимые изменения в файловую систему
  • Все по умолчанию работают только в режиме сканирования (--write включается явно)
  • Файловая система должна быть размонтирована при запуске этих инструментов
  • Требуется btrfs-progs v6.19.1 или аналогичная, с применённым патчем EEXIST

Сборка

Инструменты используют внутренний API btrfs-progs и должны собираться внутри дерева исходных кодов btrfs-progs:

root@kitploit:~
# 1. Clone btrfs-progs
git clone --depth 1 --branch v6.19.1 https://github.com/kdave/btrfs-progs.git
cd btrfs-progs

# 2. Apply the EEXIST patch (required for batch backref injection)
patch -p1 < path/to/btrfs_fixes/patches/alloc_reserved_tree_block_eexist.patch

# 3. Configure and build base btrfs-progs
./autogen.sh
./configure
make -j$(nproc)

# 4. Copy the .c files from this repo into the btrfs-progs directory
cp path/to/btrfs_fixes/programs/*.c .

# 5. For each program, add to the Makefile:
echo '
PROGNAME: PROGNAME.o $(objects) $(libs_shared)
	@echo "  [LD]    $@"
	$(Q)$(CC) -o $@ PROGNAME.o $(objects) $(libs_shared) $(LDFLAGS) $(LIBS)
' >> Makefile

# 6. Build
make PROGNAME

Инструменты

Рекомендуемый порядок выполнения:

1. scan_and_fix_all_backrefs.c (самый важный)

Самый важный инструмент. Рекурсивно обходит каждое дерево в файловой системе (ROOT, CHUNK, EXTENT, FS, DEV, CSUM, UUID, FREE_SPACE) и обнаруживает блоки метаданных, у которых отсутствует обратная ссылка METADATA_ITEM в дереве экстентов. Вставляет все недостающие обратные ссылки в одну транзакцию, чтобы избежать проблемы «перемещения корневого дерева между фиксациями».

Использование:

root@kitploit:~
sudo ./scan_and_fix_all_backrefs /dev/sdX          # только сканирование
sudo ./scan_and_fix_all_backrefs /dev/sdX --write  # сканирование + вставка

2. fix_owner_refs.c

Исправляет owner в inline TREE_BLOCK_REF, когда он не соответствует фактическому btrfs_header_owner() блока. Несоответствия возникают при перераспределении блоков между деревьями во время неудачных попыток восстановления.

root@kitploit:~
sudo ./fix_owner_refs /dev/sdX          # сканирование
sudo ./fix_owner_refs /dev/sdX --write  # исправление

3. fix_bad_levels.c

Исправляет записи METADATA_ITEM и EXTENT_ITEM с неверным уровнем. Повреждённые уровни (например, 50, 55, 237) — это мусор, оставшийся после того, как btrfs check --repair вошёл в цикл. Проверяется по реальному btrfs_header_level() блока.

root@kitploit:~
sudo ./fix_bad_levels /dev/sdX          # сканирование
sudo ./fix_bad_levels /dev/sdX --write  # исправление

4. fix_duplicate_extents.c

Удаляет дубликаты METADATA_ITEM (одинаковый bytenr, разные уровни в ключе). Сохраняет тот, уровень которого совпадает с btrfs_header_level, и удаляет другой.

root@kitploit:~
sudo ./fix_duplicate_extents /dev/sdX          # сканирование
sudo ./fix_duplicate_extents /dev/sdX --write  # удаление дубликатов

5. remove_stale_ptrs.c

Сканирует каждый узел уровня‑1 FS_TREE. Обнаруживает устаревшие дочерние указатели с помощью трёх проверок: несоответствие owner, несоответствие first_key или first_key с типом, недопустимым для FS_TREE (например, BLOCK_GROUP_ITEM). Удаляет их с помощью btrfs_del_ptr.

root@kitploit:~
sudo ./remove_stale_ptrs /dev/sdX          # сканирование
sudo ./remove_stale_ptrs /dev/sdX --write  # удаление

6. fix_uuid_tree.c / fix_csum_tree.c

Создаёт пустой лист для дерева UUID / дерева CSUM соответственно. Полезно, когда ROOT_ITEM указывает на блок, который был переназначен другому дереву. Ядро автоматически пересоздаёт дерево UUID при монтировании RW. При пустом дереве CSUM файлы с флагом NODATASUM не дают сбоя проверки.

root@kitploit:~
sudo ./fix_uuid_tree /dev/sdX
sudo ./fix_csum_tree /dev/sdX

7. set_nodatasum.c

Устанавливает флаг BTRFS_INODE_NODATASUM на инодах обычных файлов. Используйте это, если дерево контрольных сумм пусто, но файлы всё ещё имеют ожидаемые контрольные суммы, что вызывает ошибки чтения. С NODATASUM ядро пропускает поиск контрольных сумм.

root@kitploit:~
sudo ./set_nodatasum /dev/sdX          # сканирование
sudo ./set_nodatasum /dev/sdX --write  # применение

8. fix_fstree_node.c

Версия с жёстко заданным списком устаревших блоков. Предпочитайте remove_stale_ptrs, который обнаруживает их автоматически. Используйте это только если вам нужен ручной контроль над тем, какие конкретные блоки удалять.

9. add_backrefs.c

Начальная версия с жёстко заданным списком недостающих обратных ссылок. Предпочитайте scan_and_fix_all_backrefs, который обнаруживает их автоматически.

Набор инструментов сессии 2 (2026-04-04/05): Расширенное восстановление при массовых повреждениях

Когда базовых инструментов, описанных выше, было недостаточно (пул с 200 000+ ошибок, распределённых по нескольким деревьям), были созданы следующие дополнительные инструменты:

scan_fstree_extents.c + scan_extent_tree.c

Сканеры проходов 1 и 2, которые обходят FS_TREE и дерево экстентов соответственно, создавая TSV-файлы со всеми сопоставлениями ссылок/экстентов. Используются для построения входных данных для rebuild_extent_tree_apply, когда дерево экстентов нужно перестроить с нуля.

rebuild_extent_tree_apply.c (тяжёлый записывающий инструмент)

Основной записывающий инструмент фазы 3. Принимает предварительно свёрнутый список ссылок (из разницы scan_fstree_extents + scan_extent_tree) и вставляет 3M+ EXTENT_DATA_REF в дерево экстентов порциями по 5000 за транзакцию. Ограничивает каждые 50 тыс. элементов, чтобы избежать зависаний при решингел DM-SMR. Проверено успешно: 3 248 617 вставок примерно за 34 минуты на 3× WD40EFAX SMR-дисках.

root@kitploit:~
sudo ./rebuild_extent_tree_apply /dev/sdX1 refs_folded.txt to_insert.txt watermark.txt --dryrun
sudo ./rebuild_extent_tree_apply /dev/sdX1 refs_folded.txt to_insert.txt watermark.txt --write

patch_block_group_used.c

Точечный исправитель одного поля для BLOCK_GROUP_ITEM.used, когда записывающий инструмент фазы 3 оставляет определённую группу блоков с превышением из-за уже существующих перекрывающихся file_extent_items. Использует прямой установщик btrfs_set_block_group_used, чтобы избежать учёта space_info через btrfs_update_block_group (который нам здесь НЕ нужен). Предварительно проверяет flags & BTRFS_BLOCK_GROUP_DATA.

root@kitploit:~
sudo ./patch_block_group_used /dev/sdX1 <bg_bytenr> <bg_length> <new_used> --write

remove_extent_items_by_key.c

Удаляет жёстко заданный список EXTENT_ITEM (bytenr, num_bytes, expected_inode) из дерева экстентов. Используется для очистки перекрывающихся устаревших экстентов в одном листе, которые препятствуют монтированию RO. Перед удалением выполняет проверки для каждого элемента (7 инвариантов, включая белый список inode). Запускается с rebuilding_extent_tree=1 + reinit_extent_tree=true, чтобы пропустить учёт пространства (вызывающий сначала вручную исправляет used через patch_block_group_used).

clean_orphan_dir_entries.c

Очищает записи-сироты DIR_ITEM + DIR_INDEX из FS_TREE. Порции по 100 записей за транзакцию. Обновляет i_size родительского INODE_ITEM (уменьшение на namelen × 2: исправлена критическая ошибка: v1 уменьшала только на namelen, оставляя каталоги в недопустимом состоянии). Жёстко заданный список исключений для критических имён каталогов верхнего уровня (например, pelis, series, music, backups, homestorage). НИКОГДА не уменьшайте i_size на сырой namelen: BTRFS хранит учёт namelen × 2.

clean_orphan_inode_refs.c

Обходит FS_TREE в поисках элементов INODE_REF, у которых key.offset (родительский inode) находится в списке родителей-сирот. Пропускает INODE_EXTREF, чтобы избежать ложных срабатываний (у EXTREF key.offset — это хеш, а не идентификатор родителя). Порции по 32 за транзакцию.

fix_dir_inode_counts.c

Пересчитывает i_size = sum(name_len × 2) и nlink = 1 для inode каталогов, чьи счётчики были повреждены предыдущими ошибками очистки сирот. КРИТИЧЕСКИ ВАЖНО для безопасности: если какой-либо каталог имеет nlink = 2, одна команда rm -rf по его пути молча удалит тысячи подкаталогов (rmdir-бомба). Обходит записи DIR_INDEX, перекрёстно проверяет DIR_ITEM на обнаружение коллизий хешей (эмпирически подтверждено 0 коллизий).

remove_orphan_inode_subtrees.c

Удаляет поддеревья inode-сирот (семейства каталогов + отдельные REG) из FS_TREE. Для каждой цели: обходит и удаляет EXTENT_DATA, INODE_REF, INODE_EXTREF, XATTR и, наконец, INODE_ITEM. Транзакция на каждое семейство каталогов (атомарно для поддерева), порции по 50 для отдельных REG. Жёстко заданный параноидальный список исключений.

⚠️ ВАЖНОЕ ПРЕДУПРЕЖДЕНИЕ ПО БЕЗОПАСНОСТИ: см. критерий «Неуязвимое подмножество» ниже.

remove_stale_ptrs_v2.c

Улучшенная версия remove_stale_ptrs: обнаруживает пустые листья с parent expected_key (v1 пропускала этот случай), рекурсивное сканирование 2 уровней (корень→уровень1 + уровень1→листья), динамический буфер (без ограничения 512), терпим к сбоям read_tree_block.

insert_one_extent_poc.c

PoC для вставки одного экстента с проверкой. Используется для проверки пути API перед запуском rebuild_extent_tree_apply.

Критерий «Неуязвимое подмножество» (КРИТИЧЕСКИ ВАЖНО)

Во время сессии 2026-04-05 remove_orphan_inode_subtrees дважды упал на одном и том же утверждении BUG_ON по двум разным причинам:

Вектор аварии 1: Прямой вызов btrfs_cow_block(leaf) для смешанного листа (gen 3601, содержит как inode-сироты, так и живые) → update_ref_for_cow обходит дочерние элементы → __btrfs_mod_ref(inc=1) по устаревшим дочерним элементам-соседям → btrfs_free_extent(phantom) возвращает -ENOENT → BUG_ON → SIGABRT.

Вектор аварии 2: btrfs_del_items после очистки опустошает лист ниже LEAF_DATA_SIZE/4 = 4096 байт → вызывает push_leaf_left(sibling) или push_leaf_right(sibling) → если у соседа gen ≤ last_snapshot = 3701, btrfs_block_can_be_shared возвращает 1 → update_ref_for_cow входит в ветку refs > 1 → btrfs_inc_ref(cow_sibling, 0) → __btrfs_mod_ref(cow, level=0, inc=1) → перебирает все EXTENT_DATA устаревшего соседа → btrfs_inc_extent_ref(phantom_bytenr) → BUG_ON(err) в extent-tree.c:1302 → SIGABRT.

Флаги fs_info->rebuilding_extent_tree = 1 и trans->reinit_extent_tree = true НЕ спасают путь INC: они освобождают только от BTRFS_DROP_DELAYED_REF (проверено в extent-tree.c:3885). BTRFS_ADD_DELAYED_REF (из btrfs_inc_ref) является фатальным.

Критерий «Неуязвимое подмножество» для любого целевого inode, который будет удалён:

  1. У листа, содержащего INODE_ITEM inode, gen > 3701 (после аварии)
  2. У родительского узла уровня 1 этого листа gen > 3701
  3. Оценочное количество байт used после очистки > 4096 (не вызывается перебалансировка)
  4. ВСЕ непосредственные соседи в родительском узле имеют gen > 3701 (даже если условие 3 не выполняется, перебалансировка на соседи после аварии безопасна)
  5. Любые цели обратных ссылок (EXTENT_DATA disk_bytenr) разрешаются в текущем дереве экстентов (нет -ENOENT при поиске обратной ссылки)

Нарушение любого из условий 3+4 вызывает вектор аварии 2. Условие 5 снимается reinit_extent_tree для DROP, но НЕ для INC (именно это вызывает push_leaf_left).

Эмпирический шаблон проверки

Для любого набора потенциальных inode-сирот обойдите дамп FS_TREE и классифицируйте каждый целевой лист по 5 условиям неуязвимости. Пример шаблона (анонимизированный):

Листья, где ≥90% элементов являются сиротами, представляют собой опасную зону: они с уверенностью опустошатся ниже порога перебалансировки (LEAF_DATA_SIZE/4 = 4096 байт), что вынудит push_leaf_left/right. Если какой-либо непосредственный сосед в родительском узле имеет gen ≤ last_snapshot, толчок запускает CoW на этом соседе, который входит в путь btrfs_block_can_be_shared → refs > 1 → btrfs_inc_ref → __btrfs_mod_ref(inc=1) и аварийно завершается с BUG_ON(err) в btrfs_inc_extent_ref.

Смягчение: исключите проблемные inode из входного файла. Инструмент обрабатывает то, что проходит предварительную проверку; листья со смешанными безопасными/небезопасными целями могут быть частично обработаны путём указания только безопасного подмножества. Семантика транзакций для каждого семейства означает, что каждое безопасное семейство фиксируется атомарно, даже если другие семейства исключены.

Эмпирический результат: начиная с N кандидатов-сирот, после применения всех 5 условий окончательное безопасное подмножество составило ~14% от входных данных, но это подмножество зафиксировалось без единого BUG_ON, с нулевым расхождением по базовой sha256 живых файлов, полученной до записи.

Патч: EEXIST в alloc_reserved_tree_block

patches/alloc_reserved_tree_block_eexist.patch изменяет btrfs-progs так, что когда alloc_reserved_tree_block обнаруживает, что METADATA_ITEM уже существует, он возвращает 0 вместо распространения EEXIST. Это необходимо для работы пакетной вставки обратных ссылок: при вставке множества обратных ссылок система отложенных ссылок также пытается создать METADATA_ITEM для блоков, только что выделенных через COW, и сталкивается с теми, которые мы уже вставили.

Полный рабочий процесс для серьёзного восстановления

root@kitploit:~
# 1. Backup
mkdir -p backup
for DEV in /dev/sdX1 /dev/sdY1; do
  sudo dd if=$DEV of=backup/$(basename $DEV).sb bs=4096 count=1 skip=16
done

# 2. Make sure the filesystem is unmounted
sudo umount /mnt/pool 2>/dev/null

# 3. Zero the log tree (if applicable)
sudo btrfs rescue zero-log /dev/sdX1

# 4. Scan + fix everything (in order)
sudo ./scan_and_fix_all_backrefs /dev/sdX1 --write
sudo ./fix_bad_levels /dev/sdX1 --write
sudo ./fix_owner_refs /dev/sdX1 --write
sudo ./fix_duplicate_extents /dev/sdX1 --write
sudo ./remove_stale_ptrs /dev/sdX1 --write

# 5. Re-scan to verify convergence
sudo ./scan_and_fix_all_backrefs /dev/sdX1
sudo ./remove_stale_ptrs /dev/sdX1

# 6. If the csum tree is broken:
sudo ./fix_csum_tree /dev/sdX1
sudo ./set_nodatasum /dev/sdX1 --write

# 7. Try mounting RW
sudo mount -o rw /dev/sdX1 /mnt/pool

# 8. If it mounts, verify with btrfs check readonly
sudo btrfs check --force /dev/sdX1

Известные ограничения

  1. Каждое исправление может создавать новые проблемы через COW: когда инструмент изменяет дерево экстентов, btrfs выполняет COW для затронутых узлов. Новые узлы копируют указатели из старых, что может распространять устаревшие указатели. Может потребоваться несколько проходов.

  2. Несоответствия ссылок на экстенты данных не исправляются: эти инструменты затрагивают только обратные ссылки метаданных. Неправильные счётчики ссылок на экстенты данных (часто после неудачных запусков btrfs check --repair) не очищаются.

  3. Inode-сироты не очищаются: записи каталогов-сирот в FS_TREE (ссылки на несуществующие inode) не удаляются.

  4. Не заменяет btrfs check --repair: эти инструменты нацелены на конкретные сценарии. При лёгких или умеренных повреждениях лучше использовать btrfs check --repair.

Вынесенные уроки

  1. НИКОГДА не делайте жёсткую перезагрузку файловой системы BTRFS на нескольких устройствах: сочетанное повреждение дерева свободного пространства и дерева экстентов чрезвычайно трудно исправить.

  2. НИКОГДА не запускайте btrfs check --repair несколько раз подряд, если первый запуск не решил все проблемы: он может войти в бесконечный цикл и сделать файловую систему значительно хуже.

  3. Всегда делайте резервную копию суперблоков перед каждой операцией записи.

  4. trans->reinit_extent_tree = true является ключом к игнорированию сбоев DROP в отложенных ссылках для блоков без обратных ссылок.

  5. fs_info->rebuilding_extent_tree = 1 отключает проверки пространства во время исправлений.

  6. Одна большая фиксация с множеством вставок лучше, чем много маленьких фиксаций, потому что промежуточные фиксации перемещают корневое дерево.

  7. backup_slots в суперблоке НЕ являются историческими резервными копиями: это скользящее окно только из 4 последних фиксаций. Цикл btrfs check --repair из 46 000+ фиксаций повернёт каждый слот около 11 000 раз за несколько минут, уничтожая любое состояние до аварии, которое можно восстановить из ядра. Для реального хранения вам нужны явные btrfs subvolume snapshot или потоки btrfs send на другое устройство.

Отказ от ответственности

Эти инструменты были написаны для конкретного случая восстановления, когда стандартные инструменты не работали. Они не протестированы для общего использования. Используйте их только если вы понимаете код и принимаете риск потери данных.

Всегда копируйте свои данные перед попыткой любого восстановления, если это вообще возможно.

Лицензия

GPL-2.0 (совместима с btrfs-progs, чей внутренний API используют эти инструменты).

Скачать инструмент
LeafGenOrphan items / totalPost-purge used (est)Rebalance?Immediate siblingsVerdict
$LEAF_Aпосле авариив основном сироты, интенсивная очистканиже порогаДАвсе после аварии✓ безопасно
$LEAF_Bпосле авариив основном живые, лёгкая очисткавыше порогаНЕТчистый родитель✓ безопасно
$LEAF_Cпосле авариипочти 100% сиротзначительно ниже 4096ДА принудительноустаревшие до аварии❌ СБОЙ

reinit_extent_tree АСИММЕТРИЧЕН: освобождает только от BTRFS_DROP_DELAYED_REF, НЕ от BTRFS_ADD_DELAYED_REF. Любой путь кода, который вызывает btrfs_inc_ref для устаревшего листа (включая push_leaf_left/right во время перебалансировки), всё равно вызовет сбой через btrfs_inc_extent_ref → BUG_ON(err).

  • Критерий безопасности для обработки inode в повреждённом FS_TREE должен включать соседей, а не только сам целевой лист. См. раздел «Критерий «Неуязвимое подмножество»».

  • Базовая sha256 ЖИВЫХ файлов является единственным эмпирическим доказательством инвариантов. Снимайте её до любой операции записи, сравнивайте после. Любое несовпадение = откат.

  • У каталогов i_size хранится как sum(name_len × 2), НЕ sum(name_len). Любой инструмент очистки сирот, который уменьшает i_size при удалении записи, должен уменьшать на namelen × 2. Ошибка в этом оставляет каталоги в недопустимом состоянии, которое может проявиться позже как nlink = 2: это вызывает rmdir-бомбу, если пул смонтирован RW (один rm -rf для родителя может молча удалить тысячи подкаталогов).

  • Агенты-эксперты с эмпирическими данными критически важны. В сессии 2026-04-05 использовались два параллельных рецензента Opus (внутреннее устройство btrfs + эксплуатация), которые анализировали предложенный план на основе вывода dump-tree. Они выявили детерминированный вектор аварии (push_leaf_left → устаревший сосед), который повторил бы предыдущие сбои. Текстовый обзор плана без эмпирического анализа dump-tree не смог бы этого обнаружить.