探索CVE-2026-31431的热补丁修复方案

admin 2026-06-30 07:49:00 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档分析了CVE-2026-31431漏洞的Linux页缓存机制安全问题,指出漏洞成因是aeadrecvmsg函数将输入源直接用作输出源,以及cryptoauthencesn_decrypt函数未完全还原输出源。文档通过IPsecESP协议场景和strongwan模拟演示了AEAD算法的解密过程,并提供了包含hmac-sha1和AES-CBC的Python代码示例。建议采用热补丁方案修复漏洞,在保证业务连续性的同时实现安全防护。 综合评分: 85 文章分类: 漏洞分析,解决方案,安全开发,漏洞POC,应急响应


cover_image

探索CVE-2026-31431的热补丁修复方案

wanglxi wanglxi

看雪学苑

2026年6月27日 17:59 上海

在小说阅读器读本章

去阅读

在修复CVE-2026-31431的过程中,同事希望能有一个热补丁方案,减少对现有业务的影响。

该方案不仅要防御攻击,还要避免影响现在和未来的业务。所以就有了下面对热补丁方案的探索。

#

一、漏洞原理解析

具体原理,0xlane大佬已经分析的很详细了。

Copy Fail 深度研究:Linux 页缓存漏洞的根因、利用与检测

https://bbs.kanxue.com/thread-291167.htm

这里只做简析:

该漏洞主要利用了2个点:

  • _aead_recvmsg 函数中,为了提升效率,将 输入源 直接用作 输出源
  • crypto_authenc_esn_decrypt 函数中,将 输出源  用于存放临时数据,且未还原完全。

#

二、涉及业务场景分析

1. 使用场景

基于 rfc4303 的描述,该数据的实际使用场景是IPsec的ESP(Encapsulating Security Payload)部分,用于应对重放攻击。

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               Security Parameters Index (SPI)                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      Sequence Number                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+---
|                    IV (optional)                              | ^ p
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | a
|                    Rest of Payload Data  (variable)           | | y
~                                                               ~ | l
|                                                               | | o
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | a
|               |         TFC Padding * (optional, variable)    | v d
+-+-+-+-+-+-+-+-+         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+---
|                         |        Padding (0-255 bytes)        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               |  Pad Length   | Next Header   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Integrity Check Value-ICV   (variable)                |
~                                                               ~
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

这是实际数据网络数据包的内容。

                                       What    What    What
# of     Requ'd  Encrypt Integ    is
                       bytes      [1]   Covers  Covers  Xmtd
                       ------   ------  ------  ------  ------
SPI                       4        M              Y     plain
Seq# (low-order bits)     4        M              Y     plain       p
                                                             ------ a
IV                     variable    O              Y     plain     | y
IP datagram [2]        variable  M or D    Y      Y     cipher[3] |-l
TFC padding [4]        variable    O       Y      Y     cipher[3] | o
                                                             ------ a
Padding                 0-255      M       Y      Y     cipher[3]   d
Pad Length                1        M       Y      Y     cipher[3]
Next Header               1        M       Y      Y     cipher[3]
Seq# (high-order bits)    4     if ESN [5]        Y     not xmtd
ICV Padding            variable if need           Y     not xmtd
ICV                    variable   M [6]                 plain

           [1] M = mandatory; O = optional; D = dummy
           [2] If tunnel mode -> IP datagram
               If transport mode -> next header and data
           [3] ciphertext if encryption has been selected
           [4] Can be used only if payload specifies its "real" length
           [5] See section 2.2.1
           [6] mandatory if a separate integrity algorithm is used

这是解密时所需要的内容。

其中有一点需要指出的是:

  • 网络中传输的 Sequence Number,只是 Seq# (low-order bits)
  • Seq# (high-order bits) 保存在各自的本地,在 Seq# (low-order bits) 溢出时,各自加1
  • 因在加解密时需要,内核 esp_input_set_header 在这个函数会将 Seq# (high-order bits) 放到原数据中

经过解密算法的调整,就变成了应用数据的样子。

2. 具体应用

通过 strongwan 应用模拟使用场景,发现跟上面文档有些许出入,但流程是一样的。

先说 AEAD (Authenticated Encryption with Additional Data)—— 通过附加数据进行认证和加密。

该算法同时具备:认证和加密的功能。这样的好处除了保证数据的完整性和机密性外,认证机制还能防御密文攻击。

