Libseccomp: BOGUE : problème de sandbox Tor avec libseccomp v2.4.0

Créé le 24 mars 2019  ·  68Commentaires  ·  Source: seccomp/libseccomp

Il y a une petite régression avec 2.40 et Tor :

https://trac.torproject.org/projects/tor/ticket/29819#comment :5

et je me demande si c'est une lacune du code Tor ?

bug priorithigh

Tous les 68 commentaires

Salut @toralf , malheureusement, je ne connais pas très bien le code de sandboxing Tor. Pouvez-vous capturer la sortie PFC libseccomp du filtre Tor libseccomp afin que nous puissions la comparer avec le filtre Tor souhaité ?

Voir la fonction seccomp_export_pfc() comme moyen de capturer la sortie PFC.

@drakenclimber, vous n'auriez aucune expérience avec Tor, n'est-ce pas ?

Salut @toralf , malheureusement je ne connais pas très bien le code de sandboxing Tor
Eh bien, cela semble être un problème avec le code Tor, il peut également être reproduit là-bas sur différents systèmes.
Merci pour votre aide.

Merci de nous avoir tenus au courant @toralf , si vous avez besoin d'aide/de conseils sur l'écriture de filtres libseccomp pour Tor (ou toute autre application), faites-le nous savoir, nous serons ravis de vous aider.

Bonne chance!

Je m'interroge sur votre avis sur https://trac.torproject.org/projects/tor/ticket/29819#comment :10 ?

Rebonjour. Comme je l'ai dit précédemment, je ne suis vraiment pas très familier avec les filtres Tor libseccomp et il n'y a pas beaucoup d'informations dans le ticket lié. Je pense que cela pourrait être utile si vous pouviez capturer la sortie PFC de libseccomp à partir des filtres libseccomp de Tor afin que nous puissions la comparer au filtre souhaité.

J'ai obtenu la sortie PFC ( pfc.txt ).

rt_sigaction(1 (=SIGHUP), …) est la cause du plantage mais je ne sais pas si le PFC essaie de me dire que c'est autorisé ou non :

  # filter for syscall "rt_sigaction" (13) [priority: 65526]
  if ($syscall == 13)
    if ($a0.hi32 == 0)
      if ($a0.lo32 == 25)
        action ALLOW;
      if ($a0.lo32 == 17)
        action ALLOW;
      if ($a0.lo32 == 15)
        action ALLOW;
      if ($a0.lo32 == 13)
        action ALLOW;
      if ($a0.lo32 == 12)
        action ALLOW;
      if ($a0.lo32 == 10)
        action ALLOW;
      if ($a0.lo32 == 2)
        action ALLOW;
      if ($a0.lo32 == 1)
        action ALLOW;

J'obtiens le même filtre rt_sigaction pour le cas qui échoue. Pour les versions de libseccomp qui réussissent, j'obtiens :

  # filter for syscall "rt_sigaction" (13) [priority: 65526]
  if ($syscall == 13)
    if ($a0.hi32 == 0)
      if ($a0.lo32 == 25)
        action ALLOW;
      if ($a0.lo32 == 17)
        action ALLOW;
      if ($a0.lo32 == 1)
        action ALLOW;
      if ($a0.lo32 == 12)
        action ALLOW;
      if ($a0.lo32 == 10)
        action ALLOW;
      if ($a0.lo32 == 13)
        action ALLOW;
      if ($a0.lo32 == 15)
        action ALLOW;
      if ($a0.lo32 == 2)
        action ALLOW;

L'ordre est différent, mais les signaux autorisés semblent être les mêmes.

@pgerber pouvez-vous partager la sortie PFC complète pour l'ancien/cas de réussite ?

@pgerber selon la sortie PFC, rt_sigaction(1, ...) devrait être autorisé. Bien qu'il soit possible qu'il y ait un problème avec BPF qui est généré. Je veux d'abord examiner de plus près la sortie PFC de @pgerber.

Hmm, rien ne me saute aux yeux immédiatement. Cela vous dérangerait-il de capturer la sortie BPF ? J'ai seulement besoin de la sortie libseccomp v2.4.0 BPF.

Sortie BPF pour v2.4.0 :

seccomp.bpf.gz

Merci @pgerber , pour référence, voici le BPF démonté :

https://gist.github.com/pcmoore/ec93299cf460683464fdc3847b4cdf13

D'accord, oui, il semble que le BPF soit incorrect. Prenons le cas de rt_sigaction(1, ...) utilisant le BPF fourni (voir l'essentiel ci-dessus) :

D'abord le préambule normal x86_64 :

 line  OP   JT   JF   K
=================================
 0000: 0x20 0x00 0x00 0x00000004   ld  $data[4]
 0001: 0x15 0x00 0x85 0xc000003e   jeq 3221225534 true:0002 false:0135
 0002: 0x20 0x00 0x00 0x00000000   ld  $data[0]
 0003: 0x35 0x00 0x01 0x40000000   jge 1073741824 true:0004 false:0005
 0004: 0x15 0x00 0x82 0xffffffff   jeq 4294967295 true:0005 false:0135

... finalement nous arrivons à rt_sigaction() avec un numéro d'appel système de 13 :


 0150: 0x15 0x00 0x0a 0x0000000d   jeq 13   true:0151 false:0161
 0151: 0x20 0x00 0x00 0x00000014   ld  $data[20]
 0152: 0x15 0x00 0x83 0x00000000   jeq 0    true:0153 false:0284
 0153: 0x20 0x00 0x00 0x00000010   ld  $data[16]
 0154: 0x15 0x7d 0x00 0x00000019   jeq 25   true:0280 false:0155
 0155: 0x15 0x7c 0x00 0x00000011   jeq 17   true:0280 false:0156
 0156: 0x15 0x7b 0x00 0x0000000f   jeq 15   true:0280 false:0157
 0157: 0x15 0x7a 0x00 0x0000000d   jeq 13   true:0280 false:0158
 0158: 0x15 0x79 0x00 0x0000000c   jeq 12   true:0280 false:0159
 0159: 0x15 0x78 0x00 0x0000000a   jeq 10   true:0280 false:0160
 0160: 0x15 0x77 0xc2 0x00000002   jeq 2    true:0280 false:0355

... et nous ne voyons pas 1/SIGHUP dans la liste, nous devons prendre la fausse branche à la fin qui passe à la ligne 355 :

 0355: 0x20 0x00 0x00 0x00000018   ld  $data[24]
 0356: 0x54 0x00 0x00 0xfff7f7ff   and 0xfff7f7ff
 0357: 0x15 0xb8 0xb7 0x00000001   jeq 1    true:0542 false:0541

... ce que nous pouvons voir n'est certainement pas correct.

Le PFC semble correct, il s'agit donc probablement d'un problème avec le générateur de code BPF. Merci d'avoir attiré notre attention sur ce point et d'avoir fourni la sortie du filtre afin que nous puissions identifier que le problème venait de libseccomp. Nous examinerons cela et tiendrons ce problème à jour au fur et à mesure que nous progresserons.

@drakenclimber corriger cela est évidemment une priorité pour libseccomp, toute aide que vous pouvez fournir serait la bonne.

@pgerber avez-vous un petit patch pour Tor pour passer l'appel seccomp_export_bpf() ? Aussi, quelque chose de spécial que je dois faire pour reproduire cela en utilisant Tor ?

(Mes excuses, je ne suis pas un utilisateur de Tor et j'ai pensé que je nous ferais gagner du temps à tous les deux en demandant.)

Inutile de vous excuser, il n'est pas encore trop tard pour devenir un utilisateur de Tor.

Cela devrait fonctionner :

  1. Appliquez ce correctif ou extrayez la branche pour obtenir la sortie pbf/pfc.
  2. Il semble qu'il n'y ait pas de liste de dépendances. Donc, soit assurez-vous que automake est installé et demandez-lui ce qui manque pendant la construction ou utilisez votre gestionnaire de paquets pour installer les dépendances (fedora: dnf builddep tor , debian: apt build-dep tor ).
  3. Ensuite, construisez tor comme décrit dans le README : sh autogen.sh && ./configure && make
  4. Enfin, exécutez tor avec le bac à sable activé : ./src/app/tor --Sandbox 1 --DataDirectory data
  5. Attendez qu'il plante avec (Sandbox) Caught a bad syscall attempt (syscall rt_sigaction) . Cela devrait prendre une fraction de seconde.

Merci @pgerber :)

En regardant de plus près le bloc de code qui commence à la ligne 355 :

 0355: 0x20 0x00 0x00 0x00000018   ld  $data[24]
 0356: 0x54 0x00 0x00 0xfff7f7ff   and 0xfff7f7ff
 0357: 0x15 0xb8 0xb7 0x00000001   jeq 1    true:0542 false:0541

