0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

【openssl】从openssl的常用接口浅谈【内存泄漏】

嵌入式物联网开发 来源:嵌入式物联网开发 作者:嵌入式物联网开发 2022-08-31 11:24 次阅读

openssl是一个很有名的开源软件,它在解决SSL/TLS通讯上提供了一套行之有效的解决方案,同时在软件算法领域,它也集成绝大部分常见的算法,真可谓是程序员开发网络通讯和信息安全加解密的一个利器。

熟悉github的朋友,一定在github上目睹过openssl的真容【https://github.com/openssl/openssl】,它的官网地址是【/index.html】。就拿github来说,高达8.8K颗星,被fork4千多次,总共有2万多次的提交记录,足以可见该开源项目的热度有多高。

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

​编辑

然而,就是这样的一个开源利器,能给我们工作带来便利的同时,倘若你使用不当,那么给你带来的不是喜悦,而是烦恼。通过观察openssl提供的API,你会发现,它的很多API返回的都是指针类型,在应用层调用时,仅需用一个对应类型的指针去接收返回的指针,即可取得对应的数据或操作方法,使用非常灵活。类似这样的接口有很多,例如:

//新生成一个BIGNUM结构
BIGNUM *BN_new(void);

//将s中的len位的正整数转化为大数
BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret); 
 
//初始化一个RSA结构
RSA * RSA_new(void);
 
//RSA私钥产生函数
//产生一个模为num位的密钥对,e为公开的加密指数,一般为65537(0x10001)
RSA *RSA_generate_key(int num, unsigned long e,void (*callback)(int,int,void *), void *cb_arg);
 
//从文件中加载RSAPublicKey格式公钥证书
RSA *PEM_read_RSAPublicKey(FILE *fp, RSA **x, pem_password_cb *cb, void *u);
 
//从BIO重加载RSAPublicKey格式公钥证书
RSA *PEM_read_bio_RSAPublicKey(BIO *bp, RSA **x, pem_password_cb *cb, void *u);

 
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

聪明的你,留意到这些“生成”功能的API接口的同时,一定也留意到它们都有对应的“销毁”API接口。上面列表一一对应的是:

//释放一个BIGNUM结构,释放完后a=NULL;
void BN_free(BIGNUM *a);

//释放一个RSA结构
void RSA_free(RSA *rsa);
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

看到这里,也许你就会明白我今天要讲的主题了,既然这些“生成”API提供了返回指针类型的功能,那么很明显指针所指向内容的存储空间,必定是在openssl内部通过malloc等动态内存申请的方式获取的;所以在使用了这段内存后,自然而然就是要执行内存释放的动作,这与C语言动态内存申请讲的“malloc--free”必须配套使用,是如出一辙的;只不过,现在这些openssl的API是把malloc和free的动作封装在了接口的内部,而暴露给调用者的只有类型XXX_init和XXX_free,或者XXX_new和XXX_delete,诸如此类的接口,仅此而已。

回到openssl的API接口的使用上来,博主有一次在使用openssl的某个接口有些疑惑,想在网上找找调用的demo时,结果一搜,一眼就进到 【OpenSSL编程-RSA编程详解 http://www.qmailer.net/archives/216.html】这篇博文。它确实给初学者提供了几组常用API的简单demo,正常情况下这些代码都是能跑通的,但近来我在日常工作中,有在做一些【内存泄露】相关的【代码优化】工作,所以对这个切入点比较敏感,果不其然,细读其中的部分示例代码,就发现了其中不严谨的地方,很有可能就会发生【内存泄露】的风险。如果本身系统的内存比较吃紧,比如像在嵌入式系统上运行,这样的【内存泄露】可以说是致命的。

还是拿代码来说事,以下代码片段是上文提及的参考博文中截取到的,如下:

1. 数据加、密解密示例

#include
#include
#include
#include
#include
#include
 
#define PRIKEY "prikey.pem"
#define PUBKEY "pubkey.pem"
#define BUFFSIZE 4096
 