该算法的解密过程,分为两个部分:认证检查、密文解码。

而实际过程可能有所变化:比如 echainiv(authencesn(hmac(sha1),cbc(aes))) 其中算法解释:

  • echainiv: 该算法会从解密数据中提取加密向量。
  • authencesn:authenc的进阶版本,authenc就是 最基本的AEAD,整个解密数据就是认证和密文内容; 而 authencesn 在其基础上加了esn (AEAD wrapper for IPsec with extended sequence numbers); 就是加上了IPsec协议在传输过程中的序列号,就是上面 rfc4303 中提到的 Seq。 会多一步数据重新排列的过程,而此过程就是该漏洞需要利用的一环。
  • hmac(sha1):认证部分算法
  • cbc(aes):加解密部分算法

其中数据移动部分的示意图如下:

其中authencesn部分用python模拟的代码如下(其中数据是从模拟环境中抓取的):

import hmac
import hashlib
import struct
from Crypto.Cipher import AES

tb = lambda s: bytes.fromhex(s.replace(" ", ""))

_UINT = struct.Struct("<I")
UINT =&nbsp;lambda&nbsp;x: _UINT.pack(x)

class&nbsp;AlgKey(object):
&nbsp; &nbsp; K_TMPL =&nbsp;">II%ds"
def&nbsp;__init__(self, typ, idd, keys):
&nbsp; &nbsp; &nbsp; &nbsp; self.typ = typ
&nbsp; &nbsp; &nbsp; &nbsp; self.idd = idd
&nbsp; &nbsp; &nbsp; &nbsp; self.keys = keys

def&nbsp;to_bytes(self):
&nbsp; &nbsp; &nbsp; &nbsp; s = self.K_TMPL %&nbsp;len(self.keys)
return&nbsp;struct.pack(s, self.typ, self.idd, self.keys)

data =&nbsp;bytes.fromhex("c082dfa20000000000000001a6017e0773cfd2552042e8c79de14c4a17ac32e6b7a98d64c6571a9b0dc37e0667b866b090e8e7e87e5db15c7fdf23b7d9234bae0f58e17811ceba4f09aa9b39b1efadd2c2c1945f5d75e7928baae128596fbaa5dc8ad902129d5e6bad7077a287365fd7272ad583caccabd56955219279c4bda766c56f6922daf876")
IV = tb("a6 01 7e 07 73 cf d2 55 20 42 e8 c7 9d e1 4c 4a")
IV = UINT(len(IV)) + IV
iv = IV[4:]
RESULT =&nbsp;bytes.fromhex("521178d78d411de70b69e2ffb866411c0a1046100000403b00150001575e196a000000008e13020000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536370102030405060708090a0a04")

def&nbsp;f_setkey():
&nbsp; &nbsp; KEY = AlgKey(0x08000100,&nbsp;0x10, tb("eb c8 87 75 dc 36 06 36 77 e9 5e 94 10 10 00 fc 0e 26 46 d4 64 bc 51 0e 47 47 18 4c f8 62 06 c2 74 b6 47 d1")).to_bytes()
&nbsp; &nbsp; keylen =&nbsp;len(KEY)

&nbsp; &nbsp; rta_len, rta_type = struct.unpack("<HH", KEY[:4])

&nbsp; &nbsp; param = KEY[4:]
&nbsp; &nbsp; enckeylen = struct.unpack(">I", param[:4])[0]

&nbsp; &nbsp; key = KEY[rta_len:]
&nbsp; &nbsp; keylen -= rta_len

&nbsp; &nbsp; authkeylen = keylen - enckeylen
&nbsp; &nbsp; authkey = key[:authkeylen]
&nbsp; &nbsp; enckey = key[authkeylen:]

print(authkeylen,&nbsp;bytes.hex(authkey))
print(enckeylen,&nbsp;bytes.hex(enckey))
return&nbsp;authkey, enckey

def&nbsp;f_valid(key, data):
&nbsp; &nbsp; ihash = data[-ASIZE:]
&nbsp; &nbsp; data = data[:4] + data[8:-ASIZE] + data[4:8]

&nbsp; &nbsp; f_hash = hashlib.sha256
&nbsp; &nbsp; hmac_obj = hmac.new(key, digestmod=f_hash)
&nbsp; &nbsp; hmac_obj.digest_size =&nbsp;32
&nbsp; &nbsp; hmac_obj.block_size =&nbsp;64
&nbsp; &nbsp; hmac_obj.update(data)
&nbsp; &nbsp; r = hmac_obj.digest()
&nbsp; &nbsp; r = r[:ASIZE]
print("F=",&nbsp;bytes.hex(r))
print("I=",&nbsp;bytes.hex(ihash))

