/* EVMC: Ethereum Client-VM Connector API. * Copyright 2018-2019 The EVMC Authors. * Licensed under the Apache License, Version 2.0. */ #include #include #include #include #include #include #include #if defined(EVMC_LOADER_MOCK) #include "../../test/unittests/loader_mock.h" #elif _WIN32 #include #define DLL_HANDLE HMODULE #define DLL_OPEN(filename) LoadLibrary(filename) #define DLL_CLOSE(handle) FreeLibrary(handle) #define DLL_GET_CREATE_FN(handle, name) (evmc_create_fn)(uintptr_t) GetProcAddress(handle, name) #define DLL_GET_ERROR_MSG() NULL #else #include #define DLL_HANDLE void* #define DLL_OPEN(filename) dlopen(filename, RTLD_LAZY) #define DLL_CLOSE(handle) dlclose(handle) #define DLL_GET_CREATE_FN(handle, name) (evmc_create_fn)(uintptr_t) dlsym(handle, name) #define DLL_GET_ERROR_MSG() dlerror() #endif #ifdef __has_attribute #if __has_attribute(format) #define ATTR_FORMAT(archetype, string_index, first_to_check) \ __attribute__((format(archetype, string_index, first_to_check))) #endif #endif #ifndef ATTR_FORMAT #define ATTR_FORMAT(...) #endif /* * Limited variant of strcpy_s(). */ #if !defined(EVMC_LOADER_MOCK) static #endif int strcpy_sx(char* dest, size_t destsz, const char* src) { size_t len = strlen(src); if (len >= destsz) { // The input src will not fit into the dest buffer. // Set the first byte of the dest to null to make it effectively empty string // and return error. dest[0] = 0; return 1; } memcpy(dest, src, len); dest[len] = 0; return 0; } #define PATH_MAX_LENGTH 4096 static const char* last_error_msg = NULL; #define LAST_ERROR_MSG_BUFFER_SIZE 511 // Buffer for formatted error messages. // It has one null byte extra to avoid buffer read overflow during concurrent access. static char last_error_msg_buffer[LAST_ERROR_MSG_BUFFER_SIZE + 1]; ATTR_FORMAT(printf, 2, 3) static enum evmc_loader_error_code set_error(enum evmc_loader_error_code error_code, const char* format, ...) { va_list args; va_start(args, format); if (vsnprintf(last_error_msg_buffer, LAST_ERROR_MSG_BUFFER_SIZE, format, args) < LAST_ERROR_MSG_BUFFER_SIZE) last_error_msg = last_error_msg_buffer; va_end(args); return error_code; } evmc_create_fn evmc_load(const char* filename, enum evmc_loader_error_code* error_code) { last_error_msg = NULL; // Reset last error. enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS; evmc_create_fn create_fn = NULL; if (!filename) { ec = set_error(EVMC_LOADER_INVALID_ARGUMENT, "invalid argument: file name cannot be null"); goto exit; } const size_t length = strlen(filename); if (length == 0) { ec = set_error(EVMC_LOADER_INVALID_ARGUMENT, "invalid argument: file name cannot be empty"); goto exit; } else if (length > PATH_MAX_LENGTH) { ec = set_error(EVMC_LOADER_INVALID_ARGUMENT, "invalid argument: file name is too long (%d, maximum allowed length is %d)", (int)length, PATH_MAX_LENGTH); goto exit; } DLL_HANDLE handle = DLL_OPEN(filename); if (!handle) { // Get error message if available. last_error_msg = DLL_GET_ERROR_MSG(); if (last_error_msg) ec = EVMC_LOADER_CANNOT_OPEN; else ec = set_error(EVMC_LOADER_CANNOT_OPEN, "cannot open %s", filename); goto exit; } // Create name buffer with the prefix. const char prefix[] = "evmc_create_"; const size_t prefix_length = strlen(prefix); char prefixed_name[sizeof(prefix) + PATH_MAX_LENGTH]; strcpy_sx(prefixed_name, sizeof(prefixed_name), prefix); // Find filename in the path. const char* sep_pos = strrchr(filename, '/'); #if _WIN32 // On Windows check also Windows classic path separator. const char* sep_pos_windows = strrchr(filename, '\\'); sep_pos = sep_pos_windows > sep_pos ? sep_pos_windows : sep_pos; #endif const char* name_pos = sep_pos ? sep_pos + 1 : filename; // Skip "lib" prefix if present. const char lib_prefix[] = "lib"; const size_t lib_prefix_length = strlen(lib_prefix); if (strncmp(name_pos, lib_prefix, lib_prefix_length) == 0) name_pos += lib_prefix_length; char* base_name = prefixed_name + prefix_length; strcpy_sx(base_name, PATH_MAX_LENGTH, name_pos); // Trim all file extensions. char* ext_pos = strchr(prefixed_name, '.'); if (ext_pos) *ext_pos = 0; // Replace all "-" with "_". char* dash_pos = base_name; while ((dash_pos = strchr(dash_pos, '-')) != NULL) *dash_pos++ = '_'; // Search for the built function name. create_fn = DLL_GET_CREATE_FN(handle, prefixed_name); if (!create_fn) create_fn = DLL_GET_CREATE_FN(handle, "evmc_create"); if (!create_fn) { DLL_CLOSE(handle); ec = set_error(EVMC_LOADER_SYMBOL_NOT_FOUND, "EVMC create function not found in %s", filename); } exit: if (error_code) *error_code = ec; return create_fn; } const char* evmc_last_error_msg() { const char* m = last_error_msg; last_error_msg = NULL; return m; } struct evmc_vm* evmc_load_and_create(const char* filename, enum evmc_loader_error_code* error_code) { // First load the DLL. This also resets the last_error_msg; evmc_create_fn create_fn = evmc_load(filename, error_code); if (!create_fn) return NULL; enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS; struct evmc_vm* vm = create_fn(); if (!vm) { ec = set_error(EVMC_LOADER_VM_CREATION_FAILURE, "creating EVMC VM of %s has failed", filename); goto exit; } if (!evmc_is_abi_compatible(vm)) { ec = set_error(EVMC_LOADER_ABI_VERSION_MISMATCH, "EVMC ABI version %d of %s mismatches the expected version %d", vm->abi_version, filename, EVMC_ABI_VERSION); evmc_destroy(vm); vm = NULL; goto exit; } exit: if (error_code) *error_code = ec; return vm; } /// Gets the token delimited by @p delim character of the string pointed by the @p str_ptr. /// If the delimiter is not found, the whole string is returned. /// The @p str_ptr is also slided after the delimiter or to the string end /// if the delimiter is not found (in this case the @p str_ptr points to an empty string). static char* get_token(char** str_ptr, char delim) { char* str = *str_ptr; char* delim_pos = strchr(str, delim); if (delim_pos) { // If the delimiter is found, null it to get null-terminated prefix // and slide the str_ptr after the delimiter. *delim_pos = '\0'; *str_ptr = delim_pos + 1; } else { // Otherwise, slide the str_ptr to the end and return the whole string as the prefix. *str_ptr += strlen(str); } return str; } struct evmc_vm* evmc_load_and_configure(const char* config, enum evmc_loader_error_code* error_code) { enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS; struct evmc_vm* vm = NULL; char config_copy_buffer[PATH_MAX_LENGTH]; if (strcpy_sx(config_copy_buffer, sizeof(config_copy_buffer), config) != 0) { ec = set_error(EVMC_LOADER_INVALID_ARGUMENT, "invalid argument: configuration is too long (maximum allowed length is %d)", (int)sizeof(config_copy_buffer)); goto exit; } char* options = config_copy_buffer; const char* path = get_token(&options, ','); vm = evmc_load_and_create(path, error_code); if (!vm) return NULL; if (vm->set_option == NULL && strlen(options) != 0) { ec = set_error(EVMC_LOADER_INVALID_OPTION_NAME, "%s (%s) does not support any options", vm->name, path); goto exit; } while (strlen(options) != 0) { char* option = get_token(&options, ','); // Slit option into name and value by taking the name token. // The option variable will have the value, can be empty. const char* name = get_token(&option, '='); enum evmc_set_option_result r = vm->set_option(vm, name, option); switch (r) { case EVMC_SET_OPTION_SUCCESS: break; case EVMC_SET_OPTION_INVALID_NAME: ec = set_error(EVMC_LOADER_INVALID_OPTION_NAME, "%s (%s): unknown option '%s'", vm->name, path, name); goto exit; case EVMC_SET_OPTION_INVALID_VALUE: ec = set_error(EVMC_LOADER_INVALID_OPTION_VALUE, "%s (%s): unsupported value '%s' for option '%s'", vm->name, path, option, name); goto exit; default: ec = set_error(EVMC_LOADER_INVALID_OPTION_VALUE, "%s (%s): unknown error when setting value '%s' for option '%s'", vm->name, path, option, name); goto exit; } } exit: if (error_code) *error_code = ec; if (ec == EVMC_LOADER_SUCCESS) return vm; if (vm) evmc_destroy(vm); return NULL; }