概要

先日、Linuxカーネルの脆弱性「CVE-2026-98432」について、原因と暫定対策を記事にしました。

その後、2026年10月30日付で、Red HatおよびCIQからnftablesを利用した緩和策が公開されました。

今回の脆弱性は、IPv4 TCP接続の処理中に解放済みメモリへアクセスするUse-After-Free(UAF)に起因するものです。

攻撃者がIPv4オプションを含むTCPパケットを送信することで、問題のある処理経路に到達する可能性があります。

そこで、IPv4オプション付きTCPパケットをカーネルのTCP処理に到達する前に破棄するというのが、今回の緩和策です。

参考資料:

CIQの資料では、MPTCPを無効化した状態でも通常のTCP通信から脆弱な処理経路に到達できることが確認されています。そのため、MPTCPの無効化だけに頼らず、nftablesによる通信制御を追加することにしました。

今回は、実際に運用しているサーバに暫定ルールを追加し、systemdによる永続化の設定まで行った記録です。

環境

  • OS:Ubuntu 24.04 LTS
  • Webサーバ:Apache
  • ファイアウォール:UFW
  • IPアドレス制御:ipset
  • 不正アクセス対策:fail2ban
  • iptables:v1.8.10(nf_tables)
  • 作業日:2026年10月31日

筆者環境では、UFWとipsetを組み合わせたアクセス制御を行っています。

今回の作業前に、nftablesのルールセットを確認しました。

sudo nft list ruleset | wc -l

結果は、

6089

6,089行。

この時点で、今回の作業方針はほぼ決まりました。既存のファイアウォールには、できる限り手を加えないことです。

実際、iptablesのバージョンを確認すると、

sudo iptables -V
iptables v1.8.10 (nf_tables)

となっていました。また、nftablesのテーブル一覧は以下のとおりです。

sudo nft list tables
table ip6 filter
table ip filter
table ip nat
table ip6 nat
table ip raw
table inet f2b-table

UFWなどがiptables-nftを通じて管理しているテーブルに加え、fail2ban用の独立したテーブルも存在しています。

今回の緩和策は、これらとは別に専用テーブルを作成する方針としました。

フェイズゼロ:政治交渉

以前の記事でも触れましたが、システムの保守作業には技術的な準備だけでなく、作業時間の確保や関係者との調整が必要です。

今回も例外ではありません。

ただし、今回の作業では、もう一つ交渉相手がいました。

自分自身です。

既に稼働しているファイアウォールの設定を変更するのは、できれば避けたい作業です。

しかも、SSHで接続しているサーバのネットワーク制御を変更するとなれば、設定を間違えた瞬間に自分自身を締め出す可能性があります。

そこで、今回の作業では以下を徹底しました。

  • 既存のUFW・ipset・fail2banの設定は変更しない。
  • nftablesにCVE対策専用のテーブルを作成する。
  • 変更前のルールセットを保存する。
  • シリアルコンソールを確保する。
  • 問題が発生した場合は、専用テーブルだけを削除する。

特に重要なのが、シリアルコンソールです。

SSH接続を失った場合でも、ネットワーク経由ではない復旧手段を確保してから作業に入りました。

退路を確保してから前進する。

今回は、これが最も重要な準備でした。

さっくりとした手順

  1. 作業ディレクトリへ移動 cd /home/hoge/work/cve-2026-98432

任意のディレクトリを指定します。

  1. 既存ルールセットをバックアップ sudo nft -a list ruleset > nftables-before-cve-2026-98432.txt
  2. テーブル一覧をバックアップ sudo nft list tables > nftables-tables-before.txt
  3. 既存のCVE対策用テーブルを確認 sudo nft list table inet cve_2026_98432
  4. 暫定ルールの設定ファイルを作成 nano cve-2026-98432.nft
  5. 構文チェック sudo nft --check --file cve-2026-98432.nft
  6. 暫定ルールを適用 sudo nft --file cve-2026-98432.nft
  7. 適用結果を確認 sudo nft -a list table inet cve_2026_98432
  8. 永続化用の設定ファイルを作成 sudo nano /etc/nftables/cve-2026-98432.nft
  9. systemdユニットを作成 sudo nano /etc/systemd/system/cve-2026-98432-nft.service
  10. 永続化用設定の構文チェック sudo nft --check --file /etc/nftables/cve-2026-98432.nft
  11. systemdユニットの検証 sudo systemd-analyze verify /etc/systemd/system/cve-2026-98432-nft.service
  12. systemdの設定を再読み込み sudo systemctl daemon-reload
  13. 自動起動を有効化 sudo systemctl enable cve-2026-98432-nft.service
  14. 自動起動の登録状態を確認 systemctl is-enabled cve-2026-98432-nft.service
  15. 既存の防御機構を確認 sudo ufw statussudo ipset list -namesudo fail2ban-client status

