mirror of
https://github.com/MariaDB/server.git
synced 2025-01-17 04:22:27 +01:00
MDEV-13167 InnoDB key rotation is not skipping unused pages
In key rotation, we must initialize unallocated but previously initialized pages, so that if encryption is enabled on a table, all clear-text data for the page will eventually be overwritten. But we should not rotate keys on pages that were never allocated after the data file was created. According to the latching order rules, after acquiring the tablespace latch, no page latches of previously allocated user pages may be acquired. So, key rotation should check the page allocation status after acquiring the page latch, not before. But, the latching order rules also prohibit accessing pages that were not allocated first, and then acquiring the tablespace latch. Such behaviour would indeed result in a deadlock when running the following tests: encryption.innodb_encryption-page-compression encryption.innodb-checksum-algorithm Because the key rotation is accessing potentially unallocated pages, it cannot reliably check if these pages were allocated. It can only check the page header. If the page number is zero, we can assume that the page is unallocated. fil_crypt_rotate_page(): Detect uninitialized pages by FIL_PAGE_OFFSET. Page 0 is never encrypted, and on other pages that are initialized, FIL_PAGE_OFFSET must contain the page number. fil_crypt_is_page_uninitialized(): Remove. It suffices to check the page number field in fil_crypt_rotate_page().
This commit is contained in:
parent
a00b74d994
commit
97f9d3c080
2 changed files with 64 additions and 72 deletions
|
@ -1649,20 +1649,6 @@ fil_crypt_find_page_to_rotate(
|
|||
return found;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
Check if a page is uninitialized (doesn't need to be rotated)
|
||||
@param[in] frame Page to check
|
||||
@param[in] zip_size zip_size or 0
|
||||
@return true if page is uninitialized, false if not. */
|
||||
static inline
|
||||
bool
|
||||
fil_crypt_is_page_uninitialized(
|
||||
const byte *frame,
|
||||
uint zip_size)
|
||||
{
|
||||
return (buf_page_is_zeroes(frame, zip_size));
|
||||
}
|
||||
|
||||
#define fil_crypt_get_page_throttle(state,offset,mtr,sleeptime_ms) \
|
||||
fil_crypt_get_page_throttle_func(state, offset, mtr, \
|
||||
sleeptime_ms, __FILE__, __LINE__)
|
||||
|
@ -1823,6 +1809,7 @@ fil_crypt_rotate_page(
|
|||
fil_space_crypt_t *crypt_data = space->crypt_data;
|
||||
|
||||
ut_ad(space->n_pending_ops > 0);
|
||||
ut_ad(offset > 0);
|
||||
|
||||
/* In fil_crypt_thread where key rotation is done we have
|
||||
acquired space and checked that this space is not yet
|
||||
|
@ -1851,31 +1838,40 @@ fil_crypt_rotate_page(
|
|||
byte* frame = buf_block_get_frame(block);
|
||||
uint kv = mach_read_from_4(frame+FIL_PAGE_FILE_FLUSH_LSN_OR_KEY_VERSION);
|
||||
|
||||
/* check if tablespace is closing after reading page */
|
||||
if (!space->is_stopping()) {
|
||||
if (space->is_stopping()) {
|
||||
/* The tablespace is closing (in DROP TABLE or
|
||||
TRUNCATE TABLE or similar): avoid further access */
|
||||
} else if (!*reinterpret_cast<uint32_t*>(FIL_PAGE_OFFSET
|
||||
+ frame)) {
|
||||
/* It looks like this page was never
|
||||
allocated. Because key rotation is accessing
|
||||
pages in a pattern that is unlike the normal
|
||||
B-tree and undo log access pattern, we cannot
|
||||
invoke fseg_page_is_free() here, because that
|
||||
could result in a deadlock. If we invoked
|
||||
fseg_page_is_free() and released the
|
||||
tablespace latch before acquiring block->lock,
|
||||
then the fseg_page_is_free() information
|
||||
could be stale already. */
|
||||
ut_ad(kv == 0);
|
||||
ut_ad(page_get_space_id(frame) == 0);
|
||||
} else if (fil_crypt_needs_rotation(
|
||||
crypt_data->encryption,
|
||||
kv, key_state->key_version,
|
||||
key_state->rotate_key_age)) {
|
||||
|
||||
if (kv == 0 &&
|
||||
fil_crypt_is_page_uninitialized(frame, zip_size)) {
|
||||
;
|
||||
} else if (fil_crypt_needs_rotation(
|
||||
crypt_data->encryption,
|
||||
kv, key_state->key_version,
|
||||
key_state->rotate_key_age)) {
|
||||
modified = true;
|
||||
|
||||
modified = true;
|
||||
/* force rotation by dummy updating page */
|
||||
mlog_write_ulint(frame + FIL_PAGE_SPACE_ID,
|
||||
space_id, MLOG_4BYTES, &mtr);
|
||||
|
||||
/* force rotation by dummy updating page */
|
||||
mlog_write_ulint(frame +
|
||||
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID,
|
||||
space_id, MLOG_4BYTES, &mtr);
|
||||
|
||||
/* statistics */
|
||||
state->crypt_stat.pages_modified++;
|
||||
} else {
|
||||
if (crypt_data->is_encrypted()) {
|
||||
if (kv < state->min_key_version_found) {
|
||||
state->min_key_version_found = kv;
|
||||
}
|
||||
/* statistics */
|
||||
state->crypt_stat.pages_modified++;
|
||||
} else {
|
||||
if (crypt_data->is_encrypted()) {
|
||||
if (kv < state->min_key_version_found) {
|
||||
state->min_key_version_found = kv;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1649,20 +1649,6 @@ fil_crypt_find_page_to_rotate(
|
|||
return found;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
Check if a page is uninitialized (doesn't need to be rotated)
|
||||
@param[in] frame Page to check
|
||||
@param[in] zip_size zip_size or 0
|
||||
@return true if page is uninitialized, false if not. */
|
||||
static inline
|
||||
bool
|
||||
fil_crypt_is_page_uninitialized(
|
||||
const byte *frame,
|
||||
uint zip_size)
|
||||
{
|
||||
return (buf_page_is_zeroes(frame, zip_size));
|
||||
}
|
||||
|
||||
#define fil_crypt_get_page_throttle(state,offset,mtr,sleeptime_ms) \
|
||||
fil_crypt_get_page_throttle_func(state, offset, mtr, \
|
||||
sleeptime_ms, __FILE__, __LINE__)
|
||||
|
@ -1823,6 +1809,7 @@ fil_crypt_rotate_page(
|
|||
fil_space_crypt_t *crypt_data = space->crypt_data;
|
||||
|
||||
ut_ad(space->n_pending_ops > 0);
|
||||
ut_ad(offset > 0);
|
||||
|
||||
/* In fil_crypt_thread where key rotation is done we have
|
||||
acquired space and checked that this space is not yet
|
||||
|
@ -1851,31 +1838,40 @@ fil_crypt_rotate_page(
|
|||
byte* frame = buf_block_get_frame(block);
|
||||
uint kv = mach_read_from_4(frame+FIL_PAGE_FILE_FLUSH_LSN_OR_KEY_VERSION);
|
||||
|
||||
/* check if tablespace is closing after reading page */
|
||||
if (!space->is_stopping()) {
|
||||
if (space->is_stopping()) {
|
||||
/* The tablespace is closing (in DROP TABLE or
|
||||
TRUNCATE TABLE or similar): avoid further access */
|
||||
} else if (!*reinterpret_cast<uint32_t*>(FIL_PAGE_OFFSET
|
||||
+ frame)) {
|
||||
/* It looks like this page was never
|
||||
allocated. Because key rotation is accessing
|
||||
pages in a pattern that is unlike the normal
|
||||
B-tree and undo log access pattern, we cannot
|
||||
invoke fseg_page_is_free() here, because that
|
||||
could result in a deadlock. If we invoked
|
||||
fseg_page_is_free() and released the
|
||||
tablespace latch before acquiring block->lock,
|
||||
then the fseg_page_is_free() information
|
||||
could be stale already. */
|
||||
ut_ad(kv == 0);
|
||||
ut_ad(page_get_space_id(frame) == 0);
|
||||
} else if (fil_crypt_needs_rotation(
|
||||
crypt_data->encryption,
|
||||
kv, key_state->key_version,
|
||||
key_state->rotate_key_age)) {
|
||||
|
||||
if (kv == 0 &&
|
||||
fil_crypt_is_page_uninitialized(frame, zip_size)) {
|
||||
;
|
||||
} else if (fil_crypt_needs_rotation(
|
||||
crypt_data->encryption,
|
||||
kv, key_state->key_version,
|
||||
key_state->rotate_key_age)) {
|
||||
modified = true;
|
||||
|
||||
modified = true;
|
||||
/* force rotation by dummy updating page */
|
||||
mlog_write_ulint(frame + FIL_PAGE_SPACE_ID,
|
||||
space_id, MLOG_4BYTES, &mtr);
|
||||
|
||||
/* force rotation by dummy updating page */
|
||||
mlog_write_ulint(frame +
|
||||
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID,
|
||||
space_id, MLOG_4BYTES, &mtr);
|
||||
|
||||
/* statistics */
|
||||
state->crypt_stat.pages_modified++;
|
||||
} else {
|
||||
if (crypt_data->is_encrypted()) {
|
||||
if (kv < state->min_key_version_found) {
|
||||
state->min_key_version_found = kv;
|
||||
}
|
||||
/* statistics */
|
||||
state->crypt_stat.pages_modified++;
|
||||
} else {
|
||||
if (crypt_data->is_encrypted()) {
|
||||
if (kv < state->min_key_version_found) {
|
||||
state->min_key_version_found = kv;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue