This is an archive of the discontinued LLVM Phabricator instance.

[GVN] permit GVN of non-local loads for ASAN unless undef or alloca is produced
Needs RevisionPublic

Authored by nickdesaulniers on Feb 14 2023, 4:20 PM.

Details

Summary

A "non-local load" is the load of value from a pointer defined in
another basic block than the basic block of the load.

While compiling the Linux kernel with CONFIG_FORTIFY_SOURCE=y and
CONFIG_KASAN=y, we discovered a curious case where a repeated condition
check that should have been optimized out was not.

It looks like GVN's ability to process "non local loads" was simply
disabled outright in pr25924 due to an external report.

That fix was too broad IMO. While we do want ASAN (and HWASAN) to help
us spot loads that produce undef, since those are likely OOB reads, it
still would be nice when we don't have undef to allow GVN to proceed. I
think this is a better balance. It should allow us to better optimize
KASAN binaries without regressions in the sanitizer's ability to help
spot OOB access.

Another important case to check is alloca; alloca is by definition not
initialized.

Tested with the original reproducer from the mailing list, as well as
the above linux kernel configs.

See also the original fix:
commit c7810baaa676 ("Disable gvn non-local speculative loads under asan.")

Link: https://github.com/ClangBuiltLinux/linux/issues/1687
Link: https://github.com/llvm/llvm-project/issues/25924
Link: https://lists.llvm.org/pipermail/llvm-dev/2015-November/092427.html
Link: http://lists.llvm.org/pipermail/llvm-dev/attachments/20151114/1c7d8dbe/attachment.c

Diff Detail

Event Timeline

Herald added a project: Restricted Project. · View Herald TranscriptFeb 14 2023, 4:20 PM
Herald added a subscriber: hiraditya. · View Herald Transcript
nickdesaulniers requested review of this revision.Feb 14 2023, 4:20 PM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 14 2023, 4:20 PM
nickdesaulniers edited the summary of this revision. (Show Details)
  • typo in commit message
nickdesaulniers retitled this revision from [GVN] permit GVN for ASAN unless undef is produced to [GVN] permit GVN of non-local loads for ASAN unless undef is produced.
  • update commit oneline
nickdesaulniers edited the summary of this revision. (Show Details)
  • include sha of original fix in commit message
nikic requested changes to this revision.Feb 14 2023, 11:52 PM

I don't think this is right as implemented. Imagine you have if (...) { store v, p } x = load p. GVN would convert this to something like x = phi(v, undef). With asan, we want this to report if the if is not taken. However, I believe that your checks will permit the transform, because the load folding result is not literally undef (but is still undef on one path).

This revision now requires changes to proceed.Feb 14 2023, 11:52 PM

I don't think this is right as implemented. Imagine you have if (...) { store v, p } x = load p. GVN would convert this to something like x = phi(v, undef). With asan, we want this to report if the if is not taken. However, I believe that your checks will permit the transform, because the load folding result is not literally undef (but is still undef on one path).

Is this what you're imagining? https://godbolt.org/z/Pc3YezE44

Seems like ASAN already misses that? In this case, we go to replace the load with alloca, so adding a check remedies that case. But it doesn't change the fact that asan misses that C test case.

nickdesaulniers retitled this revision from [GVN] permit GVN of non-local loads for ASAN unless undef is produced to [GVN] permit GVN of non-local loads for ASAN unless undef or alloca is produced.
nickdesaulniers edited the summary of this revision. (Show Details)
  • check for alloca as well

Pinging @eugenis @fmayer @pcc who may have more recently touched ASan/HWASan.

Not an expert in GVN, but your reasoning makes sense. Do the KASAN tests in the kernel pass (need to use -next, mainline is currently broken)? Wondering how we can double check there are no new false positives nor false negatives.

Not convinced that this is right. The original fix is for a false postive, not a false negative - i.e., we want to prevent speculation of a memory access that is not provably safe.

See https://lists.llvm.org/pipermail/llvm-dev/2015-November/092484.html. Note that the report is on heap memory, not alloca.

