stable kernelにコントリビュートする

この記事はLinux Advent Calendar 2021の18日目の記事です。

stable kernelへのコントリビュート

コントリビュートの方法としては次のような方法があると思います。

  • テストする

  • patchをレビューする

  • patchをバックポートする

  • その他

本記事ではpatchをバックポートする場合について書いてみたいと思います。基本的にはEverything you ever wanted to know about Linux -stable releases — The Linux Kernel documentationを読みましょうというところではありますが😑

patchをバックポートする理由

通常、mainlineに入ったバグ修正のpatchはstable treeに取り込まれますが、何かしらの理由で取り込まれていなかったり、patchが当たらずにエラーになったりして取り込まれないという場合があります。後者の場合は特にpatchの修正が必要になってきます。

patchをバックポートする

git-amを使ってpatchを当ててコンフリクトしたところを直してといった感じで通常通りに修正すればOKです。

stable kernel固有のお作法

バックポートしたpatchの先頭はこのような行が必要です。2行目のcommit hashの行は何パターンか書き方がある感じです。ここでは自分が書いてる書き方です。

From: <Author name><author email>

commit <commit hash>  upstream.

git-amでpatchを当てて、コミットメッセージを修正してcommit hashの行をつけてます。Fromの行についてはコミットメッセージには含めないで大丈夫です。これはgit-send-emailがつけてくれます。

適当なリポジトリを作って流れを試してみる

適当にリポジトリを作って流れを見てみましょう。branchはこんな感じになっています。signed-offに使うメールアドレスや名前は~/.gitconfigとリポジトリ内の.git/configを使い分けてやってます。

masami@moon:~/test-prg$ git branch 
* main
  test-1.y

ここでこんなコミットを作ったとします。この段階ではAuthorはmasami256です。

commit e8757ffa89dc4aeb1dedec7f811b3fecaf4de75a (HEAD -> main)
Author: masami256 <masami256+test@gmail.com>
Date:   Sat Dec 11 19:43:35 2021 +0900

    Add show() to display argv
    
    Add new function show() to display argv values.
    
    Signed-off-by: masami256 <masami256+test@gmail.com>

git-format-patchでtest-1.yブランチ向けにpatchを作ります。

masami@moon:~/test-prg$ git format-patch -1 e8757ffa89dc4aeb1dedec7f811b3fecaf4de75a test-1.y 
0001-Add-show-to-display-argv.patch

できたpatchはこのようになります。

masami@moon:~/test-prg$ cat 0001-Add-show-to-display-argv.patch 
From e8757ffa89dc4aeb1dedec7f811b3fecaf4de75a Mon Sep 17 00:00:00 2001
From: masami256 <masami256+test@gmail.com>
Date: Sat, 11 Dec 2021 19:43:35 +0900
Subject: [PATCH] Add show() to display argv

Add new function show() to display argv values.

Signed-off-by: masami256 <masami256+test@gmail.com>
---
 test.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/test.c b/test.c
index c0e2886..704995c 100644
--- a/test.c
+++ b/test.c
@@ -1,9 +1,13 @@
 #include <stdio.h>
 
-int main(int argc, char **argv)
+void show(char **av)
 {
-       while (*argv)
-               printf("%s\n", *argv++);
+       while (*av)
+               printf("%s\n", *av++);
+}
 
+int main(int argc, char **argv)
+{
+       show(argv);
        return 0;
 }
-- 
2.33.1

ここで、patchが素直に当たらないようにtest-1.yブランチのコードを適当に変えてみましょう。こんな感じにします。

diff --git a/test.c b/test.c
index c0e2886..b36a1ab 100644
--- a/test.c
+++ b/test.c
@@ -2,8 +2,8 @@
 
 int main(int argc, char **argv)
 {
-       while (*argv)
-               printf("%s\n", *argv++);
+       for (int i = 0; i < argc; i++)
+               printf("%s\n", argv[i]);
 
        return 0;
 }

そして、test-1.yブランチから適当なブランチ(ここではtest-1.y-work)を作ってそこでgit-amを実行すると当然patchが当たらないのでエラーになります。

masami@moon:~/test-prg$ git am --reject 0001-Add-show-to-display-argv.patch
Applying: Add show() to display argv
Checking patch test.c...
error: while searching for:
#include <stdio.h>

int main(int argc, char **argv)
{
        while (*argv)
                printf("%s\n", *argv++);

        return 0;
}

error: patch failed: test.c:1
Applying patch test.c with 1 reject...
Rejected hunk #1.
Patch failed at 0001 Add show() to display argv
hint: Use 'git am --show-current-patch=diff' to see the failed patch
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".

--rejectオプションをつけてるので.rejファイルもできてます。

masami@moon:~/test-prg$ cat test.c.rej 
diff a/test.c b/test.c  (rejected hunks)
@@ -1,9 +1,13 @@
 #include <stdio.h>
 
-int main(int argc, char **argv)
+void show(char **av)
 {
-       while (*argv)
-               printf("%s\n", *argv++);
+       while (*av)
+               printf("%s\n", *av++);
+}
 
+int main(int argc, char **argv)
+{
+       show(argv);
        return 0;
 }

それはさておき、0001-Add-show-to-display-argv.patchを適用するようにコードを修正しましょう。。。

masami@moon:~/test-prg$ git am --continue
Applying: Add show() to display argv

コミットメッセージを修正します。

masami@moon:~/test-prg$ git commit --amend -s

こんな感じのコミットメッセージにしました。commit hashを足したのと、最下部のsigned-offは-sオプションでついたもの、その上のfix ~は自分で書いたものです。

commit 870abfd3042fb950e59c4f1ad9e156d0b68df259 (HEAD -> test-1.y-work)
Author: masami256 <masami256+test@gmail.com>
Date:   Sat Dec 11 19:43:35 2021 +0900

    Add show() to display argv
    
    commit e8757ffa89dc4aeb1dedec7f811b3fecaf4de75a upstream.
    
    Add new function show() to display argv values.
    
    Signed-off-by: masami256 <masami256+test@gmail.com>
    [fix conflict to use show()]
    Signed-off-by: Masami Ichikawa <masami256@gmail.com>

test-1.yブランチ向けにgit-format-patchでpatchを再作成します。

masami@moon:~/test-prg$ git format-patch -1 870abfd3042fb950e59c4f1ad9e156d0b68df259 test-1.y
0001-Add-show-to-display-argv.patch
masami@moon:~/test-prg$ cat 0001-Add-show-to-display-argv.patch
From 870abfd3042fb950e59c4f1ad9e156d0b68df259 Mon Sep 17 00:00:00 2001
From: masami256 <masami256+test@gmail.com>
Date: Sat, 11 Dec 2021 19:43:35 +0900
Subject: [PATCH] Add show() to display argv

