Qual seria a melhor maneira de solucionar esse problema glibc?

26

Eu administro uma caixa Gentoo Hardened que usa recursos de arquivo para eliminar a maior parte da necessidade de binários raiz setuid (por exemplo, /bin/pingtem CAP_NET_RAW, etc).

De fato, o único binário que me resta é este:

abraxas ~ # find / -xdev -type f -perm -u=s
/usr/lib64/misc/glibc/pt_chown
abraxas ~ # 

Se eu remover o bit setuid ou remontar meu sistema de arquivos raiz nosuid, sshd e GNU Screen parem de funcionar, porque eles chamam grantpt(3)seus pesudoterminals principais e a glibc aparentemente executa esse programa para mostrar e chmod o pseudoterminal escravo em baixo /dev/pts/, e o GNU Screen se preocupa quando essa função falha.

O problema é que a página de manual grantpt(3)declara explicitamente que, no Linux, com o devptssistema de arquivos montado, esse binário auxiliar não é necessário; o kernel definirá automaticamente o UID & GID do escravo como o verdadeiro UID & GID do processo que foi aberto /dev/ptmx(chamando getpt(3)).

Eu escrevi um pequeno programa de exemplo para demonstrar isso:

#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
    int master;
    char slave[16];
    struct stat slavestat;
    if ((master = getpt()) < 0) {
        fprintf(stderr, "getpt: %m\n");
        return 1;
    }
    printf("Opened a UNIX98 master terminal, fd = %d\n", master);
    /* I am not going to call grantpt() because I am trying to
     * demonstrate that it is not necessary with devpts mounted,
     * the owners and mode will be set automatically by the kernel.
     */
    if (unlockpt(master) < 0) {
        fprintf(stderr, "unlockpt: %m\n");
        return 2;
    }
    memset(slave, 0, sizeof(slave));
    if (ptsname_r(master, slave, sizeof(slave)) < 0) {
        fprintf(stderr, "ptsname: %m\n");
        return 2;
    }
    printf("Device name of slave pseudoterminal: %s\n", slave);
    if (stat(slave, &slavestat) < 0) {
        fprintf(stderr, "stat: %m\n");
        return 3;
    }
    printf("Information for device %s:\n", slave);
    printf("    Owner UID:  %d\n", slavestat.st_uid);
    printf("    Owner GID:  %d\n", slavestat.st_gid);
    printf("    Octal mode: %04o\n", slavestat.st_mode & 00007777);
    return 0;
}

Observe-o em ação com o bit setuid no programa mencionado acima removido:

aaron@abraxas ~ $ id
uid=1000(aaron) gid=100(users) groups=100(users)
aaron@abraxas ~ $ ./ptytest 
Opened a UNIX98 master terminal, fd = 3
Device name of slave pseudoterminal: /dev/pts/17
Information for device /dev/pts/17:
    Owner UID:  1000
    Owner GID:  100
    Octal mode: 0620

Eu tenho apenas algumas idéias sobre como solucionar esse problema:

1) Substitua o programa por um esqueleto que simplesmente retorne 0.

2) Patch grantpt () na minha libc para não fazer nada.

Posso automatizar os dois, mas alguém tem uma recomendação para um sobre o outro, ou recomendações sobre como resolver isso?

Uma vez resolvido, posso finalmente mount -o remount,nosuid /.

Aaron Jones
fonte
Enquanto aguardo uma resposta, fui com a abordagem 1 e o sshd e o GNU Screen ainda funcionam.
Aaron Jones
Quais são exatamente os programas que falham? Talvez eles estejam quebrados e verifiquem não o pty(como deveriam), mas o programa?
vonbrand
Qualquer programa que não possui CAP_CHOWN e CAP_FOWNER, chama grantpt () e o binário auxiliar não é iniciado com EUID == 0, terá um código de retorno diferente de zero para grantpt () e os programas DEVEM abortar a criação de PTS quando isso acontecer , conforme ptmx (4).
Aaron Jones
3
Essa "solução" me dá os espertos ... no melhor dos casos, trata de um bug, provavelmente cria um novo bug, no pior caso, cria uma séria vulnerabilidade de segurança. Por favor, leve isso com os desenvolvedores glibc.
vonbrand
3
Em seguida, relate isso para o pessoal da glibc.
vonbrand

Respostas:

2

Se sua glibc estiver razoavelmente atual e os devpts estiverem configurados corretamente, não será necessário chamar o pt_chownajudante.

Você pode estar enfrentando um problema conhecido / potencial ao remover o setuid-root pt_chown.

grantpt()Com suporte devfsno glibc-2.7 , as alterações foram feitas no glibc-2.11, de modo que, em DEVFS_SUPER_MAGICvez de procurar explicitamente , ele verifica se precisa fazer algum trabalho antes de tentar chown()ou voltar a invocar pt_chown.

A partir de glibc-2.17/sysdeps/unix/grantpt.c

  ...
  uid_t uid = __getuid ();
  if (st.st_uid != uid)
    {
       if (__chown (buf, uid, st.st_gid) < 0)
       goto helper;
    }
  ...

Uma estrofe semelhante é usada para verificar o gid e as permissões. O problema é que o uid, gid e mode devem corresponder às expectativas (você, tty e exatamente 620; confirme com /usr/libexec/pt_chown --help). Caso contrário, chown()(o que exigiria os recursos CAP_CHOWN, CAP_FOWNER do binário / processo de chamada) é tentado e, se isso falhar, o pt_chownauxiliar externo (que deve ser configurado como raiz) é tentado. Para pt_chownpoder usar os recursos, ele (e, portanto, sua glibc) deve ter sido compilado HAVE_LIBCAP. No entanto , parece que pt_chowné (como no glibc-2.17 e, como você notou, embora você não tenha declarado a versão) codificado para querer, geteuid()==0 independentemente do HAVE_LIBCAPcódigo relevante de glibc-2.17/login/programs/pt_chown.c:

  ...
  if (argc == 1 && euid == 0)
    {
#ifdef HAVE_LIBCAP
  /* Drop privileges.  */
     if (uid != euid)
  ...
#endif
    /* Normal invocation of this program is with no arguments and
       with privileges.  */
    return do_pt_chown ();
  }
...
  /* Check if we are properly installed.  */
  if (euid != 0)
    error (FAIL_EXEC, 0, gettext ("needs to be installed setuid `root'"));

(Esperar que geteuid()==0antes de tentar usar recursos não pareça realmente com o espírito de recursos, eu iria registrar um bug nesse caso.)

Uma solução potencial pode ser dar CAP_CHOWN, CAP_FOWNER aos programas afetados, mas eu realmente não recomendo isso, pois você não pode restringir isso aos ptys, é claro.

Se isso não ajudar a resolvê-lo, faça o patch sshde screenseja um pouco menos desagradável do que o patch glibc. Como o problema está na glibc, uma abordagem mais limpa seria o uso seletivo de injeção de DLL para implementar um manequim grantpt().

mr.spuratic
fonte
"Como o problema está na glibc, uma abordagem mais limpa seria o uso seletivo de injeção de DLL para implementar um grantpt fictício ()." Brilhante. Por que eu não pensei nisso? Obrigado. :)
Aaron Jones