2012-11-01 14 views
9

Ich versuche, einen Daemon zu schreiben, der als root mit einem setuid Bit beginnt, aber dann schnell wieder auf den Benutzer, der den Prozess ausführt. Der Daemon muss jedoch die Fähigkeit behalten, neue Threads auf "Echtzeit" -Priorität zu setzen. Der Code, den ich verwende die Priorität wie folgt eingestellt ist (läuft in einem Gewinde, sobald es erzeugt wird):Drop root UID, während CAP_SYS_NICE

struct sched_param sched_param; 
memset(&sched_param, 0, sizeof(sched_param)); 
sched_param.sched_priority = 90; 

if(-1 == sched_setscheduler(0, SCHED_FIFO, &sched_param)) { 
    // If we get here, we have an error, for example "Operation not permitted" 
} 

jedoch der Teil I habe Probleme mit sich, die uid Einstellung, während die Fähigkeit beibehalten um den obigen Anruf zu sched_setscheduler zu machen.

Ich habe einige Codes, der nahe im Hauptthread meiner Bewerbung bis zur Inbetriebnahme läuft:

if (getgid() != getegid() || getuid() != geteuid()) { 
    cap_value_t cap_values[] = {CAP_SYS_NICE}; 
    cap_t caps; 
    caps = cap_get_proc(); 
    cap_set_flag(caps, CAP_PERMITTED, 1, cap_values, CAP_SET); 
    cap_set_proc(caps); 
    prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); 
    cap_free(caps); 
    setegid(getgid()); 
    seteuid(getuid()); 
} 

Das Problem ist, dass dieser Code nach der Ausführung, erhalte ich „Operation nicht zulässig“, wenn sched_setscheduler Aufruf wie angedeutet im Kommentar oben. Was mache ich falsch?

+0

Anstelle von 'seteuid (geteuid());' verwenden Sie eine explizite 'steuid (0);' und verwenden Sie 'seteuid()' im gesamten Code, mit Ausnahme des allerersten Aufrufs von 'setuid (0);'. –

+0

@ H2CO3 Es gibt ein paar Dinge, die ich nicht verstehe über Ihren Kommentar, zuerst benutze ich nicht den Code "seteuid (geteuid())" (es gibt ein extra 'e' in Ihrem). Zweitens, warum würde ich "setuid (0)" nennen, versuche ich die "root" Bezeichnung fallen zu lassen. Vielleicht könnten Sie eine Antwort posten, um Ihren Vorschlag zu konkretisieren. – brooks94

+0

yep das war ein Tippfehler, sorry. Also, ich meine nach dem Ablegen von root, können Sie ** root um 'setuid (0); re-gain, um keine Fehler in Bezug auf eine unzureichende Ebene von Berechtigungen zu erhalten ... Nicht, dass Sie versuchen reparieren? Oder fehlt mir etwas? –

Antwort

22

Edited den Grund für den ursprünglichen Ausfall zu beschreiben:

Es gibt drei Arten von Funktionen in Linux: vererbbar, zulässig und wirksam. Inheritable definiert, welche Funktionen in einem exec() zulässig bleiben. Zulässig definiert, welche Funktionen für einen Prozess zulässig sind. Effektiv definiert, welche Funktionen gerade aktiv sind.

Wenn Sie den Besitzer oder die Gruppe eines Prozesses von "root" in "nicht root" ändern, wird die effektive Funktion immer gelöscht.

Standardmäßig ist auch der zulässige Capability-Satz gelöscht, aber der Aufruf von prctl(PR_SET_KEEPCAPS, 1L) vor der Identitätsänderung weist den Kernel an, den zulässigen Satz intakt zu lassen.

Nachdem der Prozess die Identität zurück in den nicht privilegierten Benutzer geändert hat, muss CAP_SYS_NICE zur effektiven Menge hinzugefügt werden. (Es muss auch in der erlaubten Menge eingestellt sein. Wenn Sie also Ihre Fähigkeitsmenge löschen, denken Sie daran, sie auch einzustellen. Wenn Sie nur die aktuelle Fähigkeitsmenge ändern, wissen Sie, dass sie bereits gesetzt ist, weil Sie sie geerbt haben.

) ist

