From 8afac267cbca4bfd0402ab7b27862ad4287c5500 Mon Sep 17 00:00:00 2001 From: Peter Macko <44851174+macko1@users.noreply.github.com> Date: Fri, 1 May 2026 10:11:15 +0200 Subject: [PATCH 1/6] Revert changes and only refactor grub_bootloader_argument OVAL template --- .../grub2_bootloader_argument/oval.template | 705 +++++++++++++----- 1 file changed, 503 insertions(+), 202 deletions(-) diff --git a/shared/templates/grub2_bootloader_argument/oval.template b/shared/templates/grub2_bootloader_argument/oval.template index 480a4e6caee1..13b407ad4f4b 100644 --- a/shared/templates/grub2_bootloader_argument/oval.template +++ b/shared/templates/grub2_bootloader_argument/oval.template @@ -1,305 +1,583 @@ {{#- - We set defaults to "off", and products should enable relevant ones depending on how the product configures grub. - - /boot/loader/entries/* may not exist. - - If they exist, they can reference variables defined in grubenv, or they can contain literal args - - The grub cfg may either use those loader entries, or it can contain literal values as well - - Kernel opts can be stored in /etc/default/grub so they are persistent between kernel upgrades + grub2_bootloader_argument OVAL template + ======================================== + + Checks that a kernel boot argument (e.g. audit=1) is set across all + relevant grub configuration files for the given product. + + LOCATIONS WHERE KERNEL ARGS LIVE (per product): + ================================================ + + Product Boot entries Persistent config Other + --------------- ---------------------------------- ------------------------ --------------------------- + RHEL 9+, Fedora /boot/loader/entries/*.conf /etc/default/grub (bootc: kargs.d/*.toml) + (args inline on "options" line) + RHEL 8, OL8 /boot/loader/entries/*.conf /etc/default/grub + (args inline OR via $kernelopts) + + /boot/grub2/grubenv (kernelopts=) + OL7 /boot/grub2/grub.cfg (vmlinuz) /etc/default/grub + Ubuntu /boot/grub2/grub.cfg (vmlinuz) /etc/default/grub /etc/default/grub.d/*.cfg + + WHAT THIS TEMPLATE DOES: + ======================== + + Kernel boot arguments (e.g. audit=1, pti=on, audit_backlog_limit=8192) are + settings passed to the Linux kernel at boot time via the bootloader (grub2). + Security policies require certain arguments to be set so the kernel enables + specific security features (auditing, memory protections, CPU mitigations, etc). + + The problem is that grub2 stores these arguments in DIFFERENT files depending + on the OS version and platform. This template generates an OVAL check that + verifies the argument is present in ALL the right places for the given product. + + KEY CONCEPTS: + ============= + + --- Boot entries (what the system actually boots) --- + + /boot/loader/entries/*.conf (BLS = Boot Loader Specification entries) + One .conf file per installed kernel. Each has an "options" line with kernel args. + RHEL 9+/Fedora: args are always inline on the options line. + RHEL 8/OL8: args can be inline OR the entry can say "$kernelopts", + which grub expands from /boot/grub2/grubenv at boot time. + + /boot/grub2/grubenv (RHEL 8 / OL8 only) + grub environment file. Contains "kernelopts=..." with the actual kernel args. + Only relevant when boot entries reference $kernelopts instead of listing args inline. + + $kernelopts (RHEL 8 / OL8 only) + A grub environment variable. Boot entries can say "$kernelopts" on their options + line instead of listing args inline. grub expands it from grubenv at boot time. + On RHEL 9+ this indirection no longer exists -- args are always inline. + + /boot/grub2/grub.cfg (OL7, Ubuntu only) + Generated grub config. Contains "linux /vmlinuz-... root=... arg=val ..." lines. + These platforms dont use BLS entries -- grub.cfg IS the boot config. + + --- Persistent config (survives grub2-mkconfig regeneration) --- + + /etc/default/grub (all products) + Persistent grub configuration. grub2-mkconfig reads this file and bakes the + values into grub.cfg or boot entries. Contains two relevant variables: + GRUB_CMDLINE_LINUX="..." -- args passed to ALL boot entries + GRUB_CMDLINE_LINUX_DEFAULT="..." -- args passed to non-recovery entries ONLY + If GRUB_DISABLE_RECOVERY=true is also set, there are no recovery entries, + so _DEFAULT effectively applies to all entries too. + + /etc/default/grub.d/*.cfg (Ubuntu only) + Drop-in override files for /etc/default/grub. Same variables, same semantics. + Checked in addition to /etc/default/grub on Ubuntu. + + --- bootc / RHEL Image Mode --- + + bootc (RHEL Image Mode) is a way to run RHEL as an immutable container-based OS. + The root filesystem is a container image. There is no traditional grub config -- + the bootloader is managed by bootc itself. Kernel args are defined in TOML files + shipped inside the image. + + /usr/lib/bootc/kargs.d/*.toml + TOML files with kargs = ["arg=val", "arg2=val2"]. + On bootc systems, grub files (/boot/loader/entries, grubenv, grub.cfg, etc.) + do NOT exist. Kernel args live here instead. + The OVAL definition has two mutually exclusive branches: one for bootc systems + (checks kargs.d) and one for normal grub systems (checks grub files). + + CRITERIA TREE (what must pass for the definition to be compliant): + ================================================================== + + definition (OR) + | + +-- [bootc] AND + | +-- extend_definition: IS bootc + | +-- test: kargs.d/*.toml has arg=value + | + +-- [normal grub] AND + +-- extend_definition: NOT bootc (if bootc supported) + | + +-- [RHEL 8 / OL8] entries check: + | +-- test: EACH /boot/loader/entries/*.conf has arg=value inline OR $kernelopts + | +-- (OR) + | +-- NO entry uses $kernelopts (skip grubenv) + | +-- grubenv has arg=value (BIOS or UEFI path) + | + +-- [RHEL 9+ / Fedora] entries check: + | +-- test: EACH /boot/loader/entries/*.conf has arg=value inline + | + +-- [OL7 / Ubuntu] grub.cfg check: + | +-- (OR) grub.cfg has arg=value (BIOS or UEFI path) + | + +-- [all products] /etc/default/grub check: + +-- (OR) + +-- GRUB_CMDLINE_LINUX has arg=value + | (+ grub.d drop-in on Ubuntu) + +-- AND + +-- GRUB_CMDLINE_LINUX_DEFAULT has arg=value + | (+ grub.d drop-in on Ubuntu) + +-- GRUB_DISABLE_RECOVERY=true + + DATA FLOW (current -- will change in the rewrite): + ==================================================== + + Currently, objects extract the FULL options/cmdline line, and the shared + state uses a regex to check if arg=value appears as a word in that line. + This means all comparisons are "pattern match" on "string" datatype. + + Object (extracts full line) State (regex checks arg=value in line) + ┌─────────────────────────┐ ┌────────────────────────────────────┐ + │ /boot/loader/entries/ │ │ subexpression ~ "pattern match" │ + │ ^options (.*)$ │────────>│ ^(?:.*\s)?arg=value(?:\s.*)?$ │ + │ │ │ │ + │ /boot/grub2/grubenv │ │ (same state, shared by all tests) │ + │ ^kernelopts=(.*)$ │────────>│ │ + │ │ └────────────────────────────────────┘ + │ /etc/default/grub │ │ + │ ^GRUB_CMDLINE_LINUX= │────────────────────────┘ + │ "(.*)"$ │ + └─────────────────────────┘ + + TARGET (after rewrite -- sysctl-style extract-then-compare): + + Object (extracts JUST the value) State (native OVAL comparison) + ┌─────────────────────────┐ ┌────────────────────────────────────┐ + │ /boot/loader/entries/ │ │ subexpression │ + │ ^options.*arg=(\S+) │────────>│ operation="equals" │ + │ │ │ datatype="int" │ + │ /boot/grub2/grubenv │ │ value=8192 │ + │ ^kernelopts.*arg= │────────>│ │ + │ (\S+) │ │ (same state, shared by all tests) │ + │ │ └────────────────────────────────────┘ + │ /etc/default/grub │ │ + │ ^GRUB_CMDLINE_LINUX= │────────────────────────┘ + │ ".*arg=(\S+).*"$ │ + └─────────────────────────┘ + + The check_* flags below control which locations are verified per product. -#}} -{{% set system_with_expanded_kernel_options_in_loader_entries = false -%}} -{{% set system_with_kernel_options_in_grubenv = false -%}} -{{% set system_with_expanded_kernel_options_in_loader_entries_or_with_options_in_grubenv = false -%}} -{{% set system_with_kernel_options_in_etc_default_grub = true -%}} -{{% set system_with_kernel_options_in_etc_default_grub_d = false -%}} -{{% set system_with_expanded_kernel_options_in_grub_cfg = false -%}} -{{% set system_with_bios_and_uefi_support = false -%}} - -{{% if product in ["fedora", "ol9", "rhel9", "rhel10"] -%}} -{{% set system_with_expanded_kernel_options_in_loader_entries = true %}} -{{%- endif -%}} - -{{% if product in ["ol8", "rhel8"] -%}} -{{% set system_with_expanded_kernel_options_in_loader_entries_or_with_options_in_grubenv = true -%}} -{{%- endif -%}} - -{{% if product in ["ol7"] or 'ubuntu' in product -%}} -{{% set system_with_expanded_kernel_options_in_grub_cfg = true %}} -{{%- endif -%}} - -{{% if 'ubuntu' in product -%}} -{{% set system_with_kernel_options_in_etc_default_grub_d = true -%}} -{{%- endif -%}} - -{{% if grub2_uefi_boot_path and grub2_uefi_boot_path != grub2_boot_path -%}} -{{% set system_with_bios_and_uefi_support = true %}} -{{%- endif -%}} +{{% set check_boot_loader_entries = product in ["fedora", "ol9", "rhel9", "rhel10"] %}} +{{% set check_boot_loader_entries_or_grubenv = product in ["ol8", "rhel8"] %}} +{{% set check_etc_default_grub = true %}} +{{% set check_etc_default_grub_d = 'ubuntu' in product %}} +{{% set check_grub_cfg = product in ["ol7"] or 'ubuntu' in product %}} +{{% set has_separate_bios_and_uefi = grub2_uefi_boot_path and grub2_uefi_boot_path != grub2_boot_path %}} + +{{# DEFINITION: {{{ _RULE_ID }}} + Class: compliance + Purpose: ensure the kernel boot argument {{{ ARG_NAME_VALUE }}} is set in all + relevant grub configuration files for this product. + Top-level OR: bootc branch vs normal grub branch. + Only one branch can be true at runtime (bootc XOR normal grub). + See the ASCII art criteria tree at the top of this file for the full picture. #}} - {{{ oval_metadata("Ensure " + ARG_NAME_VALUE + " is configured in the kernel line in /etc/default/grub.", rule_title=rule_title) }}} + {{{ oval_metadata("Ensure " + ARG_NAME_VALUE + " is set as a kernel boot argument.", rule_title=rule_title) }}} - - {{% if bootable_containers_supported == "true" %}} - - {{% endif %}} - {{% if system_with_expanded_kernel_options_in_loader_entries_or_with_options_in_grubenv %}} - - - - {{% if system_with_bios_and_uefi_support -%}} - - {{%- endif %}} - - {{% if system_with_bios_and_uefi_support -%}} - - - {{%- endif %}} - - {{% elif system_with_kernel_options_in_grubenv -%}} - - {{% if system_with_bios_and_uefi_support -%}} - - {{%- endif %}} - - {{% if system_with_bios_and_uefi_support -%}} - + + {{%- if bootable_containers_supported == "true" %}} + {{# CRITERIA BRANCH: bootc / RHEL Image Mode + Platforms: RHEL 9+, RHEL 10 with bootc support (bootable_containers_supported) + The system must be bootc AND kargs.d/*.toml must contain {{{ ARG_NAME_VALUE }}}. + On non-bootc systems, the extend_definition fails and this whole branch is skipped. #}} + + + {{%- endif %}} - {{% elif system_with_expanded_kernel_options_in_loader_entries -%}} - - {{%- endif %}} - {{% if system_with_expanded_kernel_options_in_grub_cfg -%}} - {{% if system_with_bios_and_uefi_support -%}} + + {{# CRITERIA BRANCH: normal grub (non-bootc) + Platforms: all products + All sub-checks must pass: bootc guard, boot entries, and /etc/default/grub. + The specific sub-checks emitted depend on the check_* flags set per product. #}} + + + {{%- if bootable_containers_supported == "true" %}} + {{# EXTEND_DEFINITION: NOT bootc guard + Platforms: RHEL 9+, RHEL 10 with bootc support (bootable_containers_supported) + Fails this "normal grub" branch on bootc systems where grub files do not exist. + negate="true" -- passes only if the system is NOT bootc. #}} + + {{%- endif %}} + + {{%- if check_boot_loader_entries_or_grubenv %}} + {{# CRITERIA BRANCH: RHEL 8 / OL8 boot loader entries + $kernelopts + grubenv + Platforms: RHEL 8, OL8 (check_boot_loader_entries_or_grubenv) + Two-part check: + 1. EACH /boot/loader/entries/*.conf (omit rescue) has {{{ ARG_NAME_VALUE }}} + inline on the options line OR contains $kernelopts. + 2. If any entry uses $kernelopts, then grubenv must also have {{{ ARG_NAME_VALUE }}}. + The $kernelopts indirection is an RHEL 8 mechanism: entries say "$kernelopts" and + grub expands it from /boot/grub2/grubenv at boot time. #}} + + + {{# CRITERIA: $kernelopts grubenv gate + Passes if no entry uses $kernelopts (all args on options line, grubenv irrelevant) + OR grubenv has {{{ ARG_NAME_VALUE }}} (BIOS or UEFI path). #}} + + + {{# CRITERION: no $kernelopts in any entry (negated) + If no entry uses $kernelopts, all args are on the options line and grubenv is irrelevant. #}} + + + {{# CRITERIA: grubenv has arg=value + BIOS: {grub2_boot_path}/grubenv (e.g. /boot/grub2/grubenv) + UEFI: {grub2_uefi_boot_path}/grubenv (e.g. /boot/efi/EFI/redhat/grubenv) + UEFI criterion only emitted when has_separate_bios_and_uefi is true. #}} + + {{%- if has_separate_bios_and_uefi %}} + {{%- endif %}} - - {{% if system_with_bios_and_uefi_support -%}} - + + {{%- endif %}} + + {{%- if check_boot_loader_entries %}} + {{# CRITERION: RHEL 9+ / Fedora / OL9 boot loader entries + Platforms: RHEL 9+, Fedora, OL9 (check_boot_loader_entries) + Checks: EACH /boot/loader/entries/*.conf (omit rescue) has {{{ ARG_NAME_VALUE }}} + directly on the options line. No $kernelopts indirection on these platforms. #}} + + {{%- endif %}} + + {{%- if check_grub_cfg %}} + {{# CRITERIA: OL7 / Ubuntu grub.cfg + Platforms: OL7, Ubuntu (check_grub_cfg) + BIOS: {grub2_boot_path}/grub.cfg (e.g. /boot/grub2/grub.cfg) + UEFI: {grub2_uefi_boot_path}/grub.cfg (e.g. /boot/efi/EFI/redhat/grub.cfg) + UEFI criterion only emitted when has_separate_bios_and_uefi is true. #}} + + + {{%- if has_separate_bios_and_uefi %}} + {{%- endif %}} - {{%- endif %}} - {{% if system_with_kernel_options_in_etc_default_grub -%}} + + {{%- endif %}} + + {{% if check_etc_default_grub %}} + {{# CRITERIA: /etc/default/grub persistent configuration + Platforms: all (check_etc_default_grub = true) + Passes if GRUB_CMDLINE_LINUX has {{{ ARG_NAME_VALUE }}} (applies to all entries), + OR GRUB_CMDLINE_LINUX_DEFAULT has it + GRUB_DISABLE_RECOVERY=true. + _DEFAULT only covers non-recovery boots, so disabling recovery ensures coverage. #}} + + {{# CRITERIA: GRUB_CMDLINE_LINUX (applies to all entries) + Checks /etc/default/grub, plus grub.d drop-ins on Ubuntu. #}} - - {{% if system_with_kernel_options_in_etc_default_grub_d -%}} - - {{%- endif %}} + + {{% if check_etc_default_grub_d %}} + + {{% endif %}} + + {{# CRITERIA: GRUB_CMDLINE_LINUX_DEFAULT + GRUB_DISABLE_RECOVERY=true + _DEFAULT only applies to non-recovery entries, so recovery must be disabled + to ensure the arg is on ALL boot entries. Checks /etc/default/grub, + plus grub.d drop-ins on Ubuntu. #}} - - {{% if system_with_kernel_options_in_etc_default_grub_d -%}} - - {{%- endif %}} + + {{% if check_etc_default_grub_d %}} + + {{% endif %}} + comment="Verify GRUB_DISABLE_RECOVERY=true in /etc/default/grub" /> - {{%- endif %}} - - {{% if bootable_containers_supported == "true" %}} - - - + {{% endif %}} + - {{% endif %}} + -{{% if system_with_expanded_kernel_options_in_loader_entries_or_with_options_in_grubenv %}} - - + - + {{# Collect the "options" line from each /boot/loader/entries/*.conf. + Platforms: RHEL 8, OL8 (check_boot_loader_entries_or_grubenv) + Each .conf file produces one collected item. Rescue entries (*rescue.conf) excluded. + Shared by two tests: the arg-or-$kernelopts check and the $kernelopts-presence check. #}} + /boot/loader/entries/ ^.*\.conf$ + {{# regex: ^options (.*)$ -- captures the full space-separated arg list after "options " #}} ^options (.*)$ 1 - state_grub2_rescue_entry_for_{{{ _RULE_ID }}} + state_grub2_{{{ _RULE_ID }}}_is_rescue_entry - + {{# Exclude filter: matches filenames ending in "rescue.conf". + Used by boot entry objects to skip rescue entries -- we only care about normal entries. #}} + + {{# regex: .*rescue\.conf$ -- matches rescue entry filenames to exclude them #}} .*rescue\.conf$ - - + {{% endif %}} - -{{%- if system_with_kernel_options_in_etc_default_grub %}} - - + - + {{# Collect GRUB_CMDLINE_LINUX="..." from /etc/default/grub. + Platforms: all (check_etc_default_grub = true) + Extracts everything between the double quotes as subexpression. #}} + /etc/default/grub + {{# regex: ^\s*GRUB_CMDLINE_LINUX="(.*)"$ -- captures everything between the quotes #}} ^\s*GRUB_CMDLINE_LINUX="(.*)"$ 1 - - + - + {{# Collect GRUB_CMDLINE_LINUX_DEFAULT="..." from /etc/default/grub. + Platforms: all (check_etc_default_grub = true) + Extracts everything between the double quotes as subexpression. + Only passed to non-recovery boot entries by grub2-mkconfig. #}} + /etc/default/grub + {{# regex: ^\s*GRUB_CMDLINE_LINUX_DEFAULT="(.*)"$ -- captures everything between the quotes #}} ^\s*GRUB_CMDLINE_LINUX_DEFAULT="(.*)"$ 1 {{%- endif %}} -{{% if system_with_kernel_options_in_etc_default_grub_d -%}} - - - - - - - - - +{{%- if check_etc_default_grub_d %}} + {{# Check GRUB_CMDLINE_LINUX in /etc/default/grub.d/*.cfg drop-ins for {{{ ARG_NAME_VALUE }}}. + Platforms: Ubuntu (check_etc_default_grub_d) + Same as the /etc/default/grub check but from drop-in config files. + Passes if at least one drop-in has it (check="at least one"). #}} + + + + - - /etc/default/grub.d/[^/]+\.cfg - ^\s*GRUB_CMDLINE_LINUX="(.*)"$ - 1 - + {{# Check GRUB_CMDLINE_LINUX_DEFAULT in /etc/default/grub.d/*.cfg drop-ins for {{{ ARG_NAME_VALUE }}}. + Platforms: Ubuntu (check_etc_default_grub_d) + Same as the /etc/default/grub check but from drop-in config files. + Requires GRUB_DISABLE_RECOVERY=true (same as the non-drop-in variant). #}} + + + + - - /etc/default/grub.d/*.cfg - ^\s*GRUB_CMDLINE_LINUX_DEFAULT="(.*)"$ - 1 - + {{# Collect GRUB_CMDLINE_LINUX="..." from /etc/default/grub.d/*.cfg drop-in files. + Platforms: Ubuntu (check_etc_default_grub_d) + Same regex as the /etc/default/grub object, but iterates over all .cfg drop-ins. #}} + + {{# regex: /etc/default/grub.d/[^/]+\.cfg -- match .cfg drop-in files (Ubuntu) #}} + /etc/default/grub.d/[^/]+\.cfg + {{# regex: same as /etc/default/grub -- captures everything between the quotes #}} + ^\s*GRUB_CMDLINE_LINUX="(.*)"$ + 1 + + + {{# Collect GRUB_CMDLINE_LINUX_DEFAULT="..." from /etc/default/grub.d/*.cfg drop-in files. + Platforms: Ubuntu (check_etc_default_grub_d) + Same regex as the /etc/default/grub object, but iterates over all .cfg drop-ins. + Only applies to non-recovery entries (same as GRUB_CMDLINE_LINUX_DEFAULT). #}} + + /etc/default/grub.d/*.cfg + {{# regex: same as /etc/default/grub -- captures everything between the quotes #}} + ^\s*GRUB_CMDLINE_LINUX_DEFAULT="(.*)"$ + 1 + {{%- endif %}} -{{%- if system_with_kernel_options_in_grubenv or system_with_expanded_kernel_options_in_loader_entries_or_with_options_in_grubenv %}} -{{%- macro test_and_object_for_kernel_options_grub_env(base_name, path) %}} - - + - + {{# Collect kernelopts=... from grubenv (everything after "kernelopts="). + This is the grub environment variable that boot entries expand via $kernelopts. #}} + {{{ path }}} + {{# regex: ^kernelopts=(.*)$ -- captures the full space-separated arg list from kernelopts #}} ^kernelopts=(.*)$ 1 -{{%- endmacro %}} + {{%- endmacro %}} -{{{ test_and_object_for_kernel_options_grub_env("grub2_" ~ SANITIZED_ARG_NAME ~ "_argument_grub_env", grub2_boot_path ~ "/grubenv") }}} -{{% if system_with_bios_and_uefi_support -%}} -{{{ test_and_object_for_kernel_options_grub_env("grub2_" ~ SANITIZED_ARG_NAME ~ "_argument_grub_env_uefi", grub2_uefi_boot_path ~ "/grubenv") }}} -{{%- endif %}} + {{{- test_and_object_for_grubenv() }}} + {{%- if has_separate_bios_and_uefi -%}} + {{{- test_and_object_for_grubenv(variant="uefi") }}} + {{%- endif %}} {{%- endif %}} -{{%- if system_with_expanded_kernel_options_in_loader_entries %}} +{{%- if check_boot_loader_entries %}} + {{# Check EACH /boot/loader/entries/*.conf (omit rescue) for {{{ ARG_NAME_VALUE }}} on options line. + Platforms: RHEL 9+, Fedora, OL9 (check_boot_loader_entries) + No $kernelopts indirection -- RHEL 9+ has all kernel args on the options line. #}} - + {{# Collect the "options" line from each /boot/loader/entries/*.conf. + Platforms: RHEL 9+, Fedora, OL9 (check_boot_loader_entries) + Each .conf file produces one collected item. Rescue entries (*rescue.conf) excluded. + Unlike the RHEL 8 variant, no $kernelopts indirection -- args are always on the options line. #}} + /boot/loader/entries/ ^.*\.conf$ + {{# regex: ^options (.*)$ -- captures the full space-separated arg list after "options " #}} ^options (.*)$ 1 - state_grub2_rescue_entry_for_{{{ _RULE_ID }}} + state_grub2_{{{ _RULE_ID }}}_is_rescue_entry - + {{# Exclude filter: matches filenames ending in "rescue.conf" (same as above). #}} + + {{# regex: .*rescue\.conf$ -- matches rescue entry filenames to exclude them #}} .*rescue\.conf$ {{%- endif %}} -{{%- if system_with_expanded_kernel_options_in_grub_cfg %}} -{{%- macro test_and_object_for_kernel_options_grub_cfg(base_name, path) %}} - - + - + {{# Collect kernel command line from vmlinuz lines in grub.cfg. + Captures from "root=" onwards, which includes all kernel args. + Each vmlinuz line corresponds to one boot entry. #}} + {{{ path }}} - {{% if product in ["ol7"] or 'ubuntu' in product %}} - ^.*/vmlinuz.*(root=.*)$ - {{% else %}} - ^set default_kernelopts=(.*)$ - {{% endif %}} + {{# regex: ^.*/vmlinuz.*(root=.*)$ -- captures from "root=" onwards on vmlinuz lines (OL7, Ubuntu) #}} + ^.*/vmlinuz.*(root=.*)$ 1 -{{%- endmacro %}} + {{%- endmacro %}} -{{{ test_and_object_for_kernel_options_grub_cfg("grub2_" + SANITIZED_ARG_NAME + "_argument_grub_cfg", grub2_boot_path ~ "/grub.cfg") }}} -{{% if system_with_bios_and_uefi_support -%}} -{{{ test_and_object_for_kernel_options_grub_cfg("grub2_" + SANITIZED_ARG_NAME + "_argument_grub_cfg_uefi", grub2_uefi_boot_path ~ "/grub.cfg") }}} -{{%- endif %}} + {{{ test_and_object_for_grub_cfg("bios", grub2_boot_path ~ "/grub.cfg") }}} + {{%- if has_separate_bios_and_uefi -%}} + {{{- test_and_object_for_grub_cfg("uefi", grub2_uefi_boot_path ~ "/grub.cfg") }}} + {{%- endif %}} {{%- endif %}} -{{% if system_with_expanded_kernel_options_in_loader_entries_or_with_options_in_grubenv %}} - +{{% if check_boot_loader_entries_or_grubenv %}} + {{# Match "$kernelopts" as a whole word in the captured "options" line. + Platforms: RHEL 8, OL8 (check_boot_loader_entries_or_grubenv) + $kernelopts is a grub env variable expanded at boot time from grubenv. #}} + {{# regex: ^(?:.*\s)?\$kernelopts(?:\s.*)?$ -- checks if $kernelopts appears as a word in the options line (RHEL 8 indirection) #}} + ^(?:.*\s)?\$kernelopts(?:\s.*)?$ {{% endif %}} -{{% if not ARG_VARIABLE %}} - - ^(?:.*\s)?{{{ ESCAPED_ARG_NAME_VALUE }}}(?:\s.*)?$ - -{{% else %}} - +{{# Match {{{ ARG_NAME_VALUE }}} as a whole word in the captured options/kernelopts/cmdline line. + Shared by ALL grub-location tests (except bootc kargs.d which has its own state). + If ARG_VARIABLE is set, the regex is built at scan time from an XCCDF variable. + Otherwise, the expected value is hardcoded into the regex at build time. + NOTE: in the rewrite, this entire state + local_variable goes away -- + objects will extract just the value, and the state will use operation+datatype natively. #}} +{{% if ARG_VARIABLE %}} + {{# Value comes from XCCDF variable {{{ ARG_VARIABLE }}} -- regex built at scan time. #}} + + {{# regex built at scan time via local_variable concat -- checks "arg=" as a word in the line #}} + {{# Build regex: ^(?:.*\s)?{ARG_NAME}=(?:\s.*)?$ + Matches arg=value as a whole word. IS_SUBSTRING wraps value with \S* (partial match). #}} ^(?:.*\s)?{{{ ARG_NAME }}}= @@ -314,31 +592,48 @@ - + {{# Expected value of {{{ ARG_NAME }}}, provided by XCCDF benchmark at scan time. #}} + +{{% else %}} + {{# Expected value hardcoded at build time: {{{ ESCAPED_ARG_NAME_VALUE }}} #}} + + {{# regex: ^(?:.*\s)?{ARG_NAME_VALUE}(?:\s.*)?$ -- checks "arg=value" appears as a whole word in the full options line #}} + ^(?:.*\s)?{{{ ESCAPED_ARG_NAME_VALUE }}}(?:\s.*)?$ + {{% endif %}} -{{% if bootable_containers_supported == "true" %}} +{{# Bootc / RHEL Image Mode: kernel args live in /usr/lib/bootc/kargs.d/*.toml instead of grub. #}} +{{%- if bootable_containers_supported == "true" %}} + {{# Check /usr/lib/bootc/kargs.d/*.toml for {{{ ARG_NAME_VALUE }}} in the kargs array. + Platforms: RHEL 9+, RHEL 10 with bootc support (bootable_containers_supported) + Passes if at least one .toml file has it (check="at least one"). + Has its own state (not shared) because TOML wraps values in quotes. #}} - + - + {{# Collect kargs array from /usr/lib/bootc/kargs.d/*.toml. + Captures everything between the square brackets: kargs = ["arg=val", "arg2=val2"] #}} + /usr/lib/bootc/kargs.d/ ^.*\.toml$ + {{# regex: ^kargs = \[([^\]]+)\]$ -- captures contents of the TOML kargs array, e.g. "arg=val", "arg2=val2" #}} ^kargs = \[([^\]]+)\]$ 1 -{{% if not ARG_VARIABLE %}} - - ^.*"{{{ ESCAPED_ARG_NAME_VALUE }}}".*$ - -{{% else %}} + {{# Match {{{ ARG_NAME_VALUE }}} as a quoted string in the TOML kargs array (e.g. "arg=val"). + Separate from state_argument because TOML wraps values in double quotes. + Same ARG_VARIABLE / hardcoded split as state_argument. #}} + {{%- if ARG_VARIABLE %}} + {{# Build regex: ^.*"{ARG_NAME}=".*$ + Matches "arg=value" as a quoted string in TOML. IS_SUBSTRING wraps value with \S*. #}} @@ -355,8 +650,14 @@ - -{{% endif %}} + {{# Expected value of {{{ ARG_NAME }}}, same XCCDF variable as above. #}} + + {{%- else %}} + {{# Expected value hardcoded at build time, with surrounding quotes for TOML format. #}} + + ^.*"{{{ ESCAPED_ARG_NAME_VALUE }}}".*$ + + {{%- endif %}} {{% endif %}} From cdff9b37b3c9c18f51137bf6d30af6ed74a34360 Mon Sep 17 00:00:00 2001 From: Peter Macko <44851174+macko1@users.noreply.github.com> Date: Thu, 14 May 2026 12:37:19 +0200 Subject: [PATCH 2/6] Add operation and datatype support to template.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The grub2_bootloader_argument template could only do exact string matching — no numeric comparisons. Follow the sysctl template pattern and add operation and datatype parameters. - Compute test scenario values (pass/fail/boundary) per operation - Validate operation/datatype combinations at build time - Require explicit operation/datatype for arg_variable rules - Require arg_value to be a quoted string in rule.yml - Remove deprecated is_substring parameter - Rename sanitized_arg_name to arg_name_underscored - Update bash.template accordingly --- .../grub2_bootloader_argument/bash.template | 2 +- .../grub2_bootloader_argument/template.py | 218 ++++++++++++++++-- 2 files changed, 201 insertions(+), 19 deletions(-) diff --git a/shared/templates/grub2_bootloader_argument/bash.template b/shared/templates/grub2_bootloader_argument/bash.template index f43f3484c039..ec0016ec3f5a 100644 --- a/shared/templates/grub2_bootloader_argument/bash.template +++ b/shared/templates/grub2_bootloader_argument/bash.template @@ -14,7 +14,7 @@ if {{{ bash_bootc_build() }}} ; then if grep -q -E "{{{ ARG_NAME }}}" "$KARGS_DIR/*.toml" ; then sed -i -E "s/^(\s*kargs\s*=\s*\[.*)\"{{{ ARG_NAME }}}=[^\"]*\"(.*]\s*)/\1\"{{{ ARG_NAME_VALUE }}}\"\2/" "$KARGS_DIR/*.toml" else - echo "kargs = [\"{{{ ARG_NAME_VALUE }}}\"]" >> "$KARGS_DIR/10-{{{ SANITIZED_ARG_NAME }}}.toml" + echo "kargs = [\"{{{ ARG_NAME_VALUE }}}\"]" >> "$KARGS_DIR/10-{{{ ARG_NAME_UNDERSCORED }}}.toml" fi else {{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME_VALUE) | indent(4) }}} diff --git a/shared/templates/grub2_bootloader_argument/template.py b/shared/templates/grub2_bootloader_argument/template.py index a777f04dcf13..a897314fbca8 100644 --- a/shared/templates/grub2_bootloader_argument/template.py +++ b/shared/templates/grub2_bootloader_argument/template.py @@ -1,29 +1,211 @@ import ssg.utils -def preprocess(data, lang): - if 'arg_value' in data and 'arg_variable' in data: +# Valid (datatype, operation) combinations for the OVAL ```` +# element. Ordering operations on strings would do lexicographic comparison, +# which is never what we want for kernel boot arguments. Pattern match on int +# is equally nonsensical. +VALID_OPERATIONS_BY_DATATYPE = { + "string": [ + "equals", + "not equal", + "pattern match", + ], + "int": [ + "equals", + "not equal", + "greater than", + "less than", + "greater than or equal", + "less than or equal", + ], +} + +VALID_DATATYPES = list(VALID_OPERATIONS_BY_DATATYPE.keys()) + + +def validate_rule_template_arguments(data, rule_id): + """Validate and set defaults for rule.yml template arguments. + + Sets operation/datatype defaults for non-variable rules, requires + explicit values for arg_variable rules, then validates all combinations. + """ + arg_value = data.get("arg_value") + arg_variable = data.get("arg_variable") + + # arg_value and arg_variable are mutually exclusive + if arg_value is not None and arg_variable is not None: raise RuntimeError( - "ERROR: The template should not set both 'arg_value' and 'arg_variable'.\n" - "arg_name: {0}\n" - "arg_variable: {1}".format(data['arg_value'], data['arg_variable'])) + f"The template must not set both 'arg_value' and 'arg_variable'.\n" + f"arg_name: {arg_value}\n" + f"arg_variable: {arg_variable}" + ) + + # arg_value must be quoted in rule.yml. YAML auto-parses unquoted + # scalars: 8192 becomes int, on/off become bool True/False. The + # template needs a string to build regexes and config file content — + # a silent int or bool would produce wrong patterns downstream. + if arg_value is not None and type(arg_value) is not str: + raise TypeError( + f"arg_value must be a quoted string in rule.yml, got " + f"{type(arg_value).__name__}: {arg_value!r} " + f"for rule '{rule_id}'" + ) - if 'arg_variable' in data: - data["arg_name_value"] = data["arg_name"] + # arg_variable rules must set operation and datatype explicitly. + # The .var file defines the XCCDF variable type (string/number) and + # operator (equals/greater than or equal/...). The OVAL state must + # use matching datatype and operation, but template.py cannot read + # .var files — so we force the rule author to declare both in + # rule.yml and keep them in sync with the .var file manually. + # Non-variable rules get safe defaults (equals/string). + if arg_variable is not None: + if "operation" not in data: + raise ValueError( + f"Missing 'operation' in rule '{rule_id}'. " + f"Required because arg_variable '{arg_variable}' is used — " + f"set it to match the 'operator' field in " + f"'{arg_variable}.var'." + ) + if "datatype" not in data: + raise ValueError( + f"Missing 'datatype' in rule '{rule_id}'. " + f"Required because arg_variable '{arg_variable}' is used — " + f"set it to match the 'type' field in " + f"'{arg_variable}.var'." + ) else: - if "arg_value" in data: - data["arg_name_value"] = data["arg_name"] + "=" + data["arg_value"] - else: - data["arg_name_value"] = data["arg_name"] + if "operation" not in data: + data["operation"] = "equals" + if "datatype" not in data: + data["datatype"] = "string" + + # From here on, operation and datatype are always present + operation = data["operation"] + datatype = data["datatype"] + + # Validate datatype/operation combination + if datatype not in VALID_DATATYPES: + raise ValueError( + f"Unsupported datatype '{datatype}' for rule '{rule_id}'. " + f"Must be one of: {VALID_DATATYPES}" + ) + + valid_ops = VALID_OPERATIONS_BY_DATATYPE[datatype] + if operation not in valid_ops: + raise ValueError( + f"Operation '{operation}' is not valid with " + f"datatype '{datatype}' for rule '{rule_id}'. " + f"Valid operations for '{datatype}': {valid_ops}" + ) + + # Any arg_value is a valid string, but int needs a parseable number + if datatype == "int" and arg_value is not None: + try: + int(arg_value) + except ValueError as err: + raise ValueError( + f"datatype is 'int' but arg_value '{arg_value}' " + f"is not a valid integer for rule '{rule_id}'." + ) from err + + +def preprocess(data, lang): + """Preprocess template variables before rendering. + + Validates, sets defaults, escapes names for OVAL regex, and computes + test scenario values. + """ + rule_id = data.get("_rule_id", "unknown_rule_id") # default set to trace errors easier + + validate_rule_template_arguments(data, rule_id) + + arg_name = data["arg_name"] + arg_value = data.get("arg_value") + arg_variable = data.get("arg_variable") + operation = data["operation"] + datatype = data["datatype"] - if 'is_substring' not in data: - data["is_substring"] = "false" + # --- Build derived variables --- + + # ARG_NAME_VALUE: + # arg_name and arg_value -> "arg_name=arg_value" + # arg_variable or just arg_name -> "arg_name" (arg_variable is injected in OVAL) + data["arg_name_value"] = ( + f"{arg_name}={arg_value}" if arg_value is not None + else arg_name + ) if lang == "oval": - # escape dot, this is used in oval regex - data["escaped_arg_name_value"] = data["arg_name_value"].replace(".", "\\.") - data["escaped_arg_name"] = data["arg_name"].replace(".", "\\.") - # replace . with _, this is used in test / object / state ids + # Dots in arg names must be escaped in OVAL regex patterns + # (e.g. ipv6.disable -> ipv6\.disable) + data["arg_name_escaped_dots"] = arg_name.replace(".", "\\.") + + # Dots replaced by underscores + # (e.g. ipv6.disable -> ipv6_disable) + data["arg_name_underscored"] = ssg.utils.escape_id(arg_name) + + # --- Test scenario values --- + # + # Automatus test scripts write values into GRUB config files, then oscap + # scans and the result is compared to the filename (.pass.sh / .fail.sh). + # Works because OVAL checks file contents directly — no service reload. + # + # arg_value rules: value known at build time, compute real pass/fail. + # arg_variable rules: value is an XCCDF variable resolved at scan time. + # template.py has no access to .var files, so we use + # arbitrary dummies (99999 for int). Test scripts emit a directive + # (# variables = var_name=value) that makes Automatus XSLT-patch the + # datastream so oscap resolves the variable to the dummy. + # Same approach as sysctl/template.py. + # flag-only GRUB argument (nousb): no value, skip. + # + if arg_variable is not None: + if datatype == "int": + data["test_value_pass"] = "99999" + data["test_value_fail"] = "99998" + data["value_below"] = "99998" + data["value_at_threshold"] = "99999" + data["value_above"] = "100000" + else: + # string variable — arbitrary placeholders + data["test_value_pass"] = "correct_value" + data["test_value_fail"] = "wrong_value" + + elif arg_value is not None: + # arg_value rules: value is known at build time, compute real test values + if datatype == "int": + threshold = int(arg_value) + value_below = threshold - 1 + value_at_threshold = threshold + value_above = threshold + 1 + + data["value_below"] = value_below + data["value_at_threshold"] = value_at_threshold + data["value_above"] = value_above + + if operation == "equals": + correct, wrong = value_at_threshold, value_above + elif operation == "greater than or equal": + correct, wrong = value_at_threshold, value_below + else: + raise ValueError( + f"No test values defined for int operation " + f"'{operation}' in rule '{rule_id}'" + ) + else: + # string with known value + if operation in ("equals", "pattern match"): + correct, wrong = arg_value, "wrong_value" + else: + raise ValueError( + f"No test values defined for string operation " + f"'{operation}' in rule '{rule_id}'" + ) + + data["test_value_pass"] = correct + data["test_value_fail"] = wrong + + # else: flag-only argument (e.g. nousb) — no value, no test values needed - data["sanitized_arg_name"] = ssg.utils.escape_id(data["arg_name"]) return data From 5e526c21892857848ac32adf9bb466988962ccbf Mon Sep 17 00:00:00 2001 From: Peter Macko <44851174+macko1@users.noreply.github.com> Date: Thu, 14 May 2026 12:37:25 +0200 Subject: [PATCH 3/6] Rewrite OVAL template to use operation and datatype MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Separate value extraction (object regex) from value comparison (state operation/datatype). The old template captured entire lines and validated with regex — now objects capture only the value and states compare it directly. - Objects extract only the argument value via capturing groups - States compare using OVAL-native operation and datatype - RHEL 8: split presence and value checks for entries that delegate to $kernelopts in grubenv - Fix RHEL 8 $kernelopts detection (check="all" -> "at least one") - Escape dots in bootc patterns (ipv6.disable) - nousb (flag-only args): check presence only, skip value comparison - bootc: reuse the same comparison state as other GRUB locations - Update header documentation to match new architecture --- .../grub2_bootloader_argument/oval.template | 1082 +++++++++-------- 1 file changed, 568 insertions(+), 514 deletions(-) diff --git a/shared/templates/grub2_bootloader_argument/oval.template b/shared/templates/grub2_bootloader_argument/oval.template index 13b407ad4f4b..a4ab9c24918f 100644 --- a/shared/templates/grub2_bootloader_argument/oval.template +++ b/shared/templates/grub2_bootloader_argument/oval.template @@ -1,296 +1,429 @@ {{#- - grub2_bootloader_argument OVAL template + `grub2_bootloader_argument` `OVAL` template ======================================== - Checks that a kernel boot argument (e.g. audit=1) is set across all - relevant grub configuration files for the given product. + Checks that a kernel boot argument (e.g. `audit=1`) is set across all + relevant `grub` configuration files for the given product. LOCATIONS WHERE KERNEL ARGS LIVE (per product): ================================================ - Product Boot entries Persistent config Other - --------------- ---------------------------------- ------------------------ --------------------------- - RHEL 9+, Fedora /boot/loader/entries/*.conf /etc/default/grub (bootc: kargs.d/*.toml) - (args inline on "options" line) - RHEL 8, OL8 /boot/loader/entries/*.conf /etc/default/grub - (args inline OR via $kernelopts) - + /boot/grub2/grubenv (kernelopts=) - OL7 /boot/grub2/grub.cfg (vmlinuz) /etc/default/grub - Ubuntu /boot/grub2/grub.cfg (vmlinuz) /etc/default/grub /etc/default/grub.d/*.cfg + [VISUAL TABLE: products vs. config file locations, aligned in columns.] + + Product Flags Boot entries Persistent config Other + --------------- ---------------------------------- ---------------------------------- ------------------------ --------------------------- + RHEL 9+, Fedora `uses_boot_loader_entries` `/boot/loader/entries/*.conf` `/etc/default/grub` (`bootc`: `/usr/lib/bootc/kargs.d/*.toml`) + (args on `options` line) + RHEL 8, OL8 `uses_boot_loader_entries` `/boot/loader/entries/*.conf` `/etc/default/grub` + `uses_kernelopts` (args on `options` line or via + `$kernelopts` from `grubenv`) + + `/boot/grub2/grubenv` + OL7 `uses_grub_cfg` `/boot/grub2/grub.cfg` `/etc/default/grub` + (args on `vmlinuz` lines) + Ubuntu `uses_grub_cfg` `/boot/grub/grub.cfg` `/etc/default/grub` `/etc/default/grub.d/*.cfg` + `uses_etc_default_grub_d` (args on `vmlinuz` lines) + + LONG DESCRIPTION (text equivalent of the table): + + RHEL 9+, Fedora: + Flags: `uses_boot_loader_entries` + Boot entries: `/boot/loader/entries/*.conf` (args on `options` line) + Persistent config: `/etc/default/grub` + Other: `bootc` systems use `/usr/lib/bootc/kargs.d/*.toml` instead + + RHEL 8, OL8: + Flags: `uses_boot_loader_entries`, `uses_kernelopts` + Boot entries: `/boot/loader/entries/*.conf` (args on `options` line or via + `$kernelopts` indirection from `/boot/grub2/grubenv`) + Persistent config: `/etc/default/grub` + + OL7: + Flags: `uses_grub_cfg` + Boot entries: `/boot/grub2/grub.cfg` (args on `vmlinuz` lines) + Persistent config: `/etc/default/grub` + + Ubuntu: + Flags: `uses_grub_cfg`, `uses_etc_default_grub_d` + Boot entries: `/boot/grub/grub.cfg` (args on `vmlinuz` lines) + Persistent config: `/etc/default/grub` + Other: `/etc/default/grub.d/*.cfg` (drop-in config files) WHAT THIS TEMPLATE DOES: ======================== - Kernel boot arguments (e.g. audit=1, pti=on, audit_backlog_limit=8192) are - settings passed to the Linux kernel at boot time via the bootloader (grub2). + Kernel boot arguments (e.g. `audit=1`, `pti=on`, `audit_backlog_limit=8192`) are + settings passed to the Linux kernel at boot time via the bootloader (`grub2`). Security policies require certain arguments to be set so the kernel enables - specific security features (auditing, memory protections, CPU mitigations, etc). + specific security features (auditing, memory protections, CPU mitigations, etc.). - The problem is that grub2 stores these arguments in DIFFERENT files depending - on the OS version and platform. This template generates an OVAL check that - verifies the argument is present in ALL the right places for the given product. + The problem is that `grub2` stores these arguments in different files depending + on the OS version and platform. This template generates an `OVAL` check that + verifies the argument is present in all the right places for the given product. KEY CONCEPTS: ============= --- Boot entries (what the system actually boots) --- - /boot/loader/entries/*.conf (BLS = Boot Loader Specification entries) - One .conf file per installed kernel. Each has an "options" line with kernel args. - RHEL 9+/Fedora: args are always inline on the options line. - RHEL 8/OL8: args can be inline OR the entry can say "$kernelopts", - which grub expands from /boot/grub2/grubenv at boot time. - - /boot/grub2/grubenv (RHEL 8 / OL8 only) - grub environment file. Contains "kernelopts=..." with the actual kernel args. - Only relevant when boot entries reference $kernelopts instead of listing args inline. - - $kernelopts (RHEL 8 / OL8 only) - A grub environment variable. Boot entries can say "$kernelopts" on their options - line instead of listing args inline. grub expands it from grubenv at boot time. - On RHEL 9+ this indirection no longer exists -- args are always inline. - - /boot/grub2/grub.cfg (OL7, Ubuntu only) - Generated grub config. Contains "linux /vmlinuz-... root=... arg=val ..." lines. - These platforms dont use BLS entries -- grub.cfg IS the boot config. - - --- Persistent config (survives grub2-mkconfig regeneration) --- - - /etc/default/grub (all products) - Persistent grub configuration. grub2-mkconfig reads this file and bakes the - values into grub.cfg or boot entries. Contains two relevant variables: - GRUB_CMDLINE_LINUX="..." -- args passed to ALL boot entries - GRUB_CMDLINE_LINUX_DEFAULT="..." -- args passed to non-recovery entries ONLY - If GRUB_DISABLE_RECOVERY=true is also set, there are no recovery entries, - so _DEFAULT effectively applies to all entries too. - - /etc/default/grub.d/*.cfg (Ubuntu only) - Drop-in override files for /etc/default/grub. Same variables, same semantics. - Checked in addition to /etc/default/grub on Ubuntu. - - --- bootc / RHEL Image Mode --- - - bootc (RHEL Image Mode) is a way to run RHEL as an immutable container-based OS. - The root filesystem is a container image. There is no traditional grub config -- - the bootloader is managed by bootc itself. Kernel args are defined in TOML files + `/boot/loader/entries/*.conf` (`BLS` = Boot Loader Specification entries) + `BLS` is a freedesktop.org standard: instead of one monolithic `grub.cfg` listing all + kernels, each installed kernel gets its own small `.conf` file with key-value lines + (`title`, `linux`, `initrd`, `options`), e.g.: + title Red Hat Enterprise Linux 9.3 + linux /vmlinuz-5.14.0-362.el9.x86_64 + initrd /initramfs-5.14.0-362.el9.x86_64.img + options root=/dev/mapper/rhel-root ro audit=1 pti=on + The `options` line is a space-separated list of kernel boot arguments passed + to the kernel at boot time. This is the line this template checks. + RHEL 9+/Fedora: kernel args are listed directly on the `options` line. + RHEL 8/OL8: kernel args are listed directly on the `options` line, + OR the `options` line contains `$kernelopts` -- a `grub` variable + that `grub` expands from `/boot/grub2/grubenv` at boot time. + + `/boot/grub2/grubenv` (RHEL 8 / OL8 only) + Grub environment file. Contains a line like: + kernelopts=root=/dev/mapper/rhel-root ro audit=1 pti=on + Only relevant when `BLS` entries use `$kernelopts` on their `options` line + instead of listing kernel args directly. + + `$kernelopts` (RHEL 8 / OL8 only, `/boot/loader/entries/*.conf` only) + `grub` can store named variables in `/boot/grub2/grubenv` (a `key=value` file on disk). + A `/boot/loader/entries/*.conf` file can reference a variable with `$name` on its + `options` line, and `grub` substitutes the stored value at boot time -- similar to + shell variable expansion. + On RHEL 8, the variable `$kernelopts` holds the kernel args. So a + `/boot/loader/entries/*.conf` file might say: + options $kernelopts + and `grub` replaces `$kernelopts` with the `kernelopts=...` value from + `/boot/grub2/grubenv`. + On RHEL 9+, this indirection no longer exists -- args are always listed directly. + This mechanism only exists in BLS entries (`/boot/loader/entries/*.conf`). Legacy + `grub.cfg` on OL7/Ubuntu does not use `$kernelopts`. + + PRECEDENCE (what overrides what): + + `/etc/default/grub` is the persistent source -- survives `grub2-mkconfig` regeneration. + `/boot/grub2/grubenv` (RHEL 8) is intermediate -- affects the current boot via + `$kernelopts` expansion, but does not survive regeneration. + `/boot/loader/entries/*.conf` `options` line (BLS) or `/boot/grub2/grub.cfg` + `linux /vmlinuz...` line (legacy) is what the kernel actually boots with. + `/usr/lib/bootc/kargs.d/*.toml` (in RHEL Image mode (ootc)) replaces everything above. + + The OVAL check verifies ALL layers because: + - If only `/etc/default/grub` is correct but boot entries are wrong, the system + is vulnerable . + - If only boot entries are correct but `/etc/default/grub` is wrong, the next + kernel update or `grub2-mkconfig` regeneration will break it. + + `/boot/grub2/grub.cfg` (exists on all platforms, but content differs) + Generated by `grub2-mkconfig` from `/etc/default/grub`. + On OL7 and Ubuntu (non-`BLS` platforms), `grub.cfg` contains the actual kernel + boot lines with one `linux /vmlinuz-...` line per installed kernel, e.g.: + linux /vmlinuz-5.4.0-150-generic root=/dev/mapper/ubuntu-root ro audit=1 + This template checks these `vmlinuz` lines for the required kernel argument. + On `BLS` platforms (RHEL 8+, Fedora), `grub.cfg` just contains a `blscfg` + directive that tells `grub` to load `BLS` entries from `/boot/loader/entries/`. + The kernel args are NOT in `grub.cfg` -- so this template does not check it. + + --- Persistent config (survives `grub2-mkconfig` regeneration) --- + + `/etc/default/grub` (all products) + Persistent `grub` configuration. When `grub2-mkconfig` runs (e.g. after kernel + install or `grub` update), it reads this file and writes the values into `grub.cfg` + or `BLS` boot entries. Contains two relevant shell variables: + GRUB_CMDLINE_LINUX="audit=1 pti=on" -- args added to ALL boot entries + GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" -- args added to non-recovery entries ONLY + If `GRUB_DISABLE_RECOVERY=true` is also set, there are no recovery entries, + so `_DEFAULT` effectively applies to all entries too. + + `/etc/default/grub.d/*.cfg` (Ubuntu only) + Drop-in config files that supplement `/etc/default/grub`. Can also set + `GRUB_CMDLINE_LINUX` and `GRUB_CMDLINE_LINUX_DEFAULT`. `grub2-mkconfig` + reads these in addition to `/etc/default/grub`. + + --- `bootc` / RHEL Image Mode --- + + `bootc` (RHEL Image Mode) is a way to run RHEL as an immutable container-based OS. + The root filesystem is a container image. There is no traditional `grub` config -- + the bootloader is managed by `bootc` itself. Kernel args are defined in `TOML` files shipped inside the image. - /usr/lib/bootc/kargs.d/*.toml - TOML files with kargs = ["arg=val", "arg2=val2"]. - On bootc systems, grub files (/boot/loader/entries, grubenv, grub.cfg, etc.) + `/usr/lib/bootc/kargs.d/*.toml` + `TOML` files with `kargs = ["arg=val", "arg2=val2"]`. + On `bootc` systems, `grub` files (`/boot/loader/entries`, `grubenv`, `grub.cfg`, etc.) do NOT exist. Kernel args live here instead. - The OVAL definition has two mutually exclusive branches: one for bootc systems - (checks kargs.d) and one for normal grub systems (checks grub files). + The `OVAL` definition has two mutually exclusive branches: one for `bootc` systems + (checks `/usr/lib/bootc/kargs.d/*.toml`) and one for normal `grub` systems (checks `grub` files). CRITERIA TREE (what must pass for the definition to be compliant): ================================================================== - definition (OR) + Products that support both Image Mode (`bootc`) and normal `grub` (e.g. RHEL 9+, RHEL 10) + emit BOTH branches. `oscap` evaluates both, but only one can pass -- the guards + (`IS bootc` / `NOT bootc`) ensure the wrong branch always fails. + Products without `bootc` support emit only the normal `grub` branch. + + [VISUAL TREE: indented AND/OR hierarchy showing the two branches (Image Mode vs. + normal grub) and all sub-checks within the normal grub branch.] + + definition (OR) -- passes if EITHER branch matches the system | - +-- [bootc] AND - | +-- extend_definition: IS bootc - | +-- test: kargs.d/*.toml has arg=value + +-- [Image Mode] AND (bootable_containers_supported) + | +-- extend_definition: system IS `bootc` + | +-- test: `/usr/lib/bootc/kargs.d/*.toml` has arg=value | +-- [normal grub] AND - +-- extend_definition: NOT bootc (if bootc supported) + +-- extend_definition: system is NOT `bootc` (bootable_containers_supported) | - +-- [RHEL 8 / OL8] entries check: - | +-- test: EACH /boot/loader/entries/*.conf has arg=value inline OR $kernelopts + +-- [RHEL 8 / OL8] `/boot/loader/entries/*.conf` check (uses_kernelopts): + | +-- test: EACH `/boot/loader/entries/*.conf` has arg=value on `options` line + | | OR contains `$kernelopts` | +-- (OR) - | +-- NO entry uses $kernelopts (skip grubenv) - | +-- grubenv has arg=value (BIOS or UEFI path) + | +-- NO `/boot/loader/entries/*.conf` uses `$kernelopts` (skip `/boot/grub2/grubenv`) + | +-- `/boot/grub2/grubenv` has arg=value (BIOS or UEFI path) | - +-- [RHEL 9+ / Fedora] entries check: - | +-- test: EACH /boot/loader/entries/*.conf has arg=value inline + +-- [RHEL 9+ / Fedora] `/boot/loader/entries/*.conf` check (uses_boot_loader_entries, !uses_kernelopts): + | +-- test: EACH `/boot/loader/entries/*.conf` has arg=value on `options` line | - +-- [OL7 / Ubuntu] grub.cfg check: - | +-- (OR) grub.cfg has arg=value (BIOS or UEFI path) + +-- [OL7 / Ubuntu] `/boot/grub2/grub.cfg` or `/boot/grub/grub.cfg` check (uses_grub_cfg): + | +-- (OR) BIOS or UEFI path has arg=value | - +-- [all products] /etc/default/grub check: + +-- [all products] `/etc/default/grub` check: +-- (OR) - +-- GRUB_CMDLINE_LINUX has arg=value - | (+ grub.d drop-in on Ubuntu) + +-- `GRUB_CMDLINE_LINUX` in `/etc/default/grub` has arg=value + | (+ `/etc/default/grub.d/*.cfg` drop-in on Ubuntu) +-- AND - +-- GRUB_CMDLINE_LINUX_DEFAULT has arg=value - | (+ grub.d drop-in on Ubuntu) - +-- GRUB_DISABLE_RECOVERY=true - - DATA FLOW (current -- will change in the rewrite): - ==================================================== - - Currently, objects extract the FULL options/cmdline line, and the shared - state uses a regex to check if arg=value appears as a word in that line. - This means all comparisons are "pattern match" on "string" datatype. - - Object (extracts full line) State (regex checks arg=value in line) - ┌─────────────────────────┐ ┌────────────────────────────────────┐ - │ /boot/loader/entries/ │ │ subexpression ~ "pattern match" │ - │ ^options (.*)$ │────────>│ ^(?:.*\s)?arg=value(?:\s.*)?$ │ - │ │ │ │ - │ /boot/grub2/grubenv │ │ (same state, shared by all tests) │ - │ ^kernelopts=(.*)$ │────────>│ │ - │ │ └────────────────────────────────────┘ - │ /etc/default/grub │ │ - │ ^GRUB_CMDLINE_LINUX= │────────────────────────┘ - │ "(.*)"$ │ - └─────────────────────────┘ - - TARGET (after rewrite -- sysctl-style extract-then-compare): - - Object (extracts JUST the value) State (native OVAL comparison) - ┌─────────────────────────┐ ┌────────────────────────────────────┐ - │ /boot/loader/entries/ │ │ subexpression │ - │ ^options.*arg=(\S+) │────────>│ operation="equals" │ - │ │ │ datatype="int" │ - │ /boot/grub2/grubenv │ │ value=8192 │ - │ ^kernelopts.*arg= │────────>│ │ - │ (\S+) │ │ (same state, shared by all tests) │ - │ │ └────────────────────────────────────┘ - │ /etc/default/grub │ │ - │ ^GRUB_CMDLINE_LINUX= │────────────────────────┘ - │ ".*arg=(\S+).*"$ │ - └─────────────────────────┘ - - The check_* flags below control which locations are verified per product. + +-- `GRUB_CMDLINE_LINUX_DEFAULT` in `/etc/default/grub` has arg=value + | (+ `/etc/default/grub.d/*.cfg` drop-in on Ubuntu) + +-- `GRUB_DISABLE_RECOVERY=true` in `/etc/default/grub` + + LONG DESCRIPTION (text equivalent of the criteria tree): + + The definition passes (compliant) if EITHER of two branches is true: + + Branch 1 -- Image Mode (only emitted when `bootable_containers_supported`): + Both must be true: + - The system IS a `bootc` deployment. + - `/usr/lib/bootc/kargs.d/*.toml` contains `arg=value`. + + Branch 2 -- normal grub: + All of the following must be true: + - The system is NOT a `bootc` deployment (guard; only when `bootable_containers_supported`). + - [RHEL 8 / OL8, `uses_kernelopts`]: + EACH `/boot/loader/entries/*.conf` (excluding rescue) has `arg=value` + on its `options` line, OR contains `$kernelopts`. + AND EITHER no `/boot/loader/entries/*.conf` references `$kernelopts` + (so `/boot/grub2/grubenv` is irrelevant), + OR `/boot/grub2/grubenv` contains `arg=value`. + - [RHEL 9+ / Fedora, `uses_boot_loader_entries` without `uses_kernelopts`]: + EACH `/boot/loader/entries/*.conf` (excluding rescue) has `arg=value` + on its `options` line. + - [OL7 / Ubuntu, `uses_grub_cfg`]: + `/boot/grub2/grub.cfg` or `/boot/grub/grub.cfg` has `arg=value` + (BIOS path, or UEFI path if separate). + - [all products]: + EITHER `GRUB_CMDLINE_LINUX` in `/etc/default/grub` has `arg=value` + (plus `/etc/default/grub.d/*.cfg` drop-in on Ubuntu), + OR BOTH `GRUB_CMDLINE_LINUX_DEFAULT` in `/etc/default/grub` has `arg=value` + (plus `/etc/default/grub.d/*.cfg` drop-in on Ubuntu) + AND `GRUB_DISABLE_RECOVERY=true` in `/etc/default/grub`. + + Only the sub-checks relevant to the product (controlled by `uses_*` flags) are emitted. + Branches 1 and 2 are mutually exclusive at runtime due to the `IS bootc` / `NOT bootc` guards. + + DATA FLOW: + ========== + + Each object extracts ONLY THE ARGUMENT VALUE via a capturing group. + Each state compares that value using OVAL-native operation and datatype + (equals, greater than or equal, pattern match; string or int). + No local_variable, no concat, no runtime regex assembly. + All grub-location tests share one state; bootc shares it too (value + extracted from TOML quotes in the object pattern). + + Exception: the RHEL 8 coverage path still captures the full options + line and checks for the argument name via pattern match (needed to + verify coverage across entries that may use $kernelopts). + + OBJECTS -- each extracts the value after {ARG_NAME}= from a config file: + + A. /boot/loader/entries/*.conf (RHEL 8/9/10, Fedora, OL8/9) + Pattern: ^options\s+(?:.*\s)?{ARG_NAME}=(\S+) + Captures: just the value (e.g. 8192) + + B. {grub2_boot_path}/grubenv (RHEL 8, OL8) + Pattern: ^kernelopts=(?:.*\s)?{ARG_NAME}=(\S+) + Captures: just the value + + C. {grub2_boot_path}/grub.cfg (OL7, Ubuntu) + Pattern: ^.*/vmlinuz.*\s{ARG_NAME}=(\S+) + Captures: just the value + + D. /etc/default/grub (all products) + Pattern: ^\s*GRUB_CMDLINE_LINUX="(?:.*\s)?{ARG_NAME}=([^\s"]+) + Captures: just the value (stops at whitespace or closing quote) + + E. /etc/default/grub.d/*.cfg (Ubuntu) + Same patterns as D. + + F. /usr/lib/bootc/kargs.d/*.toml (RHEL 9+, RHEL 10 — bootc) + Pattern: ^kargs\s*=\s*\[.*"{ARG_NAME}=([^"]+)".*\]$ + Captures: just the value (from within TOML quotes) + + For no-value arguments (e.g. nousb), patterns check presence only + (no capturing group, no state). + + STATES -- compare the captured value: + + 1. state_grub2_{ARG}_argument (shared by all objects) + Uses operation and datatype from rule.yml. + arg_value rules: literal comparison (e.g. equals "1") + arg_variable rules: var_ref to XCCDF variable (resolved at scan time) + + 2. state_grub2_{ARG}_argument_is_kernelopts (RHEL 8, OL8 only) + Pattern match: checks if $kernelopts appears in the full options line. + Used by the RHEL 8 coverage test with state_operator="OR". + + The uses_* flags control which locations are verified per product. -#}} -{{% set check_boot_loader_entries = product in ["fedora", "ol9", "rhel9", "rhel10"] %}} -{{% set check_boot_loader_entries_or_grubenv = product in ["ol8", "rhel8"] %}} -{{% set check_etc_default_grub = true %}} -{{% set check_etc_default_grub_d = 'ubuntu' in product %}} -{{% set check_grub_cfg = product in ["ol7"] or 'ubuntu' in product %}} -{{% set has_separate_bios_and_uefi = grub2_uefi_boot_path and grub2_uefi_boot_path != grub2_boot_path %}} - -{{# DEFINITION: {{{ _RULE_ID }}} - Class: compliance - Purpose: ensure the kernel boot argument {{{ ARG_NAME_VALUE }}} is set in all - relevant grub configuration files for this product. - Top-level OR: bootc branch vs normal grub branch. - Only one branch can be true at runtime (bootc XOR normal grub). - See the ASCII art criteria tree at the top of this file for the full picture. #}} +{{% set uses_boot_loader_entries = product in ["fedora", "ol8", "ol9", "rhel8", "rhel9", "rhel10"] %}} +{{% set uses_kernelopts = product in ["ol8", "rhel8"] %}} +{{% set uses_etc_default_grub_d = 'ubuntu' in product %}} +{{% set uses_grub_cfg = product in ["ol7"] or 'ubuntu' in product %}} +{{% set has_separate_bios_and_uefi = grub2_uefi_boot_path and grub2_uefi_boot_path != grub2_boot_path %}} +{{% set has_value = ARG_VALUE or ARG_VARIABLE %}} + +{{# DEFINITION: `{{{ _RULE_ID }}}` + Ensure the kernel boot argument {{{ ARG_NAME_VALUE }}} is set in all + relevant `grub` configuration files for this product. + See the ASCII-art criteria tree at the top of this file for the full picture. #}} {{{ oval_metadata("Ensure " + ARG_NAME_VALUE + " is set as a kernel boot argument.", rule_title=rule_title) }}} {{%- if bootable_containers_supported == "true" %}} - {{# CRITERIA BRANCH: bootc / RHEL Image Mode - Platforms: RHEL 9+, RHEL 10 with bootc support (bootable_containers_supported) - The system must be bootc AND kargs.d/*.toml must contain {{{ ARG_NAME_VALUE }}}. - On non-bootc systems, the extend_definition fails and this whole branch is skipped. #}} + {{# `bootc` / RHEL Image Mode systems + System must be `bootc` AND `/usr/lib/bootc/kargs.d/*.toml` must contain {{{ ARG_NAME_VALUE }}}. + On non-`bootc` systems the `extend_definition` fails and this branch is skipped. #}} - {{%- endif %}} - {{# CRITERIA BRANCH: normal grub (non-bootc) - Platforms: all products - All sub-checks must pass: bootc guard, boot entries, and /etc/default/grub. - The specific sub-checks emitted depend on the check_* flags set per product. #}} + {{# Non-`bootc` systems (normal `grub` configuration) #}} {{%- if bootable_containers_supported == "true" %}} - {{# EXTEND_DEFINITION: NOT bootc guard - Platforms: RHEL 9+, RHEL 10 with bootc support (bootable_containers_supported) - Fails this "normal grub" branch on bootc systems where grub files do not exist. - negate="true" -- passes only if the system is NOT bootc. #}} + {{# Guard: passes only if the system is NOT `bootc` (negate is true). + On `bootc` systems the negated `extend_definition` fails and this branch is skipped. + Without this, `bootc` systems would fail sub-checks because `grub` files do not exist. #}} {{%- endif %}} - {{%- if check_boot_loader_entries_or_grubenv %}} - {{# CRITERIA BRANCH: RHEL 8 / OL8 boot loader entries + $kernelopts + grubenv - Platforms: RHEL 8, OL8 (check_boot_loader_entries_or_grubenv) - Two-part check: - 1. EACH /boot/loader/entries/*.conf (omit rescue) has {{{ ARG_NAME_VALUE }}} - inline on the options line OR contains $kernelopts. - 2. If any entry uses $kernelopts, then grubenv must also have {{{ ARG_NAME_VALUE }}}. - The $kernelopts indirection is an RHEL 8 mechanism: entries say "$kernelopts" and - grub expands it from /boot/grub2/grubenv at boot time. #}} - - - {{# CRITERIA: $kernelopts grubenv gate - Passes if no entry uses $kernelopts (all args on options line, grubenv irrelevant) - OR grubenv has {{{ ARG_NAME_VALUE }}} (BIOS or UEFI path). #}} - - - {{# CRITERION: no $kernelopts in any entry (negated) - If no entry uses $kernelopts, all args are on the options line and grubenv is irrelevant. #}} - + {{%- if uses_kernelopts %}} + {{# RHEL 8 / OL8 BLS check — two criteria: + 1. Every `/boot/loader/entries/*.conf` has {{{ ARG_NAME }}} on the `options` line + OR contains `$kernelopts` (coverage — no entry is left unchecked). + 2. Entries that list {{{ ARG_NAME }}} directly (not via `$kernelopts`) have the + correct value (`check_existence=any_exist` — passes when all entries delegate + to `$kernelopts` and none have the arg directly). #}} + + {{%- if has_value %}} + + {{%- endif %}} - {{# CRITERIA: grubenv has arg=value - BIOS: {grub2_boot_path}/grubenv (e.g. /boot/grub2/grubenv) - UEFI: {grub2_uefi_boot_path}/grubenv (e.g. /boot/efi/EFI/redhat/grubenv) - UEFI criterion only emitted when has_separate_bios_and_uefi is true. #}} + {{# `$kernelopts` / `{grub2_boot_path}/grubenv` gate + On RHEL 8, `/boot/loader/entries/*.conf` files can either list kernel args directly + on the `options` line, or say `$kernelopts` which means: look up the actual args + in `{grub2_boot_path}/grubenv` at boot time. + If NO `/boot/loader/entries/*.conf` says `$kernelopts`, all args are already on + the `options` lines and there is no need to check `{grub2_boot_path}/grubenv` + at all (first branch of this `OR` satisfies it). + If ANY `/boot/loader/entries/*.conf` says `$kernelopts`, we fall through to the + second branch and check that `{grub2_boot_path}/grubenv` contains + {{{ ARG_NAME_VALUE }}} (BIOS or UEFI path). #}} + + + {{# No `$kernelopts` in any `/boot/loader/entries/*.conf` (negated). + If no `/boot/loader/entries/*.conf` uses `$kernelopts`, all args are on the `options` + line and `{grub2_boot_path}/grubenv` is skipped. #}} + + + {{# `{grub2_boot_path}/grubenv` has {{{ ARG_NAME_VALUE }}}. + BIOS: `{grub2_boot_path}/grubenv` (e.g. `/boot/grub2/grubenv`) + UEFI: `{grub2_uefi_boot_path}/grubenv` (e.g. `/boot/efi/EFI/redhat/grubenv`) + #}} - {{%- if has_separate_bios_and_uefi %}} - {{%- endif %}} {{%- endif %}} - {{%- if check_boot_loader_entries %}} - {{# CRITERION: RHEL 9+ / Fedora / OL9 boot loader entries - Platforms: RHEL 9+, Fedora, OL9 (check_boot_loader_entries) - Checks: EACH /boot/loader/entries/*.conf (omit rescue) has {{{ ARG_NAME_VALUE }}} - directly on the options line. No $kernelopts indirection on these platforms. #}} - {{%- endif %}} - {{%- if check_grub_cfg %}} - {{# CRITERIA: OL7 / Ubuntu grub.cfg - Platforms: OL7, Ubuntu (check_grub_cfg) - BIOS: {grub2_boot_path}/grub.cfg (e.g. /boot/grub2/grub.cfg) - UEFI: {grub2_uefi_boot_path}/grub.cfg (e.g. /boot/efi/EFI/redhat/grub.cfg) - UEFI criterion only emitted when has_separate_bios_and_uefi is true. #}} + {{%- if uses_grub_cfg %}} + {{# OL7 / Ubuntu `{grub2_boot_path}/grub.cfg` (`uses_grub_cfg`) + BIOS: `{grub2_boot_path}/grub.cfg` (e.g. `/boot/grub2/grub.cfg`) + UEFI: `{grub2_uefi_boot_path}/grub.cfg` (e.g. `/boot/efi/EFI/redhat/grub.cfg`) + UEFI criterion only emitted when `has_separate_bios_and_uefi` is true. #}} - {{%- if has_separate_bios_and_uefi %}} - {{%- endif %}} {{%- endif %}} - {{% if check_etc_default_grub %}} - {{# CRITERIA: /etc/default/grub persistent configuration - Platforms: all (check_etc_default_grub = true) - Passes if GRUB_CMDLINE_LINUX has {{{ ARG_NAME_VALUE }}} (applies to all entries), - OR GRUB_CMDLINE_LINUX_DEFAULT has it + GRUB_DISABLE_RECOVERY=true. - _DEFAULT only covers non-recovery boots, so disabling recovery ensures coverage. #}} + {{# `/etc/default/grub` persistent configuration (all products) + `GRUB_CMDLINE_LINUX` in `/etc/default/grub` has {{{ ARG_NAME_VALUE }}} (applies to all boot entries), + OR `GRUB_CMDLINE_LINUX_DEFAULT` in `/etc/default/grub` has it + `GRUB_DISABLE_RECOVERY=true`. + `GRUB_CMDLINE_LINUX_DEFAULT` only covers non-recovery boots, so disabling recovery ensures coverage. #}} - {{# CRITERIA: GRUB_CMDLINE_LINUX (applies to all entries) - Checks /etc/default/grub, plus grub.d drop-ins on Ubuntu. #}} + {{# `GRUB_CMDLINE_LINUX` (applies to all boot entries). + Checks `/etc/default/grub`, plus `/etc/default/grub.d/*.cfg` drop-ins on Ubuntu. #}} - - {{% if check_etc_default_grub_d %}} - {{% endif %}} - {{# CRITERIA: GRUB_CMDLINE_LINUX_DEFAULT + GRUB_DISABLE_RECOVERY=true - _DEFAULT only applies to non-recovery entries, so recovery must be disabled - to ensure the arg is on ALL boot entries. Checks /etc/default/grub, - plus grub.d drop-ins on Ubuntu. #}} + {{# `GRUB_CMDLINE_LINUX_DEFAULT` + `GRUB_DISABLE_RECOVERY=true` in `/etc/default/grub` + + `GRUB_CMDLINE_LINUX_DEFAULT` only applies to non-recovery boot entries, so if this variable is used, + rescue boot entries have to be disabled with GRUB_DISABLE_RECOVERY=true in `/etc/default/grub`, + to ensure the argument ARG_NAME_UNDERSCORED is set for ALL boot entries. + Also checks `/etc/default/grub.d/*.cfg` drop-ins for Ubuntu. #}} - - {{% if check_etc_default_grub_d %}} - {{% endif %}} @@ -298,366 +431,287 @@ comment="Verify GRUB_DISABLE_RECOVERY=true in /etc/default/grub" /> - {{% endif %}} - - - -{{# TESTS #}} -{{% if check_boot_loader_entries_or_grubenv %}} - {{# Check that EACH /boot/loader/entries/*.conf (omit rescue) has {{{ ARG_NAME_VALUE }}} - on the options line OR references $kernelopts (grub env variable). - Platforms: RHEL 8, OL8 (check_boot_loader_entries_or_grubenv) - Extracts the full "options" line as subexpression. - state_operator="OR" because entries may have args on options line or via $kernelopts. #}} - - - - - - - {{# Collect the "options" line from each /boot/loader/entries/*.conf. - Platforms: RHEL 8, OL8 (check_boot_loader_entries_or_grubenv) - Each .conf file produces one collected item. Rescue entries (*rescue.conf) excluded. - Shared by two tests: the arg-or-$kernelopts check and the $kernelopts-presence check. #}} - - /boot/loader/entries/ - ^.*\.conf$ - {{# regex: ^options (.*)$ -- captures the full space-separated arg list after "options " #}} - ^options (.*)$ +{{# Collect a grub shell variable (GRUB_CMDLINE_LINUX or GRUB_CMDLINE_LINUX_DEFAULT) + from /etc/default/grub or /etc/default/grub.d/*.cfg drop-ins, and check for {{{ ARG_NAME }}}. + When has_value: extracts just the value via capture group. + When not has_value (nousb): checks the arg name is present (existence only, no state). #}} +{{%- macro etc_default_grub_object_and_test(grub_var, from_grub_d=false, check="all") %}} + {{%- set name = "grub2_" ~ ARG_NAME_UNDERSCORED ~ "_" ~ grub_var|lower ~ ("_from_grub_d" if from_grub_d else "") -%}} + {{%- set source_file = "/etc/default/grub.d/*.cfg" if from_grub_d else "/etc/default/grub" -%}} + + {{%- if from_grub_d %}} + {{# operation must be pattern match -- the filepath below is a regex; + without it OVAL defaults to equals and looks for a file literally named [^/]+\.cfg. #}} + /etc/default/grub.d/[^/]+\.cfg + {{%- else %}} + {{{ source_file }}} + {{%- endif %}} + {{%- if has_value %}} + ^\s*{{{ grub_var }}}="(?:.*\s)?{{{ ARG_NAME_ESCAPED_DOTS }}}=([^\s"]+) + {{%- else %}} + ^\s*{{{ grub_var }}}="(?:.*\s)?{{{ ARG_NAME_ESCAPED_DOTS }}}(?:\s.*)?"$ + {{%- endif %}} 1 - state_grub2_{{{ _RULE_ID }}}_is_rescue_entry - {{# Exclude filter: matches filenames ending in "rescue.conf". - Used by boot entry objects to skip rescue entries -- we only care about normal entries. #}} - - {{# regex: .*rescue\.conf$ -- matches rescue entry filenames to exclude them #}} - .*rescue\.conf$ - - - {{# Check if at least one /boot/loader/entries/*.conf references $kernelopts. - Platforms: RHEL 8, OL8 (check_boot_loader_entries_or_grubenv) - Uses the same object as above. Used negated in criteria: if NO entry has $kernelopts, - all args are on the options line and the grubenv check is skipped. #}} - - - - -{{% endif %}} - -{{%- if check_etc_default_grub %}} - {{# Check GRUB_CMDLINE_LINUX in /etc/default/grub for {{{ ARG_NAME_VALUE }}}. - Platforms: all (check_etc_default_grub = true) - GRUB_CMDLINE_LINUX is passed to ALL boot entries by grub2-mkconfig. #}} - - - + + + {{%- if has_value %}} + + {{%- endif %}} - - {{# Collect GRUB_CMDLINE_LINUX="..." from /etc/default/grub. - Platforms: all (check_etc_default_grub = true) - Extracts everything between the double quotes as subexpression. #}} - - /etc/default/grub - {{# regex: ^\s*GRUB_CMDLINE_LINUX="(.*)"$ -- captures everything between the quotes #}} - ^\s*GRUB_CMDLINE_LINUX="(.*)"$ +{{%- endmacro %}} + +{{# Check grubenv for {{{ ARG_NAME_VALUE }}} in the kernelopts= line. + Platforms: RHEL 8, OL8 (uses_kernelopts). + When has_value: extracts just the value. + When not has_value: checks arg name is present (existence only). #}} +{{%- macro test_and_object_for_grubenv(variant="") %}} + {{%- set path = (grub2_uefi_boot_path if variant == "uefi" else grub2_boot_path) ~ "/grubenv" -%}} + {{%- set name = "grub2_" ~ ARG_NAME_UNDERSCORED ~ "_in_grubenv" ~ ("_uefi" if variant == "uefi" else "") -%}} + + {{{ path }}} + {{%- if has_value %}} + ^kernelopts=(?:.*\s)?{{{ ARG_NAME_ESCAPED_DOTS }}}=(\S+) + {{%- else %}} + ^kernelopts=(?:.*\s)?{{{ ARG_NAME_ESCAPED_DOTS }}}(?:\s|$) + {{%- endif %}} 1 - {{# Check GRUB_CMDLINE_LINUX_DEFAULT in /etc/default/grub for {{{ ARG_NAME_VALUE }}}. - Platforms: all (check_etc_default_grub = true) - _DEFAULT only applies to non-recovery entries, so criteria requires - GRUB_DISABLE_RECOVERY=true alongside this test. #}} - - - + + {{%- if has_value %}} + + {{%- endif %}} - - {{# Collect GRUB_CMDLINE_LINUX_DEFAULT="..." from /etc/default/grub. - Platforms: all (check_etc_default_grub = true) - Extracts everything between the double quotes as subexpression. - Only passed to non-recovery boot entries by grub2-mkconfig. #}} - - /etc/default/grub - {{# regex: ^\s*GRUB_CMDLINE_LINUX_DEFAULT="(.*)"$ -- captures everything between the quotes #}} - ^\s*GRUB_CMDLINE_LINUX_DEFAULT="(.*)"$ +{{%- endmacro %}} + +{{# Check grub.cfg vmlinuz lines for {{{ ARG_NAME_VALUE }}} in the kernel command line. + Platforms: OL7, Ubuntu (uses_grub_cfg). + When has_value: extracts just the value. + When not has_value: checks arg name is present (existence only). #}} +{{%- macro test_and_object_for_grub_cfg(variant, path) %}} + {{% set name = "grub2_" ~ ARG_NAME_UNDERSCORED ~ "_in_grub_cfg" ~ ("_uefi" if variant == "uefi" else "") -%}} + + {{{ path }}} + {{%- if has_value %}} + ^.*/vmlinuz.*\s{{{ ARG_NAME_ESCAPED_DOTS }}}=(\S+) + {{%- else %}} + ^.*/vmlinuz.*\s{{{ ARG_NAME_ESCAPED_DOTS }}}(?:\s|$) + {{%- endif %}} 1 -{{%- endif %}} - -{{%- if check_etc_default_grub_d %}} - {{# Check GRUB_CMDLINE_LINUX in /etc/default/grub.d/*.cfg drop-ins for {{{ ARG_NAME_VALUE }}}. - Platforms: Ubuntu (check_etc_default_grub_d) - Same as the /etc/default/grub check but from drop-in config files. - Passes if at least one drop-in has it (check="at least one"). #}} - - - - - - {{# Check GRUB_CMDLINE_LINUX_DEFAULT in /etc/default/grub.d/*.cfg drop-ins for {{{ ARG_NAME_VALUE }}}. - Platforms: Ubuntu (check_etc_default_grub_d) - Same as the /etc/default/grub check but from drop-in config files. - Requires GRUB_DISABLE_RECOVERY=true (same as the non-drop-in variant). #}} - - - - - - {{# Collect GRUB_CMDLINE_LINUX="..." from /etc/default/grub.d/*.cfg drop-in files. - Platforms: Ubuntu (check_etc_default_grub_d) - Same regex as the /etc/default/grub object, but iterates over all .cfg drop-ins. #}} - - {{# regex: /etc/default/grub.d/[^/]+\.cfg -- match .cfg drop-in files (Ubuntu) #}} - /etc/default/grub.d/[^/]+\.cfg - {{# regex: same as /etc/default/grub -- captures everything between the quotes #}} - ^\s*GRUB_CMDLINE_LINUX="(.*)"$ - 1 - - - {{# Collect GRUB_CMDLINE_LINUX_DEFAULT="..." from /etc/default/grub.d/*.cfg drop-in files. - Platforms: Ubuntu (check_etc_default_grub_d) - Same regex as the /etc/default/grub object, but iterates over all .cfg drop-ins. - Only applies to non-recovery entries (same as GRUB_CMDLINE_LINUX_DEFAULT). #}} - - /etc/default/grub.d/*.cfg - {{# regex: same as /etc/default/grub -- captures everything between the quotes #}} - ^\s*GRUB_CMDLINE_LINUX_DEFAULT="(.*)"$ - 1 - -{{%- endif %}} -{{%- if check_boot_loader_entries_or_grubenv %}} - {{# Check grubenv for {{{ ARG_NAME_VALUE }}} in the kernelopts= line. - Platforms: RHEL 8, OL8 (check_boot_loader_entries_or_grubenv) - BIOS: {grub2_boot_path}/grubenv (e.g. /boot/grub2/grubenv) - UEFI: {grub2_uefi_boot_path}/grubenv (e.g. /boot/efi/EFI/redhat/grubenv) - UEFI variant only emitted when has_separate_bios_and_uefi is true. - Used when boot loader entries reference $kernelopts instead of listing args on options line. #}} - {{%- macro test_and_object_for_grubenv(variant="") %}} - {{%- set path = (grub2_uefi_boot_path if variant == "uefi" else grub2_boot_path) ~ "/grubenv" -%}} - {{%- set name = "grub2_" ~ SANITIZED_ARG_NAME ~ "_in_grubenv" ~ ("_uefi" if variant == "uefi" else "") -%}} - + {{%- if has_value %}} + + {{%- endif %}} +{{%- endmacro %}} - {{# Collect kernelopts=... from grubenv (everything after "kernelopts="). - This is the grub environment variable that boot entries expand via $kernelopts. #}} - - {{{ path }}} - {{# regex: ^kernelopts=(.*)$ -- captures the full space-separated arg list from kernelopts #}} - ^kernelopts=(.*)$ - 1 - - {{%- endmacro %}} +{{# Shared state for value comparison. + Uses OVAL-native operation + datatype instead of regex. + Skipped entirely for args without a value (e.g. nousb). #}} - {{{- test_and_object_for_grubenv() }}} - {{%- if has_separate_bios_and_uefi -%}} - {{{- test_and_object_for_grubenv(variant="uefi") }}} - {{%- endif %}} -{{%- endif %}} +{{% if ARG_VARIABLE %}} + + + + + +{{% elif ARG_VALUE %}} + + {{{ ARG_VALUE }}} + +{{% endif %}} + +{{# --- /etc/default/grub (all products) --- #}} + +{{{ etc_default_grub_object_and_test("GRUB_CMDLINE_LINUX") }}} +{{{ etc_default_grub_object_and_test("GRUB_CMDLINE_LINUX_DEFAULT") }}} -{{%- if check_boot_loader_entries %}} - {{# Check EACH /boot/loader/entries/*.conf (omit rescue) for {{{ ARG_NAME_VALUE }}} on options line. - Platforms: RHEL 9+, Fedora, OL9 (check_boot_loader_entries) - No $kernelopts indirection -- RHEL 9+ has all kernel args on the options line. #}} - - - - - - {{# Collect the "options" line from each /boot/loader/entries/*.conf. - Platforms: RHEL 9+, Fedora, OL9 (check_boot_loader_entries) - Each .conf file produces one collected item. Rescue entries (*rescue.conf) excluded. - Unlike the RHEL 8 variant, no $kernelopts indirection -- args are always on the options line. #}} - + .*rescue\.conf$ + +{{% endif %}} + +{{# RHEL 8, OL8 — uses_kernelopts #}} +{{% if uses_kernelopts %}} + {{# Full-line capture from `/boot/loader/entries/*.conf` for the RHEL 8 coverage test. + Each entry produces one item with the full `options` line as subexpression. + Used to check that every entry has {{{ ARG_NAME }}} OR `$kernelopts`. #}} + /boot/loader/entries/ ^.*\.conf$ - {{# regex: ^options (.*)$ -- captures the full space-separated arg list after "options " #}} ^options (.*)$ 1 state_grub2_{{{ _RULE_ID }}}_is_rescue_entry +{{% endif %}} - {{# Exclude filter: matches filenames ending in "rescue.conf" (same as above). #}} - - {{# regex: .*rescue\.conf$ -- matches rescue entry filenames to exclude them #}} - .*rescue\.conf$ - +{{# RHEL 9+/Fedora/OL9 (uses_boot_loader_entries, not uses_kernelopts) + OR RHEL 8/OL8 (uses_kernelopts, has_value only) #}} +{{%- if (uses_boot_loader_entries and not uses_kernelopts) or (uses_kernelopts and has_value) %}} + {{# Extract the {{{ ARG_NAME }}} from each `/boot/loader/entries/*.conf`. + On RHEL 9+, this is the only BLS object — every entry must have the correct value. + + On RHEL 8, entries may use `$kernelopts` instead of listing args directly. Entries + that DO have the arg directly are checked here (`check_existence=any_exist` means + zero matches is OK — all entries delegated to `$kernelopts`). + + Edge case: if an entry has both `$kernelopts` and a direct arg override + (e.g. `options $kernelopts audit_backlog_limit=4096`), this captures the direct + value. The kernel uses the last occurrence, so the direct value wins. + #}} + + /boot/loader/entries/ + ^.*\.conf$ + {{# (?:.*\s)? skips any preceding args on the options line so the arg is matched + regardless of position (first, middle, or last). + The group is optional because the {{{ ARG_NAME }}} may be the first thing after `options`. #}} + {{%- if has_value %}} + ^options\s+(?:.*\s)?{{{ ARG_NAME_ESCAPED_DOTS }}}=(\S+) + {{%- else %}} + ^options\s+(?:.*\s)?{{{ ARG_NAME_ESCAPED_DOTS }}}(?:\s|$) + {{%- endif %}} + {{# Collect at least one match from each `/boot/loader/entries/*.conf`. #}} + 1 + {{# Exclude rescue entries. #}} + state_grub2_{{{ _RULE_ID }}}_is_rescue_entry + {{%- endif %}} -{{%- if check_grub_cfg %}} - {{# Check grub.cfg vmlinuz lines for {{{ ARG_NAME_VALUE }}} in the kernel command line. - Platforms: OL7, Ubuntu (check_grub_cfg) - BIOS: {grub2_boot_path}/grub.cfg (e.g. /boot/grub2/grub.cfg) - UEFI: {grub2_uefi_boot_path}/grub.cfg (e.g. /boot/efi/EFI/redhat/grub.cfg) - UEFI variant only emitted when has_separate_bios_and_uefi is true. #}} - {{%- macro test_and_object_for_grub_cfg(variant, path) %}} - {{% set name = "grub2_" ~ SANITIZED_ARG_NAME ~ "_in_grub_cfg" ~ ("_uefi" if variant == "uefi" else "") -%}} - - - +{{# RHEL 8, OL8 — uses_kernelopts — coverage + value tests + $kernelopts gate #}} +{{% if uses_kernelopts %}} + {{# RHEL 8 coverage state: arg name present (with or without =value) in the full `options` line. + Used by the coverage test to ensure every entry accounts for {{{ ARG_NAME }}}. #}} + + {{# Match the {{{ ARG_NAME }}} name (with optional value) in the full options line. #}} + ^(?:.*\s)?{{{ ARG_NAME_ESCAPED_DOTS }}}(?:=\S+)?(?:\s.*)?$ + + + + {{# Match the `$kernelopts` keyword in the full options line. #}} + ^(?:.*\s)?\$kernelopts(?:\s.*)?$ + + + {{# RHEL 8 coverage test: every `/boot/loader/entries/*.conf` has {{{ ARG_NAME }}} OR `$kernelopts`. + `state_operator` = `OR`: entry passes if it matches either state. + `check` = `all`: every entry must pass. #}} + + + + - {{# Collect kernel command line from vmlinuz lines in grub.cfg. - Captures from "root=" onwards, which includes all kernel args. - Each vmlinuz line corresponds to one boot entry. #}} - - {{{ path }}} - {{# regex: ^.*/vmlinuz.*(root=.*)$ -- captures from "root=" onwards on vmlinuz lines (OL7, Ubuntu) #}} - ^.*/vmlinuz.*(root=.*)$ - 1 - - {{%- endmacro %}} + {{%- if has_value %}} + {{# RHEL 8 value test: entries that list {{{ ARG_NAME }}} directly have the correct value. + `check_existence` = `any_exist`: if all entries use `$kernelopts` and none have + the arg directly, zero items are collected and the test passes (grubenv handles it). #}} + + + + + {{%- endif %}} - {{{ test_and_object_for_grub_cfg("bios", grub2_boot_path ~ "/grub.cfg") }}} + {{# $kernelopts presence test: at least one /boot/loader/entries/*.conf references $kernelopts. + Used negated in criteria: if no entry has $kernelopts, grubenv check is skipped. #}} + + + + + + {{{- test_and_object_for_grubenv() }}} {{%- if has_separate_bios_and_uefi -%}} - {{{- test_and_object_for_grub_cfg("uefi", grub2_uefi_boot_path ~ "/grub.cfg") }}} + {{{- test_and_object_for_grubenv(variant="uefi") }}} {{%- endif %}} -{{%- endif %}} - -{{% if check_boot_loader_entries_or_grubenv %}} - {{# Match "$kernelopts" as a whole word in the captured "options" line. - Platforms: RHEL 8, OL8 (check_boot_loader_entries_or_grubenv) - $kernelopts is a grub env variable expanded at boot time from grubenv. #}} - {{# regex: ^(?:.*\s)?\$kernelopts(?:\s.*)?$ -- checks if $kernelopts appears as a word in the options line (RHEL 8 indirection) #}} - - ^(?:.*\s)?\$kernelopts(?:\s.*)?$ - {{% endif %}} -{{# Match {{{ ARG_NAME_VALUE }}} as a whole word in the captured options/kernelopts/cmdline line. - Shared by ALL grub-location tests (except bootc kargs.d which has its own state). - If ARG_VARIABLE is set, the regex is built at scan time from an XCCDF variable. - Otherwise, the expected value is hardcoded into the regex at build time. - NOTE: in the rewrite, this entire state + local_variable goes away -- - objects will extract just the value, and the state will use operation+datatype natively. #}} -{{% if ARG_VARIABLE %}} - {{# Value comes from XCCDF variable {{{ ARG_VARIABLE }}} -- regex built at scan time. #}} - - {{# regex built at scan time via local_variable concat -- checks "arg=" as a word in the line #}} - - +{{# Ubuntu, only — uses_etc_default_grub_d #}} +{{%- if uses_etc_default_grub_d %}} + {{# `/etc/default/grub.d/*.cfg` drop-in files. + `GRUB_CMDLINE_LINUX` and `GRUB_CMDLINE_LINUX_DEFAULT` from `/etc/default/grub.d/*.cfg` drop-in config files. + `GRUB_CMDLINE_LINUX` uses `check` = `at least one` -- passes if any `/etc/default/grub.d/*.cfg` has the arg. #}} + {{{ etc_default_grub_object_and_test("GRUB_CMDLINE_LINUX", from_grub_d=true, check="at least one") }}} + {{{ etc_default_grub_object_and_test("GRUB_CMDLINE_LINUX_DEFAULT", from_grub_d=true) }}} +{{%- endif %}} - {{# Build regex: ^(?:.*\s)?{ARG_NAME}=(?:\s.*)?$ - Matches arg=value as a whole word. IS_SUBSTRING wraps value with \S* (partial match). #}} - - - ^(?:.*\s)?{{{ ARG_NAME }}}= - {{% if IS_SUBSTRING == "true" %}} - \S* - {{% endif %}} - - {{% if IS_SUBSTRING == "true" %}} - \S* - {{% endif %}} - (?:\s.*)?$ - - - - {{# Expected value of {{{ ARG_NAME }}}, provided by XCCDF benchmark at scan time. #}} - -{{% else %}} - {{# Expected value hardcoded at build time: {{{ ESCAPED_ARG_NAME_VALUE }}} #}} - - {{# regex: ^(?:.*\s)?{ARG_NAME_VALUE}(?:\s.*)?$ -- checks "arg=value" appears as a whole word in the full options line #}} - ^(?:.*\s)?{{{ ESCAPED_ARG_NAME_VALUE }}}(?:\s.*)?$ - -{{% endif %}} +{{# RHEL 9+, Fedora, OL9 — uses_boot_loader_entries, not uses_kernelopts #}} +{{%- if uses_boot_loader_entries and not uses_kernelopts %}} + + + {{%- if has_value %}} + + {{%- endif %}} + +{{%- endif %}} -{{# Bootc / RHEL Image Mode: kernel args live in /usr/lib/bootc/kargs.d/*.toml instead of grub. #}} +{{# OL7, Ubuntu — uses_grub_cfg #}} +{{%- if uses_grub_cfg %}} + {{# Emit BIOS variant (`{grub2_boot_path}/grub.cfg`) unconditionally; + emit UEFI variant (`{grub2_uefi_boot_path}/grub.cfg`) only when the platform has separate BIOS and UEFI paths. #}} + {{{ test_and_object_for_grub_cfg("bios", grub2_boot_path ~ "/grub.cfg") }}} + {{%- if has_separate_bios_and_uefi -%}} + {{{- test_and_object_for_grub_cfg("uefi", grub2_uefi_boot_path ~ "/grub.cfg") }}} + {{%- endif %}} +{{%- endif %}} + +{{# RHEL 9+, RHEL 10 — bootable_containers_supported #}} {{%- if bootable_containers_supported == "true" %}} - {{# Check /usr/lib/bootc/kargs.d/*.toml for {{{ ARG_NAME_VALUE }}} in the kargs array. - Platforms: RHEL 9+, RHEL 10 with bootc support (bootable_containers_supported) - Passes if at least one .toml file has it (check="at least one"). - Has its own state (not shared) because TOML wraps values in quotes. #}} - - - - - {{# Collect kargs array from /usr/lib/bootc/kargs.d/*.toml. - Captures everything between the square brackets: kargs = ["arg=val", "arg2=val2"] #}} - + {{# bootc object extracts the value from within the TOML quotes, so the shared state works directly. #}} + /usr/lib/bootc/kargs.d/ ^.*\.toml$ - {{# regex: ^kargs = \[([^\]]+)\]$ -- captures contents of the TOML kargs array, e.g. "arg=val", "arg2=val2" #}} - ^kargs = \[([^\]]+)\]$ + {{%- if has_value %}} + ^kargs\s*=\s*\[.*"{{{ ARG_NAME_ESCAPED_DOTS }}}=([^"]+)".*\]$ + {{%- else %}} + ^kargs\s*=\s*\[.*"{{{ ARG_NAME_ESCAPED_DOTS }}}".*\]$ + {{%- endif %}} 1 - {{# Match {{{ ARG_NAME_VALUE }}} as a quoted string in the TOML kargs array (e.g. "arg=val"). - Separate from state_argument because TOML wraps values in double quotes. - Same ARG_VARIABLE / hardcoded split as state_argument. #}} - {{%- if ARG_VARIABLE %}} - - - - {{# Build regex: ^.*"{ARG_NAME}=".*$ - Matches "arg=value" as a quoted string in TOML. IS_SUBSTRING wraps value with \S*. #}} - - - ^.*"{{{ ARG_NAME }}}= - {{% if IS_SUBSTRING == "true" %}} - \S* - {{% endif %}} - - {{% if IS_SUBSTRING == "true" %}} - \S* - {{% endif %}} - ".*$ - - - - {{# Expected value of {{{ ARG_NAME }}}, same XCCDF variable as above. #}} - - {{%- else %}} - {{# Expected value hardcoded at build time, with surrounding quotes for TOML format. #}} - - ^.*"{{{ ESCAPED_ARG_NAME_VALUE }}}".*$ - - {{%- endif %}} + + + {{%- if has_value %}} + + {{%- endif %}} + {{% endif %}} + From 57701cf12bfe36dfe66ac9a41f0b9433d6cb5a68 Mon Sep 17 00:00:00 2001 From: Peter Macko <44851174+macko1@users.noreply.github.com> Date: Thu, 14 May 2026 12:37:46 +0200 Subject: [PATCH 4/6] Set operation and datatype on all rule YAMLs - grub2_audit_backlog_limit_argument: "greater than or equal" + int - grub2_slub_debug_argument@ol8: "pattern match" replaces is_substring - All other 17 rules: "equals" + "string" (explicit) - var_audit_backlog_limit.var: type number, operator >= - var_rng_core_default_quality.var: type number --- linux_os/guide/auditing/grub2_audit_argument/rule.yml | 2 ++ .../auditing/grub2_audit_backlog_limit_argument/rule.yml | 2 ++ linux_os/guide/auditing/var_audit_backlog_limit.var | 4 ++-- .../system/bootloader-grub2/grub2_enable_iommu_force/rule.yml | 2 ++ .../bootloader-grub2/grub2_init_on_alloc_argument/rule.yml | 2 ++ .../guide/system/bootloader-grub2/grub2_init_on_free/rule.yml | 2 ++ .../bootloader-grub2/grub2_kernel_trust_cpu_rng/rule.yml | 2 ++ .../system/bootloader-grub2/grub2_l1tf_argument/rule.yml | 2 ++ .../guide/system/bootloader-grub2/grub2_mce_argument/rule.yml | 2 ++ .../guide/system/bootloader-grub2/grub2_mds_argument/rule.yml | 2 ++ .../grub2_page_alloc_shuffle_argument/rule.yml | 2 ++ .../guide/system/bootloader-grub2/grub2_pti_argument/rule.yml | 2 ++ .../grub2_rng_core_default_quality_argument/rule.yml | 2 ++ .../bootloader-grub2/grub2_slab_nomerge_argument/rule.yml | 2 ++ .../grub2_spec_store_bypass_disable_argument/rule.yml | 2 ++ .../bootloader-grub2/grub2_spectre_v2_argument/rule.yml | 2 ++ .../system/bootloader-grub2/grub2_vsyscall_argument/rule.yml | 2 ++ .../system/bootloader-grub2/var_rng_core_default_quality.var | 2 +- .../disabling_ipv6/grub2_ipv6_disable_argument/rule.yml | 2 ++ .../poisoning/grub2_page_poison_argument/rule.yml | 2 ++ .../restrictions/poisoning/grub2_slub_debug_argument/rule.yml | 4 +++- 21 files changed, 42 insertions(+), 4 deletions(-) diff --git a/linux_os/guide/auditing/grub2_audit_argument/rule.yml b/linux_os/guide/auditing/grub2_audit_argument/rule.yml index 6da1958af700..097faf3f374a 100644 --- a/linux_os/guide/auditing/grub2_audit_argument/rule.yml +++ b/linux_os/guide/auditing/grub2_audit_argument/rule.yml @@ -57,6 +57,8 @@ template: vars: arg_name: audit arg_value: '1' + datatype: int + operation: equals fixtext: |- {{{ describe_grub2_argument("audit=1") | indent(4) }}} diff --git a/linux_os/guide/auditing/grub2_audit_backlog_limit_argument/rule.yml b/linux_os/guide/auditing/grub2_audit_backlog_limit_argument/rule.yml index 9eb490f264fd..99b50a5c3ef7 100644 --- a/linux_os/guide/auditing/grub2_audit_backlog_limit_argument/rule.yml +++ b/linux_os/guide/auditing/grub2_audit_backlog_limit_argument/rule.yml @@ -52,3 +52,5 @@ template: vars: arg_name: audit_backlog_limit arg_variable: var_audit_backlog_limit + datatype: int + operation: greater than or equal diff --git a/linux_os/guide/auditing/var_audit_backlog_limit.var b/linux_os/guide/auditing/var_audit_backlog_limit.var index 6254b02137a1..87f0f12e2119 100644 --- a/linux_os/guide/auditing/var_audit_backlog_limit.var +++ b/linux_os/guide/auditing/var_audit_backlog_limit.var @@ -7,9 +7,9 @@ description: |- The audit_backlog_limit parameter determines how auditd records can be held in the auditd backlog. -type: string +type: number -operator: equals +operator: greater than or equal interactive: true diff --git a/linux_os/guide/system/bootloader-grub2/grub2_enable_iommu_force/rule.yml b/linux_os/guide/system/bootloader-grub2/grub2_enable_iommu_force/rule.yml index e4796100e88f..e2da2c810ee9 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_enable_iommu_force/rule.yml +++ b/linux_os/guide/system/bootloader-grub2/grub2_enable_iommu_force/rule.yml @@ -37,3 +37,5 @@ template: vars: arg_name: iommu arg_value: 'force' + datatype: string + operation: equals diff --git a/linux_os/guide/system/bootloader-grub2/grub2_init_on_alloc_argument/rule.yml b/linux_os/guide/system/bootloader-grub2/grub2_init_on_alloc_argument/rule.yml index 3f61c7dec7d1..b6604045fdcb 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_init_on_alloc_argument/rule.yml +++ b/linux_os/guide/system/bootloader-grub2/grub2_init_on_alloc_argument/rule.yml @@ -33,3 +33,5 @@ template: vars: arg_name: init_on_alloc arg_value: '1' + datatype: int + operation: equals diff --git a/linux_os/guide/system/bootloader-grub2/grub2_init_on_free/rule.yml b/linux_os/guide/system/bootloader-grub2/grub2_init_on_free/rule.yml index ef2844e6fd58..51d3ce0d988b 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_init_on_free/rule.yml +++ b/linux_os/guide/system/bootloader-grub2/grub2_init_on_free/rule.yml @@ -29,3 +29,5 @@ template: vars: arg_name: init_on_free arg_value: '1' + datatype: int + operation: equals diff --git a/linux_os/guide/system/bootloader-grub2/grub2_kernel_trust_cpu_rng/rule.yml b/linux_os/guide/system/bootloader-grub2/grub2_kernel_trust_cpu_rng/rule.yml index 3d6b750d92f1..daf0a12f5cfc 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_kernel_trust_cpu_rng/rule.yml +++ b/linux_os/guide/system/bootloader-grub2/grub2_kernel_trust_cpu_rng/rule.yml @@ -52,5 +52,7 @@ template: vars: arg_name: random.trust_cpu arg_value: 'on' + datatype: string + operation: equals backends: oval: "off" diff --git a/linux_os/guide/system/bootloader-grub2/grub2_l1tf_argument/rule.yml b/linux_os/guide/system/bootloader-grub2/grub2_l1tf_argument/rule.yml index e457bb00cce3..7c408bca491c 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_l1tf_argument/rule.yml +++ b/linux_os/guide/system/bootloader-grub2/grub2_l1tf_argument/rule.yml @@ -43,3 +43,5 @@ template: vars: arg_name: l1tf arg_variable: var_l1tf_options + datatype: string + operation: equals diff --git a/linux_os/guide/system/bootloader-grub2/grub2_mce_argument/rule.yml b/linux_os/guide/system/bootloader-grub2/grub2_mce_argument/rule.yml index 35cca812501a..1c37176567b3 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_mce_argument/rule.yml +++ b/linux_os/guide/system/bootloader-grub2/grub2_mce_argument/rule.yml @@ -36,3 +36,5 @@ template: vars: arg_name: mce arg_value: '0' + datatype: int + operation: equals diff --git a/linux_os/guide/system/bootloader-grub2/grub2_mds_argument/rule.yml b/linux_os/guide/system/bootloader-grub2/grub2_mds_argument/rule.yml index 6e24c5e5e071..a4ce30858d0d 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_mds_argument/rule.yml +++ b/linux_os/guide/system/bootloader-grub2/grub2_mds_argument/rule.yml @@ -53,3 +53,5 @@ template: vars: arg_name: mds arg_variable: var_mds_options + datatype: string + operation: equals diff --git a/linux_os/guide/system/bootloader-grub2/grub2_page_alloc_shuffle_argument/rule.yml b/linux_os/guide/system/bootloader-grub2/grub2_page_alloc_shuffle_argument/rule.yml index 49212c0bc281..ec0b325999af 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_page_alloc_shuffle_argument/rule.yml +++ b/linux_os/guide/system/bootloader-grub2/grub2_page_alloc_shuffle_argument/rule.yml @@ -39,3 +39,5 @@ template: vars: arg_name: page_alloc.shuffle arg_value: '1' + datatype: int + operation: equals diff --git a/linux_os/guide/system/bootloader-grub2/grub2_pti_argument/rule.yml b/linux_os/guide/system/bootloader-grub2/grub2_pti_argument/rule.yml index 9ba761cd3af6..e2956d122fe0 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_pti_argument/rule.yml +++ b/linux_os/guide/system/bootloader-grub2/grub2_pti_argument/rule.yml @@ -38,6 +38,8 @@ template: vars: arg_name: pti arg_value: 'on' + datatype: string + operation: equals fixtext: |- {{{ describe_grub2_argument("pti=on") | indent(4) }}} diff --git a/linux_os/guide/system/bootloader-grub2/grub2_rng_core_default_quality_argument/rule.yml b/linux_os/guide/system/bootloader-grub2/grub2_rng_core_default_quality_argument/rule.yml index 1448e85596f0..226fef331cff 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_rng_core_default_quality_argument/rule.yml +++ b/linux_os/guide/system/bootloader-grub2/grub2_rng_core_default_quality_argument/rule.yml @@ -44,3 +44,5 @@ template: vars: arg_name: rng_core.default_quality arg_variable: var_rng_core_default_quality + datatype: int + operation: equals diff --git a/linux_os/guide/system/bootloader-grub2/grub2_slab_nomerge_argument/rule.yml b/linux_os/guide/system/bootloader-grub2/grub2_slab_nomerge_argument/rule.yml index 4fff9eee7baf..91d2ebfac7ad 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_slab_nomerge_argument/rule.yml +++ b/linux_os/guide/system/bootloader-grub2/grub2_slab_nomerge_argument/rule.yml @@ -42,3 +42,5 @@ template: vars: arg_name: slab_nomerge arg_value: 'yes' + datatype: string + operation: equals diff --git a/linux_os/guide/system/bootloader-grub2/grub2_spec_store_bypass_disable_argument/rule.yml b/linux_os/guide/system/bootloader-grub2/grub2_spec_store_bypass_disable_argument/rule.yml index 608da7e8a0a7..5c3ca7464b84 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_spec_store_bypass_disable_argument/rule.yml +++ b/linux_os/guide/system/bootloader-grub2/grub2_spec_store_bypass_disable_argument/rule.yml @@ -46,3 +46,5 @@ template: vars: arg_name: spec_store_bypass_disable arg_variable: var_spec_store_bypass_disable_options + datatype: string + operation: equals diff --git a/linux_os/guide/system/bootloader-grub2/grub2_spectre_v2_argument/rule.yml b/linux_os/guide/system/bootloader-grub2/grub2_spectre_v2_argument/rule.yml index ad3c01692100..fb5cb8395e68 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_spectre_v2_argument/rule.yml +++ b/linux_os/guide/system/bootloader-grub2/grub2_spectre_v2_argument/rule.yml @@ -39,3 +39,5 @@ template: vars: arg_name: spectre_v2 arg_value: on + datatype: string + operation: equals diff --git a/linux_os/guide/system/bootloader-grub2/grub2_vsyscall_argument/rule.yml b/linux_os/guide/system/bootloader-grub2/grub2_vsyscall_argument/rule.yml index 9fc7c799c829..5723b665ebaf 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_vsyscall_argument/rule.yml +++ b/linux_os/guide/system/bootloader-grub2/grub2_vsyscall_argument/rule.yml @@ -38,6 +38,8 @@ template: vars: arg_name: vsyscall arg_value: none + datatype: string + operation: equals fixtext: |- {{{ describe_grub2_argument("vsyscall=none") | indent(4) }}} diff --git a/linux_os/guide/system/bootloader-grub2/var_rng_core_default_quality.var b/linux_os/guide/system/bootloader-grub2/var_rng_core_default_quality.var index d54caae551d5..77c2d7ca942f 100644 --- a/linux_os/guide/system/bootloader-grub2/var_rng_core_default_quality.var +++ b/linux_os/guide/system/bootloader-grub2/var_rng_core_default_quality.var @@ -8,7 +8,7 @@ description: |- interactive: true -type: string +type: number operator: equals diff --git a/linux_os/guide/system/network/network-ipv6/disabling_ipv6/grub2_ipv6_disable_argument/rule.yml b/linux_os/guide/system/network/network-ipv6/disabling_ipv6/grub2_ipv6_disable_argument/rule.yml index 7e2f1dacc65b..65ed5fe5cc13 100644 --- a/linux_os/guide/system/network/network-ipv6/disabling_ipv6/grub2_ipv6_disable_argument/rule.yml +++ b/linux_os/guide/system/network/network-ipv6/disabling_ipv6/grub2_ipv6_disable_argument/rule.yml @@ -38,3 +38,5 @@ template: vars: arg_name: ipv6.disable arg_value: '1' + datatype: int + operation: equals diff --git a/linux_os/guide/system/permissions/restrictions/poisoning/grub2_page_poison_argument/rule.yml b/linux_os/guide/system/permissions/restrictions/poisoning/grub2_page_poison_argument/rule.yml index 65ee91201ccf..8f82c74cd29b 100644 --- a/linux_os/guide/system/permissions/restrictions/poisoning/grub2_page_poison_argument/rule.yml +++ b/linux_os/guide/system/permissions/restrictions/poisoning/grub2_page_poison_argument/rule.yml @@ -40,6 +40,8 @@ template: vars: arg_name: page_poison arg_value: '1' + datatype: int + operation: equals fixtext: |- {{{ describe_grub2_argument("page_poison=1") | indent(4) }}} diff --git a/linux_os/guide/system/permissions/restrictions/poisoning/grub2_slub_debug_argument/rule.yml b/linux_os/guide/system/permissions/restrictions/poisoning/grub2_slub_debug_argument/rule.yml index ed4efee8c5dd..0c7f99d631db 100644 --- a/linux_os/guide/system/permissions/restrictions/poisoning/grub2_slub_debug_argument/rule.yml +++ b/linux_os/guide/system/permissions/restrictions/poisoning/grub2_slub_debug_argument/rule.yml @@ -40,7 +40,9 @@ template: vars: arg_name: slub_debug arg_variable: var_slub_debug_options - is_substring@ol8: "true" + datatype: string + operation: equals + operation@ol8: "pattern match" fixtext: |- {{{ describe_grub2_argument("slub_debug=" ~ xccdf_value("var_slub_debug_options")) | indent(4) }}} From 2ac0bbbd84f86955245cb1b23e9e568306e109b8 Mon Sep 17 00:00:00 2001 From: Peter Macko <44851174+macko1@users.noreply.github.com> Date: Thu, 14 May 2026 12:38:03 +0200 Subject: [PATCH 5/6] Add boundary tests and fix test value handling - 3 new tests for >= (value at threshold, above, below) - Fix wrong-value tests: use the actual argument name with a wrong value instead of replacing the entire argument with garbage - Add section comments to all grub2_bootloader_argument test scripts - Simplify Jinja conditionals in >= and pattern-match tests - Clean up template.py test value computation --- .../tests/arg_not_in_entries.fail.sh | 9 ++++---- .../tests/arg_not_in_etcdefaultgrub.fail.sh | 8 ++++--- ...n_etcdefaultgrub_recovery_disabled.fail.sh | 9 +++++--- ..._not_in_grubenv_and_not_referenced.pass.sh | 6 ++++-- .../arg_not_in_grubenv_but_referenced.fail.sh | 7 +++++-- .../tests/correct_recovery_disabled.pass.sh | 6 ++++-- .../correct_value_etcdefault_dir.pass.sh | 2 +- ...rect_value_etcdefault_dir_noupdate.fail.sh | 9 ++++---- .../correct_value_exceeds_minimum.pass.sh | 21 +++++++++++++++++++ .../tests/correct_value_grubenv_only.pass.sh | 7 +++++-- .../tests/correct_value_meets_minimum.pass.sh | 21 +++++++++++++++++++ ...rect_value_mix_entries_and_grubenv.pass.sh | 7 +++++-- .../tests/correct_value_noupdate.fail.sh | 10 +++++---- .../tests/correct_value_remediated.pass.sh | 7 +++++-- .../correct_value_substring_left.pass.sh | 15 ++++++++++--- .../correct_value_substring_right.pass.sh | 15 ++++++++++--- .../tests/invalid_rescue.pass.sh | 7 +++++-- .../tests/wrong_value_below_minimum.fail.sh | 21 +++++++++++++++++++ .../tests/wrong_value_entries.fail.sh | 14 +++++++------ .../tests/wrong_value_etcdefault.fail.sh | 16 +++++++------- .../tests/wrong_value_etcdefault_dir.fail.sh | 13 +++++++----- ...e_etcdefaultgrub_recovery_disabled.fail.sh | 12 +++++------ .../tests/wrong_value_grubenv.fail.sh | 17 ++++++++------- 23 files changed, 187 insertions(+), 72 deletions(-) create mode 100644 shared/templates/grub2_bootloader_argument/tests/correct_value_exceeds_minimum.pass.sh create mode 100644 shared/templates/grub2_bootloader_argument/tests/correct_value_meets_minimum.pass.sh create mode 100644 shared/templates/grub2_bootloader_argument/tests/wrong_value_below_minimum.fail.sh diff --git a/shared/templates/grub2_bootloader_argument/tests/arg_not_in_entries.fail.sh b/shared/templates/grub2_bootloader_argument/tests/arg_not_in_entries.fail.sh index f36c7d8bcd64..2317091f73ec 100644 --- a/shared/templates/grub2_bootloader_argument/tests/arg_not_in_entries.fail.sh +++ b/shared/templates/grub2_bootloader_argument/tests/arg_not_in_entries.fail.sh @@ -4,16 +4,17 @@ # packages = grub2,grubby {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} source common.sh +# --- Setup: populate all GRUB configs with correct value --- {{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME_VALUE) }}} -# Removes argument from kernel command line in /boot/loader/entries/*.conf - +# --- Make oscap fail: remove arg from BLS entries --- for file in /boot/loader/entries/*.conf ; do if grep -q '^.*\<{{{ ARG_NAME }}}\>=\?.*' "$file" ; then sed -i 's/\(^.*\)\<{{{ ARG_NAME }}}\>=\?[^[:space:]]*\(.*\)/\1 \2/' "$file" diff --git a/shared/templates/grub2_bootloader_argument/tests/arg_not_in_etcdefaultgrub.fail.sh b/shared/templates/grub2_bootloader_argument/tests/arg_not_in_etcdefaultgrub.fail.sh index d9da255cc194..115112b826ea 100644 --- a/shared/templates/grub2_bootloader_argument/tests/arg_not_in_etcdefaultgrub.fail.sh +++ b/shared/templates/grub2_bootloader_argument/tests/arg_not_in_etcdefaultgrub.fail.sh @@ -3,15 +3,17 @@ # platform = multi_platform_all {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} source common.sh +# --- Setup: populate all GRUB configs with correct value --- {{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME_VALUE) }}} -# Removes argument from kernel command line in /etc/default/grub +# --- Make oscap fail: remove arg from /etc/default/grub --- if grep -q '^GRUB_CMDLINE_LINUX=.*\<{{{ ARG_NAME }}}\>=\?.*"' '/etc/default/grub' ; then sed -i 's/\(^GRUB_CMDLINE_LINUX=".*\)\<{{{ ARG_NAME }}}\>=\?[^[:space:]]*\(.*"\)/\1 \2/' '/etc/default/grub' fi diff --git a/shared/templates/grub2_bootloader_argument/tests/arg_not_in_etcdefaultgrub_recovery_disabled.fail.sh b/shared/templates/grub2_bootloader_argument/tests/arg_not_in_etcdefaultgrub_recovery_disabled.fail.sh index 8291aaf0294a..0ba9fdf6717f 100644 --- a/shared/templates/grub2_bootloader_argument/tests/arg_not_in_etcdefaultgrub_recovery_disabled.fail.sh +++ b/shared/templates/grub2_bootloader_argument/tests/arg_not_in_etcdefaultgrub_recovery_disabled.fail.sh @@ -1,16 +1,19 @@ #!/bin/bash # platform = multi_platform_all + {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} source common.sh +# --- Setup: populate all GRUB configs with correct value (recovery disabled) --- {{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME_VALUE) }}} -# Removes the argument from kernel command line in /etc/default/grub +# --- Make oscap fail: remove arg from /etc/default/grub --- if grep -q '^GRUB_CMDLINE_LINUX_DEFAULT=.*\<{{{ ARG_NAME }}}\>=\?.*"' '/etc/default/grub' ; then sed -i 's/\(^GRUB_CMDLINE_LINUX_DEFAULT=".*\)\<{{{ ARG_NAME }}}\>=\?[^[:space:]]*\(.*"\)/\1 \2/' '/etc/default/grub' fi diff --git a/shared/templates/grub2_bootloader_argument/tests/arg_not_in_grubenv_and_not_referenced.pass.sh b/shared/templates/grub2_bootloader_argument/tests/arg_not_in_grubenv_and_not_referenced.pass.sh index 59d4ddd5d11b..608d074fba10 100644 --- a/shared/templates/grub2_bootloader_argument/tests/arg_not_in_grubenv_and_not_referenced.pass.sh +++ b/shared/templates/grub2_bootloader_argument/tests/arg_not_in_grubenv_and_not_referenced.pass.sh @@ -4,12 +4,14 @@ # packages = grub2,grubby {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} source common.sh +# --- Setup: populate all GRUB configs with correct value --- {{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME_VALUE) }}} # ensure that the grubenv entry is not referenced # also in RHEL 8, after performing previous steps, the only option is $kernelopts diff --git a/shared/templates/grub2_bootloader_argument/tests/arg_not_in_grubenv_but_referenced.fail.sh b/shared/templates/grub2_bootloader_argument/tests/arg_not_in_grubenv_but_referenced.fail.sh index 4b5e130626bd..7d4beba0848e 100644 --- a/shared/templates/grub2_bootloader_argument/tests/arg_not_in_grubenv_but_referenced.fail.sh +++ b/shared/templates/grub2_bootloader_argument/tests/arg_not_in_grubenv_but_referenced.fail.sh @@ -2,13 +2,16 @@ # platform = Oracle Linux 8,Red Hat Enterprise Linux 8 # packages = grub2,grubby + {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} source common.sh +# --- Make oscap fail: entries reference $kernelopts but arg missing from grubenv --- for entry in /boot/loader/entries/*.conf; do if ! grep -q '\$kernelopts' "$entry"; then sed -i 's/^\(options.*\)$/\1 \$kernelopts/' "$entry" diff --git a/shared/templates/grub2_bootloader_argument/tests/correct_recovery_disabled.pass.sh b/shared/templates/grub2_bootloader_argument/tests/correct_recovery_disabled.pass.sh index e6d17a7e2c8e..09b3f8d0be97 100644 --- a/shared/templates/grub2_bootloader_argument/tests/correct_recovery_disabled.pass.sh +++ b/shared/templates/grub2_bootloader_argument/tests/correct_recovery_disabled.pass.sh @@ -7,12 +7,14 @@ # packages = grub2,grubby {{%- endif %}} {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} source common.sh +# --- Setup: populate all GRUB configs with correct value (recovery disabled) --- {{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME_VALUE) }}} # Correct the form of default kernel command line in GRUB /etc/default/grub and applies value through Grubby diff --git a/shared/templates/grub2_bootloader_argument/tests/correct_value_etcdefault_dir.pass.sh b/shared/templates/grub2_bootloader_argument/tests/correct_value_etcdefault_dir.pass.sh index d330401f9c3e..7a4cc9a9c01e 100644 --- a/shared/templates/grub2_bootloader_argument/tests/correct_value_etcdefault_dir.pass.sh +++ b/shared/templates/grub2_bootloader_argument/tests/correct_value_etcdefault_dir.pass.sh @@ -3,7 +3,7 @@ # platform = multi_platform_ubuntu # packages = grub2 -# Clean up +# --- Setup: populate grub.d with correct value and update grub --- rm -f /etc/default/grub.d/* echo "GRUB_CMDLINE_LINUX=\"\"" > /etc/default/grub diff --git a/shared/templates/grub2_bootloader_argument/tests/correct_value_etcdefault_dir_noupdate.fail.sh b/shared/templates/grub2_bootloader_argument/tests/correct_value_etcdefault_dir_noupdate.fail.sh index da3053c291a7..2d5d4eaf15cc 100644 --- a/shared/templates/grub2_bootloader_argument/tests/correct_value_etcdefault_dir_noupdate.fail.sh +++ b/shared/templates/grub2_bootloader_argument/tests/correct_value_etcdefault_dir_noupdate.fail.sh @@ -4,15 +4,16 @@ # packages = grub2 {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} -# Clean up +# --- Setup: clear grub.d and update grub.cfg to empty state --- rm -f /etc/default/grub.d/* echo "GRUB_CMDLINE_LINUX=\"\"" > /etc/default/grub {{{ grub_command("update") }}} -# Set the correct argument without updating the grub.cfg +# --- Make oscap fail: set correct value in grub.d without updating grub.cfg --- echo "GRUB_CMDLINE_LINUX=\"\$GRUB_CMDLINE_LINUX {{{ ARG_NAME_VALUE }}}\"" > /etc/default/grub.d/custom.cfg diff --git a/shared/templates/grub2_bootloader_argument/tests/correct_value_exceeds_minimum.pass.sh b/shared/templates/grub2_bootloader_argument/tests/correct_value_exceeds_minimum.pass.sh new file mode 100644 index 000000000000..c0644fd83e99 --- /dev/null +++ b/shared/templates/grub2_bootloader_argument/tests/correct_value_exceeds_minimum.pass.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +{{% if OPERATION == "greater than or equal" %}} +# platform = multi_platform_all +{{%- if 'ubuntu' in product %}} +# packages = grub2 +{{%- else %}} +# packages = grub2,grubby +{{%- endif %}} +{{%- if ARG_VARIABLE %}} +# variables = {{{ ARG_VARIABLE }}}={{{ VALUE_AT_THRESHOLD }}} +{{%- endif %}} + +source common.sh + +# --- Setup: populate all GRUB configs with value above GTE threshold --- +{{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME ~ "=" ~ VALUE_ABOVE) }}} +{{% else %}} +# platform = Not Applicable +source common.sh +{{% endif %}} diff --git a/shared/templates/grub2_bootloader_argument/tests/correct_value_grubenv_only.pass.sh b/shared/templates/grub2_bootloader_argument/tests/correct_value_grubenv_only.pass.sh index dc6f7e6119a7..e0437f7530db 100644 --- a/shared/templates/grub2_bootloader_argument/tests/correct_value_grubenv_only.pass.sh +++ b/shared/templates/grub2_bootloader_argument/tests/correct_value_grubenv_only.pass.sh @@ -2,13 +2,16 @@ # platform = Oracle Linux 8,Red Hat Enterprise Linux 8 # packages = grub2,grubby + {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} source common.sh +# --- Setup: correct value in grubenv with BLS entries referencing $kernelopts --- # adds argument from kernel command line into /etc/default/grub file="/etc/default/grub" if grep -q '^GRUB_CMDLINE_LINUX=.*\<{{{ ARG_NAME }}}\>=\?.*"' "$file"; then diff --git a/shared/templates/grub2_bootloader_argument/tests/correct_value_meets_minimum.pass.sh b/shared/templates/grub2_bootloader_argument/tests/correct_value_meets_minimum.pass.sh new file mode 100644 index 000000000000..2779144b269a --- /dev/null +++ b/shared/templates/grub2_bootloader_argument/tests/correct_value_meets_minimum.pass.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +{{% if OPERATION == "greater than or equal" %}} +# platform = multi_platform_all +{{%- if 'ubuntu' in product %}} +# packages = grub2 +{{%- else %}} +# packages = grub2,grubby +{{%- endif %}} +{{%- if ARG_VARIABLE %}} +# variables = {{{ ARG_VARIABLE }}}={{{ VALUE_AT_THRESHOLD }}} +{{%- endif %}} + +source common.sh + +# --- Setup: populate all GRUB configs with value at GTE threshold --- +{{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME ~ "=" ~ VALUE_AT_THRESHOLD) }}} +{{% else %}} +# platform = Not Applicable +source common.sh +{{% endif %}} diff --git a/shared/templates/grub2_bootloader_argument/tests/correct_value_mix_entries_and_grubenv.pass.sh b/shared/templates/grub2_bootloader_argument/tests/correct_value_mix_entries_and_grubenv.pass.sh index 3b3687f453ca..107f30838341 100644 --- a/shared/templates/grub2_bootloader_argument/tests/correct_value_mix_entries_and_grubenv.pass.sh +++ b/shared/templates/grub2_bootloader_argument/tests/correct_value_mix_entries_and_grubenv.pass.sh @@ -2,13 +2,16 @@ # platform = Oracle Linux 8,Red Hat Enterprise Linux 8 # packages = grub2,grubby + {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} source common.sh +# --- Setup: correct value in both BLS entries and grubenv --- # adds argument from kernel command line into /etc/default/grub file="/etc/default/grub" if grep -q '^GRUB_CMDLINE_LINUX=.*\<{{{ ARG_NAME }}}\>=\?.*"' "$file"; then diff --git a/shared/templates/grub2_bootloader_argument/tests/correct_value_noupdate.fail.sh b/shared/templates/grub2_bootloader_argument/tests/correct_value_noupdate.fail.sh index 380353886a81..7a792a205307 100644 --- a/shared/templates/grub2_bootloader_argument/tests/correct_value_noupdate.fail.sh +++ b/shared/templates/grub2_bootloader_argument/tests/correct_value_noupdate.fail.sh @@ -2,18 +2,20 @@ # platform = multi_platform_all # packages = grub2 + {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} source common.sh -# Clean up +# --- Setup: clear configs and update grub.cfg to empty state --- rm -f /etc/default/grub.d/* echo "GRUB_CMDLINE_LINUX=\"\"" > /etc/default/grub {{{ grub_command("update") }}} -# Set the correct argument without updating the grub.cfg +# --- Make oscap fail: set correct value in /etc/default/grub without updating grub.cfg --- echo "GRUB_CMDLINE_LINUX=\"{{{ ARG_NAME_VALUE }}}\"" > /etc/default/grub diff --git a/shared/templates/grub2_bootloader_argument/tests/correct_value_remediated.pass.sh b/shared/templates/grub2_bootloader_argument/tests/correct_value_remediated.pass.sh index d3c822e36fc0..2155010a1c2e 100644 --- a/shared/templates/grub2_bootloader_argument/tests/correct_value_remediated.pass.sh +++ b/shared/templates/grub2_bootloader_argument/tests/correct_value_remediated.pass.sh @@ -1,16 +1,19 @@ #!/bin/bash # platform = multi_platform_all + {{%- if 'ubuntu' in product %}} # packages = grub2 {{%- else %}} # packages = grub2,grubby {{%- endif %}} {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} source common.sh +# --- Setup: populate all GRUB configs with correct value --- {{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME_VALUE) }}} diff --git a/shared/templates/grub2_bootloader_argument/tests/correct_value_substring_left.pass.sh b/shared/templates/grub2_bootloader_argument/tests/correct_value_substring_left.pass.sh index 7bf115155c3b..d710b54ea0d4 100644 --- a/shared/templates/grub2_bootloader_argument/tests/correct_value_substring_left.pass.sh +++ b/shared/templates/grub2_bootloader_argument/tests/correct_value_substring_left.pass.sh @@ -1,8 +1,14 @@ #!/bin/bash -{{% if IS_SUBSTRING != "true" %}} -# platform = Not Applicable -{{% else %}} +# Test: expected value appears inside a longer actual value (appended flags). +# Example: slub_debug on OL8 — profile expects "P", system has "PA" (P + extra flag). +# operation: pattern match finds "P" inside "PA" -> PASS. +# +# Only applicable when the rule uses operation: pattern match. + +{{% if OPERATION == "pattern match" %}} # platform = multi_platform_all +{{% else %}} +# platform = Not Applicable {{% endif %}} {{%- if 'ubuntu' in product %}} # packages = grub2 @@ -12,11 +18,14 @@ {{%- if ARG_VARIABLE %}} # variables = {{{ ARG_VARIABLE }}}=correct_value +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} {{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} {{%- endif %}} +# Append "A" to the value: e.g. slub_debug=PA instead of slub_debug=P {{%- set ARG_NAME_VALUE= ARG_NAME_VALUE ~ "A" %}} source common.sh +# --- Setup: populate all GRUB configs with value containing extra leading chars --- {{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME_VALUE) }}} diff --git a/shared/templates/grub2_bootloader_argument/tests/correct_value_substring_right.pass.sh b/shared/templates/grub2_bootloader_argument/tests/correct_value_substring_right.pass.sh index dc2440d7f6bd..b9238beaf97c 100644 --- a/shared/templates/grub2_bootloader_argument/tests/correct_value_substring_right.pass.sh +++ b/shared/templates/grub2_bootloader_argument/tests/correct_value_substring_right.pass.sh @@ -1,8 +1,14 @@ #!/bin/bash -{{% if IS_SUBSTRING != "true" %}} -# platform = Not Applicable -{{% else %}} +# Test: expected value appears inside a longer actual value (prepended flags). +# Example: slub_debug on OL8 — profile expects "P", system has "FP" (extra flag + P). +# operation: pattern match finds "P" inside "FP" -> PASS. +# +# Only applicable when the rule uses operation: pattern match. + +{{% if OPERATION == "pattern match" %}} # platform = multi_platform_all +{{% else %}} +# platform = Not Applicable {{% endif %}} {{%- if 'ubuntu' in product %}} # packages = grub2 @@ -12,11 +18,14 @@ {{%- if ARG_VARIABLE %}} # variables = {{{ ARG_VARIABLE }}}=correct_value +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} {{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} {{%- endif %}} +# Prepend "A" to the value: e.g. slub_debug=AP instead of slub_debug=P {{%- set ARG_NAME_VALUE = (ARG_NAME_VALUE | replace("=","=A")) %}} source common.sh +# --- Setup: populate all GRUB configs with value containing extra trailing chars --- {{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME_VALUE) }}} diff --git a/shared/templates/grub2_bootloader_argument/tests/invalid_rescue.pass.sh b/shared/templates/grub2_bootloader_argument/tests/invalid_rescue.pass.sh index c6d5b6b1bacc..0d55f6e745fe 100644 --- a/shared/templates/grub2_bootloader_argument/tests/invalid_rescue.pass.sh +++ b/shared/templates/grub2_bootloader_argument/tests/invalid_rescue.pass.sh @@ -2,13 +2,16 @@ # platform = Red Hat Enterprise Linux 9,Red Hat Enterprise Linux 10,multi_platform_fedora # packages = grub2,grubby + {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} source common.sh +# --- Setup: populate all GRUB configs with correct value --- {{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME_VALUE) }}} # Rule should find this file and notice it is not right diff --git a/shared/templates/grub2_bootloader_argument/tests/wrong_value_below_minimum.fail.sh b/shared/templates/grub2_bootloader_argument/tests/wrong_value_below_minimum.fail.sh new file mode 100644 index 000000000000..b2bfb8cd2777 --- /dev/null +++ b/shared/templates/grub2_bootloader_argument/tests/wrong_value_below_minimum.fail.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +{{% if OPERATION == "greater than or equal" %}} +# platform = multi_platform_all +{{%- if 'ubuntu' in product %}} +# packages = grub2 +{{%- else %}} +# packages = grub2,grubby +{{%- endif %}} +{{%- if ARG_VARIABLE %}} +# variables = {{{ ARG_VARIABLE }}}={{{ VALUE_AT_THRESHOLD }}} +{{%- endif %}} + +source common.sh + +# --- Make oscap fail: set value below GTE threshold in all configs --- +{{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME ~ "=" ~ VALUE_BELOW) }}} +{{% else %}} +# platform = Not Applicable +source common.sh +{{% endif %}} diff --git a/shared/templates/grub2_bootloader_argument/tests/wrong_value_entries.fail.sh b/shared/templates/grub2_bootloader_argument/tests/wrong_value_entries.fail.sh index 788f128b3973..7fb9cb04bc79 100644 --- a/shared/templates/grub2_bootloader_argument/tests/wrong_value_entries.fail.sh +++ b/shared/templates/grub2_bootloader_argument/tests/wrong_value_entries.fail.sh @@ -2,19 +2,21 @@ # platform = multi_platform_fedora,multi_platform_rhel # packages = grub2,grubby + {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} -{{%- set ARG_NAME_VALUE_WRONG= ARG_NAME ~ "=wrong_value" %}} -{{%- else %}} -{{%- set ARG_NAME_VALUE_WRONG= "wrong_variable" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} +{{#- Wrong value: right argument name, wrong value (e.g. audit_backlog_limit=8191) #}} +{{%- set ARG_NAME_VALUE_WRONG= ARG_NAME ~ "=" ~ TEST_VALUE_FAIL %}} source common.sh +# --- Setup: populate all GRUB configs with correct value --- {{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME_VALUE) }}} -# Breaks argument from kernel command line in /boot/loader/entries/*.conf +# --- Make oscap fail: set wrong value in BLS entries --- for file in /boot/loader/entries/*.conf ; do if grep -q '^.*\<{{{ ARG_NAME }}}\>=\?.*' "$file" ; then # modify the GRUB command-line if an ={{{ARG_NAME}}} arg already exists diff --git a/shared/templates/grub2_bootloader_argument/tests/wrong_value_etcdefault.fail.sh b/shared/templates/grub2_bootloader_argument/tests/wrong_value_etcdefault.fail.sh index b2ab149053bb..01e8dd0b2734 100644 --- a/shared/templates/grub2_bootloader_argument/tests/wrong_value_etcdefault.fail.sh +++ b/shared/templates/grub2_bootloader_argument/tests/wrong_value_etcdefault.fail.sh @@ -9,19 +9,19 @@ {{%- endif %}} {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} -{{%- set ARG_NAME_VALUE_WRONG= ARG_NAME ~ "=wrong_value" %}} -{{%- else %}} -{{%- set ARG_NAME_VALUE_WRONG= "wrong_variable" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} +{{#- Wrong value: right argument name, wrong value (e.g. audit_backlog_limit=8191) #}} +{{%- set ARG_NAME_VALUE_WRONG= ARG_NAME ~ "=" ~ TEST_VALUE_FAIL %}} source common.sh -# Clean up and make sure we are at a passing state +# --- Setup: populate /etc/default/grub with correct value and update grub.cfg --- rm -f /etc/default/grub.d/* echo "GRUB_CMDLINE_LINUX=\"{{{ ARG_NAME_VALUE }}}\"" > /etc/default/grub {{{ grub_command("update") }}} -# Set to wrong var/value -echo "GRUB_CMDLINE_LINUX=\"{{{ ARG_NAME_VALUE_WRONG }}}=\"" > /etc/default/grub +# --- Make oscap fail: set wrong value in /etc/default/grub --- +echo "GRUB_CMDLINE_LINUX=\"{{{ ARG_NAME_VALUE_WRONG }}}\"" > /etc/default/grub diff --git a/shared/templates/grub2_bootloader_argument/tests/wrong_value_etcdefault_dir.fail.sh b/shared/templates/grub2_bootloader_argument/tests/wrong_value_etcdefault_dir.fail.sh index 514b8006e4d8..bf4704bc6e73 100644 --- a/shared/templates/grub2_bootloader_argument/tests/wrong_value_etcdefault_dir.fail.sh +++ b/shared/templates/grub2_bootloader_argument/tests/wrong_value_etcdefault_dir.fail.sh @@ -4,16 +4,19 @@ # packages = grub2 {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} +{{#- Wrong value: right argument name, wrong value (e.g. audit_backlog_limit=8191) #}} +{{%- set ARG_NAME_VALUE_WRONG= ARG_NAME ~ "=" ~ TEST_VALUE_FAIL %}} -# Clean up and make sure we are at a passing state +# --- Setup: populate grub.d with correct value and update grub.cfg --- rm -f /etc/default/grub.d/* echo "GRUB_CMDLINE_LINUX=\"\"" > /etc/default/grub echo "GRUB_CMDLINE_LINUX=\"{{{ ARG_NAME_VALUE }}}\"" > /etc/default/grub.d/custom.cfg {{{ grub_command("update") }}} -# Set to wrong var/value -echo "GRUB_CMDLINE_LINUX=\"\$GRUB_CMDLINE_LINUX {{{ ARG_NAME }}}=wrong_value\"" > /etc/default/grub.d/custom.cfg +# --- Make oscap fail: set wrong value in /etc/default/grub.d/ --- +echo "GRUB_CMDLINE_LINUX=\"\$GRUB_CMDLINE_LINUX {{{ ARG_NAME_VALUE_WRONG }}}\"" > /etc/default/grub.d/custom.cfg diff --git a/shared/templates/grub2_bootloader_argument/tests/wrong_value_etcdefaultgrub_recovery_disabled.fail.sh b/shared/templates/grub2_bootloader_argument/tests/wrong_value_etcdefaultgrub_recovery_disabled.fail.sh index e7266d6c7af0..07756501bf65 100644 --- a/shared/templates/grub2_bootloader_argument/tests/wrong_value_etcdefaultgrub_recovery_disabled.fail.sh +++ b/shared/templates/grub2_bootloader_argument/tests/wrong_value_etcdefaultgrub_recovery_disabled.fail.sh @@ -2,16 +2,16 @@ # platform = multi_platform_all {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} -{{%- set ARG_NAME_VALUE_WRONG= ARG_NAME ~ "=wrong_value" %}} -{{%- else %}} -{{%- set ARG_NAME_VALUE_WRONG= "wrong_variable" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} +{{#- Wrong value: right argument name, wrong value (e.g. audit_backlog_limit=8191) #}} +{{%- set ARG_NAME_VALUE_WRONG= ARG_NAME ~ "=" ~ TEST_VALUE_FAIL %}} source common.sh -# Clean up and make sure we are at a passing state +# --- Make oscap fail: set wrong value in /etc/default/grub (recovery disabled) --- rm -f /etc/default/grub.d/* # Correct the form of default kernel command line in GRUB /etc/default/grub and applies value through Grubby diff --git a/shared/templates/grub2_bootloader_argument/tests/wrong_value_grubenv.fail.sh b/shared/templates/grub2_bootloader_argument/tests/wrong_value_grubenv.fail.sh index d17a4d81b2ff..c79c99bd93ce 100644 --- a/shared/templates/grub2_bootloader_argument/tests/wrong_value_grubenv.fail.sh +++ b/shared/templates/grub2_bootloader_argument/tests/wrong_value_grubenv.fail.sh @@ -4,25 +4,26 @@ # packages = grub2,grubby {{%- if ARG_VARIABLE %}} -# variables = {{{ ARG_VARIABLE }}}=correct_value -{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=correct_value" %}} -{{%- set ARG_NAME_VALUE_WRONG= ARG_NAME ~ "=wrong_value" %}} -{{%- else %}} -{{%- set ARG_NAME_VALUE_WRONG= "wrong_variable" %}} +# variables = {{{ ARG_VARIABLE }}}={{{ TEST_VALUE_PASS }}} +{{#- Rules that use arg_variable have no =value in ARG_NAME_VALUE, override with dummy #}} +{{%- set ARG_NAME_VALUE= ARG_NAME ~ "=" ~ TEST_VALUE_PASS %}} {{%- endif %}} +{{#- Wrong value: right argument name, wrong value (e.g. audit_backlog_limit=8191) #}} +{{%- set ARG_NAME_VALUE_WRONG= ARG_NAME ~ "=" ~ TEST_VALUE_FAIL %}} source common.sh +# --- Setup: populate all GRUB configs with correct value --- {{{ grub2_bootloader_argument_remediation(ARG_NAME, ARG_NAME_VALUE) }}} -# Break the argument in kernel command line in /boot/grub2/grubenv +# --- Make oscap fail: set wrong value in grubenv --- file="/boot/grub2/grubenv" if grep -q '^.*\<{{{ ARG_NAME }}}\>=\?.*' "$file" ; then # modify the GRUB command-line if the arg already exists - sed -i 's/\(^.*\)\<{{{ ARG_NAME }}}\>=\?[^[:space:]]*\(.*\)/\1 {{{ ARG_NAME_VALUE_WRONG }}}=wrong \2/' "$file" + sed -i 's/\(^.*\)\<{{{ ARG_NAME }}}\>=\?[^[:space:]]*\(.*\)/\1 {{{ ARG_NAME_VALUE_WRONG }}} \2/' "$file" else # no arg is present, append it - sed -i 's/\(^.*\(vmlinuz\|kernelopts\).*\)/\1 {{{ ARG_NAME_VALUE_WRONG }}}=wrong/' "$file" + sed -i 's/\(^.*\(vmlinuz\|kernelopts\).*\)/\1 {{{ ARG_NAME_VALUE_WRONG }}}/' "$file" fi # Ensure that grubenv is referenced through $kernelopts From fa546adcafb985aa55bb0d96b90d7bbd829cf0dc Mon Sep 17 00:00:00 2001 From: Peter Macko <44851174+macko1@users.noreply.github.com> Date: Thu, 14 May 2026 12:38:09 +0200 Subject: [PATCH 6/6] Document new template parameters in reference - Document operation parameter (equals, pattern match, >=) - Document datatype parameter (string, int) - Note which datatypes each operation supports - Mark unused operations as not yet implemented - Note that arg_variable rules must set operation/datatype to match the .var file --- docs/templates/template_reference.md | 50 ++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/docs/templates/template_reference.md b/docs/templates/template_reference.md index 90e59824113e..e0ba22610043 100644 --- a/docs/templates/template_reference.md +++ b/docs/templates/template_reference.md @@ -455,18 +455,48 @@ they must be of the same length. - Languages: Bash, OVAL #### grub2_bootloader_argument -- Ensures that a kernel command line argument is present in GRUB 2 configuration. +- Ensures that a kernel command line argument is present in GRUB 2 configuration. For example `nousb` or `audit_backlog_limit=8192`. -- Parameters: - - - **arg_name** - argument name, eg. `audit` - - - **arg_value** - argument value, eg. `'1'` - - - **arg_variable** - the variable used as the value for the argument, eg. `'var_slub_debug_options'` - This parameter is mutually exclusive with **arg_value**. +- Parameters: -- Languages: Ansible, Bash, OVAL, Blueprint, Kickstart + - **arg_name** (required) - kernel argument name, e.g. `audit`, `audit_backlog_limit`, `nousb`. + + - **arg_value** (optional) value of the kernel argument, e.g. `'1'`, `'on'`. + - Mutually exclusive with **arg_variable**. + - **Must be quoted** in `rule.yml` — YAML auto-parses unquoted scalars + (`8192` becomes int, `on`/`off` become bool), but the template needs a + string to build regexes and config file content. The build will fail with + a clear error if the value is not a string. + + - **arg_variable** (optional) - XCCDF variable defined in a `.var` file, + e.g. `var_audit_backlog_limit`. + - Mutually exclusive with **arg_value**. + - If used, **operation** and **datatype** has to be set to match the `.var` file's `type` and `operator` variables. + + - **operation** - OVAL comparison operation applied to the extracted value. + Default: `equals`. Supported values: + - `equals` — exact match. Works with `string` or `int`. + Use for arguments with a single known-good value (e.g. `audit=1`, + `pti=on`). + - `pattern match` — regex match. Works with `string` only. + Use when multiple values are acceptable (e.g. `slub_debug` on OL8 + where `P` must appear anywhere inside values like `FZP`). + Replaces the deprecated `is_substring` parameter. + - `greater than or equal` — numeric comparison. Works with `int` only. + Use for threshold arguments (e.g. `audit_backlog_limit>=8192`). + - Other operations (`not equal`, `greater than`, `less than`, + `less than or equal`) are validated but have no test coverage. + Adding a rule with these operations requires adding test scenarios and updating `template.py` to support them. + + - **datatype** - OVAL datatype for the comparison. Default: `string`. + Supported values: `string`, `int`. + - `string` — lexicographic comparison. Use for non-numeric values + (e.g. `on`, `force`, `none`). + - `int` — numeric comparison. Use when the value is a number + (e.g. `audit_backlog_limit=8192`, `audit=1`). Required for numeric + operations like `greater than or equal`. + +- Languages: Ansible, Bash, OVAL, Blueprint, Kickstart #### grub2_bootloader_argument_absent - Ensures that a kernel command line argument is absent in GRUB 2 configuration.