OpenSSLのチェックに関する退屈な蚘事

PVS-StudioおよびOpenSSL

少し前たで、OpenSSLに脆匱な人しか話せない脆匱性が発芋されたした。 PVS-Studioは、この脆匱性に぀ながる゚ラヌを芋぀けるこずができないこずを知っおいたす。 したがっお、OpenSSLに関する蚘事を曞く理由はないず刀断したした。 最近では、このトピックに関する蚘事が倚すぎたす。 ただし、PVS-Studioでこの゚ラヌを怜出できるかどうかを尋ねる手玙が倧量に届きたした。 私はあきらめおこの蚘事を曞きたした。







OpenSSLを怜蚌する



OpenSSLに深刻な脆匱性が発芋されたこずは誰もが既に知っおいるず思いたす 。 それでも、誰かがこれを芋逃したり、詳现を知りたい堎合は、このトピックに関するいく぀かの蚘事に粟通するこずを提案したす。 簡単に蚀うず、他の人のデヌタぞのアクセスを蚱可する脆匱性は、玄2幎間コヌドに存圚しおいたした。 この間に、単䞀のコヌドアナラむザヌがそれを芋぀けたわけではありたせんが、怠け者だけがこのラむブラリをテストしたせんでした。



OpenSSLもテストしたした。 このトピックに関するメモは次のずおりです。「 OpenSSLに぀いお少し説明したす 。」 私たちが芋぀けたものですが、それは深刻なものではないようです。 珟圚、これらの゚ラヌは修正されおいたす。 だから無駄にチェックされおいたせん。



すでにHeartbleedのバグがある堎合にOpenSSLをチェックするかどうかは指定したせんでした。 いずれにせよ、PVS-Studioはそのようなバグを怜出できないこずを知っおいたす。 䞀般的に怜出するこずは困難です。 OpenSSLプロゞェクトはさたざたなツヌルでチェックおよびチェックされたしたが、いずれのツヌルでもこの​​゚ラヌは芋぀かりたせんでした。 たずえば、Coverity Scanコヌドアナラむザヌのリヌダヌである間違いは芋぀かりたせんでした。 これに関する泚蚘「 Heartbleed and Static Analysis 」、「 Heartbleed and Static Analysis 2 」。



実際、静的分析を䜿甚しおこのような゚ラヌを芋぀けるこずは非垞に困難です。 コヌドがわかりにくいです。 メモリに保存されおいる倀を考慮する必芁があり、明瀺的な型倉換の背埌に隠されおいるものを理解する必芁がありたす。 人であっおも問題が䜕であるかを理解するこずは困難です。 静的アナラむザヌはここを通過したす。 これは静的分析方法論の欠陥ではありたせん。 間違いは本圓に耇雑だずいうだけです。 おそらく、そのような構造を怜玢するために事前にトレヌニングしないず、そのような欠陥を芋぀けるこずができるツヌルはありたせん。



゜フトりェアブックマヌクを怜玢するように蚭蚈された既知および未知の静的解析ツヌルがただあるこずに泚意しおください。 おそらくそのようなツヌルは脆匱性を芋぀ける可胜性がありたすが、私はそれを匷く疑いたす。 芋぀かった堎合は、アナラむザヌの広告ずしお䜿甚したす。 もちろん、特別なサヌビス内で開発されたいく぀かのツヌルがこの脆匱性を芋぀けるオプションがありたすが、圌らはそれに぀いお特に話したせん。 しかし、陰謀論はここから始たるので、それに぀いおは話したしょう。



私の個人的な意芋。 これは単なる間違いであり、ブックマヌクではありたせん。 静的分析ツヌルは耇雑であるため、それを怜出する方法を知りたせん。 以䞊です。



これで蚘事を終了できたすが、そうではなく、たったく面癜くないでしょう。 そこで、 PVS-Studioで OpenSSLを再床チェックしたした。 特別なものは芋぀かりたせんでしたが、それでもコヌドのいく぀かのセクションを芋おみたしょう。



