On Thu, Jul 30, 2020 at 10:11:07AM +0200, Arnd Bergmann wrote: > > On Wed, Jul 29, 2020 at 3:22 PM Denis Efremov <efremov@xxxxxxxxx> wrote: > > > And checked for leaks on x86_64 with the script test.sh > > $ cat test.sh > > #!/bin/bash > > > > for i in 4.8 5 6 7 8 9 10 > > do > > ./run_container.sh gcc-$i $(pwd)/src $(pwd)/out bash -c 'gcc test.c; ./a.out' > > ./run_container.sh gcc-$i $(pwd)/src $(pwd)/out bash -c 'gcc -O2 test.c; ./a.out' > > ./run_container.sh gcc-$i $(pwd)/src $(pwd)/out bash -c 'gcc -O3 test.c; ./a.out' > > done > > > > No leaks reported. Is it really possible this this kind of init, i.e. cmd = *ptr? > > The problem is that the behavior is dependent not just on the compiler > version but > also optimization flags, target architecture and specific structure > layouts. Most > of the time, both gcc and clang will initialize the whole structure > rather than just > the individual members, but you still can't be sure that this is true > for all configurations > that this code runs on, except by using CONFIG_GCC_PLUGIN_STRUCTLEAK. > > Kees pointed me to the lib/test_stackinit.c file in the kernel in which he has > collected a number of combinations that are known to trigger the problem. > > What I see there though are only cases of struct initializers like > > struct test_big_hole var = { .one = arg->one, .two=arg->two, .three > = arg->three, .four = arg->four }; test_stackinit.c intended to use six cases (where "full" is in the sense of "all members are named", this is intentionally testing the behavior of padding hole initialization): full static initialization: = { .one = 0, .two = 0, .three = 0, .four = 0, }; partial static init: = { .two = 0, }; full dynamic init: = { .one = arg->one, .two = arg->two, .three = arg->three, .four = arg->four, }; partial dynamic init: = { .two = arg->two, }; full runtime init: var.one = 0; var.two = 0; var.three = 0; memset(&var.four, 0, sizeof(var.four)); partial runtime init: var.two = 0; (It seems in refactoring I botched the "full static initialization" case, which I'll go fix separately.) > but not the syntax used in the floppy driver: > > struct test_big_hole var = *arg; So this one is a "whole structure copy" which I didn't have any tests for, since I'd (perhaps inappropriately) assumed would be accomplished with memcpy() internally, which means the incoming "*arg"'s padding holes would be copied as-is. If the compiler is actually doing per-member copies and leaving holes in "var" untouched, that's unexpected, so clearly that needs to be added to test_stackinit.c! :) > or the a constructor like > > struct test_big_hole var; > var = (struct test_big_hole){ .one = arg->one, .two=arg->two, .three > = arg->three, .four = arg->four }; > > Kees, do you know whether those two would behave differently? > Would it make sense to also check for those, or am I perhaps > misreading your code and it already gets checked? I *think* the above constructor would be covered under "full runtime init", but it does also seem likely it would be handled similarly to the "whole structure copy" in the previous example. I will go add more tests... -- Kees Cook