/************************************************************************
 * RSA加密解密函数
 *
 * file: test_rsa_encdec.c
 * gcc -Wall -O2 -o test_rsa_encdec test_rsa_encdec.c -lcrypto -lssl
 *
 * author: tonglulin@gmail.com by www.qmailer.net
 ************************************************************************/
 
char *my_encrypt(char *str, char *pubkey_path)
{
    RSA *rsa = NULL;
    FILE *fp = NULL;
    char *en = NULL;
    int len = 0;
    int rsa_len = 0;
 
    if ((fp = fopen(pubkey_path, "r")) == NULL) {
        return NULL;  //函数出口1
    }
 
    /* 读取公钥PEM,PUBKEY格式PEM使用PEM_read_RSA_PUBKEY函数 */
    if ((rsa = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL)) == NULL) {
        return NULL; //函数出口2
    }
 
    RSA_print_fp(stdout, rsa, 0);
 
    len = strlen(str);
    rsa_len = RSA_size(rsa);
 
    en = (char *)malloc(rsa_len + 1);
    memset(en, 0, rsa_len + 1);
 
    if (RSA_public_encrypt(rsa_len, (unsigned char *)str, (unsigned char*)en, rsa, RSA_NO_PADDING) < 0) {
        return NULL; //函数出口3
    }
 
    RSA_free(rsa);
    fclose(fp);
 
    return en; //函数出口4
}
 
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

通过简单分析,我们可以知道my_encrypt这个函数,有一个入口,但是有4个出口(见代码注释):

函数出口1: 很明显出错的可能性是,打开pubkey_path文件失败,这个很好理解,可能是文件不存在,或者路径文件不正确等等,此处出错,对外返回NULL,是完全没有问题的

函数出口2: 这里出错的可能性是fp指向的pubkey_path文件,压根不是一个pem格式的公钥文件,自然会出错;但是此处直接对外返回NULL,而不管fp的死活,这是不可取的!

函数出口3: 这里出错的可能是公钥加密输入的数据长度不对或者数据填充不对等等,然而这里也是出错后,立即对外返回NULL,完全不理fp和rsa,还有en这条友【往往更容易忽略】,这3者的死活,更是不可取的!

函数出口4: 这个没的说,正常的函数出口;大家注意,在这个正常的函数出口中,它在return前是执行了RSA_free(rsa); fclose(fp); 的动作;没错,这个就是我们要讲的使用完的内存要及时释放,它的使用需要注意的关键点就在这里。那么如上可能出现内存泄露的代码片段应该如何优化呢?直接贴上,优化后的示例代码:

char *my_encrypt(char *str, char *pubkey_path)
{
    RSA *rsa = NULL;
    FILE *fp = NULL;
    char *en = NULL;
    int len = 0;
    int rsa_len = 0;
 
    if ((fp = fopen(pubkey_path, "r")) == NULL) {
        en = NULL;
        goto exit_entry; //使用goto语句,保证函数单一入口,单一出口
    }
 
    /* 读取公钥PEM,PUBKEY格式PEM使用PEM_read_RSA_PUBKEY函数 */
    if ((rsa = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL)) == NULL) {
        return NULL;
        goto exit_entry; //使用goto语句,保证函数单一入口,单一出口
    }
 
    RSA_print_fp(stdout, rsa, 0);
 
    len = strlen(str);
    rsa_len = RSA_size(rsa);
 
    en = (char *)malloc(rsa_len + 1);
    if (!en) {
        goto exit_entry; //当en申请不到内存的时候,也不能往下执行了,需要退出
    }
    memset(en, 0, rsa_len + 1);
 
    if (RSA_public_encrypt(rsa_len, (unsigned char *)str, (unsigned char*)en, rsa, RSA_NO_PADDING) < 0) {
        if (en) { 
            free(en);   //走到这里的时候en理论上已经不为空了,当在这一步出错时,对外en的内存已经变得无意义了,所以必须要释放掉,同时将en置为NULL,防止外部调用者逻辑出错
            en = NULL;
        }
        goto exit_entry;
    }
 
