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