Libseccomp: BOGUE : gen_bpf_generate() ne gère pas correctement les échecs

Créé le 28 mai 2020  ·  15Commentaires  ·  Source: seccomp/libseccomp

Salut,

Tout d'abord, merci pour libseccomp -- nous l'utilisons avec plaisir en production depuis plusieurs années maintenant, et nous n'avons rencontré aucun problème (jusqu'à maintenant). Je ne sais pas s'il s'agit d'un bogue dans notre code, d'une incompréhension de la documentation ou de quelque chose d'autre - mais j'ai passé le mois dernier à essayer de le retrouver en vain.

Nous avons récemment mis à niveau les packages dans nos conteneurs Docker, qui incluaient une mise à niveau de libseccomp 2.3.3 (version dans les dépôts stables Debian) vers 2.4.3. D'autres packages système ont également été mis à niveau, mais je ne les ai pas enregistrés. Notre noyau n'a pas été mis à jour et est la version 4.19.0-8-amd64.

Nous utilisons SCMP_ACT_TRACE et construisons un filtre composé uniquement de règles SCMP_ACT_ALLOW ajoutées à l'aide de numéros d'appel système natifs, plutôt que des pseudo-numéros de libseccomp. Nous proposons un processus d'assistance 64 bits qui construit et charge le filtre seccomp avant exec -ing un autre binaire 64 bits.

Pour référence, il s'agit de l'intégralité de notre routine d'initialisation seccomp, en utilisant une vérification d'erreur similaire à celle de la page de manuel seccomp_rule_add .

Cependant, notre appel à seccomp_load a commencé à renvoyer -EINVAL , de l'ordre de grandeur de 1/100 000 initialisations de processus. (Le fait de ne pas pouvoir le reproduire de manière fiable a rendu le débogage fastidieux.) Il n'y a eu aucun changement de code dans notre application pendant cette période. Les appels système ajoutés au filtre sont identiques dans toutes les exécutions.

Avez-vous des idées sur ce qui pourrait mal se passer (ou même comment approfondir ce qui ne va pas), ou si cela est prévu d'une manière ou d'une autre ? Il n'y a pas beaucoup de pièces mobiles dynamiques, et je n'ai rien trouvé dans la documentation expliquant pourquoi cela pourrait se produire.

bug prioritmedium

Commentaire le plus utile

Pas encore, malheureusement. Après avoir ajouté un patch à seccomp_export_pfc , il est resté silencieux. Hier, j'ai poussé ce correctif sur toutes nos machines virtuelles (plutôt qu'un simple test) dans l'espoir de capturer le problème lorsqu'il se produira finalement.

Je trouve le silence étrange, mais pour l'instant, je l'attribue à une coïncidence car toute la logique de débogage/exportation se produit _après_ l'échec de seccomp_load , donc cela ne devrait pas affecter l'échec lui-même.

Tous les 15 commentaires

Salut @Xyene ,

Il n'y a pas beaucoup d'endroits qui renvoient -EINVAL dans le chemin de code seccomp_load(). Sur la base d'un examen rapide du code libseccomp v2.4.3, il semble que cela soit dû à un scmp_filter_ctx invalide ou au noyau se plaignant de l'appel prctl(...) qui charge le filtre.

Considérant que la v2.4.3 fonctionne généralement et que vous n'avez pas modifié votre noyau, il semble douteux que l'appel prctl(...) soit la cause qui nous conduit à un contexte de filtre invalide. Avez-vous remarqué un autre comportement étrange dans votre programme depuis la mise à niveau ? Je me demande s'il y a un problème de corruption de mémoire ailleurs qui cause le problème.

Bien qu'il soit toujours possible que la faute soit avec libseccomp, nous exécutons chaque version à travers une série de vérifications qui incluent des exécutions valgrind pour tous nos tests de régression ainsi qu'une analyse statique utilisant à la fois clang et Coverity.

De plus, bien que cela n'aide pas pour la version 2.4.3, l'une des améliorations que nous visons pour la version presque prête de la version 2.5.0 est l'amélioration de la documentation et de la gestion des codes d'erreur.