exit_entry: //函数统一出口,退出前执行相应的内存释放动作

    //先判断是否需要执行内存释放
    if (rsa) {
        RSA_free(rsa);
    }
    //文件打开的fp句柄要及时关闭
    if (fp) {
        fclose(fp);
    } 

    return en;
}
 
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

通过如上的示例代码,基本上很好地修复了因异常情况处理不当导致的【内存泄露】隐患,同时利用goto语句,使得函数的结构的紧凑性有所提高,代码的可读性也提升了不少。

有的朋友可能会有疑问,“我们在学C语言教程的时候,老师不是常常跟我们说,尽量不要使用goto语句,这样会带来代码灾难,为何博主却推荐使用goto语句来优化代码呢?”

原因很简单,C语言的goto语句并不会造成代码灾难,而是使用goto语句的程序员造成的灾难!怎么说呢,goto语句是有点偏汇编层面的关键字,它有点像汇编指令里面的jump指令,也就是说使用好它,指不定还可以提升代码的运行效率。但是值得注意的是,goto语句不能滥用,尤其是使用goto语句往前跳转,或者使用goto语句执行递归、循环等操作时,代码的逻辑将有可能变得不可控制,或者难以控制,基本上除了写代码本身的人能读懂外【估计过个一两个月,他自己也读不懂了】,其他人估计就摸不着头脑了。但是,如果像用在如上所优化的代码那样,仅仅在函数的出口做一个symbol标签,当函数中间执行异常的时候,立即跳转到定义的出口标签,同时执行一些函数退出的收尾工作,比如清理内存、释放不再使用的内存、接口返回值转换等操作;这样的代码,将会大大提升了代码的可读性,这也尽可能地将错误规避掉,让bug无处藏身,代码更加整洁,反而能编写出可读性强的高质量代码。

如上仅是提出了对【内存泄露】的小小看法和感悟,借助openssl的demo例子,也仅仅是抛砖引玉,也许读者有更高的见解。期待有读者与我一同讨论相关话题

注:文中引用了【博主:大佟,文章地址:http://www.qmailer.net/archives/216.html】的示例代码,如有版权问题,请及时与我联系。不胜感激!

​审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 接口
    +关注

    关注

    33

    文章

    7639

    浏览量

    148483
  • 开源
    +关注

    关注

    3

    文章

    2985

    浏览量

    41718
  • OpenSSL
    +关注

    关注

    0

    文章

    20

    浏览量

    8587
