カーネルおさんぽまっぷ超入門

Kernel/VM Advent Calender 2013 7日目

カーネル/VMはておくれなすごい人がけっこう集って、ネタとか披露しているようです.
今年のAdvent Calenderも既に濃い内容が集っています.

でも、私達パンピーにはカーネルとかちょっと難しすぎてそもそも
カーネル/VMという主題自体敬遠しちゃう……

ということで本当にカーネルの第一歩をやってみよう、というのがこの記事です.
記事タイトルはえりっく氏(@siritori)から無断で肖ってます.ありがとうございます


今年の他の記事が高度な内容なので
この記事が低レベル(低レイヤということでなく本当に程度が低いという意味で)ですいません.
普段からカーネル/VMに参加してる皆様、さようなら.
カーネル/VM界隈わいわいしてておもしろそうだけど怖くて敬遠してる方々、こんにちは.

前置きはこのぐらいで本文行きます. 少々長め.


1. イントロ

まず、いきなりカーネルのソースを読むのは無理です.どこから読んでいいやら、
よしんば読み始めてもどう読んで良いやらでさっぱりです.完全に未知の領域なわけですから.

最初は自分の興味のある場所から読んでいくのが良いかもしれません.

今回のネタはこれで行きましょう.ついさっきTLに流れてきました.
 実装が気になる? なら見てみればいいじゃない提督!

2. はじめかた

ということで、今回はsuコマンドがどうやってコマンドを発行したユーザーを判別しているか、
追ってみます

まずsuコマンドのソースについて.
ArchLinuxで、
$pacman -Qo /usr/bin/su
とすると、util-linuxパッケージに入ってることがわかります.
util-linuxで検索すると、ここからソースのtarballが入手できることが判明.
あ、ArchならabsのPKGBUILDに対してmakepkg -o、Gentooならportageのebuildに対してebuild unpackでソースが簡単に読める状態になります、良いですね!

こんな感じで解凍したファイル達が並びます.今回見たいのはログイン関連のコマンド(su)なので、login-utilsを見てみましょう.


su.cがありました.  まさにこれです.
中身は、こう
#include "su-common.h"
int main(int argc, char **argv)
{
return su_main(argc, argv, SU_MODE);
}
view raw su.c hosted with ❤ by GitHub
ひどい、これじゃなにもわからない

しかし、su.cの近くにsu-common.cというのがありましたね.これを読みましょう
https://gist.github.com/orumin/7837125
わー、長いナー. さすがにブログに埋め込むのも躊躇います.
とはいっても1000行ぐらいなので、ふつうといえばふつうです.
とりあえず、su.cにあったエントリポイントになっているsu_main(742行目)から読みましょう.

3. ユーザー空間からカーネル空間へ
けっこうこの時点で記事長くなってしまいましたがこれから本番なのでしばしのお付き合いを

su_main、850行目まではsuに渡されたオプションの処理とか前準備とか変数宣言やらで
興味深い場所はあまりないので、飛ばします.

ここで851行目に次の文が表れました
restricted = evaluate_uid ();
uidを評価する関数のようですね、おもしろそう.
uidとはUNIXのユーザーを識別する一意な値で、つまりuser idです
これは732行目に関数の実体が存在してて、
/*
* Returns 1 if the current user is not root
*/
static int
evaluate_uid(void)
{
uid_t ruid = getuid();
uid_t euid = geteuid();
/* if we're really root and aren't running setuid */
return (uid_t) 0 == ruid && ruid == euid ? 0 : 1;
}
view raw evaluate_uid hosted with ❤ by GitHub
このような非常に短い関数です. ユーザーがrootかどうか判別するようですね.
おっ、 目的である、ユーザーの判別が出てきましたよ.
しかもgetuid();だなんてそのままな関数まで出てきました!

しかしこのgetuid()、utils-linuxのどのソースコードにも関数の実体はあらわれません.
manページでgetuidを引いてみると、getuid(2)はシステムコール、つまり、
OSに処理を依頼する特別な関数(OSのAPI)であることが判明します.

そう、ここがユーザー空間からカーネル空間への入口なのです!

4. うさぎの穴から不思議の国へご招待

OSにシステムコールを発行する時は、まずユーザー関数としてglibcのような
Cの標準ライブラリに実装されてるシステムコール関数を呼んでいます.
たとえば、glibc-2.18なら、sysdeps/unix/sysv/linux/i386/getuid.cを見ると、
INTERNAL_SYSCALLマクロでシステムコールが発行されていて、
これはsysdeps/unix/sysv/linux/i386/sysdep.SにアセンブリでINTERNAL_SYSCALLの実体が
書いてあります.
このアセンブリ命令がプロセッサに割り込み命令を出して割り込みベクタとか割り込みハンドラがどーたらこーたらって話は割愛します.

もし、興味があるのならば上記のglibcなどを読むのも良いでしょうが、
私達が知りたいのはgetuid(2)がLinuxでどう実装されてるかであって、どう呼ばれるかではありません.そこらへんは、OSの教科書を読むほうがわかりやすいでしょう.

