Как легко диагностировать проблемы из-за доступа к несопоставленным регионам mmap? - PullRequest
1 голос
/ 04 февраля 2020

Недавно я обнаружил segfault, о котором ни Valgrind, ни Address Sanitizer не могли дать никакой полезной информации. Это произошло из-за того, что неисправная программа munmap воспроизвела файл, а затем попыталась получить доступ к ранее mmap области педа.

Следующий пример демонстрирует проблему:

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

int main()
{
    const int fd=open("/tmp/test.txt", O_RDWR);
    if(fd<0) abort();
    const char buf[]="Hello";
    if(write(fd, buf, sizeof buf) != sizeof buf) abort();

    char*const volatile ptr=mmap(NULL,sizeof buf,PROT_READ,MAP_SHARED,fd,0);
    if(!ptr) abort();
    printf("1%c\n", ptr[0]);

    if(close(fd)<0) abort();
    printf("2%c\n", ptr[0]);

    if(munmap(ptr, sizeof buf)<0) abort();
    printf("3%c\n", ptr[0]); // Cause a segfault
}

с Address Sanitizer Я получаю следующий вывод:

1H
2H
AddressSanitizer:DEADLYSIGNAL
=================================================================
==8503==ERROR: AddressSanitizer: SEGV on unknown address 0x7fe7d0836000 (pc 0x55bda425c055 bp 0x7ffda5887210 sp 0x7ffda5887140 T0)
==8503==The signal is caused by a READ memory access.
    #0 0x55bda425c054 in main /tmp/test/test1.c:22
    #1 0x7fe7cf64fb96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
    #2 0x55bda425bcd9 in _start (/tmp/test/test1+0xcd9)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /tmp/test/test1.c:22 in main

И вот соответствующая часть вывода с Valgrind:

1H
2H
==8863== Invalid read of size 1
==8863==    at 0x108940: main (test1.c:22)
==8863==  Address 0x4029000 is not stack'd, malloc'd or (recently) free'd
==8863== 
==8863== 
==8863== Process terminating with default action of signal 11 (SIGSEGV)
==8863==  Access not within mapped region at address 0x4029000
==8863==    at 0x108940: main (test1.c:22)

Сравните это со случаем, когда к malloc ed-области обращаются после free. Тестовая программа:

#include <stdio.h>
#include <string.h>
#include <malloc.h>

int main()
{
    const char buf[]="Hello";
    char*const volatile ptr=malloc(sizeof buf);
    if(!ptr)
    {
        fprintf(stderr, "malloc failed");
        return 1;
    }
    memcpy(ptr,buf,sizeof buf);
    printf("1%c\n", ptr[0]);
    free(ptr);
    printf("2%c\n", ptr[0]); // Cause a segfault
}

Вывод с адресом Sanitizer:

1H
=================================================================
==7057==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010 at pc 0x55b8f96b5003 bp 0x7ffff5179b70 sp 0x7ffff5179b60
READ of size 1 at 0x602000000010 thread T0
    #0 0x55b8f96b5002 in main /tmp/test/test1.c:17
    #1 0x7f4298fd8b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
    #2 0x55b8f96b4c49 in _start (/tmp/test/test1+0xc49)

0x602000000010 is located 0 bytes inside of 6-byte region [0x602000000010,0x602000000016)
freed by thread T0 here:
    #0 0x7f42994b3b4f in free (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10bb4f)
    #1 0x55b8f96b4fca in main /tmp/test/test1.c:16
    #2 0x7f4298fd8b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)

previously allocated by thread T0 here:
    #0 0x7f42994b3f48 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10bf48)
    #1 0x55b8f96b4e25 in main /tmp/test/test1.c:8
    #2 0x7f4298fd8b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)

Вывод с Valgrind:

1H
==6888== Invalid read of size 1
==6888==    at 0x108845: main (test1.c:17)
==6888==  Address 0x522d040 is 0 bytes inside a block of size 6 free'd
==6888==    at 0x4C30D3B: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6888==    by 0x108840: main (test1.c:16)
==6888==  Block was alloc'd at
==6888==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6888==    by 0x1087D2: main (test1.c:8)

Мой вопрос : есть любой способ заставить Valgrind или Sanitizer, или какой-либо другой Linux -совместимый инструмент выводить полезную диагностику c о контексте доступа к munmap ped области (например, где это было mmap ped и munmap ped ), аналогично приведенному выше выводу для access-after- free?

Ответы [ 2 ]

0 голосов
/ 20 февраля 2020

есть ли способ сделать Valgrind или Sanitizer, или какой-нибудь другой Linux -совместимый инструмент для вывода полезной диагностики c

Я не знаю такого инструмента, хотя это было бы относительно легко сделать его.

Ваша проблема достаточно отличается от проблем с повреждением кучи, которые требуют специализированных инструментов и, вероятно, не нуждаются в таком инструменте.

Основным отличием является аспект «действия на расстоянии»: при повреждении кучи код, в котором проявляется проблема, часто очень далек от кода, в котором возникла проблема. Отсюда необходимость отслеживать состояние памяти, иметь красные зоны и т. Д. c.

В вашем случае доступ к munmap памяти памяти приводит к немедленному cra sh. Поэтому, если вы просто регистрируете каждые mmap и munmap, которые выполняет ваша программа, вам нужно будет только искать последние munmap, которые "покрывали" адрес, на котором вы потерпели крах.

In Кроме того, большинство программ выполняют относительно немного операций mmap и munmap. Если ваша программа выполняет так много, что вы не можете зарегистрировать их все, скорее всего, она не должна этого делать (mmap и munmap относительно очень дорогих системных вызовов).

0 голосов
/ 05 февраля 2020

valgrind (и я предполагаю, что asan делает то же самое) может выдать ошибку «use after free», потому что он поддерживает список «недавно освобожденных» блоков. Такие блоки логически освобождаются, но они не возвращаются (напрямую) в используемую память для дальнейших вызовов mallo c. вместо этого они помечаются как неадресуемые. Размер этого «недавно освобожденного» списка блокировки можно настроить с помощью

--freelist-vol=<number>          volume of freed blocks queue     [20000000]
--freelist-big-blocks=<number>   releases first blocks with size>= [1000000]

. Можно было бы использовать аналогичную технику для памяти с измененным отображением: вместо того, чтобы физически ее отобразить, ее можно сохранить в список недавно не отображенных блоков, которые должны быть логически не отображены, но помечены как неадресуемые.

Обратите внимание, что вы можете смоделировать это в своей программе, имея функцию my_unmap, которая на самом деле не выполняет отображение, а скорее использует клиентские запросы valgrind. пометить эту память как не адресуемую.

...