PVS-Studioの誤検知:ウサギの穴の深さ





Unicorn PVS-StudioおよびGetNamedSecurityInfo






私たちのチームは、迅速かつ効率的な顧客サポートを提供しています。 プログラマーも私たちに質問をするので、プログラマーだけがサポートに参加し、私たちはそれらの多くについて考えなければなりません。 誤検知に関する最近のサポートリクエストの1つを説明したいと思います。





PVS-Studioアナライザーによって生成される誤検知の数を減らすために努力しています。 残念ながら、静的アナライザーは、十分な情報がないため、正しいコードとエラーを区別できないことがよくあります。 結果として、とにかく誤検知があります。 ただし、これは問題ではありません。アナライザーを調整すると、10個の警告のうち9個が実際のエラーを示す状況を簡単に達成できるからです。



誤報は一見するとそれほど大きな問題ではありませんが、私たちは絶えずそれらと戦い、診断を改善しています。 いくつかのひどい誤検知に気づきますが、その一部は顧客や無料のユーザーによって書かれています。



最近、クライアントの1人が次の内容の手紙を書きました。



何らかの理由で、アナライザーはポインターが常にヌルであると言いますが、これは明らかにそうではありません。 さらに、アナライザーはテストプロジェクトで奇妙に不安定に動作します。警告を発行するか、しないかのいずれかです。 誤検知を再現する合成例:



#include <windows.h> #include <aclapi.h> #include <tchar.h> int main() { PACL pDACL = NULL; PSECURITY_DESCRIPTOR pSD = NULL; ::GetNamedSecurityInfo(_T("ObjectName"), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDACL, NULL, &pSD); auto test = pDACL == NULL; // V547 Expression 'pDACL == 0' is always true. return 0; }
      
      





似たような反応がユーザーからどのように見えるか想像できます。 GetNamedSecurityInfo関数がpDACL変数の値を変更することはすぐにわかります。 開発者は本当にそのような単純な状況のためのハンドラーを書くことができないのでしょうか? さらに、アナライザーがメッセージを発行するかどうかの理由は明らかではありません。 おそらく、初期化されていない変数など、ツール自体に何らかのバグがありますか?



ええと...静的コードアナライザーを保守するのは簡単な仕事ではありません。 しかし、何をすべきか、私自身はそのような運命を選んだ。 袖をまくりながら、偽陽性の原因を調査しました。



まず、 GetNamedSecurityInfo関数の説明を調べ、その呼び出しが実際にpDACL変数の値の変更につながることを確認しました。 6番目の関数引数の説明は次のとおりです。

ppdacl



返されたセキュリティ記述子のDACLへのポインターを受け取る変数へのポインター、またはセキュリティ記述子にDACLがない場合はNULL。 返されるポインターは、DACL_SECURITY_INFORMATIONフラグを設定した場合にのみ有効です。 また、DACLが不要な場合、このパラメーターはNULLにできます。



PVS-Studioアナライザーは、このような単純なコードを間違いなく正しく処理し、無意味な警告を出すべきではないことを知っています。 すでにこの時点で、私の直感は、これは異常なケースであり、時間を費やす必要があることを教えてくれました。



アナライザーの現在のアルファ版、またはクライアントにインストールされたバージョンのいずれかで誤検知を再現できなかったとき、私は恐れを確認しました。 だからそうだが、アナライザーは沈黙している。



私はクライアントに、合成例のあるプログラム用に生成された前処理済みのiファイルを送信するように依頼しました。 彼はファイルを生成して送信し、私はそれを詳細に調べ始めました。



送信されたファイルで、アナライザーはすぐに誤検知を発行しました。 一方では、バグが再現されるため、これは良いことです。 一方、私はこの写真が最もよく表す感情を経験しました。







本社








なぜまさにこれらなのでしょうか? V547のアナライザーと診断がどのように機能するかをよく知っています。 まあ、そのような作動はあり得ません!



はい、お茶を入れて続けます。



GetNamedSecurityInfo関数の呼び出しは、 次のように展開されます。



 ::GetNamedSecurityInfoW(L"ObjectName", SE_FILE_OBJECT, (0x00000004L), 0, 0, &pDACL, 0, &pSD);
      
      





このコードは、前処理済みのiファイルとクライアントから送信されたファイルの両方で同じように見えます。



うーん...さて、この関数がどのように宣言されているかを調べてみましょう。 私のファイルで私は見ます:



 __declspec(dllimport) DWORD __stdcall GetNamedSecurityInfoW( LPCWSTR pObjectName, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo, PSID * ppsidOwner, PSID * ppsidGroup, PACL * ppDacl, PACL * ppSacl, PSECURITY_DESCRIPTOR * ppSecurityDescriptor );
      
      





すべてが論理的であり、すべてが明確です。 予期しないことはありません。



次に、クライアントファイルを見て...







本社???








そこで私は平行した現実から何かを見ます:



 __declspec(dllimport) DWORD __stdcall GetNamedSecurityInfoW( LPCWSTR pObjectName, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo, const PSID * ppsidOwner, const PSID * ppsidGroup, const PACL * ppDacl, const PACL * ppSacl, PSECURITY_DESCRIPTOR * ppSecurityDescriptor );
      
      





