Quelle serait la meilleure façon de contourner ce problème de glibc?

26

J'administre une boîte Gentoo Hardened qui utilise les capacités des fichiers pour éliminer la plupart des binaires setuid-root (par exemple /bin/pinga CAP_NET_RAW, etc.).

En fait, le seul binaire qu'il me reste est celui-ci:

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

Si je supprime le bit setuid ou remonte mon système de fichiers racine nosuid, sshd et GNU Screen cessent de fonctionner, car ils appellent grantpt(3)leurs maîtres pesudoterminals et glibc exécute apparemment ce programme pour afficher et chmod le pseudoterminal esclave sous /dev/pts/, et GNU Screen se soucie quand cette fonction échoue.

Le problème est que la page de manuel de grantpt(3)explicitement indique que sous Linux, avec le devptssystème de fichiers monté, aucun binaire d'assistance n'est requis; le noyau définira automatiquement l'UID et le GID de l'esclave sur le véritable UID et GID du processus qui s'est ouvert /dev/ptmx(en appelant getpt(3)).

J'ai écrit un petit exemple de programme pour le démontrer:

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

Observez-le en action avec le bit setuid sur le programme susmentionné supprimé:

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

Je n'ai que quelques idées sur la façon de contourner ce problème:

1) Remplacez le programme par un squelette qui renvoie simplement 0.

2) Patch grantpt () dans ma libc pour ne rien faire.

Je peux automatiser les deux, mais quelqu'un a-t-il une recommandation à faire l'un par rapport à l'autre, ou des recommandations sur la manière de résoudre ce problème?

Une fois que cela est résolu, je peux enfin mount -o remount,nosuid /.

Aaron Jones
la source
En attendant une réponse, j'ai opté pour l'approche 1, et sshd et GNU Screen fonctionnent toujours.
Aaron Jones
Quels sont exactement les programmes qui échouent? Peut-être qu'ils sont cassés et ne vérifient pas le pty(comme ils le devraient) mais le programme?
vonbrand
Tout programme qui n'a pas CAP_CHOWN et CAP_FOWNER, appelle grantpt () et le binaire d'assistance n'est pas démarré avec EUID == 0, aura un code retour différent de zéro pour grantpt (), et les programmes DEVRAIENT abandonner la création de PTS lorsque cela se produit , selon ptmx (4).
Aaron Jones
3
Cette "solution" me donne les caprices ... dans le meilleur des cas, elle passe au-dessus d'un bogue, elle crée probablement un nouveau bogue, dans le pire des cas, elle crée une sérieuse vulnérabilité de sécurité. Veuillez en discuter avec les développeurs de la glibc.
vonbrand
3
Signalez ensuite cela aux personnes de la glibc.
vonbrand

Réponses:

2

Si votre glibc est raisonnablement à jour et que devpts est correctement configuré, il ne devrait pas du tout être nécessaire d'appeler l' pt_chownassistant.

Vous rencontrez peut-être un problème connu / potentiel supprimant setuid-root pt_chown.

grantpt()pris en charge devfspar glibc-2.7 , des modifications ont été apportées à glibc-2.11, de sorte que plutôt que de vérifier explicitement DEVFS_SUPER_MAGIC, il vérifie plutôt s'il doit faire un travail avant d'essayer chown()ou de revenir à l'invocation pt_chown.

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

Une strophe similaire est utilisée pour vérifier le gid et les autorisations. Le hic est que l'uid, le gid et le mode doivent correspondre aux attentes (vous, tty et exactement 620; confirmez avec /usr/libexec/pt_chown --help). Sinon, chown()(ce qui nécessiterait des capacités CAP_CHOWN, CAP_FOWNER du binaire / processus appelant) est tenté, et si cela échoue, l' pt_chownassistant externe (qui doit être setuid-root) est tenté. Pour pt_chownpouvoir utiliser les capacités, il (et donc votre glibc) doit avoir été compilé avec HAVE_LIBCAP. Cependant , il semble que pt_chown(à partir de la glibc-2.17 , et comme vous l'avez remarqué bien que vous n'ayez pas indiqué la version) codé en dur à vouloir geteuid()==0 indépendamment du HAVE_LIBCAPcode pertinent 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'"));

(Attendre geteuid()==0avant d'essayer d'utiliser des capacités ne semble pas vraiment dans l'esprit des capacités, j'irais avec l'enregistrement d'un bug sur celui-ci.)

Une solution de contournement potentielle pourrait être de donner CAP_CHOWN, CAP_FOWNER aux programmes concernés, mais je ne le recommande vraiment pas car vous ne pouvez pas limiter cela aux ptys bien sûr.

Si cela ne vous aide pas à le résoudre, le rapiéçage sshdet screenest un peu moins désagréable que glibc rapiéçage. Étant donné que le problème réside dans la glibc, une approche plus propre serait l' utilisation sélective de l'injection de DLL pour implémenter un mannequin grantpt().

Mr Spuratic
la source
"Puisque le problème réside dans la glibc, une approche plus propre serait une utilisation sélective de l'injection de DLL pour implémenter une fausse subvention ()." -- Brillant. Pourquoi n'y ai-je pas pensé? Merci. :)
Aaron Jones