最終回 オリジナルセキュリティモジュールを拡張する


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


PF_PACKETとRAW Socketによるデータ送信の禁止

 LSMを利用することで、カーネル内のさまざまな処理のポイントにコールバック関数をセットできることは前述のとおりです。さらに、カーネルのリビルド時、「Socket and Networking Security Hooks」(CONFIG_SECURITY_NETWORK)を有効にしてビルドすることで、カーネル内のネットワーク処理に対してもコールバック関数をセットすることができます。

 これを利用して、PF_PACKET、Raw Socketによるデータ送信を禁止する機能を、Root Connectに追加してみましょう。

■PF_PACKETとRaw Socket

 TCPやUDPによる通常のネットワークプログラミングを行う場合は、まず、socketシステムコールを利用して、ソケットと呼ばれる通信のエンドポイントを生成します。その後、生成したソケットに対して読み書きを行うことで、データの送受信を行います。

 PF_PACKETは、データリンクアクセスのために用意されているドメインの一種です。以下の要領でソケットを生成することで、プロセスはOSのTCP/IPスタックを迂回(うかい)し、直接レイヤ2とデータの送受信を行うことができます(図1)。

図1
図1 PF_PACKET、Raw Socketの概念図

int sock = socket(PF_PACKET, type, protocol);

 また、socketシステムコールの第2引数であるソケットタイプは、通常、TCPであれば「SOCK_STREAM」、UDPであれば「SOCK_DGRAM」を指定しますが、「SOCK_RAW」と呼ばれる特別なソケットタイプを指定することも可能です。こうしたソケットは、Raw Socketと呼ばれています。

 Raw Socketを利用した場合、プロセスは、IPヘッダに続くトランスポート層以降のデータを自身で作成することができます。また、作成したRaw Socketにオプション(IP_HDRINCL)を設定することで、IPヘッダ自体の作成を行うことも可能になります。

 こうした特殊なソケットについては、manページのraw(7)、packet(7)に詳述されています。興味のある方はぜひ一読してみてください。

 PF_PACKETやRaw Socketは、独自プロトコルを実装する場合や、ポートスキャナなどの特殊なネットワークプログラムを作成する場合に有用です。しかし同時に、プロトコルに違反したフォーマットのパケットを作成、送信することも可能であるため、一部の攻撃ツール(DoS攻撃など)を実装する際にも利用されます。

 そこでここでは、LSMのsocket_sendmsgフックを利用し、これらのソケットを介したデータ送信を禁止する機能をRoot Connectに実装してみました。

 なお、この実装は非常に「コンセプト的なコード」です。これを利用した場合、PF_PACKETおよびRAW Socketを利用している正規のシステムコマンドが正常に動作しなくなるため、試す際には注意してください。具体的には、DHCPによるIPアドレスの割り当て、pingコマンドによるicmp echo requestパケットの送信などができなくなります。

■socket_sendmsgフックを利用する

 前回のRoot Connectからの変更点をリスト4に示します。

 実装には、bprm_security_checkフックと同じ要領で、socket_sendmsgフックを利用しています。socket_sendmsgフックは、ユーザー空間のプロセスがsend/sendto/sendmsgシステムコールを発行した際に、カーネル空間で呼び出されるコールバックになります。

 1 static struct security_operations rootconn_security_ops = {
 2      .ptrace =              cap_ptrace,
 3      .capget =              cap_capget,
 4      .capset_check =        cap_capset_check,
 5      .capset_set =          cap_capset_set,
 6      .capable =             cap_capable,
 7      .bprm_apply_creds =    cap_bprm_apply_creds,
 8      .bprm_set_security =   cap_bprm_set_security,
 9      .task_post_setuid =    cap_task_post_setuid,
10      .task_reparent_to_init = cap_task_reparent_to_init,
11      .bprm_check_security = rootconn_bprm_check_security,
12      .socket_sendmsg =      rootconn_socket_sendmsg,
13 };
14
15 static int rootconn_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size)
16 {
17      struct sock *sk;
18
19      if (!(sock && (sk = sock->sk)))
20       return 0;
21
22      if (sk->sk_family == PF_PACKET || sk->sk_type == SOCK_RAW)
23       return -EPERM;
24
25      return 0;
26 }
リスト4 root_conn.cの変更箇所(2) ※変更箇所は黄色表記

 まず、rootconn_security_ops変数のsocket_sendmsgメンバを、rootconn_socket_sendmsg関数のアドレスで初期化しています。rootconn_socket_sendmsg関数では、引数で渡されたソケットオブジェクトから、ソケットファミリ(ドメイン)とソケットタイプを参照し、ソケットがPF_PACKETで生成されたソケット、またはRaw Socketでないかどうか判断しています。いずれかが真であった場合、-EPERM(Operation not permitted)をエラー値として返します。

 では、実際の動作を確認してみましょう。前述の手順でRoot Connectをビルドし、カーネルにロードします。PF_PACKET、Raw Socketを利用した正規のシステムコマンドであるarping、pingのそれぞれをstraceを利用して実行した結果をリスト5、6に示します。

 straceは、引数で指定されたプロセスの実行を監視し、プロセスが発行したシステムコールの情報を取得するユーティリティです。

 1 socket(PF_PACKET, SOCK_DGRAM, 0) = 3
 2 setuid32(0)                      = 0
 3 brk(0)                           = 0x8f6e000
 4 brk(0x8f8f000)                   = 0x8f8f000
 5 ...
 6 write(1, "ARPING 192.168.1.1 from 192.168."..., 42) = 42
 7 rt_sigaction(SIGINT, {0x83e190, [], SA_RESTART}, NULL, 8) = 0
 8 rt_sigaction(SIGALRM, {0x83e950, [], SA_RESTART}, NULL, 8) = 0
 9 gettimeofday({1211190368, 872410}, NULL) = 0