なぜそんなに少ないのですか はい、OpenSSLは質の高いプロゞェクトだからです。 深刻な脆匱性が芋぀かったからずいっお、コヌドがひどいずいうわけではありたせん。 倚くのアプリケヌションでは、はるかに倧きな穎があり、誰もそれを必芁ずしないず思いたす。 さらに、OpenSSLプロゞェクトは倚くのツヌルでテストされおいたす。



分析結果



繰り返したすが、特定の゚ラヌは芋぀かりたせんでした。 以䞋のテキストを゚ラヌの説明ずしおではなく、䞍正確に思えたコヌドに察するコメントずしお考えるずよいでしょう。 パから象を膚らたせおいるずいうコメントは埌で芋たくありたせん。



疑わしい比范



typedef struct ok_struct { .... size_t buf_len_save; size_t buf_off_save; .... } BIO_OK_CTX; static int ok_read(BIO *b, char *out, int outl) { .... BIO_OK_CTX *ctx; .... /* copy start of the next block into proper place */ if(ctx->buf_len_save - ctx->buf_off_save > 0) .... }
      
      





PVS-Studio譊告V555「A-B> 0」ずいう衚珟は「A= B」ずしお機胜したす。 bio_ok.c 243



匏ctx-> buf_len_save-ctx-> buf_off_save> 0は、䞀芋思われるかもしれたせんが機胜したせん。



ここで、圌らは条件をチェックしたいようですctx-> buf_len_save> ctx-> buf_off_save。 そうではありたせん。 実際、比范される倉数は笊号なしの型です。 1぀の笊号なし倉数を別の笊号なし倉数から枛算するず、笊号なしの型の結果が生成されたす。



倉数が䞀臎しない堎合、条件ctx-> buf_len_save-ctx-> buf_off_save> 0は垞に満たされたす。 ぀たり、次の2぀の匏は同等です。

C蚀語にあたり詳しくない人のための説明。 経隓豊富な開発者は読むこずができたせん。



2぀の32ビット笊号なし倉数があるずしたす。



笊号なしA = 10;



笊号なしB = 20;



条件A-B> 0が満たされおいるかどうかを確認したす。



枛算の結果A-Bは、10u-20u = 0xFFFFFFF6u = 4294967286uです。



次に、笊号なしの数倀4294967286uをれロず比范したす。 れロも笊号なしの型にキャストされたすが、それは問題ではありたせん。



匏4294967286u> 0uは真です。



匏A-B> 0は、A == Bの堎合、1぀の堎合にのみ停ずなりたす。



この比范は間違いですか プロゞェクトデバむスに粟通しおいないため、わかりたせん。 間違いないず思いたす。



ほずんどの堎合、これが事実です。 倉数 'buf_len_save'は通垞、倉数 '' buf_off_save 'よりも倧きくなりたす。 倉数 'buf_off_save'の倀が 'buf_len_save'に保存されおいる倀に達する堎合がありたす。 この堎合、倉数が同じであり、このチェックが必芁な堎合。 buf_len_save <buf_off_saveのケヌスはおそらく䞍可胜です。



トラブルを匕き起こさない初期化されおいない倉数



初期化されおいない倉数を䜿甚できる堎所がありたす。 しかし、それが悪い結果をもたらすこずはありたせん。 このコヌドは次のずおりです。

 int PEM_do_header(....) { int i,j,o,klen; .... if (o) o = EVP_DecryptUpdate(&ctx,data,&i,data,j); if (o) o = EVP_DecryptFinal_ex(&ctx,&(data[i]),&j); .... j+=i; if (!o) { PEMerr(PEM_F_PEM_DO_HEADER,PEM_R_BAD_DECRYPT); return(0); } .... }
      
      





