setuid não funciona com conta de usuário padrão

setuid não funciona com conta de usuário padrão

Veja este programa c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    printf("UID:  %d\n", getuid());
    printf("EUID: %d\n", geteuid());
    system("id");

    printf("res=%d\n", setuid(1001));

    printf("UID:  %d\n", getuid());
    printf("EUID: %d\n", geteuid());
    system("id");
    return 0;
}

Minha conta de usuário é "teste" (id 1000). Tenho uma segunda conta de usuário: “test2” (id 1001).

Aqui está o que eu fiz:

gcc test.c -o ./a.out
sudo chown test2 ./a.out
sudo chmod u+s ./a.out

Agora, aqui está o que acontece se eu iniciar ./a.out:

UID:  1000
EUID: 1001
uid=1000(test) gid=1000(test) groups=1000(test),...
res=0
UID:  1000
EUID: 1001
uid=1000(test) gid=1000(test) groups=1000(test),...
          

Não entendo porque não vejo uid=1001 na segunda parte...

Eu tentei isso:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    printf("UID:  %d\n", getuid());
    printf("EUID: %d\n", geteuid());
    system("id");

    printf("res=%d\n", setuid(0));  // <- Here is the change

    printf("UID:  %d\n", getuid());
    printf("EUID: %d\n", geteuid());
    system("id");
    return 0;
}
          
gcc test.c -o ./a.out
sudo chown root ./a.out
sudo chmod u+s ./a.out

Aqui está o que recebo ao executar ./a.out:

UID:  1000
EUID: 0
uid=1000(test) gid=1000(test) groups=1000(test),...
res=0
UID:  0
EUID: 0
uid=0(root) gid=1000(test) groups=1000(test),...
           

Agora, funciona.

Então não entendo porque o setuid não funciona com o usuário test2 e funciona com usuários root ...

Qualquer ideia ?

Obrigado

Responder1

Há algumas coisas acontecendo aqui que causam essa confusão. Setuid funciona em ambos os casos, mas não funciona da maneira que você pensa. Primeiro, há a questão dos IDs de usuário reais e efetivos. De acordo compágina de manual setuid(2)

Se o processo de chamada for privilegiado (mais precisamente: se o processo tiver a CAP_SETUIDcapacidade em seu namespace de usuário), o UID real e o set-user-ID salvo também serão definidos.

Isso também significa que o processo de chamada nãonãotiver a CAP_SETUIDcapacidade (que é o caso de seus usuários comuns), então o UID iránãoalterar, apenas o EUID.

Então passamos para a system()chamada. Chamar system("anything")é o equivalente a chamar isto:

execl("/bin/sh", "sh", "-c", "anything", (char *) NULL);

Então ele gera /bin/sh, fornecendo argumentos para que um novo shell execute seu comando. Presumo que você use o bash como seu shell, porque o bash funciona assim

Se o shell for iniciado com o ID do usuário (grupo) efetivo diferente do ID do usuário (grupo) real e a opção -p não for fornecida, (...) o ID do usuário efetivo será definido como o ID do usuário real.

Portanto, se o seu processo não tiver CAP_SETUIDcapacidade, ele não alterará o ID do usuário real, apenas o UID efetivo. Então, quando o bash é gerado, ele descarta o UID efetivo, já que o UID não é igual ao EUID. Você pode confirmar isso ligando para

system("touch /tmp/blah")

e adicionando

FILE *file = fopen("/tmp/blah2", "w+");
fclose(file);

ao seu programa. Você verá que o proprietário de /tmp/blahserá "test" (já que o EUID será descartado na execução do shell), mas /tmp/blah2pertencerá a "test2".

informação relacionada