Отладка neovim, переполнение буфера обнаружено в OS X 10.9 libc, требуется лучший способ его отладки

Прежде всего немного контекста: я пытаюсь отладить проблему, которая происходит с neovim, я не уверен, что это также происходит с простым vim, но это не так уж важно.

Несмотря на то, что репортер использует linux, а я использую OSX 10.9, я смог получить "похожее" поведение, используя специальные компиляторы + флаги:

Когда я использую либо gcc 4.8.2 или же gcc 4.9 (dev) В сочетании с небольшой оптимизацией, фортификацией и защитой от разрушения стека neovim вылетает при запуске.

$ edit CMakeLists.txt
$ ... -Wall -O1 -g -g3 -ggdb -mtune=generic -pipe -fstack-protector --param=ssp-buffer-size=4 -D_FORTIFY_SOURCE=2 -Wextra -pedantic -Wno-unused-parameter -std=gnu99 ...

$ make clean && make cmake CMAKE_EXTRA_FLAGS="-DCMAKE_C_COMPILER=/usr/local/bin/gcc-4.8" && make

Я пытался отладить его с lldb (gdb кажется, не дает никаких символов, даже после кодирования). Я дошел до этого:

составлено с gcc 4.9:

➜  neovim git:(fortify-and-stack-protector) ✗ lldb ./build/bin/nvim
Current executable set to './build/bin/nvim' (x86_64).
(lldb) run src/eval.c
Process 1295 launched: './build/bin/nvim' (x86_64)
Process 1295 stopped
* thread #1: tid = 0x242b4f, 0x00007fff93f34866 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread, stop reason = signal SIGABRT
    frame #0: 0x00007fff93f34866 libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`__pthread_kill + 10:
-> 0x7fff93f34866:  jae    0x7fff93f34870            ; __pthread_kill + 20
   0x7fff93f34868:  movq   %rax, %rdi
   0x7fff93f3486b:  jmpq   0x7fff93f31175            ; cerror_nocancel
   0x7fff93f34870:  ret
(lldb) bt
* thread #1: tid = 0x242b4f, 0x00007fff93f34866 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread, stop reason = signal SIGABRT
    frame #0: 0x00007fff93f34866 libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x00007fff91b6435c libsystem_pthread.dylib`pthread_kill + 92
    frame #2: 0x00007fff8ce68b1a libsystem_c.dylib`abort + 125
    frame #3: 0x00007fff8ce68c91 libsystem_c.dylib`abort_report_np + 181
    frame #4: 0x00007fff8ce8c860 libsystem_c.dylib`__chk_fail + 48
    frame #5: 0x00007fff8ce8c830 libsystem_c.dylib`__chk_fail_overflow + 16
    frame #6: 0x00007fff8ce8ca7f libsystem_c.dylib`__strcpy_chk + 83
    frame #7: 0x000000010002e1a6 nvim`call_user_func [inlined] add_nr_var(nr=1, name=<unavailable>, v=<unavailable>, dp=<unavailable>) + 42 at eval.c:18744
    frame #8: 0x000000010002e17c nvim`call_user_func(fp=0x000000010030bd30, argcount=0, argvars=0x00007fff5fbfed80, rettv=0x00007fff5fbfef50, firstline=1, lastline=1, selfdict=0x0000000000000000) + 425 at eval.c:18455
    frame #9: 0x000000010002ef33 nvim`call_func(funcname=<unavailable>, len=<unavailable>, rettv=0x00007fff5fbfef50, argcount=0, argvars=0x00007fff5fbfed80, firstline=1, lastline=1, doesrange=0x00007fff5fbfef44, evaluate=1, selfdict=0x0000000000000000) + 717 at eval.c:7363
    frame #10: 0x0000000100032d1a nvim`get_func_tv(name=0x000000010030be20, len=9, rettv=0x00007fff5fbfef50, arg=0x00007fff5fbfef48, firstline=1, lastline=1, doesrange=0x00007fff5fbfef44, evaluate=1, selfdict=0x0000000000000000) + 340 at eval.c:7222
    frame #11: 0x000000010003673e nvim`ex_call(eap=0x00007fff5fbff190) + 475 at eval.c:3086
    frame #12: 0x000000010005634b nvim`do_cmdline(cmdline=<unavailable>, fgetline=0x00000001000494e3, cookie=0x00007fff5fbff790, flags=7) + 13602 at ex_docmd.c:2103
    frame #13: 0x0000000100049d52 nvim`do_source(fname=0x000000010017bb3b, check_other=<unavailable>, is_vimrc=<unavailable>) + 1615 at ex_cmds2.c:2695
    frame #14: 0x00000001001702c3 nvim`main + 251 at main.c:2009
    frame #15: 0x00000001001701c8 nvim`main(argc=<unavailable>, argv=<unavailable>) + 5152 at main.c:1919
    frame #16: 0x00007fff8b32d5fd libdyld.dylib`start + 1
