¿Cuál sería la mejor manera de solucionar este problema de glibc?

26

Administro un cuadro Gentoo Hardened que utiliza capacidades de archivo para eliminar la mayor parte de la necesidad de binarios setuid-root (por ejemplo, /bin/pingtiene CAP_NET_RAW, etc.).

De hecho, el único binario que me queda es este:

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

Si elimino el bit setuid, o vuelvo a montar mi sistema de archivos raíz nosuid, sshd y GNU Screen dejan de funcionar, porque llaman grantpt(3)a sus pesudoterminals maestros y glibc aparentemente ejecuta este programa para reconocer y cambiar el pseudoterminal esclavo /dev/pts/, y GNU Screen se preocupa por cuándo esta función falla

El problema es que la página de manual para grantpt(3)explícitamente establece que bajo Linux, con el devptssistema de archivos montado, no se requiere dicho binario auxiliar; el kernel configurará automáticamente el UID y GID del esclavo al UID y GID real del proceso que se abrió /dev/ptmx(mediante una llamada getpt(3)).

He escrito un pequeño programa de ejemplo para demostrar esto:

#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;
}

Obsérvelo en acción con el bit setuid en el programa mencionado eliminado:

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

Solo tengo algunas ideas sobre cómo solucionar este problema:

1) Reemplace el programa con un esqueleto que simplemente devuelve 0.

2) Parche grantpt () en mi libc para no hacer nada.

Puedo automatizar ambos, pero ¿alguien tiene una recomendación para uno sobre el otro, o recomendaciones sobre cómo resolver esto?

Una vez que esto se resuelva, finalmente puedo mount -o remount,nosuid /.

Aaron Jones
fuente
Mientras esperaba una respuesta, seguí con el enfoque 1, y sshd y GNU Screen todavía funcionan.
Aaron Jones
¿Cuáles son exactamente los programas que fallan? Tal vez que se rompen y comprobar no para el pty(como debe ser), pero para el programa?
vonbrand
Cualquier programa que no tenga CAP_CHOWN y CAP_FOWNER, llame a grantpt (), y el binario auxiliar no se inicie con EUID == 0, tendrá un código de retorno distinto de cero para grantpt (), y los programas DEBERÍAN cancelar la creación de PTS cuando esto ocurra , según ptmx (4).
Aaron Jones
3
Esa "solución" me da los pelos de punta ... en el mejor de los casos, oculta un error, probablemente crea un nuevo error, en el peor de los casos crea una grave vulnerabilidad de seguridad. Por favor, tómalo con los desarrolladores de glibc.
vonbrand
3
Luego informe esto a la gente glibc.
vonbrand

Respuestas:

2

Si su glibc está razonablemente actualizado y los dispositivos están configurados correctamente, no debería haber necesidad de invocar al pt_chownayudante.

Es posible que se encuentre con un problema conocido / potencial al eliminar setuid-root de pt_chown.

grantpt()admitidos devfsdesde glibc-2.7 , se realizaron cambios en glibc-2.11, sin embargo, en lugar de verificarlo explícitamente DEVFS_SUPER_MAGIC, verifica si necesita hacer algún trabajo antes de intentar chown()o recurrir a la invocación pt_chown.

Desde 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;
    }
  ...

Se usa una estrofa similar para verificar el gid y los permisos. El problema es que el uid, el gid y el modo deben coincidir con las expectativas (usted, tty y exactamente 620; confirme con /usr/libexec/pt_chown --help). Si no, chown()(que requeriría capacidades CAP_CHOWN, CAP_FOWNER del binario / proceso que realiza la llamada) se intenta, y si eso falla, se intenta el pt_chownayudante externo (que debe ser setuid-root). Para pt_chownpoder usar capacidades, debe haber sido compilado con él (y por lo tanto su glibc) HAVE_LIBCAP. Sin embargo , parece que pt_chownes (a partir de glibc-2.17 , y como notó aunque no ha indicado la versión) codificado para querer geteuid()==0 independientemente del 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 geteuid()==0antes de intentar usar las capacidades no parece estar realmente en el espíritu de las capacidades, iría registrando un error en este caso).

Una posible solución podría ser dar CAP_CHOWN, CAP_FOWNER a los programas afectados, pero realmente no lo recomiendo ya que no puede restringir eso a ptys, por supuesto.

Si eso no te ayuda a resolverlo, parchar sshdy screenes un poco menos desagradable que parchear glibc. Sin embargo, dado que el problema radica en glibc, un enfoque más limpio sería el uso selectivo de la inyección de DLL para implementar un maniquí grantpt().

Sr. púrpura
fuente
"Sin embargo, dado que el problema radica en glibc, un enfoque más limpio sería el uso selectivo de la inyección de DLL para implementar un grantpt ficticio ()". - Brillante ¿Por qué no pensé en eso? Gracias. :)
Aaron Jones