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

时间:2014-05-10 12:07:09

标签: c security operating-system

如何从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);
}

2 个答案:

答案 0 :(得分:1)

检查packetresult是否为空,通常是一种好习惯。

当标头为7个字节时,为什么要检查packet_len < 5?为什么不确保数据包至少为7个字节并将其结束?或hdr某些type不存在if(result->length + 4 + 5 > packet_len) result->hdr = ntohs(*(short*)packet); packet+=2;

我不确定你用

尝试实现的目标
packet_len

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

答案 1 :(得分:1)

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

以有线格式的规范开头。您可以使用伪代码或实际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的数据量,但依赖于该数据的数据量不可扩展。)

注意:正如@user指出的那样,如果您的size_t不超过uint32_t,那么此实现将错误地拒绝非常大的邮件。具体而言,7 + n > n不被拒绝的消息。我对此(不太可能)的情况进行了动态检查。