(lldb) frame variable
(lldb) frame info
frame #0: 0x00007fff93f34866 libsystem_kernel.dylib`__pthread_kill + 10
(lldb) frame select 7
frame #7: 0x000000010002e1a6 nvim`call_user_func [inlined] add_nr_var(nr=1, name=<unavailable>, v=<unavailable>, dp=<unavailable>) + 42 at eval.c:18744
   18741     */
   18742    static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr)
   18743    {
-> 18744      STRCPY(v->di_key, name);
   18745      v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
   18746      hash_add(&dp->dv_hashtab, DI2HIKEY(v));
   18747      v->di_tv.v_type = VAR_NUMBER;

составлено с gcc 4.8.2:

➜  neovim git:(fortify-and-stack-protector) ✗ lldb ./build/bin/nvim
Current executable set to './build/bin/nvim' (x86_64).
(lldb) rune
error: 'rune' is not a valid command.
(lldb) run
Process 3242 launched: './build/bin/nvim' (x86_64)
Process 3242 stopped
* thread #1: tid = 0x2454cb, 0x00007fff93f34866 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread, stop reason = signal SIGABRT
    frame #0: 0x00007fff93f34866 libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`__pthread_kill + 10:
-> 0x7fff93f34866:  jae    0x7fff93f34870            ; __pthread_kill + 20
   0x7fff93f34868:  movq   %rax, %rdi
   0x7fff93f3486b:  jmpq   0x7fff93f31175            ; cerror_nocancel
   0x7fff93f34870:  ret
(lldb) bt
* thread #1: tid = 0x2454cb, 0x00007fff93f34866 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread, stop reason = signal SIGABRT
    frame #0: 0x00007fff93f34866 libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x00007fff91b6435c libsystem_pthread.dylib`pthread_kill + 92
    frame #2: 0x00007fff8ce68b1a libsystem_c.dylib`abort + 125
    frame #3: 0x00007fff8ce68c91 libsystem_c.dylib`abort_report_np + 181
    frame #4: 0x00007fff8ce8c860 libsystem_c.dylib`__chk_fail + 48
    frame #5: 0x00007fff8ce8c830 libsystem_c.dylib`__chk_fail_overflow + 16
    frame #6: 0x00007fff8ce8ca7f libsystem_c.dylib`__strcpy_chk + 83
    frame #7: 0x000000010002a969 nvim`eval_init + 129 at eval.c:868
    frame #8: 0x0000000100089ffd nvim`main(argc=1, argv=0x00007fff5fbffa58) + 140 at main.c:175
    frame #9: 0x00007fff8b32d5fd libdyld.dylib`start + 1
    frame #10: 0x00007fff8b32d5fd libdyld.dylib`start + 1
(lldb) frame select 7
frame #7: 0x000000010002a969 nvim`eval_init + 129 at eval.c:868
   865
   866    for (i = 0; i < VV_LEN; ++i) {
   867      p = &vimvars[i];
-> 868      STRCPY(p->vv_di.di_key, p->vv_name);
   869      if (p->vv_flags & VV_RO)
   870        p->vv_di.di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
   871      else if (p->vv_flags & VV_RO_SBX)

Чтобы эта проблема появилась, должна быть некоторая оптимизация, однако это означает, что компилятор встроит вещи и выбросит аргументы, что раздражает и не позволяет мне сразу увидеть самые важные вещи. Будет ли комбинация флагов, которые я мог бы попробовать, чтобы сохранить проблему, но позволить лучшую отладку?

  • Это вероятно ошибка компилятора? Кажется, Clang этого как-то избегает, и GCC тоже, когда я отключаю оптимизацию.
  • gcc 4.8.2 и gcc 4.9 дают похожую ошибку (оба раза в STRCPY) но в разных местах это беспокоит меня еще больше.
  • Где код для __chk_fail_overflow а также __chk_overlap? Их вызывают strcpy_chk который я предполагаю, это замена strcpy который вставляется при компиляции с -D_FORTIFY_SOURCE=2, Я не смог понять это. Я добавил этот репозиторий: https://github.com/aosm/Libc который выглядит как libx для OSX 10.9, как я пытался проверить на собственном сайте Apple с открытым исходным кодом.
  • Как компилятор решает, что третий аргумент strcpy_chk является?! Первоначальный вызов STRCPY не включает в себя информацию о размере:

,

STRCPY(v->di_key, name);
// I think gcc/clang replace this with:
__strcpy_chk(v->di_key, name, SOME_MAGIC_SIZE);

Я надеюсь, что некоторые из переполнения стека гуру могли бы дать мне несколько советов / подсказок о том, что мне делать дальше!

РЕДАКТИРОВАТЬ: я был в состоянии скомпилировать с -Og а также gcc 4.8.2 и до сих пор провоцируют ошибку, надеюсь, это даст дополнительную информацию.

1 ответ

Таким образом, я не мог удержаться от копания дальше, и, наконец, мне пришла в голову мысль посмотреть на стек + регистры при поступлении страшных __strcpy_chkгде я нашел:

(lldb) register read
General Purpose Registers:
       rax = 0x0000000000000057
       rbx = 0x00000001001d3900  vimvars
       rcx = 0x6300f1e7a96add52
       rdx = 0x0000000000000001 /* this is probably the size parameter */
       rdi = 0x00000001001d3919  vimvars + 25
       rsi = 0x000000010016730e  "count"

Итак, GCC сделал вывод, что размер dst параметр был 1и передает это __strcpy_chk, Итак, из-за -D_FORTIFY_SOURCE=2gcc заменяет вызовы известных функций их безопасными вариантами, когда это может определить размер. Так обстоит дело здесь, как мы увидим: dst параметр является vv_di.di_key поле этой структуры:

static struct vimvar {
  char        *vv_name;         /* name of variable, without v: */
  dictitem_T vv_di;             /* value and name for key */
  char vv_filler[16];           /* space for LONGEST name below!!! */
  char vv_flags;                /* VV_COMPAT, VV_RO, VV_RO_SBX */
}

struct dictitem_S {
  typval_T di_tv;               /* type and value of the variable */
  char_u di_flags;              /* flags (only used for variable) */
  char_u di_key[1];             /* key (actually longer!) */
}

Который имеет размер 1. Gcc, конечно, не понимает, что это было как-то преднамеренно: флаг vv_filler предназначен для хранения фактической строки. Вот почему комментарий говорит, что на самом деле больше. vv_filler следует сразу за di_key в структуре.

Я не могу винить компилятор за это, это имеет смысл. Кто-то в проекте neovim занят восстановлением переводчика, чтобы стать переводчиком с VimL на lua. "Глупое" исправление на данный момент - отключить обогащение источника или понизить его до -D_FORTIFY_SOURCE=1, что должно помешать gcc сделать эти предположения. Хотя в идеале мы сможем это исправить, сохранив -D_FORTIFY_SOURCE=2 и не регресс в производительности (vimvar это, конечно, часто используемая структура).

У меня есть несколько дополнительных вопросов. Является ли то, что кодовая база vim делает здесь законным? Были некоторые дискуссии о неовим трекере, где некоторые комментаторы предположили, что полагаться на это было совершенно неопределенным. Один аргумент, который мне показался интересным, заключался в том, что между концом struct dictitem_S а также vv_filler (Дополнение). Комментатор утверждал, что это пространство было незаконным (UB). Чтение соответствующих документов C99 не дает немедленной ясности в этом конкретном случае. Разрушение структуры разрешено, но мы не были уверены, разрешено ли это для вложенных структур. то есть: структура взлома появляется в конце dictitem_S, но определенно не в конце содержащей его структуры (vimvar).

Другие вопросы по тегам