LLVM Bugzilla is read-only and represents the historical archive of all LLVM issues filled before November 26, 2021. Use github to submit LLVM bugs

Bug 16821 - Infinite loops in generated assembly when using -D_FORTIFY_SOURCE=2 and -O1
Summary: Infinite loops in generated assembly when using -D_FORTIFY_SOURCE=2 and -O1
Status: NEW
Alias: None
Product: clang
Classification: Unclassified
Component: C++ (show other bugs)
Version: trunk
Hardware: PC Linux
: P normal
Assignee: Unassigned Clang Bugs
URL:
Keywords:
: 17054 18775 (view as bug list)
Depends on:
Blocks:
 
Reported: 2013-08-07 09:35 PDT by Zan Dobersek
Modified: 2019-11-04 08:54 PST (History)
12 users (show)

See Also:
Fixed By Commit(s):


Attachments
Test case (603 bytes, text/x-c++src)
2013-08-07 09:35 PDT, Zan Dobersek
Details
Preprocessed test case (77.24 KB, application/octet-stream)
2013-08-13 12:50 PDT, Zan Dobersek
Details
LLVM representation of the test case (3.29 KB, application/octet-stream)
2013-08-13 12:51 PDT, Zan Dobersek
Details
Assembly output for the test case (1.34 KB, application/octet-stream)
2013-08-13 12:54 PDT, Zan Dobersek
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Zan Dobersek 2013-08-07 09:35:39 PDT
Created attachment 10999 [details]
Test case

Attached is a reduced and simplified test case (originating from WebKit) that loops infinitely when compiled compiled with Clang and specifying both -D_FORTIFY_SOURCE=2 and -O1 on the command line.

Breaking the loop under GDB stops in the randomValue function and disassembly sure enough shows the problem:
-----
Program received signal SIGINT, Interrupt.
0x0000000000400620 in randomValue(unsigned char*, unsigned long) ()
(gdb) disassemble 
Dump of assembler code for function _Z11randomValuePhm:
   0x0000000000400600 <+0>:	push   %rax
   0x0000000000400601 <+1>:	mov    $0x4006f4,%edi
   0x0000000000400606 <+6>:	xor    %esi,%esi
   0x0000000000400608 <+8>:	xor    %edx,%edx
   0x000000000040060a <+10>:	xor    %eax,%eax
   0x000000000040060c <+12>:	callq  0x4004f0 <open@plt>
   0x0000000000400611 <+17>:	test   %eax,%eax
   0x0000000000400613 <+19>:	js     0x400622 <_Z11randomValuePhm+34>
   0x0000000000400615 <+21>:	data32 nopw %cs:0x0(%rax,%rax,1)
=> 0x0000000000400620 <+32>:	jmp    0x400620 <_Z11randomValuePhm+32>
   0x0000000000400622 <+34>:	callq  0x4005f0 <WTFCrash>
End of assembler dump.
-----

The clang command used:
clang++ -D_FORTIFY_SOURCE=2 -O2 -stdlib=libstdc++ -o test test.cpp

Notes:
- setting _FORTIFY_SOURCE to 0 or disabling any optimizations fixes the problem,
- the problem persists when specifying any other non-null level of optimization,
- I'm using Clang 3.4 (trunk) on Ubuntu 13.04, hence also the libstdc++ choice,
- I've experienced the same problem with older versions of Clang, from at least 3.0 onwards.
Comment 1 Eli Friedman 2013-08-12 18:19:38 PDT
Please attach preprocessed source.
Comment 2 Zan Dobersek 2013-08-13 12:50:58 PDT
Created attachment 11030 [details]
Preprocessed test case

The output of the following command:
clang++ -D_FORTIFY_SOURCE=2 -O2 -stdlib=libstdc++ -E -o test.preprocessed test.cpp
Comment 3 Zan Dobersek 2013-08-13 12:51:48 PDT
Created attachment 11031 [details]
LLVM representation of the test case

The output of the following command:
clang++ -D_FORTIFY_SOURCE=2 -O2 -stdlib=libstdc++ -S -emit-llvm -o test.llvm-rep test.cpp
Comment 4 Zan Dobersek 2013-08-13 12:54:14 PDT
Created attachment 11032 [details]
Assembly output for the test case

