aboutsummaryrefslogtreecommitdiffstats
path: root/lib/vsprintf.c
diff options
context:
space:
mode:
authorRasmus Villemoes <linux@rasmusvillemoes.dk>2015-11-06 19:30:20 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2015-11-06 20:50:42 -0500
commitb006f19b055f90b73e97086490f95b83095dcc91 (patch)
treeea21a1b1720b1ba8269f0914048785a47792c253 /lib/vsprintf.c
parent5e4ee7b13b522d07196e737f399843c58569604d (diff)
lib/vsprintf.c: handle invalid format specifiers more robustly
If we meet any invalid or unsupported format specifier, 'handling' it by just printing it as a literal string is not safe: Presumably the format string and the arguments passed gcc's type checking, but that means something like sprintf(buf, "%n %pd", &intvar, dentry) would end up interpreting &intvar as a struct dentry*. When the offending specifier was %n it used to be at the end of the format string, but we can't rely on that always being the case. Also, gcc doesn't complain about some more or less exotic qualifiers (or 'length modifiers' in posix-speak) such as 'j' or 'q', but being unrecognized by the kernel's printf implementation, they'd be interpreted as unknown specifiers, and the rest of arguments would be interpreted wrongly. So let's complain about anything we don't understand, not just %n, and stop pretending that we'd be able to make sense of the rest of the format/arguments. If the offending specifier is in a printk() call we unfortunately only get a "BUG: recent printk recursion!", but at least direct users of the sprintf family will be caught. Signed-off-by: Rasmus Villemoes <linux@rasmusvillemoes.dk> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Acked-by: Kees Cook <keescook@chromium.org> Cc: Martin Kletzander <mkletzan@redhat.com> Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'lib/vsprintf.c')
-rw-r--r--lib/vsprintf.c31
1 files changed, 21 insertions, 10 deletions
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index e966a45e2f00..e35724c2b2a8 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -1772,14 +1772,14 @@ qualifier:
1772 1772
1773 case 'n': 1773 case 'n':
1774 /* 1774 /*
1775 * Since %n poses a greater security risk than utility, treat 1775 * Since %n poses a greater security risk than
1776 * it as an invalid format specifier. Warn about its use so 1776 * utility, treat it as any other invalid or
1777 * that new instances don't get added. 1777 * unsupported format specifier.
1778 */ 1778 */
1779 WARN_ONCE(1, "Please remove ignored %%n in '%s'\n", fmt);
1780 /* Fall-through */ 1779 /* Fall-through */
1781 1780
1782 default: 1781 default:
1782 WARN_ONCE(1, "Please remove unsupported %%%c in format string\n", *fmt);
1783 spec->type = FORMAT_TYPE_INVALID; 1783 spec->type = FORMAT_TYPE_INVALID;
1784 return fmt - start; 1784 return fmt - start;
1785 } 1785 }
@@ -1920,10 +1920,15 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
1920 break; 1920 break;
1921 1921
1922 case FORMAT_TYPE_INVALID: 1922 case FORMAT_TYPE_INVALID:
1923 if (str < end) 1923 /*
1924 *str = '%'; 1924 * Presumably the arguments passed gcc's type
1925 ++str; 1925 * checking, but there is no safe or sane way
1926 break; 1926 * for us to continue parsing the format and
1927 * fetching from the va_list; the remaining
1928 * specifiers and arguments would be out of
1929 * sync.
1930 */
1931 goto out;
1927 1932
1928 default: 1933 default:
1929 switch (spec.type) { 1934 switch (spec.type) {
@@ -1968,6 +1973,7 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
1968 } 1973 }
1969 } 1974 }
1970 1975
1976out:
1971 if (size > 0) { 1977 if (size > 0) {
1972 if (str < end) 1978 if (str < end)
1973 *str = '\0'; 1979 *str = '\0';
@@ -2165,9 +2171,10 @@ do { \
2165 2171
2166 switch (spec.type) { 2172 switch (spec.type) {
2167 case FORMAT_TYPE_NONE: 2173 case FORMAT_TYPE_NONE:
2168 case FORMAT_TYPE_INVALID:
2169 case FORMAT_TYPE_PERCENT_CHAR: 2174 case FORMAT_TYPE_PERCENT_CHAR:
2170 break; 2175 break;
2176 case FORMAT_TYPE_INVALID:
2177 goto out;
2171 2178
2172 case FORMAT_TYPE_WIDTH: 2179 case FORMAT_TYPE_WIDTH:
2173 case FORMAT_TYPE_PRECISION: 2180 case FORMAT_TYPE_PRECISION:
@@ -2229,6 +2236,7 @@ do { \
2229 } 2236 }
2230 } 2237 }
2231 2238
2239out:
2232 return (u32 *)(PTR_ALIGN(str, sizeof(u32))) - bin_buf; 2240 return (u32 *)(PTR_ALIGN(str, sizeof(u32))) - bin_buf;
2233#undef save_arg 2241#undef save_arg
2234} 2242}
@@ -2351,12 +2359,14 @@ int bstr_printf(char *buf, size_t size, const char *fmt, const u32 *bin_buf)
2351 break; 2359 break;
2352 2360
2353 case FORMAT_TYPE_PERCENT_CHAR: 2361 case FORMAT_TYPE_PERCENT_CHAR:
2354 case FORMAT_TYPE_INVALID:
2355 if (str < end) 2362 if (str < end)
2356 *str = '%'; 2363 *str = '%';
2357 ++str; 2364 ++str;
2358 break; 2365 break;
2359 2366
2367 case FORMAT_TYPE_INVALID:
2368 goto out;
2369
2360 default: { 2370 default: {
2361 unsigned long long num; 2371 unsigned long long num;
2362 2372
@@ -2399,6 +2409,7 @@ int bstr_printf(char *buf, size_t size, const char *fmt, const u32 *bin_buf)
2399 } /* switch(spec.type) */ 2409 } /* switch(spec.type) */
2400 } /* while(*fmt) */ 2410 } /* while(*fmt) */
2401 2411
2412out:
2402 if (size > 0) { 2413 if (size > 0) {
2403 if (str < end) 2414 if (str < end)
2404 *str = '\0'; 2415 *str = '\0';