写点什么

应用程序热补丁(一):几行代码构造免重启修复补丁

  • 2019-11-12
  • 本文字数:2507 字

    阅读完需:约 8 分钟

应用程序热补丁(一):几行代码构造免重启修复补丁

热补丁是一种在程序运行时动态修复内存中代码 bug 的技术。在 UCloud,我们使用内核热补丁和应用程序热补丁(也就是进程热补丁)来在线修复核心业务的缺陷和安全漏洞。


多年来我们使用内核热补丁技术避免了系统重启导致的业务中断、保证了操作系统的可用性。属于核心业务组件的应用程序,尤其是单点的虚拟化组件和有状态的应用程序,同样面对高可用的挑战,每次重启都会导致服务受损。


然而业界并没有成熟可靠的应用程序热补丁方案可以参考,原因在于应用程序热补丁比内核热补丁更加困难和复杂。比如内核对外提供完整的模块加载功能,可以直接加载内核模块形式的热补丁。


而应用程序需要通过外部程序通过一系列对其内存和寄存器的复杂操作来注入动态链接库形式的热补丁;应用程序包含多线程;内核在编译时会被限定使用特定的编译方式,而应用程序的编译方式则更加宽泛;内核的二进制相对简单,而应用程序二进制因为需要链接到多种的动态链接库,本身的结构会更复杂。


经过大量的研究和实践,我们针对应用程序如何免重启修复 bug,自研了一套应用程序热补丁技术而且在 UCloud 内部已经经过数十万台次修复验证。后面通过一系列文章分享其技术实现。本文先介绍一种简单实用的应用程序热补丁技术,不少场景下采用该方法编写几行代码即可免修复应用程序 bug。

原理

一般来说,应用程序热补丁的流程是:首先通过编译器将热补丁源码制作成可加载的动态链接库,然后通过加载程序将热补丁加载到目标进程的地址空间,最后在进行一致性模型检查确认安全的情况下,把原始代码替换成新的代码,完成在线修复的过程。


下面我们分别介绍热补丁本身和热补丁加载程序,热补丁本身是因 patch 而异的,加载程序是通用的。假设我们有热补丁加载程序 Loader、目标进程 T、热补丁 patch.so,目标程序的 func 函数替换为 func_v2。

热补丁

  • 编写热补丁源码,编译成动态链接库的格式的热补丁 patch.so,patch.so 中包含 func 和 func_v2 的信息。

  • 热补丁 patch.so 在被加载程序 Loader 加载到目标进程 T 地址空间的过程中,通过 dlsym 调用找到 func 的地址,并将 func 的入口指令改为可写,同时改变为跳转到 func_v2。

  • 至此,所有对 func 的调用都会被重定向到 func_v2,func_v2 执行完毕后返回,程序继续运行。

  • 如图所示:


热补丁加载程序

  • 加载程序 Loader 找到目标进程 T 的 dlopen 函数入口地址。

  • Loader 通过 ptrace 依附到目标进程 T,Loader 将热补丁的名字放入放入目标进程 T 的堆栈,将 IP 寄存器设置为 dlopen 函数的地址。

  • Loader 使目标进程 T 继续运行。因为 IP 寄存器已经设置为 dlopen 函数的入口,目标进程 T 会调用 dlopen 把热补丁加载到 T 的地址空间中。

  • 如图所示:



了解原理之后,我们一步步实现一种简单的基于 x86_64 的热补丁。(对于需要制作热补丁的同学,只需自己编写 patch.so,而 Loader 是通用的。patch.so 编写可以参考下面的例子,往往只需几行代码做相应替换。)

实现

热补丁

  1. 目标进程 T 执行 dlopen 的过程中,通过预先在热补丁(动态链接库)中写入的 constructor 函数,在加载过程中函数 func_v1 替换函数 func。


热补丁加载程序

1.Loader 得到目标进程 T 地址空间中 dlopen 入口地址


  • dlopen 函数有 libdl 提供,并不是所有的程序都加载 libdl,幸运的是,libc 中提供了同样功能的函数 __libc_dlopen_mode,并且接受的参数和 dlopen 相同。除非特殊情况,所有程序都会加载 libc。所以我们需要找到 __libc_dlopen_mode 在目标进程 T 地址空间中的函数入口地址。

  • 我们知道,不同进程中 libc 会被加载到不同的基地址,但是 libc 中函数的地址相对基地址的偏移是不变的。

  • 通过 Loader 和目标进程 T 的 /proc/pid/maps,我们可以得到 libc 在 Loader 和目标进程 T 中加载的基地址。通过 Loader 运行 dlsym,我们可以得到 Loader 中的__libc_dlopen_mode 的地址。这样我们可以得到目标进程 T 中__libc_dlopen_mode 的地址(Loader_dlopen - Loader_libc + T_libc)。



2.Loader 对目标进程 T 使用 ptrace attach,并保存 T 此时的寄存器信息。



