[PATCH 10/13] scripts/ghes_inject: add support for fuzzy logic testing

Mauro Carvalho Chehab posted 13 patches 2 weeks, 4 days ago
Maintainers: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>, John Snow <jsnow@redhat.com>, Cleber Rosa <crosa@redhat.com>
There is a newer version of this series
[PATCH 10/13] scripts/ghes_inject: add support for fuzzy logic testing
Posted by Mauro Carvalho Chehab 2 weeks, 4 days ago
Add a command to inject random errors for fuzzy logic testing.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 MAINTAINERS            |   1 +
 scripts/fuzzy_error.py | 206 +++++++++++++++++++++++++++++++++++++++++
 scripts/ghes_inject.py |   2 +
 3 files changed, 209 insertions(+)
 create mode 100644 scripts/fuzzy_error.py

diff --git a/MAINTAINERS b/MAINTAINERS
index 48067a618523..e553f8252f14 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2228,6 +2228,7 @@ F: qapi/acpi-hest.json
 F: scripts/ghes_decode.py
 F: scripts/ghes_inject.py
 F: scripts/arm_processor_error.py
+F: scripts/fuzzy_error.py
 F: scripts/pcie_bus_error.py
 F: scripts/qmp_helper.py
 
diff --git a/scripts/fuzzy_error.py b/scripts/fuzzy_error.py
new file mode 100644
index 000000000000..9f80abb72319
--- /dev/null
+++ b/scripts/fuzzy_error.py
@@ -0,0 +1,206 @@
+#!/usr/bin/env python3
+#
+# pylint: disable=C0114,R0903,R0912
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2024 Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
+
+import argparse
+import sys
+
+from time import sleep
+from random import randrange
+from qmp_helper import qmp, util, cper_guid
+
+class FuzzyError:
+    """
+    Implements Fuzzy error injection via GHES
+    """
+
+    def __init__(self, subparsers):
+        """Initialize the error injection class and add subparser"""
+
+        # as defined at UEFI spec v2.10, section N.2.2
+        # Sizes here are just hints to have some default
+        self.types = {
+            "proc-generic": {
+                "guid": cper_guid.CPER_PROC_GENERIC,
+                "default_size": 192
+            },
+            "proc-x86": {
+                "guid": cper_guid.CPER_PROC_X86,
+                "default_size": 64
+            },
+            "proc-itanium": {
+                "guid": cper_guid.CPER_PROC_ITANIUM,
+                "default_size": 64
+            },
+            "proc-arm": {
+                "guid": cper_guid.CPER_PROC_ARM,
+                "default_size": 72
+            },
+            "platform-mem": {
+                "guid": cper_guid.CPER_PLATFORM_MEM,
+                "default_size": 80
+            },
+            "platform-mem2": {
+                "guid": cper_guid.CPER_PLATFORM_MEM2,
+                "default_size": 96
+            },
+            "pcie": {
+                "guid": cper_guid.CPER_PCIE,
+                "default_size": 208
+            },
+            "pci-bus": {
+                "guid": cper_guid.CPER_PCI_BUS,
+                "default_size": 72
+            },
+            "pci-dev": {
+                "guid": cper_guid.CPER_PCI_DEV,
+                "default_size": 56
+            },
+            "firmware-error": {
+                "guid": cper_guid.CPER_FW_ERROR,
+                "default_size": 32
+            },
+            "dma-generic": {
+                "guid": cper_guid.CPER_DMA_GENERIC,
+                "default_size": 32
+            },
+            "dma-vt": {
+                "guid": cper_guid.CPER_DMA_VT,
+                "default_size": 144
+            },
+            "dma-iommu": {
+                "guid": cper_guid.CPER_DMA_IOMMU,
+                "default_size": 144
+            },
+            "ccix-per": {
+                "guid": cper_guid.CPER_CCIX_PER,
+                "default_size": 36
+            },
+            "cxl-prot-err": {
+                "guid": cper_guid.CPER_CXL_PROT_ERR,
+                "default_size": 116
+            },
+            "cxl-evt-media": {
+                "guid": cper_guid.CPER_CXL_EVT_GEN_MEDIA,
+                "default_size": 32
+            },
+            "cxl-evt-dram": {
+                "guid": cper_guid.CPER_CXL_EVT_DRAM,
+                "default_size": 64
+            },
+            "cxl-evt-mem-module": {
+                "guid": cper_guid.CPER_CXL_EVT_MEM_MODULE,
+                "default_size": 64
+            },
+            "cxl-evt-mem-sparing": {
+                "guid": cper_guid.CPER_CXL_EVT_MEM_SPARING,
+                "default_size": 64
+            },
+            "cxl-evt-phy-sw": {
+                "guid": cper_guid.CPER_CXL_EVT_PHY_SW,
+                "default_size": 64
+            },
+            "cxl-evt-virt-sw": {
+                "guid": cper_guid.CPER_CXL_EVT_VIRT_SW,
+                "default_size": 64
+            },
+            "cxl-evt-mdl-port": {
+                "guid": cper_guid.CPER_CXL_EVT_MLD_PORT,
+                "default_size": 64
+            },
+            "cxl-evt-dyna-cap": {
+                "guid": cper_guid.CPER_CXL_EVT_DYNA_CAP,
+                "default_size": 64
+            },
+            "fru-mem-poison": {
+                "guid": cper_guid.CPER_FRU_MEM_POISON,
+                "default_size": 72
+            },
+        }
+
+        parser = subparsers.add_parser("fuzzy-test", aliases=['fuzzy'],
+                                       description="Inject a fuzzy test CPER",
+                                       formatter_class=argparse.RawTextHelpFormatter)
+        g_fuzzy = parser.add_argument_group("Fuzz testing error inject")
+
+
+        cper_types = ",".join(self.types.keys())
+
+        g_fuzzy.add_argument("-T", "--type",
+                            help=f"Type of the error: {cper_types}")
+        g_fuzzy.add_argument("--min-size",
+                    type=lambda x: int(x, 0),
+                    help="Minimal size of the CPER")
+        g_fuzzy.add_argument("--max-size",
+                    type=lambda x: int(x, 0),
+                    help="Maximal size of the CPER")
+        g_fuzzy.add_argument("-z", "--zero", action="store_true",
+                            help="Zero all bytes of the CPER payload (default: %(default)s)")
+        g_fuzzy.add_argument("-t", "--timeout", type=float,
+                    default=30.0,
+                    help="Specify timeout for CPER send retries (default: %(default)s seconds)")
+        g_fuzzy.add_argument("-d", "--delay", type=float,
+                    default=0,
+                    help="Specify a delay between multiple CPER (default: %(default)s)")
+        g_fuzzy.add_argument("-c", "--count", type=int,
+                    default=1,
+                    help="Specify the number of CPER records to be sent (default: %(default)s)")
+
+        parser.set_defaults(func=self.send_cper)
+
+    def send_cper(self, args):
+        """Parse subcommand arguments and send a CPER via QMP"""
+
+        qmp_cmd = qmp(args.host, args.port, args.debug)
+
+        args.count = max(args.count, 1)
+
+        for i in range(0, args.count):
+            if i:
+                if args.delay > 0:
+                    sleep(args.delay)
+
+            # Handle global parameters
+            if args.type:
+                if not args.type in self.types:
+                    sys.exit(f"Invalid type: {args.type}")
+
+                inj_type = args.type
+            else:
+                i = randrange(len(self.types))
+                keys = list(self.types.keys())
+                inj_type = keys[i]
+
+            inject = self.types[inj_type]
+
+            guid = inject["guid"]
+            min_size = inject["default_size"]
+            max_size = min_size
+
+            if args.min_size:
+                min_size = args.min_size
+
+            if args.max_size:
+                max_size = args.max_size
+
+            size = min_size
+
+            if min_size < max_size:
+                size += randrange(max_size - min_size)
+
+            data = bytearray()
+
+            if not args.zero:
+                for i in range(size):
+                    util.data_add(data, randrange(256), 1)
+            else:
+                for i in range(size):
+                    util.data_add(data, 0, 1)
+
+            print(f"Injecting {inj_type} with {size} bytes")
+            ret = qmp_cmd.send_cper(guid, data, timeout=args.timeout)
+            if ret and ret != "OK":
+                return ret
diff --git a/scripts/ghes_inject.py b/scripts/ghes_inject.py
index 29a6a57508cd..9b0a2443fc97 100755
--- a/scripts/ghes_inject.py
+++ b/scripts/ghes_inject.py
@@ -13,6 +13,7 @@
 
 from arm_processor_error import ArmProcessorEinj
 from pcie_bus_error import PcieBusError
