Libseccomp: BUG: gen_bpf_generate () não lida corretamente com a falha

Criado em 28 mai. 2020  ·  15Comentários  ·  Fonte: seccomp/libseccomp

Oi,

Em primeiro lugar, obrigado por libseccomp - estamos usando-o felizmente em produção há vários anos e não tivemos nenhum problema (até agora). Não tenho certeza se isso é um bug em nosso código, um mal-entendido da documentação ou outra coisa - mas passei o mês passado tentando rastrear isso sem sucesso.

Recentemente, atualizamos pacotes em nossos contêineres Docker, que incluíam uma atualização de libseccomp 2.3.3 (versão em repositórios estáveis ​​do Debian) para 2.4.3. Outros pacotes de sistema também foram atualizados, mas não os gravei. Nosso kernel não foi atualizado e é a versão 4.19.0-8-amd64.

Usamos SCMP_ACT_TRACE e construímos um filtro que consiste apenas em regras SCMP_ACT_ALLOW que são adicionadas usando números de syscall nativos, em vez dos pseudo-números de libseccomp. Nós bifuramos um processo auxiliar de 64 bits que constrói e carrega o filtro seccomp antes de exec -ing outro binário de 64 bits.

Para referência, esta é toda a nossa rotina de inicialização do seccomp, usando verificação de erros semelhante à página do manual seccomp_rule_add .

No entanto, nossa chamada para seccomp_load começou a retornar -EINVAL , na ordem de magnitude de 1 / 100.000 inicializações de processo. (Não ser capaz de reproduzi-lo de forma confiável tornou a depuração entediante.) Não houve alterações de código em nosso aplicativo durante esse tempo. As syscalls adicionadas ao filtro são idênticas em todas as execuções.

Alguma ideia sobre o que pode estar errado (ou mesmo como investigar mais a fundo o que está errado), ou se isso é esperado de alguma forma? Não há muitas partes móveis dinâmicas e não consegui encontrar nada na documentação sobre por que isso pode estar acontecendo.

bug prioritmedium

Comentários muito úteis

Ainda não, infelizmente. Depois de adicionar um patch a seccomp_export_pfc , ele ficou em silêncio. Ontem eu empurrei esse patch para todas as nossas VMs (em vez de apenas um de teste) na esperança de capturar o problema quando ele eventualmente ocorrer.

Eu acho o silêncio estranho, mas por agora estou atribuindo isso a uma coincidência, já que toda a lógica de depuração / exportação acontece _após_ a falha seccomp_load , então isso não deveria estar afetando a falha em si.

Todos 15 comentários

Olá @Xyene ,

Não há muitos lugares que retornam -EINVAL no caminho do código seccomp_load (). Com base em um rápido exame do código libseccomp v2.4.3, parece que é devido a um scmp_filter_ctx inválido ou ao kernel reclamando da chamada prctl(...) que carrega o filtro.

Considerando que a v2.4.3 geralmente funciona, e você não mudou seu kernel, parece duvidoso que a chamada prctl(...) seja a causa que nos leva a um contexto de filtro inválido. Você notou algum outro comportamento estranho em seu programa desde a atualização? Estou me perguntando se há um problema de corrupção de memória em outro lugar que está causando o problema.

Embora seja sempre possível que a falha seja com libseccomp, executamos cada versão por meio de uma série de verificações que incluem execuções de valgrind para todos os nossos testes de regressão, bem como análise estática usando clang e Coverity.

Além disso, embora isso não ajude para a v2.4.3, uma das melhorias que pretendemos para a versão v2.5.0 quase pronta é a documentação aprimorada e o tratamento de códigos de erro.

Recentemente, atualizamos pacotes em nossos contêineres Docker, que incluíam uma atualização de libseccomp 2.3.3 (versão em repositórios estáveis ​​do Debian) para 2.4.3. Outros pacotes de sistema também foram atualizados, mas não os gravei. Nosso kernel não foi atualizado e é a versão 4.19.0-8-amd64.

Obrigado por verificar se o seu código e o kernel subjacente não mudaram. Isso deve ajudar a rastrear o problema.

Para referência, esta é a totalidade de nossa rotina de inicialização do seccomp, usando verificação de erro semelhante à página do manual seccomp_rule_add .

