第2回 オリジナルセキュリティモジュールの作成


Linuxカーネルには、バージョン2.6から「Linux Security Module」(LSM)というセキュリティフレームワークが導入されています。この連載ではLSMの仕組みを紹介するとともに、これを活用してオリジナルのセキュリティモジュールを作り上げていきます(編集部)

村上 純一
株式会社フォティーンフォティ技術研究所
研究開発部 αUnit シニア・リサーチエンジニア
2008/4/24

 前回「知られざるセキュリティフレームワーク『LSM』の役割」では、LSMが開発された経緯やその基本的な仕組みについて解説しました。今回は、実際にLSMを利用して、簡単なサンプルとなるセキュリティモジュールを作成してみたいと思います。

関連記事:
参考 Linux Kernel Watch 2006年5月版 「LSM不要論」に揺れるSELinux&AppArmor
http://www.atmarkit.co.jp/flinux/rensai/watch2006/watch05b.html
参考 Linux Kernel Watch番外編:セキュリティをやってるやつらは狂っている?!
http://www.atmarkit.co.jp/fsecurity/special/103kernelwatch/kernelwatch01.html

オリジナルモジュールでアクセス制御を実現

 前回解説したように、LSMは、セキュリティモジュールを実装するためのフレームワークです。具体的なアクセス制御の仕組みは、セキュリティモジュールの実装次第となります。ですから、セキュリティモジュールを開発するに当たって、どういったアクセス制御方式を実装するのかをあらかじめ決める必要があります。

 今回は、カーネルのソースコードに同梱されている「Root Plug sample LSM Module」(以降、Root Plug)をベースにして、事前に指定したネットワーク上のサーバに接続できた場合にのみroot権限でのコマンド実行を許可する「Root Connect LSM Module」(以降、Root Connect)を作成してみたいと思います(図1)。

図1
図1 Root PlugおよびRoot Connectの動作

編集部注:従来のセキュリティモジュールでは、デバイスドライバなどでも利用されているLKM(ローダブルカーネルモジュール)と呼ばれる仕組みが利用可能でした。しかし、2008年1月にリリースされたカーネル2.6.24からは、セキュリティモジュールをLKMとして実装することができなくなりました。このため、ユーザーはカーネルのビルド時に、静的にセキュリティモジュールを組み込む必要があります。

USBデバイスの接続で権限を制御する「Root Plug」

 まず、Root Plugについて説明します。

 Root Plugは、コンピュータに指定したUSBデバイスが接続されていた場合にのみ、root権限でのコマンド実行を許可するセキュリティモジュールです。USBデバイスの指定には、デバイスのベンダIDとプロダクトIDを利用します。

 Root Plugは、プログラムが実行される過程で呼び出されるbprm_check_securityフックを利用して、実行されるプログラムの「実効グループID」に基づいたアクセス制御を行います。bprm_check_securityフックのコールバック関数をリスト1に示します。

 なお、カーネルソース中のsecurity/root_plug.cが、Root Plugのソースコードになります。

 1 static int rootplug_bprm_check_security (struct linux_binprm *bprm)
 2 {
 3    struct usb_device *dev;
 4
 5    root_dbg("file %s, e_uid = %d, e_gid = %d\n",
 6          bprm->filename, bprm->e_uid, bprm->e_gid);
 7
 8    if (bprm->e_gid == 0) {
 9          dev = usb_find_device(vendor_id, product_id);
10         if (!dev) {
11             root_dbg("e_gid = 0, and device not found, "
12                 "task not allowed to run...\n");
13             return -EPERM;
14         }
15         usb_put_dev(dev);
16 }
17
18   return 0;
19 }
リスト1 rootplug_bprm_check_security関数

 linux_binprm構造体は、実効ユーザーID・実効グループID、ファイル名などの、実行するプログラムに関連する情報を格納するための構造体です。8行目で、関数の引数であるlinux_binprm構造体のポインタ中のe_gidメンバー変数の値をチェックしています。

 e_gidメンバー変数は、プログラムの実効グループIDを保持する変数です。そのため、プログラムの実効グループIDが0(root)であった場合、9行目のusb_find_device関数を呼び出すことにより、事前に指定したベンダID、プロダクトIDを持ったUSBデバイスの検索が行われます。当該USBデバイスが発見されなかった場合、関数は、-EPERM(Operation not permitted)をエラー値として返却します。

 また、Root Plugのコールバック関数のセットの一部には、Capabilityセキュリティモジュールのコールバックが利用されています(リスト2)。

 1 static struct security_operations rootplug_security_ops = {
 2     /* Use the capability functions for some of the hooks */
 3     .ptrace =       cap_ptrace,
 4     .capget =       cap_capget,
 5     .capset_check =    cap_capset_check,
 6     .capset_set =     cap_capset_set,
 7     .capable =       cap_capable,
 8
 9     .bprm_apply_creds =  cap_bprm_apply_creds,
10     .bprm_set_security =  cap_bprm_set_security,
11
12         .task_post_setuid =   cap_task_post_setuid,
13         .task_reparent_to_init = cap_task_reparent_to_init,
14
15         .bprm_check_security = rootplug_bprm_check_security,
16 };
リスト2 Root Plugのコールバック関数のセット

セキュリティモジュールのビルドとロード

 では、実際にRoot Plugを利用してみましょう。環境は、前回同様CentOS 5.1とし、前回説明した手順により、カーネルソースのダウンロードおよび展開が完了しているものとします。

 LSMは、セキュリティモジュールのスタックをサポートしており、CentOSが配布しているカーネルは、標準でプライマリセキュリティモジュールにSELinux、セカンダリセキュリティモジュールにCapability LSMがカーネルに組み込まれています(※1)。そのため、そのままではRoot Plugを利用することができません。そこで、今回はセキュリティモジュールが組み込まれていないカーネルをビルドしてテストを行います。

注1:前回説明したようにプライマリセキュリティモジュールは、register_security関数/unregister_security関数によりカーネルに登録・解除されます。一方、セカンダリセキュリティモジュールは、mod_reg_security関数/mod_unreg_security関数によりプライマリセキュリティモジュールに登録されます。mod_reg_security関数/mod_unreg_security関数の実体は、プライマリセキュリティモジュールのコールバック関数呼び出しです。そのため、プライマリセキュリティモジュールはセカンダリセキュリティモジュールの登録・解除を、自身のアクセス制御方式に則って制御することが可能です。

 では、CentOS 5.1でのカーネルのリビルドの手順を以下に示します。