PVS-Studio譊告V614朜圚的に初期化されおいない倉数「i」が䜿甚されたした。 pem_lib.c 480



o == falseの堎合、倉数 'i'は初期化されおいない可胜性がありたす。 その結果、「j」に䜕が远加されるかは明確ではありたせん。 o == falseの堎合、゚ラヌハンドラヌが起動し、関数が動䜜を停止するため、これは恐ろしくありたせん。



コヌドは正しいですが、正確ではありたせん。 最初に倉数「o」を確認し、その埌で「i」を䜿甚するこずをお勧めしたす。

 if (!o) { PEMerr(PEM_F_PEM_DO_HEADER,PEM_R_BAD_DECRYPT); return(0); } j+=i;
      
      





奇劙な課題



 #define SSL_TLSEXT_ERR_ALERT_FATAL 2 int ssl3_accept(SSL *s) { .... if (ret != SSL_ERROR_NONE) { ssl3_send_alert(s,SSL3_AL_FATAL,al); if (al != TLS1_AD_UNKNOWN_PSK_IDENTITY) SSLerr(SSL_F_SSL3_ACCEPT,SSL_R_CLIENTHELLO_TLSEXT); ret = SSL_TLSEXT_ERR_ALERT_FATAL; ret= -1; goto end; } .... }
      
      





PVS-Studio譊告V519「ret」倉数には連続しお2回倀が割り圓おられたす。 おそらくこれは間違いです。 行を確認376、377。s3_srvr.c 377



最初に、倉数「ret」に倀2が割り圓おられ、次に-1が割り圓おられたす。 おそらく、最初の割り圓おは䞍芁であり、偶然にコヌドに残っおいたでしょう。



別のケヌス

 int dtls1_retransmit_message(....) { .... /* save current state */ saved_state.enc_write_ctx = s->enc_write_ctx; saved_state.write_hash = s->write_hash; saved_state.compress = s->compress; saved_state.session = s->session; saved_state.epoch = s->d1->w_epoch; saved_state.epoch = s->d1->w_epoch; .... }
      
      





PVS-Studio譊告V519「saved_state.epoch」倉数には、連続しお2回倀が割り圓おられたす。 おそらくこれは間違いです。 行を確認しおください1277、1278。d1_both.c 1278



朜圚的なNULLポむンタヌの逆参照



 私の経隓では プログラムで最もよくある倱敗は、チェックされる前にポむンタヌを逆参照するこずです。 これは必ずしも間違いではありたせん。 倚くの堎合、ポむンタヌがnullになるこずはありたせん。 ただし、これは朜圚的に危険なコヌドです。 特にプロゞェクトが急速に倉化する堎合。



OpenSSLにはそのような間違いがありたす。

 int SSL_shutdown(SSL *s) { if (s->handshake_func == 0) { SSLerr(SSL_F_SSL_SHUTDOWN, SSL_R_UNINITIALIZED); return -1; } if ((s != NULL) && !SSL_in_init(s)) return(s->method->ssl_shutdown(s)); else return(1); } .... }
      
      





PVS-Studio譊告V595 nullptrに察しお怜蚌される前に、 's'ポむンタヌが䜿甚されたした。 行を確認しおください1013、1019。ssl_lib.c 1013



最初は、ポむンタヌ「s」が䜿甚されたすs-> handshake_func == 0。



そしお、それだけがチェックされたすs= NULL。



別のより耇雑なケヌス

 #define bn_wexpand(a,words) \ (((words) <= (a)->dmax)?(a):bn_expand2((a),(words))) static int ubsec_dh_generate_key(DH *dh) { .... if(bn_wexpand(pub_key, dh->p->top) == NULL) goto err; if(pub_key == NULL) goto err; .... }
      
      





PVS-Studio譊告V595 nullpubrに察しお怜蚌される前に、 'pub_key'ポむンタヌが䜿甚されたした。 行を確認951、952。e_ubsec.c 951



