繁体   English   中英

Leetcode strStr 问题的堆缓冲区溢出

[英]heap-buffer-overflow for Leetcode strStr problem

问题出在 LeetCode 的Implement strStr 当我在我的计算机上编译和执行时,我有一个可行的解决方案,它工作正常,但是当我尝试使用 LeetCode 运行它时,它给了我一些奇怪的错误。 我不知道有什么问题。

这是我的解决方案:

#include <stdio.h>
#include <stdlib.h>

int strStr(char *haystack, char *needle) {
    if (needle[0] == '\0')
        return 0;

    int i = 0;
    int j = 0;

    while (haystack[i] != '\0') {
        while (haystack[i] == needle[j] && haystack[i] != '\0' && needle[j] != '\0') {
            i++;
            j++;
        }

        if (needle[j] == '\0') {
            return i - j;
        } else {
            j = 0;
        }

        i++;
    }
    return -1;
}

int main() {

    printf("%d\n", strStr("aaa", "aaaa"));

    return 0;
}

这是我在 LeetCode 中遇到的错误

=================================================================
==32==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000014 at pc 0x5620a8c4472e bp 0x7fff98a004c0 sp 0x7fff98a004b0
READ of size 1 at 0x602000000014 thread T0
    #2 0x7fdce2ee00b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
0x602000000014 is located 0 bytes to the right of 4-byte region [0x602000000010,0x602000000014)
allocated by thread T0 here:
    #0 0x7fdce3b25bc8 in malloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
    #3 0x7fdce2ee00b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
Shadow bytes around the buggy address:
  0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c047fff8000: fa fa[04]fa fa fa 05 fa fa fa fa fa fa fa fa fa
  0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==32==ABORTING

请解释为什么会发生这种情况?

问题是您在内部循环中增加i并在外部循环中再次增加,可能会跳过空终止符,因此访问haystack中超出末尾的字节,这具有未定义的行为。

您应该只在内部循环中增加j并比较haystack[i + j] == needle[j]

这是修改后的版本:

#include <stdio.h>

int strStr(const char *haystack, const char *needle) {
    if (needle[0] == '\0')
        return 0;

    int i = 0;

    while (haystack[i] != '\0') {
        int j = 0;
        while (needle[j] != '\0' && haystack[i + j] == needle[j]) {
            j++;
        }
        if (needle[j] == '\0') {
            return i;
        }
        i++;
    }
    return -1;
}

int main() {
    printf("%d\n", strStr("aaaaaaaaab", "aaaab"));
    printf("%d\n", strStr("aaaaaaaa", "aaaaaaaaa"));
    printf("%d\n", strStr("Hello world\n", "or"));
    return 0;
}

请注意,您可以通过重新组织代码来删除一些多余的比较:

int strStr(const char *haystack, const char *needle) {
    for (int i = 0;; i++) {
        for (int j = 0;; j++) {
            if (needle[j] == '\0')
                return i;
            if (haystack[i + j] != needle[j])
                break;
        }
        if (haystack[i] == '\0')
            return -1;
    }
}

但是请注意,此方法的最坏情况时间复杂度为O(len(haystack)*len(needle)) ,并具有以下病态示例:

strStr("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaab")

在函数strStr()中,当ij都等于3时,嵌套的while循环退出,因为条件haystack[i] != '\0'变为false 在代码下面,它检查needle[j] == '\0'对于j = 3false的, needle[j]不等于\0 然后它增加i使i的值等于4并且外部while循环迭代并检查条件haystack[i] != '\0'导致访问haystack超出它指向的缓冲区大小,因为有效索引因为haystack指向的缓冲区的范围是0 - 3 (字符串 - "aaa" )。

由于您已经发布了 AddressSanitizer 输出,以下是如何解释 ASan 输出和识别问题的说明:

ASan 报告的错误是:

ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000014

buffer-overflow意味着您的程序正在尝试访问超出数组大小的内存,精确地尝试访问超出其大小的字符串文字1)

在 ASan 输出中检查此语句:

0x602000000014 is located 0 bytes to the right of 4-byte region [0x602000000010,0x602000000014)

这意味着程序试图访问地址0x602000000014 ,该地址存在于 4 字节区域[0x602000000010,0x602000000014)0字节右侧。

这里需要注意区域[0x602000000010,0x602000000014) - 方括号[表示包含端点,圆括号)表示不包含。

4 字节区域0x602000000010 - 0x602000000013包含由haystack指针指向的字符串文字"aaa"

                         0x602000000014
                  0x602000000013      |
            0x602000000012     |      |
      0x602000000011     |     |      |
0x602000000010     |     |     |      |
             |     |     |     |      |
            +-----+-----+-----+------+-----+
            |  a  |  a  |  a  |  \0  |     |
            +-----+-----+-----+------+-----+
             \                      /
              +--------------------+
                        |
             4-byte region pointed 
              by haystack pointer

请注意,ASan 在对象的边缘创建有毒的红色区域以检测溢出或下溢,并且在编译期间 ASan 检测代码以验证每次内存访问时的影子内存状态2) 1字节的影子内存跟踪 ASan 检测程序使用的8字节内存。

现在,检查 ASan 输出的这一部分:

Shadow bytes around the buggy address:

在输出中,内存区域用=>突出显示:

=>0x0c047fff8000: fa fa[04]fa fa fa 05 fa fa fa fa fa fa fa fa fa
                       ^^^^

[04] -

  • 04表示8字节区域的前四个字节(在影子内存中与该字节映射)是可寻址的。 这意味着从0x6020000000100x602000000013的内存区域包含"aaa" (包括空终止字符)是可寻址的。
  • 方括号[]表示您的程序试图访问映射到影子内存中该字节的重做(基本上是不允许访问的内存)。

您的程序尝试访问字符串"aaa"的终止空字符\0旁边的字节,并最终尝试访问 redzone,因此 ASan 报告它。

另一篇文章已经展示了strStr()的更好实现。 我将由您来解决代码中的问题并优化strStr()函数的实现。

一个建议

使用-fsanitize=address编译程序时,也启用调试信息(例如gcc编译器的-g选项),您将在 ASan 输出中获得行号和正确的堆栈。


1)。 不知道为什么它报告heap-buffer-overflow以访问超出其大小的字符串文字。 在我的系统上,同一程序的 ASan 输出给出错误 - global-buffer-overflow ,这似乎更合适,因为通常在数据段中分配的字符串文字但它们的放置位置可能因底层平台/架构而异。

2)。 AddressSanitizer - 它是如何工作的?

由于传递给函数的字符串都没有在函数内更改,因此函数应至少声明为

int strStr( const char *haystack, const char *needle );

对于这个电话

strStr("aaa", "aaaa")

函数内的这个内部while循环

while (haystack[i] == needle[j] && haystack[i] != '\0' && needle[j] !='\0') {
    i++;
    j++;
}

由于条件停止其迭代

haystack[i] == '\0'

因为弦haystack的长度小于弦needle的长度。 但在那之后索引i递增

i++;

所以表达式

haystack[i]

在外部 while 循环的下一次迭代中

while (haystack[i] != '\0') {

访问导致未定义行为的字符串haystack之外的内存。

代替

i++;

你可以写例如

if ( haystack[i] != '\0' ) ++i;

请注意,如果在迭代循环之前考虑它们的长度,该函数可能会更有效。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM