Autostart, systemd --user, SSH local, portais e isolamento de GPU: 10 lugares onde superfície de ataque se esconde num desktop Linux moderno
Varredura ampla de uma máquina desktop Linux (systemd + Wayland), motivada pela pergunta "isso é coisa de invasão?" ao notar um mecanismo de autostart não documentado. Nenhum dos dez pontos abaixo é exclusivo de uma distro específica — a maioria se aplica a qualquer sistema systemd recente com sessão gráfica Wayland. Complementa a auditoria de firewall/controle de aplicação (portas/egress, não repetido aqui em detalhe) e o documento de redução de superfície systemd.
Compositores Wayland minimalistas (Hyprland, Sway e afins) não leem /etc/xdg/autostart nativamente. Mas gerenciadores de sessão como o uwsm (Universal Wayland Session Manager) amarram xdg-desktop-autostart.target ao ciclo de vida da sessão gráfica. Na prática:
systemctl --user list-units 'app-*' --all
mostra uma unit app-*@autostart.service para cada .desktop gerado a partir de /etc/xdg/autostart/*.desktop e ~/.config/autostart/*.desktop, a cada login — sem precisar de GNOME/KDE completo.
Isso importa porque é um vetor de persistência real e nada óbvio: qualquer .desktop malicioso solto em ~/.config/autostart roda silenciosamente a cada login, sem precisar de cron, systemd unit própria nem entrada em .bashrc. Numa auditoria real, os arquivos existentes foram verificados como legítimos (dono de pacote confirmado via pacman -Qo ou gerenciador de pacote equivalente), mas o mecanismo em si merece registro por não ser óbvio até alguém procurar.
Decisão de mitigação — não mascarar unit por unit (não fecha a porta pra um .desktop novo), mascarar o gatilho:
systemctl --user mask xdg-desktop-autostart.target
Isso é diferente de mascarar systemd --user inteiro (nem faria sentido — em setups uwsm/Hyprland, systemd --user é a espinha dorsal da sessão inteira). O alvo é só a ponte .desktop → systemd unit.
Trade-off a considerar antes de aplicar: isso também impede o autostart automático de coisas como um keyring de credenciais (usado por ferramentas de linha de comando e navegadores para armazenar tokens/senhas). Se algo passar a reclamar de token/senha não encontrado depois do mask, a correção é subir esse serviço manualmente via exec-once (ou equivalente) na config do compositor, não desfazer o mask.
Em systemd ≥256, systemd-ssh-generator cria incondicionalmente sshd-unix-local.socket a cada boot, independente do estado de sshd.service. Ou seja: sshd.service pode estar disabled/inactive (nunca respondendo na porta 22 de rede) e ainda assim existir um socket AF_UNIX local ativo escutando.
Não é exposição de rede, mas é superfície que a maioria dos desktops pessoais não usa (a menos que dependam de acesso SSH local a containers/VMs via socket Unix/VSOCK). Mascarar a família inteira:
systemctl mask sshd.service sshd@.service sshdgenkeys.service ssh-access.target sshd-unix-local.socket
systemctl stop sshd-unix-local.socket # mask sozinho não para o que já estava ativo
Lição que se repete neste documento inteiro: mask sem --now/stop não para uma unit já ativa — só impede reativação futura.
Parte de uma auditoria de superfície é confirmar que serviços de rede que você acha que não estão instalados realmente não estão (pacman -Qs samba vazio, unit not-found, etc.) — fechar esse loop explicitamente evita reabrir a mesma pergunta numa auditoria futura.
Achado 4: DNS/resolver — cadeia completa auditada, e um bug de configuração (não de segurança) que vale documentar
Cadeia completa auditada: status do resolver, /etc/resolv.conf, drop-ins de configuração, quem escuta na porta 53. Um padrão comum em sistemas com systemd-resolved: dois stubs oficiais do próprio systemd (127.0.0.53 full-stub, 127.0.0.54 modo proxy sem DNSSEC/cache) — confirmável via strings no binário e contra o man resolved.conf, não é nada injetado por terceiros.
Achado real, mas inofensivo: uma linha em /etc/hosts com uma variável de template (ex.: $hostname) nunca interpolada por algum script/instalador — o próprio systemd-resolved loga isso como hostname inválido. Vale registrar numa auditoria pra não ser redescoberto do zero achando que é sinal de algo malicioso, quando na verdade é só um instalador que deixou um placeholder sem substituir.
ss -tulnp deve mostrar só o que já está mapeado (stubs de resolver em loopback, cliente DHCP em porta efêmera, portas efêmeras de navegador para WebRTC). Nenhuma porta TCP nova exposta é o resultado esperado — o valor de reconfirmar é notar rapidamente se algo novo apareceu desde a última auditoria.
Encontrar processos dbus-daemon/keyring rodando como root, com PPid=1 (órfãos, sem parent rastreável em /proc), é um padrão clássico de "algo suspeito" à primeira vista. Antes de agir, cross-check no journal pelo horário exato de início:
journalctl --since "<hora-1min>" --until "<hora+1min>"
Isso costuma revelar a causa exata, com PID e comando de quem pediu — por exemplo, uma ferramenta de linha de comando rodada como root (numa sessão sudo -i separada) tentando ler um token salvo via D-Bus Secret Service API; como root nunca tinha sessão D-Bus própria, o dbus auto-lança um bus + keyring privados só pra atender aquele pedido pontual, e ninguém fecha depois. Benigno, mas revela um gap comum: auditd frequentemente está rodando mas não gravando onde ferramentas como ausearch esperam — vale checar /var/log/audit/audit.log separadamente.
Lição de metodologia: PPid=1 sozinho não prova nada (é reparenting normal de daemon órfão, não indício de esconderijo). O journal do serviço que fez a ativação (não do processo em si) costuma ter o comm= de quem pediu, mesmo depois do processo original ter saído.
xdg-document-portal.service (acesso a arquivo fora do sandbox pra Flatpak/Snap) é candidato seguro a mascarar se flatpak/snap não estiverem instalados (which flatpak vazio) — nada usa esse componente especificamente.
O resto do stack (xdg-desktop-portal.service, -gtk, -hyprland/equivalente do compositor, xdg-permission-store.service) costuma ter dependência reversa real e ativa: aplicativos Electron/GTK sandboxed dependem disso pra diálogo nativo de abrir/salvar arquivo, e navegadores dependem disso pra compartilhamento de tela em chamada de vídeo. Verificar com pacman -Qi xdg-desktop-portal | grep 'Required By' (ou equivalente) antes de mascarar — e só mascarar depois de decisão consciente de aceitar perder essas funções. Não é superfície de rede (tudo D-Bus local), então o ganho é redução de processo/API disponível pra qualquer coisa rodando com o mesmo UID, não fechamento de porta.
systemctl --user mask xdg-document-portal.service # órfão real, se flatpak/snap ausente
systemctl --user mask xdg-desktop-portal.service xdg-desktop-portal-gtk.service \
xdg-desktop-portal-<backend-do-compositor>.service xdg-permission-store.service # trade-off consciente
Achado 8: /dev/nvidia* em 666 e Compute Mode Default — avaliado e decidido não mitigar (num caso de usuário único)
crw-rw-rw- em /dev/nvidia0 e afins, mais nvidia-smi --query-gpu=compute_mode retornando Default, é o padrão de fábrica do driver proprietário em desktop Linux — não algo alterado. Default compute mode não garante isolamento estrito de memória de GPU entre processos concorrentes (docs oficiais NVIDIA sobre Default vs Exclusive_Process) — classe de risco real, mas documentada pela própria NVIDIA como relevante principalmente pra GPU compartilhada multi-tenant (nuvem, laboratório), não desktop single-user.
Quando isso muda de "aceitável" pra "revisitar": awk -F: '$3>=1000 && $3<60000' /etc/passwd — se existir mais de uma conta de login local com shell, o argumento de "não tem outro usuário pra isolar de" deixa de valer, e apertar a permissão via regra udev própria (660 root:video) passa a fazer sentido.
Auditoria direta do kernel, sem confiar em "o gerenciador de serviços diz que só existe um programa":
cat /sys/kernel/security/lsm # confirma 'bpf' entre os LSMs habilitados
bpftool prog list | grep '^\S*: lsm' # filtra só programas tipo LSM, kernel inteiro
find /sys/fs/bpf -mindepth 1 # objetos pinados independentes de processo
O que se busca: confirmar que só os hooks esperados existem, sem duplicata (sinal de crash-loop reintroduzindo o mesmo hook repetidamente) e sem programa de terceiros não reconhecido. find /sys/fs/bpf vazio é o resultado esperado — algo pinado e sobrevivendo fora do ciclo de vida normal de um processo é a técnica clássica de persistência de rootkit via eBPF (programa pinado continua ativo mesmo se o processo que carregou morrer, sem PID pra rastrear).
Corroboração extra, não só "existe, então confio": inspecionar os mapas por trás de um hook próprio (bpftool map show id <n>) e conferir se os dados internos batem com o que já se sabe sobre a ferramenta (ex.: capacidade do mapa batendo com um limite conhecido de uma auditoria anterior) é mais confiável do que só confirmar que "um programa com esse nome existe".
Achado 10: verificação final pós-mitigação e pós-atualização — mitigação não é garantidamente permanente
Depois de aplicar mitigações (masks, hardening), reauditar pontualmente pra confirmar que nada ficou preso nem reapareceu. E — ponto frequentemente esquecido — repetir essa verificação depois de atualizações do sistema: um mask de unit não é garantidamente permanente, porque uma atualização de pacote pode reinstalar/reabilitar a unit e desfazer o mask silenciosamente.
Duas checagens extras que valem sempre, não só depois de uma mitigação pontual — técnicas clássicas de esconder processo malicioso:
# processo rodando a partir de local típico de payload solto por script/download
for p in /proc/[0-9]*; do
readlink "$p/exe" 2>/dev/null | grep -qE '^/(tmp|dev/shm|var/tmp)/' && echo "$p"
done
# binário deletado do disco mas ainda rodando em memória (esconde de "ls", não de /proc)
for p in /proc/[0-9]*; do
readlink "$p/exe" 2>/dev/null | grep -q '(deleted)' && echo "$p"
done
Ambas vazias é o resultado esperado. O sinal de que um mask "pegou de verdade" e sobreviveu a atualizações é nenhuma das units mascaradas reaparecer na listagem de units ativas/geradas depois de um pacman -Syu/equivalente — se algo reaparecer, é sinal de que o pacote reinstalou o unit file e desfez o mask.
-
systemctl --user list-units 'app-*' --all— algum.desktopnovo/desconhecido em/etc/xdg/autostartou~/.config/autostartvirou unit? -
systemctl --user is-enabled xdg-desktop-autostart.target sshd-unix-local.socket xdg-document-portal.service xdg-desktop-portal.service xdg-desktop-portal-gtk.service xdg-permission-store.service— todosmaskedconforme decidido? -
systemctl is-enabled sshd.service sshd@.service sshdgenkeys.service ssh-access.target sshd-unix-local.socket— mascarados a nível de sistema também, se aplicável? -
ss -tulnp— nenhuma porta TCP nova exposta além dos stubs de loopback do resolver -
resolvectl status+cat /etc/hosts— DNS servers batem com o gateway esperado, sem entrada de/etc/hostsnova e não intencional -
ps auxcom qualquer processorootde nome inesperado — antes de suspeitar, cruzarjournalctl --since/--untilno segundo exato do início pra achar ocomm=de quem ativou (não confiar só emPPid, que costuma virar1por reparenting normal) -
ls -la /dev/nvidia*+awk -F: '$3>=1000 && $3<60000' /etc/passwd— se uma segunda conta de login aparecer, revisitar a permissão de dispositivo NVIDIA -
bpftool prog list | grep '^\S*: lsm'— só os hooks esperados, sem duplicata (crash-loop) e sem programa de terceiros -
find /sys/fs/bpf -mindepth 1— vazio, ou só objetos que você reconhece; algo pinado e não rastreável a um processo vivo é bandeira vermelha - processo rodando a partir de
/tmp,/dev/shmou/var/tmp(readlink /proc/<pid>/exe) — local típico de payload solto por script/download - binário deletado do disco mas ainda rodando em memória (
readlink /proc/<pid>/exetermina em(deleted)) — esconde dels, não de/proc - Repetir todo o checklist depois de qualquer atualização grande do sistema — masks e hardening não são garantidamente permanentes
man resolved.conf—127.0.0.53vs127.0.0.54(modo proxy),DNSStubListenerExtraman systemd-ssh-generator—sshd-unix-local.socketgerado incondicionalmente desde systemd 256man uwsm—wayland-session-xdg-autostart@.target, integração comxdg-desktop-autostart.target- XDG Desktop Portal — documentação oficial — papel de cada backend,
xdg-document-portalespecífico de sandbox - NVIDIA — Multi-Instance GPU / Compute Mode docs — implicações de isolamento do modo
DefaultvsEXCLUSIVE_PROCESS bpftool-prog(8)— listagem e inspeção de programas BPF carregados, incluindo tipolsm