Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test case, reproducing Botan/Windows crash. #2148

Merged
merged 2 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions src/tests/cipher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1137,3 +1137,89 @@
assert_false(supported);
#endif
}

#if defined(CRYPTO_BACKEND_BOTAN)
TEST_F(rnp_tests, test_windows_botan_crash)
Fixed Show fixed Hide fixed
{
/* Reproducer for https://github.com/randombit/botan/issues/3812 . Related CLI test
* test_sym_encrypted__rnp_aead_botan_crash */

auto data = file_to_vec("data/test_messages/message.aead-windows-issue-botan");
/* First 32 bytes are encrypted key as it was extracted from the OpenPGP stream, so
* skipping. */
uint8_t *idx = data.data() + 32;
uint8_t bufbin[64] = {0};
uint8_t outbuf[32768] = {0};
size_t outsz = sizeof(outbuf);
size_t written = 0;
size_t read = 0;
size_t diff = 0;

/* Now the data which exposes a possible crash */
struct botan_cipher_struct *cipher = NULL;
assert_int_equal(botan_cipher_init(&cipher, "AES-128/OCB", BOTAN_CIPHER_INIT_FLAG_DECRYPT),
0);

const char *key2 = "417835a476bc5958b18d41fb00cf682d";
assert_int_equal(rnp::hex_decode(key2, bufbin, 16), 16);
assert_int_equal(botan_cipher_set_key(cipher, bufbin, 16), 0);

const char *ad2 = "d40107020c0000000000000000";
assert_int_equal(rnp::hex_decode(ad2, bufbin, 13), 13);
assert_int_equal(botan_cipher_set_associated_data(cipher, bufbin, 13), 0);

const char *nonce2 = "005dbbbe0088f9d17ca2d8d464920f";
assert_int_equal(rnp::hex_decode(nonce2, bufbin, 15), 15);
assert_int_equal(botan_cipher_start(cipher, bufbin, 15), 0);

assert_int_equal(
botan_cipher_update(cipher, 0, outbuf, outsz, &written, idx, 32736, &read), 0);
diff = 32736 - read;
idx += read;

assert_int_equal(
botan_cipher_update(cipher, 0, outbuf, outsz, &written, idx, diff + 32736, &read), 0);
idx += read;
diff = diff + 32736 - read;

assert_int_equal(
botan_cipher_update(cipher, 0, outbuf, outsz, &written, idx, diff + 32736, &read), 0);
idx += read;
diff = diff + 32736 - read;

assert_int_equal(
botan_cipher_update(cipher, 0, outbuf, outsz, &written, idx, diff + 32736, &read), 0);
idx += read;
diff = diff + 32736 - read;

uint32_t ver_major = botan_version_major();
uint32_t ver_minor = botan_version_minor();
uint32_t ver_patch = botan_version_patch();
uint32_t ver = (ver_major << 16) | (ver_minor << 8) | ver_patch;
uint32_t ver_2_19_3 = (2 << 16) | (19 << 8) | 3;
uint32_t ver_3_2_0 = (3 << 16) | (2 << 8);
bool check = true;
/* Currently AV happens with versions up to 2.19.3 and 3.2.0 */
if ((ver_major == 2) && (ver <= ver_2_19_3)) {
check = false;
}
if ((ver_major == 3) && (ver <= ver_3_2_0)) {
check = false;
}

if (check) {
assert_int_equal(botan_cipher_update(cipher,

Check warning on line 1211 in src/tests/cipher.cpp

View check run for this annotation

Codecov / codecov/patch

src/tests/cipher.cpp#L1211

Added line #L1211 was not covered by tests
BOTAN_CIPHER_UPDATE_FLAG_FINAL,
outbuf,
outsz,
&written,
idx,
diff + 25119,
&read),
0);

Check warning on line 1219 in src/tests/cipher.cpp

View check run for this annotation

Codecov / codecov/patch

src/tests/cipher.cpp#L1219

Added line #L1219 was not covered by tests
}

assert_int_equal(botan_cipher_reset(cipher), 0);
assert_int_equal(botan_cipher_destroy(cipher), 0);
}
#endif
28 changes: 27 additions & 1 deletion src/tests/cli_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import tempfile
import time
import unittest
import random
from platform import architecture

