Skip to content

Commit 5535675

Browse files
djslyCopilotCopilot
committed
fix: blacklist rxrpc/esp4/esp6 modules to mitigate DirtyFrag LPE (#8475)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 097dd33 commit 5535675

6 files changed

Lines changed: 174 additions & 67 deletions

File tree

e2e/validation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func ValidateCommonLinux(ctx context.Context, s *Scenario) {
8888
_ = execScriptOnVMForScenarioValidateExitCode(ctx, s, "sudo curl http://168.63.129.16:32526/vmSettings", 0, "curl to wireserver failed")
8989

9090
validateWireServerBlocked(ctx, s)
91-
ValidateAlgifAeadMitigation(ctx, s)
91+
ValidateVulnerableKernelModulesDisabled(ctx, s)
9292

9393
// base NBC templates define a mock service principal profile that we can still use to test
9494
// the correct bootstrapping logic: https://github.com/Azure/AgentBaker/blob/master/e2e/node_config.go#L438-L441

e2e/validators.go

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2254,42 +2254,44 @@ func ValidateCollectWindowsLogsScript(ctx context.Context, s *Scenario) {
22542254
"collect-windows-logs.ps1 failed or did not produce a zip file")
22552255
}
22562256

2257-
// ValidateAlgifAeadMitigation verifies CVE-2026-31431 mitigation is active:
2258-
// the algif_aead kernel module must be blocked via modprobe config, not loaded,
2259-
// and modprobe must refuse to load it.
2260-
func ValidateAlgifAeadMitigation(ctx context.Context, s *Scenario) {
2257+
// ValidateVulnerableKernelModulesDisabled verifies that kernel modules with known
2258+
// LPE vulnerabilities are blocked via modprobe config, not loaded, and cannot be loaded.
2259+
// Covers: CVE-2026-31431 (algif_aead), DirtyFrag (esp4, esp6, rxrpc).
2260+
// To add a new CVE mitigation, append the module name to the list below.
2261+
func ValidateVulnerableKernelModulesDisabled(ctx context.Context, s *Scenario) {
22612262
s.T.Helper()
22622263

2263-
// Flatcar uses a different kernel module setup
22642264
if s.VHD.Flatcar {
2265-
s.T.Log("Skipping algif_aead validation: not applicable for Flatcar")
2265+
s.T.Log("Skipping vulnerable kernel module validation: not applicable for Flatcar")
22662266
return
22672267
}
22682268

22692269
script := strings.Join([]string{
2270-
`# Check modprobe config blocks the module`,
2271-
`if ! grep -qs 'install algif_aead /bin/false' /etc/modprobe.d/*.conf 2>/dev/null; then`,
2272-
` echo "FAIL: algif_aead disable rule not found in /etc/modprobe.d/*.conf"`,
2273-
` exit 1`,
2274-
`fi`,
2275-
`echo "PASS: modprobe config blocks algif_aead"`,
2276-
``,
2277-
`# Check module is not loaded`,
2278-
`if grep -qE '^algif_aead ' /proc/modules 2>/dev/null; then`,
2279-
` echo "FAIL: algif_aead module is loaded"`,
2280-
` exit 1`,
2281-
`fi`,
2282-
`echo "PASS: algif_aead module is not loaded"`,
2283-
``,
2284-
`# Check modprobe refuses to load it`,
2285-
`if sudo modprobe algif_aead 2>/dev/null; then`,
2286-
` echo "FAIL: modprobe algif_aead succeeded, should be blocked"`,
2287-
` sudo rmmod algif_aead 2>/dev/null || true`,
2288-
` exit 1`,
2289-
`fi`,
2290-
`echo "PASS: modprobe algif_aead correctly refused"`,
2270+
`failed=0`,
2271+
`for mod in algif_aead esp4 esp6 rxrpc; do`,
2272+
` if ! grep -qsE "^install ${mod} /bin/false" /etc/modprobe.d/*.conf 2>/dev/null; then`,
2273+
` echo "FAIL: ${mod} disable rule not found in /etc/modprobe.d/*.conf"`,
2274+
` failed=1`,
2275+
` else`,
2276+
` echo "PASS: modprobe config blocks ${mod}"`,
2277+
` fi`,
2278+
` if grep -qE "^${mod} " /proc/modules 2>/dev/null; then`,
2279+
` echo "FAIL: ${mod} module is loaded"`,
2280+
` failed=1`,
2281+
` else`,
2282+
` echo "PASS: ${mod} module is not loaded"`,
2283+
` fi`,
2284+
` if sudo modprobe "${mod}" 2>/dev/null; then`,
2285+
` echo "FAIL: modprobe ${mod} succeeded, should be blocked"`,
2286+
` sudo modprobe -r "${mod}" 2>/dev/null || true`,
2287+
` failed=1`,
2288+
` else`,
2289+
` echo "PASS: modprobe ${mod} correctly refused"`,
2290+
` fi`,
2291+
`done`,
2292+
`exit $failed`,
22912293
}, "\n")
22922294

22932295
execScriptOnVMForScenarioValidateExitCode(ctx, s, script, 0,
2294-
"CVE-2026-31431 (algif_aead) mitigation validation failed")
2296+
"Vulnerable kernel module mitigation validation failed (algif_aead/esp4/esp6/rxrpc)")
22952297
}

parts/linux/cloud-init/artifacts/cse_main.sh

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,28 @@ get_ubuntu_release() {
5050
lsb_release -r -s 2>/dev/null || echo ""
5151
}
5252

53+
# Disable a single kernel module with a known LPE vulnerability.
54+
# Writes a modprobe blacklist rule and unloads the module if loaded.
55+
# Applies to existing VHDs that don't yet have the fix baked into modprobe-CIS.conf.
56+
# Safe to run unconditionally — idempotent (overwrites with same content if already present).
57+
# Defined in cse_main.sh (not sourced) to support scriptless provisioning.
58+
#
59+
# Usage: disableVulnerableKernelModule <module_name> <description>
60+
disableVulnerableKernelModule() {
61+
local mod="$1"
62+
local desc="$2"
63+
64+
printf '# %s\ninstall %s /bin/false\nblacklist %s\n' "$desc" "$mod" "$mod" > "/etc/modprobe.d/disable-${mod}.conf"
65+
66+
if grep -q "^${mod} " /proc/modules 2>/dev/null; then
67+
if modprobe -r "$mod" 2>/dev/null; then
68+
echo "${desc}: successfully unloaded ${mod}"
69+
else
70+
echo "${desc}: failed to unload ${mod} (in use), reboot required for full mitigation"
71+
fi
72+
fi
73+
}
74+
5375
# ====== BASE PREP: BASE IMAGE PREPARATION ======
5476
# This stage prepares the base VHD image with all necessary components and configurations.
5577
# IMPORTANT: This stage must NOT join the node to the cluster.
@@ -284,21 +306,14 @@ EOF
284306

285307
logs_to_events "AKS.CSE.ensureSysctl" ensureSysctl || exit $ERR_SYSCTL_RELOAD
286308

287-
# CVE-2026-31431 (Copy Fail): Mitigate algif_aead LPE vulnerability.
288-
# Affects Ubuntu 20.04/22.04/24.04 and AzureLinux 3.0 (kernel >=4.15).
309+
# Disable kernel modules with known LPE vulnerabilities (CVE-2026-31431, DirtyFrag).
289310
# Applies to existing VHDs that don't yet have the modprobe-CIS.conf fix baked in.
290-
# Safe to run unconditionally — idempotent if already mitigated.
291-
if [ "$OS" = "$UBUNTU_OS_NAME" ] || isMarinerOrAzureLinux "$OS"; then
292-
if ! grep -qs "algif_aead" /etc/modprobe.d/*.conf 2>/dev/null; then
293-
printf "install algif_aead /bin/false\nblacklist algif_aead\n" > /etc/modprobe.d/disable-algif_aead.conf
294-
fi
295-
if grep -q '^algif_aead ' /proc/modules 2>/dev/null; then
296-
if rmmod algif_aead 2>/dev/null; then
297-
echo "CVE-2026-31431: successfully unloaded algif_aead module"
298-
else
299-
echo "CVE-2026-31431: failed to unload algif_aead (in use), reboot required for full mitigation"
300-
fi
301-
fi
311+
# To add a new CVE mitigation, add a disableVulnerableKernelModule call below.
312+
if isUbuntu "$OS" || isMarinerOrAzureLinux "$OS"; then
313+
disableVulnerableKernelModule "algif_aead" "CVE-2026-31431 (Copy Fail)"
314+
disableVulnerableKernelModule "esp4" "DirtyFrag (xfrm-ESP page-cache write)"
315+
disableVulnerableKernelModule "esp6" "DirtyFrag (xfrm-ESP6 page-cache write)"
316+
disableVulnerableKernelModule "rxrpc" "DirtyFrag (RxRPC page-cache write, bypasses AppArmor userns)"
302317
fi
303318

304319
if ! isAzureLinuxOSGuard "$OS" "$OS_VARIANT"; then

parts/linux/cloud-init/artifacts/modprobe-CIS.conf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,14 @@ blacklist usb-storage
2929
# until kernel fix is available. See https://ubuntu.com/blog/copy-fail-vulnerability-fixes-available
3030
install algif_aead /bin/false
3131
blacklist algif_aead
32+
# DirtyFrag / CopyFail2: Disable xfrm ESP and RxRPC modules to mitigate page-cache
33+
# write LPE vulnerabilities. rxrpc path bypasses AppArmor userns restrictions.
34+
# AKS platform components do not require kernel IPsec ESP/XFRM or AFS/RxRPC.
35+
# Disabling these modules prevents workloads that rely on those protocols from working
36+
# on the node. See https://github.com/V4bel/dirtyfrag
37+
install esp4 /bin/false
38+
blacklist esp4
39+
install esp6 /bin/false
40+
blacklist esp6
41+
install rxrpc /bin/false
42+
blacklist rxrpc
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/usr/bin/env shellspec
2+
3+
# Unit tests for disableVulnerableKernelModule() in cse_main.sh
4+
5+
Describe 'disableVulnerableKernelModule()'
6+
MODPROBE_DIR=""
7+
PROC_MODULES=""
8+
9+
setup() {
10+
MODPROBE_DIR="$(mktemp -d)"
11+
PROC_MODULES="$(mktemp)"
12+
# Source only the function by extracting it
13+
eval "$(sed -n '/^disableVulnerableKernelModule()/,/^}/p' parts/linux/cloud-init/artifacts/cse_main.sh | \
14+
sed "s|/etc/modprobe.d|${MODPROBE_DIR}|g; s|/proc/modules|${PROC_MODULES}|g")"
15+
}
16+
17+
cleanup() {
18+
rm -rf "$MODPROBE_DIR"
19+
rm -f "$PROC_MODULES"
20+
}
21+
22+
BeforeEach 'setup'
23+
AfterEach 'cleanup'
24+
25+
# Mock modprobe -r
26+
modprobe() { return 0; }
27+
28+
It 'creates a config file for a single module'
29+
When call disableVulnerableKernelModule "algif_aead" "CVE-2026-31431 (Copy Fail)"
30+
The file "${MODPROBE_DIR}/disable-algif_aead.conf" should be exist
31+
The contents of file "${MODPROBE_DIR}/disable-algif_aead.conf" should include "install algif_aead /bin/false"
32+
The contents of file "${MODPROBE_DIR}/disable-algif_aead.conf" should include "blacklist algif_aead"
33+
The contents of file "${MODPROBE_DIR}/disable-algif_aead.conf" should include "CVE-2026-31431"
34+
End
35+
36+
It 'creates separate config files per module'
37+
When call disableVulnerableKernelModule "esp4" "DirtyFrag ESP4"
38+
The file "${MODPROBE_DIR}/disable-esp4.conf" should be exist
39+
The contents of file "${MODPROBE_DIR}/disable-esp4.conf" should include "install esp4 /bin/false"
40+
The contents of file "${MODPROBE_DIR}/disable-esp4.conf" should include "blacklist esp4"
41+
End
42+
43+
It 'is idempotent — running twice produces same content'
44+
first_run() {
45+
disableVulnerableKernelModule "rxrpc" "DirtyFrag RxRPC"
46+
cat "${MODPROBE_DIR}/disable-rxrpc.conf"
47+
}
48+
second_run() {
49+
disableVulnerableKernelModule "rxrpc" "DirtyFrag RxRPC"
50+
cat "${MODPROBE_DIR}/disable-rxrpc.conf"
51+
}
52+
When call first_run
53+
The output should eq "$(second_run)"
54+
End
55+
56+
It 'attempts to unload a loaded module'
57+
loaded_test() {
58+
echo "rxrpc 425984 0" > "$PROC_MODULES"
59+
disableVulnerableKernelModule "rxrpc" "DirtyFrag RxRPC"
60+
}
61+
When call loaded_test
62+
The output should include "successfully unloaded rxrpc"
63+
End
64+
65+
It 'does not attempt unload when module is not loaded'
66+
not_loaded_test() {
67+
: > "$PROC_MODULES"
68+
disableVulnerableKernelModule "rxrpc" "DirtyFrag RxRPC"
69+
}
70+
When call not_loaded_test
71+
The output should not include "unloaded"
72+
End
73+
End

vhdbuilder/packer/test/linux-vhd-content-test.sh

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,35 +1262,41 @@ testNfsServerService() {
12621262
echo "$test:Finish"
12631263
}
12641264

1265-
# CVE-2026-31431 (Copy Fail): Verify algif_aead kernel module is disabled.
1266-
# The modprobe-CIS.conf should contain "install algif_aead /bin/false" which
1267-
# prevents the module from loading. Verify the config is present, the module
1268-
# is not loaded, and that attempting to load it fails.
1269-
testAlgifAeadDisabled() {
1270-
local test="testAlgifAeadDisabled"
1265+
# Verify all kernel modules with known LPE vulnerabilities are disabled.
1266+
# Covers: CVE-2026-31431 (algif_aead), DirtyFrag (esp4, esp6, rxrpc).
1267+
# To add a new CVE mitigation, append the module to the loop below.
1268+
testVulnerableKernelModulesDisabled() {
1269+
local test="testVulnerableKernelModulesDisabled"
12711270
echo "$test:Start"
12721271

1273-
# Verify modprobe config blocks the module
1274-
if ! grep -qs "install algif_aead /bin/false" /etc/modprobe.d/*.conf 2>/dev/null; then
1275-
err "$test" "algif_aead disable rule not found in /etc/modprobe.d/*.conf"
1276-
return 1
1277-
fi
1278-
echo "$test: modprobe config correctly blocks algif_aead"
1272+
local failed=0
1273+
for mod in algif_aead esp4 esp6 rxrpc; do
1274+
if ! grep -qsE "^install ${mod} /bin/false" /etc/modprobe.d/*.conf 2>/dev/null; then
1275+
err "$test" "${mod} disable rule not found in /etc/modprobe.d/*.conf"
1276+
failed=1
1277+
else
1278+
echo "$test: modprobe config correctly blocks ${mod}"
1279+
fi
12791280

1280-
# Verify the module is not currently loaded
1281-
if grep -qE '^algif_aead ' /proc/modules 2>/dev/null; then
1282-
err "$test" "algif_aead kernel module is loaded despite being disabled"
1283-
return 1
1284-
fi
1285-
echo "$test: algif_aead module is not loaded"
1281+
if grep -qE "^${mod} " /proc/modules 2>/dev/null; then
1282+
err "$test" "${mod} kernel module is loaded despite being disabled"
1283+
failed=1
1284+
else
1285+
echo "$test: ${mod} module is not loaded"
1286+
fi
1287+
1288+
if modprobe "${mod}" 2>/dev/null; then
1289+
err "$test" "modprobe ${mod} succeeded — module should be blocked"
1290+
modprobe -r "${mod}" 2>/dev/null || true
1291+
failed=1
1292+
else
1293+
echo "$test: modprobe ${mod} correctly refused to load"
1294+
fi
1295+
done
12861296

1287-
# Verify that attempting to load the module fails
1288-
if modprobe algif_aead 2>/dev/null; then
1289-
err "$test" "modprobe algif_aead succeeded — module should be blocked"
1290-
rmmod algif_aead 2>/dev/null || true
1297+
if [ "$failed" -ne 0 ]; then
12911298
return 1
12921299
fi
1293-
echo "$test: modprobe algif_aead correctly refused to load"
12941300

12951301
echo "$test:Finish"
12961302
}
@@ -2377,4 +2383,4 @@ testInspektorGadgetAssets
23772383
testPackageDownloadURLFallbackLogic
23782384
testFileOwnership $OS_SKU
23792385
testDiskQueueServiceIsActive
2380-
testAlgifAeadDisabled
2386+
testVulnerableKernelModulesDisabled

0 commit comments

Comments
 (0)