return p;
}
-static void
-parse_printf_arg_fmt (const char *fmt0, struct printf_args *args)
+/*
+ * Parse printf() fmt0 string into args arguments.
+ *
+ * The parsed arguments are either used by a printf() function to format the fmt0
+ * string or they are used to compare a format string from an untrusted source
+ * against a format string with expected arguments.
+ *
+ * When the fmt_check is set to !0, e.g. 1, then this function is executed in
+ * printf() format check mode. This enforces stricter rules for parsing the
+ * fmt0 to limit exposure to possible errors in printf() handling. It also
+ * disables positional parameters, "$", because some formats, e.g "%s%1$d",
+ * cannot be validated with the current implementation.
+ *
+ * The max_args allows to set a maximum number of accepted arguments. If the fmt0
+ * string defines more arguments than the max_args then the parse_printf_arg_fmt()
+ * function returns an error. This is currently used for format check only.
+ */
+static grub_err_t
+parse_printf_arg_fmt (const char *fmt0, struct printf_args *args,
+ int fmt_check, grub_size_t max_args)
{
const char *fmt;
char c;
fmt++;
if (*fmt == '$')
- fmt++;
+ {
+ if (fmt_check)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "positional arguments are not supported");
+ fmt++;
+ }
if (*fmt =='-')
fmt++;
case 's':
args->count++;
break;
+ case '%':
+ /* "%%" is the escape sequence to output "%". */
+ break;
+ default:
+ if (fmt_check)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "unexpected format");
+ break;
}
}
+ if (fmt_check && args->count > max_args)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "too many arguments");
+
if (args->count <= ARRAY_SIZE (args->prealloc))
args->ptr = args->prealloc;
else
args->ptr = grub_calloc (args->count, sizeof (args->ptr[0]));
if (!args->ptr)
{
+ if (fmt_check)
+ return grub_errno;
+
grub_errno = GRUB_ERR_NONE;
args->ptr = args->prealloc;
args->count = ARRAY_SIZE (args->prealloc);
break;
}
}
+
+ return GRUB_ERR_NONE;
}
static void
{
grub_size_t n;
- parse_printf_arg_fmt (fmt0, args);
+ parse_printf_arg_fmt (fmt0, args, 0, 0);
for (n = 0; n < args->count; n++)
switch (args->ptr[n].type)
return ret;
}
+grub_err_t
+grub_printf_fmt_check (const char *fmt, const char *fmt_expected)
+{
+ struct printf_args args_expected, args_fmt;
+ grub_err_t ret;
+ grub_size_t n;
+
+ if (fmt == NULL || fmt_expected == NULL)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid format");
+
+ ret = parse_printf_arg_fmt (fmt_expected, &args_expected, 1, GRUB_SIZE_MAX);
+ if (ret != GRUB_ERR_NONE)
+ return ret;
+
+ /* Limit parsing to the number of expected arguments. */
+ ret = parse_printf_arg_fmt (fmt, &args_fmt, 1, args_expected.count);
+ if (ret != GRUB_ERR_NONE)
+ {
+ free_printf_args (&args_expected);
+ return ret;
+ }
+
+ for (n = 0; n < args_fmt.count; n++)
+ if (args_fmt.ptr[n].type != args_expected.ptr[n].type)
+ {
+ ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "arguments types do not match");
+ break;
+ }
+
+ free_printf_args (&args_expected);
+ free_printf_args (&args_fmt);
+
+ return ret;
+}
+
+
/* Abort GRUB. This function does not return. */
static void __attribute__ ((noreturn))
grub_abort (void)
grub_errno = save->grub_errno;
}
+/*
+ * grub_printf_fmt_checks() a fmt string for printf() against an expected
+ * format. It is intended for cases where the fmt string could come from
+ * an outside source and cannot be trusted.
+ *
+ * While expected fmt accepts a printf() format string it should be kept
+ * as simple as possible. The printf() format strings with positional
+ * parameters are NOT accepted, neither for fmt nor for fmt_expected.
+ *
+ * The fmt is accepted if it has equal or less arguments than fmt_expected
+ * and if the type of all arguments match.
+ *
+ * Returns GRUB_ERR_NONE if fmt is acceptable.
+ */
+grub_err_t EXPORT_FUNC (grub_printf_fmt_check) (const char *fmt, const char *fmt_expected);
+
#if BOOT_TIME_STATS
struct grub_boot_time
{