C安全问题与指针误用

简介:

指针的声明与初始化

1、不恰当的指针声明

考虑如下的声明:

int* ptr1, ptr2; // ptr1为指针,ptr2为整数

正确的写法如下:

int* ptr1, *ptr2;

用类型定义代替宏定义是一个好的习惯,类型定义允许编译器检查作用域规则,而宏定义不一定会。

使用宏定义辅助声明变量,如下所示:

#define PINT int*
PINT ptr1, ptr2;

不过结果和前面所说的一致,更好的方法是使用下面的类型定义:

typedef int* PINT;
PINT ptr1, ptr2;

2、使用指针未初始化

在使用指针之前未初始化会导致运行时错误,如下面的代码:

int* p;
...
printf("%d\n", *p);

指针p未被初始化,可能含有垃圾数据

3、处理未初始化指针

  • 总是用NULL来初始化指针
  • 用assert函数
  • 用第三方工具

把指针初始化为NULL更容易检查是否使用正确,即便这样,检查空值也比较麻烦,如下所示:

int *pi = NULL;
...
if(pi == NULL) {
//不应该解引pi
} else {
//可以使用pi
}

我们可以使用assert函数来测试指针是否为空值:

assert(pi != NULL);

指针的使用问题

缓冲区溢出

缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量,使得溢出的数据覆盖在合法数据上,理想的情况是程序检查数据长度并不允许 输入超过缓冲区长度的字符,但是绝大多数程序都会假设数据长度总是与所分配的储存空间相匹配,这就为缓冲区溢出埋下隐患。操作系统所使用的缓冲区又被称 为"堆栈".。在各个操作进程之间,指令会被临时储存在"堆栈"当中,"堆栈"也会出现缓冲区溢出。

下面几种情况可能导致缓冲区的溢出:

  • 访问数组元素时没有检查索引值

  • 对数组指针做指针算术运算时不够小心

  • 用gets这样的函数从标准输入读取字符串

  • 误用strcpy和strcat这样的函数

1、测试NULL

使用malloc这样的函数的时候一定要检查返回值,否则可能会导致程序的非正常终止,下面是一般的方法:

float *vector = malloc(20 * sizeof(float));
if(vector == NULL) {
    //malloc分配内存失败
} else {
    //处理vector
}

2、错误使用解引操作

声明和初始化指针的常用方法如下:

int num;
int *pi = #

下面是一种看似等价但是错误的声明方法:

int num;
int *pi;
*pi = #

3、迷途指针

参见《C迷途指针

4、越过数组边界访问内存

没有什么可以阻止程序访问为数组分配的空间以外的内存,下面的例子中,我们声明并初始化了三个数组来说明这种行为:

#include<stdio.h>
int main()
{
    char firstName[8] = "1234567";
    char middleName[8] = "1234567";
    char lastName[8] = "1234567";
    middleName[-2] = 'X';
    middleName[0] = 'X';
    middleName[10] = 'X';
    printf("%p %s\n", firstName, firstName);
    printf("%p %s\n", middleName, middleName);
    printf("%p %s\n", lastName, lastName);
    return 0;
}

运行结果如下:

下图说明了内存分配情况:

5、错误计算数组长度

将数组传给函数时,一定要同时传递数组长度,这个信息帮助函数避免越过数组边界

#include<stdio.h>
void replace(char buffer[], char replacement, size_t size)
{
    size_t count = 0;
    while(*buffer && count++ < size) {
        *buffer = replacement;
        buffer++;
    }
}
int main()
{
    char name[8];
    strcpy(name, "Alexander");
    replace(name, '+', sizeof(name));
    printf("%s\n", name);
    return 0;
}

6、错误使用sizeof操作符

其中一个例子是试图检查指针边界但方法错误

#include<stdio.h>
int main()
{
    int buffer[20];
    int *pbuffer = buffer;
    for(int i = 0; i < sizeof(buffer); i++) {
        *(pbuffer++) = 0;
    }
    return 0;
}

改为:i < sizeof(buffer) / sizeof(int);

7、有界指针

有界指针是指指针的使用被限制在有效的区域内,C没有对这类指针提供直接的支持,但是可以自己显示地确保。如下所示:

