【问题标题】:Linux POSIX C LibPCRE `double free or corruption (fasttop)` crashLinux POSIX C LibPCRE `double free or corruption (fasttop)` 崩溃
【发布时间】:2014-03-06 15:14:01
【问题描述】:

我有以下代码(它读取进程虚拟内存并使用 libpcre 匹配一些字符串),它编译没有错误,但如果我使用 -Wall 编译它,我会收到一些警告,我将在代码之后显示。

编译后的代码可以运行,但会在*** glibc detected *** ./readmempcreuniq: double free or corruption (fasttop): 0x097b9c80 *** 崩溃,我怀疑问题出在pcre_get_substring(page, vector, pairs, 0, &buff); 线上,因为函数的第一个参数需要'const char *' 但得到'unsigned char *',我怎样才能使它正确?

#ifdef TARGET_64
// for 64bit target (see /proc/cpuinfo addr size virtual)
#define MEM_MAX (1ULL << 48)
#else
#define MEM_MAX (1ULL << 32)
#endif

#define _LARGEFILE64_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/ptrace.h>
#include <pcre.h>
#include <locale.h>

int main(int argc, char **argv)
{
    if (argc < 2) {
        printf("Usage: %s <pid>\n", argv[0]);
        exit(1);
    }

    char buf[128];
    int pid = atoi(argv[1]);
    snprintf(buf, sizeof(buf), "/proc/%d/mem", pid);
    int fd = open(buf, O_RDONLY);
    if (fd == -1) {
        fprintf(stderr, "Error opening mem file: %m\n");
        exit(1);
    }

    pcre *f;
    pcre_extra *f_ext;
    char *pattern = "([0-9]{20,22})";
    const char *errstr;
    int errchar;
    int vector[50];
    int vecsize = 50;
    int pairs;
    const char *buff;
    const unsigned char *tables;
    int a;
    int count = 0;
    const char **matches = NULL;
    const char **more_matches;

    char *loc = setlocale(LC_ALL, 0);
    setlocale(LC_ALL, loc);
    tables = pcre_maketables();

    long ptret = ptrace(PTRACE_ATTACH, pid, 0, 0);
    if (ptret == -1) {
        fprintf(stderr, "Ptrace failed: %s\n", strerror(errno));
        close(fd);
        exit(1);
    }

    unsigned char page[4096];
    unsigned long long offset = 0;


    while (offset < MEM_MAX) {
        lseek64(fd, offset, SEEK_SET);

        ssize_t ret;
        ret = read(fd, page, sizeof(page));

        if (ret > 0) {
            page[ret] = '\0';
            if((f = pcre_compile(pattern, PCRE_CASELESS|PCRE_MULTILINE, &errstr, &errchar, tables)) == NULL)
            {
                printf("Error: %s\nCharacter N%i\nPattern:%s\n", errstr, errchar, pattern);
            }
            else
            {
                f_ext = pcre_study(f, 0, &errstr);
                a = 0;

                while((pairs = pcre_exec(f, f_ext, page, sizeof(page), a, PCRE_NOTEMPTY, vector, vecsize)) >=0)
                {
                    pcre_get_substring(page, vector, pairs, 0, &buff);
                    //printf("%s\n", buff);
                    more_matches = realloc(matches, (count+1)* sizeof(*more_matches));
                    if (more_matches!=NULL)
                    {
                        matches=more_matches;
                        matches[count++]=buff;
                    }
                    else
                    {
                        free(matches);
                        puts("Error (re)allocating memory");
                        exit(1);
                    }
                    a = vector[1] + 1;
                }
                int matches_len = count;
                const char *uniques[matches_len];
                int uniques_len = 0;
                int already_exists;
                int i, j;
                for (i = 0; i < matches_len; i++)
                {
                    already_exists = 0;
                    for ( j = 0; j < uniques_len; j++)
                    {
                        if (!strcmp(matches[i], uniques[j]))
                        {
                            already_exists = 1;
                            break;
                        }
                    }
                    if (!already_exists)
                    {
                        uniques[uniques_len] = matches[i];
                        uniques_len++;
                    }
                }
                for (i = 0; i < uniques_len; i++)
                {
                    printf("%s\n", uniques[i]);
                }
                free(matches);
                pcre_free(f);
            }

        }

        offset += sizeof(page);
    }

    ptrace(PTRACE_DETACH, pid, 0, 0);
    close(fd);
    return 0;
}

