net-snmpのsnmp_が見つかりましたset_var_typed_value()関数の「欠陥」

4929 ワード

以前同僚とテストしたときにたまたまnet-snmp SDKのsnmp_を発見しましたset_var_typed_value()関数の「BUG」です.当時の過程は非常に面白く(誤打誤打)、一定の典型性(プラットフォーム間、SDK間)を持っていたので、ここで皆さんと共有します.
私の同僚は仕事の合間に独立して私たちの設備を管理できるSNMPネットワーク管理ソフトウェア(当社はすでに別のチームを設置してこの種類の製品を作っています)を開発して、私に彼に通信サーバーの部分をテストさせて、この部分はVC 6に基づいています.0開発.しばらくテストしましたが、小さな欠陥はありますが、機能的な大きなバグは現れませんでした.すべてが正常だ.
私たちがそれを正しくセクシーにして喜んでいると、突然、あるデバイスにアクセスするとタイムアウト異常が発生することに気づきました.最初はサーバコードに問題があると思っていたが,単一ステップで長い間デバッグしたが,このエラーは浮かび上がらず,かえって別のエラーが発生した.干渉を排除した後、私たちはやはり設備自体に問題があったのではないかと疑っています(この設備は審査鑑定に合格し、量産を開始した設備であり、これまで何度も似たようなテストをしてもそのような問題は発見されなかったので、最初は強い自信を持っていて、それを疑っていませんでした.このことを通じて、問題を分析する際にあまり強い主観的な判断を抱いてはいけないことを教えて、すべての可能性を見逃さないようにしました).そこでさっそくサードパーティのテストソフトMIB browserでテストしてみましたが、やはりデバイスに問題がありました.しかし、問題はどこにあるのだろうか.
エラーは再現しにくく、デバイスは現在また「ダウンタイム」状態にあり、どうしようもなく、何の良い対策も見つからず、エラーを位置決めするしかなく、おとなしくデバイスを再起動し、一度にデバイスのパラメータを読み取り(少し多く、百個以上)、最後にやっとアクセス異常を引き起こすパラメータを見つけ、同時にデバイスの状態を見てみると、デバイスのメモリ占有量が急激に上昇し、間もなくメモリが消耗していることが分かった.あれ!メモリの漏洩ですか?すぐにデバイスコードを見て、何分も目測して、みんなは目を合わせて、「こんなに短い何行のコードはどうしてメモリの漏洩が現れますか?「当時、私たちは先入観を主とする誤りを犯しました.snmpdエージェントはnet-snmpの開発パッケージで開発されたので、net-snmpは業界で最も有名なSDKで、何年も発売され、バージョンは何度も更新されているので、コードは非常に信頼できると思っていました.あ、net-snmpのコードを疑っていないので、自分のコードを疑うしかありません.関数インタフェースから返されたデータを全部印刷して、やはり間違いがあります!---インタフェースが返す配列データの長さは-1です.はい、自分のインタフェースに間違いがあります.これも当然です.
しかし、このデータの長さは-1でどのようにして来たのでしょうか.呼び出されたこのインタフェースは共有内アクセスデータであり、データはNVRAMから共有メモリに初期化され、出荷時の構成として存在し、この構成データは複数回テストされ、Flashも不良ブロックはないが、データは確かに正しくない.この件は1、2日経って、原因を考えました:この設備の上のflashは後でハードウェアの技師が交換した開発の初期に使った1枚のflashで、しかしみんなはすべて忘れても特殊なマークがなくて区別することができなくて、後で私達の配置のデータは増加して、異常なパラメータが現れて新しいデータに属して、それからプログラムをアップグレードする時flashを消去していないで古いデータがまだ存在して、Flashで初期化されていないデータ領域はすべて-1です.
最後にnet-snmpのソースコードを追跡し、ソースコードのsnmp_を発見しました.set_var_typed_value()関数には確かに設計上の欠陥がありますsnmp_set_var_typed_value()は別の関数snmp_を呼び出したset_var_value()snmp_set_var_value()では、net-snmpはlenが0に等しいかどうかの検査のみを行い、0以下の検査は行わない.
if (len <= (sizeof(vars->buf) - 1)) {
        vars->val.string = (u_char *) vars->buf;
        largeval = 0;
    }