... la comparaison avec la valeur "1" et les cibles de saut résultantes sont correctes (si vrai, saute vers un ALLOW ; si faux, saute vers un TRAP) ; c'est juste que l'argument offset est faux (24 alors qu'il devrait être 16) et qu'il applique un masque alors qu'il ne devrait pas l'être. Cela impliquerait que les optimisations BPF combinent de manière incorrecte deux blocs de code qu'elles ne devraient pas.

Pour référence, voici la sortie PFC de libseccomp v2.4.0 :

https://gist.github.com/pcmoore/cec3d35743c03cd4465752477572076d

En regardant de plus près le PFC, il existe une règle qui effectue une comparaison masquée avec l'argument un, en utilisant le masque 0xfff7f7ff et une valeur de 1 (regardez le dernier bit du filtre socket (...) ci-dessous):

  # filter for syscall "socket" (41) [priority: 65505]
  if ($syscall == 41)
    if ($a0.hi32 == 0)
      if ($a0.lo32 == 16)
        if ($a1.hi32 & 0xffffffff == 0)
          if ($a1.lo32 & 0xfff7ffff == 3)
            if ($a2.hi32 == 0)
              if ($a2.lo32 == 0)
                action ALLOW;
      if ($a0.lo32 == 10)
        if ($a1.hi32 & 0xffffffff == 0)
          if ($a1.lo32 & 0xfff7f7ff == 2)
            if ($a2.hi32 == 0)
              if ($a2.lo32 == 17)
                action ALLOW;
              if ($a2.lo32 == 0)
                action ALLOW;
          if ($a1.lo32 & 0xfff7f7ff == 1)
            if ($a2.hi32 == 0)
              if ($a2.lo32 == 6)
                action ALLOW;
      if ($a0.lo32 == 2)
        if ($a1.hi32 & 0xffffffff == 0)
          if ($a1.lo32 & 0xfff7f7ff == 2)
            if ($a2.hi32 == 0)
              if ($a2.lo32 == 17)
                action ALLOW;
              if ($a2.lo32 == 0)
                action ALLOW;
          if ($a1.lo32 & 0xfff7f7ff == 1)
            if ($a2.hi32 == 0)
              if ($a2.lo32 == 6)
                action ALLOW;
      if ($a0.lo32 == 1)
        if ($a1.hi32 & 0xffffffff == 0)
          if ($a1.lo32 & 0xfff7f7ff == 2)
            if ($a2.hi32 == 0)
              if ($a2.lo32 == 0)
                action ALLOW;
          if ($a1.lo32 & 0xfff7f7ff == 1)
            action ALLOW;

Il semble que le filtre socket (...) décrit ci-dessus provienne d'ici :

https://gitweb.torproject.org/tor.git/tree/src/lib/sandbox/sandbox.c?h=tor-0.3.5.8&id=5030edfb534245ed3f7e6b476f38a706247f3cb8#n662

 rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket),
      SCMP_CMP(0, SCMP_CMP_EQ, PF_FILE),
      SCMP_CMP_MASKED(1, SOCK_CLOEXEC|SOCK_NONBLOCK, SOCK_STREAM));

... et le filtre rt_sigaction(...) vient d'ici :

https://gitweb.torproject.org/tor.git/tree/src/lib/sandbox/sandbox.c?h=tor-0.3.5.8&id=5030edfb534245ed3f7e6b476f38a706247f3cb8#n293

static int
sb_rt_sigaction(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
{
  unsigned i;
  int rc;
  int param[] = { SIGINT, SIGTERM, SIGPIPE, SIGUSR1, SIGUSR2, SIGHUP, SIGCHLD,
#ifdef SIGXFSZ
      SIGXFSZ
#endif
      };
  (void) filter;

  for (i = 0; i < ARRAY_LENGTH(param); i++) {
    rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigaction),
        SCMP_CMP(0, SCMP_CMP_EQ, param[i]));
    if (rc)
      break;
  }

  return rc;
}

Peut-être qu'avec un peu de chance, nous pouvons créer un simple reproducteur avec juste ces règles ; cela réduirait considérablement la complexité du problème.

Malheureusement, il semble qu'un reproducteur simpliste donne les bons résultats :

#include <errno.h>
#include <limits.h>
#include <unistd.h>

#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <seccomp.h>

#include "util.h"

int main(int argc, char *argv[])
{
    int rc;
    struct util_options opts;
    scmp_filter_ctx ctx = NULL;

    rc = util_getopt(argc, argv, &opts);
    if (rc < 0)
        goto out;

    ctx = seccomp_init(SCMP_ACT_KILL);
    if (ctx == NULL)
        return ENOMEM;

    rc = seccomp_rule_add_exact(ctx, SCMP_ACT_ALLOW, 1001, 2,
                    SCMP_A0(SCMP_CMP_EQ, PF_FILE),
                    SCMP_A1(SCMP_CMP_MASKED_EQ, ~(SOCK_CLOEXEC|SOCK_NONBLOCK), SOCK_STREAM));
    if (rc != 0)
        goto out;

    {
        unsigned int i = 0;
        unsigned int sigs[] = {
            SIGINT, SIGTERM, SIGPIPE,
            SIGUSR1, SIGUSR2, SIGHUP, SIGCHLD, SIGXFSZ, 0 };

        while (sigs[i] != 0) {
            rc = seccomp_rule_add_exact(ctx, SCMP_ACT_ALLOW, 1000, 1,
                            SCMP_A0(SCMP_CMP_EQ, sigs[i++]));
            if (rc != 0)
                goto out;
        }
    }

    rc = util_filter_output(&opts, ctx);
    if (rc)
        goto out;

out:
    seccomp_release(ctx);
    return (rc < 0 ? -rc : rc);
}
# ./00-test 
#
# pseudo filter code start
#
# filter for arch x86_64 (3221225534)
if ($arch == 3221225534)
  # filter for syscall "UNKNOWN" (1001) [priority: 65531]
  if ($syscall == 1001)
    if ($a0.hi32 == 0)
      if ($a0.lo32 == 1)
        if ($a1.hi32 & 0xffffffff == 0)
          if ($a1.lo32 & 0xfff7f7ff == 1)
            action ALLOW;
  # filter for syscall "UNKNOWN" (1000) [priority: 65526]
  if ($syscall == 1000)
    if ($a0.hi32 == 0)
      if ($a0.lo32 == 25)
        action ALLOW;
      if ($a0.lo32 == 17)
        action ALLOW;
      if ($a0.lo32 == 15)
        action ALLOW;
      if ($a0.lo32 == 13)
        action ALLOW;
      if ($a0.lo32 == 12)
        action ALLOW;
      if ($a0.lo32 == 10)
        action ALLOW;
      if ($a0.lo32 == 2)
        action ALLOW;
      if ($a0.lo32 == 1)
        action ALLOW;
  # default action
  action KILL;
# invalid architecture action
action KILL;
#
# pseudo filter code end
#
# ./00-test -b | ../tools/scmp_bpf_disasm 
 line  OP   JT   JF   K
=================================
 0000: 0x20 0x00 0x00 0x00000004   ld  $data[4]
 0001: 0x15 0x00 0x1a 0xc000003e   jeq 3221225534 true:0002 false:0028
 0002: 0x20 0x00 0x00 0x00000000   ld  $data[0]
 0003: 0x35 0x00 0x01 0x40000000   jge 1073741824 true:0004 false:0005
 0004: 0x15 0x00 0x17 0xffffffff   jeq 4294967295 true:0005 false:0028
 0005: 0x15 0x00 0x09 0x000003e9   jeq 1001 true:0006 false:0015
 0006: 0x20 0x00 0x00 0x00000014   ld  $data[20]
 0007: 0x15 0x00 0x14 0x00000000   jeq 0    true:0008 false:0028
 0008: 0x20 0x00 0x00 0x00000010   ld  $data[16]
 0009: 0x15 0x00 0x12 0x00000001   jeq 1    true:0010 false:0028
 0010: 0x20 0x00 0x00 0x0000001c   ld  $data[28]
 0011: 0x15 0x00 0x10 0x00000000   jeq 0    true:0012 false:0028
 0012: 0x20 0x00 0x00 0x00000018   ld  $data[24]
 0013: 0x54 0x00 0x00 0xfff7f7ff   and 0xfff7f7ff
 0014: 0x15 0x0c 0x0d 0x00000001   jeq 1    true:0027 false:0028
 0015: 0x15 0x00 0x0c 0x000003e8   jeq 1000 true:0016 false:0028
 0016: 0x20 0x00 0x00 0x00000014   ld  $data[20]
 0017: 0x15 0x00 0x0a 0x00000000   jeq 0    true:0018 false:0028
 0018: 0x20 0x00 0x00 0x00000010   ld  $data[16]
 0019: 0x15 0x07 0x00 0x00000019   jeq 25   true:0027 false:0020
 0020: 0x15 0x06 0x00 0x00000011   jeq 17   true:0027 false:0021
 0021: 0x15 0x05 0x00 0x0000000f   jeq 15   true:0027 false:0022
 0022: 0x15 0x04 0x00 0x0000000d   jeq 13   true:0027 false:0023
 0023: 0x15 0x03 0x00 0x0000000c   jeq 12   true:0027 false:0024
 0024: 0x15 0x02 0x00 0x0000000a   jeq 10   true:0027 false:0025
 0025: 0x15 0x01 0x00 0x00000002   jeq 2    true:0027 false:0026
 0026: 0x15 0x00 0x01 0x00000001   jeq 1    true:0027 false:0028
 0027: 0x06 0x00 0x00 0x7fff0000   ret ALLOW
 0028: 0x06 0x00 0x00 0x00000000   ret KILL