Hier ist die Prozedur, die ich empfehlen, sollten Sie wie folgt vor:

  1. speichern reale Benutzer-ID, reale Gruppen-ID und zusätzliche Gruppen-IDs:

    #define _GNU_SOURCE 
    #define _BSD_SOURCE 
    #include <unistd.h> 
    #include <sys/types.h> 
    #include <sys/capability.h> 
    #include <sys/prctl.h> 
    #include <grp.h> 
    
    uid_t user = getuid(); 
    gid_t group = getgid(); 
    gid_t *gid; 
    int  gids, n; 
    
    gids = getgroups(0, NULL); 
    if (gids < 0) /* error */ 
    
    gid = malloc((gids + 1) * sizeof *gid); 
    if (!gid) /* error */ 
    
    gids = getgroups(gids, gid); 
    if (gids < 0) /* error */ 
    
  2. unnötig und privilegierte Zusatz filtern Gruppen (sei paranoid!)

    n = 0; 
    while (n < gids) 
        if (gid[n] == 0 || gid[n] == group) 
         gid[n] = gid[--gids]; 
        else 
         n++; 
    

    Weil Sie nicht "löschen" können Die zusätzlichen Gruppen-IDs (die nur die aktuelle Nummer anfordern) stellen sicher, dass die Liste niemals leer ist. Sie können der Zusatzliste immer die echte Gruppen-ID hinzufügen, um sie nicht leer zu machen.

    if (gids < 1) { 
        gid[0] = group; 
        gids = 1; 
    } 
    
  3. Schalter reale und effektive Benutzer-IDs

    if (setresuid(0, 0, 0)) /* error */ 
    
  4. Stellen Sie die CAP_SYS_NICE Fähigkeit in der CAP_PERMITTED Satz zu verankern. Ich ziehe den gesamten Satz zu löschen, und nur die vier Fähigkeiten zu halten, die erforderlich sind für diesen Ansatz zu arbeiten (und später, fallen alle, aber CAP_SYS_NICE):

    cap_value_t capability[4] = { CAP_SYS_NICE, CAP_SETUID, CAP_SETGID, CAP_SETPCAP }; 
    cap_t  capabilities; 
    
    capabilities = cap_get_proc(); 
    if (cap_clear(capabilities)) /* error */ 
    if (cap_set_flag(capabilities, CAP_EFFECTIVE, 4, capability, CAP_SET)) /* error */ 
    if (cap_set_flag(capabilities, CAP_PERMITTED, 4, capability, CAP_SET)) /* error */ 
    if (cap_set_proc(capabilities)) /* error */ 
    
  5. Sagen Sie den Kernel, den Sie mögen die behalten Fähigkeiten über den Wechsel vom Root zum unprivilegierten Benutzer; Standardmäßig werden die Funktionen auf Null gelöscht, wenn sie von der Wurzel bis zur Nicht-root Identität

    if (prctl(PR_SET_KEEPCAPS, 1L)) /* error */ 
    
  6. Set realen, effektiven und gespeicherte Gruppen-IDs zu dem ursprünglich gespeicherten Gruppen-ID

    if (setresgid(group, group, group)) /* error */ 
    
  7. Set Wechsel zusätzliche Gruppen-IDs

    if (setgroups(gids, gid)) /* error */ 
    
  8. Set real, effektiv und Benutzer-IDs zu der ursprünglich gespeicherten Benutzer-ID gespeichert

    An diesem Punkt fallen die root-Berechtigungen (ohne die Möglichkeit, sie wieder zu erhalten) mit Ausnahme der CAP_SYS_NICE-Fähigkeit. Aufgrund des Übergangs von Root- zu Nicht-Root-Benutzer ist die Fähigkeit niemals wirksam. Der Kernel wird immer die effektive Fähigkeit löschen, die für einen solchen Übergang festgelegt wurde.

  9. die CAP_SYS_NICE Fähigkeit in der CAP_PERMITTED Stellen und CAP_EFFECTIVE gesetzt

    if (cap_clear(capabilities)) /* error */ 
    if (cap_set_flag(capabilities, CAP_PERMITTED, 1, capability, CAP_SET)) /* error */ 
    if (cap_set_flag(capabilities, CAP_EFFECTIVE, 1, capability, CAP_SET)) /* error */ 
    if (cap_set_flag(capabilities, CAP_PERMITTED, 3, capability + 1, CAP_CLEAR)) /* error */ 
    if (cap_set_flag(capabilities, CAP_EFFECTIVE, 3, capability + 1, CAP_CLEAR)) /* error */ 
    
    if (cap_set_proc(capabilities)) /* error */ 
    

    Beachten Sie, dass die letzten beiden cap_set_flag() Operationen, die drei Funktionen nicht mehr klar benötigt wird, so dass nur der erste, CAP_SYS_NICE bleibt.

    An dieser Stelle wird der Deskriptor der Fähigkeiten nicht mehr benötigt, also ist es eine gute Idee, ihn zu befreien.

    if (cap_free(capabilities)) /* error */ 
    
  10. Sagen Sie der Kernel Sie nicht wünschen, die Möglichkeit, über alle weiteren Änderungen von der Wurzel zu halten (auch hier nur Paranoia)

    if (prctl(PR_SET_KEEPCAPS, 0L)) /* error */ 
    

