规则1:数组作为函数参数时,必须同时将其长度作为函数的参数
通过函数参数传递数组或一块内存进行写操作时,函数参数必须同时传递数组元素个数或所传递的内存块大小,否则函数在使用数组下标或访问内存偏移时,无法判断下标或偏移的合法范围,产生越界访问的漏洞。
以下代码中,函数ParseMsg不知道msg的范围,容易产生内存越界访问漏洞。
1. int ParseMsg(BYTE *msg)
2. {
3. ...
4. }
5. ...
6. size_t len = ...
7. BYTE *msg = (BYTE *)malloc(len); //此处分配的内存块等同于字节数组
8. ...
9. ParseMsg(msg);
10. ...
正确的做法是将msg的大小作为参数传递到ParseMsg中,如下代码:
1. int ParseMsg(BYTE *msg, size_t msgLen)
2. {
3. ASSERT(msg != NULL);
4. ASSERT(msgLen != 0);
5. ...
6. }
7. ...
8. size_t len = ...
9. BYTE *msg = (BYTE *)malloc(len);
10. ...
11. ParseMsg(msg, len);
12. ...
下面的代码,msg是固定长度的数组,也必须将数组大小作为函数的参数:
1. int ParseMsg(BYTE *msg, size_t msgLen)
2. {
3. ASSERT(msg != NULL);
4. ASSERT(msgLen != 0);
5. ...
6. }
7. ...
8. BYTE msg[MAX_MSG_LEN] = {0};
9. ...
10. ParseMsg(msg, sizeof(msg));
11. ...
对于const char *类型的参数,它的长度是通过'\0'的位置计算出来,不需要传长度参数。
1. int SearchName(const char *name)
2. {
3. ...
4. }
5. ...
6. char *name = getName(...);
7. ...
8. int ret = SearchName(name);
9. ..
如果参数是char *,且参数作为写内存的缓冲区,那么必须传入其缓冲区长度。如:
1. int SaveName(char *name, size_t len, const char *inputName)
2. {
3. ...
4. ret = strcpy_s(name, len, inputName);
5. ...
6. }
7. ...
8. char name[NAME_MAX] = {0};
9. ...
10. int ret = SaveName(name, sizeof(name), inputName);
11. ...
如果函数仅对字符串中的特定字符进行一对一替换,或者删除字符串中的特定字符,这时对字符数组的访问不会超过原字符串边界,因此这类函数不需要传待修改的字符串长度。
1. void FormatPathSeparator(char *path)
2. {
3. unsigned int i = 0;
4.
5. if (path == NULL) {
6. return;
7. }
8.
9. while (path[i] != '\0') {
10. if ('\\' == path[i]) {
11. path[i] = '/';
12. }
13. i++;
14. }
15. }
例外1: 对于const struct *类型的数组入参,如果它的长度可以通过特定元素值判断结尾,那么可以不传递结构体数组的长度。
1.struct DevType {
2. int vendorID;
3. int deviceID;
4. int subDevice;
5. };
6. ...
7. const struct DevType cardIds[] = {
8. { CARD_ID, PCI_DEV_T1, PCI_ANY_ID },
9. { CARD_ID, PCI_DEV_T2, PCI_ANY_ID },
10. { CARD_ID, PCI_DEV_T3, PCI_ANY_ID },
11. { 0, }
12. };
13. ...
14. int BuildCardQueue(const struct DevType *cards)
15. {
16. int index = 0;
17.
18. ASSERT(cards != NULL);
19. while (cards[index]->vendorID != 0) {
20. ...
21. }
22. ...
23. }
例外2: 对固定长度的数组,如果用数组的头地址作为子函数参数,由于性能原因,可以不用传递其长度。 下例中,EtherAddrCopy()函数仅用于MAC地址的赋值,不会用于其他地方,且长度是可保证的,其拷贝使用的下标(或偏移)没有外部数据的影响。由于性能高度敏感,因此这里没有传入目的缓冲区dst的长度。
1. #define ETH_ALEN 6
2.
3. static const u8 ethReservedAddrBase[ETH_ALEN] = {...};
4. ...
5. void EtherAddrCopy(unsigned char *dst, const unsigned char *src)
6. {
7. dst[0] = src[0];
8. dst[1] = src[1];
9. dst[2] = src[2];
10. dst[3] = src[3];
11. dst[4] = src[4];
12. dst[5] = src[5];
13. }
14.
15. int AddDevice()
16. {
17. unsigned char mac[ETH_ALEN];
18. ...
19. EtherAddrCopy(mac, ethReservedAddrBase);
20. ...
21. }
参考:gitcode社区-开源文档-菊厂C&C++安全编程规范