【问题标题】:Why this stack smashing?为什么这个堆栈粉碎?
【发布时间】:2013-07-14 12:51:09
【问题描述】:

我得到了以下代码(log 函数的一部分):

/* set to 32 on purpose */
#define MAX_LOG_MSG_SZ 32

void log(const char *fmt, ...) {
    ....

    char msg[MAX_LOG_MSG_SZ] = {0};
    int nb_bytes = 0;

    /* get current time */
    time_t now = time(NULL);

    char time_buf[32] = {0};

    /* format time as `14 Jul 20:00:08`, and exactly 16 bytes */
    strftime(time_buf, sizeof(time_buf), "%d %b %H:%M:%S", localtime(&now));

    nb_bytes = snprintf(msg, sizeof(msg), "%s", time_buf);

    va_list ap;
    va_start(ap, fmt);
    vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ, fmt, ap);
    va_end(ap);

    ....
}

棘手的事情是当传递长参数(使其长于 32 字节)并将time_buf 更改为小于 32 的其他值(大于 16,例如 31)时,这些代码会抛出堆栈粉碎。经过几分钟的调试,我将vsnprintf的调用行改为

 vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ - nb_bytes, fmt, ap);

堆栈粉碎消失了,我认为问题已解决。

但是:在time_buf[32](或其他更大尺寸)上,为什么会出现错误调用

 vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ, fmt, ap);

不扔一个堆栈粉碎?更准确地说,为什么msg 的堆栈粉碎与那个不相关 堆栈 (time_buf) 空间有关?

更新:这是我的uname -a 输出:

 Linux coanor 3.5.0-34-generic #55-Ubuntu SMP Thu Jun 6 20:20:19 UTC 2013 i686 i686 i686 GNU/Linux

【问题讨论】:

    标签: c debugging stack-overflow


    【解决方案1】:

    使用基于堆栈的缓冲区,您必须注意不要超出分配的缓冲区。正如您自己发现的那样,您使用vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ, fmt, ap); 引入了可能的缓冲区溢出。这是因为您告诉 vsnprintf,可用空间比实际空间多(因为 nb_bytes = snprintf(msg, sizeof(msg), "%s", time_buf); 已经向缓冲区写入了一些字节)。

    因此,您的解决方法是通过MAX_LOG_MSG_SZ - nb_bytes 而不是MAX_LOG_MSG_SIZE,以避免这种影响是正确的。

    了解这一点也很重要,snprintf 及其变体总是返回将要写入的字节数,而不管实际写入的字节数是多少缓冲。

    编辑:因此,在您的情况下,您必须在字符串组合期间跟踪字符串的总长度,以确保不会超出消息缓冲区的总长度。

    【讨论】:

      【解决方案2】:
      char time_buf[32] = {0};
      /* format time as `14 Jul 20:00:08`, and exactly 16 bytes */
      strftime(time_buf, sizeof(time_buf), "%d %b %H:%M:%S", localtime(&now));
      
      nb_bytes = snprintf(msg, sizeof(msg), "%s", time_buf);
      

      所以实际上 time_buf 和 msg 包含相同的数据。 snprintf 返回成功写入 msg 的字符数,不包括空字符。

      vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ, fmt, ap);
      

      您正试图从msg+nb_bytes 给出的地址写信。您在 msg 中有 16 个字符。但是你声称你有MAX_LOG_MSG_SZ,它是 32 个字符。您正在尝试写入字符串的末尾。可能 fmt 包含超过 15 个字符。

      vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ - nb_bytes, fmt, ap);
      

      这次你适当地减去已经写入 msg 的字符,并给出 16 个字符写入vsnprintf。它服从并且不会超出字符数组的末尾。

      【讨论】:

      • 其实你错了。 snprintf 返回将被写入的字符数。不是成功写入的字节数。此错误可能会导致此代码出现严重问题。 (假设生成的字符串大于 32 并考虑下一个带有MAX_LOG_MSG_SZ - nb_bytes 的 snprintf 调用)
      • 我猜他们在这里是一样的。 OP 正在将 time_buf 复制到 msg。
      • 对于这种特定情况,这很可能是正确的。但是,当我阅读您的声明时,在我看来,情况似乎总是如此,但事实并非如此。这就是我想在评论中指出的原因。
      • 没有问题。非常感谢。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-31
      • 1970-01-01
      • 2014-06-26
      • 1970-01-01
      • 2021-09-05
      相关资源
      最近更新 更多