Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
efa09f3
Rewrite apphost in C for non-Windows platforms
Copilot Mar 27, 2026
f22e1b6
Fix compilation errors and add test_only_getenv support
Copilot Mar 27, 2026
0186dcf
Address code review feedback: remove dead code, add errno.h
Copilot Mar 27, 2026
44f43e4
Deduplicate hostmisc C code and use CMake target for containers
Copilot Mar 28, 2026
098cfb9
Use C bundle_marker implementation everywhere, remove C++ version
Copilot Mar 28, 2026
c604d5d
Remove trace.cpp, use C trace implementation everywhere with minipal …
Copilot Mar 30, 2026
e14238f
Fix: Remove apphost_pal.h dependency from apphost_trace.c
Copilot Mar 30, 2026
f639e5f
Fix trace_error_v va_list UB and trace_warning duplication
Copilot Mar 30, 2026
92d7f1b
Rewrite hostmisc C++ to wrap C implementations
Copilot Mar 31, 2026
35b7572
Address code review: fix arch param bug, use explicit type
Copilot Mar 31, 2026
7b1a73c
Rename hostmisc C files: drop apphost_ prefix, headers get _c suffix
Copilot Mar 31, 2026
41bd5dc
Add pal_char_t typedef to pal_c.h, use in function signatures
Copilot Mar 31, 2026
2b8c310
Dynamically allocate path buffers in apphost C code
Copilot Mar 31, 2026
93c6344
Fix app_path buffer to be large enough for pal_fullpath resolution
Copilot Mar 31, 2026
e665819
Make pal.h include pal_c.h (removing duplicates) and trace.c use pal …
Copilot Mar 31, 2026
331ddac
Use pal functions in utils.c/fx_ver.c, rename pal.c to pal.unix.c, re…
Copilot Apr 1, 2026
c54b77f
Dynamically allocate c_fx_ver_t buffers, combine bundle_marker header…
Copilot Apr 2, 2026
ab76094
Add static apphost_hostfxr_resolver for singlefilehost, update CMakeL…
Copilot Apr 2, 2026
e4f053b
Revert unrelated changes accidentally included in c54b77f
Copilot Apr 2, 2026
3a420c0
Add wmain support to apphost_main.c and Windows PAL C++ implementations
Copilot Apr 2, 2026
58bd99c
Fix code review issues: extern "C" balance, pal_getenv overflow, wmai…
Copilot Apr 2, 2026
2261fcd
Move pal_strdup to pal_c.h, deduplicate utils.h defines using utils_c.h
Copilot Apr 3, 2026
25fe448
Remove FEATURE_APPHOST from corehost.cpp; all apphost targets now use…
Copilot Apr 3, 2026
60daf24
Move corehost.cpp to dotnet/dotnet.cpp, remove FEATURE_LIBHOST/CURHOS…
Copilot Apr 4, 2026
f5df70d
Add extern "C" guards to _c.h headers; combine fx_ver_c.h with C++ eq…
Copilot Apr 4, 2026
73d8e39
Rename fx_ver_c.h to fx_ver.h; remove thin fxr/fx_ver.h wrapper
Copilot Apr 4, 2026
0e40552
Inline _c.h headers into their C++ counterparts; rename bundle_marker…
Copilot Apr 6, 2026
3887d7f
Address review feedback: move files, simplify CMake, clean up headers
Copilot Apr 6, 2026
88f985f
Don't include current dir for minipal. That breaks a lot
jkoritzinsky Apr 8, 2026
c4d69cd
CMake cleanup to get host.native building on linux
jkoritzinsky Apr 8, 2026
1ec7a9d
Fix code review issues: macros, off-by-one, race condition, memory le…
Copilot Apr 8, 2026
3bef172
Fix Windows build error: revert _X macro to single-step L ## s
Copilot Apr 17, 2026
b14e833
Fix _X macro for C11 mode: use two-step pattern in C, single-step in C++
Copilot Apr 20, 2026
919d9a4
Fix C4210 and missing minipal_objects in libnethost/libhostfxr
Copilot Apr 24, 2026
f5b55d9
Fix win-x86 calling convention mismatch: add __cdecl to trace_error_w…
Copilot Apr 24, 2026
b3c62e3
Consolidate test-only marker into a single embed in utils.c
Copilot Apr 27, 2026
234bc43
Skip hostmisc_c for browser-wasm: fxr_resolver.c uses dn-vector.h not…
Copilot Apr 28, 2026
5fb4435
Don't link minipal target in hostmisc on browser-wasm
Copilot Apr 28, 2026
f374e2a
Fix fxr_resolver search precedence: env should fall back when app-rel…
Copilot Apr 28, 2026
7b6e47c
Fix trace-to-file and directory-exists: handle TRACEFILE directory pa…
Copilot Apr 29, 2026
d7dd614
Fix Windows pal_directory_exists and registry config location
jkoritzinsky Apr 30, 2026
90e0e0c
Fix missing DOTNET_ROOT(x86) WOW64 fallback in C implementation
jkoritzinsky May 1, 2026
986cfef
Potential fix for pull request finding
elinor-fung May 11, 2026
f8771f0
Address code review comments: fix error handling, move bundle_marker,…
Copilot May 11, 2026
3cdefcc
Eliminate dn-containers dependency from fxr_resolver.c
Copilot May 12, 2026
40cf8a1
Revert dn-containers infrastructure changes
Copilot May 12, 2026
2b0114b
Address code review comments
Copilot May 12, 2026
f36a122
Fix Windows build: add utils.cpp to hostmisc_c
Copilot May 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/native/corehost/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ if(NOT CLR_CMAKE_TARGET_BROWSER)
include_directories(${CLR_SRC_NATIVE_DIR}/external/)
endif()

add_subdirectory(${CLR_SRC_NATIVE_DIR}/minipal minipal)
add_subdirectory(hostcommon)
add_subdirectory(hostmisc)
add_subdirectory(nethost)
Expand Down
379 changes: 379 additions & 0 deletions src/native/corehost/apphost/apphost.c
Comment thread
jkoritzinsky marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,379 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

Comment thread
elinor-fung marked this conversation as resolved.
#include "pal.h"
#include "trace.h"
#include "utils.h"
#include "bundle_marker.h"
#include "apphost_hostfxr_resolver.h"
#include "error_codes.h"
#include "hostfxr.h"

#if defined(_WIN32)
#include "apphost.windows.h"
#endif

#include <string.h>
#include <stdio.h>
#include <inttypes.h>

#if defined(FEATURE_STATIC_HOST)
extern void apphost_static_init(void);
#endif

/**
* Detect if the apphost executable is allowed to load and execute a managed assembly.
*
* - The exe is built with a known hash string at some offset in the image
* - The exe is useless as is with the built-in hash value, and will fail with an error message
* - The hash value should be replaced with the managed DLL filename with optional relative path
* - The optional path is relative to the location of the apphost executable
* - The relative path plus filename are verified to reference a valid file
* - The filename should be "NUL terminated UTF-8" by "dotnet build"
* - The managed DLL filename does not have to be the same name as the apphost executable name
* - The exe may be signed at this point by the app publisher
* - Note: the maximum size of the filename and relative path is 1024 bytes in UTF-8 (not including NUL)
* o https://en.wikipedia.org/wiki/Comparison_of_file_systems
* has more details on maximum file name sizes.
*/
#define EMBED_HASH_HI_PART_UTF8 "c3ab8ff13720e8ad9047dd39466b3c89" // SHA-256 of "foobar" in UTF-8
#define EMBED_HASH_LO_PART_UTF8 "74e592c2fa383d4a3960714caef0c4f2"
#define EMBED_HASH_FULL_UTF8 (EMBED_HASH_HI_PART_UTF8 EMBED_HASH_LO_PART_UTF8) // NUL terminated

#define EMBED_SZ (int)(sizeof(EMBED_HASH_FULL_UTF8) / sizeof(EMBED_HASH_FULL_UTF8[0]))
#define EMBED_MAX (EMBED_SZ > 1025 ? EMBED_SZ : 1025) // 1024 DLL name length, 1 NUL

