C/C++ 指针操作

2021-05-28 10:22 更新

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

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

正确:

size_t pointer_length = sizeof(void*);

可能错误:

size_t structure_length = sizeof(Foo*);

可能是:

size_t structure_length = sizeof(Foo);

关联漏洞:

  • 中风险-逻辑漏洞

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

错误:

int a[3];
...;


if (a > 0)
  ...;

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

int a[3];
...;


if (&a[0])
  ...;

可能是:

int a[3];
...;


if(a[0] > 0)
  ...;

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

关联漏洞:

  • 中风险-逻辑漏洞

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

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

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

关联漏洞:

  • 高风险-内存破坏

8.4 【必须】检查空指针

错误:

*foo = 100;


if (!foo) {
  ERROR("foobar");
}

正确:

if (!foo) {
  ERROR("foobar");
}


*foo = 100;

错误:

void Foo(char* bar) {
  *bar = '\0';
}

正确:

void Foo(char* bar) {
  if(bar)
    *bar = '\0';
  else
    ...;
}

关联漏洞:

  • 低风险-拒绝服务

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

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

错误:

void foo() {
  char* p = (char*)malloc(100);
  memcpy(p, "hello", 6);
  // 此时p所指向的内存已被释放,但是p所指的地址仍然不变
  printf("%s\n", p);
  free(p);
  // 未设置为NULL,可能导致UAF等内存错误


  if (p != NULL) {  // 没有起到防错作用
    printf("%s\n", p); // 错误使用已经释放的内存
  }
}

正确:

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

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

关联漏洞:

  • 高风险-内存破坏

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

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

错误:

const int NAME_TYPE = 1;
const int ID_TYPE = 2;


// 该类型根据 msg_type 进行区分,如果在对MessageBuffer进行操作时没有判断目标对象,则存在类型混淆
struct MessageBuffer {
  int msg_type;
  union {
    const char *name;
    int name_id;
  };
};


void Foo() {
  struct MessageBuffer buf;
  const char* default_message = "Hello World";
  // 设置该消息类型为 NAME_TYPE,因此buf预期的类型为 msg_type + name
  buf.msg_type = NAME_TYPE;
  buf.name = default_message;
  printf("Pointer of buf.name is %p\n", buf.name);


  // 没有判断目标消息类型是否为ID_TYPE,直接修改nameID,导致类型混淆
  buf.name_id = user_controlled_value;


  if (buf.msg_type == NAME_TYPE) {
    printf("Pointer of buf.name is now %p\n", buf.name);
    // 以NAME_TYPE作为类型操作,可能导致非法内存读写
    printf("Message: %s\n", buf.name);
  } else {
    printf("Message: Use ID %d\n", buf.name_id);
  }
}

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

void Foo() {
  struct MessageBuffer buf;
  const char* default_message = "Hello World";
  // 设置该消息类型为 NAME_TYPE,因此buf预期的类型为 msg_type + name
  buf.msg_type = NAME_TYPE;
  buf.name = default_msessage;
  printf("Pointer of buf.name is %p\n", buf.name);


  // 判断目标消息类型是否为 ID_TYPE,不是预期类型则做对应操作
  if (buf.msg_type == ID_TYPE)
    buf.name_id = user_controlled_value;


  if (buf.msg_type == NAME_TYPE) {
    printf("Pointer of buf.name is now %p\n", buf.name);
    printf("Message: %s\n", buf.name);
  } else {
    printf("Message: Use ID %d\n", buf.name_id);
  }
}

关联漏洞:

  • 高风险-内存破坏

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

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

错误例子:

class Foo {
 public:
  explicit Foo(int num) { data_ = num; };
  void Function() { printf("Obj is %p, data = %d\n", this, data_); };
 private:
  int data_;
};


std::unique_ptr<Foo> fool_u_ptr = nullptr;
Foo* pfool_raw_ptr = nullptr;


void Risk() {
  fool_u_ptr = make_unique<Foo>(1);


  // 从独占智能指针中获取原始指针,<Foo>(1)
  pfool_raw_ptr = fool_u_ptr.get();
  // 调用<Foo>(1)的函数
  pfool_raw_ptr->Function();


  // 独占智能指针重新赋值后会释放内存
  fool_u_ptr = make_unique<Foo>(2);
  // 通过原始指针操作会导致UAF,pfool_raw_ptr指向的对象已经释放
  pfool_raw_ptr->Function();
}




// 输出:
// Obj is 0000027943087B80, data = 1
// Obj is 0000027943087B80, data = -572662307

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

void Safe() {
  fool_u_ptr = make_unique<Foo>(1);
  // 调用<Foo>(1)的函数
  fool_u_ptr->function();


  fool_u_ptr = make_unique<Foo>(2);
  // 调用<Foo>(2)的函数
  fool_u_ptr->function();
}


// 输出:
// Obj is 000002C7BB550830, data = 1
// Obj is 000002C7BB557AF0, data = 2

关联漏洞:

  • 高风险-内存破坏
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号