Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

lib/string_helpers.c: change semantics of string_escape_mem

The current semantics of string_escape_mem are inadequate for one of its
current users, vsnprintf(). If that is to honour its contract, it must
know how much space would be needed for the entire escaped buffer, and
string_escape_mem provides no way of obtaining that (short of allocating a
large enough buffer (~4 times input string) to let it play with, and
that's definitely a big no-no inside vsnprintf).

So change the semantics for string_escape_mem to be more snprintf-like:
Return the size of the output that would be generated if the destination
buffer was big enough, but of course still only write to the part of dst
it is allowed to, and (contrary to snprintf) don't do '\0'-termination.
It is then up to the caller to detect whether output was truncated and to
append a '\0' if desired. Also, we must output partial escape sequences,
otherwise a call such as snprintf(buf, 3, "%1pE", "\123") would cause
printf to write a \0 to buf[2] but leaving buf[0] and buf[1] with whatever
they previously contained.

This also fixes a bug in the escaped_string() helper function, which used
to unconditionally pass a length of "end-buf" to string_escape_mem();
since the latter doesn't check osz for being insanely large, it would
happily write to dst. For example, kasprintf(GFP_KERNEL, "something and
then %pE", ...); is an easy way to trigger an oops.

In test-string_helpers.c, the -ENOMEM test is replaced with testing for
getting the expected return value even if the buffer is too small. We
also ensure that nothing is written (by relying on a NULL pointer deref)
if the output size is 0 by passing NULL - this has to work for
kasprintf("%pE") to work.

In net/sunrpc/cache.c, I think qword_add still has the same semantics.
Someone should definitely double-check this.

In fs/proc/array.c, I made the minimum possible change, but longer-term it
should stop poking around in seq_file internals.

[andriy.shevchenko@linux.intel.com: simplify qword_add]
[andriy.shevchenko@linux.intel.com: add missed curly braces]
Signed-off-by: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Acked-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Rasmus Villemoes and committed by
Linus Torvalds
41416f23 3aeddc7d

+44 -73
+2 -2
fs/proc/array.c
··· 99 99 buf = m->buf + m->count; 100 100 101 101 /* Ignore error for now */ 102 - string_escape_str(tcomm, &buf, m->size - m->count, 103 - ESCAPE_SPACE | ESCAPE_SPECIAL, "\n\\"); 102 + buf += string_escape_str(tcomm, buf, m->size - m->count, 103 + ESCAPE_SPACE | ESCAPE_SPECIAL, "\n\\"); 104 104 105 105 m->count = buf - m->buf; 106 106 seq_putc(m, '\n');
+4 -4
include/linux/string_helpers.h
··· 47 47 #define ESCAPE_ANY_NP (ESCAPE_ANY | ESCAPE_NP) 48 48 #define ESCAPE_HEX 0x20 49 49 50 - int string_escape_mem(const char *src, size_t isz, char **dst, size_t osz, 50 + int string_escape_mem(const char *src, size_t isz, char *dst, size_t osz, 51 51 unsigned int flags, const char *esc); 52 52 53 53 static inline int string_escape_mem_any_np(const char *src, size_t isz, 54 - char **dst, size_t osz, const char *esc) 54 + char *dst, size_t osz, const char *esc) 55 55 { 56 56 return string_escape_mem(src, isz, dst, osz, ESCAPE_ANY_NP, esc); 57 57 } 58 58 59 - static inline int string_escape_str(const char *src, char **dst, size_t sz, 59 + static inline int string_escape_str(const char *src, char *dst, size_t sz, 60 60 unsigned int flags, const char *esc) 61 61 { 62 62 return string_escape_mem(src, strlen(src), dst, sz, flags, esc); 63 63 } 64 64 65 - static inline int string_escape_str_any_np(const char *src, char **dst, 65 + static inline int string_escape_str_any_np(const char *src, char *dst, 66 66 size_t sz, const char *esc) 67 67 { 68 68 return string_escape_str(src, dst, sz, ESCAPE_ANY_NP, esc);
+7 -42
lib/string_helpers.c
··· 274 274 return false; 275 275 } 276 276 277 - if (out + 2 > end) { 278 - *dst = out + 2; 279 - return true; 280 - } 281 - 282 277 if (out < end) 283 278 *out = '\\'; 284 279 ++out; ··· 304 309 return false; 305 310 } 306 311 307 - if (out + 2 > end) { 308 - *dst = out + 2; 309 - return true; 310 - } 311 - 312 312 if (out < end) 313 313 *out = '\\'; 314 314 ++out; ··· 322 332 if (c) 323 333 return false; 324 334 325 - if (out + 2 > end) { 326 - *dst = out + 2; 327 - return true; 328 - } 329 - 330 335 if (out < end) 331 336 *out = '\\'; 332 337 ++out; ··· 336 351 static bool escape_octal(unsigned char c, char **dst, char *end) 337 352 { 338 353 char *out = *dst; 339 - 340 - if (out + 4 > end) { 341 - *dst = out + 4; 342 - return true; 343 - } 344 354 345 355 if (out < end) 346 356 *out = '\\'; ··· 357 377 static bool escape_hex(unsigned char c, char **dst, char *end) 358 378 { 359 379 char *out = *dst; 360 - 361 - if (out + 4 > end) { 362 - *dst = out + 4; 363 - return true; 364 - } 365 380 366 381 if (out < end) 367 382 *out = '\\'; ··· 424 449 * it if needs. 425 450 * 426 451 * Return: 427 - * The amount of the characters processed to the destination buffer, or 428 - * %-ENOMEM if the size of buffer is not enough to put an escaped character is 429 - * returned. 430 - * 431 - * Even in the case of error @dst pointer will be updated to point to the byte 432 - * after the last processed character. 452 + * The total size of the escaped output that would be generated for 453 + * the given input and flags. To check whether the output was 454 + * truncated, compare the return value to osz. There is room left in 455 + * dst for a '\0' terminator if and only if ret < osz. 433 456 */ 434 - int string_escape_mem(const char *src, size_t isz, char **dst, size_t osz, 457 + int string_escape_mem(const char *src, size_t isz, char *dst, size_t osz, 435 458 unsigned int flags, const char *esc) 436 459 { 437 - char *p = *dst; 460 + char *p = dst; 438 461 char *end = p + osz; 439 462 bool is_dict = esc && *esc; 440 - int ret; 441 463 442 464 while (isz--) { 443 465 unsigned char c = *src++; ··· 474 502 escape_passthrough(c, &p, end); 475 503 } 476 504 477 - if (p > end) { 478 - *dst = end; 479 - return -ENOMEM; 480 - } 481 - 482 - ret = p - *dst; 483 - *dst = p; 484 - return ret; 505 + return p - dst; 485 506 } 486 507 EXPORT_SYMBOL(string_escape_mem);
+20 -20
lib/test-string_helpers.c
··· 260 260 return NULL; 261 261 } 262 262 263 + static __init void 264 + test_string_escape_overflow(const char *in, int p, unsigned int flags, const char *esc, 265 + int q_test, const char *name) 266 + { 267 + int q_real; 268 + 269 + q_real = string_escape_mem(in, p, NULL, 0, flags, esc); 270 + if (q_real != q_test) 271 + pr_warn("Test '%s' failed: flags = %u, osz = 0, expected %d, got %d\n", 272 + name, flags, q_test, q_real); 273 + } 274 + 263 275 static __init void test_string_escape(const char *name, 264 276 const struct test_string_2 *s2, 265 277 unsigned int flags, const char *esc) 266 278 { 267 - int q_real = 512; 268 - char *out_test = kmalloc(q_real, GFP_KERNEL); 269 - char *out_real = kmalloc(q_real, GFP_KERNEL); 279 + size_t out_size = 512; 280 + char *out_test = kmalloc(out_size, GFP_KERNEL); 281 + char *out_real = kmalloc(out_size, GFP_KERNEL); 270 282 char *in = kmalloc(256, GFP_KERNEL); 271 - char *buf = out_real; 272 283 int p = 0, q_test = 0; 284 + int q_real; 273 285 274 286 if (!out_test || !out_real || !in) 275 287 goto out; ··· 313 301 q_test += len; 314 302 } 315 303 316 - q_real = string_escape_mem(in, p, &buf, q_real, flags, esc); 304 + q_real = string_escape_mem(in, p, out_real, out_size, flags, esc); 317 305 318 306 test_string_check_buf(name, flags, in, p, out_real, q_real, out_test, 319 307 q_test); 308 + 309 + test_string_escape_overflow(in, p, flags, esc, q_test, name); 310 + 320 311 out: 321 312 kfree(in); 322 313 kfree(out_real); 323 314 kfree(out_test); 324 - } 325 - 326 - static __init void test_string_escape_nomem(void) 327 - { 328 - char *in = "\eb \\C\007\"\x90\r]"; 329 - char out[64], *buf = out; 330 - int rc = -ENOMEM, ret; 331 - 332 - ret = string_escape_str_any_np(in, &buf, strlen(in), NULL); 333 - if (ret == rc) 334 - return; 335 - 336 - pr_err("Test 'escape nomem' failed: got %d instead of %d\n", ret, rc); 337 315 } 338 316 339 317 static int __init test_string_helpers_init(void) ··· 343 341 /* With dictionary */ 344 342 for (i = 0; i < (ESCAPE_ANY_NP | ESCAPE_HEX) + 1; i++) 345 343 test_string_escape("escape 1", escape1, i, TEST_STRING_2_DICT_1); 346 - 347 - test_string_escape_nomem(); 348 344 349 345 return -EINVAL; 350 346 }
+6 -2
lib/vsprintf.c
··· 1235 1235 1236 1236 len = spec.field_width < 0 ? 1 : spec.field_width; 1237 1237 1238 - /* Ignore the error. We print as many characters as we can */ 1239 - string_escape_mem(addr, len, &buf, end - buf, flags, NULL); 1238 + /* 1239 + * string_escape_mem() writes as many characters as it can to 1240 + * the given buffer, and returns the total size of the output 1241 + * had the buffer been big enough. 1242 + */ 1243 + buf += string_escape_mem(addr, len, buf, buf < end ? end - buf : 0, flags, NULL); 1240 1244 1241 1245 return buf; 1242 1246 }
+5 -3
net/sunrpc/cache.c
··· 1072 1072 1073 1073 if (len < 0) return; 1074 1074 1075 - ret = string_escape_str(str, &bp, len, ESCAPE_OCTAL, "\\ \n\t"); 1076 - if (ret < 0 || ret == len) 1075 + ret = string_escape_str(str, bp, len, ESCAPE_OCTAL, "\\ \n\t"); 1076 + if (ret >= len) { 1077 + bp += len; 1077 1078 len = -1; 1078 - else { 1079 + } else { 1080 + bp += ret; 1079 1081 len -= ret; 1080 1082 *bp++ = ' '; 1081 1083 len--;