Ir para conteúdo
  • Cadastre-se

dev botao

Vazamento de memória TDFeSSL


Recommended Posts

Boa tarde!

Estou com um problema de vazamento de memória no componente ACBr.

Mas não consegui localizar exatamente onde ocorre.

O vazamento é relacionado ao certificado digital.

Utilizo carregamento de PFX com a configuração cryWinCrypt, além do lixo na memória ele também cria diversos arquivos na pasta "..\AppData\Roaming\Microsoft\Crypto\RSA".  OBS: Se alguém souber uma forma de evitar a criação dos arquivos na pasta RSA ou a exclusão automática ao termino da execução seria de grande ajuda. Minha rotina é com milhares de empresas e com diversas ações, em um dia está estourando o disco.

Mas vamos ao caso da MemoryLeak, no exemplo abaixo eu rodei um loop de 1000, nele é possível ver que a memória sobe. Fazendo esse teste com cryOpenSSL a memória permanece estável (OBS: Usando ACBr com cryOpenSSL em consultas(Distribuicao e consulta de status) o consumo da memória também sobe, mas não sei se é o acbr ou falha do meu código, ainda estou tentando localizar):

unit ACBrDFeSSL;

  function LoadCertificado( const AFileName: String ): AnsiString;
  var
    vMemory: TBytesStream;
    vBytes:  TArray<Byte>;
  begin
    vMemory := TBytesStream.Create();
    try
      vMemory.LoadFromFile( AFileName );
      vBytes := vMemory.Bytes;
      SetString(Result, PAnsiChar(@vBytes[0]), Length(vBytes));
    finally
      vMemory.Free;
    end;
  end;

procedure TForm2.MetodoTeste( ALoop: Integer );
var
 vDFeSSL: TDFeSSL;
 vPFX:    Boolean;
 vCT:     Integer;
 vMetodo: TSSLCryptLib;
begin
  vPFX := True;
  vMetodo := cryWinCrypt;
  for vCT := 1 to ALoop do begin
    vDFeSSL := TDFeSSL.Create;
    //  TSSLCryptLib = (cryNone, cryOpenSSL, cryCapicom, cryWinCrypt);
    try
      if vPFX then begin
        vDFeSSL.DadosPFX    := LoadCertificado(ExtractFilePath( Application.ExeName )+'certificado.pfx');
        vDFeSSL.Senha       := '123456';
      end else begin
        vDFeSSL.NumeroSerie := '';
      end;
      vDFeSSL.SSLCryptLib := vMetodo;
      vDFeSSL.CarregarCertificado;
    finally
      vDFeSSL.DescarregarCertificado;
      FreeAndNil( vDFeSSL );
    end;
  end;
end;

 

Link para o comentário
Compartilhar em outros sites

14 horas atrás, Juliomar Marchetti disse:

porque tu cria o arquivo para usar? porque não joga direto no memorystream e dai no componente?

Não entendi. Porque eu crio TDFeSSL ou porque eu dou load no pfx no memoryStream para depois passar pro TDFeSSL ?

Na rotina original eu pego o PFX através do banco de dados. E crio o TDFeSSL para obter as informações do pfx, no caso, quero saber se o certificado é da empresa e se está vencido antes de entrar na rotina.

 

Link para o comentário
Compartilhar em outros sites

Realmente, acabei de concluir que a classe TDFeOpenSSL, localizada na unit ACBrDFeOpenSSL, está vazando memória.

Custei a descobrir devido ao fato que o despejo ocorre com alguns certificados e outros não.

Porém o defeito é silencioso, não retornando nenhuma exceção, além do mais o certificado é lido corretamente.

Vou trabalhar aqui para tentar descobrir o que se trata.

se alguém souber de alguma coisa....

 

Link para o comentário
Compartilhar em outros sites

Agora, Renato Rubinho disse:

Bom dia,

Caso tenha alguma sugestão de correção, por favor anexe o fonte corrigido, identifique como podemos simular o problema para validação do ajuste.

Bom dia, Renato!

O vazamento pode ser reproduzido com o exemplo de código no início do post. No exemplo acima está cryWinCrypt, mas o teste que fiz agora foi com cryOpenSSL.

Fiz um Loop de 1000. Mas o problema ocorre somente com alguns certificados.

Só para ressaltar, meu teste foi com a ultima versão da dll.

 

Editado por galegoga
Link para o comentário
Compartilhar em outros sites

  • Moderadores
19 horas atrás, galegoga disse:

Realmente, acabei de concluir que a classe TDFeOpenSSL, localizada na unit ACBrDFeOpenSSL, está vazando memória.

Custei a descobrir devido ao fato que o despejo ocorre com alguns certificados e outros não.

Porém o defeito é silencioso, não retornando nenhuma exceção, além do mais o certificado é lido corretamente.

Vou trabalhar aqui para tentar descobrir o que se trata.

se alguém souber de alguma coisa....

 

volto a insistir não crie o arquivo físico PFX e sim carrege do banco direto ao componente

Consultor SAC ACBr Juliomar Marchetti
 

Projeto ACBr

skype: juliomar
telegram: juliomar
e-mail: [email protected]
http://www.juliomarmarchetti.com.br
MVP_NewLogo_100x100_Black-02.png
 

 

Link para o comentário
Compartilhar em outros sites

46 minutos atrás, Juliomar Marchetti disse:

volto a insistir não crie o arquivo físico PFX e sim carrege do banco direto ao componente

Juliomar, o arquivo, na aplicação real, recebe do banco de dados. O arquivo encontra-se  em base64 armazenado na variavel FDadosPFXBase64, converto ele para para um StreamByte depois converto para Ansi para passar ao ACBr. Não sei se estou fazendo algo errado mas se estiver aceito sugestões, veja código abaixo da aplicação real:

function TDistribuicaoEngineDFeModel.GetDadosPFXToAnsi: AnsiString;
var
  vMemory: TMemoryStream;
  vOut:    TBytesStream;
  vBytes:  TArray<Byte>;
begin
  Result  := '';
  if (FDadosPFXBase64.Trim.IsEmpty) then begin
    Exit;
  end;
  vMemory := TStringStream.Create(FDadosPFXBase64);
  vOut    := TBytesStream.Create;
  try
    TNetEncoding.Base64.Decode( vMemory, vOut );
    vBytes := vOut.Bytes;
    SetString(Result, PAnsiChar(@vBytes[0]), Length(vBytes));
  finally
    vOut.Free;
    vMemory.Free;
  end;
end;

Agora eu não entendi seu posicionamento, ler um arquivo físico do disco é problema ? ao meu ver deveria ser a mesma coisa.

O código do início do post, lendo um arquivo físico do disco, é o código de teste que criei para constatar onde estava o problema. É um código de teste.

Agora, vou fazer um outro teste, vi que há uma atualização do openSSL "3.1.3" com dll nomeada libcrypto-3..., meus teste foram com a dll  versão "1.1.1.10" libcrypto-1_1-... Vou atualizar meu acbr e fazer novos teste. Reporto aqui o resultado.

Editado por galegoga
Link para o comentário
Compartilhar em outros sites

  • Moderadores
Em 03/01/2024 at 16:24, galegoga disse:
vDFeSSL.DadosPFX    := LoadCertificado(ExtractFilePath( Application.ExeName )+'certificado.pfx');

se tu tá carregando no banco porque tem o pfx aqui?

Consultor SAC ACBr Juliomar Marchetti
 

Projeto ACBr

skype: juliomar
telegram: juliomar
e-mail: [email protected]
http://www.juliomarmarchetti.com.br
MVP_NewLogo_100x100_Black-02.png
 

 

Link para o comentário
Compartilhar em outros sites

18 minutos atrás, Juliomar Marchetti disse:

se tu tá carregando no banco porque tem o pfx aqui?

Esse código é de teste. Criei para ver o comportamento do componente.

Na aplicação real recebo ele via REST em Base64.

Agora, existe algum problema no componente em carregar o arquivo físico do disco ?

Link para o comentário
Compartilhar em outros sites

Acho que encontrei alguma coisa. O problema está na chamada "PKCS12parse" dentro do método TDFeOpenSSL.LerPFXInfo.

Efetuei uma pesquisa e encontrei um link que menciona memoryleak nesse método, não exatamente no método, mas a falta de execução de um método para Limpar o conteúdo.  A solução é data por Stephen Henson, veja o problema relatado com a solução:

in order to try and prove that the memory leak i am seeing
in PKCS12_parse() is not specific to my embedded system,
i compiled the following using OpenSSL 0.9.6 on solaris
void parsetest(BIO *databio)
{
EVP_PKEY *Pkey=NULL;
X509 *Cert=NULL;
STACK_OF(X509) *Ca=NULL;
PKCS12 *PK12=NULL;
PK12 = d2i_PKCS12_bio(databio, NULL);
PKCS12_parse(PK12, NULL, &Pkey, &Cert, &Ca);
PKCS12_free(PK12);
if (Pkey)
EVP_PKEY_free(Pkey);
if (Cert)
X509_free(Cert);
if (Ca)
sk_X509_free(Ca);

The above line is the problem, it just frees up the
STACK, not its contents. It should be:

sk_X509_pop_free(Ca, X509_free);

Steve.

Link oficial do problema relatado.

Ele menciona que o método sk_X509_pop_free deve ser executado para limpar o conteúdo e não somente o STACK(não sei o que é).

Tentei localizar esse método na Unit OpenSSLExt, mas não encontrei. Mas no arquivo ssl_openssl_lib.pas dentro da pasta "ACBr\Fontes\Terceiros\synalist" eu encontrei.

Tentei transpo-lo para o arquivo OpenSSLExt para utiliza-lo mas não consegui. Existem algumas diferenças entre os arquivos e me falta conhecimento para código de mais baixo nível, além de não conhecer nada sobre a documentação do OpenSSL.

Se alguém puder ajudar eu agradeço.

 

 

 

Link para o comentário
Compartilhar em outros sites

Boa tarde!

Acho que consegui algo.

Consegui reproduzir o teste do inicio do post sem que ocorra vazamento de memória na leitura do certificado.

Mas antes de prosseguir volto ao post anterior, onde é mencionado uma solução com o método sk_X509_pop_free, ele até existe no arquivo ssl_openssl.pas, mas é inócuo, já que o método não existe na DLL. "_SkX509PopFree := GetProcAddr(SSLUtilHandle, 'SK_X509_POP_FREE');" sempre retorna nulo.

A solução que encontrei foi uma comparação dos arquivos ssl_openssl.pas e ACBrDFeOpenSSL.pas.

Comparei os métodos TSSLOpenSSL.LoadPFX e TDFeOpenSSL.LerPFXInfo e peguei trechos de código de um e adicionei ao outro. Código comparados:

Código da classe: ssl_openssl.TSSLOpenSSL

function TSSLOpenSSL.LoadPFX(pfxdata: Ansistring): Boolean;
var
  cert, pkey, ca: SslPtr;
  b: PBIO;
  p12: SslPtr;
begin
  Result := False;
  b := BioNew(BioSMem);
  try
    BioWrite(b, pfxdata, Length(PfxData));
    p12 := d2iPKCS12bio(b, nil);
    if not Assigned(p12) then
      Exit;
    try
      cert := nil;
      pkey := nil;
      ca := nil;
      try {pf}
        if PKCS12parse(p12, FKeyPassword, pkey, cert, ca) > 0 then
          if SSLCTXusecertificate(Fctx, cert) > 0 then
            if SSLCTXusePrivateKey(Fctx, pkey) > 0 then
              Result := True;

        //  Set Certificate Verification chain
        if Result and (ca <> nil) then
          SslCtxCtrl(Fctx, SSL_CTRL_CHAIN, 0, ca);
      {pf}
      finally
        EvpPkeyFree(pkey);
        X509free(cert);
        SkX509PopFree(ca,_X509Free); // for ca=nil a new STACK was allocated...
      end;
      {/pf}
    finally
      PKCS12free(p12);
    end;
  finally
    BioFreeAll(b);
  end;
end;

 

Código da classe ACBrDFeOpenSSL.TDFeOpenSSL:

function TDFeOpenSSL.LerPFXInfo(const PFXData: Ansistring): Boolean;
var
  ca, p12: Pointer;
  b: PBIO;
begin
  Result := False;
  DestroyKey;  

  b := BioNew(BioSMem);
  try
    BioWrite(b, PFXData, Length(PFXData));
    p12 := d2iPKCS12bio(b, nil);
    if not Assigned(p12) then
      Exit;

    try
      DestroyCert;
      DestroyKey;
      ca := nil;
      if PKCS12parse(p12, FpDFeSSL.Senha, FPrivKey, FCert, ca) > 0 then
      begin
        if (FCert <> nil) then
        begin
          GetCertInfo( FCert );
          Result := True;
        end;
      end;
    finally
      PKCS12free(p12);
    end;
  finally
    BioFreeAll(b);
  end;
end;

Basicamente o que encontrei diferente foi o código, que passei para ACBrDFeOpenSSL :

        if SSLCTXusecertificate(Fctx, Fcert) > 0 then
          if SSLCTXusePrivateKey(Fctx, FPrivKey) > 0 then
            Result := True;

        //  Set Certificate Verification chain
        if Result and (ca <> nil) then
          SslCtxCtrl(Fctx, SSL_CTRL_CHAIN, 0, ca);

A modificação Final ficou assim:

  TDFeOpenSSL = class(TDFeSSLCryptClass)
  private
    ....
    Fctx: PSSL_CTX; // Adicionado
  proteced
    ...    
    procedure FreeContext; // Adicionado
  end;

constructor TDFeOpenSSL.Create(ADFeSSL: TDFeSSL);
begin
  inherited Create(ADFeSSL);
  ...  
  Fctx := nil;  // Adicionado
end;

destructor TDFeOpenSSL.Destroy;
begin
  ...
  FreeContext; // Adicionado
  inherited Destroy;
end;

procedure TDFeOpenSSL.FreeContext; // Adicionado
begin
  if assigned (Fctx) then
  begin
    SslCtxFree(Fctx);
    Fctx := nil;
    ErrRemoveState(0);
  end;
end;

function TDFeOpenSSL.LerPFXInfo(const PFXData: Ansistring): Boolean;

  procedure SetContex; // Adicionado
  begin
    FreeContext;
    Fctx := nil;
    case FpDFeSSL.SSLType of
      LT_SSLv2:
        Fctx := SslCtxNew(SslMethodV2);
      LT_SSLv3:
        Fctx := SslCtxNew(SslMethodV3);
      LT_TLSv1:
        Fctx := SslCtxNew(SslMethodTLSV1);
      LT_TLSv1_1:
        Fctx := SslCtxNew(SslMethodTLSV1_1);
      LT_TLSv1_2:
        Fctx := SslCtxNew(SslMethodTLSV1_2);
      LT_TLSv1_3:
        Fctx := SslCtxNew(SslMethodTLSV1_3);
      LT_all:
        begin
          //try new call for OpenSSL 1.1.0 first
          Fctx := SslCtxNew(SslTLSMethod);
          if Fctx=nil then
            //callback to previous versions
            Fctx := SslCtxNew(SslMethodV23);
        end;
    else
      Exit;
    end;
  end;

var
  ca, p12: Pointer;
  b: PBIO;
begin
  Result := False;
  DestroyKey;
  SetContex; // Adicionado

  b := BioNew(BioSMem);
  try
    BioWrite(b, PFXData, Length(PFXData));
    p12 := d2iPKCS12bio(b, nil);
    if not Assigned(p12) then
      Exit;

    try
      DestroyCert;
      DestroyKey;
      ca := nil;
      if PKCS12parse(p12, FpDFeSSL.Senha, FPrivKey, FCert, ca) > 0 then
      begin
        // Adicionado Inicio =================================================
        if SSLCTXusecertificate(Fctx, Fcert) > 0 then
          if SSLCTXusePrivateKey(Fctx, FPrivKey) > 0 then
            Result := True;

        //  Set Certificate Verification chain
        if Result and (ca <> nil) then
          SslCtxCtrl(Fctx, SSL_CTRL_CHAIN, 0, ca);
        // Adicionado Fim ====================================================

        if (FCert <> nil) then
        begin
          GetCertInfo( FCert );
          Result := True;
        end;
      end;
    finally
      PKCS12free(p12);
    end;
  finally
    BioFreeAll(b);
  end;
end;

Depois dessa modificação o memoryleak parou de acontecer. Não sei o que esses métodos fazem, mas me parece que ele cria um vinculação da Variável "ca" com Fctx  e quando o método FreeContex finaliza Fctx ele também deve estar eliminando os objetos apontados por "ca". Li na documentação de PKCS12parse que quando é passado "ca" como null ele cria um objeto internamente. Talvez seja isso.

 

  • Curtir 2
Link para o comentário
Compartilhar em outros sites

  • 1 mês depois ...
  • Consultores

Bom dia,

Criada a TK-5127 para avaliação.

Obrigado pela contribuição.

  • Curtir 1
Consultor SAC ACBr

Alexandre de Paula
Ajude o Projeto ACBr crescer - Assine o SAC                    

Projeto ACBr     Telefone:(15) 2105-0750 WhatsApp(15)99790-2976.  ícone Discórdia Discord   

Projeto ACBr - A maior comunidade Open Source de Automação Comercial do Brasil

 

 

Link para o comentário
Compartilhar em outros sites

  • Consultores
9 minutos atrás, Alexandre Tymoszenko disse:

Isto é sinal que a sugestão será implementada?

Não necessariamente. Sempre que você ver um numero de TK é que registramos no nosso backlog.

A partir daí (depedendo da disponibilidade dos consultores e prioridades das tarefas) ela vai para análise.

Depois de analisada aí sim é definido se a implementação é aceita, se é modificada ou se é recusada.

Mas em todo caso uma vez registrada é sempre dado um retorno aqui no topico do forum, tanto quando aceita quanto recusada.

Consultor SAC ACBr

Alexandre de Paula
Ajude o Projeto ACBr crescer - Assine o SAC                    

Projeto ACBr     Telefone:(15) 2105-0750 WhatsApp(15)99790-2976.  ícone Discórdia Discord   

Projeto ACBr - A maior comunidade Open Source de Automação Comercial do Brasil

 

 

Link para o comentário
Compartilhar em outros sites

  • Membros Pro
20 horas atrás, Alexandre de Paula disse:

Não necessariamente. Sempre que você ver um numero de TK é que registramos no nosso backlog.

A partir daí (depedendo da disponibilidade dos consultores e prioridades das tarefas) ela vai para análise.

Depois de analisada aí sim é definido se a implementação é aceita, se é modificada ou se é recusada.

Mas em todo caso uma vez registrada é sempre dado um retorno aqui no topico do forum, tanto quando aceita quanto recusada.

Entendi. Porém, acredito que poderia ser considerado como uma correção. Talvez, vocês não tenham muitos reportes porque a maioria deve instanciar o certificado uma única vez. Em nossa aplicação aqui na empresa, como temos emissão em mais de um CNPJ, o problema foi bem claro. 

Link para o comentário
Compartilhar em outros sites

  • Consultores

Sim. Isso é levado em consideração pra priorizar as atividades. Assim como a solicitação ser realizada por um assinante PRO também.

Assim que tivermos um posicionamento retornamos aqui.

  • Curtir 1
  • Obrigado 1
Consultor SAC ACBr

Alexandre de Paula
Ajude o Projeto ACBr crescer - Assine o SAC                    

Projeto ACBr     Telefone:(15) 2105-0750 WhatsApp(15)99790-2976.  ícone Discórdia Discord   

Projeto ACBr - A maior comunidade Open Source de Automação Comercial do Brasil

 

 

Link para o comentário
Compartilhar em outros sites

  • Membros Pro
15 horas atrás, Alexandre de Paula disse:

Sim. Isso é levado em consideração pra priorizar as atividades. Assim como a solicitação ser realizada por um assinante PRO também.

Assim que tivermos um posicionamento retornamos aqui.

Obrigado pelos esclarecimentos, Alexandre. 

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

  • 2 semanas depois ...

Seria bom que colocasse esse ajuste, recentemente tive que atualizar o ACBr e tive que modificar novamente a unit.

Não consegui solucionar utilizando a versão que utiliza componentes do Windows. Ele gera diversos arquivos numa pasta do AppData ficando enorme.

Usando o OpenSSL consegui resolver o problema que consumia muita memória, em uma hora de uso a aplicação consumia 500MB só de leitura de certificado e continuava a crescer até dar crash no aplicativo.

  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

  • 2 semanas depois ...
  • Consultores
Em 08/01/2024 at 18:14, galegoga disse:

Boa noite!

Segue o arquivo com a sugestão de correção para evitar memoryLeak na leitura de alguns certificados.

ACBrDFeOpenSSL.pas 20.33 kB · 6 downloads

Me parece que você também fez modificações no arquivo OpenSSLExt.pas e não o colocou aqui.

Ou talvez você esteja utilizando outra versão... poderia disponibilizar?

Eu não encontrei o SslMethodTLSV1_3 citado na linha 480 da unit ACBrDFeOpenSSL.pas.

 

  • Curtir 1

[]'s

Consultor SAC ACBr

Elton
Profissionalize o ACBr na sua empresa, conheça o ACBr Pro.

Projeto ACBr     Telefone:(15) 2105-0750 WhatsApp(15)99790-2976.

Um engenheiro de Controle de Qualidade(QA) entra num bar. Pede uma cerveja. Pede zero cervejas.
Pede 99999999 cervejas. Pede -1 cervejas. Pede um jacaré. Pede asdfdhklçkh.
Link para o comentário
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisar ser um membro para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar Agora
×
×
  • Criar Novo...

Informação Importante

Colocamos cookies em seu dispositivo para ajudar a tornar este site melhor. Você pode ajustar suas configurações de cookies, caso contrário, assumiremos que você está bem para continuar.