Dies funktioniert auf x86-64 GCC -4.6.3, libc6-2.15.0ubuntu10.3, und linux-3.5.0-18 Kernel auf Xubuntu 12.04.1 LTS, nach der Installation des libcap-dev Pakets.

Edited hinzufügen:

Sie können den Prozess vereinfachen, indem nur auf dem effektiven Benutzer-ID unter Berufung Wurzel zu sein, wie die ausführbaren setuid root ist. In diesem Fall müssen Sie sich auch nicht um die zusätzlichen Gruppen kümmern, da das Setuid-Root nur die effektive Benutzer-ID und nichts anderes betrifft. Wenn Sie wieder zum ursprünglichen realen Benutzer zurückkehren, benötigen Sie technisch gesehen nur den einen setresuid()-Aufruf am Ende des Verfahrens (und den setresgid(), wenn die ausführbare Datei auch als setgid root markiert ist), um sowohl gespeicherte als auch effektive Benutzer- (und Gruppen-) IDs festzulegen zum echten Benutzer.

Allerdings ist der Fall, in dem Sie die ursprüngliche Benutzeridentität wiederfinden, selten, und der Fall, in dem Sie die Identität eines genannten Benutzers erhalten, ist üblich, und dieses Verfahren wurde ursprünglich für Letzteres entwickelt. Sie würden initgroups() verwenden, um die richtigen zusätzlichen Gruppen für den benannten Benutzer zu erhalten, und so weiter. In diesem Fall ist es wichtig, die tatsächlichen, effektiven und gespeicherten Benutzer- und Gruppen-IDs sowie die zusätzlichen Gruppen-IDs sorgfältig zu berücksichtigen, da andernfalls der Prozess zusätzliche Gruppen von dem Benutzer erben würde, der den Prozess ausgeführt hat.

Das Verfahren ist paranoid, aber Paranoia ist keine schlechte Sache, wenn Sie mit sicherheitsrelevanten Problemen zu tun haben. Für den Revert-Back-to-Real-User-Fall kann es vereinfacht werden.


Bearbeitet am 2013-03-17 um ein einfaches Testprogramm zu zeigen. Dies setzt voraus, dass setuid root installiert ist, aber es werden alle Berechtigungen und Funktionen gelöscht (außer CAP_SYS_NICE, das für die Scheduler-Bearbeitung über den normalen Regeln benötigt wird). Ich habe die "überschüssigen" Operationen, die ich vorziehe, reduziert, in der Hoffnung, dass andere das leichter zu lesen finden.

#define _GNU_SOURCE 
#define _BSD_SOURCE 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/capability.h> 
#include <sys/prctl.h> 
#include <grp.h> 
#include <errno.h> 

#include <string.h> 
#include <sched.h> 
#include <stdio.h> 


