Jaki byłby najlepszy sposób obejścia tego problemu z Glibc?

26

Zarządzam modułem Gentoo Hardened, który korzysta z możliwości plików, aby wyeliminować większość potrzebnych plików binarnych z katalogu root setuid (np. /bin/pingMa CAP_NET_RAW itp.).

W rzeczywistości, jedyne pliki binarne, które mi pozostały, to:

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

Jeśli usunę bit setuid lub ponownie zainstaluję mój główny system plików nosuid, sshd i GNU Screen przestaną działać, ponieważ wywołują grantpt(3)swoje pesudotermale nadrzędne i glibc najwyraźniej uruchamia ten program, aby zmienić i przeskoczyć pseudoterminal podrzędny /dev/pts/, a GNU Screen dba o to, kiedy ta funkcja zawodzi.

Problem polega na tym, że strona podręcznika grantpt(3)wyraźnie stwierdza, że ​​pod Linuksem, z devptszamontowanym systemem plików, nie jest wymagany taki plik binarny pomocnika; jądro automatycznie ustawi UID i GID urządzenia podrzędnego na rzeczywisty UID i GID procesu, który został otwarty /dev/ptmx(przez wywołanie getpt(3)).

Napisałem mały przykładowy program, aby to zademonstrować:

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

Obserwuj to w akcji z usuniętym bitem setuid z wyżej wymienionego programu:

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

Mam tylko kilka pomysłów na obejście tego problemu:

1) Zamień program na szkielet, który po prostu zwraca 0.

2) Patch granpt () w moim libc, aby nic nie robić.

Mogę zautomatyzować oba z nich, ale czy ktoś ma rekomendacje dla jednego lub drugiego, czy też jak to rozwiązać?

Gdy problem zostanie rozwiązany, w końcu mogę mount -o remount,nosuid /.

Aaron Jones
źródło
Podczas gdy czekam na odpowiedź, wybrałem podejście 1, a sshd i GNU Screen nadal działają.
Aaron Jones
Czym dokładnie są te programy, które zawodzą? Może one są łamane, a sprawdzić nie dla pty(jak powinny), ale do programu?
vonbrand
Każdy program, który nie ma CAP_CHOWN i CAP_FOWNER, wywołuje grantpt (), a plik binarny pomocnika nie jest uruchamiany z EUID == 0, będzie miał niezerowy kod powrotu dla grantpt (), a programy MUSZĄ przerwać tworzenie PTS, gdy to nastąpi , zgodnie z ptmx (4).
Aaron Jones
3
To „rozwiązanie” daje mi wolę ... w najlepszym przypadku dokumentuje błąd, prawdopodobnie tworzy nowy błąd, w najgorszym przypadku stwarza poważną lukę w zabezpieczeniach. Proszę o kontakt z deweloperami glibc.
vonbrand
3
Następnie zgłoś to ludziom glibc.
vonbrand

Odpowiedzi:

2

Jeśli twój glibc jest dość aktualny, a devpts jest skonfigurowane poprawnie, nie powinno być potrzeby wywoływaniapt_chown pomocnika.

Być może napotykasz znany / potencjalny problem podczas usuwania rootkera setuidpt_chown .

grantpt()obsługiwany devfsprzez glibc-2.7 , zmiany zostały wprowadzone w glibc-2.11, ale zamiast jawnego sprawdzania DEVFS_SUPER_MAGIC, zamiast tego sprawdza, czy musi wykonać jakąkolwiek pracę przed podjęciem próby chown()powrotu do wywołaniapt_chown .

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

Podobna zwrotka służy do sprawdzania gid i uprawnień. Chodzi o to, że identyfikator UID, GID i tryb muszą odpowiadać oczekiwaniom (ty, tty i dokładnie 620; potwierdź za pomocą /usr/libexec/pt_chown --help). Jeśli nie, chown()(co wymagałoby możliwości CAP_CHOWN, CAP_FOWNER wywołującego pliku binarnego / procesu), podejmowana jest próba, a jeśli to się nie powiedzie, podejmowana jest próba użycia pt_chownzewnętrznego pomocnika (który musi być rootem setuid). Aby pt_chownmóc korzystać z możliwości, należy go skompilować (a więc i glibc) HAVE_LIBCAP. Wydaje się jednak , że pt_chownjest (od wersji glibc-2.17 i jak zauważyłeś, choć nie podałeś wersji) na stałe zakodowany, aby chcieć, geteuid()==0 niezależnie od HAVE_LIBCAP, odpowiedniego kodu z 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'"));

(Przy nadziei geteuid()==0 przed próbą użycia możliwości nie wydaje się być w duchu możliwości, postanowiłbym zgłosić błąd w tym.)

Potencjalnym obejściem może być przekazanie CAP_CHOWN, CAP_FOWNER zagrożonym programom, ale tak naprawdę nie polecam tego, ponieważ nie można tego oczywiście ograniczyć do ptys.

Jeśli to nie pomoże go rozwiązać, łatanie sshdi screenjest nieco mniej nieprzyjemne niż łatanie glibc. Ponieważ problem leży w glibc, czystszym podejściem byłoby selektywne użycie zastrzyku DLL do implementacji manekina grantpt().

pan. spuratic
źródło
„Ponieważ problem leży w glibc, czystszym podejściem byłoby wybiórcze użycie wstrzykiwania DLL do wdrożenia fałszywego granpt ().” -- Znakomity. Dlaczego o tym nie pomyślałem? Dzięki. :)
Aaron Jones