+from fuzzy_error import FuzzyError
 
 EINJ_DESC = """
 Handle ACPI GHESv2 error injection logic QEMU QMP interface.
@@ -42,6 +43,7 @@ def main():
 
     ArmProcessorEinj(subparsers)
     PcieBusError(subparsers)
+    FuzzyError(subparsers)
 
     args = parser.parse_args()
     if "func" in args:
-- 
2.52.0
Re: [PATCH 10/13] scripts/ghes_inject: add support for fuzzy logic testing
Posted by Jonathan Cameron via qemu development 2 weeks, 4 days ago
On Wed, 21 Jan 2026 12:25:18 +0100
Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:

> Add a command to inject random errors for fuzzy logic testing.
> 
> Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
Seems reasonable, but maybe some more text in the description on
what types of fuzzy records it generates?  I.e. what is constrained
or at least starts as being standard values vs what is entirely random

J
Re: [PATCH 10/13] scripts/ghes_inject: add support for fuzzy logic testing
Posted by Mauro Carvalho Chehab 2 weeks, 4 days ago
On Wed, Jan 21, 2026 at 01:37:10PM +0000, Jonathan Cameron wrote:
> On Wed, 21 Jan 2026 12:25:18 +0100
> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> 
> > Add a command to inject random errors for fuzzy logic testing.
> > 
> > Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
> Seems reasonable, but maybe some more text in the description on
> what types of fuzzy records it generates?  I.e. what is constrained
> or at least starts as being standard values vs what is entirely random

I'll improve at the next version. By default, it just randomly picks
a valid GUID from the lis, and selects a default size that would be
a valid choice.

There is a parameter to force it to use an specific type:

    $ ghes_inject.py fuzzy -h

    Inject fuzzy test CPER packets

    options:
      -h, --help            show this help message and exit

    Fuzz testing error inject:
      -T, --type TYPE       Type of the error: proc-generic,proc-x86,proc-itanium,proc-arm,platform-mem,platform-mem2,pcie,pci-bus,pci-dev,firmware-error,dma-generic,dma-vt,dma-iommu,ccix-per,cxl-prot-err,cxl-evt-media,cxl-evt-dram,cxl-evt-mem-module,cxl-evt-mem-sparing,cxl-evt-phy-sw,cxl-evt-virt-sw,cxl-evt-mdl-port,cxl-evt-dyna-cap,fru-mem-poison
      --min-size MIN_SIZE   Minimal size of the CPER
      --max-size MAX_SIZE   Maximal size of the CPER
      -z, --zero            Zero all bytes of the CPER payload (default: False)
      -t, --timeout TIMEOUT
                            Specify timeout for CPER send retries (default: 30.0 seconds)
      -d, --delay DELAY     Specify a delay between multiple CPER (default: 0)
      -c, --count COUNT     Specify the number of CPER records to be sent (default: 1)

and parameters to allow it to mangle with the payload size.

When -T is not used, it randomly pics a valid GUID. When it is
used, all injected packages will have the same type.

Right now, the fuzzy-testing is mangling just with the CPER
payload, so GUIDs are valid. see:


  $ ghes_inject.py -d fuzzy
  Injecting cxl-evt-dyna-cap with 64 bytes
  GUID: ca95afa7-f183-4018-8c2f-95268e101a2a
  Generic Error Status Block (20 bytes):
      00000000  01 00 00 00 00 00 00 00 00 00 00 00 88 00 00 00   ................
      00000010  00 00 00 00                                       ....

  Generic Error Data Entry (72 bytes):
      00000000  a7 af 95 ca 83 f1 18 40 8c 2f 95 26 8e 10 1a 2a   .......@./.&...*
      00000010  00 00 00 00 00 03 00 00 40 00 00 00 00 00 00 00   ........@.......
      00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
      00000030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
      00000040  00 00 00 00 00 00 00 00                           ........

  Payload (64 bytes):
      00000000  32 ba ed 9c 1f ea ac cd 8c 8f 44 7b ab 4b c1 8f   2.........D{.K..
      00000010  68 32 8a c1 07 dd 0f 93 54 de 09 a8 42 79 80 1f   h2......T...By..
      00000020  f4 e8 0c 85 02 2d 0b 7d f5 64 32 8e 3b d6 f1 6b   .....-.}.d2.;..k
      00000030  73 39 97 00 54 30 aa e6 39 f0 5d 95 1c b1 cd 0f   s9..T0..9.].....

The first two tables (GESB and GEDE) aren't randomized, and the GUID is always
a valid one. Jus the payload contains either random numbers (default) or are 
always zero:

  $ ghes_inject.py -d fuzzy -z
  Injecting cxl-evt-media with 32 bytes
  GUID: fbcd0a77-c260-417f-85a9-088b1621eba6
  Generic Error Status Block (20 bytes):
      00000000  01 00 00 00 00 00 00 00 00 00 00 00 68 00 00 00   ............h...
      00000010  00 00 00 00                                       ....

  Generic Error Data Entry (72 bytes):
      00000000  77 0a cd fb 60 c2 7f 41 85 a9 08 8b 16 21 eb a6   w...`..A.....!..
      00000010  00 00 00 00 00 03 00 00 20 00 00 00 00 00 00 00   ........ .......
      00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
      00000030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
      00000040  00 00 00 00 00 00 00 00                           ........

  Payload (32 bytes):
      00000000  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
      00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................

-- 
Thanks,
Mauro