Nous avons récemment mis à niveau les packages dans nos conteneurs Docker, qui incluaient une mise à niveau de libseccomp 2.3.3 (version dans les dépôts stables Debian) vers 2.4.3. D'autres packages système ont également été mis à niveau, mais je ne les ai pas enregistrés. Notre noyau n'a pas été mis à jour et est la version 4.19.0-8-amd64.

Merci d'avoir vérifié que votre code et le noyau sous-jacent n'ont pas changé. Cela devrait aider à localiser le problème.

Pour référence, il s'agit de l'intégralité de notre routine d'initialisation seccomp, en utilisant une vérification d'erreur similaire à celle de la page de manuel seccomp_rule_add .

Votre filtre me semble raisonnable.

Avez-vous des idées sur ce qui pourrait mal se passer (ou même comment approfondir ce qui ne va pas), ou si cela est prévu d'une manière ou d'une autre ? Il n'y a pas beaucoup de pièces mobiles dynamiques, et je n'ai rien trouvé dans la documentation expliquant pourquoi cela pourrait se produire.

J'ai parcouru le code v2.4.3 seccomp_load() , et je pense qu'il n'y a que deux endroits où libseccomp génère un code de retour de -EINVAL :

Les deux erreurs ci-dessus sont effectivement causées par un filtre invalide. Cela me semble peu probable sur la base de votre code de filtre.

Il convient de noter que la valeur de retour par défaut du noyau dans seccomp_set_mode_filter() est -EINVAL , il est donc possible que quelque chose d'autre sur le système ait changé, nous amenant à tomber dans cette voie. Vous mentionnez que vous utilisez Docker ; est-ce que vous désactivez le filtre seccomp Docker par défaut ?

Je serais tenté d'ajouter un peu plus de débogage à votre code dans le if after seccomp_load() échoue. Par exemple, nous pourrions afficher le PFC et/ou le BPF du filtre lui-même pour vérifier qu'il semble raisonnable. Voir seccomp_export_pfc() et seccomp_export_bpf() .

J'ai parcouru le code v2.4.3 seccomp_load() , et je pense qu'il n'y a que deux endroits où libseccomp génère un code de retour de -EINVAL :

Gardez à l'esprit que tous les échecs trouvés dans gen_bpf_generate(...) , ou ci-dessous, sont effectivement combinés dans -ENOMEM par sys_filter_load(...) sur src/system.c:267 .

Je déteste retomber dans la "corruption de la mémoire!" si rapidement, mais il semble que ce soit le cas ici.

Merci pour les réponses rapides et détaillées ! Ils ont généré plusieurs pistes d'exploration :slightly_smiliing_face:

Avez-vous remarqué un autre comportement étrange dans votre programme depuis la mise à niveau ? Je me demande s'il y a un problème de corruption de mémoire ailleurs qui cause le problème.

Non, juste ça. Nos tests unitaires et d'intégration continuent de réussir, et à part ce très rare EINVAL , aucune erreur n'est enregistrée dans la prod. Cela le rend certainement déroutant; J'ai également suspecté une corruption de la mémoire, mais je n'ai trouvé aucune preuve à l'appui :slightly_frowning_face:

Pour un peu plus de contexte :

  • le programme est une application Python appelant du C++ via Cython (le GIL est maintenu pendant ce temps, donc Python ne fait pas d'allocations sur d'autres threads)
  • les fourches latérales C++, et dans l'enfant configure le filtre seccomp avant l'exécution
  • toutes les allocations de mémoire qui se produisent après le fork, avant l'exécution proviennent de libseccomp elle-même, dans seccomp_init etc.
  • il y a précisément 4 mallocs de tableau entre l'appel dans le code C++ et le bifurcation, ils sont tous dans Cython et ont des plages appropriées lors de l'écriture (la ligne 435 appelle le code seccomp précédemment lié) - toutes les autres allocations/écritures/etc. sont dans l'interpréteur Python

