• 16
    Like
  • 0
    Comment

Evil Bit (悪意のビット)

IPv4ヘッダにはまだ使われていない領域が1ビットだけ残っている。 IPフラグメントオフセット領域の最上位ビットだ。RFC791には「予約済み、ゼロでなければならない」と書いてある。

  Flags:  3 bits

    Various Control Flags.

      Bit 0: reserved, must be zero
      Bit 1: (DF) 0 = May Fragment,  1 = Don't Fragment.
      Bit 2: (MF) 0 = Last Fragment, 1 = More Fragments.

          0   1   2
        +---+---+---+
        |   | D | M |
        | 0 | F | F |
        +---+---+---+

これを、RFC3514は次のように改める提案をした。「もし、このビットに1がセットされているなら、パケットは悪意を持っている。安全なシステムは、そのようなパケットに対して防衛措置を講ずべきだ。」攻撃者がこの悪意のビットを立ててくれれば、システム管理者は複雑なセキュリティ対策の一切から解放される。これで世界に平和が訪れる。素晴らしい。実装しなければならない。

          0
        +---+
        |   |
        | E |
        +---+

   Currently-assigned values are defined as follows:

   0x0  If the bit is set to 0, the packet has no evil intent.  Hosts,
        network elements, etc., SHOULD assume that the packet is
        harmless, and SHOULD NOT take any defensive measures.  (We note
        that this part of the spec is already implemented by many common
        desktop operating systems.)

   0x1  If the bit is set to 1, the packet has evil intent.  Secure
        systems SHOULD try to defend themselves against such packets.
        Insecure systems MAY chose to crash, be penetrated, etc.

実際、このRFC3514は2003年4月1日のエイプリルフールに発表されるとほぼ同時にFreeBSDに実装された。(が、無粋ながら付け加えると、すぐにこの変更は元に戻された。)
翌2004年のエイプリルフールには、Linux 2.6.5 の netfilter 向けパッチを書いた人がいる。

Linux 4.9 に実装してみる

クリスマス、世界平和の願いを込めて、このRFC3514をLinuxカーネルに実装しよう。今回は、2003年のBSD実装に近いものを作る。その過程でsetsockopt(2)getsockopt(2)sysctl(8)を追加することになる。もしこれらを実装したいとき、参考になればうれしい。

まず悪意のパケットを送信するソケットというものを作りたい。ソケットオプションをセットするフラグをstruct inet_sockに置いておこう。この構造体はソケットごとに作られるので、ここにbool evilフラグを追加して、悪意のパケットを送信するソケットかどうかをセットしておくことにする。

/include/net/inet_sock.h
@@ -214,6 +214,7 @@ struct inet_sock {
        __be32                  mc_addr;
        struct ip_mc_socklist __rcu     *mc_list;
        struct inet_cork_full   cork;
+       bool                    evil;
 };

続いて、IPヘッダのフラグメントオフセット領域の最上位ビットを定義する。IP_CE(輻輳フラグ)なるものがすでにあるが、使われていないので気にせず同じ値をIP_EVILとして定義しておく。

