Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
btrfs_fixes — Herramientas personalizadas de reparación BTRFS para corrupción grave del árbol de extent donde btrfs check --repair falla (segfault, bucle o bloqueo) | Kitploit
Herramientas/GitHubGitHub/msedek/btrfs_fixes
Análisis de VulnerabilidadesAnálisis ForenseRecuperación de DatosPapers e InvestigaciónAprendizaje y Educación
GitHubmsedek/btrfs_fixes

btrfs_fixes

Herramientas personalizadas de reparación BTRFS para corrupción grave del árbol de extent donde btrfs check --repair falla (segfault, bucle o bloqueo)

Ver Repositorio
94hace 5 mesesAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

btrfs_fixes: Herramientas personalizadas de reparación de BTRFS

Herramientas escritas durante la recuperación de un pool BTRFS multidivisa de 12 TB con corrupción grave del árbol de extent que los comandos nativos (btrfs check --repair, --init-extent-tree, etc.) no pudieron reparar.

Consulte INCIDENT-ANALYSIS.md para un estudio de caso estructurado de la recuperación, una clasificación de causa raíz y un conjunto de propuestas constructivas para mejoras en btrfs-progs que habrían evitado la necesidad de la mayoría de estas herramientas.

Cuándo usar estas herramientas

Use estas herramientas SOLO si btrfs check --repair se cuelga, entra en un bucle infinito o deja el sistema de archivos en peor estado que antes.

