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

195 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```sh
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`.