已关闭
[Bug]: CRYPT_decrypt 处理全零或空明文时会越界读取 #8474
Wwwing301创建于  25 天前关闭于  17 天前
Wwwing301
25 天前 创建

测试类型

安全

测试版本

7.0.0LTS

问题描述

基于 openGauss master 提交 f8a1e2d3d0bd1cff05cb2769b509a8ac1ab0f3b1

CRYPT_decrypt() 解密后会从明文末尾向前查找填充标记。当前游标使用无符号整数,并且没有检查是否已经到达缓冲区开头。

如果解密结果全部为 0x00,游标会一直减到 0 以下并变成一个很大的正数;如果明文长度本来就是 0,第一次访问就已经越界。两个输入都能在 AddressSanitizer 下稳定触发越界读。

操作系统和硬件信息

Ubuntu 22.04.5 LTS,x86_64,AddressSanitizer/UndefinedBehaviorSanitizer。

测试环境

企业版单机

被测功能

公共密码函数 CRYPT_decrypt() 对解密结果的填充处理。
本次只运行独立 C++ 最小测试并链接 OpenSSL,没有启动数据库。

预置条件

操作步骤

g++ -std=gnu++17 -O0 -g -fno-omit-frame-pointer \
  -fsanitize=address,undefined poc.cc -lcrypto -o poc

./poc valid-control
./poc invalid-padding-control
./poc all-zero-trigger
./poc zero-length-trigger

后两个命令会被 ASan 中止,需要分别执行。

预期输出

有效填充正常解密;空明文、全零明文或找不到填充标记时安全返回错误,不读取缓冲区之外。

实际输出

有效填充对照返回 RET=0 LEN=3,普通错误填充能安全返回错误。全零和零长度两个用例都在
以下循环处触发 ASan 越界读:

while (*(pucPlainText + oLen) == 0)
AddressSanitizer:DEADLYSIGNAL

日志信息

相关代码:

  • src/common/port/cipher.cpp:1179-1233
  • src/gausskernel/cbb/utils/aes/aes.cpp:571-633
  • src/gausskernel/cbb/utils/aes/cipherfn.cpp:257-340,2222-2307

提单组织

社区用户

测试代码

#include <array>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <openssl/evp.h>
#include <openssl/obj_mac.h>

using GS_UINT32 = unsigned int;
using GS_UCHAR = unsigned char;

static const EVP_CIPHER *get_evp_cipher_by_id(GS_UINT32 id)
{
    return id == NID_aes_128_cbc ? EVP_aes_128_cbc() : nullptr;
}

GS_UINT32 CRYPT_decrypt(GS_UINT32 ulAlgId, const GS_UCHAR* pucKey, GS_UINT32 ulKeyLen,
    const GS_UCHAR* pucIV, GS_UINT32 ulIVLen, GS_UCHAR* pucCipherText, GS_UINT32 ulCLen,
    GS_UCHAR* pucPlainText, GS_UINT32* pulPLen)
{
    EVP_CIPHER_CTX* ctx = NULL;
    const EVP_CIPHER* cipher = NULL;
    int dec_num = 0;
    unsigned int blocksize;
    unsigned int oLen;

    if (pucCipherText == NULL) return 1;
    cipher = get_evp_cipher_by_id(ulAlgId);
    if (cipher == NULL) return 1;
    ctx = EVP_CIPHER_CTX_new();
    if (ctx == NULL) return 1;
    EVP_CipherInit_ex(ctx, cipher, NULL, pucKey, pucIV, 0);
    EVP_CIPHER_CTX_set_padding(ctx, 0);
    if (!EVP_DecryptUpdate(ctx, pucPlainText, &dec_num, pucCipherText, ulCLen)) goto err;
    *pulPLen = dec_num;
    if (!EVP_DecryptFinal(ctx, pucPlainText + dec_num, &dec_num)) goto err;
    *pulPLen += dec_num;
    blocksize = EVP_CIPHER_CTX_block_size(ctx);
    oLen = (*pulPLen) - 1;
    while (*(pucPlainText + oLen) == 0) oLen--;
    if (oLen >= ((*pulPLen) - blocksize) && *(pucPlainText + oLen) == 0x80)
        (*pulPLen) = oLen;
    else
        goto err;
    pucPlainText[oLen] = '\0';
    EVP_CIPHER_CTX_free(ctx);
    return 0;
err:
    EVP_CIPHER_CTX_free(ctx);
    return 1;
}

static std::vector<unsigned char> encrypt_block(const std::array<unsigned char, 16> &plain)
{
    std::array<unsigned char, 16> key{};
    std::array<unsigned char, 16> iv{};
    std::vector<unsigned char> cipher(32);
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    int first = 0, final = 0;
    if (ctx == nullptr || EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), nullptr,
            key.data(), iv.data()) != 1 || EVP_CIPHER_CTX_set_padding(ctx, 0) != 1 ||
        EVP_EncryptUpdate(ctx, cipher.data(), &first, plain.data(), plain.size()) != 1 ||
        EVP_EncryptFinal_ex(ctx, cipher.data() + first, &final) != 1) std::abort();
    EVP_CIPHER_CTX_free(ctx);
    cipher.resize(static_cast<std::size_t>(first + final));
    return cipher;
}