利用者にも責任がありますが、このような設計はインタフェースの中では確かに現れるべきではありません(net-snmpは非常に優秀で、私はそれを突っ込むのではなく、本人のレベルが低く、ただ事について話しています).
筆者はこのことに興味を持って、さらに追跡して、net-snmpソースコードのどの部分がメモリの消費を招いたのかを見てみましょう.ソースコードを確認すると、以下の点で問題が発生していることがわかります.
 /** FALL THROUGH */
    case ASN_PRIV_IMPLIED_OCTET_STR:
    case ASN_OCTET_STR:
    case ASN_BIT_STR:
    case ASN_OPAQUE:
    case ASN_NSAP:
        if (largeval) {
            vars->val.string = (u_char *) malloc(vars->val_len + 1);
        }
        if (vars->val.string == NULL) {
            snmp_log(LOG_ERR,"no storage for string
"); return 1; } memmove(vars->val.string, value, vars->val_len); /* * Make sure the string is zero-terminated; some bits of code make * this assumption. Easier to do this here than fix all these wrong * assumptions. */ vars->val.string[vars->val_len] = '\0'; break;

トレース関数の実行プロセスは、caseではmallocとmemmoveが実行されていますが、ええと、二人のメモリの問題になる可能性がありますか?ちょっと似ているような気がしますが、彼らは標準的なC関数なので、このような間違いを犯すことはできないでしょう.malloc(0)は問題ないはずですが、memmove()はlen<0で間違いがあるので、テストするしかありません.テストでは、ホスト上でmemmove()len<0の場合にエラーが発生しますが、メモリの上昇は発生しません.組み込みシステムでは正しく実行できますが、メモリの上昇は発生しません.snmp_set_var_valueのmemmoveでハードコーディングが表示されます.それはnet-snmpの他の場所の関数がもたらしたのですか?ソースコードを見ても、この可能性は発見されていません.もし友达が原因を知っていたら、交流を歓迎します!
△net-snmp.5.4.2.1バージョンを使用しています.新しくリリースされたバージョン5.7.2を確認しました.ソースコードの一部が変更されていますが、このBUGはまだあります.
/*********************************************************************************************************************/
たまたまこの現象を発見したが、原因を理解することができなかった.その後、筆者は私の下手なchenglishを使って、net-snmpの公式サイトにこの「BUG」を提出し、答えを期待していたが、長い間返事がなかった.今日突然この事を思い出して、更に公式サイトに行って見て、私の質問がnet-snmp首席開発者Niels Baggesen先森の答えを得たことを発見しました.彼は仕方なく言った.
Unfortunately we cannot check the length for being negative, as the type of the length argument is size_t which is unsigned.
It is a bit difficult to find a meaningful upper limit to check against.

   “unfortunately,a bit difficult”.ははは、「老先生」は私のような基礎がしっかりしていない「プログラム猿」に少し失望したのではないでしょうか.
これでやっとこのエラーの原因が分かりました.パラメータのデータ型を無視しました.snmp_set_var_value()の関数プロトタイプは次のとおりです.
int
snmp_set_var_value(netsnmp_variable_list * vars,
                   const u_char * value, size_t len)

32ビットシステムでsize_tはほとんどunsigned int型であり、符号なしであり、符号なし数は正負を比較することはできず、snmpにとって上限判定に汎用的な大数を見つけることも困難であり、プログラムの移植性を保証するためにsize_を使用せざるを得ないtタイプ(残念ですね…).関数呼び出し時にsize_tパラメータはint型パラメータに渡され、自動的にパラメータがパラメータタイプに変換されます.また、プログラムでlenをvars->val_に割り当てるとlen時にコンパイラも暗黙的なタイプ変換を行いました.
vars->val_len = len;

この付与文では、コンパイラが等号の右側の値を等号の左変数のタイプに変換してから付与します.いずれにしても、最終vars->val_lenの値は−1のバイナリコードに対応する符号なし整数(0 xffffff)となる.その後悲劇が起こりました(ハードコーディングテストの結果から、vars->val_lenの値が大きすぎるため、この関数のmallocやmemmoveではなく、プログラムの他の場所でメモリの問題が発生し、具体的な問題がどこにあるのかは私も深く考えていません).