首页
社区
课程
招聘
[原创]探索CVE-2026-31431的热补丁修复方案
发表于: 1天前 396

[原创]探索CVE-2026-31431的热补丁修复方案

1天前
396

零、背景

在修复CVE-2026-31431的过程中,同事希望能有一个热补丁方案,减少对现有业务的影响。
该方案不仅要防御攻击,还要避免影响现在和未来的业务。
所以就有了下面对热补丁方案的探索。

一、漏洞原理解析

具体原理,0xlane大佬已经分析的很详细了——
Copy Fail 深度研究:Linux 页缓存漏洞的根因、利用与检测
这里只做简析:
漏洞原理解析
该漏洞主要利用了2个点:

  1. _aead_recvmsg 函数中,为了提升效率,将 输入源 直接用作 输出源
  2. 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

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

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

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

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

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 = lambda x: _UINT.pack(x)

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

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


data = 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 = bytes.fromhex("521178d78d411de70b69e2ffb866411c0a1046100000403b00150001575e196a000000008e13020000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536370102030405060708090a0a04")

def f_setkey():
    KEY = AlgKey(0x08000100, 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()
    keylen = len(KEY)

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

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

    key = KEY[rta_len:]
    keylen -= rta_len

    authkeylen = keylen - enckeylen
    authkey = key[:authkeylen]
    enckey = key[authkeylen:]

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

def f_valid(key, data):
    ihash = data[-ASIZE:]		
    data = data[:4] + data[8:-ASIZE] + data[4:8]
    
    f_hash = hashlib.sha256
    hmac_obj = hmac.new(key, digestmod=f_hash)
    hmac_obj.digest_size = 32
    hmac_obj.block_size = 64
    hmac_obj.update(data)
    r = hmac_obj.digest()
    r = r[:ASIZE]
    print("F=", bytes.hex(r))
    print("I=", bytes.hex(ihash))

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

    data = data[ALEN:-ASIZE]

    aes = AES.new(key, AES.MODE_CBC, iv)
    r = aes.decrypt(data)
    print("D:", bytes.hex(r))
    print("R:", bytes.hex(RESULT))
    assert 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 int crypto_authenc_esn_decrypt_tail(struct aead_request *req,
                       unsigned int flags)
{
    struct crypto_aead *authenc_esn = crypto_aead_reqtfm(req);
    unsigned int authsize = crypto_aead_authsize(authenc_esn);
    struct authenc_esn_request_ctx *areq_ctx = aead_request_ctx(req);
    struct crypto_authenc_esn_ctx *ctx = crypto_aead_ctx(authenc_esn);
    struct skcipher_request *skreq = (void *)(areq_ctx->tail +
                          ctx->reqoff);
    struct crypto_ahash *auth = ctx->auth;
    u8 *ohash = PTR_ALIGN((u8 *)areq_ctx->tail,
                  crypto_ahash_alignmask(auth) + 1);
    unsigned int cryptlen = req->cryptlen - authsize;   <== [0]
    unsigned int assoclen = req->assoclen;
    struct scatterlist *dst = req->dst;
    u8 *ihash = ohash + crypto_ahash_digestsize(auth);  <== [1]
    u32 tmp[2];

    if (!authsize)
        goto decrypt;

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

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

decrypt:

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

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

    return 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 @@ static int crypto_authenc_esn_decrypt_tail(struct aead_request *req,
        scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 0);
        scatterwalk_map_and_copy(tmp, dst, 0, 8, 1);
 
+       scatterwalk_map_and_copy(ihash, dst, assoclen + cryptlen, 4, 1);
+
        if (crypto_memneq(ihash, ohash, authsize))
                return -EBADMSG;

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

三、总结

各家操作系统的修复方案,都没有推出CopyFail热补丁,是我不敢按官方方案做热补丁的主要原因。
做为一个临时的修补方案,我想内存还原法可以在不怎么影响性能、不影响业务逻辑的情况下,防御漏洞攻击。


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 1天前 被wanglxi编辑 ,原因:
收藏
免费 1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回