MDEV-38246 aria_read index failed on encrypted database during backup

The backup of encrypted Aria tables was not supported.
Added support for this. One complication is that the page checksum is
for the not encrypted page. To be able to verify the checksum I have to
temporarly decrypt the page.
In the backup we store the encrypted pages.

Other things:
- Fixed some (not critical) memory leaks in mariabackup
This commit is contained in:
Monty 2025-12-06 16:29:26 +02:00 committed by Sergei Golubchik
commit a9e353e84f
15 changed files with 313 additions and 47 deletions

View file

@ -18,6 +18,7 @@
#include "maria_def.h"
#include "ma_blockrec.h" /* PAGE_SUFFIX_SIZE */
#include "ma_checkpoint.h"
#include "ma_crypt.h"
#include <aria_backup.h>
/**
@ -30,8 +31,11 @@
@return X errno
*/
int aria_get_capabilities(File kfile, ARIA_TABLE_CAPABILITIES *cap)__attribute__((visibility("default"))) ;
int aria_get_capabilities(File kfile, ARIA_TABLE_CAPABILITIES *cap)
int aria_get_capabilities(File kfile, const char *table_name,
ARIA_TABLE_CAPABILITIES *cap)
__attribute__((visibility("default"))) ;
int aria_get_capabilities(File kfile, const char *table_name,
ARIA_TABLE_CAPABILITIES *cap)
{
MARIA_SHARE share;
int error= 0;
@ -42,6 +46,7 @@ int aria_get_capabilities(File kfile, ARIA_TABLE_CAPABILITIES *cap)
DBUG_ENTER("aria_get_capabilities");
bzero(cap, sizeof(*cap));
bzero(&share, sizeof(share));
if (my_pread(kfile,share.state.header.file_version, head_length, 0,
MYF(MY_NABP)))
DBUG_RETURN(HA_ERR_NOT_A_TABLE);
@ -67,13 +72,14 @@ int aria_get_capabilities(File kfile, ARIA_TABLE_CAPABILITIES *cap)
goto err;
}
_ma_base_info_read(disc_cache + base_pos, &share.base);
strmake(cap->filename, table_name, sizeof(cap->filename)-1);
cap->transactional= share.base.born_transactional;
cap->checksum= MY_TEST(share.options & HA_OPTION_PAGE_CHECKSUM);
cap->online_backup_safe= cap->transactional && cap->checksum;
cap->header_size= share.base.keystart;
cap->keypage_header= ((share.base.born_transactional ?
LSN_STORE_SIZE + TRANSID_SIZE :
0) + KEYPAGE_KEYID_SIZE + KEYPAGE_FLAG_SIZE +
LSN_STORE_SIZE + TRANSID_SIZE : 0) +
KEYPAGE_KEYID_SIZE + KEYPAGE_FLAG_SIZE +
KEYPAGE_USED_SIZE);
cap->block_size= share.base.block_size;
cap->data_file_type= share.state.header.data_file_type;
@ -81,10 +87,23 @@ int aria_get_capabilities(File kfile, ARIA_TABLE_CAPABILITIES *cap)
cap->compression= share.base.compression_algorithm;
cap->encrypted= MY_TEST(share.base.extra_options &
MA_EXTRA_OPTIONS_ENCRYPTED);
if (cap->encrypted)
{
share.data_file_name.str= (char*) table_name; /* in case of error */
share.crypt_data= 0;
if (maria_read_crypt_data(kfile, &share))
{
error= HA_ERR_DECRYPTION_FAILED;
goto err;
}
cap->keypage_header+= ma_crypt_get_index_page_header_space(&share);
cap->crypt_data= share.crypt_data;
cap->crypt_page_header_space= ma_crypt_get_data_page_header_space();
}
if (share.state.header.data_file_type == BLOCK_RECORD)
{
/* Calulate how man pages the row bitmap covers. From _ma_bitmap_init() */
/* Calulate how many pages the row bitmap covers. From _ma_bitmap_init() */
aligned_bit_blocks= (cap->block_size - PAGE_SUFFIX_SIZE) / 6;
/*
In each 6 bytes, we have 6*8/3 = 16 pages covered
@ -100,9 +119,26 @@ int aria_get_capabilities(File kfile, ARIA_TABLE_CAPABILITIES *cap)
err:
my_free(disc_cache);
if (error)
aria_free_capabilities(cap);
DBUG_RETURN(error);
} /* aria_get_capabilities */
void aria_free_capabilities(ARIA_TABLE_CAPABILITIES *cap)
{
DBUG_ENTER("aria_free_capabilities");
if (cap->crypt_data)
{
MARIA_SHARE share;
share.crypt_data= cap->crypt_data;
ma_crypt_free(&share);
cap->crypt_data= 0;
}
DBUG_VOID_RETURN;
}
/****************************************************************************
** store MARIA_BASE_INFO
****************************************************************************/
@ -171,14 +207,14 @@ int aria_read_index(File kfile, ARIA_TABLE_CAPABILITIES *cap, ulonglong block,
uchar *buffer)
{
MARIA_SHARE share;
int retry= 0;
int retry= 0, error= 0;
DBUG_ENTER("aria_read_index");
share.keypage_header= cap->keypage_header;
share.block_size= cap->block_size;
do
for (;;)
{
int error;
size_t length;
if ((length= my_pread(kfile, buffer, cap->block_size,
block * cap->block_size, MYF(0))) != cap->block_size)
@ -199,15 +235,53 @@ int aria_read_index(File kfile, ARIA_TABLE_CAPABILITIES *cap, ulonglong block,
length= _ma_get_page_used(&share, buffer);
if (length > cap->block_size - CRC_SIZE)
DBUG_RETURN(HA_ERR_CRASHED);
if (cap->encrypted)
{
/* We have to decrypt block to be able to calculate checksum */
PAGECACHE_IO_HOOK_ARGS io_arg;
my_bool crypt_error;
io_arg.data= (void*) &share;
io_arg.page= buffer;
io_arg.crypt_buf= (uchar*) my_alloca(cap->block_size);
io_arg.pageno= block;
share.crypt_data= cap->crypt_data;
share.silence_encryption_errors= 1;
share.crypt_page_header_space= cap->crypt_page_header_space;
share.open_file_name.str= cap->filename;
crypt_error= ma_crypt_index_post_read_hook(0, &io_arg);
my_afree(io_arg.crypt_buf);
if (!crypt_error)
DBUG_RETURN(0); /* ok */
error= my_errno;
if (error == HA_ERR_DECRYPTION_FAILED ||
error == HA_ERR_WRONG_CRC)
goto retry;
break; /* Give error */
}
error= maria_page_crc_check(buffer, block, &share,
MARIA_NO_CRC_NORMAL_PAGE,
(int) length);
if (error == 0 || my_errno != HA_ERR_WRONG_CRC)
DBUG_RETURN(error);
if (error == 0)
DBUG_RETURN(0);
if ((error= my_errno) != HA_ERR_WRONG_CRC)
break;
}
retry:
if (retry++ >= MAX_RETRY)
{
error= HA_ERR_WRONG_CRC;
break;
}
my_sleep(100000); /* Sleep 0.1 seconds */
} while (retry++ < MAX_RETRY);
DBUG_RETURN(HA_ERR_WRONG_CRC);
}
my_printf_error(error,
"Error %d reading index file %s block %lld",
MYF(ME_FATAL|ME_ERROR_LOG),
error, cap->filename, block);
DBUG_RETURN(error);
}
@ -229,7 +303,7 @@ int aria_read_data(File dfile, ARIA_TABLE_CAPABILITIES *cap, ulonglong block,
uchar *buffer, size_t *bytes_read)
{
MARIA_SHARE share;
int retry= 0;
int retry= 0, error= 0;
DBUG_ENTER("aria_read_data");
share.keypage_header= cap->keypage_header;
@ -243,11 +317,9 @@ int aria_read_data(File dfile, ARIA_TABLE_CAPABILITIES *cap, ulonglong block,
DBUG_RETURN(HA_ERR_END_OF_FILE);
DBUG_RETURN(*bytes_read > 0 ? 0 : (my_errno ? my_errno : -1));
}
*bytes_read= cap->block_size;
do
for (;;)
{
int error;
size_t length;
if ((length= my_pread(dfile, buffer, cap->block_size,
block * cap->block_size, MYF(0))) != cap->block_size)
@ -265,17 +337,55 @@ int aria_read_data(File dfile, ARIA_TABLE_CAPABILITIES *cap, ulonglong block,
if (length == cap->block_size)
{
if (!_ma_check_if_zero(buffer, share.block_size - CRC_SIZE))
error= 0;
DBUG_RETURN(0);
/* Test if encrypted pages. Note that bitmap pages are not encrypted */
if (cap->encrypted && (block % cap->bitmap_pages_covered))
{
/* We have to decrypt block to be able to calculate checksum */
PAGECACHE_IO_HOOK_ARGS io_arg;
my_bool crypt_error;
io_arg.data= (void*) &share;
io_arg.page= buffer;
io_arg.crypt_buf= (uchar*) my_alloca(cap->block_size);
io_arg.pageno= block;
share.crypt_data= cap->crypt_data;
share.silence_encryption_errors= 1;
share.crypt_page_header_space= cap->crypt_page_header_space;
share.open_file_name.str= cap->filename;
crypt_error= ma_crypt_data_post_read_hook(0, &io_arg);
my_afree(io_arg.crypt_buf);
if (!crypt_error)
DBUG_RETURN(0); /* ok */
if (my_errno == HA_ERR_DECRYPTION_FAILED || my_errno== HA_ERR_WRONG_CRC)
goto retry;
error= my_errno;
break;
}
else
{
error= maria_page_crc_check(buffer, block, &share,
((block % cap->bitmap_pages_covered) == 0 ?
MARIA_NO_CRC_BITMAP_PAGE :
MARIA_NO_CRC_NORMAL_PAGE),
share.block_size - CRC_SIZE);
if (error == 0 || my_errno != HA_ERR_WRONG_CRC)
DBUG_RETURN(error);
((block % cap->bitmap_pages_covered) == 0 ?
MARIA_NO_CRC_BITMAP_PAGE :
MARIA_NO_CRC_NORMAL_PAGE),
share.block_size - CRC_SIZE);
if (error == 0)
DBUG_RETURN(0);
if ((error= my_errno) != HA_ERR_WRONG_CRC)
break;
}
}
retry:
if (retry++ >= MAX_RETRY)
{
error= HA_ERR_WRONG_CRC;
break;
}
my_sleep(100000); /* Sleep 0.1 seconds */
} while (retry++ < MAX_RETRY);
DBUG_RETURN(HA_ERR_WRONG_CRC);
}
my_printf_error(error,
"Error %d reading data file %s block %lld",
MYF(ME_FATAL|ME_ERROR_LOG),
error, cap->filename, block);
DBUG_RETURN(error);
}

