Yury, are you OK with this patch? >From 7f21827cdfe9780b4949b22bcd19efa721b463d2 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa <penguin-kernel@xxxxxxxxxxxxxxxxxxx> Date: Wed, 4 Apr 2018 21:12:10 +0900 Subject: [PATCH] lib/bitmap: Rewrite __bitmap_parselist(). syzbot is catching stalls at __bitmap_parselist() [1]. The trigger is unsigned long v = 0; bitmap_parselist("7:,", &v, BITS_PER_LONG); which results in hitting infinite loop at while (a <= b) { off = min(b - a + 1, used_size); bitmap_set(maskp, a, off); a += group_size; } due to used_size == group_size == 0. Current code is difficult to read due to too many flag variables. Let's rewrite it. My understanding of "range:used_size/group_size" is "start[-end[:used_size/group_size]]" format. Please check whether my understanding is correct... [1] https://syzkaller.appspot.com/bug?id=ad7e0351fbc90535558514a71cd3edc11681997a Signed-off-by: Tetsuo Handa <penguin-kernel@xxxxxxxxxxxxxxxxxxx> Reported-by: syzbot <syzbot+6887cbb011c8054e8a3d@xxxxxxxxxxxxxxxxxxxxxxxxx> Fixes: 0a5ce0831d04382a ("lib/bitmap.c: make bitmap_parselist() thread-safe and much faster") Cc: Yury Norov <ynorov@xxxxxxxxxxxxxxxxxx> Cc: Noam Camus <noamca@xxxxxxxxxxxx> Cc: Rasmus Villemoes <linux@xxxxxxxxxxxxxxxxxx> Cc: Matthew Wilcox <mawilcox@xxxxxxxxxxxxx> Cc: Mauro Carvalho Chehab <mchehab@xxxxxxxxxx> Cc: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx> --- lib/bitmap.c | 183 +++++++++++++++++++++++++---------------------------------- 1 file changed, 78 insertions(+), 105 deletions(-) diff --git a/lib/bitmap.c b/lib/bitmap.c index 9e498c7..9cef440 100644 --- a/lib/bitmap.c +++ b/lib/bitmap.c @@ -485,6 +485,58 @@ int bitmap_print_to_pagebuf(bool list, char *buf, const unsigned long *maskp, } EXPORT_SYMBOL(bitmap_print_to_pagebuf); +static bool get_uint(const char **buf, unsigned int *res) +{ + const char *p = *buf; + + if (!isdigit(*p)) + return false; + *res = simple_strtoul(p, (char **) buf, 10); + return p < *buf; +} + +static int __bitmap_parse_one_chunk(const char *buf, unsigned long *maskp, + const unsigned int nmaskbits) +{ + unsigned int start; + unsigned int end; + unsigned int group_size; + unsigned int used_size; + + while (*buf && isspace(*buf)) + buf++; + if (!get_uint(&buf, &start)) + return -EINVAL; + if (*buf == '-') { + buf++; + if (!get_uint(&buf, &end) || start > end) + return -EINVAL; + if (*buf == ':') { + buf++; + if (!get_uint(&buf, &used_size) || *buf++ != '/' || + !get_uint(&buf, &group_size) || + used_size > group_size) + return -EINVAL; + } else { + group_size = used_size = end - start + 1; + } + } else { + end = start; + group_size = used_size = 1; + } + if (end >= nmaskbits) + return -ERANGE; + while (start <= end) { + const unsigned int bits = min(end - start + 1, used_size); + + bitmap_set(maskp, start, bits); + start += group_size; + } + while (*buf && isspace(*buf)) + buf++; + return *buf ? -EINVAL : 0; +} + /** * __bitmap_parselist - convert list format ASCII string to bitmap * @buf: read nul-terminated user string from this buffer @@ -511,113 +563,34 @@ int bitmap_print_to_pagebuf(bool list, char *buf, const unsigned long *maskp, * - ``-ERANGE``: bit number specified too large for mask */ static int __bitmap_parselist(const char *buf, unsigned int buflen, - int is_user, unsigned long *maskp, - int nmaskbits) + const int is_user, unsigned long *maskp, + const int nmaskbits) { - unsigned int a, b, old_a, old_b; - unsigned int group_size, used_size, off; - int c, old_c, totaldigits, ndigits; - const char __user __force *ubuf = (const char __user __force *)buf; - int at_start, in_range, in_partial_range; - - totaldigits = c = 0; - old_a = old_b = 0; - group_size = used_size = 0; + int err = 0; bitmap_zero(maskp, nmaskbits); - do { - at_start = 1; - in_range = 0; - in_partial_range = 0; - a = b = 0; - ndigits = totaldigits; - - /* Get the next cpu# or a range of cpu#'s */ - while (buflen) { - old_c = c; - if (is_user) { - if (__get_user(c, ubuf++)) - return -EFAULT; - } else - c = *buf++; - buflen--; - if (isspace(c)) - continue; - - /* A '\0' or a ',' signal the end of a cpu# or range */ - if (c == '\0' || c == ',') - break; - /* - * whitespaces between digits are not allowed, - * but it's ok if whitespaces are on head or tail. - * when old_c is whilespace, - * if totaldigits == ndigits, whitespace is on head. - * if whitespace is on tail, it should not run here. - * as c was ',' or '\0', - * the last code line has broken the current loop. - */ - if ((totaldigits != ndigits) && isspace(old_c)) - return -EINVAL; - - if (c == '/') { - used_size = a; - at_start = 1; - in_range = 0; - a = b = 0; - continue; - } - - if (c == ':') { - old_a = a; - old_b = b; - at_start = 1; - in_range = 0; - in_partial_range = 1; - a = b = 0; - continue; - } - - if (c == '-') { - if (at_start || in_range) - return -EINVAL; - b = 0; - in_range = 1; - at_start = 1; - continue; - } - - if (!isdigit(c)) - return -EINVAL; - - b = b * 10 + (c - '0'); - if (!in_range) - a = b; - at_start = 0; - totaldigits++; - } - if (ndigits == totaldigits) - continue; - if (in_partial_range) { - group_size = a; - a = old_a; - b = old_b; - old_a = old_b = 0; - } else { - used_size = group_size = b - a + 1; - } - /* if no digit is after '-', it's wrong*/ - if (at_start && in_range) - return -EINVAL; - if (!(a <= b) || !(used_size <= group_size)) - return -EINVAL; - if (b >= nmaskbits) - return -ERANGE; - while (a <= b) { - off = min(b - a + 1, used_size); - bitmap_set(maskp, a, off); - a += group_size; - } - } while (buflen && c == ','); - return 0; + while (buflen && !err) { + char *cp; + char tmpbuf[256]; + unsigned int size = min(buflen, + (unsigned int) sizeof(tmpbuf) - 1); + + if (!is_user) + memcpy(tmpbuf, buf, size); + else if (copy_from_user(tmpbuf, (const char __user __force *) + buf, size)) + return -EFAULT; + tmpbuf[size] = '\0'; + cp = strchr(tmpbuf, ','); + if (cp) { + *cp = '\0'; + size = cp - tmpbuf + 1; + } else if (size != buflen) + return -EINVAL; /* Chunk too long. */ + buflen -= size; + buf += size; + err = __bitmap_parse_one_chunk(tmpbuf, maskp, nmaskbits); + } + return err; } int bitmap_parselist(const char *bp, unsigned long *maskp, int nmaskbits) -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe cgroups" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html