Desafio da Semana #12


 


Por: Roberto Alexis Farah


 


Olá pessoal!


Tenho demorado para publicar novos artigos porque tenho usado meu tempo livre no desenvolvimento de duas ferramentas, uma delas pública que, em breve, vocês tomarão conhecimento através desse blog. 😉


 


No desafio de hoje abordarei dois problemas que estão entre os mais comuns que encontro em códigos VB.NET, C# e ASP.NET.


Embora ambos são abordados juntos aqui, na prática são problemas independentes.


 


 


CENÁRIO


 


Eis o código fonte. Pode ser traduzido para VB.NET, optei por fazer em C# simplesmente porque conheço mais:


 


namespace Demo


{


          class Program


          {


                   static void Main(string[] args)


                   {


                             string str = “Abcd”;


                            


                             DoSomething(str);


                            


                   }


                  


                   static bool DoSomething(string str)


                   {


                             // Checa se string é nula/vazia.


                             if(0 == str.Length)


                             {


                                      return false;


                             }


                            


                             str = str.ToUpper();


                                     


                             Microsoft.Office.Interop.Word.Application msWord = null;


                                                                            


                             try


                             {


                                      msWord = new Microsoft.Office.Interop.Word.Application();


 


                                      object saveChanges = false;


                                      object missing         = null;


                            


                                      msWord.Quit(ref saveChanges, ref missing, ref missing);


 


                             }


                             catch(OutOfMemoryException ex)


                             {


                                      Console.WriteLine(“Não há memória disponível…”);


                             }


                             finally


                             {


                                      // Como liberar o objeto COM?


                             }


                            


                             return true;


                   }


          }


}


 


 


SINTOMA


 


Temos dois problemas e dois sintomas no código acima.


 


Primeiro Sintoma:


 


Algumas vezes o código acima pode sofrer um crash por NullReferenceException quando o parâmetro ‘str’ é utilizado, como na comparação com str.Length.


 


Caso real: Em incidentes que trabalhei esse tipo de exceção costuma surpreender os desenvolvedores que assumem erroneamente que os parâmetros de métodos estão sendo testados antes do uso.


 


 


Segundo Sintoma:


 


Sempre que o componente Word é utilizado ele nunca é liberado da memória.


 


Caso real: Em incidentes que trabalhei esse foi o problema número um relacionado a contador de instância de componentes no COM+ usados via .NET nunca decrementarem quando supostamente liberados da memória!


 


 


OBJETIVO


 


Identifique ambos PROBLEMAS e proponha uma SOLUÇÃO para cada um deles.


 


 


Até semana que vem!


 

