Don't overflow the output length in EVP_CipherUpdate calls.
CVE-2021-23840
(Imported from upstream's 6a51b9e1d0cf0bf8515f7201b68fb0a3482b3dc1.)
This differs slightly from upstream's version:
- EVP_R_OUTPUT_WOULD_OVERFLOW didn't seem necessary when ERR_R_OVERFLOW
already exists. (Also since we use CIPHER_R_*, it wouldn't have helped
with compatibility anyway. Though there's probably something to be
said for us folding CIPHER_R_* back into EVP_R_*.)
- For simplicity, just check in_len + bl at the top, rather than trying
to predict the exact number of bytes written.
Update-Note: Passing extremely large input lengths into EVP_CipherUpdate
will now fail. Use EVP_AEAD instead, which is size_t-based and has more
explicit output bounds.
Change-Id: I31835c89dcdecb6b112828f57deb798dc7187db5
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/45685
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/fipsmodule/cipher/cipher.c b/crypto/fipsmodule/cipher/cipher.c
index c50c6c5..1522379 100644
--- a/crypto/fipsmodule/cipher/cipher.c
+++ b/crypto/fipsmodule/cipher/cipher.c
@@ -57,6 +57,7 @@
#include <openssl/cipher.h>
#include <assert.h>
+#include <limits.h>
#include <string.h>
#include <openssl/err.h>
@@ -240,14 +241,20 @@
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, uint8_t *out, int *out_len,
const uint8_t *in, int in_len) {
- int i, j, bl;
+ // Ciphers that use blocks may write up to |bl| extra bytes. Ensure the output
+ // does not overflow |*out_len|.
+ int bl = ctx->cipher->block_size;
+ if (bl > 1 && in_len > INT_MAX - bl) {
+ OPENSSL_PUT_ERROR(CIPHER, ERR_R_OVERFLOW);
+ return 0;
+ }
if (ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) {
- i = ctx->cipher->cipher(ctx, out, in, in_len);
- if (i < 0) {
+ int ret = ctx->cipher->cipher(ctx, out, in, in_len);
+ if (ret < 0) {
return 0;
} else {
- *out_len = i;
+ *out_len = ret;
}
return 1;
}
@@ -267,8 +274,7 @@
}
}
- i = ctx->buf_len;
- bl = ctx->cipher->block_size;
+ int i = ctx->buf_len;
assert(bl <= (int)sizeof(ctx->buf));
if (i != 0) {
if (bl - i > in_len) {
@@ -277,7 +283,7 @@
*out_len = 0;
return 1;
} else {
- j = bl - i;
+ int j = bl - i;
OPENSSL_memcpy(&ctx->buf[i], in, j);
if (!ctx->cipher->cipher(ctx, out, ctx->buf, bl)) {
return 0;
@@ -353,8 +359,13 @@
int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, uint8_t *out, int *out_len,
const uint8_t *in, int in_len) {
- int fix_len;
- unsigned int b;
+ // Ciphers that use blocks may write up to |bl| extra bytes. Ensure the output
+ // does not overflow |*out_len|.
+ unsigned int b = ctx->cipher->block_size;
+ if (b > 1 && in_len > INT_MAX - (int)b) {
+ OPENSSL_PUT_ERROR(CIPHER, ERR_R_OVERFLOW);
+ return 0;
+ }
if (ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) {
int r = ctx->cipher->cipher(ctx, out, in, in_len);
@@ -376,15 +387,12 @@
return EVP_EncryptUpdate(ctx, out, out_len, in, in_len);
}
- b = ctx->cipher->block_size;
assert(b <= sizeof(ctx->final));
-
+ int fix_len = 0;
if (ctx->final_used) {
OPENSSL_memcpy(out, ctx->final, b);
out += b;
fix_len = 1;
- } else {
- fix_len = 0;
}
if (!EVP_EncryptUpdate(ctx, out, out_len, in, in_len)) {
diff --git a/include/openssl/cipher.h b/include/openssl/cipher.h
index c6bec48..3feadea 100644
--- a/include/openssl/cipher.h
+++ b/include/openssl/cipher.h
@@ -558,6 +558,10 @@
// block_mask contains |cipher->block_size| minus one. (The block size
// assumed to be a power of two.)
+ //
+ // TODO(davidben): This is redundant with |cipher->block_size| and constant
+ // for the whole |EVP_CIPHER|. Move it there, or possibly even remove it and
+ // do the subtraction on demand.
int block_mask;
uint8_t final[EVP_MAX_BLOCK_LENGTH]; // possible final block