Add interim mitigations for CVE-2026-98432

CVE-2026-98432 is a use-after-free in the Linux IPv4 TCP option
handling (inet_opt/ireq_opt are freed with a plain kfree()) that is
reachable by sending a TCP SYN carrying IP options. No fixed kernel has
been released for any supported Ubuntu release yet, so mitigate in
role[base] until one is:

* Drop inbound IPv4 TCP segments that carry IP options (no legitimate
  traffic uses them) and log the drops, rate limited, so probing is
  visible. The rules are added to /etc/ufw/before.rules so they survive
  ufw reloads, and are idempotent.

* Disable Multipath TCP where the kernel supports it (5.6+). Go
  programs built with Go >= 1.24 open MPTCP listeners by default, which
  is the precondition of the published exploit; nothing here needs
  MPTCP. Hosts on older kernels (5.4, no MPTCP) skip this.

The sysctl drop-in is written directly rather than via the core sysctl
resource, whose reload runs `sysctl -p` against /etc/sysctl.conf - a
file that no longer exists on Ubuntu 26.04.

Opt node_exporter (Go >= 1.24, so it opened an MPTCP listener) out of
MPTCP with GODEBUG=multipathtcp=0, and restart it when the unit
changes so the change takes effect without a separate restart.
This commit is contained in:
raucao committed 2026-10-31 14:33:13 +04:00
1 parent c8f15c63b4
commit b299910dc6
3 files changed
+85

No files matched your search

@@ -0,0 +1,78 @@
#
# Cookbook:: kosmos-base
# Recipe:: cve_2026_98432
#
# Interim mitigations for CVE-2026-98432: a use-after-free in the Linux IPv4
# TCP option handling (inet_opt/ireq_opt are freed with a plain kfree()) that
# is reachable by sending a TCP SYN carrying IP options. No fixed kernel has
# been released for Ubuntu yet, so until every host runs a patched kernel we:
#
# 1. drop inbound IPv4 TCP segments that carry IP options (no legitimate
# traffic uses them), and
# 2. disable Multipath TCP where the running kernel supports it, so that Go
# programs built with Go >= 1.24 (e.g. node_exporter) stop opening MPTCP
# listeners - the precondition of the published exploit.
#
# This recipe (and the matching change in kosmos_prometheus::node_exporter) can
# be removed once all hosts run a kernel containing the fix.
#
# --- 1. Drop inbound IPv4 TCP segments that carry IP options ----------------
#
# ufw rebuilds its ruleset from /etc/ufw/before.rules on every start/reload, so
# the restriction has to live in that file to survive. The u32 expression
# `0&0x0F000000=0x05000000` matches a segment whose IP header length (IHL) is
# 5, i.e. one *without* IP options; negating it drops any segment that carries
# them. Drops are logged (rate limited) so probing is visible.
before_rules = "/etc/ufw/before.rules"
cve_marker = "CVE-2026-98432"
ipopts_match = '-p tcp -m u32 ! --u32 "0&0x0F000000=0x05000000"'
ruby_block "ufw: drop IPv4 TCP segments with IP options (#{cve_marker})" do
block do
editor = Chef::Util::FileEdit.new(before_rules)
editor.search_file_delete_line(/#{cve_marker}/)
editor.insert_line_after_match(
/^# End required lines$/,
"# #{cve_marker}: drop IPv4 TCP segments carrying IP options\n" \
"-A ufw-before-input -m comment --comment \"#{cve_marker}\" #{ipopts_match} " \
"-m limit --limit 5/min -j LOG --log-prefix \"[#{cve_marker}] ipopts \" --log-level 4\n" \
"-A ufw-before-input -m comment --comment \"#{cve_marker}\" #{ipopts_match} -j DROP"
)
editor.write_file
raise "#{cve_marker}: failed to add the ip-options drop rule to #{before_rules}" \
unless ::File.read(before_rules).include?(cve_marker)
end
not_if { ::File.exist?(before_rules) && ::File.read(before_rules).include?(cve_marker) }
notifies :run, "execute[reload ufw]"
end
execute "reload ufw" do
command "ufw reload"
action :nothing
end
# --- 2. Disable Multipath TCP where the kernel supports it ------------------
#
# MPTCP is in-tree since Linux 5.6; the /proc entry only exists when it is
# available, so the compile-time guard keeps older (5.4) hosts from writing a
# sysctl for a key their kernel does not know. Nothing in our stack needs MPTCP.
#
# Not using the core `sysctl` resource here: its reload runs `sysctl -p`, which
# reads /etc/sysctl.conf - a file that does not exist on Ubuntu 26.04, making
# the resource fail. Writing the drop-in and applying the value explicitly works
# on every release we run.
if ::File.exist?("/proc/sys/net/mptcp/enabled")
file "/etc/sysctl.d/99-chef-net.mptcp.enabled.conf" do
content "net.mptcp.enabled = 0\n"
owner "root"
group "root"
mode "0644"
end
execute "disable multipath TCP (net.mptcp.enabled=0)" do
command "sysctl -w net.mptcp.enabled=0"
not_if { ::File.read("/proc/sys/net/mptcp/enabled").strip == "0" }
end
end
@@ -77,6 +77,8 @@ unless node.chef_environment == "development"
include_recipe "kosmos-base::firewall"
include_recipe "kosmos-base::cve_2026_98432"
include_recipe "kosmos-base::tmux"
include_recipe "kosmos-postfix"
@@ -59,6 +59,10 @@ systemd_unit "node_exporter.service" do
User: "node_exporter",
Group: "node_exporter",
ExecStart: "/usr/local/bin/node_exporter --web.listen-address=:#{node_exporter_port} --collector.textfile.directory=/var/lib/node_exporter/textfile",
# node_exporter 1.11.1 is built with Go >= 1.24, which opens MPTCP
# listeners by default on Linux since 5.6. That is the precondition of
# the CVE-2026-98432 exploit, and we do not need MPTCP, so opt out.
Environment: "GODEBUG=multipathtcp=0",
Restart: "on-failure",
RestartSec: "5",
NoNewPrivileges: "yes",
@@ -70,6 +74,7 @@ systemd_unit "node_exporter.service" do
},
})
triggers_reload true
notifies :restart, "service[node_exporter]", :delayed
action :create
end