gets函数的不安性详解
1 为什么gets()函数还在我们的代码中?
好吧,最终还是发生了。我们遇到了一个非常严重,并且非常普遍的缓冲区溢出问题。这个问题造成了非常大的影响,修复这个问题的过程,将会非常艰难,非常 慢,代价非常高。在我看来,可能在这个世界上,会有不少软件产品经理这样问程序员们:“为什么你没有警告过我?”,估计这些被问到的程序员中,有很多都会 直接回答道:“我警告过你了,你什么没有听进去?“
在软件开发的过程中,总是存在一个矛盾:正确的解决问题和快速的解决问题。这个问题在安全领域更加的突出。因此在接下来的几周时间里,我们来聊聊这个矛盾。这个矛盾的如下两个方面,在我们聊的过程中相当的重要:
不管你针对问题的解决方案有多完美,如果没有人使用这个解决方案,都是无用功
不管是处于什么目的,如果你没有使用完美解决方案,那所有的考虑都是白费功夫。因为你的代码里没有实现该解决方案
让我们从这个看起来非常俗气的例子开始吧:C标准库中的 gets() 函数。 这个函数的定义如下:
char * gets ( char * str );
gets() 函数的形参只有一个指针。它会从标准输入流中读字符到一块连续的内存地址空间中。这块地址空间的开始位置就是指针 str 指向的位置。当在输入流中遇到文件结束符( EOF )或者换行符(n)时,读取操作结束。当读入换行符(n)时,该字符不会被放入那块连续的地址空间中。在读取结束时, gets() 会自动在内存空间的末尾追加一个 NULL 字符。经过上述这些操作,对于程序员来说,这个函数得到的就是从标准输入进来的,以 NULL 字符结尾的C字符串。如果读入的字符流是一整行的话,行尾的换行符将会被舍去。
这个函数方便,也有局限性。 C程序员们经常使用它读取标准输入。下面的代码是一种典型的应用场景:
代码如下 | 复制代码 |
char input[100]; printf("Yes or no?n"); gets(input); /* and so on… */ |
在过去的30年里,许多C编程社区的同仁们都已经意识到 gets() 函数不安全,而且在保证接口不变的情况下,也无法被改良。原因也比较直观,这个函数只有一个指针作为参数,该指针指向的内存空间将用于保存读入数据。但是 gets() 函数无法知道它需要使用多大的内存空间。如果在标准输入中读入足够长的,不包含换行符的字符留, gets() 函数肯定会覆盖掉指定的内存区域,而程序员对此无能为力。
此外,除了 gets() 函数缺乏安全性,还有它的小伙伴 fgets() 也有问题。 这个函数的原型如下:
代码如下 | 复制代码 |
char * fgets ( char * str, int num, FILE * stream ); |
str 是一个指针,指向一块内存区域,读入的数据将会存储到这块内存空间。num 是一个整数,指定了内存空间的大小, stream 是一个文件指针,指定了可以从哪里读取。可能第一眼看过去,你会和我当时一样,觉得前面的那段不安全代码,可以使用 fgets() 函数重写,来避免遇到缓冲区溢出的问题。
代码如下 | 复制代码 |
char input[100]; printf("Yes or no?n"); fgets(input, 100, stdin); /* and so on… */ |
不过, gets() 函数和 fgets() 函数有个不同点。fgets() 函数会在遇到换行符时停止,并且其保存到内存中的数据会包含该换行符,而 gets() 函数会排除换行符。因此,就简单的这么重写代码无法实现完全同等的功能。而为了保证代码安全,又实现完全相同的功能,我们就需要检查内存地址中的字符,如 果在结尾有换行符,就将其删除。
因此我们可以用拍脑袋的方式, 得到下面的代码。这段代码既安全,又能保证和 gets() 函数的行为相同。
代码如下 | 复制代码 |
/* This code doesn't work! */ char input[100]; printf("Yes or no?n"); fgets(input, 100, stdin); char *last = input + strlen(input) – 1; if (*last == 'n') *last = ''; /* and so on… */ |
可是,虽然代码变复杂了,但是还是存在一个隐藏问题,该问题会导致程序崩溃,或者有安全隐患。当程序执行时,如果标准输入流已经得到了所有可用的字符,但 是还没有遇到文件结束符( EOF), fgets() 函数将会通过将 input[0] 标记为 NULL 字符的形式,直接返回一个 NULL 字符串。此时, strlen(intput) 的返回值为0, 因此导致 last 指针指向 input 数组之前的那个字符。因为不能确定这个字符到底是什么,这段代码的行为将因此无法判断。
做个随堂小练习吧, 请自行修复一下这段代码。 点击这里查看修复方法
在我过去工作过的一家公司里,曾经的经理是一个对安全非常敏感的人,他要求 gets() 函数从所有本地的C库中移除。这个要求,就导致我们经常需要重写从其他地方拿到的代码。所以有下面这段对话,也就不足为奇了。
A:你发给我的那段代码,你看了吗?我们需要重写里面的部分代码,去掉对 gets() 函数的调用
B:为什么 gets() 函数不能出现在代码中?
A:<长篇大论的解释>此处忽略5421个字
B:哈,有意思
A:如果你需要的话,我们很乐意发给你修改后的代码
B: 好的,我很乐意,发给我吧。不过现在我能告诉你的是,我们暂时还不能做什么,因为我们只能在客户发现并报告此问题的情况下,才能修改代码。
虽然 gets() 函数早就被公认为不安全的,但是它仍然存在于 C89 和 C99 标准,并最终在 C2011 标准中移除了。但这仅仅是在语言标准中的移除,当我检查自己的一些代码时,发现仍有地方用到了它。而且以我目前对C的了解,更有意思的是,目前在C语言库中,还没有一个安全并且方便的取代 gets() 函数的方法。
各位通读了文章的朋友,能否回答如下几个问题:
在读此文之前,你知道 gets() 函数是不安全的吗?
你所工作的地方,有限制使用 gets() 函数的相关规定吗?
你曾经冲写过代码来避免使用 gets() 函数吗?
关于 gets() 函数,你有什么想了解的吗?
请下周继续关注此讨论。
2 实战:如何解决 gets() 函数的安全问题
2.1 工具链的安全警告
目前GCC默认就会为包含对 gets() 函数调用的代码,报出警告信息。
比如下面的代码:
代码如下 | 复制代码 |
#include<stdio.h> int main(void) { char c[5]; gets(c); puts(c); } |
就会给出下面的提示信息:
代码如下 | 复制代码 |
gets_warn.c:(.text+0xd): warning: the `gets’ function is dangerous and should not be used. |
2.2 安全的gets()实现
C11 标准(ISO/IEC 9899:201x)中, gets() 函数被删除, 引入了新的函数 gets_s().
C11 K.3.5.4.1 The gets_s function
代码如下 | 复制代码 |
#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> char *gets_s(char *s, rsize_t n); |
因为目前GCC中还没有完全实现此标准, 因此 gets_s() 函数尚未包含在目前的GNU 工具链中。Clang里也暂时没有增加对 gets_s 的支持。
所以最通用的做法,可能是自己实现一个。 如下是一种实现方式:
代码如下 | 复制代码 |
char *gets_s(char * str, int num) { if (fgets(str, int, stdin) != 0) { size_t len = strlen(str); if (len > 0 && buffer[len-1] == 'n') buffer[len-1] = ''; return buffer; } return 0; } |
2.3 C标准库中其他存在安全隐患的函数
除了像 gets() 函数这类,非常不安全的函数外。C语言中因为缺少对数组越界的检查,指针的广泛使用,导致不少函数如果使用不当,容易被***利用, 存在安全隐患。
strcpy : 建议使用 strncpy
strcat : 建议使用 strncat
sprintf : 建议使用 snprintf
如果你想自己实现一些字符串操作函数,那么下面这种接口设计值得推荐。即务必要规定好目标地址空间的大小:
size_t foobar(char *dest, size_t buf_size, /* operands here */)
微软在MSDN中也就如何安全的使用C语言标准库接口给出了建议,感兴趣的朋友,可以看看 https://msdn.microsoft.com/en-us/library/bb288454.aspx。
转载于:https://blog.51cto.com/10704527/1763072
gets函数的不安性详解相关推荐
- 在python中使用关键字define定义函数_python自定义函数def的应用详解
这里是三岁,来和大家唠唠自定义函数,这一个神奇的东西,带大家白话玩转自定义函数 自定义函数,编程里面的精髓! def 自定义函数的必要函数:def 使用方法:def 函数名(参数1,参数2,参数-): ...
- 函数assert()详解
函数assert()详解: 断言assert是一个宏,该宏在<assert>中,,当使用assert时候,给他个参数,即一个判读为真的表达式.预处理器产生测试该断言的代码,如果断言不为真, ...
- python def函数报错详解_python自定义函数def的应用详解
这篇文章主要介绍了python自定义函数def的应用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 这里是三岁,来和大家唠唠 ...
- PHP函数call_user_func和call_user_func_array详解
今天在群里面,有个叫lewis的在问call_user_func_array的用法,因为之前一直没有用过,也不能说什么,于是看一下手册,发现是这么写的: call_user_func_array (P ...
- 哈希函数(散列函数)详解
哈希函数(散列函数)详解 Hash,一般翻译做"散列",也有直接音译为"哈希"的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换 ...
- php。defined,PHP defined()函数的使用图文详解
PHP defined()函数的使用图文详解 PHP defined() 函数 例子 定义和用法 defined() 函数检查某常量是否存在. 若常量存在,则返回 true,否则返回 false. 语 ...
- python中tile的用法_python3中numpy函数tile的用法详解
tile函数位于python模块 numpy.lib.shape_base中,他的功能是重复某个数组.比如tile(A,n),功能是将数组A重复n次,构成一个新的数组,我们还是使用具体的例子来说明问题 ...
- Delphi Format函数功能及用法详解
DELPHI中Format函数功能及用法详解 DELPHI中Format函数功能及用法详解function Format(const Format: string; const Args: array ...
- python中的json函数_python中装饰器、内置函数、json的详解
装饰器 装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象. 先看简单例子: def run(): time.sleep(1 ...
最新文章
- Linq之延迟加载特性
- python用中文怎么说-如何实现python设置中文界面?
- xx is not in the sudoers file 问题解决
- Office文档在线预览/在线编辑解决方案 - 毕升OfficeAPI说明
- 微服务的隔离和熔断机制
- Docker Daemon和Docker Client关系
- 8汉化 netreflector_Reflector下载_.NET Reflector官方中文版下载-华军软件园
- 手机安全修改IMEI的方法
- 【Multisim】模拟电子技术综合设计实验:正弦波、方波、三角波信号发生器的设计与搭建
- 290万人考研:所有的不平凡,从不认命开始
- kindle如何设置不闪屏_kindle闪屏怎么解决
- [练气期]计算机视觉之从矩阵本质修炼图像几何变换秘籍
- 【TWVRP】基于matlab粒子群算法求解带时间窗的车辆路径规划问题(总成本最低)【含Matlab源码 2590期】
- GCC 9.4 编译error: catching polymorphic type ‘class std::bad_alloc’ by value [-Werror=catch-value=]
- matlab多元回归分析怎么计算,第11讲_matlab多元回归分析
- Python math.fabs() 方法
- 【DKN】(二)config.py
- 利用平台系统运营店铺五大法则
- 32位和64位系统支持的最大内存
- CGB2109-Day12-用户模块管理