Seu filtro parece razoável para mim.

Alguma ideia sobre o que pode estar errado (ou mesmo como investigar mais a fundo o que está errado), ou se isso é esperado de alguma forma? Não há muitas partes móveis dinâmicas e não consegui encontrar nada na documentação sobre por que isso pode estar acontecendo.

Eu olhei através do código v2.4.3 seccomp_load() e acho que há apenas dois lugares onde libseccomp gera um código de retorno de -EINVAL :

Ambos os erros acima são efetivamente causados ​​por um filtro inválido. Isso me parece improvável com base no código do seu filtro.

É importante notar que o valor de retorno padrão do kernel em seccomp_set_mode_filter() é -EINVAL , então é possível que algo mais no sistema tenha mudado, levando-nos a cair nesse caminho. Você mencionou que está executando no Docker; você está desativando o filtro Docker seccomp padrão?

Eu ficaria tentado a adicionar mais depuração ao seu código dentro do if após seccomp_load() falhar. Por exemplo, podemos gerar o PFC e / ou o BPF do próprio filtro para verificar se ele parece razoável. Veja seccomp_export_pfc() e seccomp_export_bpf() .

Eu olhei através do código v2.4.3 seccomp_load() e acho que há apenas dois lugares onde libseccomp gera um código de retorno de -EINVAL :

Tenha em mente que quaisquer falhas encontradas em gen_bpf_generate(...) , ou abaixo, são efetivamente combinadas em -ENOMEM por sys_filter_load(...) em src / system.c: 267 .

Eu odeio voltar para "corrupção de memória!" tão rapidamente, mas parece que pode ser o caso aqui.

Obrigado pelas respostas rápidas e detalhadas! Eles geraram vários caminhos de exploração: ligeiramente_smiling_face:

Você notou algum outro comportamento estranho em seu programa desde a atualização? Estou me perguntando se há um problema de corrupção de memória em outro lugar que está causando o problema.

Não, só isso. Nossos testes de unidade e integração continuam a passar e, além deste muito raro EINVAL , nenhum erro está sendo registrado no prod. Isso certamente o torna intrigante; Eu também suspeitei de corrupção de memória, mas não consegui encontrar nenhuma evidência para apoiá-la: ligeiramente_frowning_face:

Para um pouco mais de contexto:

  • o programa é um aplicativo Python que chama algum C ++ via Cython (o GIL é mantido durante este tempo, então o Python não está fazendo alocações em outros threads)
  • as bifurcações laterais do C ++, e no filho configura o filtro seccomp antes de exec
  • todas as alocações de memória que acontecem pós-fork, pré-exec são do próprio libseccomp, em seccomp_init etc.
  • há precisamente 4 mallocs de array entre chamar o código C ++ e bifurcar, eles estão todos no Cython e têm intervalos apropriados ao gravar neles (a linha 435 chama o código seccomp vinculado anteriormente) - todas as outras alocações / gravações / etc. estão dentro do interpretador Python

Enquanto digitava, tive uma ideia: ouvi histórias horríveis sobre malloc não ser seguro para usar após a bifurcação e temos algumas dentro do próprio libseccomp. O próprio aplicativo Python _é_ multithread, mas sempre mantemos o GIL enquanto estamos no código nativo, então isso deve ser seguro (?). Eu só ouvi falar de deadlocks acontecendo através de malloc-after-fork, no entanto. (Eu acho que isso faz com que a próxima ordem de negócios mova seccomp_init et al. Antes do fork, apenas chamando seccomp_load post-fork e vendo se os erros continuam acontecendo.)

Eu ficaria tentado a adicionar mais depuração ao seu código dentro do if after seccomp_load () falhar.

Obrigado pela sugestão! Eu adicionei uma chamada para seccomp_export_pfc , bem como despejei o conteúdo da entrada no filtro ( config->syscall_whitelist ). Farei o acompanhamento na próxima vez que isso falhar.

Olá @Xyene - já se passou cerca de uma semana, só queria verificar se há algo novo que você encontrou.

Ainda não, infelizmente. Depois de adicionar um patch a seccomp_export_pfc , ele ficou em silêncio. Ontem eu empurrei esse patch para todas as nossas VMs (em vez de apenas um de teste) na esperança de capturar o problema quando ele eventualmente ocorrer.

