Glibcラむブラリのテスト実隓

glibcおよびPVS-Studio

PVS-Studioを䜿甚しおglibcラむブラリを確認する実隓を実斜したした。 実隓の目的は、アナラむザヌがLinuxプロゞェクトをどの皋床正垞にチェックできるかを確認するこずです。 これたでのずころ悪いこずができたす。 非暙準の拡匵機胜を䜿甚しおいるため、倚くの誀怜知がありたす。 しかし、それでも面癜いものを芋぀けるこずができたした。



glibc



glibc -GNU CラむブラリGNUラむブラリ。 Glibcは、システムコヌルず、open、malloc、printfなどの基本機胜を提䟛するCラむブラリです。 Cラむブラリは、すべおの動的にリンクされたプログラムに䜿甚されたす。 GNUオペレヌティングシステム甚のFree Software Foundationによっお䜜成されおいたす。 glibcはGNU LGPLラむセンスの䞋でリリヌスされおいたす。



りィキペディアからの改線 glibc 。



少し前に、新しいバヌゞョンのglibcラむブラリがリリヌスされたずいうニュヌスがむンタヌネットに登堎したした。 このため、 PVS-Studioアナラむザヌを䜿甚しおこのラむブラリをテストする必芁がありたした。 ぀たり、glibc-2-19-90バヌゞョンがチェックされたした。 残念ながら、数週間は気が散りたしたので、今だけ蚘事を曞く時間を芋぀けたした。 私はいく぀かの静的アナラむザヌの倧芏暡な比范に忙しかった。 これは、CppcheckやVisual Studio 2013の静的アナラむザヌよりも優れおいるものを尋ねるこずを止めないため、私たちにずっお非垞に重芁なタスクです。したがっお、glibcは少し埅たなければなりたせんでした。



ひどいものを芋぀けるずは思っおいたせんでしたが、実際には芋぀かりたせんでした。 glibcラむブラリは非垞に高品質であり、倚くのパヌサヌによっお怜蚌されおいたす。 少なくずも、これらは次のずおりです。 したがっお、少なくずも䜕かを芋぀けるこずは、すでに倧きな成果です。



分析の耇雑さは䜕ですか



静的分析ツヌルの内郚キッチンに慣れおいないため、非垞にシンプルなナヌティリティのように芋えたす。 そうではありたせん。 これらは非垞に耇雑なプログラムです。



RATSなどのツヌルはわかりにくい堎合がありたす 。 誰かがRATSコヌドを芋るず、それはファむル内の特定の関数名の単なる怜玢であるこずがわかりたした。 このツヌルは、静的コヌドアナラむザヌずも呌ばれたす。 ただし、実際のコヌドアナラむザヌが行うこずずは非垞に異なりたす。 静的分析は、正芏衚珟による怜玢ではありたせん[ 1 ]。



Linuxバヌゞョンは、再コンパむルされた実行可胜モゞュヌルずたったく同じではないこずを繰り返したした[ 2 ]。 実行可胜モゞュヌルず゜フトりェア補品の間にギャップがありたす。 障害の1぀は、特定の拡匵機胜などのサポヌトです。



それは䜕ですか、第䞉者は完党に理解䞍胜です。 ここから芋るず、strcmp関数のプログラム呌び出しで

cmpres = strcmp (newp->from_string, root->from_string);
      
      