View file

@ -37,8 +37,9 @@ struct st_crypt_key
struct st_maria_crypt_data
{
struct st_encryption_scheme scheme;
uint space;
mysql_mutex_t lock; /* protecting keys */
uint space;
my_bool no_alloc;
};
/**
@ -105,6 +106,7 @@ ma_crypt_create(MARIA_SHARE* share)
(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;
crypt_data->no_alloc= 0;
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));
@ -156,7 +158,7 @@ ma_crypt_write(MARIA_SHARE* share, File file)
}
uchar*
ma_crypt_read(MARIA_SHARE* share, uchar *buff, my_bool silent)
ma_crypt_read(MARIA_SHARE* share, uchar *buff, my_bool silent, my_bool no_alloc)
{
uchar type= buff[0];
uchar iv_length= buff[1];
@ -176,7 +178,8 @@ ma_crypt_read(MARIA_SHARE* share, uchar *buff, my_bool silent)
{
/* opening a table */
MARIA_CRYPT_DATA *crypt_data=
(MARIA_CRYPT_DATA*)my_malloc(PSI_INSTRUMENT_ME, sizeof(MARIA_CRYPT_DATA), MYF(MY_ZEROFILL));
(MARIA_CRYPT_DATA*) my_malloc(PSI_INSTRUMENT_ME,
sizeof(MARIA_CRYPT_DATA), MYF(MY_ZEROFILL));
uint key_version;
crypt_data->scheme.type= type;
@ -185,6 +188,7 @@ ma_crypt_read(MARIA_SHARE* share, uchar *buff, my_bool silent)
crypt_data->scheme.locker= crypt_data_scheme_locker;
crypt_data->scheme.key_id= get_encryption_key_id(share);
crypt_data->space= uint4korr(buff + 2);
crypt_data->no_alloc= no_alloc;
memcpy(crypt_data->scheme.iv, buff + 6, sizeof(crypt_data->scheme.iv));
share->crypt_data= crypt_data;
@ -226,8 +230,8 @@ static my_bool ma_crypt_pre_read_hook(PAGECACHE_IO_HOOK_ARGS *args)
return 0;
}
static my_bool ma_crypt_data_post_read_hook(int res,
PAGECACHE_IO_HOOK_ARGS *args)
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;
@ -263,7 +267,8 @@ static my_bool ma_crypt_data_post_read_hook(int res,
uchar *tmp= args->page;
args->page= args->crypt_buf;
args->crypt_buf= NULL;
my_free(tmp);
if (!share->crypt_data->no_alloc)
my_free(tmp);
}
return maria_page_crc_check_data(res, args);
@ -361,8 +366,8 @@ void ma_crypt_set_data_pagecache_callbacks(PAGECACHE_FILE *file,
}
}
static my_bool ma_crypt_index_post_read_hook(int res,
PAGECACHE_IO_HOOK_ARGS *args)
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;
@ -403,7 +408,8 @@ static my_bool ma_crypt_index_post_read_hook(int res,
uchar *tmp= args->page;
args->page= args->crypt_buf;
args->crypt_buf= NULL;
my_free(tmp);
if (!share->crypt_data->no_alloc)
my_free(tmp);
}
return maria_page_crc_check_index(res, args);