// This avoids compiler optimization which cause EMBED_HASH_HI_PART_UTF8 EMBED_HASH_LO_PART_UTF8
// to be placed adjacent causing them to match EMBED_HASH_FULL_UTF8 when searched for replacing.
// See https://github.com/dotnet/runtime/issues/109611 for more details.
static bool compare_memory_nooptimization(volatile const char* a, volatile const char* b, size_t length)
{
for (size_t i = 0; i < length; i++)
{
if (*a++ != *b++)
return false;
}
return true;
}

// app_dll receives the embedded DLL name as a pal_char_t string.
// app_dll_len is the buffer size in pal_char_t characters.
static bool is_exe_enabled_for_execution(pal_char_t* app_dll, size_t app_dll_len)
{
// Contains the EMBED_HASH_FULL_UTF8 value at compile time or the managed DLL name replaced by "dotnet build".
// Must not be 'const' because strlen below could be determined at compile time (=64) instead of the actual
// length of the string at runtime.
// Always narrow UTF-8, regardless of platform.
static char embed[EMBED_MAX] = EMBED_HASH_FULL_UTF8;

static const char hi_part[] = EMBED_HASH_HI_PART_UTF8;
static const char lo_part[] = EMBED_HASH_LO_PART_UTF8;

size_t binding_len = strlen(&embed[0]);

if (binding_len >= app_dll_len)
{
trace_error(_X("The managed DLL bound to this executable could not be retrieved from the executable image."));
return false;
}

// Check if the path exceeds the max allowed size
if (binding_len > EMBED_MAX - 1)
{
trace_error(_X("The managed DLL bound to this executable is longer than the max allowed length (%d)"), EMBED_MAX - 1);
return false;
}

// Check if the value is the same as the placeholder to detect unbound executables
size_t hi_len = sizeof(hi_part) - 1;
size_t lo_len = sizeof(lo_part) - 1;
if (binding_len >= (hi_len + lo_len)
&& compare_memory_nooptimization(&embed[0], hi_part, hi_len)
&& compare_memory_nooptimization(&embed[hi_len], lo_part, lo_len))
{
trace_error(_X("This executable is not bound to a managed DLL to execute."));
return false;
}
Comment thread
elinor-fung marked this conversation as resolved.

#if defined(_WIN32)
// Convert embedded UTF-8 path to wide string
if (!pal_utf8_to_palstr(&embed[0], app_dll, app_dll_len))
{
trace_error(_X("The managed DLL bound to this executable could not be retrieved from the executable image."));
return false;
}
#else
memcpy(app_dll, embed, binding_len + 1);
#endif

trace_info(_X("The managed DLL bound to this executable is: '%s'"), app_dll);
return true;
}

static void need_newer_framework_error(const pal_char_t* dotnet_root, const pal_char_t* host_path)
{
pal_char_t download_url[1024];
utils_get_download_url(download_url, ARRAY_SIZE(download_url));

trace_error(
MISSING_RUNTIME_ERROR_FORMAT,
INSTALL_OR_UPDATE_NET_ERROR_MESSAGE,
host_path,
utils_get_current_arch_name(),
_STRINGIFY(HOST_VERSION),
dotnet_root,
download_url,
_STRINGIFY(HOST_VERSION));
}

// C equivalent of propagate_error_writer_t
typedef struct {
hostfxr_set_error_writer_fn set_error_writer;
bool error_writer_set;
} propagate_error_writer_state_t;

static void propagate_error_writer_init(propagate_error_writer_state_t* state, hostfxr_set_error_writer_fn set_error_writer)
{
trace_flush();

state->set_error_writer = set_error_writer;
state->error_writer_set = false;

trace_error_writer_fn error_writer = trace_get_error_writer();
if (error_writer != NULL && set_error_writer != NULL)
{
set_error_writer((hostfxr_error_writer_fn)error_writer);
state->error_writer_set = true;
}
}

static void propagate_error_writer_cleanup(propagate_error_writer_state_t* state)
{
if (state->error_writer_set && state->set_error_writer != NULL)
{
state->set_error_writer(NULL);
state->error_writer_set = false;
}
}

