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:
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 */
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;
}
Schalter reale und effektive Benutzer-IDs
if (setresuid(0, 0, 0)) /* error */
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 */
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 */
Set realen, effektiven und gespeicherte Gruppen-IDs zu dem ursprünglich gespeicherten Gruppen-ID
if (setresgid(group, group, group)) /* error */
Set Wechsel zusätzliche Gruppen-IDs
if (setgroups(gids, gid)) /* error */
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.
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 */
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, ¶m) == -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, ¶m) == -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).
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);'. –
@ 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
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? –