【问题标题】:Can `recv()` result in a buffer overflow?`recv()` 会导致缓冲区溢出吗?
【发布时间】:2013-02-17 16:52:19
【问题描述】:

我正在向自己介绍 C/C++ 中的套接字编程,并使用 send()recv() 通过 TCP 套接字在客户端和服务器程序之间交换数据。

以下是我的代码的一些相关摘录:

server.c

char recv_data[1024];

// Socket setup and so on ommited...

bytes_recieved = recv(connected, recv_data, 1024, 0);
recv_data[bytes_recieved] = '\0';

client.c:

char send_data[1024];

// Setup ommited...

send(connected, send_data, strlen(send_data), 0);

recv() 本身是否提供任何防止缓冲区溢出的保护?例如,如果我将第三个参数更改为recv(),使其高于recv_data 指向的缓冲区(例如4000) - 这会导致缓冲区溢出吗? (我实际上已经尝试过这样做,但似乎无法触发段错误)。

我实际上是在尝试创建一个故意易受攻击的服务器程序以更好地理解这些问题,这就是我尝试通过recv() 溢出的原因。

修正

并非不相关,将找出为什么上面的client.c 发送的字节数会超过strlen(send_data) 指定的1024 字节。我正在使用gets(send_data) 从标准输入填充该缓冲区,但如果我通过标准输入输入超过 1024 个字节,server.c 程序显示它接收所有字节! :)。 strlen(send_data) for send() 是否不限制发送的字节数?

【问题讨论】:

  • 与您的问题无关,但您拼写错误。现在改正比以后改好。 :-)
  • @JamesMcLaughlin 很好,先生!
  • 缓冲区溢出总是程序中的一个错误,但它们只是有时会导致段错误,这取决于 libc 如何在幕后布置内存。正如您所观察到的,也可能存在损坏的数据,或者一切似乎都可以正常工作。这就是为什么缓冲区溢出如此难以追踪的原因。

标签: c++ c sockets buffer-overflow recv


【解决方案1】:

例如,如果我将 recv() 的第三个参数更改为高于 recv_data 指向的缓冲区(例如 4000)的值 - 这会导致缓冲区溢出吗?

当然可以。如果网络缓冲区有 4000 字节的数据,则将其放入缓冲区。关键是,recv 像任何其他需要缓冲区的 C API 一样,它的长度认为调用者将传递缓冲区的实际长度,如果调用者传递了不正确的长度,那么错误在于调用者,它可能导致到未定义的行为。

在 C 中,当您将数组传递给函数时,被调用的函数无法知道数组的大小。因此,所有 API 仅依赖于您提供的输入。

char recv_data[1024];

// Socket setup and so on ommited...

bytes_recieved = recv(connected, recv_data, 1024, 0);
recv_data[bytes_recieved] = '\0';

上面的代码可能会以多种方式引起问题。在以下情况下会导致未定义的行为:
(a) 如果recv返回-1,那么你是直接索引recv_data缓冲区而不检查返回值
(b) 如果recv返回1024,那么同样会导致越界访问,因为大小为1024的数组应该从0访问到1023

【讨论】:

  • 看起来如果 server.c recv_data 缓冲区是 4000 字节,我在 recv() 调用中指定长度参数是 3000 字节,我从客户端发送了 5000 - 第一次调用recv() 只会复制 3000 个字节,但如果我再次调用它,它将收到 1000 个字节。所以(网络缓冲区?)必须保留尚未处理的数据 - 这就是为什么我会在随后对 recv() 的调用中看到它?
  • 是的。当然。网络缓冲区将保留数据,并只为您提供与缓冲区大小一样多的数据。
  • @csjohn:理解 TCP 是一个字节流协议很重要……这意味着数据包可以在传输过程中被拆分或重组。如果您从客户端发送 5000,即使接收方要求 3000,它也可能会得到 1..3000 字节而不会出现错误。由接收者决定是否有足够的数据来处理其他recv() 调用中的数据重新组合它。例如。在您上面的场景中,即使 如果第一个 recv() 获得 3000 个字节,第二个调用也可能获得 50 和第三个 950(如果发生了另一个 send(),则可能更多)。
【解决方案2】:

这个

recv_data[bytes_recieved] = '\0';

如果接收到 1024 个字节,可能会导致缓冲区溢出。

您可能想更改此设置

bytes_recieved = recv(connected, recv_data, 1024, 0);

成为

bytes_recieved = recv(connected, recv_data, 1024 - 1, 0);

这样bytes_recieved 永远不会大于1023,这是recv_data 的最大有效索引。


您的系统调用 (recv()/send()) 也缺少错误检查。在以任何其他方式使用结果之前测试它们是否返回了-1


参考您的修改:

strlen() 尝试返回从其参数指向的字符开始直到第一个 NUL/0 字符的字符数。此数字可以是任何值,具体取决于将终止 0 放置在何处。

如果这个0-terminator 的搜索运行在分配给strlen()s 参数的内存后面,程序肯定会遇到未定义的行为,因此可以返回任何值。

所以回答你的问题:如果send_data 不是 0-终止 strlen() 使应用程序运行到未定义的行为,因此它可能会崩溃或strlen() 返回大于 1024 的值,所以send() 会尝试发送这个数量的字符。

【讨论】:

  • 我也很怀疑,尽管我似乎无法触发段错误。我已将呼叫更改为recv(connected, recv_data, 4048, 0);。在客户端,send_data 缓冲区只有 1024 字节,但是当我发送垃圾邮件说 5000 字节的输入时,服务器仍然显示 bytes_received 是 5000 字节。
  • @csjohn 是什么让您认为缓冲区溢出仅在触发段错误时才存在?
  • @jalf 没什么,我只是感觉我扔进的垃圾的数量会触发一个:)。
  • @csjohn 你不能保证这一点。当您 幸运 时,缓冲区溢出会导致段错误。这是一个很好的、干净的结果,可以调试,并且不会导致程序状态或数据的进一步损坏。但大多数时候,你并不走运。因此,您必须自己保持警惕,避免发生任何此类溢出,即使它们看似不会造成任何伤害。因为关键字是“貌似”。
  • @jalf 是的 - 感谢您的意见。我从来没有真的必须在 C/C++ 中做任何严肃的事情(学校里的操作系统课程除外),所以我被高级语言的负担得起的宠坏了。
【解决方案3】:

即使您发送比recv() 缓冲区更大的字节,您仍然可以在成功调用recv() 时发送recv() 它,这就是您说bytes_received 仍然是5000 字节的原因,因为,假设您发送5000 字节,而您的接收缓冲区是1000 字节,在第一次调用recv() 时它只会获得1000 字节,在下一次调用时,再次获得1000 字节,直到它收到你所有的数据。所以,我认为这里没有缓冲区溢出。这就是 TCP 的工作原理。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多