/include/net/ip.h
@@ -89,6 +89,7 @@ struct ip_ra_chain {
 extern struct ip_ra_chain __rcu *ip_ra_chain;

 /* IP flags. */
+#define IP_EVIL        0x8000          /* Flag: "The Evil Bit"         */
 #define IP_CE          0x8000          /* Flag: "Congestion"           */
 #define IP_DF          0x4000          /* Flag: "Don't Fragment"       */
 #define IP_MF          0x2000          /* Flag: "More Fragments"       */

struct netns_ipv4にはsysctl(8)で制御する、以下の3つのフラグを置いておこう。

  • int sysctl_ip_do_rfc3514: RFC3514機能を有効にする
  • int sysctl_ip_speak_no_evil: システムとして悪意のパケットは送信せず破棄する
  • int sysctl_ip_hear_no_evil: システムとして悪意のパケットは受信せず破棄する
/include/net/netns/ipv4.h
@@ -116,6 +116,11 @@ struct netns_ipv4 {
        int sysctl_igmp_llm_reports;
        int sysctl_igmp_qrv;

+       /* RFC3514 */
+       int sysctl_ip_do_rfc3514;
+       int sysctl_ip_speak_no_evil;
+       int sysctl_ip_hear_no_evil;
+
        struct ping_group_range ping_group_range;

        atomic_t dev_addr_genid;

次に、ユーザがソケットオプションに指定するためのIP_EVIL_INTENTフラグを定義する。他と被らなければなんでもよかったので666にした。

/include/uapi/linux/in.h
@@ -160,6 +160,9 @@ struct in_addr {
 #define IP_DEFAULT_MULTICAST_TTL        1
 #define IP_DEFAULT_MULTICAST_LOOP       1

+/* RFC3514: The security flag in the IPv4 header */
+#define IP_EVIL_INTENT                 666
+
 /* Request struct for multicast socket ops */

 #if __UAPI_DEF_IP_MREQ

さて、setsockopt(2)を追加しよう。SOL_IPレベルにIP_EVIL_INTENTオプションを追加する。ソケットオプションがセットされるとinet->evilフラグが立って、悪意のパケットを送信するソケットになる。

/net/ipv4/ip_sockglue.c
@@ -614,6 +614,7 @@ static int do_ip_setsockopt(struct sock *sk, int level,
        case IP_MULTICAST_LOOP:
        case IP_RECVORIGDSTADDR:
        case IP_CHECKSUM:
+       case IP_EVIL_INTENT:
                if (optlen >= sizeof(int)) {
                        if (get_user(val, (int __user *) optval))
                                return -EFAULT;
@@ -1166,6 +1167,16 @@ static int do_ip_setsockopt(struct sock *sk, int level,
                inet->min_ttl = val;
                break;

+       case IP_EVIL_INTENT:
+               if (!net->ipv4.sysctl_ip_do_rfc3514)
+                       goto e_inval;
+               if (optlen < 1)
+                       goto e_inval;
+               if (val != 0 && val != 1)
+                       goto e_inval;
+               inet->evil = val;
+               break;
+
        default:
                err = -ENOPROTOOPT;
                break;

そして、悪意のパケットを送信するソケットかどうかを判別するための、getsockopt(2)を実装する。inet->evilフラグが立っているかどうかを返すだけでよい。

/net/ipv4/ip_sockglue.c
@@ -1497,6 +1508,9 @@ static int do_ip_getsockopt(struct sock *sk, int level, int optname,
        case IP_MINTTL:
                val = inet->min_ttl;
                break;
+       case IP_EVIL_INTENT:
+               val = inet->evil;
+               break;
        default:
                release_sock(sk);
                return -ENOPROTOOPT;

次はprocfsに以下の3つのファイルを作る。それぞれがsysctl(8)に対応している。

  • /proc/net/ipv4/rfc3515
  • /proc/net/ipv4/ip_speak_no_evil
  • /prok/net/ipv4/ip_hear_no_evil
/net/ipv4/sysctl_net_ipv4.c
@@ -971,6 +971,33 @@ static struct ctl_table ipv4_net_table[] = {
                .extra2         = &one,
        },
 #endif
+       {
+               .procname       = "rfc3514",
+               .data           = &init_net.ipv4.sysctl_ip_do_rfc3514,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_dointvec_minmax,
+               .extra1         = &zero,
+               .extra2         = &one
+       },
+       {
+               .procname       = "ip_speak_no_evil",
+               .data           = &init_net.ipv4.sysctl_ip_speak_no_evil,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_dointvec_minmax,
+               .extra1         = &zero,
+               .extra2         = &one
+       },
+       {
+               .procname       = "ip_hear_no_evil",
+               .data           = &init_net.ipv4.sysctl_ip_hear_no_evil,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_dointvec_minmax,
+               .extra1         = &zero,
+               .extra2         = &one
+       },
        { }
 };

いよいよ通信部分。まず受信処理。悪意のパケットを受信しないフラグsysctl_ip_hear_no_evilが立っていて、かつ受信したパケットが悪意のパケットip_hdr(skb)->frag_off & htons(IP_EVIL)なら破棄する。また、RFC3514にはDropped packets SHOULD be noted in the appropriate MIB variable.とあるのでそれに従いパケットを破棄した場合には適切にMIB情報を更新する。

/net/ipv4/ip_input.c
@@ -430,6 +430,15 @@ int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,

        iph = ip_hdr(skb);

+       /*
+        * Check for RFC3514 (EVIL) packets.
+        */
+       if (net->ipv4.sysctl_ip_hear_no_evil &&
+           ip_hdr(skb)->frag_off & htons(IP_EVIL)) {
+               __IP_INC_STATS(net, IPSTATS_MIB_INDISCARDS);
+               goto inhdr_error;
+       }
+
        /*
         *      RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.
         *

続いて送信処理。もし、RFC3514機能を有効にするsysctl_ip_do_rfc3514フラグが立っていて、悪意のパケットを送信するソケットinet_sk(sk)->evilフラグが立っていたら、IPヘッダの悪意のビットを立てる。

/net/ipv4/ip_output.c
@@ -97,6 +97,9 @@ int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
 {
        struct iphdr *iph = ip_hdr(skb);

+       if (net->ipv4.sysctl_ip_do_rfc3514 && inet_sk(sk)->evil)
+               iph->frag_off |= htons(IP_EVIL);
+
        iph->tot_len = htons(skb->len);
        ip_send_check(iph);

ただし、sysctl_ip_speak_no_evilフラグが立っている場合には、悪意のパケットを検知し、送信せずに破棄する。もちろん破棄した旨、MIB情報も適切に更新する。

/net/ipv4/ip_output.c
@@ -186,6 +189,14 @@ static int ip_finish_output2(struct net *net, struct sock *sk, struct sk_buff *s
        struct neighbour *neigh;
        u32 nexthop;

+       /* RFC3514 */
+       if (net->ipv4.sysctl_ip_speak_no_evil &&
+           ip_hdr(skb)->frag_off & htons(IP_EVIL)) {
+               IP_INC_STATS(net, IPSTATS_MIB_OUTDISCARDS);
+               kfree_skb(skb);
+               return -EACCES;
+       }
+
        if (rt->rt_type == RTN_MULTICAST) {
                IP_UPD_PO_STATS(net, IPSTATS_MIB_OUTMCAST, skb->len);
        } else if (rt->rt_type == RTN_BROADCAST)

準拠とまで言えるかはともかく、これで概ねRFC3514の内容を実装できた。割と簡単に実装できることに驚ろくと思う。バグがあればご指摘いただきたい。

このコードを使った攻撃は極めて容易に遮断できるので、攻撃者が悪用しても問題ないだろう。むしろ攻撃者全員に使ってほしい。そうなればインターネットは愛と平和で満たされる。

レリーズ!!