缓冲区溢出是指当计算机程序向缓冲区内填充的数据位数超过了缓冲区本身的容量。溢出的数据覆盖在合法数据上。理想情况是,程序检查数据长度并且不允许输入超过缓冲区长度的字符串。但是绝大多数程序都会假设数据长度总是与所分配的存储空间相匹配,这就为缓冲区溢出埋下隐患。
操作系统所使用的缓冲区又被称为堆栈,在各个操作进程之间,指令被临时存储在堆栈当中,堆栈也会出现缓冲区溢出。 当一个超长的数据进入到缓冲区时,超出部分就会被写入其他缓冲区,其他缓冲区存放的可能是数据、下一条指令的指针,或者是其他程序的输出内容,这些内容都被覆盖或者破坏掉。可见一小部分数据或者一套指令的溢出就可能导致一个程序或者操作系统崩溃。
例如:
#include <stdio.h> #include <string.h> #include <iostream> using namespace std; int main(int argc, char *argv[]) { char buf[10]; strcpy(buf, argv[1]); cout<<buf; return 0; }
连续输入20个字符就产生了溢出。
C语言常用的strcpy、sprintf、strcat 等函数都非常容易导致缓冲区溢出问题。
程序运行时,其内存里面一般都包含这些部分:
(1)程序参数和程序环境;
(2)程序堆栈,它通常在程序执行时增长,一般情况下,它向下朝堆增长。
(3)堆,它也在程序执行时增长,相反,它向上朝堆栈增长;
(4)BSS 段,它包含未初始化的全局可用的数据(例如,全局变量);
(5)数据段,它包含初始化的全局可用的数据(通常是全局变量);
(6)文本段,它包含只读程序代码。
BSS、数据和文本段组成静态内存:在程序运行之前这些段的大小已经固定。程序运行时虽然可以更改个别变量,但不能将数据分配到这些段中。
以下面的程序为例:
#include <stdio.h> char buf[3] = "abc"; int i; int main() { i = 1; return 0; }
其中,i属于BBS段,而buf属于数据段。两者都属于静态内存,因为他们在程序中虽然可以改变值,但是其分配的内存大小是固定的,如buf的数据大于三个字符,将会覆盖其他数据。
与静态内存形成对比,堆和堆栈是动态的,可以在程序运行的时候改变大小。堆的程序员接口因语言而异。在C语言中,堆是经由malloc()和其它相关函数来访问的,而C++中的new运算符则是堆的程序员接口。堆栈则比较特殊,主要是在调用函数时来保存现场,以便函数返回之后能继续运行。