うーん..linux-3.6.2/fs/namei.c
似たようなシチュエーションがたくさんあります。たとえば、rename
syscall は実際には次のように定義されます。
SYSCALL_DEFINE2(rename, const char __user *, oldname, const char __user *, newname)
{
return sys_renameat(AT_FDCWD, oldname, AT_FDCWD, newname);
}
つまり、システムコールから別のシステムコールを呼び出しても問題ありません。問題は、ポインター引数がユーザー空間ポインターであるのに対し、カーネルポインターを提供しようとしているということです。fileName
ユーザー空間に割り当てる必要がありますが、カーネル空間にある必要があります。
正しい解決策は、2 つの関数 ( yours とsys_renameat()
in fs/namei.c
) から共通のコードを抜き出し、両方のシステムコールから関数を呼び出すことです。これをアップストリームに含めようとしていないと仮定すると-もしそうなら、それはリファクタリングと再考の時間です-の内容をsys_renameat
自分の関数に簡単にコピーできます。それほど大きくありません。また、このようなファイルシステム操作に必要なチェックとロックについて理解することも役立ちます。
問題と解決策を説明するために編集されました。
非常に現実的な意味では、通常のプロセスによって割り当てられたメモリ (ユーザー空間メモリ) とカーネルによって割り当てられたメモリ (カーネル空間) は、カーネルとユーザー空間のバリアによって完全に分離されています。
あなたのコードはその障壁を無視しているため、まったく機能しません。(x86 では、カーネルとユーザー空間の障壁がカーネル側から簡単に突き破られるため、おそらく多少は機能します。) また、ファイル名に 256 バイトのスタックを使用しますが、これは禁物です: カーネル スタックはリソースは非常に限られているため、慎重に使用する必要があります。
通常のプロセス (ユーザー空間プロセス) は、カーネル メモリにアクセスできません。あなたは試すことができます、それはうまくいきません。これが障壁が存在する理由です。(そのようなバリアをサポートしていないハードウェアを備えた特定の組み込みシステムがありますが、この議論の目的のためにそれらを無視しましょう。x86 ではバリアはカーネル側から簡単に突き破ることができますが、それが意味するものではないことを覚えておいてください。そこにはありません.あなたのために働くように見えるので、それはどういうわけか正しいと思い込まないでください.)
バリアの性質として、ほとんどのアーキテクチャではカーネルにもバリアが存在します。
カーネル プログラマーを支援するために、バリアを越えてユーザー空間を指すポインターは とマークされてい__user
ます。これは、それらを逆参照して動作することを期待することはできないことを意味します。と を使用する必要がありcopy_from_user()
ますcopy_to_user()
。syscall パラメーターだけではありません。カーネルからユーザー空間データにアクセスするときは、これら 2 つの関数を使用する必要があります。
すべての syscall は、ユーザー空間データに対して機能します。表示されるすべてのポインターには、 マークが付いています (またはマークする必要があります) __user
。すべての syscall は、ユーザー空間からデータにアクセスするために必要なすべての作業を行います。
問題は、カーネル空間データ をシステムコールに提供しようとしていることですinputFile
。inputFile
システムコールは常にバリアを通過しようとしますが、バリアの同じ側にあるため、機能しません!
inputFile
バリアの反対側にコピーする正気の方法は実際にはありません。もちろん、それを行う方法はありますし、それほど難しくはありませんが、正気ではありません。
それでは、私が上で説明した正しい解決策を探ってみましょう。
まず最初に、renameat
syscall が現在の (3.6.2) Linux カーネルで実際にどのように見えるかを見てみましょう (このコードは GPLv2 の下でライセンスされていることに注意してください)。syscallはrename
単に を使用してそれを呼び出しますsys_renameat(AT_FDCWD, oldname, AT_FDCWD, newname)
。コードが何をするかについての説明を挿入します。
SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
int, newdfd, const char __user *, newname)
{
struct dentry *old_dir, *new_dir;
struct dentry *old_dentry, *new_dentry;
struct dentry *trap;
struct nameidata oldnd, newnd;
char *from;
char *to;
int error;
カーネルでは、スタックは限られたリソースです。かなりの数の変数を使用できますが、ローカル配列は深刻な問題になります。上記のローカル変数リストは、典型的なシステムコールで見られる最大のものです。
rename 呼び出しの場合、関数は最初にファイル名を含む親ディレクトリを見つける必要があります。
error = user_path_parent(olddfd, oldname, &oldnd, &from);
if (error)
goto exit;
注: この時点以降、古いディレクトリとパスは、使用後に を呼び出して解放する必要がありますpath_put(&oldnd.path); putname(from);
。
error = user_path_parent(newdfd, newname, &newnd, &to);
if (error)
goto exit1;
注: この時点以降、新しいディレクトリとパスは、使用後に を呼び出して解放する必要がありますpath_put(&newnd.path); putname(to);
。
次のステップは、2 つが同じファイルシステムに存在することを確認することです。
error = -EXDEV;
if (oldnd.path.mnt != newnd.path.mnt)
goto exit2;
ディレクトリの最後のコンポーネントは、通常のディレクトリである必要があります。
old_dir = oldnd.path.dentry;
error = -EBUSY;
if (oldnd.last_type != LAST_NORM)
goto exit2;
new_dir = newnd.path.dentry;
if (newnd.last_type != LAST_NORM)
goto exit2;
また、ディレクトリを含むマウントは書き込み可能でなければなりません。成功した場合、これはマウントにロックを適用することに注意してくださいmnt_drop_write(oldnd.path.mnt)
。システムコールが戻る前に、常に呼び出しとペアにする必要があります。
error = mnt_want_write(oldnd.path.mnt);
if (error)
goto exit2;
次に、nameidata ルックアップ フラグが更新され、ディレクトリが既知であることを反映します。
oldnd.flags &= ~LOOKUP_PARENT;
newnd.flags &= ~LOOKUP_PARENT;
newnd.flags |= LOOKUP_RENAME_TARGET;
次に、名前変更の間、2 つのディレクトリがロックされます。これは、対応するロック解除呼び出しと組み合わせる必要がありますunlock_rename(new_dir, old_dir)
。
trap = lock_rename(new_dir, old_dir);
次に、実際に存在するファイルが検索されます。これが成功した場合、以下を呼び出して dentry を解放する必要がありますdput(old_dentry)
。
old_dentry = lookup_hash(&oldnd);
error = PTR_ERR(old_dentry);
if (IS_ERR(old_dentry))
goto exit3;
/* source must exist */
error = -ENOENT;
if (!old_dentry->d_inode)
goto exit4;
/* unless the source is a directory trailing slashes give -ENOTDIR */
if (!S_ISDIR(old_dentry->d_inode->i_mode)) {
error = -ENOTDIR;
if (oldnd.last.name[oldnd.last.len])
goto exit4;
if (newnd.last.name[newnd.last.len])
goto exit4;
}
/* source should not be ancestor of target */
error = -EINVAL;
if (old_dentry == trap)
goto exit4;
新しいファイル名のエントリも検索されます (存在する可能性があります)。繰り返しますが、成功した場合、この dentry もdput(new_dentry)
後で使用して解放する必要があります。
new_dentry = lookup_hash(&newnd);
error = PTR_ERR(new_dentry);
if (IS_ERR(new_dentry))
goto exit4;
/* target should not be an ancestor of source */
error = -ENOTEMPTY;
if (new_dentry == trap)
goto exit5;
この時点で、関数はすべてが正常であることを確認しました。次に、 を呼び出して、(アクセス モードなどに関して) 操作を続行できるかどうかを確認する必要がありますsecurity_path_rename(struct path *old_dir, struct dentry *old_dentry, struct path *new_dir, struct dentry *new_dentry)
。(ユーザー空間プロセスの ID の詳細は で維持されcurrent
ます。)
error = security_path_rename(&oldnd.path, old_dentry,
&newnd.path, new_dentry);
if (error)
goto exit5;
名前の変更に反対がなければ、実際の名前変更は以下を使用して行うことができますvfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry)
。
error = vfs_rename(old_dir->d_inode, old_dentry,
new_dir->d_inode, new_dentry);
この時点で、すべての作業が完了し (error
がゼロの場合は成功)、あとはさまざまなルックアップを解放するだけです。
exit5:
dput(new_dentry);
exit4:
dput(old_dentry);
exit3:
unlock_rename(new_dir, old_dir);
mnt_drop_write(oldnd.path.mnt);
exit2:
path_put(&newnd.path);
putname(to);
exit1:
path_put(&oldnd.path);
putname(from);
exit:
return error;
}
名前の変更操作は以上です。ご覧のとおり、表示copy_from_user()
される明示的なものはありません。user_path_parent()
を呼び出すgetname()
呼び出し、それgetname_flags()
を行う呼び出し。必要なチェックをすべて無視すると、要約すると
char *result = __getname(); /* Reserve PATH_MAX+1 bytes of kernel memory for one file name */
in len;
len = strncpy_from_user(result, old/newname, PATH_MAX);
if (len <= 0) {
__putname(result);
/* An error occurred, abort! */
}
if (len >= PATH_MAX) {
__putname(result);
/* path is too long, abort! */
}
/* Finally, add it to the audit context for the current process. */
audit_getname(result);
そして、不要になった後、
putname(result);
だから、あなたの問題に対する簡単な解決策はありません。システムコールを魔法のように機能させる単一の関数呼び出しはありません。でどのように適切に処理されているかを見て、書き直す必要がありますfs/namei.c
。難しいことではありませんが、慎重かつ細心の注意を払って行う必要があります。そして何よりも、「この単純なことを最小限の変更で機能させようとする」というアプローチではうまくいかないことを受け入れます。