[llvm] 2bc57d8 - Don't override __attribute__((no_stack_protector)) by inlining (PR52886)

Hans Wennborg via llvm-commits llvm-commits at lists.llvm.org
Thu Jan 13 03:05:21 PST 2022


Author: Hans Wennborg
Date: 2022-01-13T12:04:49+01:00
New Revision: 2bc57d85ebf244f19a3046295b58eb8c667f947d

URL: https://github.com/llvm/llvm-project/commit/2bc57d85ebf244f19a3046295b58eb8c667f947d
DIFF: https://github.com/llvm/llvm-project/commit/2bc57d85ebf244f19a3046295b58eb8c667f947d.diff

LOG: Don't override __attribute__((no_stack_protector)) by inlining (PR52886)

Since 26c6a3e736d3, LLVM's inliner will "upgrade" the caller's stack protector
attribute based on the callee. This lead to surprising results with Clang's
no_stack_protector attribute added in 4fbf84c1732f (D46300). Consider the
following code compiled with clang -fstack-protector-strong -Os
(https://godbolt.org/z/7s3rW7a1q).

  extern void h(int* p);

  inline __attribute__((always_inline)) int g() {
    return 0;
  }

  int __attribute__((__no_stack_protector__)) f() {
    int a[1];
    h(a);
    return g();
  }

LLVM will inline g() into f(), and f() would get a stack protector, against the
users explicit wishes, potentially breaking the program e.g. if h() changes the
value of the stack cookie. That's a miscompile.

More recently, bc044a88ee3c (D91816) addressed this problem by preventing
inlining when the stack protector is disabled in the caller and enabled in the
callee or vice versa. However, the problem remained if the callee is marked
always_inline as in the example above. This affected users, see e.g.
http://crbug.com/1274129 and http://llvm.org/pr52886.

One way to fix this would be to prevent inlining also in the always_inline
case. Despite the name, always_inline does not guarantee inlining, so this
would be legal but potentially surprising to users.

However, I think the better fix is to not enable the stack protector in a
caller based on the callee. The motivation for the old behaviour is unclear, it
seems counter-intuitive, and causes real problems as we've seen.

This commit implements that fix, which means in the example above, g() gets
inlined into f() (also without always_inline), and f() is emitted without stack
protector. I think that matches most developers' expectations, and that's also
what GCC does.

Another effect of this change is that a no_stack_protector function can now be
inlined into a stack protected function, e.g. (https://godbolt.org/z/hafP6W856):

  extern void h(int* p);

  inline int __attribute__((__no_stack_protector__)) __attribute__((always_inline)) g() {
    return 0;
  }

  int f() {
    int a[1];
    h(a);
    return g();
  }

I think that's fine. Such code would be unusual since no_stack_protector is
normally applied to a program entry point which sets up the stack canary. And
even if such code exists, inlining doesn't change the semantics: there is still
no stack cookie setup/check around entry/exit of the g() code region, but there
may be in the surrounding context, as there was before inlining. This also
matches GCC.

See also the discussion at https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94722

Differential revision: https://reviews.llvm.org/D116589

Added: 
    

Modified: 
    llvm/docs/LangRef.rst
    llvm/lib/Analysis/InlineCost.cpp
    llvm/lib/IR/Attributes.cpp
    llvm/test/ThinLTO/X86/nossp.ll
    llvm/test/Transforms/Inline/inline_ssp.ll

Removed: 
    llvm/test/Transforms/Inline/inline_nossp.ll


################################################################################
diff  --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 3a47cb50f9d2c..33ff3a8e85dbe 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1995,11 +1995,9 @@ example:
     Variables that are identified as requiring a protector will be arranged
     on the stack such that they are adjacent to the stack protector guard.
 
-    A function with the ``ssp`` attribute but without the ``alwaysinline``
-    attribute cannot be inlined into a function without a
-    ``ssp/sspreq/sspstrong`` attribute. If inlined, the caller will get the
-    ``ssp`` attribute. ``call``, ``invoke``, and ``callbr`` instructions with
-    the ``alwaysinline`` attribute force inlining.
+    If a function with an ``ssp`` attribute is inlined into a calling function,
+    the attribute is not carried over to the calling function.
+
 ``sspstrong``
     This attribute indicates that the function should emit a stack smashing
     protector. This attribute causes a strong heuristic to be used when
@@ -2024,12 +2022,10 @@ example:
 
     This overrides the ``ssp`` function attribute.
 
-    A function with the ``sspstrong`` attribute but without the
-    ``alwaysinline`` attribute cannot be inlined into a function without a
-    ``ssp/sspstrong/sspreq`` attribute. If inlined, the caller will get the
-    ``sspstrong`` attribute unless the ``sspreq`` attribute exists.  ``call``,
-    ``invoke``, and ``callbr`` instructions with the ``alwaysinline`` attribute
-    force inlining.
+    If a function with an ``sspstrong`` attribute is inlined into a calling
+    function which has an ``ssp`` attribute, the calling function's attribute
+    will be upgraded to ``sspstrong``.
+
 ``sspreq``
     This attribute indicates that the function should *always* emit a stack
     smashing protector. This overrides the ``ssp`` and ``sspstrong`` function
@@ -2046,11 +2042,9 @@ example:
     #. Variables that have had their address taken are 3rd closest to the
        protector.
 
-    A function with the ``sspreq`` attribute but without the ``alwaysinline``
-    attribute cannot be inlined into a function without a
-    ``ssp/sspstrong/sspreq`` attribute. If inlined, the caller will get the
-    ``sspreq`` attribute.  ``call``, ``invoke``, and ``callbr`` instructions
-    with the ``alwaysinline`` attribute force inlining.
+    If a function with an ``sspreq`` attribute is inlined into a calling
+    function which has an ``ssp`` or ``sspstrong`` attribute, the calling
+    function's attribute will be upgraded to ``sspreq``.
 
 ``strictfp``
     This attribute indicates that the function was called from a scope that

diff  --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp
index ff31e81aad08d..5dce896feb442 100644
--- a/llvm/lib/Analysis/InlineCost.cpp
+++ b/llvm/lib/Analysis/InlineCost.cpp
@@ -2898,15 +2898,6 @@ Optional<InlineResult> llvm::getAttributeBasedInliningDecision(
   if (Call.isNoInline())
     return InlineResult::failure("noinline call site attribute");
 
-  // Don't inline functions if one does not have any stack protector attribute
-  // but the other does.
-  if (Caller->hasStackProtectorFnAttr() && !Callee->hasStackProtectorFnAttr())
-    return InlineResult::failure(
-        "stack protected caller but callee requested no stack protector");
-  if (Callee->hasStackProtectorFnAttr() && !Caller->hasStackProtectorFnAttr())
-    return InlineResult::failure(
-        "stack protected callee but caller requested no stack protector");
-
   return None;
 }
 

diff  --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp
index 9726397311875..542b47001160c 100644
--- a/llvm/lib/IR/Attributes.cpp
+++ b/llvm/lib/IR/Attributes.cpp
@@ -1921,6 +1921,12 @@ static void setOR(Function &Caller, const Function &Callee) {
 /// If the inlined function had a higher stack protection level than the
 /// calling function, then bump up the caller's stack protection level.
 static void adjustCallerSSPLevel(Function &Caller, const Function &Callee) {
+  // If the calling function has *no* stack protection level (e.g. it was built
+  // with Clang's -fno-stack-protector or no_stack_protector attribute), don't
+  // change it as that could change the program's semantics.
+  if (!Caller.hasStackProtectorFnAttr())
+    return;
+
   // If upgrading the SSP attribute, clear out the old SSP Attributes first.
   // Having multiple SSP attributes doesn't actually hurt, but it adds useless
   // clutter to the IR.

diff  --git a/llvm/test/ThinLTO/X86/nossp.ll b/llvm/test/ThinLTO/X86/nossp.ll
index c542a85c6f74b..cfc54d595ad77 100644
--- a/llvm/test/ThinLTO/X86/nossp.ll
+++ b/llvm/test/ThinLTO/X86/nossp.ll
@@ -23,7 +23,8 @@ declare void @ssp_callee() ssp
 
 ; nossp caller should be able to inline nossp callee.
 define void @nossp_caller() {
-; CHECK-LABEL: @nossp_caller
+; CHECK-LABEL: define void @nossp_caller()
+; CHECK-NOT: #0
 ; CHECK-NEXT: tail call void @foo
   tail call void @nossp_callee()
   ret void
@@ -31,28 +32,34 @@ define void @nossp_caller() {
 
 ; ssp caller should be able to inline ssp callee.
 define void @ssp_caller() ssp {
-; CHECK-LABEL: @ssp_caller
+; CHECK-LABEL: define void @ssp_caller()
+; CHECK-SAME: #0
 ; CHECK-NEXT: tail call void @foo
   tail call void @ssp_callee()
   ret void
 }
 
-; nossp caller should *NOT* be able to inline ssp callee.
+; nossp caller should be able to inline ssp callee.
+; the ssp attribute is not propagated.
 define void @nossp_caller2() {
-; CHECK-LABEL: @nossp_caller2
-; CHECK-NEXT: tail call void @ssp_callee
+; CHECK-LABEL: define void @nossp_caller2()
+; CHECK-NOT: #0
+; CHECK-NEXT: tail call void @foo
   tail call void @ssp_callee()
   ret void
 }
 
-; ssp caller should *NOT* be able to inline nossp callee.
+; ssp caller should be able to inline nossp callee.
 define void @ssp_caller2() ssp {
-; CHECK-LABEL: @ssp_caller2
-; CHECK-NEXT: tail call void @nossp_callee
+; CHECK-LABEL: define void @ssp_caller2()
+; CHECK-SAME: #0
+; CHECK-NEXT: tail call void @foo
   tail call void @nossp_callee()
   ret void
 }
 
+; CHECK: attributes #0 = { ssp }
+
 ;--- b.ll
 target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
 target triple = "x86_64-pc-linux-gnu"

diff  --git a/llvm/test/Transforms/Inline/inline_nossp.ll b/llvm/test/Transforms/Inline/inline_nossp.ll
deleted file mode 100644
index c7145c8e4a7d5..0000000000000
--- a/llvm/test/Transforms/Inline/inline_nossp.ll
+++ /dev/null
@@ -1,50 +0,0 @@
-; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
-; RUN: opt -passes='cgscc(inline)' %s -S -pass-remarks-missed=inline 2>&1 | FileCheck --check-prefixes=CHECK,CHECK-INLINE %s
-; RUN: opt -passes=always-inline -o - -S %s | FileCheck %s
-
-; CHECK-INLINE: 'ssp' not inlined into 'nossp_caller' because it should never be inlined (cost=never): stack protected callee but caller requested no stack protector
-; CHECK-INLINE: 'nossp' not inlined into 'ssp_caller' because it should never be inlined (cost=never): stack protected caller but callee requested no stack protector
-
-; Not interesting to test.
-define i32 @nossp() { ret i32 41 }
-define i32 @ssp() sspstrong { ret i32 42 }
-define i32 @nossp_alwaysinline() alwaysinline { ret i32 43 }
-define i32 @ssp_alwaysinline() sspstrong alwaysinline { ret i32 44 }
-
-; @ssp should not be inlined due to mismatch stack protector.
-; @ssp_alwaysinline should be inlined due to alwaysinline.
-define i32 @nossp_caller() {
-; CHECK-LABEL: @nossp_caller(
-; CHECK-NEXT:    [[TMP1:%.*]] = call i32 @ssp()
-; CHECK-NEXT:    ret i32 44
-;
-  call i32 @ssp()
-  %2 = call i32 @ssp_alwaysinline()
-  ret i32 %2
-}
-; @nossp should not be inlined due to mismatch stack protector.
-; @nossp_alwaysinline should be inlined due to alwaysinline.
-define i32 @ssp_caller() sspstrong {
-; CHECK-LABEL: @ssp_caller(
-; CHECK-NEXT:    [[TMP1:%.*]] = call i32 @nossp()
-; CHECK-NEXT:    ret i32 43
-;
-  call i32 @nossp()
-  %2 = call i32 @nossp_alwaysinline()
-  ret i32 %2
-}
-
-; The alwaysinline attribute can also appear on the CallBase (ie. the call
-; site), ie. when __attribute__((flatten)) is used on the caller. Treat this
-; the same as if the caller had the fn attr alwaysinline and permit inline
-; substitution, despite the mismatch between caller and callee on ssp attrs.
-;
-; Curiously, the always_inline attribute on a CallInst is only expanded by the
-; inline pass, but not always_inline pass!
-define i32 @nossp_alwaysinline_caller() {
-; CHECK-INLINE-LABEL: @nossp_alwaysinline_caller(
-; CHECK-INLINE-NEXT:    ret i32 42
-;
-  %1 = call i32 @ssp() alwaysinline
-  ret i32 %1
-}

diff  --git a/llvm/test/Transforms/Inline/inline_ssp.ll b/llvm/test/Transforms/Inline/inline_ssp.ll
index ccfe934531598..a4f73f4dcd5ad 100644
--- a/llvm/test/Transforms/Inline/inline_ssp.ll
+++ b/llvm/test/Transforms/Inline/inline_ssp.ll
@@ -9,15 +9,23 @@
 
 ; These first four functions (@fun_sspreq, @fun_sspstrong, @fun_ssp, @fun_nossp)
 ; are used by the remaining functions to ensure that the SSP attributes are
-; propagated correctly.  The caller should have its SSP attribute set as:
+; propagated correctly.  If the caller had an SSP attribute before inlining, it
+; should have its new SSP attribute set as:
 ; strictest(caller-ssp-attr, callee-ssp-attr), where strictness is ordered as:
-;  sspreq > sspstrong > ssp > [no ssp]
+;  sspreq > sspstrong > ssp
+
 define internal void @fun_sspreq() sspreq {
 entry:
   %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* @.str3, i32 0, i32 0))
   ret void
 }
 
+define internal void @fun_sspreq_alwaysinline() sspreq alwaysinline {
+entry:
+  %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* @.str3, i32 0, i32 0))
+  ret void
+}
+
 define internal void @fun_sspstrong() sspstrong {
 entry:
   %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str2, i32 0, i32 0))
@@ -66,6 +74,13 @@ entry:
   ret void
 }
 
+define void @alwaysinline_req_nossp() {
+entry:
+; CHECK: @alwaysinline_req_nossp() {
+  call void @fun_sspreq_alwaysinline()
+  ret void
+}
+
 define void @inline_strong_req() sspreq {
 entry:
 ; CHECK: @inline_strong_req() #[[SSPREQ]]


        


More information about the llvm-commits mailing list