コード障害「ヌルポインターの逆参照」の検出

この記事では、 AppChecker静的コードアナライザーを使用して、オープンソースプロジェクトのエラーと脆弱性を検出するための一連の出版物を開きます 。 このシリーズでは、深刻な脆弱性につながる可能性のあるプログラムコードの最も一般的な欠陥について検討します。 今日は、「nullポインターの逆参照」などの欠陥に焦点を当てます。







nullポインター(CWE-476)の逆参照は、無効なポインターを使用してプログラムがメモリにアクセスする場合の欠陥です。 このような呼び出しは、未定義のプログラム動作につながり、ほとんどの場合、プログラムのクラッシュにつながります。



以下は、NULLポインターアクセスの例です。 この場合、ほとんどの場合、プログラムはエラーメッセージを発行せずに動作します。



#include <iostream> class A { public: void bar() { std::cout << "Test!\n"; } }; int main() { A* a = 0; a->bar(); return 0; }
      
      





次に、プログラムがクラッシュする例を考えてみましょう。 この例は前の例と非常に似ていますが、わずかな違いがあります。



 #include <iostream> class A { int x; public: void bar() { std::cout << x << "Test!\n"; } }; int main() { A* a = 0; a->bar(); return 0; }
      
      





それでは、なぜあるプログラムでは問題なく動作するのに、別のプログラムではうまく動作しないのでしょうか? 実際には、2番目のケースでは、呼び出されたメソッドがnullオブジェクトのフィールドの1つを参照しているため、アドレス空間の予測不可能な領域から情報を読み取ることになります。 前者の場合、メソッドはオブジェクトのフィールドにアクセスしないため、プログラムはほとんどの場合正しく終了します。



次のC ++コードスニペットを検討してください。



 if( !pColl ) pColl->SetNextTxtFmtColl( *pDoc->GetTxtCollFromPool( nNxt ));
      
      





pColl == NULLの場合、この条件ステートメントの本体が実行されることは簡単にわかります。 ただし、オペレーターの本体では、pCollポインターが逆参照され、プログラムがクラッシュする可能性があります。



通常、このような欠陥は、開発者の不注意により発生します。 ほとんどの場合、このタイプのブロックはエラー処理のコードで使用されます。 そのような欠陥を特定するために、静的分析のさまざまな方法、たとえば署名分析またはシンボリック実行を適用できます。 最初のケースでは、抽象構文ツリー(AST)で「条件演算子」タイプのノードを探す署名が書き込まれます。このノードには、次の形式の式があります。 a、a == 0など。また、演算子の本体には、このオブジェクトへのアピールまたはこのポインターの逆参照があります。 この後、たとえば、この変数を参照解除する前に、値を割り当てることができるように、誤検知を除外する必要があります。



 if(!a) { a = new A(); a->bar(); }
      
      





条件内の式は自明ではありません。



2番目のケースでは、操作中、アナライザーは変数の値を「監視」します。 条件if(!A)を処理した後、アナライザーは、条件演算子の本体で変数aがゼロに等しいことを理解します。 したがって、その逆参照は誤りと見なすことができます。



上記のコードスニペットは、人気のある無料のApache OpenOfficeオフィスアプリケーションスイートバージョン4.1.2から抜粋したものです。 AppChecker静的コードアナライザーを使用して、コードの欠陥が検出されました。 開発者はこの欠陥を通知され、この欠陥が修正されたパッチをリリースしました)。



Oracle MySQL Server 5.7.10で見つかった同様の欠陥を考えてみましょう:



 bool sp_check_name(LEX_STRING *ident) { if (!ident || !ident->str || !ident->str[0] || ident->str[ident->length-1] == ' ') { my_error(ER_SP_WRONG_NAME, MYF(0), ident->str); return true; } .. }
      
      





この例では、identが0の場合、条件は真になり、行が実行されます。



 my_error(ER_SP_WRONG_NAME, MYF(0), ident->str);
      
      





nullポインターを逆参照します。 どうやら、エラーをキャッチするこのコードを作成するプロセスの開発者は、このような状況が発生する可能性があることを考慮していませんでした。 正しい解決策は、ident = 0の場合に別のエラーハンドラーを作成することです。



NULLポインターの逆参照は、プログラミング言語に依存しない欠陥であると推測するのは簡単です。 前の2つの例ではC ++のコードを示しましたが、AppChecker静的アナライザーを使用すると、JavaおよびPHPのプロジェクトで同様の問題を見つけることができます。 対応する例を示します。



Javaで記述されたBIMサーバーバージョンbimserver 1.4.0-FINAL-2015-11-04を構築するための管理および集中化情報システムのコードスニペットについて考えます。



 if (requestUri.equals("") || requestUri.equals("/") || requestUri == null) { requestUri = "/index.html"; }
      
      





この例では、requestUri変数が最初にアクセスされ、その後にのみヌルポインターのチェックが行われます。 この欠陥を回避するには、これらのアクションのシーケンスを変更するだけで十分でした。



次に、phpで記述された人気のあるphabricator Webアプリケーションコレクションのコードスニペットについて考えます。



 if (!$device) { throw new Exception( pht( 'Invalid device name ("%s"). There is no device with this name.', $device->getName())); }
      
      





この場合、$ device = NULLの場合にのみ条件が満たされますが、その後$ device-> getName()を呼び出すと致命的なエラーが発生します。



このような欠陥は非常に長い間気付かれない場合がありますが、ある時点で条件が満たされ、プログラムのクラッシュにつながります。 このような欠陥は単純で見かけ上は見落とされがちですが、オープンソースと商用プロジェクトの両方で頻繁に発見されます。



更新:



AppCheckerの無料版へのリンク: https ://file.cnpo.ru/index.php/s/o1cLkNrUX4plHMV



All Articles