错误:

xtmtrx@server:~/regex/proc$ ./readmempcreuniq 5663
92991999918876543209
99299299292663552673
111992229922222288
119988922220000077
*** glibc detected *** ./readmempcreuniq: double free or corruption (fasttop): 0x097b9c80 ***
======= Backtrace: =========
/lib/libc.so.6(+0x6c0c1)[0xb7dfa0c1]
/lib/libc.so.6(+0x6d930)[0xb7dfb930]
/lib/libc.so.6(+0x71681)[0xb7dff681]
/lib/libc.so.6(realloc+0xe3)[0xb7dffb13]
./readmempcreuniq[0x8048c86]
/lib/libc.so.6(__libc_start_main+0xe7)[0xb7da4ce7]
./readmempcreuniq[0x80488b1]
======= Memory map: ========
08048000-0804a000 r-xp 00000000 fd:01 68388533                           /root/regex/proc/readmempcreuniq
0804a000-0804b000 r--p 00001000 fd:01 68388533                           /root/regex/proc/readmempcreuniq
0804b000-0804c000 rw-p 00002000 fd:01 68388533                           /root/regex/proc/readmempcreuniq
097a1000-097c2000 rw-p 097a1000 00:00 0                                  [heap]
b7c00000-b7c21000 rw-p b7c00000 00:00 0
b7c21000-b7d00000 ---p b7c21000 00:00 0
b7d66000-b7d80000 r-xp 00000000 fd:01 65901968                           /lib/libgcc_s.so.1
b7d80000-b7d81000 r--p 00019000 fd:01 65901968                           /lib/libgcc_s.so.1
b7d81000-b7d82000 rw-p 0001a000 fd:01 65901968                           /lib/libgcc_s.so.1
b7d8c000-b7d8e000 rw-p b7d8c000 00:00 0
b7d8e000-b7ee5000 r-xp 00000000 fd:01 65901949                           /lib/libc-2.12.1.so
b7ee5000-b7ee7000 r--p 00157000 fd:01 65901949                           /lib/libc-2.12.1.so
b7ee7000-b7ee8000 rw-p 00159000 fd:01 65901949                           /lib/libc-2.12.1.so
b7ee8000-b7eeb000 rw-p b7ee8000 00:00 0
b7eeb000-b7f1e000 r-xp 00000000 fd:01 65901993                           /lib/libpcre.so.3.12.1
b7f1e000-b7f1f000 r--p 00032000 fd:01 65901993                           /lib/libpcre.so.3.12.1
b7f1f000-b7f20000 rw-p 00033000 fd:01 65901993                           /lib/libpcre.so.3.12.1
b7f29000-b7f2c000 rw-p b7f29000 00:00 0
b7f2c000-b7f48000 r-xp 00000000 fd:01 65901940                           /lib/ld-2.12.1.so
b7f48000-b7f49000 r--p 0001b000 fd:01 65901940                           /lib/ld-2.12.1.so
b7f49000-b7f4a000 rw-p 0001c000 fd:01 65901940                           /lib/ld-2.12.1.so
bf8c1000-bf8d6000 rw-p 7ffffffe9000 00:00 0                              [stack]
Aborted

使用-Wall 开关编译代码的警告:

xtmtrx@server:~/regex/proc$ gcc -o readmempcreuniq readmempcreuniq.c -lpcre -Wall readmempcreuniq.c:在函数'main'中:readmempcreuniq.c:83:警告:传递参数3中的指针目标 'pcre_exec' 的签名不同/usr/include/pcre.h:286:注意: 预期为 'const char *' 但参数的类型为 'unsigned char *' readmempcreuniq.c:85:警告:传递参数 1 中的指针目标 'pcre_get_substring' 的签名不同/usr/include/pcre.h:297: 注意:预期为 'const char *' 但参数的类型为 'unsigned char *'

编辑:

根据@stdcall 提示,我使用efence 编译了程序,然后在核心转储上使用了 GDB:

xtmtrx@server:~/regex/proc$ ./readmempcreuniq 6036

  Electric Fence 2.1 Copyright (C) 1987-1998 Bruce Perens.
