2015-05-27 00:18:20 +02:00
|
|
|
/*
|
|
|
|
Copyright (c) 2015, MariaDB
|
|
|
|
|
|
|
|
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
|
2019-05-11 21:19:05 +02:00
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
|
2015-05-27 00:18:20 +02:00
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
Limitation of encrypted IO_CACHEs
|
|
|
|
1. Designed to support temporary files only (open_cached_file, fd=-1)
|
|
|
|
2. Created with WRITE_CACHE, later can be reinit_io_cache'ed to
|
|
|
|
READ_CACHE and WRITE_CACHE in any order arbitrary number of times.
|
|
|
|
3. no seeks for writes, but reinit_io_cache(WRITE_CACHE, seek_offset)
|
|
|
|
is allowed (there's a special hack in reinit_io_cache() for that)
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "../mysys/mysys_priv.h"
|
|
|
|
#include "log.h"
|
|
|
|
#include "mysqld.h"
|
|
|
|
#include "sql_class.h"
|
|
|
|
|
|
|
|
static uint keyid, keyver;
|
|
|
|
|
|
|
|
#define set_iv(IV, N1, N2) \
|
|
|
|
do { \
|
|
|
|
compile_time_assert(sizeof(IV) >= sizeof(N1) + sizeof(N2)); \
|
|
|
|
memcpy(IV, &(N1), sizeof(N1)); \
|
|
|
|
memcpy(IV + sizeof(N1), &(N2), sizeof(N2)); \
|
|
|
|
} while(0)
|
|
|
|
|
|
|
|
static int my_b_encr_read(IO_CACHE *info, uchar *Buffer, size_t Count)
|
|
|
|
{
|
|
|
|
my_off_t pos_in_file= info->pos_in_file + (info->read_end - info->buffer);
|
|
|
|
my_off_t old_pos_in_file= pos_in_file, pos_offset= 0;
|
|
|
|
IO_CACHE_CRYPT *crypt_data=
|
|
|
|
(IO_CACHE_CRYPT *)(info->buffer + info->buffer_length + MY_AES_BLOCK_SIZE);
|
|
|
|
uchar *wbuffer= (uchar*)&(crypt_data->inbuf_counter);
|
|
|
|
uchar *ebuffer= (uchar*)(crypt_data + 1);
|
|
|
|
DBUG_ENTER("my_b_encr_read");
|
|
|
|
|
|
|
|
if (pos_in_file == info->end_of_file)
|
|
|
|
{
|
MDEV-10259 mysqld crash with certain statement length and...
order with Galera and encrypt-tmp-files=1
Problem:- If trans_cache (IO_CACHE) uses encrypted tmp file
then on next DML server will crash.
Case:-
Lets take a case , we have a table t1 , We try to do 2 inserts in t1
1. A really long insert so that trans_cache has to use temp_file
2. Just a small insert
Analysis:- Actually server crashes from inside of galera
library.
/lib64/libc.so.6(abort+0x175)[0x7fb5ba779dc5]
/usr/lib64/galera/libgalera_smm.so(_ZN6galera3FSMINS_9TrxHandle5State...
mysys/stacktrace.c:247(my_print_stacktrace)[0x7fb5a714940e]
sql/signal_handler.cc:160(handle_fatal_signal)[0x7fb5a715c1bd]
sql/wsrep_hton.cc:257(wsrep_rollback)[0x7fb5bcce923a]
sql/wsrep_hton.cc:268(wsrep_rollback)[0x7fb5bcce9368]
sql/handler.cc:1658(ha_rollback_trans(THD*, bool))[0x7fb5bcd4f41a]
sql/handler.cc:1483(ha_commit_trans(THD*, bool))[0x7fb5bcd4f804]
but actual issue is not in galera but in mariadb, because for 2nd
insert we should never call rollback. We are calling rollback because
log_and_order fails it fails because write_cache fails , It fails
because after reinit_io_cache(trans_cache) , my_b_bytes_in_cache says 0
so we look into tmp_file for data , which is obviously wrong since temp
was used for previous insert and it no longer exist.
wsrep_write_cache_inc() reads the IO_CACHE in a loop, filling it with
my_b_fill() until it returns "0 bytes read". Later
MYSQL_BIN_LOG::write_cache() does the same. wsrep_write_cache_inc()
assumes that reading a zero bytes past EOF leaves the old data in the
cache
Solution:- There is two issue in my_b_encr_read
1st we should never equal read_end to info->buffer. I mean this
does not make sense read_end should always point to end of buffer.
2nd For most of the case(apart from async IO_CACHE) info->pos_in_file
should be equal to info->buffer position wrt to temp file , since
in this case we are not changing info->buffer it should remain
unchanged.
2018-05-18 14:05:33 +02:00
|
|
|
/* reading past EOF should not empty the cache */
|
|
|
|
info->read_pos= info->read_end;
|
2015-05-27 00:18:20 +02:00
|
|
|
info->error= 0;
|
|
|
|
DBUG_RETURN(MY_TEST(Count));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info->seek_not_done)
|
|
|
|
{
|
2017-09-28 12:38:02 +02:00
|
|
|
my_off_t wpos;
|
2015-05-27 00:18:20 +02:00
|
|
|
|
|
|
|
pos_offset= pos_in_file % info->buffer_length;
|
|
|
|
pos_in_file-= pos_offset;
|
|
|
|
|
|
|
|
wpos= pos_in_file / info->buffer_length * crypt_data->block_length;
|
|
|
|
|
|
|
|
if ((mysql_file_seek(info->file, wpos, MY_SEEK_SET, MYF(0))
|
|
|
|
== MY_FILEPOS_ERROR))
|
|
|
|
{
|
|
|
|
info->error= -1;
|
|
|
|
DBUG_RETURN(1);
|
|
|
|
}
|
|
|
|
info->seek_not_done= 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
size_t copied;
|
|
|
|
uint elength, wlength, length;
|
|
|
|
uchar iv[MY_AES_BLOCK_SIZE]= {0};
|
|
|
|
|
|
|
|
DBUG_ASSERT(pos_in_file % info->buffer_length == 0);
|
|
|
|
|
|
|
|
if (info->end_of_file - pos_in_file >= info->buffer_length)
|
|
|
|
wlength= crypt_data->block_length;
|
|
|
|
else
|
|
|
|
wlength= crypt_data->last_block_length;
|
|
|
|
|
|
|
|
if (mysql_file_read(info->file, wbuffer, wlength, info->myflags | MY_NABP))
|
|
|
|
{
|
|
|
|
info->error= -1;
|
|
|
|
DBUG_RETURN(1);
|
|
|
|
}
|
|
|
|
|
2017-10-03 21:43:43 +02:00
|
|
|
elength= wlength - (uint)(ebuffer - wbuffer);
|
2015-05-27 00:18:20 +02:00
|
|
|
set_iv(iv, pos_in_file, crypt_data->inbuf_counter);
|
|
|
|
|
2015-09-04 10:32:52 +02:00
|
|
|
if (encryption_crypt(ebuffer, elength, info->buffer, &length,
|
|
|
|
crypt_data->key, sizeof(crypt_data->key),
|
|
|
|
iv, sizeof(iv), ENCRYPTION_FLAG_DECRYPT,
|
|
|
|
keyid, keyver))
|
2015-05-27 00:18:20 +02:00
|
|
|
{
|
|
|
|
my_errno= 1;
|
|
|
|
DBUG_RETURN(info->error= -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
DBUG_ASSERT(length <= info->buffer_length);
|
|
|
|
|
2017-09-28 12:38:02 +02:00
|
|
|
copied= MY_MIN(Count, (size_t)(length - pos_offset));
|
2015-05-27 00:18:20 +02:00
|
|
|
|
|
|
|
memcpy(Buffer, info->buffer + pos_offset, copied);
|
|
|
|
Count-= copied;
|
|
|
|
Buffer+= copied;
|
|
|
|
|
|
|
|
info->read_pos= info->buffer + pos_offset + copied;
|
|
|
|
info->read_end= info->buffer + length;
|
|
|
|
info->pos_in_file= pos_in_file;
|
|
|
|
pos_in_file+= length;
|
|
|
|
pos_offset= 0;
|
|
|
|
|
|
|
|
if (wlength < crypt_data->block_length && pos_in_file < info->end_of_file)
|
|
|
|
{
|
2017-09-28 12:38:02 +02:00
|
|
|
info->error= (int)(pos_in_file - old_pos_in_file);
|
2015-05-27 00:18:20 +02:00
|
|
|
DBUG_RETURN(1);
|
|
|
|
}
|
|
|
|
} while (Count);
|
|
|
|
|
|
|
|
DBUG_RETURN(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int my_b_encr_write(IO_CACHE *info, const uchar *Buffer, size_t Count)
|
|
|
|
{
|
|
|
|
IO_CACHE_CRYPT *crypt_data=
|
|
|
|
(IO_CACHE_CRYPT *)(info->buffer + info->buffer_length + MY_AES_BLOCK_SIZE);
|
|
|
|
uchar *wbuffer= (uchar*)&(crypt_data->inbuf_counter);
|
|
|
|
uchar *ebuffer= (uchar*)(crypt_data + 1);
|
|
|
|
DBUG_ENTER("my_b_encr_write");
|
|
|
|
|
|
|
|
if (Buffer != info->write_buffer)
|
|
|
|
{
|
|
|
|
Count-= Count % info->buffer_length;
|
|
|
|
if (!Count)
|
|
|
|
DBUG_RETURN(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info->seek_not_done)
|
|
|
|
{
|
2018-02-07 18:54:11 +01:00
|
|
|
DBUG_ASSERT(info->pos_in_file % info->buffer_length == 0);
|
2018-02-11 00:23:17 +01:00
|
|
|
my_off_t wpos= info->pos_in_file / info->buffer_length * crypt_data->block_length;
|
2015-05-27 00:18:20 +02:00
|
|
|
|
2018-02-07 18:54:11 +01:00
|
|
|
if ((mysql_file_seek(info->file, wpos, MY_SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR))
|
2015-05-27 00:18:20 +02:00
|
|
|
{
|
|
|
|
info->error= -1;
|
|
|
|
DBUG_RETURN(1);
|
|
|
|
}
|
|
|
|
info->seek_not_done= 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info->pos_in_file == 0)
|
|
|
|
{
|
|
|
|
if (my_random_bytes(crypt_data->key, sizeof(crypt_data->key)))
|
|
|
|
{
|
|
|
|
my_errno= 1;
|
|
|
|
DBUG_RETURN(info->error= -1);
|
|
|
|
}
|
|
|
|
crypt_data->counter= 0;
|
|
|
|
|
|
|
|
IF_DBUG(crypt_data->block_length= 0,);
|
|
|
|
}
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
size_t length= MY_MIN(info->buffer_length, Count);
|
|
|
|
uint elength, wlength;
|
|
|
|
uchar iv[MY_AES_BLOCK_SIZE]= {0};
|
|
|
|
|
|
|
|
crypt_data->inbuf_counter= crypt_data->counter;
|
|
|
|
set_iv(iv, info->pos_in_file, crypt_data->inbuf_counter);
|
|
|
|
|
2018-02-06 13:55:58 +01:00
|
|
|
if (encryption_crypt(Buffer, (uint)length, ebuffer, &elength,
|
|
|
|
crypt_data->key, (uint) sizeof(crypt_data->key),
|
|
|
|
iv, (uint) sizeof(iv), ENCRYPTION_FLAG_ENCRYPT,
|
2015-09-04 10:32:52 +02:00
|
|
|
keyid, keyver))
|
2015-05-27 00:18:20 +02:00
|
|
|
{
|
|
|
|
my_errno= 1;
|
|
|
|
DBUG_RETURN(info->error= -1);
|
|
|
|
}
|
2017-10-03 21:43:43 +02:00
|
|
|
wlength= elength + (uint)(ebuffer - wbuffer);
|
2015-05-27 00:18:20 +02:00
|
|
|
|
|
|
|
if (length == info->buffer_length)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
block_length should be always the same. that is, encrypting
|
|
|
|
buffer_length bytes should *always* produce block_length bytes
|
|
|
|
*/
|
|
|
|
DBUG_ASSERT(crypt_data->block_length == 0 || crypt_data->block_length == wlength);
|
2018-02-06 13:55:58 +01:00
|
|
|
DBUG_ASSERT(elength <= encryption_encrypted_length((uint)length, keyid, keyver));
|
2015-05-27 00:18:20 +02:00
|
|
|
crypt_data->block_length= wlength;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* if we write a partial block, it *must* be the last write */
|
|
|
|
IF_DBUG(info->write_function= 0,);
|
|
|
|
crypt_data->last_block_length= wlength;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mysql_file_write(info->file, wbuffer, wlength, info->myflags | MY_NABP))
|
|
|
|
DBUG_RETURN(info->error= -1);
|
|
|
|
|
|
|
|
Buffer+= length;
|
|
|
|
Count-= length;
|
|
|
|
info->pos_in_file+= length;
|
|
|
|
crypt_data->counter++;
|
|
|
|
} while (Count);
|
|
|
|
DBUG_RETURN(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
determine what key id and key version to use for IO_CACHE temp files
|
|
|
|
|
|
|
|
First, try key id 2, if it doesn't exist, use key id 1.
|
|
|
|
|
|
|
|
(key id 1 is the default system key id, used pretty much everywhere, it must
|
|
|
|
exist. key id 2 is for tempfiles, it can be used, for example, to set a
|
|
|
|
faster encryption algorithm for temporary files)
|
|
|
|
|
|
|
|
This looks like it might have a bug: if an encryption plugin is unloaded when
|
|
|
|
there's an open IO_CACHE, that IO_CACHE will become unreadable after reinit.
|
|
|
|
But in fact it is safe, as an encryption plugin can only be unloaded on
|
|
|
|
server shutdown.
|
|
|
|
|
|
|
|
Note that encrypt_tmp_files variable is read-only.
|
|
|
|
*/
|
2015-09-08 17:07:34 +02:00
|
|
|
int init_io_cache_encryption()
|
2015-05-27 00:18:20 +02:00
|
|
|
{
|
|
|
|
if (encrypt_tmp_files)
|
|
|
|
{
|
2015-05-27 12:08:13 +02:00
|
|
|
keyid= ENCRYPTION_KEY_TEMPORARY_DATA;
|
|
|
|
keyver= encryption_key_get_latest_version(keyid);
|
2015-05-27 00:18:20 +02:00
|
|
|
if (keyver == ENCRYPTION_KEY_VERSION_INVALID)
|
2015-05-27 12:08:13 +02:00
|
|
|
{
|
|
|
|
keyid= ENCRYPTION_KEY_SYSTEM_DATA;
|
|
|
|
keyver= encryption_key_get_latest_version(keyid);
|
|
|
|
}
|
2015-09-08 17:07:34 +02:00
|
|
|
if (keyver == ENCRYPTION_KEY_VERSION_INVALID)
|
|
|
|
{
|
|
|
|
sql_print_error("Failed to enable encryption of temporary files");
|
|
|
|
return 1;
|
|
|
|
}
|
2015-05-27 00:18:20 +02:00
|
|
|
|
2015-09-08 17:07:34 +02:00
|
|
|
if (keyver != ENCRYPTION_KEY_NOT_ENCRYPTED)
|
|
|
|
{
|
|
|
|
sql_print_information("Using encryption key id %d for temporary files", keyid);
|
|
|
|
_my_b_encr_read= my_b_encr_read;
|
|
|
|
_my_b_encr_write= my_b_encr_write;
|
|
|
|
return 0;
|
|
|
|
}
|
2015-05-27 00:18:20 +02:00
|
|
|
}
|
2015-09-08 17:07:34 +02:00
|
|
|
|
|
|
|
_my_b_encr_read= 0;
|
|
|
|
_my_b_encr_write= 0;
|
|
|
|
return 0;
|
2015-05-27 00:18:20 +02:00
|
|
|
}
|
|
|
|
|