commit e8757ffa89dc4aeb1dedec7f811b3fecaf4de75a upstream.

Add new function show() to display argv values.

Signed-off-by: masami256 <masami256+test@gmail.com>
[fix conflict to use show()]
Signed-off-by: Masami Ichikawa <masami256@gmail.com>
---
 test.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/test.c b/test.c
index 4f83ff9..704995c 100644
--- a/test.c
+++ b/test.c
@@ -1,9 +1,13 @@
 #include <stdio.h>
 
-int main(int argc, char **argv)
+void show(char **av)
 {
-       for (int i; i < argc; i++)
-               printf("%s\n", argv[i]);
+       while (*av)
+               printf("%s\n", *av++);
+}
 
+int main(int argc, char **argv)
+{
+       show(argv);
        return 0;
 }
-- 
2.33.1

これをgit-send-emailでメールします。

masami@moon:~/test-prg$ git send-email --to=masami256+test@gmail.com ./0001-Add-show-to-display-argv.patch
./0001-Add-show-to-display-argv.patch
(mbox) Adding cc: masami256 <masami256+test@gmail.com> from line 'From: masami256 <masami256+test@gmail.com>'
(body) Adding cc: masami256 <masami256+test@gmail.com> from line 'Signed-off-by: masami256 <masami256+test@gmail.com>'
(body) Adding cc: Masami Ichikawa <masami256@gmail.com> from line 'Signed-off-by: Masami Ichikawa <masami256@gmail.com>'

From: Masami Ichikawa <masami256@gmail.com>
To: masami256+test@gmail.com
Cc: Masami Ichikawa <masami256@gmail.com>
Subject: [PATCH] Add show() to display argv
Date: Sat, 11 Dec 2021 20:37:23 +0900
Message-Id: <20211211113723.79728-1-masami256@gmail.com>
X-Mailer: git-send-email 2.33.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit

    The Cc list above has been expanded by additional
    addresses found in the patch commit message. By default
    send-email prompts before sending whenever this occurs.
    This behavior is controlled by the sendemail.confirm
    configuration setting.

    For additional information, run 'git send-email --help'.
    To retain the current behavior, but squelch this message,
    run 'git config --global sendemail.confirm auto'.

Send this email? ([y]es|[n]o|[e]dit|[q]uit|[a]ll): 

まず、e[dit]でメールのサブジェクトを変更します。

From 870abfd3042fb950e59c4f1ad9e156d0b68df259 Mon Sep 17 00:00:00 2001
From: masami256 <masami256+test@gmail.com>
Date: Sat, 11 Dec 2021 19:43:35 +0900
Subject: [PATCH for test-1.y] Add show() to display argv

commit e8757ffa89dc4aeb1dedec7f811b3fecaf4de75a upstream.

Add new function show() to display argv values.

Signed-off-by: masami256 <masami256+test@gmail.com>
[fix conflict to use show()]
Signed-off-by: Masami Ichikawa <masami256@gmail.com>

この時点では送信するpatchのコミットメッセージはオリジナルのままです。 変更したらエディタを終了してメニューに戻ります。そして、y[es]で送信しましょう。

(mbox) Adding cc: masami256 <masami256+test@gmail.com> from line 'From: masami256 <masami256+test@gmail.com>'
(body) Adding cc: masami256 <masami256+test@gmail.com> from line 'Signed-off-by: masami256 <masami256+test@gmail.com>'
(body) Adding cc: Masami Ichikawa <masami256@gmail.com> from line 'Signed-off-by: Masami Ichikawa <masami256@gmail.com>'

From: Masami Ichikawa <masami256@gmail.com>
To: masami256+test@gmail.com
Cc: Masami Ichikawa <masami256@gmail.com>
Subject: [PATCH for test-1.y] Add show() to display argv
Date: Sat, 11 Dec 2021 20:37:24 +0900
Message-Id: <20211211113723.79728-1-masami256@gmail.com>
X-Mailer: git-send-email 2.33.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit

Send this email? ([y]es|[n]o|[e]dit|[q]uit|[a]ll): y
OK. Log says:
Server: smtp.gmail.com
MAIL FROM:<masami256@gmail.com>
RCPT TO:<masami256+test@gmail.com>
RCPT TO:<masami256@gmail.com>
From: Masami Ichikawa <masami256@gmail.com>
To: masami256+test@gmail.com
Cc: Masami Ichikawa <masami256@gmail.com>
Subject: [PATCH for test-1.y] Add show() to display argv
Date: Sat, 11 Dec 2021 20:37:24 +0900
Message-Id: <20211211113723.79728-1-masami256@gmail.com>
X-Mailer: git-send-email 2.33.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit

Result: 250 

届いたメールを確認するとこのようになっていて、Fromの行が先頭に追加され、ここはオリジナルのAuthorが設定されているのがわかります。

f:id:masami256:20211211203912p:plain
mail

ここまでの手順をstable kernelの場合も行うことになります。

メーリングリストにpatchを送る

git-format-patchでpatchが作れたらあとはメールを送るだけです。もし、バックポート対象のpatchがバックポートしたいブランチに当てるのに失敗していた場合はloreの該当メールにgit-send-emailでpatchを送る方法が書かれています。こんな感じで。なので指示の通りにメールを送りましょう。そうでない場合はmainlineにpatchを送るときと同様に必要な宛先を調べてto、ccをセットしましょう。

まとめ

この記事ではstable kernelにpatchをバックポートする流れを紹介してみました。どんなpatchをバックポートするかですが、自分は CIP Kernel Team: Helping CIP Sustain Industrial Grade Systemsのkernel teamの作業の一環として脆弱性の修正をバックポートする感じでやってます。

Linux 5.14.4のregressionがどんな感じだったのか調べる

Linux 5.14.4のリリースして同日にregressionが報告されて次の日には5.14.5がリリースされてたんですが、これがどんなバグだったのかなというのを調べたメモです。

バグ報告の内容としては5.14.4でNextcloudっていうphpのwwebアプリケーションを実行するとハングアップするということでした。このときにbisectも行われていて、[PATCH 5.14 011/334] posix-cpu-timers: Force next expiration recalc after itimer resetがbad commitというところまで判明してました。スレッドではpatchの作者さんによる修正patchを送ったよというメールもありますが、5.1.4.5では関連するpatchのrevertにて対応されています。

5.14.5でrevertされたのは2つのpatchでした。5.14.5はこのregressionの修正のみのリリースでした。

まずbad commitのほうでこれはposix-cpu-timers: Force next expiration recalc after itimer resetですね。こちらはこのようなpatchでした。

diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c
index 517be7fd175e..a002685f688d 100644
--- a/kernel/time/posix-cpu-timers.c
+++ b/kernel/time/posix-cpu-timers.c
@@ -1346,8 +1346,6 @@ void set_process_cpu_timer(struct task_struct *tsk, unsigned int clkid,
            }
        }
 