さて、Linuxカーネルのソースコードは/usr/src/linux/にあったりします.
もしなければ、https://www.kernel.org/からダウンロードしましょう
今回私が読むのはLinux-3.12.2です.

LinuxやBSDといった現代のUNIXライクOSのソースコードはきちんとディレクトリにソースが分かれていて、目的のものを見つけやすいです.
これはLinuxカーネルのソースディレクトリですが、だいたいディレクトリ名で何が入ってるか
想像が付くとおもいます.
わかりにくいのは省略された名称であるmmやipc、archとかですが、mmはメモリ管理 archはCPUの命令の違いとかを吸収するためのアーキテクチャ依存のコード、ipcはプロセス間通信といった具合です.

重要なのは、ユーザー空間の普通のアプリの常識はカーネル空間ではそのまま通用するわけじゃありません
2つの空間はメモリアドレスで分離されてたりするため、別の空間のアドレスをそのままCで読みにはいけないです.
また、カーネル空間に標準Cライブラリがあるわけではありません
(だってカーネル空間の機能をAPI(システムコール)で呼びだして、glibcとかを実装していますから)


では今回はシステムコールを読みたいので、kernelディレクトリに移動しましょう.


さすがに、ここらへんになるとソースが膨大で、目的のソースを勘や総当たりで読むだけじゃ
目的の関数に到達できません
そこで、Linuxの便利なコマンドを紹介します
find ./ -name \*.c -or -name \*.h -exec grep 目的の関数名 {} /dev/null \;
これで目的の関数(今回はgetuid)を探せますね.
最近は
grep -R '目的の関数名' *.c
 でも再帰的に探してくれそうな気がしますし、-execより-printでパイプを使ってxargsのほうが速いかもしれません.


また、VimやEmacsのユーザーならばctags, etags, gtags等々のソフトを使ってクロスリファレンスをまず作成すると簡単に目的の関数にアクセスできます


とにかくこれで探してみると
SYSCALL_DEFINE0(getuid)
{
/* Only we change this so SMP safe */
return from_kuid_munged(current_user_ns(), current_uid());
}
view raw sys.c hosted with ❤ by GitHub
kernel/sys.c:831の中にこういう関数がみつかります.
このSYSCALL_DEFINE0というマクロを、またまたgrepで探すと
include/linux/syscall.h:170にそのマクロを発見できます.
引数で渡した文字列(この場合getuid)をつかってsys_渡した文字列(今回はsys_getuid)という名前をリンカに渡すようにするマクロのようで、実装とはあんまり関係ありません.

関数のなかでuidを取得する実装に関係ありそうなのは、current_uid()な気がしますね


grepによるとcurrent_uid()はinclude/linux/cred.h:331に定義してあり、
#define current_uid()        (current_cred_xxx(uid))
 また、current_cred_xxx(uid)は同ファイル:326にて
#define current_cred_xxx(xxx)            \
({                        \
    current_cred()->xxx;            \
})
と定義されています.current_cred_xxx(uid)はcurrent_cred()の返り値のメンバuidを参照してるわけです.
同ファイル:252によると
/**
* current_cred - Access the current task's subjective credentials
*
* Access the subjective credentials of the current task. RCU-safe,
* since nobody else can modify it.
*/
#define current_cred() \
rcu_dereference_protected(current->cred, 1)
view raw cred.h:252 hosted with ❤ by GitHub

ということらしいので、currentというのは現在のタスクを示しているようです.

ここででてくるRCUというのは、Read Copy Updateの略で、
読み込み、コピー、更新の動作を排他的に行うAPIらしいです リード・コピー・アップデート[Wikipedia ja]
ここで使われてるマクロ関数はinclude/linux/rcupdata.hに定義がありますが、
つまるところ引数で渡されたcurrent->credというポインタ値が指す先の値を取得してるようです.

そして、取得した構造体のメンバにあるuidを
curent_cred_xxx(uid) ( == curent_uid())で取得することで、
現在動作してるタスクのuidが判明し、
それが、getuidシステムコールの中で使われていることが判明しました.

ここでcred構造体は、include/linux/cred.hに戻って79行目をみると
/*
* The security context of a task
*
* The parts of the context break down into two categories:
*
* (1) The objective context of a task. These parts are used when some other
* task is attempting to affect this one.
*
* (2) The subjective context. These details are used when the task is acting
* upon another object, be that a file, a task, a key or whatever.
*
* Note that some members of this structure belong to both categories - the
* LSM security pointer for instance.
*
* A task has two security pointers. task->real_cred points to the objective
* context that defines that task's actual details. The objective part of this
* context is used whenever that task is acted upon.
*
* task->cred points to the subjective context that defines the details of how
* that task is going to act upon another object. This may be overridden
* temporarily to point to another security context, but normally points to the
* same context as task->real_cred.
*/
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
};
view raw cred.h:79 hosted with ❤ by GitHub
と、たしかにuidというメンバを持っています.
currentの定義については、arch/x86/include/asm/current.hやarch/x86/include/asm/percpu.hに
あって、currentはカレントタスクを取得するget_current()の別名であることがわかりますが、
ここにはx86のアセンブリがマクロを多用しつつ書かれており、主題から逸れるので
興味のある人は読んでみてください


