1 /* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License, version 2.0,
5    as published by the Free Software Foundation.
6 
7    This program is also distributed with certain software (including
8    but not limited to OpenSSL) that is licensed under separate terms,
9    as designated in a particular file or component or in included license
10    documentation.  The authors of MySQL hereby grant you an additional
11    permission to link the program and your derivative works with the
12    separately licensed software that they have included with MySQL.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License, version 2.0, for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
22 
23 #include "plugin/keyring/checker/checker.h"
24 
25 #include <mysql/psi/mysql_file.h>
26 #include <memory>
27 
28 #include "my_compiler.h"
29 
30 namespace keyring {
31 
32 const my_off_t Checker::EOF_TAG_SIZE = 3;
33 const std::string Checker::eofTAG = "EOF";
34 
35 /**
36   checks if keyring file structure is invalid
37   @param[in] file       - file handle to be checked
38   @param[in] file_size  - total file size
39   @param[in] digest     - file digest value
40   @param[out] arch       - (optional) architecture type of file int lengths
41 
42   @retval false   - file structure is valid
43   @retval true    - file structure is invalid
44  */
check_file_structure(File file,size_t file_size,Digest * digest,Converter::Arch * arch)45 bool Checker::check_file_structure(File file, size_t file_size, Digest *digest,
46                                    Converter::Arch *arch) {
47   // should we detect file architecture
48   if (arch != nullptr) {
49     // detect architecture, leave on error
50     *arch = detect_architecture(file, file_size);
51     if (*arch == Converter::Arch::UNKNOWN) return true;
52   }
53 
54   // default assumptions
55   bool is_invalid = true;
56 
57   // file size affects required validation steps
58   if (file_size == 0)
59     is_invalid = !is_empty_file_correct(digest);
60   else
61     is_invalid = !is_file_size_correct(file_size) ||
62                  !is_file_tag_correct(file) || !is_file_version_correct(file) ||
63                  !is_dgst_correct(file, digest);
64 
65   return is_invalid;
66 }
67 
is_empty_file_correct(Digest * digest)68 bool Checker::is_empty_file_correct(Digest *digest) {
69   return strlen(dummy_digest) == digest->length &&
70          strncmp(dummy_digest, reinterpret_cast<const char *>(digest->value),
71                  std::min(static_cast<unsigned int>(strlen(dummy_digest)),
72                           digest->length)) == 0;
73 }
74 
is_file_tag_correct(File file)75 bool Checker::is_file_tag_correct(File file) {
76   uchar tag[EOF_TAG_SIZE + 1];
77   mysql_file_seek(file, 0, MY_SEEK_END, MYF(0));
78   if (unlikely(mysql_file_tell(file, MYF(0)) < EOF_TAG_SIZE))
79     return false;  // File does not contain tag
80 
81   if (file_seek_to_tag(file) ||
82       unlikely(mysql_file_read(file, tag, EOF_TAG_SIZE, MYF(0)) !=
83                EOF_TAG_SIZE))
84     return false;
85   tag[3] = '\0';
86   mysql_file_seek(file, 0, MY_SEEK_SET, MYF(0));
87   return eofTAG == reinterpret_cast<char *>(tag);
88 }
89 
is_file_version_correct(File file)90 bool Checker::is_file_version_correct(File file) {
91   std::unique_ptr<uchar[]> version(new uchar[file_version.length() + 1]);
92   version.get()[file_version.length()] = '\0';
93   mysql_file_seek(file, 0, MY_SEEK_SET, MYF(0));
94   if (unlikely(mysql_file_read(file, version.get(), file_version.length(),
95                                MYF(0)) != file_version.length() ||
96                file_version != reinterpret_cast<char *>(version.get())))
97     return false;
98 
99   mysql_file_seek(file, 0, MY_SEEK_SET, MYF(0));
100   return true;
101 }
102 
103 /**
104   detects machine architecture of serialized key data length
105  */
detect_architecture(File file,size_t file_size)106 Converter::Arch Checker::detect_architecture(File file, size_t file_size) {
107   // empty file should use default integer format
108   auto native_arch = Converter::get_native_arch();
109   if (file_size == 0 || file_size == file_version.length() + eof_size())
110     return native_arch;
111 
112   // determine detection order for candidates
113   Converter::Arch detection_order[] = {
114       Converter::Arch::LE_64, Converter::Arch::LE_32, Converter::Arch::BE_64,
115       Converter::Arch::BE_32};
116 
117   // length conversion variables
118   uchar src[8] = {0};
119   char dst[8] = {0};
120   size_t length[5] = {0};
121 
122   for (auto arch : detection_order) {
123     size_t location = file_version.length();
124     bool skip_arch = false;
125 
126     // determine new word width, rewind the keyring file
127     auto arch_width = Converter::get_width(arch);
128     if (mysql_file_seek(file, location, MY_SEEK_SET, MYF(0)) ==
129         MY_FILEPOS_ERROR)
130       return Converter::Arch::UNKNOWN;
131 
132     // we'll read if there's at least one key worth of data ahead
133     while (location + 5 * arch_width + eof_size() <= file_size) {
134       // load and calculate sizes
135       for (size_t i = 0; i < 5; i++) {
136         // failure to read is detection failure
137         if (mysql_file_read(file, src, arch_width, MYF(0)) != arch_width)
138           return Converter::Arch::UNKNOWN;
139 
140         // conversion must be possible
141         if (!Converter::convert(reinterpret_cast<char *>(src), dst, arch,
142                                 native_arch)) {
143           skip_arch = true;
144           break;
145         }
146 
147         // store dimensions, increase position
148         length[i] = Converter::native_value(dst);
149         location += arch_width;
150       }
151 
152       if (skip_arch) break;
153 
154       // key size has to be memory aligned
155       if (length[0] % arch_width != 0) {
156         skip_arch = true;
157         break;
158       }
159 
160       // verify that native values add up within padding size
161       auto total =
162           5 * arch_width + length[1] + length[2] + length[3] + length[4];
163       if (total > length[0] || total + arch_width < length[0]) {
164         skip_arch = true;
165         break;
166       }
167 
168       // we move location according to total size
169       location += length[0] - 5 * arch_width;
170       mysql_file_seek(file, location, MY_SEEK_SET, MYF(0));
171     }
172 
173     if (skip_arch) continue;
174 
175     // there were no errors - detection is successful
176     if (location == file_size - eof_size()) return arch;
177   }
178 
179   return Converter::Arch::UNKNOWN;
180 }
181 
182 }  // namespace keyring
183