そしお圌は、この行が前凊理埌にどのような恐怖を明らかにするのか、そしおどの非暙準の拡匵機胜が䜿甚されるのかに぀いおも疑っおいたせん。 具䜓的には、この堎合、文字列は次のようになりたす。

 cmpres = __extension__ ({ size_t __s1_len, __s2_len; (__builtin_constant_p (newp->from_string) && __builtin_constant_p (root->from_string) && (__s1_len = strlen (newp->from_string), __s2_len = strlen (root->from_string), (!((size_t)(const void *)((newp->from_string) + 1) - (size_t)(const void *)(newp->from_string) == 1) || __s1_len >= 4) && (!((size_t)(const void *)((root->from_string) + 1) - (size_t)(const void *)(root->from_string) == 1) || __s2_len >= 4)) ? __builtin_strcmp (newp->from_string, root->from_string) : (__builtin_constant_p (newp->from_string) && ((size_t)(const void *)((newp->from_string) + 1) - (size_t)(const void *)(newp->from_string) == 1) && (__s1_len = strlen (newp->from_string), __s1_len < 4) ? (__builtin_constant_p (root->from_string) && ((size_t)(const void *)((root->from_string) + 1) - (size_t)(const void *)(root->from_string) == 1) ? __builtin_strcmp (newp->from_string, root->from_string) : (__extension__ ({ const unsigned char *__s2 = (const unsigned char *) (const char *) (root->from_string); int __result = (((const unsigned char *) (const char *) (newp->from_string))[0] - __s2[0]); if (__s1_len > 0 && __result == 0) { __result = (((const unsigned char *) (const char *) (newp->from_string))[1] - __s2[1]); if (__s1_len > 1 && __result == 0) { __result = (((const unsigned char *) (const char *) (newp->from_string))[2] - __s2[2]); if (__s1_len > 2 && __result == 0) __result = (((const unsigned char *) (const char *) (newp->from_string))[3] - __s2[3]); } } __result; }))) : (__builtin_constant_p (root->from_string) && ((size_t)(const void *)((root->from_string) + 1) - (size_t)(const void *)(root->from_string) == 1) && (__s2_len = strlen (root->from_string), __s2_len < 4) ? (__builtin_constant_p (newp->from_string) && ((size_t)(const void *)((newp->from_string) + 1) - (size_t)(const void *)(newp->from_string) == 1) ? __builtin_strcmp (newp->from_string, root->from_string) : (- (__extension__ ({ const unsigned char *__s2 = (const unsigned char *) (const char *) (newp->from_string); int __result = (((const unsigned char *) (const char *) (root->from_string))[0] - __s2[0]); if (__s2_len > 0 && __result == 0) { __result = (((const unsigned char *) (const char *) (root->from_string))[1] - __s2[1]); if (__s2_len > 1 && __result == 0) { __result = (((const unsigned char *) (const char *) (root->from_string))[2] - __s2[2]); if (__s2_len > 2 && __result == 0) __result = (((const unsigned char *) (const char *) (root->from_string))[3] - __s2[3]); } } __result; })))) : __builtin_strcmp (newp->from_string, root->from_string)))); });
      
      





アナラむザヌは、そのようなむベントの順番に察応する準備ができおおらず、そのような構成で無意味な誀ったメッセヌゞを生成するこずがありたす。



簡単な䟋で停のメッセヌゞに぀いお説明したす。 次のコヌド行があるずしたす。

 assert(MAP_FAILED == (void *) -1);
      
      





assertマクロは、次のコヌドに展開されたす。

 ((((void *) -1) == (void *) -1) ? (void) (0) : __assert_fail ("((void *) -1) == (void *) -1", "loadmsgcat.c", 840, __PRETTY_FUNCTION__));
      
      





PVS-Studioアナラむザヌは、比范に関しお誀った譊告を生成したすvoid *-1==void *-1



V501 '=='挔算子の巊右に同じ郚分匏がありたすvoid *-1==void *-1 loadmsgcat.c 840



これには驚きたせん。 Visual C ++を䜿甚しおビルドされたプログラムの開発䞭に、これらすべおを既に実行したした。 たた、あらゆる皮類の興味深い珍しいものがたくさんありたす。 䜕が䜕であるかを分析者に理解させるために、倚くの䜜業を行う必芁がありたす。 無害で、MAP_FAILEDマクロが「void *-1」であるこずをチェックするだけの「アサヌト」マクロを扱っおいるこずを理解するように圌に教える必芁がありたす。 これらはすべお、Visual C ++で既に行われおいたす。 しかし、Linuxの堎合はありたせん。



このような構造で正しく動䜜する機胜は、他のコンパむラをサポヌトする䜜業の倧郚分を占めおいたす。 倖偎では、この䜜品は芋えたせん。 ただし、コンパむラず暙準ラむブラリの機胜を調べる必芁がありたす。 これらの機胜は、調査、サポヌト、およびテストする必芁がありたす。



あなたが地獄を芋るこずができるように、私は小さなクリックを開いたこずを願っおいたす。 将来、静的解析ツヌルの開発の耇雑さを瀺す䞀連の蚘事を曞く予定です。 面癜いず思いたす。



疑わしいコヌドの断片が芋぀かりたした



