diff --git a/cfbs.json b/cfbs.json index 549f89d..c225a1d 100644 --- a/cfbs.json +++ b/cfbs.json @@ -142,6 +142,47 @@ "bundles delete_home_dotshosts:main" ] }, + "dirtyfrag": { + "description": "Detect and optionally mitigate CVE-2026-43284 (DirtyFrag) and CVE-2026-43500 in the Linux kernel.", + "tags": ["security", "inventory", "detection", "mitigation"], + "subdirectory": "security/dirtyfrag", + "steps": [ + "copy dirtyfrag.cf services/cfbs/modules/dirtyfrag/dirtyfrag.cf", + "copy patched-kernels.json services/cfbs/modules/dirtyfrag/patched-kernels.json", + "policy_files services/cfbs/modules/dirtyfrag/dirtyfrag.cf", + "bundles dirtyfrag:main", + "input ./input.json def.json" + ], + "input": [ + { + "type": "string", + "variable": "mitigate_esp", + "namespace": "dirtyfrag", + "bundle": "main", + "label": "Mitigate CVE-2026-43284 (ESP/IPComp)", + "question": "Blacklist esp4, esp6, ipcomp, ipcomp6 kernel modules? (breaks IPsec) [true/false]", + "default": "false" + }, + { + "type": "string", + "variable": "mitigate_rxrpc", + "namespace": "dirtyfrag", + "bundle": "main", + "label": "Mitigate CVE-2026-43500 (RxRPC)", + "question": "Blacklist rxrpc kernel module? (breaks AFS/RxRPC) [true/false]", + "default": "false" + }, + { + "type": "string", + "variable": "mitigate_userns", + "namespace": "dirtyfrag", + "bundle": "main", + "label": "Mitigate CVE-2026-43284 via user namespaces", + "question": "Set user.max_user_namespaces=0? (blocks ESP exploit without disabling IPsec, may break rootless containers) [true/false]", + "default": "false" + } + ] + }, "demo": { "description": "Enables convenient and insecure settings for demoing CFEngine.", "subdirectory": "management/demo", diff --git a/security/dirtyfrag/README.md b/security/dirtyfrag/README.md new file mode 100644 index 0000000..5c529a7 --- /dev/null +++ b/security/dirtyfrag/README.md @@ -0,0 +1,190 @@ +Dirty Frag is a pair of kernel page-cache write vulnerabilities affecting Linux kernel modules that use nonlinear sk_buff (skb) fragments. An unprivileged local attacker with access to a network namespace can trigger out-of-bounds memory writes, potentially leading to privilege escalation. + +- **CVE-2026-43284** (xfrm-ESP/IPComp): Affects `esp4.ko`, `esp6.ko`, `ipcomp.ko`, and `ipcomp6.ko` modules when unprivileged user namespaces are enabled. Patched in stable kernel trees as of May 2026. +- **CVE-2026-43500** (RxRPC): Affects `rxrpc.ko` module. Patches available for some distros as of May 2026; mitigation via module blacklisting where unpatched. + +## Vulnerability conditions + +- **CVE-2026-43284**: Requires `esp4`, `esp6`, `ipcomp`, or `ipcomp6` kernel modules present AND `/proc/sys/kernel/unprivileged_userns_clone` set to `1` +- **CVE-2026-43500**: Requires `rxrpc` kernel module present (no additional prerequisites) + +## Inventory + +After adding this module you can view Dirty Frag vulnerability status in Mission Portal Inventory Report: + +[![Inventory showing Dirty Frag status](https://raw.githubusercontent.com/cfengine/modules/master/security/dirtyfrag/inventory-status.png)](https://raw.githubusercontent.com/cfengine/modules/master/security/dirtyfrag/inventory-status.png) + +- **Dirty Frag CVE-2026-43284 (xfrm-ESP) status**: + - `VULNERABLE (esp4, esp6 loaded)` -- vulnerable modules currently in memory (names vary by host) + - `VULNERABLE (modules on disk, none loaded)` -- modules present but not loaded; latent risk + - `PATCHED (kernel fix applied)` -- running kernel version includes the fix (auto-detected or admin-declared) + - `MITIGATED (blacklist in place)` -- modprobe blacklist active for esp4/esp6/ipcomp/ipcomp6 + - `MITIGATED (userns disabled)` -- `user.max_user_namespaces=0` sysctl active, blocking the exploit path without unloading IPsec + - `NOT AFFECTED` -- vulnerable modules not present on this host +- **Dirty Frag CVE-2026-43500 (RxRPC) status**: + - `VULNERABLE (rxrpc loaded)` -- module currently in memory + - `VULNERABLE (module on disk, not loaded)` -- module present but not loaded; latent risk + - `PATCHED (kernel fix applied)` -- running kernel version includes the fix (auto-detected or admin-declared) + - `MITIGATED (blacklist in place)` -- modprobe blacklist active + - `NOT AFFECTED` -- rxrpc module not present on this host + +## Mitigation + +Each CVE has an independent toggle and separate conf file: + +**CVE-2026-43284** (ESP/IPComp) -- `/etc/modprobe.d/dirtyfrag-esp.conf`: + +``` +# Dirty Frag CVE-2026-43284 mitigation: block xfrm-ESP and IPComp +install esp4 /bin/false +install esp6 /bin/false +install ipcomp /bin/false +install ipcomp6 /bin/false +``` + +**CVE-2026-43500** (RxRPC) -- `/etc/modprobe.d/dirtyfrag-rxrpc.conf`: + +``` +# Dirty Frag CVE-2026-43500 mitigation: block RxRPC +install rxrpc /bin/false +``` + +This prevents the vulnerable modules from loading. When mitigation is first applied, already-loaded modules are unloaded via `rmmod`. + +**CVE-2026-43284 alternative** (user namespaces) -- `/etc/sysctl.d/dirtyfrag-userns.conf`: + +``` +# Dirty Frag CVE-2026-43284 mitigation: disable unprivileged user namespaces +# Blocks ESP/IPComp exploit without disabling IPsec. +# WARNING: May affect rootless containers and sandboxed applications. +user.max_user_namespaces = 0 +``` + +This blocks the ESP/IPComp exploit path without blacklisting the modules, preserving IPsec functionality. Use this instead of `mitigate_esp` on hosts that require IPsec. Note: this does **not** mitigate CVE-2026-43500 (RxRPC) and may break rootless containers (Podman, Docker rootless), Flatpak, and browser sandboxes. Applied via `sysctl --system` on first write. + +All mitigations are **disabled by default** -- the module only reports status unless the corresponding CMDB variable (`dirtyfrag:main.mitigate_esp`, `dirtyfrag:main.mitigate_rxrpc`, or `dirtyfrag:main.mitigate_userns`) is set to `"true"`. See the table below for the full set of toggles. + +## Usage + +Add the policy to your inputs: + +``` +inputs "security/dirtyfrag/dirtyfrag.cf" +``` + +To enable mitigation, set one or both variables in your site's `def.json` (Augments): + +```json +{ + "variables": { + "dirtyfrag:main.mitigate_esp": { "value": "true" }, + "dirtyfrag:main.mitigate_rxrpc": { "value": "true" }, + "dirtyfrag:main.mitigate_userns": { "value": "true" }, + "dirtyfrag:main.esp_patched": { "value": "true" }, + "dirtyfrag:main.rxrpc_patched": { "value": "true" } + } +} +``` + +| Variable | What it does | Trade-off | +|----------|-------------|-----------| +| `mitigate_esp` | Blacklists esp4, esp6, ipcomp, ipcomp6 | Breaks IPsec | +| `mitigate_rxrpc` | Blacklists rxrpc | Breaks AFS/RxRPC | +| `mitigate_userns` | Sets `user.max_user_namespaces=0` | May break rootless containers/sandboxes | +| `esp_patched` | Declare CVE-2026-43284 as patched | Admin must verify kernel is actually patched | +| `rxrpc_patched` | Declare CVE-2026-43500 as patched | Admin must verify kernel is actually patched | + +Typical combinations: +- **Most hosts**: `mitigate_esp` + `mitigate_rxrpc` (full protection) +- **IPsec hosts**: `mitigate_userns` + `mitigate_rxrpc` (preserves IPsec) +- **Container hosts needing IPsec**: `mitigate_rxrpc` only (partial, accept ESP risk until patched kernel) + +Default behavior (variables unset) is status-only reporting. + +## Detection details + +The module checks for vulnerable modules in three ways: + +1. **On-disk `.ko` files** under `/lib/modules/$(kernel_version)/` +2. **Compressed variants** (`.ko.zst`, `.ko.xz`) on distros that compress modules +3. **Currently loaded modules** via `/sys/module/` entries + +For CVE-2026-43284, the module also checks whether unprivileged user namespaces are enabled (`/proc/sys/kernel/unprivileged_userns_clone`), since the exploit requires namespace access. + +## Kernel patch detection + +The module automatically detects whether the running kernel includes fixes for the Dirty Frag CVEs by comparing the kernel version (`uname -r`) against known-patched versions from distro security advisories. This data is maintained in `patched-kernels.json`, shipped alongside the policy. + +Currently tracked distros: + +| Distro | CVE-2026-43284 | CVE-2026-43500 | +|--------|---------------|---------------| +| RHEL/CentOS/Alma/Rocky 8 | 4.18.0-553.123.2 | 4.18.0-553.123.2 | +| RHEL/CentOS/Alma/Rocky 9 | 5.14.0-611.54.1 | 5.14.0-611.54.3 | +| RHEL/CentOS/Alma/Rocky 10 | 6.12.0-124.55.2 | 6.12.0-124.55.3 | +| Debian 11 (Bullseye) | 5.10.251-4 | 5.10.251-4 | +| Debian 12 (Bookworm) | 6.1.170-3 | 6.1.170-3 | +| Debian 13 (Trixie) | 6.12.86-1 | 6.12.86-1 | +| SLES 15 SP7 | 6.4.0-150700.53.45.1 | 6.4.0-150700.53.45.1 | + +When a patched kernel is detected, the status reports `PATCHED (kernel fix applied)` instead of `VULNERABLE`. The module uses `sort -V` (version sort from coreutils) to compare kernel versions. + +For distros not in the data file, or hosts running custom/backported kernels, set the admin override variables `esp_patched` and/or `rxrpc_patched` to `"true"` via augments. + +To update the patched kernel data, edit `patched-kernels.json` and redeploy. The data file is intentionally separate from the policy so it can be updated independently. + +## Adding exceptions + +To exclude specific hosts from mitigation, use conditional augments to override them to a value other than `"true"`. + +## Mission Portal — operator reference + +The module is structured to give Mission Portal operators four things: inventory columns for at-a-glance state, a filterable mitigation-method enum, queryable classes for targeting, and CVE-linked promise stakeholders for audit traceability. + +### Inventory columns + +| Attribute name | Values | Filterable as | +|---|---|---| +| `Dirty Frag CVE-2026-43284 (xfrm-ESP) status` | `VULNERABLE (...)`, `PATCHED (kernel fix applied)`, `MITIGATED (blacklist in place)`, `MITIGATED (userns disabled)`, `NOT AFFECTED` | starts-with regex (`^VULNERABLE`, `^PATCHED`, `^MITIGATED`) | +| `Dirty Frag CVE-2026-43500 (RxRPC) status` | same shape, no userns option | same | +| `Dirty Frag CVE-2026-43284 mitigation method` | `kernel-patch`, `modprobe`, `userns`, `admin-override`, `none`, `not-applicable` | exact match | +| `Dirty Frag CVE-2026-43500 mitigation method` | `kernel-patch`, `modprobe`, `admin-override`, `none`, `not-applicable` | exact match | + +The detailed status strings are for humans; the method enums are for dashboards and filters. + +### Queryable classes (collected as `report`, not in default inventory columns) + +| Class | Meaning | Use case | +|---|---|---| +| `dirtyfrag_vulnerable` | Any tracked CVE is unmitigated on this host | Targeting: "patch these now" | +| `dirtyfrag_esp_mitigated` | ESP mitigation path is active (any of blacklist / userns / patched) | Audit: confirm coverage | +| `dirtyfrag_rxrpc_mitigated` | RxRPC mitigation path is active | Same | +| `dirtyfrag_esp_needs_mitigation` | ESP exposure exists and no mitigation in place | Targeting fragments | +| `dirtyfrag_rxrpc_needs_mitigation` | Same for RxRPC | Same | +| `dirtyfrag_esp_present`, `dirtyfrag_rxrpc_present` | Module is loadable or loaded | Scoping | + +Tagged with `meta => { "report" }` so cf-hub collects them for queries but they don't add columns to the default inventory view. + +### Recommended alerts + +Configure in Mission Portal → **Alerts**. The module ships no alerts itself; operators wire conditions appropriate to their fleet's risk tolerance: + +- **Inventory condition** — alert when `Dirty Frag CVE-2026-43284 (xfrm-ESP) status` matches regex `^VULNERABLE`. Catches new unmitigated hosts as they join the fleet or as kernels regress. +- **Inventory condition** — alert when `Dirty Frag CVE-2026-43284 mitigation method` equals `userns` on more than N hosts. The userns path is fragile (breaks rootless containers); knowing how many hosts depend on it informs upgrade prioritization. +- **Policy condition** — alert on any **promises not kept** for handles `dirtyfrag_esp_modprobe_blacklist`, `dirtyfrag_rxrpc_modprobe_blacklist`, `dirtyfrag_esp_modprobe_unload`, `dirtyfrag_rxrpc_modprobe_unload`, `dirtyfrag_userns_sysctl_conf`, `dirtyfrag_userns_sysctl_reapply`. These are the file/command promises that enforce mitigation; a failure means the conf wasn't written or the module wasn't unloaded. + +### Promise handles (for report filtering) + +| Handle | What | When it fails | +|---|---|---| +| `dirtyfrag_esp_modprobe_blacklist` | Writes `/etc/modprobe.d/dirtyfrag-esp.conf` | Filesystem error, conflicting writes | +| `dirtyfrag_rxrpc_modprobe_blacklist` | Writes `/etc/modprobe.d/dirtyfrag-rxrpc.conf` | Same | +| `dirtyfrag_userns_sysctl_conf` | Writes `/etc/sysctl.d/dirtyfrag-userns.conf` | Same | +| `dirtyfrag_esp_modprobe_unload` | `modprobe -r` on currently-loaded ESP/IPComp modules | Module busy (existing IPsec tunnel) | +| `dirtyfrag_rxrpc_modprobe_unload` | `modprobe -r rxrpc` | Module busy | +| `dirtyfrag_userns_sysctl_reapply` | `sysctl --system` to load the userns conf | sysctl conf rejected | + +### Compliance traceability + +Every file and command promise carries a **promisee arrow** linking to its CVE: `"${path}" -> { "CVE-2026-43284" }` and `"${path}" -> { "CVE-2026-43500" }`. In Mission Portal's promise detail view, this surfaces as the stakeholder / linked identifier. Searching the audit history for `CVE-2026-43284` returns every policy artifact addressing it. + diff --git a/security/dirtyfrag/dirtyfrag.cf b/security/dirtyfrag/dirtyfrag.cf new file mode 100644 index 0000000..80bbdf1 --- /dev/null +++ b/security/dirtyfrag/dirtyfrag.cf @@ -0,0 +1,438 @@ +body file control +{ + namespace => "dirtyfrag"; +} + +bundle agent main +# @brief Detects Dirty Frag (CVE-2026-43284, CVE-2026-43500) vulnerability +# and applies CMDB-toggleable mitigations. CMDB variables, mitigation +# trade-offs and inventory states are documented in README.md. +# +# @inventory Dirty Frag CVE-2026-43284 (xfrm-ESP) status - Vulnerability/mitigation state for the xfrm-ESP/IPComp CVE. +# @inventory Dirty Frag CVE-2026-43500 (RxRPC) status - Vulnerability/mitigation state for the RxRPC CVE. +# @inventory Dirty Frag CVE-2026-43284 mitigation method - Which mitigation strategy is currently active for ESP (kernel-patch, modprobe, userns, admin-override, none, not-applicable). +# @inventory Dirty Frag CVE-2026-43500 mitigation method - Which mitigation strategy is currently active for RxRPC (kernel-patch, modprobe, admin-override, none, not-applicable). +# @class dirtyfrag_vulnerable - true if any tracked CVE is unmitigated on this host (queryable; not in default inventory column set). +# @class dirtyfrag_esp_mitigated - true if any ESP mitigation path is active on this host. +# @class dirtyfrag_rxrpc_mitigated - true if any RxRPC mitigation path is active on this host. +{ + vars: + # --- Patched-kernel data lookup --------------------------------- + # Minimum patched kernel package versions per distro live in + # patched-kernels.json next to this policy. The data file is + # walked here; comparison happens in the classes:: block below. + "_data_file" + string => "$(this.promise_dirname)/patched-kernels.json"; + + "_data" + data => readjson("${_data_file}"), + if => fileexists("${_data_file}"); + + # Indexing goes inside ${...}; see lessons-learned/data-indexing.md. + "_entries_idx" + slist => getindices("_data[entries]"), + if => isvariable("_data"); + + "_os_id" + string => "$(default:sys.os_release[ID])", + if => isvariable("default:sys.os_release[ID]"); + + "_os_ver" + string => "$(default:sys.os_release[VERSION_ID])", + if => isvariable("default:sys.os_release[VERSION_ID]"); + + # Find matching entry: the entry whose id_match and version_match + # both match this host's os-release ID and VERSION_ID. + "_matched_idx" + string => "${_entries_idx}", + if => and( + regcmp("${_data[entries][${_entries_idx}][id_match]}", "${_os_id}"), + regcmp("${_data[entries][${_entries_idx}][version_match]}", "${_os_ver}") + ); + + "_esp_patched_ver" + string => "${_data[entries][${_matched_idx}][cve_2026_43284]}", + if => isvariable("_matched_idx"); + + "_rxrpc_patched_ver" + string => "${_data[entries][${_matched_idx}][cve_2026_43500]}", + if => isvariable("_matched_idx"); + + # --- Installed kernel package version -------------------------- + # RHEL family: sys.release is the package NEVR -- compare directly. + # Debian/Ubuntu: sys.release is an ABI version; the package version + # lives in package metadata. packagesmatching() reads the + # package_inventory cache populated by the standard masterfiles + # apt_get inventory. See lessons-learned/packagesmatching.md. + # Other: fall back to sys.release as best-effort. + "_kernel_pkgs" + data => packagesmatching( + "linux-image-$(default:sys.release)", ".*", ".*", "apt_get" + ), + if => regcmp("debian|ubuntu", "$(default:sys.os_release[ID])"); + + "_kernel_pkg_ver" + string => "$(_kernel_pkgs[0][version])", + if => isvariable("_kernel_pkgs[0][version]"); + + "_kernel_pkg_ver" + string => "$(default:sys.release)", + if => not(regcmp("debian|ubuntu", "$(default:sys.os_release[ID])")); + + # --- Mitigation-conf paths and contents ----------------------- + "_esp_conf_path" string => "/etc/modprobe.d/dirtyfrag-esp.conf"; + "_rxrpc_conf_path" string => "/etc/modprobe.d/dirtyfrag-rxrpc.conf"; + "_userns_conf_path" string => "/etc/sysctl.d/dirtyfrag-userns.conf"; + + "_esp_conf_content" + string => concat( + "# Dirty Frag CVE-2026-43284 mitigation: block xfrm-ESP and IPComp$(const.n)", + "install esp4 /bin/false$(const.n)", + "install esp6 /bin/false$(const.n)", + "install ipcomp /bin/false$(const.n)", + "install ipcomp6 /bin/false$(const.n)" + ); + + "_rxrpc_conf_content" + string => concat( + "# Dirty Frag CVE-2026-43500 mitigation: block RxRPC$(const.n)", + "install rxrpc /bin/false$(const.n)" + ); + + "_userns_conf_content" + string => concat( + "# Dirty Frag CVE-2026-43284 mitigation: disable unprivileged user namespaces$(const.n)", + "# Blocks ESP/IPComp exploit without disabling IPsec.$(const.n)", + "# WARNING: May affect rootless containers and sandboxed applications.$(const.n)", + "user.max_user_namespaces = 0$(const.n)" + ); + + # --- Module files on disk ------------------------------------- + # Single findfiles glob covers .ko, .ko.xz, .ko.zst and any future + # compression scheme. Empty list means the modules aren't shipped + # for this kernel build. + "_esp_module_files" + slist => findfiles( + "/lib/modules/$(default:sys.release)/kernel/net/ipv4/esp4.ko*", + "/lib/modules/$(default:sys.release)/kernel/net/ipv6/esp6.ko*", + "/lib/modules/$(default:sys.release)/kernel/net/ipv4/ipcomp.ko*", + "/lib/modules/$(default:sys.release)/kernel/net/ipv6/ipcomp6.ko*" + ); + + "_rxrpc_module_files" + slist => findfiles( + "/lib/modules/$(default:sys.release)/kernel/net/rxrpc/rxrpc.ko*" + ); + + # --- Unprivileged user namespace setting ---------------------- + # sysctlvalue works across RHEL/SLES/Debian regardless of the + # /proc/sys/kernel/unprivileged_userns_clone vs user.max_user_namespaces + # path differences. + "_max_userns" string => sysctlvalue("user.max_user_namespaces"); + + # --- Loaded-module names for the VULNERABLE status string ----- + # Each slot is the module name when loaded, "" otherwise. + # filter(".+", ...) drops the empties; join glues with ", ". + "_esp_loaded_list" + slist => { + ifelse("_esp4_loaded", "esp4", ""), + ifelse("_esp6_loaded", "esp6", ""), + ifelse("_ipcomp_loaded", "ipcomp", ""), + ifelse("_ipcomp6_loaded", "ipcomp6", ""), + }; + + "_esp_loaded_names" + string => join(", ", + filter(".+", "_esp_loaded_list", "false", "false", "10")); + + # --- Status strings ------------------------------------------- + "_esp_status" + string => ifelse( + "dirtyfrag_esp_needs_mitigation._esp_any_loaded", + "VULNERABLE ($(_esp_loaded_names) loaded)", + "dirtyfrag_esp_needs_mitigation", + "VULNERABLE (modules on disk, none loaded)", + "_esp_kernel_patched|_esp_admin_patched", + "PATCHED (kernel fix applied)", + "dirtyfrag_esp_present._esp_mitigated", + "MITIGATED (blacklist in place)", + "dirtyfrag_esp_present._userns_conf_exists", + "MITIGATED (userns disabled)", + "NOT AFFECTED" + ); + + "_rxrpc_status" + string => ifelse( + "dirtyfrag_rxrpc_needs_mitigation._rxrpc_loaded", + "VULNERABLE (rxrpc loaded)", + "dirtyfrag_rxrpc_needs_mitigation", + "VULNERABLE (module on disk, not loaded)", + "_rxrpc_kernel_patched|_rxrpc_admin_patched", + "PATCHED (kernel fix applied)", + "dirtyfrag_rxrpc_present._rxrpc_mitigated", + "MITIGATED (blacklist in place)", + "NOT AFFECTED" + ); + + # --- Per-CVE mitigation method (one-word enum) ----------------- + # Single keyword per CVE so MP can filter "show me all hosts where + # mitigation method is `userns`" -- much cleaner than regex-matching + # the long _esp_status string. ifelse takes first-match order. + "_esp_mitigation_method" + string => ifelse( + "dirtyfrag_esp_needs_mitigation", "none", + "_esp_kernel_patched", "kernel-patch", + "_esp_admin_patched", "admin-override", + "_esp_conf_exists", "modprobe", + "_userns_conf_exists", "userns", + "not-applicable" + ); + + "_rxrpc_mitigation_method" + string => ifelse( + "dirtyfrag_rxrpc_needs_mitigation", "none", + "_rxrpc_kernel_patched", "kernel-patch", + "_rxrpc_admin_patched", "admin-override", + "_rxrpc_conf_exists", "modprobe", + "not-applicable" + ); + + # --- Inventory output for Mission Portal ---------------------- + # The CVE id and module family live in attribute_name (the column + # header); the value is just the state so it's easy to filter on. + "inventory_dirtyfrag_esp" + string => "$(_esp_status)", + meta => { + "inventory", + "attribute_name=Dirty Frag CVE-2026-43284 (xfrm-ESP) status", + }, + comment => "CVE-2026-43284 xfrm-ESP mitigation status"; + + "inventory_dirtyfrag_rxrpc" + string => "$(_rxrpc_status)", + meta => { + "inventory", + "attribute_name=Dirty Frag CVE-2026-43500 (RxRPC) status", + }, + comment => "CVE-2026-43500 RxRPC mitigation status"; + + "inventory_dirtyfrag_esp_method" + string => "$(_esp_mitigation_method)", + meta => { + "inventory", + "attribute_name=Dirty Frag CVE-2026-43284 mitigation method", + }, + comment => "Active mitigation strategy for CVE-2026-43284 (filterable enum)"; + + "inventory_dirtyfrag_rxrpc_method" + string => "$(_rxrpc_mitigation_method)", + meta => { + "inventory", + "attribute_name=Dirty Frag CVE-2026-43500 mitigation method", + }, + comment => "Active mitigation strategy for CVE-2026-43500 (filterable enum)"; + + classes: + # --- CMDB toggles --------------------------------------------- + "_mitigate_esp" expression => strcmp("true", "$(mitigate_esp)"); + "_mitigate_rxrpc" expression => strcmp("true", "$(mitigate_rxrpc)"); + "_mitigate_userns" expression => strcmp("true", "$(mitigate_userns)"); + + # --- Admin override: manually declare host as patched --------- + "_esp_admin_patched" expression => strcmp("true", "$(esp_patched)"); + "_rxrpc_admin_patched" expression => strcmp("true", "$(rxrpc_patched)"); + + # --- Kernel-patch comparison ---------------------------------- + # sort -V -C exits 0 iff stdin is already version-sorted, so the + # pipeline below answers "is installed >= patched?" in one fork. + # This keeps useshell despite the noshell preference elsewhere: + # CFEngine has no semantic-version comparison primitive, sort -V + # needs stdin (no single-arg form), and the noshell alternatives + # (tempfile + commands:, or a bundled wrapper script) are heavier + # for no real safety gain on a static, internally-built string. + # See lessons-learned/version-compare-shell-exception.md. + "_patch_data_matched" + expression => isvariable("_matched_idx"); + + "_esp_kernel_patched" + expression => returnszero( + "printf '%s\\n%s\\n' '$(_esp_patched_ver)' '$(_kernel_pkg_ver)' | sort -V -C", + "useshell" + ), + if => and( + isvariable("_esp_patched_ver"), + isvariable("_kernel_pkg_ver") + ); + + "_rxrpc_kernel_patched" + expression => returnszero( + "printf '%s\\n%s\\n' '$(_rxrpc_patched_ver)' '$(_kernel_pkg_ver)' | sort -V -C", + "useshell" + ), + if => and( + isvariable("_rxrpc_patched_ver"), + isvariable("_kernel_pkg_ver") + ); + + # --- Unprivileged user namespace enabled? --------------------- + "_userns_disabled" expression => strcmp("0", "${_max_userns}"); + "_userns_enabled" not => "_userns_disabled"; + + # --- xfrm-ESP/IPComp modules ---------------------------------- + # On-disk presence collapses to: did findfiles return anything? + # some(".+", slist) is true when the list has any non-empty element. + "_esp_files_present" expression => some(".+", "_esp_module_files"); + + "_esp4_loaded" expression => isdir("/sys/module/esp4"); + "_esp6_loaded" expression => isdir("/sys/module/esp6"); + "_ipcomp_loaded" expression => isdir("/sys/module/ipcomp"); + "_ipcomp6_loaded" expression => isdir("/sys/module/ipcomp6"); + + "_esp_any_loaded" + or => { + "_esp4_loaded", "_esp6_loaded", "_ipcomp_loaded", "_ipcomp6_loaded" + }; + + "dirtyfrag_esp_present" + or => { "_esp_files_present", "_esp_any_loaded" }; + + # --- RxRPC module --------------------------------------------- + "_rxrpc_files_present" expression => some(".+", "_rxrpc_module_files"); + "_rxrpc_loaded" expression => isdir("/sys/module/rxrpc"); + + "dirtyfrag_rxrpc_present" + or => { "_rxrpc_files_present", "_rxrpc_loaded" }; + + # --- Mitigation conf files in place? -------------------------- + "_esp_conf_exists" expression => fileexists("${_esp_conf_path}"); + "_rxrpc_conf_exists" expression => fileexists("${_rxrpc_conf_path}"); + "_userns_conf_exists" expression => fileexists("${_userns_conf_path}"); + + # ESP is mitigated by modprobe blacklist, patched kernel, or + # admin override. Userns mitigation is handled in the + # vulnerability condition (see dirtyfrag_esp_needs_mitigation). + "_esp_mitigated" + or => { + "_esp_conf_exists", + "_esp_kernel_patched", + "_esp_admin_patched", + }; + + # RxRPC is mitigated by the modprobe blacklist, patched kernel, + # or admin override. + "_rxrpc_mitigated" + or => { + "_rxrpc_conf_exists", + "_rxrpc_kernel_patched", + "_rxrpc_admin_patched", + }; + + # --- Per-CVE vulnerability checks ----------------------------- + # ESP is vulnerable if modules are present, not mitigated, + # AND userns mitigation conf is not in place. Userns conf is + # not in _esp_mitigated (which tracks blacklist/mitigation + # confs); it's tracked here so disabling userns blocks the + # exploit vector directly. + "dirtyfrag_esp_needs_mitigation" + and => { "dirtyfrag_esp_present", "!_esp_mitigated", "!_userns_conf_exists" }, + meta => { "report" }, + scope => "namespace"; + + "dirtyfrag_rxrpc_needs_mitigation" + and => { "dirtyfrag_rxrpc_present", "!_rxrpc_mitigated" }, + meta => { "report" }, + scope => "namespace"; + + # --- Composite roll-ups for targeting and querying ------------ + # Tagged `report` (not `inventory`) so cf-hub collects them but they + # don't clutter the default Mission Portal inventory columns. Use + # for class filters in reports, alerts, and CMDB-style targeting. + "dirtyfrag_vulnerable" + or => { "dirtyfrag_esp_needs_mitigation", "dirtyfrag_rxrpc_needs_mitigation" }, + meta => { "report" }, + scope => "namespace"; + + "dirtyfrag_esp_mitigated" + or => { "_esp_mitigated", "_userns_conf_exists" }, + meta => { "report" }, + scope => "namespace"; + + "dirtyfrag_rxrpc_mitigated" + or => { "_rxrpc_mitigated" }, + meta => { "report" }, + scope => "namespace"; + + files: + # CVE refs go in the promisee slot (-> { ... }); Mission Portal + # surfaces this as the "stakeholder / linked identifier" on the + # promise detail page so operators can search "CVE-2026-43284" + # and find every policy artifact addressing it. + # handle gives each promise a stable, queryable name in reports. + + _mitigate_esp:: + "${_esp_conf_path}" -> { "CVE-2026-43284" } + handle => "dirtyfrag_esp_modprobe_blacklist", + create => "true", + content => "${_esp_conf_content}", + comment => "Blacklist xfrm-ESP/IPComp modules to mitigate Dirty Frag CVE-2026-43284"; + + _mitigate_rxrpc:: + "${_rxrpc_conf_path}" -> { "CVE-2026-43500" } + handle => "dirtyfrag_rxrpc_modprobe_blacklist", + create => "true", + content => "${_rxrpc_conf_content}", + comment => "Blacklist RxRPC module to mitigate Dirty Frag CVE-2026-43500"; + + _mitigate_userns:: + "${_userns_conf_path}" -> { "CVE-2026-43284" } + handle => "dirtyfrag_userns_sysctl_conf", + create => "true", + content => "${_userns_conf_content}", + comment => "Disable unprivileged user namespaces to block CVE-2026-43284 ESP exploit path"; + + commands: + # Key off "module is loaded + mitigation desired", not "conf was + # just written". The previous _conf_repaired guard missed the + # case where the conf file exists from a prior run and the module + # got loaded since -- the agent would never re-run the unload. + # modprobe -r (vs rmmod) tolerates "some of the named modules + # aren't loaded", which simplifies the per-module gating to a + # single class. + + _mitigate_esp._esp_any_loaded:: + "/sbin/modprobe" -> { "CVE-2026-43284" } + handle => "dirtyfrag_esp_modprobe_unload", + arglist => { "-r", "esp4", "esp6", "ipcomp", "ipcomp6" }, + comment => "Unload ESP/IPComp modules while ESP mitigation is active"; + + _mitigate_rxrpc._rxrpc_loaded:: + "/sbin/modprobe" -> { "CVE-2026-43500" } + handle => "dirtyfrag_rxrpc_modprobe_unload", + arglist => { "-r", "rxrpc" }, + comment => "Unload RxRPC module while RxRPC mitigation is active"; + + # sysctl --system is idempotent; re-running it when the conf says + # 0 but the live value isn't 0 picks up cases where the conf was + # written but never applied (e.g. between writing and the next + # boot). + _mitigate_userns._userns_enabled:: + "/sbin/sysctl" -> { "CVE-2026-43284" } + handle => "dirtyfrag_userns_sysctl_reapply", + arglist => { "--system" }, + comment => "Re-apply user.max_user_namespaces=0 while userns is enabled"; + + reports: + inform_mode._patch_data_matched:: + "Dirty Frag: matched distro $(_os_id) $(_os_ver) (entry $(_matched_idx)); kernel package version=$(_kernel_pkg_ver)"; + + inform_mode._esp_kernel_patched:: + "Dirty Frag CVE-2026-43284: kernel $(_kernel_pkg_ver) >= $(_esp_patched_ver) (PATCHED)"; + + inform_mode._rxrpc_kernel_patched:: + "Dirty Frag CVE-2026-43500: kernel $(_kernel_pkg_ver) >= $(_rxrpc_patched_ver) (PATCHED)"; + + inform_mode:: + + "Dirty Frag CVE-2026-43284 (xfrm-ESP/IPComp): $(_esp_status)"; + "Dirty Frag CVE-2026-43500 (RxRPC): $(_rxrpc_status)"; +} diff --git a/security/dirtyfrag/inventory-status.png b/security/dirtyfrag/inventory-status.png new file mode 100644 index 0000000..9bb8165 Binary files /dev/null and b/security/dirtyfrag/inventory-status.png differ diff --git a/security/dirtyfrag/patched-kernels.json b/security/dirtyfrag/patched-kernels.json new file mode 100644 index 0000000..928be01 --- /dev/null +++ b/security/dirtyfrag/patched-kernels.json @@ -0,0 +1,87 @@ +{ + "_meta": { + "description": "Minimum patched kernel package versions for Dirty Frag CVEs. Keyed by distro match (os_release ID + major version). The policy matches the running host against these entries and uses sort -V to compare the installed kernel package version against the minimum patched version.", + "updated": "2026-05-15", + "sources": [ + "https://access.redhat.com/security/vulnerabilities/RHSB-2026-003", + "https://security-tracker.debian.org/tracker/CVE-2026-43284", + "https://security-tracker.debian.org/tracker/CVE-2026-43500", + "https://ubuntu.com/security/CVE-2026-43500", + "https://almalinux.org/blog/2026-05-07-dirty-frag/", + "https://explore.alas.aws.amazon.com/CVE-2026-43284.html" + ], + "placeholder_note": "TBD-pending-USN sorts higher than any numeric version under sort -V, so the policy will report VULNERABLE until the entry is updated with the real fix version. Admins on hosts known to be patched can set dirtyfrag:main.esp_patched / .rxrpc_patched in site augments to override." + }, + "entries": [ + { + "label": "RHEL/CentOS/Alma/Rocky/OL 7 (ELS)", + "id_match": "^(rhel|centos|rocky|almalinux|ol)$", + "version_match": "^7(\\..*|$)", + "cve_2026_43284": "TBD-pending-USN", + "cve_2026_43500": "TBD-pending-USN" + }, + { + "label": "RHEL/CentOS/Alma/Rocky/OL 8", + "id_match": "^(rhel|centos|rocky|almalinux|ol)$", + "version_match": "^8(\\..*|$)", + "cve_2026_43284": "4.18.0-553.123.2", + "cve_2026_43500": "4.18.0-553.123.2" + }, + { + "label": "RHEL/CentOS/Alma/Rocky/OL 9", + "id_match": "^(rhel|centos|rocky|almalinux|ol)$", + "version_match": "^9(\\..*|$)", + "cve_2026_43284": "5.14.0-611.54.1", + "cve_2026_43500": "5.14.0-611.54.3" + }, + { + "label": "RHEL/CentOS/Alma/Rocky/OL 10", + "id_match": "^(rhel|centos|rocky|almalinux|ol)$", + "version_match": "^10(\\..*|$)", + "cve_2026_43284": "6.12.0-124.55.2", + "cve_2026_43500": "6.12.0-124.55.3" + }, + { + "label": "Debian 11 (Bullseye)", + "id_match": "^debian$", + "version_match": "^11(\\..*|$)", + "cve_2026_43284": "5.10.251-4", + "cve_2026_43500": "5.10.251-4" + }, + { + "label": "Debian 12 (Bookworm)", + "id_match": "^debian$", + "version_match": "^12(\\..*|$)", + "cve_2026_43284": "6.1.170-3", + "cve_2026_43500": "6.1.170-3" + }, + { + "label": "Debian 13 (Trixie)", + "id_match": "^debian$", + "version_match": "^13(\\..*|$)", + "cve_2026_43284": "6.12.86-1", + "cve_2026_43500": "6.12.86-1" + }, + { + "label": "Ubuntu 20.04 LTS (focal, ESM)", + "id_match": "^ubuntu$", + "version_match": "^20\\.04", + "cve_2026_43284": "TBD-pending-USN", + "cve_2026_43500": "TBD-pending-USN" + }, + { + "label": "Ubuntu 22.04 LTS (jammy)", + "id_match": "^ubuntu$", + "version_match": "^22\\.04", + "cve_2026_43284": "TBD-pending-USN", + "cve_2026_43500": "TBD-pending-USN" + }, + { + "label": "Ubuntu 24.04 LTS (noble)", + "id_match": "^ubuntu$", + "version_match": "^24\\.04", + "cve_2026_43284": "TBD-pending-USN", + "cve_2026_43500": "TBD-pending-USN" + } + ] +}