Casos documentados donde ayudan:

  • btrfs check --repair se cuelga en (Issue #525)
[3/8] checking extents
  • btrfs check --init-extent-tree se bloquea
  • btrfs check --repair entra en un bucle infinito repitiendo las mismas reparaciones
  • Árbol de extent con miles de METADATA_ITEM que llevan owner/level/generation incorrectos
  • FS_TREE con punteros hijo obsoletos que referencian bloques reutilizados por otros árboles
  • El pool solo se monta con rescue=all,ro, falla al montar RW
  • Estas herramientas NO son para corrupción leve. Para daños normales, pruebe primero btrfs check --repair.

    Advertencias

    • HAGA UNA COPIA DE SEGURIDAD de los metadatos ANTES de ejecutar cualquier herramienta con --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
      
    • Estas herramientas realizan cambios irreversibles en el sistema de archivos
    • Todas por defecto están en modo solo escaneo (--write es optativo)
    • El sistema de archivos debe estar desmontado al ejecutar estas herramientas
    • Requiere btrfs-progs v6.19.1 o similar, con el parche EEXIST aplicado

    Compilación

    Las herramientas usan la API interna de btrfs-progs y deben compilarse dentro del árbol de fuentes de btrfs-progs:

    root@kitploit:~
    # 1. Clonar btrfs-progs
    git clone --depth 1 --branch v6.19.1 https://github.com/kdave/btrfs-progs.git
    cd btrfs-progs
    
    # 2. Aplicar el parche EEXIST (necesario para la inyección por lotes de backrefs)
    patch -p1 < ruta/a/btrfs_fixes/patches/alloc_reserved_tree_block_eexist.patch
    
    # 3. Configurar y compilar btrfs-progs base
    ./autogen.sh
    ./configure
    make -j$(nproc)
    
    # 4. Copiar los archivos .c de este repositorio al directorio de btrfs-progs
    cp ruta/a/btrfs_fixes/programs/*.c .
    
    # 5. Para cada programa, agregar al Makefile:
    echo '
    PROGNAME: PROGNAME.o $(objects) $(libs_shared)
    	@echo "  [LD]    $@"
    	$(Q)$(CC) -o $@ PROGNAME.o $(objects) $(libs_shared) $(LDFLAGS) $(LIBS)
    ' >> Makefile
    
    # 6. Compilar
    make PROGNAME
    

    Herramientas

    Orden de ejecución recomendado:

    1. scan_and_fix_all_backrefs.c (la más importante)

    La herramienta más importante. Recorre recursivamente cada árbol en el sistema de archivos (ROOT, CHUNK, EXTENT, FS, DEV, CSUM, UUID, FREE_SPACE) y detecta bloques de metadatos que carecen de un METADATA_ITEM backref en el árbol de extent. Inyecta todos los backrefs faltantes en una sola transacción para evitar el problema de "el árbol raíz se mueve entre commits".

    Uso:

    root@kitploit:~
    sudo ./scan_and_fix_all_backrefs /dev/sdX          # solo escaneo
    sudo ./scan_and_fix_all_backrefs /dev/sdX --write  # escaneo + inyección
    

    2. fix_owner_refs.c

    Corrige el owner en el TREE_BLOCK_REF inline cuando no coincide con el btrfs_header_owner() real del bloque. Las discrepancias ocurren cuando los bloques se reasignan entre árboles durante reparaciones fallidas.

    root@kitploit:~
    sudo ./fix_owner_refs /dev/sdX          # escaneo
    sudo ./fix_owner_refs /dev/sdX --write  # corregir
    

    3. fix_bad_levels.c

    Corrige entradas METADATA_ITEM y EXTENT_ITEM con un nivel incorrecto. Niveles corruptos (p. ej. 50, 55, 237) son basura dejada por btrfs check --repair al entrar en un bucle. Se verifica contra el btrfs_header_level() real del bloque.

    root@kitploit:~
    sudo ./fix_bad_levels /dev/sdX          # escaneo
    sudo ./fix_bad_levels /dev/sdX --write  # corregir
    

    4. fix_duplicate_extents.c

    Elimina METADATA_ITEM duplicados (mismo bytenr, diferentes niveles en la clave). Conserva aquel cuyo nivel coincide con btrfs_header_level y elimina el otro.

    root@kitploit:~
    sudo ./fix_duplicate_extents /dev/sdX          # escaneo
    sudo ./fix_duplicate_extents /dev/sdX --write  # eliminar duplicados
    

    5. remove_stale_ptrs.c

    Escanea cada nodo de nivel 1 del FS_TREE. Detecta punteros hijo obsoletos usando tres comprobaciones: discrepancia de owner, discrepancia de first_key, o un first_key cuyo tipo no es válido para el FS_TREE (p. ej. BLOCK_GROUP_ITEM). Los elimina con btrfs_del_ptr.

    root@kitploit:~
    sudo ./remove_stale_ptrs /dev/sdX          # escaneo
    sudo ./remove_stale_ptrs /dev/sdX --write  # eliminar
    

    6. fix_uuid_tree.c / fix_csum_tree.c

    Crea una hoja vacía para el árbol UUID / árbol CSUM respectivamente. Útil cuando el ROOT_ITEM apunta a un bloque que fue reasignado a otro árbol. El kernel regenera el árbol UUID automáticamente al montar RW. Con un árbol CSUM vacío, los archivos marcados como NODATASUM no fallan la verificación.

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

    7. set_nodatasum.c

    Establece la marca BTRFS_INODE_NODATASUM en inodos de archivo regulares. Use esto si el árbol de csum está vacío pero los archivos aún tienen checksums esperados, lo que causa errores de lectura. Con NODATASUM, el kernel omite las búsquedas de csum.

    root@kitploit:~
    sudo ./set_nodatasum /dev/sdX          # escaneo
    sudo ./set_nodatasum /dev/sdX --write  # aplicar
    

    8. fix_fstree_node.c

    Versión con una lista codificada de bloques obsoletos. Prefiera remove_stale_ptrs, que los detecta automáticamente. Use esto solo si necesita control manual sobre qué bloques específicos eliminar.

    9. add_backrefs.c

    Versión inicial con una lista codificada de backrefs faltantes. Prefiera scan_and_fix_all_backrefs, que los detecta automáticamente.

    Conjunto de herramientas de la Sesión 2 (2026-04-04/05): Recuperación extendida para corrupción masiva

    Cuando las herramientas base anteriores fueron insuficientes (pool con 200K+ errores distribuidos en múltiples árboles), se construyeron estas herramientas adicionales:

    scan_fstree_extents.c + scan_extent_tree.c

    Escáneres de Pasada 1 y Pasada 2 que recorren el FS_TREE y el árbol de extent respectivamente, produciendo archivos TSV con cada mapeo ref/extent. Se usan para construir entrada para rebuild_extent_tree_apply cuando el árbol de extent necesita ser reconstruido desde cero.

    rebuild_extent_tree_apply.c (escritor pesado)

    El escritor principal de la Fase 3. Toma una lista preprocesada de refs (del diff de scan_fstree_extents

    • scan_extent_tree) e inyecta 3M+ EXTENT_DATA_REF en el árbol de extent en fragmentos de 5000 por transacción. Se regula cada 50K elementos para evitar bloqueos por re-shingle en discos DM-SMR. Se verificó exitoso con 3,248,617 inserciones en ~34 min en 3 discos SMR WD40EFAX.
    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

    Parche quirúrgico de un solo campo para BLOCK_GROUP_ITEM.used cuando el escritor de Fase 3 deja un bg específico con exceso debido a file_extent_items superpuestos preexistentes. Utiliza el setter directo btrfs_set_block_group_used para evitar la contabilidad btrfs_update_block_group space_info (que NO queremos aquí). Prevalida 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

    Elimina una lista codificada de EXTENT_ITEM (bytenr, num_bytes, expected_inode) del árbol de extent. Se usa para limpiar extents obsoletos superpuestos en una sola hoja que impiden el montaje RO. Comprobaciones de cordura por elemento antes de eliminar (7 invariantes incluyendo lista blanca de inodos). Se ejecuta con rebuilding_extent_tree=1 + reinit_extent_tree=true para omitir la contabilidad de espacio (quien llama parchea used manualmente primero mediante patch_block_group_used).

    clean_orphan_dir_entries.c

    Limpia entradas huérfanas DIR_ITEM + DIR_INDEX del FS_TREE. Fragmentos de 100 entradas por transacción. Actualiza i_size del INODE_ITEM padre (decremento en namelen × 2: error crítico corregido: v1 decrementaba solo namelen, dejando los directorios en estado inválido). Lista de exclusión codificada para nombres de directorio críticos de nivel superior (p. ej. pelis, series, music, backups, homestorage). NUNCA decremente i_size por namelen bruto: BTRFS almacena contabilidad de namelen × 2.

    clean_orphan_inode_refs.c

    Recorre el FS_TREE en busca de elementos INODE_REF cuyo key.offset (inodo padre) está en una lista de padres huérfanos. Omite INODE_EXTREF para evitar falsos positivos (el key.offset de EXTREF es un hash, no un ID de padre). Fragmentos de 32 por transacción.

    fix_dir_inode_counts.c

    Recalcula i_size = sum(name_len × 2) y nlink = 1 para inodos de directorio cuyas cuentas fueron corrompidas por errores anteriores de limpieza de huérfanos. CRÍTICO para la seguridad: si cualquier DIR tiene nlink = 2, un solo rm -rf en su ruta eliminará silenciosamente miles de subdirectorios (bomba rmdir). Recorre las entradas DIR_INDEX, verifica DIR_ITEM para detección de colisión hash (0 colisiones verificadas empíricamente).

    remove_orphan_inode_subtrees.c

    Elimina subárboles de inodos huérfanos (familias DIR + REG independientes) del FS_TREE. Para cada objetivo: recorre y elimina EXTENT_DATA, INODE_REF, INODE_EXTREF, XATTR, y finalmente INODE_ITEM. Transacción por familia DIR (atómica por subárbol), fragmentos de 50 para REG independientes. Lista de exclusión paranoica codificada.

    ⚠️ ADVERTENCIA DE SEGURIDAD IMPORTANTE: consulte "Criterio de subconjunto a prueba de balas" más abajo.

    remove_stale_ptrs_v2.c

    Versión mejorada de remove_stale_ptrs: detecta hojas vacías con parent expected_key (v1 omitía este caso), escaneo recursivo de 2 niveles (root→level1 + level1→hojas), búfer dinámico (sin límite de 512), tolera fallos de read_tree_block.

    insert_one_extent_poc.c

    PoC para inserción de un solo extent con validación. Se usó para validar la ruta API antes de ejecutar rebuild_extent_tree_apply.

    Criterio de subconjunto a prueba de balas (CRÍTICO)

    Durante la sesión del 2026-04-05, remove_orphan_inode_subtrees falló dos veces en la misma aserción BUG_ON por dos razones diferentes:

    Vector de fallo 1: btrfs_cow_block(leaf) directo sobre hoja MIXTA (gen 3601, contiene tanto inodos huérfanos como vivos) → update_ref_for_cow recorre hijos → __btrfs_mod_ref(inc=1) sobre hijos hermanos obsoletos → btrfs_free_extent(phantom) devuelve -ENOENT → BUG_ON → SIGABRT.

    Vector de fallo 2 (descubierto después, evadido filtrando): btrfs_del_items posterior a la purga drena una hoja por debajo de LEAF_DATA_SIZE/4 = 4096 bytes → invoca push_leaf_left(sibling) o push_leaf_right(sibling) → si el hermano tiene gen ≤ last_snapshot = 3701, btrfs_block_can_be_shared devuelve 1 → update_ref_for_cow entra en la ruta refs > 1 → btrfs_inc_ref(cow_sibling, 0) → __btrfs_mod_ref(cow, level=0, inc=1) → itera todos los EXTENT_DATA del hermano obsoleto → btrfs_inc_extent_ref(phantom_bytenr) → BUG_ON(err) en extent-tree.c:1302 → SIGABRT.

    Las marcas fs_info->rebuilding_extent_tree = 1 y trans->reinit_extent_tree = true NO salvan la ruta INC: solo eximen BTRFS_DROP_DELAYED_REF (verificado en extent-tree.c:3885). BTRFS_ADD_DELAYED_REF (de btrfs_inc_ref) es fatal.

    Criterio a prueba de balas para cualquier inodo objetivo que será eliminado:

    1. La hoja que alberga el INODE_ITEM del inodo tiene gen > 3701 (post-fallo)
    2. El padre de nivel 1 de la hoja tiene gen > 3701
    3. Bytes used estimados después de la purga > 4096 (sin desencadenante de rebalanceo)
    4. TODOS los hermanos inmediatos en el nodo padre tienen gen > 3701 (incluso si la condición 3 falla, el rebalanceo hacia hermanos post-fallo es seguro)
    5. Cualquier objetivo de backref (EXTENT_DATA disk_bytenr) se resuelve en el árbol de extent actual (sin -ENOENT en la búsqueda de backref)

    Violar cualquiera de las condiciones 3+4 desencadena el vector de fallo 2. La condición 5 queda eximida por reinit_extent_tree para DROP pero NO para INC (que es lo que invoca push_leaf_left).

    Patrón de validación empírica

    Para cualquier conjunto candidato de inodos huérfanos, recorra el volcado del FS_TREE y clasifique cada hoja objetivo según las 5 condiciones a prueba de balas. Ejemplo de patrón (anonimizado):

    HojaGenElementos huérfanos / totalUsado post-purga (est)¿Rebalanceo?Hermanos inmediatosVeredicto
    $LEAF_Apost-fallomayoría huérfanos, purga fuertepor debajo del umbralSÍtodos post-fallo✓ seguro
    $LEAF_Bpost-fallomayoría vivos, purga ligerapor encima del umbralNOpadre limpio✓ seguro
    $LEAF_Cpost-fallocasi 100% huérfanosmuy por debajo de 4096SÍ forzadoobsoleto pre-fallo❌ FALLO

    Las hojas donde ≥90% de los elementos son huérfanos son la zona de peligro: drenarán por debajo del umbral de rebalanceo (LEAF_DATA_SIZE/4 = 4096 bytes) con certeza, forzando push_leaf_left/right. Si algún hermano inmediato en el nodo padre tiene gen ≤ last_snapshot, el push desencadena CoW en ese hermano, que entra en la ruta btrfs_block_can_be_shared → refs > 1 → btrfs_inc_ref → __btrfs_mod_ref(inc=1) y falla con BUG_ON(err) en btrfs_inc_extent_ref.

    Mitigación: excluya los inodos problemáticos del archivo de entrada. La herramienta procesa lo que pasa la validación previa; las hojas con objetivos mezclados seguros/no seguros pueden procesarse parcialmente listando solo el subconjunto seguro. La semántica de transacción por familia significa que cada familia segura se confirma atómicamente incluso si otras familias se excluyen.

    Resultado empírico de una sesión: partiendo de N huérfanos candidatos, tras aplicar las 5 condiciones, el subconjunto seguro final fue ~14% de la entrada, pero ese subconjunto se confirmó sin un solo BUG_ON, con un diff de 0 bytes sobre un sha256 de referencia de archivos vivos capturados antes de la escritura.

    El parche: EEXIST en alloc_reserved_tree_block

    patches/alloc_reserved_tree_block_eexist.patch modifica btrfs-progs para que cuando alloc_reserved_tree_block encuentra que el METADATA_ITEM ya existe, devuelva 0 en lugar de propagar EEXIST. Esto es necesario para que funcione la inyección por lotes de backrefs: al inyectar muchos backrefs, el sistema de refs diferidas también intenta crear METADATA_ITEM para bloques recién asignados mediante COW y colisiona con los que ya insertamos.

    Flujo de trabajo completo para recuperación severa

    root@kitploit:~
    # 1. Copia de seguridad
    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. Asegúrese de que el sistema de archivos esté desmontado
    sudo umount /mnt/pool 2>/dev/null
    
    # 3. Cero en el árbol de registro (si aplica)
    sudo btrfs rescue zero-log /dev/sdX1
    
    # 4. Escanear + corregir todo (en orden)
    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-escanear para verificar convergencia
    sudo ./scan_and_fix_all_backrefs /dev/sdX1
    sudo ./remove_stale_ptrs /dev/sdX1
    
    # 6. Si el árbol csum está roto:
    sudo ./fix_csum_tree /dev/sdX1
    sudo ./set_nodatasum /dev/sdX1 --write
    
    # 7. Intentar montar RW
    sudo mount -o rw /dev/sdX1 /mnt/pool
    
    # 8. Si monta, verificar con btrfs check readonly
    sudo btrfs check --force /dev/sdX1
    

    Limitaciones conocidas

    1. Cada reparación puede crear nuevos problemas a través de COW: cuando una herramienta modifica el árbol de extent, btrfs hace CoW de los nodos afectados. Los nuevos nodos copian punteros de los antiguos, lo que puede propagar punteros obsoletos. Pueden ser necesarios múltiples pases.

    2. Las discrepancias de ref en extent de datos no se corrigen: estas herramientas solo tocan backrefs de metadatos. Los recuentos de ref incorrectos en extents de datos (comunes después de ejecuciones fallidas de btrfs check --repair) no se limpian.

    3. Los inodos huérfanos no se limpian: las entradas de directorio huérfanas en el FS_TREE (referencias a inodos que ya no existen) no se eliminan.

    4. No reemplaza a btrfs check --repair: estas herramientas se dirigen a escenarios específicos. Para daños leves o moderados, btrfs check --repair es mejor.

    Lecciones aprendidas

    1. NUNCA apague repentinamente un sistema de archivos BTRFS multidivisa: la corrupción combinada del árbol de espacio libre + árbol de extent es extremadamente difícil de reparar.

    2. NUNCA ejecute btrfs check --repair varias veces seguidas si la primera ejecución no resolvió todo: puede entrar en un bucle infinito y empeorar drásticamente el sistema de archivos.

    3. Siempre haga una copia de seguridad de los superbloques antes de cada operación de escritura.

    4. trans->reinit_extent_tree = true es clave para ignorar fallos de DROP en refs diferidas para bloques sin backrefs.

    5. fs_info->rebuilding_extent_tree = 1 deshabilita las comprobaciones de espacio durante las reparaciones.

    6. Un commit grande con muchas inserciones es mejor que muchos commits pequeños, porque los commits intermedios mueven el árbol raíz.

    7. Los backup_slots en el SB NO son copias de seguridad históricas: son una ventana deslizante de solo los últimos 4 commits. Un bucle de btrfs check --repair de 46,000+ commits rotará cada ranura ~11,000 veces en minutos, destruyendo cualquier estado previo al fallo que se pueda recuperar del kernel. Para una retención real necesita flujos explícitos btrfs subvolume snapshot o btrfs send a otro dispositivo.

    8. reinit_extent_tree es ASIMÉTRICO: solo exime BTRFS_DROP_DELAYED_REF, NO BTRFS_ADD_DELAYED_REF. Cualquier ruta de código que llame a btrfs_inc_ref en una hoja obsoleta (incluyendo push_leaf_left/right durante el rebalanceo) aún fallará mediante btrfs_inc_extent_ref → BUG_ON(err).

    9. El criterio de seguridad para procesar inodos en un FS_TREE dañado debe incluir hermanos, no solo la hoja objetivo en sí. Consulte la sección "Criterio de subconjunto a prueba de balas".

    10. El sha256 de referencia de los archivos VIVOS es la única prueba empírica de invariantes. Captúrelo antes de cualquier operación de escritura, compare después. Cualquier discrepancia = reversión.

    11. El i_size de los DIR se almacena como sum(name_len × 2), NO sum(name_len). Cualquier herramienta de limpieza de huérfanos que decremente i_size al eliminar una entrada debe decrementar en namelen × 2. Si se hace incorrectamente, los DIR quedan en un estado inválido que puede manifestarse como nlink = 2 más tarde: lo que desencadena una bomba rmdir si el pool está montado RW (un solo rm -rf en un padre puede eliminar miles de subdirectorios silenciosamente).

    12. Los agentes revisores expertos con evidencia empírica son críticos. La sesión del 2026-04-05 utilizó dos revisores Opus paralelos (internos de btrfs + operaciones) que analizaron el plan propuesto contra la salida de dump-tree. Ellos detectaron un vector de fallo determinista (push_leaf_left → hermano obsoleto) que habría repetido los fallos anteriores. Una revisión textual del plan sin análisis empírico de dump-tree lo habría pasado por alto.

    Descargo de responsabilidad

    Estas herramientas fueron escritas para un caso de recuperación específico donde las herramientas nativas estaban fallando. No están probadas para casos de uso general. Úselas solo si entiende el código y acepta el riesgo de pérdida de datos.

    Siempre copie sus datos antes de intentar cualquier reparación si es posible.

    Licencia

    GPL-2.0 (compatible con btrfs-progs, cuya API interna usan estas herramientas).

    Descargar herramienta