最近調研和研發關於c++支持ws和wss協議的網絡底層,意外發現了Openssl內部出現死循環的情況。網絡底層採用boost::asio和Openssl的方式支持wss協議。
平時使用都是正常,等到最近壓測的時候發現,一段時間後會出現死循環的情況,經過一段時間的調查發現竟然是Openssl底層導致的死循環,死循環的堆棧的信息如下:
19:40:03.441 d:\tddownload\code\openssl-1.1.0i_test\crypto\bio\bss_bio.c (325): bio_write
19:40:03.441 d:\tddownload\code\openssl-1.1.0i_test\crypto\bio\bio_lib.c (232): BIO_write
19:40:03.441 d:\tddownload\code\openssl-1.1.0i_test\ssl\record\rec_layer_s3.c (930): ssl3_write_pending
19:40:03.441 d:\tddownload\code\openssl-1.1.0i_test\ssl\record\rec_layer_s3.c (881): do_ssl3_write
後來又寫了一些測試代碼,發現實際上是無符號整形溢出。溢出的接口名稱:bio_write
源碼如下:
static int bio_write(BIO *bio, const char *buf, int num_)
{
size_t num = num_;
size_t rest;
struct bio_bio_st *b;
BIO_clear_retry_flags(bio);
if (!bio->init || buf == NULL || num == 0)
return 0;
b = bio->ptr;
assert(b != NULL);
assert(b->peer != NULL);
assert(b->buf != NULL);
b->request = 0;
if (b->closed) {
/* we already closed */
BIOerr(BIO_F_BIO_WRITE, BIO_R_BROKEN_PIPE);
return -1;
}
assert(b->len <= b->size);
if (b->len == b->size) {
BIO_set_retry_write(bio); /* buffer is full */
return -1;
}
/* we can write */
if (num > b->size - b->len)
num = b->size - b->len;
/* now write "num" bytes */
rest = num;
assert(rest > 0);
do { /* one or two iterations */
size_t write_offset;
size_t chunk;
assert(b->len + rest <= b->size);
write_offset = b->offset + b->len;
if (write_offset >= b->size)
write_offset -= b->size;
/* b->buf[write_offset] is the first byte we can write to. */
if (write_offset + rest <= b->size)
chunk = rest;
else
/* wrap around ring buffer */
chunk = b->size - write_offset;
memcpy(b->buf + write_offset, buf, chunk);
b->len += chunk;
assert(b->len <= b->size);
rest -= chunk;
buf += chunk;
}
while (rest);
return num;
}
死循環是因爲,chunk會大於rest,然後導致rest溢出,然後chunk的值一直爲0,導致死循環。
剛接觸Openssl不確定是否是因爲使用上的問題導致的,不過代碼確實有不夠健壯的地方。
目前該問題已經反饋到Openssl的github的issues上,希望官方組織能及時修復該問題。