Understanding and using c pointers 第七章读书笔记

tonybai在他的网站上写了一个本书的知识点纲要

安全问题与指针的不恰当使用

本章从三个方面来讨论安全问题
1.指针声明与初始化
2.指针的不当使用
3.内存回收问题(deallocation problems)

第一部分:指针的声明与初始化
指针声明不当。如:

int* ptr1,ptr2;
作者本意是声明两个int指针,但是实际结果是只有ptr1被声明为指针,ptr2没有被声明为指针,所以应该这样声明int *ptr1,*ptr2;

#define PINT int*
PINT ptr1,ptr2;	//宏替换是代码的替换,所以依然只有ptr1被声明为指针,ptr2没有被声明为指针

typedef int* PINT;
PINT ptr1,ptr2;	//使用typedef这样就将ptr1和ptr2都声明为指针了
指针使用前没有初始化
使用未经初始化的指针可能会导致运行时错误。有时也称为野指针

int *p1;
...
printf("%d\n",*pi);	//pi在使用时根本没有初始化,里面存的是垃圾

处理未初始化的指针
1.初始化指针时将其置为NULL
2.使用assert函数
3.使用第三方工具

如:
int *pi = NULL;	//置为NULL
...
if(pi == NULL)	//判断是否为NULL
{
	
}
else
{
	
}
也可以使用assert,调试时非常有用
assert(pi != NULL);

第二部分:指针的使用问题

该部分会讨论有关字符串、结构和函数指针等等,许多安全问题都是以缓冲区溢出这个概念为中心。
缓冲区溢出可通过以下几种方式发生
1.数组中使用索引访问元素时不检查索引的值
2.使用数组指针进行指针运算时粗心大意
3.使用像gets之类的函数从标准输入中读取字符串
4.strcpy和strcat的不当使用


检查是否为NULL(Test for NULL)
对使用malloc函数返回的值一定要进行防空判断如:

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

(误用解引用操作符*)Misuse of the Deference Operator

正确情况
int num;
int *pi = &m;	/*这里的*是用来声明指针*/
错误情况:
int num;
int *pi;	/*声明了一个指针*/
*pi = #	/*这里的*是解引用,应该改为pi = #*/

悬挂指针(Dangling Poiners第二章说过)


数组访问越界(Accessing Memory Outside the Bounds of an Array)

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);
其一种可能的内存分配如下图:



数组大小计算错误(Calculating the Array Size Incorrectly)

#include <stdio.h>
#include <ctype.h>
#include <string.h>

void replace(char buffer[],char replacement,size_t size);

main()
{
	char name[8];
	strcpy(name,"Alexander");/* 越界了,因为name数组只能有8个char,减去一个NUL字符,实际只能有7个char,但是Alexander有8个字符 */
	replace(name,'+',sizeof(name));/* 这里数组元素个数应该用sizeof(name) / sizeof(char),这样做图方便,因为sizeof(char)就是1 */
	printf("%s\n",name);	/* 输出++++++++r */
}

void replace(char buffer[],char replacement,size_t size)
{
	size_t count = 0;
	while(*buffer != '\0' && count++ < size)
	{
		*buffer = replacement;
		buffer++;
	}
}

误用sizeof操作符(Misusing the sizeof Operator)
int buffer[20];
int *pbuffer = buffer;
int i;
for(i = 0; i < sizeof(buffer); i++)	/* sizeof(buffer)的使用造成越界,应该用sizeof(buffer) / sizeof(int) */
{
	*(pbuffer++) = 0;
}

永远匹配指针类型(Always Match Pointer Types)

It is a good idea to always use the appropriate pointer type for the data.

/* 这个例子非常适合在计算机对不同数值的表示中讲,且涉及到了机器的大小端法表设计,非常好 */
int num = 2147483647;
int *pi = &num;
short *ps = (short*)pi;
printf("pi: %p,Value(16):%x,Value(10):%d\n",pi,*pi,*pi);
printf("ps: %p,Value(16):%hx,Value(10):%hd\n",ps,(unsigned short)*ps,(unsigned short)*ps);
小端法如下图:



有界限的指针(Bounded Pointers)

C语言并没有提供直接的支持,但是可以像下面一样由程序员控制

#define SIZE 32
char name[SIZE];
char *p = name;
if(name != NULL)
{
	if(p > name  && p < name + SIZE)
	{
		//合法指针
	}
	else
	{
		//不合法指针
	}
}
使用这种方法很繁琐(tedious),可以使用静态分析等等