Not convinced that this is right. The original fix is for a false postive, not a false negative - i.e., we want to prevent speculation of a memory access that is not provably safe.

See https://lists.llvm.org/pipermail/llvm-dev/2015-November/092484.html. Note that the report is on heap memory, not alloca.

I tested my change against the original bug report; the artifact is still live and linked from there. It does not regress the original false positive report.

nikic requested changes to this revision.Mar 7 2023, 6:22 AM

I don't think this is right as implemented. Imagine you have if (...) { store v, p } x = load p. GVN would convert this to something like x = phi(v, undef). With asan, we want this to report if the if is not taken. However, I believe that your checks will permit the transform, because the load folding result is not literally undef (but is still undef on one path).

Is this what you're imagining? https://godbolt.org/z/Pc3YezE44

Seems like ASAN already misses that? In this case, we go to replace the load with alloca, so adding a check remedies that case. But it doesn't change the fact that asan misses that C test case.

In this case SROA should drop the alloca and it will never get to GVN. You want something more like this: https://godbolt.org/z/8z66K7xbh

I still don't think that this makes sense as-implemented. You are only checking the final result after SSA construction. I believe this needs to at least check all AvailableValues for isUndefValue().

llvm/lib/Transforms/Scalar/GVN.cpp
1773

I don't get the alloca check here. V is the result of the load, not the loaded pointer.

This revision now requires changes to proceed.Mar 7 2023, 6:22 AM

Do the KASAN tests in the kernel pass (need to use -next, mainline is currently broken)? Wondering how we can double check there are no new false positives nor false negatives.

How do I run those?

You want something more like this: https://godbolt.org/z/8z66K7xbh

Does ASAN catch that case? ;)

Do the KASAN tests in the kernel pass (need to use -next, mainline is currently broken)? Wondering how we can double check there are no new false positives nor false negatives.

How do I run those?

Just CONFIG_KASAN_KUNIT_TEST=y should do and then boot kernel.

I don't think this is right as implemented. Imagine you have if (...) { store v, p } x = load p. GVN would convert this to something like x = phi(v, undef). With asan, we want this to report if the if is not taken. However, I believe that your checks will permit the transform, because the load folding result is not literally undef (but is still undef on one path).

Is this what you're imagining? https://godbolt.org/z/Pc3YezE44

Seems like ASAN already misses that? In this case, we go to replace the load with alloca, so adding a check remedies that case. But it doesn't change the fact that asan misses that C test case.

In this case SROA should drop the alloca and it will never get to GVN. You want something more like this: https://godbolt.org/z/8z66K7xbh

I still don't think that this makes sense as-implemented. You are only checking the final result after SSA construction. I believe this needs to at least check all AvailableValues for isUndefValue().

Something like this?

