2

I'm trying to use -ftrap-function flag from clang manual to catch CFI (call frame information) errors in a custom handler.

Here is a basic example generating a CFI error:

#include <stdio.h>
#include <stdlib.h>

__attribute__((used)) extern "C" void CatchCfi() {
  printf("catched\n");
}

struct Foo {
  Foo(const char* s) : command(s) {}
  virtual ~Foo() {}

  void fooStuff() { printf("fooStuff\n"); }

  const char* command;
};

struct Bar {
  Bar(const char* s) : name(s) {}
  virtual ~Bar() {}

  void barStuff() { printf("barStuff\n"); }

  const char* name;
};

enum class WhichObject { FooObject, BarObject };

static void* allocator(WhichObject w, const char* arg) {
  switch (w) {
    case WhichObject::FooObject:
      return new Foo(arg);
    case WhichObject::BarObject:
      return new Bar(arg);
  }
}

int main(int argc, const char* argv[]) {
  void* ptr = nullptr;
  (void)(argc);
  (void)(argv);

  ptr = allocator(WhichObject::BarObject, "system(\"/bin/sh\")");

  Foo* fooptr = static_cast<Foo*>(ptr);
  fooptr->fooStuff();

  printf("not printed when compiled with -O2\n");
  return 0;
}

I build it with these CFI related clang options:

-ftrap-function=CatchCfi -fsanitize=cfi-vcall -fvisibility=hidden -fsanitize=cfi-derived-cast -fsanitize=cfi-unrelated-cast -flto=thin

When this example is built without optimization it works as I want. Output:

catched
fooStuff
not printed when compiled with -O2

The problem appear when I build it with -O2 option:

catched
Trace/breakpoint trap (core dumped)

GDB shows that the program is receiving SIGTRAP just after CatchCfi returns:

(gdb) r
Starting program: /home/romex/browser/src/out/debug/hello_cfi 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
catched

Program received signal SIGTRAP, Trace/breakpoint trap.
0x000000000020118a in ?? ()
(gdb) bt
#0  0x000000000020118a in ?? ()
#1  0x00000000002010f0 in frame_dummy ()
#2  0x00007ffff748e830 in __libc_start_main (main=0x201180 <main(int, char const**)>, argc=1, argv=0x7fffffffde18, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, 
    stack_end=0x7fffffffde08) at ../csu/libc-start.c:291
#3  0x0000000000201029 in _start ()
Warning: the current language does not match this frame.
(gdb) 

How to fix that? I'm wondering if somebody has a success story dealing with ftrap-function flag? May be there is some specific optimization flag fixing this error? Thanks.

YSC
  • 38,212
  • 9
  • 96
  • 149
rkuksin
  • 59
  • 5

2 Answers2

1

I've updated your code so it works as expected. My environment does not raise SIGTRAP, thus I inserted __builtin_trap() call. As mentioned @YSC it is UB. The program can not continue after your trap function returns, you must restore the program to well known good state before SIGTRAP raised.

#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

jmp_buf env;

__attribute__((used)) extern "C" void CatchCfi() {
  printf("catched\n");
  longjmp(env, 1);
}

struct Foo {
  Foo(const char* s) : command(s) {}
  virtual ~Foo() {}

  void fooStuff() { printf("fooStuff\n"); }

  const char* command;
};

struct Bar {
  Bar(const char* s) : name(s) {}
  virtual ~Bar() {}

  void barStuff() { printf("barStuff\n"); }

  const char* name;
};

enum class WhichObject { FooObject, BarObject };

static void* allocator(WhichObject w, const char* arg) {
  switch (w) {
    case WhichObject::FooObject:
      return new Foo(arg);
    case WhichObject::BarObject:
      return new Bar(arg);
  }
}

int main(int argc, const char* argv[]) {
  void* ptr = nullptr;
  (void)(argc);
  (void)(argv);

  ptr = allocator(WhichObject::BarObject, "system(\"/bin/sh\")");

  int val = setjmp(env);

  if (!val) {
    Foo* fooptr = static_cast<Foo*>(ptr);
    fooptr->fooStuff();
    __builtin_trap();
  }

  printf("not printed when compiled with -O2\n");
  return 0;
}
273K
  • 29,503
  • 10
  • 41
  • 64
-1

Since ptr is a pointer to a Bar,

Foo* fooptr = static_cast<Foo*>(ptr);
fooptr->fooStuff();

is undefined behavior and the compiler is not compelled to work as you expect him to.

YSC
  • 38,212
  • 9
  • 96
  • 149
  • I did this intentionally to trigger CFI sanitizer. – rkuksin Sep 18 '17 at 09:53
  • @rkuksin I'm sorry but C++ has nothing more to say. Once you break the rules, intentionally or not, once [Undefined Behaviour](http://en.cppreference.com/w/cpp/language/ub) is invoked, nothing is guaranteed. Your program may crash, or [nasal demons](http://www.catb.org/jargon/html/N/nasal-demons.html) may appear. And there's nothing that can be done. – YSC Sep 19 '17 at 09:08
  • @YSC: I guess you don't know UBSan yet, it's compiler instrumentation to report when you hit UB. So perfectly fine, as your compiler inserts code to prevent the UB (and report it as such) – JVApen Jul 26 '19 at 20:16