LKMLでパッチをレビューしていて気になったのと、一部の混乱の原因には私も加担していると思っているので、個人用のメモを兼ねてここらでまとめておきたい。適当にメモしたので間違っていたらあとで修正する。
対象となるカーネルは、linux-5.0(-rcX)である。
背景
Linuxカーネルには独自に実装されたstring操作系(特にコピー系の)関数がいくつか存在する。基本的には標準ライブラリから持ってきたもの(カーネルはビルド時に標準ライブラリをリンクできない。標準ライブラリの関数はユーザ空間で実行されることを前提にしている)が多いのだが、OS特有の特権レベルやメモリ空間を意識した拡張関数も存在する。
問題は、これらの実装の中で、実装を見ていくと'\0'つまりNULL終端の扱いが色々と違っているのだ。
strncpy系
strncpy()
、strlcpy()
、strscpy()
、strncpy_from_user()
、strncpy_from_unsafe()
がある。
-
strncpy(dst,src,n)
:srcからdstへ文字列をコピーする。文字列がnより長い場合'\0'はコピーされない。dstを返す。 -
strlcpy(dst,src,n)
:srcからdstへ文字列をコピーする。文字列がn-1より長いと'\0'を最後に追加する。srcにある文字列の'\0'を含まない長さを返す。(コピーした長さではない) -
strscpy(dst,src,n)
:srcからdstへ文字列をコピーする。文字列がn-1より長いと'\0'を最後に追加して-E2BIGを返す。コピーした文字列の長さを返す。(常に戻り値がn-1より小さい) -
strncpy_from_user(dst,src,n)
:ユーザ空間にあるsrcからdstへ文字列をコピーする。文字列がnより長い場合'\0'はコピーされない。コピーした文字列の'\0'を含まない長さを返す。ユーザ空間の上限を越える、あるいはページフォルトが起きた場合は-EFAULTを返す。 -
strncpy_from_unsafe(dst,src,n)
:カーネル空間にあるsrc(但しアクセスできるとは限らない)からdstへ文字列をコピーする。文字列がn-1より長いと'\0'を最後に追加する。'\0'を含むコピーした文字列の長さ(最大値n)を返す。アクセスに失敗した場合-EFAULTを返す。
strlen系
strlen()
、strnlen()
、strnlen_user()
がある。
-
strlen(str)
,strnlen(str,n)
: '\0'をカウントしないで文字列の長さを返す。nより長い場合はnを返す。 -
strnlen_user(str,n)
: '\0'を含むユーザ空間にある文字列の長さを返す。nより長い場合はn+1以上の値を返す。nは'\0'を含む値であること。ユーザ空間の上限を越える、あるいはページフォルトが起きた場合は0を返す。
strdup系
kstrdup()
、strndup_user()
、memdup_user_null()
、kmemdup_null()
がある。memdup_null系は文字列の長さが分かっている場合にstrdup系の代わりに使われることが望ましい。
-
kstrndup(str,n,gpf)
:メモリを確保し、最大nバイトの文字列をコピーする。文字列がnより長い場合、n文字コピーし、最後に'\0'を追加する。つまり、確保するメモリは最大 n + 1 になる可能性がある。メモリが確保できなければENOMEMを返す。 -
strndup_user(str,n)
:メモリを確保し、最大n-1バイトの文字列をコピーする。文字列がn-1より長い場合は-EINVALを返し、ユーザ空間アクセスに失敗した場合は-EFAULTを返す。メモリが確保できなければENOMEMを返す。 -
kmemdup_null(src,n,gpf)
:メモリを確保し、nバイトのデータをコピーし、n+1バイト目に'\0'を追加する。メモリが確保できなければENOMEMを返す。 -
memdup_user_null(src,n)
:メモリを確保し、nバイトのデータをコピーし、n+1バイト目に'\0'を追加する。ユーザ空間アクセスに失敗した場合は-EFAULTを返し、メモリが確保できなければENOMEMを返す。
番外編
-
strstarts(str,prefix)
: strがprefixから始まっていればtrue、そうでなければfalseを返す。 -
str_has_prefix(stre,prefix)
: strがprefixから始まっていればprefixの長さを、そうでなければ0を返す。
同じ処理だが戻り値が違う。それだけである。ftraceの中の処理で多用されるパターンで、prefixの長さがわかったほうがいいよね、ということで後者が導入されたんだけど・・・。
まとめ
strn系のカーネル内APIの混乱ぶりが分かっていただけただろうか・・・。strcpy系は0フィルするかどうか書き忘れたけど、多分strcpy以外は0フィルしないだろう。