-       if (!*newval)
-           return;
        *newval += now;
    }

もう一つはtime: Handle negative seconds correctly in timespec64_to_ns()です。こちらはこんな感じのpatchでした。

--- a/include/linux/time64.h
+++ b/include/linux/time64.h
@@ -25,7 +25,9 @@ struct itimerspec64 {
 #define TIME64_MIN            (-TIME64_MAX - 1)
 
 #define KTIME_MAX         ((s64)~((u64)1 << 63))
+#define KTIME_MIN          (-KTIME_MAX - 1)
 #define KTIME_SEC_MAX         (KTIME_MAX / NSEC_PER_SEC)
+#define KTIME_SEC_MIN          (KTIME_MIN / NSEC_PER_SEC)
 
 /*
  * Limits for settimeofday():
@@ -124,10 +126,13 @@ static inline bool timespec64_valid_sett
  */
 static inline s64 timespec64_to_ns(const struct timespec64 *ts)
 {
-   /* Prevent multiplication overflow */
-   if ((unsigned long long)ts->tv_sec >= KTIME_SEC_MAX)
+   /* Prevent multiplication overflow / underflow */
+   if (ts->tv_sec >= KTIME_SEC_MAX)
        return KTIME_MAX;
 
+   if (ts->tv_sec <= KTIME_SEC_MIN)
+       return KTIME_MIN;
+
    return ((s64) ts->tv_sec * NSEC_PER_SEC) + ts->tv_nsec;
 }

patchだけだとわかりにくいので全体を見てみましょう。。。まずはset_process_cpu_timer()から。revert後の5.14.5ではこんな関数です。

void set_process_cpu_timer(struct task_struct *tsk, unsigned int clkid,
               u64 *newval, u64 *oldval)
{
    u64 now, *nextevt;

    if (WARN_ON_ONCE(clkid >= CPUCLOCK_SCHED))
        return;

    nextevt = &tsk->signal->posix_cputimers.bases[clkid].nextevt;
    now = cpu_clock_sample_group(clkid, tsk, true);

    if (oldval) {
        /*
        * We are setting itimer. The *oldval is absolute and we update
        * it to be relative, *newval argument is relative and we update
        * it to be absolute.
        */
        if (*oldval) {
            if (*oldval <= now) {
                /* Just about to fire. */
                *oldval = TICK_NSEC;
            } else {
                *oldval -= now;
            }
        }

        if (!*newval)
            return;
        *newval += now;
    }

    /*
    * Update expiration cache if this is the earliest timer. CPUCLOCK_PROF
    * expiry cache is also used by RLIMIT_CPU!.
    */
    if (*newval < *nextevt)
        *nextevt = *newval;

    tick_dep_set_signal(tsk, TICK_DEP_BIT_POSIX_TIMER);
}

↓の部分がpatchが変更する箇所ですね。

     if (!*newval)
            return;
        *newval += now;

patchではif文のところを消して常にnewvalが指す値にnowの値を足すようになってます。元のコードではnewvalの指す値が0なら何もしないでreturnするんですが、patchではif文を消してるので常にその先が実行されるようになりますね。そうすると、今までは呼ばれることがなかったtick_dep_set_signal()が実行されるようになって本来送る必要のないシグナルを送ってしまうと修正patchのコミットメッセージにも書かれています。これがregressionとして現れてたんですかねぇ。

もう一つのrevertはtime: Handle negative seconds correctly in timespec64_to_ns()です。こちらの変更箇所はtimespec64_to_ns()です。これもrevert後のコードを見るとこんな感じになってます。

static inline s64 timespec64_to_ns(const struct timespec64 *ts)
{
    /* Prevent multiplication overflow */
    if ((unsigned long long)ts->tv_sec >= KTIME_SEC_MAX)
        return KTIME_MAX;

    return ((s64) ts->tv_sec * NSEC_PER_SEC) + ts->tv_nsec;
}

patchでの変更は次のようになっています。

-    /* Prevent multiplication overflow */
-   if ((unsigned long long)ts->tv_sec >= KTIME_SEC_MAX)
+   /* Prevent multiplication overflow / underflow */
+   if (ts->tv_sec >= KTIME_SEC_MAX)
        return KTIME_MAX;
 
+   if (ts->tv_sec <= KTIME_SEC_MIN)
+       return KTIME_MIN;
+

timespec64構造体のtv_secはtime64_tでこれは__s64のtypedefとなってます。ということでtv_secは符号ありの64bit整数値です。ということで、コミットメッセージにも書いてますが、もともとはs64の値をunsigned long long(64bitの符号なし整数ですね)にキャストしてチェックしているので負の整数の場合にKTIME_MAXを返すことになりますと。で、patchでは符号ありの整数としてKTIME_SEC_MAXを超えたり、KTIME_SEC_MIN以下になる場合には上限値や下限値を返す感じにしています。ここだけ見るとそうだなと思うんですが、今までと返る値が変わることによる影響もあったんでしょうか。。。

いずれにせよ、5.14.5では2つのpatchのrevertが行われてregressionが修正されたということでした。

Raspberry Piのdtoverlay・dtparam、dtbそしてブートプロセスのメモ

 はじめに

この記事はRaspberry Pi 3B+の実際の挙動と公式のドキュメントから大体こんな感じだろうというところで書いてるので正確さは期待しないでください。

下図のような構成でLinux kernel(Raspberry Pi向けのカーネルじゃなくて、mainlineとかstable treeのカーネル)を使うときにdtoverlay・dtparamを使う方法を調べたメモです。なので、自分でビルドしたカーネルを使う必要がなければRaspberry Pi OSとかmeta-raspberrypiを使うのが良いかと思います。

-> Raspberry Piのブートローダー  
  -> u-boot 
    -> Linux kernel

Linux kernel source とdtbファイル

mainlineのカーネルにはbcm2837-rpi-3-b-plus.dts等のdtsファイルがあるのでこれをビルドして使えばmainlineのカーネルでもRaspberry Piで動きます。だがしかし、config.txtでdtoverlay・dtparamを使うのはできませんでした。なんでかというと、Raspberry Piカーネルとmainlineのカーネルにあるdtsファイルを見比べるとRaspberry Piカーネルのほうにはoverridesがあって設定をoverrideできるようになってるんですね。例えばarm/boot/dts/bcm2710-rpi-3-b-plus.dtsだとこんな感じで設定があります。

/ {
    __overrides__ {
        act_led_gpio = <&act_led>,"gpios:4";
        act_led_activelow = <&act_led>,"gpios:8";
        act_led_trigger = <&act_led>,"linux,default-trigger";

        pwr_led_gpio = <&pwr_led>,"gpios:4";
        pwr_led_activelow = <&pwr_led>,"gpios:8";
        pwr_led_trigger = <&pwr_led>,"linux,default-trigger";

        eee = <&eth_phy>,"microchip,eee-enabled?";
        tx_lpi_timer = <&eth_phy>,"microchip,tx-lpi-timer:0";
        eth_led0 = <&eth_phy>,"microchip,led-modes:0";
        eth_led1 = <&eth_phy>,"microchip,led-modes:4";
        eth_downshift_after = <&eth_phy>,"microchip,downshift-after:0";
        eth_max_speed = <&eth_phy>,"max-speed:0";
    };
};

上記のブロックはmainlineのほうにはありません。そんなわけでdtoverlay・dtparamを手軽に使うならdtbファイルはブートローダーなんかと一緒に配布されてるdtbファイル(firmwareのリポジトリ)を使いましょうという感じです。

dtoverlay・dtparamはどのように処理されてるか

Raspberry Piブートローダーが自身のデバイスに合う適切なdtbファイルを読んでくれます(公式ドキュメントのどこかにそんなことが書いてありました)。そして、config.txtに記載されてるdtoverlay・dtparamの記述に沿ってメモリ上に読み込まれてるdtbファイルのデータを更新します。そしてそのメモリ上で更新したdtbファイルを使うという流れです。

u-bootをブートローダーとして使いたい場合

Raspberry Piブートローダーはすでにdtbファイルを読み込んでdtoverlay・dtparamの設定もメモリ上で反映させてます。そのため、u-bootがfatloadとかして自分でdtbファイルを読んじゃうと意味がありません。なので、config.txtでdtbファイルを読み込むアドレスを指定し、u-bootのほうはそのアドレスからdtbファイルを読む感じにします。例えばconfig.txtで下記のように読み込むアドレスを指定しておきます。

device_tree_address=0x02600000

u-bootのほうはこんな感じでfdtコマンドを使ってconfig.txtで指定したメモリアドレスを利用します。

setenv fdt_addr_r 0x02600000
fdt addr ${fdt_addr_r}

こんな感じにすればRaspberry Piブートローダーが設定したdtbファイルをu-bootから読めて、それを更にカーネルの起動に利用することができます。

fanotify(7)めも

fapolicydがどのようにアプリケーションの実行を禁止しているのだろうか?と思って調べためもです。

fapolicydがアプリケーションの実行を禁止する仕組み

仕組みとしてはfanotifyの仕組みを使ってファイルが実行のために開かれた場合に通知を受け取り、設定されたルールを調べて実行可能かどうかを返すということをしてます。

fanotify

fanotifyの機能を使うには2つの関数があります。fanotify_init(2)fanotify_mark(2)の2つです。fanotify_init(2)で初期化をしてファイルディスクリプタを得ます。次にfanotify_mark(2)で通知を受け取りたいイベントなどを設定します。イベントの受信はpoll(2)を使います。pollfd構造体の配列のうち1つはfanotify_init(2)によって初期化したfdを設定します。

実行の許可

fanotify_response構造体のresponse変数にFAN_ALLOWもしくはFAN_DENYの値を設定することでその後の処理の継続を許可するか拒否するか設定します。レスポンスはfdに対して書き込みます。

サンプル

fanotify(7)にあるサンプルコードベースに作ってみました。実行するアプリケーションが/tmp/lsの場合だけ許可しません。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/fanotify.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
#include <limits.h>
#include <unistd.h>

static void handle_events(int fd)
{
    struct fanotify_event_metadata *metadata;
    struct fanotify_event_metadata buf[256];
    struct fanotify_response response;
    ssize_t len;

    while (1) {
        len = read(fd, buf, sizeof(buf));
        if (len == -1 && errno != EAGAIN) {
            perror("read()");
            exit(-1);
        }

        if (len <= 0)
            break;

        metadata = buf;

        while (FAN_EVENT_OK(metadata, len)) {
            if (metadata->vers != FANOTIFY_METADATA_VERSION) {
                fprintf(stderr, "fanotify version mismatch\n");
                exit(-1);
            }

            if (metadata->fd >= 0) {
                if (metadata->mask & FAN_OPEN_EXEC_PERM) {
                    char *proc_path, path[PATH_MAX] = { '\0' };
                    ssize_t path_len;
                    
                    asprintf(&proc_path, "/proc/self/fd/%d", metadata->fd);
                    path_len = readlink(proc_path, path, sizeof(path) - 1);
                    if (path_len == 1) {
                        perror("readlink()");
                        exit(-1);
                    }

                    if (!strcmp(path, "/tmp/ls")) {
                        printf("[+]Deny execute application %s (pid:%d)\n", path, metadata->pid);
                        response.response = FAN_DENY;
                    } else
                        response.response = FAN_ALLOW;

                    response.fd = metadata->fd;
                    write(fd, &response, sizeof(response));

                    free(proc_path);
                }
            }

            close(metadata->fd);
            metadata = FAN_EVENT_NEXT(metadata, len);
        }
    }
}

int main(int argc, char **argv)
{
    int fd;
    int poll_num;
    struct pollfd fds[2];
    
    fd = fanotify_init(FAN_CLOEXEC | FAN_NONBLOCK | FAN_CLASS_CONTENT,
        O_RDONLY | O_LARGEFILE | O_CLOEXEC | O_NOATIME);
    
    if (fd == -1) {
        perror("fanotify_init()");
        exit(-1);
    }

    if (fanotify_mark(fd, FAN_MARK_ADD | FAN_MARK_MOUNT,
        FAN_OPEN_EXEC_PERM, -1, "/tmp") == -1 ) {
        perror("fanotify_mark()");
        exit(-1);
    }

    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;
    fds[1].fd = fd;
    fds[1].events = POLLIN;

    printf("[+]Press ctrl-c to quit program\n");

    while (1) {
        poll_num = poll(fds, 2, -1);
        if (poll_num == -1) {
            if (errno == EINTR)
                continue;
            perror("poll()");
            exit(-1);
        }

        if (poll_num > 0) {
            if (fds[1].revents & POLLIN)
                handle_events(fd);
        }
    }

    return 0;
}

このコードを動かすとこんな感じです。

masami@kerntest:~/prevent_exec$ sudo ./a.out
[+]Press ctrl-c to quit program
[+]Deny execute application /tmp/ls (pid:3058)

実行を止められる側はこんな感じ。

masami@kerntest:~/prevent_exec$ /tmp/ls2
a.out  prevent_exec.c
masami@kerntest:~/prevent_exec$ /tmp/ls
-bash: /tmp/ls: Operation not permitted

Linuxプログラミングインタフェース

Linuxプログラミングインタフェース

マイペースなOSSコントリビュート生活 fedora編

この記事はLinux Advent Calendar 2020の17日目の記事です。

OSSのコントリビュート方法はpatchを書いたりドキュメントを翻訳したり、バグレポートしたりと色々な方法がありますね。fedoraでテストというとリリース前のベータ版のテスト、パッケージがアップデートされた際のテスト(QA:Updates Testing - Fedora Project Wiki)、テストイベントのTest Days(QA/Test Days - Fedora Project Wiki)などがあります。

updates-testing

このなかで手軽なものとしてはupdates testingがあります。fedoranはパッケージがアップデートされる場合、最初にupdates-testingリポジトリに更新されたパッケージが入ります。その後、基本的には以下のルールに則ってパッケージがupdateリポジトリに入るか更新がリジェクトされるかのいずれかになります。

By default, test updates with karma of 3 are automatically sent out as full official updates, while test updates with karma of -3 are automatically withdrawn from the testing repository. 

テストした人が実際にパッケージを使ってみて問題なかった・修正されたと書かれているバグが直ってたなんてことを確認したりしてみて結果をフィードバックします。これはコマンドラインのアプリでも出来ますし、bodhiと呼ばれるWebインターフェースもあります。

bodhi.fedoraproject.org

bodhiのパッケージの画面ではこのような感じでチェック項目があります。これはkernel-5.9.14-200.fc33のときのものです。バグフィックスに関しては自分の環境だと再現できないとかあると思うのでそういう場合はグレーの👍を選択します。問題が合ったときは👎を選択し、特に問題なかったら緑の👍を選択する感じです。

f:id:masami256:20201227232230p:plain

テストの内容はカーネルの例のように指定されている場合もありますし、無い場合もあります。基本的には指定されたテストケース以外のことについては実際に使ってみてOKかどうかってところでみんなやってると思います。全部のアップデートをテストしようとか気張らずに自分がよく使ってるものだけでも簡単にやろうくらい気軽な感じでできます😊 セキュリティアップデートもupdates-testingリポジトリに最初に入るので更新して使ってみた結果のフィードバックをするのって割と大事かななんて思います。

Test Days(Test Week)

こちらはカーネルGnomeなんかの主要なパッケージのメジャーバージョンが上がるような時に行われることが多いです。カーネルですとfedoraはリリース後にもカーネルのメジャーバージョンは上がりますのでイベントに参加して自分の環境で問題ないか確認しておくのは良いかもしれません。Test Daysではiosイメージ、リリースしているfedoraのバージョン用のrpmファイルが用意されますのでお好きな方法を選択できます。カーネルだとテスト内容はupdates-testingと一緒です。こちらも割と手軽ですが通常のupdates-testingではなく専用のイベントが用意されるのでシステム的にインパクトの大きな変更が入るということなので場合によっては自分の環境だと問題があるってこともあると思います。まあ、そういうのを見つけるためのイベントなわけですが😃

カーネルのTest Weekで自分が実際にバグに当たってupstreamに報告とパッチを投げたり・テストしたり、fedoraで修正されたことをupdates-testingで確認したときのログが↓ですので興味がある人は読んでみてください🙋 kernhack.hatenablog.com


fedoraに限らず使ってみて結果をフィードバック(特にバグレポート)するというのは重要な貢献だと思いますので、OSSに貢献したいけど何をすれば良いか🤔と考えている人は新しいバージョンを使ってみて結果をフィードバックするというのも検討してみてはいかがでしょうか👍

OSSライセンスの教科書

OSSライセンスの教科書

1999年発行(日本語翻訳版)のLinuxカーネル解説本を見返してみる

この記事はLinux Advent Calendar 2020の24日目の記事です。

自分がLinuxを使いだした頃、OSはどうやってブートしてんるんだろう🤔とか色々知りたくて買ったのが↓の本です。

f:id:masami256:20201225194100j:plain
Linuxカーネルインターナル

買った当時は知識がなくて読むのが大変だったけど今はそれなりに読めるようになり、さすがに成長したなとも思ってみたり😃 初版が1999年6月25日と書いてあるので2000年台の始めの頃に買ったんでしょう。それにしても翻訳版の初版が発行された時点からでも21年前の本ということか。

この本が対象としているのはLinux 2.0です。目次は次のようになってます。メモリ管理、ファイルシステム、モジュール、ネットワーク等々ありますね。基本的な機能は変わってないので現在のカーネルでこの目次の内容に沿ってコードを読んでみるというのは面白いかもしれません。この記事を書いている時点での最新のLinuxのバージョンは5.10.2です。本で説明されてるこの構造体は今でもあるのかな?と思ったときは5.10.2で調べてます。本書は付属としてLinux 2.0のソースコード入りCDが付いてます。

序文

本書の序文はLinusさんが書いています。「OSの開発は今も昔もエキサイティングなプロジェクトだ。でも、カーネルの内部を詳しく知ろうとするとドキュメントが足りないのが難点で、ハックしようと思ったらコードを読むしかなかった。それは良いことなんだけど、本書のようにLinuxの使用とカーネルにつて説明する本書のようなドキュメントが出たのは良いね」といった感じのことが書かれてます。

はじめに

本書は著者さんによる同名の著書の第2版で、Linux2.0をカバーしていると書いてあるので第1版は1.xの解説をしてたということなんでしょう。本書の方針として上げている「Linux はオープンなOS。隠された秘密は何もない。問題に遭遇したらソースコードに当たれば良い」ってのが個人的に好きです。

第1章 Linuxオペレーティングシステム

Linuxがどんなものかの説明です。機能や、Linuxディストリビューションとは?といったことの説明が書かれています。

第2章 カーネルコンパイル

カーネルハックをする上でカーネルコンパイルは必須ですからね。最近のカーネルに関しては@progrunner17さんによるLinuxカーネルビルド大全が詳しいです。本書ではカーネルのコードの場所は/usr/src/linuxを使用しています。最近は任意なところにgitでコードをクローンしてビルドできますが当時は/usr/src/linuxが標準だったんですね。この章ではこのディレクトリにはこの機能のコードがあるよなんてのも軽く解説しています。本章ではカーネルコンパイル方法、i386以外のarchを使いたい場合のMakefileの変更方法なんかも説明されてます。

第3章 カーネルの概要

章の流れとしてはカーネルとはなにか?といったところの説明、プロセスとタスク、システムコール、割り込みルーチン、プロセスとスレッドなどの用語の説明カーネルのデータ構造、シグナル、割り込み、カーネルの最初のc関数、スケジューラ、システムコールの実装(仕組み、fork()などいくつかのシステムコールの説明)など盛りだくさんです。この章で説明されている構造体のtask_struct、mm_struct、fs_struct、inode、fileなどは今でも重要な立ち位置ですね。それとは逆にプロセスを登録するプロセステーブルは今ではもうありませんね。メモリの確保・解放だと__get_free_pages()、kmalloc()、kfree()なんかも今でも使われてますね。

第4章 メモリ管理

Linux 2.0の時点で複数のアーキテクチャに対応しているのでまずはアーキテクチャ非依存のモデルについて説明しています(仮想アドレス空間など)。次はi386のページテーブルの説明がきて、LinuxのPGD・PMD・PTEの説明になります。そしてvm_area_struct構造体(これも今もありますね)、brkシステムコールmmapシステムコールの説明ときて、カーネルが使うセグメント(x86のセグメント)の説明があります。つぎにkmallo()・kfree()やvmalloc()・vfree()の説明です。この次にバッファキャッシュの説明があります。ここで説明されているbuffer_head構造体もいまでもあります。bdflushの説明もあります。バッファキャッシュの管理・利用方法の説明もここで行っています。あとはswap処理、空きページの管理(今でもページの管理に使うpage構造体はこの当時からあります)。最後にページフォルトの処理を説明して本章は終わりです。

第5章 プロセス間通信

まずはカーネル内の処理で同期を取る方法の説明があります。キューを使って寝て待って、処理が終わったら起こしてもらったり、セマフォを使うなどの説明です。fcntl(2)によるファイルのロックの処理についても説明されてます。あとはpipeの実装、ptrace(2)の仕様、System V IPC、共有メモリ、Unixドメインソケットを使ったプロセス間通信などです。この辺りの機能は今でも使われますね。

第6章 Linuxファイルシステム

VFSの説明、カーネルでのファイルシステムの表現、マウント処理、スーパーブロックの処理(super_operations構造体に含まれる各種関数の説明)、inodeの処理(inode_operations構造体に含まれる各種関数の説明)、file構造体の説明、ファイル操作(file_operations構造体に含まれる各種関数の説明)、ファイルを開くときの処理、procファイルシステムext2ファイルシステムのデータ構造、ブロックへの配置アルゴリズムなどの説明があります。ジャーナリングファイルシステムについての説明はさすがにないですが今だと当たり前にある機能とも言えるので現在カーネルの解説本を書くなら必須項目になるんでしょうね。

第7章 Linuxデバイスドライバ

ブロックデバイス・キャラクターデバイス、メジャー・マイナー番号の説明があり、割り込み・ポーリングの説明、ハードウェアの検出などの説明があります。ドライバの実装の説明としてはPC内蔵スピーカーを例にしたりしてます。デバドラの実装の説明では初期化、open・read/write・ioctlの実装方法など説明してます。また、DMAの操作も説明してます。

第8章 ネットワークの実装

ネットワークの実装ではsk_buff構造体の説明、proto構造体の説明など構造体の説明の他に、ネットワークデバイスの実装の説明があります。プロトコルの説明としてはarp、ipの説明があります。ルーティングやパケットフィルタについても説明されています。そして、tcpの説明ももちろんあります。

第9章 モジュールとデバッグ

カーネルモジュールの説明です。モジュールがどのようにロードされるか、モジュールのタイプ別にどのような関数を実装するべき(ファイルシステムならregister_filesystem()と言った感じで)かといった説明などあります。 モジュールの例としてはPCMCIAのモジュールを使って説明してます。デバッグでは今でも使われるprintkデバッグgdbを使ったデバッグ方法について触れられています。printkデバッグでは最良のデバッガ:printk()と書かれています。この当時だとカーネルデバッグというとprintk()というのが主だったんでしょうね。カーネルのビルド・再起動に時間はかかるけど手軽さは確かにありますが今ならftrace、perf、eBPFなどがお薦めの手段でしょうか。

第10章 マルチプロセッシング

この章ではSMPの実装におけるカーネルの初期化、cpu間の通信、割り込み処理についての説明があります。smp対応のカーネルコンパイルする場合はMakefileSMP = 1の部分のコメントを削除してねって説明がありますが、時代を感じるところですね。

Appendix A システムコール

この章は色々なシステムコールについての説明です。例えばfork()ならカーネルのどのファイルにこのシステムコールの実装が合って、プロトタイプはこうでシステムコールの仕様はこんなんだよといった感じの説明があります。カーネルよりなmanページと言った感じでしょうか。システムコールはプロセス管理系、ファイルシステム系、プロセス間通信系、メモリ管理系というような感じでカテゴライズされています。

Appendix B カーネル関連のコマンド

manのセクション8というかpsだったりfreeだったりといったコマンドの説明があります。また、pid1のinitプロセスについても説明があります。/etc/inittabの説明もあります。ほかにはstrace、shutdown、ifconfig、traceroute、mountコマンドのなどの説明があります。

Appendix C Procファイルシステム

/procにあるファイルの説明です。14ページほどあるので結構なボリュームですよね。

Appendix D ブートプロセス

BIOSから始まるブートプロセスの説明です。ブートローダーのLILOについての説明もあります。当時はブートローダーといえばLILOでしたね。カーネルの再構築後にLILOのコマンド忘れて起動できないとかよくやったもんです😭 LILOの説明もファイルの書式、オプション、エラーメッセージの意味、起動時の表示メッセージの意味などの説明がありかなり親切です。LIで止まっている場合の原因はXXXだよみたいな説明があってホント親切だと思います。

Appendix E 重要なカーネル関数

カーネルのコードを書く時に使う関数の説明ですmanのセクション9に近いでしょうか。


というわけで1999年発行のLinuxカーネル解説本を見返してみたわけですが、基本的な機能は変わってないのでというかメモリ管理とかファイルシステムは今でも重要な機能ですが実装についてはだいぶ変わっているのでその辺の差分を見ていくとカーネルの進化の歴史を辿れますね。

この目次の内容でLinux 5.10版も見てみたいですね👍 

vgrep便利ですねという話

この記事はLinuxその2 Advent Calendar 2020の4日目の記事です。

今回はtoolネタです。vgrepというgrep系のツールがあって結構便利です。これは今年のOpen Source Summit + Embedded Linux Conference North America 2020でのAsk the Expert SessionでGreg KHさんがコードを調べる時にvgrepを使ってると言っていたので知りました。

使い方はわりと簡単なのでREADME.mdを見ればOKだと思います。

grepとの比較

grep系のコマンドなので速さはどうなの?というところですが、qemu上のこんなriscv64環境(cpu4個、メモリ8GB)で試してみます。fedoraのriscv64版にもvgrepパッケージあります。

masami@fedora-riscv:~/linux-kernel$ uname -a
Linux fedora-riscv 5.10.0-rc7-ktest+ #8 SMP Mon Dec 7 10:35:20 EST 2020 riscv64 riscv64 riscv64 GNU/Linux

vgrepの実行。

masami@fedora-riscv:~/linux-kernel$ sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"
masami@fedora-riscv:~/linux-kernel$ time vgrep --no-less "kmem_cache_alloc(" >/dev/null

real    0m49.499s
user    0m20.440s
sys     1m2.571s

find+grep(with xargs)の実行。

masami@fedora-riscv:~/linux-kernel$ sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"
masami@fedora-riscv:~/linux-kernel$ time (find . -name "*.c" -o -name "*.h" | xargs grep "kmem_cache_alloc(" >/dev/null)

real    1m0.191s
user    0m4.412s
sys     0m48.670s

find+grepはコマンドをパイプ繋いでいるしというのはありますがvgrep良い感じですね。

使ってみる

まずは普通に検索してみます(ここからは普通にx86_64のデスクトップ環境で)。

masami@moon:~/linux-kernel$ vgrep "kmem_cache_alloc("
Index File                                                     Line Content
    0 Documentation/core-api/memory-allocation.rst              163 cache allocator. The cache should be set up with kmem_cache_create() or
    1 arch/arm64/mm/pgd.c                                        54 pgd_cache = kmem_cache_create("pgd_cache", PGD_SIZE, PGD_SIZE,
    2 arch/powerpc/kernel/rtas_flash.c                          713 flash_block_cache = kmem_cache_create("rtas_flash_cache",
    3 arch/powerpc/kvm/book3s_64_mmu_radix.c                   1442 kvm_pte_cache = kmem_cache_create("kvm-pte", size, size, 0, pte_ctor);
    4 arch/powerpc/kvm/book3s_64_mmu_radix.c                   1448 kvm_pmd_cache = kmem_cache_create("kvm-pmd", size, size, 0, pmd_ctor);
    5 arch/powerpc/kvm/book3s_mmu_hpte.c                        377 hpte_cache = kmem_cache_create("kvm-spt", sizeof(struct hpte_cache),
    6 arch/powerpc/mm/init-common.c                             127 new = kmem_cache_create(name, table_size, align, 0, ctor(shift));
    7 arch/powerpc/perf/hv-24x7.c                              1732 hv_page_cache = kmem_cache_create("hv-page-4096", 4096, 4096, 0, NULL);
    8 arch/powerpc/platforms/cell/spufs/inode.c                 786 spufs_inode_cache = kmem_cache_create("spufs_inode_cache",
    9 arch/powerpc/platforms/pseries/setup.c                    315 dtl_cache = kmem_cache_create("dtl", DISPATCH_LOG_BYTES,
   10 arch/powerpc/sysdev/xive/native.c                         518 xive_provision_cache = kmem_cache_create("xive-provision",
   11 arch/s390/kernel/nmi.c                                     86 mcesa_cache = kmem_cache_create("nmi_save_areas", size, size, 0, NULL);
   12 arch/s390/mm/pgalloc.c                                    557 base_pgt_cache = kmem_cache_create("base_pgt", sz, sz, 0, NULL);
   13 arch/s390/pci/pci.c                                       782 zdev_fmb_cache = kmem_cache_create("PCI_FMB_cache", sizeof(struct zpci_fmb),
   14 arch/s390/pci/pci_dma.c                                   635 dma_region_table_cache = kmem_cache_create("PCI_DMA_region_tables",
   15 arch/s390/pci/pci_dma.c                                   641 dma_page_table_cache = kmem_cache_create("PCI_DMA_page_tables",
   16 arch/sh/kernel/cpu/sh4/sq.c                               379 sq_cache = kmem_cache_create("store_queue_cache",
   17 arch/sh/kernel/dwarf.c                                   1171 dwarf_frame_cachep = kmem_cache_create("dwarf_frames",
   18 arch/sh/kernel/dwarf.c                                   1175 dwarf_reg_cachep = kmem_cache_create("dwarf_regs",
   19 arch/sh/kernel/process.c                                   58 task_xstate_cachep = kmem_cache_create("task_xstate", xstate_size,
   20 arch/sh/mm/pgtable.c                                       22 pgd_cachep = kmem_cache_create("pgd_cache",
   21 arch/sh/mm/pgtable.c                                       26 pmd_cachep = kmem_cache_create("pmd_cache",
   22 arch/sparc/mm/tsb.c                                       345 pgtable_cache = kmem_cache_create("pgtable_cache",
   23 arch/sparc/mm/tsb.c                                       358 tsb_caches[i] = kmem_cache_create(name,
   24 arch/x86/events/intel/lbr.c                              1596 return kmem_cache_create("x86_lbr", size, align, 0, NULL);
   25 arch/x86/kvm/mmu/mmu.c                                   5876 pte_list_desc_cache = kmem_cache_create("pte_list_desc",
   26 arch/x86/kvm/mmu/mmu.c                                   5882 mmu_page_header_cache = kmem_cache_create("kvm_mmu_page_header",
   27 arch/x86/kvm/x86.c                                       7876 x86_fpu_cache = kmem_cache_create("x86_fpu", sizeof(struct fpu),
   28 arch/x86/mm/pgtable.c                                     382 pgd_cache = kmem_cache_create("pgd_cache", PGD_SIZE, PGD_ALIGN,
   29 block/bio-integrity.c                                     471 bip_slab = kmem_cache_create("bio_integrity_payload",
   30 block/bio.c                                               107 slab = kmem_cache_create(bslab->name, sz, ARCH_KMALLOC_MINALIGN,

検索結果はデフォルトではページャとしてlessが使われます。表示内容は次のような感じです。

  1. 見つかったファイルの検索結果の番号
  2. ファイルのpath
  3. 見つかった行の行番号
  4. 検索結果の文字列

これだけだと単に検索しただけですが、この検索結果を利用してさらに操作ができます。ここで検索結果27番のファイルをエディタで見たいとします。その場合は次のように-sオプションに検索結果の番号を引数で渡すとEDITOR環境変数に設定されているエディタで該当ファイルの該当行の辺りを開いてくれます。

masami@moon:~/linux-kernel$ vgrep -s 27

全体的に前後の行をみたいという時(grepコマンドの-A、-Bオプションみたいに)はc/contextコマンドを使います。つぎの例だと検索で見つかった行を中心として前後5行ずつ表示します。

masami@moon:~/linux-kernel$ vgrep -s c5
~~~
--- 26 arch/x86/kvm/mmu/mmu.c ---------------------------------------
5877                                        sizeof(struct pte_list_desc),
5878                                        0, SLAB_ACCOUNT, NULL);
5879    if (!pte_list_desc_cache)
5880            goto out;
5881 
5882    mmu_page_header_cache = kmem_cache_create("kvm_mmu_page_header",
5883                                              sizeof(struct kvm_mmu_page),
5884                                              0, SLAB_ACCOUNT, NULL);
5885    if (!mmu_page_header_cache)
5886            goto out;
5887 
--- 27 arch/x86/kvm/x86.c ------------------------------------------
7871            r = -EOPNOTSUPP;
7872            goto out;
7873    }
7874 
7875    r = -ENOMEM;
7876    x86_fpu_cache = kmem_cache_create("x86_fpu", sizeof(struct fpu),
7877                                      __alignof__(struct fpu), SLAB_ACCOUNT,
7878                                      NULL);
7879    if (!x86_fpu_cache) {
7880            printk(KERN_ERR "kvm: failed to allocate cache for x86 fpu\n");
7881            goto out;

特定のファイル(1つもしくは複数)のみを対象にするなら検索結果の番号をさらに引数として渡します。この例では27と28、それに30番を指定してます。

masami@moon:~/linux-kernel$ vgrep -s c3 27-28,30
--- 27 arch/x86/kvm/x86.c ------------------------------------------
7873    }
7874 
7875    r = -ENOMEM;
7876    x86_fpu_cache = kmem_cache_create("x86_fpu", sizeof(struct fpu),
7877                                      __alignof__(struct fpu), SLAB_ACCOUNT,
7878                                      NULL);
7879    if (!x86_fpu_cache) {
--- 28 arch/x86/mm/pgtable.c ---------------------------------------
 379     * page for pgd. We are able to just allocate a 32-byte for pgd.
 380     * During boot time, we create a 32-byte slab for pgd table allocation.
 381     */
 382    pgd_cache = kmem_cache_create("pgd_cache", PGD_SIZE, PGD_ALIGN,
 383                                  SLAB_PANIC, NULL);
 384 }
 385 
--- 30 block/bio.c ------------------------------------------------
 104    bslab = &bio_slabs[entry];
 105 
 106    snprintf(bslab->name, sizeof(bslab->name), "bio-%d", entry);
 107    slab = kmem_cache_create(bslab->name, sz, ARCH_KMALLOC_MINALIGN,
 108                             SLAB_HWCACHE_ALIGN, NULL);
 109    if (!slab)
 110            goto out_unlock;

ディレクトリごとに何個のhitが合ったかなんてのも見れます。

masami@moon:~/linux-kernel$ vgrep -s tree
Matches Directory
    493 
      1 Documentation
      1 Documentation/core-api
     28 arch
      1 arch/arm64
      1 arch/arm64/mm
      9 arch/powerpc
      1 arch/powerpc/kernel
      3 arch/powerpc/kvm
      1 arch/powerpc/mm
      1 arch/powerpc/perf
      2 arch/powerpc/platforms
      1 arch/powerpc/platforms/cell
      1 arch/powerpc/platforms/cell/spufs
      1 arch/powerpc/platforms/pseries
      1 arch/powerpc/sysdev
      1 arch/powerpc/sysdev/xive
      5 arch/s390
      1 arch/s390/kernel
      1 arch/s390/mm
      3 arch/s390/pci
      6 arch/sh
      4 arch/sh/kernel
      1 arch/sh/kernel/cpu
      1 arch/sh/kernel/cpu/sh4
      2 arch/sh/mm
      2 arch/sparc
      2 arch/sparc/mm
      5 arch/x86
      1 arch/x86/events
      1 arch/x86/events/intel
      3 arch/x86/kvm
      2 arch/x86/kvm/mmu
      1 arch/x86/mm
      6 block
    141 drivers
      1 drivers/acpi
      9 drivers/block
      1 drivers/block/aoe
      4 drivers/block/drbd
      1 drivers/block/xen-blkback
      7 drivers/crypto
      1 drivers/crypto/axis
      2 drivers/crypto/caam

別の検索を行いたい場合は最初にやったみたいにvgrep 検索ワードでも出来ますし、g/grepコマンドを利用して再検索することもできます。

masami@moon:~/linux-kernel$ vgrep -s g "bootconfig"
Index File                                                   Line Content
    0 Documentation/admin-guide/bootconfig.rst                  3 .. _bootconfig:
    1 Documentation/admin-guide/bootconfig.rst                 81 overriding the default value by adding (partial) custom bootconfigs
    2 Documentation/admin-guide/bootconfig.rst                 82 without parsing the default bootconfig.
    3 Documentation/admin-guide/bootconfig.rst                126 /proc/bootconfig
    4 Documentation/admin-guide/bootconfig.rst                129 /proc/bootconfig is a user-space interface of the boot config.
    5 Documentation/admin-guide/bootconfig.rst                143 [initrd][bootconfig][padding][size(le32)][checksum(le32)][#BOOTCONFIG\n]
    6 Documentation/admin-guide/bootconfig.rst                149 (``\0``) will be added. Thus the ``size`` is the length of the bootconfig
    7 Documentation/admin-guide/bootconfig.rst                157 loader passes a longer size, the kernel feils to find the bootconfig data.
    8 Documentation/admin-guide/bootconfig.rst                159 To do this operation, Linux kernel provides "bootconfig" command under
    9 Documentation/admin-guide/bootconfig.rst                160 tools/bootconfig, which allows admin to apply or delete the config file
   10 Documentation/admin-guide/bootconfig.rst                163 # make -C tools/bootconfig
   11 Documentation/admin-guide/bootconfig.rst                165 To add your boot config file to initrd image, run bootconfig as below
   12 Documentation/admin-guide/bootconfig.rst                168 # tools/bootconfig/bootconfig -a your-config /boot/initrd.img-X.Y.Z
   13 Documentation/admin-guide/bootconfig.rst                172 # tools/bootconfig/bootconfig -d /boot/initrd.img-X.Y.Z
   14 Documentation/admin-guide/bootconfig.rst                174 Then add "bootconfig" on the normal kernel command line to tell the
   15 Documentation/admin-guide/bootconfig.rst                175 kernel to look for the bootconfig at the end of the initrd file.
   16 Documentation/admin-guide/bootconfig.rst                190 Anyway, since bootconfig command verifies it when appending a boot config
   17 Documentation/admin-guide/bootconfig.rst                237 .. kernel-doc:: include/linux/bootconfig.h
   18 Documentation/admin-guide/bootconfig.rst                238 .. kernel-doc:: lib/bootconfig.c
   19 Documentation/admin-guide/index.rst                      70 bootconfig
   20 Documentation/admin-guide/kernel-parameters.txt         446 bootconfig    [KNL]
   21 Documentation/admin-guide/kernel-parameters.txt         450 See Documentation/admin-guide/bootconfig.rst
   22 Documentation/trace/boottime-trace.rst                   17 this uses bootconfig file to describe tracing feature programming.
   23 Documentation/trace/boottime-trace.rst                   27 .. [1] See :ref:`Documentation/admin-guide/bootconfig.rst <bootconfig>`
   24 Documentation/translations/zh_CN/admin-guide/index.rst   69 bootconfig

vgrepはこんな感じの使い方です。わりとシンプルで使いやすいので気になった人は使ってみてください。