当前位置: 首页 > news >正文

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。










本文转自 ye小灰灰  51CTO博客,原文链接:http://blog.51cto.com/10704527/1763072,如需转载请自行联系原作者

相关文章:

  • Silverlight知识链接整理(11月-12月)
  • ORACLE 分区与索引
  • C# 视频监控系列(2):客户端——封装API (1)
  • Silverlight书籍推荐阅读排行榜【续】
  • 文本编辑命令
  • VI批量替换
  • Heron and His Triangle 2017 沈阳区域赛
  • Oracle在线 redo log文件丢失后的恢复
  • python time 与datetime之间的区别与联系
  • Exchange帐号如何手动更新?
  • MySQL体系结构:八大功能模块浅析
  • 如何读取磁盘温度
  • 【FTP】FTP 命令模式下 PASV OR PORT
  • mysql如何保证redolog和binlog的一致性,安全性,效率。
  • C++连接mysql数据库的两种方法
  • canvas 高仿 Apple Watch 表盘
  • CSS 三角实现
  • in typeof instanceof ===这些运算符有什么作用
  • overflow: hidden IE7无效
  • React的组件模式
  • tweak 支持第三方库
  • 从地狱到天堂,Node 回调向 async/await 转变
  • 开源SQL-on-Hadoop系统一览
  • 学习使用ExpressJS 4.0中的新Router
  • 一份游戏开发学习路线
  • 以太坊客户端Geth命令参数详解
  • 在Mac OS X上安装 Ruby运行环境
  • (02)vite环境变量配置
  • (5)STL算法之复制
  • (BFS)hdoj2377-Bus Pass
  • (Redis使用系列) SpringBoot 中对应2.0.x版本的Redis配置 一
  • (附源码)springboot青少年公共卫生教育平台 毕业设计 643214
  • (完整代码)R语言中利用SVM-RFE机器学习算法筛选关键因子
  • (一)Linux+Windows下安装ffmpeg
  • (转)编辑寄语:因为爱心,所以美丽
  • .NET CORE 3.1 集成JWT鉴权和授权2
  • .NET Framework杂记
  • .NET/C# 获取一个正在运行的进程的命令行参数
  • .NET开源项目介绍及资源推荐:数据持久层 (微软MVP写作)
  • .net用HTML开发怎么调试,如何使用ASP.NET MVC在调试中查看控制器生成的html?
  • /ThinkPHP/Library/Think/Storage/Driver/File.class.php  LINE: 48
  • @RequestMapping用法详解
  • [ vulhub漏洞复现篇 ] Celery <4.0 Redis未授权访问+Pickle反序列化利用
  • [\u4e00-\u9fa5] //匹配中文字符
  • [BUG] Hadoop-3.3.4集群yarn管理页面子队列不显示任务
  • [Bugku]密码???[writeup]
  • [C#]OpenCvSharp使用帧差法或者三帧差法检测移动物体
  • [C#]winform利用seetaface6实现C#人脸检测活体检测口罩检测年龄预测性别判断眼睛状态检测
  • [Flutter]打包IPA
  • [hive] posexplode函数
  • [IT生活推荐]大家一起来玩游戏喽,来的都进!
  • [leetcode] 61. 旋转链表
  • [LeetCode]Balanced Binary Tree
  • [linux][调度] 内核抢占入门 —— 高优先级线程被唤醒时会立即抢占当前线程吗 ?
  • [mysql]游标和触发器