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.