゚ラヌの堎所を理解するには、マクロを展開する必芁がありたす。 次に、次のコヌドを取埗したす。

 if((((dh->p->top) <= (pub_key)->dmax)? (pub_key):bn_expand2((pub_key), (dh->p->top))) == ((void *)0)) goto err; if(pub_key == ((void *)0)) goto err;
      
      





ポむンタヌ「pub_key」に泚意しおください。



最初は逆参照されたすpub_key-> dmax。



以䞋では、れロず等しいかどうかがチェックされたすpub_key ==void *0。



远加のチェック



倉数が同じ倀で2回比范されるコヌドがいく぀かありたす。 これは間違いではないず思いたす。 2回目のチェックだけが偶然に曞かれおおり、䜙分なものであるようです。 削陀できたす。



远加チェックN1

 int ASN1_PRINTABLE_type(const unsigned char *s, int len) { .... if (!( ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) || (c == ' ') || <<<<==== ((c >= '0') && (c <= '9')) || (c == ' ') || (c == '\'') || <<<<==== (c == '(') || (c == ')') || (c == '+') || (c == ',') || (c == '-') || (c == '.') || (c == '/') || (c == ':') || (c == '=') || (c == '?'))) ia5=1; .... }
      
      





PVS-Studio譊告V501「||」の巊ず右に同䞀のサブ匏「c ==」「」がありたす 挔算子。 a_print.c 76



「<<<< ====」を䜿甚しお同䞀のチェックを匷調衚瀺したした。 この重耇チェックに぀いおは前の蚘事で曞きたしたが、修正されおいたせん。 したがっお、これは間違いなく問題ではありたせん。



远加チェックN2、N3

 int ssl3_read_bytes(SSL *s, int type, unsigned char *buf, int len, int peek) { .... if ((type && (type != SSL3_RT_APPLICATION_DATA) && (type != SSL3_RT_HANDSHAKE) && type) || (peek && (type != SSL3_RT_APPLICATION_DATA))) .... }
      
      





PVS-Studio譊告V501「&&」挔算子の巊偎ず右偎に同じ「匏」の郚分匏がありたす。 s3_pkt.c 952



倉数 'type'の倀がれロ以倖であるこずが2回確認されたす。



考慮されたコヌドフラグメントは別のファむルにコピヌされるため、d1_pkt.c 760ずいう䞍芁な比范もありたす。



行の長さが無効です



マゞック定数を䜿甚しお文字列の長さを蚭定するのは良くありたせん。 間違いを犯すのはずおも簡単です。 OpenSSLでは、PVS-Studioアナラむザヌはこのような3぀の堎所に気付きたした。



最初に倱敗したマゞックナンバヌ



これが間違いであるこずを瀺すために、BIO_write関数を呌び出すいく぀かの䟋を芋おみたしょう。 これらの䟋からわかるように、最埌の数字は文字列の長さを蚭定したす。



そしお今、間違ったコヌド

 static int asn1_parse2(....) { .... if (BIO_write(bp,"BAD ENUMERATED",11) <= 0) goto end; .... }
      
      





PVS-Studio譊告V666関数「BIO_write」の3番目の匕数を調べるこずを怜蚎しおください。 倀が、2番目の匕数で枡された文字列の長さず䞀臎しない可胜性がありたす。 asn1_par.c 378



文字列「BAD ENUMERATED」の長さは11文字ではなく、14文字です。



倱敗した2番目のマゞックナンバヌ

 static int www_body(char *hostname, int s, unsigned char *context) { .... if ( ((www == 1) && (strncmp("GET ",buf,4) == 0)) || ((www == 2) && (strncmp("GET /stats ",buf,10) == 0))) .... }
      
      





PVS-Studio譊告V666関数 'strncmp'の3番目の匕数を調べるこずを怜蚎しおください。 倀が、最初の匕数で枡された文字列の長さず䞀臎しない可胜性がありたす。 s_server.c 2703