作業詳細

1. 既存のルールセットをバックアップ

まずは、作業ディレクトリを用意しました。

今回の作業ディレクトリは以下です。任意のディレクトリを指定してください。

cd /home/hoge/work/cve-2026-98432

現在のルールセットを保存します。

sudo nft -a list ruleset > nftables-before-cve-2026-98432.txt

続いて、行数を確認しました。

wc -l nftables-before-cve-2026-98432.txt
6089 nftables-before-cve-2026-98432.txt

既存の6,089行を保存できています。

次に、テーブル一覧も保存します。

sudo nft list tables > nftables-tables-before.txt

ファイルの状態は以下でした。

nftables-before-cve-2026-98432.txt  239547 bytes
nftables-tables-before.txt              94 bytes

これで、変更前の状態を記録できました。なお、これらのファイルは復旧時の参照用です。

iptables-nftで管理されているテーブルも含まれるため、バックアップ全体を無条件に読み戻すことはしません。

2. nftablesの専用テーブルを作成

まず、同名のテーブルが存在しないことを確認します。

sudo nft list table inet cve_2026_98432

続いて、作業ディレクトリに設定ファイルを作成しました。

nano cve-2026-98432.nft

内容は以下です。

table inet cve_2026_98432 {
    chain prerouting {
        type filter hook prerouting priority -300; policy accept;
        ip hdrlength > 5 meta l4proto tcp counter drop
    }
}

それぞれの意味は以下のとおりです。

  • table inet cve_2026_98432:今回のCVE対策専用テーブル
  • hook prerouting:受信パケットの早い段階で評価
  • priority -300:raw優先度
  • ip hdrlength > 5:IPv4ヘッダーが通常の20バイトを超えるパケットを対象
  • meta l4proto tcp:TCP通信を対象
  • counter drop:該当パケットをカウントして破棄

ここで重要なのは、IPv4のIPオプションとTCPオプションは異なるという点です。

今回のルールは、TCPタイムスタンプなどの一般的なTCPオプションを一律に遮断するものではありません。

一方、IPv4オプションを使用する正当なTCP通信は遮断されるため、適用先の通信要件を考慮する必要があります。

3. 構文チェック

設定ファイルを保存したら、構文チェックを実施します。

sudo nft --check --file cve-2026-98432.nft

エラーは表示されませんでした。終了ステータスも確認します。

echo $?
0

構文チェック成功です。この段階では、まだ通信制御は変更されていません。

4. シリアルコンソールを確保

実際に適用する前に、vpsのシリアルコンソールへ接続しました。

今回の作業で最も避けたいのは、SSH接続を失ってサーバへアクセスできなくなることです。

現在のUFWでは、以下のポートを許可しています。

80/tcp
443/tcp
22/tcp

今回のnftablesルールはポート番号による判定ではないため、これらのポートにも条件が一致すれば適用されます。

そのため、SSHセッションを維持したまま作業し、必要になればシリアルコンソールから切り戻せるようにしました。

5. 暫定ルールを適用

準備ができたところで、設定を適用します。その前に:深く深呼吸を3回ぐらいしましょう。ここから先、タイポ、コマンド忘れ、全てが致命的になり得ます。

sudo nft --file cve-2026-98432.nft

エラーは表示されませんでした。続いて、適用結果を確認します。

sudo nft -a list table inet cve_2026_98432

結果は以下です。

table inet cve_2026_98432 { # handle 10
        chain prerouting { # handle 1
                type filter hook prerouting priority raw; policy accept;
                ip hdrlength > 5 meta l4proto tcp counter packets 0 bytes 0 drop # handle 2
        }
}

