1 /* EVMC: Ethereum Client-VM Connector API.
2  * Copyright 2018-2019 The EVMC Authors.
3  * Licensed under the Apache License, Version 2.0.
4  */
5 
6 #include <evmc/loader.h>
7 
8 #include <evmc/evmc.h>
9 #include <evmc/helpers.h>
10 
11 #include <stdarg.h>
12 #include <stdint.h>
13 #include <stdio.h>
14 #include <string.h>
15 
16 #if defined(EVMC_LOADER_MOCK)
17 #include "../../test/unittests/loader_mock.h"
18 #elif defined(_WIN32)
19 #include <Windows.h>
20 #define DLL_HANDLE HMODULE
21 #define DLL_OPEN(filename) LoadLibrary(filename)
22 #define DLL_CLOSE(handle) FreeLibrary(handle)
23 #define DLL_GET_CREATE_FN(handle, name) (evmc_create_fn)(uintptr_t) GetProcAddress(handle, name)
24 #define DLL_GET_ERROR_MSG() NULL
25 #else
26 #include <dlfcn.h>
27 #define DLL_HANDLE void*
28 #define DLL_OPEN(filename) dlopen(filename, RTLD_LAZY)
29 #define DLL_CLOSE(handle) dlclose(handle)
30 #define DLL_GET_CREATE_FN(handle, name) (evmc_create_fn)(uintptr_t) dlsym(handle, name)
31 #define DLL_GET_ERROR_MSG() dlerror()
32 #endif
33 
34 #ifdef __has_attribute
35 #if __has_attribute(format)
36 #define ATTR_FORMAT(archetype, string_index, first_to_check) \
37     __attribute__((format(archetype, string_index, first_to_check)))
38 #endif
39 #endif
40 
41 #ifndef ATTR_FORMAT
42 #define ATTR_FORMAT(...)
43 #endif
44 
45 /*
46  * Limited variant of strcpy_s().
47  */
48 #if !defined(EVMC_LOADER_MOCK)
49 static
50 #endif
51     int
strcpy_sx(char * dest,size_t destsz,const char * src)52     strcpy_sx(char* dest, size_t destsz, const char* src)
53 {
54     size_t len = strlen(src);
55     if (len >= destsz)
56     {
57         // The input src will not fit into the dest buffer.
58         // Set the first byte of the dest to null to make it effectively empty string
59         // and return error.
60         dest[0] = 0;
61         return 1;
62     }
63     memcpy(dest, src, len);
64     dest[len] = 0;
65     return 0;
66 }
67 
68 #define PATH_MAX_LENGTH 4096
69 
70 static const char* last_error_msg = NULL;
71 
72 #define LAST_ERROR_MSG_BUFFER_SIZE 511
73 
74 // Buffer for formatted error messages.
75 // It has one null byte extra to avoid buffer read overflow during concurrent access.
76 static char last_error_msg_buffer[LAST_ERROR_MSG_BUFFER_SIZE + 1];
77 
78 ATTR_FORMAT(printf, 2, 3)
set_error(enum evmc_loader_error_code error_code,const char * format,...)79 static enum evmc_loader_error_code set_error(enum evmc_loader_error_code error_code,
80                                              const char* format,
81                                              ...)
82 {
83     va_list args;
84     va_start(args, format);
85     if (vsnprintf(last_error_msg_buffer, LAST_ERROR_MSG_BUFFER_SIZE, format, args) <
86         LAST_ERROR_MSG_BUFFER_SIZE)
87         last_error_msg = last_error_msg_buffer;
88     va_end(args);
89     return error_code;
90 }
91 
92 
evmc_load(const char * filename,enum evmc_loader_error_code * error_code)93 evmc_create_fn evmc_load(const char* filename, enum evmc_loader_error_code* error_code)
94 {
95     last_error_msg = NULL;  // Reset last error.
96     enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS;
97     evmc_create_fn create_fn = NULL;
98 
99     if (!filename)
100     {
101         ec = set_error(EVMC_LOADER_INVALID_ARGUMENT, "invalid argument: file name cannot be null");
102         goto exit;
103     }
104 
105     const size_t length = strlen(filename);
106     if (length == 0)
107     {
108         ec = set_error(EVMC_LOADER_INVALID_ARGUMENT, "invalid argument: file name cannot be empty");
109         goto exit;
110     }
111     else if (length > PATH_MAX_LENGTH)
112     {
113         ec = set_error(EVMC_LOADER_INVALID_ARGUMENT,
114                        "invalid argument: file name is too long (%d, maximum allowed length is %d)",
115                        (int)length, PATH_MAX_LENGTH);
116         goto exit;
117     }
118 
119     DLL_HANDLE handle = DLL_OPEN(filename);
120     if (!handle)
121     {
122         // Get error message if available.
123         last_error_msg = DLL_GET_ERROR_MSG();
124         if (last_error_msg)
125             ec = EVMC_LOADER_CANNOT_OPEN;
126         else
127             ec = set_error(EVMC_LOADER_CANNOT_OPEN, "cannot open %s", filename);
128         goto exit;
129     }
130 
131     // Create name buffer with the prefix.
132     const char prefix[] = "evmc_create_";
133     const size_t prefix_length = strlen(prefix);
134     char prefixed_name[sizeof(prefix) + PATH_MAX_LENGTH];
135     strcpy_sx(prefixed_name, sizeof(prefixed_name), prefix);
136 
137     // Find filename in the path.
138     const char* sep_pos = strrchr(filename, '/');
139 #ifdef _WIN32
140     // On Windows check also Windows classic path separator.
141     const char* sep_pos_windows = strrchr(filename, '\\');
142     sep_pos = sep_pos_windows > sep_pos ? sep_pos_windows : sep_pos;
143 #endif
144     const char* name_pos = sep_pos ? sep_pos + 1 : filename;
145 
146     // Skip "lib" prefix if present.
147     const char lib_prefix[] = "lib";
148     const size_t lib_prefix_length = strlen(lib_prefix);
149     if (strncmp(name_pos, lib_prefix, lib_prefix_length) == 0)
150         name_pos += lib_prefix_length;
151 
152     char* base_name = prefixed_name + prefix_length;
153     strcpy_sx(base_name, PATH_MAX_LENGTH, name_pos);
154 
155     // Trim all file extensions.
156     char* ext_pos = strchr(prefixed_name, '.');
157     if (ext_pos)
158         *ext_pos = 0;
159 
160     // Replace all "-" with "_".
161     char* dash_pos = base_name;
162     while ((dash_pos = strchr(dash_pos, '-')) != NULL)
163         *dash_pos++ = '_';
164 
165     // Search for the built function name.
166     create_fn = DLL_GET_CREATE_FN(handle, prefixed_name);
167 
168     if (!create_fn)
169         create_fn = DLL_GET_CREATE_FN(handle, "evmc_create");
170 
171     if (!create_fn)
172     {
173         DLL_CLOSE(handle);
174         ec = set_error(EVMC_LOADER_SYMBOL_NOT_FOUND, "EVMC create function not found in %s",
175                        filename);
176     }
177 
178 exit:
179     if (error_code)
180         *error_code = ec;
181     return create_fn;
182 }
183 
evmc_last_error_msg()184 const char* evmc_last_error_msg()
185 {
186     const char* m = last_error_msg;
187     last_error_msg = NULL;
188     return m;
189 }
190 
evmc_load_and_create(const char * filename,enum evmc_loader_error_code * error_code)191 struct evmc_vm* evmc_load_and_create(const char* filename, enum evmc_loader_error_code* error_code)
192 {
193     // First load the DLL. This also resets the last_error_msg;
194     evmc_create_fn create_fn = evmc_load(filename, error_code);
195 
196     if (!create_fn)
197         return NULL;
198 
199     enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS;
200 
201     struct evmc_vm* vm = create_fn();
202     if (!vm)
203     {
204         ec = set_error(EVMC_LOADER_VM_CREATION_FAILURE, "creating EVMC VM of %s has failed",
205                        filename);
206         goto exit;
207     }
208 
209     if (!evmc_is_abi_compatible(vm))
210     {
211         ec = set_error(EVMC_LOADER_ABI_VERSION_MISMATCH,
212                        "EVMC ABI version %d of %s mismatches the expected version %d",
213                        vm->abi_version, filename, EVMC_ABI_VERSION);
214         evmc_destroy(vm);
215         vm = NULL;
216         goto exit;
217     }
218 
219 exit:
220     if (error_code)
221         *error_code = ec;
222 
223     return vm;
224 }
225 
226 /// Gets the token delimited by @p delim character of the string pointed by the @p str_ptr.
227 /// If the delimiter is not found, the whole string is returned.
228 /// The @p str_ptr is also slided after the delimiter or to the string end
229 /// if the delimiter is not found (in this case the @p str_ptr points to an empty string).
get_token(char ** str_ptr,char delim)230 static char* get_token(char** str_ptr, char delim)
231 {
232     char* str = *str_ptr;
233     char* delim_pos = strchr(str, delim);
234     if (delim_pos)
235     {
236         // If the delimiter is found, null it to get null-terminated prefix
237         // and slide the str_ptr after the delimiter.
238         *delim_pos = '\0';
239         *str_ptr = delim_pos + 1;
240     }
241     else
242     {
243         // Otherwise, slide the str_ptr to the end and return the whole string as the prefix.
244         *str_ptr += strlen(str);
245     }
246     return str;
247 }
248 
evmc_load_and_configure(const char * config,enum evmc_loader_error_code * error_code)249 struct evmc_vm* evmc_load_and_configure(const char* config, enum evmc_loader_error_code* error_code)
250 {
251     enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS;
252     struct evmc_vm* vm = NULL;
253 
254     char config_copy_buffer[PATH_MAX_LENGTH];
255     if (strcpy_sx(config_copy_buffer, sizeof(config_copy_buffer), config) != 0)
256     {
257         ec = set_error(EVMC_LOADER_INVALID_ARGUMENT,
258                        "invalid argument: configuration is too long (maximum allowed length is %d)",
259                        (int)sizeof(config_copy_buffer));
260         goto exit;
261     }
262 
263     char* options = config_copy_buffer;
264     const char* path = get_token(&options, ',');
265 
266     vm = evmc_load_and_create(path, error_code);
267     if (!vm)
268         return NULL;
269 
270     if (vm->set_option == NULL && strlen(options) != 0)
271     {
272         ec = set_error(EVMC_LOADER_INVALID_OPTION_NAME, "%s (%s) does not support any options",
273                        vm->name, path);
274         goto exit;
275     }
276 
277 
278     while (strlen(options) != 0)
279     {
280         char* option = get_token(&options, ',');
281 
282         // Slit option into name and value by taking the name token.
283         // The option variable will have the value, can be empty.
284         const char* name = get_token(&option, '=');
285 
286         enum evmc_set_option_result r = vm->set_option(vm, name, option);
287         switch (r)
288         {
289         case EVMC_SET_OPTION_SUCCESS:
290             break;
291         case EVMC_SET_OPTION_INVALID_NAME:
292             ec = set_error(EVMC_LOADER_INVALID_OPTION_NAME, "%s (%s): unknown option '%s'",
293                            vm->name, path, name);
294             goto exit;
295         case EVMC_SET_OPTION_INVALID_VALUE:
296             ec = set_error(EVMC_LOADER_INVALID_OPTION_VALUE,
297                            "%s (%s): unsupported value '%s' for option '%s'", vm->name, path,
298                            option, name);
299             goto exit;
300 
301         default:
302             ec = set_error(EVMC_LOADER_INVALID_OPTION_VALUE,
303                            "%s (%s): unknown error when setting value '%s' for option '%s'",
304                            vm->name, path, option, name);
305             goto exit;
306         }
307     }
308 
309 exit:
310     if (error_code)
311         *error_code = ec;
312 
313     if (ec == EVMC_LOADER_SUCCESS)
314         return vm;
315 
316     if (vm)
317         evmc_destroy(vm);
318     return NULL;
319 }
320