/* * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Don't lint the next line, as cpplint will suggest adding // /tools/security as an include_dir // NOLINTNEXTLINE #include "fuzz_ffi.h" #include #include "include/ffi_common.h" // Empty functions we can use for our function targets void fn(int num_args, ...) {} void closure_fn(ffi_cif* cif __UNUSED__, void* resp, void** args, void* userdata) {} void raw_closure_fn(ffi_cif* cif __UNUSED__, void* resp, ffi_raw* args, void* userdata) {} void java_raw_closure_fn(ffi_cif* cif __UNUSED__, void* resp, ffi_java_raw* args, void* userdata) {} ffi_type* generateCustomType(FuzzedDataProvider* dataProvider) { // Set our flag so we don't call a java-related function (triggers an abort) args_contain_struct = true; ffi_type* new_type = reinterpret_cast(malloc(sizeof(ffi_type))); ffi_alloc_vector.push_back(new_type); new_type->size = 0; new_type->alignment = 0; new_type->type = FFI_TYPE_STRUCT; // Generate our subobjects size_t num_elements = dataProvider->ConsumeIntegralInRange(0, MAX_NUM_ELEMENTS); new_type->elements = reinterpret_cast( malloc(sizeof(ffi_type*)*(num_elements+1))); // Nested custom structs will cause an assert, so disable them // TODO(michael.ensing@leviathansecurity.com): // change the 'false' here to true once libffi supports nested structs. // It'll just throw an assert currently. for (size_t i=0; i < num_elements; i++) { new_type->elements[i] = getRandomType(dataProvider, false); } // The final element must be a nullptr new_type->elements[num_elements] = NULL; // Get our size/alignment ffi_get_struct_offsets(abi, new_type, NULL); return new_type; } size_t getTotalSize(ffi_type* type) { if (type == NULL) { return 0; } // Start the total as the size of the object itself size_t total_size = type->size > WORDSIZE_BYTES ? type->size : WORDSIZE_BYTES; // Recursively add the size of the subelements if (type->elements != NULL) { for (size_t i=0; type->elements[i] != NULL; i++) { total_size += getTotalSize(type->elements[i]); } } return total_size; } ffi_type* getRandomType(FuzzedDataProvider* dataProvider, bool allowCustomTypes) { // Which type? Let type==NUM_TYPES be our custom struct case size_t type_index = dataProvider->ConsumeIntegralInRange(0, NUM_TYPES); ffi_type* type; if (type_index == NUM_TYPES) { if (allowCustomTypes) { type = generateCustomType(dataProvider); } else { return NULL; } } else { type = ffi_types[type_index]; } return type; } void* genArg(ffi_type* type, FuzzedDataProvider* dataProvider) { // Allocate the space for our arg // TODO(michael.ensing@leviathansecurity.com): // Properly allocate the correct amount of aligned-space, // don't just double (which should contain any alignment) size_t type_size = getTotalSize(type)*2; if (type_size == 0) { return NULL; } void* ret = malloc(type_size); std::vector bytes = dataProvider->ConsumeBytes(type_size); memcpy(ret, bytes.data(), bytes.size()); return ret; } bool buildArgArrays(ffi_type* arg_types[], void* arg_array[], size_t num_args, FuzzedDataProvider* dataProvider) { // The first value in our array should be the number of arguments arg_types[0] = &ffi_type_sint; size_t* size_ptr = reinterpret_cast(malloc(sizeof(size_t))); *size_ptr = num_args; arg_array[0] = size_ptr; // Grab our arguments for (size_t i = 1; i <= num_args; i++) { // Determine what type we're using ffi_type* type = getRandomType(dataProvider, true); if (type == NULL) { return false; } arg_types[i] = type; // Generate a value for it and add to our arguments array arg_array[i] = genArg(type, dataProvider); } // Our arrays of pointers need to be nullptr-terminated arg_types[num_args+1] = NULL; arg_array[num_args+1] = NULL; return true; } void runMainFunctions(ffi_cif* cif, void* resp_buf, void** arg_array, FuzzedDataProvider* dataProvider) { // Call function ffi_call(cif, FFI_FN(fn), resp_buf, arg_array); // Prep Closure ffi_closure* pcl = NULL; void* code; ffi_status ret; pcl = reinterpret_cast( ffi_closure_alloc(sizeof(ffi_closure), &code)); if (pcl == NULL) { return; } size_t buf_size = dataProvider->ConsumeIntegralInRange(0, MAX_RESP_SIZE); std::vector data_vector = dataProvider->ConsumeBytes(buf_size); ret = ffi_prep_closure_loc( pcl, cif, closure_fn, data_vector.data(), code); if (ret != FFI_OK) { ffi_closure_free(pcl); } } void runRawFunctions(ffi_cif* cif, void* resp_buf, void** arg_array, FuzzedDataProvider* dataProvider) { #if !FFI_NO_RAW_API && !FFI_NATIVE_RAW_API // Allocate our ffi_raw and put our args there size_t rsize = ffi_raw_size(cif); ffi_raw* raw_args = reinterpret_cast(malloc(rsize)); raw_alloc_vector.push_back(raw_args); ffi_ptrarray_to_raw(cif, arg_array, raw_args); // Call ffi_raw_call(cif, FFI_FN(fn), resp_buf, raw_args); // Prep Closure #if FFI_CLOSURES ffi_raw_closure* pcl = NULL; void* code; ffi_status ret; pcl = static_cast( ffi_closure_alloc(sizeof(ffi_raw_closure), &code)); if (pcl == NULL) { return; } size_t buf_size = dataProvider->ConsumeIntegralInRange(0, MAX_RESP_SIZE); std::vector data_vector = dataProvider->ConsumeBytes(buf_size); ret = ffi_prep_raw_closure_loc( pcl, cif, raw_closure_fn, data_vector.data(), code); if (ret != FFI_OK) { ffi_closure_free(pcl); } #endif // FFI_CLOSURES #endif // !FFI_NO_RAW_API && !FFI_NATIVE_RAW_API } void runJavaFunctions(ffi_cif* cif, void* resp_buf, void** arg_array, FuzzedDataProvider* dataProvider) { #if !defined(NO_JAVA_RAW_API) #if !FFI_NO_RAW_API && !FFI_NATIVE_RAW_API // Allocate our ffi_java_raw and put our args there size_t rsize = ffi_java_raw_size(cif); // NOTE: a buffer overread will occasionally happen if we don't // increase rsize. ffi_java_raw* raw_args = reinterpret_cast(malloc(rsize*2)); raw_alloc_vector.push_back(raw_args); ffi_ptrarray_to_raw(cif, arg_array, raw_args); // Call ffi_java_raw_call(cif, FFI_FN(fn), resp_buf, raw_args); // Prep Closure #if FFI_CLOSURES ffi_java_raw_closure* pcl = NULL; void* code; ffi_status ret; pcl = static_cast( ffi_closure_alloc(sizeof(ffi_java_raw_closure), &code)); if (pcl == NULL) { return; } size_t buf_size = dataProvider->ConsumeIntegralInRange(0, MAX_RESP_SIZE); std::vector data_vector = dataProvider->ConsumeBytes(buf_size); ret = ffi_prep_java_raw_closure_loc( pcl, cif, raw_closure_fn, data_vector.data(), code); if (ret != FFI_OK) { ffi_closure_free(pcl); } #endif // FFI_CLOSURES #endif // !FFI_NATIVE_RAW_API #endif // !NO_JAVA_RAW_API } void freeFFI(ffi_type* ffi_type) { // Make sure it's one of our structs if (ffi_type == NULL || ffi_type->type != FFI_TYPE_STRUCT) { return; } if (ffi_type->elements != NULL) { free(ffi_type->elements); } // Finally, free our object free(ffi_type); } void freeAll(void* arg_array[], size_t num_args, void* resp_buf) { // Free our custom struct objects for (const auto& ffi : ffi_alloc_vector) { freeFFI(ffi); } ffi_alloc_vector.clear(); for (const auto& raw : raw_alloc_vector) { free(raw); } raw_alloc_vector.clear(); for (size_t i=0; i <= num_args; i++) { free(arg_array[i]); } if (resp_buf) { free(resp_buf); } } extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { // Init our wrapper FuzzedDataProvider dataProvider(Data, Size); ffi_cif cif; ffi_status ret; void* resp_buf = NULL; args_contain_struct = false; ffi_type* rtype; // How many args are we sending? size_t num_args = dataProvider.ConsumeIntegralInRange(0, MAX_NUM_ARGS); // Build our array of args (+2 for leading arg_count and trailing nullptr) ffi_type* arg_types[num_args+2]; void* arg_array[num_args+2]; bool success = buildArgArrays(arg_types, arg_array, num_args, &dataProvider); if (!success) { goto free; } // Get return type rtype = dataProvider.PickValueInArray(ffi_types); // Create a buffer for our return value resp_buf = malloc(MAX_RESP_SIZE); if (resp_buf == NULL) { goto free; } // Set up our ABI // NOTE: fuzzing abi triggers an abort on linux-x86_64, // so only fuzz it on ARM #if MAX_ABI > 0 && defined(ARM) abi = static_cast( dataProvider.ConsumeIntegralInRange(0, MAX_ABI)); #endif #if HAVE_LONG_DOUBLE_VARIANT ffi_prep_types(abi); #endif // ============= Call Functions ============= ret = ffi_prep_cif_var(&cif, abi, 1, num_args, rtype, arg_types); if (ret != FFI_OK) { goto free; } runMainFunctions(&cif, resp_buf, arg_array, &dataProvider); runRawFunctions(&cif, resp_buf, arg_array, &dataProvider); if (!args_contain_struct) { runJavaFunctions(&cif, resp_buf, arg_array, &dataProvider); } free: freeAll(arg_array, num_args, resp_buf); return 0; }