a $ ./auth0 $(python -c 'print "A"*512') ERROR: password incorrect Segmentation fault b With optimisation, `correct` isn't really a variable anymore. The compiler recognises the moments it can change (i.e. at definition time and after the strcmp), and when it is used (the last if), and manages to do the same without the variable. It now uses code similar to the following control flow: if (strcmp(hash1,hash2) == 0) { printf("Starting root shell\n"); setuid(0); setgid(0); system("/bin/sh"); } else { printf("ERROR: password incorrect\n"); } So, `correct` has been eliminated. Therefore, the buffer overflow attack doesn't work anymore: there is no variable to overwrite. We can see this in auth3.s. The `bool correct = false;` statement doesn't translate to anything in assembly, while it does in auth0.s. More interestingly, we can see the control flow: 19:auth.c **** if (strcmp(hash1,hash2) == 0) { 99 .loc 1 19 0 100 001f BF000000 mov edi, OFFSET FLAT:.LC1 # tmp90, 100 00 101 0024 B96B0000 mov ecx, 107 # tmp91, 101 00 102 0029 4889C6 mov rsi, rax # hash1, hash1 103 .LBE7: 104 002c F3A6 repz cmpsb 105 .LVL4: 106 002e 7530 jne .L6 #, 107 .LVL5: 108 .LBB8: 109 .LBB9: 20:auth.c **** correct = true; 21:auth.c **** } else { 22:auth.c **** printf("ERROR: password incorrect\n"); 23:auth.c **** } 24:auth.c **** 25:auth.c **** if (correct) { 26:auth.c **** printf("Starting root shell\n"); 110 .loc 1 26 0 111 0030 BF000000 mov edi, OFFSET FLAT:.LC3 #, 111 00 112 0035 E8000000 call puts # 112 00 113 .LVL6: 27:auth.c **** setuid(0); 114 .loc 1 27 0 115 003a 31FF xor edi, edi # 116 003c E8000000 call setuid # 116 00 The jump in 106 goes to .L6 where the error printf assembly is stored: 22:auth.c **** } 138 .loc 1 22 0 139 0060 BF000000 mov edi, OFFSET FLAT:.LC2 #, 139 00 140 0065 E8000000 call puts # 140 00 If we don't jump (i.e. if the result from strcmp was 0), we continue in .LVL5, .LBB8, .LBB9 where we printf "Starting root shell" and after do the set{u,g}id stuff and start the root shell. In auth0.s we see a control flow that stays much closer to the C code. There is an actual variable `correct` which is set if the strcmp returns 0: 19:auth.c **** if (strcmp(hash1,hash2) == 0) { 76 .loc 1 19 0 77 004f 488B55E8 mov rdx, QWORD PTR [rbp-24] # tmp88, hash2 78 0053 488B45F0 mov rax, QWORD PTR [rbp-16] # tmp89, hash1 79 0057 4889D6 mov rsi, rdx #, tmp88 80 005a 4889C7 mov rdi, rax #, tmp89 81 005d E8000000 call strcmp # 81 00 82 0062 85C0 test eax, eax # D.3514 83 0064 7506 jne .L2 #, 20:auth.c **** correct = true; 84 .loc 1 20 0 85 0066 C645FF01 mov BYTE PTR [rbp-1], 1 # correct, Finally: the compiler changes the control flow because it is more efficient. In this case, I think, it is both timewise and spacewise more efficient: we store one bool less, and also don't lose time in setting and checking its value.