[英]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);
}
检查packet
和result
是否为空是通常的好习惯。
当标头是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.