En regardant à nouveau le BPF désassemblé, il semble qu'il y ait sept blocs de code différents qui sautent à la ligne 355. Cela semble étrange et faux.

FWIW J'ai essayé de le diviser en deux pour être complet ici, mais le premier problème était de trouver une bonne base de fusion, il semble que la v2.2.0 en soit une (la v2.3.0 ou encore mieux la v2.3.3 n'est malheureusement pas une base de fusion Git).
Le deuxième problème est que beaucoup de bisect échoue pendant la phase de compilation avec quelque chose comme

de -Wall -O2 -pipe -march=native -fPIC -DPIC -fvisibility=hidden -O2 -pipe -march=native -c /var/tmp/portage/sys-libs/libseccomp-2.2.0/work/libseccomp-2.2.0/src/system.c  -fPIC -DPIC -o .libs/libseccomp_la-system.o
/var/tmp/portage/sys-libs/libseccomp-2.2.0/work/libseccomp-2.2.0/src/api.c:41:14: error: variable ‘library_version’ has initializer but incomplete type
 const struct scmp_version library_version = {
              ^~~~~~~~~~~~
/var/tmp/portage/sys-libs/libseccomp-2.2.0/work/libseccomp-2.2.0/src/api.c:42:3: error: ‘const struct scmp_version’ has no member named ‘major’
  .major = SCMP_VER_MAJOR,
   ^~~~~
In file included from /var/tmp/portage/sys-libs/libseccomp-2.2.0/work/libseccomp-2.2.0/src/api.c:31:
/var/tmp/portage/sys-libs/libseccomp-2.2.0/work/libseccomp-2.2.0/include/seccomp.h:38:25: warning: excess elements in struct initializer
 #define SCMP_VER_MAJOR  2
                         ^

Je me demande donc si vous avez modifié les sources avant de créer une archive - ou - avez-vous créé une archive tar "git archive ..." ?

@drakenclimber, vous n'auriez aucune expérience avec Tor, n'est-ce pas ?

Désolé - je viens juste de me rattraper. J'étais hors de la ville pour les vacances de printemps. Aucune expérience avec Tor, désolé.

Mais je serai heureux d'aider à creuser quoi qu'il en soit

En utilisant git bisect et les excellentes étapes de reproduction dans un commentaire ci-dessus de @pgerber , j'ai pu identifier le commit défaillant - cf98f79d0894221beb9f2753c092304237617c1c

C'est donc vraiment préliminaire, mais j'ai appliqué un correctif 43a01b12317999accc1390c5c21a1893bc599b26 que nous avons développé pour résoudre le problème #112 mais finalement rejeté. L'exemple de programme ./src/app/tor --Sandbox 1 --DataDirectory data démarre maintenant correctement.

J'ai dû mettre à jour le patch pour qu'il corresponde au HEAD de la v2.4 et je l'ai poussé ici 8dec2078f088c863638501db8a51c8a8ee8b61fb. Si quelqu'un souhaite tester ce correctif _potentiel_, c'est ce qu'il doit utiliser.

Je continuerai à évaluer ce correctif pour m'assurer que la logique PFC et BPF est désormais correcte. Restez à l'écoute...

Je peux me tromper, mais je soupçonne que cela ne fait que masquer le problème en déplaçant le contrôle de 1/SIGHUP plus haut dans la liste de comparaison afin qu'il ne soit plus à la fin. Je crois que quelque chose ne va pas dans le code gen_bpf.c qui provoque la génération/marquage/hachage incorrect de ces blocs de code.

Malheureusement, nous avons eu un peu d'exercice d'incendie au travail, donc je n'ai pas pu y consacrer beaucoup de temps ces derniers jours, j'espère que cela changera bientôt.

J'ai eu ce sentiment aussi, mais mon rapide coup d'œil à travers le BPF est encourageant.

 0159: 0x15 0x00 0x0b 0x0000000d   jeq 13   true:0160 false:0171
 0160: 0x20 0x00 0x00 0x00000014   ld  $data[20]
 0161: 0x15 0x00 0x6b 0x00000000   jeq 0    true:0162 false:0269
 0162: 0x20 0x00 0x00 0x00000010   ld  $data[16]
 0163: 0x15 0x6a 0x00 0x00000001   jeq 1    true:0270 false:0164
 0164: 0x15 0x69 0x00 0x00000002   jeq 2    true:0270 false:0165
 0165: 0x15 0x68 0x00 0x0000000a   jeq 10   true:0270 false:0166
 0166: 0x15 0x67 0x00 0x0000000c   jeq 12   true:0270 false:0167
 0167: 0x15 0x66 0x00 0x0000000d   jeq 13   true:0270 false:0168
 0168: 0x15 0x65 0x00 0x0000000f   jeq 15   true:0270 false:0169
 0169: 0x15 0x64 0x00 0x00000011   jeq 17   true:0270 false:0170
 0170: 0x15 0x63 0x62 0x00000019   jeq 25   true:0270 false:0269

 ...

 0269: 0x06 0x00 0x00 0x00030000   ret TRAP
 0270: 0x06 0x00 0x00 0x7fff0000   ret ALLOW

Intéressant. Vraiment intéressant.

Bien que cela soulève la question : pourquoi ? À part changer l'ordre de la comparaison, ce correctif ne devrait pas avoir d'effet sur la génération de BPF. Nous avons certainement besoin de mieux comprendre la cause première ici, car pour le moment, c'est tout simplement étrange.

D'accord sur tous les comptes. _Pourquoi_ est définitivement la question à 64 000 $.

Je vais continuer à creuser...

Une observation, bien que je ne sois pas sûr qu'elle soit pertinente pour le problème posé, est que dans notre fonction de hachage de bloc BPF, src/gen_bpf.c:_hsh_add() , nous ne prenons pas en compte l'état de début et de fin de l'accumulateur, nous hacher les instructions BPF réelles contenues dans le bloc. À l'heure actuelle, je pense que ce n'est pas juste, mais je peux me tromper à ce sujet ...

Par exemple, quelque chose comme ceci est ce dont je parlais ci-dessus:

diff --git a/src/gen_bpf.c b/src/gen_bpf.c
index 9f8f5c3..c656fde 100644
--- a/src/gen_bpf.c
+++ b/src/gen_bpf.c
@@ -556,7 +556,7 @@ static void _state_release(struct bpf_state *state)
 static int _hsh_add(struct bpf_state *state, struct bpf_blk **blk_p,
                    unsigned int found)
 {
-       uint64_t h_val;
+       uint64_t h_val, h_val_tmp[3];
        struct bpf_hash_bkt *h_new, *h_iter, *h_prev = NULL;
        struct bpf_blk *blk = *blk_p;
        struct bpf_blk *b_iter;
@@ -569,7 +569,10 @@ static int _hsh_add(struct bpf_state *state, struct bpf_bl>
                return -ENOMEM;

        /* generate the hash */
-       h_val = hash(blk->blks, _BLK_MSZE(blk));
+       h_val_tmp[0] = hash(blk->blks, _BLK_MSZE(blk));
+       h_val_tmp[1] = hash(&blk->acc_start, sizeof(blk->acc_start));
+       h_val_tmp[2] = hash(&blk->acc_end, sizeof(blk->acc_end));
+       h_val = hash(h_val_tmp, sizeof(h_val_tmp));
        blk->hash = h_val;
        blk->flag_hash = true;
        blk->node = NULL;

FWIW, l'extrait de patch ci-dessus passe nos tests de régression.

Oh attends une minute, c'est ça ? @pgerber est-ce la sortie correcte pour tor ?

# ./src/app/tor --Sandbox 1 --DataDirectory data
Apr 04 22:50:42.248 [notice] Tor 0.4.1.0-alpha-dev (git-5bc9f960c78475b2) running on Linux with Libevent 2.1.8-stable, OpenSSL 1.1.1b, Zlib 1.2.11, Liblzma 5.2.4, and Libzstd 1.3.8.
Apr 04 22:50:42.249 [notice] Tor can't help you if you use it wrong! Learn how to be safe at https://www.torproject.org/download/download#warning
Apr 04 22:50:42.249 [notice] This version is not a stable Tor release. Expect more bugs than usual.
Apr 04 22:50:42.249 [notice] Configuration file "/usr/local/etc/tor/torrc" not present, using reasonable defaults.
Apr 04 22:50:42.256 [warn] Path for DataDirectory (data) is relative and will resolve to /home/pmoore/sources/libseccomp/tor-upstream/data. Is this what you wanted?
Apr 04 22:50:42.257 [notice] Opening Socks listener on 127.0.0.1:9050
Apr 04 22:50:42.257 [notice] Opened Socks listener on 127.0.0.1:9050
seccomp export status: 0
seccomp export status: 0
Apr 04 22:50:42.000 [notice] Bootstrapped 0% (starting): Starting
Apr 04 22:50:42.000 [warn] Could not open "data/cached-certs": Permission denied
Apr 04 22:50:42.000 [warn] Could not open "data/cached-consensus" for mmap(): Permission denied
Apr 04 22:50:42.000 [warn] Could not open "data/unverified-consensus" for mmap(): Permission denied
Apr 04 22:50:42.000 [warn] Could not open "data/cached-microdesc-consensus" for mmap(): Permission denied
Apr 04 22:50:42.000 [warn] Could not open "data/unverified-microdesc-consensus" for mmap(): Permission denied
Apr 04 22:50:42.000 [warn] Could not open "data/cached-microdescs" for mmap(): Permission denied
Apr 04 22:50:42.000 [warn] Could not open "data/cached-microdescs.new": Permission denied
Apr 04 22:50:42.000 [warn] Could not open "data/cached-descriptors" for mmap(): Permission denied
Apr 04 22:50:42.000 [warn] Could not open "data/cached-extrainfo" for mmap(): Permission denied
Apr 04 22:50:42.000 [notice] Starting with guard context "default"
Apr 04 22:50:43.000 [notice] Bootstrapped 5% (conn): Connecting to a relay
Apr 04 22:50:43.000 [notice] Bootstrapped 10% (conn_done): Connected to a relay
Apr 04 22:50:43.000 [notice] Bootstrapped 14% (handshake): Handshaking with a relay
Apr 04 22:50:43.000 [notice] Bootstrapped 15% (handshake_done): Handshake with a relay done
Apr 04 22:50:43.000 [notice] Bootstrapped 20% (onehop_create): Establishing an encrypted directory connection
Apr 04 22:50:43.000 [notice] Bootstrapped 25% (requesting_status): Asking for networkstatus consensus
Apr 04 22:50:44.000 [notice] Bootstrapped 30% (loading_status): Loading networkstatus consensus
Apr 04 22:50:44.000 [notice] I learned some more directory information, but not enough to build a circuit: We have no usable consensus.
Apr 04 22:50:45.000 [notice] Bootstrapped 40% (loading_keys): Loading authority key certs
Apr 04 22:50:45.000 [warn] Could not open "data/unverified-microdesc-consensus" for mmap(): Permission denied
Apr 04 22:50:45.000 [notice] I learned some more directory information, but not enough to build a circuit: We have no usable consensus.

Pour référence, voici la sortie BPF capturée :

... et voici la sortie PFC capturée :

Je ne suis toujours pas convaincu que le correctif _hsh_add() soit la solution à ce problème, mais je dois mentionner qu'en plus des modifications de hachage dans le correctif ci-dessus, nous devons également inclure les états de l'accumulateur dans le bloc dupliqué vérifier plus tard dans la fonction.

J'ai également réalisé que j'avais oublié d' envoyer un ping à Est-ce que la sortie tor ci-dessus vous semble sensée ?

Je crois que _hsh_add() est le problème. J'écrirai mes pensées dans quelques minutes une fois que j'aurai mon café ;)

Mes excuses d'avance, ça va être long. En fait, j'ai rencontré ce problème l'été dernier lorsque je travaillais sur les modifications de l'arbre binaire, mais je ne l'ai jamais complètement traqué à l'époque. Cette fois, je veux tout noter au même endroit, de sorte que si/quand le problème se pose à nouveau, nous aurons un seul endroit auquel nous référer.

Fond

Dans seccomp v2.3.3, la chaîne pour les rt_sigaction de Tor ressemble à ce qui suit :

chain 0: offset[20], datum = 0
nxt_t = chain 1, nxt_f = NULL

    chain 1: offset[16], datum = 25
    lvl_prv = chain 2, lvl_nxt = NULL

        chain 2: offset[16], datum = 17
        lvl_prv = chain 3, lvl_nxt = NULL
            ...

Mais la refonte massive de src/db.c ce3dda9a1747cc6a4c044eafe5a2eb653c974919 a modifié l'ordre de la liste chaînée comme suit :

chain 0: offset[20], datum = 0
nxt_t = chain 1, nxt_f = NULL

    chain 1: offset[16], datum = 25
    lvl_prv = NULL, lvl_nxt = chain 2

        chain 2: offset[16], datum = 17
        lvl_prv = NULL, lvl_nxt = chain 3
            ...

Notez que lvl_prv et lvl_nxt ont été échangés de la v2.3.3 vers le commit de remaniement massif.

J'ai pu "réparer" le problème de Tor en modifiant la liste chaînée pour utiliser à nouveau lvl_prv plutôt que lvl_nxt . Mais comme l' a souligné

La question à 64 000 $ - POURQUOI ?

tl;dr - Il y a une collision de hachage. La modification de l'algorithme de connexion de la liste chaînée fait disparaître la collision. Beurk.

Le commentaire de Paul https://github.com/seccomp/libseccomp/issues/148#issuecomment -478169825 faisait allusion à une collision de hachage, mais je voulais mieux comprendre le problème avant d'accepter simplement le changement d'ordre de la liste liée comme solution.

Un autre commentaire de Paul https://github.com/seccomp/libseccomp/issues/148#issuecomment -480128296 le résume bien. Utiliser seulement blks comme clé de hachage est semé d'embûches. Si deux appels système ont le même traitement d'arguments (par exemple, ALLOW si arg0 == 1), ils entreront en collision. Nous devons également tenir compte de l'accumulateur.

Dans le cas Tor v2.3.3, changer l'ordre permet à rt_sigaction de "gagner" la condition de course. D'autres hachages entrent en collision avec lui, mais le rt_sigaction bpf_blk a été inséré en premier, c'est donc celui qui est trouvé. Je suppose (mais je n'ai pas vérifié) qu'il pourrait y avoir des sauts incorrects pour d'autres appels système comme socket dans cet exemple Tor particulier.

Dans le cas Tor v2.4, le hachage de rt_sigaction est ajouté à la fin et entre en collision avec le hachage de socket . Cette ligne *blk_p = h_iter->blk; ramène vraiment le problème à la maison en remplaçant le bpf_blk d'origine par le bpf_blk généré par le hachage de socket.

Où allons-nous à partir d'ici?

Les changements de hachage (ou quelque chose de très similaire) proposés ci-dessus sont un _must_. (Encore une fois, j'ai rencontré cela l'été dernier, mais je n'ai pas pleinement reconnu l'étendue du problème. Je l'ai résolu par erreur avec un indicateur do_not_dedup 4310cd8e11b75d61ffa93fdaf3a51b947dbbbb91.)

Je ne pense pas que le changement lvl_prv vs lvl_nxt soit nécessaire, mais je serais tenté de l'inclure afin que nous ayons moins d'écarts par rapport à la v2.3.3.

@pcmoore , la sortie ne me semble pas correcte. En particulier, l'amorçage doit atteindre 100 % et il ne doit y avoir aucun message d' autorisation refusée . J'ai essayé le correctif ci-dessus et lorsque j'ai exécuté Tor sur la libseccomp corrigée, j'ai obtenu le même résultat.

Pour référence, voici à quoi devrait ressembler la sortie :

( Bootstrapped 100% (done): Done est un bon signe que tout fonctionne comme prévu.

[user@repro-seccomp tor]$ ./src/app/tor --Sandbox 1 --DataDirectory data
Apr 05 18:19:24.280 [notice] Tor 0.4.1.0-alpha-dev (git-a191c5c7c96deb5a) running on Linux with Libevent 2.1.8-stable, OpenSSL 1.1.1b, Zlib 1.2.11, Liblzma 5.2.4, and Libzstd 1.3.8.
Apr 05 18:19:24.280 [notice] Tor can't help you if you use it wrong! Learn how to be safe at https://www.torproject.org/download/download#warning
Apr 05 18:19:24.280 [notice] This version is not a stable Tor release. Expect more bugs than usual.
Apr 05 18:19:24.280 [notice] Configuration file "/usr/local/etc/tor/torrc" not present, using reasonable defaults.
Apr 05 18:19:24.282 [warn] Path for DataDirectory (data) is relative and will resolve to /home/user/src/tor/data. Is this what you wanted?
Apr 05 18:19:24.282 [notice] Opening Socks listener on 127.0.0.1:9050
Apr 05 18:19:24.282 [notice] Opened Socks listener on 127.0.0.1:9050
seccomp export 0
seccomp export 0
Apr 05 18:19:24.000 [notice] Bootstrapped 0% (starting): Starting
Apr 05 18:19:24.000 [notice] Starting with guard context "default"
Apr 05 18:19:25.000 [notice] Bootstrapped 5% (conn): Connecting to a relay
Apr 05 18:19:25.000 [notice] Bootstrapped 10% (conn_done): Connected to a relay
Apr 05 18:19:26.000 [notice] Bootstrapped 14% (handshake): Handshaking with a relay
Apr 05 18:19:26.000 [notice] Bootstrapped 15% (handshake_done): Handshake with a relay done
Apr 05 18:19:26.000 [notice] Bootstrapped 20% (onehop_create): Establishing an encrypted directory connection
Apr 05 18:19:27.000 [notice] Bootstrapped 25% (requesting_status): Asking for networkstatus consensus
Apr 05 18:19:28.000 [notice] Bootstrapped 30% (loading_status): Loading networkstatus consensus
Apr 05 18:19:38.000 [notice] I learned some more directory information, but not enough to build a circuit: We have no usable consensus.
Apr 05 18:19:38.000 [notice] Bootstrapped 40% (loading_keys): Loading authority key certs
Apr 05 18:19:39.000 [notice] The current consensus has no exit nodes. Tor can only build internal paths, such as paths to onion services.
Apr 05 18:19:39.000 [notice] Bootstrapped 45% (requesting_descriptors): Asking for relay descriptors
Apr 05 18:19:39.000 [notice] I learned some more directory information, but not enough to build a circuit: We need more microdescriptors: we have 0/6659, and can only build 0% of likely paths. (We have 0% of guards bw, 0% of midpoint bw, and 0% of end bw (no exits in consensus, using mid) = 0% of path bw.)
Apr 05 18:19:40.000 [notice] Bootstrapped 50% (loading_descriptors): Loading relay descriptors
Apr 05 18:19:41.000 [notice] The current consensus contains exit nodes. Tor can build exit and internal paths.
Apr 05 18:19:46.000 [notice] Bootstrapped 56% (loading_descriptors): Loading relay descriptors
Apr 05 18:19:51.000 [notice] Bootstrapped 63% (loading_descriptors): Loading relay descriptors
Apr 05 18:19:52.000 [notice] Bootstrapped 69% (loading_descriptors): Loading relay descriptors
Apr 05 18:19:52.000 [notice] Bootstrapped 75% (enough_dirinfo): Loaded enough directory info to build circuits
Apr 05 18:19:53.000 [notice] Bootstrapped 80% (ap_conn): Connecting to a relay to build circuits
Apr 05 18:19:53.000 [notice] Bootstrapped 85% (ap_conn_done): Connected to a relay to build circuits
Apr 05 18:19:53.000 [notice] Bootstrapped 89% (ap_handshake): Finishing handshake with a relay to build circuits
Apr 05 18:19:54.000 [notice] Bootstrapped 90% (ap_handshake_done): Handshake finished with a relay to build circuits
Apr 05 18:19:54.000 [notice] Bootstrapped 95% (circuit_create): Establishing a Tor circuit
Apr 05 18:19:55.000 [notice] Bootstrapped 100% (done): Done

Pour référence, strace me dit que ces appels qui finissent par être refusés :

13825 openat(AT_FDCWD, "/etc/crypto-policies/back-ends/openssl.config", O_RDONLY) = -1 EACCES (Permission denied)
13825 openat(AT_FDCWD, "data/cached-certs", O_RDONLY|O_CLOEXEC) = -1 EACCES (Permission denied)
13825 openat(AT_FDCWD, "data/cached-consensus", O_RDONLY|O_CLOEXEC) = -1 EACCES (Permission denied)
13825 openat(AT_FDCWD, "data/unverified-consensus", O_RDONLY|O_CLOEXEC) = -1 EACCES (Permission denied)
13825 openat(AT_FDCWD, "data/cached-microdesc-consensus", O_RDONLY|O_CLOEXEC) = -1 EACCES (Permission denied)
13825 openat(AT_FDCWD, "data/unverified-microdesc-consensus", O_RDONLY|O_CLOEXEC) = -1 EACCES (Permission denied)
13825 openat(AT_FDCWD, "data/cached-microdescs", O_RDONLY|O_CLOEXEC) = -1 EACCES (Permission denied)
13825 openat(AT_FDCWD, "data/cached-microdescs.new", O_RDONLY|O_CLOEXEC) = -1 EACCES (Permission denied)
13825 openat(AT_FDCWD, "data/cached-descriptors", O_RDONLY|O_CLOEXEC) = -1 EACCES (Permission denied)
13825 openat(AT_FDCWD, "data/cached-extrainfo", O_RDONLY|O_CLOEXEC) = -1 EACCES (Permission denied)
13825 openat(AT_FDCWD, "data/unverified-microdesc-consensus", O_RDONLY|O_CLOEXEC) = -1 EACCES (Permission denied)

openat semble être géré dans sandbox.c:455 :

/** Allow a single file to be opened.  If <b>use_openat</b> is true,
 * we're using a libc that remaps all the opens into openats. */
static int
allow_file_open(scmp_filter_ctx ctx, int use_openat, const char *file)
{
  if (use_openat) {
    return seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat),
                              SCMP_CMP(0, SCMP_CMP_EQ, (unsigned int)AT_FDCWD),
                              SCMP_CMP_STR(1, SCMP_CMP_EQ, file));
  } else {
    return seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open),
                              SCMP_CMP_STR(0, SCMP_CMP_EQ, file));
  }
}

