8 指针操作

8.1 【建议】检查在pointer上使用sizeof

除了测试当前指针长度,否则一般不会在pointer上使用sizeof。

正确:

  1. size_t pointer_length = sizeof(void*);

可能错误:

  1. size_t structure_length = sizeof(Foo*);

可能是:

  1. size_t structure_length = sizeof(Foo);

关联漏洞:

中风险-逻辑漏洞

8.2 【必须】检查直接将数组和0比较的代码

错误:

  1. int a[3];
  2. ...;
  3. if (a > 0)
  4. ...;

该判断永远为真,等价于:

  1. int a[3];
  2. ...;
  3. if (&a[0])
  4. ...;

可能是:

  1. int a[3];
  2. ...;
  3. if(a[0] > 0)
  4. ...;

开启足够的编译器警告(GCC 中为 -Waddress,并已包含在 -Wall 中),并设置为错误,可以在编译期间发现该问题。

关联漏洞:

中风险-逻辑漏洞

8.3 【必须】不应当向指针赋予写死的地址

特殊情况需要特殊对待(比如开发硬件固件时可能需要写死)

但是如果是系统驱动开发之类的,写死可能会导致后续的问题。

关联漏洞:

高风险-内存破坏

8.4 【必须】检查空指针

错误:

  1. *foo = 100;
  2. if (!foo) {
  3. ERROR("foobar");
  4. }

正确:

  1. if (!foo) {
  2. ERROR("foobar");
  3. }
  4. *foo = 100;

错误:

  1. void Foo(char* bar) {
  2. *bar = '\0';
  3. }

正确:

  1. void Foo(char* bar) {
  2. if(bar)
  3. *bar = '\0';
  4. else
  5. ...;
  6. }

关联漏洞:

低风险-拒绝服务

8.5 【必须】释放完后置空指针

在对指针进行释放后,需要将该指针设置为NULL,以防止后续free指针的误用,导致UAF等其他内存破坏问题。尤其是在结构体、类里面存储的原始指针。

错误:

  1. void foo() {
  2. char* p = (char*)malloc(100);
  3. memcpy(p, "hello", 6);
  4. // 此时p所指向的内存已被释放,但是p所指的地址仍然不变
  5. printf("%s\n", p);
  6. free(p);
  7. // 未设置为NULL,可能导致UAF等内存错误
  8. if (p != NULL) { // 没有起到防错作用
  9. printf("%s\n", p); // 错误使用已经释放的内存
  10. }
  11. }

正确:

  1. void foo() {
  2. char* p = (char*)malloc(100);
  3. memcpy(p, "hello", 6);
  4. // 此时p所指向的内存已被释放,但是p所指的地址仍然不变
  5. printf("%s\n", p);
  6. free(p);
  7. //释放后将指针赋值为空
  8. p = NULL;
  9. if (p != NULL) { // 没有起到防错作用
  10. printf("%s\n", p); // 错误使用已经释放的内存
  11. }
  12. }

对于 C++ 代码,使用 string、vector、智能指针等代替原始内存管理机制,可以大量减少这类错误。

关联漏洞:

高风险-内存破坏

8.6 【必须】防止错误的类型转换(type confusion)

在对指针、对象或变量进行操作时,需要能够正确判断所操作对象的原始类型。如果使用了与原始类型不兼容的类型进行访问,则存在安全隐患。

错误:

  1. const int NAME_TYPE = 1;
  2. const int ID_TYPE = 2;
  3. // 该类型根据 msg_type 进行区分,如果在对MessageBuffer进行操作时没有判断目标对象,则存在类型混淆
  4. struct MessageBuffer {
  5. int msg_type;
  6. union {
  7. const char *name;
  8. int name_id;
  9. };
  10. };
  11. void Foo() {
  12. struct MessageBuffer buf;
  13. const char* default_message = "Hello World";
  14. // 设置该消息类型为 NAME_TYPE,因此buf预期的类型为 msg_type + name
  15. buf.msg_type = NAME_TYPE;
  16. buf.name = default_message;
  17. printf("Pointer of buf.name is %p\n", buf.name);
  18. // 没有判断目标消息类型是否为ID_TYPE,直接修改nameID,导致类型混淆
  19. buf.name_id = user_controlled_value;
  20. if (buf.msg_type == NAME_TYPE) {
  21. printf("Pointer of buf.name is now %p\n", buf.name);
  22. // 以NAME_TYPE作为类型操作,可能导致非法内存读写
  23. printf("Message: %s\n", buf.name);
  24. } else {
  25. printf("Message: Use ID %d\n", buf.name_id);
  26. }
  27. }

正确(判断操作的目标是否是预期类型):

  1. void Foo() {
  2. struct MessageBuffer buf;
  3. const char* default_message = "Hello World";
  4. // 设置该消息类型为 NAME_TYPE,因此buf预期的类型为 msg_type + name
  5. buf.msg_type = NAME_TYPE;
  6. buf.name = default_msessage;
  7. printf("Pointer of buf.name is %p\n", buf.name);
  8. // 判断目标消息类型是否为 ID_TYPE,不是预期类型则做对应操作
  9. if (buf.msg_type == ID_TYPE)
  10. buf.name_id = user_controlled_value;
  11. if (buf.msg_type == NAME_TYPE) {
  12. printf("Pointer of buf.name is now %p\n", buf.name);
  13. printf("Message: %s\n", buf.name);
  14. } else {
  15. printf("Message: Use ID %d\n", buf.name_id);
  16. }
  17. }

关联漏洞:

高风险-内存破坏

8.7 【必须】智能指针使用安全

在使用智能指针时,防止其和原始指针的混用,否则可能导致对象生命周期问题,例如 UAF 等安全风险。

错误例子:

  1. class Foo {
  2. public:
  3. explicit Foo(int num) { data_ = num; };
  4. void Function() { printf("Obj is %p, data = %d\n", this, data_); };
  5. private:
  6. int data_;
  7. };
  8. std::unique_ptr<Foo> fool_u_ptr = nullptr;
  9. Foo* pfool_raw_ptr = nullptr;
  10. void Risk() {
  11. fool_u_ptr = make_unique<Foo>(1);
  12. // 从独占智能指针中获取原始指针,<Foo>(1)
  13. pfool_raw_ptr = fool_u_ptr.get();
  14. // 调用<Foo>(1)的函数
  15. pfool_raw_ptr->Function();
  16. // 独占智能指针重新赋值后会释放内存
  17. fool_u_ptr = make_unique<Foo>(2);
  18. // 通过原始指针操作会导致UAF,pfool_raw_ptr指向的对象已经释放
  19. pfool_raw_ptr->Function();
  20. }
  21. // 输出:
  22. // Obj is 0000027943087B80, data = 1
  23. // Obj is 0000027943087B80, data = -572662307

正确,通过智能指针操作:

  1. void Safe() {
  2. fool_u_ptr = make_unique<Foo>(1);
  3. // 调用<Foo>(1)的函数
  4. fool_u_ptr->function();
  5. fool_u_ptr = make_unique<Foo>(2);
  6. // 调用<Foo>(2)的函数
  7. fool_u_ptr->function();
  8. }
  9. // 输出:
  10. // Obj is 000002C7BB550830, data = 1
  11. // Obj is 000002C7BB557AF0, data = 2

关联漏洞:

高风险-内存破坏