設定ファイルではpriority -300と指定しましたが、表示上はpriority rawとなっています。これはnftablesが数値を定義済みの優先度名で表示しているためです。

また、

counter packets 0 bytes 0

となっています。この時点では、遮断条件に一致したパケットはありませんでした。

6. Webサイトの動作確認

適用後、自サイトへアクセスし、記事の登録まで行いました。

HTTPSによるアクセス、ログイン状態の維持、記事の登録と保存が正常に行えています。

単純なHTTP応答だけでなく、普段利用しているWebアプリケーションの操作が継続できることを確認しました。

少なくとも、今回確認した範囲ではWebサイトの利用に支障は発生していません。

永続化

1. 永続化用の設定ファイルを作成

暫定ルールの動作を確認できたため、再起動後にも読み込めるようにします。ただし、既存のUFWやipsetを管理している仕組みには手を加えません。

今回は、CVE対策専用の設定ファイルとsystemdユニットを作成しました。

sudo mkdir -p /etc/nftables
sudo nano /etc/nftables/cve-2026-98432.nft

内容は以下です。

add table inet cve_2026_98432
delete table inet cve_2026_98432

table inet cve_2026_98432 {
    chain prerouting {
        type filter hook prerouting priority -300; policy accept;
        ip hdrlength > 5 meta l4proto tcp counter drop
    }
}

冒頭の、

add table inet cve_2026_98432
delete table inet cve_2026_98432

は、既存の専用テーブルが存在する場合でも、同じ名前のテーブルを作り直せるようにするためのものです。

削除対象はcve_2026_98432テーブルに限定しています。

UFWやfail2banのテーブルには触れません。

2. systemdユニットを作成

続いて、専用のsystemdユニットを作成します。

sudo nano /etc/systemd/system/cve-2026-98432-nft.service

内容は以下です。

[Unit]
Description=Temporary nftables mitigation for CVE-2026-98432
Wants=network-pre.target
Before=network-pre.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/nft -f /etc/nftables/cve-2026-98432.nft
ExecStop=-/usr/sbin/nft delete table inet cve_2026_98432

[Install]
WantedBy=multi-user.target

このユニットでは、起動時に専用のnftables設定ファイルを読み込みます。停止時には、今回追加したテーブルだけを削除します。

RemainAfterExit=yesを指定しているため、ルールの読み込みが完了した後もsystemd上ではサービスを有効な状態として扱います。

3. 設定ファイルの構文チェック

まず、nftables設定ファイルを検証します。

sudo nft --check --file /etc/nftables/cve-2026-98432.nft

終了ステータスを確認しました。

echo $?
0

正常終了です。

続いて、systemdユニットも検証します。

sudo systemd-analyze verify /etc/systemd/system/cve-2026-98432-nft.service

4. systemdに登録

ユニット定義を再読み込みします。この作業の前に、もう4回ほど深呼吸をして、飲み物を何か飲みましょう。お茶菓子などがあれば更に最高です。

システムをいじる作業は全て自分の指先に全責任が伴います。

sudo systemctl daemon-reload

続いて、自動起動を有効化します。

sudo systemctl enable cve-2026-98432-nft.service

ここでは--nowを付けませんでした。

既に手動で適用したルールが動作しているため、現時点で専用テーブルを作り直す必要がないからです。

最後に、自動起動の登録状態を確認します。

systemctl is-enabled cve-2026-98432-nft.service

結果は、

enabled

となりました。

これで、systemdによる自動起動の登録まで完了です。

なお、再起動後に実際にルールが復元されるかは、まだ確認していません。

既存の防御機構を確認

今回の作業で最も気になっていたのは、既存のUFW・ipset・fail2banへの影響です。

設定ファイルを作成し、自動起動を登録した後、改めて状態を確認しました。

1. nftables

sudo nft list table inet cve_2026_98432
table inet cve_2026_98432 {
        chain prerouting {
                type filter hook prerouting priority raw; policy accept;
                ip hdrlength > 5 meta l4proto tcp counter packets 0 bytes 0 drop
        }
}

