summaryrefslogtreecommitdiff
path: root/extra/mariabackup/ds_decrypt.c
diff options
context:
space:
mode:
Diffstat (limited to 'extra/mariabackup/ds_decrypt.c')
-rw-r--r--extra/mariabackup/ds_decrypt.c665
1 files changed, 665 insertions, 0 deletions
diff --git a/extra/mariabackup/ds_decrypt.c b/extra/mariabackup/ds_decrypt.c
new file mode 100644
index 00000000000..e897ca101e5
--- /dev/null
+++ b/extra/mariabackup/ds_decrypt.c
@@ -0,0 +1,665 @@
+/******************************************************
+Copyright (c) 2017 Percona LLC and/or its affiliates.
+
+Encryption datasink implementation for XtraBackup.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; version 2 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+*******************************************************/
+
+
+#include <my_base.h>
+#include "common.h"
+#include "datasink.h"
+#include "xbcrypt.h"
+#include "xbcrypt_common.h"
+#include "crc_glue.h"
+
+typedef struct {
+ pthread_t id;
+ uint num;
+ pthread_mutex_t ctrl_mutex;
+ pthread_cond_t ctrl_cond;
+ pthread_mutex_t data_mutex;
+ pthread_cond_t data_cond;
+ my_bool started;
+ my_bool data_avail;
+ my_bool cancelled;
+ my_bool failed;
+ const uchar *from;
+ size_t from_len;
+ uchar *to;
+ size_t to_len;
+ size_t to_size;
+ const uchar *iv;
+ size_t iv_len;
+ unsigned long long offset;
+ my_bool hash_appended;
+ gcry_cipher_hd_t cipher_handle;
+ xb_rcrypt_result_t parse_result;
+} crypt_thread_ctxt_t;
+
+typedef struct {
+ crypt_thread_ctxt_t *threads;
+ uint nthreads;
+ int encrypt_algo;
+ size_t chunk_size;
+ char *encrypt_key;
+ char *encrypt_key_file;
+} ds_decrypt_ctxt_t;
+
+typedef struct {
+ ds_decrypt_ctxt_t *crypt_ctxt;
+ size_t bytes_processed;
+ ds_file_t *dest_file;
+ uchar *buf;
+ size_t buf_len;
+ size_t buf_size;
+} ds_decrypt_file_t;
+
+int ds_decrypt_encrypt_threads = 1;
+
+static ds_ctxt_t *decrypt_init(const char *root);
+static ds_file_t *decrypt_open(ds_ctxt_t *ctxt, const char *path,
+ MY_STAT *mystat);
+static int decrypt_write(ds_file_t *file, const void *buf, size_t len);
+static int decrypt_close(ds_file_t *file);
+static void decrypt_deinit(ds_ctxt_t *ctxt);
+
+datasink_t datasink_decrypt = {
+ &decrypt_init,
+ &decrypt_open,
+ &decrypt_write,
+ &decrypt_close,
+ &decrypt_deinit
+};
+
+static crypt_thread_ctxt_t *create_worker_threads(uint n);
+static void destroy_worker_threads(crypt_thread_ctxt_t *threads, uint n);
+static void *decrypt_worker_thread_func(void *arg);
+
+static
+ds_ctxt_t *
+decrypt_init(const char *root)
+{
+ ds_ctxt_t *ctxt;
+ ds_decrypt_ctxt_t *decrypt_ctxt;
+ crypt_thread_ctxt_t *threads;
+
+ if (xb_crypt_init(NULL)) {
+ return NULL;
+ }
+
+ /* Create and initialize the worker threads */
+ threads = create_worker_threads(ds_decrypt_encrypt_threads);
+ if (threads == NULL) {
+ msg("decrypt: failed to create worker threads.\n");
+ return NULL;
+ }
+
+ ctxt = (ds_ctxt_t *) my_malloc(sizeof(ds_ctxt_t) +
+ sizeof(ds_decrypt_ctxt_t),
+ MYF(MY_FAE));
+
+ decrypt_ctxt = (ds_decrypt_ctxt_t *) (ctxt + 1);
+ decrypt_ctxt->threads = threads;
+ decrypt_ctxt->nthreads = ds_decrypt_encrypt_threads;
+
+ ctxt->ptr = decrypt_ctxt;
+ ctxt->root = my_strdup(root, MYF(MY_FAE));
+
+ return ctxt;
+}
+
+static
+ds_file_t *
+decrypt_open(ds_ctxt_t *ctxt, const char *path, MY_STAT *mystat)
+{
+ ds_ctxt_t *dest_ctxt;
+
+ ds_decrypt_ctxt_t *crypt_ctxt;
+ ds_decrypt_file_t *crypt_file;
+
+ char new_name[FN_REFLEN];
+ ds_file_t *file;
+
+ xb_ad(ctxt->pipe_ctxt != NULL);
+ dest_ctxt = ctxt->pipe_ctxt;
+
+ crypt_ctxt = (ds_decrypt_ctxt_t *) ctxt->ptr;
+
+
+ file = (ds_file_t *) my_malloc(sizeof(ds_file_t) +
+ sizeof(ds_decrypt_file_t),
+ MYF(MY_FAE|MY_ZEROFILL));
+
+ crypt_file = (ds_decrypt_file_t *) (file + 1);
+
+ /* Remove the .xbcrypt extension from the filename */
+ strncpy(new_name, path, FN_REFLEN);
+ new_name[strlen(new_name) - 8] = 0;
+ crypt_file->dest_file = ds_open(dest_ctxt, new_name, mystat);
+ if (crypt_file->dest_file == NULL) {
+ msg("decrypt: ds_open(\"%s\") failed.\n", new_name);
+ goto err;
+ }
+
+ crypt_file->crypt_ctxt = crypt_ctxt;
+ crypt_file->buf = NULL;
+ crypt_file->buf_size = 0;
+ crypt_file->buf_len = 0;
+
+ file->ptr = crypt_file;
+ file->path = crypt_file->dest_file->path;
+
+ return file;
+
+err:
+ if (crypt_file->dest_file) {
+ ds_close(crypt_file->dest_file);
+ }
+ my_free(file);
+ return NULL;
+}
+
+#define CHECK_BUF_SIZE(ptr, size, buf, len) \
+ if (ptr + size - buf > (ssize_t) len) { \
+ result = XB_CRYPT_READ_INCOMPLETE; \
+ goto exit; \
+ }
+
+static
+xb_rcrypt_result_t
+parse_xbcrypt_chunk(crypt_thread_ctxt_t *thd, const uchar *buf, size_t len,
+ size_t *bytes_processed)
+{
+ const uchar *ptr;
+ uint version;
+ ulong checksum, checksum_exp;
+ ulonglong tmp;
+ xb_rcrypt_result_t result = XB_CRYPT_READ_CHUNK;
+
+ *bytes_processed = 0;
+ ptr = buf;
+
+ CHECK_BUF_SIZE(ptr, XB_CRYPT_CHUNK_MAGIC_SIZE, buf, len);
+ if (memcmp(ptr, XB_CRYPT_CHUNK_MAGIC3,
+ XB_CRYPT_CHUNK_MAGIC_SIZE) == 0) {
+ version = 3;
+ } else if (memcmp(ptr, XB_CRYPT_CHUNK_MAGIC2,
+ XB_CRYPT_CHUNK_MAGIC_SIZE) == 0) {
+ version = 2;
+ } else if (memcmp(ptr, XB_CRYPT_CHUNK_MAGIC1,
+ XB_CRYPT_CHUNK_MAGIC_SIZE) == 0) {
+ version = 1;
+ } else {
+ msg("%s:%s: wrong chunk magic at offset 0x%llx.\n",
+ my_progname, __FUNCTION__, thd->offset);
+ result = XB_CRYPT_READ_ERROR;
+ goto exit;
+ }
+
+ ptr += XB_CRYPT_CHUNK_MAGIC_SIZE;
+ thd->offset += XB_CRYPT_CHUNK_MAGIC_SIZE;
+
+ CHECK_BUF_SIZE(ptr, 8, buf, len);
+ tmp = uint8korr(ptr); /* reserved */
+ ptr += 8;
+ thd->offset += 8;
+
+ CHECK_BUF_SIZE(ptr, 8, buf, len);
+ tmp = uint8korr(ptr); /* original size */
+ ptr += 8;
+ if (tmp > INT_MAX) {
+ msg("%s:%s: invalid original size at offset 0x%llx.\n",
+ my_progname, __FUNCTION__, thd->offset);
+ result = XB_CRYPT_READ_ERROR;
+ goto exit;
+ }
+ thd->offset += 8;
+ thd->to_len = (size_t)tmp;
+
+ if (thd->to_size < thd->to_len + XB_CRYPT_HASH_LEN) {
+ thd->to = (uchar *) my_realloc(
+ thd->to,
+ thd->to_len + XB_CRYPT_HASH_LEN,
+ MYF(MY_FAE | MY_ALLOW_ZERO_PTR));
+ thd->to_size = thd->to_len;
+ }
+
+ CHECK_BUF_SIZE(ptr, 8, buf, len);
+ tmp = uint8korr(ptr); /* encrypted size */
+ ptr += 8;
+ if (tmp > INT_MAX) {
+ msg("%s:%s: invalid encrypted size at offset 0x%llx.\n",
+ my_progname, __FUNCTION__, thd->offset);
+ result = XB_CRYPT_READ_ERROR;
+ goto exit;
+ }
+ thd->offset += 8;
+ thd->from_len = (size_t)tmp;
+
+ xb_a(thd->from_len <= thd->to_len + XB_CRYPT_HASH_LEN);
+
+ CHECK_BUF_SIZE(ptr, 4, buf, len);
+ checksum_exp = uint4korr(ptr); /* checksum */
+ ptr += 4;
+ thd->offset += 4;
+
+ /* iv size */
+ if (version == 1) {
+ thd->iv_len = 0;
+ thd->iv = NULL;
+ } else {
+ CHECK_BUF_SIZE(ptr, 8, buf, len);
+
+ tmp = uint8korr(ptr);
+ if (tmp > INT_MAX) {
+ msg("%s:%s: invalid iv size at offset 0x%llx.\n",
+ my_progname, __FUNCTION__, thd->offset);
+ result = XB_CRYPT_READ_ERROR;
+ goto exit;
+ }
+ ptr += 8;
+ thd->offset += 8;
+ thd->iv_len = (size_t)tmp;
+ }
+
+ if (thd->iv_len > 0) {
+ CHECK_BUF_SIZE(ptr, thd->iv_len, buf, len);
+ thd->iv = ptr;
+ ptr += thd->iv_len;
+ }
+
+ /* for version euqals 2 we need to read in the iv data but do not init
+ CTR with it */
+ if (version == 2) {
+ thd->iv_len = 0;
+ thd->iv = 0;
+ }
+
+ if (thd->from_len > 0) {
+ CHECK_BUF_SIZE(ptr, thd->from_len, buf, len);
+ thd->from = ptr;
+ ptr += thd->from_len;
+ }
+
+ xb_ad(thd->from_len <= thd->to_len);
+
+ checksum = crc32_iso3309(0, thd->from, thd->from_len);
+ if (checksum != checksum_exp) {
+ msg("%s:%s invalid checksum at offset 0x%llx, "
+ "expected 0x%lx, actual 0x%lx.\n", my_progname,
+ __FUNCTION__, thd->offset, checksum_exp, checksum);
+ result = XB_CRYPT_READ_ERROR;
+ goto exit;
+ }
+
+ thd->offset += thd->from_len;
+
+ thd->hash_appended = version > 2;
+
+exit:
+
+ *bytes_processed = (size_t) (ptr - buf);
+
+ return result;
+}
+
+static
+int
+decrypt_write(ds_file_t *file, const void *buf, size_t len)
+{
+ ds_decrypt_file_t *crypt_file;
+ ds_decrypt_ctxt_t *crypt_ctxt;
+ crypt_thread_ctxt_t *threads;
+ crypt_thread_ctxt_t *thd;
+ uint nthreads;
+ uint i;
+ size_t bytes_processed;
+ xb_rcrypt_result_t parse_result = XB_CRYPT_READ_CHUNK;
+ my_bool err = FALSE;
+
+ crypt_file = (ds_decrypt_file_t *) file->ptr;
+ crypt_ctxt = crypt_file->crypt_ctxt;
+
+ threads = crypt_ctxt->threads;
+ nthreads = crypt_ctxt->nthreads;
+
+ if (crypt_file->buf_len > 0) {
+ thd = threads;
+
+ pthread_mutex_lock(&thd->ctrl_mutex);
+
+ do {
+ if (parse_result == XB_CRYPT_READ_INCOMPLETE) {
+ crypt_file->buf_size = crypt_file->buf_size * 2;
+ crypt_file->buf = (uchar *) my_realloc(
+ crypt_file->buf,
+ crypt_file->buf_size,
+ MYF(MY_FAE|MY_ALLOW_ZERO_PTR));
+ }
+
+ memcpy(crypt_file->buf + crypt_file->buf_len,
+ buf, MY_MIN(crypt_file->buf_size -
+ crypt_file->buf_len, len));
+
+ parse_result = parse_xbcrypt_chunk(
+ thd, crypt_file->buf,
+ crypt_file->buf_size, &bytes_processed);
+
+ if (parse_result == XB_CRYPT_READ_ERROR) {
+ pthread_mutex_unlock(&thd->ctrl_mutex);
+ return 1;
+ }
+
+ } while (parse_result == XB_CRYPT_READ_INCOMPLETE &&
+ crypt_file->buf_size < len);
+
+ if (parse_result != XB_CRYPT_READ_CHUNK) {
+ msg("decrypt: incomplete data.\n");
+ pthread_mutex_unlock(&thd->ctrl_mutex);
+ return 1;
+ }
+
+ pthread_mutex_lock(&thd->data_mutex);
+ thd->data_avail = TRUE;
+ pthread_cond_signal(&thd->data_cond);
+ pthread_mutex_unlock(&thd->data_mutex);
+
+ len -= bytes_processed - crypt_file->buf_len;
+ buf += bytes_processed - crypt_file->buf_len;
+
+ /* reap */
+
+ pthread_mutex_lock(&thd->data_mutex);
+ while (thd->data_avail == TRUE) {
+ pthread_cond_wait(&thd->data_cond,
+ &thd->data_mutex);
+ }
+
+ if (thd->failed) {
+ msg("decrypt: failed to decrypt chunk.\n");
+ err = TRUE;
+ }
+
+ xb_a(thd->to_len > 0);
+
+ if (!err &&
+ ds_write(crypt_file->dest_file, thd->to, thd->to_len)) {
+ msg("decrypt: write to destination failed.\n");
+ err = TRUE;
+ }
+
+ crypt_file->bytes_processed += thd->from_len;
+
+ pthread_mutex_unlock(&thd->data_mutex);
+ pthread_mutex_unlock(&thd->ctrl_mutex);
+
+ crypt_file->buf_len = 0;
+
+ if (err) {
+ return 1;
+ }
+ }
+
+ while (parse_result == XB_CRYPT_READ_CHUNK && len > 0) {
+ uint max_thread;
+
+ for (i = 0; i < nthreads; i++) {
+ thd = threads + i;
+
+ pthread_mutex_lock(&thd->ctrl_mutex);
+
+ parse_result = parse_xbcrypt_chunk(
+ thd, buf, len, &bytes_processed);
+
+ if (parse_result == XB_CRYPT_READ_ERROR) {
+ pthread_mutex_unlock(&thd->ctrl_mutex);
+ err = TRUE;
+ break;
+ }
+
+ thd->parse_result = parse_result;
+
+ if (parse_result != XB_CRYPT_READ_CHUNK) {
+ pthread_mutex_unlock(&thd->ctrl_mutex);
+ break;
+ }
+
+ pthread_mutex_lock(&thd->data_mutex);
+ thd->data_avail = TRUE;
+ pthread_cond_signal(&thd->data_cond);
+ pthread_mutex_unlock(&thd->data_mutex);
+
+ len -= bytes_processed;
+ buf += bytes_processed;
+ }
+
+ max_thread = (i < nthreads) ? i : nthreads - 1;
+
+ /* Reap and write decrypted data */
+ for (i = 0; i <= max_thread; i++) {
+ thd = threads + i;
+
+ if (thd->parse_result != XB_CRYPT_READ_CHUNK) {
+ break;
+ }
+
+ pthread_mutex_lock(&thd->data_mutex);
+ while (thd->data_avail == TRUE) {
+ pthread_cond_wait(&thd->data_cond,
+ &thd->data_mutex);
+ }
+
+ if (thd->failed) {
+ msg("decrypt: failed to decrypt chunk.\n");
+ err = TRUE;
+ }
+
+ xb_a(thd->to_len > 0);
+
+ if (!err && ds_write(crypt_file->dest_file, thd->to,
+ thd->to_len)) {
+ msg("decrypt: write to destination failed.\n");
+ err = TRUE;
+ }
+
+ crypt_file->bytes_processed += thd->from_len;
+
+ pthread_mutex_unlock(&thd->data_mutex);
+ pthread_mutex_unlock(&thd->ctrl_mutex);
+ }
+
+ if (err) {
+ return 1;
+ }
+ }
+
+ if (parse_result == XB_CRYPT_READ_INCOMPLETE && len > 0) {
+ crypt_file->buf_len = len;
+ if (crypt_file->buf_size < len) {
+ crypt_file->buf = (uchar *) my_realloc(
+ crypt_file->buf,
+ crypt_file->buf_len,
+ MYF(MY_FAE | MY_ALLOW_ZERO_PTR));
+ crypt_file->buf_size = len;
+ }
+ memcpy(crypt_file->buf, buf, len);
+ }
+
+ return 0;
+}
+
+static
+int
+decrypt_close(ds_file_t *file)
+{
+ ds_decrypt_file_t *crypt_file;
+ ds_file_t *dest_file;
+ int rc = 0;
+
+ crypt_file = (ds_decrypt_file_t *) file->ptr;
+ dest_file = crypt_file->dest_file;
+
+ if (ds_close(dest_file)) {
+ rc = 1;
+ }
+
+ my_free(crypt_file->buf);
+ my_free(file);
+
+ return rc;
+}
+
+static
+void
+decrypt_deinit(ds_ctxt_t *ctxt)
+{
+ ds_decrypt_ctxt_t *crypt_ctxt;
+
+ xb_ad(ctxt->pipe_ctxt != NULL);
+
+ crypt_ctxt = (ds_decrypt_ctxt_t *) ctxt->ptr;
+
+ destroy_worker_threads(crypt_ctxt->threads, crypt_ctxt->nthreads);
+
+ my_free(ctxt->root);
+ my_free(ctxt);
+}
+
+static
+crypt_thread_ctxt_t *
+create_worker_threads(uint n)
+{
+ crypt_thread_ctxt_t *threads;
+ uint i;
+
+ threads = (crypt_thread_ctxt_t *)
+ my_malloc(sizeof(crypt_thread_ctxt_t) * n,
+ MYF(MY_FAE | MY_ZEROFILL));
+
+ for (i = 0; i < n; i++) {
+ crypt_thread_ctxt_t *thd = threads + i;
+
+ thd->num = i + 1;
+
+ /* Initialize the control mutex and condition var */
+ if (pthread_mutex_init(&thd->ctrl_mutex, NULL) ||
+ pthread_cond_init(&thd->ctrl_cond, NULL)) {
+ goto err;
+ }
+
+ /* Initialize and data mutex and condition var */
+ if (pthread_mutex_init(&thd->data_mutex, NULL) ||
+ pthread_cond_init(&thd->data_cond, NULL)) {
+ goto err;
+ }
+
+ xb_crypt_cipher_open(&thd->cipher_handle);
+
+ pthread_mutex_lock(&thd->ctrl_mutex);
+
+ if (pthread_create(&thd->id, NULL, decrypt_worker_thread_func,
+ thd)) {
+ msg("decrypt: pthread_create() failed: "
+ "errno = %d\n", errno);
+ goto err;
+ }
+ }
+
+ /* Wait for the threads to start */
+ for (i = 0; i < n; i++) {
+ crypt_thread_ctxt_t *thd = threads + i;
+
+ while (thd->started == FALSE)
+ pthread_cond_wait(&thd->ctrl_cond, &thd->ctrl_mutex);
+ pthread_mutex_unlock(&thd->ctrl_mutex);
+ }
+
+ return threads;
+
+err:
+ return NULL;
+}
+
+static
+void
+destroy_worker_threads(crypt_thread_ctxt_t *threads, uint n)
+{
+ uint i;
+
+ for (i = 0; i < n; i++) {
+ crypt_thread_ctxt_t *thd = threads + i;
+
+ pthread_mutex_lock(&thd->data_mutex);
+ threads[i].cancelled = TRUE;
+ pthread_cond_signal(&thd->data_cond);
+ pthread_mutex_unlock(&thd->data_mutex);
+
+ pthread_join(thd->id, NULL);
+
+ pthread_cond_destroy(&thd->data_cond);
+ pthread_mutex_destroy(&thd->data_mutex);
+ pthread_cond_destroy(&thd->ctrl_cond);
+ pthread_mutex_destroy(&thd->ctrl_mutex);
+
+ xb_crypt_cipher_close(thd->cipher_handle);
+
+ my_free(thd->to);
+ }
+
+ my_free(threads);
+}
+
+static
+void *
+decrypt_worker_thread_func(void *arg)
+{
+ crypt_thread_ctxt_t *thd = (crypt_thread_ctxt_t *) arg;
+
+ pthread_mutex_lock(&thd->ctrl_mutex);
+
+ pthread_mutex_lock(&thd->data_mutex);
+
+ thd->started = TRUE;
+ pthread_cond_signal(&thd->ctrl_cond);
+
+ pthread_mutex_unlock(&thd->ctrl_mutex);
+
+ while (1) {
+ thd->data_avail = FALSE;
+ pthread_cond_signal(&thd->data_cond);
+
+ while (!thd->data_avail && !thd->cancelled) {
+ pthread_cond_wait(&thd->data_cond, &thd->data_mutex);
+ }
+
+ if (thd->cancelled)
+ break;
+
+ if (xb_crypt_decrypt(thd->cipher_handle, thd->from,
+ thd->from_len, thd->to, &thd->to_len,
+ thd->iv, thd->iv_len,
+ thd->hash_appended)) {
+ thd->failed = TRUE;
+ continue;
+ }
+
+ }
+
+ pthread_mutex_unlock(&thd->data_mutex);
+
+ return NULL;
+}