5,
Segmentation fault (core dumped)
xtmtrx@server:~/regex/proc$ gdb ./readmempcreuniq core
GNU gdb (GDB) 7.2-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/regex/proc/readmempcreuniq...done.
[New Thread 6093]
Reading symbols from /lib/libpcre.so.3...(no debugging symbols found)...done.
Loaded symbols for /lib/libpcre.so.3
Reading symbols from /usr/lib/libefence.so.0...(no debugging symbols found)...done.
Loaded symbols for /usr/lib/libefence.so.0
Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/libpthread.so.0...(no debugging symbols found)...done.
Loaded symbols for /lib/libpthread.so.0
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./readmempcreuniq 6036'.
Program terminated with signal 11, Segmentation fault.
#0  0x08048ef8 in main (argc=2, argv=0xbfe1d2d4) at readmempcreuniq.c:125
125                                                     uniques[uniques_len] = matches[i];

似乎@alk 是正确的,问题在于uniques[uniques_len] = matches[i];

新编辑:

根据@alk 的提示,我已经更改了行:

for ( j = 0; j < uniques_len; j++)

到:

for ( j = 0; j < matches_len; j++)

现在段错误在别处:

Program terminated with signal 11, Segmentation fault.
#0  0x08048ea7 in main (argc=2, argv=0xbfaaa3e4) at readmempcreuniq.c:118
118                                                     if (!strcmp(matches[i], uniques[j]))

【问题讨论】:

  • 警告是无害的。使用显式强制转换来摆脱它们,或者首先使用char
  • 我认为我不能只使用char 而不是unsigned char page[4096];,因此我认为我在pcre_get_substring(page, vector, pairs, 0, &amp;buff); 上遇到错误,因为此函数需要unsigned char *
  • 表格必须是无符号字符,页面必须是纯字符。
  • 在我的机器上 pcre_get_substring 需要纯字符指针。我在在线资源中也找不到任何不同之处。
  • 好的。我将再添加一件事,这就是我认为您正在尝试做的事情,建立一个独特的匹配列表)。不能保证它是完美的,因为我不知道 API,但我知道一般的算法(我认为,无论如何)。

标签: c linux pcre glibc


【解决方案1】:

在循环中的下一轮之前free()之后,您永远不会将matches 重置为NULL。因此,在第一轮重新分配后,它仍然保留原始值。

要么在进入内部处理循环之前将其设置为NULL(第一次通过冗余),要么在free(matches) 之后立即将其返回设置为NULL。或者,您可以简单地将其设置为具有初始 NULL 值的下一个外部循环的包含范围的本地,但前面提到的更改是我能想到的最小的。

示例

matches = NULL; // HERE
while((pairs = pcre_exec(f, f_ext, page, sizeof(page), a, PCRE_NOTEMPTY, vector, vecsize)) >=0)
{
        pcre_get_substring(page, vector, pairs, 0, &buff);
        //printf("%s\n", buff);
        more_matches = realloc(matches, (count+1)* sizeof(*more_matches));
        if (more_matches!=NULL)
        {
                matches=more_matches;
                matches[count++]=buff;
        }
        else
        {
                free(matches);
                puts("Error (re)allocating memory");
                exit(1);
        }
        a = vector[1] + 1;
}

或者....

for (i = 0; i < uniques_len; i++)
{
        printf("%s\n", uniques[i]);
}
free(matches);
matches = NULL; // or HERE
pcre_free(f);

更多内容

继续我注意到的事情:

这个:

ssize_t ret;
ret = read(fd, page, sizeof(page));

