Memcpy segfaulting с действительными указателями - PullRequest
1 голос
/ 28 июля 2011

Я использую libcurl в своей программе и работаю с segfault.До того, как я подал ошибку в проекте curl, я подумал, что мне нужно немного отладить.То, что я обнаружил, показалось мне очень странным, и я пока не смог понять его.

Во-первых, обратная связь от segfault:

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fffe77f6700 (LWP 592)]
0x00007ffff6a2ea5c in memcpy () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) bt
#0  0x00007ffff6a2ea5c in memcpy () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x00007ffff5bc29e5 in x509_name_oneline (a=0x7fffe3d9c3c0,
    buf=0x7fffe77f4ec0 "C=US; O=The Go Daddy Group, Inc.; OU=Go Daddy Class 2 Certification Authority\375\034<M_r\206\233\261\310\340\371\023.Jg\205\244\304\325\347\372\016#9Ph%", size=255) at ssluse.c:629
#2  0x00007ffff5bc2a6f in cert_verify_callback (ok=1, ctx=0x7fffe77f50b0)
    at ssluse.c:645
#3  0x00007ffff72c9a80 in ?? () from /lib/libcrypto.so.0.9.8
#4  0x00007ffff72ca430 in X509_verify_cert () from /lib/libcrypto.so.0.9.8
#5  0x00007ffff759af58 in ssl_verify_cert_chain () from /lib/libssl.so.0.9.8
#6  0x00007ffff75809f3 in ssl3_get_server_certificate ()
   from /lib/libssl.so.0.9.8
#7  0x00007ffff7583e50 in ssl3_connect () from /lib/libssl.so.0.9.8
#8  0x00007ffff5bc48f0 in ossl_connect_step2 (conn=0x7fffe315e9a8, sockindex=0)
    at ssluse.c:1724
#9  0x00007ffff5bc700f in ossl_connect_common (conn=0x7fffe315e9a8,
    sockindex=0, nonblocking=false, done=0x7fffe77f543f) at ssluse.c:2498
#10 0x00007ffff5bc7172 in Curl_ossl_connect (conn=0x7fffe315e9a8, sockindex=0)
    at ssluse.c:2544
#11 0x00007ffff5ba76b9 in Curl_ssl_connect (conn=0x7fffe315e9a8, sockindex=0)
...

Вызов memcpy выглядит следующим образомthis:

  memcpy(buf, biomem->data, size);
(gdb) p buf
$46 = 0x7fffe77f4ec0 "C=US; O=The Go Daddy Group, Inc.; OU=Go Daddy Class 2 Certification Authority\375\034<M_r\206\233\261\310\340\371\023.Jg\205\244\304\325\347\372\016#9Ph%"
(gdb) p biomem->data
$47 = 0x7fffe3e1ef60 "C=US; O=The Go Daddy Group, Inc.; OU=Go Daddy Class 2 Certification Authority\375\034<M_r\206\233\261\310\340\371\023.Jg\205\244\304\325\347\372\016#9Ph%"
(gdb) p size
$48 = 255

Если я поднимаюсь по фрейму, я вижу, что указатель, переданный для buf, получен из локальной переменной, определенной в вызывающей функции:

char buf[256];

Вот где этоначинает становиться странным.Я могу вручную проверить все 256 байтов как buf, так и biomem-> data, а GDB не будет жаловаться, что память недоступна.Я также могу вручную записать все 256 байтов buf, используя команду gdb set, без каких-либо ошибок.Так что, если вся задействованная память доступна для чтения и записи, почему не работает memcpy?

Также интересно то, что я могу использовать gdb для ручного вызова memcpy с задействованными указателями.Пока я передаю размер <= 160, он работает без проблем.Как только я прохожу 161 или выше, GDB получает sigsegv.Я знаю, что buf больше 160, потому что он был создан в стеке как массив из 256. biomem-> data немного сложнее для определения, но я могу хорошо прочитать байт 160 с помощью gdb.

IСледует также отметить, что эта функция (или, скорее, вызываемый мной метод curl) успешно завершается много раз до сбоя.Моя программа использует curl для многократного вызова API веб-службы во время работы.Он вызывает API каждые пять секунд или около того и работает около 14 часов, прежде чем происходит сбой.Возможно, что-то еще в моем приложении записывает за пределы и топает что-то, что создает условие ошибки.Но кажется подозрительным, что каждый раз происходит сбой в одной и той же точке, хотя время варьируется.И все указатели в GDB выглядят нормально, но memcpy по-прежнему не работает.Valgrind не находит никаких ошибок границ, но я не позволяю моей программе работать с valgrind в течение 14 часов.

Внутри самого memcpy разборка выглядит так:

(gdb) x/20i $rip-10
   0x7ffff6a2ea52 <memcpy+242>: jbe    0x7ffff6a2ea74 <memcpy+276>
   0x7ffff6a2ea54 <memcpy+244>: lea    0x20(%rdi),%rdi
   0x7ffff6a2ea58 <memcpy+248>: je     0x7ffff6a2ea90 <memcpy+304>
   0x7ffff6a2ea5a <memcpy+250>: dec    %ecx