専用テーブルは維持されています。

2. UFW

sudo ufw status
状態: アクティブ

To                         Action      From
--                         ------      ----
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
22/tcp                  ALLOW       Anywhere

UFWはアクティブのままです。

既存の許可ポートにも変更はありません。

3. ipset

sudo ipset list -name
ufw-blocklist

既存のipsetが存在していることを確認できました。

4. fail2ban

sudo fail2ban-client status
Status
|- Number of jail:      2
`- Jail list:   sshd, ufw

fail2banも、既存の2つのjailを維持しています。

これらの確認から、少なくともサービスの状態と設定の一覧については、作業前の構成が維持されていることを確認できました。

切り戻し

今回の暫定ルールに問題が発生した場合は、専用テーブルを削除します。

一時的にルールを撤去する場合

sudo nft delete table inet cve_2026_98432

この操作で、今回追加したテーブルとチェーンを削除できます。

ただし、systemdの自動起動設定は残るため、次回起動時には再び読み込まれます。

永続化設定も撤去する場合

まず、自動起動を無効化します。

sudo systemctl disable cve-2026-98432-nft.service

続いて、専用テーブルを削除します。

sudo nft delete table inet cve_2026_98432

systemdユニットを削除します。

sudo rm /etc/systemd/system/cve-2026-98432-nft.service

nftables設定ファイルも削除します。

sudo rm /etc/nftables/cve-2026-98432.nft

最後に、systemdの設定を再読み込みします。

sudo systemctl daemon-reload

今回追加した設定だけを撤去するため、既存のUFW・ipset・fail2banの設定はそのまま維持できます。

今後の確認事項

今回の作業では、以下まで完了しています。

  • 既存ルールセットのバックアップ
  • シリアルコンソールによる復旧経路の確保
  • nftables専用テーブルの追加
  • 設定ファイルの構文チェック
  • Webサイトの動作確認
  • systemdユニットの作成
  • 自動起動の有効化
  • UFW・ipset・fail2banの状態確認

残っているのは、再起動後の動作確認です。

次回の再起動後には、以下を確認します。

systemctl is-active cve-2026-98432-nft.service
sudo nft list table inet cve_2026_98432

activeと表示されるだけでなく、実際に専用テーブルが存在していることを確認します。

また、今回のルールはIPv4オプション付きTCPパケットを遮断する暫定措置です。

CIQが公開した検証結果はRocky Linux系の環境を対象としており、筆者環境のUbuntu 24.04 LTSで同等の攻撃パケットを用いた遮断試験までは実施していません。

そのため、今回確認できたのはルールの登録と通常通信の継続までです。

修正済みカーネルが提供された段階で更新を行い、不要になった暫定ルールを撤去する予定です。

おわりに

今回の作業で、最も神経を使ったのはnftablesの設定そのものではありません。

既に稼働している防御機構を壊さずに、新しい防御を追加することでした。

筆者環境では、UFW・ipset・fail2banを組み合わせてアクセス制御を行っています。その結果、nftablesのルールセットは6,089行に達していました。

そこへ緊急の脆弱性対策を追加するのですから、作業を始める前から気が重くなるのも無理はありません。

そこで、今回は既存のルールセットを編集せず、専用テーブルを追加する方式を採用しました。

さらに、シリアルコンソールを確保し、問題が発生した場合には今回追加したテーブルだけを削除できるようにしています。

結果として、既存のUFW・ipset・fail2banの設定を変更することなく、暫定緩和策の導入と永続化の登録まで進められました。

今回の作業で改めて感じたのは、システムの保守とは、何かを変更する技術だけでなく、変更しなくてよいものを見極める判断でもあるということです。

脆弱性を塞ごうとして、既存の防御機構を壊してしまっては意味がありません。

そして、その判断を支えるのがバックアップであり、切り戻し手順であり、シリアルコンソールという最後の退路です。

作業を終えた後の感想を一言で表すなら、

「心臓に悪い作業だった」

でしょうか。

とはいえ、これで修正済みカーネルが提供されるまでの間、攻撃経路を減らすための対策を一つ追加できました。

今後もカーネルの修正状況を確認しながら、必要な対応を続けていきます。