if (ret > 0) {
        page[ret] = '\0';

似乎试图设置一个空字符终止符。如果是这样,您将在一个完整填充的缓冲区上调用 未定义的行为。应该是这样的:

ssize_t ret = read(fd, page, sizeof(page)-1); // NOTE SPACE FOR TERM
if (ret > 0) {
        page[ret] = 0;

如果缓冲区的大小是特定的(您选择 4K 是有原因的),它应该是 4097 以确保最大精确的 4K 缓冲区。


还有一个……

您正在阅读该页面,我不能声称它需要或不需要像我之前在代码中显示的那样被终止。但是假设它是并且你做了我建议的(或者..不是),这看起来也有问题:

while((pairs = pcre_exec(f, f_ext, page, sizeof(page), a, PCRE_NOTEMPTY, vector, vecsize)) >=0)

这里你传递了整个缓冲区的大小; 不是您读取的实际数据的大小。我是第一个告诉你我不熟悉 API 的人,但我很确定应该是这样的:

// notice the length of the buffer passed, ret
while((pairs = pcre_exec(f, f_ext, page, ret, a, PCRE_NOTEMPTY, vector, vecsize)) >=0)

换句话说,在小尺寸读取中,您告诉它数据比实际更长。同样,我对他们的 API 很幼稚,但这似乎是合理的。


唯一匹配项...

希望更容易阅读。

int matches_len = count, uniques_len = 0;
int i = 0, j = 0;

const char *uniques[matches_len];
for (i=0; i < matches_len; ++i)
{
    for (j = 0; j < uniques_len; ++j)
    {
        if (!strcmp(matches[i], uniques[j]))
            break;
    }

    if (j == uniques_len)
        uniques[uniques_len++] = matches[i];
}

for (i = 0; i < uniques_len; ++i)
    printf("%s\n", uniques[i]);

继续……

每页后将count 重置为零。在free(matches); matches = NULL; 之后将是一个好地方。

值得注意。一旦文件读取开始失败,您的外部循环中就没有退出案例,因此文件将受到很多无法超越其结尾的抨击。直到达到限制器数量。


最后的想法

认为这与你想要做的很接近:

#define _LARGEFILE64_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/ptrace.h>
#include <pcre.h>
#include <locale.h>
#include <ctype.h>

int main(int argc, char **argv)
{
    // CHANGE TO ACCEPT PROC-ID FROM CMDLINE    
    int pid = 5916;

    setlocale(LC_ALL,"");

    const char *error = NULL;
    int erroffset = 0;
    const char **uniques = NULL;
    size_t uniques_len = 0;

    const char regex[] = "[0-9A-Fa-f]{8}";
    pcre* re = pcre_compile (regex,          /* the pattern */
                    PCRE_MULTILINE|PCRE_DOTALL|PCRE_NEWLINE_ANYCRLF,
                    &error,         /* for error message */
                    &erroffset,     /* for error offset */
                    0);             /* use default character tables */
    if (!re)
    {
        printf("pcre_compile failed (offset: %d), %s\n", erroffset, error);
        return -1;
    }

    // start proc trace
    long ptret = ptrace(PTRACE_ATTACH, pid, 0, 0);
    if (ptret == -1)
    {
        fprintf(stderr, "Ptrace failed: %s\n", strerror(errno));
        exit(1);
    }

    char path[256];
    snprintf(path, sizeof(path), "/proc/%d/maps", pid);
    FILE *maps = fopen(path, "r");
    snprintf(path, sizeof(path), "/proc/%d/mem", pid);
    int mem = open(path, O_RDONLY);

    if(maps && (mem != -1))
    {
        char buf[BUFSIZ + 1];
        while(fgets(buf, BUFSIZ, maps))
        {
            long long unsigned int start, end;
            if (sscanf(buf, "%llx-%llx", &start, &end) != 2)
                break;

            printf("reading %llx - %llx\n", start, end);

            lseek64(mem, start, SEEK_SET);
            while (start < end)
            {
                char page[4096] =  {0};
                int rd = read(mem, page, sizeof(page));
                if (rd < 0)
                    break;

                start += sizeof(page);

                int ov[128] = {0};
                unsigned int ov_len = 0;
                int rc = 0;

                while ((rc = pcre_exec(re, 0, page, (int)(rd), ov_len, 0, ov, 128)) >= 0)
                {
                    int i = 0;
                    for(; i < rc; ++i)
                    {
                        const char *sp = NULL;
                        pcre_get_substring(page, ov, rc, i, &sp);

                        // search unique list
                        size_t j=0;
                        for (;j<uniques_len;++j)
                        {
                            if (!strcmp(sp, uniques[j]))
                            break;
                        }

                        if (uniques_len == j)
                        {
                            const char **tmp = realloc(uniques, (uniques_len+1)*sizeof(*uniques));
                            if (tmp == NULL)
                            {
                                perror("Failed to resize uniques.");
                                pcre_free_substring(sp);
                            }
                            else
                            {
                                uniques = tmp;
                                uniques[uniques_len++] = sp;
                            }
                        }
                        else
                        {   // delete string. not needed
                            pcre_free_substring(sp);
                        }
                    }
                    ov_len = ov[2*(rc-1)]+1;
                }
            }
        }

        fclose(maps);
        close(mem);
    }

    size_t n = 0;
    for (; n<uniques_len; ++n)
    {
        printf("%s\n", uniques[n]);
        pcre_free_substring(uniques[n]);
    }
    printf("total uniques: %lu\n", uniques_len);
    free(uniques);

    ptrace(PTRACE_DETACH, pid, 0, 0);
    return 0;
}

警告。我对这个 API 的了解,但我在这里看到并在网上简要回顾了这些。 YMMV UAYOR。但似乎你一直都有。只是积累独立于页面的唯一性(我认为这仍然是一个问题,页面边界,但那是另一天)。

【讨论】:

  • 这样做了吗,在free(matches); 之后设置matches = NULL; 现在我得到大约5 匹配出28 预期,我得到一个Segmentation fault
  • @xtmtrx 可能需要一个类似的堆栈转储(谢谢,顺便说一句)来寻找那个。然而,这绝对是一个问题,在重复使用的realloc() 算法中并不少见。
  • 是的,我这样做了,现在glibc 错误已修复,仍然得到Segmentation fault
  • 我什至可以删除 page[ret] = '\0';,这是我用来欺骗 POSIX 正则表达式以更好地匹配的东西,因为它匹配(不像 pcre)直到找到第一个 '\0'。 (我之前在这段代码中使用过 POSIX 正则表达式,但后来改用更可靠的 pcre)
  • @xtmtrx 好的,已发布。希望就是这样。把它当作它可能的价值。为了加快实现速度,我刚开始调试时硬编码了一个本地 pid。您需要放回 pid 的 cmd-line 解析,但除此之外它应该可以工作。
【解决方案2】:

除了WhozCraiganswer指出的问题:

代码定义

  const char *uniques[matches_len];

但循环其索引j 直到&lt;uniques_len

    for ( j = 0; j < uniques_len; j++)
    {
      if (!strcmp(matches[i], uniques[j]))
      {
        already_exists = 1;
        break;
      }
    }

所以uniques 很可能被越界访问,引发未定义的行为导致崩溃。

更新:

进一步的调查表明,这在此处没有问题,尽管这是一个危险的结构。


问题在于matches 没有指向此行中正确分配的内存:

 if (!strcmp(matches[i], uniques[j]))

要揭示这个问题,请在realloc()ing 内存之后添加正确的内存初始化,方法是更改​​此代码:

          int count = 0;
          const char ** matches = NULL;
          [...]

                    more_matches = realloc(matches, (count+1)* sizeof(*more_matches));
                    if (more_matches!=NULL)
                    {
                        matches=more_matches;
                        matches[count++]=buff;
                    }

变成:

          size_t count = 0, count_prev = 0;
          const char ** matches = NULL;
          [...]

                    more_matches = realloc(matches, (count + 1) * sizeof(*more_matches));
                    if (more_matches != NULL)
                    {
                        memset(more_matches + count_prev, 0, (count + 1 - count_prev) * sizeof(*more_matches));
                        count_prev = count;
                        matches = more_matches;
                        matches[count++] = buff;
                    }

作为一般建议:调试时始终使用符号进行编译(选项 -g 到 gcc),然后在 gdb 和 Valgrind 下运行代码。这两个工具解决了您代码中的大部分问题。

【讨论】:

  • 我完全要为我错过或在你之前没有找到的东西投赞成票=P
  • @WhozCraig:我已经解决了你更快的 NULL 指针问题。以及page[ret] = 0; 的发现。 ;-)
  • 你建议如何正确编写 for ( j = 0; j &lt; uniques_len; j++) 循环,这样我就不会越界?
  • 好的。我得打电话给wtf。您将整个段归零,然后只保存 一个 (最后一个)。它有点重新分配以保留先前的内容。换句话说,丢失循环中收集的所有先前指针如何修复strcmp 调用。 ?
  • @xtmtrx:修正了我建议的调整。
猜你喜欢
  • 2018-09-30
  • 2021-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-14
  • 1970-01-01
  • 1970-01-01
  • 2020-08-14
相关资源
最近更新 更多