void test_priority(const char *const name, const int policy) 
{ 
    const pid_t   me = getpid(); 
    struct sched_param param; 

    param.sched_priority = sched_get_priority_max(policy); 
    printf("sched_get_priority_max(%s) = %d\n", name, param.sched_priority); 
    if (sched_setscheduler(me, policy, &param) == -1) 
     printf("sched_setscheduler(getpid(), %s, { %d }): %s.\n", name, param.sched_priority, strerror(errno)); 
    else 
     printf("sched_setscheduler(getpid(), %s, { %d }): Ok.\n", name, param.sched_priority); 

    param.sched_priority = sched_get_priority_min(policy); 
    printf("sched_get_priority_min(%s) = %d\n", name, param.sched_priority); 
    if (sched_setscheduler(me, policy, &param) == -1) 
     printf("sched_setscheduler(getpid(), %s, { %d }): %s.\n", name, param.sched_priority, strerror(errno)); 
    else 
     printf("sched_setscheduler(getpid(), %s, { %d }): Ok.\n", name, param.sched_priority); 

} 


int main(void) 
{ 
    uid_t  user; 
    cap_value_t root_caps[2] = { CAP_SYS_NICE, CAP_SETUID }; 
    cap_value_t user_caps[1] = { CAP_SYS_NICE }; 
    cap_t  capabilities; 

    /* Get real user ID. */ 
    user = getuid(); 

    /* Get full root privileges. Normally being effectively root 
    * (see man 7 credentials, User and Group Identifiers, for explanation 
    * for effective versus real identity) is enough, but some security 
    * modules restrict actions by processes that are only effectively root. 
    * To make sure we don't hit those problems, we switch to root fully. */ 
    if (setresuid(0, 0, 0)) { 
     fprintf(stderr, "Cannot switch to root: %s.\n", strerror(errno)); 
     return 1; 
    } 

    /* Create an empty set of capabilities. */ 
    capabilities = cap_init(); 

    /* Capabilities have three subsets: 
    *  INHERITABLE: Capabilities permitted after an execv() 
    *  EFFECTIVE:  Currently effective capabilities 
    *  PERMITTED:  Limiting set for the two above. 
    * See man 7 capabilities for details, Thread Capability Sets. 
    * 
    * We need the following capabilities: 
    *  CAP_SYS_NICE For nice(2), setpriority(2), 
    *      sched_setscheduler(2), sched_setparam(2), 
    *      sched_setaffinity(2), etc. 
    *  CAP_SETUID  For setuid(), setresuid() 
    * in the last two subsets. We do not need to retain any capabilities 
    * over an exec(). 
    */ 
    if (cap_set_flag(capabilities, CAP_PERMITTED, sizeof root_caps/sizeof root_caps[0], root_caps, CAP_SET) || 
     cap_set_flag(capabilities, CAP_EFFECTIVE, sizeof root_caps/sizeof root_caps[0], root_caps, CAP_SET)) { 
     fprintf(stderr, "Cannot manipulate capability data structure as root: %s.\n", strerror(errno)); 
     return 1; 
    } 

    /* Above, we just manipulated the data structure describing the flags, 
    * not the capabilities themselves. So, set those capabilities now. */ 
    if (cap_set_proc(capabilities)) { 
     fprintf(stderr, "Cannot set capabilities as root: %s.\n", strerror(errno)); 
     return 1; 
    } 

    /* We wish to retain the capabilities across the identity change, 
    * so we need to tell the kernel. */ 
    if (prctl(PR_SET_KEEPCAPS, 1L)) { 
     fprintf(stderr, "Cannot keep capabilities after dropping privileges: %s.\n", strerror(errno)); 
     return 1; 
    } 

    /* Drop extra privileges (aside from capabilities) by switching 
    * to the original real user. */ 
    if (setresuid(user, user, user)) { 
     fprintf(stderr, "Cannot drop root privileges: %s.\n", strerror(errno)); 
     return 1; 
    } 

    /* We can still switch to a different user due to having the CAP_SETUID 
    * capability. Let's clear the capability set, except for the CAP_SYS_NICE 
    * in the permitted and effective sets. */ 
    if (cap_clear(capabilities)) { 
     fprintf(stderr, "Cannot clear capability data structure: %s.\n", strerror(errno)); 
     return 1; 
    } 
    if (cap_set_flag(capabilities, CAP_PERMITTED, sizeof user_caps/sizeof user_caps[0], user_caps, CAP_SET) || 
     cap_set_flag(capabilities, CAP_EFFECTIVE, sizeof user_caps/sizeof user_caps[0], user_caps, CAP_SET)) { 
     fprintf(stderr, "Cannot manipulate capability data structure as user: %s.\n", strerror(errno)); 
     return 1; 
    } 

    /* Apply modified capabilities. */ 
    if (cap_set_proc(capabilities)) { 
     fprintf(stderr, "Cannot set capabilities as user: %s.\n", strerror(errno)); 
     return 1; 
    } 

    /* 
    * Now we have just the normal user privileges, 
    * plus user_caps. 
    */ 

    test_priority("SCHED_OTHER", SCHED_OTHER); 
    test_priority("SCHED_BATCH", SCHED_BATCH); 
    test_priority("SCHED_IDLE", SCHED_IDLE); 
    test_priority("SCHED_FIFO", SCHED_FIFO); 
    test_priority("SCHED_RR", SCHED_RR); 

    return 0; 
} 

