From 869101fdea7f832dcb6cc382a1e82cf383aa84f4 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/3] 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 9ea77852e2ca57e04ec5d8b2dbc734777aa4f623 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 2/3] Refactor grub_bootloader_argument OVAL template 2 --- .../grub2_bootloader_argument/oval.template | 1006 +++++++++-------- 1 file changed, 539 insertions(+), 467 deletions(-) diff --git a/shared/templates/grub2_bootloader_argument/oval.template b/shared/templates/grub2_bootloader_argument/oval.template index 13b407ad4f4b..a8917463b4d4 100644 --- a/shared/templates/grub2_bootloader_argument/oval.template +++ b/shared/templates/grub2_bootloader_argument/oval.template @@ -1,183 +1,311 @@ {{#- - 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 + Product Flags Boot entries Persistent config Other + --------------- ---------------------------------- ---------------------------------- ------------------------ --------------------------- + RHEL 9+, Fedora `uses_boot_loader_entries` `/boot/loader/entries/*.conf` `/etc/default/grub` (`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) 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) + `grub` can store named variables in `grubenv` (a `key=value` file on disk). A `BLS` + entry can reference a variable with `$name` on `options` line in `/boot/loader/entries/*.conf`, 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` entry + might say: + options $kernelopts + and `grub` replaces `$kernelopts` with the `kernelopts=...` value from `grubenv`. + On RHEL 9+, this indirection no longer exists -- args are always listed directly. + + `/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 `kargs.d`) 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. + + 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 check (uses_kernelopts): + | +-- test: EACH `.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 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 + +-- [RHEL 9+ / Fedora] boot loader entries check (uses_boot_loader_entries, !uses_kernelopts): + | +-- test: EACH `.conf` has arg=value on `options` line | - +-- [OL7 / Ubuntu] grub.cfg check: - | +-- (OR) grub.cfg has arg=value (BIOS or UEFI path) + +-- [OL7 / Ubuntu] `grub.cfg` check (uses_grub_cfg): + | +-- (OR) `grub.cfg` has arg=value (BIOS or UEFI path) | - +-- [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` 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. + +-- `GRUB_CMDLINE_LINUX_DEFAULT` has arg=value + | (+ `grub.d` drop-in on Ubuntu) + +-- `GRUB_DISABLE_RECOVERY=true` + + DATA FLOW: + ========== + + Each object extracts the FULL line (or array) as `subexpression`. + Each state uses `pattern match` to check if `arg=value` appears in that captured text. + All `grub`-location tests share one state; `bootc` has its own (quotes around value). + + OBJECTS STATES + ═══════ ══════ + + ┌─ /boot/loader/entries/ ──────────────────┐ + │ path: /boot/loader/entries/ │ + │ filename: ^.*\.conf$ │ ┌─ state_grub2_{ARG}_argument ───────────────┐ + │ pattern: ^options (.*)$ │─>│ │ + │ filter: exclude *rescue.conf │ │ ^(?:.*\s)?{ARG_NAME_VALUE}(?:\s.*)?$ │ + │ (uses_boot_loader_entries) │ │ │ + └──────────────────────────────────────────┘ │ Shared by ALL objects in this column. │ + │ Pattern match: is `{ARG_NAME_VALUE}` │ + ┌─ {grub2_boot_path}/grubenv ─────────────┐ │ present as a standalone word in the │ + │ filepath: {grub2_boot_path}/grubenv │ │ captured line? The regex enforces │ + │ pattern: ^kernelopts=(.*)$ │─>│ whitespace/start/end boundaries. │ + │ (uses_kernelopts) │ │ │ + └──────────────────────────────────────────┘ │ When `ARG_VARIABLE` is set, the regex │ + │ cannot be hardcoded at build time (the │ + │ value is not known yet). Instead, `oscap` │ + │ assembles it at runtime: an `external_ │ + │ variable` receives the value from the │ + │ `XCCDF` profile, a `local_variable` uses │ + │ `` to join fixed regex fragments │ + │ around that value, and the state references │ + │ the resulting string via `var_ref`. │ + │ │ + ┌─ {grub2_boot_path}/grub.cfg ────────────┐ │ │ + │ filepath: {grub2_boot_path}/grub.cfg │ │ │ + │ pattern: ^.*/vmlinuz.*(root=.*)$ │─>│ │ + │ (uses_grub_cfg) │ │ │ + └──────────────────────────────────────────┘ │ │ + │ │ + ┌─ /etc/default/grub ─────────────────────┐ │ │ + │ filepath: /etc/default/grub │ │ │ + │ pattern: ^\s*GRUB_CMDLINE_LINUX= │ │ │ + │ "(.*)"$ │─>│ │ + │ (all products) │ │ │ + │ + same for GRUB_CMDLINE_LINUX_DEFAULT │─>│ │ + └──────────────────────────────────────────┘ │ │ + │ │ + ┌─ /etc/default/grub.d/*.cfg ─────────────┐ │ │ + │ filepath: /etc/default/grub.d/[^/]+\.cfg│ │ │ + │ pattern: ^\s*GRUB_CMDLINE_LINUX= │ │ │ + │ "(.*)"$ │─>│ │ + │ (uses_etc_default_grub_d / Ubuntu) │ │ │ + │ + same for GRUB_CMDLINE_LINUX_DEFAULT │─>│ │ + └──────────────────────────────────────────┘ └─────────────────────────────────────────────┘ + + ┌─ /usr/lib/bootc/kargs.d/*.toml ─────────┐ ┌─ state_grub2_{ARG}_usr_lib_bootc_kargs_d ──┐ + │ path: /usr/lib/bootc/kargs.d/ │ │ │ + │ filename: ^.*\.toml$ │ │ ^.*"{ARG_NAME_VALUE}".*$ │ + │ pattern: ^kargs = \[([^\]]+)\]$ │─>│ │ + │ (bootable_containers_supported) │ │ Separate state: `TOML` wraps values in │ + └──────────────────────────────────────────┘ │ double quotes. │ + └─────────────────────────────────────────────┘ + + (uses_kernelopts) -- additional state used with `state_operator="OR"`: + ┌─ state_grub2_{ARG}_argument_is_kernelopts ─┐ + │ ^(?:.*\s)?\$kernelopts(?:\s.*)?$ │ + │ Matches `$kernelopts` as a word. │ + │ The RHEL 8 BLS test references BOTH │ + │ `state_argument` and this state with OR: │ + │ entry passes if it has arg=value OR │ + │ contains `$kernelopts`. │ + │ (Same flag as the `grubenv` object -- │ + │ both exist only when `$kernelopts` │ + │ indirection is in play.) │ + └─────────────────────────────────────────────┘ + + REGEX SEMANTICS (what each `pattern` captures as `subexpression`): + ───────────────────────────────────────────────────────────────── + + 1. ^options (.*)$ + Source: `/boot/loader/entries/*.conf` + Input: `options root=UUID=abc ro quiet audit_backlog_limit=8192` + Capture: `root=UUID=abc ro quiet audit_backlog_limit=8192` + (everything after `options ` -- the full space-separated kernel arg list) + + 2. ^kernelopts=(.*)$ + Source: `{grub2_boot_path}/grubenv` + Input: `kernelopts=root=UUID=abc ro quiet audit_backlog_limit=8192` + Capture: `root=UUID=abc ro quiet audit_backlog_limit=8192` + (everything after `kernelopts=` -- same format as #1) + + 3. ^.*/vmlinuz.*(root=.*)$ + Source: `{grub2_boot_path}/grub.cfg` + Input: ` linux /vmlinuz-5.14 root=UUID=abc ro quiet audit_backlog_limit=8192` + Capture: `root=UUID=abc ro quiet audit_backlog_limit=8192` + NOTE: the second `.*` is greedy -- it backtracks to the LAST `root=` in the line. + Only arguments AT or AFTER `root=` are captured. Arguments before `root=` are invisible. + + 4. ^\s*GRUB_CMDLINE_LINUX="(.*)"$ + Source: `/etc/default/grub` (or `/etc/default/grub.d/*.cfg`) + Input: `GRUB_CMDLINE_LINUX="ro quiet audit_backlog_limit=8192"` + Capture: `ro quiet audit_backlog_limit=8192` + (everything between the double quotes -- the full arg list) + Same pattern for `GRUB_CMDLINE_LINUX_DEFAULT`. + + 5. ^kargs = \[([^\]]+)\]$ + Source: `/usr/lib/bootc/kargs.d/*.toml` + Input: `kargs = ["audit_backlog_limit=8192", "nosmt"]` + Capture: `"audit_backlog_limit=8192", "nosmt"` + (everything between `[` and `]` -- quoted comma-separated values) + + STATE MATCHING (applied to the captured `subexpression`): + ───────────────────────────────────────────────────────── + + state_grub2_{ARG}_argument: + ^(?:.*\s)?{ARG_NAME_VALUE}(?:\s.*)?$ + Checks if `arg=value` appears as a WHOLE WORD (bounded by start/end or whitespace). + Example: `^(?:.*\s)?audit_backlog_limit=8192(?:\s.*)?$` + Matches: `root=UUID=abc ro quiet audit_backlog_limit=8192` (at end) + Matches: `audit_backlog_limit=8192 ro quiet` (at start) + Matches: `ro audit_backlog_limit=8192 quiet` (in middle) + NO match: `foo_audit_backlog_limit=8192` (not word-bounded on left) + + state_grub2_{ARG}_usr_lib_bootc_kargs_d: + ^.*"{ARG_NAME_VALUE}".*$ + Checks if `"arg=value"` appears anywhere (double-quoted, as per `TOML` syntax). + Example: `^.*"audit_backlog_limit=8192".*$` + Matches: `"audit_backlog_limit=8192", "nosmt"` + + state_grub2_{ARG}_argument_is_kernelopts: + ^(?:.*\s)?\$kernelopts(?:\s.*)?$ + Checks if `$kernelopts` appears as a whole word (same word-boundary logic). + Used by RHEL 8 BLS test with `state_operator="OR"`: entry passes if it has + the arg itself OR contains `$kernelopts` (which gets expanded at boot time). + + 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 %}} + +{{# DEFINITION: `{{{ _RULE_ID }}}` + Class: `compliance` + 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 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. #}} + {{# 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 %}} - {{# CRITERIA BRANCH: normal grub (non-bootc) + {{# 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. #}} + All sub-checks must pass: `bootc` guard, boot entries, and `/etc/default/grub`. + The specific sub-checks emitted depend on the `uses_*` 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. #}} + {{# 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) + {{%- if uses_kernelopts %}} + {{# CRITERIA BRANCH: RHEL 8 / OL8 boot loader entries + `$kernelopts` + `grubenv` + Platforms: RHEL 8, OL8 (`uses_kernelopts`) 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. #}} + 1. EACH `/boot/loader/entries/*.conf` (omit rescue) has {{{ ARG_NAME_VALUE }}} + 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). #}} + {{# 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. #}} + {{# 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. #}} + {{# 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. #}} @@ -237,21 +365,21 @@ {{%- endif %}} - {{%- if check_boot_loader_entries %}} + {{%- if uses_boot_loader_entries and not uses_kernelopts %}} {{# 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. #}} + Platforms: RHEL 9+, Fedora, OL9 (`uses_boot_loader_entries`, not `uses_kernelopts`) + Each `/boot/loader/entries/*.conf` (omit rescue) must have {{{ 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 %}} + {{# CRITERIA: OL7 / Ubuntu `grub.cfg` + Platforms: OL7, Ubuntu (`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. #}} @@ -262,34 +390,33 @@ {{%- 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: `/etc/default/grub` persistent configuration + Platforms: all + 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. #}} + {{# CRITERIA: `GRUB_CMDLINE_LINUX` (applies to all entries) + Checks `/etc/default/grub`, plus `grub.d` drop-ins on Ubuntu. #}} - {{% if check_etc_default_grub_d %}} + {{% if uses_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. #}} + {{# 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 check_etc_default_grub_d %}} + {{% if uses_etc_default_grub_d %}} {{% endif %}} @@ -298,284 +425,121 @@ comment="Verify GRUB_DISABLE_RECOVERY=true in /etc/default/grub" /> - {{% endif %}} +{{# OBJECTS, TESTS, STATES, VARIABLES #}} +{{# --- MACROS --- #}} - -{{# 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 (.*)$ - 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. #}} - - - - - - {{# 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 - - - {{# 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. #}} - - - - - - {{# 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="(.*)"$ +{{# MACRO (OBJECT + TEST): 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 it for {{{ ARG_NAME_VALUE }}}. + Parameters: + `grub_var`: `grub` config variable (`GRUB_CMDLINE_LINUX` or `GRUB_CMDLINE_LINUX_DEFAULT`) + `from_grub_d`: if true, collect from `/etc/default/grub.d/*.cfg` instead of `/etc/default/grub` + `check`: `OVAL` `check` attribute (`all` or `at least one`) #}} +{{%- macro etc_default_grub_object_and_test(grub_var, from_grub_d=false, check="all") %}} + {{%- set name = "grub2_" ~ SANITIZED_ARG_NAME ~ "_" ~ grub_var|lower ~ ("_from_grub_d" if from_grub_d else "") -%}} + {{%- set source = "/etc/default/grub.d/*.cfg" if from_grub_d else "/etc/default/grub" -%}} + + {{%- if from_grub_d %}} + {{# `operation="pattern match"` is required -- without it `OVAL` defaults to `equals`, + and no file is literally named `*.cfg`. #}} + /etc/default/grub.d/[^/]+\.cfg + {{%- else %}} + {{{ source }}} + {{%- endif %}} + ^\s*{{{ grub_var }}}="(.*)"$ 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 "") -%}} + comment="Check {{{ grub_var }}} in {{{ source }}} for {{{ ARG_NAME_VALUE }}}" + check="{{{ check }}}" check_existence="all_exist" version="1"> - - {{# Collect kernelopts=... from grubenv (everything after "kernelopts="). - This is the grub environment variable that boot entries expand via $kernelopts. #}} +{{%- endmacro %}} + +{{# MACRO (OBJECT + TEST): Check `grubenv` for {{{ ARG_NAME_VALUE }}} in the `kernelopts=` line. + Platforms: RHEL 8, OL8 (`uses_kernelopts`). + 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 the `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 "") -%}} + {{# OBJECT: 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 #}} + {{# regex: ^kernelopts=(.*)$ -- captures the full space-separated arg list from `kernelopts` #}} ^kernelopts=(.*)$ 1 - {{%- endmacro %}} - - {{{- test_and_object_for_grubenv() }}} - {{%- if has_separate_bios_and_uefi -%}} - {{{- test_and_object_for_grubenv(variant="uefi") }}} - {{%- endif %}} -{{%- endif %}} - -{{%- 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_{{{ _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 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 "") -%}} + {{# TEST: Check `grubenv` `kernelopts` line for {{{ ARG_NAME_VALUE }}}. + Uses the shared `state_argument` regex. #}} - - {{# 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. #}} +{{%- endmacro %}} + +{{# MACRO (OBJECT + TEST): Check `grub.cfg` `vmlinuz` lines for {{{ ARG_NAME_VALUE }}} in the kernel command line. + Platforms: OL7, Ubuntu (`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 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 "") -%}} + {{# OBJECT: 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) #}} + {{# regex: ^.*/vmlinuz.*(root=.*)$ -- captures from `root=` onwards on `vmlinuz` lines (OL7, Ubuntu) #}} ^.*/vmlinuz.*(root=.*)$ 1 - {{%- endmacro %}} - {{{ 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 %}} + {{# TEST: Check `grub.cfg` `vmlinuz` lines for {{{ ARG_NAME_VALUE }}}. + Uses the shared `state_argument` regex. #}} + + + + +{{%- endmacro %}} -{{% 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 %}} +{{# --- states + variables --- #}} +{{# Defines `state_grub2_{ARG}_argument` (used by all `grub`-location tests) and, when + `bootc` is supported, `state_grub2_{ARG}_usr_lib_bootc_kargs_d` (used by the `bootc` test). + `ARG_VARIABLE` set: the expected value comes from an `XCCDF` variable -- `oscap` builds the + regex at runtime by concatenating `literal_component` and `variable_component`. + `ARG_VARIABLE` unset: the expected value is hardcoded into the regex at build time. #}} -{{# 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 #}} - - + {{# EXTERNAL VARIABLE: Expected value of `{{{ ARG_NAME }}}`, provided by the `XCCDF` benchmark when `oscap` runs. + Referenced by `local_variable` regex builders for both `grub` state and `bootc` `kargs` state. #}} + - {{# Build regex: ^(?:.*\s)?{ARG_NAME}=(?:\s.*)?$ - Matches arg=value as a whole word. IS_SUBSTRING wraps value with \S* (partial match). #}} + {{# VARIABLE: Build regex by concatenating fixed strings (`literal_component`) and the + `XCCDF` variable value (`variable_component`, substituted when `oscap` runs). + Result (e.g. `{{{ ARG_NAME }}}` with value `8192`): + `IS_SUBSTRING` false: `^(?:.*\s)?{{{ ARG_NAME }}}=8192(?:\s.*)?$` (exact match) + `IS_SUBSTRING` true: `^(?:.*\s)?{{{ ARG_NAME }}}=\S*8192\S*(?:\s.*)?$` (partial match) #}} @@ -592,50 +556,19 @@ - {{# Expected value of {{{ ARG_NAME }}}, provided by XCCDF benchmark at scan time. #}} - -{{% else %}} - {{# Expected value hardcoded at build time: {{{ ESCAPED_ARG_NAME_VALUE }}} #}} + {{# STATE (XCCDF variant): Value comes from `XCCDF` variable `{{{ ARG_VARIABLE }}}` -- regex assembled when `oscap` runs. #}} - {{# 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 %}} - -{{# 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 - - {{# 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*. #}} + {{%- if bootable_containers_supported == "true" %}} + {{# VARIABLE: Build regex for `bootc` `TOML` format (values wrapped in double quotes). + Concatenates `literal_component` (fixed strings) and `variable_component` (`XCCDF` value). + Result (e.g. `{{{ ARG_NAME }}}` with value `8192`): + `IS_SUBSTRING` false: `^.*"{{{ ARG_NAME }}}=8192".*$` (exact match in `TOML`) + `IS_SUBSTRING` true: `^.*"{{{ ARG_NAME }}}=\S*8192\S*".*$` (partial match in `TOML`) #}} ^.*"{{{ ARG_NAME }}}= @@ -650,14 +583,153 @@ - {{# Expected value of {{{ ARG_NAME }}}, same XCCDF variable as above. #}} - - {{%- else %}} - {{# Expected value hardcoded at build time, with surrounding quotes for TOML format. #}} + {{# STATE (XCCDF variant): `bootc` `kargs` state -- regex assembled when `oscap` runs, from `{{{ ARG_VARIABLE }}}`. #}} + + + + {{%- endif %}} + +{{% else %}} + {{# STATE (hardcoded variant): 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.*)?$ + + + {{%- if bootable_containers_supported == "true" %}} + {{# STATE (hardcoded variant): Expected value hardcoded at build time, with surrounding quotes for `TOML` format. #}} ^.*"{{{ ESCAPED_ARG_NAME_VALUE }}}".*$ {{%- endif %}} + +{{% 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 uses_boot_loader_entries %}} + {{# OBJECT: Collect the `options` line from each `/boot/loader/entries/*.conf`. + Platforms: all `BLS` platforms (`uses_boot_loader_entries`). + Each `.conf` file produces one collected item. Rescue entries (`*rescue.conf`) excluded. + Shared by RHEL 8 (arg-or-`$kernelopts` + `$kernelopts`-presence) and RHEL 9+ (arg-only) tests. #}} + + /boot/loader/entries/ + ^.*\.conf$ + {{# regex: ^options (.*)$ -- captures the full space-separated arg list after `options` #}} + ^options (.*)$ + 1 + state_grub2_{{{ _RULE_ID }}}_is_rescue_entry + + + {{# FILTER STATE: Exclude filenames ending in `rescue.conf`. + Boot entry objects use this filter to skip rescue entries. #}} + + {{# regex: .*rescue\.conf$ -- matches rescue entry filenames to exclude them #}} + .*rescue\.conf$ + {{% endif %}} +{{% if uses_kernelopts %}} + {{# STATE: Match `$kernelopts` as a whole word in the captured `options` line. + Platforms: RHEL 8, OL8 (`uses_kernelopts`). + `$kernelopts` is a `grub` environment 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.*)?$ + + + {{# TEST: Check that EACH `/boot/loader/entries/*.conf` (omit rescue) has {{{ ARG_NAME_VALUE }}} + on the `options` line OR references `$kernelopts`. + Platforms: RHEL 8, OL8 (`uses_kernelopts`). + Extracts the full `options` line as `subexpression`. + `state_operator="OR"` because entries may have args on the `options` line or via `$kernelopts`. #}} + + + + + + + {{# TEST: Check if at least one `/boot/loader/entries/*.conf` references `$kernelopts`. + Platforms: RHEL 8, OL8 (`uses_kernelopts`). + Reuses `obj_grub2_{ARG}_entries`. Used negated in criteria: if NO entry has `$kernelopts`, + all args are on the `options` line and the `grubenv` check is skipped. #}} + + + + + + {{# Emit BIOS variant unconditionally; emit UEFI variant only when the platform + has separate BIOS and UEFI `grubenv` paths (e.g. `/boot/grub2/` vs. `/boot/efi/EFI/.../`). #}} + {{{- test_and_object_for_grubenv() }}} + {{%- if has_separate_bios_and_uefi -%}} + {{{- test_and_object_for_grubenv(variant="uefi") }}} + {{%- endif %}} +{{% endif %}} + +{{%- if uses_etc_default_grub_d %}} + {{# `/etc/default/grub.d/*.cfg` drop-in files (Ubuntu). + `GRUB_CMDLINE_LINUX` and `GRUB_CMDLINE_LINUX_DEFAULT` from drop-in config files. + `GRUB_CMDLINE_LINUX` uses `check="at least one"` -- passes if any drop-in 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 %}} + +{{%- if uses_boot_loader_entries and not uses_kernelopts %}} + {{# TEST: Check each `/boot/loader/entries/*.conf` (omit rescue) for {{{ ARG_NAME_VALUE }}} on the `options` line. + Platforms: RHEL 9+, Fedora, OL9 (`uses_boot_loader_entries`, not `uses_kernelopts`). + No `$kernelopts` indirection -- RHEL 9+ lists all kernel args directly on the `options` line. #}} + + + + +{{%- endif %}} + +{{%- if uses_grub_cfg %}} + {{# Emit BIOS variant unconditionally; emit UEFI variant only when the platform + has separate BIOS and UEFI `grub.cfg` paths (e.g. `/boot/grub2/` vs. `/boot/efi/EFI/.../`). #}} + {{{ 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 %}} + +{{# --- `bootc` / RHEL Image Mode: object + test (ARG_VARIABLE-independent) --- #}} + +{{%- if bootable_containers_supported == "true" %}} + {{# OBJECT: 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 + + + {{# TEST: 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. #}} + + + + +{{% endif %}} + + From 8df1575a2dbaba2e4f907f4a6374ad0047225cf7 Mon Sep 17 00:00:00 2001 From: Peter Macko <44851174+macko1@users.noreply.github.com> Date: Wed, 6 May 2026 17:04:58 +0200 Subject: [PATCH 3/3] Make comments more accessible for screen readers. --- .../grub2_bootloader_argument/oval.template | 152 +++++++++++++++++- 1 file changed, 144 insertions(+), 8 deletions(-) diff --git a/shared/templates/grub2_bootloader_argument/oval.template b/shared/templates/grub2_bootloader_argument/oval.template index a8917463b4d4..568d1f04c417 100644 --- a/shared/templates/grub2_bootloader_argument/oval.template +++ b/shared/templates/grub2_bootloader_argument/oval.template @@ -8,6 +8,8 @@ LOCATIONS WHERE KERNEL ARGS LIVE (per product): ================================================ + [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`: `kargs.d/*.toml`) @@ -21,6 +23,31 @@ 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: ======================== @@ -116,6 +143,9 @@ (`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 | +-- [Image Mode] AND (bootable_containers_supported) @@ -146,6 +176,38 @@ | (+ `grub.d` drop-in on Ubuntu) +-- `GRUB_DISABLE_RECOVERY=true` + 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 entry references `$kernelopts` (so `grubenv` is irrelevant), + OR `{grub2_boot_path}/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`]: + `{grub2_boot_path}/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 `grub.d` drop-in on Ubuntu), + OR BOTH `GRUB_CMDLINE_LINUX_DEFAULT` has `arg=value` + (plus `grub.d` drop-in on Ubuntu) + AND `GRUB_DISABLE_RECOVERY=true`. + + 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: ========== @@ -153,6 +215,10 @@ Each state uses `pattern match` to check if `arg=value` appears in that captured text. All `grub`-location tests share one state; `bootc` has its own (quotes around value). + [VISUAL DIAGRAM: 6 object boxes on the left (one per config file location) with + arrows pointing to 3 state boxes on the right (shared grub state, bootc state, + and kernelopts state). Shows which objects feed which states.] + OBJECTS STATES ═══════ ══════ @@ -211,16 +277,86 @@ (uses_kernelopts) -- additional state used with `state_operator="OR"`: ┌─ state_grub2_{ARG}_argument_is_kernelopts ─┐ │ ^(?:.*\s)?\$kernelopts(?:\s.*)?$ │ - │ Matches `$kernelopts` as a word. │ - │ The RHEL 8 BLS test references BOTH │ - │ `state_argument` and this state with OR: │ - │ entry passes if it has arg=value OR │ - │ contains `$kernelopts`. │ - │ (Same flag as the `grubenv` object -- │ - │ both exist only when `$kernelopts` │ - │ indirection is in play.) │ + │ │ + │ Pattern match: is `$kernelopts` present │ + │ as a standalone word in the captured │ + │ `options` line from │ + │ `/boot/loader/entries/*.conf`? │ + │ │ + │ The RHEL 8 BLS test applies BOTH │ + │ `state_grub2_{ARG}_argument` and this │ + │ state with `state_operator="OR"`: │ + │ an entry passes if its `options` line │ + │ contains `{ARG_NAME_VALUE}` OR contains │ + │ `$kernelopts`. │ + │ │ + │ Same `uses_kernelopts` flag as the │ + │ `{grub2_boot_path}/grubenv` object -- │ + │ both exist only on RHEL 8 / OL8. │ └─────────────────────────────────────────────┘ + LONG DESCRIPTION (text equivalent of the diagram; same information, no box-drawing): + ────────────────────────────────────────────────────────────────────────────────────── + + There are 6 objects (data collectors) and 3 states (pass/fail conditions). + + OBJECTS -- each extracts a line of text from a config file: + + A. `/boot/loader/entries/*.conf` (flag: `uses_boot_loader_entries`) + OVAL element: `` + `` + Pattern: `^options (.*)$` -- captures the kernel arg list after `options `. + Filter: excludes filenames matching `*rescue.conf`. + Tested against: `state_grub2_{ARG}_argument`. + + B. `{grub2_boot_path}/grubenv` (flag: `uses_kernelopts`) + OVAL element: `` + Pattern: `^kernelopts=(.*)$` -- captures the arg list after `kernelopts=`. + Tested against: `state_grub2_{ARG}_argument`. + + C. `{grub2_boot_path}/grub.cfg` (flag: `uses_grub_cfg`) + OVAL element: `` + Pattern: `^.*/vmlinuz.*(root=.*)$` -- captures from `root=` to end of line. + Tested against: `state_grub2_{ARG}_argument`. + + D. `/etc/default/grub` (all products) + OVAL element: `` + Pattern: `^\s*GRUB_CMDLINE_LINUX="(.*)"$` -- captures contents between quotes. + Same pattern repeated for `GRUB_CMDLINE_LINUX_DEFAULT`. + Tested against: `state_grub2_{ARG}_argument`. + + E. `/etc/default/grub.d/*.cfg` (flag: `uses_etc_default_grub_d` / Ubuntu) + OVAL element: `` + Pattern: same as D. + Same pattern repeated for `GRUB_CMDLINE_LINUX_DEFAULT`. + Tested against: `state_grub2_{ARG}_argument`. + + F. `/usr/lib/bootc/kargs.d/*.toml` (flag: `bootable_containers_supported`) + OVAL element: `` + `` + Pattern: `^kargs = \[([^\]]+)\]$` -- captures array contents between brackets. + Tested against: `state_grub2_{ARG}_usr_lib_bootc_kargs_d`. + + STATES -- each applies a regex to the captured text: + + 1. `state_grub2_{ARG}_argument` (shared by objects A through E) + Regex: `^(?:.*\s)?{ARG_NAME_VALUE}(?:\s.*)?$` + Question: is `{ARG_NAME_VALUE}` present as a standalone word (bounded by + whitespace or start/end of string)? + When `ARG_VARIABLE` is set, the regex is assembled at runtime: `oscap` reads + the value from the `XCCDF` profile, a `local_variable` concatenates it into + the regex pattern, and the state references it via `var_ref`. + + 2. `state_grub2_{ARG}_usr_lib_bootc_kargs_d` (used by object F only) + Regex: `^.*"{ARG_NAME_VALUE}".*$` + Question: is `"{ARG_NAME_VALUE}"` present anywhere (with surrounding quotes, + as per `TOML` syntax)? + + 3. `state_grub2_{ARG}_argument_is_kernelopts` (flag: `uses_kernelopts`) + Regex: `^(?:.*\s)?\$kernelopts(?:\s.*)?$` + Question: is `$kernelopts` present as a standalone word? + Used ONLY by the RHEL 8 BLS test, which applies both state 1 and state 3 + with `state_operator="OR"`: an entry passes if it contains `{ARG_NAME_VALUE}` + OR contains `$kernelopts`. + REGEX SEMANTICS (what each `pattern` captures as `subexpression`): ─────────────────────────────────────────────────────────────────