gets函数的不安性详解-阿里云开发者社区

开发者社区> 余二五> 正文

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,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
4067 0
GetSystemMetrics()函数的用法
可以用GetSystemMetrics函数可以获取系统分辨率,但这只是其功能之一,GetSystemMetrics函数只有一个参数,称之为「索引」,这个索引有75个标识符,通过设置不同的标识符就可以获取系统分辨率、窗体显示区域的宽度和高度、滚动条的宽度和高度。
806 0
getch()、getche()和getchar()函数
原文:getch()、getche()和getchar()函数 getch()、getche()和getchar()函数(1) getch()和getche()函数  这两个函数都是从键盘上读入一个字符。
972 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
4478 0
getline函数(精华版)
在我的印象中,getline函数经常出现在自己的视野里,模糊地记得它经常用来读取字符串   。但是又对它的参数不是很了解,今天又用到了getline函数,现在来细细地总结一下:   首先要明白设计getline函数的目的,其实很简单,就是从流中读取字符串。
998 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
10767 0
+关注
12613
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载