The output of the following command:
clang++ -D_FORTIFY_SOURCE=2 -O2 -stdlib=libstdc++ -S -o test.compiled test.cpp
Comment 5 Zan Dobersek 2013-08-13 12:55:26 PDT
FWIW, the same problem persists when using libc++.
Comment 6 Eli Friedman 2013-08-13 16:06:42 PDT
Rafael, this is probably yours; it's related to the CodeGenModule::isTriviallyRecursive hack.
Comment 7 Rafael Ávila de Espíndola 2013-08-13 17:12:42 PDT
(In reply to comment #6)
> Rafael, this is probably yours; it's related to the
> CodeGenModule::isTriviallyRecursive hack.

I think this is a case we are just not able to fully represent in the IL currently. The preprocessed source has what looks like

void bar(void);
extern void __foo_alias () __asm__ ("foo");
extern __inline void foo()
{
  bar();
  return __foo_alias ();
}
void *p;
void zed() {
  foo();
  p = foo;
}

where bar in here represents the checks added by -D D_FORTIFY_SOURCE=2.

The hack we have in clang tries to see these "fake recursive" functions and call the external one directly. I think it is not kicking in because of a lack of -fgnu89-inline in the command line, but that is not what we want in here as doing that will drop the checks the user is trying to add.


What gcc 4.8 -O0 produces in the above example is

        call    foo
        movq    $foo, p(%rip)

When optimizing a call to bar shows up, showing that the inline version of foo was used.

What I think we need is an IL like

declare void @bar()
declare void @foo()
define available_externally void @foo_inline() {
  call void @bar()
  call void @foo()
  ret void
}
@p = common global i8* null, align 8
define void @zed() {
  call void @foo_inline()
  store i8* bitcast (void ()* @foo to i8*), i8** @p, align 8
  ret void
}

the logic being:

* the declaration of  __foo_alias produces foo in the llvm IR because of the ASM.
* the definition of an gnu inline foo in C when a declaration of foo is already present (because of the ASM) causes clang to emit an available_externally definition with an unique name (foo_inline in the above example).
* calls to __foo_alias in C produce calls to foo in the IR.
* call to foo in C produce calls to foo_inline in the IR.
* Other user of foo in C use foo in the IR.

This would work for the original testcase since the inline function is marked always_inline, but in general we need something like

define available_externally void @foo_inline() external @foo {

to tell codegen to produce a reference to foo if foo_inline is not inlined.
Comment 8 Eli Friedman 2013-08-13 19:15:21 PDT
(In reply to comment #7)
> The hack we have in clang tries to see these "fake recursive" functions and
> call the external one directly. I think it is not kicking in because of a
> lack of -fgnu89-inline in the command line,

This is C++; -fgnu89-inline wouldn't have any effect.
Comment 9 Rafael Ávila de Espíndola 2013-08-13 19:33:19 PDT
(In reply to comment #8)
> (In reply to comment #7)
> > The hack we have in clang tries to see these "fake recursive" functions and
> > call the external one directly. I think it is not kicking in because of a
> > lack of -fgnu89-inline in the command line,
> 
> This is C++; -fgnu89-inline wouldn't have any effect.

The glibc source code has:

#  define __extern_always_inline \
  extern __always_inline __attribute__ ((__gnu_inline__))

...

#define __fortify_function __extern_always_inline __attribute_artificial__


but I guess the glibc being used in this test is older or clang is not getting there because of ifdefs (there is no gnu_inline in the testcase).

With c++ semantics this glibc trick becomes *really* odd, since we get a linkonce_odr function in on TU that is different from a strong function in another TU.
Comment 10 Eli Friedman 2013-09-02 20:49:10 PDT
*** Bug 17054 has been marked as a duplicate of this bug. ***
Comment 11 manjian 2013-09-07 03:48:54 PDT
When will this bug get fixed?
(In reply to comment #10)
> *** Bug 17054 has been marked as a duplicate of this bug. ***
Comment 12 manjian 2013-09-09 22:39:06 PDT
I have read the code.And actually thinking clang is the one generating the right code.
Comment 13 Richard Smith 2014-02-09 00:51:52 PST
*** Bug 18775 has been marked as a duplicate of this bug. ***
Comment 14 Kostya Serebryany 2014-02-10 00:46:21 PST
So, is this a bug in the fresh libc headers or in clang? 
If in clang, any hope to fix? 
This is blocking the build of Chromium on Ubuntu 13.10 and newer.
Comment 15 Rafael Ávila de Espíndola 2014-02-10 08:18:18 PST
(In reply to comment #14)
> So, is this a bug in the fresh libc headers or in clang? 
> If in clang, any hope to fix? 
> This is blocking the build of Chromium on Ubuntu 13.10 and newer.

I would call it a messing feature in clang. I have changed my opinion on how to implement this. I think that given

void bar(void);
extern void __foo_alias () __asm__ ("foo");
extern __inline void foo()
{
  bar();
  return __foo_alias ();
}
void *p;
void zed() {
  foo();
  p = foo;
}


clang should produce

declare void @bar()
declare void @foo()
define internal void @foo_inline() {
  call void @bar()
  call void @foo()
  ret void
}
@p = common global i8* null, align 8
define void @zed() {
  call void @foo_inline()
  store i8* bitcast (void ()* @foo_inline to i8*), i8** @p, align 8
  ret void
}

This is not exactly the same thing that gcc produces: gcc will simply not use the wrapper if it is not a direct call or if it is not inlined. Hopefully all users will consider this to be a bug and not a feature of how this implemented in gcc.

The advantages are:
* in the above example bar is always called before foo
* we don't need any new features in llvm
Comment 16 Sylvestre Ledru 2014-02-19 11:05:31 PST
An other test case:

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main ()
{
    char *data;
    int  pagesize;
    int fd, foo;
    pagesize = getpagesize ();
    fd = open ("conftest.mmap", O_RDWR);
    data = (char *) malloc (pagesize);
    foo = read (fd, data, pagesize);
}

clang++ -o conftest -g  -O2  -D_FORTIFY_SOURCE=2 conftest.cpp

In the asm generated, clang will replace the call by __read_chk while
g++ uses read.
Comment 17 Alexander Potapenko 2014-05-12 06:47:27 PDT
Am I right this is a Linux-only bug because OSX uses different system headers?
Comment 18 Sylvestre Ledru 2014-05-12 06:55:57 PDT
Yes, I don't think Mac OS X has fortify sources
Comment 19 Alexander Potapenko 2014-05-12 07:52:27 PDT
It actually does, there's a _FORTIFY_SOURCE definition in _types.h that controls _USE_FORTIFY_LEVEL. But the implementation seems to differ, so I wonder whether the issue in question applies to OSX.
Comment 20 Sylvestre Ledru 2014-05-12 07:56:25 PDT
OK. My bad.

I just tested on my Debian Jessie (currently testing) and I cannot reproduce my test case...
Comment 21 Zachary Wasserman 2015-02-13 14:56:58 PST
We also found a problem with this on CentOS7 and clang++ 3.4.2. Below is the minimal test case where we can see that the __readlink_chk function isn't called at all and is instead replaced by the infinite loop.

#include <unistd.h>

int main() {
  auto path_max = pathconf(0, _PC_PATH_MAX);
  auto result_heap = (char*)malloc(path_max);
  auto size = readlink(0, result_heap, path_max);
}

$ /usr/bin/clang++ -O1 -D_FORTIFY_SOURCE=1 -o test -std=c++11 test.cpp

$ gdb -batch -ex 'file ./test' -ex 'disassemble main'
Dump of assembler code for function main:
   0x0000000000400640 <+0>:     push   %rax
   0x0000000000400641 <+1>:     xor    %edi,%edi
   0x0000000000400643 <+3>:     mov    $0x4,%esi
   0x0000000000400648 <+8>:     callq  0x400540 <pathconf@plt>
   0x000000000040064d <+13>:    mov    %rax,%rdi
   0x0000000000400650 <+16>:    callq  0x400520 <malloc@plt>
   0x0000000000400655 <+21>:    data32 nopw %cs:0x0(%rax,%rax,1)
   0x0000000000400660 <+32>:    jmp    0x400660 <main+32>
End of assembler dump.