Verhindern Sie, dass der Prozess unter Linux einen neuen Dateideskriptor öffnet, aber erlauben Sie den Empfang von Dateideskriptoren über Sockets

9

Ich arbeite derzeit an einem Projekt, in dem ich einen übergeordneten Prozess habe, der ein Socketpair einrichtet, gabelt und dann dieses Socketpair zur Kommunikation verwendet. Das Kind sollte, wenn es eine Datei (oder eine andere auf Dateideskriptoren basierende Ressource) öffnen möchte, immer zum übergeordneten fdElement gehen, die Ressource anfordern und die über das Socketpair gesendete Datei erhalten. Außerdem möchte ich verhindern, dass das Kind einen Dateideskriptor selbst öffnet.

Ich bin darüber gestolpert, setrlimitwas das Kind erfolgreich daran hindert, neue Dateideskriptoren zu öffnen, aber es scheint auch alle Dateideskriptoren ungültig zu machen, die über die anfängliche Socket-Verbindung gesendet wurden. Gibt es unter Linux eine Methode, mit der ein einzelner Prozess eine Datei öffnen, seinen Dateideskriptor an andere Prozesse senden und diese verwenden kann, ohne dass diese anderen Prozesse einen Dateideskriptor selbst öffnen können?

Für meinen Anwendungsfall kann dies eine beliebige Kernelkonfiguration, ein Systemaufruf usw. sein, solange sie nach dem Fork angewendet werden kann und solange sie für alle Dateideskriptoren gilt (nicht nur für Dateien, sondern auch für Sockets, Socketpairs usw.).

jklmnn
quelle
1
Sie könnten an seccomp interessiert sein.
user253751

Antworten:

6

Was Sie hier haben, ist genau der Anwendungsfall von seccomp .

Mit seccomp können Sie Systemaufrufe auf verschiedene Arten filtern. Was Sie in dieser Situation tun mögen , ist, direkt nach fork(), eine installieren seccompFilter, der die Verwendung von nicht zulässt open(2), openat(2), socket(2)(und mehr). Um dies zu erreichen, können Sie Folgendes tun:

  1. Erstellen Sie zunächst einen seccomp-Kontext seccomp_init(3)mit dem Standardverhalten von SCMP_ACT_ALLOW.
  2. seccomp_rule_add(3)Fügen Sie dann dem Kontext eine Regel hinzu, indem Sie für jeden Systemaufruf, den Sie ablehnen möchten, eine Regel verwenden . Sie können SCMP_ACT_KILLden Vorgang beenden, wenn der Syscall versucht wird SCMP_ACT_ERRNO(val), damit der Syscall den angegebenen errnoWert oder einen anderen actionauf der Handbuchseite definierten Wert nicht zurückgibt.
  3. Laden Sie den Kontext mit seccomp_load(3), um ihn effektiv zu machen.

Bevor Sie fortfahren, beachten Sie, dass ein Blacklist-Ansatz wie dieser im Allgemeinen schwächer ist als ein Whitelist-Ansatz. Es erlaubt jeden Systemaufruf, der nicht explizit verboten ist, und kann zu einer Umgehung des Filters führen . Wenn Sie der Meinung sind, dass der untergeordnete Prozess, den Sie ausführen möchten, in böswilliger Absicht versucht, den Filter zu umgehen, oder wenn Sie bereits wissen, welche Systemaufrufe von den untergeordneten Prozessen benötigt werden, ist ein Whitelist-Ansatz besser, und Sie sollten das Gegenteil der oben genannten Schritte ausführen: Erstellen Sie einen Filter mit der Standardaktion SCMP_ACT_KILLund lassen Sie die erforderlichen Systemaufrufe mit zu SCMP_ACT_ALLOW. In Bezug auf den Code ist der Unterschied minimal (die Whitelist ist wahrscheinlich länger, aber die Schritte sind gleich).

Hier ist ein Beispiel für das oben Genannte (ich mache es exit(-1)im Fehlerfall nur der Einfachheit halber):

#include <stdlib.h>
#include <seccomp.h>

static void secure(void) {
    int err;
    scmp_filter_ctx ctx;

    int blacklist[] = {
        SCMP_SYS(open),
        SCMP_SYS(openat),
        SCMP_SYS(creat),
        SCMP_SYS(socket),
        SCMP_SYS(open_by_handle_at),
        // ... possibly more ...
    };

    // Create a new seccomp context, allowing every syscall by default.
    ctx = seccomp_init(SCMP_ACT_ALLOW);
    if (ctx == NULL)
        exit(-1);

    /* Now add a filter for each syscall that you want to disallow.
       In this case, we'll use SCMP_ACT_KILL to kill the process if it
       attempts to execute the specified syscall. */

    for (unsigned i = 0; i < sizeof(blacklist) / sizeof(blacklist[0]); i++) {
        err = seccomp_rule_add(ctx, SCMP_ACT_KILL, blacklist[i], 0);
        if (err)
            exit(-1);
    }

    // Load the context making it effective.
    err = seccomp_load(ctx);
    if (err)
        exit(-1);
}

Jetzt können Sie in Ihrem Programm die obige Funktion aufrufen, um den Seccomp-Filter direkt nach dem folgenden anzuwenden fork():

child_pid = fork();
if (child_pid == -1)
    exit(-1);

if (child_pid == 0) {
    secure();

    // Child code here...

    exit(0);
} else {
    // Parent code here...
}

Einige wichtige Hinweise zu seccomp:

  • Ein einmal angewendeter Seccomp-Filter kann durch den Prozess nicht entfernt oder geändert werden.
  • Wenn der Filter dies zulässt fork(2)oder clone(2)zulässt, werden alle untergeordneten Prozesse durch denselben Filter eingeschränkt.
  • Wenn dies execve(2)zulässig ist, bleibt der vorhandene Filter während eines Aufrufs von erhalten execve(2).
  • Wenn der prctl(2)Systemaufruf zulässig ist, kann der Prozess weitere Filter anwenden.
Marco Bonelli
quelle
2
Schwarze Liste für eine Sandbox? Im Allgemeinen ist es eine schlechte Idee, stattdessen eine Whitelist zu erstellen.
Deduplikator
@Deduplicator Ich weiß, aber ein Whitelist-Ansatz trifft nicht sehr gut auf die Situation von OP zu, da sie nur das Öffnen neuer Dateideskriptoren verbieten möchten. Ich werde am Ende eine Notiz hinzufügen.
Marco Bonelli
Danke für die Antwort, das brauche ich. Eine Whitelist ist in der Tat besser für die Anwendung, die ich ursprünglich beabsichtigt hatte. Ich hatte nur nicht daran gedacht, dass es mehr Dinge gibt, die man einschränken sollte, als nur Dateideskriptoren zu öffnen.
jklmnn
@jklmnn ja genau. Ich habe in der Tat nur vergessen, dass socket(2)auch ein erstellen kann fd, so dass auch das blockiert werden sollte. Wenn Sie den untergeordneten Prozess kennen, ist ein Whitelist-Ansatz besser.
Marco Bonelli
@MarcoBonelli Eine Whitelist ist definitiv besser. Offhand, creat(), dup(), und dup2()sind alle Linux - Systemaufrufe , dass return Dateideskriptoren. Es gibt viele Möglichkeiten, eine schwarze Liste zu umgehen ...
Andrew Henle