En tapant ceci, j'ai eu une idée : j'ai entendu des histoires d'horreur sur l'utilisation dangereuse de malloc après un fork, et nous en avons quelques-unes dans libseccomp lui-même. L'application Python elle-même est multithread, mais nous détenons toujours le GIL dans le code natif, cela devrait donc être sûr (?). Cependant, je n'ai entendu parler que d'impasses via malloc-after-fork. (Je suppose que cela fait passer le prochain ordre du jour seccomp_init et al. avant le fork, en appelant uniquement seccomp_load après le fork, et en voyant si les erreurs continuent de se produire.)

Je serais tenté d'ajouter un peu plus de débogage à votre code dans le if after seccomp_load() échoue.

Merci pour la suggestion! J'ai ajouté un appel à seccomp_export_pfc , ainsi que le vidage du contenu de l'entrée dans le filtre ( config->syscall_whitelist ). Je ferai un suivi la prochaine fois que cela échouera.

Salut @Xyene - puisque cela fait environ une semaine, je voulais juste vérifier et voir s'il y avait quelque chose de nouveau que vous avez trouvé ?

Pas encore, malheureusement. Après avoir ajouté un patch à seccomp_export_pfc , il est resté silencieux. Hier, j'ai poussé ce correctif sur toutes nos machines virtuelles (plutôt qu'un simple test) dans l'espoir de capturer le problème lorsqu'il se produira finalement.

Je trouve le silence étrange, mais pour l'instant, je l'attribue à une coïncidence car toute la logique de débogage/exportation se produit _après_ l'échec de seccomp_load , donc cela ne devrait pas affecter l'échec lui-même.

Le progrès!

Il s'avère que la raison pour laquelle il est resté silencieux est que seccomp_export_bpf était en erreur de segmentation (devrait-il le faire, s'il est appelé après seccomp_load ?), Et cela a été signalé ailleurs et non là où je cherchais des échecs seccomp. Plus important encore, j'ai rencontré un cas où je peux reproduire le problème de manière fiable dans environ 150 invocations, donc avec quelques travaux de plomberie, je devrais être en mesure d'extraire des vidages de mémoire.

D'accord, j'ai extrait un coredump, et voici la trace : https://gist.github.com/Xyene/920f1cb098784a031f53c66a2f49d167

