ノータブルコード9 - C言語で文字列を扱うときはNULL終端されているかどうかに注意する - 2020-06-19 - ククログ

ククログ

株式会社クリアコード > ククログ > ノータブルコード9 - C言語で文字列を扱うときはNULL終端されているかどうかに注意する

ノータブルコード9 - C言語で文字列を扱うときはNULL終端されているかどうかに注意する

全文検索エンジンGroongaの他にPostgreSQLで高速に全文検索できる拡張PGroongaの開発にも参加している堀本です。 今回は、PGroongaの開発中に「注意しないと危ないな」と思ったコードを紹介します。

PGroongaはPostgreSQLの拡張として開発されています。 そのため、当然ですが、PostgreSQLとデータのやり取りを行います。 PostgreSQLには、PostgreSQLの型があり、以下のような型が組み込みの型として用意されています。

https://www.postgresql.jp/document/12/html/datatype.html#DATATYPE-TABLE

この中で文字列を格納する型としてよく使われるのがtext型(長さ制限のない可変長文字列)です。 今回PGroongaに新しい関数を実装するのに、以下のようにエラーログを出力する処理を書きました。

if (desc->natts <= i)
{
	ereport(ERROR,
		(errcode(ERRCODE_INTERNAL_ERROR),
		 errmsg("pgroonga: an invlid value was specified for column name: %s",
			 columnNameData)));
}

新しく実装した関数は以下のようなインターフェースを持っていて、PGroongaのインデックスを設定したカラムの名前を与えると、それに対応するPGroongaが内部で管理しているテーブルの名前を返すようになっています。

text pgroonga_index_column_name_string(indexName text, columnName text)

冒頭のコードは、この関数の第二引数に存在しないカラムの名前を指定された場合にエラーログを出力するようにしています。 ここで、ログに出力しているcolumnNameDataは、以下のようにtext型の変数からVARDATA_ANYマクロでデータを取り出しています。

const text *columnName = PG_GETARG_TEXT_PP(1);
const char *columnNameData = VARDATA_ANY(columnName);

エラーログで出力しているcolumnNameDataの方はchar *型なので、%sを使って出力するので問題ないように見えますが、PostgreSQLのtext型はNULL終端されていないので、%sを使って出力すると、意図しない領域まで出力してしまう可能性があります。 このようなバグを防ぐため、PostgreSQLのtext型を使用する場合は以下のように、必ず長さ指定をする必要があります。

if (desc->natts <= i)
{
	ereport(ERROR,
		(errcode(ERRCODE_INTERNAL_ERROR),
-		 errmsg("pgroonga: an invlid value was specified for column name: %s",
-			 columnNameData)));
+		 errmsg("pgroonga: an invlid value was specified for column name: %.*s",
+				(const int)columnNameSize,
+				columnNameData)));
}

C言語ではNULL終端の文字列として文字列を扱う場合(C言語の標準の文字列関数)とデータとデータの長さで文字列を扱う場合(printf%.*sを使う場合など)と文字列の開始位置のポインターと終了位置のポインターで文字列を扱う場合があり、扱いを間違うと思わぬバグを仕込むことになってしまいます。

C言語の文字列に関わる問題は多いですが、改めてC言語の文字列の扱いには注意が必要だと感じたコードの紹介でした。