Debugging de baixo nível: entendendo exit code 126, kernel VFS e falhas de posix_spawn no Bun
Voltar para blog

Debugging de baixo nível: entendendo exit code 126, kernel VFS e falhas de posix_spawn no Bun

05/09/2026 · 4 min · Desenvolvimento

Recentemente, me deparei com um problema durante o build de uma aplicação frontend em um servidor Linux. Parece algo trivial à primeira vista (permissão negada), mas quando olho sob a perspectiva do kernel, syscalls e gerenciamento de processos, a investigação revela detalhes importantes sobre a camada VFS e a execução de binários nativos.

Abaixo, compartilho o diagnóstico, o rastreamento com strace e a solução definitiva para o problema.


O cenário e o erro inicial#

Ao rodar o comando de build da aplicação utilizando o runtime Bun:

[root@server app]# bun run build
$ vite build
/usr/bin/bash: /home/user/public_html/app/node_modules/.bin/vite: Permission denied
error: script "build" exited with code 126

Mesmo autenticado como root, a execução retornava Permission denied com Exit Code 126.

No padrão POSIX, o código de saída 126 possui um significado preciso: o comando foi encontrado, mas não pôde ser executado. Se o arquivo não existisse no caminho ou no PATH, o shell retornaria 127.


Levantamento de hipóteses#

Mesmo para o superusuário (root), o kernel Linux impõe regras rígidas via execve(2). As principais frentes a serem investigadas eram:

  1. Restrição de Montagem (noexec no VFS): A partição (/home ou /tmp) poderia estar montada com a flag noexec, fazendo com que a camada VFS do kernel (do_execveat_common) retornasse -EACCES sumariamente.
  2. Ausência do Bit de Execução (S_IXUSR/S_IXUGO): O root ignora restrições normais de leitura e escrita (CAP_DAC_OVERRIDE), mas para carregar scripts com shebang (#!/usr/bin/...) e binários ELF, o kernel obriga que ao menos um bit +x esteja explicitamente ativo no inode.
  3. Mecanismos de Hardening/MAC: SELinux, AppArmor ou perfis de isolamento poderiam estar bloqueando a transição de contexto.

Investigando com a linha de comando e tracing#

Parti para a validação no terminal.

1. Verificação de flags de montagem#

Primeiro, verifiquei se a partição possuía restrições de execução:

mount | grep -E '/home|/tmp'
# (Retorno vazio)

Isso descartou o noexec em partições dedicadas, indicando que /home fazia parte do próprio diretório raiz (/).

Em seguida, analisei a estrutura do atalho dentro de .bin:

ls -l /home/user/public_html/app/node_modules/.bin/vite
# lrwxrwxrwx 1 www www 19 Mar 13 00:34 /home/user/public_html/app/node_modules/.bin/vite -> ../vite/bin/vite.js

O symlink aparecia com rwxrwxrwx, o que é padrão (no Linux, as permissões de links simbólicos são apenas representativas). O ponto crítico estava no alvo do link, verificado com -L:

ls -lL /home/user/public_html/app/node_modules/.bin/vite
# -rw-r--r-- 1 www www 1670 Mar 9 22:51 /home/user/public_html/app/node_modules/.bin/vite

Identificada a causa primária do Exit 126: o arquivo real vite.js estava com permissão 644 (-rw-r--r--). Não havia nenhum bit x habilitado no inode de destino.

3. Validação via syscall tracing (strace)#

Para confirmar o comportamento exato no nível do kernel, tracei as chamadas execve:

strace -f -e trace=execve bun run build
execve("/root/.bun/bin/bun", ["bun", "run", "build"], 0x7ffd04079048 /* 32 vars */) = 0
$ vite build
strace: Process 2840710 attached
[pid 2840710] execve("/usr/bin/bash", ["/usr/bin/bash", "-c", "vite build"], 0x38ee2620980 /* 41 vars */) = 0
[pid 2840710] execve("/home/user/public_html/app/node_modules/.bin/vite", ["vite", "build"], 0x55f2245b4a00 /* 40 vars */) = -1 EACCES (Permission denied)
/usr/bin/bash: /home/user/public_html/app/node_modules/.bin/vite: Permission denied
[pid 2840710] +++ exited with 126 +++
error: script "build" exited with code 126

O fluxo ficou transparente:

  1. O Bun invoca o bash para rodar o script de build.
  2. O Bash tenta resolver e executar o arquivo .bin/vite.
  3. O kernel segue o link simbólico até vite.js, verifica o inode, nota a ausência do bit x e retorna -1 EACCES.

O segundo bloqueio: o posix_spawn do esbuild#

Sabendo que era um script JS, tentei rodar o arquivo diretamente através do interpretador para contornar a chamada do execve no wrapper:

bun ./node_modules/vite/bin/vite.js build

O comando avançou na interpretação inicial, mas falhou em seguida:

failed to load config from /home/user/public_html/app/vite.config.js
error during build:
Error: The service was stopped: EACCES: permission denied, posix_spawn '/home/user/public_html/app/node_modules/@esbuild/linux-x64/bin/esbuild'
    at (/home/user/public_html/app/node_modules/esbuild/lib/main.js:1083:29)
    at (/home/user/public_html/app/node_modules/esbuild/lib/main.js:703:9)
    at afterClose (/home/user/public_html/app/node_modules/esbuild/lib/main.js:693:25)

Ao chamar bun ./node_modules/vite/bin/vite.js build, o runtime abriu e interpretou o vite.js via leitura simples. Porém, durante a execução, o Vite precisou disparar sua dependência nativa compilada (esbuild) utilizando a API posix_spawn (que por baixo dos panos invoca vfork/execve).

Como o arquivo binário ELF /home/user/public_html/app/node_modules/@esbuild/linux-x64/bin/esbuild também havia sido extraído sem permissões de execução (decorrente de umask restritiva ou descompactação sem flags de preservação de atributos), o kernel disparou novamente o EACCES.


Resolução e correção definitiva#

A causa raiz foi a perda sistêmica dos bits de execução em arquivos executáveis e binários da árvore node_modules.

Para corrigir o problema em todos os pontos necessários:

# 1. Garantir execução em todos os scripts da pasta .bin
chmod -R +x /home/user/public_html/app/node_modules/.bin/

# 2. Localizar e aplicar o bit de execução cirurgicamente em todos os diretórios bin dos pacotes (incluindo o binário nativo do esbuild)
find /home/user/public_html/app/node_modules/ -path "*/bin/*" -exec chmod +x {} +

# (Opcional / Alternativa direcionada para binários nativos do esbuild)
find /home/user/public_html/app/node_modules/ -type f -name "esbuild" -exec chmod +x {} +

O resultado#

Com os bits de execução restaurados nos arquivos reais:


Considerações práticas de execução e VFS#

Diagnosticar códigos de saída e permissões no Linux exige olhar para o comportamento do kernel:

Este artigo foi útil?

Deixe uma reação rápida para apoiar o conteúdo:

CC BY-NC

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários