-
Notifications
You must be signed in to change notification settings - Fork 334
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
CVE-2016-6225: xtrabackup encryption is not setting the IV correctly #266
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,7 +36,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA | |
|
||
#include "xbcrypt.h" | ||
|
||
#if !defined(GCRYPT_VERSION_NUMBER) || (GCRYPT_VERSION_NUMBER < 0x010600) | ||
GCRY_THREAD_OPTION_PTHREAD_IMPL; | ||
#endif | ||
|
||
#define XB_CRYPT_CHUNK_SIZE ((size_t) (xtrabackup_encrypt_chunk_size)) | ||
|
||
|
@@ -101,7 +103,6 @@ static uint encrypt_algos[] = { GCRY_CIPHER_NONE, GCRY_CIPHER_AES128, | |
static uint encrypt_algo; | ||
static const uint encrypt_mode = GCRY_CIPHER_MODE_CTR; | ||
static uint encrypt_key_len = 0; | ||
static const unsigned char encrypt_iv[] = "Percona Xtrabackup is Awesome!!!"; | ||
static size_t encrypt_iv_len = 0; | ||
|
||
static | ||
|
@@ -132,6 +133,7 @@ encrypt_init(const char *root) | |
|
||
/* Acording to gcrypt docs (and my testing), setting up the threading | ||
callbacks must be done first, so, lets give it a shot */ | ||
#if !defined(GCRYPT_VERSION_NUMBER) || (GCRYPT_VERSION_NUMBER < 0x010600) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as per above comments |
||
gcry_error = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); | ||
if (gcry_error) { | ||
msg("encrypt: unable to set libgcrypt thread cbs - " | ||
|
@@ -140,6 +142,7 @@ encrypt_init(const char *root) | |
gcry_strerror(gcry_error)); | ||
return NULL; | ||
} | ||
#endif | ||
|
||
/* Version check should be the very next call because it | ||
makes sure that important subsystems are intialized. */ | ||
|
@@ -181,7 +184,6 @@ encrypt_init(const char *root) | |
/* Set up the iv length */ | ||
encrypt_iv_len = gcry_cipher_get_algo_blklen(encrypt_algo); | ||
xb_a(encrypt_iv_len > 0); | ||
xb_a(encrypt_iv_len <= sizeof(encrypt_iv)); | ||
|
||
/* Now set up the key */ | ||
if (xtrabackup_encrypt_key == NULL && | ||
|
@@ -345,7 +347,8 @@ encrypt_write(ds_file_t *file, const void *buf, size_t len) | |
|
||
if (xb_crypt_write_chunk(crypt_file->xbcrypt_file, | ||
threads[i].to, | ||
threads[i].from_len, | ||
threads[i].from_len + | ||
XB_CRYPT_HASH_LEN, | ||
threads[i].to_len, | ||
threads[i].iv, | ||
encrypt_iv_len)) { | ||
|
@@ -424,8 +427,8 @@ create_worker_threads(uint n) | |
thd->cancelled = FALSE; | ||
thd->data_avail = FALSE; | ||
|
||
thd->to = (char *) my_malloc(XB_CRYPT_CHUNK_SIZE, | ||
MYF(MY_FAE)); | ||
thd->to = (char *) my_malloc(XB_CRYPT_CHUNK_SIZE + | ||
XB_CRYPT_HASH_LEN, MYF(MY_FAE)); | ||
|
||
thd->iv = (char *) my_malloc(encrypt_iv_len, | ||
MYF(MY_FAE)); | ||
|
@@ -552,6 +555,14 @@ encrypt_worker_thread_func(void *arg) | |
if (thd->cancelled) | ||
break; | ||
|
||
/* ensure that XB_CRYPT_HASH_LEN is the correct length | ||
of XB_CRYPT_HASH hashing algorithm output */ | ||
assert(gcry_md_get_algo_dlen(XB_CRYPT_HASH) == | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. May want to add some comments in here that we want to force an error to write to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I defined There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes I understand that, I just meant that a source code comment would have been appreciated here e.g. #Ensure the returned value of gcry_md_get_algo_dlen(XB_CRYPT_HASH) is equal to XB_CRYPT_HASH_LEN or throw an assertion.
assert(gcry_md_get_algo_dlen(XB_CRYPT_HASH) ==
XB_CRYPT_HASH_LEN); Entirely depends on your coding standard though, so feel free to not include something like this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment added |
||
XB_CRYPT_HASH_LEN); | ||
|
||
memcpy(thd->to, thd->from, thd->from_len); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thd->to char* pointer containing So this confirms we are running a memcpy to memory of the plaintext right before it is encrypted ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is an interesting part actually. I want to encrypt buffer pointed by
The caveat is that option 1 doesn't work for me in multithreaded environments in CentOS 5 and 6 (sigh), so I went with option 2. As for plaintext chunks left over in the memory... Yes, we don't scrape the buffer, but it will be overwritten with the next chunk, and so on. At the moment when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Disclaimer is I may be wrong here, but C/C++ does not have any Garbage Collection as I noted above, the Linux Kernel does not (be default) run any GC / Purge against free memory. You are literally waiting for a process to come along and overwrite the same address in memory, or the system is shutdown (must be powered off) and allowed to cold boot after some time (even then this doesn't address physical memory artifacts). Perhaps I am overthinking this, however I think that right after we are done with this buffer writing an equivalent length of random data would ensure the artifact is removed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sometime the key copies in memory are overwritten once they are not used anymore (http://security.stackexchange.com/questions/74280/would-it-be-good-secure-programming-practice-to-overwrite-a-sensitive-variable etc). If you choose to do so, make sure not to use memset which can be optimized away (memset_s if available is OK) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It probably makes sense to wipe out buffers for long living processes when keys or data are no longer used may still reside in processes memory and even be accessible to remote attacker via buffer overflow or other potential bugs. I think xtrabackup is not the case. Encryption thread working as follows:
Yes, 16 kbytes of plaintext are left in the
Lets say attacker has an access to Am I missing something here? |
||
gcry_md_hash_buffer(XB_CRYPT_HASH, thd->to + thd->from_len, | ||
thd->from, thd->from_len); | ||
thd->to_len = thd->from_len; | ||
|
||
if (encrypt_algo != GCRY_CIPHER_NONE) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we only run the encryption where it is not set to the algorithm There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possible values for |
||
|
@@ -568,7 +579,7 @@ encrypt_worker_thread_func(void *arg) | |
} | ||
|
||
xb_crypt_create_iv(thd->iv, encrypt_iv_len); | ||
gcry_error = gcry_cipher_setiv(thd->cipher_handle, | ||
gcry_error = gcry_cipher_setctr(thd->cipher_handle, | ||
thd->iv, | ||
encrypt_iv_len); | ||
if (gcry_error) { | ||
|
@@ -582,18 +593,22 @@ encrypt_worker_thread_func(void *arg) | |
|
||
gcry_error = gcry_cipher_encrypt(thd->cipher_handle, | ||
thd->to, | ||
thd->to_len, | ||
thd->from, | ||
thd->from_len); | ||
thd->to_len + | ||
XB_CRYPT_HASH_LEN, | ||
thd->to, | ||
thd->from_len + | ||
XB_CRYPT_HASH_LEN); | ||
if (gcry_error) { | ||
msg("encrypt: unable to encrypt buffer - " | ||
"%s : %s\n", gcry_strsource(gcry_error), | ||
gcry_strerror(gcry_error)); | ||
thd->to_len = 0; | ||
} | ||
} else { | ||
memcpy(thd->to, thd->from, thd->from_len); | ||
memcpy(thd->to, thd->from, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as per comments for line 561 |
||
thd->from_len + XB_CRYPT_HASH_LEN); | ||
} | ||
thd->to_len += XB_CRYPT_HASH_LEN; | ||
} | ||
|
||
pthread_mutex_unlock(&thd->data_mutex); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,9 +24,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA | |
#include "xbcrypt.h" | ||
#include <gcrypt.h> | ||
|
||
#if !defined(GCRYPT_VERSION_NUMBER) || (GCRYPT_VERSION_NUMBER < 0x010600) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as per comments above, why we requiring to be less than a certain version ? |
||
GCRY_THREAD_OPTION_PTHREAD_IMPL; | ||
#endif | ||
|
||
#define XBCRYPT_VERSION "1.0" | ||
#define XBCRYPT_VERSION "1.1" | ||
|
||
typedef enum { | ||
RUN_MODE_NONE, | ||
|
@@ -56,8 +58,6 @@ static uint encrypt_algos[] = { GCRY_CIPHER_NONE, | |
static int encrypt_algo = 0; | ||
static int encrypt_mode = GCRY_CIPHER_MODE_CTR; | ||
static uint encrypt_key_len = 0; | ||
static const unsigned char v1_encrypt_iv[] = | ||
"Percona Xtrabackup is Awesome!!!"; | ||
static size_t encrypt_iv_len = 0; | ||
|
||
static struct my_option my_long_options[] = | ||
|
@@ -130,7 +130,9 @@ mode_encrypt(File filein, File fileout); | |
int | ||
main(int argc, char **argv) | ||
{ | ||
#if !defined(GCRYPT_VERSION_NUMBER) || (GCRYPT_VERSION_NUMBER < 0x010600) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as per comments above, why we requiring to be less than a certain version ? |
||
gcry_error_t gcry_error; | ||
#endif | ||
File filein = 0; | ||
File fileout = 0; | ||
|
||
|
@@ -142,6 +144,7 @@ main(int argc, char **argv) | |
|
||
/* Acording to gcrypt docs (and my testing), setting up the threading | ||
callbacks must be done first, so, lets give it a shot */ | ||
#if !defined(GCRYPT_VERSION_NUMBER) || (GCRYPT_VERSION_NUMBER < 0x010600) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as per comments above, why we requiring to be less than a certain version ? |
||
gcry_error = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); | ||
if (gcry_error) { | ||
msg("%s: unable to set libgcrypt thread cbs - " | ||
|
@@ -150,6 +153,7 @@ main(int argc, char **argv) | |
gcry_strerror(gcry_error)); | ||
return 1; | ||
} | ||
#endif | ||
|
||
/* Version check should be the very first call because it | ||
makes sure that important subsystems are intialized. */ | ||
|
@@ -303,6 +307,7 @@ mode_decrypt(File filein, File fileout) | |
xb_rcrypt_result_t result; | ||
gcry_cipher_hd_t cipher_handle; | ||
gcry_error_t gcry_error; | ||
my_bool hash_appended; | ||
|
||
if (encrypt_algo != GCRY_CIPHER_NONE) { | ||
gcry_error = gcry_cipher_open(&cipher_handle, | ||
|
@@ -338,7 +343,7 @@ mode_decrypt(File filein, File fileout) | |
/* Walk the encrypted chunks, decrypting them and writing out */ | ||
while ((result = xb_crypt_read_chunk(xbcrypt_file, &chunkbuf, | ||
&originalsize, &chunksize, | ||
&ivbuf, &ivsize)) | ||
&ivbuf, &ivsize, &hash_appended)) | ||
== XB_CRYPT_READ_CHUNK) { | ||
|
||
if (encrypt_algo != GCRY_CIPHER_NONE) { | ||
|
@@ -352,13 +357,9 @@ mode_decrypt(File filein, File fileout) | |
} | ||
|
||
if (ivsize) { | ||
gcry_error = gcry_cipher_setiv(cipher_handle, | ||
ivbuf, | ||
ivsize); | ||
} else { | ||
gcry_error = gcry_cipher_setiv(cipher_handle, | ||
v1_encrypt_iv, | ||
encrypt_iv_len); | ||
gcry_error = gcry_cipher_setctr(cipher_handle, | ||
ivbuf, | ||
ivsize); | ||
} | ||
if (gcry_error) { | ||
msg("%s:decrypt: unable to set cipher iv - " | ||
|
@@ -369,16 +370,10 @@ mode_decrypt(File filein, File fileout) | |
} | ||
|
||
if (decryptbufsize < originalsize) { | ||
if (decryptbufsize) { | ||
decryptbuf = my_realloc(decryptbuf, | ||
originalsize, | ||
MYF(MY_WME)); | ||
decryptbufsize = originalsize; | ||
} else { | ||
decryptbuf = my_malloc(originalsize, | ||
MYF(MY_WME)); | ||
decryptbufsize = originalsize; | ||
} | ||
decryptbuf = my_realloc(decryptbuf, | ||
originalsize, | ||
MYF(MY_WME | MY_ALLOW_ZERO_PTR)); | ||
decryptbufsize = originalsize; | ||
} | ||
|
||
/* Try to decrypt it */ | ||
|
@@ -400,8 +395,29 @@ mode_decrypt(File filein, File fileout) | |
decryptbuf = chunkbuf; | ||
} | ||
|
||
if (hash_appended) { | ||
uchar hash[XB_CRYPT_HASH_LEN]; | ||
|
||
originalsize -= XB_CRYPT_HASH_LEN; | ||
|
||
/* ensure that XB_CRYPT_HASH_LEN is the correct length | ||
of XB_CRYPT_HASH hashing algorithm output */ | ||
assert(gcry_md_get_algo_dlen(XB_CRYPT_HASH) == | ||
XB_CRYPT_HASH_LEN); | ||
gcry_md_hash_buffer(XB_CRYPT_HASH, hash, decryptbuf, | ||
originalsize); | ||
if (memcmp(hash, (char *) decryptbuf + originalsize, | ||
XB_CRYPT_HASH_LEN) != 0) { | ||
msg("%s:%s invalid plaintext hash. " | ||
"Wrong encrytion key specified?\n", | ||
my_progname, __FUNCTION__); | ||
result = XB_CRYPT_READ_ERROR; | ||
goto err; | ||
} | ||
} | ||
|
||
/* Write it out */ | ||
if (my_write(fileout, decryptbuf, originalsize, | ||
if (my_write(fileout, (const uchar *) decryptbuf, originalsize, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can I presume we're expecting multibyte characters and hence this is why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actually, this buffer is used in two places. We pass it to libgrypt and we pass it to |
||
MYF(MY_WME | MY_NABP))) { | ||
msg("%s:decrypt: unable to write output chunk.\n", | ||
my_progname); | ||
|
@@ -456,7 +472,7 @@ mode_encrypt(File filein, File fileout) | |
{ | ||
size_t bytesread; | ||
size_t chunkbuflen; | ||
void *chunkbuf = NULL; | ||
uchar *chunkbuf = NULL; | ||
void *ivbuf = NULL; | ||
size_t encryptbuflen = 0; | ||
size_t encryptedlen = 0; | ||
|
@@ -504,11 +520,20 @@ mode_encrypt(File filein, File fileout) | |
ivbuf = my_malloc(encrypt_iv_len, MYF(MY_FAE)); | ||
|
||
/* now read in data in chunk size, encrypt and write out */ | ||
chunkbuflen = opt_encrypt_chunk_size; | ||
chunkbuf = my_malloc(chunkbuflen, MYF(MY_FAE)); | ||
while ((bytesread = my_read(filein, chunkbuf, chunkbuflen, | ||
chunkbuflen = opt_encrypt_chunk_size + XB_CRYPT_HASH_LEN; | ||
chunkbuf = (uchar *) my_malloc(chunkbuflen, MYF(MY_FAE)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you mean There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in
|
||
while ((bytesread = my_read(filein, chunkbuf, opt_encrypt_chunk_size, | ||
MYF(MY_WME))) > 0) { | ||
|
||
size_t origbuflen = bytesread + XB_CRYPT_HASH_LEN; | ||
|
||
/* ensure that XB_CRYPT_HASH_LEN is the correct length | ||
of XB_CRYPT_HASH hashing algorithm output */ | ||
assert(XB_CRYPT_HASH_LEN == | ||
gcry_md_get_algo_dlen(XB_CRYPT_HASH)); | ||
gcry_md_hash_buffer(XB_CRYPT_HASH, chunkbuf + bytesread, | ||
chunkbuf, bytesread); | ||
|
||
if (encrypt_algo != GCRY_CIPHER_NONE) { | ||
gcry_error = gcry_cipher_reset(cipher_handle); | ||
|
||
|
@@ -521,7 +546,7 @@ mode_encrypt(File filein, File fileout) | |
} | ||
|
||
xb_crypt_create_iv(ivbuf, encrypt_iv_len); | ||
gcry_error = gcry_cipher_setiv(cipher_handle, | ||
gcry_error = gcry_cipher_setctr(cipher_handle, | ||
ivbuf, | ||
encrypt_iv_len); | ||
|
||
|
@@ -533,26 +558,19 @@ mode_encrypt(File filein, File fileout) | |
continue; | ||
} | ||
|
||
if (encryptbuflen < bytesread) { | ||
if (encryptbuflen) { | ||
encryptbuf = my_realloc(encryptbuf, | ||
bytesread, | ||
MYF(MY_WME)); | ||
encryptbuflen = bytesread; | ||
} else { | ||
encryptbuf = my_malloc(bytesread, | ||
MYF(MY_WME)); | ||
encryptbuflen = bytesread; | ||
} | ||
if (encryptbuflen < origbuflen) { | ||
encryptbuf = my_realloc(encryptbuf, origbuflen, | ||
MYF(MY_WME | MY_ALLOW_ZERO_PTR)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
encryptbuflen = origbuflen; | ||
} | ||
|
||
gcry_error = gcry_cipher_encrypt(cipher_handle, | ||
encryptbuf, | ||
encryptbuflen, | ||
chunkbuf, | ||
bytesread); | ||
origbuflen); | ||
|
||
encryptedlen = bytesread; | ||
encryptedlen = origbuflen; | ||
|
||
if (gcry_error) { | ||
msg("%s:encrypt: unable to encrypt chunk - " | ||
|
@@ -563,11 +581,12 @@ mode_encrypt(File filein, File fileout) | |
goto err; | ||
} | ||
} else { | ||
encryptedlen = bytesread; | ||
encryptedlen = origbuflen; | ||
encryptbuf = chunkbuf; | ||
} | ||
|
||
if (xb_crypt_write_chunk(xbcrypt_file, encryptbuf, bytesread, | ||
if (xb_crypt_write_chunk(xbcrypt_file, encryptbuf, | ||
bytesread + XB_CRYPT_HASH_LEN, | ||
encryptedlen, ivbuf, encrypt_iv_len)) { | ||
msg("%s:encrypt: abcrypt_write_chunk() failed.\n", | ||
my_progname); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,9 +25,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA | |
#include "common.h" | ||
|
||
#define XB_CRYPT_CHUNK_MAGIC1 "XBCRYP01" | ||
#define XB_CRYPT_CHUNK_MAGIC2 "XBCRYP02" /* must be same size as ^^ */ | ||
#define XB_CRYPT_CHUNK_MAGIC2 "XBCRYP02" | ||
#define XB_CRYPT_CHUNK_MAGIC3 "XBCRYP03" /* must be same size as ^^ */ | ||
#define XB_CRYPT_CHUNK_MAGIC_CURRENT XB_CRYPT_CHUNK_MAGIC3 | ||
#define XB_CRYPT_CHUNK_MAGIC_SIZE (sizeof(XB_CRYPT_CHUNK_MAGIC1)-1) | ||
|
||
#define XB_CRYPT_HASH GCRY_MD_SHA256 | ||
#define XB_CRYPT_HASH_LEN 32 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
/****************************************************************************** | ||
Write interface */ | ||
typedef struct xb_wcrypt_struct xb_wcrypt_t; | ||
|
@@ -66,7 +71,7 @@ typedef enum { | |
|
||
xb_rcrypt_result_t xb_crypt_read_chunk(xb_rcrypt_t *crypt, void **buf, | ||
size_t *olen, size_t *elen, void **iv, | ||
size_t *ivlen); | ||
size_t *ivlen, my_bool *hash_appended); | ||
|
||
int xb_crypt_read_close(xb_rcrypt_t *crypt); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we not rely on the GCRYPT_VERSION_NUMBER to be always available and shouldn't we error out if this is not available ? (I presume this is likely to be backwards compatibility)
Also why do we require the version to be less than
0x010600
(67072)?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. I added this check for compatibility. Older libgrypt versions required to use
and
to work correctly in multithreaded environment. This code no longer needed and deprecated since libgrypt 1.6. Compiler gives warnings for this code. Unfortunately I cannot just remove it, because we support CentOS 5 and 6. Version number is declared in the
gcrypt.h
as follows:0x010703 is 1.7.3. So, hexadecimal number in fact should be read as decimal. Ask libgrcypt maintainers why :)