J'ai ajouté du débogage expert à la fonction, comme ceci :

fprintf(stderr, "use_openat: %d, file: %s\n", use_openat, file);

Quelles sorties :

use_openat: 1, file: data/extended_orport_auth_cookie
use_openat: 1, file: data/control_auth_cookie
use_openat: 1, file: <stdout>
use_openat: 1, file: /usr/local/etc/tor/torrc-defaults
use_openat: 1, file: /usr/local/etc/tor/torrc
use_openat: 1, file: /etc/resolv.conf
use_openat: 1, file: /proc/meminfo
use_openat: 1, file: /etc/hosts
use_openat: 1, file: /dev/random
use_openat: 1, file: /dev/urandom
use_openat: 1, file: /dev/srandom
use_openat: 1, file: data/key-pinning-journal
use_openat: 1, file: data/v3-status-votes.tmp
use_openat: 1, file: data/v3-status-votes
use_openat: 1, file: data/unparseable-desc.tmp
use_openat: 1, file: data/unparseable-desc
use_openat: 1, file: data/sr-state.tmp
use_openat: 1, file: data/sr-state
use_openat: 1, file: data/state.tmp
use_openat: 1, file: data/state
use_openat: 1, file: data/cached-extrainfo.tmp.tmp
use_openat: 1, file: data/cached-extrainfo.new.tmp
use_openat: 1, file: data/cached-extrainfo.new
use_openat: 1, file: data/cached-extrainfo.tmp
use_openat: 1, file: data/cached-extrainfo
use_openat: 1, file: data/cached-descriptors.tmp.tmp
use_openat: 1, file: data/cached-descriptors.new.tmp
use_openat: 1, file: data/cached-descriptors.new
use_openat: 1, file: data/cached-descriptors.tmp
use_openat: 1, file: data/cached-descriptors
use_openat: 1, file: data/cached-microdescs.new.tmp
use_openat: 1, file: data/cached-microdescs.new
use_openat: 1, file: data/cached-microdescs.tmp
use_openat: 1, file: data/cached-microdescs
use_openat: 1, file: data/cached-microdesc-consensus.tmp
use_openat: 1, file: data/cached-microdesc-consensus
use_openat: 1, file: data/unverified-microdesc-consensus.tmp
use_openat: 1, file: data/unverified-microdesc-consensus
use_openat: 1, file: data/unverified-consensus.tmp
use_openat: 1, file: data/unverified-consensus
use_openat: 1, file: data/cached-consensus.tmp
use_openat: 1, file: data/cached-consensus
use_openat: 1, file: data/cached-certs.tmp
use_openat: 1, file: data/cached-certs
use_openat: 1, file: data/keys
use_openat: 1, file: data