字符串相关的安全问题(String Security Issues)

strcpy和strcat可能会导致缓冲溢出,C11中新引入的strcpy_s和strcat_s目前只有微软Visual C++支持,这两个新函数在发生缓冲区溢出时返回错误
同样还有新引入的scanf_s和wscanf_s

char firstName[8];
int result;
result = strcpy_s(firstName,sizeof(firstName),"Alexander");
The use of some functions can result in an attacker accessing memory using a technique known as  format string attacks.
作者在这里举了一个printf的例子
int main(int argc,char** argv)
{
	printf(argv[1]);	/* 这里就可能会造成字符串攻击 */
	...
}
更多这方面的信息请参阅 hackerproof.org


指针运算与结构(Pointer Arithmetic and Structures)

对于数组可以放心使用指针运算,因为数组保证内存分配是连续的,而对于结构其位域可能不在连续的内存上(as the structure's fields may not be allocated in consecutive regions of memory)
比如下面结构:

typedef struct _employee
{
	char name[10];
	int age;
} Employee;
name分配了10个字节,其后跟着一个int,但是int要对齐到能被4整除的位置,所以name和age之间就有位填补(gap)。如图:


Even if the memory within a structure is contiguous, it is not a good practice to use pointer arithmetic with the structure’s fields.
即使结构的内存是连续的,最好也不要使用指针运算

typedef struct _item
{
	int partNumber;
	int quantity;
	int binNumber;
} Item;

Item part = {12345, 35, 107};
int *pi = &part.partNumber;
printf("Part number: %d\n",*pi);
pi++;
printf("Quantity: %d\n",*pi);
pi++;
printf("Bin number: %d\n",*pi);
正常情况下输出结果是我们期待的,但它并不能保证总是起作用(but it is not guaranteed to work)下面是一种较好的办法
int *pi = &part.partNumber;
printf("Part number: %d\n",*pi);
pi = &part.quantity;
printf("Quantity: %d\n",*pi);
pi = &part.binNumber;
printf("Bin number: %d\n",*pi);
更好的办法是:
printf("Part number: %d\n",part.partNumber);
printf("Quantity: %d\n",part.quantity);
printf("Bin number: %d\n",part.binNumber);

函数指针问题(Function Pointers Issues)

函数指针误用与滥用会导致不可预测的结果,考虑如下函数:

int getSystemStatus()
{
	int status;
	...
	return status;
}
判断系统状态是否为0的最佳方法如下:
if(getSystemStatus() == 0)
{
	printf("Status is 0\n");
}
else
{
	printf("Status is not 0\n");
}
但如果忘了写()的话,代码运行就不正常了
if(getSystemStatus == 0)
{
	printf("Status is 0\n");
}
else
{
	printf("Status is not 0\n");	/* 总会执行着这句 */
}
这样写的话也不科学:
if(getSystemStatus)	/*函数指针,肯定不为0*/
{
	//总会执行这段代码
}

Do not assign a function to a function pointer when their signatures differ.This can result in undefined behaviour.例子如下:

int (*fptrCompute)(int,int);
int add(int n1,int n2,int n3)
{
	return n1 + n2 + n3;
}
fptrCompute = add;
fptrCompute(2,5);	/* 实际需要3个参数,但却只传了2个 ,代码会编译通过gcc给出了警告,但是结果却很难预料了 */



第三部分:内存回收问题(Memory Deallocation Issues)

多次释放(Double free)

char *name = (char*) malloc(...);
...
free(name);		/* 第一次释放 */
...
free(name);		/* 第二次释放 */

cert.org上有有关这方面的更多资料,一种简单的避免双重释放就是释放完后即置为NULL,也可以写自己的free函数

char *name = (char *) malloc(...);
...
free(name);
name = NULL;

清除敏感数据(Clearing Sensitive Data)

程序退出时清除敏感数据有助于隐私保存,像陈老师那样把照片没彻底删除再跑去修电脑实在是太危险了。如:

char name[32];
int userID;
char *securityQuestion;

//assign values
...

//delete sensitive information
memset(name,0,sizeof(name));
userID = 0;
memset(securityQuestion,0,strlen(securityQuestion));
如果name被声明为指针,那么在回收之前要清除其内存如:
char *name = (char*) malloc(...);
...
memset(name,0,sizeof(name));
free(name);

使用静态分析工具(Using Static Analysis Tools)
有许多静态分析工具可用于检测指针的不当使用。GCC编译器使用-Wall报告所有的编译警告。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值