diff --git a/src/coreclr/hosts/corerun/corerun.cpp b/src/coreclr/hosts/corerun/corerun.cpp index f17f19f6f2942c..7286a4dec89515 100644 --- a/src/coreclr/hosts/corerun/corerun.cpp +++ b/src/coreclr/hosts/corerun/corerun.cpp @@ -70,6 +70,12 @@ namespace envvar // Variable used to preload a mock hostpolicy for testing. const char_t* mockHostPolicy = W("MOCK_HOSTPOLICY"); + + // Variable used to indicate how app assemblies should be provided to the runtime + // - PROPERTY: corerun will pass the paths vias the TRUSTED_PLATFORM_ASSEMBLIES property + // - EXTERNAL: corerun will pass an external assembly probe to the runtime for app assemblies + // - Not set: same as PROPERTY + const char_t* appAssemblies = W("APP_ASSEMBLIES"); } static void wait_for_debugger() @@ -242,6 +248,37 @@ size_t HOST_CONTRACT_CALLTYPE get_runtime_property( return -1; } +// Paths for external assembly probe +static char* s_core_libs_path = nullptr; +static char* s_core_root_path = nullptr; + +static bool HOST_CONTRACT_CALLTYPE external_assembly_probe( + const char* path, + void** data_start, + int64_t* size) +{ + // Get just the file name + const char* name = path; + const char* pos = strrchr(name, '/'); + if (pos != NULL) + name = pos + 1; + + // Try to map the file from our known app assembly paths + for (const char* dir : { s_core_libs_path, s_core_root_path }) + { + if (dir == nullptr) + continue; + + std::string full_path = dir; + assert(full_path.back() == pal::dir_delim); + full_path.append(name); + if (pal::try_map_file_readonly(full_path.c_str(), data_start, size)) + return true; + } + + return false; +} + static int run(const configuration& config) { platform_specific_actions actions; @@ -295,7 +332,37 @@ static int run(const configuration& config) native_search_dirs << core_root << pal::env_path_delim; } - string_t tpa_list = build_tpa(core_root, core_libs); + string_t tpa_list; + string_t app_assemblies_env = pal::getenv(envvar::appAssemblies); + bool use_external_assembly_probe = false; + if (app_assemblies_env.empty() || app_assemblies_env == W("PROPERTY")) + { + // Use the TRUSTED_PLATFORM_ASSEMBLIES property to pass the app assemblies to the runtime. + tpa_list = build_tpa(core_root, core_libs); + } + else if (app_assemblies_env == W("EXTERNAL")) + { + // Use the external assembly probe to load assemblies from the app assembly paths. + use_external_assembly_probe = true; + if (!core_libs.empty()) + { + pal::string_utf8_t core_libs_utf8 = pal::convert_to_utf8(core_libs.c_str()); + s_core_libs_path = (char*)::malloc(core_libs_utf8.length() + 1); + ::strcpy(s_core_libs_path, core_libs_utf8.c_str()); + } + + if (!core_root.empty()) + { + pal::string_utf8_t core_root_utf8 = pal::convert_to_utf8(core_root.c_str()); + s_core_root_path = (char*)::malloc(core_root_utf8.length() + 1); + ::strcpy(s_core_root_path, core_root_utf8.c_str()); + } + } + else + { + pal::fprintf(stderr, W("Unknown value for APP_ASSEMBLIES environment variable: %s\n"), app_assemblies_env.c_str()); + return -1; + } { // Load hostpolicy if requested. @@ -376,7 +443,8 @@ static int run(const configuration& config) (void*)&config, &get_runtime_property, nullptr, - nullptr }; + nullptr, + use_external_assembly_probe ? &external_assembly_probe : nullptr }; propertyKeys.push_back(HOST_PROPERTY_RUNTIME_CONTRACT); std::stringstream ss; ss << "0x" << std::hex << (size_t)(&host_contract); @@ -457,6 +525,8 @@ static int run(const configuration& config) if (exit_code != -1) exit_code = latched_exit_code; + ::free((void*)s_core_libs_path); + ::free((void*)s_core_root_path); return exit_code; } diff --git a/src/coreclr/hosts/corerun/corerun.hpp b/src/coreclr/hosts/corerun/corerun.hpp index f904cd444e13ae..48e6ed4c1295e0 100644 --- a/src/coreclr/hosts/corerun/corerun.hpp +++ b/src/coreclr/hosts/corerun/corerun.hpp @@ -134,6 +134,34 @@ namespace pal return INVALID_FILE_ATTRIBUTES != ::GetFileAttributesW(file_path.c_str()); } + inline bool try_map_file_readonly(const char* path, void** mapped, int64_t* size) + { + HANDLE file = ::CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) + return false; + + HANDLE file_mapping = ::CreateFileMappingA(file, nullptr, PAGE_READONLY, 0, 0, nullptr); + if (file_mapping == nullptr) + { + ::CloseHandle(file); + return false; + } + + void* mapped_local = ::MapViewOfFile(file_mapping, FILE_MAP_READ, 0, 0, 0); + if (mapped_local == nullptr) + { + ::CloseHandle(file); + ::CloseHandle(file_mapping); + return false; + } + + *size = ::GetFileSize(file, nullptr); + *mapped = mapped_local; + ::CloseHandle(file_mapping); + ::CloseHandle(file); + return true; + } + // Forward declaration void ensure_trailing_delimiter(pal::string_t& dir); @@ -300,7 +328,9 @@ class platform_specific_actions final #else // !TARGET_WINDOWS #include #include +#include #include +#include #include #include @@ -309,7 +339,6 @@ class platform_specific_actions final #include #include #else // !__APPLE__ -#include #include #endif // !__APPLE__ @@ -434,6 +463,33 @@ namespace pal return true; } + inline bool try_map_file_readonly(const char* path, void** mapped, int64_t* size) + { + int fd = open(path, O_RDONLY); + if (fd == -1) + return false; + + struct stat buf; + if (fstat(fd, &buf) == -1) + { + close(fd); + return false; + } + + int64_t size_local = buf.st_size; + void* mapped_local = mmap(NULL, size_local, PROT_READ, MAP_PRIVATE, fd, 0); + if (mapped == MAP_FAILED) + { + close(fd); + return false; + } + + *mapped = mapped_local; + *size = size_local; + close(fd); + return true; + } + // Forward declaration template bool string_ends_with(const string_t& str, const char_t(&suffix)[LEN]); diff --git a/src/coreclr/vm/peimagelayout.cpp b/src/coreclr/vm/peimagelayout.cpp index 69febd4377f4ef..89486101c88aac 100644 --- a/src/coreclr/vm/peimagelayout.cpp +++ b/src/coreclr/vm/peimagelayout.cpp @@ -456,7 +456,9 @@ ConvertedImageLayout::ConvertedImageLayout(FlatImageLayout* source, bool disable LOG((LF_LOADER, LL_INFO100, "PEImage: Opening manually mapped stream\n")); #ifdef TARGET_WINDOWS - if (!disableMapping) + // LoadImageByMappingParts assumes the source has a file mapping handle created/managed by the runtime. + // This is not the case for non-file images (for example, external data). Only try to load by mapping for files. + if (!disableMapping && m_pOwner->IsFile()) { loadedImage = source->LoadImageByMappingParts(this->m_imageParts); if (loadedImage == NULL) diff --git a/src/tests/Loader/ExternalAssemblyProbe/ExternalAssemblyProbe.cs b/src/tests/Loader/ExternalAssemblyProbe/ExternalAssemblyProbe.cs new file mode 100644 index 00000000000000..8a0e47d1728a4b --- /dev/null +++ b/src/tests/Loader/ExternalAssemblyProbe/ExternalAssemblyProbe.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Xunit; + +public unsafe class ExternalAssemblyProbe +{ + [Fact] + public static void ExternalAppAssemblies() + { + // In order to get to this point, the runtime must have been able to find the app assemblies + // Check that the TPA is indeed empty - that is, the runtime is not relying on that property. + string tpa = AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string; + Assert.True(string.IsNullOrEmpty(tpa), "TRUSTED_PLATFORM_ASSEMBLIES should be empty"); + } +} diff --git a/src/tests/Loader/ExternalAssemblyProbe/ExternalAssemblyProbe.csproj b/src/tests/Loader/ExternalAssemblyProbe/ExternalAssemblyProbe.csproj new file mode 100644 index 00000000000000..bc7f70749cc6b4 --- /dev/null +++ b/src/tests/Loader/ExternalAssemblyProbe/ExternalAssemblyProbe.csproj @@ -0,0 +1,15 @@ + + + + true + true + + true + + + + + + +