繁体   English   中英

如何从C中安全地读取数据包中的数据?

[英]How to securely read data from a packet in C?

如何从C中读取数据包中的数据并将其转换为结构? 我的意思是,有一个类似的结构

|=======================================================================
|0123456701234567012345670123456701234567012345670123456701234567.......
|  type  |             length            |    MSG HDR    |    data

进入类似的结构

struct msg {
  char type;
  size_t length;
  int hdr;
  struct data * data;
};

以下代码是否正常?

bool parse_packet(char * packet, size_t packet_len, struct msg * result) {
    if(packet_len < 5) return false;
    result->type = *packet++;
    result->length = ntohl(*(int*)packet);
    packet+=4;
    if(result->length + 4 + 5 > packet_len)
      return false;
    if(result->length < 2)
      return false;
    result->hdr = ntohs(*(short*)packet);
    packet+=2;
    return parse_data(result, packet);
}

检查packetresult是否为空是通常的好习惯。

当标头是7个字节时,为什么要检查packet_len < 5 为什么不确保数据包至少为7个字节并将其结束? 或者某些type hdr不存在?

我不确定你想要实现的目标

if(result->length + 4 + 5 > packet_len)
    result->hdr = ntohs(*(short*)packet);
packet+=2;

如果声明的消息长度加上9大于接收的消息长度,则从消息中读取另外两个字节。 然后,无论数据的长度如何,您都要向指针添加两个并尝试解析其中的某些内容。 如果packet_len为5且result->length为4294967295怎么办? 你将读取缓冲区的末尾,就像在Heartbleed中一样。 您需要始终验证您的读取是否在边界内,并且永远不要信任数据包中声明的大小。

你有一个完全标准的情况。 这里没什么深刻或令人惊讶的。

从有线格式的规范开始。 您可以使用伪代码或实际C类型,但暗示数据在线路上打包成字节:

struct Message  // wire format, pseudo code
{
    uint8_t    type;
    uint32_t   length;      // big-endian on the wire
    uint8_t    header[2];
    uint8_t    data[length];
};

现在开始解析:

// parses a Message from (buf, size)
// precondition: "buf" points to "size" bytes of data; "msg" points to Message
// returns true on success
// msg->data is malloc()ed and contains the data on success
bool parse_message(unsigned char * buf, std::size_t size, Message * msg)
{
    if (size < 7) { return false; }

    // parse length
    uint32_t n;
    memcpy(&n, buf + 1, 4);
    n = ntohl(n);            // convert big-endian (wire) to native

    if (n > SIZE_MAX - 7)
    {
        // this is an implementation limit!
        return false;
    }

    if (size != 7 + n) { return false; }

    // copy data
    unsigned char * p = malloc(n);
    if (!p) { return false; }
    memcpy(p, buf + 7, n);

    // populate result
    msg->type = buf[0];
    msg->length = n;
    msg->header[0] = buf[5];
    msg->header[1] = buf[6];
    msg->data = p;

    return true;
}

解析长度的另一种方法是直接:

uint32_t n = (buf[1] << 24) + (buf[2] << 16) + (buf[1] << 8) + (buf[0]);

此代码假定buf 包含一个消息。 如果您正在从流中接收消息,则需要修改代码(即if (size != 7 + n) )以检查是否至少有所需数据可用,并返回消耗的数量数据也是如此,因此呼叫者可以相应地提前他们的流位置。 (在这种情况下,调用者可以计算被解析为msg->length + 7的数据量,但依赖msg->length + 7数据是不可扩展的。)

注意:正如@user指出的那样,如果你的size_t不比uint32_t宽,那么这个实现将错误地拒绝非常大的消息。 具体而言, 7 + n > n将被拒绝。 我为这个(不太可能的)条件包含了一个动态检查。

暂无
暂无

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

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