what are the rules about ssp function attributes?

Hello,

Zig is now tripping a new LLVM assert in recent main branch commits of LLVM:

Assertion failed: (!(!Caller.hasStackProtectorFnAttr() && Callee.hasStackProtectorFnAttr()) && "stack protected callee but caller requested no stack protector"), function adjustCallerSSPLevel, file /Users/kubkon/dev/llvm-project/llvm/lib/IR/Attributes.cpp, line 1951.

Caller is

; Function Attrs: nobuiltin nounwind
define internal fastcc void @std.math.big.int.llshl(%"[]usize"* nonnull readonly align 8 %0, %"[]usize"* nonnull readonly align 8 %1, i64 %2) unnamed_addr #2 !dbg !445894 {

Callee is

; Function Attrs: nobuiltin nounwind sspstrong
define internal fastcc i64 @std.math.shr.11603(i64 %0, i64 %1) unnamed_addr #1 !dbg !445958 {

So that's indeed a sspstrong function calling a non-sspstrong function. Why is that not allowed? The LLVM language reference makes it sound like you can have some functions protected and some functions not protected:

> sspstrong
>
> This attribute indicates that the function should emit a stack smashing protector.
> ...
> If a function that has an sspstrong attribute is inlined into a
> function that doesn’t have an sspstrong attribute, then the resulting
> function will have an sspstrong attribute.

Can anyone shed some light on this?

Thanks in advance!
Andrew

LangRef needs to be clarified. Sent ⚙ D93422 [LangRef] Update new ssp/sspstrong/sspreq semantics after D91816

The behavior change was due to ⚙ D91816 [Inline] prevent inlining on stack protector mismatch . How
does Zig end up merging the attributes
while the inliner blocks such inlining?

If a caller without ssp inlines a callee with ssp:
  the caller may alter %gs for its own purposes
  and will break the %gs usage in the callee.
(This one is required by the Linux kernel).

If a ssp caller inlines a callee without ssp:
  This one is a bit unclear to me why it needs to have such semantics,
probably because
  the callee expresses an intention (no ssp) and the caller should respect it.

LangRef needs to be clarified. Sent https://reviews.llvm.org/D93422

Thanks for the improved docs!

The behavior change was due to https://reviews.llvm.org/D91816 . How
does Zig end up merging the attributes
while the inliner blocks such inlining?

Here is the zig code in question:

fn llshl(r: []Limb, a: []const Limb, shift: usize) void {
     @setRuntimeSafety(debug_safety);
...
         r[dst_i] = carry | @call(.{ .modifier = .always_inline }, math.shr, .{
...

So we have an "always inline" call from a function which has runtime safety disabled. Currently, having runtime safety off for a function means that it does not get "sspstrong" attribute.

In this particular code snippet the ideal behavior from zig's perspective would be if LLVM allowed the always_inline without the ssp attribute, and then did the thing that the new docs mention:

> If a function with
> the ``sspstrong`` attribute is inlined into a function with the
> ``ssp`` attribute, the attribute in the caller will upgrade to
> ``sspstrong``.

Otherwise, Zig will need to keep track of callees and then recursively figure out whether an inline call prevents omitting stack protection attributes. Doable, but repeating the work the LLVM pass is already doing.

If a caller without ssp inlines a callee with ssp:
   the caller may alter %gs for its own purposes
   and will break the %gs usage in the callee.
(This one is required by the Linux kernel).

Wait a minute though, I don't understand why this needs to be the case. If a caller without ssp inlines a callee with it, then it seems to me that it should remove the ssp attribute from the callee at the callsite.

The %gs/Linux thing makes it sound like ssp is an ABI guarantee rather than a safety mechanism.

I suppose "always inline" should be implemented by zig itself before generating LLVM IR, and then it will get to decide how inlining works.

Anyway, thanks for your time and for the information.
Andrew

> LangRef needs to be clarified. Sent ⚙ D93422 [LangRef] Update new ssp/sspstrong/sspreq semantics after D91816
>

Thanks for the improved docs!

> The behavior change was due to ⚙ D91816 [Inline] prevent inlining on stack protector mismatch . How
> does Zig end up merging the attributes
> while the inliner blocks such inlining?

Here is the zig code in question:

fn llshl(r: Limb, a: const Limb, shift: usize) void {
     @setRuntimeSafety(debug_safety);
...
         r[dst_i] = carry | @call(.{ .modifier = .always_inline },
math.shr, .{
...

So we have an "always inline" call from a function which has runtime
safety disabled. Currently, having runtime safety off for a function
means that it does not get "sspstrong" attribute.

Yes, alwaysinline takes precedence. (Updated my patch to mention this...)

In this particular code snippet the ideal behavior from zig's
perspective would be if LLVM allowed the always_inline without the ssp
attribute, and then did the thing that the new docs mention:

> If a function with
> the ``sspstrong`` attribute is inlined into a function with the
> ``ssp`` attribute, the attribute in the caller will upgrade to
> ``sspstrong``.

Otherwise, Zig will need to keep track of callees and then recursively
figure out whether an inline call prevents omitting stack protection
attributes. Doable, but repeating the work the LLVM pass is already doing.

> If a caller without ssp inlines a callee with ssp:
> the caller may alter %gs for its own purposes
> and will break the %gs usage in the callee.
> (This one is required by the Linux kernel).

Wait a minute though, I don't understand why this needs to be the case.
If a caller without ssp inlines a callee with it, then it seems to me
that it should remove the ssp attribute from the callee at the callsite.

The %gs/Linux thing makes it sound like ssp is an ABI guarantee rather
than a safety mechanism.

I think the kernel idea is that the caller (no ssp) may not have set
%gs (during its initialization process), so upgrading from nossp to
ssp
may break the caller.
The kernel thread
https://lore.kernel.org/linux-pm/20200915172658.1432732-1-rkir@google.com/T/#u
is long and I haven't read it through, though....

I think your Zig issue may be an undiscovered bug. Can you file a bug
and attach the .ll file?

The new Clang behavior also disallows a nossp callee from being
inlined into a ssp caller. That makes the rules easier to explain but
I haven't thought very clearly about the implications though.