=> 0x7ffff6a2ea5c <memcpy+252>: mov    (%rsi),%rax
   0x7ffff6a2ea5f <memcpy+255>: mov    0x8(%rsi),%r8
   0x7ffff6a2ea63 <memcpy+259>: mov    0x10(%rsi),%r9
   0x7ffff6a2ea67 <memcpy+263>: mov    0x18(%rsi),%r10
   0x7ffff6a2ea6b <memcpy+267>: mov    %rax,(%rdi)
   0x7ffff6a2ea6e <memcpy+270>: mov    %r8,0x8(%rdi)
   0x7ffff6a2ea72 <memcpy+274>: mov    %r9,0x10(%rdi)
   0x7ffff6a2ea76 <memcpy+278>: mov    %r10,0x18(%rdi)
   0x7ffff6a2ea7a <memcpy+282>: lea    0x20(%rsi),%rsi
   0x7ffff6a2ea7e <memcpy+286>: lea    0x20(%rdi),%rdi
   0x7ffff6a2ea82 <memcpy+290>: jne    0x7ffff6a2ea30 <memcpy+208>
   0x7ffff6a2ea84 <memcpy+292>: data32 data32 nopw %cs:0x0(%rax,%rax,1)
   0x7ffff6a2ea90 <memcpy+304>: and    $0x1f,%edx
   0x7ffff6a2ea93 <memcpy+307>: mov    -0x8(%rsp),%rax
   0x7ffff6a2ea98 <memcpy+312>: jne    0x7ffff6a2e969 <memcpy+9>
   0x7ffff6a2ea9e <memcpy+318>: repz retq
(gdb) info registers
rax            0x0      0
rbx            0x7fffe77f50b0   140737077268656
rcx            0x1      1
rdx            0xff     255
rsi            0x7fffe3e1f000   140737016623104
rdi            0x7fffe77f4f60   140737077268320
rbp            0x7fffe77f4e90   0x7fffe77f4e90
rsp            0x7fffe77f4e48   0x7fffe77f4e48
r8             0x11     17
r9             0x10     16
r10            0x1      1
r11            0x7ffff6a28f7a   140737331236730
r12            0x7fffe3dde490   140737016358032
r13            0x7ffff5bc2a0c   140737316137484
r14            0x7fffe3d69b50   140737015880528
r15            0x0      0
rip            0x7ffff6a2ea5c   0x7ffff6a2ea5c <memcpy+252>
eflags         0x10203  [ CF IF RF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb) p/x $rsi
$50 = 0x7fffe3e1f000
(gdb) x/20x $rsi
0x7fffe3e1f000: 0x00000000      0x00000000      0x00000000      0x00000000
0x7fffe3e1f010: 0x00000000      0x00000000      0x00000000      0x00000000
0x7fffe3e1f020: 0x00000000      0x00000000      0x00000000      0x00000000
0x7fffe3e1f030: 0x00000000      0x00000000      0x00000000      0x00000000
0x7fffe3e1f040: 0x00000000      0x00000000      0x00000000      0x00000000

IЯ использую libcurl версии 7.21.6, c-ares версии 1.7.4 и openssl версии 1.0.0d.Моя программа многопоточная, но я зарегистрировал обратные вызовы мьютекса с openssl.Программа работает на рабочем столе Ubuntu 11.04, 64-битная.libc составляет 2,13.

Ответы [ 2 ]

4 голосов
/ 31 июля 2011

Очевидно, libcurl перечитывает исходный буфер и входит в нечитаемую память (страница на 0x7fffe3e1f000 - вы можете убедиться, что память не читается, посмотрев на /proc/<pid>/maps для отлаживаемой программы).

Вот где это начинает становиться странным.Я могу вручную проверить все 256 байтов как
buf, так и biomem->data без gdb, жалуясь на то, что память недоступна.

Существует хорошо известный недостаток ядра Linux: даже для памятис PROT_NONE (и вызывает SIGSEGV при попытке прочитать его из самого процесса), попытка с GDB на ptrace(PEEK_DATA,...) завершается успешно.Это объясняет, почему вы можете исследовать 256 байтов исходного буфера в GDB, даже если только 96 из них фактически доступны.

Попробуйте запустить вашу программу под Valgrind, скорее всего, она скажет вам, что вы memcpyслишком большой буфер, выделенный для кучи.

1 голос
/ 28 июля 2011

Есть ли у вас возможность создания "зоны сминания"?

То есть, намеренно увеличивать размер двух буферов или в случае, если структура ставит дополнительный неиспользуемый элемент после пункта назначения?

Затем вы заполняете исходную помятость чем-то вроде «0xDEADBEEF», а пункт назначения с сомом - чем-то приятным.Если при каждом изменении назначения вам есть над чем работать.

256 немного наводит на размышления, есть ли вероятность того, что его можно будет трактовать как количество со знаком, становясь -1, а значит, очень большим?Не могу понять, как GDB не показывал бы это, но ...

...