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:
- Restrição de Montagem (
noexecno VFS): A partição (/homeou/tmp) poderia estar montada com a flagnoexec, fazendo com que a camada VFS do kernel (do_execveat_common) retornasse-EACCESsumariamente. - Ausência do Bit de Execução (
S_IXUSR/S_IXUGO): Orootignora 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+xesteja explicitamente ativo no inode. - 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 (/).
2. Inspeção do link simbólico e do arquivo real#
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:
- O Bun invoca o
bashpara rodar o script de build. - O Bash tenta resolver e executar o arquivo
.bin/vite. - O kernel segue o link simbólico até
vite.js, verifica o inode, nota a ausência do bitxe 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:
- O wrapper em
.bin/vitepassou a ser executável pelo shell, permitindo o uso natural debun run build. - O processo do Vite pôde instanciar o binário nativo do
esbuildviaposix_spawnsem bloqueios do kernel.
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:
- Mesmo o
rootrespeita o bit+x: Ao contrário de leitura e escrita, onderootignora restrições convencionais, o kernel Linux se recusa a executar inodes que não tenham explicitamente pelo menos um bit de execução marcado (S_IXUGO). - Symlinks não mascaram permissões: As permissões
777de um symlink são irrelevantes para oexecve; a validação ocorre no inode do arquivo de destino. - Ferramentas de build modernas dependem de binários nativos: Ferramentas como Vite, Turbopack e Next.js utilizam binários nativos (
esbuild,swc). Apenas contornar o script principal via interpretador não resolve se os binários filhos estiverem sem permissão de execução.
Este artigo foi útil?
Deixe uma reação rápida para apoiar o conteúdo:
Este post está licenciado sob CC BY-NC.



Comentários
Participe da discussão abaixo.
0 comentários