博客

嵌入式编程和物联网——内存管理是要事!

2021-05-18 | 作者: Tan Rahman and Jane Yang

物联网设备有两种:高端和低端设备。用于高端设备的操作系统为全功能型,包括Windows、Linux或Android。例如,亚马逊的echo设备使用基于Android的Fire操作系统,而三星智能手表则使用基于Linux的Tizen。这些设备可连接电源或有定期充电的电池。然而,低端设备的内存容量非常小,并且通常有固定寿命而且无法充电的少许能量。这些设备使用诸如Contiki或甚至自行设计的操作系统,这些操作系统是专门为这类有限的低内存/低能量所设计的。

随着低端物联网设备以及无数创造这些设备的公司的快速增长和大量涌现,许多开发人员正面临着编写适用的小应用程序的挑战。为编写嵌入式软件选择正确的编程语言,对于交付高性能和高效的设备来说是至关重要的。

“如果你在裸机上编写传感器,你很有可能是在用C!”

                                    Ian Skerrett, Eclipse Foundation

在设备端,计算能力通常是非常有限的。C/C++编程语言在这里最恰当,这是因为它们对编写靠近硬件层的代码来说是最理想的、它们不需要很多计算能力,并且能够直接在内存上操作。这对传感器和网关硬件层应用程序来说是毫无悬念的选择。然而,C/C++的缺点之一,就是如果开发人员不熟悉最佳实践,则C/ C++的语法会很快变得杂乱和混乱。

用C/C++编程的一个基本要求是对内存管理的牢固理解。如今许多开发人员精通诸如Java和JavaScript这样的编程语言,这些语言有内置的处理内存方式,实质上就是“受管理的运行时”。用这些语言编程让程序员避开繁琐的内存管理工作,而无需手动分配和释放内存。当用C/C++编程时,你几乎可以操纵代码的每一部分来让它达到最佳性能,但你可能会犯下无数不同的小错误,由于有手动管理内存的需要,这些错误常常导致更长的开发时间。

C从70年代早期就存在了,而C++是从80年代早期。两者在今天都还被活跃地使用,这是有原因的。它们被创建为编译型语言,能够提供低层次的内存访问,访问逻辑可以有效得映射到机器指令。这点个特性,外加上最小运行时,可以支持更高的性能。然而,一种批评是大家所认为的复杂性。因为这个缘故,与其它更新的语言比起来,许多开发人员在C和C++上有更陡的学习曲线。

“因为C和C++不是内存安全的语言,攻击可以利用C/C++代码中的内存漏洞来获得非法访问权限。”

                                                                                                           梁宇宁,鉴释

在用C编程时,编写劣质的代码可能会造成奇怪的漏洞,这些漏洞要数周或数月才发现,并且会显示出瞬变且有误导性的行为。它们可能会破坏栈或堆,并造成最终的失败或制造漏洞;在突发事件后可能在继续运行了数百条指令后才发生失败的状况。这些常常是由手动内存管理带来的挑战。

以下是当需要在代码里手动管理内存时发现的一些常见问题:

  • 没有释放(MSF) 

程序已经分配了堆内存,但未能释放那块内存。必须使用malloc和free手动分配和释放堆上的数据。你作为程序员要负责释放不再需要的堆内存。未释放内存会导致内存泄漏。如果进程运行时间很长,这会是问题。这会导致核心转储、意外行为并影响系统性能。

如果攻击者能导致内存泄漏,他们可以利用在低内存情况下发生的问题或启动拒绝服务(DoS)攻击。

相关漏洞 – CWE-401: Missing Release of Memory after Effective Lifetime

 

  • 空指针解引用(NPD)

始终需要检查指向由calloc/malloc返回的内存的指针。如果该指针为空,我们就应该认为内存分配是不成功的,并且不应使用该指针进行任何操作。它会导致分段失败或不可预测的程序行为。NPD通常是由很少遇到的错误情况引起的,这意味着在正常测试阶段里很难找到它们。

此结果是应用程序的完整性受到破坏、机密数据遭到暴露,并且系统可用性会中断。

相关漏洞 – CWE-476: NULL Pointer Dereference

 

  • 使用悬空指针(UDR)

悬空指针是已用于指向无效内存资源(例如已释放的内存)的指针。这会导致具有未定义行为的内存故障。例如,访问内存可能会导致应用程序崩溃。这些问题经常被忽略,因为旧观点认为它们不会造成危险的安全风险。然而业已证实的是,它们允许攻击者制造远程代码执行攻击。

通过修改指针指向处,例如把它重定向至有恶意的代码,或者通过用恶意代码重写指向的内存可以达到这一点。

相关漏洞 – CWE-825: Expired Pointer Dereference

 

  • 释放后重用(UAF)

如果程序试图访问已被释放的内存,它会造成该程序崩溃或意外程序行为。释放后重用情况与悬空指针直接相关。

这种情况可能通过损坏有效数据影响完整性、由于损坏数据而导致程序崩溃,并且让攻击者可以启动恶意代码。

相关漏洞 – CWE-416: Use After Free

 

还有很多没有在上面列出的问题。识别这些问题的业界标准方法是在编译程序时利用静态代码分析工具。由鉴释开发的静态代码分析工具“爱科识”就能解决这些内存问题以及更多其它问题。

鉴释的很多客户都开发物联网产品,并且非常意识到保持竞争优势的必要性,以便在不牺牲质量和安全性的前提下将其产品早日推向市场。使用爱科识能帮助他们在“静态时”而不是运行时识别这些难以找到的漏洞,因为它们更难以修复,这从而显著降低修复时间。通过更深入探索编译器流程,我们可以调查数据流,并指出内存管理不当之处。这是通过使用流敏感分析、跨函数分析、上下文敏感分析以及对象敏感分析来实现的。

拥有真正理解用C/C++语言编程的复杂性所需的技能和技术的开发人员越来越难找到了,因为越来越多的开发人员专注于学习解释型语言,而不是编译型语言。很少有开发人员会在没有自动内存管理(例如垃圾收集)的额外好处的情况下喜欢用更接近机器代码的语言编程。然而,对C/C++的需求仍然和最初开发它们时同样重要,有人甚至会说这些语言驱动了世界。我们不要忘了,诸如Windows、Linux和MacOS这样的主流操作系统基本上是用C写的。在日常生活中,C/C++存在于各种活动背后,例如早上叫你起床的闹钟、车里为你提供信息的仪表板显示,到你用来买上班前第一杯咖啡的电子支付。

对于新手和有经验的开发人员来说,使用静态代码分析工具都有助于在软件开发的各个阶段中感到安心。它们协助确保你的内存管理是高质量的,从而能让你的软件有完整性、高性能和安全性。

 

 

点击此处,了解爱科识是如何帮助物联网开发人员识别与内存管理有关,并且难以找出的漏洞,

或联系我们获取产品演示

通过使用我们的网站,表明您已经阅读并理解我们的Cookie政策及隐私政策