File 0003-mkimage-validate-SBAT-CSV-against-build-time-SBAT-le.patch of Package grub2
From 129be9d64365b4c02deb3668723235a6194f5124 Mon Sep 17 00:00:00 2001
From: Michael Chang <mchang@suse.com>
Date: Mon, 30 Mar 2026 17:54:46 +0800
Subject: [PATCH 3/4] mkimage: validate SBAT CSV against build-time SBAT level
Define GLOBAL_SBAT_LEVEL in configure.ac as a C preprocessor constant so
grub-mkimage can compare the supplied SBAT metadata against the
build-time GRUB SBAT generation.
When an SBAT file is provided, validate it before embedding it into EFI
or powerpc-ieee1275 images. Warn about malformed records, non-ASCII
bytes, empty lines, invalid generation fields, duplicate "sbat" or
"grub" entries, and missing required entries. Also warn when the "grub"
generation does not match GLOBAL_SBAT_LEVEL.
This catches broken or stale SBAT metadata at image creation time
without changing image formats or making the build fail.
Signed-off-by: Michael Chang <mchang@suse.com>
---
configure.ac | 1 +
util/mkimage.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 179 insertions(+)
diff --git a/configure.ac b/configure.ac
index ea414138f..5f137e1f0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -44,6 +44,7 @@ AC_INIT([GRUB2],[2.14],[bug-grub@gnu.org])
# 6: https://lists.gnu.org/archive/html/grub-devel/2025-11/msg00155.html
GLOBAL_SBAT_LEVEL=6
AC_SUBST([GLOBAL_SBAT_LEVEL])
+AC_DEFINE_UNQUOTED([GLOBAL_SBAT_LEVEL], [$GLOBAL_SBAT_LEVEL], [GRUB Global SBAT level])
AC_ARG_WITH([sbat-file],
AS_HELP_STRING([--with-sbat-file=FILE],
diff --git a/util/mkimage.c b/util/mkimage.c
index 2920c0249..3bb5e1be5 100644
--- a/util/mkimage.c
+++ b/util/mkimage.c
@@ -882,6 +882,181 @@ init_pe_section(const struct grub_install_image_target_desc *image_target,
ret_; \
}))
+static char **
+parse_sbat_fields (char *line)
+{
+ char **fields;
+ size_t i;
+ char *field = line;
+
+ fields = xmalloc (6 * sizeof (*fields));
+
+ for (i = 0; i < 6; i++)
+ {
+ char *comma;
+
+ fields[i] = field;
+
+ if (i == 5)
+ break;
+
+ if (*field == '\0')
+ {
+ free (fields);
+ return NULL;
+ }
+
+ comma = grub_strchr (field, ',');
+ if (comma == NULL)
+ {
+ free (fields);
+ return NULL;
+ }
+
+ *comma = '\0';
+ field = comma + 1;
+ }
+
+ /* The last field consumes the remainder of the line, so reject extras. */
+ if (grub_strchr (field, ',') != NULL)
+ {
+ free (fields);
+ return NULL;
+ }
+
+ return fields;
+}
+
+static int
+validate_sbat_line (char *line, const char *sbat_path, unsigned int line_num,
+ char ***fields_out, unsigned long *generation)
+{
+ const unsigned char *ptr;
+ const char *tail;
+ char **fields;
+
+ for (ptr = (const unsigned char *) line; *ptr != '\0'; ptr++)
+ if (*ptr < 0x20 || *ptr > 0x7e)
+ {
+ grub_util_warn (
+ _("invalid non-ASCII byte 0x%02x in SBAT entry %u of `%s'"),
+ (unsigned int) *ptr,
+ line_num,
+ sbat_path);
+ return 0;
+ }
+
+ fields = parse_sbat_fields (line);
+ if (fields == NULL)
+ {
+ grub_util_warn (_("invalid SBAT entry %u in `%s': expected 6 "
+ "comma-separated fields"),
+ line_num,
+ sbat_path);
+ return 0;
+ }
+
+ *generation = grub_strtoul (fields[1], &tail, 10);
+ if (*fields[1] == '\0' || *tail != '\0')
+ {
+ grub_util_warn (_("invalid SBAT generation `%s' in entry %u of `%s'"),
+ fields[1],
+ line_num,
+ sbat_path);
+ free (fields);
+ return 0;
+ }
+
+ *fields_out = fields;
+ return 1;
+}
+
+static void
+check_sbat_file (const char *sbat_path)
+{
+ char *buf;
+ char *ptr;
+ size_t sbat_size;
+ int found_grub = 0;
+ int found_sbat = 0;
+ unsigned int line_num = 0;
+
+ sbat_size = grub_util_get_image_size (sbat_path);
+ buf = xmalloc (sbat_size + 1);
+ grub_util_load_image (sbat_path, buf);
+ buf[sbat_size] = '\0';
+
+ for (ptr = buf; *ptr != '\0'; )
+ {
+ char *line = ptr;
+ char *next = grub_strchr (ptr, '\n');
+ char **fields;
+ unsigned long generation;
+
+ line_num++;
+
+ if (next)
+ {
+ *next = '\0';
+ ptr = next + 1;
+ }
+ else
+ ptr += grub_strlen (ptr);
+
+ /* Accept CRLF line endings by dropping trailing carriage return. */
+ {
+ size_t len = grub_strlen (line);
+
+ if (len > 0 && line[len - 1] == '\r')
+ line[len - 1] = '\0';
+ }
+
+ /* Warn about and skip empty lines. */
+ if (*line == '\0')
+ {
+ grub_util_warn (_("empty line %u in `%s'"), line_num, sbat_path);
+ continue;
+ }
+
+ if (!validate_sbat_line (line, sbat_path, line_num, &fields, &generation))
+ continue;
+
+ if (grub_strcmp (fields[0], "sbat") == 0)
+ {
+ if (found_sbat)
+ grub_util_warn (_ ("duplicate SBAT version entry in `%s' at line %u"),
+ sbat_path,
+ line_num);
+ found_sbat = 1;
+ }
+
+ if (grub_strcmp (fields[0], "grub") == 0)
+ {
+ if (found_grub)
+ grub_util_warn (_ ("duplicate GRUB SBAT entry in `%s' at line %u"),
+ sbat_path,
+ line_num);
+ found_grub = 1;
+ if (generation != (unsigned long) GLOBAL_SBAT_LEVEL)
+ grub_util_warn (_("Global SBAT level %lu in `%s' does not match "
+ "the build-time Global SBAT level %d"),
+ generation,
+ sbat_path,
+ GLOBAL_SBAT_LEVEL);
+ }
+
+ free (fields);
+ }
+
+ if (!found_sbat)
+ grub_util_warn (_("no SBAT version entry found in `%s'"), sbat_path);
+
+ if (!found_grub)
+ grub_util_warn (_("no GRUB SBAT entry found in `%s'"), sbat_path);
+
+ free (buf);
+}
+
void
grub_install_generate_image (const char *dir, const char *prefix,
FILE *out, const char *outname, char *mods[],
@@ -973,6 +1148,9 @@ grub_install_generate_image (const char *dir, const char *prefix,
grub_util_error (_("%s file should contain at least an SBAT header"), sbat_path);
}
+ if (sbat_path != NULL)
+ check_sbat_file (sbat_path);
+
if (appsig_size != 0 && image_target->id != IMAGE_PPC)
grub_util_error (_("appended signature can be support only to powerpc-ieee1275 images"));
--
2.53.0