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報告所有的編譯警告。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章