View file

@ -26,9 +26,12 @@ uint ma_crypt_get_index_page_header_space(struct st_maria_share *);
uint ma_crypt_get_file_length(); /* bytes needed in file */
int ma_crypt_create(struct st_maria_share *); /* create encryption data */
int ma_crypt_write(struct st_maria_share *, File); /* write encryption data */
uchar* ma_crypt_read(struct st_maria_share *, uchar *buff,
my_bool silent); /* read crypt data*/
uchar *ma_crypt_read(struct st_maria_share *, uchar *buff,
my_bool silent, my_bool no_alloc); /* read crypt data */
my_bool ma_crypt_data_post_read_hook(int res,
PAGECACHE_IO_HOOK_ARGS *args);
my_bool ma_crypt_index_post_read_hook(int res,
PAGECACHE_IO_HOOK_ARGS *args);
void ma_crypt_set_data_pagecache_callbacks(struct st_pagecache_file *file,
struct st_maria_share *share);

View file

@ -973,7 +973,7 @@ MARIA_HA *maria_open(const char *name, int mode, uint open_flags,
if (MY_TEST(share->base.extra_options & MA_EXTRA_OPTIONS_ENCRYPTED))
{
if (!(disk_pos= ma_crypt_read(share, disk_pos,
MY_TEST(open_flags & HA_OPEN_FOR_DROP))))
MY_TEST(open_flags & HA_OPEN_FOR_DROP), 0)))
goto err;
}
@ -1258,6 +1258,52 @@ err:
} /* maria_open */
/*
Read the crypt from a maria key file
Crypt_data stored in share->crypt_data
*/
my_bool maria_read_crypt_data(File kfile, MARIA_SHARE *share)
{
size_t base_pos, info_length, crypt_length;
uchar *cache;
uint keys, key_segs, uniques, unique_key_parts, columns;
my_bool error;
DBUG_ENTER("maria_read_crypt_data");
DBUG_ASSERT(share->base.extra_options & MA_EXTRA_OPTIONS_ENCRYPTED);
keys= (uint) share->state.header.keys;
key_segs= mi_uint2korr(share->state.header.key_parts);
uniques= (uint) share->state.header.uniques;
unique_key_parts= mi_uint2korr(share->state.header.unique_key_parts);
columns= share->base.fields;
base_pos= mi_uint2korr(share->state.header.base_pos);
/* Same calculation as in maria_create() */
info_length= base_pos+(uint) (MARIA_BASE_INFO_SIZE+
keys * MARIA_KEYDEF_SIZE+
uniques * MARIA_UNIQUEDEF_SIZE +
(key_segs + unique_key_parts)*HA_KEYSEG_SIZE+
columns*(MARIA_COLUMNDEF_SIZE + 2));
crypt_length= mi_uint2korr(share->state.header.header_length) - info_length;
if ((longlong) crypt_length < 0 || crypt_length > 1024)
{
my_errno=HA_ERR_NOT_A_TABLE;
DBUG_RETURN(1);
}
cache= (uchar*) my_alloca(crypt_length);
error= 0;
if (mysql_file_pread(kfile, cache, crypt_length, info_length,
MYF(MY_FNABP | MY_WME)) ||
!ma_crypt_read(share, cache, 0, 1))
error= 1;
my_afree(cache);
DBUG_RETURN(error);
}
/*
Reallocate a buffer, if the current buffer is not large enough
*/