3.将目标进程 T 的 %RIP 指向 dlopen,热补丁的名字的字符串放入堆栈,字符串的地址写入 %rdi,RTLD_NOW 的值写入 %rsi 作为 dlopen 的 flag。同时把 dlopen 返回地址设置为非法地址 0x0(把 0x0 压入栈中),这样 Loader 可以捕获目标进程 T 产生的 SIGSEGV 信号进而重新获得 T 的控制权。



  1. Loader 使目标进程 T 继续运行。当 T 执行完 dlopen 之后,T 产生的 SIGSEGV 信号被 Loader 捕获,Loader 重新获得 T 进程的控制权。



5.Loader 通过读取目标进程 T 此时的 %rax 寄存器得到 dlopen 的返回值,恢复 T 最开始的执行状态,最后释放对 T 的控制。



至此对目标进程 T 的热补丁就完成了,下面我们看一个例子。

验证

假设我们运行 target 程序,每隔一秒打印 Hello 一次:



target 程序由 target 本身和 libold.so 组成,分别代码如下:



编译如下:



我们想要修改 print 函数,变成打印“Goodbye”。我们需要编写热补丁 new.c,并添加新函数和 constructor:



编译:



然后通过加载程序对 target 进程打入热补丁 libnew.so,最后我们对 target 程序打入这个热补丁,观察变化:



我们发现热补丁确实改变了 print 函数,最后通过 gdb 进一步确认,可以看出 print 函数的入口被修改成 48 b8 dc b6 15 a9 c1 7f 00 00 ff e0,与我们的预期相符:


总结

我们介绍了应用程序热补丁的基本原理,实践了一个应用程序热补丁 demo。此类热补丁适用于动态替换共享链接库中的可见函数,可以修复例如 glibc “GHOST 漏洞”(CVE-2015-0235)等等,在 UCloud 我们利用热补丁修复了若干缺陷,在用户没有感知的情况下把 bug 快速及时的修复。这些热补丁修复程序里,绝大多数代码是通用的,只需少数几行做特殊替换。


本文介绍的热补丁技术对于适用的场景非常理想,简单可靠,但存在几个缺点:


  • 手写热补丁代码门槛较高,特别是被修复函数的依赖函数链较长时手写热补丁很容易出错;

  • 无法修复局部函数和局部变量(只能修复全局可见的函数和变量)。


后面的文章我们会介绍如一种更加先进的应用程序热补丁技术。


本文转载自公众号 UCloud 技术(ID:ucloud_tech)。


原文链接:


https://mp.weixin.qq.com/s/BJhpWihFh3UcxMrvaZaOnw


2019-11-12 16:183814

评论

发布
暂无评论
发现更多内容

百度搜索稳定性问题分析的故事(上)

百度开发者中心

百度搜索

CDH 的安装(四)

大数据技术指南

CDH 7月日更

Python利器!Jupyter最大威胁终于来了

Jackpop

Python Jupyter Notebook

IntelliJ IDEA必备7款Python插件

Jackpop

IDEA pycharm

Linux之find exec

入门小站

Linux

【Redis】数据结构

awen

redis 数据结构

底层技术支撑智慧出行,汽车智能化发展下区块链大放异彩

CECBC

公司内部使用的数仓命名规范

白贺BaiHe

大数据 数仓 数仓规范 7月日更

十二张图,一脚踹开消息队列的大门

三分恶

消息队列

🏆 【JVM 性能调优】「对象内存分配」输出常用参数

洛神灬殇

JVM 7月日更

凭借这5步,我30分钟学会了Python爬虫!

Jackpop

爬虫 爬虫教程

Flink进入大厂面试准备,收藏这一篇就够了

五分钟学大数据

flink 7月日更

香帅:2021年财富格局新变化

石云升

读书笔记 7月日更

3分钟评估 你的运维监控系统是“救命稻草”还是“鸡肋”

鹿小U

运维自动化 监控系统 运维平台

为啥Java应用迁移到容器后会出现OOM?

Java架构师迁哥

多地“真金白银”力撑区块链,中西部欲弯道超车新赛道

CECBC

2020年的焦虑与忧伤

Emotion

疫情 考研 研究生

北鲲云助力生命科学,高性能计算突破屏障

北鲲云

每天学习10个实用Javascript代码片段(三)

devpoint

JavaScript JSON格式化 7月日更

网络攻防学习笔记 Day68

穿过生命散发芬芳

网络攻防 7月日更

Clickhouse Projection 特性探索

GrowingIO技术专栏

数据库 大数据 数据分析 OLAP Clickhouse

5分钟速读之Rust权威指南(三十八)模板语法

wzx

rust

相约山城重庆!HarmonyOS Connect伙伴峰会将于7月16日举办

科技汇

React 18探秘(上)

Teobler

大前端 React

领域驱动设计101 - 聚合

luojiahu

领域驱动设计 DDD

聊聊事务与分布式系统-从零讲到通透

刘绍

sql 分布式 事务 2PC XA

模块七 - 王者荣耀商城异地多活架构设计

华仔架构训练营

区块链时代下,企业如何打造数据要素的“新竞争力”?

CECBC

魔方、齿轮、融合剂:当新ICT技术邂逅教育产业

脑极体

什么是数据集市

奔向架构师

数据仓库 数据架构 7月日更

妇与术(三):机器人可以没有性别,但关系技术离不开女性

脑极体

应用程序热补丁(一):几行代码构造免重启修复补丁_文化 & 方法_王超_InfoQ精选文章