【问题标题】:C catching strcat buffer overflowC 捕获 strcat 缓冲区溢出
【发布时间】:2020-10-02 17:29:57
【问题描述】:

这个子程序接受三个用户输入:一个文本字符串、一个文件路径和一个 1 位标志。它将文件加载到缓冲区中,然后按顺序将标志和文件缓冲区附加到用作有效负载的 char 数组中。它返回有效负载和原始用户字符串。

我收到一个错误,其中我对文件缓冲区、标志和有效负载的某些字符串操作似乎损坏了 user_string 所在的内存。我通过将 strcat(flag, buffer) 交换为 strcpy(payload, flag) 来修复错误,(是我最初打算写的),但我仍然对导致此错误的原因感到困惑。

我从阅读文档(https://www.gnu.org/software/libc/manual/html_node/Concatenating-Strings.htmlhttps://www.gnu.org/software/libc/manual/html_node/Concatenating-Strings.html)的猜测是 strcat 扩展 to 字符串 strlen(to) 字节到不受保护的内存,文件内容加载到在缓冲区溢出中复制的缓冲区。

我的问题是:

  1. 我的猜测正确吗?

  2. 有没有办法可靠地防止这种情况发生?用if(){} 检查来捕捉这类事情有点不可靠,因为它不会始终返回明显错误的东西;你期望一个长度为filelength+1 的字符串并得到一个filelength+1 的字符串。

  3. 奖励/无关:调用变量而不对其进行操作是否有任何计算成本/缺点/影响?

/*
user inputs:
argv[0] = tendigitaa/four
argv[1] = ~/Desktop/helloworld.txt
argv[2] = 1

helloworld.txt is a text file containing (no quotes) : "Hello World"
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>

int main (int argc, char **argv) {

    char user_string[100] = "0";
    char file_path[100] = "0";
    char flag[1] = "0";

    strcpy(user_string, argv[1]);
    strcpy(file_path, argv[2]);
    strcpy(flag, argv[3]);

    /*
    at this point printfs of the three declared variables return the same as the user inputs.

    ======
    ======
    a bunch of other stuff happens...
    ======
    ======
    and then this point printfs of the three declared variables return the same as the user inputs.
    */

    FILE *file;
    char * buffer = 0;
    long filelength;

    file = fopen(file_path, "r");

    if (file) {
        fseek(file, 0, SEEK_END);
        filelength = ftell(file);
        fseek(file, 0, SEEK_SET);
        buffer = malloc(filelength);
        printf("stringcheck1: %s \n", user_string);
        if (buffer) {
            fread(buffer, 1, filelength, file);
        }
    }

    long payloadlen = filelength + 1;
    char payload[payloadlen];
    printf("stringcheck2: %s \n", user_string);
    strcpy(payload, flag);
    printf("stringcheck3: %s \n", user_string);
    strcat(flag, buffer);
    printf("stringcheck4: %s \n", user_string); //bug here
    free(buffer);
    printf("stringcheck5: %s \n", user_string);

    payload; user_string; //bonus question: does this line have any effect on the program or computational cost?

    return 0;
}

/*
printf output:

stringcheck1: tendigitaa/four
stringcheck2: tendigitaa/four
stringcheck3: tendigitaa/four
stringcheck4: lo World
stringcheck5: lo World
*/

注意:将此部分从主程序中取出会导致stringcheck 4 出现段错误,而不是返回“lo World”。该行为在其他方面是等效的。

【问题讨论】:

  • 你的编译器对奖金问题有什么看法?答案:警告:声明无效。
  • 什么是不受保护的内存以及它与 strcat 问题的关系?

标签: c string buffer strcpy strcat


【解决方案1】:

strcat 完全按照文档中的说明进行操作:

char *strcat(char *restrict s1, const char *restrict s2); strcat() 函数应附加s2 指向的字符串的副本 (包括终止的空字节)指向字符串的末尾 通过s1s2 的初始字节覆盖末尾的空字节 的s1。如果复制发生在重叠的对象之间,则 行为未定义。

s1 必须分配足够的内存来容纳两个字符串以及终止的 nul

链接的文章是关于编写自己的字符串连接函数的。如何编写这样的函数取决于应用程序 - 那里有说明。有很多方法。

在您的程序中,目标字符数组不够大,结果是未定义的行为,它甚至不够大容纳单个字符串。

我强烈建议学习一些C strings 基础知识。

如果你想要更安全的 strcat,你可以自己写一个,例如:

char *mystrcat(const char *str1, const char *str2)
{
    char *dest = NULL;
    size_t str1_length, str2_length;

    if(str1 && str2)
    {
        dest = malloc((str1_length = strlen(str1)) + (str2_length = strlen(str2)) + 1);
        if(dest)
        {
            memcpy(dest, str1, str1_length);
            memcpy(dest + str1_length, str2, str2_length);
        }
    }
    return dest;
}

但为了安全,我们总是要付出代价 - 代码更长,效率更低。 C 语言被设计为尽可能高效地牺牲安全性并引入未定义行为的想法。

【讨论】:

    【解决方案2】:

    您不能将非空字符串存储在 1 字符数组中。字符串需要空间来容纳字符串内容和空终止符。

    所以当你声明时

    char flag[1] = "1";
    

    您只分配了一个字节,其中包含字符1。没有空终止符。

    将 this 与任何字符串函数一起使用将导致未定义的行为,因为它们会查找空终止符来查找字符串的结尾。

    strcat(flag, buffer) 将搜索数组外部的空终止符,然后在其后附加buffer。所以这显然会导致写入时缓冲区溢出。

    strcpy(payload, flag) 也是错误的。它会在flag 字节之后寻找一个空终止符来知道何时停止复制到payload,因此它会复制的不仅仅是flag(除非它后面恰好有一个空字节)。

    您可以通过增加大小来解决strcpy() 问题:

    char flag[2] = "1";
    

    您也可以将大小留空,编译器会使其足够大以容纳初始化它的字符串,包括空字节:

    char flag[] = "1";
    

    【讨论】:

      【解决方案3】:

      导致问题的那一行是因为 strcat() 试图将缓冲区塞入只有一个字符长的标志,而您没有分配更多空间来容纳缓冲区。

      如果要将缓冲区放入flag,我建议使用realloc() 增加flag 的长度以包含缓冲区的长度。

      此外,您打印的唯一内容是 user_string。我不确定您是否尝试打印正在使用的其他字符串。

      【讨论】:

      • 你不能在数组上使用realloc(),它必须是一个指针。
      • 正如 Barmar 的回答中所指出的,缓冲区 flag 甚至不足以存储具有单个字符的字符串。因此,flag 的内容不是以空值结尾的。因此,问题不在于flag 需要更多空间来容纳buffer,而在于它首先不是字符串(因为字符串必须以空值结尾)。因此,即使buffer 是一个空字符串,strcat 操作也会导致undefined behavior
      猜你喜欢
      • 1970-01-01
      • 2012-02-05
      • 1970-01-01
      • 2011-09-07
      • 1970-01-01
      • 2015-12-16
      • 1970-01-01
      • 2010-11-11
      • 1970-01-01
      相关资源
      最近更新 更多