10 gettimeofday({1211190368, 872538}, NULL) = 0
11 sendto(3, "\0\1\10\0\6\4\0\1\0\f)=s\17\300\250\1`\377\377\377\377\377\377\300\250\1\1", 28, 0, {sa_family=AF_PACKET, proto=0x806, if2, pkttype=PACKET_HOST, addr(6)={1, ffffffffffff}, 20) = -1 EPERM (Operation not permitted)
12 alarm(1)                         = 0
13 recvfrom(3, 0xbf90cbd0, 4096, 0, 0xbf90dbf0, 0xbf90cbc8) = ? ERESTARTSYS (To be restarted)
14 --- SIGALRM (Alarm clock) @ 0 (0) ---
15 gettimeofday({1211190369, 872857}, NULL) = 0
16 write(1, "Sent 0 probes (0 broadcast(s))\n", 31) = 31
17 write(1, "Received 0 response(s)\n", 23) = 23
18 exit_group(1)                    = ?
リスト5 「strace arping -c 1 -I eth0 192.168.1.1」の実行結果(一部抜粋)

 1行目で、PF_PACKETを第1引数にsocketシステムコールを発行していることが分かります。続く11行目でsendtoシステムコールを発行しており、その返り値として-EPERMが返されています。

1 socket(PF_INET, SOCK_RAW, IPPROTO_ICMP) = 3
2 ...
3 ioctl(3, SIOCGIFINDEX, {ifr_name="eth0", ifr_index=2}) = 0
4 setsockopt(3, SOL_RAW, ICMP_FILTER, ~(ICMP_ECHOREPLY|ICMP_DEST_UNREACH|ICMP_SOURCE_QUENCH|ICMP_REDIRECT|ICMP_TIME_EXCEEDED|ICMP_PARAMETERPROB), 4) = 0
5 setsockopt(3, SOL_IP, IP_RECVERR, [1], 4) = 0
6 setsockopt(3, SOL_SOCKET, SO_SNDBUF, [324], 4) = 0
7 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [65536], 4) = 0
8 getsockopt(3, SOL_SOCKET, SO_RCVBUF, [131072], [4]) = 0
9 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
10 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f7300
11 write(1, "PING 192.168.1.1 (192.168.1.1) f"..., 77) = 77
12 setsockopt(3, SOL_SOCKET, SO_TIMESTAMP, [1], 4) = 0
13 setsockopt(3, SOL_SOCKET, SO_SNDTIMEO, "\1\0\0\0\0\0\0\0", 8) = 0
14 setsockopt(3, SOL_SOCKET, SO_RCVTIMEO, "\1\0\0\0\0\0\0\0", 8) = 0
15 getpid() = 3933
16 rt_sigaction(SIGINT, {0xaaa1f0, [], SA_INTERRUPT}, NULL, 8) = 0
17 rt_sigaction(SIGALRM, {0xaaa1f0, [], SA_INTERRUPT}, NULL, 8) = 0
18 rt_sigaction(SIGQUIT, {0xaaa210, [], SA_INTERRUPT}, NULL, 8) = 0
19 gettimeofday({1211190776, 670366}, NULL) = 0
20 ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
21 ioctl(1, TIOCGWINSZ, {ws_row=30, ws_col=100, ws_xpixel=0, ws_ypixel=0}) = 0
22 gettimeofday({1211190776, 671366}, NULL) = 0
23 gettimeofday({1211190776, 672414}, NULL) = 0
24 sendmsg(3, {msg_name(16)={sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("192.168.1.1")}, msg_iov(1)=[{"\10\0\336\23]\17\0\1\370M1H\236B\n\0\10\t\n\v\f\r\16\17\20\21\22\23\24\25\26\27"..., 64}], msg_controllen=24, {cmsg_len=24, cmsg_level=SOL_IP, cmsg_type=, ...}, msg_flags=0}, 0) = -1 EPERM (Operation not permitted)
25 ...
リスト6 「strace ping -c 1 -I eth0 192.168.1.1」の実行結果(一部抜粋)

 arping同様、1行目でRaw Socketを生成しており、24行目でsendtoシステムコールを発行していることが分かります。こちらもsendtoシステムコールの返り値として-EPERMが返却されていることが確認できます。この結果、パケットの送信が禁止されます。

気軽にカーネルへのチャレンジを

 以上、3回に分けてLSMの仕組みと、それを活用したオリジナルモジュールの作成について解説してきましたが、いかがだったでしょうか?

 これを機に、SELinuxのように、真剣にセキュリティポリシーやセキュリティモジュールについて考えるのも面白いと思います。また、「カーネルをフックするためのフレームワーク」ぐらいの軽い気持ちで、まずは手を動かしてみるのもLinuxならではの面白さではないでしょうか。この連載が、LSMを通じてカーネルに手を出すきっかけになれば幸いです。

2/2

Index
Inside Linux Security Module
 最終回 オリジナルセキュリティモジュールを拡張する
  Page 1
 securityfsを活用しよう
 インターフェイスの実装
  Page 2
 PF_PACKETとRAW Socketによるデータ送信の禁止
 気軽にカーネルへのチャレンジを


 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 記事ランキング

本日 月間