SCMP_CMP et SCMP_CMP_STR sont définis dans le bac à

Je ne suis pas un spécialiste du seccomp. En fait, je ne l'ai jamais vraiment utilisé, mais j'ai toujours compris que le déréférencement n'est pas autorisé car il peut être un peu dangereux d'autoriser le code fourni par l'utilisateur à le faire dans le noyau. Si c'est le cas, la chaîne ne peut pas être comparée car elle est passée en tant que pointeur. La documentation du noyau pour seccomp semble le confirmer :

Les programmes BPF ne peuvent pas déréférencer les pointeurs, ce qui contraint tous les filtres à évaluer directement les arguments d'appel système uniquement.

De plus, SCMP_CMP_STR semble en effet simplement comparer les pointeurs.

get_cachedir_fname() semble être utilisé pour obtenir le chemin de fichiers comme cached-consensus , par exemple. Donc, j'ai pensé que je verrais juste si je récupère une adresse statique :

Encore une fois, du code de débogage sophistiqué que j'ai ajouté à main.c cette fois :

  fprintf(stderr, "1: get_cachedir_fname(\"cached-consensus\") = %1$p -> %1$s\n", get_cachedir_fname("cached-consensus"));
  fprintf(stderr, "2: get_cachedir_fname(\"cached-consensus\") = %1$p -> %1$s\n", get_cachedir_fname("cached-consensus"));
  fprintf(stderr, "3: get_cachedir_fname(\"cached-consensus\") = %1$p -> %1$s\n", get_cachedir_fname("cached-consensus"));
  fprintf(stderr, "4: get_cachedir_fname(\"cached-consensus\") = %1$p -> %1$s\n", get_cachedir_fname("cached-consensus"));