from cli_common import (file_text, find_utility, is_windows, list_upto,
Expand Down Expand Up @@ -47,6 +48,8 @@
RNP_BLOWFISH = True
RNP_CAST5 = True
RNP_RIPEMD160 = True
# Botan may cause AV during OCB decryption in certain cases, see https://github.com/randombit/botan/issues/3812
RNP_BOTAN_OCB_AV = False

if sys.version_info >= (3,):
unichr = chr
Expand Down Expand Up @@ -861,6 +864,7 @@ def gpg_check_features():

def rnp_check_features():
global RNP_TWOFISH, RNP_BRAINPOOL, RNP_AEAD, RNP_AEAD_EAX, RNP_AEAD_OCB, RNP_AEAD_OCB_AES, RNP_IDEA, RNP_BLOWFISH, RNP_CAST5, RNP_RIPEMD160
global RNP_BOTAN_OCB_AV
ret, out, _ = run_proc(RNP, ['--version'])
if ret != 0:
raise_err('Failed to get RNP version.')
Expand All @@ -869,6 +873,14 @@ def rnp_check_features():
RNP_AEAD_OCB = re.match(r'(?s)^.*AEAD:.*OCB.*', out) is not None
RNP_AEAD = RNP_AEAD_EAX or RNP_AEAD_OCB
RNP_AEAD_OCB_AES = RNP_AEAD_OCB and re.match(r'(?s)^.*Backend.*OpenSSL.*', out) is not None
# Botan OCB crash
if re.match(r'(?s)^.*Backend.*Botan.*', out):
match = re.match(r'(?s)^.*Backend version: ([\d]+)\.([\d]+)\.([\d]+).*$', out)
ver = [int(match.group(1)), int(match.group(2)), int(match.group(3))]
if ver <= [2, 19, 3]:
RNP_BOTAN_OCB_AV = True
if (ver >= [3, 0, 0]) and (ver <= [3, 2, 0]):
RNP_BOTAN_OCB_AV = True
# Twofish
RNP_TWOFISH = re.match(r'(?s)^.*Encryption:.*TWOFISH.*', out) is not None
# Brainpool curves
Expand All @@ -887,6 +899,7 @@ def rnp_check_features():
print('RNP_AEAD_EAX: ' + str(RNP_AEAD_EAX))
print('RNP_AEAD_OCB: ' + str(RNP_AEAD_OCB))
print('RNP_AEAD_OCB_AES: ' + str(RNP_AEAD_OCB_AES))
print('RNP_BOTAN_OCB_AV: ' + str(RNP_BOTAN_OCB_AV))

def setup(loglvl):
# Setting up directories.
Expand Down Expand Up @@ -4159,12 +4172,25 @@ def test_sym_encryption__rnp_aead(self):
AEAD_C = list_upto(CIPHERS, Encryption.RUNS)
AEAD_M = list_upto(AEADS, Encryption.RUNS)
AEAD_B = list_upto([None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16], Encryption.RUNS)
SIZES = Encryption.SIZES_R
random.shuffle(SIZES)

# Encrypt and decrypt cleartext using the AEAD
for size, cipher, aead, bits, z in zip(Encryption.SIZES_R, AEAD_C,
for size, cipher, aead, bits, z in zip(SIZES, AEAD_C,
AEAD_M, AEAD_B, Encryption.Z_R):
if RNP_BOTAN_OCB_AV and (aead == 'ocb') and (size > 30000):
continue
rnp_sym_encryption_rnp_aead(size, cipher, z, [aead, bits], GPG_AEAD)

def test_sym_encrypted__rnp_aead_botan_crash(self):
if RNP_BOTAN_OCB_AV:
return
dst, = reg_workfiles('cleartext', '.txt')
rnp_decrypt_file(data_path('test_messages/message.aead-windows-issue'), dst)
remove_files(dst)
rnp_decrypt_file(data_path('test_messages/message.aead-windows-issue2'), dst)
remove_files(dst)

def test_aead_chunk_edge_cases(self):
if not RNP_AEAD:
print('AEAD is not available for RNP - skipping.')
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.