1 /*
2 Copyright (c) 2015, 2020, MariaDB
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; version 2 of the License.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
16
17 /*************************************************************************
18 Limitation of encrypted IO_CACHEs
19 1. Designed to support temporary files only (open_cached_file, fd=-1)
20 2. Created with WRITE_CACHE, later can be reinit_io_cache'ed to
21 READ_CACHE and WRITE_CACHE in any order arbitrary number of times.
22 3. no seeks for writes, but reinit_io_cache(WRITE_CACHE, seek_offset)
23 is allowed (there's a special hack in reinit_io_cache() for that)
24 */
25
26 #include "../mysys/mysys_priv.h"
27 #include "log.h"
28 #include "mysqld.h"
29 #include "sql_class.h"
30
31 static uint keyid, keyver;
32
33 #define set_iv(IV, N1, N2) \
34 do { \
35 compile_time_assert(sizeof(IV) >= sizeof(N1) + sizeof(N2)); \
36 memcpy(IV, &(N1), sizeof(N1)); \
37 memcpy(IV + sizeof(N1), &(N2), sizeof(N2)); \
38 } while(0)
39
my_b_encr_read(IO_CACHE * info,uchar * Buffer,size_t Count)40 static int my_b_encr_read(IO_CACHE *info, uchar *Buffer, size_t Count)
41 {
42 my_off_t pos_in_file= info->pos_in_file + (info->read_end - info->buffer);
43 my_off_t old_pos_in_file= pos_in_file, pos_offset= 0;
44 IO_CACHE_CRYPT *crypt_data=
45 (IO_CACHE_CRYPT *)(info->buffer + info->buffer_length + MY_AES_BLOCK_SIZE);
46 uchar *wbuffer= (uchar*)&(crypt_data->inbuf_counter);
47 uchar *ebuffer= (uchar*)(crypt_data + 1);
48 DBUG_ENTER("my_b_encr_read");
49
50 if (pos_in_file == info->end_of_file)
51 {
52 /* reading past EOF should not empty the cache */
53 info->read_pos= info->read_end;
54 info->error= 0;
55 DBUG_RETURN(MY_TEST(Count));
56 }
57
58 if (info->seek_not_done)
59 {
60 my_off_t wpos;
61
62 pos_offset= pos_in_file % info->buffer_length;
63 pos_in_file-= pos_offset;
64
65 wpos= pos_in_file / info->buffer_length * crypt_data->block_length;
66
67 if ((mysql_file_seek(info->file, wpos, MY_SEEK_SET, MYF(0))
68 == MY_FILEPOS_ERROR))
69 {
70 info->error= -1;
71 DBUG_RETURN(1);
72 }
73 info->seek_not_done= 0;
74 if (info->next_file_user)
75 {
76 IO_CACHE *c;
77 for (c= info->next_file_user;
78 c!= info;
79 c= c->next_file_user)
80 {
81 c->seek_not_done= 1;
82 }
83 }
84 }
85
86 do
87 {
88 uint elength, wlength, length;
89 uchar iv[MY_AES_BLOCK_SIZE]= {0};
90
91 DBUG_ASSERT(pos_in_file % info->buffer_length == 0);
92
93 if (info->end_of_file - pos_in_file >= info->buffer_length)
94 wlength= crypt_data->block_length;
95 else
96 wlength= crypt_data->last_block_length;
97
98 if (mysql_file_read(info->file, wbuffer, wlength, info->myflags | MY_NABP))
99 {
100 info->error= -1;
101 DBUG_RETURN(1);
102 }
103
104 elength= wlength - (uint)(ebuffer - wbuffer);
105 set_iv(iv, pos_in_file, crypt_data->inbuf_counter);
106
107 if (encryption_crypt(ebuffer, elength, info->buffer, &length,
108 crypt_data->key, sizeof(crypt_data->key),
109 iv, sizeof(iv), ENCRYPTION_FLAG_DECRYPT,
110 keyid, keyver))
111 {
112 my_errno= 1;
113 DBUG_RETURN(info->error= -1);
114 }
115
116 DBUG_ASSERT(length <= info->buffer_length);
117
118 size_t copied= MY_MIN(Count, (size_t)(length - pos_offset));
119 if (copied)
120 {
121 memcpy(Buffer, info->buffer + pos_offset, copied);
122 Count-= copied;
123 Buffer+= copied;
124 }
125
126 info->read_pos= info->buffer + pos_offset + copied;
127 info->read_end= info->buffer + length;
128 info->pos_in_file= pos_in_file;
129 pos_in_file+= length;
130 pos_offset= 0;
131
132 if (wlength < crypt_data->block_length && pos_in_file < info->end_of_file)
133 {
134 info->error= (int)(pos_in_file - old_pos_in_file);
135 DBUG_RETURN(1);
136 }
137 } while (Count);
138
139 DBUG_RETURN(0);
140 }
141
my_b_encr_write(IO_CACHE * info,const uchar * Buffer,size_t Count)142 static int my_b_encr_write(IO_CACHE *info, const uchar *Buffer, size_t Count)
143 {
144 IO_CACHE_CRYPT *crypt_data=
145 (IO_CACHE_CRYPT *)(info->buffer + info->buffer_length + MY_AES_BLOCK_SIZE);
146 uchar *wbuffer= (uchar*)&(crypt_data->inbuf_counter);
147 uchar *ebuffer= (uchar*)(crypt_data + 1);
148 DBUG_ENTER("my_b_encr_write");
149
150 if (Buffer != info->write_buffer)
151 {
152 Count-= Count % info->buffer_length;
153 if (!Count)
154 DBUG_RETURN(0);
155 }
156
157 if (info->seek_not_done)
158 {
159 DBUG_ASSERT(info->pos_in_file % info->buffer_length == 0);
160 my_off_t wpos= info->pos_in_file / info->buffer_length * crypt_data->block_length;
161
162 if ((mysql_file_seek(info->file, wpos, MY_SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR))
163 {
164 info->error= -1;
165 DBUG_RETURN(1);
166 }
167 info->seek_not_done= 0;
168 }
169
170 if (info->pos_in_file == 0)
171 {
172 if (my_random_bytes(crypt_data->key, sizeof(crypt_data->key)))
173 {
174 my_errno= 1;
175 DBUG_RETURN(info->error= -1);
176 }
177 crypt_data->counter= 0;
178
179 IF_DBUG(crypt_data->block_length= 0,);
180 }
181
182 do
183 {
184 size_t length= MY_MIN(info->buffer_length, Count);
185 uint elength, wlength;
186 uchar iv[MY_AES_BLOCK_SIZE]= {0};
187
188 crypt_data->inbuf_counter= crypt_data->counter;
189 set_iv(iv, info->pos_in_file, crypt_data->inbuf_counter);
190
191 if (encryption_crypt(Buffer, (uint)length, ebuffer, &elength,
192 crypt_data->key, (uint) sizeof(crypt_data->key),
193 iv, (uint) sizeof(iv), ENCRYPTION_FLAG_ENCRYPT,
194 keyid, keyver))
195 {
196 my_errno= 1;
197 DBUG_RETURN(info->error= -1);
198 }
199 wlength= elength + (uint)(ebuffer - wbuffer);
200
201 if (length == info->buffer_length)
202 {
203 /*
204 block_length should be always the same. that is, encrypting
205 buffer_length bytes should *always* produce block_length bytes
206 */
207 DBUG_ASSERT(crypt_data->block_length == 0 || crypt_data->block_length == wlength);
208 DBUG_ASSERT(elength <= encryption_encrypted_length((uint)length, keyid, keyver));
209 crypt_data->block_length= wlength;
210 }
211 else
212 {
213 /* if we write a partial block, it *must* be the last write */
214 IF_DBUG(info->write_function= 0,);
215 crypt_data->last_block_length= wlength;
216 }
217
218 if (mysql_file_write(info->file, wbuffer, wlength, info->myflags | MY_NABP))
219 DBUG_RETURN(info->error= -1);
220
221 Buffer+= length;
222 Count-= length;
223 info->pos_in_file+= length;
224 crypt_data->counter++;
225 } while (Count);
226 DBUG_RETURN(0);
227 }
228
229 /**
230 determine what key id and key version to use for IO_CACHE temp files
231
232 First, try key id 2, if it doesn't exist, use key id 1.
233
234 (key id 1 is the default system key id, used pretty much everywhere, it must
235 exist. key id 2 is for tempfiles, it can be used, for example, to set a
236 faster encryption algorithm for temporary files)
237
238 This looks like it might have a bug: if an encryption plugin is unloaded when
239 there's an open IO_CACHE, that IO_CACHE will become unreadable after reinit.
240 But in fact it is safe, as an encryption plugin can only be unloaded on
241 server shutdown.
242
243 Note that encrypt_tmp_files variable is read-only.
244 */
init_io_cache_encryption()245 int init_io_cache_encryption()
246 {
247 if (encrypt_tmp_files)
248 {
249 keyid= ENCRYPTION_KEY_TEMPORARY_DATA;
250 keyver= encryption_key_get_latest_version(keyid);
251 if (keyver == ENCRYPTION_KEY_VERSION_INVALID)
252 {
253 keyid= ENCRYPTION_KEY_SYSTEM_DATA;
254 keyver= encryption_key_get_latest_version(keyid);
255 }
256 if (keyver == ENCRYPTION_KEY_VERSION_INVALID)
257 {
258 sql_print_error("Failed to enable encryption of temporary files");
259 return 1;
260 }
261
262 if (keyver != ENCRYPTION_KEY_NOT_ENCRYPTED)
263 {
264 sql_print_information("Using encryption key id %d for temporary files", keyid);
265 _my_b_encr_read= my_b_encr_read;
266 _my_b_encr_write= my_b_encr_write;
267 return 0;
268 }
269 }
270
271 _my_b_encr_read= 0;
272 _my_b_encr_write= 0;
273 return 0;
274 }
275
276