% cd rpmbuild/BUILD/kernel-2.6.18/linux-2.6.18.i686
% make menuconfig
% make
% sudo make modules_install
% sudo installkernel 2.6.18-prep arch/i386/boot/bzImage System.map

 make menuconfigコマンドによるカーネルの細かいコンフィグレーションは、コンピュータの構成によって異なるため割愛しますが、以下の設定を忘れずに行ってください。

  • 利用するUSBデバイスのサポート

  • カーネルへのSELinuxの組み込みの解除
    Security options→NSA SELinux Support

  • カーネルへのCapability LSMの組み込みの解除
    Security options→Default Linux Capabilities

  • Root Plugのモジュール化
    Security options→Root Plug Support

 installkernelコマンドにより、grubにインストールしたカーネルのエントリが自動的に追加されるため、システムを再起動し、grubからカーネルとして2.6.18-prepを選択し、OSを起動します。

 無事にOSが起動したら、アクセス制御に利用するUSBデバイスをコンピュータに接続して、ベンダIDとプロダクトIDの確認を行います。どちらも/proc/bus/usb/devicesを参照することで確認することができます。今回は、ELECOM製のUSBメモリを利用しました(リスト3)。

 1
 2  T: Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=12 MxCh= 2
 3  B: Alloc= 11/900 us ( 1%), #Int= 1, #Iso= 0
 4  D: Ver= 1.10 Cls=09(hub ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
 5  P: Vendor=0000 ProdID=0000 Rev= 2.06
 6  S: Manufacturer=Linux 2.6.18-53.1.14.el5 uhci_hcd
 7  S: Product=UHCI Host Controller
 8  S: SerialNumber=0000:00:07.2
 9  C:* #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr= 0mA
10  I: If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hub
11  E: Ad=81(I) Atr=03(Int.) MxPS= 2 Ivl=255ms
12
13  T: Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=12 MxCh= 0
14  D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
15  P: Vendor=1307 ProdID=0163 Rev= 1.00
16  S: Manufacturer=USBest Technology
17  S: Product=USB Mass Storage Device
18  S: SerialNumber=fd829e116c20be
19  C:* #Ifs= 1 Cfg#= 1 Atr=80 MxPwr= 80mA
20  I: If#= 0 Alt= 0 #EPs= 3 Cls=08(stor.) Sub=06 Prot=50 Driver=usb-storage
21  E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
22  E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
23  E: Ad=83(I) Atr=03(Int.) MxPS= 64 Ivl=8ms
24
25  T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=02 Dev#= 3 Spd=12 MxCh= 7
26  D: Ver= 1.10 Cls=09(hub ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1
27  P: Vendor=0e0f ProdID=0002 Rev= 1.00
28  S: Product=VMware Virtual USB Hub
29  C:* #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr= 0mA
30  I: If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hub
31  E: Ad=81(I) Atr=03(Int.) MxPS= 1 Ivl=255ms
リスト3 /proc/bus/usb/devicesの内容

 15行目より、当該USBメモリのベンダID(0x1307)プロダクトID(0x0163)を確認することができます。

 次に、以下のコマンドを実行して、Root Plugをカーネルにロードします。

% sudo modprobe root_plug vendor_id=0x1307 product_id=0x0163

 これでRoot Plugのセットアップは完了です。試しに筆者の手元の環境でUSBメモリを接続した状態/取り除いた状態で、sudoコマンドからwhoamiコマンドを実行した結果を示します。

・USBメモリを接続した状態

% sudo whoami
root

・USBメモリを取り除いた状態

% sudo whoami
sudo: unable to execute /usr/bin/whoami: Operation not permitted

 このように、USBメモリを取り除いた状態では、sudoコマンドの実行が制限されていることが確認できます。

第1回へ
1/2

Index
Inside Linux Security Module
 第2回 オリジナルセキュリティモジュールの作成
Page 1
 オリジナルモジュールでアクセス制御を実現
 USBデバイスの接続で権限を制御する「Root Plug」
 セキュリティモジュールのビルドとロード  
  Page 2
 サーバ接続に基づきアクセスを制御する「Root Connect」
 サーバプログラムを用いて挙動をテスト
 アイデア次第で広がるモジュール


 Linux Squareフォーラム Linux/システム学習関連記事
連載:Windowsユーザーに教えるLinuxの常識(全12回)
Windowsのセオリーが通用しないLinux。Linux初心者向けに、LinuxというOSの考え方/常識をゼロから伝授!
連載:LFSで作って学ぶLinuxの仕組み(全4回)
管理者(root)は、何をしなければならないのか? 管理に際して検討すべきことは? 管理のための技術とは? など、駆け出し管理者のための考え方や方法論を検討する
連載:Linux管理者への道(全8回)
「Linux From Scratch」というシンプルなLinuxをインストール&環境構築する作業を通して、LinuxがOSとして機能するための仕組みや設定を見直そう
Linux Squareフォーラム全記事インデックス

MONOist組み込み開発フォーラムの中から、Linux関連記事を紹介します


Linux & OSS フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Linux & OSS 記事ランキング

本日 月間