Skip to content

Commit 095d141

Browse files
oleg-nesterovtorvalds
authored andcommitted
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 <[email protected]> Cc: Andi Kleen <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Signed-off-by: Linus Torvalds <[email protected]>
1 parent 30493cc commit 095d141

File tree

1 file changed

+40
-47
lines changed

1 file changed

+40
-47
lines changed

lib/argv_split.c

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,17 @@
88
#include <linux/slab.h>
99
#include <linux/export.h>
1010

11-
static const char *skip_arg(const char *cp)
12-
{
13-
while (*cp && !isspace(*cp))
14-
cp++;
15-
16-
return cp;
17-
}
18-
1911
static int count_argc(const char *str)
2012
{
2113
int count = 0;
14+
bool was_space;
2215

23-
while (*str) {
24-
str = skip_spaces(str);
25-
if (*str) {
16+
for (was_space = true; *str; str++) {
17+
if (isspace(*str)) {
18+
was_space = true;
19+
} else if (was_space) {
20+
was_space = false;
2621
count++;
27-
str = skip_arg(str);
2822
}
2923
}
3024

@@ -39,10 +33,8 @@ static int count_argc(const char *str)
3933
*/
4034
void argv_free(char **argv)
4135
{
42-
char **p;
43-
for (p = argv; *p; p++)
44-
kfree(*p);
45-
36+
argv--;
37+
kfree(argv[0]);
4638
kfree(argv);
4739
}
4840
EXPORT_SYMBOL(argv_free);
@@ -59,43 +51,44 @@ EXPORT_SYMBOL(argv_free);
5951
* considered to be a single argument separator. The returned array
6052
* is always NULL-terminated. Returns NULL on memory allocation
6153
* failure.
54+
*
55+
* The source string at `str' may be undergoing concurrent alteration via
56+
* userspace sysctl activity (at least). The argv_split() implementation
57+
* attempts to handle this gracefully by taking a local copy to work on.
6258
*/
6359
char **argv_split(gfp_t gfp, const char *str, int *argcp)
6460
{
65-
int argc = count_argc(str);
66-
char **argv = kzalloc(sizeof(*argv) * (argc+1), gfp);
67-
char **argvp;
68-
69-
if (argv == NULL)
70-
goto out;
71-
72-
if (argcp)
73-
*argcp = argc;
74-
75-
argvp = argv;
76-
77-
while (*str) {
78-
str = skip_spaces(str);
79-
80-
if (*str) {
81-
const char *p = str;
82-
char *t;
83-
84-
str = skip_arg(str);
61+
char *argv_str;
62+
bool was_space;
63+
char **argv, **argv_ret;
64+
int argc;
65+
66+
argv_str = kstrndup(str, KMALLOC_MAX_SIZE - 1, gfp);
67+
if (!argv_str)
68+
return NULL;
69+
70+
argc = count_argc(argv_str);
71+
argv = kmalloc(sizeof(*argv) * (argc + 2), gfp);
72+
if (!argv) {
73+
kfree(argv_str);
74+
return NULL;
75+
}
8576

86-
t = kstrndup(p, str-p, gfp);
87-
if (t == NULL)
88-
goto fail;
89-
*argvp++ = t;
77+
*argv = argv_str;
78+
argv_ret = ++argv;
79+
for (was_space = true; *argv_str; argv_str++) {
80+
if (isspace(*argv_str)) {
81+
was_space = true;
82+
*argv_str = 0;
83+
} else if (was_space) {
84+
was_space = false;
85+
*argv++ = argv_str;
9086
}
9187
}
92-
*argvp = NULL;
93-
94-
out:
95-
return argv;
88+
*argv = NULL;
9689

97-
fail:
98-
argv_free(argv);
99-
return NULL;
90+
if (argcp)
91+
*argcp = argc;
92+
return argv_ret;
10093
}
10194
EXPORT_SYMBOL(argv_split);

0 commit comments

Comments
 (0)