Beachten Sie, dass Sie sich auf Dateifunktionen verlassen können, wenn Sie wissen, dass die Binärdatei nur auf relativ neuen Linux-Kernel ausgeführt wird. Dann Ihre main() braucht keiner der Identität oder Fähigkeit Manipulation - Sie können alles in main() mit Ausnahme der test_priority() Funktionen entfernen - und Sie nur Ihre binären geben, sagen ./testprio, die CAP_SYS_NICE Priorität:

sudo setcap 'cap_sys_nice=pe' ./testprio 

können Sie laufen getcap zu sehen, welche Prioritäten gewährt werden, wenn ein binäres ausgeführt wird:

getcap ./testprio 

die anzeigen soll

./testprio = cap_sys_nice+ep 

Dateifunktionen scheinen bisher wenig genutzt zu sein. Auf meinem eigenen System ist gnome-keyring-daemon der einzige mit Dateifunktionen (CAP_IPC_LOCK, zum Sperren von Speicher).

+0

Danke, ich denke ich verstehe. Warum wird Schritt 3 benötigt? Vermutlich ist die ausführbare Datei wie setuid und gehört root? Außerdem, ich denke du hast "CAP_SET_NICE" ein paar Orte wo du "CAP_SYS_NICE" meinst? – brooks94

+0

@ brooks94, danke; Fest. Eine effektive Benutzer-ID, die root-technisch ist, reicht aus, aber der vollständige Wechsel zum root-Benutzer sollte es weniger wahrscheinlich machen, dass irgendwelche Shenanigans (basierend auf Signalen oder '/ proc/PID /' -Zugriffen) Erfolg haben, während der Prozess erhöhte Privilegien hat. Betrachten Sie es als rein defensives und mehr als leicht paranoides Detail. –

+0

Gibt es auch einen fehlenden Aufruf von cap_set_proc (Fähigkeiten) in Schritt 4? – brooks94

1

Ich habe einige Code, der nahe im Hauptthread meiner Bewerbung bis zur Inbetriebnahme läuft:

Sie diese Fähigkeiten in jedem Thread erwerben müssen, in der sie verwendet werden soll, oder verwenden Sie das CAP_INHERITABLE Set .

Von capabilities(7):

Linux teilt die Privilegien traditionell mit Super-User zugeordnet in verschiedene Einheiten, wie Fähigkeiten bekannt, die können unabhängig voneinander aktiviert und deaktiviert werden. Fähigkeiten sind ein pro-thread Attribut.

+0

Also dann habe ich keine Wahl, mein Daemon muss Root-Privilegien während seiner Lebensdauer behalten? – brooks94

+0

Ich glaube nicht. Versuchen Sie, 'CAP_PERMITTED' in' CAP_INHERITABLE' zu ändern. –