Skip to content

Commit

Permalink
MDEV-13167 InnoDB key rotation is not skipping unused pages
Browse files Browse the repository at this point in the history
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().
  • Loading branch information
dr-m committed Aug 23, 2017
1 parent a00b74d commit 97f9d3c
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 78 deletions.
74 changes: 35 additions & 39 deletions storage/innobase/fil/fil0crypt.cc
Expand Up @@ -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__)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 (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;

/* 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;
}
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)) {

modified = true;

/* force rotation by dummy updating page */
mlog_write_ulint(frame + FIL_PAGE_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;
}
}

Expand Down
74 changes: 35 additions & 39 deletions storage/xtradb/fil/fil0crypt.cc
Expand Up @@ -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__)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 (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;

/* 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;
}
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)) {

modified = true;

/* force rotation by dummy updating page */
mlog_write_ulint(frame + FIL_PAGE_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;
}
}

Expand Down

0 comments on commit 97f9d3c

Please sign in to comment.