Files
reiserfsprogs/tests/vm/RESULTS.md
T
lgcosta 7b5421ce49
build / build (clang, ) (push) Failing after 7s
build / build (clang, --enable-warnings) (push) Failing after 5s
build / build (gcc, ) (push) Failing after 5s
build / build (gcc, --enable-io-failure-emulation) (push) Failing after 5s
build / build (gcc, --enable-warnings) (push) Failing after 6s
build / out-of-tree (push) Failing after 6s
build / sanitizers (push) Failing after 5s
Support clang builds; fix the defects clang and its sanitizers exposed
Clang (22.1.8 on the host, 14.0.6 in the Debian VM) now builds the tree
warning-free with --enable-warnings, in-tree and out-of-tree, with -j, and
the full VM battery passes with CC=clang: 12/12. ASan/UBSan builds run the
real tool paths (mkfs, check, tune, resize, rollback save/restore/reject)
with halt_on_error and report zero errors. Documented in tests/vm/RESULTS.md;
the CI gained clang+warnings, out-of-tree and sanitizer jobs.

Fixes found by this work:

- reiserfscore/Makefile.am: reiserfs_err.c and reiserfs_err.h shared one
  recipe. With -j, make runs the recipe once per stale target, so two
  compile_et invocations race: one deletes reiserfs_err.c ("no input
  files") while another job compiles it. Attached the recipe to
  reiserfs_err.c only; reiserfs_err.h merely depends on it. Reproduced
  with clang+ASan, stable across 5 clang / 3 asan / 3 gcc parallel builds.

- reiserfscore/Makefile.am: the rule read the source-local
  reiserfs_err.et, so VPATH builds could not find it. Use $(srcdir)/.
  make distcheck now passes.

- reiserfscore/reiserfslib.c: reiserfs_close() did fsync(fs->fs_dev) after
  reiserfs_free() released fs -- a 4-byte heap-use-after-free, flagged
  exactly at reiserfslib.c:406/407 by ASan (found by clang+ASan; gcc never
  caught it because the freed heap usually still holds the old value).
  Moved fsync before the free; reiserfs_flush() has already written
  journal and dirty buffers at that point and free_buffers() never
  writes, so the ordering preserves durability semantics.

- fsck/semantic_check.c, fsck/semantic_rebuild.c: three %K arguments passed
  &tmp_deh->deh2_dir_id where a struct reiserfs_key * is expected. It
  printed the right values only because deh2_dir_id sits at offset 4 and
  the key is read from the pointer as if dir_id sat at offset 0. Build a
  proper local key with set_key_dirid()/set_key_objectid() (the same
  idiom debugreiserfs/scan.c already uses). Verified -Waddress-of-packed-
  member (clang-only) is gone and output is unchanged.

- reiserfscore/hashes.c: four unreachable *(int *)0 = 0 stores (guarded by
  len >= 16/12/8/4 tests that cannot hold after the while (len >= 16)
  loop, len <= 15). Removed; clang's -Wnull-dereference goes quiet.
  Behavior verified: keyed_hash/yura_hash/r5_hash outputs for 16 inputs
  from 0 to 17 bytes are byte-identical before and after.

run_in_vm.sh: propagate CC into the guest, force a remount of /mnt/data
(the host rewrites the backing file under the guest), exclude generated
reiserfs_err.[ch] from the source tar. run_tests.sh: report which compiler
the battery actually used.
2026-11-12 08:51:29 -03:00

8.1 KiB
Raw Blame History

Resultados dos testes em VM

Bateria executada com tests/vm/run_in_vm.sh em duas distribuições, ambas em KVM (qemu:///session), com o código de 293e6a7.

Ambiente

Debian 12 (bookworm) Alpine 3.20.3
Kernel 6.1.0-54-cloud-amd64 6.6.49-0-virt
libc glibc 2.36 musl 1.2.5
gcc 12.2.0 13.2.1
init systemd + cloud-init OpenRC + cloud-init

Resultado por item

# Teste Debian Alpine
1 autogen.sh passa passa
1 configure --enable-warnings passa falha (ver abaixo)
1 make -j passa, 0 warnings não alcançado
2 mkreiserfs passa não alcançado
3 reiserfsck --check passa não alcançado
4 debugreiserfs -S passa não alcançado
5 reiserfstune -l passa não alcançado
6 resize_reiserfs 128→256 MiB passa não alcançado
7 fsck pós-resize passa não alcançado
8 rollback: grava passa não alcançado
8 rollback: restaura passa não alcançado
8 rollback: magic inválida rejeitada passa não alcançado
8 rollback: arquivo inexistente rejeitado passa não alcançado

Debian: 12/12. Alpine: bloqueado no item 1.

Achado 1 (bloqueante): não compila em musl

O configure do Alpine falha com:

checking for register_printf_modifier... no
checking for register_printf_specifier... no
configure: error: reiserfsprogs requires a method to add printf modifiers

Causa: reiserfscore/prints.c usa register_printf_specifier(3), extensão glibc-only (<printf.h>). O musl não fornece printf.h nem essas funções. O configure.ac trata isso como erro fatal explícito — o comportamento é correto, mas o projeto não compila fora da glibc.

Alcance medido:

Especificador Uso
%k 139
%K 106
%H 52
%M 25
%b 9
%U 4
%y 2
%30H, %20y, %20k, %20K 4
total 341

Todos passam por reiserfs_warning() / reiserfs_info(). Portar exigiria substituir o mecanismo por funções de formatação explícitas (por exemplo char *format_key(const struct reiserfs_key *) com %s), uma mudança de API interna ampla — não um ajuste local.

Impacto prático: reiserfsprogs fica restrito à glibc. Como o único uso atual da ferramenta é resgate de dados, isso importa: live images de recuperação baseadas em Alpine/musl não conseguem compilar o projeto.

Achado 2 (corrigido nesta rodada): warning real no gcc 12

O gcc 12.2 do Debian acusou, duas vezes:

../include/reiserfs_fs.h:63:54: warning: ‘relocated_ih’ may be used uninitialized

O gcc 16 do host não acusa. Investigação:

  • fsck/semantic_check.c:620 passa &relocated_ih para check_semantic_pass();
  • o bloco que escreveria *new_ih está comentado (linhas 471–486);
  • a chamada em check_check_regular_file() (linha 466) passa NULL como new_ih, com o comentário /* relocate ? new_ih : */ NULL;
  • portanto RELOCATED nunca é retornado por esse caminho e case RELOCATED: (linha 665) leria relocated_ih não inicializado.

Não é alcançável hoje, mas é uma leitura potencial de lixo de pilha atrás de um #case que o compilador não consegue descartar. Correção: inicialização explícita = { 0 } com comentário explicando por que permanece zero.

Verificado: 0 warnings no gcc 12.2 após a correção.

Em fsck/semantic_rebuild.c o caminho equivalente é seguro: o bloco if (relocate) { ... *new_ih = *ih; retval = RELOCATED; } está ativo (linhas 808–822), então new_ih é escrito antes do case RELOCATED: (linha 999).

Validação com clang

Ferramentaria: Fedora 44 (host) com clang 22.1.8, Debian 12 na VM com clang 14.0.6. Todos os builds abaixo usam --enable-warnings (-Wall -Wextra).

Build Resultado
host clang in-tree, -j 0/5 falhas, 0 warnings
host clang out-of-tree, -j passa, 0 warnings
host clang + ASan/UBSan out-of-tree, -j 0/3 falhas, 0 warnings
host gcc out-of-tree, -j 3/3 limpo
host make distcheck passa (VPATH + install/uninstall + tarballs)
VM Debian clang 14.0.6, bateria completa 12/12, 0 warnings

O que clang expôs (e foi corrigido)

  1. Corrida de build paralela em compile_et (reiserfscore/Makefile.am): reiserfs_err.c e reiserfs_err.h eram alvos de UMA mesma receita; com -j o make executa a receita duas vezes em paralelo e um processo apaga o .c enquanto o outro compila → clang: error: no input files. Reproduzido com clang+ASan; a correção vale para qualquer compilador. A receita ficou atrelada apenas a reiserfs_err.c, e reiserfs_err.h: reiserfs_err.c apenas ordena os dois. 5/5 builds paralelos limpos após a correção.

  2. Build out-of-tree quebrado (pré-existente, também com gcc): a regra referenciava reiserfs_err.et sem $(srcdir)/. Corrigido junto.

  3. -Waddress-of-packed-member (só o clang aponta; 3 sítios): fsck/semantic_check.c e fsck/semantic_rebuild.c passavam &tmp_deh->deh2_dir_id como argumento %K, que espera struct reiserfs_key *. Funcionava por coincidência de offsets (deh2_dir_id em 4 lido como k2_dir_id em 0 do cast). Agora montam uma reiserfs_key local via set_key_dirid/set_key_objectid — saída idêntica, sem depender de coincidência.

  4. -Wnull-dereference em reiserfscore/hashes.c: quatro stores mortos *(int *)0 = 0 protegidos por condições inalcançáveis (após o loop while (len >= 16), len <= 15; os ramos testavam len >= 16/12/8/4). Removidos. Comportamento verificado: saída de keyed_hash, yura_hash e r5_hash para 16 entradas (vazio a 17 bytes) byte a byte idêntica antes e depois (diff vazio).

  5. heap-use-after-free real, encontrado por ASan com clang: reiserfs_close() chamava fsync(fs->fs_dev) DEPOIS de reiserfs_free(), que libera o fs — leitura pendente de 4 bytes em memória libertada (o ASan apontou exatamente reiserfslib.c:406/407; o gcc nunca acusou porque o heap ainda "devolve" o valor velho). Corrigido movendo o fsync para antes do free — a escrita já está no dispositivo neste ponto (reiserfs_flush() despeja journal e buffers; os free_buffers() de reiserfs_free() apenas validam e libertam, sem escrever). Revalidado com ASan+UBSan em halt_on_error: mkfs, check, tune, debugreiserfs, resize 64→128 MiB e rollback completo — zero erros; bateria completa no host gcc continua 12/12.

Não-bugs registrados

  • LeakSanitizer: 136 bytes, 1 alocação em debugreiserfs (getmem no main, de vida de processo; o exit recolhe). Aceitável para ferramenta one-shot; por isso o job de CI com sanitizadores roda com detect_leaks=0.
  • AddressSanitizer: WARNING: unexpected format specifier ... %U: o interceptor do ASan não conhece os especificadores customizados do projeto. Informativo, uma vez por processo.

Achado 3: limitação do ambiente (não do código)

Nas imagens cloud o cloud-init não aplicou a senha do usuário: o datasource NoCloud exige o rótulo cidata e o CD correto, e o sshd demora a subir. O console serial, porém, fica disponível assim que o getty sobe — por isso o tests/vm/guestcmd.py dirige o pty e é o canal de automação usado. Isso não afeta o produto; afeta apenas como os testes são conduzidos.

Além disso, a rede user-mode (passt) não entrega DHCP nem DNS por padrão: a interface sobe como enp1s0 mas fica sem IP. Foi necessário configurar IP e resolver manualmente para instalar pacotes nas VMs.

Como reproduzir

cd tests/vm
./setup_env.sh                 # imagens + VMs (uma vez)
./run_in_vm.sh rfs-debian      # gcc: build + bateria completa
CC=clang ./run_in_vm.sh rfs-debian   # clang no guest (instalar antes: ver README)
./run_in_vm.sh rfs-alpine      # deve falhar no configure (esperado)
./teardown_env.sh

# host: builds paralelos in-tree e out-of-tree, distcheck
make distcheck
CC=clang ./configure --enable-warnings && make -j"$(nproc)"

Detalhes do ambiente, topologia e limitações: tests/vm/README.md.