さて、もうあとはラストスパートです

一度kernel/sys.cの831行目に帰ってみると、先に判明したcurrent_uid()ほか
current_user_ns()を引数にfrom_kuid_munged()の返り値を、システムコールの
返り値にしてますね.

このfrom_kuid_mungedはkernel/user_namespace.cに定義されており、
static u32 map_id_up(struct uid_gid_map *map, u32 id)
{
unsigned idx, extents;
u32 first, last;
/* Find the matching extent */
extents = map->nr_extents;
smp_read_barrier_depends();
for (idx = 0; idx < extents; idx++) {
first = map->extent[idx].lower_first;
last = first + map->extent[idx].count - 1;
if (id >= first && id <= last)
break;
}
/* Map the id or note failure */
if (idx < extents)
id = (id - first) + map->extent[idx].first;
else
id = (u32) -1;
return id;
}
/**
* make_kuid - Map a user-namespace uid pair into a kuid.
* @ns: User namespace that the uid is in
* @uid: User identifier
*
* Maps a user-namespace uid pair into a kernel internal kuid,
* and returns that kuid.
*
* When there is no mapping defined for the user-namespace uid
* pair INVALID_UID is returned. Callers are expected to test
* for and handle handle INVALID_UID being returned. INVALID_UID
* may be tested for using uid_valid().
*/
kuid_t make_kuid(struct user_namespace *ns, uid_t uid)
{
/* Map the uid to a global kernel uid */
return KUIDT_INIT(map_id_down(&ns->uid_map, uid));
}
EXPORT_SYMBOL(make_kuid);
/**
* from_kuid - Create a uid from a kuid user-namespace pair.
* @targ: The user namespace we want a uid in.
* @kuid: The kernel internal uid to start with.
*
* Map @kuid into the user-namespace specified by @targ and
* return the resulting uid.
*
* There is always a mapping into the initial user_namespace.
*
* If @kuid has no mapping in @targ (uid_t)-1 is returned.
*/
uid_t from_kuid(struct user_namespace *targ, kuid_t kuid)
{
/* Map the uid from a global kernel uid */
return map_id_up(&targ->uid_map, __kuid_val(kuid));
}
EXPORT_SYMBOL(from_kuid);
/**
* from_kuid_munged - Create a uid from a kuid user-namespace pair.
* @targ: The user namespace we want a uid in.
* @kuid: The kernel internal uid to start with.
*
* Map @kuid into the user-namespace specified by @targ and
* return the resulting uid.
*
* There is always a mapping into the initial user_namespace.
*
* Unlike from_kuid from_kuid_munged never fails and always
* returns a valid uid. This makes from_kuid_munged appropriate
* for use in syscalls like stat and getuid where failing the
* system call and failing to provide a valid uid are not an
* options.
*
* If @kuid has no mapping in @targ overflowuid is returned.
*/
uid_t from_kuid_munged(struct user_namespace *targ, kuid_t kuid)
{
uid_t uid;
uid = from_kuid(targ, kuid);
if (uid == (uid_t) -1)
uid = overflowuid;
return uid;
}
EXPORT_SYMBOL(from_kuid_munged);
これらがその関数です.
コメントを読むに、current_uid()で取得したのはカーネル空間のid(kid)なので、
それをユーザーの名前空間に変換してやってるみたいで、
実際にその処理をしているのがmap_id_up()のようです.
ここで使われてるmapという構造体の型はstruct uid_gid_mapで、
定義はgrepするとinclude/linux/user_namespace.hに発見できます

getuid(2)がfrom_kuid_mungedの引数としてcurrent_user_ns()の返り値を渡しているので、
current_user_ns()がstruct uid_gid_mapを返り値にしててnsはname spaceの略であることに
気がつけるかとおもいます.
current_user_nsについては今回は追いません(わたしが疲れましたし記事も長くなりました).

5. カーネル空間からの帰還

かくして、カーネル空間での仕事を終えたgetuid(2)は値を返してまたsu_common.cの
851行目に帰ってきます.
ここからさらにsuの仕事は続きます(カンジンの権限昇格はまだしてないですね)

しかし、これでカーネルに潜っていって、カーネルのソースを"読む"ことを体験できたのではないでしょうか.
正直最後のほうはちょっと丁寧さがなくなってて雑になってる解説でしたが、ここまで読んだ方は
ご苦労さまでした.

私はパンピーなので(大事なことなので二回(ry)今回読んだソースの解釈には
誤りがあるかもしれません.しかし、考え推測しながら読むことで、
一歩か二歩ぐらいはOSの仕組みへの理解に近づけたことでしょう.
そして、読んでいく上でどういう法則でソースコードが分割してあるかもなんとなく
想像が付くかもしれません


これから、current_uid()の返り値を常に0に置き換えてみたりとか(常にrootで動作!)
そういった改変や遊びをしたり、今回端折ったアーキテクチャ依存の部分や割り込みの仕組みに
ついても調べてみると、いろいろと楽しかったり理解が深まったりするんじゃないかな!
そしてようこそカーネル空間の不思議な国へ!

以上でした