Eu acho o silêncio estranho, mas por agora estou atribuindo isso a uma coincidência, já que toda a lógica de depuração / exportação acontece _após_ a falha seccomp_load , então isso não deveria estar afetando a falha em si.

Progresso!

Acontece que o motivo do silêncio é porque seccomp_export_bpf estava em segfaulting (deveria, se chamado depois de seccomp_load ?), E isso estava sendo relatado em outro lugar e não onde eu estava procurando por falhas de seccomp. Mais importante, encontrei um caso em que posso reproduzir o problema de forma confiável em aproximadamente 150 invocações, portanto, com algum trabalho de encanamento, devo ser capaz de extrair alguns despejos de memória.

Tudo bem, retirei um coredump e este foi o rastreamento: https://gist.github.com/Xyene/920f1cb098784a031f53c66a2f49d167

Isso era um pouco suspeito, já que estava travando dentro da rotina realloc do jemalloc. Além disso, o uso de glibc malloc resolve o problema (infelizmente, não é uma opção de longo prazo neste caso devido a problemas de fragmentação).

Em seguida, puxei o jemalloc, compilei-o com -O0 e símbolos de depuração e fiz a reprodução novamente. Desta vez, ele travou em seccomp_load , ao invés de depois! Enviei esse rastreamento aqui: https://gist.github.com/Xyene/5da56168bcea337da85b2cd30704d12e

Um trecho desse traço:

#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

Pesquisando em jemalloc, parece que 0x5a é usado para marcar bytes livres como livres , com a intenção específica de travar o código que está tentando liberar algo que já foi liberado.

gen_bpf.c:511 na v2.4.3 é: https://github.com/seccomp/libseccomp/blob/1dde9d94e0848e12da20602ca38032b91d521427/src/gen_bpf.c#L505 -L513

Mas, isso não faz muito sentido, já que a vida útil do programa é apenas o corpo de sys_filter_load :

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

Acho que identifiquei pelo menos um problema. Em gen_bpf_generate ;

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

state.bpf = prgm desde que zmalloc não falhe. Em seguida, _gen_bpf_build_bpf é chamado e baseado em rc , state.bpf é definido como NULL .

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

Considerando o caso em que rc != 0 , state.bpf ainda está definido como prgm no momento da chamada para _state_release . Isso fará com que a memória apontada por prgm seja liberada.

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

Em seguida, gen_bpf_generate will return prgm , que apesar de ter sido liberado, ainda é um ponteiro diferente de zero.

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

De volta a sys_filter_load , gen_bpf_generate retorna, e prgm não é- NULL então continua.

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

Finalmente, no final de sys_filter_load , gen_bpf_release é chamado no já livre prgm .

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

Isso não resolve a preocupação de por que _gen_bpf_build_bpf falharia em primeiro lugar, mas parece algo ruim que poderia acontecer se isso acontecesse.

Editar: na verdade, parece que provavelmente foi corrigido como um efeito colateral de https://github.com/seccomp/libseccomp/commit/3a1d1c977065f204b96293cccfe7d3e5aa0d7ace.

Considerando o caso em que rc! = 0, state.bpf ainda está definido como prgm no momento da chamada para _state_release. Isso fará com que a memória apontada por prgm seja liberada.

Ah ha! Boa captura @Xyene!

Acho que precisamos consertar isso além de 3a1d1c977065f204b96293cccfe7d3e5aa0d7ace, deixe-me pensar nisso por um minuto ... Não acho que a correção será muito difícil ... e ver se consigo um PR.

Acho que precisamos corrigir isso além de 3a1d1c9, deixe-me pensar nisso por um minuto ... Não acho que a correção será muito difícil ... e ver se consigo um PR.

Opa, eu estava olhando para um código antigo quando o escrevi; sim, acredito que 3a1d1c9 corrige isso para nós, mas precisaremos de um patch para o branch release-2.4. Vou trabalhar nisso agora.

_ (Meta: Vou continuar atualizando esta mensagem com minhas descobertas à medida que prossigo, então tenho um lugar para anotá-las sem enviar spam por e-mail para vocês:) _

Tudo bem, de volta ao 2.4.3 com o patch aplicado, consegui retirar o filtro que estava falhando: link .