ppDaclの正式な引数 constとしてマークされていることに注意してください。



ワット? WTF? ワット? WTF?



なんとconst !? 彼はどこから来たの!?



少なくとも、アナライザーがここで非難されるべきではないことはすぐに明らかになり、その名誉を守ることができます。



引数は定数オブジェクトへのポインターです。 アナライザーの観点から、 GetNamedSecurityInfoW関数はポインターが参照するオブジェクトを変更できないことがわかりました。 したがって、ここに:



 PACL pDACL = NULL; PSECURITY_DESCRIPTOR pSD = NULL; ::GetNamedSecurityInfo(_T("ObjectName"), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDACL, NULL, &pSD); auto test = pDACL == NULL; // V547 Expression 'pDACL == 0' is always true.
      
      





pDACL変数変更できず、アナライザーは妥当な警告を生成します(式 'pDACL == 0'は常にtrueです)。



警告が発行される理由は理解できます。 しかし、このconstがどこから来たのかは明確ではありません。 そこにはありえない!



ただし、推測があり、インターネットでの検索で確認されます。 不正な機能説明を含む古い無効なaclapi.hファイルがあることがわかりました。 また、インターネット上で2つの興味深いリンクを見つけました。





そのため、かつてaclapi.hファイル(6.0.6002.18005-Windows 6.0)に関数の説明がありました。



 WINADVAPI DWORD WINAPI GetNamedSecurityInfoW( __in LPWSTR pObjectName, __in SE_OBJECT_TYPE ObjectType, __in SECURITY_INFORMATION SecurityInfo, __out_opt PSID * ppsidOwner, __out_opt PSID * ppsidGroup, __out_opt PACL * ppDacl, __out_opt PACL * ppSacl, __out_opt PSECURITY_DESCRIPTOR * ppSecurityDescriptor );
      
      





それから誰かが仮引数pObjectNameの型を修正したかったが、途中でconstを書くことでポインタの型を台無しにした。 そして、aclapi.h(6.1.7601.23418-Windows 7.0)は次のようになりました。



 WINADVAPI DWORD WINAPI GetNamedSecurityInfoW( __in LPCWSTR pObjectName, __in SE_OBJECT_TYPE ObjectType, __in SECURITY_INFORMATION SecurityInfo, __out_opt const PSID * ppsidOwner, __out_opt const PSID * ppsidGroup, __out_opt const PACL * ppDacl, __out_opt const PACL * ppSacl, __out PSECURITY_DESCRIPTOR * ppSecurityDescriptor );
      
      









diff 1








クライアントによって使用されるのは、正確に間違ったaclapi.hファイルであることが明らかになります。 彼は後にこの仮説を通信で確認した。 最新バージョンを使用しているため、エラーは再現しませんでした。



aclapi.h(6.3.9600.17415-Windows_8.1)の既に修正された関数の説明は次のとおりです。



 WINADVAPI DWORD WINAPI GetNamedSecurityInfoW( _In_ LPCWSTR pObjectName, _In_ SE_OBJECT_TYPE ObjectType, _In_ SECURITY_INFORMATION SecurityInfo, _Out_opt_ PSID * ppsidOwner, _Out_opt_ PSID * ppsidGroup, _Out_opt_ PACL * ppDacl, _Out_opt_ PACL * ppSacl, _Out_ PSECURITY_DESCRIPTOR * ppSecurityDescriptor );
      
      









diff 2








引数の型pObjectNameは同じままですが、余分なconstは削除されました。 すべてが元の場所に戻りましたが、関数宣言が正しくないヘッダーファイルは引き続き存在します。



これらすべてをクライアントに伝えます。 彼は、状況が解消されたことを喜んでおり、喜んでいます。 さらに、彼は偽陽性を見るかどうかの理由を見つけます。



このテストプロジェクトでツールセットを実験したことを忘れていました。 デバッグテストプロジェクトでは、構成はデフォルトでVisual Studio 2017-「Visual Studio 2017(v141)」のプラットフォームツールセットで構成されますが、リリース構成は「Visual Studio 2015-Windows XP(v140_xp)」で構成されます。 昨日、私はある時点で構成を変更し、警告が表示されたり消えたりしました。



それだけです 調査を終了することができます。 クライアントで、ヘッダーファイルでこのバグを考慮に入れるために、アナライザーで特別なバックアップを行わないことを決定します。 主なことは、状況が明確になったことです。 sayingにもあるように、「ケースはクローズされています。」



おわりに



PVS-Studioアナライザーは、プログラムコードから多くの情報を収集し、さまざまな分析技術に使用する複雑なソフトウェア製品です 。 具体的には、この場合、アナライザーの過剰なインテリジェンスにより、関数の誤った説明のために、誤検知が発生し始めたという事実につながりました。



私たちの顧客になると、あなたは私と私の同僚から迅速で質の高いサポートを受けられます。











この記事を英語圏の聴衆と共有したい場合は、翻訳へのリンクを使用してください:Andrey Karpov。 PVS-Studioの誤検知:ウサギの穴の深さ



All Articles