Estou tentando escrever um programa que desligue rapidamente um laptop sem solicitar uma senha ou confirmação. O laptop está rodando Linux, especificamente Manjaro.
Para esse fim, estou tentando fazer um setuid
executável chamado downnow
que executa shutdown
com argumentos fixos (e também gera um shell). Estou usando system
aqui apenas para manter o programa curto, é uma péssima ideia do ponto de vista da segurança.
// downnow.c
#include <stdlib.h>
int main() {
system("shutdown --no-wall --halt now");
return 0;
}
então eu compilei downnow
, movi para /bin
, mudei seu dono e dei as permissões setuid e setgid:
$ sudo chown root /bin/downnow
$ sudo chgrp root /bin/downnow
$ sudo chmod u+s /bin/downnow
$ sudo chmod g+s /bin/downnow
No entanto, quando tento executar downnow
como usuário sem privilégios, ele não pode se comunicar com o systemd.
$ downnow
Failed to halt system via logind: Interactive authentication required.
Failed to talk to init daemon.
Recebo a mesma mensagem antes e depois de ch{own,grp,mod}
ing.
stat
relata as mesmas permissões em /bin/downnow
e /usr/bin/sudo
.
$ stat /bin/downnow
File: /bin/downnow
Size: XXXX Blocks: XX IO Block: XXXX regular file
Device: XXXXX/XXXXX Inode: XXXXXX Links: 1
Access: (6755/-rwsr-sr-x) Uid: ( 0/ root) Gid: ( 0/ root)
$ stat /usr/bin/sudo
File: /usr/bin/sudo
Size: XXXXXX Blocks: XXX IO Block: XXXX regular file
Device: XXXXX/XXXXX Inode: XXXXXXX Links: 1
Access: (4755/-rwsr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Por que pode downnow
deixar de elevar seus privilégios quando sudo
funciona perfeitamente?
Definir o bit de permissão apenas permite que seu aplicativo use a
setuid
chamada e, por si só, não altera suas permissões. Para fazer isso, você precisa definir o uid comsetuid(uid_t uid)
. Veja a página do manual para detalhes: https://linux.die.net/man/2/setuidVocê pode usar
geteuid()
para obter o uid efetivo atual (ou seja, o proprietário do arquivo com a permissão setuid). Veja a página do manual: https://linux.die.net/man/2/geteuidExemplo:
Provavelmente devido à falta de uma
setuid(2)
chamada. Aqui está um antes e depois em torno de tal chamada:Além disso, seu wrapper não é realmente seguro; espero que não escape para sistemas multiusuários. Provavelmente seria muito mais seguro evitar a chamada desnecessária do shell (a menos que você goste de vulnerabilidades do tipo shellshock ou o tratamento estranho de variáveis de ambiente duplicadas por, digamos,
bash
...) e, em vez disso, use umaexec(3)
chamada para substituir seu processoshutdown
diretamente:Alguns desligamentos acidentais depois...