diff --git a/llvm/lib/Transforms/Scalar/GVN.cpp b/llvm/lib/Transforms/Scalar/GVN.cpp
index f36fa0bd0979..ca80c0b40557 100644
--- a/llvm/lib/Transforms/Scalar/GVN.cpp
+++ b/llvm/lib/Transforms/Scalar/GVN.cpp
@@ -1781,11 +1781,14 @@ bool GVNPass::processNonLocalLoad(LoadInst *Load) {
     // If a non-local load would result in producing undef or an alloca (which
     // is not initialized), don't fold away control flow. We want the
     // sanitizers to help report this case.
-    if (isa<UndefValue>(V) || isa<AllocaInst>(V)) {
-      Function *F = Load->getParent()->getParent();
-      if (F->hasFnAttribute(Attribute::SanitizeAddress) ||
-          F->hasFnAttribute(Attribute::SanitizeHWAddress))
-        return false;
+    Function *F = Load->getParent()->getParent();
+    if (F->hasFnAttribute(Attribute::SanitizeAddress) ||
+        F->hasFnAttribute(Attribute::SanitizeHWAddress)) {
+      for (AvailableValueInBlock &AV : ValuesPerBlock) {
+        if (AV.AV.isUndefValue()) {
+          return false;
+        }
+      }
     }
 
     Load->replaceAllUsesWith(V);

That doesn't seem to work, though perhaps you envisioned something else?

Do the KASAN tests in the kernel pass (need to use -next, mainline is currently broken)? Wondering how we can double check there are no new false positives nor false negatives.

How do I run those?

Just CONFIG_KASAN_KUNIT_TEST=y should do and then boot kernel.

I've done so and the system boots. Was there supposed to be anything printed to the console? I just enabled KASAN=y, KUNIT=y, KASAN_KUNIT_TEST=y.

pcc added a comment.May 1 2023, 3:04 PM

I don't think this is right as implemented. Imagine you have if (...) { store v, p } x = load p. GVN would convert this to something like x = phi(v, undef). With asan, we want this to report if the if is not taken. However, I believe that your checks will permit the transform, because the load folding result is not literally undef (but is still undef on one path).

Is this what you're imagining? https://godbolt.org/z/Pc3YezE44

Seems like ASAN already misses that? In this case, we go to replace the load with alloca, so adding a check remedies that case. But it doesn't change the fact that asan misses that C test case.

In this case SROA should drop the alloca and it will never get to GVN. You want something more like this: https://godbolt.org/z/8z66K7xbh

I still don't think that this makes sense as-implemented. You are only checking the final result after SSA construction. I believe this needs to at least check all AvailableValues for isUndefValue().

Something like this?

diff --git a/llvm/lib/Transforms/Scalar/GVN.cpp b/llvm/lib/Transforms/Scalar/GVN.cpp
index f36fa0bd0979..ca80c0b40557 100644
--- a/llvm/lib/Transforms/Scalar/GVN.cpp
+++ b/llvm/lib/Transforms/Scalar/GVN.cpp
@@ -1781,11 +1781,14 @@ bool GVNPass::processNonLocalLoad(LoadInst *Load) {
     // If a non-local load would result in producing undef or an alloca (which
     // is not initialized), don't fold away control flow. We want the
     // sanitizers to help report this case.
-    if (isa<UndefValue>(V) || isa<AllocaInst>(V)) {
-      Function *F = Load->getParent()->getParent();
-      if (F->hasFnAttribute(Attribute::SanitizeAddress) ||
-          F->hasFnAttribute(Attribute::SanitizeHWAddress))
-        return false;
+    Function *F = Load->getParent()->getParent();
+    if (F->hasFnAttribute(Attribute::SanitizeAddress) ||
+        F->hasFnAttribute(Attribute::SanitizeHWAddress)) {
+      for (AvailableValueInBlock &AV : ValuesPerBlock) {
+        if (AV.AV.isUndefValue()) {
+          return false;
+        }
+      }
     }
 
     Load->replaceAllUsesWith(V);

That doesn't seem to work, though perhaps you envisioned something else?

Do the KASAN tests in the kernel pass (need to use -next, mainline is currently broken)? Wondering how we can double check there are no new false positives nor false negatives.

How do I run those?

Just CONFIG_KASAN_KUNIT_TEST=y should do and then boot kernel.

I've done so and the system boots. Was there supposed to be anything printed to the console? I just enabled KASAN=y, KUNIT=y, KASAN_KUNIT_TEST=y.

I think you also need to enable CONFIG_FTRACE=y or patch the kernel to have the config KASAN_KUNIT_TEST select TRACING. See https://lore.kernel.org/all/CAMn1gO7Ve4-d6vP4jvASQsTZ2maHsMF6gKHL3RXSuD9N3tAOfQ@mail.gmail.com/

Do the KASAN tests in the kernel pass (need to use -next, mainline is currently broken)? Wondering how we can double check there are no new false positives nor false negatives.

How do I run those?

Just CONFIG_KASAN_KUNIT_TEST=y should do and then boot kernel.

I've done so and the system boots. Was there supposed to be anything printed to the console? I just enabled KASAN=y, KUNIT=y, KASAN_KUNIT_TEST=y.

I think you also need to enable CONFIG_FTRACE=y or patch the kernel to have the config KASAN_KUNIT_TEST select TRACING. See https://lore.kernel.org/all/CAMn1gO7Ve4-d6vP4jvASQsTZ2maHsMF6gKHL3RXSuD9N3tAOfQ@mail.gmail.com/

$ grep -rn -e FTRACE=y -e KASAN=y -e KUNIT=y -e KASAN_KUNIT .config
665:CONFIG_HAVE_KPROBES_ON_FTRACE=y
4942:CONFIG_HAVE_ARCH_KASAN=y
4947:CONFIG_KASAN=y
4954:CONFIG_KASAN_KUNIT_TEST=y
5050:CONFIG_HAVE_DYNAMIC_FTRACE=y
5069:CONFIG_FTRACE=y
5139:CONFIG_KUNIT=y

(I did have that enabled) ;)

pcc added a comment.May 1 2023, 5:44 PM

Do the KASAN tests in the kernel pass (need to use -next, mainline is currently broken)? Wondering how we can double check there are no new false positives nor false negatives.

How do I run those?

Just CONFIG_KASAN_KUNIT_TEST=y should do and then boot kernel.

I've done so and the system boots. Was there supposed to be anything printed to the console? I just enabled KASAN=y, KUNIT=y, KASAN_KUNIT_TEST=y.

I think you also need to enable CONFIG_FTRACE=y or patch the kernel to have the config KASAN_KUNIT_TEST select TRACING. See https://lore.kernel.org/all/CAMn1gO7Ve4-d6vP4jvASQsTZ2maHsMF6gKHL3RXSuD9N3tAOfQ@mail.gmail.com/

$ grep -rn -e FTRACE=y -e KASAN=y -e KUNIT=y -e KASAN_KUNIT .config
665:CONFIG_HAVE_KPROBES_ON_FTRACE=y
4942:CONFIG_HAVE_ARCH_KASAN=y
4947:CONFIG_KASAN=y
4954:CONFIG_KASAN_KUNIT_TEST=y
5050:CONFIG_HAVE_DYNAMIC_FTRACE=y
5069:CONFIG_FTRACE=y
5139:CONFIG_KUNIT=y

(I did have that enabled) ;)

Huh, that should be enough to run the tests. You should be seeing a lot of console output spew at boot time ending in something like:

[   11.399154] ok 1 kasan

(or not ok 1 kasan if any of the tests failed). It works for me in the arm64 kernel, both master and next.

Do the KASAN tests in the kernel pass (need to use -next, mainline is currently broken)? Wondering how we can double check there are no new false positives nor false negatives.

How do I run those?

Just CONFIG_KASAN_KUNIT_TEST=y should do and then boot kernel.

I've done so and the system boots. Was there supposed to be anything printed to the console? I just enabled KASAN=y, KUNIT=y, KASAN_KUNIT_TEST=y.

I think you also need to enable CONFIG_FTRACE=y or patch the kernel to have the config KASAN_KUNIT_TEST select TRACING. See https://lore.kernel.org/all/CAMn1gO7Ve4-d6vP4jvASQsTZ2maHsMF6gKHL3RXSuD9N3tAOfQ@mail.gmail.com/

$ grep -rn -e FTRACE=y -e KASAN=y -e KUNIT=y -e KASAN_KUNIT .config
665:CONFIG_HAVE_KPROBES_ON_FTRACE=y
4942:CONFIG_HAVE_ARCH_KASAN=y
4947:CONFIG_KASAN=y
4954:CONFIG_KASAN_KUNIT_TEST=y
5050:CONFIG_HAVE_DYNAMIC_FTRACE=y
5069:CONFIG_FTRACE=y
5139:CONFIG_KUNIT=y

(I did have that enabled) ;)

Huh, that should be enough to run the tests. You should be seeing a lot of console output spew at boot time ending in something like:

[   11.399154] ok 1 kasan

(or not ok 1 kasan if any of the tests failed). It works for me in the arm64 kernel, both master and next.

Sorry, I may have been booting the wrong kernel image. With D144057 applied and the above kconfigs on x86_64, I see:

[    5.923941] # kasan: pass:44 fail:0 skip:14 total:58
[    5.926411] # Totals: pass:44 fail:0 skip:14 total:58
[    5.927858] ok 1 kasan