【问题标题】:clang-linux: reporting CFI errors without crashing. ftrap-function and -O2clang-linux:报告 CFI 错误而不崩溃。 ftrap 函数和 -O2
【发布时间】:2017-09-18 08:12:41
【问题描述】:

我正在尝试使用来自 clang manual 的 -ftrap-function 标志来捕获自定义处理程序中的 CFI (call frame information) 错误。

这是一个生成 CFI 错误的基本示例:

#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;
}

我使用这些与 CFI 相关的 clang 选项构建它:

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

当这个示例在没有优化的情况下构建时,它可以按我的意愿工作。输出:

catched
fooStuff
not printed when compiled with -O2

当我使用 -O2 选项构建它时出现问题:

catched
Trace/breakpoint trap (core dumped)

GDB 显示程序在 CatchCfi 返回后正在接收 SIGTRAP:

(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) 

如何解决这个问题? 我想知道是否有人有处理 ftrap-function 标志的成功案例?可能有一些特定的优化标志修复了这个错误? 谢谢。

【问题讨论】:

    标签: c++ linux optimization clang


    【解决方案1】:

    我已更新您的代码,使其按预期工作。我的环境没有引发SIGTRAP,因此我插入了__builtin_trap() 调用。正如@YSC 提到的,它是UB。在您的陷阱函数返回后程序无法继续,您必须将程序恢复到SIGTRAP 引发之前的众所周知的良好状态。

    #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;
    }
    

    【讨论】:

      【解决方案2】:

      由于ptr 是指向Bar 的指针,

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

      是未定义的行为,编译器不会按您期望的那样工作。

      【讨论】:

      • 我故意这样做是为了触发 CFI sanitizer。
      • @rkuksin 很抱歉,C++ 无话可说。一旦你有意或无意地违反规则,一旦调用Undefined Behaviour,就没有任何保证。您的程序可能会崩溃,或者可能会出现nasal demons。而且没有什么可以做的。
      • @YSC:我猜你还不知道 UBSan,它是当你点击 UB 时报告的编译器工具。非常好,因为您的编译器插入代码以防止 UB(并报告它)
      猜你喜欢
      • 1970-01-01
      • 2021-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多