Linux の sysctl net.ipv4. icmp_echo_ignore_broadcasts / macOS (XNU) sysctl net.inet.icmp.bmcastecho の実装を読む

Linuxsysctl net.ipv4. icmp_echo_ignore_broadcasts と、 macOS / XNU の sysctl net.inet.icmp.bmcastecho の実装を調べたエントリです

何の sysctl ですか?

Linuxsysctl net.ipv4. icmp_echo_ignore_broadcastsCIS BenchmarksSmurf 攻撃対策として取り上げられています。

www.cisecurity.org

Smurf 攻撃とは何ですか?

Smurf 攻撃自体の説明は他のサイトで確認してください

ja.wikipedia.org

SMURF攻撃とは、攻撃者が標的となるサーバーをInternet Control Message Protocol(ICMP)パケットで圧倒させようとする分散サービス妨害(DDoS)攻撃です。1つまたは複数のコンピューターネットワークに対して、標的のデバイスのスプーフィングIPアドレスでリクエストを行うことにより、コンピューターネットワークは標的サーバーに応答し、初期攻撃トラフィックを増幅し、潜在的に標的を圧倒し、アクセス不能にします。この攻撃ベクトルは一般に、解決された脆弱性と見なされており、もはや流行していません。

引用: https://www.cloudflare.com/ja-jp/learning/ddos/smurf-ddos-attack

sysctl net.ipv4. icmp_echo_ignore_broadcasts の詳細

sysctl net.ipv4. icmp_echo_ignore_broadcasts あるいは /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts の説明を、Red Hat Cusotmer Portal から引用します。

icmp_echo_ignore_all 及び icmp_echo_ignore_broadcasts — 各ホストからの ICMP ECHO パケット、もしくはブロードキャスト及びマルチキャストのアドレスを起点とする ICMP ECHO パケットのみを無視することをそれぞれカーネルに許可します。0 の値は、カーネルによる応答を許可し、1 の値はパケットを無視します。

引用: https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/6/html/deployment_guide/s2-proc-dir-sys

macOS / XNU にも同様の sysctl 設定はあるのか?

macOS ( XNU ) では、Linux と同様に機能する sysctl として sysctl net.inet.icmp.bmcastecho が用意されています。なお、bmcastechoBroadcast Multicast Echo の略称のようです。

sysctl の実装を読もう

Smurf 攻撃の sysctl の設定方法を取り扱った記事はたくさんありますが、実装 (= カーネルのソース) まで踏み込んでるエントリは無いようなので、調べて見ましょう。

macOS ( XNU ) のソースも調べてました

Linux の実装

Linux カーネルのソースで grep (ag) icmp_echo_ignore_broadcasts net/ として、 sysctl の定義を見つけることができました。 1️⃣ の部分です。

static struct ctl_table ipv4_net_table[] = {

...