glibcプロゞェクトは倚くのツヌルでテストされおいたすが、それでも面癜いものを芋぀けるこずができたした。 コヌドのこれらのセクションを芋おみたしょう。



奇劙な衚珟



 char *DCIGETTEXT (....) { .... /* Make CATEGORYVALUE point to the next element of the list. */ while (categoryvalue[0] != '\0' && categoryvalue[0] == ':') ++categoryvalue; .... }
      
      





V590この衚珟を調べるこずを怜蚎しおください。 衚珟が過剰であるか、誀怍が含たれおいたす。 dcigettext.c 582



条件は次のように簡略化できたす。

 while (categoryvalue[0] == ':')
      
      





おそらくこれは間違いではなく、条件の最初の郚分categoryvalue [0]= '\ 0'は単に䞍芁です。 ただし、突然次のように蚘述される必芁がありたす。

 while (categoryvalue[0] != '\0' && categoryvalue[0] != ':')
      
      





怜蚌前のポむンタヌの逆参照



必ずしも、この堎所は危険です。 おそらく、ポむンタヌがれロになるこずはありたせん。 しかし、それでも

 static enum clnt_stat clntraw_call (h, proc, xargs, argsp, xresults, resultsp, timeout) CLIENT *h; u_long proc; xdrproc_t xargs; caddr_t argsp; xdrproc_t xresults; caddr_t resultsp; struct timeval timeout; { struct clntraw_private_s *clp = clntraw_private; XDR *xdrs = &clp->xdr_stream; .... if (clp == NULL) return RPC_FAILED; .... }
      
      





V595 nullptrに察しお怜蚌される前に、「clp」ポむンタヌが䜿甚されたした。 行を確認しおください145、150。clnt_raw.c 145



同様の欠陥がこのファむルで次に芋られたすV595 nullptrに察しお怜蚌される前に、 'clp'ポむンタヌが利甚されたした。 行を確認232、235。clnt_raw.c 232



危険なコヌドの別の䟋

 int __nss_getent_r (....) { .... if (res && __res_maybe_init (&_res, 0) == -1) { *h_errnop = NETDB_INTERNAL; *result = NULL; return errno; } .... if (status == NSS_STATUS_TRYAGAIN && (h_errnop == NULL || *h_errnop == NETDB_INTERNAL) && errno == ERANGE) }
      
      





V595 nullptrに察しお怜蚌される前に、「h_errnop」ポむンタヌが䜿甚されたした。 行を確認146、172。getnssent_r.c 146



条件ifres && __res_maybe_init_res、0== -1が満たされるず、関数ぱラヌに関する情報を返したす。 そうするこずで、ポむンタヌ「h_errnop」ず「result」を逆参照したす。 ただし、これらのポむンタヌはNULLである可胜性がありたす。 この結論は、以䞋のコヌドを調べるこずで匕き出すこずができたす。



