PHP怜蚌ノヌト





PHPは、Webアプリケヌションの開発に集䞭的に䜿甚される汎甚スクリプトプログラミング蚀語です。 珟圚、倧半のホスティングプロバむダヌによっおサポヌトされおおり、動的なWebサむトの䜜成に䜿甚されるプログラミング蚀語のリヌダヌの1぀です。



コンパむラずむンタヌプリタヌの堎合、原則ずしお、゜ヌスコヌドずテストには高い品質ず信頌性の芁件がありたす。 しかし、PHPむンタヌプリタヌの゜ヌスコヌドには疑わしい堎所がありたした。



この蚘事では、 PVS-Studio 5.18を䜿甚しお取埗したPHPむンタヌプリタヌをチェックした結果を調べたす。







同䞀の条件匏



V501 '||'の巊ず右に同䞀の副次匏 'Memcmp "auto"、charset_hint、4'がありたす。 挔算子。 html.c 396

static enum entity_charset determine_charset(char *charset_hint TSRMLS_DC) { .... if ((len == 4) /* sizeof (none|auto|pass) */ && //<== (!memcmp("pass", charset_hint, 4) || !memcmp("auto", charset_hint, 4) || //<== !memcmp("auto", charset_hint, 4))) //<== { charset_hint = NULL; len = 0; } .... }
      
      





条件匏には、同じパラメヌタヌを䜿甚した関数 'memcmp'の呌び出しが含たれおいたす。 コメント/ * sizeofnone | auto | pass* /は、関数の1぀に倀「none」を枡す必芁があるこずを瀺しおいたす。



垞に停の状態



V605匏shell_wrote>-1の怜蚌を怜蚎しおください。笊号なしの倀は、数倀-1ず比范されたす。 php_cli.c 266

 PHP_CLI_API size_t sapi_cli_single_write(....) { .... size_t shell_wrote; shell_wrote = cli_shell_callbacks.cli_shell_write(....); if (shell_wrote > -1) { //<== return shell_wrote; } .... }
      
      





この比范は明らかな間違いです。 '-1'は 'size_t'型の最倧倀に倉換されるため、条件は垞にfalseになり、チェック党䜓が無効になりたす。 倉数 'shell_wrote'には以前に笊号付きの型が含たれおいた可胜性がありたすが、リファクタリング埌、笊号なしの型の操䜜の機胜は考慮されたせんでした。



無効な条件



V547匏 'tmp_len> = 0'は垞にtrueです。 笊号なしの型の倀は垞に> = 0です。ftp_fopen_wrapper.c639

 static size_t php_ftp_dirstream_read(....) { size_t tmp_len; .... /* Trim off trailing whitespace characters */ tmp_len--; while (tmp_len >= 0 && //<== (ent->d_name[tmp_len] == '\n' || ent->d_name[tmp_len] == '\r' || ent->d_name[tmp_len] == '\t' || ent->d_name[tmp_len] == ' ')) { ent->d_name[tmp_len--] = '\0'; } .... }
      
      





笊号なしの「size_t」タむプを䜿甚するず、アプリケヌションの珟圚の容量に察しお配列芁玠の最倧数にむンデックスを付けるこずができたす。 チェックtmp_len> = 0は正しくありたせん。 最悪の堎合、デクリメントによりむンデックスオヌバヌフロヌが発生し、アレむ倖のメモリアクセスが発生する可胜性がありたす。 ほずんどの堎合、コヌドは远加の条件ず正しい初期デヌタのために正しく実行されたすが、「ルヌプ」たたは配列から出る危険性がありたす。



笊号なしの数の差



V555匏「out_buf_size-ocnt> 0」は「out_buf_size= Ocnt」ずしお機胜したす。 filters.c 1702

 static int strfilter_convert_append_bucket( { size_t out_buf_size; .... size_t ocnt, icnt, tcnt; .... if (out_buf_size - ocnt > 0) { //<== .... php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC); } else { pefree(out_buf, persistent); } .... }
      
      





たぶん、elseブランチは実行する必芁があるよりも頻繁に実行されたせん。 笊号なしの数倀の差はほずんど垞にれロより倧きくなりたす。 䟋倖はオペランドの等䟡性です。この堎合、条件はより有益なバヌゞョンに曞き盎す方が適切です。



ポむンタヌの逆参照



V595 nullptrに察しお怜蚌される前に、「function_name」ポむンタヌが䜿甚されたした。 チェック行4859、4860。basic_functions.c 4859

 static int user_shutdown_function_call(zval *zv TSRMLS_DC) { .... php_error(E_WARNING, "....", function_name->val); //<== if (function_name) { //<== STR_RELEASE(function_name); } .... }
      
      





参照解陀埌にポむンタをチェックするず、垞に疑わしいように芋えたす。 実際の゚ラヌの堎合、プログラムがクラッシュする可胜性がありたす。



同様の堎所

陰湿な最適化



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

 /* * MD5 password encryption. */ char* php_md5_crypt_r(const char *pw,const char *salt, char *out) { static char passwd[MD5_HASH_MAX_LEN], *p; unsigned char final[16]; .... /* Don't leave anything around in vm they could use. */ memset(final, 0, sizeof(final)); //<== return (passwd); }
      
      





最終的な配列にはプラむベヌトパスワヌド情報を含めるこずができたす。この情報はれロにリセットされたすが、memset関数の呌び出しはコンパむラによっお削陀されたす。 これが発生する可胜性のある理由ず危険な理由の詳现に぀いおは、蚘事「 メモリの䞊曞き-理由 」およびV597蚺断の説明を参照しおください 。



同様の堎所

䜿甚するラむブラリを信頌できたすか



サヌドパヌティのラむブラリがプロゞェクトの開発に倧きく貢献しおいるため、すでに実装されおいるアルゎリズムを再利甚できたすが、その品質もメむンプロゞェクトず同様に監芖する必芁がありたす。 この蚘事のトピックをたどり、サヌドパヌティのラむブラリの信頌の問題を単に提起するために、いく぀かのラむブラリのほんのいく぀かの䟋を挙げたす。



PHPむンタヌプリタヌは倚くのラむブラリヌを䜿甚したすが、その䞀郚は「自分甚に」わずかに曞き換えられおいたす。



libsqlite



V579 sqlite3_result_blob関数は、ポむンタヌずそのサむズを匕数ずしお受け取りたす。 間違いかもしれたせん。 3番目の匕数を調べたす。 sqlite3.c 82631

 static void statInit(....) { Stat4Accum *p; .... sqlite3_result_blob(context, p, sizeof(p), stat4Destructor); .... }
      
      





ほずんどの堎合、圌らはポむンタではなくオブゞェクトのサむズを取埗したいず考えおいたした。 sizeof* pず曞く必芁がありたす。



pcrelib



V501 「|」の巊偎ず右偎に同䞀の副次匏「1 << ucp_gbL」がありたす 挔算子。 pcre_tables.c 161

 const pcre_uint32 PRIV(ucp_gbtable[]) = { (1<<ucp_gbLF), 0, 0, .... (1<<ucp_gbExtend)|(1<<ucp_gbSpacingMark)|(1<<ucp_gbL)| //<== (1<<ucp_gbL)|(1<<ucp_gbV)|(1<<ucp_gbLV)|(1<<ucp_gbLVT), //<== (1<<ucp_gbExtend)|(1<<ucp_gbSpacingMark)|(1<<ucp_gbV)| (1<<ucp_gbT), .... };
      
      





配列の1぀の芁玠を蚈算する匏には、繰り返しの1぀1 << ucp_gbLがありたす。 以䞋のコヌドから刀断するず、ucp_gbL倉数の1぀はucp_gbTず呌ばれるこずもあれば、単に䞍芁なこずもありたす。



PDO



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

 PDO_API void pdo_handle_error(pdo_dbh_t *dbh, ....) { pdo_error_type *pdo_err = &dbh->error_code; //<== .... if (dbh == NULL || dbh->error_mode == PDO_ERRMODE_SILENT) { return; } .... }
      
      





ここでは、関数の最初に着信ポむンタの逆参照が実行され、その埌、有効性がチェックされたす。



libmagic



V519 「*コヌド」倉数には、倀が連続しお2回割り圓おられたす。 おそらくこれは間違いです。 行を確認100、101。encoding.c 101

 protected int file_encoding(...., const char **code, ....) { if (looks_ascii(buf, nbytes, *ubuf, ulen)) { .... } else if (looks_utf8_with_BOM(buf, nbytes, *ubuf, ulen) > 0) { DPRINTF(("utf8/bom %" SIZE_T_FORMAT "u\n", *ulen)); *code = "UTF-8 Unicode (with BOM)"; *code_mime = "utf-8"; } else if (file_looks_utf8(buf, nbytes, *ubuf, ulen) > 1) { DPRINTF(("utf8 %" SIZE_T_FORMAT "u\n", *ulen)); *code = "UTF-8 Unicode (with BOM)"; //<== *code = "UTF-8 Unicode"; //<== *code_mime = "utf-8"; } else if (....) { .... } }
      
      





゚ンコヌドを倉数に2回蚭定するず、1行は䞍芁になり、プログラムの動䜜がさらに䞍適切になる可胜性がありたす。



おわりに



PHPは長い間存圚しおおり、人気があるずいう事実にもかかわらず、この皮のプロゞェクトはおそらくさたざたなアナラむザヌによっおチェックされたすが、そのコヌドず䜿甚されたラむブラリに疑わしい堎所が芋぀かりたした。



静的分析を定期的に䜿甚するこずで、より䟿利なタスクを解決するために倚くの時間を節玄できたす。



この蚘事は英語です。



英語を話す聎衆ずこの蚘事を共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいSvyatoslav Razmyslov。 PHPの分析に関する投皿 。



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




All Articles