static int exe_start(const int argc, const pal_char_t* argv[])
{
#if defined(FEATURE_STATIC_HOST)
apphost_static_init();
#endif

// Use realpath/GetModuleFileName to find the path of the host, resolving any symlinks.
pal_char_t* host_path = (pal_char_t*)malloc(APPHOST_PATH_MAX * sizeof(pal_char_t));
if (host_path == NULL)
return CurrentHostFindFailure;

Comment thread
elinor-fung marked this conversation as resolved.
host_path[0] = _X('\0'); // Initialize in case get_own_executable_path fails
if (!pal_get_own_executable_path(host_path, APPHOST_PATH_MAX) || !pal_fullpath(host_path, APPHOST_PATH_MAX))
{
trace_error(_X("Failed to resolve full path of the current executable [%s]"), host_path[0] != _X('\0') ? host_path : _X("<unknown>"));
free(host_path);
return CurrentHostFindFailure;
}

bool requires_hostfxr_startupinfo_interface = false;

// FEATURE_APPHOST path: read embedded DLL name
pal_char_t embedded_app_name[EMBED_MAX];
if (!is_exe_enabled_for_execution(embedded_app_name, ARRAY_SIZE(embedded_app_name)))
{
free(host_path);
return AppHostExeNotBoundFailure;
}

if (pal_strchr(embedded_app_name, _X('/')) != NULL
#if defined(_WIN32)
|| pal_strchr(embedded_app_name, _X('\\')) != NULL
#endif
)
{
requires_hostfxr_startupinfo_interface = true;
}

pal_char_t* app_dir = utils_get_directory_alloc(host_path);
if (app_dir == NULL)
{
free(host_path);
return AppPathFindFailure;
}

size_t dir_len = pal_strlen(app_dir);
size_t name_len = pal_strlen(embedded_app_name);
size_t app_path_init = dir_len + name_len + 2; // dir + sep + name + NUL
size_t app_path_len = app_path_init > APPHOST_PATH_MAX ? app_path_init : APPHOST_PATH_MAX;
pal_char_t* app_path = (pal_char_t*)malloc(app_path_len * sizeof(pal_char_t));
if (app_path == NULL)
{
free(app_dir);
free(host_path);
return AppPathFindFailure;
}
pal_str_printf(app_path, app_path_len, _X("%s"), app_dir);
utils_append_path(app_path, app_path_len, embedded_app_name);

if (bundle_marker_is_bundle())
{
trace_info(_X("Detected Single-File app bundle"));
}
else if (!pal_fullpath(app_path, app_path_len))
{
trace_error(_X("The application to execute does not exist: '%s'."), app_path);
free(app_path);
free(app_dir);
free(host_path);
return AppPathFindFailure;
}

free(app_dir);
pal_char_t* app_root = utils_get_directory_alloc(app_path);
if (app_root == NULL)
{
free(app_path);
free(host_path);
return AppPathFindFailure;
}

hostfxr_resolver_t fxr;
hostfxr_resolver_init(&fxr, app_root);

int rc = fxr.status_code;
if (rc != Success)
{
hostfxr_resolver_cleanup(&fxr);
free(app_root);
free(app_path);
free(host_path);
return rc;
}

if (bundle_marker_is_bundle())
{
hostfxr_main_bundle_startupinfo_fn hostfxr_main_bundle_startupinfo = hostfxr_resolver_resolve_main_bundle_startupinfo(&fxr);
if (hostfxr_main_bundle_startupinfo != NULL)
{
const pal_char_t* host_path_cstr = host_path;
const pal_char_t* dotnet_root_cstr = fxr.dotnet_root != NULL && fxr.dotnet_root[0] != _X('\0') ? fxr.dotnet_root : NULL;
const pal_char_t* app_path_cstr = app_path[0] != _X('\0') ? app_path : NULL;
int64_t bundle_header_offset = bundle_marker_header_offset();

trace_info(_X("Invoking fx resolver [%s] hostfxr_main_bundle_startupinfo"), fxr.fxr_path);
trace_info(_X("Host path: [%s]"), host_path);
trace_info(_X("Dotnet path: [%s]"), fxr.dotnet_root != NULL ? fxr.dotnet_root : _X(""));
trace_info(_X("App path: [%s]"), app_path);
trace_info(_X("Bundle Header Offset: [%" PRId64 "]"), bundle_header_offset);

hostfxr_set_error_writer_fn set_error_writer = hostfxr_resolver_resolve_set_error_writer(&fxr);
propagate_error_writer_state_t propagate_state;
propagate_error_writer_init(&propagate_state, set_error_writer);
rc = hostfxr_main_bundle_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr, bundle_header_offset);
propagate_error_writer_cleanup(&propagate_state);
}
else
{
trace_error(_X("The required library %s does not support single-file apps."), fxr.fxr_path);
need_newer_framework_error(fxr.dotnet_root != NULL ? fxr.dotnet_root : _X(""), host_path);
rc = FrameworkMissingFailure;
}
}
else
{
hostfxr_main_startupinfo_fn hostfxr_main_startupinfo = hostfxr_resolver_resolve_main_startupinfo(&fxr);
if (hostfxr_main_startupinfo != NULL)
{
const pal_char_t* host_path_cstr = host_path;
const pal_char_t* dotnet_root_cstr = fxr.dotnet_root != NULL && fxr.dotnet_root[0] != _X('\0') ? fxr.dotnet_root : NULL;
const pal_char_t* app_path_cstr = app_path[0] != _X('\0') ? app_path : NULL;

trace_info(_X("Invoking fx resolver [%s] hostfxr_main_startupinfo"), fxr.fxr_path);
trace_info(_X("Host path: [%s]"), host_path);
trace_info(_X("Dotnet path: [%s]"), fxr.dotnet_root != NULL ? fxr.dotnet_root : _X(""));
trace_info(_X("App path: [%s]"), app_path);

hostfxr_set_error_writer_fn set_error_writer = hostfxr_resolver_resolve_set_error_writer(&fxr);
propagate_error_writer_state_t propagate_state;
propagate_error_writer_init(&propagate_state, set_error_writer);

rc = hostfxr_main_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr);

if (trace_get_error_writer() != NULL && rc == (int)FrameworkMissingFailure && set_error_writer == NULL)
{
need_newer_framework_error(fxr.dotnet_root != NULL ? fxr.dotnet_root : _X(""), host_path);
}

propagate_error_writer_cleanup(&propagate_state);
}
#if !defined(FEATURE_STATIC_HOST)
else
{
if (requires_hostfxr_startupinfo_interface)
{
trace_error(_X("The required library %s does not support relative app dll paths."), fxr.fxr_path);
rc = CoreHostEntryPointFailure;
}
else
{
trace_info(_X("Invoking fx resolver [%s] v1"), fxr.fxr_path);

// Previous corehost trace messages must be printed before calling trace::setup in hostfxr
trace_flush();

hostfxr_main_fn main_fn_v1 = hostfxr_resolver_resolve_main_v1(&fxr);
if (main_fn_v1 != NULL)
{
rc = main_fn_v1(argc, argv);
}
else
{
trace_error(_X("The required library %s does not contain the expected entry point."), fxr.fxr_path);
rc = CoreHostEntryPointFailure;
}
}
}
#endif // !defined(FEATURE_STATIC_HOST)
}

hostfxr_resolver_cleanup(&fxr);
free(app_root);
free(app_path);
free(host_path);
return rc;
}

#if defined(_WIN32)
int __cdecl wmain(int argc, const pal_char_t* argv[])
#else
int main(const int argc, const pal_char_t* argv[])
#endif
{
trace_setup();

if (trace_is_enabled())
{
pal_char_t version_desc[256];
utils_get_host_version_description(version_desc, ARRAY_SIZE(version_desc));
trace_info(_X("--- Invoked apphost [version: %s] main = {"), version_desc);
for (int i = 0; i < argc; ++i)
{
trace_info(_X("%s"), argv[i]);
}
trace_info(_X("}"));
}

#if defined(_WIN32)
apphost_buffer_errors();
#endif

int exit_code = exe_start(argc, argv);

trace_flush();

#if defined(_WIN32)
apphost_write_buffered_errors(exit_code);
#endif

return exit_code;
}
Loading
Loading