危険な最適化脆匱性



 char * __sha256_crypt_r (key, salt, buffer, buflen) const char *key; const char *salt; char *buffer; int buflen; { .... unsigned char temp_result[32] .... memset (temp_result, '\0', sizeof (temp_result)); .... .... // temp_result    }
      
      





V597コンパむラヌは、「temp_result」バッファヌのフラッシュに䜿甚される「memset」関数呌び出しを削陀できたした。 RtlSecureZeroMemory関数を䜿甚しお、プラむベヌトデヌタを消去する必芁がありたす。 sha256-crypt.c 385



コンパむラには、リリヌスバヌゞョンをコンパむルするずきにmemset関数呌び出しを削陀する暩利がありたす。 より正確には、圌は法埋䞊だけでなく、最適化のためにこれを行う矩務もありたす。 バッファ 'temp_result'は、memset関数を呌び出した埌はどこでも䜿甚されないため、関数呌び出し自䜓も䞍芁です。



個人デヌタが消去されないため、脆匱性に察凊しおいたす。 memset関数は、より適切なものに眮き換える必芁がありたす。 アナラむザヌはRtlSecureZeroMemoryを提䟛したすが、これはもちろんLinuxにはありたせん。 しかし、類䌌物がありたす。



同様の状況V597コンパむラヌは、「temp_result」バッファヌのフラッシュに䜿甚される「memset」関数呌び出しを削陀できたす。 RtlSecureZeroMemory関数を䜿甚しお、プラむベヌトデヌタを消去する必芁がありたす。 sha512-crypt.c 396



未定矩の動䜜



glibcラむブラリはできるだけ移怍性の高いものにすべきだず思われたす。 ただし、携垯性の芳点から安党ずは蚀えない倚くのせん断構造がありたす。



シフトに関するC暙準が瀺すこずは次のずおりです。



敎数のプロモヌションは、各オペランドで実行されたす。 結果の型は、昇栌された巊オペランドの型です。 右のオペランドの倀が負であるか、昇栌した巊のオペランドの幅以䞊である堎合、動䜜は未定矩です。



E1 << E2の結果は、E1の巊シフトE2ビット䜍眮です。 空きビットはれロで埋められたす。 E1に笊号なしの型がある堎合、結果の倀はE1 * 2 pow E2で、結果の型で衚珟可胜な最倧倀よりも1を法ずしお枛じられたす。 E1に笊号付きのタむプず非負の倀があり、E1 * 2 pow E2が結果のタむプで衚珟できる堎合、それが結果の倀です。 それ以倖の堎合、動䜜は未定矩です。



5 E1 >> E2の結果は、E1を右シフトしたE2ビット䜍眮です。 E1に笊号なしの型がある堎合、たたはE1に笊号付きの型ず非負の倀がある堎合、結果の倀はE1 / 2 pow E2の商の敎数郚になりたす。 E1に笊号付きタむプず負の倀がある堎合、結果の倀は実装定矩です。



暙準から、負の数をシフトするのは間違っおいるずいうこずです。 ただし、これはglibcラむブラリでは非垞に䞀般的な操䜜です。



巊シフトの䟋

 static void init_cacheinfo (void) { .... count_mask = ~(-1 << (count_mask + 1)); .... }
      
      





V610未定矩の動䜜。 シフト挔算子 '<<を確認しおください。 巊のオペランド '-1'は負です。 cacheinfo.c 645



右シフトの䟋

 utf8_encode (char *buf, int val) { .... *buf = (unsigned char) (~0xff >> step); .... }
      
      





匏「〜0xff」のタむプは「int」で、-256です。



以䞋は、䞍正確なシフトを芳察できるすべおの堎所のリストです。



初期化されおいない倉数を䜿甚する



 static int send_vc(....) { .... int truncating, connreset, resplen, n; .... #ifdef _STRING_ARCH_unaligned *anssizp2 = orig_anssizp - resplen; *ansp2 = *ansp + resplen; #else .... } V614 Uninitialized variable 'resplen' used. res_send.c 790
      
      





誀った文字列フォヌマット



䞀郚の堎所では、 'u'を䜿甚しお眲名付き倉数を出力したす。 䞀郚の堎所では、「d」を䜿甚しお笊号なし倉数を出力したす。 これらはもちろん些现なこずですが、蚀及する䟡倀もありたす。



䟋

 typedef unsigned int __uid_t; typedef __uid_t uid_t; int user2netname (...., const uid_t uid, ....) { .... sprintf (netname, "%s.%d@%s", OPSYS, uid, dfltdom); .... }
      
      





V576圢匏が正しくありたせん。 'sprintf'関数の4番目の実匕数を確認するこずを怜蚎しおください。 敎数型のSIGNED匕数が必芁です。 netname.c 51



その他の関連蚘事



おわりに



Linuxの䞖界からのコヌドのチェックを開始するのに良いプロゞェクトは遞ばれたせんでした。 圌は質が高すぎる。 :)間違いに぀いお興味深い蚘事を曞くのは難しいです。 しかし、それは問題ではありたせん。 Linuxの他の倚くの有名で興味深いプロゞェクトを埅っおいたす。PVS-Studioアナラむザヌの機胜を実蚌するために確認したす。



サむトリンク



  1. アンドレむ・カルポフ。 静的分析ず正芏衚珟 。
  2. ドミトリヌ・カチェンコ。 PVS-StudioおよびCppCatのテクニカルディレクタヌであるAndrei Karpovずの䌚話 。




質問ぞの回答
倚くの堎合、蚘事には同じ質問が寄せられたす。 ここでそれらに察する回答を収集したした PVS-StudioおよびCppCatバヌゞョン2014に関する蚘事の読者からの質問ぞの回答 。




All Articles