mirror of
https://github.com/MariaDB/server.git
synced 2025-01-17 12:32:27 +01:00
3d241eb948
This patch fixes the following issues in Aria error reporting in case of read errors & crashed tables: - Added the table name to the most error messages, including in case of read errors or when encrypting/decrypting a table. The format for error messages was changed sligtly to accomodate logging of errors from lower level routines. - If we got an read error from storage (hard disk, ssd, S3 etc) we only reported 'table is crashed'. Now the error number from the storage is reported. - Added checking of read failure from records_in_range() - Calls to ma_set_fatal_error() did not inform the SQL level of errors (to not spam the user with multiple error messages). Now the first error message and any fatal error messages are reported to the user.
525 lines
16 KiB
C
525 lines
16 KiB
C
/*
|
|
Copyright (c) 2013 Google Inc.
|
|
Copyright (c) 2014, 2015 MariaDB Corporation
|
|
|
|
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 St, Fifth Floor, Boston, MA 02110-1335 USA */
|
|
|
|
#include "maria_def.h"
|
|
#include "ma_blockrec.h"
|
|
#include <my_crypt.h>
|
|
|
|
#define CRYPT_SCHEME_1 1
|
|
#define CRYPT_SCHEME_1_ID_LEN 4 /* 4 bytes for counter-block */
|
|
#define CRYPT_SCHEME_1_IV_LEN 16
|
|
#define CRYPT_SCHEME_1_KEY_VERSION_SIZE 4
|
|
|
|
#ifdef HAVE_PSI_INTERFACE
|
|
PSI_mutex_key key_CRYPT_DATA_lock;
|
|
#endif
|
|
|
|
struct st_crypt_key
|
|
{
|
|
uint key_version;
|
|
uchar key[CRYPT_SCHEME_1_IV_LEN];
|
|
};
|
|
|
|
struct st_maria_crypt_data
|
|
{
|
|
struct st_encryption_scheme scheme;
|
|
uint space;
|
|
mysql_mutex_t lock; /* protecting keys */
|
|
};
|
|
|
|
/**
|
|
determine what key id to use for Aria encryption
|
|
|
|
Same logic as for tempfiles: if key id 2 exists - use it,
|
|
otherwise use key id 1.
|
|
|
|
Key id 1 is system, it always exists. Key id 2 is optional,
|
|
it allows to specify fast low-grade encryption for temporary data.
|
|
*/
|
|
static uint get_encryption_key_id(MARIA_SHARE *share)
|
|
{
|
|
if (share->options & HA_OPTION_TMP_TABLE &&
|
|
encryption_key_id_exists(ENCRYPTION_KEY_TEMPORARY_DATA))
|
|
return ENCRYPTION_KEY_TEMPORARY_DATA;
|
|
else
|
|
return ENCRYPTION_KEY_SYSTEM_DATA;
|
|
}
|
|
|
|
uint
|
|
ma_crypt_get_data_page_header_space()
|
|
{
|
|
return CRYPT_SCHEME_1_KEY_VERSION_SIZE;
|
|
}
|
|
|
|
uint
|
|
ma_crypt_get_index_page_header_space(MARIA_SHARE *share)
|
|
{
|
|
if (share->base.born_transactional)
|
|
{
|
|
return CRYPT_SCHEME_1_KEY_VERSION_SIZE;
|
|
}
|
|
else
|
|
{
|
|
/* if the index is not transactional, we add 7 bytes LSN anyway
|
|
to be used for counter block
|
|
*/
|
|
return LSN_STORE_SIZE + CRYPT_SCHEME_1_KEY_VERSION_SIZE;
|
|
}
|
|
}
|
|
|
|
uint
|
|
ma_crypt_get_file_length()
|
|
{
|
|
return 2 + CRYPT_SCHEME_1_IV_LEN + CRYPT_SCHEME_1_ID_LEN;
|
|
}
|
|
|
|
static void crypt_data_scheme_locker(struct st_encryption_scheme *scheme,
|
|
int unlock)
|
|
{
|
|
MARIA_CRYPT_DATA *crypt_data = (MARIA_CRYPT_DATA*)scheme;
|
|
if (unlock)
|
|
mysql_mutex_unlock(&crypt_data->lock);
|
|
else
|
|
mysql_mutex_lock(&crypt_data->lock);
|
|
}
|
|
|
|
int
|
|
ma_crypt_create(MARIA_SHARE* share)
|
|
{
|
|
MARIA_CRYPT_DATA *crypt_data=
|
|
(MARIA_CRYPT_DATA*)my_malloc(PSI_INSTRUMENT_ME, sizeof(MARIA_CRYPT_DATA), MYF(MY_ZEROFILL));
|
|
crypt_data->scheme.type= CRYPT_SCHEME_1;
|
|
crypt_data->scheme.locker= crypt_data_scheme_locker;
|
|
mysql_mutex_init(key_CRYPT_DATA_lock, &crypt_data->lock, MY_MUTEX_INIT_FAST);
|
|
crypt_data->scheme.key_id= get_encryption_key_id(share);
|
|
my_random_bytes(crypt_data->scheme.iv, sizeof(crypt_data->scheme.iv));
|
|
my_random_bytes((uchar*)&crypt_data->space, sizeof(crypt_data->space));
|
|
share->crypt_data= crypt_data;
|
|
share->crypt_page_header_space= CRYPT_SCHEME_1_KEY_VERSION_SIZE;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
ma_crypt_free(MARIA_SHARE* share)
|
|
{
|
|
if (share->crypt_data != NULL)
|
|
{
|
|
mysql_mutex_destroy(&share->crypt_data->lock);
|
|
my_free(share->crypt_data);
|
|
share->crypt_data= NULL;
|
|
}
|
|
}
|
|
|
|
int
|
|
ma_crypt_write(MARIA_SHARE* share, File file)
|
|
{
|
|
MARIA_CRYPT_DATA *crypt_data= share->crypt_data;
|
|
uchar buff[2 + 4 + sizeof(crypt_data->scheme.iv)];
|
|
if (crypt_data == 0)
|
|
return 0;
|
|
|
|
buff[0]= crypt_data->scheme.type;
|
|
buff[1]= sizeof(buff) - 2;
|
|
|
|
int4store(buff + 2, crypt_data->space);
|
|
memcpy(buff + 6, crypt_data->scheme.iv, sizeof(crypt_data->scheme.iv));
|
|
|
|
if (mysql_file_write(file, buff, sizeof(buff), MYF(MY_NABP)))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
uchar*
|
|
ma_crypt_read(MARIA_SHARE* share, uchar *buff)
|
|
{
|
|
uchar type= buff[0];
|
|
uchar iv_length= buff[1];
|
|
|
|
/* currently only supported type */
|
|
if (type != CRYPT_SCHEME_1 ||
|
|
iv_length != sizeof(((MARIA_CRYPT_DATA*)1)->scheme.iv) + 4)
|
|
{
|
|
my_printf_error(HA_ERR_UNSUPPORTED,
|
|
"Unsupported crypt scheme! type: %d iv_length: %d\n",
|
|
MYF(ME_FATAL|ME_ERROR_LOG),
|
|
type, iv_length);
|
|
return 0;
|
|
}
|
|
|
|
if (share->crypt_data == NULL)
|
|
{
|
|
/* opening a table */
|
|
MARIA_CRYPT_DATA *crypt_data=
|
|
(MARIA_CRYPT_DATA*)my_malloc(PSI_INSTRUMENT_ME, sizeof(MARIA_CRYPT_DATA), MYF(MY_ZEROFILL));
|
|
|
|
crypt_data->scheme.type= type;
|
|
mysql_mutex_init(key_CRYPT_DATA_lock, &crypt_data->lock,
|
|
MY_MUTEX_INIT_FAST);
|
|
crypt_data->scheme.locker= crypt_data_scheme_locker;
|
|
crypt_data->scheme.key_id= get_encryption_key_id(share);
|
|
crypt_data->space= uint4korr(buff + 2);
|
|
memcpy(crypt_data->scheme.iv, buff + 6, sizeof(crypt_data->scheme.iv));
|
|
share->crypt_data= crypt_data;
|
|
}
|
|
|
|
share->crypt_page_header_space= CRYPT_SCHEME_1_KEY_VERSION_SIZE;
|
|
return buff + 2 + iv_length;
|
|
}
|
|
|
|
static int ma_encrypt(MARIA_SHARE *, MARIA_CRYPT_DATA *, const uchar *,
|
|
uchar *, uint, uint, LSN, uint *);
|
|
static int ma_decrypt(MARIA_SHARE *, MARIA_CRYPT_DATA *, const uchar *,
|
|
uchar *, uint, uint, LSN, uint);
|
|
|
|
static my_bool ma_crypt_pre_read_hook(PAGECACHE_IO_HOOK_ARGS *args)
|
|
{
|
|
MARIA_SHARE *share= (MARIA_SHARE*) args->data;
|
|
uchar *crypt_buf= my_malloc(PSI_INSTRUMENT_ME, share->block_size, MYF(0));
|
|
if (crypt_buf == NULL)
|
|
{
|
|
args->crypt_buf= NULL; /* for post-hook */
|
|
return 1;
|
|
}
|
|
|
|
/* swap pointers to read into crypt_buf */
|
|
args->crypt_buf= args->page;
|
|
args->page= crypt_buf;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static my_bool ma_crypt_data_post_read_hook(int res,
|
|
PAGECACHE_IO_HOOK_ARGS *args)
|
|
{
|
|
MARIA_SHARE *share= (MARIA_SHARE*) args->data;
|
|
const uint size= share->block_size;
|
|
const uchar page_type= args->page[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK;
|
|
const uint32 key_version_offset= (page_type <= TAIL_PAGE) ?
|
|
KEY_VERSION_OFFSET : FULL_PAGE_KEY_VERSION_OFFSET;
|
|
|
|
if (res == 0)
|
|
{
|
|
const uchar *src= args->page;
|
|
uchar* dst= args->crypt_buf;
|
|
uint pageno= (uint)args->pageno;
|
|
LSN lsn= lsn_korr(src);
|
|
const uint head= (page_type <= TAIL_PAGE) ?
|
|
PAGE_HEADER_SIZE(share) : FULL_PAGE_HEADER_SIZE(share);
|
|
const uint tail= CRC_SIZE;
|
|
const uint32 key_version= uint4korr(src + key_version_offset);
|
|
|
|
/* 1 - copy head */
|
|
memcpy(dst, src, head);
|
|
/* 2 - decrypt page */
|
|
res= ma_decrypt(share, share->crypt_data,
|
|
src + head, dst + head, size - (head + tail), pageno, lsn,
|
|
key_version);
|
|
/* 3 - copy tail */
|
|
memcpy(dst + size - tail, src + size - tail, tail);
|
|
/* 4 clear key version to get correct crc */
|
|
int4store(dst + key_version_offset, 0);
|
|
}
|
|
|
|
if (args->crypt_buf != NULL)
|
|
{
|
|
uchar *tmp= args->page;
|
|
args->page= args->crypt_buf;
|
|
args->crypt_buf= NULL;
|
|
my_free(tmp);
|
|
}
|
|
|
|
return maria_page_crc_check_data(res, args);
|
|
}
|
|
|
|
static void store_rand_lsn(uchar * page)
|
|
{
|
|
LSN lsn= 0;
|
|
lsn+= rand();
|
|
lsn<<= 32;
|
|
lsn+= rand();
|
|
lsn_store(page, lsn);
|
|
}
|
|
|
|
static my_bool ma_crypt_data_pre_write_hook(PAGECACHE_IO_HOOK_ARGS *args)
|
|
{
|
|
MARIA_SHARE *share= (MARIA_SHARE*) args->data;
|
|
const uint size= share->block_size;
|
|
uint key_version;
|
|
uchar *crypt_buf= my_malloc(PSI_INSTRUMENT_ME, share->block_size, MYF(0));
|
|
|
|
if (crypt_buf == NULL)
|
|
{
|
|
args->crypt_buf= NULL; /* for post-hook */
|
|
return 1;
|
|
}
|
|
|
|
if (!share->base.born_transactional)
|
|
{
|
|
/* store a random number instead of LSN (for counter block) */
|
|
store_rand_lsn(args->page);
|
|
}
|
|
|
|
maria_page_crc_set_normal(args);
|
|
|
|
{
|
|
const uchar *src= args->page;
|
|
uchar* dst= crypt_buf;
|
|
uint pageno= (uint)args->pageno;
|
|
LSN lsn= lsn_korr(src);
|
|
const uchar page_type= src[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK;
|
|
const uint head= (page_type <= TAIL_PAGE) ?
|
|
PAGE_HEADER_SIZE(share) : FULL_PAGE_HEADER_SIZE(share);
|
|
const uint tail= CRC_SIZE;
|
|
const uint32 key_version_offset= (page_type <= TAIL_PAGE) ?
|
|
KEY_VERSION_OFFSET : FULL_PAGE_KEY_VERSION_OFFSET;
|
|
|
|
DBUG_ASSERT(page_type < MAX_PAGE_TYPE);
|
|
|
|
/* 1 - copy head */
|
|
memcpy(dst, src, head);
|
|
/* 2 - encrypt page */
|
|
if (ma_encrypt(share, share->crypt_data,
|
|
src + head, dst + head, size - (head + tail), pageno, lsn,
|
|
&key_version))
|
|
return 1;
|
|
/* 3 - copy tail */
|
|
memcpy(dst + size - tail, src + size - tail, tail);
|
|
/* 4 - store key version */
|
|
int4store(dst + key_version_offset, key_version);
|
|
}
|
|
|
|
/* swap pointers to instead write out the encrypted block */
|
|
args->crypt_buf= args->page;
|
|
args->page= crypt_buf;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ma_crypt_post_write_hook(int res,
|
|
PAGECACHE_IO_HOOK_ARGS *args)
|
|
{
|
|
if (args->crypt_buf != NULL)
|
|
{
|
|
uchar *tmp= args->page;
|
|
args->page= args->crypt_buf;
|
|
args->crypt_buf= NULL;
|
|
my_free(tmp);
|
|
}
|
|
|
|
maria_page_write_failure(res, args);
|
|
}
|
|
|
|
void ma_crypt_set_data_pagecache_callbacks(PAGECACHE_FILE *file,
|
|
MARIA_SHARE *share
|
|
__attribute__((unused)))
|
|
{
|
|
/* Only use encryption if we have defined it */
|
|
if (encryption_key_id_exists(get_encryption_key_id(share)))
|
|
{
|
|
file->pre_read_hook= ma_crypt_pre_read_hook;
|
|
file->post_read_hook= ma_crypt_data_post_read_hook;
|
|
file->pre_write_hook= ma_crypt_data_pre_write_hook;
|
|
file->post_write_hook= ma_crypt_post_write_hook;
|
|
}
|
|
}
|
|
|
|
static my_bool ma_crypt_index_post_read_hook(int res,
|
|
PAGECACHE_IO_HOOK_ARGS *args)
|
|
{
|
|
MARIA_SHARE *share= (MARIA_SHARE*) args->data;
|
|
const uint block_size= share->block_size;
|
|
const uint page_used= _ma_get_page_used(share, args->page);
|
|
|
|
if (res ||
|
|
page_used < share->keypage_header ||
|
|
page_used >= block_size - CRC_SIZE)
|
|
{
|
|
res= 1;
|
|
my_errno= HA_ERR_DECRYPTION_FAILED;
|
|
}
|
|
else
|
|
{
|
|
const uchar *src= args->page;
|
|
uchar* dst= args->crypt_buf;
|
|
uint pageno= (uint)args->pageno;
|
|
LSN lsn= lsn_korr(src);
|
|
const uint head= share->keypage_header;
|
|
const uint tail= CRC_SIZE;
|
|
const uint32 key_version= _ma_get_key_version(share, src);
|
|
/* page_used includes header (but not trailer) */
|
|
const uint size= page_used - head;
|
|
|
|
/* 1 - copy head */
|
|
memcpy(dst, src, head);
|
|
/* 2 - decrypt page */
|
|
res= ma_decrypt(share, share->crypt_data,
|
|
src + head, dst + head, size, pageno, lsn, key_version);
|
|
/* 3 - copy tail */
|
|
memcpy(dst + block_size - tail, src + block_size - tail, tail);
|
|
/* 4 clear key version to get correct crc */
|
|
_ma_store_key_version(share, dst, 0);
|
|
}
|
|
|
|
if (args->crypt_buf != NULL)
|
|
{
|
|
uchar *tmp= args->page;
|
|
args->page= args->crypt_buf;
|
|
args->crypt_buf= NULL;
|
|
my_free(tmp);
|
|
}
|
|
|
|
return maria_page_crc_check_index(res, args);
|
|
}
|
|
|
|
static my_bool ma_crypt_index_pre_write_hook(PAGECACHE_IO_HOOK_ARGS *args)
|
|
{
|
|
MARIA_SHARE *share= (MARIA_SHARE*) args->data;
|
|
const uint block_size= share->block_size;
|
|
const uint page_used= _ma_get_page_used(share, args->page);
|
|
uint key_version;
|
|
uchar *crypt_buf= my_malloc(PSI_INSTRUMENT_ME, block_size, MYF(0));
|
|
if (crypt_buf == NULL)
|
|
{
|
|
args->crypt_buf= NULL; /* for post-hook */
|
|
return 1;
|
|
}
|
|
|
|
if (!share->base.born_transactional)
|
|
{
|
|
/* store a random number instead of LSN (for counter block) */
|
|
store_rand_lsn(args->page);
|
|
}
|
|
|
|
maria_page_crc_set_index(args);
|
|
|
|
{
|
|
const uchar *src= args->page;
|
|
uchar* dst= crypt_buf;
|
|
uint pageno= (uint)args->pageno;
|
|
LSN lsn= lsn_korr(src);
|
|
const uint head= share->keypage_header;
|
|
const uint tail= CRC_SIZE;
|
|
/* page_used includes header (but not trailer) */
|
|
const uint size= page_used - head;
|
|
|
|
/* 1 - copy head */
|
|
memcpy(dst, src, head);
|
|
/* 2 - encrypt page */
|
|
if (ma_encrypt(share, share->crypt_data,
|
|
src + head, dst + head, size, pageno, lsn, &key_version))
|
|
{
|
|
my_free(crypt_buf);
|
|
return 1;
|
|
}
|
|
/* 3 - copy tail */
|
|
memcpy(dst + block_size - tail, src + block_size - tail, tail);
|
|
/* 4 - store key version */
|
|
_ma_store_key_version(share, dst, key_version);
|
|
#ifdef HAVE_valgrind
|
|
/* 5 - keep valgrind happy by zeroing not used bytes */
|
|
bzero(dst+head+size, block_size - size - tail - head);
|
|
#endif
|
|
}
|
|
|
|
/* swap pointers to instead write out the encrypted block */
|
|
args->crypt_buf= args->page;
|
|
args->page= crypt_buf;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ma_crypt_set_index_pagecache_callbacks(PAGECACHE_FILE *file,
|
|
MARIA_SHARE *share
|
|
__attribute__((unused)))
|
|
{
|
|
file->pre_read_hook= ma_crypt_pre_read_hook;
|
|
file->post_read_hook= ma_crypt_index_post_read_hook;
|
|
file->pre_write_hook= ma_crypt_index_pre_write_hook;
|
|
file->post_write_hook= ma_crypt_post_write_hook;
|
|
}
|
|
|
|
static int ma_encrypt(MARIA_SHARE *share, MARIA_CRYPT_DATA *crypt_data,
|
|
const uchar *src, uchar *dst, uint size,
|
|
uint pageno, LSN lsn,
|
|
uint *key_version)
|
|
{
|
|
int rc;
|
|
uint32 dstlen= 0; /* Must be set because of error message */
|
|
|
|
*key_version = encryption_key_get_latest_version(crypt_data->scheme.key_id);
|
|
if (*key_version == ENCRYPTION_KEY_VERSION_INVALID)
|
|
{
|
|
/*
|
|
We use this error for both encryption and decryption, as in normal
|
|
cases it should be impossible to get an error here.
|
|
*/
|
|
my_errno= HA_ERR_DECRYPTION_FAILED;
|
|
my_printf_error(HA_ERR_DECRYPTION_FAILED,
|
|
"Unknown key id %u for %s. Can't continue!",
|
|
MYF(ME_FATAL|ME_ERROR_LOG),
|
|
crypt_data->scheme.key_id,
|
|
share->open_file_name.str);
|
|
return 1;
|
|
}
|
|
|
|
rc= encryption_scheme_encrypt(src, size, dst, &dstlen,
|
|
&crypt_data->scheme, *key_version,
|
|
crypt_data->space, pageno, lsn);
|
|
|
|
/* The following can only fail if the encryption key is wrong */
|
|
DBUG_ASSERT(!my_assert_on_error || rc == MY_AES_OK);
|
|
DBUG_ASSERT(!my_assert_on_error || dstlen == size);
|
|
if (! (rc == MY_AES_OK && dstlen == size))
|
|
{
|
|
my_errno= HA_ERR_DECRYPTION_FAILED;
|
|
my_printf_error(HA_ERR_DECRYPTION_FAILED,
|
|
"failed to encrypt '%s' rc: %d dstlen: %u size: %u\n",
|
|
MYF(ME_FATAL|ME_ERROR_LOG),
|
|
share->open_file_name.str, rc, dstlen, size);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ma_decrypt(MARIA_SHARE *share, MARIA_CRYPT_DATA *crypt_data,
|
|
const uchar *src, uchar *dst, uint size,
|
|
uint pageno, LSN lsn,
|
|
uint key_version)
|
|
{
|
|
int rc;
|
|
uint32 dstlen= 0; /* Must be set because of error message */
|
|
|
|
rc= encryption_scheme_decrypt(src, size, dst, &dstlen,
|
|
&crypt_data->scheme, key_version,
|
|
crypt_data->space, pageno, lsn);
|
|
|
|
DBUG_ASSERT(!my_assert_on_error || rc == MY_AES_OK);
|
|
DBUG_ASSERT(!my_assert_on_error || dstlen == size);
|
|
if (! (rc == MY_AES_OK && dstlen == size))
|
|
{
|
|
my_errno= HA_ERR_DECRYPTION_FAILED;
|
|
if (!share->silence_encryption_errors)
|
|
my_printf_error(HA_ERR_DECRYPTION_FAILED,
|
|
"failed to decrypt '%s' rc: %d dstlen: %u size: %u\n",
|
|
MYF(ME_FATAL|ME_ERROR_LOG),
|
|
share->open_file_name.str, rc, dstlen, size);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|