Line data Source code
1 : /* Copyright (C) 2020 Wildfire Games.
2 : *
3 : * Permission is hereby granted, free of charge, to any person obtaining
4 : * a copy of this software and associated documentation files (the
5 : * "Software"), to deal in the Software without restriction, including
6 : * without limitation the rights to use, copy, modify, merge, publish,
7 : * distribute, sublicense, and/or sell copies of the Software, and to
8 : * permit persons to whom the Software is furnished to do so, subject to
9 : * the following conditions:
10 : *
11 : * The above copyright notice and this permission notice shall be included
12 : * in all copies or substantial portions of the Software.
13 : *
14 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 : * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 : * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 : * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 : * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 : * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 : * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 : */
22 :
23 : /*
24 : * partial implementation of VC8's secure CRT functions
25 : */
26 :
27 : #include "precompiled.h"
28 :
29 : #include <cstdio>
30 : #include <cstring>
31 : #include <cerrno>
32 : #include <cstdarg>
33 :
34 : #include "lib/secure_crt.h"
35 :
36 : #if OS_ANDROID
37 : # include <boost/algorithm/string/replace.hpp>
38 : #endif
39 :
40 : // we were included from wsecure_crt.cpp; skip all stuff that
41 : // must only be done once.
42 : #ifndef WSECURE_CRT
43 : static const StatusDefinition secureCrtStatusDefinitions[] = {
44 : { ERR::STRING_NOT_TERMINATED, L"Invalid string (no 0 terminator found in buffer)" }
45 : };
46 1 : STATUS_ADD_DEFINITIONS(secureCrtStatusDefinitions);
47 : #endif
48 :
49 :
50 : // written against http://std.dkuug.dk/jtc1/sc22/wg14/www/docs/n1031.pdf .
51 : // optimized for size - e.g. strcpy calls strncpy with n = SIZE_MAX.
52 :
53 : // since char and wide versions of these functions are basically the same,
54 : // this source file implements generic versions and bridges the differences
55 : // with these macros. wsecure_crt.cpp #defines WSECURE_CRT and
56 : // includes this file.
57 :
58 : // Note: These defines are all #undef:ed at the end of the file - remember to
59 : // add a corresponding #undef when adding a #define.
60 : #ifdef WSECURE_CRT
61 : # define tchar wchar_t
62 : # define tstring std::wstring
63 : # define T(string_literal) L ## string_literal
64 : # define tnlen wcsnlen
65 : # define tncpy_s wcsncpy_s
66 : # define tcpy_s wcscpy_s
67 : # define tncat_s wcsncat_s
68 : # define tcat_s wcscat_s
69 : # define tcmp wcscmp
70 : # define tcpy wcscpy
71 : # define tvsnprintf vswprintf // used by implementation
72 : # define tvsprintf_s vswprintf_s
73 : # define tsprintf_s swprintf_s
74 : #else
75 : # define tchar char
76 : # define tstring std::string
77 : # define T(string_literal) string_literal
78 : # define tnlen strnlen
79 : # define tncpy_s strncpy_s
80 : # define tcpy_s strcpy_s
81 : # define tncat_s strncat_s
82 : # define tcat_s strcat_s
83 : # define tcmp strcmp
84 : # define tcpy strcpy
85 : # define tvsnprintf vsnprintf // used by implementation
86 : # define tvsprintf_s vsprintf_s
87 : # define tsprintf_s sprintf_s
88 : #endif // #ifdef WSECURE_CRT
89 :
90 :
91 : // return <retval> and raise an assertion if <condition> doesn't hold.
92 : // usable as a statement.
93 : #define ENFORCE(condition, err_to_warn, retval) STMT(\
94 : if(!(condition)) \
95 : { \
96 : DEBUG_WARN_ERR(err_to_warn); \
97 : return retval; \
98 : } \
99 : )
100 :
101 : // raise a debug warning if <len> is the size of a pointer.
102 : // catches bugs such as: tchar* s = ..; tcpy_s(s, sizeof(s), T(".."));
103 : // if warnings get annoying, replace with debug_printf. usable as a statement.
104 : //
105 : // currently disabled due to high risk of false positives.
106 : #define WARN_IF_PTR_LEN(len)\
107 : /*
108 : ENSURE(len != sizeof(char*));
109 : */
110 :
111 :
112 : // skip our implementation if already available, but not the
113 : // self-test and the t* defines (needed for test).
114 : #if EMULATE_SECURE_CRT
115 :
116 : #if !OS_UNIX || OS_MACOSX || OS_OPENBSD
117 : // return length [in characters] of a string, not including the trailing
118 : // null character. to protect against access violations, only the
119 : // first <max_len> characters are examined; if the null character is
120 : // not encountered by then, <max_len> is returned.
121 : size_t tnlen(const tchar* str, size_t max_len)
122 : {
123 : // note: we can't bail - what would the return value be?
124 : ENSURE(str != 0);
125 :
126 : WARN_IF_PTR_LEN(max_len);
127 :
128 : size_t len;
129 : for(len = 0; len < max_len; len++)
130 : if(*str++ == '\0')
131 : break;
132 :
133 : return len;
134 : }
135 : #endif // !OS_UNIX
136 :
137 : #if OS_ANDROID
138 : static tstring androidFormat(const tchar *fmt)
139 : {
140 : // FIXME handle %%hs, %%ls, etc
141 : tstring ret(fmt);
142 : boost::algorithm::replace_all(ret, T("%ls"), T("%S"));
143 : boost::algorithm::replace_all(ret, T("%hs"), T("%s"));
144 : return ret;
145 : }
146 : #endif
147 :
148 : // copy at most <max_src_chars> (not including trailing null) from
149 : // <src> into <dst>, which must not overlap.
150 : // if thereby <max_dst_chars> (including null) would be exceeded,
151 : // <dst> is set to the empty string and ERANGE returned; otherwise,
152 : // 0 is returned to indicate success and that <dst> is null-terminated.
153 : //
154 : // note: padding with zeroes is not called for by N1031.
155 5137 : int tncpy_s(tchar* dst, size_t max_dst_chars, const tchar* src, size_t max_src_chars)
156 : {
157 : // the MS implementation returns EINVAL and allows dst = 0 if
158 : // max_dst_chars = max_src_chars = 0. no mention of this in
159 : // 3.6.2.1.1, so don't emulate that behavior.
160 5137 : ENFORCE(dst != 0, ERR::INVALID_POINTER, EINVAL);
161 5134 : ENFORCE(max_dst_chars != 0, ERR::INVALID_SIZE, EINVAL); // N1031 says ERANGE, MSDN/MSVC says EINVAL
162 5133 : *dst = '\0'; // in case src ENFORCE is triggered
163 5133 : ENFORCE(src != 0, ERR::INVALID_POINTER, EINVAL);
164 :
165 : WARN_IF_PTR_LEN(max_dst_chars);
166 : WARN_IF_PTR_LEN(max_src_chars);
167 :
168 : // copy string until null character encountered or limit reached.
169 : // optimized for size (less comparisons than MS impl) and
170 : // speed (due to well-predicted jumps; we don't bother unrolling).
171 5131 : tchar* p = dst;
172 5131 : size_t chars_left = std::min(max_dst_chars, max_src_chars);
173 103055 : while(chars_left != 0)
174 : {
175 : // success: reached end of string normally.
176 53634 : if((*p++ = *src++) == '\0')
177 4672 : return 0;
178 48962 : chars_left--;
179 : }
180 :
181 : // which limit did we hit?
182 : // .. dst, and last character wasn't null: overflow.
183 459 : if(max_dst_chars <= max_src_chars)
184 : {
185 16 : *dst = '\0';
186 16 : ENFORCE(0, ERR::INVALID_SIZE, ERANGE);
187 : }
188 : // .. source: success, but still need to null-terminate the destination.
189 443 : *p = '\0';
190 443 : return 0;
191 : }
192 :
193 :
194 : // copy <src> (including trailing null) into <dst>, which must not overlap.
195 : // if thereby <max_dst_chars> (including null) would be exceeded,
196 : // <dst> is set to the empty string and ERANGE returned; otherwise,
197 : // 0 is returned to indicate success and that <dst> is null-terminated.
198 4673 : int tcpy_s(tchar* dst, size_t max_dst_chars, const tchar* src)
199 : {
200 4673 : return tncpy_s(dst, max_dst_chars, src, SIZE_MAX);
201 : }
202 :
203 :
204 : // append <src> to <dst>, which must not overlap.
205 : // if thereby <max_dst_chars> (including null) would be exceeded,
206 : // <dst> is set to the empty string and ERANGE returned; otherwise,
207 : // 0 is returned to indicate success and that <dst> is null-terminated.
208 31 : int tncat_s(tchar* dst, size_t max_dst_chars, const tchar* src, size_t max_src_chars)
209 : {
210 31 : ENFORCE(dst != 0, ERR::INVALID_POINTER, EINVAL);
211 28 : ENFORCE(max_dst_chars != 0, ERR::INVALID_SIZE, EINVAL); // N1031 says ERANGE, MSDN/MSVC says EINVAL
212 : // src is checked in tncpy_s
213 :
214 : // WARN_IF_PTR_LEN not necessary: both max_dst_chars and max_src_chars
215 : // are checked by tnlen / tncpy_s (respectively).
216 :
217 27 : const size_t dst_len = tnlen(dst, max_dst_chars);
218 27 : if(dst_len == max_dst_chars)
219 : {
220 1 : *dst = '\0';
221 1 : ENFORCE(0, ERR::STRING_NOT_TERMINATED, EINVAL); // N1031/MSDN says ERANGE, MSVC says EINVAL
222 : }
223 :
224 26 : tchar* const end = dst+dst_len;
225 26 : const size_t chars_left = max_dst_chars-dst_len;
226 26 : int ret = tncpy_s(end, chars_left, src, max_src_chars);
227 : // if tncpy_s overflowed, we need to clear the start of our string
228 : // (not just the appended part). can't do that by default, because
229 : // the beginning of dst is not changed in normal operation.
230 26 : if(ret != 0)
231 11 : *dst = '\0';
232 26 : return ret;
233 : }
234 :
235 :
236 : // append <src> to <dst>, which must not overlap.
237 : // if thereby <max_dst_chars> (including null) would be exceeded,
238 : // <dst> is set to the empty string and ERANGE returned; otherwise,
239 : // 0 is returned to indicate success and that <dst> is null-terminated.
240 : //
241 : // note: implemented as tncat_s(dst, max_dst_chars, src, SIZE_MAX)
242 16 : int tcat_s(tchar* dst, size_t max_dst_chars, const tchar* src)
243 : {
244 16 : return tncat_s(dst, max_dst_chars, src, SIZE_MAX);
245 : }
246 :
247 :
248 441 : int tvsprintf_s(tchar* dst, size_t max_dst_chars, const tchar* fmt, va_list ap)
249 : {
250 441 : if(!dst || !fmt || max_dst_chars == 0)
251 : {
252 6 : errno = EINVAL;
253 6 : return -1;
254 : }
255 :
256 : #if OS_ANDROID
257 : // Workaround for https://code.google.com/p/android/issues/detail?id=109074
258 : // (vswprintf doesn't null-terminate strings)
259 : memset(dst, 0, max_dst_chars * sizeof(tchar));
260 :
261 : const int ret = tvsnprintf(dst, max_dst_chars, androidFormat(fmt).c_str(), ap);
262 : #else
263 435 : const int ret = tvsnprintf(dst, max_dst_chars, fmt, ap);
264 : #endif
265 435 : if(ret < 0 || ret >= int(max_dst_chars)) // not enough space
266 : {
267 4 : dst[0] = '\0';
268 4 : return -1;
269 : }
270 431 : return ret; // negative if error, else # chars written (excluding '\0')
271 : }
272 :
273 :
274 399 : int tsprintf_s(tchar* buf, size_t max_chars, const tchar* fmt, ...)
275 : {
276 : va_list ap;
277 399 : va_start(ap, fmt);
278 399 : int len = tvsprintf_s(buf, max_chars, fmt, ap);
279 399 : va_end(ap);
280 399 : return len;
281 6 : }
282 :
283 : #endif // #if EMULATE_SECURE_CRT
284 :
285 : #undef tchar
286 : #undef T
287 : #undef tnlen
288 : #undef tncpy_s
289 : #undef tcpy_s
290 : #undef tncat_s
291 : #undef tcat_s
292 : #undef tcmp
293 : #undef tcpy
294 : #undef tvsnprintf
295 : #undef tvsprintf_s
296 : #undef tsprintf_s
|