int main(int argc, char **argv)
{
    if (argc != 2) return 2;
    std::array<unsigned char, 16> key{}, iv{};
    std::array<unsigned char, 64> output{};
    GS_UINT32 output_len = 0;
    std::vector<unsigned char> cipher;
    const std::string mode(argv[1]);
    if (mode == "valid-control") {
        std::array<unsigned char, 16> plain{};
        plain[0] = 'a'; plain[1] = 'b'; plain[2] = 'c'; plain[3] = 0x80;
        cipher = encrypt_block(plain);
    } else if (mode == "invalid-padding-control") {
        std::array<unsigned char, 16> plain{}; plain.fill('A'); cipher = encrypt_block(plain);
    } else if (mode == "all-zero-trigger") {
        std::array<unsigned char, 16> plain{}; cipher = encrypt_block(plain);
    } else if (mode == "zero-length-trigger") {
        cipher.assign(1, 0);
    } else return 2;
    const GS_UINT32 cipher_len = mode == "zero-length-trigger" ? 0U :
        static_cast<GS_UINT32>(cipher.size());
    const GS_UINT32 ret = CRYPT_decrypt(NID_aes_128_cbc, key.data(), key.size(),
        iv.data(), iv.size(), cipher.data(), cipher_len, output.data(), &output_len);
    std::printf("RET=%u LEN=%u\n", ret, output_len);
    if (mode == "valid-control")
        return ret == 0 && output_len == 3 && std::memcmp(output.data(), "abc", 3) == 0 ? 0 : 3;
    if (mode == "invalid-padding-control") return ret == 1 ? 0 : 3;
    return 4;
}



likedislike
opengauss_bot
opengauss_bot成员
25 天前 评论:

This issue requires an assignee. Since you haven't specified one, we've assigned TestManager as the default assignee for this issue.

likedislike
opengauss_botopengauss_bot成员
25 天前 将 TestManager 设为负责人
opengauss_bot
opengauss_bot成员
25 天前 评论:

Welcome To openGauss Community

Hey @Wwwing301 , thanks for your contribution to the community.

Bot Usage Manual

I'm the Bot here serving you. You can find the instructions on how to interact with me at Here . That means you can comment below every pull request or issue to trigger Bot Commands. You can self-configure the PR merge rules for this repository. For more details, please refer to Here.

Contact Guide

If you have any questions, please contact the SIG: StorageEngine, AI, CM, CloudNative, SecurityTechnology, SQLEngine ,
and any of the maintainers: @CarrotGo, @chendong76, @chenxiaobin19, @congzhou2603, @dodders, @hwworkholic, @jemappellehc, @libiao2024, @muyulinzhong, @quemingjian, @shenzheng4, @shirley_zhengx, @superlchf, @totaj, @wlff234, @wofanzheng, @ywzq1161327784 ,
and any of the committers: @Igali, @bihua111, @cailei19, @h_ray, @levy53071, @libiao2024, @lihaixiao, @mrzack, @wangfeihuo, @wuyuechuan, @xiong_xjun, @zhangfengzhi123, @zhangxubo, @zhangzq131, @zjh_hw .

likedislike
opengauss_botopengauss_bot成员
25 天前 添加了label:sig/SQLEngine
liuzhen001liuzhen001成员
25 天前 关联了看板:openGauss 7.0.0-LTS
ywzq1161327784ywzq1161327784成员
25 天前 将 shijuzheng1997 设为负责人
ywzq1161327784ywzq1161327784成员
25 天前 移除了负责人 TestManager
shijuzheng1997shijuzheng1997成员
23 天前 关联了pull request:[700]修复CRYPT_decrypt 处理全零或空明文时会越界读取的问题
shijuzheng1997shijuzheng1997成员
23 天前 关联了pull request:修复CRYPT_decrypt 处理全零或空明文时会越界读取的问题
ywzq1161327784ywzq1161327784成员
22 天前 issue状态由 待办的 改变为 已完成
shijuzheng1997
shijuzheng1997成员
20 天前 评论:

20260912 开发自验
memcheck 编译 commit点: 68f12854
连续执行20次

select gs_decrypt_aes128('AAAAAAAAAAAAAAAAAAAAAOKfWT0nb9ND2XUSbCR538QAAQIDBAUGBwgJCgsMDQ4P', 'AttackKeyForOG');

无宕机
图片.png

likedislike
shijuzheng1997shijuzheng1997成员
20 天前 issue状态由 已完成 改变为 待回归
l1azzzy
l1azzzy成员
17 天前 评论:

验收日期:2026-9-15
验收结论:验收通过
验收版本:7.0.0.B023
image.png

likedislike
l1azzzyl1azzzy成员
17 天前 issue状态由 待回归 改变为 已验收
l1azzzyl1azzzy成员
17 天前 关闭了 issue