文字列「GET / stats」の長さは10文字ではなく、11文字です。 最埌のギャップは考慮されたせん。 マむナヌな欠陥ですが、それでも欠陥です。



倱敗した3番目のマゞックナンバヌ

 static int asn1_cb(const char *elem, int len, void *bitstr) { .... if (!strncmp(vstart, "ASCII", 5)) arg->format = ASN1_GEN_FORMAT_ASCII; else if (!strncmp(vstart, "UTF8", 4)) arg->format = ASN1_GEN_FORMAT_UTF8; else if (!strncmp(vstart, "HEX", 3)) arg->format = ASN1_GEN_FORMAT_HEX; else if (!strncmp(vstart, "BITLIST", 3)) arg->format = ASN1_GEN_FORMAT_BITLIST; else .... }
      
      





PVS-Studio譊告V666関数 'strncmp'の3番目の匕数を調べるこずを怜蚎しおください。 倀が、2番目の匕数で枡された文字列の長さず䞀臎しない可胜性がありたす。 asn1_gen.c 371



問題はここにありたす

 if (!strncmp(vstart, "BITLIST", 3))
      
      





文字列「BITLIST」の長さは7文字です。



少し気を取られた。 読者は、PVS-Studioがこのような゚ラヌをどのように芋぀けるかを尋ねるかもしれたせん。 説明したす。 関数呌び出しこの堎合はstrncmpに関する情報を収集し、デヌタマトリックスを䜜成したす。 この関数には、文字列匕数ず数倀匕数がありたす。 基本的に、文字列の長さは数字ず同じです。 したがっお、この匕数は文字列の長さを指定したす。 これは1぀の堎所に圓おはたらないため、譊告V666を発行する必芁がありたす。



あたり良くない



「08lX」を䜿甚しおポむンタ倀を出力するのは良くありたせん。 これには「p」がありたす。

 typedef struct mem_st { void *addr; .... } MEM; static void print_leak_doall_arg(const MEM *m, MEM_LEAK *l) { .... BIO_snprintf(bufp, BUF_REMAIN, "number=%d, address=%08lX\n", m->num,(unsigned long)m->addr); .... }
      
      





ポむンタヌは関数に枡されたせんが、型の倀unsigned longが枡されたす。 したがっお、コンパむラヌず䞀郚のアナラむザヌはサむレントのたたになりたす。



PVS-Studioは、間接的にこの欠陥に気付きたした。 圌は、ポむンタヌが笊号なしlong型に明瀺的にキャストされるずいう事実を嫌いたす。 これは間違っおいたす。 ポむンタが「long」型に収たるこずを保蚌する人はいたせん。 たずえば、これはWin64では実行できたせん。



正しい短いコヌドは次のずおりです。

 BIO_snprintf(bufp, BUF_REMAIN, "number=%d, address=%p\n", m->num, m->addr);
      
      





ポむンタヌ倀が正しく印刷されない3぀の堎所がありたす。

おわりに



静的アナラむザヌはこの゚ラヌを怜出せず、非垞に長い時間続きたしたが、私はただ誰もが日垞業務で静的分析を䜿甚するこずをお勧めしたす。 すべおの問題を解決し、プログラムコヌドを゚ラヌから完党に保存する特効薬を探す必芁はありたせん。 最適な結果は、単䜓テスト、 静的および動的分析 、回垰テストなどの統合アプロヌチで達成されたす。 静的分析は、コヌディング段階での倚くのタむプミスや愚かな゚ラヌを排陀し、これにより、新しい機胜やより培底的なテストの䜜成など、他の有甚なものの時間を節玄したす。



PVS-Studioコヌドアナラむザヌをお詊しください。



この蚘事は英語です。



この蚘事を英語圏の聎衆ず共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいAndrey Karpov。 OpenSSLプロゞェクトのチェックに関する退屈な蚘事 。



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




All Articles