【OPENSSL】RSACreateKey阻塞 | 一次开源代码的调试记录

一、问题背景

在做交换设备升级tls1.3,之前做的升级都很顺利,但最近有一台交换设备升级后开机时卡住。然后编译了一版x86测试,x86上开机正常,测试tls1.3也是升级成功的。调试了代码,设备启动时初始LoginEncrypt模块时要生成Rsa密钥对,结果就是卡在这个函数里导致启动不了。设备信息: AMRv7芯片,带有Linux操作系统。

二、阻塞原因(结论)

开头先给结论: 生成密钥前需要获取随机数,在linux系统中随机数生成依赖字符设备/dev/random,当系统运行时各种交互带来的随机熵会存入到random中,生成随机数时就会往该设备里读随机字符,如果熵不够,读取设备时就会发生阻塞。还有一种字符设备/dev/urandom不会发生阻塞,熵不够时只是生成的随机数质量比较低,所以阻塞是因为os的/dev目录下没有urandom,只有random,又是在开机时需要随机数,导致熵不够发生阻塞。

三、调试记录

首先开源代码里是没有RSACreateKey函数的,这一般是我们使用时自己写的函数,源码提供了RSA_generate_key函数,所以就从这个函数开始往下追。

RSA *RSA_generate_key(int bits, unsigned long e_value,
                      void (*callback) (int, int, void *), void *cb_arg)
{
 /***/

    if (RSA_generate_key_ex(rsa, bits, e, cb)) {
        BN_free(e);
        BN_GENCB_free(cb);
        return rsa;
    }
 /***/
}

我们根据RSA_generate_key的源码,继续往下查RSA_generate_key_ex函数。在继续往下追之前,先考虑一下哪些情况会发生阻塞?我首先怀疑发生阻塞是因为在某个死循环里退不出来,其次是锁。所以后面追代码的时候都优先检查循环和锁。

密钥生成大致步骤就是先生成随机数,然后计算密钥,再检查密钥是否安全,这是个循环操作。其中有不少中间函数都是有for(;;)死循环和goto语句循环的,但非常可惜,这些循环都只执行了一到两次,也遇到过锁,但是实际上锁也没有发挥作用。

最后往下追代码,大致知道了问题出在随机数生成的时候,因为没有生成随机数,导致后面的操作都无法进行。openssl源码中生成随机数的函数是bnrand(),查找openssl的官方文档看看这个函数的信息,很可惜,没有我想看到的bnrand()会发生阻塞的描述。所以只能进函数继续分析。再往下分析,在执行drbg->get_entropy时卡住,因为是个函数指针,实际调用函数如下: 

size_t rand_drbg_get_entropy(RAND_DRBG *drbg,
                             unsigned char **pout,
                             int entropy, size_t min_len, size_t max_len,
                             int prediction_resistance)
{
    /***/
    if (drbg->parent != NULL) {
        size_t bytes_needed = rand_pool_bytes_needed(pool, 1 /*entropy_factor*/);
        unsigned char *buffer = rand_pool_add_begin(pool, bytes_needed);

        if (buffer != NULL) {
            size_t bytes = 0;
            printf("1");
            rand_drbg_lock(drbg->parent);
            printf("2");
            if (RAND_DRBG_generate(drbg->parent,
                                   buffer, bytes_needed,
                                   prediction_resistance,
                                   (unsigned char *)&drbg, sizeof(drbg)) != 0)
                bytes = bytes_needed;
            printf("3");
            rand_drbg_unlock(drbg->parent);

            rand_pool_add_end(pool, bytes, 8 * bytes);
            entropy_available = rand_pool_entropy_available(pool);
        }

    } else {
        printf("4");
        entropy_available = rand_pool_acquire_entropy(pool);
        printf("5");
    }
    /***/
}

比较奇怪的是,打印的结果是1、2、4,所以猜测RAND_DRBG_generate函数又回调了drbg->get_entropy,然后第二次走的4,就简单追了下RAND_DRBG_generate,确实如此,所以再往下追rand_pool_acquire_entropy好了。

追到这问题快解决了。查看了rand_pool_acquire_entropy函数的定义:

size_t rand_pool_acquire_entropy(RAND_POOL *pool)
{
    /***/
#   if defined(OPENSSL_RAND_SEED_DEVRANDOM)
    if (wait_random_seeded()) {
        size_t bytes_needed;
        unsigned char *buffer;
        size_t i;

        bytes_needed = rand_pool_bytes_needed(pool, 1 /*entropy_factor*/);
        for (i = 0; bytes_needed > 0 && i < OSSL_NELEM(random_device_paths);
             i++) {
            ssize_t bytes = 0;
            /* Maximum number of consecutive unsuccessful attempts */
            int attempts = 3;
            const int fd = get_random_device(i);

            if (fd == -1)
                continue;

            while (bytes_needed != 0 && attempts-- > 0) {
                buffer = rand_pool_add_begin(pool, bytes_needed);
                bytes = read(fd, buffer, bytes_needed);

                if (bytes > 0) {
                    rand_pool_add_end(pool, bytes, 8 * bytes);
                    bytes_needed -= bytes;
                    attempts = 3; /* reset counter on successful attempt */
                } else if (bytes < 0 && errno != EINTR) {
                    break;
                }
            }
            if (bytes < 0 || !keep_random_devices_open)
                close_random_device(i);

            bytes_needed = rand_pool_bytes_needed(pool, 1);
        }
        entropy_available = rand_pool_entropy_available(pool);
        if (entropy_available > 0)
            return entropy_available;
    }
#   endif
    /***/
}

最后定位到read函数阻塞,我记得read函数不会阻塞来着,然后查看了一下fd是打开的哪一个文件,文件地址定义在random_device_paths里:

#  define DEVRANDOM "/dev/urandom", "/dev/random", "/dev/hwrng", "/dev/srandom"

可以看到优先打开urandom,其次是random。实际debug for循环确实执行了两次,第一次fd -1走了continue进入下一次循环,第二次fd 8,然后read阻塞。所以查了一下random和urandom的资料,确实random刚启动时熵不够会导致阻塞。我又从代码的os目录下查看了/dev的文件情况,没有urandom。然后看看之前其他升级过的设备,os下都有urandom。那么问题很显然就是因为这个了!最后在os目录下找到生成设备文件的脚本,把urandom添加上去,重新编译,终于成功启动了!!

生成urandom字符设备的命令: 

$sudo mknod urandom c 1 9

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值