    {
        .procname   = "icmp_echo_ignore_broadcasts",
        .data       = &init_net.ipv4.sysctl_icmp_echo_ignore_broadcasts, 1️⃣
        .maxlen     = sizeof(int),
        .mode       = 0644,
        .proc_handler   = proc_dointvec
    },

sysctl_icmp_echo_ignore_broadcasts は icmp_recv() で参照されます

icmp_rcv()

IP レイヤから ICMP を受け取って処理するのが icmp_rcv() です。

static const struct net_protocol icmp_protocol = {
    .handler =  icmp_rcv,
    .err_handler =  icmp_err,
    .no_policy =    1,
    .netns_ok = 1,
};

ネットワークスタックで icmp_rcv() がどのような位置づけなのかは、wiki.bit-hive.com さんの Linuxカーネルメモの図が参考になります。(いつもありがとうございます)

wiki.bit-hive.com

続いて icmp_rcv() と sysctl_icmp_echo_ignore_broadcasts のソースです

int icmp_rcv(struct sk_buff *skb) {

    struct icmphdr *icmph;
    struct rtable *rt = skb_rtable(skb);
    struct net *net = dev_net(rt->dst.dev);
    bool success;

... 

    /*
    *  Parse the ICMP message
    */

    if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {  2️⃣
        /*
        *  RFC 1122: 3.2.2.6 An ICMP_ECHO to broadcast MAY be
        *    silently ignored (we let user decide with a sysctl).
        *  RFC 1122: 3.2.2.8 An ICMP_TIMESTAMP MAY be silently
        *    discarded if to broadcast/multicast.
        */
        if ((icmph->type == ICMP_ECHO ||
             icmph->type == ICMP_TIMESTAMP) &&
            net->ipv4.sysctl_icmp_echo_ignore_broadcasts) { 1️⃣
            goto error;
        }
        
        ... 
    }
    
    
    success = icmp_pointers[icmph->type].handler(skb); 4️⃣
...

error:
    __ICMP_INC_STATS(net, ICMP_MIB_INERRORS); 3️⃣
    goto drop;
}

sysctl が参照されるのは 1️⃣ の部分ですね。 ICMP がマルチキャストあるいはブロードキャストかどうかは 2️⃣ で判定しているようです。

1️⃣ で無視された ICMP は 2️⃣ でエラーとして統計されます。統計は /proc/net/snmpInErrors で確認できます。

ubuntu@primary:~$ grep Icmp: /proc/net/snmp
Icmp: InMsgs InErrors InCsumErrors InDestUnreachs InTimeExcds InParmProbs InSrcQuenchs InRedirects InEchos InEchoReps InTimestamps InTimestampReps InAddrMasks InAddrMaskReps OutMsgs OutErrors OutDestUnreachs OutTimeExcds OutParmProbs OutSrcQuenchs OutRedirects OutEchos OutEchoReps OutTimestamps OutTimestampReps OutAddrMasks OutAddrMaskReps
Icmp: 40 0 0 40 0 0 0 0 0 0 0 0 0 0 40 0 40 0 0 0 0 0 0 0 0 0 0

4️⃣ を見ると ICMP のタイプに応じてハンドラの関数が呼び出される実装になっています (例 icmph->type == 8 (ICMP_ECHO なら Echo Request を処理するコードにつながる ) 。ハンドラの詳細はこのエントリでは扱いません。

ここまで読んで sysctl_icmp_echo_ignore_broadcasts がどのように機能するか把握できました。

macOS / XNU

macOS / XNU での sysctl 設定は全くアテがない状態で調べました。 sysctl -a | grep icmp の中から「これかな?」と調べていきました。

 $ sysctl -a | grep icmp
net.inet.icmp.maskrepl: 0
net.inet.icmp.icmplim: 250
net.inet.icmp.timestamp: 0
net.inet.icmp.icmplim_random_incr: 250
net.inet.icmp.drop_redirect: 1
net.inet.icmp.log_redirect: 0
net.inet.icmp.bmcastecho: 1 👈
net.inet.tcp.icmp_may_rst: 1

net.inet.icmp.bmcastecho の名前がそれっぽいので、ググてって調べたところ golang/go の以下の issue コメントがヒントになり、Smurf 攻撃対策の sysctl なのだと絞り込むことができました

https://github.com/golang/go/issues/5811#issuecomment-66081853

Nope, I completely forgot that old RFC says "An ICMP Echo Request destined to an IP broadcast or IP multicast address MAY be silently discarded" and almost all implementations follow that direction. Will fix the test case.

後はソースを探るだけです。 bsd/netinet/ip_icmp.c に sysctl の定義があります

/*
 * ICMP broadcast echo sysctl
 */

static int      icmpbmcastecho = 1;
SYSCTL_INT(_net_inet_icmp, OID_AUTO, bmcastecho, CTLFLAG_RW | CTLFLAG_LOCKED,
    &icmpbmcastecho, 0, "");

icmpbmcastecho が参照されているのは以下の箇所です

/*
 * Process a received ICMP message.
 */
void
icmp_input(struct mbuf *m, int hlen)
{
    struct sockaddr_in icmpsrc, icmpdst, icmpgw;
    struct icmp *icp;
    struct ip *ip = mtod(m, struct ip *);
    int icmplen;
    int i;
    struct in_ifaddr *ia;
    void (*ctlfunc)(int, struct sockaddr *, void *, struct ifnet *);
    int code;
    boolean_t should_log_redirect = false;


...

    case ICMP_ECHO:
        if ((m->m_flags & (M_MCAST | M_BCAST))) {
            if (icmpbmcastecho == 0) {  1️⃣
                icmpstat.icps_bmcastecho++;
                break;
            }
        }


...

    case ICMP_TSTAMP:
        if (icmptimestamp == 0) { 
            break;
        }

        if (!icmpbmcastecho 1️⃣
            && (m->m_flags & (M_MCAST | M_BCAST)) != 0) {
            icmpstat.icps_bmcasttstamp++;
            break;
        }

Linux カーネルと細かい差異はあれど、同様に読める感じですね。XNU の実装を読むのはこの辺で終わり。 ( ユーザランドから ICMP の統計を確認する方法がよくわからなかった )

Linux で検証

multipass で起動した 2台の VM (ホスト名は primarysecond ) で検証します。 second で net.ipv4.icmp_echo_ignore_broadcasts=0 に設定します

ubuntu@sencod :~$ sudo sysctl net.ipv4.icmp_echo_ignore_broadcasts=0

primary から ping-b をつけてブロードキャストすると応答が返ってきます ( DUP になるのか〜 )

       -b
           Allow pinging a broadcast address.
ubuntu@primary:~$ sudo ping -b 192.168.64.255
WARNING: pinging broadcast address
PING 192.168.64.255 (192.168.64.255) 56(84) bytes of data.
64 bytes from 192.168.64.1: icmp_seq=1 ttl=64 time=4.07 ms
64 bytes from 192.168.64.14: icmp_seq=1 ttl=64 time=4.75 ms (DUP!)

なお sysctl_icmp_echo_ignore_broadcasts の初期値は、 Linux カーネルのソースでは 1 です。ディストリビューションや自分で入れた設定で 0 にしない限りは 1 のままですね。

 /* Control parameters for ECHO replies. */
    net->ipv4.sysctl_icmp_echo_ignore_all = 0;
    net->ipv4.sysctl_icmp_echo_ignore_broadcasts = 1;

感想

  • Linux カーネル、 XNU ともに ICMP を実装しているコードは、比較的平易に読める感じです
    • カーネルの実装は難しく全体を理解しようとするとなかなかカロリーの高い作業になりますが ... 一部だけ読むならなんとかなりますね