A causa relatada agora é ENOMEM vez de EINVAL , o que eu acho que é esperado, dado que _gen_bpf_build_bpf está falhando e retornando um programa NULL . O PFC imprime bem, no entanto. Modificar o código seccomp para relatar o valor de retorno de _gen_bpf_build_bpf mostra EFAULT como a causa.

Como um hack rápido, executei :%s/return -EFAULT/abort() em src/gen_bpf.c e consegui extrair este rastreamento de pilha:

Rastreamento de pilha 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

Isso corresponde à linha 1943:

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

Dada a natureza da substituição, acho que podemos excluir qualquer EFAULT em qualquer função auxiliar, uma vez que elas teriam sido abortadas primeiro.

Depois disso, tentei reproduzir o mesmo com HEAD - ainda acontece. Em seguida, %s:/goto build_bpf_free_blks/abort() e repita. A causa foi:

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

Felizmente, essa função era curta e tinha apenas alguns pontos de falha. Outra rodada de abort inserções mais tarde;

Vestígio

(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

Portanto, está realloc falhando novamente, e _bpf_append_blk está retornando -ENOMEM que foi mascarado por _gen_bpf_build_bpf e se transformou em -EFAULT . Isso não é grande coisa, mas como você disse que um relatório de erro melhor é uma meta de 2,5, pensei em mencioná-lo, pois parece estar dentro do escopo: ligeiramente_smiling_face:

Algumas cutucadas com 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

Este realmente começa a parecer uma falha de alocador ...

Aha, esta história finalmente chegou à sua conclusão _trincante_ - descobri o que está acontecendo e verifiquei uma correção: ligeiramente_sorrindo_face:

Uma vez que pode ser uma história interessante, aqui está:

O processo principal que separa o trabalhador geralmente fica em ~ 80 MB RSS. Depois de se bifurcar, ele restringe o uso de memória por meio de rlimit , às vezes até 64 MB. Isso o coloca em uma posição em que seu uso de memória atual excede seu limite, mas isso é permitido por rlimit . _ Na maioria das vezes, o alocador de memória terá memória livre suficiente disponível para atender às rotinas de inicialização do libseccomp sem solicitar mais do kernel. Mas quando _não_ e precisa solicitar espaço para uma arena extra ou algo assim, o kernel não vai fornecer, pois o processo já está acima do seu limite.

Em 2.4.3, essa falha em obter memória se manifestou em EINVAL e um double-free. No mestre post- https://github.com/seccomp/libseccomp/commit/3a1d1c977065f204b96293cccfe7d3e5aa0d7ace , EFAULT é relatado em seu lugar. Com https://github.com/seccomp/libseccomp/pull/257 aplicado, ENOMEM é informado corretamente.

O motivo pelo qual isso acontece tão raramente torna-se óbvio: é inteiramente dependente se o alocador tem memória suficiente disponível para construir o programa BPF sem solicitar mais do kernel. O alocador da glibc é mais flexível quanto a permitir o acúmulo de fragmentação, então isso nunca aconteceu com ela no lugar. jemalloc coloca limites mais estreitos e leva a um aumento da probabilidade de precisar solicitar memória durante seccomp_load - apenas o suficiente para notar as falhas resultantes, mas ainda é irritante para rastrear.

A correção, então, é simplesmente mover todas as chamadas de setrlimit para _após_ seccomp_load . Ao fazer isso, realloc não falha mais em _bpf_append_blk e o filtro é carregado com êxito. Isso significa que o filtro precisa permitir setrlimit , mas no meu caso isso era aceitável. De maneira mais geral, acho que esse problema seria resolvido por algo como https://github.com/seccomp/libseccomp/issues/123.

@pcmoore , @drakenclimber - obrigado novamente por toda a sua ajuda na depuração deste problema! Estou feliz por poder deixar isso para trás agora, mas suas dicas foram inestimáveis ​​para chegar lá: smiley:

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

kloetzl picture kloetzl  ·  19Comentários

srd424 picture srd424  ·  18Comentários

vasanthaganeshk picture vasanthaganeshk  ·  9Comentários

alban picture alban  ·  9Comentários

pcmoore picture pcmoore  ·  20Comentários