Fix undefined/miscompiled construct in kernel parameters
PATCH 1/1 Fix undefined/miscompiled construct in kernel parameters
The parse_args call in init/main.c does pointer arithmetic between two different external symbols. This is undefined in C (only pointer arthmetic in the same symbol is defined) and gets miscompiled on AMD64 with gcc 3.2, resulting in a triple fault at boot when an driver with new style parameters is compiled in. In my case it was triggered by oprofile.
This patch works around this by passing the end pointer directly to the low level function and comparing it there. Strictly this is still undefined, but should be ok because gcc has to handle these pointers in another function correctly.
It is also possible to use an empty asm() with dummy input/out to work around this, but I didn't do that for now.
The construct may appear in other cases too, but I didn't see any miscompilation so far.
Please apply,
-Andi
include/linux/moduleparam.h +1 -1
@@ -67,7 +67,7 @@ struct kparam_string {extern int parse_args (const char *name,char *args,struct kernel_param *params, -unsigned num, +struct kernel_param *end,int (*unknown)(char *param,char *val));/* All the helper functions */
init/main.c +1 -1
@@ -404,7 +404,7 @@ asmlinkage void __init start_kernel(voidpage_alloc_init ();printk ("Kernel command line: %s\n" , saved_command_line);parse_args ("Booting kernel" , command_line, &__start___param, - &__stop___param - &__start___param, + &__stop___param, &unknown_bootoption);trap_init ();rcu_init ();
kernel/module.c +6 -6
@@ -929,7 +929,7 @@ static int obsolete_params(const char *n kp[i].arg = &obsparm[i]; } - ret =parse_args (name, args, kp, num,NULL ); + ret =parse_args (name, args, kp, &kp[num],NULL ); out:kfree (kp);return ret;
@@ -1622,11 +1622,11 @@ static struct module *load_module(void _ (char *)sechdrs[strindex].sh_addr); }else {/* Size of section 0 is 0, so this works well if no params */ - err =parse_args (mod->name, mod->args, - (struct kernel_param *) - sechdrs[setupindex].sh_addr, - sechdrs[setupindex].sh_size - /sizeof (struct kernel_param ), +struct kernel_param *parm = (struct kernel_param *) + sechdrs[setupindex].sh_addr; + err =parse_args (mod->name, mod->args, parm, + &parm[sechdrs[setupindex].sh_size + /sizeof (struct kernel_param )],NULL ); }if (err <0 )
kernel/params.c +4 -4
@@ -46,13 +46,13 @@ static inline int parameq(const char *instatic int parse_one (char *param,char *val,struct kernel_param *params, -unsigned num_params, +struct kernel_param *end,int (*handle_unknown)(char *param,char *val)) {unsigned int i;/* Find parameter */ -for (i =0 ; i < num_params; i++) { +for (i =0 ; ¶ms[i] < end; i++) {if (parameq (param, params[i].name)) {DEBUGP ("They are equal! Calling %p\n" , params[i].set);
@@ -109,7 +109,7 @@ static char *next_arg(char *args, char *int parse_args (const char *name,char *args,struct kernel_param *params, -unsigned num, +struct kernel_param *end,int (*unknown)(char *param,char *val)) {char *param, *val;
@@ -120,7 +120,7 @@ int parse_args(const char *name,int ret; args =next_arg (args, ¶m, &val); - ret =parse_one (param, val, params, num, unknown); + ret =parse_one (param, val, params, end, unknown);switch (ret) {case -ENOENT : printk(KERN_ERR "%s: Unknown parameter `%s'\n",
Quoted 4 lines · Andi Kleen
The
parse_argscall ininit/main.cdoes pointer arithmetic between two different external symbols. This is undefined in C (only pointer arthmetic in the same symbol is defined) and gets miscompiled on AMD64 with gcc 3.2,
That's silly. You're making the code less readable, with some silly parameters. Why does it get miscompiled on amd64, and let's just fix that one.
Linus
In message <[email protected]> you wri te:
Quoted 10 lines · Andi Kleen
Quoted 4 lines
The
parse_argscall ininit/main.cdoes pointer arithmetic between two different external symbols. This is undefined in C (only pointer arthmetic in the same symbol is defined) and gets miscompiled on AMD64 with gcc 3.2,That's silly. You're making the code less readable, with some silly parameters. Why does it get miscompiled on amd64, and let's just fix that one.
AFAICT, Roman's fix is correct; Richard admonished me in the past for such code, IIRC, but this one slipped through.
Since Andi reports that even that doesn't work for x86-64, I'd say apply this patch based on his: it's an arbitrary change anyway.
The compiler should be fixed, too, but if this is the only compiler bug which effects the kernel, we should consider ourselves lucky.
(Andi: your module.c patch was a little convoluted). Cheers,
Rusty.
include/linux/moduleparam.h +1 -1
@@ -67,7 +67,7 @@ struct kparam_string {extern int parse_args (const char *name,char *args,struct kernel_param *params, -unsigned num, +struct kernel_param *end,int (*unknown)(char *param,char *val));/* All the helper functions */
init/main.c +1 -1
@@ -404,7 +404,7 @@ asmlinkage void __init start_kernel(voidpage_alloc_init ();printk ("Kernel command line: %s\n" , saved_command_line);parse_args ("Booting kernel" , command_line, &__start___param, - &__stop___param - &__start___param, + &__stop___param, &unknown_bootoption);trap_init ();rcu_init ();
kernel/module.c +4 -5
@@ -929,7 +929,7 @@ static int obsolete_params(const char *n kp[i].arg = &obsparm[i]; } - ret =parse_args (name, args, kp, num,NULL ); + ret =parse_args (name, args, kp, kp + num,NULL ); out:kfree (kp);return ret;
@@ -1623,10 +1623,9 @@ static struct module *load_module(void _ }else {/* Size of section 0 is 0, so this works well if no params */ err =parse_args (mod->name, mod->args, - (struct kernel_param *) - sechdrs[setupindex].sh_addr, - sechdrs[setupindex].sh_size - /sizeof (struct kernel_param ), + (void *)sechdrs[setupindex].sh_addr, + (void *)sechdrs[setupindex].sh_addr + + sechdrs[setupindex].sh_size,NULL ); }if (err <0 )
kernel/params.c +4 -4
@@ -46,13 +46,13 @@ static inline int parameq(const char *instatic int parse_one (char *param,char *val,struct kernel_param *params, -unsigned num_params, +struct kernel_param *end,int (*handle_unknown)(char *param,char *val)) {unsigned int i;/* Find parameter */ -for (i =0 ; i < num_params; i++) { +for (i =0 ; ¶ms[i] < end; i++) {if (parameq (param, params[i].name)) {DEBUGP ("They are equal! Calling %p\n" , params[i].set);
@@ -109,7 +109,7 @@ static char *next_arg(char *args, char *int parse_args (const char *name,char *args,struct kernel_param *params, -unsigned num, +struct kernel_param *end,int (*unknown)(char *param,char *val)) {char *param, *val;
@@ -120,7 +120,7 @@ int parse_args(const char *name,int ret; args =next_arg (args, ¶m, &val); - ret =parse_one (param, val, params, num, unknown); + ret =parse_one (param, val, params, end, unknown);switch (ret) {case -ENOENT : printk(KERN_ERR "%s: Unknown parameter `%s'\n",
--
Anyone who quotes me in their sig is an idiot. -- Rusty Russell.
Quoted 2 lines · Rusty Russell
Since Andi reports that even that doesn't work for x86-64, I'd say apply this patch based on his: it's an arbitrary change anyway.
No, Andi located the *real* problem. The compiler was over-aligning these objects, which added padding, which broke the array semantics you were looking for. The solution is to add an attribute aligned; he's sent a patch to Linus already.
r~
In message <[email protected]> you write:
Quoted 8 lines · Rusty Russell
Quoted 2 lines
Since Andi reports that even that doesn't work for x86-64, I'd say apply this patch based on his: it's an arbitrary change anyway.
No, Andi located the *real* problem. The compiler was over-aligning these objects, which added padding, which broke the array semantics you were looking for. The solution is to add an attribute aligned; he's sent a patch to Linus already.
Thanks for the explanation. Sorry I missed it.
Rusty.
--
Anyone who quotes me in their sig is an idiot. -- Rusty Russell.
Quoted 3 lines · Rusty Russell
AFAICT, Roman's fix is correct; Richard admonished me in the past for such code, IIRC, but this one slipped through.
Roman's fix is fine, but the fact is, the original code was also fine. Yes, the C standard has all these rules about "within objects" for pointer differences, but the "objects" themselves can come from outside the compiler. As they did in this case.
(Yeah, I could see the compiler warning about cases it suspects might be separate objects, but the end result should still be the right one).
In general, I accept _local_ uglifications to work around compiler problems. But I do not accept non-local stuff like making for ugly calling conventions etc, which is why Andi's original patch was not acceptable to me.
It turns out that the real bug was somewhere in the tool chain, and the linker should either honor alignment requirements or warn about them when it cannot. I suspect in this case the alignment requirement wasn't properly passed down the chain somewhere, I dunno. The problem is fixed, but for future reference please keep this in mind when working around compiler problems.
If worst comes to worst, we'll have notes about certain compiler versions just not working. It's certainly happened before.
Linus