def&nbsp;f_decrypt(key, data, iv):
print("I:",&nbsp;bytes.hex(IV))
print("i:",&nbsp;bytes.hex(data[8:ALEN]))

&nbsp; &nbsp; data = data[ALEN:-ASIZE]

&nbsp; &nbsp; aes = AES.new(key, AES.MODE_CBC, iv)
&nbsp; &nbsp; r = aes.decrypt(data)
print("D:",&nbsp;bytes.hex(r))
print("R:",&nbsp;bytes.hex(RESULT))
assert&nbsp;r == RESULT
print("PASSED")

authkey, enckey = f_setkey()
f_valid(authkey, data)
f_decrypt(enckey, data, iv)

结论:通过详细分析 authencesn 部分的逻辑,最终可得出——被改写且未还原的Seq high数据,在后面的解密过程并未用到。

三、漏洞修复方案

1、 官方修复方案

官方补丁 主要是回滚了 “_aead_recvmsg 函数中 将 输入源 直接用作 输出源” 的代码,但这个方案无法做热补丁(涉及面太广,存在运行时数据等原因) 该方案可以作为后续升级内核时使用,在此就暂且不论。

2、 热补丁方案

热补丁方案只能通过修补 ——“crypto_authenc_esn_decrypt 函数中,将 输出源  用于存放临时数据,且未还原完全”——这一点来做。 查看近期其他几个类似漏洞 Dirty Frag 、Fragnesia 的官方修复方案,都是采用了和本漏洞 Copy Fail相同 的封堵策略,丝毫没有修改 crypto_authenc_esn_decrypt 的意思。 所以我们要动此函数,要更加谨慎才行。 通过分析漏洞机制,热补丁方案主要想了以下几种:

2.1 内存平移

该方案的目标是尝试不修改最后的4字节,因为前面有未使用的4字节空间。 原作者设计代码时,应该是本着性能考量的目的,尽量减少内存拷贝。 通过上面的示意图,可以看到内存平移方案不会修改最后的4字节数据。

但依然还是要修改4字节数据,只是位置发生了移动。

攻击者,只需要扩大splice页的长度,就可以绕过此方案,所以该方案还需要一个数据还原操作,来预防此问题。

这样的话还不如直接使用下面的 “内存还原” 方案。

PS: patchwork中的补丁 使用的是此方案

2.2 内存还原

该方案只是解决,原实现代码在最后只还原了aead associated data部分内存的问题。

猜想是因为,最终需要向用户态返回这部分数据,所以必须要还原。

我们需要再加一个操作,将最后4字节也还原,这样就达到了不修改原数据的目的,从而截断了攻击。

