logoalt Hacker News

strenholmelast Tuesday at 4:38 PM2 repliesview on HN

If the optimizer sees that you're loading uninitialized memory, it can reason that since the result of uninitialized memory is garbage, doing any computation on that result is also garbage, and happily delete said computation as a result

This is an interesting assertion, and one that is easy enough to prove true.

Let’s take the following C code, which uses the same XOF algorithm (but not implementation) as my application (Deadwood):

  #include<stdio.h>
  #include<stdint.h>
  #include<stdlib.h>
  #define b(z) for(c=0;c<z;c++)
  uint32_t c,e[42],f[42],g=19,h
  =13,n[45],i,j,k;void m(){j=0;
  b(12)f[c+c%3*h]^=e[c+1];b(g){
  i=c*7%g;k=e[i++];k^=e[i%g]|~e
  [(i+1)%g];j=j+c;n[c]=n[c+g]=k
  >>j%32|k<<-j%32;}for(i=39;i--
  ;f[i+1]=f[i])e[i]=n[i]^n[i+1]
  ^n[i+4];b(3)e[c+h]^=f[c*h]=f[
  c*h+h];*e^=1;}int main(int c,
  char**v){char*q=malloc(2);if(
  q==0)return 0;q[0]&=31;q[0]|=
  1;q[1]=0;for(;;m()){b(3){for(
  j=0;j<4;){f[c*h]^=k=(*q?255&
  *q:1)<<8*j++;e[c+16]^=k;if(!
  *q++){b(18)m();b(8){j=c;b(1)
  printf("%02x",(e[1+j%2]>>8*c)
  &255);c=j;if(c%2)m();}puts(
  "");return 0;}}}}}
This code, as I’m sure the parent poster can clearly see, uses four bits of uninitialized allocated memory as its source of entropy. As per the parent’s assertion, there should therefore exist a compiler whose optimizer will cause this XOF to not correctly run.

The above code can have one of the following possible 16 outputs:

  0a5d51f3745c7266
  f84b051f67115f1a
  f87105c4ecfefe67
  92074ac8e1e7a42e
  1441ac245f288e18
  87023372e57ae001
  047a3ddd14209546
  340b2ff47c61172e
  bfb9289ed096f977
  dfd56a7a8d7d723e
  2151460954a80242
  6822335c6e0160dc
  3783ce3cae3d0774
  4e0156df46c00bac
  69795d939d211e7a
If the above code has any but one of the above 16 outputs, this is a real world case where a C compiler, seeing uninitialized memory being used, optimizes out the code which uses said uninitialized memory as an input, and therefore will not output one of the above 16 possible words.

I’ve tested the above code in GCC -O3 and clang -O3; both generate one of the above 16 possible outputs (each one generating a different output).

If there really is a compiler out there which does “happily delete said computation”, which would give a different output than one of the 16 outputs above, please name that compiler, the version of said compiler used, and all compile-time flags used with said compiler.

While I’ve never heard of a real world case where a compiler would refuse to run code using uninitialized memory as yet another source of entropy for a secure PRNG, I do know of a real world case where a very nasty security hole was caused because someone incorrectly removed code using uninitialized memory as part of an entropy pool: CVE-2008-0166


Replies

titzerlast Tuesday at 7:08 PM

Parent is right. Use of uninitialized memory is UB, and incidentally, the type of UB that the C standard is not working to define, but is relying on sanitizers to find in source programs, since it is considered always a bug.

This entire thread has a lot of "no security issues have ever been found in my code, and I test a lot. Therefore no bugs will ever exist in my code and we're all safe." To see you doing this in an explicitly security-conscious setting is distressing.

If anything, I see assertions like this and juxtaposed with blatant, willful misunderstanding of how C and C compilers work and it does the opposite of inspiring confidence.

Look at CVE-2009-1897; this is the classic example of how C compilers are happy to try to optimize code in the face of UB and lead to worse problems.

> If the above code has any but one of the above 16 outputs

I don't think you understand how insane optimizations in the face of UB can be. Just go look at this issue:

https://github.com/llvm/llvm-project/issues/174844?utm_sourc...

wat10000last Tuesday at 8:13 PM

Remember that a compiler is allowed to do anything when it sees undefined behavior, which includes doing the thing you want it to do.

Here's a little example of code disappearing due to a read of uninitialized memory:

    void test(int x) {
        int uninit;
        puts("hello");
        if (uninit)
            puts("non-zero");
        else
            puts("zero");
    }
clang 23.1.0 -O3 targeting ARMv8 deletes both branches of the if. Not only that, it deletes the code to return from the function. The very last instruction of the function is `bl puts`, meaning that after puts returns, it will start executing whatever function happened to come after this one in memory. That's probably a good thing in context, because that's likely to crash or infinite loop and make it clear that something went badly wrong, but the failure could easily be something more subtle that just disables some random seeding while otherwise executing normally.
show 1 reply