google / sanitizers

AddressSanitizer, ThreadSanitizer, MemorySanitizer
Other
11.01k stars 998 forks source link

MSan false negatives due to "[SCCP] Don't mark edges feasible when resolving undefs" #1730

Open thurstond opened 4 months ago

thurstond commented 4 months ago

Reproducer

Since late June 2022, this example is no longer caught by MSan with clang -O1 -fsanitize=memory:

#include <cstdio>

int main (int argc, char* argv[]) {
  int x; // Uninitialized
  int y = 0;

  if (x == y) {
    printf ("x == y\n");
  } else {
    printf ("x != y\n");
  }

  return 0;
}

edit: the example can be further simplified by removing the variable y, and simply comparing x == 0

Root Cause

This is due to

"[SCCP] Don't mark edges feasible when resolving undefs

As branch on undef is immediate undefined behavior, there is no need to mark one of the edges as feasible. We can leave all the edges non-feasible. In IPSCCP, we can replace the branch with an unreachable terminator." -- https://github.com/llvm/llvm-project/commit/1f88d804083a8a1b68df1e6677920e38ab2a6b40

MSan cannot detect the uninitialized memory access in the example, because the entire main function is marked by IPSCCP as unreachable (since x==y is a branch on undef, both branches are removed), long before the MSan pass has a chance to instrument the code.

Note that when the example is compiled at -O1 without MSan, the program has no output whatsoever.

IR Dump

Immediately prior to IPSCCP:

*** IR Dump After OpenMPOptPass on [module] ***

define dso_local noundef i32 @main(i32 noundef %argc, ptr noundef %argv) #0 {
entry:
  br i1 undef, label %if.then, label %if.else

if.then:                                          ; preds = %entry
  %call = call i32 (ptr, ...) @printf(ptr noundef @.str)
  br label %if.end

if.else:                                          ; preds = %entry
  %call1 = call i32 (ptr, ...) @printf(ptr noundef @.str.1)
  br label %if.end

if.end:                                           ; preds = %if.else, %if.then
  ret i32 0
}

After IPSCCP (clang before 1f88d804083a8a1b68df1e6677920e38ab2a6b40):

*** IR Dump After IPSCCPPass on [module] ***

define dso_local noundef i32 @main(i32 noundef %argc, ptr noundef %argv) #0 {
entry:
  br label %if.else

if.else:                                          ; preds = %entry
  %call1 = call i32 (ptr, ...) @printf(ptr noundef @.str.1)
  br label %if.end

if.end:                                           ; preds = %if.else
  ret i32 0
}

After IPSCCP (clang at/after 1f88d804083a8a1b68df1e6677920e38ab2a6b40):

*** IR Dump After IPSCCPPass on [module] ***

define dso_local noundef i32 @main(i32 noundef %argc, ptr noundef %argv) #0 {
entry:
  unreachable
}