View file

@ -1659,6 +1659,7 @@ int _ma_open_keyfile(MARIA_SHARE *share);
void _ma_setup_functions(MARIA_SHARE *share);
my_bool _ma_dynmap_file(MARIA_HA *info, my_off_t size);
void _ma_remap_file(MARIA_HA *info, my_off_t size);
my_bool maria_read_crypt_data(File kfile, MARIA_SHARE *share);
MARIA_RECORD_POS _ma_write_init_default(MARIA_HA *info, const uchar *record);
my_bool _ma_write_abort_default(MARIA_HA *info);

View file

@ -411,7 +411,7 @@ int aria_copy_to_s3(ms3_st *s3_client, const char *aws_bucket,
O_RDONLY | O_SHARE | O_NOFOLLOW | O_CLOEXEC,
MYF(MY_WME))) < 0)
DBUG_RETURN(1);
if ((error= aria_get_capabilities(file, &cap)))
if ((error= aria_get_capabilities(file, table_name, &cap)))
{
fprintf(stderr, "Got error %d when reading Aria header from %s\n",
error, path);

View file

@ -103,7 +103,7 @@ static int copy_table(const char *table_name, int stage)
O_RDONLY | O_SHARE | O_NOFOLLOW | O_CLOEXEC,
MYF(MY_WME))) < 0)
goto err;
if ((error= aria_get_capabilities(org_file, &cap)))
if ((error= aria_get_capabilities(org_file, table_name, &cap)))
{
fprintf(stderr, "aria_get_capabilities failed: %d\n", error);
goto err;