1*4882a593SmuzhiyunFrom 83603bea6ce8fdff5ab3fbc4c9e592a8c71a8706 Mon Sep 17 00:00:00 2001 2*4882a593SmuzhiyunFrom: Thomas Frauendorfer | Miray Software <tf@miray.de> 3*4882a593SmuzhiyunDate: Thu, 4 Feb 2021 19:02:33 +0100 4*4882a593SmuzhiyunSubject: [PATCH] kern/misc: Add function to check printf() format against 5*4882a593Smuzhiyun expected format 6*4882a593Smuzhiyun 7*4882a593SmuzhiyunThe grub_printf_fmt_check() function parses the arguments of an untrusted 8*4882a593Smuzhiyunprintf() format and an expected printf() format and then compares the 9*4882a593Smuzhiyunarguments counts and arguments types. The arguments count in the untrusted 10*4882a593Smuzhiyunformat string must be less or equal to the arguments count in the expected 11*4882a593Smuzhiyunformat string and both arguments types must match. 12*4882a593Smuzhiyun 13*4882a593SmuzhiyunTo do this the parse_printf_arg_fmt() helper function is extended in the 14*4882a593Smuzhiyunfollowing way: 15*4882a593Smuzhiyun 16*4882a593Smuzhiyun 1. Add a return value to report errors to the grub_printf_fmt_check(). 17*4882a593Smuzhiyun 18*4882a593Smuzhiyun 2. Add the fmt_check argument to enable stricter format verification: 19*4882a593Smuzhiyun - the function expects that arguments definitions are always 20*4882a593Smuzhiyun terminated by a supported conversion specifier. 21*4882a593Smuzhiyun - positional parameters, "$", are not allowed, as they cannot be 22*4882a593Smuzhiyun validated correctly with the current implementation. For example 23*4882a593Smuzhiyun "%s%1$d" would assign the first args entry twice while leaving the 24*4882a593Smuzhiyun second one unchanged. 25*4882a593Smuzhiyun - Return an error if preallocated space in args is too small and 26*4882a593Smuzhiyun allocation fails for the needed size. The grub_printf_fmt_check() 27*4882a593Smuzhiyun should verify all arguments. So, if validation is not possible for 28*4882a593Smuzhiyun any reason it should return an error. 29*4882a593Smuzhiyun This also adds a case entry to handle "%%", which is the escape 30*4882a593Smuzhiyun sequence to print "%" character. 31*4882a593Smuzhiyun 32*4882a593Smuzhiyun 3. Add the max_args argument to check for the maximum allowed arguments 33*4882a593Smuzhiyun count in a printf() string. This should be set to the arguments count 34*4882a593Smuzhiyun of the expected format. Then the parse_printf_arg_fmt() function will 35*4882a593Smuzhiyun return an error if the arguments count is exceeded. 36*4882a593Smuzhiyun 37*4882a593SmuzhiyunThe two additional arguments allow us to use parse_printf_arg_fmt() in 38*4882a593Smuzhiyunprintf() and grub_printf_fmt_check() calls. 39*4882a593Smuzhiyun 40*4882a593SmuzhiyunWhen parse_printf_arg_fmt() is used by grub_printf_fmt_check() the 41*4882a593Smuzhiyunfunction parse user provided untrusted format string too. So, in 42*4882a593Smuzhiyunthat case it is better to be too strict than too lenient. 43*4882a593Smuzhiyun 44*4882a593SmuzhiyunSigned-off-by: Thomas Frauendorfer | Miray Software <tf@miray.de> 45*4882a593SmuzhiyunReviewed-by: Daniel Kiper <daniel.kiper@oracle.com> 46*4882a593SmuzhiyunSigned-off-by: Stefan Sørensen <stefan.sorensen@spectralink.com> 47*4882a593Smuzhiyun--- 48*4882a593Smuzhiyun grub-core/kern/misc.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++--- 49*4882a593Smuzhiyun include/grub/misc.h | 16 ++++++++++ 50*4882a593Smuzhiyun 2 files changed, 94 insertions(+), 4 deletions(-) 51*4882a593Smuzhiyun 52*4882a593Smuzhiyundiff --git a/grub-core/kern/misc.c b/grub-core/kern/misc.c 53*4882a593Smuzhiyunindex 22417f7..90317b6 100644 54*4882a593Smuzhiyun--- a/grub-core/kern/misc.c 55*4882a593Smuzhiyun+++ b/grub-core/kern/misc.c 56*4882a593Smuzhiyun@@ -632,8 +632,26 @@ grub_lltoa (char *str, int c, unsigned long long n) 57*4882a593Smuzhiyun return p; 58*4882a593Smuzhiyun } 59*4882a593Smuzhiyun 60*4882a593Smuzhiyun-static void 61*4882a593Smuzhiyun-parse_printf_arg_fmt (const char *fmt0, struct printf_args *args) 62*4882a593Smuzhiyun+/* 63*4882a593Smuzhiyun+ * Parse printf() fmt0 string into args arguments. 64*4882a593Smuzhiyun+ * 65*4882a593Smuzhiyun+ * The parsed arguments are either used by a printf() function to format the fmt0 66*4882a593Smuzhiyun+ * string or they are used to compare a format string from an untrusted source 67*4882a593Smuzhiyun+ * against a format string with expected arguments. 68*4882a593Smuzhiyun+ * 69*4882a593Smuzhiyun+ * When the fmt_check is set to !0, e.g. 1, then this function is executed in 70*4882a593Smuzhiyun+ * printf() format check mode. This enforces stricter rules for parsing the 71*4882a593Smuzhiyun+ * fmt0 to limit exposure to possible errors in printf() handling. It also 72*4882a593Smuzhiyun+ * disables positional parameters, "$", because some formats, e.g "%s%1$d", 73*4882a593Smuzhiyun+ * cannot be validated with the current implementation. 74*4882a593Smuzhiyun+ * 75*4882a593Smuzhiyun+ * The max_args allows to set a maximum number of accepted arguments. If the fmt0 76*4882a593Smuzhiyun+ * string defines more arguments than the max_args then the parse_printf_arg_fmt() 77*4882a593Smuzhiyun+ * function returns an error. This is currently used for format check only. 78*4882a593Smuzhiyun+ */ 79*4882a593Smuzhiyun+static grub_err_t 80*4882a593Smuzhiyun+parse_printf_arg_fmt (const char *fmt0, struct printf_args *args, 81*4882a593Smuzhiyun+ int fmt_check, grub_size_t max_args) 82*4882a593Smuzhiyun { 83*4882a593Smuzhiyun const char *fmt; 84*4882a593Smuzhiyun char c; 85*4882a593Smuzhiyun@@ -660,7 +678,12 @@ parse_printf_arg_fmt (const char *fmt0, struct printf_args *args) 86*4882a593Smuzhiyun fmt++; 87*4882a593Smuzhiyun 88*4882a593Smuzhiyun if (*fmt == '$') 89*4882a593Smuzhiyun- fmt++; 90*4882a593Smuzhiyun+ { 91*4882a593Smuzhiyun+ if (fmt_check) 92*4882a593Smuzhiyun+ return grub_error (GRUB_ERR_BAD_ARGUMENT, 93*4882a593Smuzhiyun+ "positional arguments are not supported"); 94*4882a593Smuzhiyun+ fmt++; 95*4882a593Smuzhiyun+ } 96*4882a593Smuzhiyun 97*4882a593Smuzhiyun if (*fmt =='-') 98*4882a593Smuzhiyun fmt++; 99*4882a593Smuzhiyun@@ -691,9 +714,19 @@ parse_printf_arg_fmt (const char *fmt0, struct printf_args *args) 100*4882a593Smuzhiyun case 's': 101*4882a593Smuzhiyun args->count++; 102*4882a593Smuzhiyun break; 103*4882a593Smuzhiyun+ case '%': 104*4882a593Smuzhiyun+ /* "%%" is the escape sequence to output "%". */ 105*4882a593Smuzhiyun+ break; 106*4882a593Smuzhiyun+ default: 107*4882a593Smuzhiyun+ if (fmt_check) 108*4882a593Smuzhiyun+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "unexpected format"); 109*4882a593Smuzhiyun+ break; 110*4882a593Smuzhiyun } 111*4882a593Smuzhiyun } 112*4882a593Smuzhiyun 113*4882a593Smuzhiyun+ if (fmt_check && args->count > max_args) 114*4882a593Smuzhiyun+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "too many arguments"); 115*4882a593Smuzhiyun+ 116*4882a593Smuzhiyun if (args->count <= ARRAY_SIZE (args->prealloc)) 117*4882a593Smuzhiyun args->ptr = args->prealloc; 118*4882a593Smuzhiyun else 119*4882a593Smuzhiyun@@ -701,6 +734,9 @@ parse_printf_arg_fmt (const char *fmt0, struct printf_args *args) 120*4882a593Smuzhiyun args->ptr = grub_calloc (args->count, sizeof (args->ptr[0])); 121*4882a593Smuzhiyun if (!args->ptr) 122*4882a593Smuzhiyun { 123*4882a593Smuzhiyun+ if (fmt_check) 124*4882a593Smuzhiyun+ return grub_errno; 125*4882a593Smuzhiyun+ 126*4882a593Smuzhiyun grub_errno = GRUB_ERR_NONE; 127*4882a593Smuzhiyun args->ptr = args->prealloc; 128*4882a593Smuzhiyun args->count = ARRAY_SIZE (args->prealloc); 129*4882a593Smuzhiyun@@ -791,6 +827,8 @@ parse_printf_arg_fmt (const char *fmt0, struct printf_args *args) 130*4882a593Smuzhiyun break; 131*4882a593Smuzhiyun } 132*4882a593Smuzhiyun } 133*4882a593Smuzhiyun+ 134*4882a593Smuzhiyun+ return GRUB_ERR_NONE; 135*4882a593Smuzhiyun } 136*4882a593Smuzhiyun 137*4882a593Smuzhiyun static void 138*4882a593Smuzhiyun@@ -798,7 +836,7 @@ parse_printf_args (const char *fmt0, struct printf_args *args, va_list args_in) 139*4882a593Smuzhiyun { 140*4882a593Smuzhiyun grub_size_t n; 141*4882a593Smuzhiyun 142*4882a593Smuzhiyun- parse_printf_arg_fmt (fmt0, args); 143*4882a593Smuzhiyun+ parse_printf_arg_fmt (fmt0, args, 0, 0); 144*4882a593Smuzhiyun 145*4882a593Smuzhiyun for (n = 0; n < args->count; n++) 146*4882a593Smuzhiyun switch (args->ptr[n].type) 147*4882a593Smuzhiyun@@ -1105,6 +1143,42 @@ grub_xasprintf (const char *fmt, ...) 148*4882a593Smuzhiyun return ret; 149*4882a593Smuzhiyun } 150*4882a593Smuzhiyun 151*4882a593Smuzhiyun+grub_err_t 152*4882a593Smuzhiyun+grub_printf_fmt_check (const char *fmt, const char *fmt_expected) 153*4882a593Smuzhiyun+{ 154*4882a593Smuzhiyun+ struct printf_args args_expected, args_fmt; 155*4882a593Smuzhiyun+ grub_err_t ret; 156*4882a593Smuzhiyun+ grub_size_t n; 157*4882a593Smuzhiyun+ 158*4882a593Smuzhiyun+ if (fmt == NULL || fmt_expected == NULL) 159*4882a593Smuzhiyun+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid format"); 160*4882a593Smuzhiyun+ 161*4882a593Smuzhiyun+ ret = parse_printf_arg_fmt (fmt_expected, &args_expected, 1, GRUB_SIZE_MAX); 162*4882a593Smuzhiyun+ if (ret != GRUB_ERR_NONE) 163*4882a593Smuzhiyun+ return ret; 164*4882a593Smuzhiyun+ 165*4882a593Smuzhiyun+ /* Limit parsing to the number of expected arguments. */ 166*4882a593Smuzhiyun+ ret = parse_printf_arg_fmt (fmt, &args_fmt, 1, args_expected.count); 167*4882a593Smuzhiyun+ if (ret != GRUB_ERR_NONE) 168*4882a593Smuzhiyun+ { 169*4882a593Smuzhiyun+ free_printf_args (&args_expected); 170*4882a593Smuzhiyun+ return ret; 171*4882a593Smuzhiyun+ } 172*4882a593Smuzhiyun+ 173*4882a593Smuzhiyun+ for (n = 0; n < args_fmt.count; n++) 174*4882a593Smuzhiyun+ if (args_fmt.ptr[n].type != args_expected.ptr[n].type) 175*4882a593Smuzhiyun+ { 176*4882a593Smuzhiyun+ ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "arguments types do not match"); 177*4882a593Smuzhiyun+ break; 178*4882a593Smuzhiyun+ } 179*4882a593Smuzhiyun+ 180*4882a593Smuzhiyun+ free_printf_args (&args_expected); 181*4882a593Smuzhiyun+ free_printf_args (&args_fmt); 182*4882a593Smuzhiyun+ 183*4882a593Smuzhiyun+ return ret; 184*4882a593Smuzhiyun+} 185*4882a593Smuzhiyun+ 186*4882a593Smuzhiyun+ 187*4882a593Smuzhiyun /* Abort GRUB. This function does not return. */ 188*4882a593Smuzhiyun static void __attribute__ ((noreturn)) 189*4882a593Smuzhiyun grub_abort (void) 190*4882a593Smuzhiyundiff --git a/include/grub/misc.h b/include/grub/misc.h 191*4882a593Smuzhiyunindex ee48eb7..d1c5709 100644 192*4882a593Smuzhiyun--- a/include/grub/misc.h 193*4882a593Smuzhiyun+++ b/include/grub/misc.h 194*4882a593Smuzhiyun@@ -440,6 +440,22 @@ grub_error_load (const struct grub_error_saved *save) 195*4882a593Smuzhiyun grub_errno = save->grub_errno; 196*4882a593Smuzhiyun } 197*4882a593Smuzhiyun 198*4882a593Smuzhiyun+/* 199*4882a593Smuzhiyun+ * grub_printf_fmt_checks() a fmt string for printf() against an expected 200*4882a593Smuzhiyun+ * format. It is intended for cases where the fmt string could come from 201*4882a593Smuzhiyun+ * an outside source and cannot be trusted. 202*4882a593Smuzhiyun+ * 203*4882a593Smuzhiyun+ * While expected fmt accepts a printf() format string it should be kept 204*4882a593Smuzhiyun+ * as simple as possible. The printf() format strings with positional 205*4882a593Smuzhiyun+ * parameters are NOT accepted, neither for fmt nor for fmt_expected. 206*4882a593Smuzhiyun+ * 207*4882a593Smuzhiyun+ * The fmt is accepted if it has equal or less arguments than fmt_expected 208*4882a593Smuzhiyun+ * and if the type of all arguments match. 209*4882a593Smuzhiyun+ * 210*4882a593Smuzhiyun+ * Returns GRUB_ERR_NONE if fmt is acceptable. 211*4882a593Smuzhiyun+ */ 212*4882a593Smuzhiyun+grub_err_t EXPORT_FUNC (grub_printf_fmt_check) (const char *fmt, const char *fmt_expected); 213*4882a593Smuzhiyun+ 214*4882a593Smuzhiyun #if BOOT_TIME_STATS 215*4882a593Smuzhiyun struct grub_boot_time 216*4882a593Smuzhiyun { 217*4882a593Smuzhiyun-- 218*4882a593Smuzhiyun2.14.2 219*4882a593Smuzhiyun 220