#define SIZE 32
char name[SIZE];
char *p = name;
if(name != NULL) {
    if(p >= name && p < name + SIZE) {
        //有效指针,继续
    } else {
        //无效指针,错误分支
    }
}

一种有趣的变化是创建一个指针检验函数;

下面的代码定义一个函数消除无效指针:

int valid(void *ptr) {
  return (ptr != NULL);
}

下面的代码依赖于_etext的地址,定义于很多的类linux操作系统,在windows上无效:

#include <stdio.h>
#include <stdlib.h>
 
int valid(void *p) {
  extern char _etext;
  return (p != NULL) && ((char*) p > &_etext);
}
 
int global;
 
int main(void) {
  int local;
 
  printf("pointer to local var valid? %d\n", valid(&local));
  printf("pointer to static var valid? %d\n", valid(&global));
  printf("pointer to function valid? %d\n", valid((void *)main));
 
  int *p = (int *) malloc(sizeof(int));
  printf("pointer to heap valid? %d\n", valid(p));
  printf("pointer to end of allocated heap valid? %d\n", valid(++p));
  free(--p);
  printf("pointer to freed heap valid? %d\n", valid(p));
  printf("null pointer valid? %d\n", valid(NULL));
 
  return 0;
}

在linux平台运行结果如下:

pointer to local var valid?  1
pointer to  static  var valid?  1
pointer to function valid?  0
pointer to heap valid?  1
pointer to end of allocated heap valid?  1
pointer to freed heap valid?  1
null  pointer valid?  0
另一种方法是利用ANSI-C和C++的边界检查工具(CBMC)
目录
相关文章
|
2月前
|
Rust 安全 开发者
Rust的安全特性概览:守护内存安全与空指针的终结者
Rust作为一种系统级编程语言,以其独特的内存安全特性和对空指针的严格管理,为开发者提供了更加稳健和安全的编程环境。本文将对Rust的内存安全机制、空指针处理策略以及其他安全特性进行概览,旨在展示Rust如何帮助开发者构建更加安全和可靠的软件系统。
|
5月前
|
存储 安全 编译器
深入解析Go非类型安全指针:技术全解与最佳实践2
深入解析Go非类型安全指针:技术全解与最佳实践2
206 0
|
5月前
|
存储 Rust 安全
深入解析Go非类型安全指针:技术全解与最佳实践1
深入解析Go非类型安全指针:技术全解与最佳实践
180 0
|
6月前
|
存储 安全 Go
Golang 语言中的非类型安全指针
Golang 语言中的非类型安全指针
19 0
|
11月前
|
安全 Windows
因为你安全了,所以你危险了——空指针引用
1.本文章属于系列文章《因为你安全了,所以你危险了》中的第一篇 2.本篇文章的作者是Gcow安全团队复眼小组的晏子霜,未经允许禁止转载 3.本篇文章需要你对GDI子系统有一定了解,最好阅读过部分关于Windows显示驱动,打印机驱动,以及调色板这一块的源码.并对DDI函数有一定了解,以及编写Windows Kernel Exploit的能力
|
安全 C++ 数据挖掘
安全编程-c++野指针和内存泄漏
摘要:   尽管C++ 野指针和内存泄漏一直被诟病,但是在实时性很强的应用场合,c++ 仍然是不二之选。游戏服务器开发仍然使用c++ 作为主语言,但是大多结合动态脚本技术,一方面规避了野指针和内存泄露,一方面获得了开发效率和扩展性的红利。
1014 0
|
16天前
|
存储 C语言
C语言 — 指针进阶篇(下)
C语言 — 指针进阶篇(下)
20 0
|
16天前
|
存储 C语言 C++
C语言 — 指针进阶篇(上)
C语言 — 指针进阶篇(上)
26 0
|
22天前
|
存储 程序员 C语言
C语言指针的概念、语法和实现
在C语言中,指针是其最重要的概念之一。 本文将介绍C语言指针的概念、语法和实现,以及如何使用它们来编写高效的代码。
13 0
|
1月前
|
存储 人工智能 编译器
C语言指针详解
指针运算,指针和数组,二级指针
C语言指针详解