嵌入式 Linux C语言(九)——C语言的安全问题和指针陷阱

嵌入式 Linux C语言(九)——C语言的安全问题和指针陷阱

C语言是灵活度和自由度较大的编程语言,作为C语言核心的指针更是让C语言程序员可以越过安全的栅栏,对某些内存区域进行破坏性访问,引发安全风险。很多安全问题都能追根溯源到指针的误用。本文将从指针的角度解读C语言常见的安全问题和指针陷阱。

一、指针的声明和初始化

1、不恰当的指针声明

int* ptr1, ptr2;//声明ptr1为int指针,ptr2为整型

int *ptr1, *ptr2;//ptr1,ptr2都声明为指针

#define PINT int *

PINT ptr1, ptr2;//等价于int* ptr1, ptr2;

推荐方式:

typedef int * PINT

PINT ptr1, ptr2;//等价于int *ptr1, *ptr2;

2、使用指针前未初始化

初始化指针前使用指针会导致运行时错误,这种指针称为野指针。

int *p;

printf(“%d\n”, *p);

指针变量p未被赋值指针(地址),此时*p将会导致不可预知的情况。

3、处理未初始化指针

有三种方法可以用来处理未初始化的指针:

A、用NULL初始化指针

int *p = NULL;

if(NULL == p)

{

}

B、用assert函数

assert(NULL != p);

C、用第三方工具

二、误用指针

很多安全问题聚焦于缓冲区溢出,覆写对象边界以外的内存就会导致缓冲区溢出,这块内存可能是本程序的地址空间,也可能是其他进程的,如果是程序地址空间以外的内存,大部分操作系统会发出一段错误然后中止程序。如果缓冲区溢出发生在应用程序的地址空间内,就会导致对数据的未授权访问和控制转移到其他代码段,导致系统被攻陷。下列情况可能导致缓冲区溢出:

A、访问数组元数时没有检查索引项

B、对数组指针做指针算数运算时不够小心

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

D、误用strcpy和strcat这样的函数

如果缓冲区溢出发生在栈帧的元数上,就可能把栈帧的返回地址部分覆写为对同一时间创建的恶意代码的调用。

1、测试NULL

对于动态分配内存函数一定要检查返回值,否则如果内存分配失败程序可能会非正常终止。

char *vetcor = (char *)malloc(128*sizeof(char));

if(NULL == vector)

{

//Malloc failure.

}

2、错误使用解引操作

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

int num;

int *p = #

但是以下是错误的

int num;

int *p ;

*p = #//正确为p = &num

3、迷途指针

释放指针后却仍然在引用原来的内存,就会产生迷途指针。如果在释放指针后仍然在试图操作原来的内存,读操作可能会返回无效数据,写操作可能会破坏这块内存的值,导致其他正在使用这块内存的程序出现异常。

4、数组访问越界

C语言中数组并没有提供防止访问数组越界的机制,因此必须由程序员保证对数组的访问不越界。尤其是用指针方式访问数组元素时一定要保证不能越过数组边界。

5、错误计算数组长度

将数组传递给函数时,一定要同时传递数组长度。数组长度参数可以避免缓冲区溢出。strcpy函数就是一个允许缓冲区溢出的函数,因此尽量避免使用,使用具有缓冲区保护机制的strncpy函数。

三、释放问题

1、重复释放

重复释放是指将同一块内存释放两次,如:

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

...................

free(name);

....................

free(name);

为了避免对同一块内存的重复释放,一般释放指针后将指针置为NULL。

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

...................

free(name);

name = NULL;

2、清除敏感信息

当应用程序终止后,大部分操作系统都不会把用到的内存清零或执行别的操作,系统可能会将之前用过的内存分配给其他程序使用,如果这些内存原来保存的是身份信息、密码信息等敏感数据,这样做就是不安全的,因为其他人可以使用这部分内存,可以通过覆写将内存中敏感数据清空。

四、指针类型转换

指针类型的转换可以实现一些特殊的功能,如:

访问有特殊目的的地址

判断机器的字节序

1、访问特殊用途的地址

在嵌入式系统开发中,很多特殊功能寄存器是统一编址的,通过访问这些特殊功能寄存器可以控制相应的外设的功能。

#define WTCON ( *((unsigned long *)0xE2700000))

WTCON |= 0X3<<8;

通过指针类型的转换,可以WTCON寄存器的某些位设置为1,进而控制外设。

2、判断机器的字节序

字节序是指数据在内存单元中字节的存储顺序。字节序一般分为小字节序和大字节序,也称小端模式和大端模式。小端模式表示整数的4字节的中的低地址存储整数数据的低位。通过将整数的地址从指针转换为char,打印出每个字节的内存就可以知道机器的字节序。

#include <stdio.h>

int main(int argc, char**argv)

{

int num = 0x12345678;

char *p = (char *)#

int i;

for(i = 0; i < 4; i++)

{

printf("%p:%2x\n", p,(unsigned char)*p++);

}

return 0;

}

运行结果如下:

0x7fffe8f13cb9:78

0x7fffe8f13cba:56

0x7fffe8f13cbb:34

0x7fffe8f13cbc:12

结论:低位存储低字节,小端模式。

时间: 2024-11-16 10:33:58

嵌入式 Linux C语言(九)——C语言的安全问题和指针陷阱的相关文章

嵌入式linux C++语言(九)——模板

嵌入式linux C++语言(九)--模板 泛型(Generic Programming)即是指具有在多种数据类型上皆可操作的含意. 泛型编程的代表作品STL是一种高效.泛型.可交互操作的软件组件.    泛型编程最初诞生于C++中,目的是为了实现C++的STL(标准模板库).其语言支持机制就是模板(Templates).模板的精神其实很简单:参数化类型.换句话说,把一个原本特定于某个类型的算法或类当中的类型信息抽掉,抽出来做成模板参数T. 一.函数模板 1.函数重载实现的泛型 #include

嵌入式linux C++语言(四)——类与对象

嵌入式linux C++语言(四)--类与对象 类的设计和使用如下: #include <iostream>#include <stdlib.h>#include <stdio.h>#include <string.h>using namespace std;class Stack{public:    Stack(int size=1024);    ~Stack();    void init();    bool isEmpty();    bool

嵌入式linux C++语言(二)——C++对C语言基础语法的扩展

嵌入式linux C++语言(二)--C++对C语言基础语法的扩展 C++是基于C语言扩展发展而来的面向对象的程序设计语言,本文将主要讨论C++语言基于C语言扩展的方面. 一.类型增强 1.类型检查更严格 在C语言中: const int a = 100; int *p = &a; 在C++语言中: const int a = 100;//必须在定义的时候初始化 const int *p = &a; 在C++语言中不能隐式转换数据类型. error: invalid conversion

嵌入式 Linux C语言——C语言基础

嵌入式 Linux C语言--C语言基础 一.数据类型 1.基本数据类型 数据类型是创建变量的模型.变量名是连续存储空间的别名,程序中使用变量命名存储空间,通过变量可以使用存储空间.变量所占的内存大小取决于创建变量的数据类型. 2.有符号和无符号 有符号数中数据类型的最高位用于标识数据的符号,最高位为1表示为负数,最高位为0表示为正数. 计算机中有符号数通常使用补码表示,正数的补码为正数本身,负数的补码为负数的绝对值的各位取反后加1. 计算机中无符号数通常使用原码表示,无符号数默认为正数,没有符

嵌入式linux C++语言(七)——继承与派生

嵌入式linux C++语言(七)--继承与派生 一.继承 在C++编程中软件可重用性(software reusability)是通过继承(inheritance)机制来实现的.类的继承,是新的类从已有类那里得到已有的特性.或从已有类产生新类的过程就是类的派生.原有的类称为基类或父类,产生的新类称为派生类或子类. 派生类的声明:class 派生类名:[继承方式] 基类名{派生类成员声明:};    一个派生类可以同时有多个基类,这种情况称为多重继承,派生类只有一个基类,称为单继承. 继承方式规

嵌入式linux C++语言(一)——C++简介

嵌入式linux C++语言(一)--C++简介 一.C++简介 C语言作是结构化和模块化的语言,适合处理较小规模的程序.对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言并不合适.为了解决软件危机, 20世纪80年代, 计算机界提出了OOP(object oriented programming)思想,支持面向对象的程序设计语言应运而生.Smalltalk 就是当时问世的一种面向对象的语言.在实践工作中,由于C语言的广泛使用,在C语言的基础上根据面向对象的思想发展了C语言,形成了C

嵌入式Linux C语言(三)——指针与函数

嵌入式Linux C语言(三)--指针与函数 指针对函数的功能有巨大的贡献,指针能够将数据传递给函数,并且允许函数对数据进行修改.指针对于函数的作用主要有两方面:将指针传递给函数和声明函数指针. 一.程序的栈和堆 程序的栈和堆是C语言程序运行的运行时元素. 1.程序栈 程序栈是支持函数执行的内存区域,通常和堆共享一块内存区域,通常程序栈占据内存区域的下部,堆用内存区域的上部.程序栈存放栈帧,栈帧存放函数参数和局部变量.调用函数时,函数的栈帧被推倒栈上,栈向上长出一个栈帧,当函数终止时,函数的栈帧

嵌入式linux C++语言(六)——运算符重载

嵌入式linux C++语言(六)--运算符重载 运算符重载的本质是函数重载. 一.重载基础 1.运算符重载的语法 返值类型 operator 运算符名称(形参表列){    重载实体;} 2.友元重载 可以将运算符重载函数声明位友元函数 #include <iostream> using namespace std; class Complex { public:     Complex(float x=0, float y=0)         :_x(x),_y(y){}     voi

嵌入式linux C++语言(五)——友元

嵌入式linux C++语言(五)--友元 面向对象编程的类的设计机制实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,成员函数一般定义为公有的,是类与外部的通信接口.在实践中,类外的某些函数需要频繁地访问类的数据成员,将类外的函数定义为类的友元函数.除了友元函数外,还有友元类,两者统称为友元.友元的作用是提高了程序的运行效率(即减少了类型检查和安全性检查等都需要时间开销),但它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员. 友元可以是一个函数,该函数被称为友元函数. 一.