2011-07-28 3 views
0

내 프로그램에서 libcurl을 사용 중이며 segfault로 실행 중입니다. 컬 프로젝트에 버그를 제기하기 전에 약간의 디버깅을 할 것이라고 생각했습니다. 내가 찾은 것은 나에게 매우 이상하게 보였고 나는 아직 그것을 이해할 수 없었다.Memcpy 유효한 포인터로 segfaulting

첫째, 세그먼트 폴트의 역 추적 :

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

호출 함수에 정의 된 지역 변수 :

char buf[256]; 

여기가 이상하게 들릴 곳이 있습니다. 나는 메모리가 접근 가능하지 않다는 불평을하지 않고 buf와 biomem-> data의 256 바이트를 수동으로 검사 할 수있다. 또한 오류없이 gdb set 명령을 사용하여 256 바이트의 buf를 수동으로 작성할 수 있습니다. 따라서 관련된 모든 메모리가 읽기 쉽고 쓰기 가능한 경우 memcpy가 실패하는 이유는 무엇입니까?

흥미로운 점은 gdb를 사용하여 포인터가 포함 된 memcpy를 수동으로 호출 할 수 있다는 것입니다. 크기가 < = 160이면 문제없이 실행됩니다. 161 이상을 통과하자마자 gdb는 sigsegv를 얻습니다. buf가 256보다 큰 스택을 생성했기 때문에 buf가 160보다 큰 것을 알고 있습니다. biomem-> data는 그림을 그리는 것이 조금 어렵지만, gdb를 사용하여 바이트 160을 훨씬 앞선 것으로 읽을 수 있습니다.

또한이 함수 (또는이 호출의 컬 메소드)가 크래시 전에 여러 번 성공적으로 완료되었음을 언급해야합니다. 내 프로그램은 curl을 사용하여 웹 서비스 API가 실행되는 동안 반복적으로 호출합니다. API가 5 초마다 호출되며 충돌하기 전에 약 14 시간 동안 실행됩니다. 내 응용 프로그램의 다른 무언가가 범위를 벗어나서 오류 조건을 만드는 무언가를 쾅쾅 치고있을 가능성이 있습니다. 그러나 타이밍이 다를지라도 매번 정확히 같은 시점에 충돌하는 것은 의심스러운 것 같습니다. 그리고 모든 포인터는 gdb에서 괜찮아 보이지만 memcpy는 여전히 실패합니다. Valgrind는 경계 오류를 찾지 못하지만 14 시간 동안 valgrind로 프로그램을 실행시키지 않았습니다. 방어 적이기 자체 내에서

은 분해는 다음과 같습니다

(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 

내가 libcurl에 버전 7.21.6, C-아르 버전 1.7.4 및하려면 openssl 버전 1.0.0d을 사용하고 있습니다. 내 프로그램은 멀티 스레드이지만 openssl에 뮤텍스 콜백을 등록했습니다. 이 프로그램은 Ubuntu 11.04 데스크탑 (64 비트)에서 실행됩니다. libc는 2.13입니다.

+0

gdb를 신뢰하지 마십시오. 모든 것을 다시 확인하십시오. gdb가 메모리 영역을 읽거나 쓸 수있는 영역이라면 해당 영역이 유효하다는 의미는 아닙니다. 불가능할 경우 지역이 반드시 유효하지는 않습니다. 스레드가 보여주는 스레드가 항상 중지/크래시 된 스레드는 아닙니다. 나는 이것을 몇 번이나, 특히 6.x뿐만 아니라 7.x에도 물 렸습니다. –

+0

16 진수가 아닌 10 진수를 테스트 했습니까? –

+0

크기가 256보다 작습니까? –

답변

1

"부서지기 쉬운 구역"을 만들 수 있습니까?

즉, 의도적으로 두 버퍼의 크기를 늘리거나 대상 다음에 여분의 미사용 요소를 넣는 구조의 경우에는?

그런 다음 "0xDEADBEEF"와 같은 소스 crumple을 시드하고 som과 함께 멋진 대상을 시드합니다. 목적지가 모두 바뀌면 작업 할 수있는 것이 있습니다.

256 조금 암시 적이기 때문에, 어떻게 든 서명 된 수량으로 취급 될 수있는 가능성이 있으므로 -1이되고 따라서 매우 커질 수 있습니까? gdb가 어떻게 보이지 않을지 모르지만 ...소스 버퍼를 읽는 동안-, 읽을 메모리에 스테핑

+0

libcurl에 디버그 코드를 추가하여 crumple 영역을 만들려고합니다. – ryan

3

은 분명히 libcurl이다 (0x7fffe3e1f000에서 페이지 - 당신은 메모리 디버깅중인 프로그램에 대한 /proc/<pid>/maps보고 읽을 확인할 수 있습니다).

여기가 이상해지기 시작한 곳입니다. 나는 메모리가 접근 가능하지 않다는 불평을하지 않고 수동으로
buf와 biomem->data의 모든 256 바이트를 검사 할 수있다. 심지어 PROT_NONE을 가지고 메모리 (및 프로세스 자체에서 읽어 시도에서 SIGSEGV 원인) ptrace(PEEK_DATA,...) 성공에 GDB에 의해, 시도 :

이 잘 알려진 리눅스 커널 결함이다. 이것은 GDB에서 소스 버퍼의 256 바이트를 검사 할 수있는 이유를 설명합니다.

Valgrind에서 프로그램을 실행하면 memcpy이 너무 작음을 알 수 있습니다.

+0

아, 포인터를 확인하는 데 gdb를 사용할 수 없다는 것을 알고있는 것이 좋습니다. 나는 그것을 어떻게 놓쳤는 지 모른다. 그러나 소스 버퍼 (biomem-> data)는 어리석은 길이 (biomem-> length)의 0x7fff000000a5를 갖는다. biomem 구조체를 채우는 OpenSSL 메서드는 또한 쓰여진 바이트 수를 반환합니다. biomem-> length와 리턴 값 중 작은 것을 사용하도록 libcurl을 수정했을 때, 문제는 사라졌습니다. 하지만 libcurl이 biomem-> length에 의존 할 수 있어야하기 때문에 이것이 유효한 수정이라고 생각하지 않습니다. 아직도 biomem-> 길이가 손상되는 곳을 찾아 파기. – ryan