La sortie était :

1: get_cachedir_fname("cached-consensus") = 0x5742416b64b0 -> data/cached-consensus
2: get_cachedir_fname("cached-consensus") = 0x5742416b64d0 -> data/cached-consensus
3: get_cachedir_fname("cached-consensus") = 0x5742416b64f0 -> data/cached-consensus
4: get_cachedir_fname("cached-consensus") = 0x5742416b6510 -> data/cached-consensus

Se pourrait-il que ce correctif ou certains des correctifs de libseccomp v2.4.0 fassent que cela fonctionne correctement ? D'après ce que j'ai vu dans les notes de version, il y a eu quelques correctifs pour garantir que BPF est généré correctement. Ou est-ce que j'ai raté quelque chose ici ?

@pcmoore , la sortie ne me semble pas correcte. En particulier, l'amorçage doit atteindre 100 % et il ne doit y avoir aucun message _Permission Denied_. J'ai essayé le correctif ci-dessus et lorsque j'ai exécuté Tor sur la libseccomp corrigée, j'ai obtenu le même résultat.

Pour référence, voici à quoi devrait ressembler la sortie :

( Bootstrapped 100% (done): Done est un bon signe que tout fonctionne comme prévu.

Apr 05 18:19:55.000 [notice] Bootstrapped 100% (done): Done

Merci @pgerber. Je peux voir Done sur mon poste de travail.

Pas ici, si j'autorise openat avec n'importe quel argument, tout fonctionne comme prévu. Pourtant, sans, Tor reste bloqué à 40%.

@drakenclimber , Permission Denied ?

J'ai vu des erreurs _Permission Denied_, mais seulement lorsque nous avons des problèmes de collision de hachage. Voici mon journal le plus récent - pas aussi joli que celui de @pgerber mais il semblait heureux.

Apr 05 15:09:18.000 [notice] Bootstrapped 0% (starting): Starting
Apr 05 15:09:18.000 [notice] Starting with guard context "default"
Apr 05 15:09:19.000 [notice] Bootstrapped 5% (conn): Connecting to a relay
Apr 05 15:09:19.000 [notice] Bootstrapped 10% (conn_done): Connected to a relay
Apr 05 15:09:19.000 [notice] Bootstrapped 14% (handshake): Handshaking with a relay
Apr 05 15:09:20.000 [notice] Bootstrapped 15% (handshake_done): Handshake with a relay done
Apr 05 15:09:20.000 [notice] Bootstrapped 20% (onehop_create): Establishing an encrypted directory connection
Apr 05 15:09:21.000 [notice] Bootstrapped 25% (requesting_status): Asking for networkstatus consensus
Apr 05 15:09:21.000 [notice] Bootstrapped 30% (loading_status): Loading networkstatus consensus
Apr 05 15:09:22.000 [notice] I learned some more directory information, but not enough to build a circuit: We have no usable consensus.
Apr 05 15:09:22.000 [notice] Bootstrapped 40% (loading_keys): Loading authority key certs
Apr 05 15:09:22.000 [notice] The current consensus has no exit nodes. Tor can only build internal paths, such as paths to onion services.
Apr 05 15:09:22.000 [notice] Bootstrapped 45% (requesting_descriptors): Asking for relay descriptors
Apr 05 15:09:22.000 [notice] I learned some more directory information, but not enough to build a circuit: We need more microdescriptors: we have 0/6695, and can only build 0% of likely paths. (We have 0% of guards bw, 0% of midpoint bw, and 0% of end bw (no exits in consensus, using mid) = 0% of path bw.)
Apr 05 15:09:25.000 [notice] Bootstrapped 50% (loading_descriptors): Loading relay descriptors
Apr 05 15:09:26.000 [notice] The current consensus contains exit nodes. Tor can build exit and internal paths.
Apr 05 15:09:41.000 [notice] Bootstrapped 55% (loading_descriptors): Loading relay descriptors
Apr 05 15:09:41.000 [notice] Bootstrapped 61% (loading_descriptors): Loading relay descriptors
Apr 05 15:09:41.000 [notice] Bootstrapped 70% (loading_descriptors): Loading relay descriptors
Apr 05 15:09:41.000 [notice] Bootstrapped 75% (enough_dirinfo): Loaded enough directory info to build circuits
Apr 05 15:09:41.000 [notice] Bootstrapped 80% (ap_conn): Connecting to a relay to build circuits
Apr 05 15:09:41.000 [notice] Bootstrapped 85% (ap_conn_done): Connected to a relay to build circuits
Apr 05 15:09:41.000 [notice] Bootstrapped 89% (ap_handshake): Finishing handshake with a relay to build circuits
Apr 05 15:09:41.000 [notice] Bootstrapped 90% (ap_handshake_done): Handshake finished with a relay to build circuits
Apr 05 15:09:41.000 [notice] Bootstrapped 95% (circuit_create): Establishing a Tor circuit
Apr 05 15:09:42.000 [notice] Bootstrapped 100% (done): Done

J'ai annulé toutes mes diverses modifications (qui étaient plusieurs), et je suis également capable de reproduire le problème openat.

@drakenclimber il semble que nous soyons d'accord sur le hachage, c'est un bon signe que nous nous _hsh_add(...) toujours vérifié par rapport à l'ensemble du bloc d'instructions BPF si la valeur de hachage correspondait à un bloc existant. Même si les blocs d'instructions étaient hachés à la même valeur, ils étaient clairement différents et auraient dû échouer au contrôle memcmp(...) , n'est-ce pas ?

Quoi qu'il en soit, ajouter que l'état de l'accumulateur est la bonne chose à faire, je crains juste que nous ne masquions un problème plus important.

En ce qui concerne les échecs open(...) et openat(...) avec EACCES/ERRNO(13) , je vois les morceaux pertinents suivants dans le Tor PFC :

  # filter for syscall "open" (2) [priority: 65533]
  if ($syscall == 2)
    if ($a1.hi32 & 0xffffffff == 0)
      if ($a1.lo32 & 0xfff5f6ff == 0)
        action ERRNO(13);
  # filter for syscall "openat" (257) [priority: 65478]
  if ($syscall == 257)
    if ($a2.hi32 & 0xffffffff == 0)
      if ($a2.lo32 & 0xfff5f6ff == 0)
        action ERRNO(13);



md5-048001dc157d405099277546730de105



  rc = seccomp_rule_add_1(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(open),
                SCMP_CMP_MASKED(1, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_NOFOLLOW,
                                O_RDONLY));
  if (rc != 0) {
    log_err(LD_BUG,"(Sandbox) failed to add open syscall, received libseccomp "
        "error %d", rc);
    return rc;
  }

  rc = seccomp_rule_add_1(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(openat),
                SCMP_CMP_MASKED(2, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_NOFOLLOW,
                                O_RDONLY));
  if (rc != 0) {
    log_err(LD_BUG,"(Sandbox) failed to add openat syscall, received "
            "libseccomp error %d", rc);
    return rc;
  }

Je soupçonne que ces règles sont le problème, je vais essayer de les supprimer pour voir ce qui se passe.

Cela a définitivement fait une différence positive:

# ./src/app/tor --Sandbox 1 --DataDirectory data
Apr 08 23:23:37.090 [notice] Tor 0.4.1.0-alpha-dev (git-540c52d4dc619b65) running on Linux with Libevent 2.1.8-stable, OpenSSL 1.1.1b, Zlib 1.2.11, Liblzma 5.2.4, and Libzstd 1.3.8.
Apr 08 23:23:37.090 [notice] Tor can't help you if you use it wrong! Learn how to be safe at https://www.torproject.org/download/download#warning
Apr 08 23:23:37.090 [notice] This version is not a stable Tor release. Expect more bugs than usual.
Apr 08 23:23:37.090 [notice] Configuration file "/usr/local/etc/tor/torrc" not present, using reasonable defaults.
Apr 08 23:23:37.098 [warn] Path for DataDirectory (data) is relative and will resolve to /home/pmoore/sources/libseccomp/tor-upstream/data. Is this what you wanted?
Apr 08 23:23:37.100 [notice] Opening Socks listener on 127.0.0.1:9050
Apr 08 23:23:37.100 [notice] Opened Socks listener on 127.0.0.1:9050
seccomp export status: 0
seccomp export status: 0
Apr 08 23:23:37.000 [notice] Bootstrapped 0% (starting): Starting
Apr 08 23:23:37.000 [notice] Starting with guard context "default"
Apr 08 23:23:38.000 [notice] Bootstrapped 5% (conn): Connecting to a relay
Apr 08 23:23:38.000 [notice] Bootstrapped 10% (conn_done): Connected to a relay
Apr 08 23:23:38.000 [notice] Bootstrapped 14% (handshake): Handshaking with a relay
Apr 08 23:23:38.000 [notice] Bootstrapped 15% (handshake_done): Handshake with a relay done
Apr 08 23:23:38.000 [notice] Bootstrapped 75% (enough_dirinfo): Loaded enough directory info to build circuits
Apr 08 23:23:38.000 [notice] Bootstrapped 90% (ap_handshake_done): Handshake finished with a relay to build circuits
Apr 08 23:23:38.000 [notice] Bootstrapped 95% (circuit_create): Establishing a Tor circuit
Apr 08 23:23:38.000 [notice] Bootstrapped 100% (done): Done

Pour référence, voici mon patch libseccomp actuel :

commit 6e965cce0249d643bcd2f51b49adf319f7cd106b
Author: Paul Moore <[email protected]>
Date:   Fri Mar 29 23:47:23 2019 -0400

    bpf: add accumulator state to the instruction block hash

    This addresses a problem where dissimilar instruction blocks were
    improperly hashed to the same value because we were not taking into
    account the accumulator state.

    See the GitHub issue below for more information:
     * https://github.com/seccomp/libseccomp/issues/148

    Signed-off-by: Paul Moore <[email protected]>

diff --git a/src/gen_bpf.c b/src/gen_bpf.c
index 9f8f5c3..a95e0f7 100644
--- a/src/gen_bpf.c
+++ b/src/gen_bpf.c
@@ -141,6 +141,9 @@ struct bpf_blk {

 struct bpf_hash_bkt {
        struct bpf_blk *blk;
+       struct acc_state acc_start;
+       struct acc_state acc_end;
+
        struct bpf_hash_bkt *next;
        unsigned int found;
 };
@@ -556,7 +559,7 @@ static void _state_release(struct bpf_state *state)
 static int _hsh_add(struct bpf_state *state, struct bpf_blk **blk_p,
                    unsigned int found)
 {
-       uint64_t h_val;
+       uint64_t h_val, h_val_tmp[3];
        struct bpf_hash_bkt *h_new, *h_iter, *h_prev = NULL;
        struct bpf_blk *blk = *blk_p;
        struct bpf_blk *b_iter;
@@ -569,11 +572,16 @@ static int _hsh_add(struct bpf_state *state, struct bpf_b>
                return -ENOMEM;

        /* generate the hash */
-       h_val = hash(blk->blks, _BLK_MSZE(blk));
+       h_val_tmp[0] = hash(blk->blks, _BLK_MSZE(blk));
+       h_val_tmp[1] = hash(&blk->acc_start, sizeof(blk->acc_start));
+       h_val_tmp[2] = hash(&blk->acc_end, sizeof(blk->acc_end));
+       h_val = hash(h_val_tmp, sizeof(h_val_tmp));
        blk->hash = h_val;
        blk->flag_hash = true;
        blk->node = NULL;
        h_new->blk = blk;
+       h_new->acc_start = blk->acc_start;
+       h_new->acc_end = blk->acc_end;
        h_new->found = (found ? 1 : 0);

        /* insert the block into the hash table */
@@ -584,7 +592,9 @@ hsh_add_restart:
                        if ((h_iter->blk->hash == h_val) &&
                            (_BLK_MSZE(h_iter->blk) == _BLK_MSZE(blk)) &&
                            (memcmp(h_iter->blk->blks, blk->blks,
-                                   _BLK_MSZE(blk)) == 0)) {
+                                   _BLK_MSZE(blk)) == 0) &&
+                           _ACC_CMP_EQ(h_iter->acc_start, blk->acc_start) &&
+                           _ACC_CMP_EQ(h_iter->acc_end, blk->acc_end)) {
                                /* duplicate block */
                                free(h_new);

Laissez-moi essayer d'expliquer ce qui se passe et pourquoi je pense que vous ne voulez pas laisser ces deux règles de filtre open(...)/openat(...) dans sb_open(...) .

Alors que vous aviez plusieurs règles open(...)/openat(...) , vous aviez une règle pour chaque appel système ouvert qui vérifiait uniquement les indicateurs passés à l'appel système. Si un appel aux appels système ouverts correspondait au filtre open/openat de Tor uniquement, ERRNO (13) serait renvoyé, quelles que soient les autres règles installées par Tor.

Cela a-t-il du sens?

@drakenclimber Tout d'abord, le correctif ci-dessus vous semble-t-il raisonnable ? Si oui, puis-je ajouter votre Acked-by au patch ? Indépendamment de toute autre chose, je pense que c'est une solution que nous devrions faire.

En ce qui concerne l'annulation de certaines modifications pour préserver l'ordre des vérifications d'arguments de la v2.3.x, j'aimerais laisser les choses telles qu'elles sont dans la v2.4.0 simplement parce qu'il existe maintenant un ordre explicite. Avant la version v2.4.0, l'ordre des vérifications d'arguments dépendait de l'ordre dans lequel les règles étaient ajoutées au filtre, mais à partir de la v2.4.0, nous ordonnons explicitement les vérifications d'arguments. Bien qu'il diffère de v2.3.x, il ne devrait pas provoquer la rupture des filtres à moins qu'il n'y ait d'autres problèmes (comme nous l'avons vu ici).

@pgerber Je ne connaissais pas les subtilités de openat et seccomp, alors j'ai creusé plus profondément. Il semble que vos préoccupations soient correctes. Le noyau effectue une comparaison de pointeurs et non une comparaison de chaînes.

J'ai écrit un petit test pour vérifier ce comportement.

Voici les parties critiques de celui-ci:

        char foo1[] = "foo";
        char foo2[] = "foo";

        rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 2,
                              SCMP_A0(SCMP_CMP_EQ, (uint64_t)AT_FDCWD),
                              SCMP_A1(SCMP_CMP_EQ, (uint64_t)foo1));

        /* seccomp will allow this command to succeed */
        foo1_fd = openat(AT_FDCWD, foo1, O_CREAT);

        /* seccomp will fail this command */
        foo2_fd = openat(AT_FDCWD, foo2, O_CREAT);

Je ne sais toujours pas comment libseccomp v2.3.3 et Tor ne rencontrent pas ce problème. Le BPF pour v2.3.3 et v2.4 (avec le correctif de hachage) est très similaire. Je vais continuer à chercher ça.

@pgerber Je ne connaissais pas les subtilités d'openat et de seccomp, alors j'ai creusé plus profondément. Il semble que vos préoccupations soient correctes. Le noyau effectue une comparaison de pointeurs et non une comparaison de chaînes.

Désolé, j'ai oublié de répondre hier soir. Oui, vous avez tous les deux raison, pour des raisons de sécurité, le code seccomp du noyau ne regarde pas les pointeurs fournis par l'utilisateur.

Cependant, comme je l'ai mentionné ci-dessus, les filtres open(...)/openat(...) semblent être un problème avec les filtres que Tor a configurés et non avec libseccomp lui-même. Cela peut avoir fonctionné avec libseccomp v2.3.x en raison d'autres bogues dans le code libseccomp. La version 2.4.0 de libseccomp publiée a corrigé de nombreux bogues dans le code de génération de filtre de libseccomp.

Cependant, comme je l'ai mentionné ci-dessus, les filtres open(...)/openat(...) semblent être un problème avec les filtres que Tor a configurés et non avec libseccomp lui-même.

Merci @pcmoore. Je suis d'accord. J'ai parcouru un peu leur code et ils utilisent différents pointeurs de chaîne dans la base de code. Cela s'avérera problématique pour seccomp et la commande openat() .

J'ai eu la chance de parcourir la logique openat BPF de Tor v2.3.3 et la logique openat BPF semble saine. (Mais je dirai que c'est un chemin très étroit pour que tout appel openat soit autorisé. Un pointeur spécifique doit être utilisé et seuls quelques drapeaux fcntl sont légaux.) Comme vous l'avez partagé, de nombreux bogues ont été corrigés depuis alors.

@pgerber et autres - une fois que nous aurons obtenu le correctif de collision de hachage dans libseccomp, faites-moi savoir si vous voulez un autre regard sur les modifications de seccomp que vous devez apporter de votre côté.

Merci pour vos réponses, tous les deux.

J'ai laissé un commentaire sur le bug tracker de Tor dans l'espoir d'avoir des retours de certains développeurs Tor réels. Il semblerait qu'il y ait une logique en place pour allouer statiquement toutes les chaînes utilisées comme chemins prot_strings() dans sandbox.c:1239 ) et les réutiliser plus tard ( sandbox_intern_string() dans sandbox:1169 ).

Je suppose que ce que @pcmoore a mentionné dans un commentaire précédent à propos de l'existence de filtres simplement en regardant les drapeaux de open() / openat() /... pourrait être la raison pour laquelle Tor ne fonctionne toujours pas.

J'ai pu créer un cas de test qui reproduit la collision de hachage signalée par Tor. Je l'ai ajouté (et le correctif _hsh_add() ) pour tirer la demande #149

Laissez-moi essayer d'expliquer ce qui se passe et pourquoi je pense que vous ne voulez pas laisser ces deux règles de filtre open(...)/openat(...) dans sb_open(...) .

Alors que vous aviez plusieurs règles open(...)/openat(...) , vous aviez une règle pour chaque appel système ouvert qui vérifiait uniquement les indicateurs passés à l'appel système. Si un appel aux appels système ouverts correspondait au filtre open/openat de Tor uniquement, ERRNO (13) serait renvoyé, quelles que soient les autres règles installées par Tor.

Cela a-t-il du sens?

Je ne suis pas sûr de bien comprendre cela. Tor utilise actuellement cette logique :

if path is explicitly allowed {
   allow
} elif read only access {
   // Some libraries try to read some files which they don't strictly need.
   deny with EPERM
} else {
   raise SIGSYS
}

Pour y parvenir, Tor suppose que les règles d'ordre sont ajoutées dans l'ordre dans lequel elles sont ensuite appliquées. Si je vous ai bien compris @pcmoore , l'ordre dans lequel les règles sont ajoutées n'a plus d'impact sur l'application des règles de commande. Ai-je bien compris ? Si c'est effectivement le cas, existe-t-il encore un moyen de restaurer l'ancienne logique ? De plus, je m'attendrais à ce que ce soit un modèle courant et je m'attendrais également à ce que, tôt ou tard, d'autres rencontrent le même problème que Tor lors de la mise à niveau vers la v2.4.0. Intuitivement, je m'attendrais à ce que la règle soit appliquée dans l'ordre donné par l'utilisateur.

Je m'attendrais également à ce que, tôt ou tard, d'autres rencontrent le même problème que Tor lors de la mise à niveau vers la v2.4.0. Intuitivement, je m'attendrais à ce que la règle soit appliquée dans l'ordre donné par l'utilisateur.

Chez Gentoo ces packages étaient au moins candidats :

# equery d libseccomp
 * These packages depend on libseccomp:
app-emulation/qemu-3.1.0-r4 (seccomp ? >=sys-libs/libseccomp-2.1.0)
                            (seccomp ? >=sys-libs/libseccomp-2.1.0[static-libs(+)])
app-misc/pax-utils-1.2.3 (seccomp ? sys-libs/libseccomp)
kde-plasma/kscreenlocker-5.14.5 (seccomp ? sys-libs/libseccomp)
net-dns/bind-tools-9.12.3_p4 (seccomp ? sys-libs/libseccomp)
net-libs/gnutls-3.6.7 (seccomp ? sys-libs/libseccomp)
net-vpn/tor-0.4.0.4_rc (seccomp ? sys-libs/libseccomp)

Pour y parvenir, Tor suppose que les règles d'ordre sont ajoutées dans l'ordre dans lequel elles sont ensuite appliquées. Si je vous ai bien compris @pcmoore , l'ordre dans lequel les règles sont ajoutées n'a plus d'impact sur l'application des règles de commande. Ai-je bien compris ?

Tout d'abord, je pense qu'une petite précision s'impose. L'"ordre" dont @drakenclimber et moi parlions est l'ordre lié à la vérification des valeurs des arguments syscall pour le même argument syscall. Imaginez un appel système nommé "foo" qui prend un seul argument numérique, par exemple int foo(int arg); . Si vous avez créé plusieurs règles libseccomp pour filtrer les valeurs d'argument à l'aide de LT/LE/GT/GE, un ordre doit être appliqué pour garantir que les règles sont appliquées correctement. Dans libseccomp v2.4.0, nous gérons cela correctement et appliquons même un ordre strict pour les comparaisons EQ/NE, principalement pour que les filtres soient plus prévisibles quel que soit l'ordre dans lequel ils sont créés.

L'ordre dont vous parlez, l'ordre général entre les règles en fonction de l'ordre dans lequel elles sont créées, est quelque peu lié mais c'est assez différent dans la pratique et ce n'est pas quelque chose que libseccomp a jamais garanti. Même les fonctions de priorité d'appel système ne sont que des "indices" pour le moteur d'optimisation de filtre et ne garantissent pas un ordre strict.

Si c'est effectivement le cas, existe-t-il encore un moyen de restaurer l'ancienne logique ? De plus, je m'attendrais à ce que ce soit un modèle courant et je m'attendrais également à ce que, tôt ou tard, d'autres rencontrent le même problème que Tor lors de la mise à niveau vers la v2.4.0. Intuitivement, je m'attendrais à ce que la règle soit appliquée dans l'ordre donné par l'utilisateur.

Malheureusement, ce n'est pas la façon dont libseccomp a jamais fonctionné. En réfléchissant rapidement, je ne sais pas à quel point ce serait difficile, mais nous pourrons peut-être permettre aux appelants de demander un ordre de règle spécifique à l'avenir, mais cela handicaperait gravement les optimisations de règle/BPF au point que je ne ' Je ne crois pas que nous puissions faire quelque chose de significatif.

Les seules garanties fournies par libseccomp concernant l'exécution des règles sont que les comparaisons d'arguments au sein d'une même règle sont appliquées à l'aide d'un booléen « AND » pour déterminer si une invocation syscall particulière correspond, et que les règles individuelles sont combinées à l'aide d'un booléen « OR ». Je m'excuse si ce n'était pas clair depuis le début.

Si vous rencontrez des difficultés pour adapter le filtre Tor, veuillez nous en informer et nous essaierons de vous aider.

Je pense que cela devrait maintenant être corrigé dans les branches master et release-2.4.

@drakenclimber Je pense que nous devrions bientôt publier une version 2.4.1 (par exemple cette semaine) pour résoudre ce problème, qu'en pensez-vous ? Avez-vous vu d'autres bogues dans la version 2.4.0 ?

Je pense que nous devrions faire une version v2.4.1 bientôt (par exemple cette semaine) pour résoudre ce problème, qu'en pensez-vous ?

Oui, je pense que ce bug est suffisamment sérieux pour mériter une autre version. Faites-moi savoir comment je peux vous aider.

Avez-vous vu d'autres bogues dans la version 2.4.0 ?

Nan. En fait, cela a résolu un problème qui me dérangeait avec le travail de l'arbre binaire. Je pense que ça a l'air assez solide.

Oui, je pense que ce bug est suffisamment sérieux pour mériter une autre version. Faites-moi savoir comment je peux vous aider.

D'accord, puisque nous sommes d'accord, je vais commencer à travailler sur le processus de publication de la version 2.4.1.

En ce qui concerne l'aide... Je pensais que nous aurions un peu plus de temps avant la prochaine version pour mettre en place tous nos processus de co-mainteneur, mais cela n'a pas tout à fait fonctionné comme prévu :) Dans l'intérêt de sortir cela de la porte rapidement je vais juste m'en occuper (ça devrait être relativement facile vu le seul correctif). Cependant, je viens d'ajouter le problème #146 en tant que bloqueur pour la v2.4.2, nous sommes donc sur le point de vous brancher complètement pour la v2.4.2 ;)

Vous l'avez probablement déjà vu, mais si ce n'est pas le cas, j'ai documenté le processus de publication dans https://github.com/seccomp/libseccomp/blob/master/RELEASE_PROCESS.md .

Nan. En fait, cela a résolu un problème qui me dérangeait avec le travail de l'arbre binaire. Je pense que ça a l'air assez solide.

Doigts croisés.

@toralf @pgerber puisqu'il semble que nous ayons résolu ce problème dans la libseccomp en amont, je vais fermer ce bogue maintenant et démarrer la libseccomp v2.4.1 avec le correctif dont nous avons discuté ici. Si vous avez d'autres questions ou préoccupations, veuillez nous en informer. Merci encore d'avoir attiré notre attention sur ce problème et de nous avoir aidés pendant que nous arrivions à une solution.

Pour référence, libseccomp v2.4.1 est sorti avec le correctif de ce bogue.

Cette page vous a été utile?
0 / 5 - 0 notes