Отладка 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=2
gcc заменяет вызовы известных функций их безопасными вариантами, когда это может определить размер. Так обстоит дело здесь, как мы увидим: 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
).