Comments (6)

  1. Marcondes says:

    Olá Farah!!!

    Aqui vai minha solução

    PRIMEIRO SINTOMA:

    A premissa de que "sempre teremos o parâmetro inicializado e com valores pronto para eu usar" é sempre a causa de problemas em runtime quando estamos com os aplicativos em produção. Os cenários são sempre diferentes daqueles que pressupomos durante o desenvolvimento. Eu tenho comigo a seguinte premissa sempre: "toda vez que meu código depender de dados externos, nunca irei confiar na sua origem". Pode parecer um pouco absurdo, pq no caso de um método de um programa que você mesmo desenvolveu e que você mesmo está utilizando, a fonte dos dados é você 🙂 portanto você não estaria confiando nem em você. Mas essa é a regra de ouro: você muita vezes acaba dando tiro no próprio pé, portanto, mais vale uma verificação em código do que uma bronca em produção.

    O primeiro problema apresentado se encontra dentro do método DoSomething(…). A verificação if (0 == str.Length) { … está errada pois pressupõe que o parâmetro str não seja nulo. Este teste apenas vale para testar se a string passada é vazia (string vazia não é igual a parâmetro nulo). Portanto, para resolver este problema, basta utilizarmos o método estático da classe String que verifica para nós se a string é nula. Assim, o trecho de código novo ficaria assim:

    static bool DoSomething(string str)

    {

     // Checa se string é nula/vazia.

     if(String.IsNullOrEmpty(Length))

     {

        return false;

     }

     str = str.ToUpper();

     (……)

    SEGUNDO SINTOMA:

    O segundo sintoma é causado principalmente pelo desconhecimento de muitos desenvolvedores do funcionamento interno do interop COM .NET: no exemplo do código postado, o objeto do word só será liberado (se um dia for!!!!) quando o garbage collector coletar o wrapper .NET criado para realização das chamadas ao COM do word. Em outras palavras, o comportamento é não determiniístico e portanto você poderá ficar com recursos não gerenciados (no caso o objeto COM da instância do WORD) carregados na memória. Quando o wrapper foi criado, internamento o objeto COM do WORD foi acionado (na verdade, o método AddRef da interface IUnknown foi chamado), adicionando um contador para a instância Word criada. Pois bem, para liberar de vez o objeto, precisamos zerar a contagem de referência. Para fazer isso, precisamos utilizar o método

    system.runtime.interopservices.marshal.finalreleasecomobject

    para liberar o recurso COM não gerenciado da memória. Essa feature que eu estou utilizando, assim como a da string no caso 1, são recursos do framework 2.0.

    Assim, para liberar o objeto WORD, fazemos o seguinte:

    try

    {

    msWord = new Microsoft.Office.Interop.Word.Application();

    object saveChanges = false;

    object missing         = null;

    msWord.Quit(ref saveChanges, ref missing, ref missing);

    }

    catch(OutOfMemoryException ex)

    {

    Console.WriteLine("Não há memória disponível…");

    }

    finally

    {

    // Como liberar o objeto COM? ASSIM DESTE JEITO 😉

    System.Runtime.InteropServices.Marshal.Finalreleasecomobject(msWord);

    msword = null;

    }

    Lembrando que se estivéssemos no framework 1.1, poderíamos liberar o objeto da mesma forma só que utilizando o método ReleaseComObject dentro de um loop até que o retorno do método fosse zero (0) (isso pq o ReleaseComObject liberava apenas uma referência do contador).

    É isso aí Farah!

    Super abraço!

    Marcondes                                      

  2. Roberto Farah says:

    Oi Marcondes!

    Muito completo e didático seu post! Estou me perguntando se ainda preciso publicar a resposta. 🙂

    Obrigado

  3. Weber Ress says:

    Olá Marcondes, td bem ?

    Da forma que vc definiu "str", ela estaria vulnerável a ataques de Buffer Overflow. Correto ?

    []’s !

    Weber Ress

  4. Marcondes says:

    Olá Weber, tudo bem?

    a definição da variável str é do tipo string, um objeto gerenciado dentro do CLR. Como o parâmetro do método DoSomethind é do tipo string, e este é o único ponto de entrada para o método, não estamos permitindo que haja qualquer tipo de acesso direto ao endereçamento do objeto string, o valor lido do parâmetro (não importa o que seja) será interpretado como o tamanho total da string. Não vamos ter problema de buffer overflow.

    []´s

  5. Weber Ress says:

    Olá ! Feliz 2007 !! 🙂

    O código abaixo é suscetível a Buffer Overflow, pois não estou verificando qual o tamanho de Name *antes* de copiar para NameBuffer, que possui um tamanho limitado.

    void SayHello(char * Name)

    {

    char NameBuffer[256];

    strcpy(NameBuffer,Name);

    printf("Hello %s",NameBuffer);

    }

    Da forma que vc colocou no comentário anterior, como um ambiente CLR se protegeria de uma situação como esta ? Entendi que o acesso direto a memória é impedido através do CLR, mas este novo cenário seria tratado pelo CLR ?

    []’s !!

    Weber Ress

  6. Roberto Farah says:

    Olá Weber,

    O código que você colocou é suscetível a buffer overflow sim. No ambiente CLR o risco de buffer overflow é bastante minimizado, entretanto, se você fizer chamadas para código não gerenciado via, por exemplo, C# ou VB.NET, você fica sujeito aos problemas típicos de código nativo.

    Esse artigo esclarece bem isso:

    http://msdn.microsoft.com//msdnmag/issues/04/04/securitybriefs/

    Em relação a Buffer Overflow em código nativo, como no exemplo que você colocou, há dois artigos meus falando sobre isso:

    Desafio Da Semana #8 – Crash – Buffer Overflow

    http://blogs.technet.com/latam/archive/2006/07/24/443173.aspx

    Desafio Da Semana #9 – Como explorar um Buffer Overflow

    http://blogs.technet.com/latam/archive/2006/08/11/446117.aspx

    []s,

    Roberto