【学习】安全编程之“函数参数”篇1

规则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++安全编程规范

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容