C'était un peu suspect, car il plante dans la routine realloc jemalloc. De plus, l'utilisation de la glibc malloc résout le problème (malheureusement, ce n'est pas une option à long terme dans ce cas en raison de problèmes de fragmentation).

Ensuite, j'ai extrait jemalloc, je l'ai compilé avec -O0 et des symboles de débogage, et j'ai réexécuté la reproduction. Cette fois, il s'est écrasé dans seccomp_load , plutôt qu'après ! J'ai téléchargé cette trace ici : https://gist.github.com/Xyene/5da56168bcea337da85b2cd30704d12e

Un extrait de cette trace :

#9  0x00007ff962698495 in free (ptr=0x5a5a5a5a5a5a5a5a) at src/jemalloc.c:2867
No locals.
#10 0x00007ff96062d087 in _program_free (prg=prg@entry=0x7ff95e963010) at gen_bpf.c:511
No locals.
#11 0x00007ff96062f605 in gen_bpf_release (program=program@entry=0x7ff95e963010) at gen_bpf.c:1986
No locals.
#12 0x00007ff96062c04f in sys_filter_load (col=col@entry=0x7ff95e9a5000) at system.c:293
        rc = -1
        prgm = 0x7ff95e963010
#13 0x00007ff96062b666 in seccomp_load (ctx=ctx@entry=0x7ff95e9a5000) at api.c:286
        col = 0x7ff95e9a5000

En recherchant jemalloc, il semble que 0x5a soit utilisé pour marquer les octets libres comme libres , avec l'intention spécifique de planter du code qui essaie de libérer quelque chose qui a déjà été libéré.

gen_bpf.c:511 dans la version 2.4.3 est : https://github.com/seccomp/libseccomp/blob/1dde9d94e0848e12da20602ca38032b91d521427/src/gen_bpf.c#L505 -L513

Mais, cela n'a pas beaucoup de sens puisque la durée de vie du programme n'est que le corps de sys_filter_load :

https://github.com/seccomp/libseccomp/blob/1dde9d94e0848e12da20602ca38032b91d521427/src/system.c#L260 -L296

Je pense avoir repéré au moins un problème. En gen_bpf_generate ;

https://github.com/seccomp/libseccomp/blob/1dde9d94e0848e12da20602ca38032b91d521427/src/gen_bpf.c#L1963 -L1966

state.bpf = prgm tant que zmalloc n'a pas échoué. Ensuite, _gen_bpf_build_bpf est appelé, et en fonction de son rc , state.bpf est défini sur NULL .

https://github.com/seccomp/libseccomp/blob/1dde9d94e0848e12da20602ca38032b91d521427/src/gen_bpf.c#L1968 -L1971

Considérant le cas où rc != 0 , state.bpf est toujours défini sur prgm au moment de l'appel à _state_release . Cela libérera la mémoire pointée par prgm .

https://github.com/seccomp/libseccomp/blob/1dde9d94e0848e12da20602ca38032b91d521427/src/gen_bpf.c#L539

Ensuite, gen_bpf_generate va return prgm , qui malgré avoir été libéré, est toujours un pointeur différent de zéro.

https://github.com/seccomp/libseccomp/blob/1dde9d94e0848e12da20602ca38032b91d521427/src/gen_bpf.c#L1971 -L1974

De retour dans sys_filter_load , gen_bpf_generate revient, et prgm n'est pas- NULL donc ça continue.

https://github.com/seccomp/libseccomp/blob/1dde9d94e0848e12da20602ca38032b91d521427/src/system.c#L265 -L267

Enfin, à la fin de sys_filter_load , gen_bpf_release est appelé sur le prgm déjà gratuit.

https://github.com/seccomp/libseccomp/blob/1dde9d94e0848e12da20602ca38032b91d521427/src/system.c#L292 -L295

Cela ne répond pas à la question de savoir pourquoi _gen_bpf_build_bpf échouerait en premier lieu, mais cela semble être quelque chose de grave qui pourrait arriver si c'était le cas.

Edit: en fait, cela semble avoir été corrigé comme un effet secondaire de https://github.com/seccomp/libseccomp/commit/3a1d1c977065f204b96293cccfe7d3e5aa0d7ace.

Considérant le cas où rc != 0, state.bpf est toujours défini sur prgm au moment de l'appel à _state_release. Cela libérera la mémoire pointée par prgm.

Ah ha ! Bonne prise @Xyene !

Je pense que nous devons résoudre ce problème au-delà de 3a1d1c977065f204b96293cccfe7d3e5aa0d7ace, laissez-moi y réfléchir une minute ... Je ne pense pas que la solution sera trop difficile ... et voir si je peux trouver un PR.

Je pense que nous devons résoudre ce problème au-delà de 3a1d1c9, laissez-moi y réfléchir une minute ... Je ne pense pas que la solution sera trop difficile ... et voir si je peux trouver un PR.

Oups, je regardais du vieux code quand j'ai écrit ça ; oui, je pense que 3a1d1c9 corrige cela pour nous, mais nous aurons besoin d'un correctif pour la branche release-2.4. Je vais travailler dessus maintenant.

_(Meta : je vais continuer à mettre à jour ce message avec mes découvertes au fur et à mesure, donc j'ai un endroit pour les écrire sans vous envoyer de spam :)_

Bon, de retour sur 2.4.3 avec le patch appliqué, j'ai pu retirer le filtre qui échouait : lien .

La cause signalée est maintenant ENOMEM au lieu de EINVAL , ce qui, je suppose, est attendu étant donné que _gen_bpf_build_bpf échoue et renvoie un programme NULL . Le PFC s'imprime bien, cependant. La modification du code seccomp pour signaler la valeur de retour de _gen_bpf_build_bpf montre EFAULT comme cause.

En guise de hack rapide, j'ai exécuté :%s/return -EFAULT/abort() sur src/gen_bpf.c et j'ai pu extraire cette trace de pile :

Trace de pile EFAULT

(gdb) bt full
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
        set = {__val = {0, 140084028365964, 140083248439464, 140083248438968, 140083248431088, 140084028368143, 28659884033, 140083965300736, 
            140083248439464, 140083248438968, 140083248431088, 140084028351031, 140084019988760, 140083248439624, 140083248431200, 140084028372597}}
        pid = <optimized out>
        tid = <optimized out>
        ret = <optimized out>
#1  0x00007f67daa4d55b in __GI_abort () at abort.c:79
        save_stage = 1
        act = {__sigaction_handler = {sa_handler = 0x7f67d6f3eec0, sa_sigaction = 0x7f67d6f3eec0}, sa_mask = {__val = {140083965300736, 
              140083965300736, 0, 0, 140083248438968, 140083248438968, 140083248439464, 140083248431504, 140084028417173, 140083964793344, 
              140083965300736, 140083248431552, 140083994791895, 140083248431552, 140083994787642, 140083965300736}}, sa_flags = -1404894496, 
          sa_restorer = 0x0}
        sigs = {__val = {32, 0 <repeats 15 times>}}
#2  0x00007f67d8bfd455 in _gen_bpf_build_bpf (state=0x7f67ac4302e0, col=0x7f67d6f63040) at gen_bpf.c:1943
        rc = 0
        iter = 1
        h_val = 1425818561
        res_cnt = 0
        jmp_len = 0
        arch_x86_64 = 0
        arch_x32 = -1
        instr = {op = 32, jt = {tgt = {imm_j = 0 '\000', imm_k = 0, hash = 0, db = 0x0, blk = 0x0, nxt = 0}, type = TGT_NONE}, jf = {tgt = {
              imm_j = 0 '\000', imm_k = 0, hash = 0, db = 0x0, blk = 0x0, nxt = 0}, type = TGT_NONE}, k = {tgt = {imm_j = 4 '\004', imm_k = 4, 
              hash = 4, db = 0x4, blk = 0x4, nxt = 4}, type = TGT_K}}
        i_iter = 0x7f67d6fdcb60
        b_badarch = 0x7f67d6fd9000
        b_default = 0x7f67d6fd9060
        b_head = 0x7f67d6fda1a0
        b_tail = 0x7f67d6fd9000
        b_iter = 0x0
        b_new = 0x7f67d6fe3300
        b_jmp = 0x0
        db_secondary = 0x0
        pseudo_arch = {token = 0, token_bpf = 0, size = ARCH_SIZE_UNSPEC, endian = ARCH_ENDIAN_LITTLE, syscall_resolve_name = 0x0, 
          syscall_resolve_num = 0x0, syscall_rewrite = 0x0, rule_add = 0x0}
#3  0x00007f67d8bfd560 in gen_bpf_generate (col=0x7f67d6f63040) at gen_bpf.c:1971
        rc = 0
        state = {htbl = {0x0 <repeats 256 times>}, attr = 0x7f67d6f63044, bad_arch_hsh = 889798935, def_hsh = 742199527, arch = 0x7f67ac4301e0, 
          bpf = 0x7f67d6f64010}
        prgm = 0x7f67d6f64010
#4  0x00007f67d8bf64a7 in sys_filter_load (col=0x7f67d6f63040) at system.c:265
        rc = 32615
        prgm = 0x0
#5  0x00007f67d8bf4f10 in seccomp_load (ctx=0x7f67d6f63040) at api.c:287
        col = 0x7f67d6f63040

Cela correspond à la ligne 1943 :

https://github.com/seccomp/libseccomp/blob/1dde9d94e0848e12da20602ca38032b91d521427/src/gen_bpf.c#L1935 -L1943

Compte tenu de la nature du remplacement, je pense que nous pouvons exclure tout EFAULT dans n'importe quelle fonction d'assistance, car ceux-ci auraient été abandonnés en premier.

Après cela, j'ai essayé de reproduire la même chose avec HEAD - cela arrive toujours. Ensuite, %s:/goto build_bpf_free_blks/abort() et répétez. La cause était :

https://github.com/seccomp/libseccomp/blob/34bf78abc9567b66c72dbe67e7f243072162a25f/src/gen_bpf.c#L2219 -L2220

Heureusement, cette fonction était courte et n'avait qu'une poignée de points de défaillance. Une autre série d'insertions de abort plus tard ;

Trace

(gdb) bt full
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
        set = {__val = {0, 140050183343588, 0, 448, 140049402494880, 140049402509040, 140049402494832, 140050183342988, 140049402495088, 
            140049402509040, 140049402494896, 140050183343588, 4294967296, 140049402509040, 140049402509040, 140049402509040}}
        pid = <optimized out>
        tid = <optimized out>
        ret = <optimized out>
#1  0x00007f5ff953055b in __GI_abort () at abort.c:79
        save_stage = 1
        act = {__sigaction_handler = {sa_handler = 0x7f5ff595d260, sa_sigaction = 0x7f5ff595d260}, sa_mask = {__val = {139642271694862, 
              140050119389792, 0, 0, 140049402502840, 0, 140049402503336, 140049402502888, 140049402502840, 112, 384, 140049402502840, 140050149861504, 
              140049402495328, 140050149857273, 392}}, sa_flags = 448, sa_restorer = 0x7f5ff595d240}
        sigs = {__val = {32, 0 <repeats 15 times>}}
#2  0x00007f5ff76edee5 in _bpf_append_blk (prg=0x7f5ff5964010, blk=0x7f5ff59df1a0) at gen_bpf.c:452
        rc = -12
        i_new = 0x0
        i_iter = 0x7f5ff59fa178
        old_cnt = 48
        iter = 1
#3  0x00007f5ff76f3716 in _gen_bpf_build_bpf (state=0x7f5fcae302d0, col=0x7f5ff59c5000) at gen_bpf.c:2223
        rc = 0
        iter = 1
        h_val = 1425818561
        res_cnt = 0
        jmp_len = 0
        arch_x86_64 = 0
        arch_x32 = -1
        instr = {op = 32, jt = {tgt = {imm_j = 0 '\000', imm_k = 0, hash = 0, db = 0x0, blk = 0x0, nxt = 0}, type = TGT_NONE}, jf = {tgt = {
              imm_j = 0 '\000', imm_k = 0, hash = 0, db = 0x0, blk = 0x0, nxt = 0}, type = TGT_NONE}, k = {tgt = {imm_j = 4 '\004', imm_k = 4, 
              hash = 4, db = 0x4, blk = 0x4, nxt = 4}, type = TGT_K}}
        i_iter = 0x7f5ff59e1b60
        b_badarch = 0x7f5ff59de000
        b_default = 0x7f5ff59de060
        b_head = 0x7f5ff59df1a0
        b_tail = 0x7f5ff59de000
        b_iter = 0x7f5ff59df1a0
        b_new = 0x7f5ff59e8300
        b_jmp = 0x7f5ff59df0e0
        db_secondary = 0x0
        pseudo_arch = {token = 0, token_bpf = 0, size = ARCH_SIZE_UNSPEC, endian = ARCH_ENDIAN_LITTLE, syscall_resolve_name = 0x0, 
          syscall_resolve_num = 0x0, syscall_rewrite = 0x0, rule_add = 0x0}
#4  0x00007f5ff76f3874 in gen_bpf_generate (col=0x7f5ff59c5000, prgm_ptr=0x7f5fcae30b40) at gen_bpf.c:2270
        rc = 0
        state = {htbl = {0x0, 0x7f5ff593ef80, 0x7f5ff593efe0, 0x7f5ff593efc0, 0x0, 0x7f5ff595d000, 0x7f5ff593ef60, 0x7f5ff593ef00, 
            0x0 <repeats 248 times>}, attr = 0x7f5ff59c5004, bad_arch_hsh = 889798935, def_hsh = 742199527, bpf = 0x7f5ff5964010, 
          arch = 0x7f5fcae301c0, b_head = 0x7f5ff59e8300, b_tail = 0x7f5ff59de120, b_new = 0x7f5ff59e8300}
        prgm = <optimized out>
#5  0x00007f5ff76eb275 in sys_filter_load (col=0x7f5ff59c5000, rawrc=false) at system.c:307
        rc = 0
        prgm = 0x0
#6  0x00007f5ff76e9505 in seccomp_load (ctx=0x7f5ff59c5000) at api.c:386
        col = 0x7f5ff59c5000
        rawrc = false

https://github.com/seccomp/libseccomp/blob/34bf78abc9567b66c72dbe67e7f243072162a25f/src/gen_bpf.c#L449 -L452

Donc realloc échoue à nouveau, et _bpf_append_blk renvoie -ENOMEM qui est masqué par _gen_bpf_build_bpf et transformé en -EFAULT . Ce n'est pas grave, mais puisque vous avez dit qu'un meilleur rapport d'erreurs est un objectif de 2,5, j'ai pensé que je le mentionnerais car cela semble dans le champ d'application :slightly_smiling_face:

Quelques piqûres avec GDB :

(gdb) f 2
#2  0x00007f5ff76edee5 in _bpf_append_blk (prg=0x7f5ff5964010, blk=0x7f5ff59df1a0) at gen_bpf.c:452
452         abort();
(gdb) info args
prg = 0x7f5ff5964010
blk = 0x7f5ff59df1a0
(gdb) print prg->blks
$4 = (bpf_instr_raw *) 0x7f5ff59fa000
(gdb) x/32bx &prg->blks
0x7f5ff5964018: 0x00    0xa0    0x9f    0xf5    0x5f    0x7f    0x00    0x00
0x7f5ff5964020: 0x5a    0x5a    0x5a    0x5a    0x5a    0x5a    0x5a    0x5a
0x7f5ff5964028: 0x5a    0x5a    0x5a    0x5a    0x5a    0x5a    0x5a    0x5a
0x7f5ff5964030: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
(gdb) print ((prg)->blk_cnt * sizeof(*((prg)->blks)))
$5 = 392
(gdb) print prg->blk_cnt
$6 = 49

Celui-ci commence vraiment à ressembler à un échec d'allocateur...

Aha, cette histoire a finalement atteint sa conclusion passionnante - j'ai compris ce qui se passait et vérifié un correctif :slightly_smiling_face:

Comme cela pourrait faire une histoire intéressante, la voici:

Le processus principal qui sépare le travailleur se situe généralement à environ 80 Mo RSS. Après avoir bifurqué, il restreint l'utilisation de la mémoire via rlimit , parfois à 64 Mo. Cela le place dans une position où son utilisation actuelle de la mémoire dépasse sa limite, mais cela est autorisé par rlimit . _La plupart du temps_, l'allocateur de mémoire aura suffisamment de mémoire libre pour entretenir les routines d'initialisation de libseccomp sans en demander plus au noyau. Mais quand il _ne le fait pas_, et a besoin de demander de l'espace pour une arène supplémentaire ou quelque chose du genre, le noyau ne le fournira pas puisque le processus a déjà dépassé sa limite.

En 2.4.3, cet échec à obtenir de la mémoire se manifestait par EINVAL et un double-libre. Dans la publication principale https://github.com/seccomp/libseccomp/commit/3a1d1c977065f204b96293cccfe7d3e5aa0d7ace , EFAULT est signalé à la place. Avec https://github.com/seccomp/libseccomp/pull/257 appliqué, ENOMEM est correctement signalé.

La raison pour laquelle cela se produit si rarement devient alors évidente : cela dépend entièrement du fait que l'allocateur dispose de suffisamment de mémoire pour construire le programme BPF sans demander plus au noyau. L'allocateur de la glibc est plus lâche pour permettre à la fragmentation de s'accumuler, donc cela ne s'est jamais produit avec lui en place. jemalloc place des limites plus strictes et conduit à une probabilité accrue d'avoir besoin de demander de la mémoire pendant seccomp_load - juste assez pour remarquer les échecs qui en résultent, mais toujours exaspérant à retrouver.

La solution consiste donc simplement à déplacer tous les appels setrlimit vers _after_ seccomp_load . Ce faisant, realloc n'échoue plus dans _bpf_append_blk et le filtre se charge avec succès. Cela signifie que le filtre doit autoriser setrlimit , mais dans mon cas, c'était acceptable. Plus généralement, je pense que ce problème serait résolu par quelque chose comme https://github.com/seccomp/libseccomp/issues/123.

@pcmoore , @drakenclimber -- merci encore pour toute votre aide dans le débogage de ce problème ! Je suis content de pouvoir mettre ça derrière moi maintenant, mais vos conseils ont été inestimables pour y arriver :smiley:

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