收藏 人收藏

    评论

    相关推荐

    嵌入式学习-ElfBoard ELF 1板卡-移植openssl

    openssl是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连线者的身份。这个包被广泛应用于互联网的网页服务器上。下面这篇文章就给各位小伙伴介绍一下如何在
    发表于 12-28 08:53

    Openssl怎样移植到ARM开发板

    Openssl移植到ARM开发板openssl源码下载源码编译配置使用生成的库文件openssl源码下载openssl源码可以在官网下载:https://www.
    发表于 07-01 07:46

    openssl库移植过程记录

    openssl库移植1 什么是openssl可以这么说,只要你的应用程序和网络安全有关系,不管是http还是mqtt,那么都需要依赖到openssl;首先openssl是一个开源库,功
    发表于 11-04 06:43

    openssl有哪些应用呢

    1、环境配置在me.h文件里修改以下宏定义ME_COM_MBEDTLS 0ME_COM_MATRIXSSL 0ME_COM_NANOSSL 0ME_COM_OPENSSL 12、添加
    发表于 12-16 07:41

    i.MX8MP开发板中移植OpenSSL工具

    也是围绕这三个功能部分进行规划的。作为一个基于密码学的安全开发包,OpenSSL提供的功能相当强大和全面,囊括了主要的密码算法、常用的密钥和证书封装管理功能以及SSL协议,并提供了丰富的应用程序供测试
    发表于 08-25 10:04

    OpenSSL的SE05x是否支持openssl主机加密并积极测试?

    设置经过身份验证的会话并观察持续的内存泄漏。通过 valgrind 深入研究,我在 `sss_host_mac_init()` 中发现了内存泄漏。然后我能够确定
    发表于 04-10 08:08

    OpenSSL在电子商务安全中的应用

    基于公共密钥技术的SSL 协议已经成为Internet 上广泛应用的安全通信标准,源码开放的OpenSSL是对SSL 协议的完整实现。介绍了OpenSSL 在电子商务安全中的应用。
    发表于 08-31 15:58 12次下载

    OpenSSL中AES算法的研究与优化

    AES 算法是新一代加密标准算法,文中对OpenSSL 中AES 算法的基本原理和性能进行了分析,针对其速度慢的缺点,提出了一些改进AES 算法的策略,改进的算法能有效提高加密算法操
    发表于 12-25 16:06 12次下载

    如何才能使用OpenSSL实现一个基本的安全连接的详细概述

    OpenSSL API 的文档有些含糊不清。因为还没有多少关于 OpenSSL 使用的教程,所以对初学者来说,在 应用程序中使用它可能会有一些困难。那么怎样才能使用 OpenSSL 实现一个基本的安全连接呢? 本教程将帮助您解决
    的头像 发表于 05-27 10:26 5787次阅读
    如何才能使用<b class='flag-5'>OpenSSL</b>实现一个基本的安全连接的详细概述

    密码学OpenSSL的入门基础知识整理合集

    本文是使用 OpenSSL 的密码学基础知识的两篇文章中的第一篇,OpenSSL 是在 Linux 和其他系统上流行的生产级库和工具包。(要安装 OpenSSL 的最新版本,请参阅 这里
    的头像 发表于 02-07 15:29 3295次阅读
    密码学<b class='flag-5'>OpenSSL</b>的入门基础知识整理合集

    嵌入式Linux系统openssl库移植

    openssl库移植1 什么是openssl可以这么说,只要你的应用程序和网络安全有关系,不管是http还是mqtt,那么都需要依赖到openssl;首先openssl是一个开源库,功
    发表于 11-01 16:31 8次下载
    嵌入式Linux系统<b class='flag-5'>openssl</b>库移植

    openssl】利用openssl命令行快速生成RSA私钥

    openssl】如何利用openssl命令行快速生成RSA私钥?
    的头像 发表于 08-31 12:58 2731次阅读
    【<b class='flag-5'>openssl</b>】利用<b class='flag-5'>openssl</b>命令行快速生成RSA私钥

    如何修补 Rust 中即将出现的OpenSSL漏洞

    OpenSSL将在 11 月 1 日披露一个新的严重漏洞并发布补丁版本。 为了保护您的 Rust 程序,您需要做的就是更新系统范围内的 OpenSSL 安装。
    的头像 发表于 10-31 09:46 825次阅读

    飞凌i.MX8MP开发板OpenSSL的使用方法

    在OKMX8MP-C开发板中,飞凌移植了OpenSSL工具, 作为一个基于密码学的安全开发包,OpenSSL提供的功能相当强大和全面,囊括了主要的密码算法、常用的密钥和证书封装管理功能以及SSL协议
    的头像 发表于 08-25 15:19 472次阅读
    飞凌i.MX8MP开发板<b class='flag-5'>OpenSSL</b>的使用方法

    为什么嵌入式系统设计人员应该关注OpenSSL

      首先,一些基础知识。比如OpenSSL常用的安全套接字层 (SSL) 提供了 Web 服务器和浏览器之间的加密链接技术,而 OpenSSL 则为保护网络通信的应用程序提供了加密库。它可以处理消息摘要、随机数、数字证书和签名
    的头像 发表于 06-29 10:25 326次阅读