static&nbsp;int&nbsp;crypto_authenc_esn_decrypt_tail(struct&nbsp;aead_request&nbsp;*req,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;unsigned int flags)
{
struct&nbsp;crypto_aead&nbsp;*authenc_esn =&nbsp;crypto_aead_reqtfm(req);
&nbsp; &nbsp; unsigned int authsize =&nbsp;crypto_aead_authsize(authenc_esn);
struct&nbsp;authenc_esn_request_ctx&nbsp;*areq_ctx =&nbsp;aead_request_ctx(req);
struct&nbsp;crypto_authenc_esn_ctx&nbsp;*ctx =&nbsp;crypto_aead_ctx(authenc_esn);
struct&nbsp;skcipher_request&nbsp;*skreq = (void *)(areq_ctx->tail +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ctx->reqoff);
struct&nbsp;crypto_ahash&nbsp;*auth = ctx->auth;
u8&nbsp;*ohash =&nbsp;PTR_ALIGN((u8&nbsp;*)areq_ctx->tail,
crypto_ahash_alignmask(auth) +&nbsp;1);
&nbsp; &nbsp; unsigned int cryptlen = req->cryptlen - authsize; &nbsp; <== [0]
&nbsp; &nbsp; unsigned int assoclen = req->assoclen;
struct&nbsp;scatterlist&nbsp;*dst = req->dst;
u8&nbsp;*ihash = ohash +&nbsp;crypto_ahash_digestsize(auth); &nbsp;<== [1]
u32&nbsp;tmp[2];

if&nbsp;(!authsize)
&nbsp; &nbsp; &nbsp; &nbsp; goto decrypt;

/* Move high-order bits of sequence number back. */
scatterwalk_map_and_copy(tmp, dst,&nbsp;4,&nbsp;4,&nbsp;0); &nbsp; &nbsp; &nbsp; &nbsp;<== [2]
scatterwalk_map_and_copy(tmp +&nbsp;1, dst, assoclen + cryptlen,&nbsp;4,&nbsp;0); &nbsp;<== [3]
scatterwalk_map_and_copy(tmp, dst,&nbsp;0,&nbsp;8,&nbsp;1); &nbsp; &nbsp; &nbsp; &nbsp;<== [4]

if&nbsp;(crypto_memneq(ihash, ohash, authsize))
return&nbsp;-EBADMSG;

decrypt:

sg_init_table(areq_ctx->dst,&nbsp;2);
&nbsp; &nbsp; dst =&nbsp;scatterwalk_ffwd(areq_ctx->dst, dst, assoclen); &nbsp;<== [5]

skcipher_request_set_tfm(skreq, ctx->enc);
skcipher_request_set_callback(skreq, flags,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; req->base.complete, req->base.data);
skcipher_request_set_crypt(skreq, dst, dst, cryptlen, req->iv); &nbsp;<== [6]

return&nbsp;crypto_skcipher_decrypt(skreq);
}

crypto_authenc_esn_decrypt_tail 函数是 crypto_authenc_esn_decrypt 最后还原数据的地方。

其中,[2] 和 [3] 是读取 低4位 和 高4低的数据,然后通过 [4] 将数据移动到最初始的位置。

我需要在下面加一个还原最后4字节的操作,数据在 [1] 处的 ihash 里。

但我们需要保证这块数据不会被后面的操作用到。

[5] 和 [6] 处限定了后面要使用的数据的范围。

通过上面的示意图,我们看到,后面所使用的数据并未使用到后面的 4字节数据,因为前面有强制性约束 authsize >=4。

至于为什么原作者不还原这4字节数据,个人认为是性能方面的考量(因为没必要),但也不排除可能是有的加密算法需要——这个需要后面跟原作者或相关内核开发者交流确认。

目前的暂时用于测试的补丁方案会是这样:

diff --git a/crypto/authencesn.c b/crypto/authencesn.c
index b60e61b..43b21bf 100644
--- a/crypto/authencesn.c
+++ b/crypto/authencesn.c
@@ -242,6 +242,8 @@&nbsp;static int crypto_authenc_esn_decrypt_tail(struct aead_request *req,
&nbsp; &nbsp; &nbsp; &nbsp; scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 0);
&nbsp; &nbsp; &nbsp; &nbsp; scatterwalk_map_and_copy(tmp, dst, 0, 8, 1);

+ &nbsp; &nbsp; &nbsp; scatterwalk_map_and_copy(ihash, dst, assoclen + cryptlen, 4, 1);
+
&nbsp; &nbsp; &nbsp; &nbsp; if (crypto_memneq(ihash, ohash, authsize))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return -EBADMSG;

PS: 该方案存在一个可以绕过的地方,如果在认证阶段找到抛异常的路径,依然可以绕过。

四、总结

各家操作系统的修复方案,都没有推出CopyFail热补丁,是我不敢按官方方案做热补丁的主要原因。

做为一个临时的修补方案,我想内存还原法可以在不怎么影响性能、不影响业务逻辑的情况下,防御漏洞攻击。

#

看雪ID:wanglxi

https://bbs.kanxue.com/user-home-485651.htm

*本文为看雪论坛优秀文章,由 wanglxi 原创,转载请注明来自看雪社区

第十届安全开发者峰会【议题征集】-欢迎投稿

往期推荐

ret2dlresolve分析

ELF GOT Hook 实战

面向复现的逆向工程实践:Hermes 在设备刷写、提权与 Frida 魔改中的自动化能力验证

把 .o 变成 .ko:GKI 安全特性的铁幕

实战APP全流程分析(检测绕过/登录分析/视频解锁/native加密/广告绕过)

球分享

球点赞

球在看

点击阅读原文查看更多


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:看雪学苑 wanglxi wanglxi《探索CVE-2026-31431的热补丁修复方案》

评论:0   参与:  0