argv_split(): teach it to handle mutable strings

argv_split() allocates argv[count_argc(str)] array and assumes that it
will find the same number of arguments later.  This is obviously wrong if
this string can be changed, say, by sysctl.

With this patch argv_split() kstrndup's the whole string and does not
split it, we simply replace the spaces with zeroes and keep the allocated
memory in argv[-1] for argv_free(arg).

We do not use argv[0] because:

	- str can be all-spaces or empty. In fact this case is fine,
	  we could kfree() it before return, but:

	- str can have a space at the start, and we can not rely on
	  kstrndup(skip_spaces(str)) because it can equally race if
	  this string is mutable.

Also, simplify count_argc() and kill the no longer used skip_arg().

Signed-off-by: Oleg Nesterov <oleg@redhat.com>
Cc: Andi Kleen <andi@firstfloor.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Oleg Nesterov 2013-04-29 16:18:10 -07:00 committed by Linus Torvalds
parent 30493cc9dd
commit 095d141b2e

View File

@ -8,23 +8,17 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/export.h> #include <linux/export.h>
static const char *skip_arg(const char *cp)
{
while (*cp && !isspace(*cp))
cp++;
return cp;
}
static int count_argc(const char *str) static int count_argc(const char *str)
{ {
int count = 0; int count = 0;
bool was_space;
while (*str) { for (was_space = true; *str; str++) {
str = skip_spaces(str); if (isspace(*str)) {
if (*str) { was_space = true;
} else if (was_space) {
was_space = false;
count++; count++;
str = skip_arg(str);
} }
} }
@ -39,10 +33,8 @@ static int count_argc(const char *str)
*/ */
void argv_free(char **argv) void argv_free(char **argv)
{ {
char **p; argv--;
for (p = argv; *p; p++) kfree(argv[0]);
kfree(*p);
kfree(argv); kfree(argv);
} }
EXPORT_SYMBOL(argv_free); EXPORT_SYMBOL(argv_free);
@ -59,43 +51,44 @@ EXPORT_SYMBOL(argv_free);
* considered to be a single argument separator. The returned array * considered to be a single argument separator. The returned array
* is always NULL-terminated. Returns NULL on memory allocation * is always NULL-terminated. Returns NULL on memory allocation
* failure. * failure.
*
* The source string at `str' may be undergoing concurrent alteration via
* userspace sysctl activity (at least). The argv_split() implementation
* attempts to handle this gracefully by taking a local copy to work on.
*/ */
char **argv_split(gfp_t gfp, const char *str, int *argcp) char **argv_split(gfp_t gfp, const char *str, int *argcp)
{ {
int argc = count_argc(str); char *argv_str;
char **argv = kzalloc(sizeof(*argv) * (argc+1), gfp); bool was_space;
char **argvp; char **argv, **argv_ret;
int argc;
if (argv == NULL) argv_str = kstrndup(str, KMALLOC_MAX_SIZE - 1, gfp);
goto out; if (!argv_str)
return NULL;
argc = count_argc(argv_str);
argv = kmalloc(sizeof(*argv) * (argc + 2), gfp);
if (!argv) {
kfree(argv_str);
return NULL;
}
*argv = argv_str;
argv_ret = ++argv;
for (was_space = true; *argv_str; argv_str++) {
if (isspace(*argv_str)) {
was_space = true;
*argv_str = 0;
} else if (was_space) {
was_space = false;
*argv++ = argv_str;
}
}
*argv = NULL;
if (argcp) if (argcp)
*argcp = argc; *argcp = argc;
return argv_ret;
argvp = argv;
while (*str) {
str = skip_spaces(str);
if (*str) {
const char *p = str;
char *t;
str = skip_arg(str);
t = kstrndup(p, str-p, gfp);
if (t == NULL)
goto fail;
*argvp++ = t;
}
}
*argvp = NULL;
out:
return argv;
fail:
argv_free(argv);
return NULL;
} }
EXPORT_SYMBOL(argv_split); EXPORT_SYMBOL(argv_split);