From 708eeaa4ac334fbcba11e3db573f2d3ffb764225 Mon Sep 17 00:00:00 2001 From: Hanlu Li Date: Mon, 20 Jul 2026 18:31:03 +0800 Subject: [PATCH 1/8] LATX, kzt: rebase Step1-4 integration on latest upstream Signed-off-by: Hanlu Li --- linux-user/main.c | 43 + target/i386/cpu.h | 9 + target/i386/latx/context/box64context.c | 69 + .../latx/context/dlopen_recycle_transaction.c | 27 + target/i386/latx/context/elf_plt_relocation.c | 9 + target/i386/latx/context/elfloader.c | 527 +++- target/i386/latx/context/elfmap.c | 110 + target/i386/latx/context/kzt_bridge_exact.c | 16 + target/i386/latx/context/kzt_guest_dynamic.c | 265 ++ .../context/kzt_guest_dynamic_diagnostics.c | 499 ++++ .../latx/context/kzt_guest_library_adapter.c | 75 + .../latx/context/kzt_guest_library_binding.c | 1598 +++++++++++ .../latx/context/kzt_guest_link_map_reader.c | 387 +++ target/i386/latx/context/kzt_guest_registry.c | 1551 +++++++++++ .../latx/context/kzt_guest_registry_context.c | 147 ++ .../latx/context/kzt_jump_slot_production.c | 1189 +++++++++ .../i386/latx/context/kzt_jump_slot_route.c | 250 ++ target/i386/latx/context/kzt_lazy_binding.c | 201 ++ .../i386/latx/context/kzt_lazy_diagnostics.c | 247 ++ .../latx/context/kzt_observation_adapter.c | 371 +++ target/i386/latx/context/kzt_owner_resolver.c | 304 +++ target/i386/latx/context/kzt_patch_planner.c | 397 +++ .../i386/latx/context/kzt_patch_spike_guard.c | 388 +++ .../latx/context/kzt_patch_spike_writer.c | 384 +++ .../latx/context/kzt_plt_resolver_adapter.c | 85 + .../i386/latx/context/kzt_rela_diagnostics.c | 439 ++++ .../context/kzt_rela_immediate_candidate.c | 174 ++ .../latx/context/kzt_rela_request_enricher.c | 371 +++ .../latx/context/kzt_rela_runtime_bridge.c | 476 ++++ .../context/kzt_runtime_candidate_shadow.c | 388 +++ .../context/kzt_runtime_got_plt_candidate.c | 995 +++++++ .../context/kzt_wrapper_bridge_provider.c | 183 ++ target/i386/latx/context/kzt_wrapper_probe.c | 240 ++ target/i386/latx/context/librarian.c | 58 +- target/i386/latx/context/library.c | 20 +- target/i386/latx/context/meson.build | 29 + target/i386/latx/context/myalign.c | 287 +- target/i386/latx/context/wrappedlibc.c | 179 +- target/i386/latx/context/wrappedlibdl.c | 180 +- target/i386/latx/include/box64context.h | 20 + target/i386/latx/include/debug.h | 14 + .../latx/include/dlopen_recycle_transaction.h | 16 + target/i386/latx/include/elf_plt_relocation.h | 10 + target/i386/latx/include/elfmap.h | 21 + target/i386/latx/include/kzt_bridge_exact.h | 11 + target/i386/latx/include/kzt_guest_dynamic.h | 47 + .../include/kzt_guest_dynamic_diagnostics.h | 130 + .../latx/include/kzt_guest_dynamic_view.h | 68 + .../latx/include/kzt_guest_library_adapter.h | 29 + .../latx/include/kzt_guest_library_binding.h | 214 ++ .../latx/include/kzt_guest_link_map_reader.h | 72 + target/i386/latx/include/kzt_guest_registry.h | 269 ++ .../latx/include/kzt_guest_registry_context.h | 42 + .../latx/include/kzt_jump_slot_production.h | 71 + .../i386/latx/include/kzt_jump_slot_route.h | 85 + target/i386/latx/include/kzt_lazy_binding.h | 125 + .../i386/latx/include/kzt_lazy_diagnostics.h | 84 + .../latx/include/kzt_loader_callback_scope.h | 13 + .../latx/include/kzt_observation_adapter.h | 82 + target/i386/latx/include/kzt_owner_resolver.h | 56 + target/i386/latx/include/kzt_patch_planner.h | 204 ++ .../i386/latx/include/kzt_patch_spike_guard.h | 114 + .../latx/include/kzt_patch_spike_writer.h | 116 + .../latx/include/kzt_plt_resolver_adapter.h | 67 + .../i386/latx/include/kzt_rela_diagnostics.h | 140 + .../include/kzt_rela_immediate_candidate.h | 89 + .../latx/include/kzt_rela_request_enricher.h | 44 + .../latx/include/kzt_rela_runtime_bridge.h | 36 + .../latx/include/kzt_rela_stub_detector.h | 18 + .../include/kzt_runtime_candidate_shadow.h | 108 + .../include/kzt_runtime_got_plt_candidate.h | 75 + .../include/kzt_wrapper_bridge_provider.h | 72 + target/i386/latx/include/kzt_wrapper_probe.h | 87 + target/i386/latx/include/latx-options.h | 16 +- target/i386/latx/include/librarian.h | 5 + target/i386/latx/latx-options.c | 10 + tests/unit/kzt/guest_e2e/build_guest_probe.sh | 81 + tests/unit/kzt/guest_e2e/kzt_guest_main.c | 79 + tests/unit/kzt/guest_e2e/kzt_guest_probe.c | 85 + tests/unit/kzt/guest_e2e/kzt_guest_probe.h | 19 + tests/unit/kzt/kzt_test_options.c | 3 + tests/unit/kzt/kzt_test_options.h | 8 + .../kzt/test_dlopen_recycle_transaction.c | 354 +++ tests/unit/kzt/test_elf_map_range.c | 250 ++ .../unit/kzt/test_guest_dynamic_diagnostics.c | 496 ++++ tests/unit/kzt/test_guest_dynamic_parser.c | 450 ++++ tests/unit/kzt/test_guest_dynamic_snapshot.c | 500 ++++ tests/unit/kzt/test_guest_library_binding.c | 1692 ++++++++++++ .../kzt/test_guest_library_binding_teardown.c | 51 + tests/unit/kzt/test_guest_link_map_reader.c | 591 +++++ tests/unit/kzt/test_guest_registry.c | 1261 +++++++++ .../kzt/test_guest_registry_concurrency.c | 529 ++++ tests/unit/kzt/test_guest_registry_context.c | 378 +++ tests/unit/kzt/test_jump_slot_route.c | 558 ++++ tests/unit/kzt/test_observation_adapter.c | 2326 +++++++++++++++++ tests/unit/kzt/test_owner_resolver.c | 338 +++ tests/unit/kzt/test_patch_planner.c | 509 ++++ tests/unit/kzt/test_patch_spike_guard.c | 520 ++++ tests/unit/kzt/test_patch_spike_writer.c | 907 +++++++ tests/unit/kzt/test_real_guest_e2e.py | 214 ++ tests/unit/kzt/test_real_guest_performance.py | 148 ++ .../unit/kzt/test_registry_diagnostics_gate.c | 300 +++ .../unit/kzt/test_rela_immediate_candidate.c | 618 +++++ tests/unit/kzt/test_rela_request_enricher.c | 765 ++++++ .../unit/kzt/test_rela_runtime_helper_chain.c | 479 ++++ ...test_runtime_candidate_enrichment_shadow.c | 1093 ++++++++ .../unit/kzt/test_runtime_got_plt_candidate.c | 933 +++++++ tests/unit/kzt/test_wi236_enrichment_matrix.c | 325 +++ .../test_wi237_jump_slot_route_contract.py | 89 + .../kzt/test_wi238_structured_diagnostics.c | 695 +++++ tests/unit/kzt/test_wi254_loader_contract.py | 114 + tests/unit/kzt/test_wi256_lazy_binding.c | 489 ++++ tests/unit/kzt/test_wi256_lazy_diagnostics.c | 341 +++ ..._wi256_lazy_diagnostics_source_contract.py | 137 + .../kzt/test_wi256_lazy_production_bridge.c | 1434 ++++++++++ .../kzt/test_wi256_plt_resolver_adapter.c | 346 +++ ...test_wi256_plt_resolver_source_contract.py | 139 + ...82_registry_observation_source_contract.py | 130 + tests/unit/kzt/test_wrapper_bridge_provider.c | 381 +++ tests/unit/kzt/test_wrapper_probe.c | 405 +++ .../kzt/wi231_step4_writer_test_report.md | 92 + tests/unit/meson.build | 516 ++++ 122 files changed, 37938 insertions(+), 77 deletions(-) create mode 100644 target/i386/latx/context/dlopen_recycle_transaction.c create mode 100644 target/i386/latx/context/elf_plt_relocation.c create mode 100644 target/i386/latx/context/elfmap.c create mode 100644 target/i386/latx/context/kzt_bridge_exact.c create mode 100644 target/i386/latx/context/kzt_guest_dynamic.c create mode 100644 target/i386/latx/context/kzt_guest_dynamic_diagnostics.c create mode 100644 target/i386/latx/context/kzt_guest_library_adapter.c create mode 100644 target/i386/latx/context/kzt_guest_library_binding.c create mode 100644 target/i386/latx/context/kzt_guest_link_map_reader.c create mode 100644 target/i386/latx/context/kzt_guest_registry.c create mode 100644 target/i386/latx/context/kzt_guest_registry_context.c create mode 100644 target/i386/latx/context/kzt_jump_slot_production.c create mode 100644 target/i386/latx/context/kzt_jump_slot_route.c create mode 100644 target/i386/latx/context/kzt_lazy_binding.c create mode 100644 target/i386/latx/context/kzt_lazy_diagnostics.c create mode 100644 target/i386/latx/context/kzt_observation_adapter.c create mode 100644 target/i386/latx/context/kzt_owner_resolver.c create mode 100644 target/i386/latx/context/kzt_patch_planner.c create mode 100644 target/i386/latx/context/kzt_patch_spike_guard.c create mode 100644 target/i386/latx/context/kzt_patch_spike_writer.c create mode 100644 target/i386/latx/context/kzt_plt_resolver_adapter.c create mode 100644 target/i386/latx/context/kzt_rela_diagnostics.c create mode 100644 target/i386/latx/context/kzt_rela_immediate_candidate.c create mode 100644 target/i386/latx/context/kzt_rela_request_enricher.c create mode 100644 target/i386/latx/context/kzt_rela_runtime_bridge.c create mode 100644 target/i386/latx/context/kzt_runtime_candidate_shadow.c create mode 100644 target/i386/latx/context/kzt_runtime_got_plt_candidate.c create mode 100644 target/i386/latx/context/kzt_wrapper_bridge_provider.c create mode 100644 target/i386/latx/context/kzt_wrapper_probe.c create mode 100644 target/i386/latx/include/dlopen_recycle_transaction.h create mode 100644 target/i386/latx/include/elf_plt_relocation.h create mode 100644 target/i386/latx/include/elfmap.h create mode 100644 target/i386/latx/include/kzt_bridge_exact.h create mode 100644 target/i386/latx/include/kzt_guest_dynamic.h create mode 100644 target/i386/latx/include/kzt_guest_dynamic_diagnostics.h create mode 100644 target/i386/latx/include/kzt_guest_dynamic_view.h create mode 100644 target/i386/latx/include/kzt_guest_library_adapter.h create mode 100644 target/i386/latx/include/kzt_guest_library_binding.h create mode 100644 target/i386/latx/include/kzt_guest_link_map_reader.h create mode 100644 target/i386/latx/include/kzt_guest_registry.h create mode 100644 target/i386/latx/include/kzt_guest_registry_context.h create mode 100644 target/i386/latx/include/kzt_jump_slot_production.h create mode 100644 target/i386/latx/include/kzt_jump_slot_route.h create mode 100644 target/i386/latx/include/kzt_lazy_binding.h create mode 100644 target/i386/latx/include/kzt_lazy_diagnostics.h create mode 100644 target/i386/latx/include/kzt_loader_callback_scope.h create mode 100644 target/i386/latx/include/kzt_observation_adapter.h create mode 100644 target/i386/latx/include/kzt_owner_resolver.h create mode 100644 target/i386/latx/include/kzt_patch_planner.h create mode 100644 target/i386/latx/include/kzt_patch_spike_guard.h create mode 100644 target/i386/latx/include/kzt_patch_spike_writer.h create mode 100644 target/i386/latx/include/kzt_plt_resolver_adapter.h create mode 100644 target/i386/latx/include/kzt_rela_diagnostics.h create mode 100644 target/i386/latx/include/kzt_rela_immediate_candidate.h create mode 100644 target/i386/latx/include/kzt_rela_request_enricher.h create mode 100644 target/i386/latx/include/kzt_rela_runtime_bridge.h create mode 100644 target/i386/latx/include/kzt_rela_stub_detector.h create mode 100644 target/i386/latx/include/kzt_runtime_candidate_shadow.h create mode 100644 target/i386/latx/include/kzt_runtime_got_plt_candidate.h create mode 100644 target/i386/latx/include/kzt_wrapper_bridge_provider.h create mode 100644 target/i386/latx/include/kzt_wrapper_probe.h create mode 100755 tests/unit/kzt/guest_e2e/build_guest_probe.sh create mode 100644 tests/unit/kzt/guest_e2e/kzt_guest_main.c create mode 100644 tests/unit/kzt/guest_e2e/kzt_guest_probe.c create mode 100644 tests/unit/kzt/guest_e2e/kzt_guest_probe.h create mode 100644 tests/unit/kzt/kzt_test_options.c create mode 100644 tests/unit/kzt/kzt_test_options.h create mode 100644 tests/unit/kzt/test_dlopen_recycle_transaction.c create mode 100644 tests/unit/kzt/test_elf_map_range.c create mode 100644 tests/unit/kzt/test_guest_dynamic_diagnostics.c create mode 100644 tests/unit/kzt/test_guest_dynamic_parser.c create mode 100644 tests/unit/kzt/test_guest_dynamic_snapshot.c create mode 100644 tests/unit/kzt/test_guest_library_binding.c create mode 100644 tests/unit/kzt/test_guest_library_binding_teardown.c create mode 100644 tests/unit/kzt/test_guest_link_map_reader.c create mode 100644 tests/unit/kzt/test_guest_registry.c create mode 100644 tests/unit/kzt/test_guest_registry_concurrency.c create mode 100644 tests/unit/kzt/test_guest_registry_context.c create mode 100644 tests/unit/kzt/test_jump_slot_route.c create mode 100644 tests/unit/kzt/test_observation_adapter.c create mode 100644 tests/unit/kzt/test_owner_resolver.c create mode 100644 tests/unit/kzt/test_patch_planner.c create mode 100644 tests/unit/kzt/test_patch_spike_guard.c create mode 100644 tests/unit/kzt/test_patch_spike_writer.c create mode 100755 tests/unit/kzt/test_real_guest_e2e.py create mode 100644 tests/unit/kzt/test_real_guest_performance.py create mode 100644 tests/unit/kzt/test_registry_diagnostics_gate.c create mode 100644 tests/unit/kzt/test_rela_immediate_candidate.c create mode 100644 tests/unit/kzt/test_rela_request_enricher.c create mode 100644 tests/unit/kzt/test_rela_runtime_helper_chain.c create mode 100644 tests/unit/kzt/test_runtime_candidate_enrichment_shadow.c create mode 100644 tests/unit/kzt/test_runtime_got_plt_candidate.c create mode 100644 tests/unit/kzt/test_wi236_enrichment_matrix.c create mode 100644 tests/unit/kzt/test_wi237_jump_slot_route_contract.py create mode 100644 tests/unit/kzt/test_wi238_structured_diagnostics.c create mode 100644 tests/unit/kzt/test_wi254_loader_contract.py create mode 100644 tests/unit/kzt/test_wi256_lazy_binding.c create mode 100644 tests/unit/kzt/test_wi256_lazy_diagnostics.c create mode 100644 tests/unit/kzt/test_wi256_lazy_diagnostics_source_contract.py create mode 100644 tests/unit/kzt/test_wi256_lazy_production_bridge.c create mode 100644 tests/unit/kzt/test_wi256_plt_resolver_adapter.c create mode 100644 tests/unit/kzt/test_wi256_plt_resolver_source_contract.py create mode 100644 tests/unit/kzt/test_wi382_registry_observation_source_contract.py create mode 100644 tests/unit/kzt/test_wrapper_bridge_provider.c create mode 100644 tests/unit/kzt/test_wrapper_probe.c create mode 100644 tests/unit/kzt/wi231_step4_writer_test_report.md diff --git a/linux-user/main.c b/linux-user/main.c index e397b76516d..ed38cd4619f 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -80,6 +80,7 @@ int box64_pagesize; uintptr_t box64_load_addr = 0; int dlsym_error = 0; int kzt_call_log = 0; +int kzt_registry_diagnostics = 0; int cycle_log = 0; int allow_missing_libs = 1; int box64_nogtk = 0; @@ -635,6 +636,33 @@ static void handle_arg_latx_kzt(const char *arg) { option_kzt = strtol(arg, NULL, 0); } + +static void handle_arg_latx_kzt_lazy_diagnostics(const char *arg) +{ + option_kzt_lazy_diagnostics = strtol(arg, NULL, 0) > 0; +} + +static void handle_arg_latx_kzt_registry_diagnostics(const char *arg) +{ + kzt_registry_diagnostics = strtol(arg, NULL, 0); +} + +static void handle_arg_latx_kzt_patch_spike(const char *arg) +{ + option_kzt_patch_spike = strtol(arg, NULL, 0) > 0; +} + +static void handle_arg_latx_kzt_patch_spike_write(const char *arg) +{ + option_kzt_patch_spike_write = strtol(arg, NULL, 0) > 0; +} + +static void handle_arg_latx_kzt_patch_spike_budget(const char *arg) +{ + long value = strtol(arg, NULL, 0); + + option_kzt_patch_spike_budget = value > 0 ? (unsigned long)value : 0; +} #endif static void handle_arg_latx_fputag(const char *arg) @@ -818,6 +846,21 @@ static const struct qemu_argument arg_table[] = { #if defined(CONFIG_LATX_KZT) {"latx-kzt", "LATX_KZT", true, handle_arg_latx_kzt, "", "enable kuzhitong"}, + {"latx-kzt-lazy-diagnostics", "LATX_KZT_LAZY_DIAGNOSTICS", + true, handle_arg_latx_kzt_lazy_diagnostics, + "", "enable KZT lazy completion diagnostics"}, + {"latx-kzt-registry-diagnostics", "LATX_KZT_REGISTRY_DIAGNOSTICS", + true, handle_arg_latx_kzt_registry_diagnostics, + "", "enable KZT guest registry diagnostics"}, + {"latx-kzt-patch-spike", "LATX_KZT_PATCH_SPIKE", + true, handle_arg_latx_kzt_patch_spike, + "", "enable KZT controlled patch spike planning"}, + {"latx-kzt-patch-spike-write", "LATX_KZT_PATCH_SPIKE_WRITE", + true, handle_arg_latx_kzt_patch_spike_write, + "", "enable KZT controlled patch spike writes"}, + {"latx-kzt-patch-spike-budget", "LATX_KZT_PATCH_SPIKE_BUDGET", + true, handle_arg_latx_kzt_patch_spike_budget, + "", "set KZT controlled patch spike write budget"}, #endif {"latx-fputag", "LATX_FPUTAG", true, handle_arg_latx_fputag, "", "enable fputag"}, diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 7a3f8aeb894..63e53473d3f 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -24,6 +24,10 @@ #include "cpu-qom.h" #include "exec/cpu-defs.h" #include "qapi/qapi-types-common.h" +#ifdef CONFIG_LATX_KZT +#include "kzt_lazy_binding.h" +#include "kzt_loader_callback_scope.h" +#endif /* The x86 has a strong memory model with some store-after-load re-ordering */ #define TCG_GUEST_DEFAULT_MO (TCG_MO_ALL & ~TCG_MO_ST_LD) @@ -1404,6 +1408,11 @@ typedef struct HVFX86LazyFlags { typedef struct CPUX86State { #ifdef CONFIG_LATX +#ifdef CONFIG_LATX_KZT + kzt_lazy_binding_pending_t kzt_lazy_binding_pending; + uintptr_t kzt_lazy_original_return; + kzt_guest_library_loader_scope_t kzt_guest_library_loader_scope; +#endif void* checksum_fail_tb; void *ibtc_table_p; uint64_t vregs[5]; diff --git a/target/i386/latx/context/box64context.c b/target/i386/latx/context/box64context.c index c052561d035..d122a115035 100755 --- a/target/i386/latx/context/box64context.c +++ b/target/i386/latx/context/box64context.c @@ -19,10 +19,18 @@ #include "librarian.h" #include "library.h" #include "wrapper.h" +#ifdef CONFIG_LATX_KZT +#include "kzt_guest_registry.h" +#include "kzt_guest_registry_context.h" +#include "kzt_guest_library_binding.h" +#endif #include box64context_t *NewBox64Context(int argc) { +#ifdef CONFIG_LATX_KZT + kzt_patch_spike_config_t patch_spike_config; +#endif // init and put default values box64context_t *context = (box64context_t*)box_calloc(1, sizeof(box64context_t)); @@ -38,10 +46,56 @@ box64context_t *NewBox64Context(int argc) context->argc = argc; context->argv = (char**)box_calloc(context->argc+1, sizeof(char*)); pthread_mutex_init(&context->mutex_lock, NULL); +#ifdef CONFIG_LATX_KZT + /* Optional acceleration metadata is created on first KZT use so a + * context that never observes a guest link-map keeps the old footprint. */ + pthread_mutex_init(&context->kzt_bridge_mutex, NULL); + (void)kzt_guest_library_access_init(&context->kzt_guest_library_access); + kzt_patch_spike_config_from_options(&patch_spike_config); + kzt_patch_spike_guard_init(&context->kzt_patch_spike_guard, + &patch_spike_config); +#endif return context; } +#ifdef CONFIG_LATX_KZT +kzt_guest_registry_t *KztGuestRegistryForContext(box64context_t *context) +{ + if (!context) { + return NULL; + } + return kzt_guest_registry_context_get( + &context->kzt_guest_registry_context, &context->mutex_lock); +} + +kzt_guest_library_bindings_t *KztGuestLibraryBindingsForContext(box64context_t *context) +{ + return context ? context->kzt_guest_library_access.bindings : NULL; +} + +int KztGuestLibraryLookupForContext( + box64context_t *context, + const kzt_guest_library_binding_key_t *key, + kzt_guest_library_handle_t *handle) +{ + return context + ? kzt_guest_library_access_lookup( + &context->kzt_guest_library_access, key, handle) + : -1; +} +#endif + +kzt_patch_spike_guard_t *KztPatchSpikeGuardForContext(box64context_t *context) +{ +#ifdef CONFIG_LATX_KZT + return context ? &context->kzt_patch_spike_guard : NULL; +#else + (void)context; + return NULL; +#endif +} + EXPORTDYN void FreeBox64Context(box64context_t** context) { @@ -53,10 +107,25 @@ void FreeBox64Context(box64context_t** context) box64context_t* ctx = *context; // local copy to do the cleanning +#ifdef CONFIG_LATX_KZT + /* FreeBox64Context is entered only after guest execution and loader + * callbacks have stopped. Close the context-owned lookup gate first and + * drain acquired handles, while keeping registry/binding storage alive + * for the librarian destruction pass below. */ + kzt_guest_library_access_begin_teardown( + &ctx->kzt_guest_library_access); +#endif + if(ctx->local_maplib) FreeLibrarian(&ctx->local_maplib); if(ctx->maplib) FreeLibrarian(&ctx->maplib); +#ifdef CONFIG_LATX_KZT + kzt_guest_library_access_destroy(&ctx->kzt_guest_library_access); + kzt_guest_registry_context_destroy(&ctx->kzt_guest_registry_context, + &ctx->mutex_lock); + pthread_mutex_destroy(&ctx->kzt_bridge_mutex); +#endif FreeDictionnary(&ctx->versym); for(int i=0; ielfsize; ++i) { diff --git a/target/i386/latx/context/dlopen_recycle_transaction.c b/target/i386/latx/context/dlopen_recycle_transaction.c new file mode 100644 index 00000000000..88586ee3342 --- /dev/null +++ b/target/i386/latx/context/dlopen_recycle_transaction.c @@ -0,0 +1,27 @@ +#include "dlopen_recycle_transaction.h" + +void *dlopen_recycle_transaction( + void *success_handle, size_t *count, int increment_count, void *opaque, + dlopen_recycle_prepare_fn prepare, dlopen_recycle_step_fn guest_open, + dlopen_recycle_step_fn reload, + dlopen_recycle_rollback_fn rollback) +{ + int guest_opened = 0; + + if (!success_handle || !count || !prepare || !guest_open || !reload || + !rollback) + return NULL; + prepare(opaque); + if (guest_open(opaque) != 0) { + rollback(opaque, 0); + return NULL; + } + guest_opened = 1; + if (reload(opaque) != 0) { + rollback(opaque, guest_opened); + return NULL; + } + if (increment_count) + ++*count; + return success_handle; +} diff --git a/target/i386/latx/context/elf_plt_relocation.c b/target/i386/latx/context/elf_plt_relocation.c new file mode 100644 index 00000000000..1ea8a754f6c --- /dev/null +++ b/target/i386/latx/context/elf_plt_relocation.c @@ -0,0 +1,9 @@ +#include "elf_plt_relocation.h" + +int elf_plt_relocation_apply(elf_plt_relocation_apply_fn apply, void *opaque, + int *need_resolver) +{ + if (!apply) + return -1; + return apply(opaque, need_resolver) ? -1 : 0; +} diff --git a/target/i386/latx/context/elfloader.c b/target/i386/latx/context/elfloader.c index 691cf583503..0f2b0fc9e5d 100755 --- a/target/i386/latx/context/elfloader.c +++ b/target/i386/latx/context/elfloader.c @@ -31,6 +31,19 @@ #include "dictionnary.h" #include "symbols.h" #include "lsenv.h" +#include "kzt_rela_stub_detector.h" +#include "kzt_guest_registry.h" +#include "kzt_observation_adapter.h" +#include "kzt_jump_slot_production.h" +#include "kzt_lazy_diagnostics.h" +#include "kzt_plt_resolver_adapter.h" +#include "elf_plt_relocation.h" +#include "elfmap.h" + +#ifdef CONFIG_LATX_KZT +#include "qemu.h" +extern int wine_option_kzt; +#endif void* my__IO_2_1_stderr_ = NULL; void* my__IO_2_1_stdin_ = NULL; @@ -649,6 +662,7 @@ int RelocateElfRELA(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t uint64_t *p = (uint64_t*)(rela[i].r_offset + head->delta); uintptr_t offs = 0; uintptr_t end = 0; + library_t *resolved_provider = NULL; int version = head->VerSym?((Elf64_Half*)((uintptr_t)head->VerSym+head->delta))[ELF64_R_SYM(rela[i].r_info)]:-1; if(version!=-1) version &=0x7fff; const char* vername = GetSymbolVersion(head, version); @@ -664,9 +678,13 @@ int RelocateElfRELA(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t }*/ // so weak symbol are the one left if(!offs && !end) { - GetGlobalSymbolStartEnd(maplib, symname, &offs, &end, head, version, vername); + GetGlobalSymbolStartEndWithProvider( + maplib, symname, &offs, &end, head, version, vername, + &resolved_provider); if(!offs && !end && local_maplib) { - GetGlobalSymbolStartEnd(local_maplib, symname, &offs, &end, head, version, vername); + GetGlobalSymbolStartEndWithProvider( + local_maplib, symname, &offs, &end, head, version, + vername, &resolved_provider); } } } @@ -685,19 +703,64 @@ int RelocateElfRELA(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t *p = offs/* + rela[i].r_addend*/; // not addend it seems } break; - case R_X86_64_JUMP_SLOT: + case R_X86_64_JUMP_SLOT: { // apply immediatly for gobject closure marshal or for LOCAL binding. Also, apply immediatly if it doesn't jump in the got - tmp = (uintptr_t)(*p); + uintptr_t slot_observation = (uintptr_t)(*p); + uintptr_t expected_guest_target = slot_observation; + uintptr_t legacy_target = offs + rela[i].r_addend; + int slot_is_unresolved_stub = + kzt_rela_slot_current_is_unresolved_stub( + slot_observation, + KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, + head->delta, head->plt, head->plt_end, + head->gotplt, head->gotplt_end); + tmp = slot_observation; if (bind==STB_LOCAL || !tmp || !((tmp>=head->plt && tmpplt_end) || (tmp>=head->gotplt && tmpgotplt_end)) || !need_resolv || bindnow - ) { + ) { if (offs){ if(p) { +#ifdef CONFIG_LATX_KZT + if (option_kzt || wine_option_kzt) { + kzt_jump_slot_route_result_t route_result; + + if (kzt_production_jump_slot_route( + my_context, resolved_provider, + legacy_target, head, + need_resolv != NULL, i, + &rela[i], p, + slot_observation, + slot_is_unresolved_stub, + ELF64_R_SYM(rela[i].r_info), + symname, vername, + 1, expected_guest_target, + legacy_target, + &route_result) == 0 && + route_result.status == + KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED) { + break; + } + if (route_result.status == + KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED || + route_result.status == + KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH) { + break; + } + printf_kzt_registry_diagnostics( + "KZT eager route fallback slot=%p " + "observed=%p legacy=%p selected=%p " + "status=%d symbol=%s\n", + p, (void *)slot_observation, (void*)legacy_target, + (void *)route_result.selected_target, + route_result.status, + symname ? symname : "(none)"); + } +#endif printf_log(LOG_INFO, "RelocateElfRELA : Apply %s R_X86_64_JUMP_SLOT @%p with sym=%s (%p -> %p)\n", (bind==STB_LOCAL)?"Local":"Global", p, symname, *(void**)p, (void*)(offs+rela[i].r_addend)); - *p =(uint64_t) (offs + rela[i].r_addend); + *p =(uint64_t) legacy_target; } else { printf_log(LOG_INFO, "Warning, Symbol %s found, but Jump Slot Offset is NULL \n", symname); } @@ -709,6 +772,7 @@ int RelocateElfRELA(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t *need_resolv = 1; } break; + } /* case R_X86_64_NONE: break; @@ -730,9 +794,189 @@ int RelocateElfRELA(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t return 0; } +typedef struct elf_plt_rela_context { + lib_t *maplib; + lib_t *local_maplib; + int bindnow; + elfheader_t *head; + int count; + Elf64_Rela *rela; +} elf_plt_rela_context_t; + +static int RelocateElfPltRELA(void *opaque, int *need_resolver) +{ + elf_plt_rela_context_t *rela = opaque; + return RelocateElfRELA(rela->maplib, rela->local_maplib, rela->bindnow, + rela->head, rela->count, rela->rela, + need_resolver); +} + +#ifdef CONFIG_LATX_KZT +static void KztLazyBindingCompleteResolver(void); + +static int kzt_elfloader_read_guest_memory(uintptr_t guest_addr, void *dst, + size_t size, void *opaque) +{ + void *host_ptr; + + (void)opaque; + if ((!dst && size) || (!guest_addr && size)) { + return -1; + } + if (!size) { + return 0; + } + host_ptr = lock_user(VERIFY_READ, (abi_ulong)guest_addr, size, true); + if (!host_ptr) { + return -1; + } + memcpy(dst, host_ptr, size); + unlock_user(host_ptr, (abi_ulong)guest_addr, 0); + return 0; +} + +static int kzt_elfloader_head_identity( + const elfheader_t *head, + kzt_guest_link_map_identity_t *identity) +{ + size_t i; + + if (!head || !identity || !head->PHEntries) { + return -1; + } + memset(identity, 0, sizeof(*identity)); + for (i = 0; i < head->numPHEntries; ++i) { + const Elf64_Phdr *entry = &head->PHEntries[i]; + + if (entry->p_type != PT_DYNAMIC) { + continue; + } + if (head->delta < 0 || + entry->p_vaddr > UINTPTR_MAX - (uintptr_t)head->delta) { + return -1; + } + identity->load_bias = (uintptr_t)head->delta; + identity->dynamic_addr = entry->p_vaddr + identity->load_bias; + return identity->dynamic_addr ? 0 : -1; + } + return -1; +} + +static void kzt_observe_plt_source(elfheader_t *head, + uintptr_t guest_link_map, + const kzt_guest_link_map_identity_t *object_identity) +{ + const kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = kzt_elfloader_read_guest_memory, + }; + kzt_observation_adapter_request_t request; + kzt_observation_adapter_result_t result; + uintptr_t map_start = 0; + uintptr_t map_end = 0; + uintptr_t confirmed_main_head = 0; + uintptr_t namespace_head = 0; + uintptr_t predecessor = 0; + kzt_guest_registry_t *registry; + kzt_guest_link_map_identity_t main_identity = { 0 }; + int main_namespace; + int range_available; + + if (!head || !guest_link_map || !(option_kzt || wine_option_kzt)) { + return; + } + registry = KztGuestRegistryForContext(my_context); + (void)kzt_guest_registry_context_get_main_namespace_head( + &my_context->kzt_guest_registry_context, &confirmed_main_head); + if (confirmed_main_head && object_identity && + kzt_guest_registry_context_has_main_namespace_evidence( + &my_context->kzt_guest_registry_context, registry, + guest_link_map, object_identity->load_bias, + object_identity->dynamic_addr)) { + main_namespace = 1; + } else { + if (confirmed_main_head && + kzt_guest_link_map_read_predecessor( + guest_link_map, &reader_ops, &predecessor) == 0 && + predecessor == confirmed_main_head) { + main_namespace = 1; + } else if (confirmed_main_head || + kzt_elfloader_head_identity(elf_header, &main_identity) == 0) { + main_namespace = kzt_guest_link_map_classify_namespace( + guest_link_map, &main_identity, confirmed_main_head, &reader_ops, + &namespace_head); + } else { + main_namespace = -1; + } + if (main_namespace == 1 && !confirmed_main_head && + kzt_guest_registry_context_confirm_main_namespace_head( + &my_context->kzt_guest_registry_context, + &my_context->mutex_lock, namespace_head) != 0) { + main_namespace = -1; + } + } + range_available = GetElfLoadRange( + head->PHEntries, head->numPHEntries, head->delta, TARGET_PAGE_SIZE, + &map_start, &map_end) == 0; + memset(&request, 0, sizeof(request)); + request.enabled = 1; + request.link_map_addr = guest_link_map; + request.registry = registry; + request.library_bindings = + KztGuestLibraryBindingsForContext(my_context); + request.reader_ops = &reader_ops; + request.namespace_id_present = main_namespace == 1; + request.namespace_id = 0; + request.map_range_present = range_available; + request.map_start = map_start; + request.map_end = map_end; + result = KZT_OBSERVATION_ADAPTER_DISABLED; + (void)kzt_observe_guest_object_from_callback(&request, &result); + printf_kzt_registry_diagnostics( + "KZT PLT source observation result=%d link_map=0x%lx " + "main_namespace=%d map_start=0x%lx map_end=0x%lx\n", + result, (unsigned long)guest_link_map, main_namespace, + (unsigned long)map_start, (unsigned long)map_end); +} +#endif + int RelocateElfPlt(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t* head) { int need_resolver = 0; + uintptr_t resolver_got = head->pltgot ? head->pltgot : head->got; + uintptr_t resolver_got_runtime = resolver_got ? + resolver_got + head->delta : 0; + uintptr_t guest_link_map = 0; + uintptr_t guest_resolver = 0; +#ifdef CONFIG_LATX_KZT + uintptr_t kzt_evidence_got = head->pltgot; + uintptr_t kzt_evidence_got_runtime = kzt_evidence_got ? + kzt_evidence_got + head->delta : 0; + kzt_guest_link_map_identity_t expected_identity = { 0 }; + kzt_guest_link_map_identity_t observed_identity = { 0 }; + int resolver_snapshot_available = 0; + + if (kzt_evidence_got_runtime && (option_kzt || wine_option_kzt) && + kzt_elfloader_head_identity(head, &expected_identity) == 0 && + kzt_elfloader_read_guest_memory( + kzt_evidence_got_runtime + 8, &guest_link_map, + sizeof(guest_link_map), NULL) == 0 && + kzt_elfloader_read_guest_memory( + kzt_evidence_got_runtime + 16, &guest_resolver, + sizeof(guest_resolver), NULL) == 0 && guest_resolver && + kzt_guest_link_map_read_identity( + guest_link_map, + &(kzt_guest_link_map_reader_ops_t) { + .read_memory = kzt_elfloader_read_guest_memory, + }, + &observed_identity) == 0 && + kzt_guest_link_map_identity_matches( + &observed_identity, expected_identity.load_bias, + expected_identity.dynamic_addr)) { + resolver_snapshot_available = 1; + kzt_observe_plt_source(head, guest_link_map, &observed_identity); + head->self_link_map = guest_link_map; + } +#endif head->had_RelocateElfPlt = 1; if(head->pltrel) { int cnt = head->pltsz / head->pltent; @@ -743,32 +987,83 @@ int RelocateElfPlt(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t* // return -1; return 0; } else if(head->pltrel==DT_RELA) { + elf_plt_rela_context_t rela = { + .maplib = maplib, + .local_maplib = local_maplib, + .bindnow = bindnow, + .head = head, + .count = cnt, + .rela = (Elf64_Rela *)(head->jmprel + head->delta), + }; DumpRelATable(head, cnt, (Elf64_Rela *)(head->jmprel + head->delta), "PLT"); printf_log(LOG_INFO, "Applying %d PLT Relocation(s) with Addend for %s\n", cnt, head->name); - if(RelocateElfRELA(maplib, local_maplib, bindnow, head, cnt, (Elf64_Rela *)(head->jmprel + head->delta), &need_resolver)) - //return -1; + if(elf_plt_relocation_apply(RelocateElfPltRELA, &rela, + &need_resolver)) { printf_log(LOG_INFO, "RelocateElfRELA run ERROR!"); + return -1; + } } if(need_resolver) { if(pltResolver==~0LL) { pltResolver = AddBridge(my_context->system, vFE, PltResolver, 0, "PltResolver"); } - if(head->pltgot) { - if(dl_runtime_resolver ==~0LL){ - dl_runtime_resolver = *(uintptr_t*)(head->pltgot+head->delta+16); +#ifdef CONFIG_LATX_KZT + if ((option_kzt || wine_option_kzt) && + !my_context->kzt_lazy_completion_bridge) { + my_context->kzt_lazy_completion_bridge = AddBridge( + my_context->system, vFE, + KztLazyBindingCompleteResolver, 0, + "KztLazyBindingCompleteResolver"); + } +#endif + if(resolver_got_runtime) { +#ifdef CONFIG_LATX_KZT + if (!resolver_snapshot_available) { +#endif + guest_link_map = + *(uintptr_t *)(resolver_got_runtime + 8); + guest_resolver = + *(uintptr_t *)(resolver_got_runtime + 16); +#ifdef CONFIG_LATX_KZT + } +#endif +#ifdef CONFIG_LATX_KZT + if (option_kzt || wine_option_kzt) { + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_guest_lazy_resolver_t resolver = { + .link_map_slot = resolver_got_runtime + 8, + .resolver_slot = resolver_got_runtime + 16, + .guest_link_map = guest_link_map, + .guest_resolver = guest_resolver, + }; + if (kzt_guest_registry_find_by_link_map( + KztGuestRegistryForContext(my_context), + guest_link_map, &snapshot) == 0 && snapshot && + snapshot->namespace_id.status == KZT_GUEST_FIELD_OK && + snapshot->namespace_id.value == 0) { + (void)kzt_guest_registry_publish_lazy_resolver( + KztGuestRegistryForContext(my_context), + guest_link_map, snapshot->generation, 0, + &resolver); + } + kzt_guest_object_snapshot_free(snapshot); } - *(uintptr_t*)(head->pltgot+head->delta+16) = pltResolver; - head->self_link_map = *(uintptr_t*)(head->pltgot+head->delta+8); - *(uintptr_t*)(head->pltgot+head->delta+8) = (uintptr_t)head; - printf_log(LOG_INFO, "PLT Resolver injected in plt.got at %p\n", (void*)(head->pltgot+head->delta+16)); - } else if(head->got) { +#endif if(dl_runtime_resolver ==~0LL){ dl_runtime_resolver = *(uintptr_t*)(head->got+head->delta+16); } - *(uintptr_t*)(head->got+head->delta+16) = pltResolver; - head->self_link_map = *(uintptr_t*)(head->got+head->delta+8); - *(uintptr_t*)(head->got+head->delta+8) = (uintptr_t)head; - printf_log(LOG_INFO, "PLT Resolver injected in got at %p\n", (void*)(head->got+head->delta+16)); + *(uintptr_t*)(resolver_got_runtime+16) = pltResolver; +#ifdef CONFIG_LATX_KZT + if (!(option_kzt || wine_option_kzt) || + resolver_snapshot_available) { + head->self_link_map = guest_link_map; + } +#else + head->self_link_map = guest_link_map; +#endif + *(uintptr_t*)(resolver_got_runtime+8) = (uintptr_t)head; + printf_log(LOG_INFO, "PLT Resolver injected in got at %p\n", + (void*)(resolver_got_runtime+16)); } } } @@ -1291,6 +1586,114 @@ static void Push64(CPUX86State *cpu, uint64_t v) uintptr_t pltResolver = ~0LL; uintptr_t dl_runtime_resolver = ~0LL; uintptr_t link_map_obj=0; +#ifdef CONFIG_LATX_KZT +static void KztLazyBindingCompleteResolver(void) +{ + CPUX86State *cpu = (CPUX86State *)lsenv->cpu_state; + kzt_lazy_binding_pending_t *pending = + &cpu->kzt_lazy_binding_pending; + uintptr_t original_return = cpu->kzt_lazy_original_return; + uintptr_t slot_addr = pending->slot_addr; + char symbol[KZT_LAZY_BINDING_SYMBOL_MAX]; + kzt_lazy_binding_result_t result; + + (void)slot_addr; + + symbol[0] = '\0'; + if (pending->symbol) { + strncpy(symbol, pending->symbol, sizeof(symbol) - 1); + symbol[sizeof(symbol) - 1] = '\0'; + } + if (pending->armed) { + if (option_kzt_lazy_diagnostics) { + kzt_lazy_binding_pending_t pending_snapshot = *pending; + kzt_lazy_diagnostic_emit_result_t diagnostic_result; + + pending_snapshot.symbol = pending->symbol ? + pending_snapshot.symbol_storage : NULL; + pending_snapshot.version = pending->version ? + pending_snapshot.version_storage : NULL; + (void)kzt_production_lazy_complete( + (void *)pending->context_id, pending, &result); + (void)kzt_lazy_diagnostics_emit_production( + &pending_snapshot, &result, &diagnostic_result); + } else { + (void)kzt_production_lazy_complete( + (void *)pending->context_id, pending, &result); + } + printf_log(LOG_DEBUG, + "KZT: lazy complete status=%d reason=%d slot=%p " + "before=%p after=%p sym=%s\n", + result.status, result.reason, + (void *)slot_addr, + (void *)result.slot_before, (void *)result.slot_after, + symbol[0] ? symbol : "(none)"); + } + kzt_lazy_binding_cancel(pending); + cpu->kzt_lazy_original_return = 0; + Push64(cpu, original_return); +} + +typedef struct kzt_plt_resolver_production_state { + elfheader_t *head; + uintptr_t slot_addr; + uintptr_t unresolved_stub; + const char *symbol; + const char *version; + long addend; +} kzt_plt_resolver_production_state_t; + +static int kzt_plt_resolver_lookup_source( + uintptr_t object_head, kzt_plt_resolver_source_t *source, void *opaque) +{ + kzt_plt_resolver_production_state_t *state = opaque; + kzt_guest_registry_t *registry = KztGuestRegistryForContext(my_context); + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_guest_lazy_resolver_t resolver; + int result = -1; + + if (!state || !state->head || object_head != (uintptr_t)state->head || + !source || !state->head->self_link_map || + kzt_guest_registry_find_by_link_map( + registry, state->head->self_link_map, &snapshot) != 0 || + !snapshot || snapshot->namespace_id.status != KZT_GUEST_FIELD_OK || + snapshot->namespace_id.value != 0 || + kzt_guest_registry_find_lazy_resolver( + registry, state->head->self_link_map, snapshot->generation, 0, + &resolver) != 0 || + resolver.guest_link_map != state->head->self_link_map) { + goto out; + } + *source = (kzt_plt_resolver_source_t) { + .enabled = my_context->kzt_lazy_completion_bridge != 0, + .context_id = (uintptr_t)my_context, + .object_head = object_head, + .source_link_map = state->head->self_link_map, + .source_generation = snapshot->generation, + .namespace_id = snapshot->namespace_id.value, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + .slot_addr = state->slot_addr, + .unresolved_stub = state->unresolved_stub, + .symbol = state->symbol, + .version = state->version, + .addend = state->addend, + .guest_resolver = resolver.guest_resolver, + }; + result = 0; +out: + kzt_guest_object_snapshot_free(snapshot); + return result; +} + +static int kzt_plt_resolver_begin_lazy_binding( + const kzt_lazy_binding_begin_request_t *request, + kzt_lazy_binding_pending_t *pending, + kzt_lazy_binding_result_t *result, void *opaque) +{ + (void)opaque; + return kzt_lazy_binding_begin(request, pending, result); +} +#endif void PltResolver(void) { CPUX86State *cpu = (CPUX86State*)lsenv->cpu_state; @@ -1311,15 +1714,61 @@ void PltResolver(void) uint64_t *p = (uint64_t*)(rel->r_offset + h->delta); uintptr_t offs = 0; uintptr_t end = 0; + library_t *resolved_provider = NULL; + + (void)bind; + +#ifdef CONFIG_LATX_KZT + if (option_kzt_lazy_diagnostics) { + printf_kzt_registry_diagnostics( + "kzt_lazy_resolver_entry symbol=%s slot=%p source=%p\n", + symname ? symname : "(none)", (void *)p, + (void *)h->self_link_map); + } + if (option_kzt || wine_option_kzt) { + kzt_plt_resolver_production_state_t state = { + .head = h, + .slot_addr = (uintptr_t)p, + .unresolved_stub = p ? + __atomic_load_n((uintptr_t *)p, __ATOMIC_ACQUIRE) : 0, + .symbol = symname, + .version = vername, + .addend = rel->r_addend, + }; + kzt_plt_resolver_runtime_ops_t ops = { + .lookup_source = kzt_plt_resolver_lookup_source, + .begin_lazy_binding = kzt_plt_resolver_begin_lazy_binding, + .pending = &cpu->kzt_lazy_binding_pending, + .completion_bridge = my_context->kzt_lazy_completion_bridge, + .original_return = &cpu->kzt_lazy_original_return, + .opaque = &state, + }; + kzt_plt_resolver_enter_result_t enter_result; + + if (kzt_plt_resolver_enter(cpu, &ops, &enter_result) == 0 && + enter_result.status != KZT_PLT_RESOLVER_LEGACY_FRAME_RESTORED) { + return; + } + } +#endif + + (void)Pop64(cpu); + (void)Pop64(cpu); library_t* lib = h->lib; lib_t* local_maplib = GetMaplib(lib); - GetGlobalSymbolStartEnd(my_context->maplib, symname, &offs, &end, h, version, vername); + GetGlobalSymbolStartEndWithProvider( + my_context->maplib, symname, &offs, &end, h, version, vername, + &resolved_provider); if(!offs && !end && local_maplib) { - GetGlobalSymbolStartEnd(local_maplib, symname, &offs, &end, h, version, vername); + GetGlobalSymbolStartEndWithProvider( + local_maplib, symname, &offs, &end, h, version, vername, + &resolved_provider); } if(!offs && !end && !version) - GetGlobalSymbolStartEnd(my_context->maplib, symname, &offs, &end, h, -1, NULL); + GetGlobalSymbolStartEndWithProvider( + my_context->maplib, symname, &offs, &end, h, -1, NULL, + &resolved_provider); if (!offs) { // printf_log(LOG_INFO, "Error: PltResolver: Symbol %s(ver %d: %s%s%s) not found, cannot apply R_X86_64_JUMP_SLOT %p (%p) in %s\n", symname, version, symname, vername?"@":"", vername?vername:"", p, *(void**)p, h->name); @@ -1331,6 +1780,38 @@ void PltResolver(void) } else { offs = (uintptr_t)getAlternate((void*)offs); if(p) { + uintptr_t slot_observation = (uintptr_t)(*p); + uintptr_t legacy_target = offs; +#ifdef CONFIG_LATX_KZT + if (option_kzt || wine_option_kzt) { + kzt_jump_slot_route_result_t route_result; + + if (kzt_production_jump_slot_route( + my_context, resolved_provider, legacy_target, h, + 1, slot, rel, p, + slot_observation, 1, + ELF64_R_SYM(rel->r_info), symname, + vername, 0, 0, legacy_target, &route_result) == 0 && + route_result.status == + KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED) { + printf_kzt_registry_diagnostics( + "KZT lazy route applied slot=%p observed=%p " + "legacy=%p selected=%p symbol=%s\n", + p, (void *)slot_observation, (void*)legacy_target, + (void *)route_result.selected_target, + symname ? symname : "(none)"); + Push64(cpu, route_result.selected_target); + return; + } + if (route_result.status == + KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED || + route_result.status == + KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH) { + Push64(cpu, route_result.final_value); + return; + } + } +#endif printf_log(LOG_INFO, " Apply %s R_X86_64_JUMP_SLOT %p with sym=%s(ver %d: %s%s%s) (%p -> %p / %s)\n", (bind==STB_LOCAL)?"Local":"Global", p, symname, version, symname, vername?"@":"", vername?vername:"",*(void**)p, (void*)offs, ElfName(FindElfAddress(my_context, offs))); *p = offs; } else { diff --git a/target/i386/latx/context/elfmap.c b/target/i386/latx/context/elfmap.c new file mode 100644 index 00000000000..88ad385ae4c --- /dev/null +++ b/target/i386/latx/context/elfmap.c @@ -0,0 +1,110 @@ +#include + +#include "elfmap.h" + +static int AddToAddress(uintptr_t base, uint64_t offset, uintptr_t *result) +{ + if (offset > UINTPTR_MAX || base > UINTPTR_MAX - (uintptr_t)offset) { + return -1; + } + + *result = base + (uintptr_t)offset; + return 0; +} + +int GetElfLoadRange(const Elf64_Phdr *program_headers, + size_t program_header_count, + uintptr_t load_bias, + uintptr_t page_size, + uintptr_t *map_start, + uintptr_t *map_end) +{ + uintptr_t first = UINTPTR_MAX; + uintptr_t last = 0; + uintptr_t page_mask; + int found_load_segment = 0; + + if (!program_headers || !program_header_count || !map_start || !map_end || + map_start == map_end || !page_size || + (page_size & (page_size - 1)) != 0) { + return -1; + } + + page_mask = page_size - 1; + for (size_t i = 0; i < program_header_count; ++i) { + const Elf64_Phdr *header = &program_headers[i]; + uint64_t segment_end; + uintptr_t aligned_start; + uintptr_t aligned_end; + uintptr_t runtime_start; + uintptr_t runtime_end; + + if (header->p_type != PT_LOAD || header->p_memsz == 0) { + continue; + } + + if (header->p_vaddr > UINT64_MAX - header->p_memsz) { + return -1; + } + segment_end = header->p_vaddr + header->p_memsz; + + if (header->p_vaddr > UINTPTR_MAX || segment_end > UINTPTR_MAX || + segment_end > UINTPTR_MAX - page_mask) { + return -1; + } + + aligned_start = (uintptr_t)header->p_vaddr & ~page_mask; + aligned_end = ((uintptr_t)segment_end + page_mask) & ~page_mask; + if (AddToAddress(load_bias, aligned_start, &runtime_start) != 0 || + AddToAddress(load_bias, aligned_end, &runtime_end) != 0) { + return -1; + } + + if (runtime_start < first) { + first = runtime_start; + } + if (runtime_end > last) { + last = runtime_end; + } + found_load_segment = 1; + } + + if (!found_load_segment || first >= last) { + return -1; + } + + *map_start = first; + *map_end = last; + return 0; +} + +int GetElfDynamicAddress(const Elf64_Phdr *program_headers, + size_t program_header_count, + uintptr_t load_bias, + uintptr_t *dynamic_addr) +{ + uintptr_t found = 0; + + if (!program_headers || !program_header_count || !dynamic_addr) { + return -1; + } + + for (size_t i = 0; i < program_header_count; ++i) { + const Elf64_Phdr *header = &program_headers[i]; + + if (header->p_type != PT_DYNAMIC) { + continue; + } + if (found || header->p_vaddr > UINTPTR_MAX || + AddToAddress(load_bias, header->p_vaddr, &found) != 0 || + !found) { + return -1; + } + } + + if (!found) { + return -1; + } + *dynamic_addr = found; + return 0; +} diff --git a/target/i386/latx/context/kzt_bridge_exact.c b/target/i386/latx/context/kzt_bridge_exact.c new file mode 100644 index 00000000000..769d10ae8b8 --- /dev/null +++ b/target/i386/latx/context/kzt_bridge_exact.c @@ -0,0 +1,16 @@ +#include "kzt_bridge_exact.h" + +#include "bridge_private.h" + +int kzt_bridge_is_exact(uintptr_t target, kzt_bridge_wrapper_t wrapper, + void *native_symbol) +{ + onebridge_t *entry = (onebridge_t *)target; + + if (!entry || entry->CC != 0xCC || entry->S != 'S' || + entry->C != 'C' || (entry->C3 != 0xC3 && entry->C3 != 0xC2) || + entry->w != wrapper || entry->f != (uintptr_t)native_symbol) { + return 0; + } + return 1; +} diff --git a/target/i386/latx/context/kzt_guest_dynamic.c b/target/i386/latx/context/kzt_guest_dynamic.c new file mode 100644 index 00000000000..70422ddc1ad --- /dev/null +++ b/target/i386/latx/context/kzt_guest_dynamic.c @@ -0,0 +1,265 @@ +#include "kzt_guest_dynamic.h" + +#include + +static void kzt_guest_dynamic_field_set( + kzt_guest_dynamic_field_t *field, + uint64_t value, + kzt_guest_dynamic_address_semantics_t semantics) +{ + field->present = 1; + field->value = value; + field->address_semantics = semantics; +} + +static int kzt_guest_dynamic_read_entry( + uintptr_t dynamic_addr, + size_t index, + const kzt_guest_link_map_reader_ops_t *reader_ops, + Elf64_Dyn *entry, + uintptr_t *entry_addr) +{ + uintptr_t offset; + + if (index > UINTPTR_MAX / sizeof(*entry)) { + return -1; + } + + offset = index * sizeof(*entry); + if (dynamic_addr > UINTPTR_MAX - offset) { + return -1; + } + + *entry_addr = dynamic_addr + offset; + return reader_ops->read_memory(*entry_addr, entry, sizeof(*entry), + reader_ops->opaque) == 0 ? 0 : -1; +} + +static int kzt_guest_dynamic_add_needed(kzt_guest_dynamic_view_t *view, + uint64_t offset) +{ + size_t new_count = view->needed_count + 1; + + if (new_count < view->needed_count || + new_count > KZT_GUEST_DYNAMIC_NEEDED_LIMIT) { + return -1; + } + + view->needed_offsets[view->needed_count] = offset; + view->needed_count = new_count; + view->needed_address_semantics = KZT_GUEST_DYNAMIC_STRING_TABLE_OFFSET; + return 0; +} + +static void kzt_guest_dynamic_record_unknown_tag( + kzt_guest_dynamic_view_t *view, + int64_t tag, + size_t index) +{ + if (view->unknown_tag_count == 0) { + view->first_unknown_tag = tag; + view->first_unknown_tag_index = index; + } + ++view->unknown_tag_count; +} + +static int kzt_guest_dynamic_record_entry(kzt_guest_dynamic_view_t *view, + const Elf64_Dyn *entry, + size_t index) +{ + uint64_t value = entry->d_un.d_val; + uint64_t ptr = entry->d_un.d_ptr; + + switch (entry->d_tag) { + case DT_NEEDED: + return kzt_guest_dynamic_add_needed(view, value); + case DT_SYMTAB: + kzt_guest_dynamic_field_set(&view->symtab, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_STRTAB: + kzt_guest_dynamic_field_set(&view->strtab, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_SYMENT: + kzt_guest_dynamic_field_set(&view->syment, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_STRSZ: + kzt_guest_dynamic_field_set(&view->strsz, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_HASH: + kzt_guest_dynamic_field_set(&view->hash, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_GNU_HASH: + kzt_guest_dynamic_field_set(&view->gnu_hash, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_VERSYM: + kzt_guest_dynamic_field_set(&view->versym, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_VERNEED: + kzt_guest_dynamic_field_set(&view->verneed, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_VERNEEDNUM: + kzt_guest_dynamic_field_set(&view->verneednum, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_VERDEF: + kzt_guest_dynamic_field_set(&view->verdef, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_VERDEFNUM: + kzt_guest_dynamic_field_set(&view->verdefnum, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_RELA: + kzt_guest_dynamic_field_set(&view->rela, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_RELASZ: + kzt_guest_dynamic_field_set(&view->relasz, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_RELAENT: + kzt_guest_dynamic_field_set(&view->relaent, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_REL: + kzt_guest_dynamic_field_set(&view->rel, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_RELSZ: + kzt_guest_dynamic_field_set(&view->relsz, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_RELENT: + kzt_guest_dynamic_field_set(&view->relent, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_JMPREL: + kzt_guest_dynamic_field_set(&view->jmprel, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_PLTRELSZ: + kzt_guest_dynamic_field_set(&view->pltrelsz, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_PLTREL: + kzt_guest_dynamic_field_set(&view->pltrel, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_PLTGOT: + kzt_guest_dynamic_field_set(&view->pltgot, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + default: + kzt_guest_dynamic_record_unknown_tag(view, entry->d_tag, index); + break; + } + + return 0; +} + +static void kzt_guest_dynamic_publish_view( + kzt_guest_dynamic_parse_result_t *result, + const kzt_guest_dynamic_view_t *view) +{ + result->status = view->status; + result->entry_count = view->entry_count; + result->scan_limit = view->scan_limit; + result->unknown_tag_count = view->unknown_tag_count; + result->first_unknown_tag = view->first_unknown_tag; + result->first_unknown_tag_index = view->first_unknown_tag_index; + result->view = *view; +} + +int kzt_guest_dynamic_parse( + uintptr_t dynamic_addr, + uintptr_t load_bias, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_dynamic_parse_result_t *result) +{ + kzt_guest_dynamic_view_t view; + size_t i; + + if (!result) { + return -1; + } + + memset(result, 0, sizeof(*result)); + result->status = KZT_GUEST_DYNAMIC_ERROR; + result->error = KZT_GUEST_DYNAMIC_ERROR_INVALID_ARGUMENT; + + if (!dynamic_addr || !reader_ops || !reader_ops->read_memory) { + return -1; + } + + memset(&view, 0, sizeof(view)); + view.dynamic_addr = dynamic_addr; + view.load_bias = load_bias; + view.scan_limit = KZT_GUEST_DYNAMIC_SCAN_LIMIT; + + for (i = 0; i < KZT_GUEST_DYNAMIC_SCAN_LIMIT; ++i) { + Elf64_Dyn entry; + uintptr_t entry_addr = 0; + + if (kzt_guest_dynamic_read_entry(dynamic_addr, i, reader_ops, + &entry, &entry_addr) != 0) { + view.status = KZT_GUEST_DYNAMIC_READ_ERROR; + view.entry_count = i; + result->read_error_addr = entry_addr; + result->error = KZT_GUEST_DYNAMIC_ERROR_READ_FAILURE; + kzt_guest_dynamic_publish_view(result, &view); + return 0; + } + + if (entry.d_tag == DT_NULL) { + view.status = KZT_GUEST_DYNAMIC_COMPLETE; + view.entry_count = i; + view.has_null = 1; + result->error = KZT_GUEST_DYNAMIC_ERROR_NONE; + kzt_guest_dynamic_publish_view(result, &view); + return 0; + } + + if (kzt_guest_dynamic_record_entry(&view, &entry, i) != 0) { + view.status = KZT_GUEST_DYNAMIC_ERROR; + view.entry_count = i; + result->status = KZT_GUEST_DYNAMIC_ERROR; + result->error = KZT_GUEST_DYNAMIC_ERROR_TOO_MANY_NEEDED; + kzt_guest_dynamic_publish_view(result, &view); + return 0; + } + } + + view.status = KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL; + view.entry_count = KZT_GUEST_DYNAMIC_SCAN_LIMIT; + result->error = KZT_GUEST_DYNAMIC_ERROR_SCAN_LIMIT_EXCEEDED; + kzt_guest_dynamic_publish_view(result, &view); + return 0; +} + +void kzt_guest_dynamic_view_destroy(kzt_guest_dynamic_view_t *view) +{ + if (!view) { + return; + } + + memset(view, 0, sizeof(*view)); +} + +void kzt_guest_dynamic_parse_result_clear( + kzt_guest_dynamic_parse_result_t *result) +{ + if (!result) { + return; + } + + kzt_guest_dynamic_view_destroy(&result->view); + memset(result, 0, sizeof(*result)); +} diff --git a/target/i386/latx/context/kzt_guest_dynamic_diagnostics.c b/target/i386/latx/context/kzt_guest_dynamic_diagnostics.c new file mode 100644 index 00000000000..b2db896b980 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_dynamic_diagnostics.c @@ -0,0 +1,499 @@ +#include "kzt_guest_dynamic_diagnostics.h" + +#include +#include +#include + +typedef struct kzt_guest_dynamic_field_spec { + const char *name; + size_t offset; +} kzt_guest_dynamic_field_spec_t; + +static int kzt_guest_dynamic_status_is_blocking( + kzt_guest_dynamic_status_t status) +{ + return status == KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL || + status == KZT_GUEST_DYNAMIC_READ_ERROR || + status == KZT_GUEST_DYNAMIC_ERROR; +} + +static kzt_guest_dynamic_diagnostic_match_t kzt_guest_dynamic_compare_size( + size_t old_value, + size_t new_value) +{ + return old_value == new_value ? KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED : + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH; +} + +static kzt_guest_dynamic_diagnostic_match_t kzt_guest_dynamic_compare_status( + kzt_guest_dynamic_status_t old_status, + kzt_guest_dynamic_status_t new_status) +{ + return old_status == new_status ? KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED : + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH; +} + +static void kzt_guest_dynamic_count_field_match( + kzt_guest_dynamic_diagnostic_report_t *report, + kzt_guest_dynamic_diagnostic_match_t match) +{ + switch (match) { + case KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED: + ++report->matched_count; + break; + case KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_OLD: + ++report->missing_old_count; + break; + case KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_NEW: + ++report->missing_new_count; + break; + case KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH: + ++report->mismatch_count; + break; + } +} + +static void kzt_guest_dynamic_add_field_report( + kzt_guest_dynamic_diagnostic_report_t *report, + const kzt_guest_dynamic_diagnostic_field_t *field) +{ + if (report->field_count >= KZT_GUEST_DYNAMIC_DIAGNOSTIC_FIELD_LIMIT) { + return; + } + + report->fields[report->field_count++] = *field; + kzt_guest_dynamic_count_field_match(report, field->match); +} + +static kzt_guest_dynamic_diagnostic_match_t kzt_guest_dynamic_compare_field( + const kzt_guest_dynamic_field_t *old_field, + const kzt_guest_dynamic_field_t *new_field) +{ + if (!old_field->present && !new_field->present) { + return KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED; + } + if (!old_field->present) { + return KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_OLD; + } + if (!new_field->present) { + return KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_NEW; + } + if (old_field->value != new_field->value || + old_field->address_semantics != new_field->address_semantics) { + return KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH; + } + + return KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED; +} + +static const kzt_guest_dynamic_field_t *kzt_guest_dynamic_field_at( + const kzt_guest_dynamic_view_t *view, + const kzt_guest_dynamic_field_spec_t *spec) +{ + return (const kzt_guest_dynamic_field_t *)((const char *)view + + spec->offset); +} + +static void kzt_guest_dynamic_compare_named_field( + kzt_guest_dynamic_diagnostic_report_t *report, + const kzt_guest_dynamic_view_t *old_view, + const kzt_guest_dynamic_view_t *new_view, + const kzt_guest_dynamic_field_spec_t *spec) +{ + const kzt_guest_dynamic_field_t *old_field = + kzt_guest_dynamic_field_at(old_view, spec); + const kzt_guest_dynamic_field_t *new_field = + kzt_guest_dynamic_field_at(new_view, spec); + kzt_guest_dynamic_diagnostic_field_t field = { + .name = spec->name, + .match = kzt_guest_dynamic_compare_field(old_field, new_field), + .old_present = old_field->present, + .old_value = old_field->value, + .old_address_semantics = old_field->address_semantics, + .new_present = new_field->present, + .new_value = new_field->value, + .new_address_semantics = new_field->address_semantics, + }; + + kzt_guest_dynamic_add_field_report(report, &field); +} + +static int kzt_guest_dynamic_needed_offsets_equal( + const kzt_guest_dynamic_view_t *old_view, + const kzt_guest_dynamic_view_t *new_view) +{ + size_t i; + + if (old_view->needed_count != new_view->needed_count || + old_view->needed_address_semantics != + new_view->needed_address_semantics) { + return 0; + } + + for (i = 0; i < old_view->needed_count; ++i) { + if (old_view->needed_offsets[i] != new_view->needed_offsets[i]) { + return 0; + } + } + + return 1; +} + +static void kzt_guest_dynamic_compare_needed_offsets( + kzt_guest_dynamic_diagnostic_report_t *report, + const kzt_guest_dynamic_view_t *old_view, + const kzt_guest_dynamic_view_t *new_view) +{ + int old_present = old_view->needed_count > 0; + int new_present = new_view->needed_count > 0; + kzt_guest_dynamic_diagnostic_field_t field = { + .name = "needed_offsets", + .old_present = old_present, + .old_value = old_present ? old_view->needed_offsets[0] : 0, + .old_address_semantics = old_view->needed_address_semantics, + .old_count = old_view->needed_count, + .new_present = new_present, + .new_value = new_present ? new_view->needed_offsets[0] : 0, + .new_address_semantics = new_view->needed_address_semantics, + .new_count = new_view->needed_count, + }; + + if (!old_present && !new_present) { + field.match = KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED; + } else if (!old_present) { + field.match = KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_OLD; + } else if (!new_present) { + field.match = KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_NEW; + } else if (!kzt_guest_dynamic_needed_offsets_equal(old_view, new_view)) { + field.match = KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH; + } else { + field.match = KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED; + } + + kzt_guest_dynamic_add_field_report(report, &field); +} + +static kzt_guest_dynamic_diagnostic_match_t kzt_guest_dynamic_compare_unknown( + const kzt_guest_dynamic_parse_result_t *old_result, + const kzt_guest_dynamic_parse_result_t *new_result) +{ + if (old_result->unknown_tag_count != new_result->unknown_tag_count) { + return KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH; + } + + if (old_result->unknown_tag_count == 0) { + return KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED; + } + + return old_result->first_unknown_tag == new_result->first_unknown_tag && + old_result->first_unknown_tag_index == + new_result->first_unknown_tag_index ? + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED : + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH; +} + +static size_t kzt_guest_dynamic_count_summary_differences( + const kzt_guest_dynamic_diagnostic_report_t *report) +{ + size_t count = report->missing_old_count + report->missing_new_count + + report->mismatch_count; + + if (report->status_match != KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED) { + ++count; + } + if (report->entry_count_match != KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED) { + ++count; + } + if (report->unknown_tags_match != KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED) { + ++count; + } + + return count; +} + +static void kzt_guest_dynamic_summary_set_field( + kzt_guest_dynamic_diagnostic_summary_t *summary, + const kzt_guest_dynamic_diagnostic_field_t *field) +{ + summary->first_difference_kind = + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_FIELD; + summary->first_difference_name = field->name; + summary->first_difference_match = field->match; + summary->first_old_present = field->old_present; + summary->first_new_present = field->new_present; + summary->first_old_value = field->old_value; + summary->first_new_value = field->new_value; + summary->first_old_count = field->old_count; + summary->first_new_count = field->new_count; +} + +static void kzt_guest_dynamic_summary_set_status( + kzt_guest_dynamic_diagnostic_summary_t *summary, + const kzt_guest_dynamic_diagnostic_report_t *report) +{ + summary->first_difference_kind = + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_STATUS; + summary->first_difference_name = "status"; + summary->first_difference_match = report->status_match; + summary->first_old_present = 1; + summary->first_new_present = 1; + summary->first_old_value = report->old_status; + summary->first_new_value = report->new_status; +} + +static void kzt_guest_dynamic_summary_set_entry_count( + kzt_guest_dynamic_diagnostic_summary_t *summary, + const kzt_guest_dynamic_diagnostic_report_t *report) +{ + summary->first_difference_kind = + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_ENTRY_COUNT; + summary->first_difference_name = "entry_count"; + summary->first_difference_match = report->entry_count_match; + summary->first_old_present = 1; + summary->first_new_present = 1; + summary->first_old_count = report->old_entry_count; + summary->first_new_count = report->new_entry_count; +} + +static void kzt_guest_dynamic_summary_set_unknown_tags( + kzt_guest_dynamic_diagnostic_summary_t *summary, + const kzt_guest_dynamic_diagnostic_report_t *report) +{ + summary->first_difference_kind = + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_UNKNOWN_TAGS; + summary->first_difference_name = "unknown_tags"; + summary->first_difference_match = report->unknown_tags_match; + summary->first_old_present = report->old_unknown_tag_count > 0; + summary->first_new_present = report->new_unknown_tag_count > 0; + summary->first_old_count = report->old_unknown_tag_count; + summary->first_new_count = report->new_unknown_tag_count; + summary->first_old_tag = report->old_first_unknown_tag; + summary->first_new_tag = report->new_first_unknown_tag; + summary->first_old_tag_index = report->old_first_unknown_tag_index; + summary->first_new_tag_index = report->new_first_unknown_tag_index; +} + +int kzt_guest_dynamic_diagnostics_summarize( + const kzt_guest_dynamic_diagnostic_report_t *report, + uintptr_t link_map_addr, + unsigned long generation, + kzt_guest_dynamic_diagnostic_summary_t *summary) +{ + size_t i; + + if (!report || !summary) { + return -1; + } + + memset(summary, 0, sizeof(*summary)); + summary->link_map_addr = link_map_addr; + summary->generation = generation; + summary->matched = report->difference_count == 0; + summary->blocking = report->blocking_count > 0; + summary->difference_count = report->difference_count; + summary->blocking_count = report->blocking_count; + summary->old_status = report->old_status; + summary->new_status = report->new_status; + summary->old_entry_count = report->old_entry_count; + summary->new_entry_count = report->new_entry_count; + summary->old_unknown_tag_count = report->old_unknown_tag_count; + summary->new_unknown_tag_count = report->new_unknown_tag_count; + summary->old_first_unknown_tag = report->old_first_unknown_tag; + summary->new_first_unknown_tag = report->new_first_unknown_tag; + summary->old_first_unknown_tag_index = + report->old_first_unknown_tag_index; + summary->new_first_unknown_tag_index = + report->new_first_unknown_tag_index; + summary->first_difference_kind = + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_NONE; + summary->first_difference_name = "none"; + summary->first_difference_match = + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED; + + if (report->status_match != KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED || + report->blocking_count > 0) { + kzt_guest_dynamic_summary_set_status(summary, report); + return 0; + } + + if (report->entry_count_match != KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED) { + kzt_guest_dynamic_summary_set_entry_count(summary, report); + return 0; + } + + if (report->unknown_tags_match != + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED) { + kzt_guest_dynamic_summary_set_unknown_tags(summary, report); + return 0; + } + + for (i = 0; i < report->field_count; ++i) { + if (report->fields[i].match != + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED) { + kzt_guest_dynamic_summary_set_field(summary, + &report->fields[i]); + return 0; + } + } + + return 0; +} + +int kzt_guest_dynamic_diagnostics_format_summary( + const kzt_guest_dynamic_diagnostic_summary_t *summary, + char *buffer, + size_t buffer_size) +{ + int written; + const char *first_name; + + if (!summary || !buffer || buffer_size == 0) { + return -1; + } + + first_name = summary->first_difference_name ? + summary->first_difference_name : "none"; + written = snprintf( + buffer, buffer_size, + "kzt_guest_dynamic_compare link_map=0x%lx generation=%lu " + "matched=%d blocking=%d differences=%lu blocking_count=%lu " + "first=%s kind=%d match=%d old_present=%d new_present=%d " + "old_value=0x%llx new_value=0x%llx old_count=%lu new_count=%lu " + "old_tag=%lld new_tag=%lld old_tag_index=%lu new_tag_index=%lu " + "old_status=%d new_status=%d old_entries=%lu new_entries=%lu " + "old_unknown_tags=%lu new_unknown_tags=%lu", + (unsigned long)summary->link_map_addr, summary->generation, + summary->matched, summary->blocking, + (unsigned long)summary->difference_count, + (unsigned long)summary->blocking_count, first_name, + summary->first_difference_kind, summary->first_difference_match, + summary->first_old_present, summary->first_new_present, + (unsigned long long)summary->first_old_value, + (unsigned long long)summary->first_new_value, + (unsigned long)summary->first_old_count, + (unsigned long)summary->first_new_count, + (long long)summary->first_old_tag, + (long long)summary->first_new_tag, + (unsigned long)summary->first_old_tag_index, + (unsigned long)summary->first_new_tag_index, + summary->old_status, summary->new_status, + (unsigned long)summary->old_entry_count, + (unsigned long)summary->new_entry_count, + (unsigned long)summary->old_unknown_tag_count, + (unsigned long)summary->new_unknown_tag_count); + + if (written < 0 || (size_t)written >= buffer_size) { + return -1; + } + + return 0; +} + +int kzt_guest_dynamic_diagnostics_compare( + const kzt_guest_dynamic_parse_result_t *old_result, + const kzt_guest_dynamic_parse_result_t *new_result, + kzt_guest_dynamic_diagnostic_report_t *report) +{ + const kzt_guest_dynamic_view_t *old_view; + const kzt_guest_dynamic_view_t *new_view; + const kzt_guest_dynamic_field_spec_t fields[] = { +#define KZT_DYNAMIC_FIELD(name) \ + { #name, offsetof(kzt_guest_dynamic_view_t, name) } + KZT_DYNAMIC_FIELD(symtab), + KZT_DYNAMIC_FIELD(strtab), + KZT_DYNAMIC_FIELD(syment), + KZT_DYNAMIC_FIELD(strsz), + KZT_DYNAMIC_FIELD(hash), + KZT_DYNAMIC_FIELD(gnu_hash), + KZT_DYNAMIC_FIELD(versym), + KZT_DYNAMIC_FIELD(verneed), + KZT_DYNAMIC_FIELD(verneednum), + KZT_DYNAMIC_FIELD(verdef), + KZT_DYNAMIC_FIELD(verdefnum), + KZT_DYNAMIC_FIELD(rela), + KZT_DYNAMIC_FIELD(relasz), + KZT_DYNAMIC_FIELD(relaent), + KZT_DYNAMIC_FIELD(rel), + KZT_DYNAMIC_FIELD(relsz), + KZT_DYNAMIC_FIELD(relent), + KZT_DYNAMIC_FIELD(jmprel), + KZT_DYNAMIC_FIELD(pltrelsz), + KZT_DYNAMIC_FIELD(pltrel), + KZT_DYNAMIC_FIELD(pltgot), +#undef KZT_DYNAMIC_FIELD + }; + size_t i; + + if (!old_result || !new_result || !report) { + return -1; + } + + memset(report, 0, sizeof(*report)); + + old_view = &old_result->view; + new_view = &new_result->view; + + report->old_status = old_result->status; + report->new_status = new_result->status; + report->old_error = old_result->error; + report->new_error = new_result->error; + report->old_read_error_addr = old_result->read_error_addr; + report->new_read_error_addr = new_result->read_error_addr; + report->old_entry_count = old_result->entry_count; + report->new_entry_count = new_result->entry_count; + report->old_unknown_tag_count = old_result->unknown_tag_count; + report->new_unknown_tag_count = new_result->unknown_tag_count; + report->old_first_unknown_tag = old_result->first_unknown_tag; + report->new_first_unknown_tag = new_result->first_unknown_tag; + report->old_first_unknown_tag_index = old_result->first_unknown_tag_index; + report->new_first_unknown_tag_index = new_result->first_unknown_tag_index; + report->old_truncated = old_result->status == + KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL; + report->new_truncated = new_result->status == + KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL; + report->old_read_error = old_result->status == KZT_GUEST_DYNAMIC_READ_ERROR; + report->new_read_error = new_result->status == KZT_GUEST_DYNAMIC_READ_ERROR; + report->status_match = kzt_guest_dynamic_compare_status(old_result->status, + new_result->status); + report->entry_count_match = kzt_guest_dynamic_compare_size( + old_result->entry_count, new_result->entry_count); + report->unknown_tags_match = kzt_guest_dynamic_compare_unknown(old_result, + new_result); + + if (kzt_guest_dynamic_status_is_blocking(old_result->status)) { + ++report->blocking_count; + } + if (kzt_guest_dynamic_status_is_blocking(new_result->status)) { + ++report->blocking_count; + } + + for (i = 0; i < sizeof(fields) / sizeof(fields[0]); ++i) { + kzt_guest_dynamic_compare_named_field(report, old_view, new_view, + &fields[i]); + } + kzt_guest_dynamic_compare_needed_offsets(report, old_view, new_view); + report->difference_count = kzt_guest_dynamic_count_summary_differences( + report); + + return 0; +} + +const kzt_guest_dynamic_diagnostic_field_t * +kzt_guest_dynamic_diagnostic_find_field( + const kzt_guest_dynamic_diagnostic_report_t *report, + const char *name) +{ + size_t i; + + if (!report || !name) { + return NULL; + } + + for (i = 0; i < report->field_count; ++i) { + if (!strcmp(report->fields[i].name, name)) { + return &report->fields[i]; + } + } + + return NULL; +} diff --git a/target/i386/latx/context/kzt_guest_library_adapter.c b/target/i386/latx/context/kzt_guest_library_adapter.c new file mode 100644 index 00000000000..328e6f420fa --- /dev/null +++ b/target/i386/latx/context/kzt_guest_library_adapter.c @@ -0,0 +1,75 @@ +#include "kzt_guest_library_adapter.h" + +#include "box64context.h" +#include "kzt_guest_library_binding.h" +#include "library.h" +#include "library_private.h" + +static kzt_guest_library_object_type_t loader_object_type(library_t *library) +{ + return library && library->type == LIB_WRAPPED + ? KZT_GUEST_LIBRARY_OBJECT_WRAPPED + : library && library->type == LIB_EMULATED + ? KZT_GUEST_LIBRARY_OBJECT_EMULATED + : KZT_GUEST_LIBRARY_OBJECT_UNSUPPORTED; +} + +void kzt_guest_library_note_loader_pair(box64context_t *context, + uintptr_t link_map_addr, + library_t *library) +{ +#ifdef CONFIG_LATX_KZT + kzt_guest_library_object_type_t type; + if (!context || !link_map_addr || !library) return; + type = loader_object_type(library); + (void)kzt_guest_library_publish_loader_pair( + KztGuestLibraryBindingsForContext(context), link_map_addr, library, + type); +#else + (void)context; + (void)link_map_addr; + (void)library; +#endif +} + +void kzt_guest_library_note_loader_pair_pending( + box64context_t *context, + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr, library_t *library) +{ +#ifdef CONFIG_LATX_KZT + if (!context || !scope || !link_map_addr || !library) return; + (void)kzt_guest_library_loader_scope_note_pair( + scope, link_map_addr, library, loader_object_type(library)); +#else + (void)context; (void)scope; (void)link_map_addr; (void)library; +#endif +} + +void kzt_guest_library_publish_loader_pair_scoped( + box64context_t *context, + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr, library_t *library) +{ +#ifdef CONFIG_LATX_KZT + if (!context || !scope || !link_map_addr || !library) return; + (void)kzt_guest_library_loader_scope_publish_pair( + scope, link_map_addr, library, loader_object_type(library)); +#else + (void)context; (void)scope; (void)link_map_addr; (void)library; +#endif +} + +void kzt_guest_library_publish_loader_observed_scoped( + box64context_t *context, + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr) +{ +#ifdef CONFIG_LATX_KZT + if (!context || !scope || !link_map_addr) return; + (void)kzt_guest_library_loader_scope_publish_observed( + scope, link_map_addr); +#else + (void)context; (void)scope; (void)link_map_addr; +#endif +} diff --git a/target/i386/latx/context/kzt_guest_library_binding.c b/target/i386/latx/context/kzt_guest_library_binding.c new file mode 100644 index 00000000000..41d25dd73d4 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_library_binding.c @@ -0,0 +1,1598 @@ +#include "kzt_guest_library_binding.h" + +#include +#include +#include +#include +#include + +#include "kzt_guest_registry.h" + +typedef struct kzt_guest_library_binding_entry { + kzt_guest_library_binding_key_t key; + library_t *library; + kzt_guest_library_object_type_t object_type; + kzt_guest_library_binding_state_t state; + unsigned int references; + int retire_started; +} kzt_guest_library_binding_entry_t; + +typedef struct kzt_guest_library_lifecycle { + library_t *library; + kzt_guest_library_binding_state_t state; + int destroy_started; + uintptr_t fallback_closed_addr; + unsigned long fallback_closed_epoch; +} kzt_guest_library_lifecycle_t; + +typedef struct kzt_guest_library_callback_gate { + struct kzt_guest_library_callback_gate *next; + uintptr_t link_map_addr; + library_t *closed_by; + unsigned long state; +} kzt_guest_library_callback_gate_t; + +#define KZT_CALLBACK_GATE_CLOSED \ + (1UL << (sizeof(unsigned long) * 8 - 1)) +#define KZT_CALLBACK_GATE_READERS (KZT_CALLBACK_GATE_CLOSED - 1) + +typedef struct kzt_guest_library_pending { + uintptr_t link_map_addr; + library_t *library; + kzt_guest_library_object_type_t object_type; + int active; +} kzt_guest_library_pending_t; + +typedef struct kzt_guest_library_observed { + kzt_guest_library_binding_key_t key; + int claimed; + library_t *retire_owner; + int retire_started; +} kzt_guest_library_observed_t; + +#define KZT_LOADER_ATTEMPT_SLOTS 16 +#define KZT_LOADER_ATTEMPT_OBJECTS 16 + +typedef enum kzt_guest_library_loader_pair_state { + KZT_LOADER_PAIR_EMPTY = 0, + KZT_LOADER_PAIR_PREPARED, + KZT_LOADER_PAIR_PUBLISHED, +} kzt_guest_library_loader_pair_state_t; + +typedef struct kzt_guest_library_loader_object { + uintptr_t link_map_addr; + library_t *library; + kzt_guest_library_object_type_t object_type; + kzt_guest_library_loader_pair_state_t pair_state; + kzt_guest_library_callback_gate_t *transition_gate; + kzt_guest_library_lifecycle_t *transition_fallback; + library_t *next_closed_by; + int transition_pending; + int reopen; +} kzt_guest_library_loader_object_t; + +typedef struct kzt_guest_library_loader_attempt { + unsigned long identity; + unsigned long cookie; + pthread_t owner; + size_t object_count; + int active; + kzt_guest_library_loader_object_t objects[KZT_LOADER_ATTEMPT_OBJECTS]; +} kzt_guest_library_loader_attempt_t; + +typedef struct kzt_guest_library_loader_state { + unsigned long epoch; + kzt_guest_library_loader_attempt_t attempts[KZT_LOADER_ATTEMPT_SLOTS]; +} kzt_guest_library_loader_state_t; + +struct kzt_guest_library_bindings { + pthread_mutex_t lock; + pthread_cond_t idle; + int shutting_down; + kzt_guest_library_binding_entry_t *entries; + size_t count; + size_t capacity; + kzt_guest_library_lifecycle_t *lifecycles; + size_t lifecycle_count; + size_t lifecycle_capacity; + kzt_guest_library_pending_t *pending; + size_t pending_count; + size_t pending_capacity; + kzt_guest_library_observed_t *observed; + size_t observed_count; + size_t observed_capacity; + kzt_guest_library_callback_gate_t *callback_gates; + unsigned int fallback_callback_readers; + kzt_guest_library_loader_state_t *loader_state; + struct { + unsigned long registry_missing; + unsigned long retire_unprovable; + } diagnostics; +}; + +#ifdef KZT_GUEST_LIBRARY_BINDING_TEST +static long fail_after = -1; +static kzt_guest_library_binding_test_retire_fn test_before_registry_retire; +static void *test_before_registry_retire_opaque; +static kzt_guest_library_binding_test_lifecycle_wait_fn + test_before_lifecycle_wait; +static void *test_before_lifecycle_wait_opaque; + +void kzt_guest_library_binding_test_set_alloc_failure_after(long allocations) +{ + fail_after = allocations; +} +void kzt_guest_library_binding_test_set_before_registry_retire( + kzt_guest_library_binding_test_retire_fn hook, void *opaque) +{ + test_before_registry_retire = hook; + test_before_registry_retire_opaque = opaque; +} +void kzt_guest_library_binding_test_set_before_lifecycle_wait( + kzt_guest_library_binding_test_lifecycle_wait_fn hook, void *opaque) +{ + test_before_lifecycle_wait = hook; + test_before_lifecycle_wait_opaque = opaque; +} +static int should_fail_alloc(void) +{ + if (fail_after < 0) return 0; + if (fail_after == 0) return 1; + --fail_after; + return 0; +} +#else +static int should_fail_alloc(void) { return 0; } +#endif + +static void *binding_calloc(size_t count, size_t size) +{ + return should_fail_alloc() ? NULL : calloc(count, size); +} + +static kzt_guest_library_loader_object_t *find_gate_transition_locked( + kzt_guest_library_bindings_t *bindings, + kzt_guest_library_callback_gate_t *gate); + +static int grow_array(void **array, size_t *capacity, size_t element_size) +{ + size_t next = *capacity ? *capacity * 2 : 8; + void *grown; + if (next < *capacity || next > SIZE_MAX / element_size || + should_fail_alloc()) + return -1; + grown = realloc(*array, next * element_size); + if (!grown) return -1; + *array = grown; + *capacity = next; + return 0; +} + +static int same_key(const kzt_guest_library_binding_key_t *a, + const kzt_guest_library_binding_key_t *b) +{ + return a->link_map_addr == b->link_map_addr && + a->generation == b->generation && + a->namespace_id == b->namespace_id && + a->namespace_kind == b->namespace_kind; +} + +static int supported_key(const kzt_guest_library_binding_key_t *key) +{ + return key && key->link_map_addr && key->generation && + key->namespace_kind == KZT_GUEST_LIBRARY_NAMESPACE_MAIN && + key->namespace_id == 0; +} + +static kzt_guest_library_lifecycle_t *find_lifecycle_locked( + kzt_guest_library_bindings_t *bindings, library_t *library) +{ + for (size_t i = 0; i < bindings->lifecycle_count; ++i) + if (bindings->lifecycles[i].library == library) + return &bindings->lifecycles[i]; + return NULL; +} + +static kzt_guest_library_observed_t *find_observed_locked( + kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr) +{ + for (size_t i = 0; i < bindings->observed_count; ++i) + if (bindings->observed[i].key.link_map_addr == link_map_addr) + return &bindings->observed[i]; + return NULL; +} + +static kzt_guest_library_observed_t *find_observed_key_locked( + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_binding_key_t *key) +{ + for (size_t i = 0; i < bindings->observed_count; ++i) + if (same_key(&bindings->observed[i].key, key)) + return &bindings->observed[i]; + return NULL; +} + +static kzt_guest_library_callback_gate_t *find_callback_gate_locked( + kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr) +{ + for (kzt_guest_library_callback_gate_t *gate = bindings->callback_gates; + gate; gate = gate->next) + if (gate->link_map_addr == link_map_addr) + return gate; + return NULL; +} + +static kzt_guest_library_callback_gate_t *find_callback_gate( + kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr) +{ + kzt_guest_library_callback_gate_t *gate = + __atomic_load_n(&bindings->callback_gates, __ATOMIC_ACQUIRE); + for (; gate; gate = gate->next) + if (gate->link_map_addr == link_map_addr) + return gate; + return NULL; +} + +static kzt_guest_library_callback_gate_t *add_callback_gate_locked( + kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr) +{ + kzt_guest_library_callback_gate_t *gate = + binding_calloc(1, sizeof(*gate)); + if (!gate) return NULL; + gate->link_map_addr = link_map_addr; + gate->next = bindings->callback_gates; + __atomic_store_n(&bindings->callback_gates, gate, __ATOMIC_RELEASE); + return gate; +} + +static void close_callback_addr_locked( + kzt_guest_library_bindings_t *bindings, + kzt_guest_library_lifecycle_t *lifecycle, library_t *library, + uintptr_t link_map_addr) +{ + kzt_guest_library_callback_gate_t *gate; + if (!link_map_addr) return; + lifecycle->fallback_closed_epoch = + bindings->loader_state ? bindings->loader_state->epoch : 0; + gate = find_callback_gate_locked(bindings, link_map_addr); + if (!gate) + gate = add_callback_gate_locked(bindings, link_map_addr); + if (!gate) { + /* A lifecycle normally has one current link_map. Keep its closed + * address inline so allocation failure cannot admit a late callback + * or block unrelated libraries. */ + lifecycle->fallback_closed_addr = link_map_addr; + return; + } + unsigned long state = __atomic_load_n(&gate->state, __ATOMIC_ACQUIRE); + if ((state & KZT_CALLBACK_GATE_CLOSED) && + (state & KZT_CALLBACK_GATE_READERS) && gate->closed_by && + gate->closed_by != library) { + kzt_guest_library_loader_object_t *transition = + find_gate_transition_locked(bindings, gate); + if (transition) { + transition->next_closed_by = library; + transition->reopen = 0; + } else { + lifecycle->fallback_closed_addr = link_map_addr; + } + } else { + gate->closed_by = library; + } + __atomic_fetch_or(&gate->state, KZT_CALLBACK_GATE_CLOSED, + __ATOMIC_ACQ_REL); +} + +static int shutting_down(kzt_guest_library_bindings_t *bindings) +{ + return __atomic_load_n(&bindings->shutting_down, __ATOMIC_ACQUIRE); +} + +static kzt_guest_library_loader_attempt_t *find_loader_attempt_locked( + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_loader_scope_t *scope) +{ + if (!scope || scope->bindings != bindings || !scope->identity || + !bindings->loader_state || + !scope->cookie) + return NULL; + for (size_t i = 0; i < KZT_LOADER_ATTEMPT_SLOTS; ++i) { + kzt_guest_library_loader_attempt_t *attempt = + &bindings->loader_state->attempts[i]; + if (attempt->active && attempt->identity == scope->identity && + attempt->cookie == scope->cookie) + return attempt; + } + return NULL; +} + +static kzt_guest_library_loader_attempt_t *loader_scope_valid_locked( + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_loader_scope_t *scope) +{ + kzt_guest_library_loader_attempt_t *attempt = + find_loader_attempt_locked(bindings, scope); + unsigned long newest = 0; + + if (!attempt || !pthread_equal(attempt->owner, pthread_self())) + return NULL; + for (size_t i = 0; i < KZT_LOADER_ATTEMPT_SLOTS; ++i) { + kzt_guest_library_loader_attempt_t *candidate = + &bindings->loader_state->attempts[i]; + if (candidate->active && + pthread_equal(candidate->owner, attempt->owner) && + candidate->identity > newest) + newest = candidate->identity; + } + return newest == attempt->identity ? attempt : NULL; +} + +static kzt_guest_library_loader_object_t *find_loader_object_locked( + kzt_guest_library_loader_attempt_t *attempt, uintptr_t link_map_addr) +{ + for (size_t i = 0; i < attempt->object_count; ++i) + if (attempt->objects[i].link_map_addr == link_map_addr) + return &attempt->objects[i]; + return NULL; +} + +static kzt_guest_library_loader_object_t *find_gate_transition_locked( + kzt_guest_library_bindings_t *bindings, + kzt_guest_library_callback_gate_t *gate) +{ + if (!bindings->loader_state) return NULL; + for (size_t i = 0; i < KZT_LOADER_ATTEMPT_SLOTS; ++i) { + kzt_guest_library_loader_attempt_t *attempt = + &bindings->loader_state->attempts[i]; + for (size_t j = 0; j < attempt->object_count; ++j) { + kzt_guest_library_loader_object_t *object = &attempt->objects[j]; + if (object->transition_pending && + object->transition_gate == gate) + return object; + } + } + return NULL; +} + +static void clear_loader_transition_locked( + kzt_guest_library_bindings_t *bindings, + kzt_guest_library_loader_object_t *object) +{ + kzt_guest_library_loader_attempt_t *attempt = NULL; + if (!bindings->loader_state || !object) return; + for (size_t i = 0; i < KZT_LOADER_ATTEMPT_SLOTS; ++i) { + kzt_guest_library_loader_attempt_t *candidate = + &bindings->loader_state->attempts[i]; + if (object >= candidate->objects && + object < candidate->objects + KZT_LOADER_ATTEMPT_OBJECTS) { + attempt = candidate; + break; + } + } + object->transition_gate = NULL; + object->transition_fallback = NULL; + object->next_closed_by = NULL; + object->transition_pending = 0; + object->reopen = 0; + if (attempt && !attempt->active) { + int pending = 0; + for (size_t i = 0; i < attempt->object_count; ++i) + pending |= attempt->objects[i].transition_pending; + if (!pending) memset(attempt, 0, sizeof(*attempt)); + } +} + +static int observe_loader_object_locked( + kzt_guest_library_loader_attempt_t *attempt, uintptr_t link_map_addr) +{ + if (find_loader_object_locked(attempt, link_map_addr)) + return 0; + if (attempt->object_count == KZT_LOADER_ATTEMPT_OBJECTS) + return -1; + attempt->objects[attempt->object_count++].link_map_addr = link_map_addr; + return 0; +} + +static void reopen_callback_addr_locked( + kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr, + library_t *library, kzt_guest_library_loader_attempt_t *attempt) +{ + kzt_guest_library_callback_gate_t *gate = + find_callback_gate_locked(bindings, link_map_addr); + (void)library; + + if (gate && (__atomic_load_n(&gate->state, __ATOMIC_ACQUIRE) & + KZT_CALLBACK_GATE_CLOSED)) { + unsigned long state = __atomic_load_n(&gate->state, __ATOMIC_ACQUIRE); + if (state & KZT_CALLBACK_GATE_READERS) { + kzt_guest_library_loader_object_t *object = + attempt ? find_loader_object_locked(attempt, link_map_addr) + : NULL; + if (object) { + object->transition_gate = gate; + object->transition_pending = 1; + object->reopen = 1; + } + } else { + kzt_guest_library_lifecycle_t *owner = + find_lifecycle_locked(bindings, gate->closed_by); + if (owner) owner->fallback_closed_epoch = 0; + gate->closed_by = NULL; + __atomic_fetch_and(&gate->state, ~KZT_CALLBACK_GATE_CLOSED, + __ATOMIC_ACQ_REL); + } + } + for (size_t i = 0; i < bindings->lifecycle_count; ++i) { + if (bindings->lifecycles[i].fallback_closed_addr == link_map_addr) { + if (bindings->fallback_callback_readers) { + kzt_guest_library_loader_object_t *object = + attempt ? find_loader_object_locked( + attempt, link_map_addr) : NULL; + if (object) { + object->transition_fallback = &bindings->lifecycles[i]; + object->transition_pending = 1; + object->reopen = 1; + } + } else { + bindings->lifecycles[i].fallback_closed_addr = 0; + bindings->lifecycles[i].fallback_closed_epoch = 0; + } + } + } +} + +static int callback_access_busy_locked( + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_lifecycle_t *lifecycle, library_t *library) +{ + if (bindings->fallback_callback_readers) + return 1; + for (kzt_guest_library_callback_gate_t *gate = bindings->callback_gates; + gate; gate = gate->next) { + if (gate->closed_by == library && + (__atomic_load_n(&gate->state, __ATOMIC_ACQUIRE) & + KZT_CALLBACK_GATE_READERS)) + return 1; + } + return 0; +} + +static kzt_guest_library_binding_result_t bind_locked( + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_binding_key_t *key, library_t *library, + kzt_guest_library_object_type_t object_type) +{ + kzt_guest_library_lifecycle_t *lifecycle; + if (shutting_down(bindings) || !supported_key(key) || !library || + object_type == KZT_GUEST_LIBRARY_OBJECT_MAIN || + object_type == KZT_GUEST_LIBRARY_OBJECT_UNSUPPORTED) + return KZT_GUEST_LIBRARY_BINDING_ERROR; + lifecycle = find_lifecycle_locked(bindings, library); + if (!lifecycle || lifecycle->state != KZT_GUEST_LIBRARY_BINDING_LIVE) + return KZT_GUEST_LIBRARY_BINDING_ERROR; + for (size_t i = 0; i < bindings->count; ++i) { + kzt_guest_library_binding_entry_t *entry = &bindings->entries[i]; + if (!same_key(&entry->key, key) || + entry->state == KZT_GUEST_LIBRARY_BINDING_DEAD) + continue; + return entry->state == KZT_GUEST_LIBRARY_BINDING_LIVE && + entry->library == library && + entry->object_type == object_type + ? KZT_GUEST_LIBRARY_BINDING_UNCHANGED + : KZT_GUEST_LIBRARY_BINDING_CONFLICT; + } + if (bindings->count == bindings->capacity && + grow_array((void **)&bindings->entries, &bindings->capacity, + sizeof(*bindings->entries)) != 0) + return KZT_GUEST_LIBRARY_BINDING_ERROR; + bindings->entries[bindings->count++] = + (kzt_guest_library_binding_entry_t){ + .key = *key, + .library = library, + .object_type = object_type, + .state = KZT_GUEST_LIBRARY_BINDING_LIVE, + }; + return KZT_GUEST_LIBRARY_BINDING_ADDED; +} + +kzt_guest_library_bindings_t *kzt_guest_library_bindings_init(void) +{ + kzt_guest_library_bindings_t *bindings = + binding_calloc(1, sizeof(*bindings)); + if (!bindings) return NULL; + if (pthread_mutex_init(&bindings->lock, NULL) != 0) { + free(bindings); + return NULL; + } + if (pthread_cond_init(&bindings->idle, NULL) != 0) { + pthread_mutex_destroy(&bindings->lock); + free(bindings); + return NULL; + } + return bindings; +} + +void kzt_guest_library_bindings_begin_teardown( + kzt_guest_library_bindings_t *bindings) +{ + if (!bindings) return; + pthread_mutex_lock(&bindings->lock); + __atomic_store_n(&bindings->shutting_down, 1, __ATOMIC_RELEASE); + for (size_t i = 0; i < bindings->pending_count; ++i) + bindings->pending[i].active = 0; + for (size_t i = 0; i < bindings->count; ++i) + bindings->entries[i].state = KZT_GUEST_LIBRARY_BINDING_UNLOADING; + for (;;) { + int busy = 0; + for (size_t i = 0; i < bindings->count; ++i) + busy |= bindings->entries[i].references != 0; + for (size_t i = 0; i < bindings->lifecycle_count; ++i) + busy |= bindings->lifecycles[i].state == + KZT_GUEST_LIBRARY_BINDING_UNLOADING; + busy |= bindings->fallback_callback_readers != 0; + for (kzt_guest_library_callback_gate_t *gate = + bindings->callback_gates; + gate; gate = gate->next) + busy |= (__atomic_load_n(&gate->state, __ATOMIC_ACQUIRE) & + KZT_CALLBACK_GATE_READERS) != 0; + if (!busy) break; + pthread_cond_wait(&bindings->idle, &bindings->lock); + } + pthread_mutex_unlock(&bindings->lock); +} + +void kzt_guest_library_bindings_destroy(kzt_guest_library_bindings_t **slot) +{ + kzt_guest_library_bindings_t *bindings; + if (!slot || !(bindings = *slot)) return; + kzt_guest_library_bindings_begin_teardown(bindings); + pthread_cond_destroy(&bindings->idle); + pthread_mutex_destroy(&bindings->lock); + while (bindings->callback_gates) { + kzt_guest_library_callback_gate_t *next = + bindings->callback_gates->next; + free(bindings->callback_gates); + bindings->callback_gates = next; + } + free(bindings->observed); + free(bindings->pending); + if (bindings->loader_state) free(bindings->loader_state); + free(bindings->lifecycles); + free(bindings->entries); + free(bindings); + *slot = NULL; +} + +int kzt_guest_library_access_init(kzt_guest_library_access_t *access) +{ + if (!access) return -1; + memset(access, 0, sizeof(*access)); + if (pthread_mutex_init(&access->lock, NULL) != 0) + return -1; + access->initialized = 1; + access->bindings = kzt_guest_library_bindings_init(); + access->accepting = 1; + return 0; +} + +void kzt_guest_library_access_begin_teardown( + kzt_guest_library_access_t *access) +{ + if (!access || !access->initialized) return; + pthread_mutex_lock(&access->lock); + access->accepting = 0; + /* Closing the context-owned gate under its lock drains any lookup already + * inside the gate and prevents new acquisitions. Drop the gate before + * waiting for binding handles or unload owners so a source-lease holder + * can fast-fail provider lookup and release its lease. */ + pthread_mutex_unlock(&access->lock); + kzt_guest_library_bindings_begin_teardown(access->bindings); +} + +void kzt_guest_library_access_destroy(kzt_guest_library_access_t *access) +{ + if (!access || !access->initialized) return; + kzt_guest_library_access_begin_teardown(access); + kzt_guest_library_bindings_destroy(&access->bindings); + pthread_mutex_destroy(&access->lock); + memset(access, 0, sizeof(*access)); +} + +int kzt_guest_library_track(kzt_guest_library_bindings_t *bindings, + library_t *library) +{ + kzt_guest_library_lifecycle_t *lifecycle; + int result = -1; + if (!bindings || !library) return -1; + pthread_mutex_lock(&bindings->lock); + if (shutting_down(bindings)) { + goto out; + } + lifecycle = find_lifecycle_locked(bindings, library); + if (lifecycle) { + result = lifecycle->state == KZT_GUEST_LIBRARY_BINDING_LIVE ? 0 : -1; + goto out; + } + if (bindings->lifecycle_count == bindings->lifecycle_capacity && + grow_array((void **)&bindings->lifecycles, + &bindings->lifecycle_capacity, + sizeof(*bindings->lifecycles)) != 0) { + goto out; + } + bindings->lifecycles[bindings->lifecycle_count++] = + (kzt_guest_library_lifecycle_t){ + .library = library, + .state = KZT_GUEST_LIBRARY_BINDING_LIVE, + }; + result = 0; +out: + pthread_mutex_unlock(&bindings->lock); + return result; +} + +int kzt_guest_library_reactivate(kzt_guest_library_bindings_t *bindings, + library_t *library) +{ + kzt_guest_library_lifecycle_t *lifecycle; + int result = -1; + if (!bindings || !library) return -1; + pthread_mutex_lock(&bindings->lock); + lifecycle = find_lifecycle_locked(bindings, library); + if (shutting_down(bindings) || !lifecycle || lifecycle->destroy_started) { + goto out; + } + if (lifecycle->state == KZT_GUEST_LIBRARY_BINDING_LIVE) { + result = 0; + goto out; + } + if (lifecycle->state != KZT_GUEST_LIBRARY_BINDING_DEAD) + goto out; + lifecycle->state = KZT_GUEST_LIBRARY_BINDING_LIVE; + result = 0; +out: + pthread_mutex_unlock(&bindings->lock); + return result; +} + +int kzt_guest_library_loader_scope_begin( + kzt_guest_library_bindings_t *bindings, + kzt_guest_library_loader_scope_t *scope) +{ + int result = -1; + pthread_t owner = pthread_self(); + + if (scope) memset(scope, 0, sizeof(*scope)); + if (!bindings || !scope) return -1; + pthread_mutex_lock(&bindings->lock); + if (!shutting_down(bindings) && + (!bindings->loader_state || bindings->loader_state->epoch != ULONG_MAX)) { + kzt_guest_library_loader_attempt_t *slot = NULL; + if (!bindings->loader_state) + bindings->loader_state = binding_calloc( + 1, sizeof(*bindings->loader_state)); + if (!bindings->loader_state) + goto out; + for (size_t i = 0; i < KZT_LOADER_ATTEMPT_SLOTS; ++i) { + kzt_guest_library_loader_attempt_t *attempt = + &bindings->loader_state->attempts[i]; + if (!attempt->active && !attempt->identity && !slot) + slot = attempt; + } + if (slot) { + unsigned long identity = ++bindings->loader_state->epoch; + unsigned long cookie = identity ^ (unsigned long)(uintptr_t)bindings ^ + (unsigned long)(uintptr_t)slot; + if (!cookie) cookie = ~identity; + memset(slot, 0, sizeof(*slot)); + slot->identity = identity; + slot->cookie = cookie; + slot->owner = owner; + slot->active = 1; + scope->bindings = bindings; + scope->identity = identity; + scope->cookie = cookie; + result = 0; + } + } +out: + pthread_mutex_unlock(&bindings->lock); + return result; +} + +void kzt_guest_library_loader_scope_end( + kzt_guest_library_loader_scope_t *scope) +{ + kzt_guest_library_bindings_t *bindings; + if (!scope || !(bindings = scope->bindings)) return; + pthread_mutex_lock(&bindings->lock); + kzt_guest_library_loader_attempt_t *attempt = + find_loader_attempt_locked(bindings, scope); + if (attempt && pthread_equal(attempt->owner, pthread_self())) { + int pending = 0; + for (size_t i = 0; i < attempt->object_count; ++i) + pending |= attempt->objects[i].transition_pending; + if (pending) + attempt->active = 0; + else + memset(attempt, 0, sizeof(*attempt)); + } + pthread_mutex_unlock(&bindings->lock); + memset(scope, 0, sizeof(*scope)); +} + +static int callback_access_begin( + kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr, + const kzt_guest_library_loader_scope_t *scope, + kzt_guest_library_callback_access_t *access) +{ + kzt_guest_library_callback_gate_t *gate; + kzt_guest_library_loader_attempt_t *attempt; + kzt_guest_library_lifecycle_t *fallback_owner = NULL; + int closed_seen = 0; + + if (access) memset(access, 0, sizeof(*access)); + if (!bindings || !link_map_addr || !access) return -1; + if (shutting_down(bindings)) return -1; + gate = find_callback_gate(bindings, link_map_addr); + if (gate) { + unsigned long state = + __atomic_load_n(&gate->state, __ATOMIC_ACQUIRE); + if (state & KZT_CALLBACK_GATE_CLOSED) + goto slow; + do { + if ((state & KZT_CALLBACK_GATE_READERS) == + KZT_CALLBACK_GATE_READERS) + return -1; + } while (!__atomic_compare_exchange_n( + &gate->state, &state, state + 1, 1, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)); + if (scope && scope->bindings) { + int valid; + pthread_mutex_lock(&bindings->lock); + kzt_guest_library_loader_attempt_t *fast_attempt = + loader_scope_valid_locked(bindings, scope); + valid = fast_attempt && + observe_loader_object_locked( + fast_attempt, link_map_addr) == 0; + pthread_mutex_unlock(&bindings->lock); + if (!valid) { + __atomic_fetch_sub(&gate->state, 1, __ATOMIC_ACQ_REL); + return -1; + } + } + if (shutting_down(bindings)) { + unsigned long previous = __atomic_fetch_sub( + &gate->state, 1, __ATOMIC_ACQ_REL); + if ((previous & KZT_CALLBACK_GATE_READERS) == 1) { + pthread_mutex_lock(&bindings->lock); + pthread_cond_broadcast(&bindings->idle); + pthread_mutex_unlock(&bindings->lock); + } + return -1; + } + access->bindings = bindings; + access->link_map_addr = link_map_addr; + access->gate = gate; + return 0; + } + +slow: + pthread_mutex_lock(&bindings->lock); + if (shutting_down(bindings)) { + pthread_mutex_unlock(&bindings->lock); + return -1; + } + if (!scope || !scope->bindings) { + for (size_t i = 0; i < bindings->lifecycle_count; ++i) { + if (bindings->lifecycles[i].fallback_closed_addr == + link_map_addr) { + pthread_mutex_unlock(&bindings->lock); + return -1; + } + } + gate = find_callback_gate_locked(bindings, link_map_addr); + if (gate && (__atomic_load_n(&gate->state, __ATOMIC_ACQUIRE) & + KZT_CALLBACK_GATE_CLOSED)) { + pthread_mutex_unlock(&bindings->lock); + return -1; + } + if (!gate) + gate = add_callback_gate_locked(bindings, link_map_addr); + goto acquire_locked; + } + attempt = loader_scope_valid_locked(bindings, scope); + if (!attempt) { + pthread_mutex_unlock(&bindings->lock); + return -1; + } + for (size_t i = 0; i < bindings->lifecycle_count; ++i) { + kzt_guest_library_lifecycle_t *lifecycle = &bindings->lifecycles[i]; + if (lifecycle->fallback_closed_addr != link_map_addr) + continue; + closed_seen = 1; + fallback_owner = lifecycle; + if (!attempt || attempt->identity <= lifecycle->fallback_closed_epoch) { + pthread_mutex_unlock(&bindings->lock); + return -1; + } + } + gate = find_callback_gate_locked(bindings, link_map_addr); + if (gate && (__atomic_load_n(&gate->state, __ATOMIC_ACQUIRE) & + KZT_CALLBACK_GATE_CLOSED)) { + kzt_guest_library_lifecycle_t *owner = + find_lifecycle_locked(bindings, gate->closed_by); + unsigned long closed_epoch = owner ? owner->fallback_closed_epoch : 0; + + closed_seen = 1; + if (!attempt || attempt->identity <= closed_epoch) { + pthread_mutex_unlock(&bindings->lock); + return -1; + } + } + if (attempt && observe_loader_object_locked(attempt, link_map_addr) != 0) { + pthread_mutex_unlock(&bindings->lock); + return -1; + } + if (!gate) + gate = add_callback_gate_locked(bindings, link_map_addr); + if (gate && closed_seen && fallback_owner && + !(__atomic_load_n(&gate->state, __ATOMIC_ACQUIRE) & + KZT_CALLBACK_GATE_CLOSED)) { + gate->closed_by = fallback_owner->library; + __atomic_fetch_or(&gate->state, KZT_CALLBACK_GATE_CLOSED, + __ATOMIC_ACQ_REL); + } +acquire_locked: + access->bindings = bindings; + access->link_map_addr = link_map_addr; + if (gate) { + unsigned long state = __atomic_load_n(&gate->state, __ATOMIC_ACQUIRE); + if ((state & KZT_CALLBACK_GATE_READERS) == KZT_CALLBACK_GATE_READERS) { + pthread_mutex_unlock(&bindings->lock); + memset(access, 0, sizeof(*access)); + return -1; + } + __atomic_add_fetch(&gate->state, 1, __ATOMIC_ACQ_REL); + access->gate = gate; + } else { + ++bindings->fallback_callback_readers; + access->fallback = 1; + } + pthread_mutex_unlock(&bindings->lock); + return 0; +} + +int kzt_guest_library_callback_access_begin( + kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr, + kzt_guest_library_callback_access_t *access) +{ + return callback_access_begin(bindings, link_map_addr, NULL, access); +} + +int kzt_guest_library_callback_access_begin_scoped( + kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr, + const kzt_guest_library_loader_scope_t *scope, + kzt_guest_library_callback_access_t *access) +{ + return callback_access_begin(bindings, link_map_addr, scope, access); +} + +void kzt_guest_library_callback_access_end( + kzt_guest_library_callback_access_t *access) +{ + kzt_guest_library_bindings_t *bindings; + if (!access || !(bindings = access->bindings)) return; + if (access->gate) { + kzt_guest_library_callback_gate_t *gate = access->gate; + unsigned long previous = __atomic_fetch_sub( + &gate->state, 1, __ATOMIC_ACQ_REL); + int last = (previous & KZT_CALLBACK_GATE_READERS) == 1; + if (last && ((previous & KZT_CALLBACK_GATE_CLOSED) || + shutting_down(bindings))) { + pthread_mutex_lock(&bindings->lock); + if (!shutting_down(bindings) && + (__atomic_load_n(&gate->state, __ATOMIC_ACQUIRE) & + KZT_CALLBACK_GATE_READERS) == 0) { + kzt_guest_library_loader_object_t *transition = + find_gate_transition_locked(bindings, gate); + if (transition && transition->next_closed_by) { + gate->closed_by = transition->next_closed_by; + clear_loader_transition_locked(bindings, transition); + } else if (transition && transition->reopen) { + kzt_guest_library_lifecycle_t *owner = + find_lifecycle_locked(bindings, gate->closed_by); + if (owner) owner->fallback_closed_epoch = 0; + gate->closed_by = NULL; + __atomic_fetch_and(&gate->state, + ~KZT_CALLBACK_GATE_CLOSED, + __ATOMIC_ACQ_REL); + clear_loader_transition_locked(bindings, transition); + } + } + pthread_cond_broadcast(&bindings->idle); + pthread_mutex_unlock(&bindings->lock); + } + memset(access, 0, sizeof(*access)); + return; + } + pthread_mutex_lock(&bindings->lock); + if (access->fallback) { + if (bindings->fallback_callback_readers) + --bindings->fallback_callback_readers; + if (!bindings->fallback_callback_readers && + !shutting_down(bindings)) { + if (bindings->loader_state) { + for (size_t i = 0; i < KZT_LOADER_ATTEMPT_SLOTS; ++i) { + kzt_guest_library_loader_attempt_t *attempt = + &bindings->loader_state->attempts[i]; + for (size_t j = 0; j < attempt->object_count; ++j) { + kzt_guest_library_loader_object_t *object = + &attempt->objects[j]; + if (object->transition_pending && object->reopen && + object->transition_fallback) { + object->transition_fallback->fallback_closed_addr = 0; + object->transition_fallback->fallback_closed_epoch = 0; + clear_loader_transition_locked(bindings, object); + } + } + } + } + } + } + pthread_cond_broadcast(&bindings->idle); + pthread_mutex_unlock(&bindings->lock); + memset(access, 0, sizeof(*access)); +} + +kzt_guest_library_binding_result_t kzt_guest_library_bind( + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_binding_key_t *key, library_t *library, + kzt_guest_library_object_type_t object_type) +{ + kzt_guest_library_binding_result_t result; + if (!bindings) return KZT_GUEST_LIBRARY_BINDING_DISABLED; + pthread_mutex_lock(&bindings->lock); + result = bind_locked(bindings, key, library, object_type); + pthread_mutex_unlock(&bindings->lock); + return result; +} + +static kzt_guest_library_binding_result_t note_exact_pair_locked( + kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr, + library_t *library, kzt_guest_library_object_type_t object_type) +{ + kzt_guest_library_observed_t *observed; + kzt_guest_library_lifecycle_t *lifecycle; + kzt_guest_library_binding_result_t result; + lifecycle = find_lifecycle_locked(bindings, library); + if (shutting_down(bindings) || !lifecycle || + lifecycle->state != KZT_GUEST_LIBRARY_BINDING_LIVE) { + result = KZT_GUEST_LIBRARY_BINDING_ERROR; + return result; + } + observed = find_observed_locked(bindings, link_map_addr); + if (observed) { + if (observed->retire_owner) { + return KZT_GUEST_LIBRARY_BINDING_CANCELLED; + } + result = bind_locked(bindings, &observed->key, library, object_type); + if (result == KZT_GUEST_LIBRARY_BINDING_ADDED || + result == KZT_GUEST_LIBRARY_BINDING_UNCHANGED) + observed->claimed = 1; + return result; + } + for (size_t i = 0; i < bindings->pending_count; ++i) { + kzt_guest_library_pending_t *pending = &bindings->pending[i]; + if (!pending->active || pending->link_map_addr != link_map_addr) + continue; + result = pending->library == library && + pending->object_type == object_type + ? KZT_GUEST_LIBRARY_BINDING_PENDING + : KZT_GUEST_LIBRARY_BINDING_CONFLICT; + return result; + } + if (bindings->pending_count == bindings->pending_capacity && + grow_array((void **)&bindings->pending, &bindings->pending_capacity, + sizeof(*bindings->pending)) != 0) { + return KZT_GUEST_LIBRARY_BINDING_ERROR; + } + bindings->pending[bindings->pending_count++] = + (kzt_guest_library_pending_t){ + .link_map_addr = link_map_addr, + .library = library, + .object_type = object_type, + .active = 1, + }; + return KZT_GUEST_LIBRARY_BINDING_PENDING; +} + +kzt_guest_library_binding_result_t kzt_guest_library_note_exact_pair( + kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr, + library_t *library, kzt_guest_library_object_type_t object_type) +{ + kzt_guest_library_binding_result_t result; + if (!link_map_addr || !library || + object_type == KZT_GUEST_LIBRARY_OBJECT_MAIN || + object_type == KZT_GUEST_LIBRARY_OBJECT_UNSUPPORTED) + return KZT_GUEST_LIBRARY_BINDING_ERROR; + if (!bindings) return KZT_GUEST_LIBRARY_BINDING_DISABLED; + pthread_mutex_lock(&bindings->lock); + result = note_exact_pair_locked(bindings, link_map_addr, library, + object_type); + pthread_mutex_unlock(&bindings->lock); + return result; +} + +kzt_guest_library_binding_result_t +kzt_guest_library_loader_scope_note_pair( + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr, library_t *library, + kzt_guest_library_object_type_t object_type) +{ + kzt_guest_library_bindings_t *bindings; + kzt_guest_library_loader_attempt_t *attempt; + kzt_guest_library_loader_object_t *object; + kzt_guest_library_lifecycle_t *lifecycle; + + if (!scope || !(bindings = scope->bindings) || !link_map_addr || !library || + object_type == KZT_GUEST_LIBRARY_OBJECT_MAIN || + object_type == KZT_GUEST_LIBRARY_OBJECT_UNSUPPORTED) + return KZT_GUEST_LIBRARY_BINDING_ERROR; + pthread_mutex_lock(&bindings->lock); + attempt = loader_scope_valid_locked(bindings, scope); + object = attempt ? find_loader_object_locked(attempt, link_map_addr) : NULL; + lifecycle = find_lifecycle_locked(bindings, library); + if (shutting_down(bindings) || !object || !lifecycle || + lifecycle->state != KZT_GUEST_LIBRARY_BINDING_LIVE || + lifecycle->destroy_started || + (object->pair_state != KZT_LOADER_PAIR_EMPTY && + (object->library != library || + object->object_type != object_type))) { + pthread_mutex_unlock(&bindings->lock); + return KZT_GUEST_LIBRARY_BINDING_ERROR; + } + object->library = library; + object->object_type = object_type; + if (object->pair_state == KZT_LOADER_PAIR_EMPTY) + object->pair_state = KZT_LOADER_PAIR_PREPARED; + pthread_mutex_unlock(&bindings->lock); + return KZT_GUEST_LIBRARY_BINDING_PENDING; +} + +kzt_guest_library_binding_result_t +kzt_guest_library_loader_scope_publish_pair( + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr, library_t *library, + kzt_guest_library_object_type_t object_type) +{ + kzt_guest_library_binding_result_t result; + kzt_guest_library_bindings_t *bindings; + kzt_guest_library_loader_attempt_t *attempt; + kzt_guest_library_loader_object_t *object; + kzt_guest_library_lifecycle_t *lifecycle; + + if (!scope || !(bindings = scope->bindings) || !link_map_addr || !library) + return KZT_GUEST_LIBRARY_BINDING_ERROR; + pthread_mutex_lock(&bindings->lock); + attempt = loader_scope_valid_locked(bindings, scope); + object = attempt ? find_loader_object_locked(attempt, link_map_addr) : NULL; + lifecycle = find_lifecycle_locked(bindings, library); + if (shutting_down(bindings) || !object || + object->pair_state != KZT_LOADER_PAIR_PREPARED || + object->library != library || + object->object_type != object_type || !lifecycle || + lifecycle->state != KZT_GUEST_LIBRARY_BINDING_LIVE || + lifecycle->destroy_started) { + pthread_mutex_unlock(&bindings->lock); + return KZT_GUEST_LIBRARY_BINDING_ERROR; + } + result = note_exact_pair_locked(bindings, link_map_addr, library, + object_type); + if (result != KZT_GUEST_LIBRARY_BINDING_ADDED && + result != KZT_GUEST_LIBRARY_BINDING_UNCHANGED && + result != KZT_GUEST_LIBRARY_BINDING_PENDING) { + pthread_mutex_unlock(&bindings->lock); + return result; + } + object->pair_state = KZT_LOADER_PAIR_PUBLISHED; + reopen_callback_addr_locked(bindings, link_map_addr, library, attempt); + pthread_mutex_unlock(&bindings->lock); + return result; +} + +kzt_guest_library_binding_result_t +kzt_guest_library_loader_scope_publish_observed( + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr) +{ + kzt_guest_library_bindings_t *bindings; + library_t *library = NULL; + kzt_guest_library_object_type_t object_type = + KZT_GUEST_LIBRARY_OBJECT_UNSUPPORTED; + + if (!scope || !(bindings = scope->bindings)) + return KZT_GUEST_LIBRARY_BINDING_ERROR; + pthread_mutex_lock(&bindings->lock); + kzt_guest_library_loader_attempt_t *attempt = + loader_scope_valid_locked(bindings, scope); + kzt_guest_library_loader_object_t *object = + attempt ? find_loader_object_locked(attempt, link_map_addr) : NULL; + if (object) { + library = object->library; + object_type = object->object_type; + } + pthread_mutex_unlock(&bindings->lock); + if (!library) + return KZT_GUEST_LIBRARY_BINDING_ERROR; + return kzt_guest_library_loader_scope_publish_pair( + scope, link_map_addr, library, object_type); +} + +kzt_guest_library_binding_result_t kzt_guest_library_publish_loader_pair( + kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr, + library_t *library, kzt_guest_library_object_type_t object_type) +{ + kzt_guest_library_binding_result_t result = + kzt_guest_library_note_exact_pair(bindings, link_map_addr, library, + object_type); + + if (!bindings || (result != KZT_GUEST_LIBRARY_BINDING_ADDED && + result != KZT_GUEST_LIBRARY_BINDING_UNCHANGED && + result != KZT_GUEST_LIBRARY_BINDING_PENDING)) + return result; + pthread_mutex_lock(&bindings->lock); + kzt_guest_library_lifecycle_t *lifecycle = + find_lifecycle_locked(bindings, library); + if (!shutting_down(bindings) && lifecycle && + lifecycle->state == KZT_GUEST_LIBRARY_BINDING_LIVE && + !lifecycle->destroy_started) { + kzt_guest_library_callback_gate_t *gate = + find_callback_gate_locked(bindings, link_map_addr); + int same_reactivated_library = + gate && gate->closed_by == library; + for (size_t i = 0; i < bindings->lifecycle_count; ++i) + if (bindings->lifecycles[i].library == library && + bindings->lifecycles[i].fallback_closed_addr == link_map_addr) + same_reactivated_library = 1; + if (same_reactivated_library) + reopen_callback_addr_locked(bindings, link_map_addr, library, + NULL); + } + pthread_mutex_unlock(&bindings->lock); + return result; +} + +kzt_guest_library_binding_result_t kzt_guest_library_note_observation( + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_binding_key_t *key) +{ + kzt_guest_library_observed_t *observed; + kzt_guest_library_binding_result_t result = + KZT_GUEST_LIBRARY_BINDING_UNCHANGED; + if (!supported_key(key)) return KZT_GUEST_LIBRARY_BINDING_ERROR; + if (!bindings) return KZT_GUEST_LIBRARY_BINDING_DISABLED; + pthread_mutex_lock(&bindings->lock); + if (shutting_down(bindings)) { + result = KZT_GUEST_LIBRARY_BINDING_DISABLED; + goto out; + } + for (size_t i = 0; i < bindings->count; ++i) { + kzt_guest_library_binding_entry_t *entry = &bindings->entries[i]; + if (entry->state == KZT_GUEST_LIBRARY_BINDING_UNLOADING && + same_key(&entry->key, key)) { + /* Phase 1 has assigned this exact generation to the binding + * lifecycle owner. This check deliberately precedes the + * address-only pending cancellation path. */ + result = KZT_GUEST_LIBRARY_BINDING_RETIRE_OWNED; + goto out; + } + } + observed = find_observed_key_locked(bindings, key); + if (observed && observed->retire_owner) { + result = KZT_GUEST_LIBRARY_BINDING_RETIRE_OWNED; + goto out; + } + { + int active_pending = 0; + int cancelled_pending = 0; + for (size_t i = 0; i < bindings->pending_count; ++i) { + kzt_guest_library_pending_t *pending = &bindings->pending[i]; + if (pending->link_map_addr != key->link_map_addr) + continue; + if (pending->active) + active_pending = 1; + else { + kzt_guest_library_lifecycle_t *lifecycle = + find_lifecycle_locked(bindings, pending->library); + if (lifecycle && + lifecycle->state != KZT_GUEST_LIBRARY_BINDING_LIVE) + cancelled_pending = 1; + } + } + if (cancelled_pending && !active_pending) { + for (size_t i = 0; i < bindings->pending_count; ++i) { + kzt_guest_library_pending_t *pending = &bindings->pending[i]; + kzt_guest_library_lifecycle_t *lifecycle; + if (pending->active || + pending->link_map_addr != key->link_map_addr) + continue; + lifecycle = find_lifecycle_locked(bindings, + pending->library); + if (lifecycle && + lifecycle->state != KZT_GUEST_LIBRARY_BINDING_LIVE) + pending->link_map_addr = 0; + } + result = KZT_GUEST_LIBRARY_BINDING_CANCELLED; + goto out; + } + } + observed = find_observed_key_locked(bindings, key); + if (!observed) { + kzt_guest_library_observed_t *same_addr = + find_observed_locked(bindings, key->link_map_addr); + if (same_addr && !same_addr->retire_owner) + observed = same_addr; + } + if (observed) { + observed->key = *key; + observed->claimed = 0; + } else { + if (bindings->observed_count == bindings->observed_capacity && + grow_array((void **)&bindings->observed, + &bindings->observed_capacity, + sizeof(*bindings->observed)) != 0) { + result = KZT_GUEST_LIBRARY_BINDING_ERROR; + goto out; + } + bindings->observed[bindings->observed_count++] = + (kzt_guest_library_observed_t){ .key = *key }; + observed = &bindings->observed[bindings->observed_count - 1]; + } + for (size_t i = 0; i < bindings->pending_count; ++i) { + kzt_guest_library_pending_t *pending = &bindings->pending[i]; + kzt_guest_library_binding_result_t one; + if (!pending->active || pending->link_map_addr != key->link_map_addr) + continue; + one = bind_locked(bindings, key, pending->library, + pending->object_type); + if (one == KZT_GUEST_LIBRARY_BINDING_ADDED || + one == KZT_GUEST_LIBRARY_BINDING_UNCHANGED) { + pending->active = 0; + observed->claimed = 1; + if (one == KZT_GUEST_LIBRARY_BINDING_ADDED) + result = one; + } else if (one == KZT_GUEST_LIBRARY_BINDING_CONFLICT) { + result = one; + } + } +out: + pthread_mutex_unlock(&bindings->lock); + return result; +} + +static int lookup_bindings(kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_binding_key_t *key, + kzt_guest_library_handle_t *handle) +{ + if (handle) memset(handle, 0, sizeof(*handle)); + if (!bindings || !key || !handle) return -1; + pthread_mutex_lock(&bindings->lock); + if (shutting_down(bindings)) { + pthread_mutex_unlock(&bindings->lock); + return -1; + } + for (size_t i = 0; i < bindings->count; ++i) { + kzt_guest_library_binding_entry_t *entry = &bindings->entries[i]; + kzt_guest_library_lifecycle_t *lifecycle; + if (entry->state != KZT_GUEST_LIBRARY_BINDING_LIVE || + !same_key(&entry->key, key)) + continue; + lifecycle = find_lifecycle_locked(bindings, entry->library); + if (!lifecycle || lifecycle->state != KZT_GUEST_LIBRARY_BINDING_LIVE) + continue; + ++entry->references; + handle->bindings = bindings; + /* A 1-based index remains stable when forced growth reallocates the + * backing array while this handle protects library lifetime. */ + handle->entry = (void *)(uintptr_t)(i + 1); + handle->library = entry->library; + handle->object_type = entry->object_type; + pthread_mutex_unlock(&bindings->lock); + return 0; + } + pthread_mutex_unlock(&bindings->lock); + return -1; +} + +#ifdef KZT_GUEST_LIBRARY_BINDING_TEST +int kzt_guest_library_lookup(kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_binding_key_t *key, + kzt_guest_library_handle_t *handle) +{ + return lookup_bindings(bindings, key, handle); +} +#endif + +int kzt_guest_library_access_lookup( + kzt_guest_library_access_t *access, + const kzt_guest_library_binding_key_t *key, + kzt_guest_library_handle_t *handle) +{ + int result = -1; + if (handle) memset(handle, 0, sizeof(*handle)); + if (!access || !access->initialized || !key || !handle) return -1; + pthread_mutex_lock(&access->lock); + if (access->accepting && access->bindings) + result = lookup_bindings(access->bindings, key, handle); + pthread_mutex_unlock(&access->lock); + return result; +} + +void kzt_guest_library_handle_release(kzt_guest_library_handle_t *handle) +{ + kzt_guest_library_bindings_t *bindings; + if (!handle || !(bindings = handle->bindings) || !handle->entry) return; + pthread_mutex_lock(&bindings->lock); + size_t index = (size_t)(uintptr_t)handle->entry - 1; + if (index < bindings->count) { + kzt_guest_library_binding_entry_t *entry = + &bindings->entries[index]; + if (entry->references && --entry->references == 0) + pthread_cond_broadcast(&bindings->idle); + } + pthread_mutex_unlock(&bindings->lock); + memset(handle, 0, sizeof(*handle)); +} + +static void unload_library(kzt_guest_library_bindings_t *bindings, + kzt_guest_registry_t *registry, + library_t *library, + uintptr_t guest_link_map_hint, int permanent) +{ + kzt_guest_library_lifecycle_t *lifecycle; + int owns_lifecycle = 0; + int waits_for_lifecycle = 0; + if (!bindings || !library) return; + pthread_mutex_lock(&bindings->lock); + + /* Phase 1 closes every binding-side path without allocation. Only the + * caller that changes this library from LIVE to UNLOADING may attach the + * library as owner of an exact, unclaimed observation. Registry + * retirement is deliberately deferred: it may wait for a source lease, + * and a lease holder may need bindings->lock for provider lookup. */ + lifecycle = find_lifecycle_locked(bindings, library); + if (!lifecycle) { + goto out; + } + if (permanent) lifecycle->destroy_started = 1; + if (lifecycle->state != KZT_GUEST_LIBRARY_BINDING_LIVE) { + waits_for_lifecycle = + lifecycle->state == KZT_GUEST_LIBRARY_BINDING_UNLOADING; + goto wait_or_out; + } + lifecycle->state = KZT_GUEST_LIBRARY_BINDING_UNLOADING; + owns_lifecycle = 1; + if (guest_link_map_hint) { + for (size_t i = 0; i < bindings->observed_count; ++i) { + kzt_guest_library_observed_t *observed = + &bindings->observed[i]; + if (observed->key.link_map_addr == guest_link_map_hint && + !observed->claimed && !observed->retire_owner) { + observed->retire_owner = library; + break; + } + } + close_callback_addr_locked(bindings, lifecycle, library, + guest_link_map_hint); + } + for (size_t i = 0; i < bindings->pending_count; ++i) + if (bindings->pending[i].library == library) + bindings->pending[i].active = 0; + for (size_t i = 0; i < bindings->count; ++i) { + kzt_guest_library_binding_entry_t *entry = &bindings->entries[i]; + if (entry->library != library || + entry->state == KZT_GUEST_LIBRARY_BINDING_DEAD) + continue; + entry->state = KZT_GUEST_LIBRARY_BINDING_UNLOADING; + entry->retire_started = 0; + close_callback_addr_locked(bindings, lifecycle, library, + entry->key.link_map_addr); + for (size_t j = 0; j < bindings->observed_count; ++j) + if (same_key(&bindings->observed[j].key, &entry->key)) { + memset(&bindings->observed[j], 0, + sizeof(bindings->observed[j])); + } + } + + /* A callback that entered first may still need this lock for observation + * publication and loader-pair binding, so wait with pthread_cond_wait. + * New callbacks for the closed address are rejected before their first + * guest-memory read. */ + while (callback_access_busy_locked(bindings, lifecycle, library)) + pthread_cond_wait(&bindings->idle, &bindings->lock); + + /* Phase 2 claims one exact key by value, drops bindings->lock, and only + * then enters the registry. Re-scanning after every lock acquisition is + * safe across concurrent array growth/realloc and needs no allocation. */ + for (;;) { + kzt_guest_library_binding_key_t retire_key = { 0 }; + int from_observation = 0; + + if (owns_lifecycle) { + for (size_t i = 0; i < bindings->count; ++i) { + kzt_guest_library_binding_entry_t *entry = + &bindings->entries[i]; + if (entry->library == library && + entry->state == KZT_GUEST_LIBRARY_BINDING_UNLOADING && + !entry->retire_started) { + retire_key = entry->key; + entry->retire_started = 1; + break; + } + } + } + if (!retire_key.link_map_addr) { + for (size_t i = 0; i < bindings->observed_count; ++i) { + kzt_guest_library_observed_t *observed = + &bindings->observed[i]; + if (observed->retire_owner == library && + !observed->retire_started) { + retire_key = observed->key; + observed->retire_started = 1; + from_observation = 1; + break; + } + } + } + if (!retire_key.link_map_addr) + break; + + pthread_mutex_unlock(&bindings->lock); +#ifdef KZT_GUEST_LIBRARY_BINDING_TEST + if (test_before_registry_retire) + test_before_registry_retire( + bindings, &retire_key, library, from_observation, + test_before_registry_retire_opaque); +#endif + if (!registry) { + fprintf(stderr, + "KZT binding retire unavailable (registry missing): link_map=%p generation=%lu; continuing legacy flow\n", + (void *)retire_key.link_map_addr, + retire_key.generation); + pthread_mutex_lock(&bindings->lock); + ++bindings->diagnostics.registry_missing; + pthread_mutex_unlock(&bindings->lock); + } else if (kzt_guest_registry_retire( + registry, retire_key.link_map_addr, + retire_key.generation) != 0 && + kzt_guest_registry_wait_retired( + registry, retire_key.link_map_addr, + retire_key.generation) != 0) { + /* An exact generation already in UNLOADING is waited to DEAD by + * wait_retired(). Disabled/missing/replaced/unprovable state is + * a new-KZT failure and must leave the legacy process alive. */ + fprintf(stderr, + "KZT binding retire state unprovable: link_map=%p generation=%lu; continuing legacy flow\n", + (void *)retire_key.link_map_addr, + retire_key.generation); + pthread_mutex_lock(&bindings->lock); + ++bindings->diagnostics.retire_unprovable; + pthread_mutex_unlock(&bindings->lock); + } + pthread_mutex_lock(&bindings->lock); + + if (from_observation) { + for (size_t i = 0; i < bindings->observed_count; ++i) { + kzt_guest_library_observed_t *observed = + &bindings->observed[i]; + if (observed->retire_owner == library && + observed->retire_started && + same_key(&observed->key, &retire_key)) { + memset(observed, 0, sizeof(*observed)); + break; + } + } + } + } + +wait_or_out: + if (!owns_lifecycle) { + while (waits_for_lifecycle) { + lifecycle = find_lifecycle_locked(bindings, library); + if (!lifecycle || + lifecycle->state != KZT_GUEST_LIBRARY_BINDING_UNLOADING) + break; +#ifdef KZT_GUEST_LIBRARY_BINDING_TEST + if (test_before_lifecycle_wait) + test_before_lifecycle_wait( + bindings, library, test_before_lifecycle_wait_opaque); +#endif + pthread_cond_wait(&bindings->idle, &bindings->lock); + } + goto out; + } + + /* Phase 3 waits only for binding handles. Handle release takes + * bindings->lock but no registry or access lock. */ + for (;;) { + int busy = 0; + for (size_t i = 0; i < bindings->count; ++i) + if (bindings->entries[i].library == library && + bindings->entries[i].references) + busy = 1; + if (!busy) break; + pthread_cond_wait(&bindings->idle, &bindings->lock); + } + for (size_t i = 0; i < bindings->count; ++i) + if (bindings->entries[i].library == library) + bindings->entries[i].state = KZT_GUEST_LIBRARY_BINDING_DEAD; + lifecycle = find_lifecycle_locked(bindings, library); + if (lifecycle) + lifecycle->state = KZT_GUEST_LIBRARY_BINDING_DEAD; + pthread_cond_broadcast(&bindings->idle); +out: + pthread_mutex_unlock(&bindings->lock); +} + +#ifdef KZT_GUEST_LIBRARY_BINDING_TEST +int kzt_guest_library_binding_test_get_diagnostics( + kzt_guest_library_bindings_t *bindings, + unsigned long *registry_missing, + unsigned long *retire_unprovable) +{ + if (registry_missing) *registry_missing = 0; + if (retire_unprovable) *retire_unprovable = 0; + if (!bindings) return -1; + pthread_mutex_lock(&bindings->lock); + if (registry_missing) + *registry_missing = bindings->diagnostics.registry_missing; + if (retire_unprovable) + *retire_unprovable = bindings->diagnostics.retire_unprovable; + pthread_mutex_unlock(&bindings->lock); + return 0; +} + +int kzt_guest_library_binding_test_snapshot( + kzt_guest_library_bindings_t *bindings, library_t *library, + kzt_guest_library_binding_state_t *lifecycle_state, + size_t *active_pending, size_t *live_entries) +{ + kzt_guest_library_lifecycle_t *lifecycle; + if (lifecycle_state) + *lifecycle_state = KZT_GUEST_LIBRARY_BINDING_DEAD; + if (active_pending) *active_pending = 0; + if (live_entries) *live_entries = 0; + if (!bindings || !library) return -1; + pthread_mutex_lock(&bindings->lock); + lifecycle = find_lifecycle_locked(bindings, library); + if (lifecycle_state && lifecycle) + *lifecycle_state = lifecycle->state; + for (size_t i = 0; i < bindings->pending_count; ++i) + if (bindings->pending[i].library == library && + bindings->pending[i].active && active_pending) + ++*active_pending; + for (size_t i = 0; i < bindings->count; ++i) + if (bindings->entries[i].library == library && + bindings->entries[i].state == KZT_GUEST_LIBRARY_BINDING_LIVE && + live_entries) + ++*live_entries; + pthread_mutex_unlock(&bindings->lock); + return lifecycle ? 0 : -1; +} +#endif + +void kzt_guest_library_unbind(kzt_guest_library_bindings_t *bindings, + kzt_guest_registry_t *registry, + library_t *library, + uintptr_t guest_link_map_hint) +{ + unload_library(bindings, registry, library, guest_link_map_hint, 1); +} + +void kzt_guest_library_inactivate(kzt_guest_library_bindings_t *bindings, + kzt_guest_registry_t *registry, + library_t *library, + uintptr_t guest_link_map_hint) +{ + unload_library(bindings, registry, library, guest_link_map_hint, 0); +} diff --git a/target/i386/latx/context/kzt_guest_link_map_reader.c b/target/i386/latx/context/kzt_guest_link_map_reader.c new file mode 100644 index 00000000000..3e5207942d8 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_link_map_reader.c @@ -0,0 +1,387 @@ +#include +#include +#include +#include + +#include "kzt_guest_link_map_reader.h" + +#define KZT_GUEST_LINK_MAP_WALK_LIMIT 256 + +/* The public x86_64 link_map prefix is stable. Fields after l_prev are glibc + * implementation details and must not be used as registry evidence. */ +typedef struct kzt_guest_link_map_public_prefix { + uint64_t l_addr; + uint64_t l_name; + uint64_t l_ld; + uint64_t l_next; + uint64_t l_prev; +} kzt_guest_link_map_public_prefix_t; + +#ifdef KZT_GUEST_LINK_MAP_READER_TEST +static long test_alloc_failure_after = -1; + +void kzt_guest_link_map_reader_test_set_alloc_failure_after(long allocations) +{ + test_alloc_failure_after = allocations; +} +#endif + +static void *kzt_link_map_reader_malloc(size_t size) +{ +#ifdef KZT_GUEST_LINK_MAP_READER_TEST + if (test_alloc_failure_after == 0) { + return NULL; + } + if (test_alloc_failure_after > 0) { + --test_alloc_failure_after; + } +#endif + return malloc(size); +} + +static void kzt_link_map_reader_free(void *ptr) +{ + free(ptr); +} + +static void kzt_guest_scalar_set_unknown(kzt_guest_scalar_field_t *field) +{ + field->value = 0; + field->status = KZT_GUEST_FIELD_UNKNOWN; +} + +static void kzt_guest_string_set_unknown(kzt_guest_string_field_t *field) +{ + field->value = NULL; + field->status = KZT_GUEST_FIELD_UNKNOWN; +} + +static void kzt_guest_observation_init(kzt_guest_object_observation_t *observation) +{ + memset(observation, 0, sizeof(*observation)); + kzt_guest_scalar_set_unknown(&observation->load_bias); + kzt_guest_scalar_set_unknown(&observation->dynamic_addr); + kzt_guest_scalar_set_unknown(&observation->map_start); + kzt_guest_scalar_set_unknown(&observation->map_end); + kzt_guest_scalar_set_unknown(&observation->namespace_id); + kzt_guest_string_set_unknown(&observation->path); + observation->soname.value = NULL; + observation->soname.status = KZT_GUEST_FIELD_NOT_PARSED; + observation->dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED; +} + +static int kzt_guest_read_memory(uintptr_t guest_addr, + void *dst, + size_t size, + const kzt_guest_link_map_reader_ops_t *ops) +{ + if (!ops || !ops->read_memory) { + return -1; + } + return ops->read_memory(guest_addr, dst, size, ops->opaque) == 0 ? 0 : -1; +} + +static int kzt_guest_field_addr(uintptr_t base, size_t offset, uintptr_t *addr) +{ + if (base > UINTPTR_MAX - offset) { + return -1; + } + + *addr = base + offset; + return 0; +} + +static int kzt_guest_read_uintptr_field( + uintptr_t base, + size_t offset, + size_t field_size, + const kzt_guest_link_map_reader_ops_t *ops, + uintptr_t *value) +{ + uintptr_t addr; + uint64_t raw = 0; + + if (field_size > sizeof(raw) || + kzt_guest_field_addr(base, offset, &addr) != 0) { + return -1; + } + + if (kzt_guest_read_memory(addr, &raw, field_size, ops) != 0) { + return -1; + } + + *value = (uintptr_t)raw; + return 0; +} + +static void kzt_guest_read_scalar_field( + uintptr_t base, + size_t offset, + size_t field_size, + const kzt_guest_link_map_reader_ops_t *ops, + kzt_guest_scalar_field_t *field) +{ + uintptr_t value = 0; + + field->value = 0; + if (kzt_guest_read_uintptr_field(base, offset, field_size, ops, &value) != 0) { + field->status = KZT_GUEST_FIELD_READ_ERROR; + return; + } + + field->value = value; + field->status = KZT_GUEST_FIELD_OK; +} + +static int kzt_guest_read_link_map_pointer( + uintptr_t link_map_addr, + size_t offset, + size_t field_size, + const kzt_guest_link_map_reader_ops_t *ops, + uintptr_t *value) +{ + *value = 0; + return kzt_guest_read_uintptr_field(link_map_addr, offset, field_size, + ops, value); +} + +int kzt_guest_link_map_read_identity( + uintptr_t link_map_addr, + const kzt_guest_link_map_reader_ops_t *ops, + kzt_guest_link_map_identity_t *identity) +{ + if (!identity) { + return -1; + } + memset(identity, 0, sizeof(*identity)); + if (!link_map_addr || !ops || !ops->read_memory || + kzt_guest_read_uintptr_field( + link_map_addr, + offsetof(kzt_guest_link_map_public_prefix_t, l_addr), + sizeof(((kzt_guest_link_map_public_prefix_t *)0)->l_addr), + ops, &identity->load_bias) != 0 || + kzt_guest_read_uintptr_field( + link_map_addr, + offsetof(kzt_guest_link_map_public_prefix_t, l_ld), + sizeof(((kzt_guest_link_map_public_prefix_t *)0)->l_ld), + ops, &identity->dynamic_addr) != 0) { + memset(identity, 0, sizeof(*identity)); + return -1; + } + return 0; +} + +int kzt_guest_link_map_identity_matches( + const kzt_guest_link_map_identity_t *identity, + uintptr_t expected_load_bias, + uintptr_t expected_dynamic_addr) +{ + return identity && expected_dynamic_addr && + identity->load_bias == expected_load_bias && + identity->dynamic_addr == expected_dynamic_addr; +} + +int kzt_guest_link_map_read_predecessor( + uintptr_t link_map_addr, + const kzt_guest_link_map_reader_ops_t *ops, + uintptr_t *predecessor) +{ + if (!predecessor) { + return -1; + } + *predecessor = 0; + if (!link_map_addr || !ops || !ops->read_memory) { + return -1; + } + return kzt_guest_read_uintptr_field( + link_map_addr, + offsetof(kzt_guest_link_map_public_prefix_t, l_prev), + sizeof(((kzt_guest_link_map_public_prefix_t *)0)->l_prev), + ops, predecessor); +} + +int kzt_guest_link_map_classify_namespace( + uintptr_t link_map_addr, + const kzt_guest_link_map_identity_t *main_identity, + uintptr_t confirmed_main_head, + const kzt_guest_link_map_reader_ops_t *ops, + uintptr_t *namespace_head) +{ + uintptr_t visited[KZT_GUEST_LINK_MAP_WALK_LIMIT]; + uintptr_t current = link_map_addr; + size_t count = 0; + + if (namespace_head) { + *namespace_head = 0; + } + + if (!current || !ops || !ops->read_memory || + (!confirmed_main_head && + (!main_identity || !main_identity->dynamic_addr))) { + return -1; + } + + while (count < KZT_GUEST_LINK_MAP_WALK_LIMIT) { + uintptr_t previous = 0; + size_t i; + + for (i = 0; i < count; ++i) { + if (visited[i] == current) { + return -1; + } + } + visited[count++] = current; + if (kzt_guest_read_uintptr_field( + current, + offsetof(kzt_guest_link_map_public_prefix_t, l_prev), + sizeof(((kzt_guest_link_map_public_prefix_t *)0)->l_prev), + ops, &previous) != 0) { + return -1; + } + + if (!previous) { + kzt_guest_link_map_identity_t head_identity; + + if (namespace_head) { + *namespace_head = current; + } + if (confirmed_main_head) { + return current == confirmed_main_head ? 1 : 0; + } + if (kzt_guest_link_map_read_identity(current, ops, + &head_identity) != 0) { + if (namespace_head) { + *namespace_head = 0; + } + return -1; + } + return kzt_guest_link_map_identity_matches( + &head_identity, main_identity->load_bias, + main_identity->dynamic_addr) ? 1 : 0; + } + current = previous; + } + + return -1; +} + +int kzt_guest_link_map_read_name_snapshot( + uintptr_t guest_name_addr, + const kzt_guest_link_map_reader_ops_t *ops, + size_t max_len, + kzt_guest_string_field_t *name) +{ + char *snapshot; + size_t i; + + if (!name) { + return -1; + } + + kzt_guest_string_set_unknown(name); + if (!guest_name_addr) { + return 0; + } + + if (max_len == 0) { + name->status = KZT_GUEST_FIELD_TRUNCATED; + return 0; + } + + snapshot = kzt_link_map_reader_malloc(max_len + 1); + if (!snapshot) { + name->status = KZT_GUEST_FIELD_READ_ERROR; + return 0; + } + + for (i = 0; i < max_len; ++i) { + if (kzt_guest_read_memory(guest_name_addr + i, &snapshot[i], 1, ops) != 0) { + kzt_link_map_reader_free(snapshot); + name->status = KZT_GUEST_FIELD_READ_ERROR; + return 0; + } + + if (snapshot[i] == '\0') { + name->value = snapshot; + name->status = KZT_GUEST_FIELD_OK; + return 0; + } + } + + snapshot[max_len] = '\0'; + name->value = snapshot; + name->status = KZT_GUEST_FIELD_TRUNCATED; + return 0; +} + +int kzt_guest_link_map_read_observation( + uintptr_t link_map_addr, + const kzt_guest_link_map_reader_ops_t *ops, + kzt_guest_object_observation_t *observation) +{ + uintptr_t guest_name_addr = 0; + int name_ptr_read; + + if (!observation) { + return -1; + } + + kzt_guest_observation_init(observation); + + if (!link_map_addr || !ops || !ops->read_memory) { + return -1; + } + + observation->link_map_addr = link_map_addr; + kzt_guest_read_scalar_field( + link_map_addr, + offsetof(kzt_guest_link_map_public_prefix_t, l_addr), + sizeof(((kzt_guest_link_map_public_prefix_t *)0)->l_addr), ops, + &observation->load_bias); + kzt_guest_read_scalar_field( + link_map_addr, + offsetof(kzt_guest_link_map_public_prefix_t, l_ld), + sizeof(((kzt_guest_link_map_public_prefix_t *)0)->l_ld), ops, + &observation->dynamic_addr); + + name_ptr_read = kzt_guest_read_link_map_pointer( + link_map_addr, + offsetof(kzt_guest_link_map_public_prefix_t, l_name), + sizeof(((kzt_guest_link_map_public_prefix_t *)0)->l_name), ops, + &guest_name_addr); + if (name_ptr_read != 0) { + observation->path.value = NULL; + observation->path.status = KZT_GUEST_FIELD_READ_ERROR; + return 0; + } + + if (kzt_guest_link_map_read_name_snapshot( + guest_name_addr, ops, KZT_GUEST_LINK_MAP_NAME_LIMIT, + &observation->path) != 0) { + return -1; + } + + return 0; +} + +void kzt_guest_link_map_observation_clear( + kzt_guest_object_observation_t *observation) +{ + if (!observation) { + return; + } + + kzt_guest_link_map_string_clear(&observation->path); + kzt_guest_link_map_string_clear(&observation->soname); + kzt_guest_observation_init(observation); +} + +void kzt_guest_link_map_string_clear(kzt_guest_string_field_t *field) +{ + if (!field) { + return; + } + + kzt_link_map_reader_free((void *)field->value); + kzt_guest_string_set_unknown(field); +} diff --git a/target/i386/latx/context/kzt_guest_registry.c b/target/i386/latx/context/kzt_guest_registry.c new file mode 100644 index 00000000000..6f374ced3ee --- /dev/null +++ b/target/i386/latx/context/kzt_guest_registry.c @@ -0,0 +1,1551 @@ +#include +#include +#include +#include +#include +#include + +#include "kzt_guest_registry.h" + +#define KZT_GUEST_REGISTRY_INITIAL_CAPACITY 8 + +struct kzt_guest_registry { + pthread_mutex_t lock; + pthread_cond_t leases_idle; + int lock_ready; + int disabled; + int destroying; + unsigned long active_api_users; + kzt_guest_object_snapshot_t *objects; + size_t count; + size_t capacity; + unsigned long next_generation; + kzt_guest_registry_diagnostics_t diagnostics; + kzt_guest_registry_diagnostic_config_t diagnostic_config; + kzt_guest_registry_event_summary_t diagnostic_events[ + KZT_GUEST_REGISTRY_RESULT_COUNT]; +}; + +#ifdef KZT_GUEST_REGISTRY_TEST +static kzt_guest_registry_test_hook_fn test_after_api_enter; +static void *test_after_api_enter_opaque; +static kzt_guest_registry_test_hook_fn test_before_retire_wait; +static void *test_before_retire_wait_opaque; +static kzt_guest_registry_test_hook_fn test_after_retire_wake; +static void *test_after_retire_wake_opaque; +static kzt_guest_registry_test_hook_fn test_after_destroy_disable; +static void *test_after_destroy_disable_opaque; +#endif + +static int kzt_registry_api_enter(kzt_guest_registry_t *registry) +{ + if (!registry) { + return -1; + } + + __atomic_add_fetch(®istry->active_api_users, 1, __ATOMIC_ACQ_REL); +#ifdef KZT_GUEST_REGISTRY_TEST + if (test_after_api_enter) { + test_after_api_enter(test_after_api_enter_opaque); + } +#endif + if (__atomic_load_n(®istry->destroying, __ATOMIC_ACQUIRE)) { + pthread_mutex_lock(®istry->lock); + if (__atomic_sub_fetch(®istry->active_api_users, 1, + __ATOMIC_ACQ_REL) == 0) { + pthread_cond_broadcast(®istry->leases_idle); + } + pthread_mutex_unlock(®istry->lock); + return -1; + } + + return 0; +} + +static int kzt_registry_api_lock(kzt_guest_registry_t *registry) +{ + if (kzt_registry_api_enter(registry) != 0) { + return -1; + } + pthread_mutex_lock(®istry->lock); + return 0; +} + +static void kzt_registry_api_unlock(kzt_guest_registry_t *registry) +{ + if (__atomic_sub_fetch(®istry->active_api_users, 1, + __ATOMIC_ACQ_REL) == 0) { + pthread_cond_broadcast(®istry->leases_idle); + } + pthread_mutex_unlock(®istry->lock); +} + +static void kzt_registry_api_leave(kzt_guest_registry_t *registry) +{ + pthread_mutex_lock(®istry->lock); + kzt_registry_api_unlock(registry); +} + +#ifdef KZT_GUEST_REGISTRY_TEST +static long test_alloc_failure_after = -1; +static long test_dynamic_commit_failure_after = -1; +static int test_fail_next_cond_init; + +void kzt_guest_registry_test_set_after_api_enter( + kzt_guest_registry_test_hook_fn hook, void *opaque) +{ + test_after_api_enter = hook; + test_after_api_enter_opaque = opaque; +} + +void kzt_guest_registry_test_set_after_retire_wake( + kzt_guest_registry_test_hook_fn hook, void *opaque) +{ + test_after_retire_wake = hook; + test_after_retire_wake_opaque = opaque; +} + +void kzt_guest_registry_test_set_before_retire_wait( + kzt_guest_registry_test_hook_fn hook, void *opaque) +{ + test_before_retire_wait = hook; + test_before_retire_wait_opaque = opaque; +} + +void kzt_guest_registry_test_set_after_destroy_disable( + kzt_guest_registry_test_hook_fn hook, void *opaque) +{ + test_after_destroy_disable = hook; + test_after_destroy_disable_opaque = opaque; +} + +void kzt_guest_registry_test_set_alloc_failure_after(long allocations) +{ + test_alloc_failure_after = allocations; +} + +void kzt_guest_registry_test_set_dynamic_commit_failure_after(long commits) +{ + test_dynamic_commit_failure_after = commits; +} + +void kzt_guest_registry_test_fail_next_cond_init(void) +{ + test_fail_next_cond_init = 1; +} + +static int kzt_registry_test_should_fail_dynamic_commit(void) +{ + if (test_dynamic_commit_failure_after == 0) { + return 1; + } + if (test_dynamic_commit_failure_after > 0) { + --test_dynamic_commit_failure_after; + } + return 0; +} +#endif + +static void *kzt_registry_calloc(size_t count, size_t size) +{ +#ifdef KZT_GUEST_REGISTRY_TEST + if (test_alloc_failure_after == 0) { + return NULL; + } + if (test_alloc_failure_after > 0) { + --test_alloc_failure_after; + } +#endif + return calloc(count, size); +} + +static void kzt_registry_free(void *ptr) +{ + free(ptr); +} + +static char *kzt_registry_strdup(const char *value) +{ + size_t len; + char *copy; + + if (!value) { + value = ""; + } + + len = strlen(value); + copy = kzt_registry_calloc(len + 1, 1); + if (!copy) { + return NULL; + } + + memcpy(copy, value, len); + return copy; +} + +static int kzt_field_is_reliable(kzt_guest_field_status_t status) +{ + return status == KZT_GUEST_FIELD_OK; +} + +static int kzt_string_status_has_snapshot(kzt_guest_field_status_t status) +{ + return status == KZT_GUEST_FIELD_OK || + status == KZT_GUEST_FIELD_TRUNCATED; +} + +static void kzt_free_string_field(kzt_guest_string_field_t *field) +{ + if (!field) { + return; + } + + kzt_registry_free((void *)field->value); + field->value = NULL; +} + +static int kzt_copy_string_field(kzt_guest_string_field_t *dst, + const kzt_guest_string_field_t *src) +{ + dst->status = src->status; + dst->value = NULL; + + if (!kzt_string_status_has_snapshot(src->status)) { + return 0; + } + + dst->value = kzt_registry_strdup(src->value); + return dst->value ? 0 : -1; +} + +static int kzt_dynamic_field_equal( + const kzt_guest_dynamic_field_t *left, + const kzt_guest_dynamic_field_t *right) +{ + return left->present == right->present && + left->value == right->value && + left->address_semantics == right->address_semantics; +} + +static int kzt_dynamic_needed_equal( + const kzt_guest_dynamic_view_t *left, + const kzt_guest_dynamic_view_t *right) +{ + size_t i; + + if (left->needed_count != right->needed_count) { + return 0; + } + + if (left->needed_count > 0 && + left->needed_address_semantics != right->needed_address_semantics) { + return 0; + } + + for (i = 0; i < left->needed_count; ++i) { + if (left->needed_offsets[i] != right->needed_offsets[i]) { + return 0; + } + } + + return 1; +} + +static int kzt_dynamic_view_equal( + const kzt_guest_dynamic_view_t *left, + const kzt_guest_dynamic_view_t *right) +{ + return left->dynamic_addr == right->dynamic_addr && + left->load_bias == right->load_bias && + left->status == right->status && + left->entry_count == right->entry_count && + left->has_null == right->has_null && + left->scan_limit == right->scan_limit && + left->unknown_tag_count == right->unknown_tag_count && + left->first_unknown_tag == right->first_unknown_tag && + left->first_unknown_tag_index == right->first_unknown_tag_index && + kzt_dynamic_field_equal(&left->symtab, &right->symtab) && + kzt_dynamic_field_equal(&left->strtab, &right->strtab) && + kzt_dynamic_field_equal(&left->syment, &right->syment) && + kzt_dynamic_field_equal(&left->strsz, &right->strsz) && + kzt_dynamic_field_equal(&left->hash, &right->hash) && + kzt_dynamic_field_equal(&left->gnu_hash, &right->gnu_hash) && + kzt_dynamic_field_equal(&left->versym, &right->versym) && + kzt_dynamic_field_equal(&left->verneed, &right->verneed) && + kzt_dynamic_field_equal(&left->verneednum, &right->verneednum) && + kzt_dynamic_field_equal(&left->verdef, &right->verdef) && + kzt_dynamic_field_equal(&left->verdefnum, &right->verdefnum) && + kzt_dynamic_field_equal(&left->rela, &right->rela) && + kzt_dynamic_field_equal(&left->relasz, &right->relasz) && + kzt_dynamic_field_equal(&left->relaent, &right->relaent) && + kzt_dynamic_field_equal(&left->rel, &right->rel) && + kzt_dynamic_field_equal(&left->relsz, &right->relsz) && + kzt_dynamic_field_equal(&left->relent, &right->relent) && + kzt_dynamic_field_equal(&left->jmprel, &right->jmprel) && + kzt_dynamic_field_equal(&left->pltrelsz, &right->pltrelsz) && + kzt_dynamic_field_equal(&left->pltrel, &right->pltrel) && + kzt_dynamic_field_equal(&left->pltgot, &right->pltgot) && + kzt_dynamic_needed_equal(left, right); +} + +static kzt_guest_field_status_t kzt_dynamic_view_field_status( + const kzt_guest_dynamic_view_t *view) +{ + switch (view->status) { + case KZT_GUEST_DYNAMIC_COMPLETE: + return KZT_GUEST_FIELD_OK; + case KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL: + return KZT_GUEST_FIELD_TRUNCATED; + case KZT_GUEST_DYNAMIC_READ_ERROR: + return KZT_GUEST_FIELD_READ_ERROR; + case KZT_GUEST_DYNAMIC_ERROR: + return KZT_GUEST_FIELD_READ_ERROR; + } + + return KZT_GUEST_FIELD_UNKNOWN; +} + +static void kzt_free_snapshot_strings(kzt_guest_object_snapshot_t *snapshot) +{ + if (!snapshot) { + return; + } + + kzt_free_string_field(&snapshot->path); + kzt_free_string_field(&snapshot->soname); + memset(&snapshot->dynamic_view, 0, sizeof(snapshot->dynamic_view)); +} + +static void kzt_free_snapshot_array(kzt_guest_object_snapshot_t *objects, + size_t count) +{ + size_t i; + + if (!objects) { + return; + } + + for (i = 0; i < count; ++i) { + kzt_free_snapshot_strings(&objects[i]); + } + kzt_registry_free(objects); +} + +static int kzt_copy_snapshot(kzt_guest_object_snapshot_t *dst, + const kzt_guest_object_snapshot_t *src) +{ + *dst = *src; + dst->path.value = NULL; + dst->soname.value = NULL; + + if (kzt_copy_string_field(&dst->path, &src->path) != 0) { + return -1; + } + if (kzt_copy_string_field(&dst->soname, &src->soname) != 0) { + kzt_free_string_field(&dst->path); + return -1; + } + + return 0; +} + +static int kzt_snapshot_from_observation( + kzt_guest_object_snapshot_t *snapshot, + const kzt_guest_object_observation_t *observation, + unsigned long generation) +{ + memset(snapshot, 0, sizeof(*snapshot)); + + snapshot->link_map_addr = observation->link_map_addr; + snapshot->load_bias = observation->load_bias; + snapshot->dynamic_addr = observation->dynamic_addr; + snapshot->map_start = observation->map_start; + snapshot->map_end = observation->map_end; + snapshot->namespace_id = observation->namespace_id; + snapshot->dynamic_view_status = observation->dynamic_view_status; + snapshot->state = KZT_GUEST_OBJECT_DISCOVERED; + snapshot->generation = generation; + + if (kzt_copy_string_field(&snapshot->path, &observation->path) != 0) { + return -1; + } + if (kzt_copy_string_field(&snapshot->soname, &observation->soname) != 0) { + kzt_free_string_field(&snapshot->path); + return -1; + } + + return 0; +} + +static int kzt_scalar_conflicts(const kzt_guest_scalar_field_t *current, + const kzt_guest_scalar_field_t *incoming) +{ + return kzt_field_is_reliable(current->status) && + kzt_field_is_reliable(incoming->status) && + current->value != incoming->value; +} + +static int kzt_string_conflicts(const kzt_guest_string_field_t *current, + const kzt_guest_string_field_t *incoming) +{ + const char *left; + const char *right; + + if (!kzt_field_is_reliable(current->status) || + !kzt_field_is_reliable(incoming->status)) { + return 0; + } + + left = current->value ? current->value : ""; + right = incoming->value ? incoming->value : ""; + return strcmp(left, right) != 0; +} + +static int kzt_update_scalar_field(kzt_guest_scalar_field_t *current, + const kzt_guest_scalar_field_t *incoming) +{ + if (kzt_field_is_reliable(current->status) || + !kzt_field_is_reliable(incoming->status)) { + return 0; + } + + *current = *incoming; + return 1; +} + +static int kzt_update_string_field(kzt_guest_string_field_t *current, + const kzt_guest_string_field_t *incoming) +{ + char *copy; + + if (kzt_field_is_reliable(current->status) || + !kzt_field_is_reliable(incoming->status)) { + return 0; + } + + copy = kzt_registry_strdup(incoming->value); + if (!copy) { + return -1; + } + + kzt_free_string_field(current); + current->value = copy; + current->status = incoming->status; + return 1; +} + +static int kzt_update_status_field(kzt_guest_field_status_t *current, + kzt_guest_field_status_t incoming) +{ + if (kzt_field_is_reliable(*current) || + !kzt_field_is_reliable(incoming)) { + return 0; + } + + *current = incoming; + return 1; +} + +static int kzt_observation_conflicts( + const kzt_guest_object_snapshot_t *current, + const kzt_guest_object_observation_t *incoming) +{ + return kzt_scalar_conflicts(¤t->load_bias, + &incoming->load_bias) || + kzt_scalar_conflicts(¤t->dynamic_addr, + &incoming->dynamic_addr) || + kzt_scalar_conflicts(¤t->map_start, + &incoming->map_start) || + kzt_scalar_conflicts(¤t->map_end, + &incoming->map_end) || + kzt_scalar_conflicts(¤t->namespace_id, + &incoming->namespace_id) || + kzt_string_conflicts(¤t->path, &incoming->path) || + kzt_string_conflicts(¤t->soname, &incoming->soname); +} + +static int kzt_update_snapshot(kzt_guest_object_snapshot_t *current, + const kzt_guest_object_observation_t *incoming, + int *updated) +{ + int ret; + + *updated |= kzt_update_scalar_field(¤t->load_bias, + &incoming->load_bias); + *updated |= kzt_update_scalar_field(¤t->dynamic_addr, + &incoming->dynamic_addr); + *updated |= kzt_update_scalar_field(¤t->map_start, + &incoming->map_start); + *updated |= kzt_update_scalar_field(¤t->map_end, + &incoming->map_end); + *updated |= kzt_update_scalar_field(¤t->namespace_id, + &incoming->namespace_id); + + ret = kzt_update_string_field(¤t->path, &incoming->path); + if (ret < 0) { + return -1; + } + *updated |= ret; + + ret = kzt_update_string_field(¤t->soname, &incoming->soname); + if (ret < 0) { + return -1; + } + *updated |= ret; + + *updated |= kzt_update_status_field(¤t->dynamic_view_status, + incoming->dynamic_view_status); + return 0; +} + +static ssize_t kzt_find_object_index(kzt_guest_registry_t *registry, + uintptr_t link_map_addr) +{ + size_t i; + + for (i = 0; i < registry->count; ++i) { + if (registry->objects[i].link_map_addr == link_map_addr) { + return (ssize_t)i; + } + } + + return -1; +} + +static int kzt_registry_ensure_capacity(kzt_guest_registry_t *registry) +{ + kzt_guest_object_snapshot_t *objects; + size_t new_capacity; + + if (registry->count < registry->capacity) { + return 0; + } + + new_capacity = registry->capacity ? + registry->capacity * 2 : KZT_GUEST_REGISTRY_INITIAL_CAPACITY; + objects = kzt_registry_calloc(new_capacity, sizeof(*objects)); + if (!objects) { + ++registry->diagnostics.allocation_failures; + return -1; + } + + if (registry->objects) { + memcpy(objects, registry->objects, + registry->count * sizeof(*registry->objects)); + kzt_registry_free(registry->objects); + } + + registry->objects = objects; + registry->capacity = new_capacity; + return 0; +} + +static const char *kzt_registry_result_name(kzt_guest_registry_result_t result) +{ + switch (result) { + case KZT_GUEST_REGISTRY_ADDED: + return "added"; + case KZT_GUEST_REGISTRY_UNCHANGED: + return "unchanged"; + case KZT_GUEST_REGISTRY_UPDATED: + return "updated"; + case KZT_GUEST_REGISTRY_CONFLICT: + return "conflict"; + case KZT_GUEST_REGISTRY_DISABLED: + return "disabled"; + case KZT_GUEST_REGISTRY_ERROR: + return "error"; + case KZT_GUEST_REGISTRY_RESULT_COUNT: + break; + } + + return "unknown"; +} + +static const char *kzt_guest_field_status_name( + kzt_guest_field_status_t status) +{ + switch (status) { + case KZT_GUEST_FIELD_OK: + return "ok"; + case KZT_GUEST_FIELD_UNKNOWN: + return "unknown"; + case KZT_GUEST_FIELD_READ_ERROR: + return "read_error"; + case KZT_GUEST_FIELD_TRUNCATED: + return "truncated"; + case KZT_GUEST_FIELD_NOT_PARSED: + return "not_parsed"; + } + + return "invalid"; +} + +static void kzt_registry_note_counter(kzt_guest_registry_t *registry, + kzt_guest_registry_result_t result) +{ + switch (result) { + case KZT_GUEST_REGISTRY_ADDED: + ++registry->diagnostics.added; + break; + case KZT_GUEST_REGISTRY_UNCHANGED: + ++registry->diagnostics.unchanged; + break; + case KZT_GUEST_REGISTRY_UPDATED: + ++registry->diagnostics.updated; + break; + case KZT_GUEST_REGISTRY_CONFLICT: + ++registry->diagnostics.conflicts; + break; + case KZT_GUEST_REGISTRY_DISABLED: + ++registry->diagnostics.disabled; + break; + case KZT_GUEST_REGISTRY_ERROR: + ++registry->diagnostics.errors; + break; + case KZT_GUEST_REGISTRY_RESULT_COUNT: + break; + } +} + +static void kzt_registry_init_empty_diagnostic( + kzt_guest_registry_observation_diagnostic_t *diagnostic, + kzt_guest_registry_result_t result, + uintptr_t link_map_addr) +{ + if (!diagnostic) { + return; + } + + memset(diagnostic, 0, sizeof(*diagnostic)); + diagnostic->result = result; + diagnostic->link_map_addr = link_map_addr; +} + +static void kzt_registry_note_result( + kzt_guest_registry_t *registry, + kzt_guest_registry_result_t result, + uintptr_t link_map_addr, + unsigned long generation, + kzt_guest_registry_observation_diagnostic_t *diagnostic) +{ + kzt_guest_registry_event_summary_t *event = NULL; + int emitted = 0; + + kzt_registry_note_counter(registry, result); + + if (registry->diagnostic_config.enabled && + result < KZT_GUEST_REGISTRY_RESULT_COUNT) { + event = ®istry->diagnostic_events[result]; + event->result = result; + ++event->observed; + event->last_link_map_addr = link_map_addr; + event->last_generation = generation; + if (event->emitted < registry->diagnostic_config.throttle_limit) { + ++event->emitted; + emitted = 1; + } else { + ++event->suppressed; + } + } + + if (!diagnostic) { + return; + } + + memset(diagnostic, 0, sizeof(*diagnostic)); + diagnostic->enabled = registry->diagnostic_config.enabled; + diagnostic->emitted = emitted; + diagnostic->result = result; + diagnostic->link_map_addr = link_map_addr; + diagnostic->generation = generation; + diagnostic->object_count = registry->count; + diagnostic->counters = registry->diagnostics; + if (event) { + diagnostic->result_observations = event->observed; + diagnostic->result_suppressed = event->suppressed; + } +} + +kzt_guest_registry_t *kzt_guest_registry_init(void) +{ + kzt_guest_registry_t *registry; + + registry = kzt_registry_calloc(1, sizeof(*registry)); + if (!registry) { + return NULL; + } + + if (pthread_mutex_init(®istry->lock, NULL) != 0) { + kzt_registry_free(registry); + return NULL; + } + + registry->lock_ready = 1; +#ifdef KZT_GUEST_REGISTRY_TEST + if (test_fail_next_cond_init) { + test_fail_next_cond_init = 0; + pthread_mutex_destroy(®istry->lock); + kzt_registry_free(registry); + return NULL; + } +#endif + if (pthread_cond_init(®istry->leases_idle, NULL) != 0) { + pthread_mutex_destroy(®istry->lock); + kzt_registry_free(registry); + return NULL; + } + registry->next_generation = 1; + registry->capacity = KZT_GUEST_REGISTRY_INITIAL_CAPACITY; + registry->objects = kzt_registry_calloc(registry->capacity, + sizeof(*registry->objects)); + if (!registry->objects) { + registry->capacity = 0; + registry->disabled = 1; + ++registry->diagnostics.init_failures; + ++registry->diagnostics.allocation_failures; + } + + return registry; +} + +void kzt_guest_registry_destroy(kzt_guest_registry_t **registry_ptr) +{ + kzt_guest_registry_t *registry; + + if (!registry_ptr || !*registry_ptr) { + return; + } + + registry = *registry_ptr; + *registry_ptr = NULL; + + if (registry->lock_ready) { + __atomic_store_n(®istry->destroying, 1, __ATOMIC_RELEASE); + pthread_mutex_lock(®istry->lock); + registry->disabled = 1; + pthread_cond_broadcast(®istry->leases_idle); +#ifdef KZT_GUEST_REGISTRY_TEST + if (test_after_destroy_disable) { + test_after_destroy_disable(test_after_destroy_disable_opaque); + } +#endif + for (;;) { + size_t i; + int busy = __atomic_load_n(®istry->active_api_users, + __ATOMIC_ACQUIRE) != 0; + + for (i = 0; i < registry->count; ++i) { + if (registry->objects[i].active_source_leases) { + busy = 1; + break; + } + } + if (!busy) { + break; + } + pthread_cond_wait(®istry->leases_idle, ®istry->lock); + } + pthread_mutex_unlock(®istry->lock); + } + + kzt_free_snapshot_array(registry->objects, registry->count); + registry->objects = NULL; + registry->count = 0; + registry->capacity = 0; + + pthread_cond_destroy(®istry->leases_idle); + if (registry->lock_ready) { + pthread_mutex_destroy(®istry->lock); + } + + kzt_registry_free(registry); +} + +kzt_guest_registry_result_t kzt_guest_registry_observe( + kzt_guest_registry_t *registry, + const kzt_guest_object_observation_t *observation) +{ + return kzt_guest_registry_observe_with_diagnostic(registry, observation, + NULL); +} + +kzt_guest_registry_result_t kzt_guest_registry_observe_with_diagnostic( + kzt_guest_registry_t *registry, + const kzt_guest_object_observation_t *observation, + kzt_guest_registry_observation_diagnostic_t *diagnostic) +{ + kzt_guest_registry_result_t result; + ssize_t index; + int updated = 0; + uintptr_t link_map_addr = observation ? observation->link_map_addr : 0; + unsigned long generation = 0; + + if (!registry) { + kzt_registry_init_empty_diagnostic(diagnostic, + KZT_GUEST_REGISTRY_DISABLED, + link_map_addr); + return KZT_GUEST_REGISTRY_DISABLED; + } + + if (kzt_registry_api_lock(registry) != 0) { + kzt_registry_init_empty_diagnostic(diagnostic, + KZT_GUEST_REGISTRY_DISABLED, + link_map_addr); + return KZT_GUEST_REGISTRY_DISABLED; + } + ++registry->diagnostics.observations; + + if (registry->disabled) { + result = KZT_GUEST_REGISTRY_DISABLED; + goto out; + } + + if (!observation || observation->link_map_addr == 0) { + result = KZT_GUEST_REGISTRY_ERROR; + goto out; + } + + index = kzt_find_object_index(registry, observation->link_map_addr); + if (index < 0) { + if (kzt_registry_ensure_capacity(registry) != 0) { + result = KZT_GUEST_REGISTRY_ERROR; + goto out; + } + generation = registry->next_generation; + if (kzt_snapshot_from_observation(®istry->objects[registry->count], + observation, generation) != 0) { + ++registry->diagnostics.allocation_failures; + result = KZT_GUEST_REGISTRY_ERROR; + goto out; + } + ++registry->next_generation; + ++registry->count; + result = KZT_GUEST_REGISTRY_ADDED; + goto out; + } + + generation = registry->objects[index].generation; + if (registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { + kzt_guest_object_snapshot_t replacement; + generation = registry->next_generation; + if (kzt_snapshot_from_observation(&replacement, observation, + generation) != 0) { + ++registry->diagnostics.allocation_failures; + result = KZT_GUEST_REGISTRY_ERROR; + goto out; + } + kzt_free_snapshot_strings(®istry->objects[index]); + registry->objects[index] = replacement; + ++registry->next_generation; + result = KZT_GUEST_REGISTRY_ADDED; + goto out; + } + if (kzt_observation_conflicts(®istry->objects[index], observation)) { + result = KZT_GUEST_REGISTRY_CONFLICT; + goto out; + } + + if (kzt_update_snapshot(®istry->objects[index], observation, + &updated) != 0) { + ++registry->diagnostics.allocation_failures; + result = KZT_GUEST_REGISTRY_ERROR; + goto out; + } + + result = updated ? KZT_GUEST_REGISTRY_UPDATED : + KZT_GUEST_REGISTRY_UNCHANGED; + +out: + kzt_registry_note_result(registry, result, link_map_addr, generation, + diagnostic); + kzt_registry_api_unlock(registry); + return result; +} + +int kzt_guest_registry_retire(kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation) +{ + ssize_t index; + if (!registry || !link_map_addr || !generation || + kzt_registry_api_lock(registry) != 0) return -1; + index = kzt_find_object_index(registry, link_map_addr); + if (registry->disabled || index < 0 || + registry->objects[index].generation != generation || + registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || + registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return -1; + } + registry->objects[index].state = KZT_GUEST_OBJECT_UNLOADING; + for (;;) { + index = kzt_find_object_index(registry, link_map_addr); + if (registry->disabled || index < 0 || + registry->objects[index].generation != generation || + registry->objects[index].state != KZT_GUEST_OBJECT_UNLOADING) { + kzt_registry_api_unlock(registry); + return -1; + } + if (!registry->objects[index].active_source_leases) { + break; + } +#ifdef KZT_GUEST_REGISTRY_TEST + if (test_before_retire_wait) { + test_before_retire_wait(test_before_retire_wait_opaque); + } +#endif + pthread_cond_wait(®istry->leases_idle, ®istry->lock); +#ifdef KZT_GUEST_REGISTRY_TEST + if (test_after_retire_wake) { + pthread_mutex_unlock(®istry->lock); + test_after_retire_wake(test_after_retire_wake_opaque); + pthread_mutex_lock(®istry->lock); + } +#endif + } + registry->objects[index].state = KZT_GUEST_OBJECT_DEAD; + pthread_cond_broadcast(®istry->leases_idle); + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_wait_retired(kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation) +{ + ssize_t index; + if (!registry || !link_map_addr || !generation || + kzt_registry_api_lock(registry) != 0) + return -1; + for (;;) { + index = kzt_find_object_index(registry, link_map_addr); + if (registry->disabled || index < 0 || + registry->objects[index].generation != generation) { + kzt_registry_api_unlock(registry); + return -1; + } + if (registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return 0; + } + if (registry->objects[index].state != KZT_GUEST_OBJECT_UNLOADING) { + kzt_registry_api_unlock(registry); + return -1; + } + pthread_cond_wait(®istry->leases_idle, ®istry->lock); + } +} + +int kzt_guest_registry_source_lease_acquire( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + uintptr_t namespace_id, + kzt_guest_registry_source_lease_t *lease) +{ + ssize_t index; + + if (lease) { + memset(lease, 0, sizeof(*lease)); + } + if (!registry || !link_map_addr || !generation || namespace_id != 0 || + !lease) { + return -1; + } + + if (kzt_registry_api_lock(registry) != 0) { + return -1; + } + index = kzt_find_object_index(registry, link_map_addr); + if (registry->disabled || index < 0 || + registry->objects[index].generation != generation || + registry->objects[index].namespace_id.status != KZT_GUEST_FIELD_OK || + registry->objects[index].namespace_id.value != namespace_id || + registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || + registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return -1; + } + + ++registry->objects[index].active_source_leases; + lease->registry = registry; + lease->link_map_addr = link_map_addr; + lease->generation = generation; + lease->namespace_id = namespace_id; + lease->active = 1; + kzt_registry_api_unlock(registry); + return 0; +} + +void kzt_guest_registry_source_lease_release( + kzt_guest_registry_source_lease_t *lease) +{ + kzt_guest_registry_t *registry; + ssize_t index; + + if (!lease || !lease->active || !(registry = lease->registry)) { + return; + } + + pthread_mutex_lock(®istry->lock); + index = kzt_find_object_index(registry, lease->link_map_addr); + if (index >= 0 && + registry->objects[index].generation == lease->generation && + registry->objects[index].active_source_leases) { + --registry->objects[index].active_source_leases; + if (!registry->objects[index].active_source_leases) { + pthread_cond_broadcast(®istry->leases_idle); + } + } + pthread_mutex_unlock(®istry->lock); + memset(lease, 0, sizeof(*lease)); +} + +int kzt_guest_registry_find_by_link_map( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_object_snapshot_t **snapshot) +{ + ssize_t index; + + if (snapshot) { + *snapshot = NULL; + } + if (!registry || !snapshot || link_map_addr == 0) { + return -1; + } + + if (kzt_registry_api_lock(registry) != 0) { + return -1; + } + if (registry->disabled) { + kzt_registry_api_unlock(registry); + return -1; + } + + index = kzt_find_object_index(registry, link_map_addr); + if (index < 0 || + registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || + registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return -1; + } + + *snapshot = kzt_registry_calloc(1, sizeof(**snapshot)); + if (!*snapshot) { + ++registry->diagnostics.allocation_failures; + kzt_registry_api_unlock(registry); + return -1; + } + + if (kzt_copy_snapshot(*snapshot, ®istry->objects[index]) != 0) { + ++registry->diagnostics.allocation_failures; + kzt_registry_free(*snapshot); + *snapshot = NULL; + kzt_registry_api_unlock(registry); + return -1; + } + + kzt_registry_api_unlock(registry); + return 0; +} + +kzt_guest_registry_result_t kzt_guest_registry_commit_dynamic_view( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + const kzt_guest_dynamic_view_t *view) +{ + kzt_guest_object_snapshot_t *object; + kzt_guest_field_status_t status; + kzt_guest_registry_result_t result; + ssize_t index; + + if (!registry) { + return KZT_GUEST_REGISTRY_DISABLED; + } + + if (!view || link_map_addr == 0 || generation == 0) { + return KZT_GUEST_REGISTRY_ERROR; + } + +#ifdef KZT_GUEST_REGISTRY_TEST + if (kzt_registry_test_should_fail_dynamic_commit()) { + return KZT_GUEST_REGISTRY_ERROR; + } +#endif + + if (kzt_registry_api_lock(registry) != 0) { + return KZT_GUEST_REGISTRY_DISABLED; + } + if (registry->disabled) { + result = KZT_GUEST_REGISTRY_DISABLED; + goto out; + } + + index = kzt_find_object_index(registry, link_map_addr); + if (index < 0 || + registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || + registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { + result = KZT_GUEST_REGISTRY_ERROR; + goto out; + } + + object = ®istry->objects[index]; + if (object->generation != generation || + object->state == KZT_GUEST_OBJECT_UNLOADING || + object->state == KZT_GUEST_OBJECT_DEAD) { + result = KZT_GUEST_REGISTRY_ERROR; + goto out; + } + status = kzt_dynamic_view_field_status(view); + /* A complete view is the only source suitable for relocation decisions. + * Keep it when a later best-effort read is incomplete, while allowing the + * first incomplete view to remain available for diagnostics. */ + if (object->dynamic_view_status == KZT_GUEST_FIELD_OK && + status != KZT_GUEST_FIELD_OK) { + result = KZT_GUEST_REGISTRY_UNCHANGED; + goto out; + } + if (object->dynamic_view_status == status && + kzt_dynamic_view_equal(&object->dynamic_view, view)) { + result = KZT_GUEST_REGISTRY_UNCHANGED; + goto out; + } + + object->dynamic_view = *view; + object->dynamic_view_status = status; + if (status == KZT_GUEST_FIELD_OK) { + object->state = KZT_GUEST_OBJECT_PARSED; + } else if (object->state == KZT_GUEST_OBJECT_PARSED) { + object->state = KZT_GUEST_OBJECT_DISCOVERED; + } + result = KZT_GUEST_REGISTRY_UPDATED; + +out: + kzt_registry_api_unlock(registry); + return result; +} + +int kzt_guest_registry_find_dynamic_view( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_dynamic_view_t *view, + kzt_guest_field_status_t *status, + unsigned long *generation) +{ + ssize_t index; + + if (view) { + memset(view, 0, sizeof(*view)); + } + if (status) { + *status = KZT_GUEST_FIELD_NOT_PARSED; + } + if (generation) { + *generation = 0; + } + if (!registry || !view || link_map_addr == 0) { + return -1; + } + + if (kzt_registry_api_lock(registry) != 0) { + return -1; + } + if (registry->disabled) { + kzt_registry_api_unlock(registry); + return -1; + } + + index = kzt_find_object_index(registry, link_map_addr); + if (index < 0 || + registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || + registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return -1; + } + + *view = registry->objects[index].dynamic_view; + if (status) { + *status = registry->objects[index].dynamic_view_status; + } + if (generation) { + *generation = registry->objects[index].generation; + } + + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_publish_lazy_resolver( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + uintptr_t namespace_id, + const kzt_guest_lazy_resolver_t *resolver) +{ + kzt_guest_object_snapshot_t *object; + ssize_t index; + + if (!registry || !link_map_addr || !generation || !resolver || + !resolver->link_map_slot || !resolver->resolver_slot || + !resolver->guest_link_map || !resolver->guest_resolver || + resolver->guest_link_map != link_map_addr || + namespace_id != 0) { + return -1; + } + + if (kzt_registry_api_lock(registry) != 0) { + return -1; + } + index = kzt_find_object_index(registry, link_map_addr); + if (registry->disabled || index < 0) { + kzt_registry_api_unlock(registry); + return -1; + } + object = ®istry->objects[index]; + if (object->generation != generation || + object->namespace_id.status != KZT_GUEST_FIELD_OK || + object->namespace_id.value != namespace_id || + object->state == KZT_GUEST_OBJECT_WRAPPER_READY || + object->state == KZT_GUEST_OBJECT_PATCHED || + object->state == KZT_GUEST_OBJECT_UNLOADING || + object->state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return -1; + } + if (object->lazy_resolver.valid) { + int same = object->lazy_resolver.link_map_slot == + resolver->link_map_slot && + object->lazy_resolver.resolver_slot == + resolver->resolver_slot && + object->lazy_resolver.guest_link_map == + resolver->guest_link_map && + object->lazy_resolver.guest_resolver == + resolver->guest_resolver; + kzt_registry_api_unlock(registry); + return same ? 0 : -1; + } + object->lazy_resolver = *resolver; + object->lazy_resolver.valid = 1; + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_find_lazy_resolver( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + uintptr_t namespace_id, + kzt_guest_lazy_resolver_t *resolver) +{ + kzt_guest_object_snapshot_t *object; + ssize_t index; + + if (resolver) { + memset(resolver, 0, sizeof(*resolver)); + } + if (!registry || !link_map_addr || !generation || !resolver || + namespace_id != 0) { + return -1; + } + + if (kzt_registry_api_lock(registry) != 0) { + return -1; + } + index = kzt_find_object_index(registry, link_map_addr); + if (registry->disabled || index < 0) { + kzt_registry_api_unlock(registry); + return -1; + } + object = ®istry->objects[index]; + if (object->generation != generation || + object->namespace_id.status != KZT_GUEST_FIELD_OK || + object->namespace_id.value != namespace_id || + object->state == KZT_GUEST_OBJECT_UNLOADING || + object->state == KZT_GUEST_OBJECT_DEAD || + !object->lazy_resolver.valid) { + kzt_registry_api_unlock(registry); + return -1; + } + *resolver = object->lazy_resolver; + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_dump_snapshot( + kzt_guest_registry_t *registry, + kzt_guest_registry_dump_t *dump) +{ + size_t i; + + if (dump) { + dump->objects = NULL; + dump->count = 0; + } + if (!registry || !dump) { + return -1; + } + + if (kzt_registry_api_lock(registry) != 0) { + return -1; + } + if (registry->disabled) { + kzt_registry_api_unlock(registry); + return -1; + } + + if (registry->count == 0) { + kzt_registry_api_unlock(registry); + return 0; + } + + dump->objects = kzt_registry_calloc(registry->count, + sizeof(*dump->objects)); + if (!dump->objects) { + ++registry->diagnostics.allocation_failures; + kzt_registry_api_unlock(registry); + return -1; + } + + for (i = 0; i < registry->count; ++i) { + if (kzt_copy_snapshot(&dump->objects[i], ®istry->objects[i]) != 0) { + ++registry->diagnostics.allocation_failures; + kzt_free_snapshot_array(dump->objects, i + 1); + dump->objects = NULL; + dump->count = 0; + kzt_registry_api_unlock(registry); + return -1; + } + } + dump->count = registry->count; + + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_get_diagnostics( + kzt_guest_registry_t *registry, + kzt_guest_registry_diagnostics_t *diagnostics) +{ + if (!registry || !diagnostics) { + return -1; + } + + if (kzt_registry_api_lock(registry) != 0) { + return -1; + } + *diagnostics = registry->diagnostics; + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_configure_diagnostics( + kzt_guest_registry_t *registry, + const kzt_guest_registry_diagnostic_config_t *config) +{ + if (!registry || !config) { + return -1; + } + + if (kzt_registry_api_lock(registry) != 0) { + return -1; + } + registry->diagnostic_config.enabled = !!config->enabled; + registry->diagnostic_config.throttle_limit = + config->throttle_limit ? config->throttle_limit : 1; + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_get_diagnostic_report( + kzt_guest_registry_t *registry, + kzt_guest_registry_diagnostic_report_t *report) +{ + if (!registry || !report) { + return -1; + } + + if (kzt_registry_api_lock(registry) != 0) { + return -1; + } + memset(report, 0, sizeof(*report)); + report->config = registry->diagnostic_config; + report->counters = registry->diagnostics; + memcpy(report->events, registry->diagnostic_events, + sizeof(report->events)); + report->event_count = KZT_GUEST_REGISTRY_RESULT_COUNT; + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_note_diagnostic( + kzt_guest_registry_t *registry, + kzt_guest_registry_result_t result, + uintptr_t link_map_addr, + kzt_guest_registry_observation_diagnostic_t *diagnostic) +{ + if (!registry || result >= KZT_GUEST_REGISTRY_RESULT_COUNT) { + kzt_registry_init_empty_diagnostic(diagnostic, result, link_map_addr); + return -1; + } + + if (kzt_registry_api_lock(registry) != 0) { + kzt_registry_init_empty_diagnostic(diagnostic, result, + link_map_addr); + return -1; + } + kzt_registry_note_result(registry, result, link_map_addr, 0, diagnostic); + kzt_registry_api_unlock(registry); + return 0; +} + +static int kzt_guest_registry_dump_emit( + kzt_guest_registry_dump_sink_fn sink, + void *opaque, + const char *fmt, + ...) +{ + char line[1024]; + va_list ap; + int len; + + va_start(ap, fmt); + len = vsnprintf(line, sizeof(line), fmt, ap); + va_end(ap); + if (len < 0) { + return -1; + } + + line[sizeof(line) - 1] = '\0'; + return sink(line, opaque); +} + +static int kzt_guest_registry_dump_emit_scalar( + kzt_guest_registry_dump_sink_fn sink, + void *opaque, + const char *name, + kzt_guest_scalar_field_t field) +{ + return kzt_guest_registry_dump_emit( + sink, opaque, "%s=0x%lx(%s)", name, (unsigned long)field.value, + kzt_guest_field_status_name(field.status)); +} + +int kzt_guest_registry_dump_text( + kzt_guest_registry_t *registry, + kzt_guest_registry_dump_sink_fn sink, + void *opaque) +{ + kzt_guest_registry_diagnostic_report_t report; + kzt_guest_registry_dump_t dump = { 0 }; + size_t i; + int ret = -1; + + if (!registry || !sink || kzt_registry_api_enter(registry) != 0) { + return -1; + } + + if (kzt_guest_registry_get_diagnostic_report(registry, &report) != 0) { + goto out; + } + if (kzt_guest_registry_dump_snapshot(registry, &dump) != 0) { + goto out; + } + + if (kzt_guest_registry_dump_emit( + sink, opaque, + "kzt_guest_registry diagnostics enabled=%d throttle_limit=%lu " + "observations=%lu added=%lu unchanged=%lu updated=%lu " + "conflicts=%lu disabled=%lu errors=%lu init_failures=%lu " + "allocation_failures=%lu objects=%lu", + report.config.enabled, report.config.throttle_limit, + report.counters.observations, report.counters.added, + report.counters.unchanged, report.counters.updated, + report.counters.conflicts, report.counters.disabled, + report.counters.errors, report.counters.init_failures, + report.counters.allocation_failures, + (unsigned long)dump.count) != 0) { + goto out; + } + + for (i = 0; i < report.event_count; ++i) { + const kzt_guest_registry_event_summary_t *event = &report.events[i]; + + if (event->observed == 0) { + continue; + } + if (kzt_guest_registry_dump_emit( + sink, opaque, + "kzt_guest_registry event result=%s observed=%lu " + "emitted=%lu suppressed=%lu last_link_map=0x%lx " + "last_generation=%lu", + kzt_registry_result_name(event->result), event->observed, + event->emitted, event->suppressed, + (unsigned long)event->last_link_map_addr, + event->last_generation) != 0) { + goto out; + } + } + + for (i = 0; i < dump.count; ++i) { + const kzt_guest_object_snapshot_t *object = &dump.objects[i]; + + if (kzt_guest_registry_dump_emit( + sink, opaque, + "kzt_guest_registry object link_map=0x%lx generation=%lu " + "state=%d ", + (unsigned long)object->link_map_addr, object->generation, + object->state) != 0 || + kzt_guest_registry_dump_emit_scalar(sink, opaque, "load_bias", + object->load_bias) != 0 || + kzt_guest_registry_dump_emit_scalar(sink, opaque, " dynamic_addr", + object->dynamic_addr) != 0 || + kzt_guest_registry_dump_emit_scalar(sink, opaque, " map_start", + object->map_start) != 0 || + kzt_guest_registry_dump_emit_scalar(sink, opaque, " map_end", + object->map_end) != 0 || + kzt_guest_registry_dump_emit( + sink, opaque, + " namespace_id=0x%lx(%s) dynamic_view=%s path_status=%s " + "path=\"%s\" soname_status=%s soname=\"%s\"", + (unsigned long)object->namespace_id.value, + kzt_guest_field_status_name(object->namespace_id.status), + kzt_guest_field_status_name(object->dynamic_view_status), + kzt_guest_field_status_name(object->path.status), + object->path.value ? object->path.value : "", + kzt_guest_field_status_name(object->soname.status), + object->soname.value ? object->soname.value : "") != 0) { + goto out; + } + } + + ret = 0; + +out: + kzt_guest_registry_dump_free(&dump); + kzt_registry_api_leave(registry); + return ret; +} + +void kzt_guest_object_snapshot_free(kzt_guest_object_snapshot_t *snapshot) +{ + if (!snapshot) { + return; + } + + kzt_free_snapshot_strings(snapshot); + kzt_registry_free(snapshot); +} + +void kzt_guest_registry_dump_free(kzt_guest_registry_dump_t *dump) +{ + if (!dump) { + return; + } + + kzt_free_snapshot_array(dump->objects, dump->count); + dump->objects = NULL; + dump->count = 0; +} diff --git a/target/i386/latx/context/kzt_guest_registry_context.c b/target/i386/latx/context/kzt_guest_registry_context.c new file mode 100644 index 00000000000..ecc5b9731ad --- /dev/null +++ b/target/i386/latx/context/kzt_guest_registry_context.c @@ -0,0 +1,147 @@ +#include "kzt_guest_registry_context.h" + +enum kzt_guest_registry_context_state { + KZT_GUEST_REGISTRY_CONTEXT_UNINITIALIZED = 0, + KZT_GUEST_REGISTRY_CONTEXT_READY, + KZT_GUEST_REGISTRY_CONTEXT_UNAVAILABLE, + KZT_GUEST_REGISTRY_CONTEXT_DESTROYING, +}; + +kzt_guest_registry_t *kzt_guest_registry_context_get( + kzt_guest_registry_context_t *context, + pthread_mutex_t *context_lock) +{ + kzt_guest_registry_t *registry; + int state; + + if (!context || !context_lock) { + return NULL; + } + + state = __atomic_load_n(&context->state, __ATOMIC_ACQUIRE); + if (state == KZT_GUEST_REGISTRY_CONTEXT_READY) { + return __atomic_load_n(&context->registry, __ATOMIC_ACQUIRE); + } + if (state != KZT_GUEST_REGISTRY_CONTEXT_UNINITIALIZED) { + return NULL; + } + + pthread_mutex_lock(context_lock); + state = __atomic_load_n(&context->state, __ATOMIC_RELAXED); + if (state == KZT_GUEST_REGISTRY_CONTEXT_UNINITIALIZED) { + registry = kzt_guest_registry_init(); + __atomic_store_n(&context->registry, registry, __ATOMIC_RELEASE); + __atomic_store_n( + &context->state, + registry ? KZT_GUEST_REGISTRY_CONTEXT_READY : + KZT_GUEST_REGISTRY_CONTEXT_UNAVAILABLE, + __ATOMIC_RELEASE); + } else { + registry = state == KZT_GUEST_REGISTRY_CONTEXT_READY ? + __atomic_load_n(&context->registry, __ATOMIC_ACQUIRE) : NULL; + } + pthread_mutex_unlock(context_lock); + return registry; +} + +void kzt_guest_registry_context_destroy( + kzt_guest_registry_context_t *context, + pthread_mutex_t *context_lock) +{ + kzt_guest_registry_t *registry; + + if (!context || !context_lock) { + return; + } + + pthread_mutex_lock(context_lock); + __atomic_store_n(&context->state, + KZT_GUEST_REGISTRY_CONTEXT_DESTROYING, + __ATOMIC_RELEASE); + registry = __atomic_exchange_n(&context->registry, NULL, + __ATOMIC_ACQ_REL); + __atomic_store_n(&context->main_namespace_head, 0, __ATOMIC_RELEASE); + pthread_mutex_unlock(context_lock); + + kzt_guest_registry_destroy(®istry); +} + +int kzt_guest_registry_context_get_main_namespace_head( + const kzt_guest_registry_context_t *context, + uintptr_t *head) +{ + uintptr_t value; + + if (head) { + *head = 0; + } + if (!context || !head || + __atomic_load_n(&context->state, __ATOMIC_ACQUIRE) == + KZT_GUEST_REGISTRY_CONTEXT_DESTROYING) { + return -1; + } + value = __atomic_load_n(&context->main_namespace_head, + __ATOMIC_ACQUIRE); + if (!value) { + return -1; + } + *head = value; + return 0; +} + +int kzt_guest_registry_context_confirm_main_namespace_head( + kzt_guest_registry_context_t *context, + pthread_mutex_t *context_lock, + uintptr_t head) +{ + uintptr_t current; + int result = -1; + + if (!context || !context_lock || !head) { + return -1; + } + + pthread_mutex_lock(context_lock); + if (__atomic_load_n(&context->state, __ATOMIC_RELAXED) != + KZT_GUEST_REGISTRY_CONTEXT_DESTROYING) { + current = __atomic_load_n(&context->main_namespace_head, + __ATOMIC_RELAXED); + if (!current || current == head) { + __atomic_store_n(&context->main_namespace_head, head, + __ATOMIC_RELEASE); + result = 0; + } + } + pthread_mutex_unlock(context_lock); + return result; +} + +int kzt_guest_registry_context_has_main_namespace_evidence( + const kzt_guest_registry_context_t *context, + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + uintptr_t load_bias, + uintptr_t dynamic_addr) +{ + kzt_guest_object_snapshot_t *snapshot = NULL; + uintptr_t main_head = 0; + int matches = 0; + + if (!registry || !link_map_addr || !dynamic_addr || + kzt_guest_registry_context_get_main_namespace_head( + context, &main_head) != 0 || !main_head) { + return 0; + } + if (kzt_guest_registry_find_by_link_map( + registry, link_map_addr, &snapshot) == 0 && snapshot && + snapshot->load_bias.status == KZT_GUEST_FIELD_OK && + snapshot->load_bias.value == load_bias && + snapshot->dynamic_addr.status == KZT_GUEST_FIELD_OK && + snapshot->dynamic_addr.value == dynamic_addr && + snapshot->namespace_id.status == KZT_GUEST_FIELD_OK && + snapshot->namespace_id.value == 0) { + matches = 1; + } + kzt_guest_object_snapshot_free(snapshot); + return matches; +} diff --git a/target/i386/latx/context/kzt_jump_slot_production.c b/target/i386/latx/context/kzt_jump_slot_production.c new file mode 100644 index 00000000000..69d9dac8adb --- /dev/null +++ b/target/i386/latx/context/kzt_jump_slot_production.c @@ -0,0 +1,1189 @@ +#include "qemu/osdep.h" + +#include "kzt_jump_slot_production.h" + +#ifdef CONFIG_LATX_KZT + +#include + +#if defined(CONFIG_USER_ONLY) && !defined(KZT_JUMP_SLOT_PRODUCTION_TEST) +#include "qemu.h" +#endif + +#include "box64context.h" +#include "debug.h" +#include "elfload_dump.h" +#include "elfloader_private.h" +#include "kzt_rela_diagnostics.h" +#include "library.h" +#include "librarian.h" +#include "kzt_rela_request_enricher.h" +#include "kzt_rela_runtime_bridge.h" +#include "kzt_owner_resolver.h" +#include "kzt_runtime_candidate_shadow.h" + +typedef struct kzt_production_jump_slot_state { + box64context_t *context; + int slot_current_value_is_unresolved_stub; + uintptr_t resolved_target; + kzt_wrapper_bridge_provider_t wrapper_provider; + kzt_rela_immediate_candidate_request_t initial_request; + kzt_rela_request_enricher_result_t base_enrich_result; + kzt_rela_request_enricher_result_t bridge_enrich_result; + kzt_rela_immediate_candidate_request_t last_request; + kzt_rela_immediate_writer_result_t writer_result; + kzt_guest_registry_source_lease_t source_lease; + kzt_patch_candidate_t runtime_candidate; + kzt_runtime_got_plt_candidate_result_t runtime_candidate_result; + char runtime_candidate_strings[512]; + uintptr_t required_source_link_map; + unsigned long required_source_generation; + int discover_bridge_from_provider; + const char *failure_stage; +} kzt_production_jump_slot_state_t; + +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST +void kzt_jump_slot_production_test_before_source_lease_acquire(void); +void kzt_jump_slot_production_test_before_source_memory_access(void); +void kzt_jump_slot_production_test_before_slot_load(void); +void kzt_jump_slot_production_test_after_slot_load(uintptr_t *value); +void kzt_jump_slot_production_test_after_slot_cas(int exchanged); +void kzt_jump_slot_production_test_shadow_run(void); +int kzt_jump_slot_production_test_begin_slot_write( + uintptr_t slot_addr, kzt_patch_spike_permission_lease_t *lease); +int kzt_jump_slot_production_test_end_slot_write( + kzt_patch_spike_permission_lease_t *lease); +#endif + +static int production_read_guest_memory(uintptr_t address, void *dst, + size_t size, void *opaque) +{ + (void)opaque; + if (!address || !dst || !size) { + return -1; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + memcpy(dst, (const void *)address, size); +#else + void *host_ptr; + + host_ptr = lock_user(VERIFY_READ, (abi_ulong)address, size, true); + if (!host_ptr) { + return -1; + } + memcpy(dst, host_ptr, size); + unlock_user(host_ptr, (abi_ulong)address, 0); +#endif + return 0; +} + +static int production_shadow_expected_target( + const kzt_patch_candidate_t *candidate, uintptr_t *expected, + void *opaque) +{ + const kzt_production_jump_slot_state_t *state = opaque; + + if (!candidate || !expected || !state || + !state->last_request.expected_guest_target) { + return -1; + } + *expected = state->last_request.expected_guest_target; + return 0; +} + +static kzt_runtime_candidate_shadow_stub_classification_t +production_shadow_classify_stub(const kzt_patch_candidate_t *candidate, + void *opaque) +{ + const kzt_production_jump_slot_state_t *state = opaque; + + (void)candidate; + return state && state->slot_current_value_is_unresolved_stub ? + KZT_RUNTIME_CANDIDATE_SHADOW_STUB_MATCH : + KZT_RUNTIME_CANDIDATE_SHADOW_STUB_NO_MATCH; +} + +static void production_shadow_runtime_candidate( + kzt_production_jump_slot_state_t *state, + const kzt_rela_immediate_candidate_request_t *request, + const kzt_wrapper_bridge_provider_t *wrapper_provider) +{ + kzt_guest_dynamic_view_t view; + kzt_guest_field_status_t status; + unsigned long generation; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = production_read_guest_memory, + }; + kzt_patch_candidate_t candidate; + kzt_runtime_candidate_shadow_record_t record; + char strings[512]; + kzt_runtime_got_plt_candidate_request_t collector; + kzt_runtime_candidate_shadow_input_t input; + kzt_runtime_candidate_shadow_result_t result; + + /* Shadow is diagnostic-only. With diagnostics disabled, avoid a second + * candidate read plus planner formatting and logging. */ + if (!kzt_registry_diagnostics_enabled() || !state || !request || + !request->source.known || !request->source.link_map_addr || + !request->source.generation || request->table_kind == + KZT_PATCH_TABLE_UNKNOWN || !request->entry_addr) { + return; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_shadow_run(); +#endif + if (kzt_guest_registry_find_dynamic_view( + KztGuestRegistryForContext(state->context), + request->source.link_map_addr, &view, &status, &generation) != 0 || + status != KZT_GUEST_FIELD_OK || generation != request->source.generation || + view.status != KZT_GUEST_DYNAMIC_COMPLETE) { + return; + } + + memset(&candidate, 0, sizeof(candidate)); + memset(&record, 0, sizeof(record)); + memset(strings, 0, sizeof(strings)); + collector = (kzt_runtime_got_plt_candidate_request_t) { + .view = &view, + .reader_ops = &reader_ops, + .source = &request->source, + .dynamic_view_generation = generation, + .only_entry = 1, + .only_table_kind = request->table_kind, + .only_entry_index = request->entry_index, + .candidates = &candidate, + .candidate_capacity = 1, + .string_storage = strings, + .string_storage_size = sizeof(strings), + }; + input = (kzt_runtime_candidate_shadow_input_t) { + .collector_request = &collector, + .registry = KztGuestRegistryForContext(state->context), + .wrapper_manifest = wrapper_provider ? &wrapper_provider->manifest : + NULL, + .bridge_ops = wrapper_provider ? &wrapper_provider->bridge_ops : NULL, + .resolve_expected_guest_target = production_shadow_expected_target, + .expected_target_opaque = state, + .classify_stub = production_shadow_classify_stub, + .stub_classifier_opaque = state, + .records = &record, + .record_capacity = 1, + }; + (void)kzt_runtime_candidate_shadow_run(&input, &result); +} + +static int production_slot_load(uintptr_t slot_addr, uintptr_t *value, + void *opaque) +{ + (void)opaque; + if (!slot_addr || !value) { + return -1; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_before_slot_load(); +#endif + *value = __atomic_load_n((uintptr_t *)slot_addr, __ATOMIC_ACQUIRE); +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_after_slot_load(value); +#endif + return 0; +} + +static int production_slot_cas(uintptr_t slot_addr, uintptr_t *expected, + uintptr_t replacement, void *opaque) +{ + int exchanged; + + (void)opaque; + if (!slot_addr || !expected) { + return -1; + } + exchanged = __atomic_compare_exchange_n( + (uintptr_t *)slot_addr, expected, replacement, 0, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE) + ? 1 + : 0; +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_after_slot_cas(exchanged); +#endif + return exchanged; +} + +static int production_slot_fits_page(uintptr_t slot_addr, uintptr_t page_mask) +{ + uintptr_t slot_last; + + if (!slot_addr || (slot_addr & (sizeof(uintptr_t) - 1)) || + slot_addr > UINTPTR_MAX - (sizeof(uintptr_t) - 1)) { + return 0; + } + slot_last = slot_addr + sizeof(uintptr_t) - 1; + return (slot_addr & page_mask) == (slot_last & page_mask); +} + +/* Use linux-user bookkeeping, never a host mprotect on a guest slot. */ +static int production_slot_begin_write( + uintptr_t slot_addr, kzt_patch_spike_permission_lease_t *lease, + void *opaque) +{ + (void)opaque; + if (!slot_addr || !lease) { + return -1; + } + memset(lease, 0, sizeof(*lease)); +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + if (!production_slot_fits_page(slot_addr, ~(uintptr_t)0xfff)) { + return -1; + } + lease->guest_page_length = 0x1000; + return kzt_jump_slot_production_test_begin_slot_write(slot_addr, lease); +#elif defined(CONFIG_USER_ONLY) + { + abi_ulong guest_addr; + abi_ulong guest_last; + int flags; + int permissions; + + if (!production_slot_fits_page(slot_addr, TARGET_PAGE_MASK) || + !h2g_valid((void *)slot_addr) || + !h2g_valid((void *)(slot_addr + sizeof(uintptr_t) - 1))) { + return -1; + } + guest_addr = h2g((void *)slot_addr); + guest_last = h2g((void *)(slot_addr + sizeof(uintptr_t) - 1)); + if (guest_last < guest_addr || + guest_last - guest_addr != sizeof(uintptr_t) - 1 || + (guest_addr & TARGET_PAGE_MASK) != + (guest_last & TARGET_PAGE_MASK)) { + return -1; + } + lease->guest_page = guest_addr & TARGET_PAGE_MASK; + lease->guest_page_length = TARGET_PAGE_SIZE; + flags = page_get_flags(guest_addr); + permissions = flags & PAGE_BITS; + lease->checked = 1; + lease->original_permissions = permissions; + lease->was_writable = (flags & PAGE_WRITE) != 0; + if (!(flags & PAGE_VALID) || !permissions) { + return -1; + } + if (lease->was_writable) { + return 0; + } + if (target_mprotect(lease->guest_page, TARGET_PAGE_SIZE, + permissions | PAGE_WRITE) != 0) { + return -1; + } + lease->write_enabled = 1; + return 0; + } +#else + return -1; +#endif +} + +static int production_slot_end_write(kzt_patch_spike_permission_lease_t *lease, + void *opaque) +{ + (void)opaque; + if (!lease) { + return -1; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + return kzt_jump_slot_production_test_end_slot_write(lease); +#elif defined(CONFIG_USER_ONLY) + if (!lease->checked || !lease->guest_page_length || + (lease->guest_page & ~TARGET_PAGE_MASK) || + lease->guest_page_length != TARGET_PAGE_SIZE || + (lease->original_permissions & ~PAGE_BITS)) { + return -1; + } + if (!lease->write_enabled) { + return 0; + } + return target_mprotect(lease->guest_page, lease->guest_page_length, + lease->original_permissions); +#else + return -1; +#endif +} + +static int production_validate_write_generation( + const kzt_patch_decision_t *decision, void *opaque) +{ + kzt_production_jump_slot_state_t *state = opaque; + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_guest_object_snapshot_t *owner_snapshot = NULL; + kzt_guest_dynamic_view_t view; + kzt_guest_field_status_t dynamic_status; + unsigned long dynamic_generation = 0; + int valid = 0; + + if (!state || !decision || !decision->source.known || + !decision->source.link_map_addr || !decision->source.generation || + !decision->current_owner.known || + !decision->current_owner.link_map_addr || + !decision->current_owner.generation || + !decision->dynamic_view_available || + !decision->dynamic_view_generation || + (state->required_source_link_map && + decision->source.link_map_addr != state->required_source_link_map) || + (state->required_source_generation && + decision->source.generation != state->required_source_generation) || + kzt_guest_registry_find_by_link_map( + KztGuestRegistryForContext(state->context), + decision->source.link_map_addr, &snapshot) != 0 || !snapshot || + snapshot->generation != decision->source.generation || + kzt_guest_registry_find_dynamic_view( + KztGuestRegistryForContext(state->context), + decision->source.link_map_addr, &view, &dynamic_status, + &dynamic_generation) != 0 || + dynamic_status != KZT_GUEST_FIELD_OK || + dynamic_generation != decision->dynamic_view_generation || + kzt_guest_registry_find_by_link_map( + KztGuestRegistryForContext(state->context), + decision->current_owner.link_map_addr, &owner_snapshot) != 0 || + !owner_snapshot || + owner_snapshot->generation != decision->current_owner.generation) { + goto out; + } + valid = 1; +out: + kzt_guest_object_snapshot_free(snapshot); + kzt_guest_object_snapshot_free(owner_snapshot); + return valid ? 0 : -1; +} + +static int production_enrich( + kzt_rela_immediate_candidate_request_t *request, + const kzt_wrapper_bridge_provider_t *wrapper_provider, + kzt_rela_request_enricher_result_t *enrich_result, + kzt_production_jump_slot_state_t *state) +{ + kzt_rela_request_enricher_input_t enrich_input = { + .registry = KztGuestRegistryForContext(state->context), + .slot_current_value_is_unresolved_stub = + state->slot_current_value_is_unresolved_stub, + .wrapper_manifest = wrapper_provider ? &wrapper_provider->manifest : + NULL, + .bridge_ops = wrapper_provider ? &wrapper_provider->bridge_ops : NULL, + }; + return kzt_rela_immediate_request_enrich(request, &enrich_input, + enrich_result); +} + +static int production_request_is_main_namespace( + kzt_production_jump_slot_state_t *state, + const kzt_rela_immediate_candidate_request_t *request) +{ + kzt_guest_object_snapshot_t *snapshot = NULL; + int is_main = 0; + + if (!state || !request || !request->source.known || + !request->source.link_map_addr || !request->source.generation || + kzt_guest_registry_find_by_link_map( + KztGuestRegistryForContext(state->context), + request->source.link_map_addr, &snapshot) != 0 || !snapshot) { + goto out; + } + is_main = snapshot->generation == request->source.generation && + snapshot->namespace_id.status == KZT_GUEST_FIELD_OK && + snapshot->namespace_id.value == 0; +out: + kzt_guest_object_snapshot_free(snapshot); + return is_main ? 0 : -1; +} + +static int production_runtime_candidate_matches_request( + const kzt_patch_candidate_t *candidate, + const kzt_rela_immediate_candidate_request_t *request) +{ + return candidate && request && + request->relocation_type == R_X86_64_JUMP_SLOT && + candidate->reloc_type == KZT_PATCH_RELOCATION_JUMP_SLOT && + candidate->source.known && request->source.known && + candidate->source.link_map_addr == request->source.link_map_addr && + candidate->source.generation == request->source.generation && + candidate->dynamic_view_available && + candidate->dynamic_view_generation == request->source.generation && + candidate->table_kind == request->table_kind && + candidate->entry_index == request->entry_index && + candidate->entry_addr == request->entry_addr && + candidate->slot_addr == request->slot_addr && + candidate->slot_current_value_present && + request->slot_current_value_present && + candidate->slot_current_value == request->slot_current_value && + candidate->symbol_index == request->symbol_index && + candidate->symbol_name && request->symbol_name && + strcmp(candidate->symbol_name, request->symbol_name) == 0 && + kzt_symbol_version_evidence_matches( + candidate->version_evidence, candidate->version, + request->version_evidence, request->version); +} + +static int production_collect_runtime_candidate( + kzt_production_jump_slot_state_t *state, + kzt_rela_immediate_candidate_request_t *request) +{ + kzt_guest_dynamic_view_t view; + kzt_guest_field_status_t status; + unsigned long generation; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = production_read_guest_memory, + }; + kzt_runtime_got_plt_candidate_request_t collector; + + if (!state || !request || !request->source.link_map_addr || + !request->source.generation || + kzt_guest_registry_find_dynamic_view( + KztGuestRegistryForContext(state->context), + request->source.link_map_addr, &view, &status, &generation) != 0 || + status != KZT_GUEST_FIELD_OK || + generation != request->source.generation || + view.status != KZT_GUEST_DYNAMIC_COMPLETE) { + return -1; + } + + memset(&state->runtime_candidate, 0, sizeof(state->runtime_candidate)); + memset(state->runtime_candidate_strings, 0, + sizeof(state->runtime_candidate_strings)); + collector = (kzt_runtime_got_plt_candidate_request_t) { + .view = &view, + .reader_ops = &reader_ops, + .source = &request->source, + .dynamic_view_generation = generation, + .only_entry = 1, + .only_table_kind = request->table_kind, + .only_entry_index = request->entry_index, + .candidates = &state->runtime_candidate, + .candidate_capacity = 1, + .string_storage = state->runtime_candidate_strings, + .string_storage_size = sizeof(state->runtime_candidate_strings), + }; +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_before_source_memory_access(); +#endif + if (kzt_runtime_got_plt_candidates_collect( + &collector, &state->runtime_candidate_result) != 0 || + state->runtime_candidate_result.status != + KZT_RUNTIME_GOT_PLT_CANDIDATE_OK || + state->runtime_candidate_result.candidate_count != 1 || + !production_runtime_candidate_matches_request( + &state->runtime_candidate, request)) { + return -1; + } + + request->dynamic_addr = state->runtime_candidate.dynamic_addr; + request->load_bias = state->runtime_candidate.load_bias; + request->dynamic_view_generation = + state->runtime_candidate.dynamic_view_generation; + request->dynamic_view_available = 1; + request->entry_addr = state->runtime_candidate.entry_addr; + request->slot_current_value = + state->runtime_candidate.slot_current_value; + request->symbol_name = state->runtime_candidate.symbol_name; + request->version_evidence = + state->runtime_candidate.version_evidence; + request->version = state->runtime_candidate.version; + return 0; +} + +static int production_enrich_base( + kzt_rela_immediate_candidate_request_t *request, void *opaque) +{ + kzt_production_jump_slot_state_t *state = opaque; + + state->failure_stage = "BASE_EVIDENCE"; + int status = production_enrich(request, NULL, &state->base_enrich_result, + state); + + if (status == 0 && + production_request_is_main_namespace(state, request) != 0) { + /* Namespace evidence is not optional: only namespace zero has a + * binding/lease contract today, so other or unknown namespaces never + * reach the planner or a native write. */ + status = -1; + } + if (status == 0 && state->required_source_link_map && + (request->source.link_map_addr != state->required_source_link_map || + request->source.generation != state->required_source_generation)) { + status = -1; + } + if (status == 0 && !state->required_source_link_map) { +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_before_source_lease_acquire(); +#endif + status = kzt_guest_registry_source_lease_acquire( + KztGuestRegistryForContext(state->context), + request->source.link_map_addr, request->source.generation, 0, + &state->source_lease); + } + if (status == 0) { + status = production_collect_runtime_candidate(state, request); + } + if (status == 0) { + state->last_request = *request; + state->failure_stage = "EXACT_LIBRARY_BINDING"; + } + return status; +} + +static int production_acquire_exact( + const kzt_patch_object_ref_t *owner, library_t *resolved_provider, + kzt_guest_library_handle_t *handle, void *opaque) +{ + kzt_production_jump_slot_state_t *state = opaque; + kzt_guest_library_binding_key_t key; + kzt_guest_object_snapshot_t *snapshot = NULL; + int result = -1; + + state->failure_stage = "EXACT_LIBRARY_BINDING"; + if (!owner || !owner->known || !owner->link_map_addr || + !owner->generation || !resolved_provider || !handle) { + return -1; + } + if (kzt_guest_registry_find_by_link_map( + KztGuestRegistryForContext(state->context), + owner->link_map_addr, &snapshot) != 0 || !snapshot || + snapshot->generation != owner->generation || + snapshot->namespace_id.status != KZT_GUEST_FIELD_OK || + snapshot->namespace_id.value != 0) { + goto out; + } + key = (kzt_guest_library_binding_key_t){ + .link_map_addr = owner->link_map_addr, + .generation = owner->generation, + .namespace_id = snapshot->namespace_id.value, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + if (KztGuestLibraryLookupForContext(state->context, &key, handle) != 0) { + goto out; + } + if (handle->library != resolved_provider) { + kzt_guest_library_handle_release(handle); + goto out; + } + result = 0; + state->failure_stage = "BRIDGE_EVIDENCE"; +out: + kzt_guest_object_snapshot_free(snapshot); + return result; +} + +static void production_release_exact(kzt_guest_library_handle_t *handle, + void *opaque) +{ + (void)opaque; + kzt_guest_library_handle_release(handle); +} + +static int production_enrich_bridge( + kzt_rela_immediate_candidate_request_t *request, + library_t *held_provider, void *opaque) +{ + kzt_production_jump_slot_state_t *state = opaque; + int status; + + state->failure_stage = "BRIDGE_EVIDENCE"; + memset(&state->wrapper_provider, 0, sizeof(state->wrapper_provider)); + status = state->discover_bridge_from_provider ? + kzt_rela_runtime_wrapper_provider_discover_with_version_evidence( + state->context, held_provider, request->symbol_name, + request->version_evidence, request->version, + &state->wrapper_provider) : + kzt_rela_runtime_wrapper_provider_prepare_with_version_evidence( + state->context, held_provider, state->resolved_target, + request->symbol_name, request->version_evidence, + request->version, + &state->wrapper_provider); + + if (status <= 0) { + /* Keep source, dynamic, current-owner, and symbol evidence in the + * same request. The planner records the missing wrapper/provider as + * unsupported and the route performs the legacy fallback once. */ + status = production_enrich(request, NULL, + &state->bridge_enrich_result, state); + if (status == 0) { + state->last_request = *request; + production_shadow_runtime_candidate(state, request, NULL); + } + return status; + } + pthread_mutex_lock(&state->context->kzt_bridge_mutex); + status = production_enrich(request, &state->wrapper_provider, + &state->bridge_enrich_result, state); + pthread_mutex_unlock(&state->context->kzt_bridge_mutex); + if (status == 0) { + state->last_request = *request; + state->failure_stage = "SOURCE_IDENTITY"; + production_shadow_runtime_candidate( + state, request, &state->wrapper_provider); + } + return status; +} + +static int production_validate_source_identity( + const kzt_rela_immediate_candidate_request_t *request, void *opaque) +{ + kzt_production_jump_slot_state_t *state = opaque; + kzt_guest_object_snapshot_t *snapshot = NULL; + int valid = 0; + + state->failure_stage = "SOURCE_IDENTITY"; + if (!request || !request->source.known || + !request->source.link_map_addr || !request->source.generation || + (state->required_source_link_map && + (request->source.link_map_addr != state->required_source_link_map || + request->source.generation != state->required_source_generation)) || + kzt_guest_registry_find_by_link_map( + KztGuestRegistryForContext(state->context), + request->source.link_map_addr, &snapshot) != 0 || !snapshot || + snapshot->generation != request->source.generation || + snapshot->namespace_id.status != KZT_GUEST_FIELD_OK || + snapshot->namespace_id.value != 0) { + goto out; + } + valid = 1; + state->failure_stage = "WRITER"; +out: + kzt_guest_object_snapshot_free(snapshot); + return valid; +} + +static kzt_jump_slot_route_writer_status_t production_try_writer( + const kzt_rela_immediate_candidate_request_t *request, + const kzt_patch_spike_slot_ops_t *slot_ops, void *opaque) +{ + kzt_production_jump_slot_state_t *state = opaque; + kzt_patch_spike_guard_t *guard = + KztPatchSpikeGuardForContext(state->context); + int status; + + if (!slot_ops || !slot_ops->begin_write || !slot_ops->end_write || + !slot_ops->validate_generation) { + return KZT_JUMP_SLOT_ROUTE_WRITER_PRESERVE; + } + if (kzt_rela_immediate_jump_slot_try_write( + request, guard, slot_ops, &state->writer_result) != 0) { + return KZT_JUMP_SLOT_ROUTE_WRITER_ERROR; + } + state->last_request = *request; + if (state->writer_result.record.action == + KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST) { + return KZT_JUMP_SLOT_ROUTE_WRITER_PRESERVE; + } + if (!state->writer_result.writer_called) { + return KZT_JUMP_SLOT_ROUTE_WRITER_DECLINED; + } + if (state->writer_result.record.failure == + KZT_PATCH_SPIKE_FAILURE_PERMISSION_ENABLE_FAILED || + state->writer_result.record.failure == + KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED || + state->writer_result.record.failure == + KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED || + state->writer_result.record.failure == + KZT_PATCH_SPIKE_FAILURE_GENERATION_MISMATCH) { + return KZT_JUMP_SLOT_ROUTE_WRITER_PRESERVE; + } + status = state->writer_result.skip_legacy_write + ? KZT_JUMP_SLOT_ROUTE_WRITER_APPLIED + : KZT_JUMP_SLOT_ROUTE_WRITER_ERROR; + return status; +} + +static int production_diagnostic_sink(const char *line, size_t length, + void *opaque) +{ + (void)opaque; + printf_kzt_registry_diagnostics("%.*s\n", (int)length, line); + return 0; +} + +static void production_emit_diagnostic( + kzt_production_jump_slot_state_t *state, + const kzt_jump_slot_route_result_t *route_result) +{ + char buffer[KZT_RELA_DIAGNOSTIC_LINE_LIMIT]; + kzt_rela_diagnostic_throttle_t throttle; + kzt_rela_immediate_diagnostic_input_t input; + kzt_rela_immediate_diagnostic_result_t result; + const kzt_rela_immediate_candidate_request_t *request; + + if (!kzt_registry_diagnostics_enabled() || !state || !route_result) { + return; + } + request = state->last_request.slot_addr ? &state->last_request : + &state->initial_request; + if (!state->last_request.slot_addr) { + printf_kzt_registry_diagnostics( + "kzt_rela_fail_open stage=%s route_status=%d writer_status=%d " + "source_link_map=0x%lx slot=0x%lx symbol=%s version=%s " + "legacy_fallback=%d\n", + state->failure_stage ? state->failure_stage : "UNKNOWN", + route_result->status, route_result->writer_status, + (unsigned long)(request ? request->source.link_map_addr : 0), + (unsigned long)(request ? request->slot_addr : 0), + request && request->symbol_name ? request->symbol_name : "(none)", + request && request->version ? request->version : "(none)", + route_result->legacy_fallback_attempted); + return; + } + if (kzt_rela_diagnostic_throttle_init(&throttle, 1) != 0) { + return; + } + input = (kzt_rela_immediate_diagnostic_input_t) { + .mode = KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS_WRITE_ENABLED, + .request = &state->last_request, + .result = &state->writer_result, + .legacy_fallback = route_result->legacy_fallback_attempted, + .throttle = &throttle, + .buffer = buffer, + .buffer_size = sizeof(buffer), + .sink = production_diagnostic_sink, + }; + (void)kzt_rela_immediate_diagnostic_emit(&input, &result); +} + +static int production_jump_slot_route( + box64context_t *context, library_t *resolved_provider, + uintptr_t resolved_target, elfheader_t *head, int need_resolv_present, + int entry_index, Elf64_Rela *rela, uint64_t *slot, + uintptr_t slot_current_value, + int slot_current_value_is_unresolved_stub, unsigned long symbol_index, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *version, + int expected_guest_target_present, uintptr_t expected_guest_target, + uintptr_t legacy_target, int preserve_observed_on_failure, + uintptr_t required_source_link_map, + unsigned long required_source_generation, + int discover_bridge_from_provider, + kzt_jump_slot_route_result_t *route_result) +{ + kzt_jump_slot_route_input_t input; + kzt_jump_slot_route_ops_t ops; + kzt_production_jump_slot_state_t state; + int status; + + if (!context || !head || !rela || !slot || !route_result) { + return -1; + } + + memset(&input, 0, sizeof(input)); + input.enabled = 1; + input.preserve_observed_on_failure = preserve_observed_on_failure; + input.expected_guest_target_present = expected_guest_target_present; + input.resolved_target_matches_legacy = + resolved_target == legacy_target; + input.resolved_provider = resolved_provider; + input.request.relocation_type = ELF64_R_TYPE(rela->r_info); + input.request.table_kind = need_resolv_present ? KZT_PATCH_TABLE_PLT_RELA : + KZT_PATCH_TABLE_RELA; + input.request.entry_index = entry_index; + input.request.entry_addr = (uintptr_t)rela; + input.request.source.known = 1; + input.request.source.link_map_addr = required_source_link_map; + input.request.source.generation = required_source_generation; + input.request.source.map_start = (uintptr_t)head->memory; + input.request.source.map_end = (uintptr_t)head->memory + head->memsz; + input.request.source.soname = head->name; + input.request.source.path = head->path; + input.request.dynamic_addr = (uintptr_t)head->Dynamic; + input.request.load_bias = head->delta; + input.request.slot_addr = (uintptr_t)slot; + input.request.slot_current_value_present = 1; + input.request.slot_current_value = slot_current_value; + input.request.expected_guest_target = expected_guest_target; + input.request.legacy_target = legacy_target; + input.request.symbol_index = symbol_index; + input.request.symbol_name = symbol_name; + input.request.version_evidence = version_evidence; + input.request.version = version; + state = (kzt_production_jump_slot_state_t){ + .context = context, + .initial_request = input.request, + .slot_current_value_is_unresolved_stub = + slot_current_value_is_unresolved_stub, + .resolved_target = resolved_target, + .required_source_link_map = required_source_link_map, + .required_source_generation = required_source_generation, + .discover_bridge_from_provider = discover_bridge_from_provider, + .failure_stage = "ROUTE_PRECONDITIONS", + }; + ops = (kzt_jump_slot_route_ops_t){ + .enrich_base = production_enrich_base, + .acquire_exact_provider = production_acquire_exact, + .release_exact_provider = production_release_exact, + .enrich_bridge = production_enrich_bridge, + .validate_source_identity = production_validate_source_identity, + .try_native_writer = production_try_writer, + .load_slot = production_slot_load, + .compare_exchange_slot = production_slot_cas, + .begin_slot_write = production_slot_begin_write, + .end_slot_write = production_slot_end_write, + .validate_write_generation = production_validate_write_generation, + .opaque = &state, + }; + status = kzt_jump_slot_route_apply(&input, &ops, route_result); + if (status != 0) { + kzt_guest_registry_source_lease_release(&state.source_lease); + return -1; + } + production_emit_diagnostic(&state, route_result); + kzt_guest_registry_source_lease_release(&state.source_lease); + + printf_log(LOG_DEBUG, + "KZT: shared JUMP_SLOT route status=%d writer=%d " + "slot=%p observed=%p expected_guest=%p selected=%p " + "legacy=%p exact=%d/%d sym=%s\n", + route_result->status, route_result->writer_status, + (void *)slot, (void *)route_result->observed_value, + (void *)expected_guest_target, + (void *)route_result->selected_target, (void *)legacy_target, + route_result->exact_provider_acquired, + route_result->exact_provider_matched, + symbol_name ? symbol_name : "(none)"); + return 0; +} + +int kzt_production_jump_slot_route( + box64context_t *context, library_t *resolved_provider, + uintptr_t resolved_target, elfheader_t *head, int need_resolv_present, + int entry_index, Elf64_Rela *rela, uint64_t *slot, + uintptr_t slot_current_value, + int slot_current_value_is_unresolved_stub, unsigned long symbol_index, + const char *symbol_name, const char *version, + int expected_guest_target_present, uintptr_t expected_guest_target, + uintptr_t legacy_target, kzt_jump_slot_route_result_t *route_result) +{ + return production_jump_slot_route( + context, resolved_provider, resolved_target, head, + need_resolv_present, entry_index, rela, slot, slot_current_value, + slot_current_value_is_unresolved_stub, symbol_index, symbol_name, + version && version[0] ? KZT_SYMBOL_VERSION_VERSIONED : + KZT_SYMBOL_VERSION_UNKNOWN, + version, expected_guest_target_present, expected_guest_target, + legacy_target, 0, 0, 0, 0, route_result); +} + +int kzt_production_jump_slot_route_with_version_evidence( + box64context_t *context, library_t *resolved_provider, + uintptr_t resolved_target, elfheader_t *head, int need_resolv_present, + int entry_index, Elf64_Rela *rela, uint64_t *slot, + uintptr_t slot_current_value, + int slot_current_value_is_unresolved_stub, unsigned long symbol_index, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *version, int expected_guest_target_present, + uintptr_t expected_guest_target, uintptr_t legacy_target, + kzt_jump_slot_route_result_t *route_result) +{ + return production_jump_slot_route( + context, resolved_provider, resolved_target, head, + need_resolv_present, entry_index, rela, slot, slot_current_value, + slot_current_value_is_unresolved_stub, symbol_index, symbol_name, + version_evidence, version, expected_guest_target_present, + expected_guest_target, legacy_target, 0, 0, 0, 0, route_result); +} + +static elfheader_t *production_find_source_head( + box64context_t *context, uintptr_t link_map) +{ + int i; + + if (!context || !link_map) { + return NULL; + } + for (i = 0; i < context->elfsize; ++i) { + if (context->elfs[i] && context->elfs[i]->self_link_map == link_map) { + return context->elfs[i]; + } + } + return NULL; +} + +int kzt_production_lazy_route_guest_target_leased( + box64context_t *context, + const kzt_lazy_binding_pending_t *pending, + uintptr_t guest_target, + const kzt_guest_registry_source_lease_t *source_lease, + kzt_lazy_binding_route_result_t *result) +{ + kzt_jump_slot_route_result_t route_result; + elfheader_t *head; + Elf64_Rela *rela; + Elf64_Sym *sym; + const char *symbol_name; + const char *version_name; + kzt_symbol_version_evidence_t version_evidence; + kzt_owner_resolution_t owner_resolution; + kzt_guest_library_binding_key_t provider_key; + kzt_guest_library_handle_t provider_handle; + library_t *resolved_provider = NULL; + unsigned long symbol_index; + int version; + size_t count; + + if (!result) { + return -1; + } + memset(result, 0, sizeof(*result)); + result->status = KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED; + result->selected_target = guest_target; + result->final_value = guest_target; + if (!context || !pending || !pending->armed || !guest_target || + !pending->symbol || + !kzt_symbol_version_evidence_valid(pending->version_evidence, + pending->version)) { + return 0; + } + if (!source_lease || !source_lease->active || + source_lease->registry != KztGuestRegistryForContext(context) || + source_lease->link_map_addr != pending->source_link_map || + source_lease->generation != pending->source_generation || + source_lease->namespace_id != pending->namespace_id || + pending->namespace_kind != KZT_GUEST_LIBRARY_NAMESPACE_MAIN || + pending->namespace_id != 0) { + return 0; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_before_source_memory_access(); +#endif + head = production_find_source_head(context, pending->source_link_map); + if (!head || !head->jmprel || !head->pltent || !head->DynSym) { + return 0; + } + count = head->pltsz / head->pltent; + if (pending->relocation_index >= count) { + return 0; + } + rela = (Elf64_Rela *)(head->jmprel + head->delta) + + pending->relocation_index; + if (ELF64_R_TYPE(rela->r_info) != R_X86_64_JUMP_SLOT || + rela->r_offset + head->delta != pending->slot_addr) { + return 0; + } + symbol_index = ELF64_R_SYM(rela->r_info); + sym = &head->DynSym[symbol_index]; + symbol_name = SymName(head, sym); + version = head->VerSym ? + ((Elf64_Half *)((uintptr_t)head->VerSym + head->delta))[symbol_index] : + -1; + if (version == -1 || (version & 0x7fff) < 2) { + version_evidence = KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + version_name = NULL; + } else { + version &= 0x7fff; + version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + version_name = GetSymbolVersion(head, version); + } + if (!symbol_name || strcmp(symbol_name, pending->symbol) != 0 || + !kzt_symbol_version_evidence_matches( + version_evidence, version_name, pending->version_evidence, + pending->version)) { + return 0; + } + + kzt_owner_resolver_init(&owner_resolution); + if (kzt_owner_resolver_resolve_current( + KztGuestRegistryForContext(context), guest_target, guest_target, + &owner_resolution) != 0 || + owner_resolution.status != KZT_OWNER_RESOLVER_RESOLVED || + owner_resolution.owner_match != KZT_PATCH_OWNER_MATCH || + !owner_resolution.current_owner.known) { + return 0; + } + provider_key = (kzt_guest_library_binding_key_t) { + .link_map_addr = owner_resolution.current_owner.link_map_addr, + .generation = owner_resolution.current_owner.generation, + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + memset(&provider_handle, 0, sizeof(provider_handle)); + if (KztGuestLibraryLookupForContext(context, &provider_key, + &provider_handle) != 0 || + !provider_handle.library) { + kzt_guest_library_handle_release(&provider_handle); + return 0; + } + resolved_provider = provider_handle.library; + /* guest_target is only the guest loader's observed bridge/CAS evidence. + * Bridge enrichment resolves and verifies the provider's native symbol + * independently with dlvsym. */ + if (production_jump_slot_route( + context, resolved_provider, guest_target, head, 1, + pending->relocation_index, rela, + (uint64_t *)pending->slot_addr, guest_target, 0, symbol_index, + symbol_name, version_evidence, version_name, 1, guest_target, + guest_target, 1, + pending->source_link_map, pending->source_generation, + 1, + &route_result) != 0) { + kzt_guest_library_handle_release(&provider_handle); + result->status = KZT_LAZY_BINDING_ROUTE_ERROR; + return -1; + } + kzt_guest_library_handle_release(&provider_handle); + result->selected_target = route_result.selected_target; + result->final_value = route_result.final_value; + switch (route_result.status) { + case KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED: + result->status = KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED; + break; + case KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH: + result->status = KZT_LAZY_BINDING_ROUTE_CAS_MISMATCH; + break; + case KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED: + default: + result->status = KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED; + break; + } + return 0; +} + +int kzt_production_lazy_source_lease_acquire( + box64context_t *context, + const kzt_lazy_binding_pending_t *pending, + kzt_guest_registry_source_lease_t *source_lease) +{ + kzt_guest_registry_t *registry; + + if (!context || !pending || !source_lease || !pending->armed || + !pending->source_link_map || !pending->source_generation || + pending->namespace_kind != KZT_GUEST_LIBRARY_NAMESPACE_MAIN || + pending->namespace_id != 0) { + return -1; + } + memset(source_lease, 0, sizeof(*source_lease)); + registry = KztGuestRegistryForContext(context); + if (!registry) { + return -1; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_before_source_lease_acquire(); +#endif + return kzt_guest_registry_source_lease_acquire( + registry, pending->source_link_map, + pending->source_generation, pending->namespace_id, source_lease); +} + +int kzt_production_lazy_load_slot_with_lease( + box64context_t *context, + const kzt_lazy_binding_pending_t *pending, + uintptr_t slot_addr, + uintptr_t *value, + kzt_guest_registry_source_lease_t *source_lease) +{ + if (!slot_addr || !value || !source_lease || + kzt_production_lazy_source_lease_acquire( + context, pending, source_lease) != 0) { + return -1; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_before_slot_load(); +#endif + *value = __atomic_load_n((uintptr_t *)slot_addr, __ATOMIC_ACQUIRE); +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_after_slot_load(value); +#endif + return 0; +} + +int kzt_production_lazy_route_guest_target( + box64context_t *context, + const kzt_lazy_binding_pending_t *pending, + uintptr_t guest_target, + kzt_lazy_binding_route_result_t *result) +{ + kzt_guest_registry_source_lease_t source_lease = { 0 }; + int status; + + if (!result) { + return -1; + } + memset(result, 0, sizeof(*result)); + result->status = KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED; + result->selected_target = guest_target; + result->final_value = guest_target; + if (!context || !pending || !pending->armed || !guest_target || + kzt_production_lazy_source_lease_acquire( + context, pending, &source_lease) != 0) { + return 0; + } + status = kzt_production_lazy_route_guest_target_leased( + context, pending, guest_target, &source_lease, result); + kzt_guest_registry_source_lease_release(&source_lease); + return status; +} + +typedef struct kzt_lazy_completion_production_state { + box64context_t *context; + const kzt_lazy_binding_pending_t *pending; + kzt_guest_registry_source_lease_t source_lease; +} kzt_lazy_completion_production_state_t; + +static int kzt_lazy_production_load_slot(uintptr_t slot_addr, + uintptr_t *value, void *opaque) +{ + kzt_lazy_completion_production_state_t *state = opaque; + + return state ? kzt_production_lazy_load_slot_with_lease( + state->context, state->pending, slot_addr, value, + &state->source_lease) : + -1; +} + +static int kzt_lazy_production_validate( + const kzt_lazy_binding_pending_t *pending, uintptr_t guest_target, + void *opaque) +{ + kzt_lazy_completion_production_state_t *state = opaque; + box64context_t *context = state ? state->context : NULL; + kzt_guest_lazy_resolver_t resolver; + + if (!pending || !context || pending->context_id != (uintptr_t)context || + !guest_target || !state->source_lease.active || + state->source_lease.namespace_id != pending->namespace_id || + kzt_guest_registry_find_lazy_resolver( + KztGuestRegistryForContext(context), pending->source_link_map, + pending->source_generation, pending->namespace_id, + &resolver) != 0) { + return 0; + } + return resolver.guest_link_map == pending->source_link_map && + resolver.guest_resolver == pending->guest_resolver; +} + +static int kzt_lazy_production_route( + const kzt_lazy_binding_pending_t *pending, uintptr_t guest_target, + kzt_lazy_binding_route_result_t *result, void *opaque) +{ + kzt_lazy_completion_production_state_t *state = opaque; + + return state ? kzt_production_lazy_route_guest_target_leased( + state->context, pending, guest_target, + &state->source_lease, result) : + -1; +} + +int kzt_production_lazy_complete( + box64context_t *context, kzt_lazy_binding_pending_t *pending, + kzt_lazy_binding_result_t *result) +{ + kzt_lazy_completion_production_state_t state = { + .context = context, + .pending = pending, + }; + kzt_lazy_binding_ops_t ops = { + .load_slot = kzt_lazy_production_load_slot, + .validate_post_bind = kzt_lazy_production_validate, + .route_guest_target = kzt_lazy_production_route, + .opaque = &state, + }; + int status = kzt_lazy_binding_complete(pending, &ops, result); + + kzt_guest_registry_source_lease_release(&state.source_lease); + return status; +} + +#endif diff --git a/target/i386/latx/context/kzt_jump_slot_route.c b/target/i386/latx/context/kzt_jump_slot_route.c new file mode 100644 index 00000000000..b695411693f --- /dev/null +++ b/target/i386/latx/context/kzt_jump_slot_route.c @@ -0,0 +1,250 @@ +#include "kzt_jump_slot_route.h" + +#include + +typedef struct kzt_jump_slot_route_slot_state { + const kzt_jump_slot_route_ops_t *ops; + uintptr_t last_read; + uintptr_t committed_value; + int last_read_present; + int write_succeeded; + int cas_mismatch; +} kzt_jump_slot_route_slot_state_t; + +static int route_slot_read(uintptr_t slot_addr, uintptr_t *value, void *opaque) +{ + kzt_jump_slot_route_slot_state_t *state = opaque; + + if (!state || !state->ops || !state->ops->load_slot || !value || + state->ops->load_slot(slot_addr, value, state->ops->opaque) != 0) { + return -1; + } + state->last_read = *value; + state->last_read_present = 1; + return 0; +} + +static int route_slot_write(uintptr_t slot_addr, uintptr_t value, void *opaque) +{ + kzt_jump_slot_route_slot_state_t *state = opaque; + uintptr_t expected; + int exchanged; + + if (!state || !state->ops || !state->ops->compare_exchange_slot || + !state->last_read_present) { + return -1; + } + expected = state->write_succeeded ? state->committed_value : + state->last_read; + exchanged = state->ops->compare_exchange_slot( + slot_addr, &expected, value, state->ops->opaque); + if (exchanged > 0) { + state->last_read = value; + state->committed_value = value; + state->write_succeeded = 1; + } else { + state->last_read = expected; + state->cas_mismatch = exchanged == 0; + } + return exchanged > 0 ? 0 : -1; +} + +static int route_slot_begin_write( + uintptr_t slot_addr, kzt_patch_spike_permission_lease_t *lease, + void *opaque) +{ + kzt_jump_slot_route_slot_state_t *state = opaque; + + if (!state || !state->ops || !state->ops->begin_slot_write) { + return -1; + } + return state->ops->begin_slot_write(slot_addr, lease, state->ops->opaque); +} + +static int route_slot_end_write(kzt_patch_spike_permission_lease_t *lease, + void *opaque) +{ + kzt_jump_slot_route_slot_state_t *state = opaque; + + if (!state || !state->ops || !state->ops->end_slot_write) { + return -1; + } + return state->ops->end_slot_write(lease, state->ops->opaque); +} + +static int route_slot_validate_generation(const kzt_patch_decision_t *decision, + void *opaque) +{ + kzt_jump_slot_route_slot_state_t *state = opaque; + + if (!state || !state->ops || !state->ops->validate_write_generation) { + return -1; + } + return state->ops->validate_write_generation(decision, state->ops->opaque); +} + +static int route_has_owner_evidence( + const kzt_rela_immediate_candidate_request_t *request) +{ + return request && request->owner_match == KZT_PATCH_OWNER_MATCH && + request->current_owner.known && + request->current_owner.link_map_addr && + request->current_owner.generation; +} + +static int route_owner_identity_matches( + const kzt_rela_immediate_candidate_request_t *request, + const kzt_patch_object_ref_t *acquired_owner) +{ + return route_has_owner_evidence(request) && acquired_owner && + request->current_owner.link_map_addr == + acquired_owner->link_map_addr && + request->current_owner.generation == acquired_owner->generation; +} + +static int route_legacy_fallback( + const kzt_jump_slot_route_input_t *input, + const kzt_jump_slot_route_ops_t *ops, + kzt_jump_slot_route_result_t *result) +{ + uintptr_t expected = result->observed_value; + int exchanged; + + if (input->preserve_observed_on_failure) { + result->status = KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED; + result->selected_target = result->observed_value; + result->final_value = result->observed_value; + return 0; + } + + result->legacy_fallback_attempted = 1; + result->selected_target = input->request.legacy_target; + exchanged = ops->compare_exchange_slot( + input->request.slot_addr, &expected, input->request.legacy_target, + ops->opaque); + if (exchanged < 0) { + result->status = KZT_JUMP_SLOT_ROUTE_WRITE_ERROR; + result->final_value = expected; + return 0; + } + if (exchanged == 0) { + result->status = KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH; + result->final_value = expected; + return 0; + } + result->status = KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED; + result->final_value = input->request.legacy_target; + return 0; +} + +int kzt_jump_slot_route_apply(const kzt_jump_slot_route_input_t *input, + const kzt_jump_slot_route_ops_t *ops, + kzt_jump_slot_route_result_t *result) +{ + kzt_rela_immediate_candidate_request_t request; + kzt_patch_object_ref_t acquired_owner; + kzt_guest_library_handle_t handle; + kzt_jump_slot_route_slot_state_t slot_state; + kzt_patch_spike_slot_ops_t slot_ops; + int acquired = 0; + + if (!result) { + return -1; + } + memset(result, 0, sizeof(*result)); + result->status = KZT_JUMP_SLOT_ROUTE_WRITE_ERROR; + if (!input || !ops || !ops->load_slot || + !ops->compare_exchange_slot || !input->request.slot_addr) { + return -1; + } + result->expected_guest_target = input->request.expected_guest_target; + if (!input->enabled) { + result->status = KZT_JUMP_SLOT_ROUTE_BYPASS; + return 0; + } + if (ops->load_slot(input->request.slot_addr, &result->observed_value, + ops->opaque) != 0) { + return 0; + } + if (input->request.slot_current_value_present && + result->observed_value != input->request.slot_current_value) { + result->status = KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH; + result->final_value = result->observed_value; + return 0; + } + + request = input->request; + request.slot_current_value_present = 1; + request.slot_current_value = result->observed_value; + memset(&handle, 0, sizeof(handle)); + memset(&acquired_owner, 0, sizeof(acquired_owner)); + memset(&slot_state, 0, sizeof(slot_state)); + + if (input->resolved_target_matches_legacy && + input->expected_guest_target_present && + request.expected_guest_target && input->resolved_provider && + ops->enrich_base && ops->acquire_exact_provider && + ops->release_exact_provider && ops->enrich_bridge && + ops->try_native_writer && + ops->enrich_base(&request, ops->opaque) == 0 && + route_has_owner_evidence(&request)) { + acquired_owner = request.current_owner; + if (ops->acquire_exact_provider(&acquired_owner, + input->resolved_provider, &handle, + ops->opaque) == 0) { + acquired = 1; + result->exact_provider_acquired = 1; + if (handle.library == input->resolved_provider) { + result->exact_provider_matched = 1; + if (ops->enrich_bridge(&request, handle.library, + ops->opaque) == 0 && + route_owner_identity_matches(&request, + &acquired_owner) && + (!ops->validate_source_identity || + (result->source_identity_rechecked = 1, + ops->validate_source_identity(&request, + ops->opaque) > 0))) { + slot_state = (kzt_jump_slot_route_slot_state_t){ + .ops = ops, + }; + slot_ops = (kzt_patch_spike_slot_ops_t){ + .read_slot = route_slot_read, + .write_slot = route_slot_write, + .begin_write = route_slot_begin_write, + .end_write = route_slot_end_write, + .validate_generation = route_slot_validate_generation, + .opaque = &slot_state, + }; + result->native_writer_called = 1; + result->writer_status = ops->try_native_writer( + &request, &slot_ops, ops->opaque); + } + } + } + } + + if (acquired) { + ops->release_exact_provider(&handle, ops->opaque); + } + if (result->native_writer_called && slot_state.cas_mismatch) { + result->status = KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH; + result->selected_target = slot_state.last_read; + result->final_value = slot_state.last_read; + return 0; + } + if (result->writer_status == KZT_JUMP_SLOT_ROUTE_WRITER_APPLIED) { + result->status = KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED; + result->selected_target = request.native_bridge_target; + result->final_value = request.native_bridge_target; + return 0; + } + if (result->writer_status == KZT_JUMP_SLOT_ROUTE_WRITER_PRESERVE) { + result->status = KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED; + result->selected_target = slot_state.last_read_present ? + slot_state.last_read : result->observed_value; + result->final_value = result->selected_target; + return 0; + } + + return route_legacy_fallback(input, ops, result); +} diff --git a/target/i386/latx/context/kzt_lazy_binding.c b/target/i386/latx/context/kzt_lazy_binding.c new file mode 100644 index 00000000000..0948367c45a --- /dev/null +++ b/target/i386/latx/context/kzt_lazy_binding.c @@ -0,0 +1,201 @@ +#include "kzt_lazy_binding.h" + +#include + +static void lazy_result_init(kzt_lazy_binding_result_t *result) +{ + if (!result) { + return; + } + memset(result, 0, sizeof(*result)); + result->status = KZT_LAZY_BINDING_ERROR; + result->reason = KZT_LAZY_BINDING_REASON_INVALID_REQUEST; +} + +static int lazy_copy_text(char *dst, size_t size, const char *src, + const char **stored) +{ + size_t length; + + if (stored) { + *stored = NULL; + } + if (!dst || !size || !stored) { + return -1; + } + if (!src || !src[0]) { + return 0; + } + length = strlen(src); + if (length >= size) { + return -1; + } + memcpy(dst, src, length + 1); + *stored = dst; + return 0; +} + +static void lazy_consume(kzt_lazy_binding_pending_t *pending, + kzt_lazy_binding_result_t *result) +{ + if (pending) { + memset(pending, 0, sizeof(*pending)); + } + if (result) { + result->pending_armed = 0; + result->pending_cleared = 1; + } +} + +int kzt_lazy_binding_begin( + const kzt_lazy_binding_begin_request_t *request, + kzt_lazy_binding_pending_t *pending, + kzt_lazy_binding_result_t *result) +{ + kzt_lazy_binding_pending_t candidate; + + lazy_result_init(result); + if (!request || !pending || !result) { + return -1; + } + result->selected_target = request->guest_resolver; + result->slot_before = request->unresolved_stub; + result->slot_after = request->unresolved_stub; + if (!request->enabled) { + result->status = KZT_LAZY_BINDING_BYPASS; + result->reason = KZT_LAZY_BINDING_REASON_DISABLED; + return 0; + } + /* Nested resolvers share a context. A pending owned by another context + * cannot complete this request and must not block its fail-open path. */ + if (pending->armed && pending->context_id == request->context_id) { + result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; + result->reason = KZT_LAZY_BINDING_REASON_PENDING_BUSY; + return 0; + } + if (request->namespace_kind != KZT_GUEST_LIBRARY_NAMESPACE_MAIN || + request->namespace_id != 0) { + result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; + result->reason = KZT_LAZY_BINDING_REASON_NON_MAIN_NAMESPACE; + return 0; + } + if (!request->context_id || !request->source_link_map || + !request->source_generation || !request->slot_addr || + !request->unresolved_stub || !request->guest_resolver) { + result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; + result->reason = request->guest_resolver ? + KZT_LAZY_BINDING_REASON_INVALID_REQUEST : + KZT_LAZY_BINDING_REASON_RESOLVER_MISSING; + return 0; + } + + memset(&candidate, 0, sizeof(candidate)); + candidate.context_id = request->context_id; + candidate.source_link_map = request->source_link_map; + candidate.source_generation = request->source_generation; + candidate.namespace_id = request->namespace_id; + candidate.namespace_kind = request->namespace_kind; + candidate.relocation_index = request->relocation_index; + candidate.slot_addr = request->slot_addr; + candidate.unresolved_stub = request->unresolved_stub; + candidate.guest_resolver = request->guest_resolver; + candidate.addend = request->addend; + candidate.version_evidence = request->version_evidence; + if (lazy_copy_text(candidate.symbol_storage, + sizeof(candidate.symbol_storage), request->symbol, + &candidate.symbol) != 0 || + lazy_copy_text(candidate.version_storage, + sizeof(candidate.version_storage), request->version, + &candidate.version) != 0) { + result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; + result->reason = KZT_LAZY_BINDING_REASON_INVALID_REQUEST; + return 0; + } + candidate.armed = 1; + *pending = candidate; + /* Repair the pointers after the structure copy. */ + pending->symbol = candidate.symbol ? pending->symbol_storage : NULL; + pending->version = candidate.version ? pending->version_storage : NULL; + result->status = KZT_LAZY_BINDING_HANDOFF_GUEST; + result->reason = KZT_LAZY_BINDING_REASON_NONE; + result->pending_armed = 1; + return 0; +} + +int kzt_lazy_binding_complete( + kzt_lazy_binding_pending_t *pending, + const kzt_lazy_binding_ops_t *ops, + kzt_lazy_binding_result_t *result) +{ + uintptr_t guest_target = 0; + kzt_lazy_binding_route_result_t route_result; + + lazy_result_init(result); + if (!pending || !ops || !result || !pending->armed || !ops->load_slot) { + return -1; + } + if (ops->load_slot(pending->slot_addr, &guest_target, ops->opaque) != 0) { + result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; + result->reason = KZT_LAZY_BINDING_REASON_SLOT_READ_ERROR; + lazy_consume(pending, result); + return 0; + } + result->slot_before = guest_target; + result->slot_after = guest_target; + result->selected_target = guest_target; + if (guest_target == pending->unresolved_stub) { + result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; + result->reason = KZT_LAZY_BINDING_REASON_SLOT_UNCHANGED; + lazy_consume(pending, result); + return 0; + } + if (!kzt_symbol_version_evidence_valid(pending->version_evidence, + pending->version)) { + result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; + result->reason = KZT_LAZY_BINDING_REASON_MISSING_VERSION; + lazy_consume(pending, result); + return 0; + } + if (!ops->validate_post_bind || + ops->validate_post_bind(pending, guest_target, ops->opaque) <= 0) { + result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; + result->reason = KZT_LAZY_BINDING_REASON_POST_BIND_INVALID; + lazy_consume(pending, result); + return 0; + } + if (!ops->route_guest_target) { + result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; + result->reason = KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE; + lazy_consume(pending, result); + return 0; + } + memset(&route_result, 0, sizeof(route_result)); + if (ops->route_guest_target(pending, guest_target, &route_result, + ops->opaque) != 0 || + route_result.status == KZT_LAZY_BINDING_ROUTE_ERROR) { + result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; + result->reason = KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE; + } else if (route_result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED) { + result->status = KZT_LAZY_BINDING_NATIVE_APPLIED; + result->reason = KZT_LAZY_BINDING_REASON_NATIVE_APPLIED; + } else if (route_result.status == KZT_LAZY_BINDING_ROUTE_CAS_MISMATCH) { + result->status = KZT_LAZY_BINDING_CAS_MISMATCH; + result->reason = KZT_LAZY_BINDING_REASON_CAS_MISMATCH; + } else { + result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; + result->reason = KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE; + } + result->selected_target = route_result.selected_target ? + route_result.selected_target : guest_target; + result->slot_after = route_result.final_value ? + route_result.final_value : guest_target; + lazy_consume(pending, result); + return 0; +} + +void kzt_lazy_binding_cancel(kzt_lazy_binding_pending_t *pending) +{ + if (pending) { + memset(pending, 0, sizeof(*pending)); + } +} diff --git a/target/i386/latx/context/kzt_lazy_diagnostics.c b/target/i386/latx/context/kzt_lazy_diagnostics.c new file mode 100644 index 00000000000..9464edfae45 --- /dev/null +++ b/target/i386/latx/context/kzt_lazy_diagnostics.c @@ -0,0 +1,247 @@ +#include "kzt_lazy_diagnostics.h" + +#include +#include + +#define KZT_LAZY_DIAGNOSTIC_SCHEMA 1UL + +static void kzt_lazy_diagnostic_copy_token(char *dst, size_t dst_size, + const char *src) +{ + size_t i; + + if (!dst || dst_size == 0) { + return; + } + if (!src || !src[0]) { + src = "(none)"; + } + + for (i = 0; i + 1 < dst_size && src[i]; ++i) { + unsigned char value = (unsigned char)src[i]; + + if (value <= ' ' || value == '"' || value == '\\' || value == '=') { + dst[i] = '_'; + } else { + dst[i] = (char)value; + } + } + dst[i] = '\0'; +} + +static const char *kzt_lazy_diagnostic_status_name( + const kzt_lazy_binding_result_t *binding_result) +{ + if (!binding_result) { + return "INVALID_RESULT"; + } + + switch (binding_result->status) { + case KZT_LAZY_BINDING_BYPASS: + return "BYPASS"; + case KZT_LAZY_BINDING_HANDOFF_GUEST: + return "HANDOFF_GUEST"; + case KZT_LAZY_BINDING_WAITING_GUEST_TARGET: + return "WAITING_GUEST_TARGET"; + case KZT_LAZY_BINDING_NATIVE_APPLIED: + return "NATIVE_APPLIED"; + case KZT_LAZY_BINDING_CAS_MISMATCH: + return "CAS_MISMATCH"; + case KZT_LAZY_BINDING_ERROR: + return "ERROR"; + case KZT_LAZY_BINDING_GUEST_PRESERVED: + switch (binding_result->reason) { + case KZT_LAZY_BINDING_REASON_SLOT_UNCHANGED: + return "SLOT_UNCHANGED"; + case KZT_LAZY_BINDING_REASON_MISSING_VERSION: + return "MISSING_VERSION"; + default: + return "GUEST_PRESERVED"; + } + } + + return "UNKNOWN"; +} + +static const char *kzt_lazy_diagnostic_reason_name( + kzt_lazy_binding_reason_t reason) +{ + switch (reason) { + case KZT_LAZY_BINDING_REASON_NONE: + return "NONE"; + case KZT_LAZY_BINDING_REASON_DISABLED: + return "DISABLED"; + case KZT_LAZY_BINDING_REASON_INVALID_REQUEST: + return "INVALID_REQUEST"; + case KZT_LAZY_BINDING_REASON_PENDING_OCCUPIED: + return "PENDING_OCCUPIED"; + case KZT_LAZY_BINDING_REASON_GENERATION_CHANGED: + return "GENERATION_CHANGED"; + case KZT_LAZY_BINDING_REASON_NON_MAIN_NAMESPACE: + return "NON_MAIN_NAMESPACE"; + case KZT_LAZY_BINDING_REASON_RESOLVER_MISSING: + return "RESOLVER_MISSING"; + case KZT_LAZY_BINDING_REASON_SLOT_UNCHANGED: + return "SLOT_UNCHANGED"; + case KZT_LAZY_BINDING_REASON_MISSING_VERSION: + return "MISSING_VERSION"; + case KZT_LAZY_BINDING_REASON_POST_BIND_INVALID: + return "POST_BIND_INVALID"; + case KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE: + return "NATIVE_UNAVAILABLE"; + case KZT_LAZY_BINDING_REASON_NATIVE_APPLIED: + return "NATIVE_APPLIED"; + case KZT_LAZY_BINDING_REASON_CAS_MISMATCH: + return "CAS_MISMATCH"; + case KZT_LAZY_BINDING_REASON_SLOT_READ_ERROR: + return "SLOT_READ_ERROR"; + case KZT_LAZY_BINDING_REASON_CAS_ERROR: + return "CAS_ERROR"; + } + + return "UNKNOWN"; +} + +int kzt_lazy_diagnostic_record_build( + const kzt_lazy_binding_pending_t *pending, + const kzt_lazy_binding_result_t *binding_result, + kzt_lazy_diagnostic_record_t *record) +{ + if (!pending || !binding_result || !record) { + return -1; + } + + memset(record, 0, sizeof(*record)); + record->schema = KZT_LAZY_DIAGNOSTIC_SCHEMA; + kzt_lazy_diagnostic_copy_token(record->symbol, sizeof(record->symbol), + pending->symbol); + kzt_lazy_diagnostic_copy_token( + record->first_execution_route, + sizeof(record->first_execution_route), "guest"); + kzt_lazy_diagnostic_copy_token( + record->completion_route_status, + sizeof(record->completion_route_status), + kzt_lazy_diagnostic_status_name(binding_result)); + record->slot_before = pending->unresolved_stub; + record->slot_after_guest = binding_result->slot_before; + record->selected_second_target = binding_result->slot_after; + kzt_lazy_diagnostic_copy_token(record->reason, sizeof(record->reason), + kzt_lazy_diagnostic_reason_name( + binding_result->reason)); + return 0; +} + +kzt_lazy_diagnostic_format_status_t kzt_lazy_diagnostic_format( + const kzt_lazy_diagnostic_record_t *record, + char *buffer, + size_t buffer_size) +{ + int written; + + if (!record || !buffer || buffer_size == 0) { + return KZT_LAZY_DIAGNOSTIC_FORMAT_ERROR; + } + + written = snprintf( + buffer, buffer_size, + "kzt_lazy_diagnostic schema=%lu symbol=%s first_execution_route=%s " + "completion_route_status=%s slot_before=0x%lx " + "slot_after_guest=0x%lx selected_second_target=0x%lx reason=%s", + record->schema, record->symbol, record->first_execution_route, + record->completion_route_status, + (unsigned long)record->slot_before, + (unsigned long)record->slot_after_guest, + (unsigned long)record->selected_second_target, record->reason); + if (written < 0) { + return KZT_LAZY_DIAGNOSTIC_FORMAT_ERROR; + } + if ((size_t)written >= buffer_size) { + return KZT_LAZY_DIAGNOSTIC_FORMAT_TRUNCATED; + } + + return KZT_LAZY_DIAGNOSTIC_FORMAT_OK; +} + +int kzt_lazy_diagnostics_stderr_sink(const char *line, + size_t line_length, + void *opaque) +{ + FILE *stream = opaque ? (FILE *)opaque : stderr; + + if (!line || !stream) { + return -1; + } + if (fwrite(line, 1, line_length, stream) != line_length) { + return -1; + } + if (fputc('\n', stream) == EOF) { + return -1; + } + return fflush(stream); +} + +int kzt_lazy_diagnostics_emit( + const kzt_lazy_diagnostic_input_t *input, + kzt_lazy_diagnostic_emit_result_t *result) +{ + if (!input || !result) { + return -1; + } + + memset(result, 0, sizeof(*result)); + result->status = KZT_LAZY_DIAGNOSTIC_EMIT_DISABLED; + result->format_status = KZT_LAZY_DIAGNOSTIC_FORMAT_ERROR; + if (!input->enabled) { + return 0; + } + if (kzt_lazy_diagnostic_record_build(input->pending, + input->binding_result, + &result->record) != 0) { + result->status = KZT_LAZY_DIAGNOSTIC_EMIT_FORMAT_FAILED; + return 0; + } + result->record_present = 1; + result->format_status = kzt_lazy_diagnostic_format( + &result->record, input->buffer, input->buffer_size); + if (result->format_status == KZT_LAZY_DIAGNOSTIC_FORMAT_TRUNCATED) { + result->status = KZT_LAZY_DIAGNOSTIC_EMIT_FORMAT_TRUNCATED; + return 0; + } + if (result->format_status != KZT_LAZY_DIAGNOSTIC_FORMAT_OK) { + result->status = KZT_LAZY_DIAGNOSTIC_EMIT_FORMAT_FAILED; + return 0; + } + if (!input->sink) { + result->status = KZT_LAZY_DIAGNOSTIC_EMIT_SINK_FAILED; + result->sink_status = -1; + return 0; + } + + result->sink_status = input->sink(input->buffer, strlen(input->buffer), + input->sink_opaque); + if (result->sink_status != 0) { + result->status = KZT_LAZY_DIAGNOSTIC_EMIT_SINK_FAILED; + return 0; + } + + result->status = KZT_LAZY_DIAGNOSTIC_EMIT_EMITTED; + return 0; +} + +int kzt_lazy_diagnostics_emit_production( + const kzt_lazy_binding_pending_t *pending, + const kzt_lazy_binding_result_t *binding_result, + kzt_lazy_diagnostic_emit_result_t *result) +{ + char buffer[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; + kzt_lazy_diagnostic_input_t input; + + memset(&input, 0, sizeof(input)); + input.enabled = 1; + input.pending = pending; + input.binding_result = binding_result; + input.buffer = buffer; + input.buffer_size = sizeof(buffer); + input.sink = kzt_lazy_diagnostics_stderr_sink; + return kzt_lazy_diagnostics_emit(&input, result); +} diff --git a/target/i386/latx/context/kzt_observation_adapter.c b/target/i386/latx/context/kzt_observation_adapter.c new file mode 100644 index 00000000000..da9b78e9e7f --- /dev/null +++ b/target/i386/latx/context/kzt_observation_adapter.c @@ -0,0 +1,371 @@ +#include "kzt_observation_adapter.h" + +#include + +#include "kzt_guest_library_binding.h" + +static kzt_observation_adapter_result_t kzt_adapter_result_from_registry( + kzt_guest_registry_result_t registry_result) +{ + switch (registry_result) { + case KZT_GUEST_REGISTRY_ADDED: + return KZT_OBSERVATION_ADAPTER_ADDED; + case KZT_GUEST_REGISTRY_UNCHANGED: + return KZT_OBSERVATION_ADAPTER_UNCHANGED; + case KZT_GUEST_REGISTRY_UPDATED: + return KZT_OBSERVATION_ADAPTER_UPDATED; + case KZT_GUEST_REGISTRY_CONFLICT: + return KZT_OBSERVATION_ADAPTER_CONFLICT; + case KZT_GUEST_REGISTRY_DISABLED: + case KZT_GUEST_REGISTRY_ERROR: + return KZT_OBSERVATION_ADAPTER_REGISTRY_FAILED; + case KZT_GUEST_REGISTRY_RESULT_COUNT: + break; + } + + return KZT_OBSERVATION_ADAPTER_REGISTRY_FAILED; +} + +static int kzt_adapter_registry_result_allows_dynamic_parse( + kzt_guest_registry_result_t registry_result) +{ + return registry_result == KZT_GUEST_REGISTRY_ADDED || + registry_result == KZT_GUEST_REGISTRY_UNCHANGED || + registry_result == KZT_GUEST_REGISTRY_UPDATED; +} + +static void kzt_adapter_note_dynamic_failure( + const kzt_observation_adapter_request_t *request, + kzt_guest_registry_result_t result, + kzt_observation_adapter_dynamic_diagnostic_t *dynamic_diagnostic) +{ + kzt_guest_registry_observation_diagnostic_t *registry_diagnostic = NULL; + + if (dynamic_diagnostic) { + registry_diagnostic = &dynamic_diagnostic->registry; + } + + (void)kzt_guest_registry_note_diagnostic( + request ? request->registry : NULL, result, + request ? request->link_map_addr : 0, registry_diagnostic); +} + +static void kzt_adapter_compare_dynamic_views( + const kzt_observation_adapter_request_t *request, + const kzt_guest_dynamic_parse_result_t *parse_result, + unsigned long generation, + kzt_observation_adapter_dynamic_diagnostic_t *dynamic_diagnostic) +{ + kzt_guest_dynamic_view_t existing_view = { 0 }; + kzt_guest_dynamic_parse_result_t existing_result = { 0 }; + kzt_guest_dynamic_diagnostic_report_t report; + kzt_guest_field_status_t existing_status = KZT_GUEST_FIELD_NOT_PARSED; + unsigned long existing_generation = 0; + + if (!request || !request->diagnostics_enabled || !parse_result || + !dynamic_diagnostic || generation == 0 || + kzt_guest_registry_find_dynamic_view( + request->registry, request->link_map_addr, &existing_view, + &existing_status, &existing_generation) != 0 || + existing_status != KZT_GUEST_FIELD_OK || + existing_generation != generation) { + return; + } + + existing_result.status = existing_view.status; + existing_result.error = KZT_GUEST_DYNAMIC_ERROR_NONE; + existing_result.entry_count = existing_view.entry_count; + existing_result.scan_limit = existing_view.scan_limit; + existing_result.unknown_tag_count = existing_view.unknown_tag_count; + existing_result.first_unknown_tag = existing_view.first_unknown_tag; + existing_result.first_unknown_tag_index = + existing_view.first_unknown_tag_index; + existing_result.view = existing_view; + if (kzt_guest_dynamic_diagnostics_compare(&existing_result, parse_result, + &report) != 0 || + kzt_guest_dynamic_diagnostics_summarize( + &report, request->link_map_addr, generation, + &dynamic_diagnostic->comparison) != 0) { + return; + } + + dynamic_diagnostic->comparison_attempted = 1; +} + +static int kzt_adapter_reuse_complete_dynamic_view( + const kzt_observation_adapter_request_t *request, + const kzt_guest_object_observation_t *observation, + kzt_guest_registry_result_t registry_result, + unsigned long generation, + kzt_observation_adapter_dynamic_diagnostic_t *dynamic_diagnostic) +{ + kzt_guest_dynamic_view_t view = { 0 }; + kzt_guest_field_status_t status = KZT_GUEST_FIELD_NOT_PARSED; + unsigned long existing_generation = 0; + + if (!request || !observation || request->library_bindings || + request->dynamic_diagnostics_force_compare || + registry_result != KZT_GUEST_REGISTRY_UNCHANGED || + generation == 0 || + kzt_guest_registry_find_dynamic_view( + request->registry, observation->link_map_addr, &view, &status, + &existing_generation) != 0 || + status != KZT_GUEST_FIELD_OK || + existing_generation != generation || + view.status != KZT_GUEST_DYNAMIC_COMPLETE || + view.dynamic_addr != observation->dynamic_addr.value || + view.load_bias != observation->load_bias.value) { + return 0; + } + + if (dynamic_diagnostic) { + dynamic_diagnostic->cache_hit = 1; + dynamic_diagnostic->parse_return = 0; + dynamic_diagnostic->dynamic_addr = view.dynamic_addr; + dynamic_diagnostic->status = view.status; + dynamic_diagnostic->error = KZT_GUEST_DYNAMIC_ERROR_NONE; + dynamic_diagnostic->entry_count = view.entry_count; + dynamic_diagnostic->commit_result = KZT_GUEST_REGISTRY_UNCHANGED; + } + return 1; +} + +static void kzt_observe_guest_dynamic_view( + const kzt_observation_adapter_request_t *request, + const kzt_guest_object_observation_t *observation, + kzt_guest_registry_result_t registry_result, + unsigned long generation, + kzt_observation_adapter_dynamic_diagnostic_t *dynamic_diagnostic) +{ + kzt_guest_dynamic_parse_result_t parse_result = { 0 }; + kzt_guest_registry_result_t commit_result; + int parse_return; + + if (dynamic_diagnostic) { + memset(dynamic_diagnostic, 0, sizeof(*dynamic_diagnostic)); + dynamic_diagnostic->commit_result = KZT_GUEST_REGISTRY_RESULT_COUNT; + } + + if (!request || !observation || + !kzt_adapter_registry_result_allows_dynamic_parse(registry_result)) { + return; + } + + if (observation->dynamic_addr.status != KZT_GUEST_FIELD_OK || + observation->dynamic_addr.value == 0 || + observation->load_bias.status != KZT_GUEST_FIELD_OK) { + return; + } + + if (kzt_adapter_reuse_complete_dynamic_view( + request, observation, registry_result, generation, + dynamic_diagnostic)) { + return; + } + + if (dynamic_diagnostic) { + dynamic_diagnostic->attempted = 1; + dynamic_diagnostic->dynamic_addr = observation->dynamic_addr.value; + } + + parse_return = kzt_guest_dynamic_parse(observation->dynamic_addr.value, + observation->load_bias.value, + request->reader_ops, + &parse_result); + if (dynamic_diagnostic) { + dynamic_diagnostic->parse_return = parse_return; + dynamic_diagnostic->status = parse_result.status; + dynamic_diagnostic->error = parse_result.error; + dynamic_diagnostic->entry_count = parse_result.entry_count; + dynamic_diagnostic->read_error_addr = parse_result.read_error_addr; + } + + kzt_adapter_compare_dynamic_views(request, &parse_result, generation, + dynamic_diagnostic); + + if (parse_return != 0) { + kzt_adapter_note_dynamic_failure( + request, KZT_GUEST_REGISTRY_ERROR, dynamic_diagnostic); + kzt_guest_dynamic_parse_result_clear(&parse_result); + return; + } + + if (parse_result.status != KZT_GUEST_DYNAMIC_COMPLETE) { + kzt_adapter_note_dynamic_failure( + request, KZT_GUEST_REGISTRY_ERROR, dynamic_diagnostic); + } + + commit_result = kzt_guest_registry_commit_dynamic_view( + request->registry, observation->link_map_addr, generation, + &parse_result.view); + if (dynamic_diagnostic) { + dynamic_diagnostic->commit_attempted = 1; + dynamic_diagnostic->commit_result = commit_result; + } + if (commit_result == KZT_GUEST_REGISTRY_DISABLED || + commit_result == KZT_GUEST_REGISTRY_ERROR) { + kzt_adapter_note_dynamic_failure(request, commit_result, + dynamic_diagnostic); + } + + kzt_guest_dynamic_parse_result_clear(&parse_result); +} + +static kzt_observation_adapter_result_t kzt_observe_guest_object( + const kzt_observation_adapter_request_t *request, + kzt_guest_registry_observation_diagnostic_t *registry_diagnostic, + kzt_observation_adapter_dynamic_diagnostic_t *dynamic_diagnostic) +{ + kzt_guest_object_observation_t observation; + kzt_guest_registry_result_t registry_result; + + if (!request || !request->enabled) { + if (request) { + kzt_guest_registry_note_diagnostic( + request->registry, KZT_GUEST_REGISTRY_DISABLED, + request->link_map_addr, registry_diagnostic); + } + return KZT_OBSERVATION_ADAPTER_DISABLED; + } + + if (kzt_guest_link_map_read_observation(request->link_map_addr, + request->reader_ops, + &observation) != 0) { + kzt_guest_registry_note_diagnostic( + request->registry, KZT_GUEST_REGISTRY_ERROR, + request->link_map_addr, registry_diagnostic); + return KZT_OBSERVATION_ADAPTER_READER_FAILED; + } + + if (observation.link_map_addr == 0) { + kzt_guest_link_map_observation_clear(&observation); + kzt_guest_registry_note_diagnostic( + request->registry, KZT_GUEST_REGISTRY_ERROR, + request->link_map_addr, registry_diagnostic); + return KZT_OBSERVATION_ADAPTER_READER_FAILED; + } + + if (request->namespace_id_present && + observation.namespace_id.status == KZT_GUEST_FIELD_UNKNOWN) { + observation.namespace_id.value = request->namespace_id; + observation.namespace_id.status = KZT_GUEST_FIELD_OK; + } + if (request->map_range_present && request->map_start < request->map_end && + observation.map_start.status == KZT_GUEST_FIELD_UNKNOWN && + observation.map_end.status == KZT_GUEST_FIELD_UNKNOWN) { + observation.map_start.value = request->map_start; + observation.map_start.status = KZT_GUEST_FIELD_OK; + observation.map_end.value = request->map_end; + observation.map_end.status = KZT_GUEST_FIELD_OK; + } + + registry_result = kzt_guest_registry_observe_with_diagnostic( + request->registry, &observation, registry_diagnostic); + if (kzt_adapter_registry_result_allows_dynamic_parse(registry_result) && + registry_diagnostic && registry_diagnostic->generation && + observation.namespace_id.status == KZT_GUEST_FIELD_OK && + observation.namespace_id.value == 0) { + kzt_guest_library_binding_key_t key = { + .link_map_addr = observation.link_map_addr, + .generation = registry_diagnostic->generation, + .namespace_id = observation.namespace_id.value, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + kzt_guest_library_binding_result_t binding_result = + kzt_guest_library_note_observation( + request->library_bindings, &key); + if (binding_result == KZT_GUEST_LIBRARY_BINDING_CANCELLED) { + /* A loader pair was canceled by unload before this delayed + * observation arrived. Retire this generation so address reuse + * receives fresh evidence on retry. */ + (void)kzt_guest_registry_retire( + request->registry, key.link_map_addr, key.generation); + } + } + kzt_observe_guest_dynamic_view(request, &observation, registry_result, + registry_diagnostic + ? registry_diagnostic->generation + : 0, + dynamic_diagnostic); + kzt_guest_link_map_observation_clear(&observation); + + return kzt_adapter_result_from_registry(registry_result); +} + +static void kzt_observation_adapter_emit_diagnostic( + const kzt_observation_adapter_request_t *request, + kzt_observation_adapter_result_t result, + const kzt_guest_registry_observation_diagnostic_t *registry_diagnostic, + const kzt_observation_adapter_dynamic_diagnostic_t *dynamic_diagnostic) +{ + kzt_observation_adapter_diagnostic_t diagnostic = { 0 }; + + if (!request || !request->diagnostics_enabled || !request->diagnostic) { + return; + } + + diagnostic.enabled = 1; + diagnostic.result = result; + diagnostic.link_map_addr = request->link_map_addr; + diagnostic.emitted = 0; + if (registry_diagnostic) { + diagnostic.registry = *registry_diagnostic; + diagnostic.emitted = registry_diagnostic->enabled && + registry_diagnostic->emitted; + } + if (dynamic_diagnostic) { + diagnostic.dynamic = *dynamic_diagnostic; + if (dynamic_diagnostic->registry.enabled && + dynamic_diagnostic->registry.emitted) { + diagnostic.emitted = 1; + } + } + + if (!diagnostic.emitted) { + return; + } + + request->diagnostic(&diagnostic, request->diagnostic_opaque); +} + +int kzt_observe_guest_object_from_callback( + const kzt_observation_adapter_request_t *request, + kzt_observation_adapter_result_t *result) +{ + kzt_observation_adapter_result_t observation_result; + kzt_guest_registry_observation_diagnostic_t registry_diagnostic = { 0 }; + kzt_observation_adapter_dynamic_diagnostic_t dynamic_diagnostic = { + .commit_result = KZT_GUEST_REGISTRY_RESULT_COUNT, + }; + kzt_guest_library_callback_access_t callback_access = { 0 }; + int legacy_ret = 0; + + if (request && request->library_bindings && + kzt_guest_library_callback_access_begin_scoped( + request->library_bindings, request->link_map_addr, + request->loader_scope, + &callback_access) != 0) { + /* Unload won the address gate. No reader, parser, diagnostic, or + * legacy loader flow may touch this guest object after this point. */ + if (result) *result = KZT_OBSERVATION_ADAPTER_DISABLED; + return 0; + } + + observation_result = kzt_observe_guest_object(request, + ®istry_diagnostic, + &dynamic_diagnostic); + if (result) { + *result = observation_result; + } + + if (request && request->legacy_flow) { + legacy_ret = request->legacy_flow(request->link_map_addr, + request->legacy_opaque); + } + + kzt_observation_adapter_emit_diagnostic(request, observation_result, + ®istry_diagnostic, + &dynamic_diagnostic); + kzt_guest_library_callback_access_end(&callback_access); + return legacy_ret; +} diff --git a/target/i386/latx/context/kzt_owner_resolver.c b/target/i386/latx/context/kzt_owner_resolver.c new file mode 100644 index 00000000000..e23b45d833c --- /dev/null +++ b/target/i386/latx/context/kzt_owner_resolver.c @@ -0,0 +1,304 @@ +#include "kzt_owner_resolver.h" + +#include +#include + +typedef enum kzt_owner_lookup_status { + KZT_OWNER_LOOKUP_RESOLVED = 0, + KZT_OWNER_LOOKUP_NOT_FOUND, + KZT_OWNER_LOOKUP_AMBIGUOUS, +} kzt_owner_lookup_status_t; + +void kzt_owner_resolver_init(kzt_owner_resolution_t *resolution) +{ + if (!resolution) { + return; + } + + memset(resolution, 0, sizeof(*resolution)); + resolution->status = KZT_OWNER_RESOLVER_INVALID_ARGUMENT; + resolution->owner_match = KZT_PATCH_OWNER_UNKNOWN; +} + +static int kzt_owner_field_ok(kzt_guest_field_status_t status) +{ + return status == KZT_GUEST_FIELD_OK; +} + +static int kzt_owner_string_has_value(kzt_guest_field_status_t status) +{ + return status == KZT_GUEST_FIELD_OK || + status == KZT_GUEST_FIELD_TRUNCATED; +} + +static const char *kzt_owner_snapshot_string( + const kzt_guest_string_field_t *field) +{ + if (!field || !kzt_owner_string_has_value(field->status)) { + return NULL; + } + + return field->value; +} + +static void kzt_owner_copy_text(char *dst, size_t dst_size, + const char *src) +{ + if (!dst || dst_size == 0) { + return; + } + + if (!src) { + dst[0] = '\0'; + return; + } + + snprintf(dst, dst_size, "%s", src); +} + +static int kzt_owner_snapshot_has_valid_range( + const kzt_guest_object_snapshot_t *snapshot) +{ + if (!snapshot) { + return 0; + } + if (!kzt_owner_field_ok(snapshot->map_start.status) || + !kzt_owner_field_ok(snapshot->map_end.status)) { + return 0; + } + + return snapshot->map_start.value < snapshot->map_end.value; +} + +static int kzt_owner_snapshot_contains( + const kzt_guest_object_snapshot_t *snapshot, + uintptr_t address) +{ + if (!kzt_owner_snapshot_has_valid_range(snapshot)) { + return 0; + } + + return address >= snapshot->map_start.value && + address < snapshot->map_end.value; +} + +static void kzt_owner_ref_from_snapshot( + const kzt_guest_object_snapshot_t *snapshot, + kzt_owner_resolver_text_t *text, + kzt_patch_object_ref_t *ref) +{ + const char *soname; + const char *path; + + memset(ref, 0, sizeof(*ref)); + if (!snapshot || !text) { + return; + } + + soname = kzt_owner_snapshot_string(&snapshot->soname); + path = kzt_owner_snapshot_string(&snapshot->path); + kzt_owner_copy_text(text->soname, sizeof(text->soname), soname); + kzt_owner_copy_text(text->path, sizeof(text->path), path); + + ref->known = 1; + ref->link_map_addr = snapshot->link_map_addr; + ref->map_start = snapshot->map_start.value; + ref->map_end = snapshot->map_end.value; + ref->generation = snapshot->generation; + ref->soname = text->soname[0] ? text->soname : NULL; + ref->path = text->path[0] ? text->path : NULL; +} + +static kzt_owner_lookup_status_t kzt_owner_find_by_address( + const kzt_guest_registry_dump_t *dump, + uintptr_t address, + kzt_owner_resolver_text_t *text, + kzt_patch_object_ref_t *ref, + size_t *match_count) +{ + const kzt_guest_object_snapshot_t *match = NULL; + size_t i; + size_t count = 0; + + memset(ref, 0, sizeof(*ref)); + if (text) { + memset(text, 0, sizeof(*text)); + } + if (match_count) { + *match_count = 0; + } + + if (!dump || !ref || address == 0) { + return KZT_OWNER_LOOKUP_NOT_FOUND; + } + + for (i = 0; i < dump->count; ++i) { + if (dump->objects[i].state == KZT_GUEST_OBJECT_UNLOADING || + dump->objects[i].state == KZT_GUEST_OBJECT_DEAD) { + continue; + } + if (!kzt_owner_snapshot_contains(&dump->objects[i], address)) { + continue; + } + + ++count; + if (!match) { + match = &dump->objects[i]; + } + } + + if (match_count) { + *match_count = count; + } + if (count == 0) { + return KZT_OWNER_LOOKUP_NOT_FOUND; + } + if (count > 1) { + return KZT_OWNER_LOOKUP_AMBIGUOUS; + } + + kzt_owner_ref_from_snapshot(match, text, ref); + return KZT_OWNER_LOOKUP_RESOLVED; +} + +kzt_patch_owner_match_t kzt_owner_resolver_match_refs( + const kzt_patch_object_ref_t *current_owner, + const kzt_patch_object_ref_t *expected_owner) +{ + if (!current_owner || !expected_owner || + !current_owner->known || !expected_owner->known || + current_owner->link_map_addr == 0 || + expected_owner->link_map_addr == 0 || + current_owner->generation == 0 || expected_owner->generation == 0) { + return KZT_PATCH_OWNER_UNKNOWN; + } + + if (current_owner->link_map_addr == expected_owner->link_map_addr && + current_owner->generation == expected_owner->generation) { + return KZT_PATCH_OWNER_MATCH; + } + + return KZT_PATCH_OWNER_MISMATCH; +} + +static kzt_owner_resolver_status_t kzt_owner_lookup_status_to_current( + kzt_owner_lookup_status_t status) +{ + switch (status) { + case KZT_OWNER_LOOKUP_RESOLVED: + return KZT_OWNER_RESOLVER_RESOLVED; + case KZT_OWNER_LOOKUP_AMBIGUOUS: + return KZT_OWNER_RESOLVER_CURRENT_AMBIGUOUS; + case KZT_OWNER_LOOKUP_NOT_FOUND: + return KZT_OWNER_RESOLVER_CURRENT_NOT_FOUND; + } + + return KZT_OWNER_RESOLVER_CURRENT_NOT_FOUND; +} + +static kzt_owner_resolver_status_t kzt_owner_lookup_status_to_expected( + kzt_owner_lookup_status_t status) +{ + switch (status) { + case KZT_OWNER_LOOKUP_RESOLVED: + return KZT_OWNER_RESOLVER_RESOLVED; + case KZT_OWNER_LOOKUP_AMBIGUOUS: + return KZT_OWNER_RESOLVER_EXPECTED_AMBIGUOUS; + case KZT_OWNER_LOOKUP_NOT_FOUND: + return KZT_OWNER_RESOLVER_EXPECTED_NOT_FOUND; + } + + return KZT_OWNER_RESOLVER_EXPECTED_NOT_FOUND; +} + +int kzt_owner_resolver_resolve_current( + kzt_guest_registry_t *registry, + uintptr_t current_address, + uintptr_t expected_address, + kzt_owner_resolution_t *resolution) +{ + kzt_guest_registry_dump_t dump = { 0 }; + kzt_owner_lookup_status_t current_status; + kzt_owner_lookup_status_t expected_status; + + if (!resolution) { + return -1; + } + + kzt_owner_resolver_init(resolution); + resolution->status = KZT_OWNER_RESOLVER_RESOLVED; + + if (!registry) { + resolution->status = KZT_OWNER_RESOLVER_REGISTRY_UNAVAILABLE; + return 0; + } + if (current_address == 0) { + resolution->status = KZT_OWNER_RESOLVER_CURRENT_ADDRESS_MISSING; + return 0; + } + if (expected_address == 0) { + resolution->status = KZT_OWNER_RESOLVER_EXPECTED_ADDRESS_MISSING; + return 0; + } + if (kzt_guest_registry_dump_snapshot(registry, &dump) != 0) { + resolution->status = KZT_OWNER_RESOLVER_REGISTRY_UNAVAILABLE; + return 0; + } + + current_status = kzt_owner_find_by_address( + &dump, current_address, &resolution->current_text, + &resolution->current_owner, &resolution->current_match_count); + expected_status = kzt_owner_find_by_address( + &dump, expected_address, &resolution->expected_text, + &resolution->expected_owner, &resolution->expected_match_count); + + if (current_status != KZT_OWNER_LOOKUP_RESOLVED) { + resolution->status = kzt_owner_lookup_status_to_current( + current_status); + goto out; + } + if (expected_status != KZT_OWNER_LOOKUP_RESOLVED) { + resolution->status = kzt_owner_lookup_status_to_expected( + expected_status); + goto out; + } + + resolution->owner_match = kzt_owner_resolver_match_refs( + &resolution->current_owner, &resolution->expected_owner); + if (resolution->owner_match == KZT_PATCH_OWNER_UNKNOWN) { + resolution->status = KZT_OWNER_RESOLVER_GENERATION_UNKNOWN; + } + +out: + kzt_guest_registry_dump_free(&dump); + return 0; +} + +const char *kzt_owner_resolver_status_name( + kzt_owner_resolver_status_t status) +{ + switch (status) { + case KZT_OWNER_RESOLVER_RESOLVED: + return "RESOLVED"; + case KZT_OWNER_RESOLVER_INVALID_ARGUMENT: + return "INVALID_ARGUMENT"; + case KZT_OWNER_RESOLVER_REGISTRY_UNAVAILABLE: + return "REGISTRY_UNAVAILABLE"; + case KZT_OWNER_RESOLVER_CURRENT_ADDRESS_MISSING: + return "CURRENT_ADDRESS_MISSING"; + case KZT_OWNER_RESOLVER_EXPECTED_ADDRESS_MISSING: + return "EXPECTED_ADDRESS_MISSING"; + case KZT_OWNER_RESOLVER_CURRENT_NOT_FOUND: + return "CURRENT_NOT_FOUND"; + case KZT_OWNER_RESOLVER_EXPECTED_NOT_FOUND: + return "EXPECTED_NOT_FOUND"; + case KZT_OWNER_RESOLVER_CURRENT_AMBIGUOUS: + return "CURRENT_AMBIGUOUS"; + case KZT_OWNER_RESOLVER_EXPECTED_AMBIGUOUS: + return "EXPECTED_AMBIGUOUS"; + case KZT_OWNER_RESOLVER_GENERATION_UNKNOWN: + return "GENERATION_UNKNOWN"; + } + + return "UNKNOWN"; +} diff --git a/target/i386/latx/context/kzt_patch_planner.c b/target/i386/latx/context/kzt_patch_planner.c new file mode 100644 index 00000000000..2995f2f91d4 --- /dev/null +++ b/target/i386/latx/context/kzt_patch_planner.c @@ -0,0 +1,397 @@ +#include "kzt_patch_planner.h" + +#include +#include + +static int kzt_patch_string_is_empty(const char *value) +{ + return !value || value[0] == '\0'; +} + +static const char *kzt_patch_string_or_none(const char *value) +{ + return kzt_patch_string_is_empty(value) ? "(none)" : value; +} + +static int kzt_patch_relocation_is_supported( + kzt_patch_relocation_type_t reloc_type) +{ + return reloc_type == KZT_PATCH_RELOCATION_JUMP_SLOT || + reloc_type == KZT_PATCH_RELOCATION_GLOB_DAT; +} + +static void kzt_patch_decision_copy_candidate( + const kzt_patch_candidate_t *candidate, + kzt_patch_decision_t *decision) +{ + decision->source = candidate->source; + decision->dynamic_addr = candidate->dynamic_addr; + decision->load_bias = candidate->load_bias; + decision->dynamic_view_generation = candidate->dynamic_view_generation; + decision->dynamic_view_available = candidate->dynamic_view_available; + decision->table_kind = candidate->table_kind; + decision->entry_index = candidate->entry_index; + decision->entry_addr = candidate->entry_addr; + decision->reloc_type = candidate->reloc_type; + decision->slot_addr = candidate->slot_addr; + decision->slot_current_value_present = + candidate->slot_current_value_present; + decision->slot_current_value = candidate->slot_current_value; + decision->lazy_binding_deferred = candidate->lazy_binding_deferred; + decision->symbol_index = candidate->symbol_index; + decision->symbol_name = candidate->symbol_name; + decision->version_evidence = candidate->version_evidence; + decision->version = candidate->version; + decision->current_owner = candidate->current_owner; + decision->owner_match = candidate->owner_match; + decision->wrapper_match = candidate->wrapper_match; + decision->wrapper_name = candidate->wrapper_name; + decision->wrapper_version_evidence = + candidate->wrapper_version_evidence; + decision->wrapper_symbol_version = candidate->wrapper_symbol_version; + decision->bridge_target = candidate->bridge_target; +} + +static int kzt_patch_decision_set(kzt_patch_decision_t *decision, + kzt_patch_decision_kind_t kind, + kzt_patch_reason_t reason) +{ + decision->kind = kind; + decision->reason = reason; + decision->allow_native_bridge = kind == KZT_PATCH_DECISION_APPROVED; + return 0; +} + +const char *kzt_patch_decision_kind_name(kzt_patch_decision_kind_t kind) +{ + switch (kind) { + case KZT_PATCH_DECISION_APPROVED: + return "APPROVED"; + case KZT_PATCH_DECISION_REJECTED: + return "REJECTED"; + case KZT_PATCH_DECISION_UNSUPPORTED: + return "UNSUPPORTED"; + case KZT_PATCH_DECISION_DEFERRED: + return "DEFERRED"; + case KZT_PATCH_DECISION_ERROR: + return "ERROR"; + } + + return "UNKNOWN"; +} + +const char *kzt_patch_reason_name(kzt_patch_reason_t reason) +{ + switch (reason) { + case KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE: + return "APPROVED_NATIVE_BRIDGE"; + case KZT_PATCH_REASON_ERROR_INVALID_ARGUMENT: + return "ERROR_INVALID_ARGUMENT"; + case KZT_PATCH_REASON_INPUT_UNSUPPORTED_RELOCATION: + return "INPUT_UNSUPPORTED_RELOCATION"; + case KZT_PATCH_REASON_INPUT_MALFORMED_TABLE: + return "INPUT_MALFORMED_TABLE"; + case KZT_PATCH_REASON_INPUT_MALFORMED_SLOT: + return "INPUT_MALFORMED_SLOT"; + case KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_NAME: + return "INPUT_MALFORMED_SYMBOL_NAME"; + case KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_VERSION: + return "INPUT_MALFORMED_SYMBOL_VERSION"; + case KZT_PATCH_REASON_INPUT_UNAVAILABLE_DYNAMIC_VIEW: + return "INPUT_UNAVAILABLE_DYNAMIC_VIEW"; + case KZT_PATCH_REASON_INPUT_UNAVAILABLE_CURRENT_GOT: + return "INPUT_UNAVAILABLE_CURRENT_GOT"; + case KZT_PATCH_REASON_INPUT_UNAVAILABLE_OWNER: + return "INPUT_UNAVAILABLE_OWNER"; + case KZT_PATCH_REASON_INPUT_UNAVAILABLE_WRAPPER_MANIFEST: + return "INPUT_UNAVAILABLE_WRAPPER_MANIFEST"; + case KZT_PATCH_REASON_INPUT_UNAVAILABLE_BRIDGE_TARGET: + return "INPUT_UNAVAILABLE_BRIDGE_TARGET"; + case KZT_PATCH_REASON_POLICY_KEEP_GUEST: + return "POLICY_KEEP_GUEST"; + case KZT_PATCH_REASON_POLICY_OWNER_MISMATCH: + return "POLICY_OWNER_MISMATCH"; + case KZT_PATCH_REASON_POLICY_NO_WRAPPER: + return "POLICY_NO_WRAPPER"; + case KZT_PATCH_REASON_POLICY_WRAPPER_SYMBOL_ONLY: + return "POLICY_WRAPPER_SYMBOL_ONLY"; + case KZT_PATCH_REASON_POLICY_VERSION_MISMATCH: + return "POLICY_VERSION_MISMATCH"; + case KZT_PATCH_REASON_DEFERRED_LAZY_BINDING: + return "DEFERRED_LAZY_BINDING"; + } + + return "UNKNOWN"; +} + +const char *kzt_patch_table_kind_name(kzt_patch_table_kind_t table_kind) +{ + switch (table_kind) { + case KZT_PATCH_TABLE_UNKNOWN: + return "UNKNOWN"; + case KZT_PATCH_TABLE_RELA: + return "RELA"; + case KZT_PATCH_TABLE_REL: + return "REL"; + case KZT_PATCH_TABLE_PLT_RELA: + return "PLT_RELA"; + case KZT_PATCH_TABLE_PLT_REL: + return "PLT_REL"; + case KZT_PATCH_TABLE_OTHER: + return "OTHER"; + } + + return "UNKNOWN"; +} + +const char *kzt_patch_relocation_type_name( + kzt_patch_relocation_type_t reloc_type) +{ + switch (reloc_type) { + case KZT_PATCH_RELOCATION_UNKNOWN: + return "UNKNOWN"; + case KZT_PATCH_RELOCATION_JUMP_SLOT: + return "JUMP_SLOT"; + case KZT_PATCH_RELOCATION_GLOB_DAT: + return "GLOB_DAT"; + case KZT_PATCH_RELOCATION_RELATIVE: + return "RELATIVE"; + case KZT_PATCH_RELOCATION_COPY: + return "COPY"; + case KZT_PATCH_RELOCATION_IRELATIVE: + return "IRELATIVE"; + case KZT_PATCH_RELOCATION_OTHER: + return "OTHER"; + } + + return "UNKNOWN"; +} + +const char *kzt_patch_owner_match_name(kzt_patch_owner_match_t match) +{ + switch (match) { + case KZT_PATCH_OWNER_UNKNOWN: + return "UNKNOWN"; + case KZT_PATCH_OWNER_MATCH: + return "MATCH"; + case KZT_PATCH_OWNER_MISMATCH: + return "MISMATCH"; + } + + return "UNKNOWN"; +} + +const char *kzt_patch_wrapper_match_name(kzt_patch_wrapper_match_t match) +{ + switch (match) { + case KZT_PATCH_WRAPPER_NO_MANIFEST: + return "NO_MANIFEST"; + case KZT_PATCH_WRAPPER_NO_WRAPPER: + return "NO_WRAPPER"; + case KZT_PATCH_WRAPPER_SYMBOL_ONLY: + return "SYMBOL_ONLY"; + case KZT_PATCH_WRAPPER_VERSION_MISMATCH: + return "VERSION_MISMATCH"; + case KZT_PATCH_WRAPPER_VERSION_MATCH: + return "VERSION_MATCH"; + case KZT_PATCH_WRAPPER_UNVERSIONED_MATCH: + return "UNVERSIONED_MATCH"; + } + + return "UNKNOWN"; +} + +const char *kzt_symbol_version_evidence_name( + kzt_symbol_version_evidence_t evidence) +{ + switch (evidence) { + case KZT_SYMBOL_VERSION_VERSIONED: + return "VERSIONED"; + case KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED: + return "CONFIRMED_UNVERSIONED"; + case KZT_SYMBOL_VERSION_UNKNOWN: + return "UNKNOWN"; + case KZT_SYMBOL_VERSION_ERROR: + return "ERROR"; + } + return "UNKNOWN"; +} + +int kzt_patch_planner_decide(const kzt_patch_candidate_t *candidate, + kzt_patch_decision_t *decision) +{ + if (!decision) { + return -1; + } + + memset(decision, 0, sizeof(*decision)); + if (!candidate) { + return kzt_patch_decision_set(decision, KZT_PATCH_DECISION_ERROR, + KZT_PATCH_REASON_ERROR_INVALID_ARGUMENT); + } + kzt_patch_decision_copy_candidate(candidate, decision); + + if (!kzt_patch_relocation_is_supported(candidate->reloc_type)) { + return kzt_patch_decision_set( + decision, KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNSUPPORTED_RELOCATION); + } + + if (!candidate->dynamic_view_available) { + return kzt_patch_decision_set( + decision, KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_DYNAMIC_VIEW); + } + + if (candidate->table_kind == KZT_PATCH_TABLE_UNKNOWN) { + return kzt_patch_decision_set( + decision, KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE); + } + + if (candidate->slot_addr == 0) { + return kzt_patch_decision_set( + decision, KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_MALFORMED_SLOT); + } + + if (kzt_patch_string_is_empty(candidate->symbol_name)) { + return kzt_patch_decision_set( + decision, KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_NAME); + } + + if (!kzt_symbol_version_evidence_valid(candidate->version_evidence, + candidate->version)) { + return kzt_patch_decision_set( + decision, KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_VERSION); + } + + if (!candidate->slot_current_value_present) { + return kzt_patch_decision_set( + decision, KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_CURRENT_GOT); + } + + if (candidate->lazy_binding_deferred) { + return kzt_patch_decision_set(decision, KZT_PATCH_DECISION_DEFERRED, + KZT_PATCH_REASON_DEFERRED_LAZY_BINDING); + } + + if (!candidate->current_owner.known || + candidate->owner_match == KZT_PATCH_OWNER_UNKNOWN) { + return kzt_patch_decision_set( + decision, KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_OWNER); + } + + if (candidate->owner_match == KZT_PATCH_OWNER_MISMATCH) { + return kzt_patch_decision_set(decision, KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_OWNER_MISMATCH); + } + + switch (candidate->wrapper_match) { + case KZT_PATCH_WRAPPER_NO_MANIFEST: + return kzt_patch_decision_set( + decision, KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_WRAPPER_MANIFEST); + case KZT_PATCH_WRAPPER_NO_WRAPPER: + return kzt_patch_decision_set(decision, KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_NO_WRAPPER); + case KZT_PATCH_WRAPPER_SYMBOL_ONLY: + return kzt_patch_decision_set( + decision, KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_WRAPPER_SYMBOL_ONLY); + case KZT_PATCH_WRAPPER_VERSION_MISMATCH: + return kzt_patch_decision_set(decision, KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_VERSION_MISMATCH); + case KZT_PATCH_WRAPPER_VERSION_MATCH: + case KZT_PATCH_WRAPPER_UNVERSIONED_MATCH: + break; + } + + if (!kzt_symbol_version_evidence_matches( + candidate->version_evidence, candidate->version, + candidate->wrapper_version_evidence, + candidate->wrapper_symbol_version) || + (candidate->version_evidence == KZT_SYMBOL_VERSION_VERSIONED && + candidate->wrapper_match != KZT_PATCH_WRAPPER_VERSION_MATCH) || + (candidate->version_evidence == + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED && + candidate->wrapper_match != + KZT_PATCH_WRAPPER_UNVERSIONED_MATCH)) { + return kzt_patch_decision_set(decision, KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_VERSION_MISMATCH); + } + + if (candidate->bridge_target == 0) { + return kzt_patch_decision_set( + decision, KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_BRIDGE_TARGET); + } + + return kzt_patch_decision_set(decision, KZT_PATCH_DECISION_APPROVED, + KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE); +} + +int kzt_patch_decision_format_summary(const kzt_patch_decision_t *decision, + char *buffer, + size_t buffer_size) +{ + int written; + + if (!decision || !buffer || buffer_size == 0) { + return -1; + } + + written = snprintf( + buffer, buffer_size, + "kzt_patch_decision kind=%s reason=%s allow=%d " + "link_map=0x%lx source_generation=%lu dynamic_addr=0x%lx " + "load_bias=0x%lx dynamic_view_generation=%lu " + "dynamic_view_available=%d table=%s entry_index=%lu " + "entry_addr=0x%lx reloc=%s slot=0x%lx " + "slot_current_present=%d slot_current=0x%lx lazy_deferred=%d " + "symbol_index=%lu symbol=%s version_evidence=%s version=%s " + "current_owner=0x%lx " + "current_owner_generation=%lu owner_match=%s wrapper=%s " + "wrapper_match=%s wrapper_version_evidence=%s " + "wrapper_version=%s bridge=0x%lx", + kzt_patch_decision_kind_name(decision->kind), + kzt_patch_reason_name(decision->reason), + decision->allow_native_bridge, + (unsigned long)decision->source.link_map_addr, + decision->source.generation, + (unsigned long)decision->dynamic_addr, + (unsigned long)decision->load_bias, + decision->dynamic_view_generation, + decision->dynamic_view_available, + kzt_patch_table_kind_name(decision->table_kind), + (unsigned long)decision->entry_index, + (unsigned long)decision->entry_addr, + kzt_patch_relocation_type_name(decision->reloc_type), + (unsigned long)decision->slot_addr, + decision->slot_current_value_present, + (unsigned long)decision->slot_current_value, + decision->lazy_binding_deferred, + decision->symbol_index, + kzt_patch_string_or_none(decision->symbol_name), + kzt_symbol_version_evidence_name(decision->version_evidence), + kzt_patch_string_or_none(decision->version), + (unsigned long)decision->current_owner.link_map_addr, + decision->current_owner.generation, + kzt_patch_owner_match_name(decision->owner_match), + kzt_patch_string_or_none(decision->wrapper_name), + kzt_patch_wrapper_match_name(decision->wrapper_match), + kzt_symbol_version_evidence_name( + decision->wrapper_version_evidence), + kzt_patch_string_or_none(decision->wrapper_symbol_version), + (unsigned long)decision->bridge_target); + + if (written < 0 || (size_t)written >= buffer_size) { + return -1; + } + + return 0; +} diff --git a/target/i386/latx/context/kzt_patch_spike_guard.c b/target/i386/latx/context/kzt_patch_spike_guard.c new file mode 100644 index 00000000000..a391a3fb7db --- /dev/null +++ b/target/i386/latx/context/kzt_patch_spike_guard.c @@ -0,0 +1,388 @@ +#include "kzt_patch_spike_guard.h" + +#include + +#if defined(CONFIG_LATX_KZT) +extern int option_kzt_patch_spike; +extern int option_kzt_patch_spike_write; +extern unsigned long option_kzt_patch_spike_budget; +#endif + +static void kzt_patch_spike_outcome_set( + kzt_patch_spike_outcome_t *outcome, + kzt_patch_spike_result_t result, + kzt_patch_spike_failure_t failure, + kzt_patch_spike_action_t action, + const kzt_patch_spike_guard_t *guard) +{ + outcome->result = result; + outcome->failure = failure; + outcome->action = action; + outcome->skip_legacy_write = + action != KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY; + outcome->writes_remaining = + kzt_patch_spike_guard_budget_remaining(guard); +} + +static int kzt_patch_spike_writer_ready( + const kzt_patch_spike_writer_ops_t *writer) +{ + return writer && writer->write_slot && writer->verify_slot && + writer->rollback_slot; +} + +static void kzt_patch_spike_guard_lock(kzt_patch_spike_guard_t *guard) +{ + while (__atomic_exchange_n(&guard->transaction_gate, 1, + __ATOMIC_ACQUIRE)) { + while (__atomic_load_n(&guard->transaction_gate, __ATOMIC_RELAXED)) { + } + } +} + +static void kzt_patch_spike_guard_unlock(kzt_patch_spike_guard_t *guard) +{ + __atomic_store_n(&guard->transaction_gate, 0, __ATOMIC_RELEASE); +} + +static kzt_patch_spike_failure_t kzt_patch_spike_writer_failure( + kzt_patch_spike_writer_status_t status) +{ + switch (status) { + case KZT_PATCH_SPIKE_WRITER_READ_FAILED: + return KZT_PATCH_SPIKE_FAILURE_READ_FAILED; + case KZT_PATCH_SPIKE_WRITER_EXPECTED_MISMATCH: + return KZT_PATCH_SPIKE_FAILURE_EXPECTED_MISMATCH; + case KZT_PATCH_SPIKE_WRITER_WRITE_FAILED: + return KZT_PATCH_SPIKE_FAILURE_WRITE_FAILED; + case KZT_PATCH_SPIKE_WRITER_PERMISSION_ENABLE_FAILED: + return KZT_PATCH_SPIKE_FAILURE_PERMISSION_ENABLE_FAILED; + case KZT_PATCH_SPIKE_WRITER_PERMISSION_RESTORE_FAILED: + return KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED; + case KZT_PATCH_SPIKE_WRITER_GENERATION_MISMATCH: + return KZT_PATCH_SPIKE_FAILURE_GENERATION_MISMATCH; + case KZT_PATCH_SPIKE_WRITER_OK: + return KZT_PATCH_SPIKE_FAILURE_NONE; + } + + return KZT_PATCH_SPIKE_FAILURE_WRITE_FAILED; +} + +void kzt_patch_spike_config_from_options(kzt_patch_spike_config_t *config) +{ + if (!config) { + return; + } + +#if defined(CONFIG_LATX_KZT) + config->enabled = option_kzt_patch_spike > 0; + config->write_enabled = option_kzt_patch_spike_write > 0; + config->budget = option_kzt_patch_spike_budget; +#else + config->enabled = 0; + config->write_enabled = 0; + config->budget = 0; +#endif +} + +void kzt_patch_spike_guard_init(kzt_patch_spike_guard_t *guard, + const kzt_patch_spike_config_t *config) +{ + if (!guard) { + return; + } + + memset(guard, 0, sizeof(*guard)); + if (config) { + guard->config = *config; + } +} + +int kzt_patch_spike_guard_should_plan( + const kzt_patch_spike_guard_t *guard) +{ + return guard && guard->config.enabled && + !__atomic_load_n(&guard->circuit_open, __ATOMIC_ACQUIRE); +} + +int kzt_patch_spike_guard_circuit_open( + const kzt_patch_spike_guard_t *guard) +{ + return guard && __atomic_load_n(&guard->circuit_open, __ATOMIC_ACQUIRE); +} + +unsigned long kzt_patch_spike_guard_budget_remaining( + const kzt_patch_spike_guard_t *guard) +{ + unsigned long attempts; + + if (!guard) { + return 0; + } + + attempts = __atomic_load_n(&guard->write_attempts, __ATOMIC_ACQUIRE); + if (guard->config.budget <= attempts) { + return 0; + } + + return guard->config.budget - attempts; +} + +static int kzt_patch_spike_guard_reserve_budget(kzt_patch_spike_guard_t *guard) +{ + unsigned long attempts; + + for (;;) { + attempts = __atomic_load_n(&guard->write_attempts, __ATOMIC_ACQUIRE); + if (attempts >= guard->config.budget) { + return 0; + } + if (__atomic_compare_exchange_n(&guard->write_attempts, &attempts, + attempts + 1, 0, __ATOMIC_ACQ_REL, + __ATOMIC_ACQUIRE)) { + return 1; + } + } +} + +static int kzt_patch_spike_failure_preserves_guest( + kzt_patch_spike_failure_t failure) +{ + return failure == KZT_PATCH_SPIKE_FAILURE_PERMISSION_ENABLE_FAILED || + failure == KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED || + failure == KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED || + failure == KZT_PATCH_SPIKE_FAILURE_GENERATION_MISMATCH; +} + +static int kzt_patch_spike_decision_allows_write( + const kzt_patch_decision_t *decision) +{ + return decision && decision->kind == KZT_PATCH_DECISION_APPROVED && + decision->allow_native_bridge; +} + +int kzt_patch_spike_guard_try_write( + kzt_patch_spike_guard_t *guard, + const kzt_patch_decision_t *decision, + const kzt_patch_spike_writer_ops_t *writer, + kzt_patch_spike_outcome_t *outcome) +{ + kzt_patch_spike_writer_status_t writer_status; + uintptr_t previous_value = 0; + + if (!guard || !outcome) { + return -1; + } + + memset(outcome, 0, sizeof(*outcome)); + outcome->action = KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY; + kzt_patch_spike_guard_lock(guard); + outcome->writes_remaining = + kzt_patch_spike_guard_budget_remaining(guard); + + if (__atomic_load_n(&guard->circuit_open, __ATOMIC_ACQUIRE)) { + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_CIRCUIT_OPEN, + KZT_PATCH_SPIKE_FAILURE_CIRCUIT_BREAKER_OPEN, + KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } + + if (!guard->config.enabled) { + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_DISABLED, + KZT_PATCH_SPIKE_FAILURE_NONE, + KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } + + if (!kzt_patch_spike_decision_allows_write(decision)) { + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_FAIL_OPEN, + KZT_PATCH_SPIKE_FAILURE_DECISION_NOT_APPROVED, + KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } + + if (!guard->config.write_enabled) { + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_DIAGNOSTICS_ONLY, + KZT_PATCH_SPIKE_FAILURE_WRITE_NOT_AUTHORIZED, + KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } + + if (!kzt_patch_spike_writer_ready(writer) || + !decision->slot_current_value_present) { + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_FAIL_OPEN, + KZT_PATCH_SPIKE_FAILURE_INVALID_ARGUMENT, + KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } + + if (!kzt_patch_spike_guard_reserve_budget(guard)) { + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_BUDGET_EXHAUSTED, + KZT_PATCH_SPIKE_FAILURE_BUDGET_EXHAUSTED, + KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } + + outcome->writer_called = 1; + outcome->writes_remaining = + kzt_patch_spike_guard_budget_remaining(guard); + + writer_status = writer->write_slot(decision, + decision->slot_current_value, + decision->bridge_target, + &previous_value, + writer->opaque); + outcome->previous_value = previous_value; + if (writer_status != KZT_PATCH_SPIKE_WRITER_OK) { + kzt_patch_spike_failure_t failure = + kzt_patch_spike_writer_failure(writer_status); + int preserve_guest = kzt_patch_spike_failure_preserves_guest(failure); + + if (failure == KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED) { + __atomic_store_n(&guard->circuit_open, 1, __ATOMIC_RELEASE); + } + kzt_patch_spike_outcome_set( + outcome, preserve_guest ? KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED : + KZT_PATCH_SPIKE_RESULT_FAIL_OPEN, + failure, preserve_guest ? + KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST : + KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } + + if (writer->verify_slot(decision, decision->bridge_target, + writer->opaque) != 0) { + outcome->rollback_called = 1; + if (writer->rollback_slot(decision, previous_value, + writer->opaque) != 0) { + if (writer->finish_slot && + writer->finish_slot(decision, writer->opaque) != + KZT_PATCH_SPIKE_WRITER_OK) { + __atomic_store_n(&guard->circuit_open, 1, __ATOMIC_RELEASE); + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED, + KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED, + KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } + __atomic_store_n(&guard->circuit_open, 1, __ATOMIC_RELEASE); + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED, + KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED, + KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } + + if (writer->finish_slot && + writer->finish_slot(decision, writer->opaque) != + KZT_PATCH_SPIKE_WRITER_OK) { + __atomic_store_n(&guard->circuit_open, 1, __ATOMIC_RELEASE); + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED, + KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED, + KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } + + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_FAIL_OPEN, + KZT_PATCH_SPIKE_FAILURE_VERIFY_FAILED, + KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } + + if (writer->finish_slot && + writer->finish_slot(decision, writer->opaque) != + KZT_PATCH_SPIKE_WRITER_OK) { + __atomic_store_n(&guard->circuit_open, 1, __ATOMIC_RELEASE); + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED, + KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED, + KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } + + __atomic_add_fetch(&guard->write_successes, 1, __ATOMIC_ACQ_REL); + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_APPLIED, + KZT_PATCH_SPIKE_FAILURE_NONE, + KZT_PATCH_SPIKE_ACTION_USE_NATIVE_BRIDGE, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; +} + +const char *kzt_patch_spike_result_name(kzt_patch_spike_result_t result) +{ + switch (result) { + case KZT_PATCH_SPIKE_RESULT_DISABLED: + return "DISABLED"; + case KZT_PATCH_SPIKE_RESULT_DIAGNOSTICS_ONLY: + return "DIAGNOSTICS_ONLY"; + case KZT_PATCH_SPIKE_RESULT_BUDGET_EXHAUSTED: + return "BUDGET_EXHAUSTED"; + case KZT_PATCH_SPIKE_RESULT_APPLIED: + return "APPLIED"; + case KZT_PATCH_SPIKE_RESULT_FAIL_OPEN: + return "FAIL_OPEN"; + case KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED: + return "GUEST_PRESERVED"; + case KZT_PATCH_SPIKE_RESULT_CIRCUIT_OPEN: + return "CIRCUIT_OPEN"; + } + + return "UNKNOWN"; +} + +const char *kzt_patch_spike_failure_name(kzt_patch_spike_failure_t failure) +{ + switch (failure) { + case KZT_PATCH_SPIKE_FAILURE_NONE: + return "NONE"; + case KZT_PATCH_SPIKE_FAILURE_INVALID_ARGUMENT: + return "INVALID_ARGUMENT"; + case KZT_PATCH_SPIKE_FAILURE_DECISION_NOT_APPROVED: + return "DECISION_NOT_APPROVED"; + case KZT_PATCH_SPIKE_FAILURE_WRITE_NOT_AUTHORIZED: + return "WRITE_NOT_AUTHORIZED"; + case KZT_PATCH_SPIKE_FAILURE_BUDGET_EXHAUSTED: + return "BUDGET_EXHAUSTED"; + case KZT_PATCH_SPIKE_FAILURE_EXPECTED_MISMATCH: + return "EXPECTED_MISMATCH"; + case KZT_PATCH_SPIKE_FAILURE_READ_FAILED: + return "READ_FAILED"; + case KZT_PATCH_SPIKE_FAILURE_WRITE_FAILED: + return "WRITE_FAILED"; + case KZT_PATCH_SPIKE_FAILURE_VERIFY_FAILED: + return "VERIFY_FAILED"; + case KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED: + return "ROLLBACK_FAILED"; + case KZT_PATCH_SPIKE_FAILURE_PERMISSION_ENABLE_FAILED: + return "PERMISSION_ENABLE_FAILED"; + case KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED: + return "PERMISSION_RESTORE_FAILED"; + case KZT_PATCH_SPIKE_FAILURE_GENERATION_MISMATCH: + return "GENERATION_MISMATCH"; + case KZT_PATCH_SPIKE_FAILURE_CIRCUIT_BREAKER_OPEN: + return "CIRCUIT_BREAKER_OPEN"; + } + + return "UNKNOWN"; +} diff --git a/target/i386/latx/context/kzt_patch_spike_writer.c b/target/i386/latx/context/kzt_patch_spike_writer.c new file mode 100644 index 00000000000..06121e18641 --- /dev/null +++ b/target/i386/latx/context/kzt_patch_spike_writer.c @@ -0,0 +1,384 @@ +#include "kzt_patch_spike_writer.h" + +#include + +typedef struct kzt_patch_spike_writer_state { + const kzt_patch_spike_slot_ops_t *slot_ops; + kzt_patch_spike_record_t *record; + kzt_patch_spike_permission_lease_t permission_lease; + int permission_active; +} kzt_patch_spike_writer_state_t; + +static int kzt_patch_spike_direct_read(uintptr_t slot_addr, + uintptr_t *value, + void *opaque) +{ + volatile uintptr_t *slot = (volatile uintptr_t *)slot_addr; + + (void)opaque; + if (!slot || !value) { + return -1; + } + + *value = *slot; + return 0; +} + +static int kzt_patch_spike_direct_write(uintptr_t slot_addr, + uintptr_t value, + void *opaque) +{ + volatile uintptr_t *slot = (volatile uintptr_t *)slot_addr; + + (void)opaque; + if (!slot) { + return -1; + } + + *slot = value; + return 0; +} + +static const kzt_patch_spike_slot_ops_t kzt_patch_spike_direct_slot_ops = { + .read_slot = kzt_patch_spike_direct_read, + .write_slot = kzt_patch_spike_direct_write, +}; + +static int kzt_patch_spike_slot_ops_ready( + const kzt_patch_spike_slot_ops_t *slot_ops) +{ + return slot_ops && slot_ops->read_slot && slot_ops->write_slot && + (slot_ops->begin_write != NULL) == + (slot_ops->end_write != NULL); +} + +void kzt_patch_spike_record_init(kzt_patch_spike_record_t *record, + const kzt_patch_decision_t *decision) +{ + if (!record) { + return; + } + + memset(record, 0, sizeof(*record)); + record->valid = 1; + record->result = KZT_PATCH_SPIKE_RESULT_FAIL_OPEN; + record->failure = KZT_PATCH_SPIKE_FAILURE_INVALID_ARGUMENT; + record->action = KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY; + + if (!decision) { + return; + } + + record->decision_kind = decision->kind; + record->decision_reason = decision->reason; + record->allow_native_bridge = decision->allow_native_bridge; + record->table_kind = decision->table_kind; + record->reloc_type = decision->reloc_type; + record->entry_index = decision->entry_index; + record->entry_addr = decision->entry_addr; + record->slot_addr = decision->slot_addr; + record->source_link_map = decision->source.link_map_addr; + record->current_owner_link_map = decision->current_owner.link_map_addr; + record->source_generation = decision->source.generation; + record->current_owner_generation = decision->current_owner.generation; + record->dynamic_view_generation = decision->dynamic_view_generation; + record->expected_value_present = decision->slot_current_value_present; + record->expected_value = decision->slot_current_value; + record->replacement_value = decision->bridge_target; + record->symbol_name = decision->symbol_name; + record->wrapper_name = decision->wrapper_name; +} + +static void kzt_patch_spike_record_permission( + kzt_patch_spike_record_t *record, + const kzt_patch_spike_permission_lease_t *lease) +{ + if (!record || !lease) { + return; + } + + record->permission_checked = lease->checked; + record->permission_guest_page = lease->guest_page; + record->permission_guest_page_length = lease->guest_page_length; + record->permission_original_permissions = lease->original_permissions; + record->permission_was_writable = lease->was_writable; + record->permission_write_enabled = lease->write_enabled; + record->permission_restore_attempted = lease->restore_attempted; + record->permission_restored = lease->restored; +} + +static kzt_patch_spike_writer_status_t kzt_patch_spike_writer_finish_slot( + const kzt_patch_decision_t *decision, void *opaque) +{ + kzt_patch_spike_writer_state_t *state = opaque; + + (void)decision; + if (!state || !state->record) { + return KZT_PATCH_SPIKE_WRITER_READ_FAILED; + } + if (!state->permission_active) { + return KZT_PATCH_SPIKE_WRITER_OK; + } + + state->permission_lease.restore_attempted = 1; + if (state->slot_ops->end_write && + state->slot_ops->end_write(&state->permission_lease, + state->slot_ops->opaque) != 0) { + kzt_patch_spike_record_permission(state->record, + &state->permission_lease); + state->permission_active = 0; + return KZT_PATCH_SPIKE_WRITER_PERMISSION_RESTORE_FAILED; + } + state->permission_lease.restored = 1; + kzt_patch_spike_record_permission(state->record, + &state->permission_lease); + state->permission_active = 0; + return KZT_PATCH_SPIKE_WRITER_OK; +} + +static kzt_patch_spike_writer_status_t kzt_patch_spike_writer_abort_slot( + const kzt_patch_decision_t *decision, void *opaque, + kzt_patch_spike_writer_status_t status) +{ + if (kzt_patch_spike_writer_finish_slot(decision, opaque) != + KZT_PATCH_SPIKE_WRITER_OK) { + return KZT_PATCH_SPIKE_WRITER_PERMISSION_RESTORE_FAILED; + } + return status; +} + +static int kzt_patch_spike_writer_decision_supported( + const kzt_patch_decision_t *decision) +{ + return decision && decision->kind == KZT_PATCH_DECISION_APPROVED && + decision->reason == KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE && + decision->allow_native_bridge && + decision->reloc_type == KZT_PATCH_RELOCATION_JUMP_SLOT && + decision->slot_addr && decision->slot_current_value_present && + decision->bridge_target && decision->source.known && + decision->source.link_map_addr && decision->source.generation && + decision->dynamic_view_available && + decision->dynamic_view_generation && + decision->current_owner.known && + decision->current_owner.link_map_addr && + decision->current_owner.generation && + decision->owner_match == KZT_PATCH_OWNER_MATCH; +} + +static kzt_patch_spike_writer_status_t kzt_patch_spike_writer_write_slot( + const kzt_patch_decision_t *decision, + uintptr_t expected_value, + uintptr_t replacement_value, + uintptr_t *previous_value, + void *opaque) +{ + kzt_patch_spike_writer_state_t *state = opaque; + kzt_patch_spike_record_t *record; + uintptr_t observed_value = 0; + + if (!state || !kzt_patch_spike_slot_ops_ready(state->slot_ops) || + !state->record || + !kzt_patch_spike_writer_decision_supported(decision)) { + return KZT_PATCH_SPIKE_WRITER_READ_FAILED; + } + + record = state->record; + if (state->slot_ops->validate_generation) { + record->generation_checked = 1; + if (state->slot_ops->validate_generation(decision, + state->slot_ops->opaque) != 0) { + record->generation_matched = 0; + return KZT_PATCH_SPIKE_WRITER_GENERATION_MISMATCH; + } + record->generation_matched = 1; + } + if (state->slot_ops->begin_write) { + if (state->slot_ops->begin_write(decision->slot_addr, + &state->permission_lease, + state->slot_ops->opaque) != 0) { + state->permission_active = + state->permission_lease.write_enabled != 0; + kzt_patch_spike_record_permission(record, + &state->permission_lease); + if (state->permission_active) { + return kzt_patch_spike_writer_abort_slot( + decision, state, + KZT_PATCH_SPIKE_WRITER_PERMISSION_ENABLE_FAILED); + } + return KZT_PATCH_SPIKE_WRITER_PERMISSION_ENABLE_FAILED; + } + state->permission_active = 1; + kzt_patch_spike_record_permission(record, &state->permission_lease); + } + record->read_attempted = 1; + if (state->slot_ops->read_slot(decision->slot_addr, &observed_value, + state->slot_ops->opaque) != 0) { + return kzt_patch_spike_writer_abort_slot( + decision, state, KZT_PATCH_SPIKE_WRITER_READ_FAILED); + } + + record->observed_value = observed_value; + record->previous_value = observed_value; + if (previous_value) { + *previous_value = observed_value; + } + + if (observed_value != expected_value) { + record->expected_current_matched = 0; + return kzt_patch_spike_writer_abort_slot( + decision, state, KZT_PATCH_SPIKE_WRITER_EXPECTED_MISMATCH); + } + + record->expected_current_matched = 1; + record->write_attempted = 1; + if (state->slot_ops->write_slot(decision->slot_addr, replacement_value, + state->slot_ops->opaque) != 0) { + return kzt_patch_spike_writer_abort_slot( + decision, state, KZT_PATCH_SPIKE_WRITER_WRITE_FAILED); + } + + record->write_succeeded = 1; + return KZT_PATCH_SPIKE_WRITER_OK; +} + +static int kzt_patch_spike_writer_verify_slot( + const kzt_patch_decision_t *decision, + uintptr_t expected_value, + void *opaque) +{ + kzt_patch_spike_writer_state_t *state = opaque; + kzt_patch_spike_record_t *record; + uintptr_t observed_value = 0; + + if (!state || !kzt_patch_spike_slot_ops_ready(state->slot_ops) || + !state->record || !decision || !decision->slot_addr) { + return -1; + } + + record = state->record; + record->verify_attempted = 1; + if (state->slot_ops->read_slot(decision->slot_addr, &observed_value, + state->slot_ops->opaque) != 0) { + return -1; + } + + record->verified_value = observed_value; + if (observed_value != expected_value) { + return -1; + } + + record->verify_succeeded = 1; + return 0; +} + +static int kzt_patch_spike_writer_rollback_slot( + const kzt_patch_decision_t *decision, + uintptr_t previous_value, + void *opaque) +{ + kzt_patch_spike_writer_state_t *state = opaque; + kzt_patch_spike_record_t *record; + uintptr_t observed_value = 0; + + if (!state || !kzt_patch_spike_slot_ops_ready(state->slot_ops) || + !state->record || !decision || !decision->slot_addr) { + return -1; + } + + record = state->record; + record->rollback_called = 1; + record->rollback_value = previous_value; + if (state->slot_ops->write_slot(decision->slot_addr, previous_value, + state->slot_ops->opaque) != 0) { + return -1; + } + + record->rollback_succeeded = 1; + record->rollback_verify_attempted = 1; + if (state->slot_ops->read_slot(decision->slot_addr, &observed_value, + state->slot_ops->opaque) != 0) { + return -1; + } + record->rollback_verified_value = observed_value; + if (observed_value != previous_value) { + return -1; + } + record->rollback_verify_succeeded = 1; + return 0; +} + +static void kzt_patch_spike_record_finish( + kzt_patch_spike_record_t *record, + const kzt_patch_spike_outcome_t *outcome) +{ + if (!record || !outcome) { + return; + } + + record->result = outcome->result; + record->failure = outcome->failure; + record->action = outcome->action; + record->skip_legacy_write = outcome->skip_legacy_write; + record->writes_remaining = outcome->writes_remaining; + record->writer_called = outcome->writer_called; + record->rollback_called = record->rollback_called || + outcome->rollback_called; + record->previous_value = outcome->previous_value; +} + +int kzt_patch_spike_writer_try_apply_with_slot_ops( + kzt_patch_spike_guard_t *guard, + const kzt_patch_decision_t *decision, + const kzt_patch_spike_slot_ops_t *slot_ops, + kzt_patch_spike_record_t *record) +{ + kzt_patch_spike_writer_state_t state; + kzt_patch_spike_writer_ops_t writer_ops; + kzt_patch_spike_outcome_t outcome; + const kzt_patch_spike_slot_ops_t *effective_slot_ops = slot_ops; + + if (!effective_slot_ops) { + effective_slot_ops = &kzt_patch_spike_direct_slot_ops; + } + + kzt_patch_spike_record_init(record, decision); + if (!guard || !record) { + return -1; + } + + if (!decision || + (decision->kind == KZT_PATCH_DECISION_APPROVED && + !kzt_patch_spike_writer_decision_supported(decision))) { + record->result = KZT_PATCH_SPIKE_RESULT_FAIL_OPEN; + record->failure = KZT_PATCH_SPIKE_FAILURE_INVALID_ARGUMENT; + record->action = KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY; + record->writes_remaining = + kzt_patch_spike_guard_budget_remaining(guard); + return 0; + } + + memset(&state, 0, sizeof(state)); + state.slot_ops = effective_slot_ops; + state.record = record; + writer_ops.write_slot = kzt_patch_spike_writer_write_slot; + writer_ops.verify_slot = kzt_patch_spike_writer_verify_slot; + writer_ops.rollback_slot = kzt_patch_spike_writer_rollback_slot; + writer_ops.finish_slot = kzt_patch_spike_writer_finish_slot; + writer_ops.opaque = &state; + + if (kzt_patch_spike_guard_try_write(guard, decision, &writer_ops, + &outcome) != 0) { + return -1; + } + + kzt_patch_spike_record_finish(record, &outcome); + return 0; +} + +int kzt_patch_spike_writer_try_apply(kzt_patch_spike_guard_t *guard, + const kzt_patch_decision_t *decision, + kzt_patch_spike_record_t *record) +{ + return kzt_patch_spike_writer_try_apply_with_slot_ops(guard, decision, + NULL, record); +} diff --git a/target/i386/latx/context/kzt_plt_resolver_adapter.c b/target/i386/latx/context/kzt_plt_resolver_adapter.c new file mode 100644 index 00000000000..7e4fd5e537e --- /dev/null +++ b/target/i386/latx/context/kzt_plt_resolver_adapter.c @@ -0,0 +1,85 @@ +#ifndef KZT_PLT_RESOLVER_ADAPTER_TEST +#include "qemu/osdep.h" +#endif + +#include "kzt_plt_resolver_adapter.h" + +#include + +#ifndef KZT_PLT_RESOLVER_ADAPTER_TEST +#include "target/i386/cpu.h" +#endif + +static void resolver_push64(CPUX86State *cpu, uintptr_t value) +{ + cpu->regs[R_ESP] -= sizeof(uint64_t); + *(uint64_t *)cpu->regs[R_ESP] = value; +} + +int kzt_plt_resolver_enter( + CPUX86State *cpu, const kzt_plt_resolver_runtime_ops_t *ops, + kzt_plt_resolver_enter_result_t *result) +{ + const uint64_t *frame; + kzt_plt_resolver_source_t source; + kzt_lazy_binding_begin_request_t request; + kzt_lazy_binding_result_t begin_result; + + if (result) { + memset(result, 0, sizeof(*result)); + result->status = KZT_PLT_RESOLVER_ERROR; + } + if (!cpu || !ops || !result || !ops->lookup_source || + !ops->begin_lazy_binding || !ops->pending || !cpu->regs[R_ESP]) { + return -1; + } + + frame = (const uint64_t *)cpu->regs[R_ESP]; + result->object_head = frame[0]; + result->relocation_slot = frame[1]; + result->return_address = frame[2]; + memset(&source, 0, sizeof(source)); + if (ops->lookup_source(result->object_head, &source, ops->opaque) != 0 || + !source.guest_resolver || !source.source_link_map) { + result->status = KZT_PLT_RESOLVER_LEGACY_FRAME_RESTORED; + return 0; + } + + cpu->regs[R_ESP] += 2 * sizeof(uint64_t); + memset(&request, 0, sizeof(request)); + request.enabled = source.enabled; + request.context_id = source.context_id; + request.source_link_map = source.source_link_map; + request.source_generation = source.source_generation; + request.namespace_id = source.namespace_id; + request.namespace_kind = source.namespace_kind; + request.relocation_index = result->relocation_slot; + request.slot_addr = source.slot_addr; + request.unresolved_stub = source.unresolved_stub; + request.symbol = source.symbol; + request.version_evidence = source.version_evidence; + request.version = source.version; + request.addend = source.addend; + request.guest_resolver = source.guest_resolver; + memset(&begin_result, 0, sizeof(begin_result)); + if (ops->begin_lazy_binding(&request, ops->pending, &begin_result, + ops->opaque) != 0) { + begin_result.status = KZT_LAZY_BINDING_GUEST_PRESERVED; + } + + result->selected_resolver = source.guest_resolver; + result->pending_armed = begin_result.pending_armed; + if (begin_result.status == KZT_LAZY_BINDING_HANDOFF_GUEST) { + result->status = KZT_PLT_RESOLVER_HANDOFF_GUEST; + if (ops->completion_bridge && ops->original_return) { + *ops->original_return = *(uintptr_t *)cpu->regs[R_ESP]; + *(uintptr_t *)cpu->regs[R_ESP] = ops->completion_bridge; + } + } else { + result->status = KZT_PLT_RESOLVER_GUEST_PRESERVED; + } + resolver_push64(cpu, result->relocation_slot); + resolver_push64(cpu, source.source_link_map); + resolver_push64(cpu, source.guest_resolver); + return 0; +} diff --git a/target/i386/latx/context/kzt_rela_diagnostics.c b/target/i386/latx/context/kzt_rela_diagnostics.c new file mode 100644 index 00000000000..74317768482 --- /dev/null +++ b/target/i386/latx/context/kzt_rela_diagnostics.c @@ -0,0 +1,439 @@ +#include "kzt_rela_diagnostics.h" + +#include +#include +#include + +#define KZT_RELA_DIAGNOSTIC_THROTTLE_MAGIC 0x4b5a5444U + +static int kzt_rela_diagnostic_mode_enabled( + kzt_rela_diagnostic_mode_t mode) +{ + return mode == KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS || + mode == KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS_WRITE_ENABLED; +} + +static int kzt_rela_diagnostic_mode_records_writer( + kzt_rela_diagnostic_mode_t mode) +{ + return mode == KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS_WRITE_ENABLED; +} + +kzt_rela_diagnostic_mode_t kzt_rela_diagnostic_mode_from_flags( + int diagnostics_enabled, + int write_enabled) +{ + if (diagnostics_enabled && write_enabled) { + return KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS_WRITE_ENABLED; + } + if (diagnostics_enabled) { + return KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS; + } + if (write_enabled) { + return KZT_RELA_DIAGNOSTIC_MODE_WRITE_ENABLED_ONLY; + } + + return KZT_RELA_DIAGNOSTIC_MODE_DEFAULT; +} + +const char *kzt_rela_diagnostic_reason_domain_name( + kzt_rela_diagnostic_reason_domain_t domain) +{ + switch (domain) { + case KZT_RELA_DIAGNOSTIC_REASON_CANDIDATE: + return "candidate"; + case KZT_RELA_DIAGNOSTIC_REASON_PLANNER: + return "planner"; + case KZT_RELA_DIAGNOSTIC_REASON_WRITER: + return "writer"; + } + + return "unknown"; +} + +static const char *kzt_rela_candidate_status_name( + kzt_rela_immediate_candidate_status_t status) +{ + switch (status) { + case KZT_RELA_IMMEDIATE_CANDIDATE_SKIPPED: + return "SKIPPED"; + case KZT_RELA_IMMEDIATE_CANDIDATE_PLANNED: + return "PLANNED"; + case KZT_RELA_IMMEDIATE_CANDIDATE_FAIL_OPEN: + return "FAIL_OPEN"; + } + + return "UNKNOWN"; +} + +static const char *kzt_rela_candidate_reason_name( + kzt_rela_immediate_candidate_reason_t reason) +{ + switch (reason) { + case KZT_RELA_IMMEDIATE_CANDIDATE_REASON_NONE: + return "NONE"; + case KZT_RELA_IMMEDIATE_CANDIDATE_REASON_INVALID_ARGUMENT: + return "INVALID_ARGUMENT"; + case KZT_RELA_IMMEDIATE_CANDIDATE_REASON_NON_TARGET_RELOCATION: + return "NON_TARGET_RELOCATION"; + case KZT_RELA_IMMEDIATE_CANDIDATE_REASON_DEFERRED_LAZY_BINDING: + return "DEFERRED_LAZY_BINDING"; + case KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_SLOT: + return "MISSING_SLOT"; + case KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_CURRENT_VALUE: + return "MISSING_CURRENT_VALUE"; + case KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_SYMBOL_NAME: + return "MISSING_SYMBOL_NAME"; + case KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_SYMBOL_VERSION: + return "MISSING_SYMBOL_VERSION"; + case KZT_RELA_IMMEDIATE_CANDIDATE_REASON_PLANNER_ERROR: + return "PLANNER_ERROR"; + } + + return "UNKNOWN"; +} + +static void kzt_rela_diagnostic_copy_name(char *dst, size_t dst_size, + const char *src) +{ + if (!dst || dst_size == 0) { + return; + } + + snprintf(dst, dst_size, "%s", src ? src : "UNKNOWN"); +} + +static void kzt_rela_diagnostic_copy_source(char *dst, size_t dst_size, + const char *src) +{ + size_t i; + + if (!dst || dst_size == 0) { + return; + } + if (!src || !src[0]) { + src = "(unknown)"; + } + + for (i = 0; i + 1 < dst_size && src[i]; ++i) { + unsigned char value = (unsigned char)src[i]; + + if (value <= ' ' || value == '"' || value == '\\' || value == '=') { + dst[i] = '_'; + } else { + dst[i] = (char)value; + } + } + dst[i] = '\0'; +} + +static const char *kzt_rela_diagnostic_object_name( + const kzt_patch_object_ref_t *object) +{ + if (!object) { + return NULL; + } + if (object->path && object->path[0]) { + return object->path; + } + if (object->soname && object->soname[0]) { + return object->soname; + } + + return NULL; +} + +static void kzt_rela_diagnostic_apply_request( + const kzt_rela_immediate_candidate_request_t *request, + kzt_rela_diagnostic_record_t *record) +{ + if (!request || !record) { + return; + } + + kzt_rela_diagnostic_copy_source( + record->source, sizeof(record->source), + kzt_rela_diagnostic_object_name(&request->source)); + record->source_link_map = request->source.link_map_addr; + record->current_owner = request->current_owner.link_map_addr; + record->source_generation = request->source.generation; + record->current_owner_generation = request->current_owner.generation; + record->owner_match = request->owner_match; + record->wrapper_match = request->wrapper_match; + record->bridge_target = request->native_bridge_target; + kzt_rela_diagnostic_copy_name( + record->symbol, sizeof(record->symbol), request->symbol_name); + kzt_rela_diagnostic_copy_name( + record->version, sizeof(record->version), request->version); +} + +static void kzt_rela_diagnostic_apply_decision( + const kzt_patch_decision_t *decision, + kzt_rela_diagnostic_record_t *record) +{ + if (!decision || !record) { + return; + } + + kzt_rela_diagnostic_copy_source( + record->source, sizeof(record->source), + kzt_rela_diagnostic_object_name(&decision->source)); + record->source_link_map = decision->source.link_map_addr; + record->current_owner = decision->current_owner.link_map_addr; + record->source_generation = decision->source.generation; + record->current_owner_generation = decision->current_owner.generation; + record->owner_match = decision->owner_match; + record->wrapper_match = decision->wrapper_match; + record->bridge_target = decision->bridge_target; + kzt_rela_diagnostic_copy_name( + record->symbol, sizeof(record->symbol), decision->symbol_name); + kzt_rela_diagnostic_copy_name( + record->version, sizeof(record->version), decision->version); +} + +int kzt_rela_immediate_diagnostic_record( + kzt_rela_diagnostic_mode_t mode, + const kzt_rela_immediate_candidate_request_t *request, + const kzt_rela_immediate_writer_result_t *result, + int legacy_fallback, + kzt_rela_diagnostic_record_t *record) +{ + const kzt_rela_immediate_candidate_result_t *plan = NULL; + + if (!record) { + return -1; + } + + memset(record, 0, sizeof(*record)); + kzt_rela_diagnostic_copy_source(record->source, sizeof(record->source), + NULL); + record->owner_match = KZT_PATCH_OWNER_UNKNOWN; + record->wrapper_match = KZT_PATCH_WRAPPER_NO_MANIFEST; + record->reason_domain = KZT_RELA_DIAGNOSTIC_REASON_CANDIDATE; + kzt_rela_diagnostic_copy_name(record->reason, sizeof(record->reason), + "UNAVAILABLE"); + kzt_rela_diagnostic_copy_name(record->decision, + sizeof(record->decision), "UNAVAILABLE"); + kzt_rela_diagnostic_copy_name(record->writer_result, + sizeof(record->writer_result), + "NOT_RECORDED"); + record->legacy_fallback = legacy_fallback != 0; + kzt_rela_diagnostic_apply_request(request, record); + + if (result) { + plan = &result->plan; + kzt_rela_diagnostic_copy_name( + record->decision, sizeof(record->decision), + kzt_rela_candidate_status_name(plan->status)); + kzt_rela_diagnostic_copy_name( + record->reason, sizeof(record->reason), + kzt_rela_candidate_reason_name(plan->reason)); + } + + if (plan && plan->decision_present) { + record->reason_domain = KZT_RELA_DIAGNOSTIC_REASON_PLANNER; + kzt_rela_diagnostic_apply_decision(&plan->decision, record); + kzt_rela_diagnostic_copy_name( + record->decision, sizeof(record->decision), + kzt_patch_decision_kind_name(plan->decision.kind)); + kzt_rela_diagnostic_copy_name( + record->reason, sizeof(record->reason), + kzt_patch_reason_name(plan->decision.reason)); + } + + if (kzt_rela_diagnostic_mode_records_writer(mode) && result && + result->record.valid) { + record->reason_domain = KZT_RELA_DIAGNOSTIC_REASON_WRITER; + kzt_rela_diagnostic_copy_name( + record->reason, sizeof(record->reason), + kzt_patch_spike_failure_name(result->record.failure)); + kzt_rela_diagnostic_copy_name( + record->writer_result, sizeof(record->writer_result), + kzt_patch_spike_result_name(result->record.result)); + } + + return 0; +} + +kzt_rela_diagnostic_format_status_t kzt_rela_diagnostic_format( + const kzt_rela_diagnostic_record_t *record, + char *buffer, + size_t buffer_size) +{ + int written; + + if (!record || !buffer || buffer_size == 0) { + return KZT_RELA_DIAGNOSTIC_FORMAT_ERROR; + } + + written = snprintf( + buffer, buffer_size, + "kzt_rela_diagnostic source=%s source_link_map=0x%lx " + "source_generation=%lu current_owner=0x%lx " + "current_owner_generation=%lu owner_match=%s " + "wrapper_match=%s bridge_target=0x%lx symbol=%s version=%s " + "reason_domain=%s " + "reason=%s decision=%s writer_result=%s legacy_fallback=%d", + record->source, + (unsigned long)record->source_link_map, + record->source_generation, + (unsigned long)record->current_owner, + record->current_owner_generation, + kzt_patch_owner_match_name(record->owner_match), + kzt_patch_wrapper_match_name(record->wrapper_match), + (unsigned long)record->bridge_target, + record->symbol, + record->version, + kzt_rela_diagnostic_reason_domain_name(record->reason_domain), + record->reason, + record->decision, + record->writer_result, + record->legacy_fallback); + if (written < 0) { + return KZT_RELA_DIAGNOSTIC_FORMAT_ERROR; + } + if ((size_t)written >= buffer_size) { + return KZT_RELA_DIAGNOSTIC_FORMAT_TRUNCATED; + } + + return KZT_RELA_DIAGNOSTIC_FORMAT_OK; +} + +static int kzt_rela_diagnostic_throttle_valid( + const kzt_rela_diagnostic_throttle_t *throttle) +{ + return throttle && + __atomic_load_n(&throttle->initialized, __ATOMIC_ACQUIRE) == + KZT_RELA_DIAGNOSTIC_THROTTLE_MAGIC; +} + +static void kzt_rela_diagnostic_throttle_lock( + kzt_rela_diagnostic_throttle_t *throttle) +{ + while (__atomic_exchange_n(&throttle->lock, 1U, __ATOMIC_ACQUIRE) != 0U) { + } +} + +static void kzt_rela_diagnostic_throttle_unlock( + kzt_rela_diagnostic_throttle_t *throttle) +{ + __atomic_store_n(&throttle->lock, 0U, __ATOMIC_RELEASE); +} + +int kzt_rela_diagnostic_throttle_init( + kzt_rela_diagnostic_throttle_t *throttle, + unsigned long capacity) +{ + if (!throttle) { + return -1; + } + + memset(throttle, 0, sizeof(*throttle)); + throttle->capacity = capacity; + __atomic_store_n(&throttle->initialized, + KZT_RELA_DIAGNOSTIC_THROTTLE_MAGIC, + __ATOMIC_RELEASE); + return 0; +} + +int kzt_rela_diagnostic_throttle_try_admit( + kzt_rela_diagnostic_throttle_t *throttle) +{ + int result; + + if (!kzt_rela_diagnostic_throttle_valid(throttle)) { + return -1; + } + + kzt_rela_diagnostic_throttle_lock(throttle); + if (throttle->admitted >= throttle->capacity) { + if (throttle->suppressed != ULONG_MAX) { + ++throttle->suppressed; + } + result = 0; + } else { + ++throttle->admitted; + result = 1; + } + kzt_rela_diagnostic_throttle_unlock(throttle); + return result; +} + +int kzt_rela_diagnostic_throttle_snapshot( + kzt_rela_diagnostic_throttle_t *throttle, + kzt_rela_diagnostic_throttle_snapshot_t *snapshot) +{ + if (!snapshot || !kzt_rela_diagnostic_throttle_valid(throttle)) { + return -1; + } + + kzt_rela_diagnostic_throttle_lock(throttle); + snapshot->capacity = throttle->capacity; + snapshot->admitted = throttle->admitted; + snapshot->suppressed = throttle->suppressed; + kzt_rela_diagnostic_throttle_unlock(throttle); + return 0; +} + +int kzt_rela_immediate_diagnostic_emit( + const kzt_rela_immediate_diagnostic_input_t *input, + kzt_rela_immediate_diagnostic_result_t *result) +{ + int admitted; + + if (!input || !result) { + return -1; + } + + memset(result, 0, sizeof(*result)); + result->status = KZT_RELA_DIAGNOSTIC_EMIT_DISABLED; + result->format_status = KZT_RELA_DIAGNOSTIC_FORMAT_ERROR; + if (!kzt_rela_diagnostic_mode_enabled(input->mode)) { + return 0; + } + + admitted = kzt_rela_diagnostic_throttle_try_admit(input->throttle); + if (admitted < 0) { + result->status = KZT_RELA_DIAGNOSTIC_EMIT_THROTTLE_FAILED; + return 0; + } + if (admitted == 0) { + result->status = KZT_RELA_DIAGNOSTIC_EMIT_SUPPRESSED; + return 0; + } + + if (kzt_rela_immediate_diagnostic_record( + input->mode, input->request, input->result, + input->legacy_fallback, &result->record) != 0) { + result->status = KZT_RELA_DIAGNOSTIC_EMIT_FORMAT_FAILED; + return 0; + } + result->record_present = 1; + + result->format_status = kzt_rela_diagnostic_format( + &result->record, input->buffer, input->buffer_size); + if (result->format_status == KZT_RELA_DIAGNOSTIC_FORMAT_TRUNCATED) { + result->status = KZT_RELA_DIAGNOSTIC_EMIT_FORMAT_TRUNCATED; + return 0; + } + if (result->format_status != KZT_RELA_DIAGNOSTIC_FORMAT_OK) { + result->status = KZT_RELA_DIAGNOSTIC_EMIT_FORMAT_FAILED; + return 0; + } + + if (!input->sink) { + result->status = KZT_RELA_DIAGNOSTIC_EMIT_SINK_FAILED; + result->sink_status = -1; + return 0; + } + + result->sink_status = input->sink( + input->buffer, strlen(input->buffer), input->sink_opaque); + if (result->sink_status != 0) { + result->status = KZT_RELA_DIAGNOSTIC_EMIT_SINK_FAILED; + return 0; + } + + result->status = KZT_RELA_DIAGNOSTIC_EMIT_EMITTED; + return 0; +} diff --git a/target/i386/latx/context/kzt_rela_immediate_candidate.c b/target/i386/latx/context/kzt_rela_immediate_candidate.c new file mode 100644 index 00000000000..c81f5caf9fc --- /dev/null +++ b/target/i386/latx/context/kzt_rela_immediate_candidate.c @@ -0,0 +1,174 @@ +#include "kzt_rela_immediate_candidate.h" + +#include + +#include "elf.h" + +static int kzt_rela_immediate_string_empty(const char *value) +{ + return !value || !value[0]; +} + +static void kzt_rela_immediate_result_set( + kzt_rela_immediate_candidate_result_t *result, + kzt_rela_immediate_candidate_status_t status, + kzt_rela_immediate_candidate_reason_t reason) +{ + result->status = status; + result->reason = reason; +} + +static int kzt_rela_immediate_fail_open( + kzt_rela_immediate_candidate_result_t *result, + kzt_rela_immediate_candidate_reason_t reason) +{ + kzt_rela_immediate_result_set( + result, KZT_RELA_IMMEDIATE_CANDIDATE_FAIL_OPEN, reason); + return 0; +} + +static void kzt_rela_immediate_copy_request( + const kzt_rela_immediate_candidate_request_t *request, + kzt_patch_candidate_t *candidate) +{ + memset(candidate, 0, sizeof(*candidate)); + candidate->source = request->source; + candidate->dynamic_addr = request->dynamic_addr; + candidate->load_bias = request->load_bias; + candidate->dynamic_view_generation = request->dynamic_view_generation; + candidate->dynamic_view_available = request->dynamic_view_available; + candidate->table_kind = request->table_kind; + candidate->entry_index = request->entry_index; + candidate->entry_addr = request->entry_addr; + candidate->reloc_type = KZT_PATCH_RELOCATION_JUMP_SLOT; + candidate->slot_addr = request->slot_addr; + candidate->slot_current_value_present = + request->slot_current_value_present; + candidate->slot_current_value = request->slot_current_value; + candidate->lazy_binding_deferred = request->lazy_binding_deferred; + candidate->symbol_index = request->symbol_index; + candidate->symbol_name = request->symbol_name; + candidate->version_evidence = request->version_evidence; + candidate->version = request->version; + candidate->current_owner = request->current_owner; + candidate->owner_match = request->owner_match; + candidate->wrapper_match = request->wrapper_match; + candidate->wrapper_name = request->wrapper_name; + candidate->wrapper_version_evidence = + request->wrapper_version_evidence; + candidate->wrapper_symbol_version = request->wrapper_symbol_version; + candidate->bridge_target = request->native_bridge_target; +} + +int kzt_rela_immediate_jump_slot_plan( + const kzt_rela_immediate_candidate_request_t *request, + kzt_rela_immediate_candidate_result_t *result) +{ + kzt_patch_decision_t decision; + + if (!result) { + return -1; + } + + memset(result, 0, sizeof(*result)); + if (!request) { + return kzt_rela_immediate_fail_open( + result, KZT_RELA_IMMEDIATE_CANDIDATE_REASON_INVALID_ARGUMENT); + } + + if (request->relocation_type != R_X86_64_JUMP_SLOT) { + kzt_rela_immediate_result_set( + result, KZT_RELA_IMMEDIATE_CANDIDATE_SKIPPED, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_NON_TARGET_RELOCATION); + return 0; + } + + if (request->lazy_binding_deferred) { + kzt_rela_immediate_result_set( + result, KZT_RELA_IMMEDIATE_CANDIDATE_SKIPPED, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_DEFERRED_LAZY_BINDING); + return 0; + } + + if (request->slot_addr == 0) { + return kzt_rela_immediate_fail_open( + result, KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_SLOT); + } + + if (!request->slot_current_value_present) { + return kzt_rela_immediate_fail_open( + result, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_CURRENT_VALUE); + } + + if (kzt_rela_immediate_string_empty(request->symbol_name)) { + return kzt_rela_immediate_fail_open( + result, KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_SYMBOL_NAME); + } + + if (!kzt_symbol_version_evidence_valid(request->version_evidence, + request->version)) { + return kzt_rela_immediate_fail_open( + result, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_SYMBOL_VERSION); + } + + kzt_rela_immediate_copy_request(request, &result->candidate); + result->candidate_present = 1; + + if (kzt_patch_planner_decide(&result->candidate, &decision) != 0) { + result->candidate_present = 0; + return kzt_rela_immediate_fail_open( + result, KZT_RELA_IMMEDIATE_CANDIDATE_REASON_PLANNER_ERROR); + } + + result->decision = decision; + result->decision_present = 1; + kzt_rela_immediate_result_set( + result, KZT_RELA_IMMEDIATE_CANDIDATE_PLANNED, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_NONE); + return 0; +} + +static int kzt_rela_immediate_decision_allows_writer( + const kzt_rela_immediate_candidate_result_t *plan) +{ + return plan && plan->status == KZT_RELA_IMMEDIATE_CANDIDATE_PLANNED && + plan->decision_present && + plan->decision.kind == KZT_PATCH_DECISION_APPROVED && + plan->decision.allow_native_bridge; +} + +int kzt_rela_immediate_jump_slot_try_write( + const kzt_rela_immediate_candidate_request_t *request, + kzt_patch_spike_guard_t *guard, + const kzt_patch_spike_slot_ops_t *slot_ops, + kzt_rela_immediate_writer_result_t *result) +{ + kzt_rela_immediate_writer_result_t local_result; + + if (!result) { + result = &local_result; + } + + memset(result, 0, sizeof(*result)); + result->planner_called = 1; + if (kzt_rela_immediate_jump_slot_plan(request, &result->plan) != 0) { + return 0; + } + + if (!kzt_rela_immediate_decision_allows_writer(&result->plan)) { + return 0; + } + + if (kzt_patch_spike_writer_try_apply_with_slot_ops( + guard, &result->plan.decision, slot_ops, &result->record) != 0) { + return 0; + } + + result->writer_called = result->record.writer_called; + result->skip_legacy_write = + result->record.result == KZT_PATCH_SPIKE_RESULT_APPLIED && + result->record.skip_legacy_write; + return 0; +} diff --git a/target/i386/latx/context/kzt_rela_request_enricher.c b/target/i386/latx/context/kzt_rela_request_enricher.c new file mode 100644 index 00000000000..7fe2db5ab46 --- /dev/null +++ b/target/i386/latx/context/kzt_rela_request_enricher.c @@ -0,0 +1,371 @@ +#include "kzt_rela_request_enricher.h" +#include "kzt_rela_stub_detector.h" + +#include +#include + +static int kzt_rela_add_load_bias(uintptr_t address, intptr_t load_bias, + uintptr_t *runtime_address) +{ + uintptr_t magnitude; + + if (!runtime_address) { + return 0; + } + if (load_bias >= 0) { + magnitude = (uintptr_t)load_bias; + if (address > UINTPTR_MAX - magnitude) { + return 0; + } + *runtime_address = address + magnitude; + return 1; + } + + magnitude = (uintptr_t)(-(load_bias + 1)) + 1; + if (address < magnitude) { + return 0; + } + *runtime_address = address - magnitude; + return 1; +} + +static int kzt_rela_value_in_range(uintptr_t value, uintptr_t start, + uintptr_t end) +{ + return start < end && value >= start && value < end; +} + +static int kzt_rela_value_in_loaded_range(uintptr_t value, + intptr_t load_bias, + uintptr_t start, + uintptr_t end) +{ + uintptr_t runtime_start; + uintptr_t runtime_end; + + if (start >= end || + !kzt_rela_add_load_bias(start, load_bias, &runtime_start) || + !kzt_rela_add_load_bias(end, load_bias, &runtime_end)) { + return 0; + } + + return kzt_rela_value_in_range(value, runtime_start, runtime_end); +} + +int kzt_rela_slot_current_is_unresolved_stub( + uintptr_t slot_current_value, kzt_rela_stub_coordinate_t coordinate, + intptr_t load_bias, + uintptr_t plt_start, uintptr_t plt_end, + uintptr_t gotplt_start, uintptr_t gotplt_end) +{ + if (!slot_current_value || + coordinate == KZT_RELA_STUB_COORDINATE_UNKNOWN) { + return 0; + } + + if (coordinate == KZT_RELA_STUB_COORDINATE_LINK_TIME_RAW) { + return kzt_rela_value_in_range(slot_current_value, + plt_start, plt_end) || + kzt_rela_value_in_range(slot_current_value, + gotplt_start, gotplt_end); + } + if (coordinate == KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED) { + return kzt_rela_value_in_loaded_range(slot_current_value, load_bias, + plt_start, plt_end) || + kzt_rela_value_in_loaded_range(slot_current_value, load_bias, + gotplt_start, gotplt_end); + } + + return 0; +} + +static int kzt_rela_enricher_field_ok(kzt_guest_field_status_t status) +{ + return status == KZT_GUEST_FIELD_OK; +} + +static int kzt_rela_enricher_string_has_value( + kzt_guest_field_status_t status) +{ + return status == KZT_GUEST_FIELD_OK || + status == KZT_GUEST_FIELD_TRUNCATED; +} + +static void kzt_rela_enricher_copy_text(char *dst, size_t dst_size, + const char *src) +{ + if (!dst || dst_size == 0) { + return; + } + if (!src) { + dst[0] = '\0'; + return; + } + + snprintf(dst, dst_size, "%s", src); +} + +static int kzt_rela_enricher_snapshot_has_range( + const kzt_guest_object_snapshot_t *snapshot) +{ + if (!snapshot) { + return 0; + } + if (!kzt_rela_enricher_field_ok(snapshot->map_start.status) || + !kzt_rela_enricher_field_ok(snapshot->map_end.status)) { + return 0; + } + + return snapshot->map_start.value < snapshot->map_end.value; +} + +static int kzt_rela_enricher_snapshot_contains( + const kzt_guest_object_snapshot_t *snapshot, uintptr_t address) +{ + if (!kzt_rela_enricher_snapshot_has_range(snapshot)) { + return 0; + } + + return address >= snapshot->map_start.value && + address < snapshot->map_end.value; +} + +static const char *kzt_rela_enricher_snapshot_string( + const kzt_guest_string_field_t *field) +{ + if (!field || !kzt_rela_enricher_string_has_value(field->status)) { + return NULL; + } + + return field->value; +} + +static void kzt_rela_enricher_ref_from_snapshot( + const kzt_guest_object_snapshot_t *snapshot, + kzt_rela_request_enricher_text_t *text, + kzt_patch_object_ref_t *ref) +{ + const char *soname; + const char *path; + + memset(ref, 0, sizeof(*ref)); + if (!snapshot || !text) { + return; + } + + soname = kzt_rela_enricher_snapshot_string(&snapshot->soname); + path = kzt_rela_enricher_snapshot_string(&snapshot->path); + kzt_rela_enricher_copy_text(text->soname, sizeof(text->soname), soname); + kzt_rela_enricher_copy_text(text->path, sizeof(text->path), path); + + ref->known = 1; + ref->link_map_addr = snapshot->link_map_addr; + ref->map_start = snapshot->map_start.value; + ref->map_end = snapshot->map_end.value; + ref->generation = snapshot->generation; + ref->soname = text->soname[0] ? text->soname : NULL; + ref->path = text->path[0] ? text->path : NULL; +} + +static int kzt_rela_enricher_find_unique_source( + kzt_guest_registry_t *registry, uintptr_t address, + kzt_rela_request_enricher_result_t *result) +{ + kzt_guest_registry_dump_t dump = { 0 }; + const kzt_guest_object_snapshot_t *match = NULL; + size_t i; + size_t count = 0; + + if (!registry || !address || !result) { + return -1; + } + if (kzt_guest_registry_dump_snapshot(registry, &dump) != 0) { + return -1; + } + + for (i = 0; i < dump.count; ++i) { + if (dump.objects[i].state == KZT_GUEST_OBJECT_UNLOADING || + dump.objects[i].state == KZT_GUEST_OBJECT_DEAD) { + continue; + } + if (!kzt_rela_enricher_snapshot_contains(&dump.objects[i], address)) { + continue; + } + ++count; + if (!match) { + match = &dump.objects[i]; + } + } + + if (count == 1 && match) { + kzt_rela_enricher_ref_from_snapshot( + match, &result->source_text, &result->source); + result->source_present = result->source.known; + } + + kzt_guest_registry_dump_free(&dump); + return result->source_present ? 0 : -1; +} + +static void kzt_rela_enricher_apply_source( + kzt_rela_immediate_candidate_request_t *request, + const kzt_patch_object_ref_t *source) +{ + const char *fallback_soname; + const char *fallback_path; + + if (!request || !source || !source->known) { + return; + } + + fallback_soname = request->source.soname; + fallback_path = request->source.path; + request->source = *source; + if (!request->source.soname) { + request->source.soname = fallback_soname; + } + if (!request->source.path) { + request->source.path = fallback_path; + } +} + +static void kzt_rela_enricher_apply_dynamic_view( + kzt_rela_immediate_candidate_request_t *request, + const kzt_rela_request_enricher_input_t *input, + kzt_rela_request_enricher_result_t *result) +{ + kzt_guest_dynamic_view_t view; + kzt_guest_field_status_t status; + unsigned long generation; + + request->dynamic_view_available = 0; + request->dynamic_view_generation = 0; + if (!input || !input->registry || !request->source.link_map_addr) { + return; + } + + if (kzt_guest_registry_find_dynamic_view( + input->registry, request->source.link_map_addr, &view, &status, + &generation) != 0) { + return; + } + if (status != KZT_GUEST_FIELD_OK || + view.status != KZT_GUEST_DYNAMIC_COMPLETE) { + return; + } + + request->dynamic_addr = view.dynamic_addr; + request->load_bias = view.load_bias; + request->dynamic_view_available = 1; + request->dynamic_view_generation = generation; + if (result) { + result->dynamic_view_present = 1; + } +} + +static void kzt_rela_enricher_apply_owner( + kzt_rela_immediate_candidate_request_t *request, + const kzt_rela_request_enricher_input_t *input, + kzt_rela_request_enricher_result_t *result) +{ + if (!request || !input || !result) { + return; + } + + request->owner_match = KZT_PATCH_OWNER_UNKNOWN; + memset(&request->current_owner, 0, sizeof(request->current_owner)); + kzt_owner_resolver_init(&result->owner_resolution); + if (input->slot_current_value_is_unresolved_stub) { + request->lazy_binding_deferred = 1; + return; + } + + if (kzt_owner_resolver_resolve_current( + input->registry, request->slot_current_value, + request->expected_guest_target, &result->owner_resolution) != 0) { + return; + } + + request->current_owner = result->owner_resolution.current_owner; + request->owner_match = result->owner_resolution.owner_match; + result->owner_present = request->current_owner.known; +} + +static void kzt_rela_enricher_apply_wrapper( + kzt_rela_immediate_candidate_request_t *request, + const kzt_rela_request_enricher_input_t *input, + kzt_rela_request_enricher_result_t *result) +{ + const kzt_wrapper_probe_bridge_ops_t *bridge_ops; + kzt_wrapper_probe_bridge_ops_t restricted_bridge_ops; + kzt_wrapper_probe_request_t probe_request; + + if (!request || !input || !result) { + return; + } + + request->wrapper_match = KZT_PATCH_WRAPPER_NO_MANIFEST; + request->wrapper_name = NULL; + request->wrapper_version_evidence = KZT_SYMBOL_VERSION_UNKNOWN; + request->wrapper_symbol_version = NULL; + request->native_bridge_target = 0; + + probe_request.symbol_name = request->symbol_name; + probe_request.symbol_version_evidence = request->version_evidence; + probe_request.symbol_version = request->version; + bridge_ops = input->bridge_ops; + if (bridge_ops && + (!request->dynamic_view_available || + request->owner_match != KZT_PATCH_OWNER_MATCH)) { + restricted_bridge_ops = *bridge_ops; + restricted_bridge_ops.add_bridge = NULL; + bridge_ops = &restricted_bridge_ops; + } + if (kzt_wrapper_probe_minimal_manifest( + input->wrapper_manifest, &probe_request, bridge_ops, + &result->wrapper_probe) != 0) { + return; + } + + kzt_wrapper_probe_apply_to_decision_request( + &result->wrapper_probe, &request->wrapper_match, + &request->wrapper_name, &request->wrapper_symbol_version, + &request->native_bridge_target); + request->wrapper_version_evidence = + result->wrapper_probe.wrapper_version_evidence; + result->wrapper_present = 1; +} + +void kzt_rela_request_enricher_result_init( + kzt_rela_request_enricher_result_t *result) +{ + if (!result) { + return; + } + + memset(result, 0, sizeof(*result)); + kzt_owner_resolver_init(&result->owner_resolution); + result->wrapper_probe.wrapper_match = KZT_PATCH_WRAPPER_NO_MANIFEST; +} + +int kzt_rela_immediate_request_enrich( + kzt_rela_immediate_candidate_request_t *request, + const kzt_rela_request_enricher_input_t *input, + kzt_rela_request_enricher_result_t *result) +{ + if (!request || !input || !result) { + return -1; + } + kzt_rela_request_enricher_result_init(result); + + if (request->source.known && request->source.map_start && + kzt_rela_enricher_find_unique_source( + input->registry, request->source.map_start, result) == 0) { + kzt_rela_enricher_apply_source(request, &result->source); + } + + kzt_rela_enricher_apply_dynamic_view(request, input, result); + kzt_rela_enricher_apply_owner(request, input, result); + kzt_rela_enricher_apply_wrapper(request, input, result); + return 0; +} diff --git a/target/i386/latx/context/kzt_rela_runtime_bridge.c b/target/i386/latx/context/kzt_rela_runtime_bridge.c new file mode 100644 index 00000000000..821f12b3b67 --- /dev/null +++ b/target/i386/latx/context/kzt_rela_runtime_bridge.c @@ -0,0 +1,476 @@ +#include "kzt_rela_runtime_bridge.h" + +#include +#include +#include + +#include "box64context.h" +#include "kzt_bridge_exact.h" +#include "khash.h" +#include "librarian_private.h" +#include "library.h" +#include "library_private.h" + +extern uintptr_t CheckBridged(bridge_t *bridge, void *fnc); +extern uintptr_t AddCheckBridge(bridge_t *bridge, wrapper_t wrapper, + void *fnc, int stack_bytes, + const char *name) __attribute__((weak)); +extern void *GetNativeSymbolUnversionned( + void *lib, const char *name) __attribute__((weak)); + +static int kzt_rela_runtime_wrapper_map_entry( + kh_symbolmap_t *map, const char *symbol_name, wrapper_t *wrapper) +{ + khint_t key; + + if (!map || !symbol_name || !wrapper) { + return 0; + } + key = kh_get(symbolmap, map, symbol_name); + if (key == kh_end(map)) { + return 0; + } + + *wrapper = kh_value(map, key); + return *wrapper ? 1 : -1; +} + +typedef struct kzt_rela_runtime_wrapper_candidate { + wrapper_t wrapper; + const char *native_name; + int custom_wrapper; + int stack_bytes; +} kzt_rela_runtime_wrapper_candidate_t; + +typedef struct kzt_rela_runtime_provider_state { + box64context_t *context; + uintptr_t resolved_target; + int discover_bridge; +} kzt_rela_runtime_provider_state_t; + +static void *kzt_rela_runtime_lookup_native( + void *handle, const char *native_name, int custom_wrapper) +{ + void *native_symbol; + + if (!handle || !native_name || !native_name[0]) { + return NULL; + } + native_symbol = dlsym(handle, native_name); + if (!native_symbol && !custom_wrapper && GetNativeSymbolUnversionned) { + native_symbol = GetNativeSymbolUnversionned(handle, native_name); + } + return native_symbol; +} + +static int kzt_rela_runtime_custom_native_name( + const library_t *lib, const char *symbol_name, char *buffer, + size_t buffer_size) +{ + const char *prefix; + + if (!lib || !symbol_name || !buffer || buffer_size == 0) { + return -1; + } + prefix = lib->altmy ? lib->altmy : "my_"; + if (snprintf(buffer, buffer_size, "%s%s", prefix, symbol_name) >= + (int)buffer_size) { + return -1; + } + return 0; +} + +static uintptr_t kzt_rela_runtime_provider_exact_bridge( + library_t *provider, uintptr_t resolved_target, wrapper_t wrapper, + void *native_symbol) +{ + uintptr_t provider_target; + + if (!provider || !provider->priv.w.bridge || !wrapper || !native_symbol) { + return 0; + } + + /* CheckBridged is a read-only lookup keyed by the native symbol. Do not + inspect resolved_target as a onebridge_t until the provider map proves + that the target belongs to this provider. */ + provider_target = CheckBridged(provider->priv.w.bridge, native_symbol); + if (!provider_target || + (resolved_target && provider_target != resolved_target)) { + return 0; + } + + return kzt_bridge_is_exact(provider_target, wrapper, native_symbol) ? + provider_target : 0; +} + +static int kzt_rela_runtime_context_owns_library( + box64context_t *context, library_t *library) +{ + lib_t *scopes[2]; + size_t i; + int j; + + if (!context || !library) { + return 0; + } + scopes[0] = context->maplib; + scopes[1] = context->local_maplib; + for (i = 0; i < sizeof(scopes) / sizeof(scopes[0]); ++i) { + if (!scopes[i]) { + continue; + } + for (j = 0; j < scopes[i]->libsz; ++j) { + if (scopes[i]->libraries[j] == library) { + return 1; + } + } + } + return 0; +} + +static int kzt_rela_runtime_match_lifetime_valid( + const kzt_wrapper_bridge_provider_match_t *match) +{ + box64context_t *context; + library_t *provider; + struct link_map *lookup_owner = NULL; + struct link_map *native_owner = NULL; + Dl_info symbol_info; + void *expected_handle; + void *native_symbol; + + if (!match || !match->context_owner || !match->wrapper_provider || + !match->native_lookup_handle || !match->native_owner || + !match->bridge_owner || !match->bridge_storage || + !match->native_name[0] || !match->abi_wrapper || + !match->native_symbol || + !match->wrapper_provider_lifetime_bound || + !match->native_owner_lifetime_bound || + !match->bridge_owner_lifetime_bound) { + return 0; + } + + context = match->context_owner; + provider = match->wrapper_provider; + expected_handle = match->custom_wrapper ? provider->priv.w.box64lib : + provider->priv.w.lib; + if (!provider->active || provider->type != LIB_WRAPPED || + provider->context != context || + !kzt_rela_runtime_context_owns_library(context, provider) || + match->bridge_owner != provider || + match->bridge_storage != provider->priv.w.bridge || + expected_handle != match->native_lookup_handle || + dlinfo(expected_handle, RTLD_DI_LINKMAP, &lookup_owner) != 0 || + !lookup_owner) { + return 0; + } + + native_symbol = kzt_rela_runtime_lookup_native( + expected_handle, match->native_name, match->custom_wrapper); + if ((uintptr_t)native_symbol != match->native_symbol || + dladdr1(native_symbol, &symbol_info, (void **)&native_owner, + RTLD_DL_LINKMAP) == 0 || + native_owner != match->native_owner) { + return 0; + } + return 1; +} + +static int kzt_rela_runtime_provider_inspect( + void *library, const char *symbol_name, const char *symbol_version, + kzt_wrapper_bridge_provider_match_t *match, void *opaque) +{ + library_t *lib = library; + kzt_rela_runtime_provider_state_t *state = opaque; + wrapper_t wrapper = NULL; + wrapper_t candidate = NULL; + const char *native_name = symbol_name; + char prefixed_name[256]; + char custom_name[256]; + struct link_map *handle_map = NULL; + struct link_map *symbol_map = NULL; + Dl_info symbol_info; + khint_t key; + uintptr_t bridge_target; + int matches = 0; + int allow_altprefix = 1; + + (void)symbol_version; + if (!lib || !state || !match || !symbol_name || + !lib->active || lib->type != LIB_WRAPPED || !lib->priv.w.lib || + !lib->priv.w.bridge || lib->context != state->context || + !kzt_rela_runtime_context_owns_library(state->context, lib) || + (!state->resolved_target && !state->discover_bridge)) { + return 0; + } + + if (kzt_rela_runtime_wrapper_map_entry( + lib->mysymbolmap, symbol_name, &candidate)) { + if (kzt_rela_runtime_custom_native_name( + lib, symbol_name, custom_name, sizeof(custom_name)) != 0) { + return -1; + } + wrapper = candidate; + native_name = custom_name; + ++matches; + allow_altprefix = 0; + match->custom_wrapper = 1; + } + if (kzt_rela_runtime_wrapper_map_entry( + lib->wmysymbolmap, symbol_name, &candidate)) { + if (kzt_rela_runtime_custom_native_name( + lib, symbol_name, custom_name, sizeof(custom_name)) != 0) { + return -1; + } + wrapper = candidate; + native_name = custom_name; + ++matches; + allow_altprefix = 0; + match->custom_wrapper = 1; + } + if (kzt_rela_runtime_wrapper_map_entry( + lib->stsymbolmap, symbol_name, &candidate)) { + if (kzt_rela_runtime_custom_native_name( + lib, symbol_name, custom_name, sizeof(custom_name)) != 0) { + return -1; + } + wrapper = candidate; + native_name = custom_name; + ++matches; + allow_altprefix = 0; + match->custom_wrapper = 1; + match->stack_bytes = sizeof(void *); + } + + if (kzt_rela_runtime_wrapper_map_entry( + lib->symbolmap, symbol_name, &candidate)) { + wrapper = candidate; + ++matches; + } + if (kzt_rela_runtime_wrapper_map_entry( + lib->wsymbolmap, symbol_name, &candidate)) { + wrapper = candidate; + ++matches; + } + if (lib->symbol2map) { + key = kh_get(symbol2map, lib->symbol2map, symbol_name); + if (key != kh_end(lib->symbol2map)) { + wrapper = kh_value(lib->symbol2map, key).w; + native_name = kh_value(lib->symbol2map, key).name; + allow_altprefix = 0; + ++matches; + } + } + if (matches == 0) { + return 0; + } + if (matches != 1 || !wrapper || !native_name || !native_name[0]) { + return -1; + } + + if (lib->priv.w.altprefix && allow_altprefix) { + if (snprintf(prefixed_name, sizeof(prefixed_name), "%s%s", + lib->priv.w.altprefix, symbol_name) >= + (int)sizeof(prefixed_name)) { + return -1; + } + native_name = prefixed_name; + } + + /* symbol_version describes the guest relocation. The host provider may + legitimately export the same ABI wrapper under a different GLIBC + version, so native lookup keeps the wrapped-library semantics. */ + match->native_symbol = (uintptr_t)kzt_rela_runtime_lookup_native( + match->custom_wrapper ? lib->priv.w.box64lib : lib->priv.w.lib, + native_name, match->custom_wrapper); + if (symbol_version && symbol_version[0] && + (strstr(symbol_version, "NOT_REAL") || + strstr(symbol_version, "UNSUPPORTED"))) { + return -1; + } + if (symbol_version && symbol_version[0] && !match->custom_wrapper && + state->resolved_target && + !kzt_rela_runtime_provider_exact_bridge( + lib, state->resolved_target, wrapper, + (void *)match->native_symbol)) { + uintptr_t versioned_symbol = (uintptr_t)dlvsym( + lib->priv.w.lib, native_name, symbol_version); + + if (!versioned_symbol || + !kzt_rela_runtime_provider_exact_bridge( + lib, state->resolved_target, wrapper, + (void *)versioned_symbol)) { + return -1; + } + match->native_symbol = versioned_symbol; + } + if (!match->native_symbol) { + return -1; + } + if (dlinfo(match->custom_wrapper ? lib->priv.w.box64lib : lib->priv.w.lib, + RTLD_DI_LINKMAP, &handle_map) != 0 || + !handle_map || + dladdr1((void *)match->native_symbol, &symbol_info, + (void **)&symbol_map, RTLD_DL_LINKMAP) == 0 || + symbol_map != handle_map) { + return -1; + } + bridge_target = kzt_rela_runtime_provider_exact_bridge( + lib, state->resolved_target, wrapper, (void *)match->native_symbol); + if (!bridge_target && + (CheckBridged(lib->priv.w.bridge, (void *)match->native_symbol) || + !state->discover_bridge)) { + return -1; + } + + match->wrapper_name = lib->name; + snprintf(match->native_name, sizeof(match->native_name), "%s", + native_name); + match->abi_wrapper = wrapper; + match->resolved_bridge_target = bridge_target; + match->context_owner = state->context; + match->wrapper_provider = lib; + match->native_lookup_handle = match->custom_wrapper ? lib->priv.w.box64lib : + lib->priv.w.lib; + match->native_owner = symbol_map; + match->bridge_owner = lib; + match->bridge_storage = lib->priv.w.bridge; + match->resolved_bridge_exact = bridge_target != 0; + /* Wrapped native handles stay live in a root context scope until + NativeLib_FinishFini runs during context teardown. */ + match->wrapper_provider_lifetime_bound = 1; + match->native_owner_lifetime_bound = 1; + match->bridge_owner_lifetime_bound = 1; + return 1; +} + +static uintptr_t kzt_rela_runtime_provider_check( + const kzt_wrapper_bridge_provider_match_t *match, void *opaque) +{ + library_t *provider; + + (void)opaque; + if (!match || !match->bridge_owner || !match->abi_wrapper || + !match->native_symbol || + !kzt_rela_runtime_match_lifetime_valid(match) || + (!!match->resolved_bridge_target != !!match->resolved_bridge_exact)) { + return 0; + } + provider = match->bridge_owner; + return kzt_rela_runtime_provider_exact_bridge( + provider, match->resolved_bridge_target, match->abi_wrapper, + (void *)match->native_symbol); +} + +static uintptr_t kzt_rela_runtime_provider_add( + const kzt_wrapper_bridge_provider_match_t *match, + const kzt_wrapper_probe_bridge_request_t *request, void *opaque) +{ + library_t *provider; + uintptr_t target; + + (void)opaque; + if (!match || !request || !match->bridge_owner || !match->abi_wrapper || + !match->native_symbol || + !kzt_rela_runtime_match_lifetime_valid(match) || + match->resolved_bridge_target || match->resolved_bridge_exact || + request->native_symbol != match->native_symbol || + !request->symbol_name || !request->symbol_name[0]) { + return 0; + } + + provider = match->bridge_owner; + if (!provider->priv.w.bridge || !AddCheckBridge) { + return 0; + } + target = AddCheckBridge(provider->priv.w.bridge, match->abi_wrapper, + (void *)match->native_symbol, match->stack_bytes, + request->symbol_name); + if (!target || + kzt_rela_runtime_provider_exact_bridge( + provider, target, match->abi_wrapper, + (void *)match->native_symbol) != target) { + return 0; + } + return target; +} + +static int kzt_rela_runtime_wrapper_provider_prepare_mode( + box64context_t *context, library_t *resolved_provider, + uintptr_t resolved_target, const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version, int discover_bridge, + kzt_wrapper_bridge_provider_t *provider) +{ + kzt_rela_runtime_provider_state_t state = { + .context = context, + .resolved_target = resolved_target, + .discover_bridge = discover_bridge, + }; + kzt_wrapper_bridge_provider_runtime_ops_t runtime_ops = { + .inspect_library = kzt_rela_runtime_provider_inspect, + .check_bridge = kzt_rela_runtime_provider_check, + .add_bridge = kzt_rela_runtime_provider_add, + .opaque = &state, + }; + void *libraries[] = { resolved_provider }; + int status; + + if (!provider) { + return -1; + } + status = kzt_wrapper_bridge_provider_prepare_with_version_evidence( + provider, libraries, 1, symbol_name, version_evidence, + symbol_version, &runtime_ops); + provider->runtime_ops.opaque = NULL; + return status; +} + +int kzt_rela_runtime_wrapper_provider_prepare( + box64context_t *context, library_t *resolved_provider, + uintptr_t resolved_target, const char *symbol_name, + const char *symbol_version, + kzt_wrapper_bridge_provider_t *provider) +{ + return kzt_rela_runtime_wrapper_provider_prepare_mode( + context, resolved_provider, resolved_target, symbol_name, + symbol_version && symbol_version[0] ? KZT_SYMBOL_VERSION_VERSIONED : + KZT_SYMBOL_VERSION_UNKNOWN, + symbol_version, 0, provider); +} + +int kzt_rela_runtime_wrapper_provider_prepare_with_version_evidence( + box64context_t *context, library_t *resolved_provider, + uintptr_t resolved_target, const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version, + kzt_wrapper_bridge_provider_t *provider) +{ + return kzt_rela_runtime_wrapper_provider_prepare_mode( + context, resolved_provider, resolved_target, symbol_name, + version_evidence, symbol_version, 0, provider); +} + +int kzt_rela_runtime_wrapper_provider_discover( + box64context_t *context, library_t *resolved_provider, + const char *symbol_name, const char *symbol_version, + kzt_wrapper_bridge_provider_t *provider) +{ + return kzt_rela_runtime_wrapper_provider_prepare_mode( + context, resolved_provider, 0, symbol_name, + symbol_version && symbol_version[0] ? KZT_SYMBOL_VERSION_VERSIONED : + KZT_SYMBOL_VERSION_UNKNOWN, + symbol_version, 1, provider); +} + +int kzt_rela_runtime_wrapper_provider_discover_with_version_evidence( + box64context_t *context, library_t *resolved_provider, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version, + kzt_wrapper_bridge_provider_t *provider) +{ + return kzt_rela_runtime_wrapper_provider_prepare_mode( + context, resolved_provider, 0, symbol_name, version_evidence, + symbol_version, 1, provider); +} diff --git a/target/i386/latx/context/kzt_runtime_candidate_shadow.c b/target/i386/latx/context/kzt_runtime_candidate_shadow.c new file mode 100644 index 00000000000..c294be625af --- /dev/null +++ b/target/i386/latx/context/kzt_runtime_candidate_shadow.c @@ -0,0 +1,388 @@ +#include "kzt_runtime_candidate_shadow.h" + +#include + +static void kzt_runtime_candidate_shadow_result_init( + kzt_runtime_candidate_shadow_result_t *result) +{ + memset(result, 0, sizeof(*result)); + result->status = KZT_RUNTIME_CANDIDATE_SHADOW_OK; + result->reason = KZT_RUNTIME_CANDIDATE_SHADOW_REASON_NONE; +} + +static int kzt_runtime_candidate_shadow_output_sizes_valid( + const kzt_runtime_candidate_shadow_input_t *input) +{ + const kzt_runtime_got_plt_candidate_request_t *collector; + + if (!input || !input->collector_request) { + return 0; + } + + collector = input->collector_request; + if ((!input->records && input->record_capacity > 0) || + input->record_capacity > + SIZE_MAX / sizeof(kzt_runtime_candidate_shadow_record_t)) { + return 0; + } + + if ((!collector->candidates && collector->candidate_capacity > 0) || + collector->candidate_capacity > + SIZE_MAX / sizeof(kzt_patch_candidate_t)) { + return 0; + } + + return 1; +} + +static void kzt_runtime_candidate_shadow_clear_outputs( + const kzt_runtime_candidate_shadow_input_t *input) +{ + const kzt_runtime_got_plt_candidate_request_t *collector; + + if (!input || !input->collector_request) { + return; + } + + collector = input->collector_request; + if (collector->candidates && collector->candidate_capacity > 0) { + memset(collector->candidates, 0, + collector->candidate_capacity * + sizeof(*collector->candidates)); + } + if (collector->string_storage && collector->string_storage_size > 0) { + memset(collector->string_storage, 0, + collector->string_storage_size); + } + if (input->records && input->record_capacity > 0) { + memset(input->records, 0, + input->record_capacity * sizeof(*input->records)); + } +} + +static int kzt_runtime_candidate_shadow_registry_generation( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long *generation) +{ + kzt_guest_registry_dump_t dump = { 0 }; + size_t matches = 0; + size_t i; + + if (!registry || link_map_addr == 0 || !generation) { + return -1; + } + + if (kzt_guest_registry_dump_snapshot(registry, &dump) != 0) { + return -1; + } + + for (i = 0; i < dump.count; ++i) { + if (dump.objects[i].link_map_addr != link_map_addr) { + continue; + } + if (dump.objects[i].state == KZT_GUEST_OBJECT_UNLOADING || + dump.objects[i].state == KZT_GUEST_OBJECT_DEAD) { + continue; + } + + *generation = dump.objects[i].generation; + ++matches; + } + + kzt_guest_registry_dump_free(&dump); + return matches == 1 && *generation != 0 ? 0 : -1; +} + +static int kzt_runtime_candidate_shadow_query_generation( + const kzt_runtime_candidate_shadow_input_t *input, + uintptr_t link_map_addr, + unsigned long *generation) +{ + *generation = 0; + if (input->query_generation) { + return input->query_generation( + link_map_addr, generation, + input->generation_query_opaque); + } + + return kzt_runtime_candidate_shadow_registry_generation( + input->registry, link_map_addr, generation); +} + +static int kzt_runtime_candidate_shadow_generation_valid( + const kzt_runtime_candidate_shadow_input_t *input, + const kzt_patch_candidate_t *candidate) +{ + unsigned long current_generation = 0; + + if (!candidate || !candidate->source.known || + candidate->source.link_map_addr == 0 || + candidate->source.generation == 0 || + candidate->dynamic_view_generation == 0) { + return 0; + } + + if (kzt_runtime_candidate_shadow_query_generation( + input, candidate->source.link_map_addr, + ¤t_generation) != 0 || + current_generation == 0) { + return 0; + } + + return candidate->source.generation == current_generation && + candidate->dynamic_view_generation == current_generation; +} + +static int kzt_runtime_candidate_shadow_batch_generation_valid( + const kzt_runtime_candidate_shadow_input_t *input, + size_t candidate_count) +{ + size_t i; + + for (i = 0; i < candidate_count; ++i) { + if (!kzt_runtime_candidate_shadow_generation_valid( + input, + &input->collector_request->candidates[i])) { + return 0; + } + } + + return 1; +} + +static int kzt_runtime_candidate_shadow_is_precise_stub( + const kzt_runtime_candidate_shadow_input_t *input, + const kzt_patch_candidate_t *candidate) +{ + if (!candidate || !candidate->slot_current_value_present || + !input->classify_stub) { + return 0; + } + + if (candidate->reloc_type != KZT_PATCH_RELOCATION_JUMP_SLOT && + candidate->reloc_type != KZT_PATCH_RELOCATION_GLOB_DAT) { + return 0; + } + + return input->classify_stub( + candidate, input->stub_classifier_opaque) == + KZT_RUNTIME_CANDIDATE_SHADOW_STUB_MATCH; +} + +static int kzt_runtime_candidate_shadow_decision_valid( + const kzt_patch_decision_t *decision) +{ + return decision && + decision->kind >= KZT_PATCH_DECISION_ERROR && + (size_t)decision->kind < + KZT_RUNTIME_CANDIDATE_SHADOW_DECISION_BUCKETS && + decision->reason >= KZT_PATCH_REASON_ERROR_INVALID_ARGUMENT && + (size_t)decision->reason < + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_BUCKETS; +} + +static void kzt_runtime_candidate_shadow_enrich_owner( + const kzt_runtime_candidate_shadow_input_t *input, + kzt_patch_candidate_t *candidate, + kzt_runtime_candidate_shadow_record_t *record) +{ + uintptr_t expected_guest_target = 0; + + memset(&candidate->current_owner, 0, sizeof(candidate->current_owner)); + candidate->owner_match = KZT_PATCH_OWNER_UNKNOWN; + kzt_owner_resolver_init(&record->owner_resolution); + + if (candidate->lazy_binding_deferred || !input->registry || + !input->resolve_expected_guest_target) { + return; + } + + if (input->resolve_expected_guest_target( + candidate, &expected_guest_target, + input->expected_target_opaque) != 0 || + expected_guest_target == 0) { + return; + } + + if (kzt_owner_resolver_resolve_current( + input->registry, candidate->slot_current_value, + expected_guest_target, &record->owner_resolution) != 0) { + return; + } + + candidate->current_owner = + record->owner_resolution.current_owner; + candidate->owner_match = record->owner_resolution.owner_match; +} + +static int kzt_runtime_candidate_shadow_enrich_wrapper( + const kzt_runtime_candidate_shadow_input_t *input, + kzt_patch_candidate_t *candidate, + kzt_runtime_candidate_shadow_record_t *record) +{ + kzt_wrapper_probe_bridge_ops_t readonly_bridge_ops = { 0 }; + const kzt_wrapper_probe_bridge_ops_t *bridge_ops = NULL; + kzt_wrapper_probe_request_t probe_request = { + .symbol_name = candidate->symbol_name, + .symbol_version_evidence = candidate->version_evidence, + .symbol_version = candidate->version, + }; + + candidate->wrapper_match = KZT_PATCH_WRAPPER_NO_MANIFEST; + candidate->wrapper_name = NULL; + candidate->wrapper_version_evidence = KZT_SYMBOL_VERSION_UNKNOWN; + candidate->wrapper_symbol_version = NULL; + candidate->bridge_target = 0; + + if (input->bridge_ops) { + readonly_bridge_ops.check_bridge = + input->bridge_ops->check_bridge; + readonly_bridge_ops.add_bridge = NULL; + readonly_bridge_ops.opaque = input->bridge_ops->opaque; + bridge_ops = &readonly_bridge_ops; + } + + if (kzt_wrapper_probe_minimal_manifest( + input->wrapper_manifest, &probe_request, bridge_ops, + &record->wrapper_probe) != 0) { + return -1; + } + + kzt_wrapper_probe_apply_to_candidate(&record->wrapper_probe, + candidate); + return 0; +} + +static void kzt_runtime_candidate_shadow_fail_open( + const kzt_runtime_candidate_shadow_input_t *input, + kzt_runtime_candidate_shadow_result_t *result, + kzt_runtime_candidate_shadow_reason_t reason) +{ + kzt_runtime_candidate_shadow_clear_outputs(input); + result->status = KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN; + result->reason = reason; + result->collector_result.candidate_count = 0; + result->candidate_count = 0; + result->record_count = 0; + result->eligible_count = 0; + result->observe_only_count = 0; + memset(result->decision_histogram, 0, + sizeof(result->decision_histogram)); + memset(result->reason_histogram, 0, + sizeof(result->reason_histogram)); +} + +int kzt_runtime_candidate_shadow_run( + const kzt_runtime_candidate_shadow_input_t *input, + kzt_runtime_candidate_shadow_result_t *result) +{ + const kzt_runtime_got_plt_candidate_request_t *collector; + size_t candidate_count; + size_t i; + int collect_status; + + if (!result) { + return -1; + } + + kzt_runtime_candidate_shadow_result_init(result); + if (!kzt_runtime_candidate_shadow_output_sizes_valid(input)) { + result->status = KZT_RUNTIME_CANDIDATE_SHADOW_ERROR; + result->reason = + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_INVALID_ARGUMENT; + return -1; + } + + collector = input->collector_request; + kzt_runtime_candidate_shadow_clear_outputs(input); + collect_status = kzt_runtime_got_plt_candidates_collect( + collector, &result->collector_result); + if (collect_status != 0 || + result->collector_result.status == + KZT_RUNTIME_GOT_PLT_CANDIDATE_ERROR) { + kzt_runtime_candidate_shadow_clear_outputs(input); + result->status = KZT_RUNTIME_CANDIDATE_SHADOW_ERROR; + result->reason = + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_COLLECTOR_ERROR; + return -1; + } + + if (result->collector_result.status == + KZT_RUNTIME_GOT_PLT_CANDIDATE_FAIL_OPEN) { + kzt_runtime_candidate_shadow_fail_open( + input, result, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_COLLECTOR_FAIL_OPEN); + return 0; + } + + candidate_count = result->collector_result.candidate_count; + if (candidate_count > input->record_capacity) { + kzt_runtime_candidate_shadow_fail_open( + input, result, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_RECORD_CAPACITY_EXCEEDED); + return 0; + } + + if (!kzt_runtime_candidate_shadow_batch_generation_valid( + input, candidate_count)) { + kzt_runtime_candidate_shadow_fail_open( + input, result, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_OBJECT_GENERATION_CHANGED); + return 0; + } + + for (i = 0; i < candidate_count; ++i) { + kzt_patch_candidate_t *candidate = &collector->candidates[i]; + kzt_runtime_candidate_shadow_record_t *record = + &input->records[i]; + + record->candidate_index = i; + candidate->lazy_binding_deferred = + kzt_runtime_candidate_shadow_is_precise_stub(input, + candidate); + kzt_runtime_candidate_shadow_enrich_owner(input, candidate, + record); + if (kzt_runtime_candidate_shadow_enrich_wrapper( + input, candidate, record) != 0 || + kzt_patch_planner_decide(candidate, + &record->decision) != 0) { + kzt_runtime_candidate_shadow_fail_open( + input, result, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_PLANNER_ERROR); + return 0; + } + + if (!kzt_runtime_candidate_shadow_decision_valid( + &record->decision)) { + kzt_runtime_candidate_shadow_fail_open( + input, result, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_PLANNER_ERROR); + return 0; + } + + record->audit_only = 1; + record->legacy_target_consumed = 0; + record->observe_only = + candidate->reloc_type == KZT_PATCH_RELOCATION_GLOB_DAT; + record->eligible = + record->decision.kind == KZT_PATCH_DECISION_APPROVED && + !record->observe_only; + result->decision_histogram[record->decision.kind]++; + result->reason_histogram[record->decision.reason]++; + result->eligible_count += record->eligible; + result->observe_only_count += record->observe_only; + } + + if (!kzt_runtime_candidate_shadow_batch_generation_valid( + input, candidate_count)) { + kzt_runtime_candidate_shadow_fail_open( + input, result, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_OBJECT_GENERATION_CHANGED); + return 0; + } + + result->candidate_count = candidate_count; + result->record_count = candidate_count; + return 0; +} diff --git a/target/i386/latx/context/kzt_runtime_got_plt_candidate.c b/target/i386/latx/context/kzt_runtime_got_plt_candidate.c new file mode 100644 index 00000000000..b329acb2e97 --- /dev/null +++ b/target/i386/latx/context/kzt_runtime_got_plt_candidate.c @@ -0,0 +1,995 @@ +#include "kzt_runtime_got_plt_candidate.h" + +#include + +#include "elf.h" + +#define KZT_RUNTIME_GOT_PLT_VERSION_SCAN_LIMIT 128 + +typedef struct kzt_runtime_got_plt_string_pool { + char *storage; + size_t size; + size_t used; +} kzt_runtime_got_plt_string_pool_t; + +static void kzt_runtime_got_plt_result_clear( + kzt_runtime_got_plt_candidate_result_t *result) +{ + memset(result, 0, sizeof(*result)); + result->status = KZT_RUNTIME_GOT_PLT_CANDIDATE_OK; + result->reason = KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_NONE; + result->table_kind = KZT_PATCH_TABLE_UNKNOWN; + result->version_evidence = KZT_SYMBOL_VERSION_UNKNOWN; +} + +static int kzt_runtime_got_plt_result_set( + kzt_runtime_got_plt_candidate_result_t *result, + kzt_runtime_got_plt_candidate_status_t status, + kzt_runtime_got_plt_candidate_reason_t reason, + kzt_patch_reason_t patch_reason, + kzt_patch_table_kind_t table_kind, + size_t entry_index, + uintptr_t entry_addr, + uintptr_t slot_addr, + uintptr_t read_error_addr) +{ + result->status = status; + result->reason = reason; + result->patch_reason_present = 1; + result->patch_reason = patch_reason; + result->candidate_count = 0; + result->table_kind = table_kind; + result->entry_index = entry_index; + result->entry_addr = entry_addr; + result->slot_addr = slot_addr; + result->read_error_addr = read_error_addr; + return 0; +} + +static int kzt_runtime_got_plt_fail_open( + kzt_runtime_got_plt_candidate_result_t *result, + kzt_runtime_got_plt_candidate_reason_t reason, + kzt_patch_reason_t patch_reason, + kzt_patch_table_kind_t table_kind, + size_t entry_index, + uintptr_t entry_addr, + uintptr_t slot_addr, + uintptr_t read_error_addr) +{ + return kzt_runtime_got_plt_result_set( + result, KZT_RUNTIME_GOT_PLT_CANDIDATE_FAIL_OPEN, reason, + patch_reason, table_kind, entry_index, entry_addr, slot_addr, + read_error_addr); +} + +static int kzt_runtime_got_plt_u64_to_size(uint64_t value, size_t *out) +{ + if (value > (uint64_t)SIZE_MAX) { + return -1; + } + + *out = (size_t)value; + return 0; +} + +static int kzt_runtime_got_plt_add_u64(uintptr_t base, + uint64_t offset, + uintptr_t *out) +{ + if (offset > (uint64_t)UINTPTR_MAX) { + return -1; + } + + if (base > UINTPTR_MAX - (uintptr_t)offset) { + return -1; + } + + *out = base + (uintptr_t)offset; + return 0; +} + +static int kzt_runtime_got_plt_entry_addr(uintptr_t table_addr, + size_t entry_size, + size_t index, + uintptr_t *entry_addr) +{ + uintptr_t offset; + + if (entry_size == 0 || index > UINTPTR_MAX / entry_size) { + return -1; + } + + offset = index * entry_size; + if (table_addr > UINTPTR_MAX - offset) { + return -1; + } + + *entry_addr = table_addr + offset; + return 0; +} + +static int kzt_runtime_got_plt_table_bounds_valid(uintptr_t table_addr, + size_t table_size) +{ + if (table_size == 0) { + return 0; + } + + if (table_addr > UINTPTR_MAX - (table_size - 1)) { + return -1; + } + + return 0; +} + +static int kzt_runtime_got_plt_has_rel_table( + const kzt_guest_dynamic_view_t *view) +{ + return view->rel.present || view->relsz.present || view->relent.present; +} + +static int kzt_runtime_got_plt_has_plt_table( + const kzt_guest_dynamic_view_t *view) +{ + return view->jmprel.present || view->pltrelsz.present || + view->pltrel.present; +} + +static int kzt_runtime_got_plt_has_rela_table( + const kzt_guest_dynamic_view_t *view) +{ + return view->rela.present || view->relasz.present || + view->relaent.present; +} + +static int kzt_runtime_got_plt_read_rela( + const kzt_guest_link_map_reader_ops_t *reader_ops, + uintptr_t entry_addr, + Elf64_Rela *rela) +{ + return reader_ops->read_memory(entry_addr, rela, sizeof(*rela), + reader_ops->opaque); +} + +static int kzt_runtime_got_plt_read_slot( + const kzt_guest_link_map_reader_ops_t *reader_ops, + uintptr_t slot_addr, + uintptr_t *slot_value) +{ + uint64_t raw_value = 0; + + if (reader_ops->read_memory(slot_addr, &raw_value, sizeof(raw_value), + reader_ops->opaque) != 0) { + return -1; + } + + if (raw_value > (uint64_t)UINTPTR_MAX) { + return -1; + } + + *slot_value = (uintptr_t)raw_value; + return 0; +} + +static int kzt_runtime_got_plt_read_sym( + const kzt_guest_link_map_reader_ops_t *reader_ops, + uintptr_t entry_addr, + Elf64_Sym *sym) +{ + return reader_ops->read_memory(entry_addr, sym, sizeof(*sym), + reader_ops->opaque); +} + +static int kzt_runtime_got_plt_read_half( + const kzt_guest_link_map_reader_ops_t *reader_ops, + uintptr_t entry_addr, + Elf64_Half *value) +{ + return reader_ops->read_memory(entry_addr, value, sizeof(*value), + reader_ops->opaque); +} + +static int kzt_runtime_got_plt_read_verneed( + const kzt_guest_link_map_reader_ops_t *reader_ops, + uintptr_t entry_addr, + Elf64_Verneed *verneed) +{ + return reader_ops->read_memory(entry_addr, verneed, sizeof(*verneed), + reader_ops->opaque); +} + +static int kzt_runtime_got_plt_read_vernaux( + const kzt_guest_link_map_reader_ops_t *reader_ops, + uintptr_t entry_addr, + Elf64_Vernaux *vernaux) +{ + return reader_ops->read_memory(entry_addr, vernaux, sizeof(*vernaux), + reader_ops->opaque); +} + +static int kzt_runtime_got_plt_read_verdef( + const kzt_guest_link_map_reader_ops_t *reader_ops, + uintptr_t entry_addr, + Elf64_Verdef *verdef) +{ + return reader_ops->read_memory(entry_addr, verdef, sizeof(*verdef), + reader_ops->opaque); +} + +static int kzt_runtime_got_plt_read_verdaux( + const kzt_guest_link_map_reader_ops_t *reader_ops, + uintptr_t entry_addr, + Elf64_Verdaux *verdaux) +{ + return reader_ops->read_memory(entry_addr, verdaux, sizeof(*verdaux), + reader_ops->opaque); +} + +static int kzt_runtime_got_plt_read_string( + const kzt_runtime_got_plt_candidate_request_t *request, + kzt_runtime_got_plt_string_pool_t *pool, + uint64_t string_offset, + const char **out) +{ + uintptr_t string_addr = 0; + uint64_t remaining; + char *terminator; + size_t readable; + size_t start; + + if (!request->view->strtab.present || !request->view->strsz.present || + string_offset >= request->view->strsz.value) { + return -1; + } + + if (kzt_runtime_got_plt_add_u64((uintptr_t)request->view->strtab.value, + string_offset, &string_addr) != 0) { + return -1; + } + + remaining = request->view->strsz.value - string_offset; + if (remaining > (uint64_t)SIZE_MAX) { + return -1; + } + + if (!pool->storage || pool->used >= pool->size) { + return -1; + } + start = pool->used; + readable = pool->size - start; + if (remaining < readable) { + readable = (size_t)remaining; + } + if (request->reader_ops->read_memory( + string_addr, &pool->storage[start], readable, + request->reader_ops->opaque) != 0) { + return -1; + } + terminator = memchr(&pool->storage[start], '\0', readable); + if (!terminator || terminator == &pool->storage[start]) { + return -1; + } + pool->used = (size_t)(terminator - pool->storage) + 1; + *out = &pool->storage[start]; + return 0; +} + +static int kzt_runtime_got_plt_read_symbol_name( + const kzt_runtime_got_plt_candidate_request_t *request, + kzt_runtime_got_plt_candidate_result_t *result, + kzt_runtime_got_plt_string_pool_t *pool, + unsigned long symbol_index, + kzt_patch_table_kind_t table_kind, + size_t entry_index, + uintptr_t entry_addr, + const char **symbol_name) +{ + uintptr_t sym_addr = 0; + size_t syment = 0; + Elf64_Sym sym; + + if (!request->view->symtab.present || !request->view->syment.present || + !request->view->strtab.present || !request->view->strsz.present) { + kzt_runtime_got_plt_fail_open( + result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MISSING_DYNAMIC_FIELD, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_NAME, table_kind, + entry_index, entry_addr, 0, 0); + return -1; + } + + if (kzt_runtime_got_plt_u64_to_size(request->view->syment.value, + &syment) != 0 || + syment != sizeof(Elf64_Sym) || + kzt_runtime_got_plt_entry_addr((uintptr_t)request->view->symtab.value, + syment, symbol_index, + &sym_addr) != 0) { + kzt_runtime_got_plt_fail_open( + result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_SYMBOL_NAME, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_NAME, table_kind, + entry_index, entry_addr, 0, 0); + return -1; + } + + if (kzt_runtime_got_plt_read_sym(request->reader_ops, sym_addr, + &sym) != 0) { + kzt_runtime_got_plt_fail_open( + result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_SYMBOL_READ_FAILED, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_NAME, table_kind, + entry_index, entry_addr, 0, sym_addr); + return -1; + } + + if (kzt_runtime_got_plt_read_string(request, pool, sym.st_name, + symbol_name) != 0) { + kzt_runtime_got_plt_fail_open( + result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_SYMBOL_NAME, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_NAME, table_kind, + entry_index, entry_addr, 0, 0); + return -1; + } + + return 0; +} + +static int kzt_runtime_got_plt_read_version_string_from_verneed( + const kzt_runtime_got_plt_candidate_request_t *request, + kzt_runtime_got_plt_string_pool_t *pool, + unsigned int version, + const char **version_name, + uintptr_t *read_error_addr) +{ + uintptr_t verneed_addr; + size_t verneed_count; + size_t i; + + if (!request->view->verneed.present) { + return 1; + } + + verneed_addr = (uintptr_t)request->view->verneed.value; + if (request->view->verneednum.present && + request->view->verneednum.value < KZT_RUNTIME_GOT_PLT_VERSION_SCAN_LIMIT) { + verneed_count = (size_t)request->view->verneednum.value; + } else { + verneed_count = KZT_RUNTIME_GOT_PLT_VERSION_SCAN_LIMIT; + } + + for (i = 0; i < verneed_count; ++i) { + Elf64_Verneed verneed; + uintptr_t aux_addr; + size_t j; + + if (kzt_runtime_got_plt_read_verneed(request->reader_ops, + verneed_addr, &verneed) != 0) { + *read_error_addr = verneed_addr; + return -1; + } + + if (kzt_runtime_got_plt_add_u64(verneed_addr, verneed.vn_aux, + &aux_addr) != 0) { + return -1; + } + + for (j = 0; j < verneed.vn_cnt && + j < KZT_RUNTIME_GOT_PLT_VERSION_SCAN_LIMIT; ++j) { + Elf64_Vernaux aux; + + if (kzt_runtime_got_plt_read_vernaux(request->reader_ops, + aux_addr, &aux) != 0) { + *read_error_addr = aux_addr; + return -1; + } + + if ((aux.vna_other & 0x7fff) == version) { + return kzt_runtime_got_plt_read_string( + request, pool, aux.vna_name, version_name) == 0 + ? 0 + : -1; + } + + if (aux.vna_next == 0) { + break; + } + if (kzt_runtime_got_plt_add_u64(aux_addr, aux.vna_next, + &aux_addr) != 0) { + return -1; + } + } + + if (verneed.vn_next == 0) { + break; + } + if (kzt_runtime_got_plt_add_u64(verneed_addr, verneed.vn_next, + &verneed_addr) != 0) { + return -1; + } + } + + return 1; +} + +static int kzt_runtime_got_plt_read_version_string_from_verdef( + const kzt_runtime_got_plt_candidate_request_t *request, + kzt_runtime_got_plt_string_pool_t *pool, + unsigned int version, + const char **version_name, + uintptr_t *read_error_addr) +{ + uintptr_t verdef_addr; + size_t verdef_count; + size_t i; + + if (!request->view->verdef.present) { + return 1; + } + + verdef_addr = (uintptr_t)request->view->verdef.value; + if (request->view->verdefnum.present && + request->view->verdefnum.value < KZT_RUNTIME_GOT_PLT_VERSION_SCAN_LIMIT) { + verdef_count = (size_t)request->view->verdefnum.value; + } else { + verdef_count = KZT_RUNTIME_GOT_PLT_VERSION_SCAN_LIMIT; + } + + for (i = 0; i < verdef_count; ++i) { + Elf64_Verdef verdef; + + if (kzt_runtime_got_plt_read_verdef(request->reader_ops, + verdef_addr, &verdef) != 0) { + *read_error_addr = verdef_addr; + return -1; + } + + if (verdef.vd_ndx == version) { + Elf64_Verdaux aux; + uintptr_t aux_addr; + + if (verdef.vd_cnt < 1 || + kzt_runtime_got_plt_add_u64(verdef_addr, verdef.vd_aux, + &aux_addr) != 0) { + return -1; + } + + if (kzt_runtime_got_plt_read_verdaux(request->reader_ops, + aux_addr, &aux) != 0) { + *read_error_addr = aux_addr; + return -1; + } + + return kzt_runtime_got_plt_read_string( + request, pool, aux.vda_name, version_name) == 0 + ? 0 + : -1; + } + + if (verdef.vd_next == 0) { + break; + } + if (kzt_runtime_got_plt_add_u64(verdef_addr, verdef.vd_next, + &verdef_addr) != 0) { + return -1; + } + } + + return 1; +} + +static int kzt_runtime_got_plt_read_symbol_version( + const kzt_runtime_got_plt_candidate_request_t *request, + kzt_runtime_got_plt_candidate_result_t *result, + kzt_runtime_got_plt_string_pool_t *pool, + unsigned long symbol_index, + kzt_patch_table_kind_t table_kind, + size_t entry_index, + uintptr_t entry_addr, + kzt_symbol_version_evidence_t *version_evidence, + const char **version_name) +{ + uintptr_t versym_addr = 0; + uintptr_t read_error_addr = 0; + Elf64_Half raw_version = 0; + unsigned int version; + int lookup_status; + + *version_evidence = KZT_SYMBOL_VERSION_UNKNOWN; + *version_name = NULL; + if (!request->view->versym.present) { + *version_evidence = KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + result->version_evidence = *version_evidence; + return 0; + } + + if (kzt_runtime_got_plt_entry_addr((uintptr_t)request->view->versym.value, + sizeof(raw_version), symbol_index, + &versym_addr) != 0) { + *version_evidence = KZT_SYMBOL_VERSION_ERROR; + result->version_evidence = *version_evidence; + kzt_runtime_got_plt_fail_open( + result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_SYMBOL_VERSION, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_VERSION, table_kind, + entry_index, entry_addr, 0, 0); + return -1; + } + + if (kzt_runtime_got_plt_read_half(request->reader_ops, versym_addr, + &raw_version) != 0) { + *version_evidence = KZT_SYMBOL_VERSION_ERROR; + result->version_evidence = *version_evidence; + kzt_runtime_got_plt_fail_open( + result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_VERSION_READ_FAILED, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_VERSION, table_kind, + entry_index, entry_addr, 0, versym_addr); + return -1; + } + + version = raw_version & 0x7fff; + if (version < 2) { + *version_evidence = KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + result->version_evidence = *version_evidence; + return 0; + } + + lookup_status = kzt_runtime_got_plt_read_version_string_from_verneed( + request, pool, version, version_name, &read_error_addr); + if (lookup_status == 1) { + lookup_status = kzt_runtime_got_plt_read_version_string_from_verdef( + request, pool, version, version_name, &read_error_addr); + } + + if (lookup_status == 0) { + *version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + result->version_evidence = *version_evidence; + return 0; + } + + *version_evidence = KZT_SYMBOL_VERSION_ERROR; + result->version_evidence = *version_evidence; + kzt_runtime_got_plt_fail_open( + result, + lookup_status < 0 + ? KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_VERSION_READ_FAILED + : KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_SYMBOL_VERSION, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_VERSION, table_kind, + entry_index, entry_addr, 0, read_error_addr); + return -1; +} + +static kzt_patch_relocation_type_t kzt_runtime_got_plt_reloc_type( + unsigned int elf_reloc_type) +{ + switch (elf_reloc_type) { + case R_X86_64_JUMP_SLOT: + return KZT_PATCH_RELOCATION_JUMP_SLOT; + case R_X86_64_GLOB_DAT: + return KZT_PATCH_RELOCATION_GLOB_DAT; + case R_X86_64_RELATIVE: + return KZT_PATCH_RELOCATION_RELATIVE; + case R_X86_64_COPY: + return KZT_PATCH_RELOCATION_COPY; + case R_X86_64_IRELATIVE: + return KZT_PATCH_RELOCATION_IRELATIVE; + } + + return KZT_PATCH_RELOCATION_OTHER; +} + +static int kzt_runtime_got_plt_target_relocation( + kzt_patch_table_kind_t table_kind, + unsigned int elf_reloc_type) +{ + if (table_kind == KZT_PATCH_TABLE_PLT_RELA) { + return elf_reloc_type == R_X86_64_JUMP_SLOT; + } + + if (table_kind == KZT_PATCH_TABLE_RELA) { + return elf_reloc_type == R_X86_64_GLOB_DAT; + } + + return 0; +} + +static int kzt_runtime_got_plt_append_candidate( + const kzt_runtime_got_plt_candidate_request_t *request, + kzt_runtime_got_plt_candidate_result_t *result, + kzt_runtime_got_plt_string_pool_t *pool, + size_t *candidate_count, + kzt_patch_table_kind_t table_kind, + size_t entry_index, + uintptr_t entry_addr, + const Elf64_Rela *rela) +{ + kzt_patch_candidate_t candidate; + uintptr_t slot_addr = 0; + uintptr_t slot_value = 0; + unsigned int elf_reloc_type = ELF64_R_TYPE(rela->r_info); + unsigned long symbol_index = ELF64_R_SYM(rela->r_info); + const char *symbol_name = NULL; + const char *version_name = NULL; + kzt_symbol_version_evidence_t version_evidence = + KZT_SYMBOL_VERSION_UNKNOWN; + + if (kzt_runtime_got_plt_add_u64(request->view->load_bias, + rela->r_offset, &slot_addr) != 0) { + kzt_runtime_got_plt_fail_open( + result, KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_SLOT_OVERFLOW, + KZT_PATCH_REASON_INPUT_MALFORMED_SLOT, table_kind, entry_index, + entry_addr, 0, 0); + return -1; + } + + if (*candidate_count >= request->candidate_capacity) { + kzt_runtime_got_plt_fail_open( + result, KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_CAPACITY_EXCEEDED, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE, table_kind, entry_index, + entry_addr, slot_addr, 0); + return -1; + } + + if (kzt_runtime_got_plt_read_slot(request->reader_ops, slot_addr, + &slot_value) != 0) { + kzt_runtime_got_plt_fail_open( + result, KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_SLOT_READ_FAILED, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_CURRENT_GOT, table_kind, + entry_index, entry_addr, slot_addr, slot_addr); + return -1; + } + + if (kzt_runtime_got_plt_read_symbol_name( + request, result, pool, symbol_index, table_kind, entry_index, + entry_addr, &symbol_name) != 0) { + return -1; + } + + if (kzt_runtime_got_plt_read_symbol_version( + request, result, pool, symbol_index, table_kind, entry_index, + entry_addr, &version_evidence, &version_name) != 0) { + return -1; + } + + memset(&candidate, 0, sizeof(candidate)); + if (request->source) { + candidate.source = *request->source; + } + candidate.dynamic_addr = request->view->dynamic_addr; + candidate.load_bias = request->view->load_bias; + candidate.dynamic_view_generation = request->dynamic_view_generation; + candidate.dynamic_view_available = 1; + candidate.table_kind = table_kind; + candidate.entry_index = entry_index; + candidate.entry_addr = entry_addr; + candidate.reloc_type = kzt_runtime_got_plt_reloc_type(elf_reloc_type); + candidate.slot_addr = slot_addr; + candidate.slot_current_value_present = 1; + candidate.slot_current_value = slot_value; + candidate.lazy_binding_deferred = 0; + candidate.symbol_index = symbol_index; + candidate.symbol_name = symbol_name; + candidate.version_evidence = version_evidence; + candidate.version = version_name; + candidate.owner_match = KZT_PATCH_OWNER_UNKNOWN; + + request->candidates[*candidate_count] = candidate; + ++*candidate_count; + return 0; +} + +static int kzt_runtime_got_plt_enumerate_rela_table( + const kzt_runtime_got_plt_candidate_request_t *request, + kzt_runtime_got_plt_candidate_result_t *result, + kzt_runtime_got_plt_string_pool_t *pool, + kzt_patch_table_kind_t table_kind, + uintptr_t table_addr, + size_t table_size, + size_t entry_size, + size_t *candidate_count) +{ + size_t entry_count; + size_t i; + + if (request->only_entry && request->only_table_kind != table_kind) { + return 0; + } + + if (entry_size != sizeof(Elf64_Rela) || + table_size % entry_size != 0) { + kzt_runtime_got_plt_fail_open( + result, KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_TABLE, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE, table_kind, 0, + table_addr, 0, 0); + return -1; + } + + if (kzt_runtime_got_plt_table_bounds_valid(table_addr, table_size) != 0) { + kzt_runtime_got_plt_fail_open( + result, KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_TABLE_OVERFLOW, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE, table_kind, 0, + table_addr, 0, 0); + return -1; + } + + entry_count = table_size / entry_size; + if (request->only_entry) { + if (request->only_entry_index >= entry_count) { + kzt_runtime_got_plt_fail_open( + result, KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_TABLE, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE, table_kind, + request->only_entry_index, table_addr, 0, 0); + return -1; + } + i = request->only_entry_index; + } else { + i = 0; + } + for (; i < entry_count; ++i) { + Elf64_Rela rela; + uintptr_t entry_addr = 0; + unsigned int elf_reloc_type; + + if (kzt_runtime_got_plt_entry_addr(table_addr, entry_size, i, + &entry_addr) != 0) { + kzt_runtime_got_plt_fail_open( + result, KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_TABLE_OVERFLOW, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE, table_kind, i, + table_addr, 0, 0); + return -1; + } + + if (kzt_runtime_got_plt_read_rela(request->reader_ops, entry_addr, + &rela) != 0) { + kzt_runtime_got_plt_fail_open( + result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_RELOCATION_READ_FAILED, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE, table_kind, i, + entry_addr, 0, entry_addr); + return -1; + } + + elf_reloc_type = ELF64_R_TYPE(rela.r_info); + if (!kzt_runtime_got_plt_target_relocation(table_kind, + elf_reloc_type)) { + if (request->only_entry) { + return 0; + } + continue; + } + + if (kzt_runtime_got_plt_append_candidate( + request, result, pool, candidate_count, table_kind, i, + entry_addr, &rela) != 0) { + return -1; + } + if (request->only_entry) { + return 0; + } + } + + return 0; +} + +static int kzt_runtime_got_plt_enumerate_plt_rela( + const kzt_runtime_got_plt_candidate_request_t *request, + kzt_runtime_got_plt_candidate_result_t *result, + kzt_runtime_got_plt_string_pool_t *pool, + size_t *candidate_count) +{ + const kzt_guest_dynamic_view_t *view = request->view; + size_t table_size = 0; + + if (request->only_entry && + request->only_table_kind != KZT_PATCH_TABLE_PLT_RELA) { + return 0; + } + + if (!kzt_runtime_got_plt_has_plt_table(view)) { + return 0; + } + + if (!view->jmprel.present || !view->pltrelsz.present || + !view->pltrel.present) { + kzt_runtime_got_plt_fail_open( + result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MISSING_DYNAMIC_FIELD, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE, + KZT_PATCH_TABLE_PLT_RELA, 0, 0, 0, 0); + return -1; + } + + if (view->pltrel.value == DT_REL) { + kzt_runtime_got_plt_fail_open( + result, KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_DT_REL_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNSUPPORTED_RELOCATION, + KZT_PATCH_TABLE_PLT_REL, 0, view->jmprel.value, 0, 0); + return -1; + } + + if (view->pltrel.value != DT_RELA || + kzt_runtime_got_plt_u64_to_size(view->pltrelsz.value, + &table_size) != 0) { + kzt_runtime_got_plt_fail_open( + result, KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_TABLE, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE, + KZT_PATCH_TABLE_PLT_RELA, 0, view->jmprel.value, 0, 0); + return -1; + } + + return kzt_runtime_got_plt_enumerate_rela_table( + request, result, pool, KZT_PATCH_TABLE_PLT_RELA, + (uintptr_t)view->jmprel.value, table_size, sizeof(Elf64_Rela), + candidate_count); +} + +static int kzt_runtime_got_plt_enumerate_rela( + const kzt_runtime_got_plt_candidate_request_t *request, + kzt_runtime_got_plt_candidate_result_t *result, + kzt_runtime_got_plt_string_pool_t *pool, + size_t *candidate_count) +{ + const kzt_guest_dynamic_view_t *view = request->view; + size_t table_size = 0; + size_t entry_size = 0; + + if (request->only_entry && + request->only_table_kind != KZT_PATCH_TABLE_RELA) { + return 0; + } + + if (!kzt_runtime_got_plt_has_rela_table(view)) { + return 0; + } + + if (!view->rela.present || !view->relasz.present || + !view->relaent.present) { + kzt_runtime_got_plt_fail_open( + result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MISSING_DYNAMIC_FIELD, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE, + KZT_PATCH_TABLE_RELA, 0, 0, 0, 0); + return -1; + } + + if (kzt_runtime_got_plt_u64_to_size(view->relasz.value, + &table_size) != 0 || + kzt_runtime_got_plt_u64_to_size(view->relaent.value, + &entry_size) != 0 || + entry_size == 0) { + kzt_runtime_got_plt_fail_open( + result, KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_TABLE, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE, KZT_PATCH_TABLE_RELA, + 0, view->rela.value, 0, 0); + return -1; + } + + return kzt_runtime_got_plt_enumerate_rela_table( + request, result, pool, KZT_PATCH_TABLE_RELA, (uintptr_t)view->rela.value, + table_size, entry_size, candidate_count); +} + +int kzt_runtime_got_plt_candidates_collect( + const kzt_runtime_got_plt_candidate_request_t *request, + kzt_runtime_got_plt_candidate_result_t *result) +{ + size_t candidate_count = 0; + kzt_runtime_got_plt_string_pool_t pool = { 0 }; + + if (!result) { + return -1; + } + + kzt_runtime_got_plt_result_clear(result); + + if (!request || !request->view || !request->reader_ops || + !request->reader_ops->read_memory || + (!request->candidates && request->candidate_capacity > 0) || + ((!request->string_storage || request->string_storage_size == 0) && + request->candidate_capacity > 0)) { + kzt_runtime_got_plt_result_set( + result, KZT_RUNTIME_GOT_PLT_CANDIDATE_ERROR, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_INVALID_ARGUMENT, + KZT_PATCH_REASON_ERROR_INVALID_ARGUMENT, + KZT_PATCH_TABLE_UNKNOWN, 0, 0, 0, 0); + return -1; + } + + pool.storage = request->string_storage; + pool.size = request->string_storage_size; + + if (request->view->status != KZT_GUEST_DYNAMIC_COMPLETE) { + return kzt_runtime_got_plt_fail_open( + result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_DYNAMIC_VIEW_UNAVAILABLE, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_DYNAMIC_VIEW, + KZT_PATCH_TABLE_UNKNOWN, 0, 0, 0, 0); + } + + if ((!request->only_entry || + request->only_table_kind == KZT_PATCH_TABLE_PLT_RELA) && + request->view->pltrel.present && + request->view->pltrel.value == DT_REL) { + return kzt_runtime_got_plt_fail_open( + result, KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_DT_REL_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNSUPPORTED_RELOCATION, + KZT_PATCH_TABLE_PLT_REL, 0, request->view->jmprel.value, 0, 0); + } + + if ((!request->only_entry || + request->only_table_kind == KZT_PATCH_TABLE_REL) && + kzt_runtime_got_plt_has_rel_table(request->view)) { + return kzt_runtime_got_plt_fail_open( + result, KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_DT_REL_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNSUPPORTED_RELOCATION, + KZT_PATCH_TABLE_REL, 0, request->view->rel.value, 0, 0); + } + + if (kzt_runtime_got_plt_enumerate_plt_rela( + request, result, &pool, &candidate_count) != 0) { + return 0; + } + + if (kzt_runtime_got_plt_enumerate_rela( + request, result, &pool, &candidate_count) != 0) { + return 0; + } + + result->candidate_count = candidate_count; + return 0; +} + +const char *kzt_runtime_got_plt_candidate_status_name( + kzt_runtime_got_plt_candidate_status_t status) +{ + switch (status) { + case KZT_RUNTIME_GOT_PLT_CANDIDATE_OK: + return "OK"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_FAIL_OPEN: + return "FAIL_OPEN"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_ERROR: + return "ERROR"; + } + + return "UNKNOWN"; +} + +const char *kzt_runtime_got_plt_candidate_reason_name( + kzt_runtime_got_plt_candidate_reason_t reason) +{ + switch (reason) { + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_NONE: + return "NONE"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_INVALID_ARGUMENT: + return "INVALID_ARGUMENT"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_DYNAMIC_VIEW_UNAVAILABLE: + return "DYNAMIC_VIEW_UNAVAILABLE"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_DT_REL_UNSUPPORTED: + return "DT_REL_UNSUPPORTED"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MISSING_DYNAMIC_FIELD: + return "MISSING_DYNAMIC_FIELD"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_TABLE: + return "MALFORMED_TABLE"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_TABLE_OVERFLOW: + return "TABLE_OVERFLOW"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_RELOCATION_READ_FAILED: + return "RELOCATION_READ_FAILED"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_SLOT_OVERFLOW: + return "SLOT_OVERFLOW"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_SLOT_READ_FAILED: + return "SLOT_READ_FAILED"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_SYMBOL_READ_FAILED: + return "SYMBOL_READ_FAILED"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_SYMBOL_NAME: + return "MALFORMED_SYMBOL_NAME"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_VERSION_READ_FAILED: + return "VERSION_READ_FAILED"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_SYMBOL_VERSION: + return "MALFORMED_SYMBOL_VERSION"; + case KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_CAPACITY_EXCEEDED: + return "CAPACITY_EXCEEDED"; + } + + return "UNKNOWN"; +} diff --git a/target/i386/latx/context/kzt_wrapper_bridge_provider.c b/target/i386/latx/context/kzt_wrapper_bridge_provider.c new file mode 100644 index 00000000000..e8b5ce84889 --- /dev/null +++ b/target/i386/latx/context/kzt_wrapper_bridge_provider.c @@ -0,0 +1,183 @@ +#include "kzt_wrapper_bridge_provider.h" + +#include + +static int kzt_wrapper_bridge_provider_string_empty(const char *value) +{ + return !value || !value[0]; +} + +static int kzt_wrapper_bridge_provider_string_equal(const char *left, + const char *right) +{ + return !kzt_wrapper_bridge_provider_string_empty(left) && + !kzt_wrapper_bridge_provider_string_empty(right) && + strcmp(left, right) == 0; +} + +static uintptr_t kzt_wrapper_bridge_provider_check_bridge( + uintptr_t native_symbol, void *opaque) +{ + kzt_wrapper_bridge_provider_t *provider = opaque; + + if (!provider || !provider->runtime_ops.check_bridge || + native_symbol != provider->match.native_symbol) { + return 0; + } + + return provider->runtime_ops.check_bridge( + &provider->match, provider->runtime_ops.opaque); +} + +static uintptr_t kzt_wrapper_bridge_provider_add_bridge( + const kzt_wrapper_probe_bridge_request_t *request, void *opaque) +{ + kzt_wrapper_bridge_provider_t *provider = opaque; + uintptr_t created; + uintptr_t verified; + + if (!provider || !request || !provider->runtime_ops.add_bridge || + !provider->runtime_ops.check_bridge || + request->native_symbol != provider->match.native_symbol || + !kzt_wrapper_bridge_provider_string_equal( + request->symbol_name, provider->entry.symbol_name) || + !kzt_symbol_version_evidence_matches( + request->symbol_version_evidence, request->symbol_version, + provider->entry.symbol_version_evidence, + provider->entry.symbol_version) || + !kzt_wrapper_bridge_provider_string_equal( + request->wrapper_name, provider->entry.wrapper_name) || + !kzt_symbol_version_evidence_matches( + request->wrapper_version_evidence, + request->wrapper_symbol_version, + provider->entry.wrapper_version_evidence, + provider->entry.wrapper_symbol_version)) { + return 0; + } + + created = provider->runtime_ops.add_bridge( + &provider->match, request, provider->runtime_ops.opaque); + if (!created) { + return 0; + } + verified = provider->runtime_ops.check_bridge( + &provider->match, provider->runtime_ops.opaque); + return verified == created ? created : 0; +} + +int kzt_wrapper_bridge_provider_prepare_with_version_evidence( + kzt_wrapper_bridge_provider_t *provider, void *const *libraries, + size_t library_count, const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version, + const kzt_wrapper_bridge_provider_runtime_ops_t *runtime_ops) +{ + kzt_wrapper_bridge_provider_match_t selected; + size_t selected_count = 0; + size_t i; + + if (!provider) { + return -1; + } + memset(provider, 0, sizeof(*provider)); + memset(&selected, 0, sizeof(selected)); + + if (!libraries || !runtime_ops || !runtime_ops->inspect_library || + !runtime_ops->check_bridge || + kzt_wrapper_bridge_provider_string_empty(symbol_name) || + !kzt_symbol_version_evidence_valid(version_evidence, + symbol_version)) { + return 0; + } + + for (i = 0; i < library_count; ++i) { + kzt_wrapper_bridge_provider_match_t candidate; + size_t j; + int duplicate = 0; + int status; + + if (!libraries[i]) { + continue; + } + for (j = 0; j < i; ++j) { + if (libraries[j] == libraries[i]) { + duplicate = 1; + break; + } + } + if (duplicate) { + continue; + } + + memset(&candidate, 0, sizeof(candidate)); + status = runtime_ops->inspect_library( + libraries[i], symbol_name, symbol_version, &candidate, + runtime_ops->opaque); + if (status < 0) { + return 0; + } + if (status == 0) { + continue; + } + if (!candidate.abi_wrapper || !candidate.native_symbol || + !candidate.context_owner || !candidate.wrapper_provider || + !candidate.native_lookup_handle || !candidate.native_owner || + !candidate.bridge_owner || !candidate.bridge_storage || + candidate.stack_bytes < 0 || !candidate.native_name[0] || + (!!candidate.resolved_bridge_target != + !!candidate.resolved_bridge_exact) || + (!candidate.resolved_bridge_target && + !runtime_ops->add_bridge) || + !candidate.wrapper_provider_lifetime_bound || + !candidate.native_owner_lifetime_bound || + !candidate.bridge_owner_lifetime_bound || + kzt_wrapper_bridge_provider_string_empty(candidate.wrapper_name)) { + return 0; + } + + selected = candidate; + if (++selected_count != 1) { + return 0; + } + } + + if (selected_count != 1) { + return 0; + } + + provider->match = selected; + provider->runtime_ops = *runtime_ops; + provider->entry.symbol_name = symbol_name; + provider->entry.symbol_version_evidence = version_evidence; + provider->entry.symbol_version = symbol_version; + provider->entry.wrapper_name = selected.wrapper_name; + provider->entry.wrapper_version_evidence = version_evidence; + provider->entry.wrapper_symbol_version = symbol_version; + provider->entry.native_symbol = selected.native_symbol; + provider->manifest.available = 1; + provider->manifest.manifest_name = selected.wrapper_name; + provider->manifest.entries = &provider->entry; + provider->manifest.entry_count = 1; + provider->bridge_ops.check_bridge = + kzt_wrapper_bridge_provider_check_bridge; + if (runtime_ops->add_bridge && !selected.resolved_bridge_exact) { + provider->bridge_ops.add_bridge = + kzt_wrapper_bridge_provider_add_bridge; + } + provider->bridge_ops.opaque = provider; + return 1; +} + +int kzt_wrapper_bridge_provider_prepare( + kzt_wrapper_bridge_provider_t *provider, void *const *libraries, + size_t library_count, const char *symbol_name, + const char *symbol_version, + const kzt_wrapper_bridge_provider_runtime_ops_t *runtime_ops) +{ + return kzt_wrapper_bridge_provider_prepare_with_version_evidence( + provider, libraries, library_count, symbol_name, + kzt_wrapper_bridge_provider_string_empty(symbol_version) + ? KZT_SYMBOL_VERSION_UNKNOWN + : KZT_SYMBOL_VERSION_VERSIONED, + symbol_version, runtime_ops); +} diff --git a/target/i386/latx/context/kzt_wrapper_probe.c b/target/i386/latx/context/kzt_wrapper_probe.c new file mode 100644 index 00000000000..010310c7dda --- /dev/null +++ b/target/i386/latx/context/kzt_wrapper_probe.c @@ -0,0 +1,240 @@ +#include "kzt_wrapper_probe.h" + +#include + +static int kzt_wrapper_probe_string_empty(const char *value) +{ + return !value || !value[0]; +} + +static int kzt_wrapper_probe_string_equal(const char *left, + const char *right) +{ + if (kzt_wrapper_probe_string_empty(left) || + kzt_wrapper_probe_string_empty(right)) { + return 0; + } + + return strcmp(left, right) == 0; +} + +static int kzt_wrapper_probe_entry_matches_symbol( + const kzt_wrapper_probe_entry_t *entry, + const kzt_wrapper_probe_request_t *request) +{ + return entry && request && + kzt_wrapper_probe_string_equal(entry->symbol_name, + request->symbol_name); +} + +static int kzt_wrapper_probe_entry_has_valid_version_evidence( + const kzt_wrapper_probe_entry_t *entry) +{ + return entry && kzt_symbol_version_evidence_valid( + entry->symbol_version_evidence, + entry->symbol_version); +} + +static int kzt_wrapper_probe_entry_matches_version( + const kzt_wrapper_probe_entry_t *entry, + const kzt_wrapper_probe_request_t *request) +{ + return entry && request && kzt_symbol_version_evidence_matches( + entry->symbol_version_evidence, entry->symbol_version, + request->symbol_version_evidence, request->symbol_version); +} + +static void kzt_wrapper_probe_result_reset(kzt_wrapper_probe_result_t *result) +{ + memset(result, 0, sizeof(*result)); + result->wrapper_match = KZT_PATCH_WRAPPER_NO_MANIFEST; + result->wrapper_version_evidence = KZT_SYMBOL_VERSION_UNKNOWN; + result->bridge_source = KZT_WRAPPER_PROBE_BRIDGE_NONE; +} + +static void kzt_wrapper_probe_result_from_entry( + kzt_wrapper_probe_result_t *result, + kzt_patch_wrapper_match_t match, + const kzt_wrapper_probe_entry_t *entry) +{ + result->wrapper_match = match; + result->wrapper_name = entry ? entry->wrapper_name : NULL; + result->wrapper_version_evidence = entry ? + entry->wrapper_version_evidence : KZT_SYMBOL_VERSION_UNKNOWN; + result->wrapper_symbol_version = entry ? entry->wrapper_symbol_version : NULL; + result->native_symbol = entry ? entry->native_symbol : 0; +} + +static void kzt_wrapper_probe_fill_bridge_request( + const kzt_wrapper_probe_entry_t *entry, + const kzt_wrapper_probe_request_t *request, + kzt_wrapper_probe_bridge_request_t *bridge_request) +{ + memset(bridge_request, 0, sizeof(*bridge_request)); + bridge_request->symbol_name = request->symbol_name; + bridge_request->symbol_version_evidence = + request->symbol_version_evidence; + bridge_request->symbol_version = request->symbol_version; + bridge_request->wrapper_name = entry->wrapper_name; + bridge_request->wrapper_version_evidence = + entry->wrapper_version_evidence; + bridge_request->wrapper_symbol_version = entry->wrapper_symbol_version; + bridge_request->native_symbol = entry->native_symbol; +} + +static void kzt_wrapper_probe_resolve_bridge( + const kzt_wrapper_probe_entry_t *entry, + const kzt_wrapper_probe_request_t *request, + const kzt_wrapper_probe_bridge_ops_t *bridge_ops, + kzt_wrapper_probe_result_t *result) +{ + kzt_wrapper_probe_bridge_request_t bridge_request; + uintptr_t target; + + if (!entry || !entry->native_symbol || !bridge_ops) { + return; + } + + if (bridge_ops->check_bridge) { + target = bridge_ops->check_bridge(entry->native_symbol, + bridge_ops->opaque); + if (target) { + result->bridge_target = target; + result->bridge_source = KZT_WRAPPER_PROBE_BRIDGE_CACHE; + return; + } + } + + if (!bridge_ops->add_bridge) { + return; + } + + kzt_wrapper_probe_fill_bridge_request(entry, request, &bridge_request); + target = bridge_ops->add_bridge(&bridge_request, bridge_ops->opaque); + if (target) { + result->bridge_target = target; + result->bridge_source = KZT_WRAPPER_PROBE_BRIDGE_ADD_BRIDGE; + } +} + +int kzt_wrapper_probe_minimal_manifest( + const kzt_wrapper_probe_manifest_t *manifest, + const kzt_wrapper_probe_request_t *request, + const kzt_wrapper_probe_bridge_ops_t *bridge_ops, + kzt_wrapper_probe_result_t *result) +{ + const kzt_wrapper_probe_entry_t *first_symbol_only = NULL; + const kzt_wrapper_probe_entry_t *first_version_mismatch = NULL; + const kzt_wrapper_probe_entry_t *version_match = NULL; + size_t i; + + if (!result) { + return -1; + } + + kzt_wrapper_probe_result_reset(result); + if (!request || kzt_wrapper_probe_string_empty(request->symbol_name)) { + return -1; + } + + if (!manifest || !manifest->available || !manifest->entries) { + result->wrapper_match = KZT_PATCH_WRAPPER_NO_MANIFEST; + return 0; + } + + for (i = 0; i < manifest->entry_count; ++i) { + const kzt_wrapper_probe_entry_t *entry = &manifest->entries[i]; + + if (!kzt_wrapper_probe_entry_matches_symbol(entry, request)) { + continue; + } + + if (!kzt_wrapper_probe_entry_has_valid_version_evidence(entry) || + !kzt_symbol_version_evidence_valid( + entry->wrapper_version_evidence, + entry->wrapper_symbol_version)) { + if (!first_symbol_only) { + first_symbol_only = entry; + } + continue; + } + + if (!first_version_mismatch) { + first_version_mismatch = entry; + } + + if (kzt_wrapper_probe_entry_matches_version(entry, request)) { + version_match = entry; + break; + } + } + + if (version_match) { + kzt_wrapper_probe_result_from_entry( + result, + request->symbol_version_evidence == + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED + ? KZT_PATCH_WRAPPER_UNVERSIONED_MATCH + : KZT_PATCH_WRAPPER_VERSION_MATCH, + version_match); + kzt_wrapper_probe_resolve_bridge(version_match, request, bridge_ops, + result); + return 0; + } + + if (first_version_mismatch) { + kzt_wrapper_probe_result_from_entry( + result, KZT_PATCH_WRAPPER_VERSION_MISMATCH, + first_version_mismatch); + return 0; + } + + if (first_symbol_only) { + kzt_wrapper_probe_result_from_entry( + result, KZT_PATCH_WRAPPER_SYMBOL_ONLY, first_symbol_only); + return 0; + } + + result->wrapper_match = KZT_PATCH_WRAPPER_NO_WRAPPER; + return 0; +} + +void kzt_wrapper_probe_apply_to_candidate( + const kzt_wrapper_probe_result_t *probe, + kzt_patch_candidate_t *candidate) +{ + if (!probe || !candidate) { + return; + } + + candidate->wrapper_match = probe->wrapper_match; + candidate->wrapper_name = probe->wrapper_name; + candidate->wrapper_version_evidence = + probe->wrapper_version_evidence; + candidate->wrapper_symbol_version = probe->wrapper_symbol_version; + candidate->bridge_target = probe->bridge_target; +} + +void kzt_wrapper_probe_apply_to_decision_request( + const kzt_wrapper_probe_result_t *probe, + kzt_patch_wrapper_match_t *wrapper_match, + const char **wrapper_name, + const char **wrapper_symbol_version, + uintptr_t *bridge_target) +{ + if (!probe) { + return; + } + if (wrapper_match) { + *wrapper_match = probe->wrapper_match; + } + if (wrapper_name) { + *wrapper_name = probe->wrapper_name; + } + if (wrapper_symbol_version) { + *wrapper_symbol_version = probe->wrapper_symbol_version; + } + if (bridge_target) { + *bridge_target = probe->bridge_target; + } +} diff --git a/target/i386/latx/context/librarian.c b/target/i386/latx/context/librarian.c index b6fdba0ea61..210da588e1d 100755 --- a/target/i386/latx/context/librarian.c +++ b/target/i386/latx/context/librarian.c @@ -221,12 +221,17 @@ static void MapLibRemoveMapLib(lib_t* dest, lib_t* src) } } -int AddNeededLib_add(lib_t* maplib, needed_libs_t* neededlibs, library_t* deplib, int local, const char* path, box64context_t* box64) +static int AddNeededLib_add_exact(lib_t* maplib, needed_libs_t* neededlibs, + library_t* deplib, int local, + const char* path, box64context_t* box64, + library_t **exact_library) { + if (exact_library) *exact_library = NULL; printf_log(LOG_INFO, "Trying to add \"%s\" to maplib%s\n", path, local?" (local)":""); // first check if lib is already loaded library_t *lib = getLib(my_context->maplib, path); if(lib) { + if (exact_library) *exact_library = lib; add_neededlib(neededlibs, lib); if (lib && deplib) add_dependedlib(&lib->depended, deplib); printf_log(LOG_INFO, "Already present in maplib => success\n"); @@ -235,6 +240,7 @@ int AddNeededLib_add(lib_t* maplib, needed_libs_t* neededlibs, library_t* deplib // check also in the local loaded lib lib = getLib(my_context->local_maplib, path); if(lib) { + if (exact_library) *exact_library = lib; printf_log(LOG_INFO, "Already present in local_maplib => success\n"); if(local) { // add lib to maplib... @@ -304,9 +310,18 @@ int AddNeededLib_add(lib_t* maplib, needed_libs_t* neededlibs, library_t* deplib lm->l_name = lib->name; lm->l_ld = GetDynamicSection(my_context->elfs[lib->priv.n.elf_index]); } + if (exact_library) *exact_library = lib; return 0; } +int AddNeededLib_add(lib_t* maplib, needed_libs_t* neededlibs, + library_t* deplib, int local, const char* path, + box64context_t* box64) +{ + return AddNeededLib_add_exact(maplib, neededlibs, deplib, local, path, + box64, NULL); +} + int AddNeededLib(lib_t* maplib, needed_libs_t* neededlibs, library_t* deplib, int local, int bindnow, const char** paths, int npath, box64context_t* box64) { if(!neededlibs) { @@ -322,6 +337,26 @@ int AddNeededLib(lib_t* maplib, needed_libs_t* neededlibs, library_t* deplib, in return 0; } +int AddNeededLibWithLibrary(lib_t* maplib, needed_libs_t* neededlibs, + library_t* deplib, int local, int bindnow, + const char* path, box64context_t* box64, + library_t **exact_library) +{ + library_t *selected = NULL; + int add_result; + (void)bindnow; + if (exact_library) *exact_library = NULL; + if (!neededlibs) + neededlibs = box_calloc(1, sizeof(needed_libs_t)); + add_result = AddNeededLib_add_exact(maplib, neededlibs, deplib, local, + path, box64, &selected); + if (!add_result && exact_library) + *exact_library = selected; + /* Preserve AddNeededLib's historical public return behavior. Exact + * callers distinguish failure through the reliably cleared output. */ + return 0; +} + library_t* GetLibMapLib(lib_t* maplib, const char* name) { printf_log(LOG_DEBUG, "Trying to Get \"%s\" to maplib\n", name); @@ -392,7 +427,7 @@ int GetNoSelfSymbolStartEnd(lib_t *maplib, const char* name, uintptr_t* start, u // nope, not found return 0; } -static int GetGlobalSymbolStartEnd_internal(lib_t *maplib, const char* name, uintptr_t* start, uintptr_t* end, elfheader_t* self, int version, const char* vername) +static int GetGlobalSymbolStartEnd_internal(lib_t *maplib, const char* name, uintptr_t* start, uintptr_t* end, elfheader_t* self, int version, const char* vername, library_t **provider) { khint_t pre_k = kh_str_hash_func(name); @@ -406,24 +441,29 @@ static int GetGlobalSymbolStartEnd_internal(lib_t *maplib, const char* name, uin //noweak=0 for(int i=0; ilibsz; ++i) { if(GetLibSymbolStartEnd(maplib->libraries[i], name, pre_k, start, end, version, vername, isLocal(self, maplib->libraries[i]))) // only weak symbol haven't been found yet - if(*start) + if(*start) { + if(provider) + *provider = maplib->libraries[i]; return 1; + } } // nope, not found return 0; } -int GetGlobalSymbolStartEnd(lib_t *maplib, const char* name, uintptr_t* start, uintptr_t* end, elfheader_t* self, int version, const char* vername) +int GetGlobalSymbolStartEndWithProvider(lib_t *maplib, const char* name, uintptr_t* start, uintptr_t* end, elfheader_t* self, int version, const char* vername, library_t **provider) { + if(provider) + *provider = NULL; if(!maplib) return 0; - if(GetGlobalSymbolStartEnd_internal(maplib, name, start, end, self, version, vername)) { + if(GetGlobalSymbolStartEnd_internal(maplib, name, start, end, self, version, vername, provider)) { if(start && end && *end==*start) { // object is of 0 sized, try to see an "_END" object of null size uintptr_t start2, end2; char* buff = (char*)box_malloc(strlen(name) + strlen("_END") + 1); strcpy(buff, name); strcat(buff, "_END"); - if(GetGlobalSymbolStartEnd_internal(maplib, buff, &start2, &end2, self, version, vername)) { + if(GetGlobalSymbolStartEnd_internal(maplib, buff, &start2, &end2, self, version, vername, NULL)) { if(end2>*end && start2==end2) *end = end2; } @@ -448,6 +488,12 @@ int GetGlobalSymbolStartEnd(lib_t *maplib, const char* name, uintptr_t* start, u return 0; } +int GetGlobalSymbolStartEnd(lib_t *maplib, const char* name, uintptr_t* start, uintptr_t* end, elfheader_t* self, int version, const char* vername) +{ + return GetGlobalSymbolStartEndWithProvider( + maplib, name, start, end, self, version, vername, NULL); +} + elfheader_t* GetGlobalSymbolElf(lib_t *maplib, const char* name, int version, const char* vername) { uintptr_t start = 0; diff --git a/target/i386/latx/context/library.c b/target/i386/latx/context/library.c index 378099df1ea..67d7f48c422 100755 --- a/target/i386/latx/context/library.c +++ b/target/i386/latx/context/library.c @@ -29,6 +29,10 @@ #include "librarian.h" #include "librarian_private.h" #include "pathcoll.h" +#ifdef CONFIG_LATX_KZT +#include "kzt_guest_library_adapter.h" +#include "kzt_guest_library_binding.h" +#endif #define GO(P, N) int wrapped##N##_init(library_t* lib, box64context_t *box64); \ void wrapped##N##_fini(library_t* lib); \ @@ -352,7 +356,6 @@ int AddSymbolsLibrary(lib_t *maplib, library_t* lib) int ReloadLibrary(library_t* lib) { - lib->active = 1; if(lib->type == LIB_EMULATED) { elfheader_t *elf_header = lib->context->elfs[lib->priv.n.elf_index]; // reload image in memory and re-run the mapping @@ -383,8 +386,21 @@ int ReloadLibrary(library_t* lib) printf_log(LOG_NONE, "Error: relocating symbols in elf %s\n", lib->name); return 1; } - RelocateElfPlt(lib->context->maplib, lib->maplib, 0, elf_header); + if(RelocateElfPlt(lib->context->maplib, lib->maplib, 0, elf_header)) { + printf_log(LOG_NONE, "Error: relocating PLT symbols in elf %s\n", lib->name); + return 1; + } } + lib->active = 1; +#ifdef CONFIG_LATX_KZT + /* Publish only after every reload step has succeeded. On failure the + * binding lifecycle remains DEAD and no exact pair is left pending. */ + if (kzt_guest_library_reactivate( + KztGuestLibraryBindingsForContext(lib->context), lib) == 0 && + lib->x86linkmap) + kzt_guest_library_note_loader_pair( + lib->context, (uintptr_t)lib->x86linkmap, lib); +#endif return 0; } void InactiveLibrary(library_t* lib) diff --git a/target/i386/latx/context/meson.build b/target/i386/latx/context/meson.build index f07cf9b44a0..b24ade609f2 100644 --- a/target/i386/latx/context/meson.build +++ b/target/i386/latx/context/meson.build @@ -61,13 +61,42 @@ my_file = files( 'library.c', 'pathcoll.c', 'fileutils.c', + 'dlopen_recycle_transaction.c', 'elfparser.c', + 'elf_plt_relocation.c', + 'elfmap.c', 'elfloader.c', 'elfload_dump.c', 'mallochook.c', 'bridge.c', 'wrapperdebug.c', 'wrapper.c', + 'kzt_guest_registry.c', + 'kzt_guest_registry_context.c', + 'kzt_guest_library_binding.c', + 'kzt_jump_slot_route.c', + 'kzt_jump_slot_production.c', + 'kzt_lazy_binding.c', + 'kzt_lazy_diagnostics.c', + 'kzt_plt_resolver_adapter.c', + 'kzt_guest_library_adapter.c', + 'kzt_bridge_exact.c', + 'kzt_guest_link_map_reader.c', + 'kzt_guest_dynamic.c', + 'kzt_guest_dynamic_diagnostics.c', + 'kzt_observation_adapter.c', + 'kzt_patch_planner.c', + 'kzt_runtime_got_plt_candidate.c', + 'kzt_runtime_candidate_shadow.c', + 'kzt_rela_immediate_candidate.c', + 'kzt_rela_request_enricher.c', + 'kzt_rela_diagnostics.c', + 'kzt_rela_runtime_bridge.c', + 'kzt_owner_resolver.c', + 'kzt_wrapper_probe.c', + 'kzt_wrapper_bridge_provider.c', + 'kzt_patch_spike_guard.c', + 'kzt_patch_spike_writer.c', 'myalign.c', 'obstack.c', 'globalsymbols.c', diff --git a/target/i386/latx/context/myalign.c b/target/i386/latx/context/myalign.c index 9e4981a9d57..c6d6a31aa85 100644 --- a/target/i386/latx/context/myalign.c +++ b/target/i386/latx/context/myalign.c @@ -9,8 +9,14 @@ #include "config-host.h" #include "lsenv.h" #include "myalign.h" +#include "debug.h" +#include "elfmap.h" #include "elfloader.h" #include "elfloader_private.h" +#ifdef CONFIG_LATX_KZT +#include "kzt_observation_adapter.h" +#include "kzt_guest_library_adapter.h" +#endif #include #include #include @@ -41,6 +47,78 @@ typedef union { } mmx87_regs_t; static int regs_abi[] = {R_EDI, R_ESI, R_EDX, R_ECX, R_R8, R_R9}; + +#ifdef CONFIG_LATX_KZT +static int kzt_registry_debug_dump_line(const char *line, void *opaque) +{ + (void)opaque; + + printf_kzt_registry_diagnostics("%s\n", line); + return 0; +} + +static int kzt_main_elf_identity( + const elfheader_t *head, + kzt_guest_link_map_identity_t *identity) +{ + size_t i; + + if (!head || !identity || !head->PHEntries) { + return -1; + } + memset(identity, 0, sizeof(*identity)); + for (i = 0; i < head->numPHEntries; ++i) { + const Elf64_Phdr *entry = &head->PHEntries[i]; + + if (entry->p_type != PT_DYNAMIC) { + continue; + } + if (head->delta < 0 || + entry->p_vaddr > UINTPTR_MAX - (uintptr_t)head->delta) { + return -1; + } + identity->load_bias = (uintptr_t)head->delta; + identity->dynamic_addr = entry->p_vaddr + identity->load_bias; + return identity->dynamic_addr ? 0 : -1; + } + return -1; +} + +static void kzt_callback_diagnostic_log( + const kzt_observation_adapter_diagnostic_t *diagnostic, + void *opaque) +{ + if (!diagnostic) { + return; + } + + printf_kzt_registry_diagnostics( + "KZT registry callback result=%d link_map=0x%lx " + "registry_result=%d generation=%lu objects=%lu " + "observed=%lu suppressed=%lu\n", + diagnostic->result, (unsigned long)diagnostic->link_map_addr, + diagnostic->registry.result, diagnostic->registry.generation, + diagnostic->registry.object_count, + diagnostic->registry.result_observations, + diagnostic->registry.result_suppressed); + + if (opaque) { + (void)kzt_guest_registry_dump_text(opaque, kzt_registry_debug_dump_line, + NULL); + } + + if (diagnostic->dynamic.comparison_attempted) { + char summary[1024]; + + if (kzt_guest_dynamic_diagnostics_format_summary( + &diagnostic->dynamic.comparison, summary, + sizeof(summary)) == 0) { + printf_kzt_registry_diagnostics("%s\n", summary); + } + } +} +#endif + uintptr_t getVArgs(int pos, uintptr_t* b, int N) { CPUX86State *cpu = (CPUX86State *)lsenv->cpu_state; @@ -2222,30 +2300,105 @@ static char* kzt_find_realsofilepath(char * filepath, char *filetmp) return filepath; } extern const char* libcName; -static void kzt_tb_callback(CPUX86State *env) +#ifdef CONFIG_LATX_KZT +static int kzt_callback_read_memory(uintptr_t guest_addr, + void *dst, + size_t size, + void *opaque) +{ + void *host_ptr; + + (void)opaque; + if (!dst && size) { + return -1; + } + if (!size) { + return 0; + } + + host_ptr = lock_user(VERIFY_READ, (abi_ulong)guest_addr, size, true); + if (!host_ptr) { + return -1; + } + + memcpy(dst, host_ptr, size); + unlock_user(host_ptr, (abi_ulong)guest_addr, 0); + return 0; +} +#endif + +static int kzt_tb_callback_legacy(uintptr_t link_map_addr, void *opaque) { - struct link_map_x64 * my_lm = (struct link_map_x64 *)env->regs[R_EAX + ld_info->reg]; + const kzt_guest_library_loader_scope_t *loader_scope = opaque; +#ifdef CONFIG_LATX_KZT + int loader_scope_active = loader_scope && loader_scope->bindings && + loader_scope->identity && loader_scope->cookie; +#endif + struct link_map_x64 * my_lm = (struct link_map_x64 *)link_map_addr; elfheader_t *h = NULL; - if ((!my_lm ||!my_lm->l_name ||!strlen(my_lm->l_name) || ! my_lm->l_addr)&& my_lm->l_addr != info1.load_addr) { +#ifdef CONFIG_LATX_KZT + int relocate_result; + int relocate_plt_result; +#endif + uintptr_t map_start = 0; + uintptr_t map_end = 0; + if (!my_lm) { + printf_log(LOG_DEBUG, "error %d debug %s link_map = %p\n", + getpid(), __func__, my_lm); + return 0; + } + if (!my_lm->l_name) { + printf_log(LOG_DEBUG, + "error %d debug %s link_map = %p{0x%lx, }\n", + getpid(), __func__, my_lm, my_lm->l_addr); + return 0; + } + if ((!my_lm->l_name[0] || !my_lm->l_addr) && + my_lm->l_addr != info1.load_addr) { printf_log(LOG_DEBUG, "error %d debug %s link_map = %p{0x%lx, %s}\n", getpid(), __func__, my_lm, my_lm->l_addr, my_lm->l_name); - return; + return 0; } printf_log(LOG_DEBUG, "%d debug %s link_map = %p{0x%lx, %s}\n", getpid(), __func__, my_lm, my_lm->l_addr, my_lm->l_name); char * rfilename = my_lm->l_name; - if (strstr(basename(rfilename), "ld-linux-x86-64.so.2")) { - AddDebugInfo(LIB_EMULATED, my_lm->l_name, my_lm->l_map_start, my_lm->l_map_end); - return; - } char filetmp[PATH_MAX] = {0}; if (rfilename[0] == '/') { rfilename = kzt_find_realsofilepath(rfilename, filetmp); } + if (strstr(basename(rfilename), "ld-linux-x86-64.so.2")) { + FILE *loader_file = fopen(rfilename, "rb"); + elfheader_t *loader_header = loader_file ? + LoadAndCheckElfHeader(loader_file, rfilename, 0) : NULL; + + if (loader_file) { + fclose(loader_file); + } + if (loader_header && + GetElfLoadRange(loader_header->PHEntries, + loader_header->numPHEntries, my_lm->l_addr, + TARGET_PAGE_SIZE, &map_start, &map_end) == 0) { + AddDebugInfo(LIB_EMULATED, my_lm->l_name, + map_start, map_end); + } else { + printf_log(LOG_INFO, + "KZT: cannot determine loader range for %s\n", + my_lm->l_name); + } + FreeElfHeader(&loader_header); + return 0; + } char * rbasename = basename(rfilename); - library_t* lib = NewLibrary(rbasename, my_context); - if (lib) { - const char* libs[] = {rbasename}; - AddNeededLib(my_context->maplib, &my_context->neededlibs, NULL, 0, 1, libs, 1, my_context); + library_t* lib = NULL; + (void)AddNeededLibWithLibrary( + my_context->maplib, &my_context->neededlibs, NULL, 0, 1, + rbasename, my_context, &lib); +#ifdef CONFIG_LATX_KZT + /* AddNeededLibWithLibrary returns the exact instance selected/created by + * this callback operation; no basename lookup is used for binding. */ + if (loader_scope_active) { + kzt_guest_library_note_loader_pair_pending( + my_context, loader_scope, link_map_addr, lib); } +#endif if (!lib && (!strncmp(rbasename, "libSDL", 6)||!strncmp(rbasename, "libCgGL.so", strlen("libCgGL.so")))) { printf_log(LOG_DEBUG, "%s libSDL need libGL.so.1\n", __func__); const char* libs[] = {"libGL.so.1"}; @@ -2254,9 +2407,15 @@ static void kzt_tb_callback(CPUX86State *env) FILE *f = fopen(rfilename, "rb"); if(!f) { printf_log(LOG_INFO, "%s Error: Cannot open \"%s\"\n", __func__, rfilename); - return; + return 0; } h = LoadAndCheckElfHeader(f, rfilename, 0); + if (!h) { + fclose(f); + printf_log(LOG_INFO, "%s Error: Cannot load ELF header for \"%s\"\n", + __func__, rfilename); + return 0; + } ElfHeadReFix(h, my_lm->l_addr); fclose(f); collectX86free(h); @@ -2271,13 +2430,109 @@ static void kzt_tb_callback(CPUX86State *env) } AddElfHeader(my_context, h); LoadNeededLibs(h, my_context->maplib, &my_context->neededlibs, NULL, 0, 0, my_context); +#ifdef CONFIG_LATX_KZT + relocate_result = RelocateElf(my_context->maplib, NULL, 0, h); + relocate_plt_result = RelocateElfPlt(my_context->maplib, NULL, 0, h); + /* Startup loading has no dlopen scope to publish the pair later. The + callback gate remains held until this function returns. Failed + relocation keeps the legacy fail-open path without publishing. */ + if (lib && !loader_scope_active && relocate_result == 0 && + relocate_plt_result == 0) { + kzt_guest_library_note_loader_pair(my_context, link_map_addr, lib); + } +#else RelocateElf(my_context->maplib, NULL, 0, h); RelocateElfPlt(my_context->maplib, NULL, 0, h); - if (lib) { - AddDebugInfo(LIB_WRAPPED, my_lm->l_name, my_lm->l_map_start, my_lm->l_map_end); +#endif + if (GetElfLoadRange(h->PHEntries, h->numPHEntries, h->delta, + TARGET_PAGE_SIZE, &map_start, &map_end) == 0) { + AddDebugInfo(lib ? LIB_WRAPPED : LIB_EMULATED, my_lm->l_name, + map_start, map_end); + } else { + printf_log(LOG_INFO, + "KZT: cannot determine ELF load range for %s\n", + my_lm->l_name); + } + return 0; +} + +static void kzt_tb_callback(CPUX86State *env) +{ + uintptr_t link_map_addr = env->regs[R_EAX + ld_info->reg]; +#ifdef CONFIG_LATX_KZT + kzt_guest_registry_t *registry = KztGuestRegistryForContext(my_context); + int diagnostics_enabled = kzt_registry_diagnostics_enabled(); + kzt_guest_registry_diagnostic_config_t diagnostic_config = { + .enabled = diagnostics_enabled, + .throttle_limit = 1, + }; + const kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = kzt_callback_read_memory, + .opaque = NULL, + }; + kzt_guest_link_map_identity_t main_identity = { 0 }; + kzt_guest_link_map_identity_t object_identity = { 0 }; + uintptr_t confirmed_main_head = 0; + uintptr_t namespace_head = 0; + uintptr_t predecessor = 0; + int main_namespace; + + (void)kzt_guest_registry_context_get_main_namespace_head( + &my_context->kzt_guest_registry_context, &confirmed_main_head); + if (kzt_guest_link_map_read_identity( + link_map_addr, &reader_ops, &object_identity) != 0) { + main_namespace = -1; + } else if (confirmed_main_head && + kzt_guest_registry_context_has_main_namespace_evidence( + &my_context->kzt_guest_registry_context, registry, + link_map_addr, object_identity.load_bias, + object_identity.dynamic_addr)) { + main_namespace = 1; } else { - AddDebugInfo(LIB_EMULATED, my_lm->l_name, my_lm->l_map_start, my_lm->l_map_end); + if (confirmed_main_head && + kzt_guest_link_map_read_predecessor( + link_map_addr, &reader_ops, &predecessor) == 0 && + predecessor == confirmed_main_head) { + main_namespace = 1; + } else if (confirmed_main_head || + kzt_main_elf_identity(elf_header, &main_identity) == 0) { + main_namespace = kzt_guest_link_map_classify_namespace( + link_map_addr, &main_identity, confirmed_main_head, &reader_ops, + &namespace_head); + } else { + main_namespace = -1; + } } + if (main_namespace == 1 && !confirmed_main_head && + kzt_guest_registry_context_confirm_main_namespace_head( + &my_context->kzt_guest_registry_context, + &my_context->mutex_lock, namespace_head) != 0) { + main_namespace = -1; + } + kzt_observation_adapter_request_t request = { + .enabled = option_kzt || wine_option_kzt, + .diagnostics_enabled = diagnostics_enabled, + .link_map_addr = link_map_addr, + .registry = registry, + .library_bindings = KztGuestLibraryBindingsForContext(my_context), + .loader_scope = &env->kzt_guest_library_loader_scope, + .reader_ops = &reader_ops, + .namespace_id_present = main_namespace == 1, + .namespace_id = 0, + .legacy_flow = kzt_tb_callback_legacy, + .legacy_opaque = &env->kzt_guest_library_loader_scope, + .diagnostic = kzt_callback_diagnostic_log, + .diagnostic_opaque = registry, + }; + + if (registry && diagnostics_enabled) { + (void)kzt_guest_registry_configure_diagnostics(registry, + &diagnostic_config); + } + (void)kzt_observe_guest_object_from_callback(&request, NULL); +#else + (void)kzt_tb_callback_legacy(link_map_addr, env); +#endif } static TranslationBlock* test_tb; static void test_x86free(CPUX86State *env) diff --git a/target/i386/latx/context/wrappedlibc.c b/target/i386/latx/context/wrappedlibc.c index c6bced3cbd0..27b6bd88578 100644 --- a/target/i386/latx/context/wrappedlibc.c +++ b/target/i386/latx/context/wrappedlibc.c @@ -68,6 +68,12 @@ #include "elfloader_private.h" #include "bridge.h" #include "globalsymbols.h" +#include "dlopen_recycle_transaction.h" +#ifdef CONFIG_LATX_KZT +#include "kzt_guest_library_binding.h" +#include "kzt_guest_library_adapter.h" +#endif +#include "kzt_loader_callback_scope.h" #define LIBNAME libc const char* libcName = @@ -77,6 +83,49 @@ const char* libcName = "libc.so.6" #endif ; + +static uint64_t run_guest_dlopen_scoped(uintptr_t function, + void *filename, int flag, + kzt_guest_library_loader_scope_t *scope) +{ +#ifdef CONFIG_LATX_KZT + __MY_CPU; + kzt_guest_library_loader_scope_t previous = + cpu->kzt_guest_library_loader_scope; + kzt_guest_library_bindings_t *bindings = + KztGuestLibraryBindingsForContext(my_context); + int scoped = scope && + kzt_guest_library_loader_scope_begin(bindings, scope) == 0; + uint64_t result; + + if (scoped) cpu->kzt_guest_library_loader_scope = *scope; + result = RunFunctionWithState(function, 2, filename, flag); + cpu->kzt_guest_library_loader_scope = previous; + return result; +#else + (void)scope; + return RunFunctionWithState(function, 2, filename, flag); +#endif +} + +static void finish_guest_dlopen_scoped( + kzt_guest_library_loader_scope_t *scope, uintptr_t link_map_addr, + library_t *library, int publish) +{ +#ifdef CONFIG_LATX_KZT + if (publish && link_map_addr) { + if (library) + kzt_guest_library_publish_loader_pair_scoped( + my_context, scope, link_map_addr, library); + else + kzt_guest_library_publish_loader_observed_scoped( + my_context, scope, link_map_addr); + } + kzt_guest_library_loader_scope_end(scope); +#else + (void)scope; (void)link_map_addr; (void)library; (void)publish; +#endif +} #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-prototypes" void* x86free; @@ -3562,13 +3611,19 @@ int init_x86dlfun(void) kzt_wine_init_x86(); return 0; } -static int callx86dlopen(void *filename, int flag, elfheader_t * h, int is_local) { - struct link_map* ret = (struct link_map*)(uintptr_t)RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlopen, 2, filename, flag); +static int callx86dlopen(void *filename, int flag, elfheader_t * h, + int is_local, int publish_pair) { + kzt_guest_library_loader_scope_t scope = { 0 }; + struct link_map* ret = (struct link_map*)(uintptr_t) + run_guest_dlopen_scoped( + (uintptr_t)my_context->dlprivate->x86dlopen, filename, flag, + &scope); if (ret) { printf_dlsym(LOG_DEBUG, "latx RunFunctionWithState dlopen %s addr %p\n", (char *)filename, (void *)ret->l_addr); h->lib->x86linkmap = ret; } else { //open error + finish_guest_dlopen_scoped(&scope, 0, NULL, 0); return -1; } h->delta = ret->l_addr; @@ -3582,6 +3637,8 @@ static int callx86dlopen(void *filename, int flag, elfheader_t * h, int is_local printf_dlsym(LOG_INFO, "Failure to Add lib => fail\n"); lsassert(0); } + finish_guest_dlopen_scoped(&scope, (uintptr_t)ret, h->lib, + publish_pair); return 0; } static void LatxResetElf(elfheader_t * h) @@ -3592,6 +3649,72 @@ static void LatxResetElf(elfheader_t * h) h->latx_type = 0; h->latx_hasfix = 0; } + +typedef struct dlopen_recycle_context { + void *filename; + int flag; + elfheader_t *header; + library_t *library; + int is_local; + int guest_open_required; +} dlopen_recycle_context_t; + +static void recycle_reset_loader_state(dlopen_recycle_context_t *recycle) +{ + linkmap_t *lm; + + recycle->library->x86linkmap = NULL; + if (!recycle->header) + return; + LatxResetElf(recycle->header); + recycle->header->delta = 0; + lm = getLinkMapLib(recycle->library); + if (lm) + lm->l_addr = 0; +} + +static void recycle_prepare(void *opaque) +{ + dlopen_recycle_context_t *recycle = opaque; + if (!recycle->guest_open_required) + return; + recycle_reset_loader_state(recycle); +} + +static int recycle_guest_open(void *opaque) +{ + dlopen_recycle_context_t *recycle = opaque; +#ifdef CONFIG_LATX_KZT + if (recycle->guest_open_required && + kzt_guest_library_reactivate( + KztGuestLibraryBindingsForContext(recycle->library->context), + recycle->library) != 0) + return -1; +#endif + return recycle->guest_open_required + ? callx86dlopen(recycle->filename, recycle->flag, + recycle->header, recycle->is_local, 0) + : 0; +} + +static int recycle_reload(void *opaque) +{ + dlopen_recycle_context_t *recycle = opaque; + return ReloadLibrary(recycle->library); +} + +static void recycle_rollback(void *opaque, int guest_opened) +{ + dlopen_recycle_context_t *recycle = opaque; + InactiveLibrary(recycle->library); + if (guest_opened && recycle->guest_open_required && + recycle->library->x86linkmap && + my_context->dlprivate->x86dlclose) + (void)RunFunctionWithState( + (uintptr_t)my_context->dlprivate->x86dlclose, 1, + recycle->library->x86linkmap); + recycle_reset_loader_state(recycle); +} void* my_dlopen(void *filename, int flag){ // TODO, handling special values for filename, like RTLD_SELF? // TODO, handling flags? @@ -3647,14 +3770,31 @@ void* my_dlopen(void *filename, int flag){ int idx = GetElfIndex(dl->libs[i]); if(idx!=-1) { printf_dlsym(LOG_DEBUG, "dlopen: Recycling, calling Init for %p (%s)\n", (void*)(i+1), rfilename); - //TODO + dlopen_recycle_context_t recycle = { + .filename = rfilename, + .flag = flag, + .library = dl->libs[i], + .is_local = is_local, + }; if (IsEmuLib(dl->libs[i])) { - elfheader_t * h = my_context->elfs[idx]; - lsassert(h); - LatxResetElf(h); - callx86dlopen(rfilename, flag, h, is_local); + recycle.header = my_context->elfs[idx]; + lsassert(recycle.header); + recycle.guest_open_required = 1; + } + void *handle = dlopen_recycle_transaction( + (void *)(i + 1), &dl->count[i], !(flag & 0x4), + &recycle, recycle_prepare, recycle_guest_open, + recycle_reload, recycle_rollback); + if (!handle) { + if(!dl->last_error) + dl->last_error = box_malloc(129); + snprintf(dl->last_error, 129, + "Cannot recycle dlopen(\"%s\"/%p, %X)\n", + rfilename, filename, flag); + return NULL; } - ReloadLibrary(dl->libs[i]); // reset memory image, redo reloc, run inits + printf_dlsym(LOG_DEBUG, "dlopen: Recycling %s/%p count=%ld (dlopened=%ld, elf_index=%d)\n", rfilename, (void*)(i+1), dl->count[i], dl->dlopened[i], GetElfIndex(dl->libs[i])); + return handle; } } if(!(flag&0x4)) @@ -3679,7 +3819,12 @@ void* my_dlopen(void *filename, int flag){ printf_dlsym(LOG_DEBUG, "warning call x86dlopen filename is %s %x\n", (char *)filename, flag); return NULL; #else - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlopen, 2, filename, flag); + kzt_guest_library_loader_scope_t scope = { 0 }; + uint64_t ret = run_guest_dlopen_scoped( + (uintptr_t)my_context->dlprivate->x86dlopen, filename, flag, + &scope); + finish_guest_dlopen_scoped(&scope, (uintptr_t)ret, NULL, + ret != 0); printf_dlsym(LOG_DEBUG, "warning call call x86dlopen filename %s %x ret=0x%lx\n", (char *)filename, flag, ret); //lsassert(0); if (ret) { @@ -3692,15 +3837,14 @@ void* my_dlopen(void *filename, int flag){ return NULL; #endif } - if(AddNeededLib(NULL, NULL, NULL, is_local, bindnow, libs, 1, my_context)) { + if(AddNeededLibWithLibrary(NULL, NULL, NULL, is_local, bindnow, + rfilename, my_context, &lib) || !lib) { printf_dlsym(strchr(rfilename,'/')?LOG_DEBUG:LOG_INFO, "Warning: Cannot dlopen(\"%s\"/%p, %X)\n", rfilename, filename, flag); if(!dl->last_error) dl->last_error = box_malloc(129); snprintf(dl->last_error, 129, "Cannot dlopen(\"%s\"/%p, %X)\n", rfilename, filename, flag); return NULL; } - lib = GetLibInternal(rfilename); - if (!lib) return NULL; lib->x86dlopenflag = flag; if (lib && lib->type == LIB_EMULATED) { // if dlopened = 0 ---> lib added but not loaded @@ -3709,7 +3853,7 @@ void* my_dlopen(void *filename, int flag){ elfheader_t * h = my_context->elfs[libidx]; lsassert(h); if (!h->latx_hasfix || !lib->x86linkmap) {//lib->x86linkmap is null ---- this lib has been needed by other elf and opened - callx86dlopen(rfilename, flag, h, is_local); + callx86dlopen(rfilename, flag, h, is_local, 1); } } //TODO:RunDeferedElfInit; @@ -3928,13 +4072,20 @@ void* my_dlsym(void *handle, void *symbol){ #if 1 if(!dl->libs[nlib]->x86linkmap) { //redlopen - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlopen, 2, dl->libs[nlib]->name, dl->libs[nlib]->x86dlopenflag); + kzt_guest_library_loader_scope_t scope = { 0 }; + uint64_t ret = run_guest_dlopen_scoped( + (uintptr_t)my_context->dlprivate->x86dlopen, + dl->libs[nlib]->name, dl->libs[nlib]->x86dlopenflag, + &scope); if (!ret) {//user sometime test for finding a func. + finish_guest_dlopen_scoped(&scope, 0, NULL, 0); printf_dlsym(LOG_NEVER, "redlopen %p return %p\n", rsymbol, (void*)NULL); return NULL; } lsassert(ret); dl->libs[nlib]->x86linkmap = (void *)ret; + finish_guest_dlopen_scoped( + &scope, (uintptr_t)ret, dl->libs[nlib], 1); ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, dl->libs[nlib]->x86linkmap , cpu->regs[R_ESI]); printf_dlsym(LOG_DEBUG, "call x86dlsym filename %s is wrapped but not find symbol, dlsym(%p, %s) ret=0x%lx\n", dl->libs[nlib]->name, dl->libs[nlib]->x86linkmap, (char *)cpu->regs[R_ESI], ret); diff --git a/target/i386/latx/context/wrappedlibdl.c b/target/i386/latx/context/wrappedlibdl.c index 1f7c3ce8626..611c4f5c637 100644 --- a/target/i386/latx/context/wrappedlibdl.c +++ b/target/i386/latx/context/wrappedlibdl.c @@ -27,6 +27,12 @@ #include "callback.h" #include "myalign.h" #include "fileutils.h" +#include "dlopen_recycle_transaction.h" +#ifdef CONFIG_LATX_KZT +#include "kzt_guest_library_adapter.h" +#include "kzt_guest_library_binding.h" +#endif +#include "kzt_loader_callback_scope.h" #define FORWORDBACK 0 dlprivate_t *NewDLPrivate(void) { @@ -81,13 +87,62 @@ int init_x86dlfun(void) my_context->dlprivate->x86dlinfo = rsyms[5]; return 0; } -static int callx86dlopen(void *filename, int flag, elfheader_t * h, int is_local) { - struct link_map* ret = (struct link_map*)(uintptr_t)RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlopen, 2, filename, flag); +static uint64_t run_guest_dlopen_scoped(uintptr_t function, + void *filename, int flag, + kzt_guest_library_loader_scope_t *scope) +{ +#ifdef CONFIG_LATX_KZT + __MY_CPU; + kzt_guest_library_loader_scope_t previous = + cpu->kzt_guest_library_loader_scope; + kzt_guest_library_bindings_t *bindings = + KztGuestLibraryBindingsForContext(my_context); + int scoped = scope && + kzt_guest_library_loader_scope_begin(bindings, scope) == 0; + uint64_t result; + + if (scoped) cpu->kzt_guest_library_loader_scope = *scope; + result = RunFunctionWithState(function, 2, filename, flag); + cpu->kzt_guest_library_loader_scope = previous; + return result; +#else + (void)scope; + return RunFunctionWithState(function, 2, filename, flag); +#endif +} + +static void finish_guest_dlopen_scoped( + kzt_guest_library_loader_scope_t *scope, uintptr_t link_map_addr, + library_t *library, int publish) +{ +#ifdef CONFIG_LATX_KZT + if (publish && link_map_addr) { + if (library) + kzt_guest_library_publish_loader_pair_scoped( + my_context, scope, link_map_addr, library); + else + kzt_guest_library_publish_loader_observed_scoped( + my_context, scope, link_map_addr); + } + kzt_guest_library_loader_scope_end(scope); +#else + (void)scope; (void)link_map_addr; (void)library; (void)publish; +#endif +} + +static int callx86dlopen(void *filename, int flag, elfheader_t * h, + int is_local, int publish_pair) { + kzt_guest_library_loader_scope_t scope = { 0 }; + struct link_map* ret = (struct link_map*)(uintptr_t) + run_guest_dlopen_scoped( + (uintptr_t)my_context->dlprivate->x86dlopen, filename, flag, + &scope); if (ret) { printf_dlsym(LOG_DEBUG, "latx RunFunctionWithState dlopen %s addr %p\n", (char *)filename, (void *)ret->l_addr); h->lib->x86linkmap = ret; } else { //open error + finish_guest_dlopen_scoped(&scope, 0, NULL, 0); return -1; } h->delta = ret->l_addr; @@ -101,6 +156,8 @@ static int callx86dlopen(void *filename, int flag, elfheader_t * h, int is_local printf_dlsym(LOG_INFO, "Failure to Add lib => fail\n"); lsassert(0); } + finish_guest_dlopen_scoped(&scope, (uintptr_t)ret, h->lib, + publish_pair); return 0; } static void LatxResetElf(elfheader_t * h) @@ -111,6 +168,72 @@ static void LatxResetElf(elfheader_t * h) h->latx_type = 0; h->latx_hasfix = 0; } + +typedef struct dlopen_recycle_context { + void *filename; + int flag; + elfheader_t *header; + library_t *library; + int is_local; + int guest_open_required; +} dlopen_recycle_context_t; + +static void recycle_reset_loader_state(dlopen_recycle_context_t *recycle) +{ + linkmap_t *lm; + + recycle->library->x86linkmap = NULL; + if (!recycle->header) + return; + LatxResetElf(recycle->header); + recycle->header->delta = 0; + lm = getLinkMapLib(recycle->library); + if (lm) + lm->l_addr = 0; +} + +static void recycle_prepare(void *opaque) +{ + dlopen_recycle_context_t *recycle = opaque; + if (!recycle->guest_open_required) + return; + recycle_reset_loader_state(recycle); +} + +static int recycle_guest_open(void *opaque) +{ + dlopen_recycle_context_t *recycle = opaque; +#ifdef CONFIG_LATX_KZT + if (recycle->guest_open_required && + kzt_guest_library_reactivate( + KztGuestLibraryBindingsForContext(recycle->library->context), + recycle->library) != 0) + return -1; +#endif + return recycle->guest_open_required + ? callx86dlopen(recycle->filename, recycle->flag, + recycle->header, recycle->is_local, 0) + : 0; +} + +static int recycle_reload(void *opaque) +{ + dlopen_recycle_context_t *recycle = opaque; + return ReloadLibrary(recycle->library); +} + +static void recycle_rollback(void *opaque, int guest_opened) +{ + dlopen_recycle_context_t *recycle = opaque; + InactiveLibrary(recycle->library); + if (guest_opened && recycle->guest_open_required && + recycle->library->x86linkmap && + my_context->dlprivate->x86dlclose) + (void)RunFunctionWithState( + (uintptr_t)my_context->dlprivate->x86dlclose, 1, + recycle->library->x86linkmap); + recycle_reset_loader_state(recycle); +} void* my_dlopen(void *filename, int flag){ // TODO, handling special values for filename, like RTLD_SELF? // TODO, handling flags? @@ -166,14 +289,31 @@ void* my_dlopen(void *filename, int flag){ int idx = GetElfIndex(dl->libs[i]); if(idx!=-1) { printf_dlsym(LOG_DEBUG, "dlopen: Recycling, calling Init for %p (%s)\n", (void*)(i+1), rfilename); - //TODO + dlopen_recycle_context_t recycle = { + .filename = rfilename, + .flag = flag, + .library = dl->libs[i], + .is_local = is_local, + }; if (IsEmuLib(dl->libs[i])) { - elfheader_t * h = my_context->elfs[idx]; - lsassert(h); - LatxResetElf(h); - callx86dlopen(rfilename, flag, h, is_local); + recycle.header = my_context->elfs[idx]; + lsassert(recycle.header); + recycle.guest_open_required = 1; } - ReloadLibrary(dl->libs[i]); // reset memory image, redo reloc, run inits + void *handle = dlopen_recycle_transaction( + (void *)(i + 1), &dl->count[i], !(flag & 0x4), + &recycle, recycle_prepare, recycle_guest_open, + recycle_reload, recycle_rollback); + if (!handle) { + if(!dl->last_error) + dl->last_error = box_malloc(129); + snprintf(dl->last_error, 129, + "Cannot recycle dlopen(\"%s\"/%p, %X)\n", + rfilename, filename, flag); + return NULL; + } + printf_dlsym(LOG_DEBUG, "dlopen: Recycling %s/%p count=%ld (dlopened=%ld, elf_index=%d)\n", rfilename, (void*)(i+1), dl->count[i], dl->dlopened[i], GetElfIndex(dl->libs[i])); + return handle; } } if(!(flag&0x4)) @@ -198,7 +338,12 @@ void* my_dlopen(void *filename, int flag){ printf_dlsym(LOG_DEBUG, "warning call x86dlopen filename is %s %x\n", (char *)filename, flag); return NULL; #else - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlopen, 2, filename, flag); + kzt_guest_library_loader_scope_t scope = { 0 }; + uint64_t ret = run_guest_dlopen_scoped( + (uintptr_t)my_context->dlprivate->x86dlopen, filename, flag, + &scope); + finish_guest_dlopen_scoped(&scope, (uintptr_t)ret, NULL, + ret != 0); printf_dlsym(LOG_DEBUG, "warning call call x86dlopen filename %s %x ret=0x%lx\n", (char *)filename, flag, ret); //lsassert(0); if (ret) { @@ -211,15 +356,14 @@ void* my_dlopen(void *filename, int flag){ return NULL; #endif } - if(AddNeededLib(NULL, NULL, NULL, is_local, bindnow, libs, 1, my_context)) { + if(AddNeededLibWithLibrary(NULL, NULL, NULL, is_local, bindnow, + rfilename, my_context, &lib) || !lib) { printf_dlsym(strchr(rfilename,'/')?LOG_DEBUG:LOG_INFO, "Warning: Cannot dlopen(\"%s\"/%p, %X)\n", rfilename, filename, flag); if(!dl->last_error) dl->last_error = box_malloc(129); snprintf(dl->last_error, 129, "Cannot dlopen(\"%s\"/%p, %X)\n", rfilename, filename, flag); return NULL; } - lib = GetLibInternal(rfilename); - if (!lib) return NULL; lib->x86dlopenflag = flag; if (lib && lib->type == LIB_EMULATED) { // if dlopened = 0 ---> lib added but not loaded @@ -228,7 +372,7 @@ void* my_dlopen(void *filename, int flag){ elfheader_t * h = my_context->elfs[libidx]; lsassert(h); if (!h->latx_hasfix || !lib->x86linkmap) {//lib->x86linkmap is null ---- this lib has been needed by other elf and opened - callx86dlopen(rfilename, flag, h, is_local); + callx86dlopen(rfilename, flag, h, is_local, 1); } } //TODO:RunDeferedElfInit; @@ -446,13 +590,20 @@ void* my_dlsym(void *handle, void *symbol){ #if 1 if(!dl->libs[nlib]->x86linkmap) { //redlopen - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlopen, 2, dl->libs[nlib]->name, dl->libs[nlib]->x86dlopenflag); + kzt_guest_library_loader_scope_t scope = { 0 }; + uint64_t ret = run_guest_dlopen_scoped( + (uintptr_t)my_context->dlprivate->x86dlopen, + dl->libs[nlib]->name, dl->libs[nlib]->x86dlopenflag, + &scope); if (!ret) {//user sometime test for finding a func. + finish_guest_dlopen_scoped(&scope, 0, NULL, 0); printf_dlsym(LOG_NEVER, "redlopen %p return %p\n", rsymbol, (void*)NULL); return NULL; } lsassert(ret); dl->libs[nlib]->x86linkmap = (void *)ret; + finish_guest_dlopen_scoped( + &scope, (uintptr_t)ret, dl->libs[nlib], 1); ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, dl->libs[nlib]->x86linkmap , cpu->regs[R_ESI]); printf_dlsym(LOG_DEBUG, "call x86dlsym filename %s is wrapped but not find symbol, dlsym(%p, %s) ret=0x%lx\n", dl->libs[nlib]->name, dl->libs[nlib]->x86linkmap, (char *)cpu->regs[R_ESI], ret); @@ -643,4 +794,3 @@ int my_dlinfo(void* handle, int request, void* info) } #include "wrappedlib_init.h" - diff --git a/target/i386/latx/include/box64context.h b/target/i386/latx/include/box64context.h index 489d1b79ad4..af02bfe7e37 100755 --- a/target/i386/latx/include/box64context.h +++ b/target/i386/latx/include/box64context.h @@ -5,6 +5,9 @@ #include "dictionnary.h" #include #include "debug.h" +#include "kzt_guest_library_binding.h" +#include "kzt_guest_registry_context.h" +#include "kzt_patch_spike_guard.h" typedef struct elfheader_s elfheader_t; typedef struct cleanup_s cleanup_t; @@ -346,12 +349,29 @@ typedef struct box64context_s { int latx_kzt_debugcap; int latx_kzt_debugsize; // number of latx_kzt_debug #endif +#ifdef CONFIG_LATX_KZT + kzt_guest_registry_context_t kzt_guest_registry_context; + kzt_guest_library_access_t kzt_guest_library_access; + kzt_patch_spike_guard_t kzt_patch_spike_guard; + pthread_mutex_t kzt_bridge_mutex; + uintptr_t kzt_lazy_completion_bridge; +#endif } box64context_t; extern box64context_t *my_context; // global context box64context_t *NewBox64Context(int argc); void FreeBox64Context(box64context_t** context); +#ifdef CONFIG_LATX_KZT +kzt_guest_registry_t *KztGuestRegistryForContext(box64context_t *context); +kzt_guest_library_bindings_t *KztGuestLibraryBindingsForContext( + box64context_t *context); +int KztGuestLibraryLookupForContext( + box64context_t *context, + const kzt_guest_library_binding_key_t *key, + kzt_guest_library_handle_t *handle); +kzt_patch_spike_guard_t *KztPatchSpikeGuardForContext(box64context_t *context); +#endif // return the index of the added header int AddElfHeader(box64context_t* ctx, elfheader_t* head); diff --git a/target/i386/latx/include/debug.h b/target/i386/latx/include/debug.h index 2f1ec47042f..8560d18bc5d 100755 --- a/target/i386/latx/include/debug.h +++ b/target/i386/latx/include/debug.h @@ -19,6 +19,7 @@ extern int box64_pagesize; extern uintptr_t box64_load_addr; extern int dlsym_error; // log dlsym error extern int kzt_call_log; +extern int kzt_registry_diagnostics; extern int allow_missing_libs; extern int box64_nogtk; extern int box64_prefer_wrapped; @@ -56,6 +57,19 @@ extern char* libGL; #define printf_dlsym(L, ...) ((void)0) #define printf_kzt_call(L, ...) ((void)0) #endif +#if defined(CONFIG_LATX_KZT) +#define kzt_registry_diagnostics_enabled() \ + (kzt_registry_diagnostics || (LOG_DEBUG <= relocation_log)) +#define printf_kzt_registry_diagnostics(...) \ + do { \ + if (kzt_registry_diagnostics_enabled()) { \ + fprintf(stderr, __VA_ARGS__); \ + } \ + } while (0) +#else +#define kzt_registry_diagnostics_enabled() 0 +#define printf_kzt_registry_diagnostics(...) ((void)0) +#endif #define EXPORT __attribute__((visibility("default"))) #define EXPORTDYN diff --git a/target/i386/latx/include/dlopen_recycle_transaction.h b/target/i386/latx/include/dlopen_recycle_transaction.h new file mode 100644 index 00000000000..89ac4df90f5 --- /dev/null +++ b/target/i386/latx/include/dlopen_recycle_transaction.h @@ -0,0 +1,16 @@ +#ifndef DLOPEN_RECYCLE_TRANSACTION_H +#define DLOPEN_RECYCLE_TRANSACTION_H + +#include + +typedef int (*dlopen_recycle_step_fn)(void *opaque); +typedef void (*dlopen_recycle_prepare_fn)(void *opaque); +typedef void (*dlopen_recycle_rollback_fn)(void *opaque, int guest_opened); + +void *dlopen_recycle_transaction( + void *success_handle, size_t *count, int increment_count, void *opaque, + dlopen_recycle_prepare_fn prepare, dlopen_recycle_step_fn guest_open, + dlopen_recycle_step_fn reload, + dlopen_recycle_rollback_fn rollback); + +#endif diff --git a/target/i386/latx/include/elf_plt_relocation.h b/target/i386/latx/include/elf_plt_relocation.h new file mode 100644 index 00000000000..49f0f860247 --- /dev/null +++ b/target/i386/latx/include/elf_plt_relocation.h @@ -0,0 +1,10 @@ +#ifndef ELF_PLT_RELOCATION_H +#define ELF_PLT_RELOCATION_H + +typedef int (*elf_plt_relocation_apply_fn)(void *opaque, + int *need_resolver); + +int elf_plt_relocation_apply(elf_plt_relocation_apply_fn apply, void *opaque, + int *need_resolver); + +#endif diff --git a/target/i386/latx/include/elfmap.h b/target/i386/latx/include/elfmap.h new file mode 100644 index 00000000000..9ef2d8f5f30 --- /dev/null +++ b/target/i386/latx/include/elfmap.h @@ -0,0 +1,21 @@ +#ifndef LATX_ELFMAP_H +#define LATX_ELFMAP_H + +#include +#include + +#include "elf.h" + +int GetElfLoadRange(const Elf64_Phdr *program_headers, + size_t program_header_count, + uintptr_t load_bias, + uintptr_t page_size, + uintptr_t *map_start, + uintptr_t *map_end); + +int GetElfDynamicAddress(const Elf64_Phdr *program_headers, + size_t program_header_count, + uintptr_t load_bias, + uintptr_t *dynamic_addr); + +#endif diff --git a/target/i386/latx/include/kzt_bridge_exact.h b/target/i386/latx/include/kzt_bridge_exact.h new file mode 100644 index 00000000000..42c9b027bef --- /dev/null +++ b/target/i386/latx/include/kzt_bridge_exact.h @@ -0,0 +1,11 @@ +#ifndef KZT_BRIDGE_EXACT_H +#define KZT_BRIDGE_EXACT_H + +#include + +typedef void (*kzt_bridge_wrapper_t)(uintptr_t fnc); + +int kzt_bridge_is_exact(uintptr_t target, kzt_bridge_wrapper_t wrapper, + void *native_symbol); + +#endif diff --git a/target/i386/latx/include/kzt_guest_dynamic.h b/target/i386/latx/include/kzt_guest_dynamic.h new file mode 100644 index 00000000000..69d2579414a --- /dev/null +++ b/target/i386/latx/include/kzt_guest_dynamic.h @@ -0,0 +1,47 @@ +#ifndef KZT_GUEST_DYNAMIC_H +#define KZT_GUEST_DYNAMIC_H + +#include +#include + +#include "elf.h" +#include "kzt_guest_dynamic_view.h" +#include "kzt_guest_link_map_reader.h" + +#ifndef DT_GNU_HASH +#define DT_GNU_HASH 0x6ffffef5 +#endif + + +typedef enum kzt_guest_dynamic_error { + KZT_GUEST_DYNAMIC_ERROR_NONE = 0, + KZT_GUEST_DYNAMIC_ERROR_INVALID_ARGUMENT, + KZT_GUEST_DYNAMIC_ERROR_ALLOCATION_FAILURE, + KZT_GUEST_DYNAMIC_ERROR_READ_FAILURE, + KZT_GUEST_DYNAMIC_ERROR_SCAN_LIMIT_EXCEEDED, + KZT_GUEST_DYNAMIC_ERROR_TOO_MANY_NEEDED, +} kzt_guest_dynamic_error_t; + +typedef struct kzt_guest_dynamic_parse_result { + kzt_guest_dynamic_status_t status; + kzt_guest_dynamic_error_t error; + size_t entry_count; + uintptr_t read_error_addr; + size_t scan_limit; + size_t unknown_tag_count; + int64_t first_unknown_tag; + size_t first_unknown_tag_index; + kzt_guest_dynamic_view_t view; +} kzt_guest_dynamic_parse_result_t; + +int kzt_guest_dynamic_parse( + uintptr_t dynamic_addr, + uintptr_t load_bias, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_dynamic_parse_result_t *result); + +void kzt_guest_dynamic_view_destroy(kzt_guest_dynamic_view_t *view); +void kzt_guest_dynamic_parse_result_clear( + kzt_guest_dynamic_parse_result_t *result); + +#endif diff --git a/target/i386/latx/include/kzt_guest_dynamic_diagnostics.h b/target/i386/latx/include/kzt_guest_dynamic_diagnostics.h new file mode 100644 index 00000000000..1225ff90a5e --- /dev/null +++ b/target/i386/latx/include/kzt_guest_dynamic_diagnostics.h @@ -0,0 +1,130 @@ +#ifndef KZT_GUEST_DYNAMIC_DIAGNOSTICS_H +#define KZT_GUEST_DYNAMIC_DIAGNOSTICS_H + +#include +#include + +#include "kzt_guest_dynamic.h" + +#define KZT_GUEST_DYNAMIC_DIAGNOSTIC_FIELD_LIMIT 32 + +typedef enum kzt_guest_dynamic_diagnostic_match { + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED = 0, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_OLD, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_NEW, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH, +} kzt_guest_dynamic_diagnostic_match_t; + +typedef enum kzt_guest_dynamic_diagnostic_difference_kind { + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_NONE = 0, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_STATUS, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_ENTRY_COUNT, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_UNKNOWN_TAGS, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_FIELD, +} kzt_guest_dynamic_diagnostic_difference_kind_t; + +typedef struct kzt_guest_dynamic_diagnostic_field { + const char *name; + kzt_guest_dynamic_diagnostic_match_t match; + int old_present; + uint64_t old_value; + kzt_guest_dynamic_address_semantics_t old_address_semantics; + size_t old_count; + int new_present; + uint64_t new_value; + kzt_guest_dynamic_address_semantics_t new_address_semantics; + size_t new_count; +} kzt_guest_dynamic_diagnostic_field_t; + +typedef struct kzt_guest_dynamic_diagnostic_report { + kzt_guest_dynamic_status_t old_status; + kzt_guest_dynamic_status_t new_status; + kzt_guest_dynamic_error_t old_error; + kzt_guest_dynamic_error_t new_error; + uintptr_t old_read_error_addr; + uintptr_t new_read_error_addr; + size_t old_entry_count; + size_t new_entry_count; + size_t old_unknown_tag_count; + size_t new_unknown_tag_count; + int64_t old_first_unknown_tag; + int64_t new_first_unknown_tag; + size_t old_first_unknown_tag_index; + size_t new_first_unknown_tag_index; + int old_truncated; + int new_truncated; + int old_read_error; + int new_read_error; + + kzt_guest_dynamic_diagnostic_match_t status_match; + kzt_guest_dynamic_diagnostic_match_t entry_count_match; + kzt_guest_dynamic_diagnostic_match_t unknown_tags_match; + + size_t field_count; + size_t matched_count; + size_t missing_old_count; + size_t missing_new_count; + size_t mismatch_count; + size_t difference_count; + size_t blocking_count; + + kzt_guest_dynamic_diagnostic_field_t + fields[KZT_GUEST_DYNAMIC_DIAGNOSTIC_FIELD_LIMIT]; +} kzt_guest_dynamic_diagnostic_report_t; + +typedef struct kzt_guest_dynamic_diagnostic_summary { + uintptr_t link_map_addr; + unsigned long generation; + int matched; + int blocking; + size_t difference_count; + size_t blocking_count; + + kzt_guest_dynamic_status_t old_status; + kzt_guest_dynamic_status_t new_status; + size_t old_entry_count; + size_t new_entry_count; + size_t old_unknown_tag_count; + size_t new_unknown_tag_count; + int64_t old_first_unknown_tag; + int64_t new_first_unknown_tag; + size_t old_first_unknown_tag_index; + size_t new_first_unknown_tag_index; + + kzt_guest_dynamic_diagnostic_difference_kind_t first_difference_kind; + const char *first_difference_name; + kzt_guest_dynamic_diagnostic_match_t first_difference_match; + int first_old_present; + int first_new_present; + uint64_t first_old_value; + uint64_t first_new_value; + size_t first_old_count; + size_t first_new_count; + int64_t first_old_tag; + int64_t first_new_tag; + size_t first_old_tag_index; + size_t first_new_tag_index; +} kzt_guest_dynamic_diagnostic_summary_t; + +int kzt_guest_dynamic_diagnostics_summarize( + const kzt_guest_dynamic_diagnostic_report_t *report, + uintptr_t link_map_addr, + unsigned long generation, + kzt_guest_dynamic_diagnostic_summary_t *summary); + +int kzt_guest_dynamic_diagnostics_compare( + const kzt_guest_dynamic_parse_result_t *old_result, + const kzt_guest_dynamic_parse_result_t *new_result, + kzt_guest_dynamic_diagnostic_report_t *report); + +const kzt_guest_dynamic_diagnostic_field_t * +kzt_guest_dynamic_diagnostic_find_field( + const kzt_guest_dynamic_diagnostic_report_t *report, + const char *name); + +int kzt_guest_dynamic_diagnostics_format_summary( + const kzt_guest_dynamic_diagnostic_summary_t *summary, + char *buffer, + size_t buffer_size); + +#endif diff --git a/target/i386/latx/include/kzt_guest_dynamic_view.h b/target/i386/latx/include/kzt_guest_dynamic_view.h new file mode 100644 index 00000000000..0bd61f53032 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_dynamic_view.h @@ -0,0 +1,68 @@ +#ifndef KZT_GUEST_DYNAMIC_VIEW_H +#define KZT_GUEST_DYNAMIC_VIEW_H + +#include +#include + +#define KZT_GUEST_DYNAMIC_SCAN_LIMIT 512 +#define KZT_GUEST_DYNAMIC_NEEDED_LIMIT 32 + +typedef enum kzt_guest_dynamic_status { + KZT_GUEST_DYNAMIC_COMPLETE = 0, + KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL, + KZT_GUEST_DYNAMIC_READ_ERROR, + KZT_GUEST_DYNAMIC_ERROR, +} kzt_guest_dynamic_status_t; + +typedef enum kzt_guest_dynamic_address_semantics { + KZT_GUEST_DYNAMIC_ADDRESS_UNKNOWN = 0, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS, + KZT_GUEST_DYNAMIC_STRING_TABLE_OFFSET, + KZT_GUEST_DYNAMIC_SCALAR, +} kzt_guest_dynamic_address_semantics_t; + +typedef struct kzt_guest_dynamic_field { + int present; + uint64_t value; + kzt_guest_dynamic_address_semantics_t address_semantics; +} kzt_guest_dynamic_field_t; + +typedef struct kzt_guest_dynamic_view { + uintptr_t dynamic_addr; + uintptr_t load_bias; + kzt_guest_dynamic_status_t status; + size_t entry_count; + int has_null; + size_t scan_limit; + size_t unknown_tag_count; + int64_t first_unknown_tag; + size_t first_unknown_tag_index; + + kzt_guest_dynamic_field_t symtab; + kzt_guest_dynamic_field_t strtab; + kzt_guest_dynamic_field_t syment; + kzt_guest_dynamic_field_t strsz; + kzt_guest_dynamic_field_t hash; + kzt_guest_dynamic_field_t gnu_hash; + kzt_guest_dynamic_field_t versym; + kzt_guest_dynamic_field_t verneed; + kzt_guest_dynamic_field_t verneednum; + kzt_guest_dynamic_field_t verdef; + kzt_guest_dynamic_field_t verdefnum; + kzt_guest_dynamic_field_t rela; + kzt_guest_dynamic_field_t relasz; + kzt_guest_dynamic_field_t relaent; + kzt_guest_dynamic_field_t rel; + kzt_guest_dynamic_field_t relsz; + kzt_guest_dynamic_field_t relent; + kzt_guest_dynamic_field_t jmprel; + kzt_guest_dynamic_field_t pltrelsz; + kzt_guest_dynamic_field_t pltrel; + kzt_guest_dynamic_field_t pltgot; + + uint64_t needed_offsets[KZT_GUEST_DYNAMIC_NEEDED_LIMIT]; + size_t needed_count; + kzt_guest_dynamic_address_semantics_t needed_address_semantics; +} kzt_guest_dynamic_view_t; + +#endif diff --git a/target/i386/latx/include/kzt_guest_library_adapter.h b/target/i386/latx/include/kzt_guest_library_adapter.h new file mode 100644 index 00000000000..04138e188df --- /dev/null +++ b/target/i386/latx/include/kzt_guest_library_adapter.h @@ -0,0 +1,29 @@ +#ifndef KZT_GUEST_LIBRARY_ADAPTER_H +#define KZT_GUEST_LIBRARY_ADAPTER_H + +#include + +#include "kzt_loader_callback_scope.h" + +typedef struct box64context_s box64context_t; +typedef struct library_s library_t; + +/* Shared production adapter used by wrappedlibc, wrappedlibdl, and loader + * callback paths whenever one operation owns both exact values. */ +void kzt_guest_library_note_loader_pair(box64context_t *context, + uintptr_t link_map_addr, + library_t *library); +void kzt_guest_library_note_loader_pair_pending( + box64context_t *context, + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr, library_t *library); +void kzt_guest_library_publish_loader_pair_scoped( + box64context_t *context, + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr, library_t *library); +void kzt_guest_library_publish_loader_observed_scoped( + box64context_t *context, + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr); + +#endif diff --git a/target/i386/latx/include/kzt_guest_library_binding.h b/target/i386/latx/include/kzt_guest_library_binding.h new file mode 100644 index 00000000000..ec5946f5cd0 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_library_binding.h @@ -0,0 +1,214 @@ +#ifndef KZT_GUEST_LIBRARY_BINDING_H +#define KZT_GUEST_LIBRARY_BINDING_H + +#include +#include +#include + +#include "kzt_loader_callback_scope.h" + +typedef struct library_s library_t; +typedef struct kzt_guest_registry kzt_guest_registry_t; +typedef struct kzt_guest_library_bindings kzt_guest_library_bindings_t; + +/* Embedded in box64context_t. This is the only lifetime gate for lookup: + * context teardown closes it before waiting for lookup handles. */ +typedef struct kzt_guest_library_access { + pthread_mutex_t lock; + kzt_guest_library_bindings_t *bindings; + int accepting; + int initialized; +} kzt_guest_library_access_t; + +typedef enum kzt_guest_library_object_type { + KZT_GUEST_LIBRARY_OBJECT_MAIN = 0, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED, + KZT_GUEST_LIBRARY_OBJECT_EMULATED, + KZT_GUEST_LIBRARY_OBJECT_UNSUPPORTED, +} kzt_guest_library_object_type_t; + +typedef enum kzt_guest_library_namespace_kind { + KZT_GUEST_LIBRARY_NAMESPACE_MAIN = 0, + KZT_GUEST_LIBRARY_NAMESPACE_EXPLICIT, + KZT_GUEST_LIBRARY_NAMESPACE_UNSUPPORTED, +} kzt_guest_library_namespace_kind_t; + +typedef enum kzt_guest_library_binding_state { + KZT_GUEST_LIBRARY_BINDING_LIVE = 0, + KZT_GUEST_LIBRARY_BINDING_UNLOADING, + KZT_GUEST_LIBRARY_BINDING_DEAD, +} kzt_guest_library_binding_state_t; + +typedef enum kzt_guest_library_binding_result { + KZT_GUEST_LIBRARY_BINDING_ADDED = 0, + KZT_GUEST_LIBRARY_BINDING_UNCHANGED, + KZT_GUEST_LIBRARY_BINDING_PENDING, + KZT_GUEST_LIBRARY_BINDING_CANCELLED, + KZT_GUEST_LIBRARY_BINDING_RETIRE_OWNED, + KZT_GUEST_LIBRARY_BINDING_CONFLICT, + KZT_GUEST_LIBRARY_BINDING_DISABLED, + KZT_GUEST_LIBRARY_BINDING_ERROR, +} kzt_guest_library_binding_result_t; + +typedef struct kzt_guest_library_binding_key { + uintptr_t link_map_addr; + unsigned long generation; + uintptr_t namespace_id; + kzt_guest_library_namespace_kind_t namespace_kind; +} kzt_guest_library_binding_key_t; + +typedef struct kzt_guest_library_handle { + kzt_guest_library_bindings_t *bindings; + void *entry; + library_t *library; + kzt_guest_library_object_type_t object_type; +} kzt_guest_library_handle_t; + +typedef struct kzt_guest_library_callback_access { + kzt_guest_library_bindings_t *bindings; + uintptr_t link_map_addr; + void *gate; + int fallback; +} kzt_guest_library_callback_access_t; + +kzt_guest_library_bindings_t *kzt_guest_library_bindings_init(void); +void kzt_guest_library_bindings_begin_teardown( + kzt_guest_library_bindings_t *bindings); +void kzt_guest_library_bindings_destroy(kzt_guest_library_bindings_t **bindings); + +int kzt_guest_library_access_init(kzt_guest_library_access_t *access); +void kzt_guest_library_access_begin_teardown( + kzt_guest_library_access_t *access); +void kzt_guest_library_access_destroy(kzt_guest_library_access_t *access); + +/* Libraries must be tracked before they become visible to loader users. If + * tracking cannot be established, exact binding remains disabled for that + * library and the legacy loader continues unchanged. */ +int kzt_guest_library_track(kzt_guest_library_bindings_t *bindings, + library_t *library); +int kzt_guest_library_reactivate(kzt_guest_library_bindings_t *bindings, + library_t *library); + +/* Guest loader calls are synchronous. A context-local scope proves that a + * callback came from a loader invocation issued after an older address was + * closed. note_pair only prepares a scope-local pair and never makes it + * visible to lookup; publish_pair commits it after the loader succeeds. + * Ending a scope without publication cancels every prepared pair. */ +int kzt_guest_library_loader_scope_begin( + kzt_guest_library_bindings_t *bindings, + kzt_guest_library_loader_scope_t *scope); +void kzt_guest_library_loader_scope_end( + kzt_guest_library_loader_scope_t *scope); +kzt_guest_library_binding_result_t +kzt_guest_library_loader_scope_note_pair( + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr, library_t *library, + kzt_guest_library_object_type_t object_type); +kzt_guest_library_binding_result_t +kzt_guest_library_loader_scope_publish_pair( + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr, library_t *library, + kzt_guest_library_object_type_t object_type); +kzt_guest_library_binding_result_t +kzt_guest_library_loader_scope_publish_observed( + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr); + +/* Pins one link_map address before the callback's first guest-memory read and + * through dynamic parsing, legacy processing, and diagnostics. Unload closes + * that address under the same lock and waits for every admitted callback. + * A closed address rejects late callbacks before any guest work. */ +int kzt_guest_library_callback_access_begin( + kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr, + kzt_guest_library_callback_access_t *access); +int kzt_guest_library_callback_access_begin_scoped( + kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr, + const kzt_guest_library_loader_scope_t *scope, + kzt_guest_library_callback_access_t *access); +void kzt_guest_library_callback_access_end( + kzt_guest_library_callback_access_t *access); + +/* Record the exact pair produced by one causal loader operation. This never + * searches by name/path/SONAME. A pair remains pending until registry + * observation supplies its generation, so either arrival order works. */ +kzt_guest_library_binding_result_t kzt_guest_library_note_exact_pair( + kzt_guest_library_bindings_t *bindings, + uintptr_t link_map_addr, + library_t *library, + kzt_guest_library_object_type_t object_type); + +/* The caller must use only the exact (address, library) returned by one + * successfully completed guest loader operation. This causal publication + * may reopen an address whose replacement is now known valid. */ +kzt_guest_library_binding_result_t kzt_guest_library_publish_loader_pair( + kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr, + library_t *library, kzt_guest_library_object_type_t object_type); + +/* Publish successful registry evidence to the exact-pair handshake. WI-254 + * intentionally supports only ordinary shared objects in LM_ID_BASE. Main + * executable objects and non-main namespaces fail open. */ +kzt_guest_library_binding_result_t kzt_guest_library_note_observation( + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_binding_key_t *key); + +kzt_guest_library_binding_result_t kzt_guest_library_bind( + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_binding_key_t *key, + library_t *library, + kzt_guest_library_object_type_t object_type); + +/* Production lookup must enter through the box64context-owned access object. + * The raw lookup is exposed only for binding white-box tests, where the test + * owns and stops all callers before destroying bindings. */ +int kzt_guest_library_access_lookup( + kzt_guest_library_access_t *access, + const kzt_guest_library_binding_key_t *key, + kzt_guest_library_handle_t *handle); +#ifdef KZT_GUEST_LIBRARY_BINDING_TEST +int kzt_guest_library_lookup(kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_binding_key_t *key, + kzt_guest_library_handle_t *handle); +#endif +void kzt_guest_library_handle_release(kzt_guest_library_handle_t *handle); + +/* Closes attachment under the bindings lock, retires exact registry + * generations without that lock, then waits for acquired lookup handles. + * guest_link_map_hint identifies only this library's unclaimed observation; + * a missing hint leaves unclaimed observations live rather than guessing. + * This is the only permitted order: access -> bindings for lookup entry; + * registry is never entered while bindings is held; handle release takes only + * bindings. */ +void kzt_guest_library_unbind(kzt_guest_library_bindings_t *bindings, + kzt_guest_registry_t *registry, + library_t *library, + uintptr_t guest_link_map_hint); +void kzt_guest_library_inactivate(kzt_guest_library_bindings_t *bindings, + kzt_guest_registry_t *registry, + library_t *library, + uintptr_t guest_link_map_hint); + +#ifdef KZT_GUEST_LIBRARY_BINDING_TEST +typedef void (*kzt_guest_library_binding_test_retire_fn)( + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_binding_key_t *key, + library_t *library, int from_observation, void *opaque); +typedef void (*kzt_guest_library_binding_test_lifecycle_wait_fn)( + kzt_guest_library_bindings_t *bindings, library_t *library, + void *opaque); + +void kzt_guest_library_binding_test_set_alloc_failure_after(long allocations); +void kzt_guest_library_binding_test_set_before_registry_retire( + kzt_guest_library_binding_test_retire_fn hook, void *opaque); +void kzt_guest_library_binding_test_set_before_lifecycle_wait( + kzt_guest_library_binding_test_lifecycle_wait_fn hook, void *opaque); +int kzt_guest_library_binding_test_snapshot( + kzt_guest_library_bindings_t *bindings, library_t *library, + kzt_guest_library_binding_state_t *lifecycle_state, + size_t *active_pending, size_t *live_entries); +int kzt_guest_library_binding_test_get_diagnostics( + kzt_guest_library_bindings_t *bindings, + unsigned long *registry_missing, + unsigned long *retire_unprovable); +#endif + +#endif diff --git a/target/i386/latx/include/kzt_guest_link_map_reader.h b/target/i386/latx/include/kzt_guest_link_map_reader.h new file mode 100644 index 00000000000..cdd362569bf --- /dev/null +++ b/target/i386/latx/include/kzt_guest_link_map_reader.h @@ -0,0 +1,72 @@ +#ifndef KZT_GUEST_LINK_MAP_READER_H +#define KZT_GUEST_LINK_MAP_READER_H + +#include +#include + +#include "kzt_guest_registry.h" + +#define KZT_GUEST_LINK_MAP_NAME_LIMIT 4096 + +typedef int (*kzt_guest_memory_read_fn)(uintptr_t guest_addr, + void *dst, + size_t size, + void *opaque); + +typedef struct kzt_guest_link_map_reader_ops { + kzt_guest_memory_read_fn read_memory; + void *opaque; +} kzt_guest_link_map_reader_ops_t; + +typedef struct kzt_guest_link_map_identity { + uintptr_t load_bias; + uintptr_t dynamic_addr; +} kzt_guest_link_map_identity_t; + +int kzt_guest_link_map_read_identity( + uintptr_t link_map_addr, + const kzt_guest_link_map_reader_ops_t *ops, + kzt_guest_link_map_identity_t *identity); + +int kzt_guest_link_map_identity_matches( + const kzt_guest_link_map_identity_t *identity, + uintptr_t expected_load_bias, + uintptr_t expected_dynamic_addr); + +int kzt_guest_link_map_read_predecessor( + uintptr_t link_map_addr, + const kzt_guest_link_map_reader_ops_t *ops, + uintptr_t *predecessor); + +/* Walk the public x86_64 link_map l_prev chain to its exact head. With a + * previously confirmed main head, classification is based only on head + * identity. Otherwise both l_addr and l_ld must match the main executable. + * Returns 1 for main, 0 for a complete non-main chain, and -1 when unknown. */ +int kzt_guest_link_map_classify_namespace( + uintptr_t link_map_addr, + const kzt_guest_link_map_identity_t *main_identity, + uintptr_t confirmed_main_head, + const kzt_guest_link_map_reader_ops_t *ops, + uintptr_t *namespace_head); + +int kzt_guest_link_map_read_name_snapshot( + uintptr_t guest_name_addr, + const kzt_guest_link_map_reader_ops_t *ops, + size_t max_len, + kzt_guest_string_field_t *name); + +int kzt_guest_link_map_read_observation( + uintptr_t link_map_addr, + const kzt_guest_link_map_reader_ops_t *ops, + kzt_guest_object_observation_t *observation); + +void kzt_guest_link_map_observation_clear( + kzt_guest_object_observation_t *observation); + +void kzt_guest_link_map_string_clear(kzt_guest_string_field_t *field); + +#ifdef KZT_GUEST_LINK_MAP_READER_TEST +void kzt_guest_link_map_reader_test_set_alloc_failure_after(long allocations); +#endif + +#endif diff --git a/target/i386/latx/include/kzt_guest_registry.h b/target/i386/latx/include/kzt_guest_registry.h new file mode 100644 index 00000000000..2b8140e6e1d --- /dev/null +++ b/target/i386/latx/include/kzt_guest_registry.h @@ -0,0 +1,269 @@ +#ifndef KZT_GUEST_REGISTRY_H +#define KZT_GUEST_REGISTRY_H + +#include +#include + +#include "kzt_guest_dynamic_view.h" + +typedef struct kzt_guest_registry kzt_guest_registry_t; + +typedef struct kzt_guest_registry_source_lease { + kzt_guest_registry_t *registry; + uintptr_t link_map_addr; + unsigned long generation; + uintptr_t namespace_id; + int active; +} kzt_guest_registry_source_lease_t; + +typedef enum kzt_guest_object_state { + KZT_GUEST_OBJECT_DISCOVERED = 0, + KZT_GUEST_OBJECT_PARSED, + KZT_GUEST_OBJECT_WRAPPER_READY, + KZT_GUEST_OBJECT_PATCHED, + KZT_GUEST_OBJECT_UNLOADING, + KZT_GUEST_OBJECT_DEAD, +} kzt_guest_object_state_t; + +typedef enum kzt_guest_field_status { + KZT_GUEST_FIELD_OK = 0, + KZT_GUEST_FIELD_UNKNOWN, + KZT_GUEST_FIELD_READ_ERROR, + KZT_GUEST_FIELD_TRUNCATED, + KZT_GUEST_FIELD_NOT_PARSED, +} kzt_guest_field_status_t; + +typedef enum kzt_guest_registry_result { + KZT_GUEST_REGISTRY_ADDED = 0, + KZT_GUEST_REGISTRY_UNCHANGED, + KZT_GUEST_REGISTRY_UPDATED, + KZT_GUEST_REGISTRY_CONFLICT, + KZT_GUEST_REGISTRY_DISABLED, + KZT_GUEST_REGISTRY_ERROR, + KZT_GUEST_REGISTRY_RESULT_COUNT, +} kzt_guest_registry_result_t; + +typedef struct kzt_guest_scalar_field { + uintptr_t value; + kzt_guest_field_status_t status; +} kzt_guest_scalar_field_t; + +typedef struct kzt_guest_string_field { + const char *value; + kzt_guest_field_status_t status; +} kzt_guest_string_field_t; + +typedef struct kzt_guest_lazy_resolver { + uintptr_t link_map_slot; + uintptr_t resolver_slot; + uintptr_t guest_link_map; + uintptr_t guest_resolver; + int valid; +} kzt_guest_lazy_resolver_t; + +typedef struct kzt_guest_object_observation { + uintptr_t link_map_addr; + kzt_guest_scalar_field_t load_bias; + kzt_guest_scalar_field_t dynamic_addr; + kzt_guest_scalar_field_t map_start; + kzt_guest_scalar_field_t map_end; + kzt_guest_scalar_field_t namespace_id; + kzt_guest_string_field_t path; + kzt_guest_string_field_t soname; + kzt_guest_field_status_t dynamic_view_status; +} kzt_guest_object_observation_t; + +typedef struct kzt_guest_object_snapshot { + uintptr_t link_map_addr; + kzt_guest_scalar_field_t load_bias; + kzt_guest_scalar_field_t dynamic_addr; + kzt_guest_scalar_field_t map_start; + kzt_guest_scalar_field_t map_end; + kzt_guest_scalar_field_t namespace_id; + kzt_guest_string_field_t path; + kzt_guest_string_field_t soname; + kzt_guest_field_status_t dynamic_view_status; + kzt_guest_dynamic_view_t dynamic_view; + kzt_guest_lazy_resolver_t lazy_resolver; + kzt_guest_object_state_t state; + unsigned long generation; + unsigned long active_source_leases; +} kzt_guest_object_snapshot_t; + +typedef struct kzt_guest_registry_dump { + kzt_guest_object_snapshot_t *objects; + size_t count; +} kzt_guest_registry_dump_t; + +typedef struct kzt_guest_registry_diagnostics { + unsigned long observations; + unsigned long added; + unsigned long unchanged; + unsigned long updated; + unsigned long conflicts; + unsigned long disabled; + unsigned long errors; + unsigned long init_failures; + unsigned long allocation_failures; +} kzt_guest_registry_diagnostics_t; + +typedef struct kzt_guest_registry_diagnostic_config { + int enabled; + unsigned long throttle_limit; +} kzt_guest_registry_diagnostic_config_t; + +typedef struct kzt_guest_registry_observation_diagnostic { + int enabled; + int emitted; + kzt_guest_registry_result_t result; + uintptr_t link_map_addr; + unsigned long generation; + unsigned long object_count; + unsigned long result_observations; + unsigned long result_suppressed; + kzt_guest_registry_diagnostics_t counters; +} kzt_guest_registry_observation_diagnostic_t; + +typedef struct kzt_guest_registry_event_summary { + kzt_guest_registry_result_t result; + unsigned long observed; + unsigned long emitted; + unsigned long suppressed; + uintptr_t last_link_map_addr; + unsigned long last_generation; +} kzt_guest_registry_event_summary_t; + +typedef struct kzt_guest_registry_diagnostic_report { + kzt_guest_registry_diagnostic_config_t config; + kzt_guest_registry_diagnostics_t counters; + kzt_guest_registry_event_summary_t events[KZT_GUEST_REGISTRY_RESULT_COUNT]; + size_t event_count; +} kzt_guest_registry_diagnostic_report_t; + +typedef int (*kzt_guest_registry_dump_sink_fn)(const char *line, + void *opaque); + +kzt_guest_registry_t *kzt_guest_registry_init(void); +/* The owner must stop starting new registry calls before destroy begins. + * Calls that have entered the API, including cond waiters and active source + * leases, are drained before the mutex/condition and registry storage die. */ +void kzt_guest_registry_destroy(kzt_guest_registry_t **registry); + +#ifdef KZT_GUEST_REGISTRY_TEST +typedef void (*kzt_guest_registry_test_hook_fn)(void *opaque); +void kzt_guest_registry_test_set_after_api_enter( + kzt_guest_registry_test_hook_fn hook, void *opaque); +void kzt_guest_registry_test_set_before_retire_wait( + kzt_guest_registry_test_hook_fn hook, void *opaque); +void kzt_guest_registry_test_set_after_retire_wake( + kzt_guest_registry_test_hook_fn hook, void *opaque); +void kzt_guest_registry_test_set_after_destroy_disable( + kzt_guest_registry_test_hook_fn hook, void *opaque); +#endif + +kzt_guest_registry_result_t kzt_guest_registry_observe( + kzt_guest_registry_t *registry, + const kzt_guest_object_observation_t *observation); + +kzt_guest_registry_result_t kzt_guest_registry_observe_with_diagnostic( + kzt_guest_registry_t *registry, + const kzt_guest_object_observation_t *observation, + kzt_guest_registry_observation_diagnostic_t *diagnostic); + +int kzt_guest_registry_find_by_link_map( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_object_snapshot_t **snapshot); + +int kzt_guest_registry_retire(kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation); +/* Defensive handoff for a violated single-retire protocol: wait for the + * already-started exact generation to become DEAD instead of allowing its + * binding owner to return before source leases drain. */ +int kzt_guest_registry_wait_retired(kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation); + +/* Pin an exact live (link_map, generation, namespace) source across a writer + * transaction. Only the main namespace is currently supported. Retire has + * a single owner: it rejects an already UNLOADING/DEAD generation, then waits + * for every active lease before completing DEAD. The guest loader therefore + * cannot unmap source slots while a lease is held. Guest memory must never be + * accessed while the registry mutex is held. */ +int kzt_guest_registry_source_lease_acquire( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + uintptr_t namespace_id, + kzt_guest_registry_source_lease_t *lease); +void kzt_guest_registry_source_lease_release( + kzt_guest_registry_source_lease_t *lease); + +kzt_guest_registry_result_t kzt_guest_registry_commit_dynamic_view( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + const kzt_guest_dynamic_view_t *view); + +int kzt_guest_registry_find_dynamic_view( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_dynamic_view_t *view, + kzt_guest_field_status_t *status, + unsigned long *generation); + +/* Resolver metadata is published only while the object is not callable. + * The generation and namespace checks make stale/non-main observations fail + * open without changing the guest resolver slots. */ +int kzt_guest_registry_publish_lazy_resolver( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + uintptr_t namespace_id, + const kzt_guest_lazy_resolver_t *resolver); + +int kzt_guest_registry_find_lazy_resolver( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + uintptr_t namespace_id, + kzt_guest_lazy_resolver_t *resolver); + +int kzt_guest_registry_dump_snapshot( + kzt_guest_registry_t *registry, + kzt_guest_registry_dump_t *dump); + +int kzt_guest_registry_get_diagnostics( + kzt_guest_registry_t *registry, + kzt_guest_registry_diagnostics_t *diagnostics); + +int kzt_guest_registry_configure_diagnostics( + kzt_guest_registry_t *registry, + const kzt_guest_registry_diagnostic_config_t *config); + +int kzt_guest_registry_get_diagnostic_report( + kzt_guest_registry_t *registry, + kzt_guest_registry_diagnostic_report_t *report); + +int kzt_guest_registry_note_diagnostic( + kzt_guest_registry_t *registry, + kzt_guest_registry_result_t result, + uintptr_t link_map_addr, + kzt_guest_registry_observation_diagnostic_t *diagnostic); + +int kzt_guest_registry_dump_text( + kzt_guest_registry_t *registry, + kzt_guest_registry_dump_sink_fn sink, + void *opaque); + +void kzt_guest_object_snapshot_free(kzt_guest_object_snapshot_t *snapshot); +void kzt_guest_registry_dump_free(kzt_guest_registry_dump_t *dump); + +#ifdef KZT_GUEST_REGISTRY_TEST +void kzt_guest_registry_test_set_alloc_failure_after(long allocations); +void kzt_guest_registry_test_set_dynamic_commit_failure_after(long commits); +void kzt_guest_registry_test_fail_next_cond_init(void); +#endif + +#endif diff --git a/target/i386/latx/include/kzt_guest_registry_context.h b/target/i386/latx/include/kzt_guest_registry_context.h new file mode 100644 index 00000000000..c0e020623d1 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_registry_context.h @@ -0,0 +1,42 @@ +#ifndef KZT_GUEST_REGISTRY_CONTEXT_H +#define KZT_GUEST_REGISTRY_CONTEXT_H + +#include + +#include "kzt_guest_registry.h" + +typedef struct kzt_guest_registry_context { + kzt_guest_registry_t *registry; + int state; + uintptr_t main_namespace_head; +} kzt_guest_registry_context_t; + +/* The caller owns the surrounding context lifetime. The context lock is + * used only for first publication and teardown; established lookups use an + * atomic fast path and registry calls never run while the context lock is + * held. */ +kzt_guest_registry_t *kzt_guest_registry_context_get( + kzt_guest_registry_context_t *context, + pthread_mutex_t *context_lock); + +void kzt_guest_registry_context_destroy( + kzt_guest_registry_context_t *context, + pthread_mutex_t *context_lock); + +int kzt_guest_registry_context_get_main_namespace_head( + const kzt_guest_registry_context_t *context, + uintptr_t *head); + +int kzt_guest_registry_context_confirm_main_namespace_head( + kzt_guest_registry_context_t *context, + pthread_mutex_t *context_lock, + uintptr_t head); + +int kzt_guest_registry_context_has_main_namespace_evidence( + const kzt_guest_registry_context_t *context, + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + uintptr_t load_bias, + uintptr_t dynamic_addr); + +#endif diff --git a/target/i386/latx/include/kzt_jump_slot_production.h b/target/i386/latx/include/kzt_jump_slot_production.h new file mode 100644 index 00000000000..2a7a8bca58e --- /dev/null +++ b/target/i386/latx/include/kzt_jump_slot_production.h @@ -0,0 +1,71 @@ +#ifndef KZT_JUMP_SLOT_PRODUCTION_H +#define KZT_JUMP_SLOT_PRODUCTION_H + +#include + +#include "elf.h" +#include "kzt_guest_registry.h" +#include "kzt_jump_slot_route.h" +#include "kzt_lazy_binding.h" + +typedef struct box64context_s box64context_t; +typedef struct elfheader_s elfheader_t; + +int kzt_production_jump_slot_route( + box64context_t *context, library_t *resolved_provider, + uintptr_t resolved_target, elfheader_t *head, int need_resolv_present, + int entry_index, Elf64_Rela *rela, uint64_t *slot, + uintptr_t slot_current_value, + int slot_current_value_is_unresolved_stub, unsigned long symbol_index, + const char *symbol_name, const char *version, + int expected_guest_target_present, uintptr_t expected_guest_target, + uintptr_t legacy_target, kzt_jump_slot_route_result_t *route_result); + +int kzt_production_jump_slot_route_with_version_evidence( + box64context_t *context, library_t *resolved_provider, + uintptr_t resolved_target, elfheader_t *head, int need_resolv_present, + int entry_index, Elf64_Rela *rela, uint64_t *slot, + uintptr_t slot_current_value, + int slot_current_value_is_unresolved_stub, unsigned long symbol_index, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *version, int expected_guest_target_present, + uintptr_t expected_guest_target, uintptr_t legacy_target, + kzt_jump_slot_route_result_t *route_result); + +int kzt_production_lazy_route_guest_target( + box64context_t *context, + const kzt_lazy_binding_pending_t *pending, + uintptr_t guest_target, + kzt_lazy_binding_route_result_t *result); + +/* Production resolver orchestration. One exact source lease covers the + * first slot load, post-bind validation, route transaction, and final + * release. */ +int kzt_production_lazy_complete( + box64context_t *context, + kzt_lazy_binding_pending_t *pending, + kzt_lazy_binding_result_t *result); + +int kzt_production_lazy_source_lease_acquire( + box64context_t *context, + const kzt_lazy_binding_pending_t *pending, + kzt_guest_registry_source_lease_t *source_lease); + +int kzt_production_lazy_load_slot_with_lease( + box64context_t *context, + const kzt_lazy_binding_pending_t *pending, + uintptr_t slot_addr, + uintptr_t *value, + kzt_guest_registry_source_lease_t *source_lease); + +/* The caller-held exact (link_map, generation, namespace) source lease must + * cover the first source slot read through the complete route transaction. */ +int kzt_production_lazy_route_guest_target_leased( + box64context_t *context, + const kzt_lazy_binding_pending_t *pending, + uintptr_t guest_target, + const kzt_guest_registry_source_lease_t *source_lease, + kzt_lazy_binding_route_result_t *result); + +#endif diff --git a/target/i386/latx/include/kzt_jump_slot_route.h b/target/i386/latx/include/kzt_jump_slot_route.h new file mode 100644 index 00000000000..bc5d244fc80 --- /dev/null +++ b/target/i386/latx/include/kzt_jump_slot_route.h @@ -0,0 +1,85 @@ +#ifndef KZT_JUMP_SLOT_ROUTE_H +#define KZT_JUMP_SLOT_ROUTE_H + +#include + +#include "kzt_guest_library_binding.h" +#include "kzt_patch_spike_writer.h" +#include "kzt_rela_immediate_candidate.h" + +typedef enum kzt_jump_slot_route_writer_status { + KZT_JUMP_SLOT_ROUTE_WRITER_DECLINED = 0, + KZT_JUMP_SLOT_ROUTE_WRITER_APPLIED, + KZT_JUMP_SLOT_ROUTE_WRITER_ERROR, + KZT_JUMP_SLOT_ROUTE_WRITER_PRESERVE, +} kzt_jump_slot_route_writer_status_t; + +typedef enum kzt_jump_slot_route_status { + KZT_JUMP_SLOT_ROUTE_BYPASS = 0, + KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED, + KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED, + KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED, + KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH, + KZT_JUMP_SLOT_ROUTE_WRITE_ERROR, +} kzt_jump_slot_route_status_t; + +typedef int (*kzt_jump_slot_route_load_fn)(uintptr_t slot_addr, + uintptr_t *value, + void *opaque); +typedef int (*kzt_jump_slot_route_cas_fn)(uintptr_t slot_addr, + uintptr_t *expected, + uintptr_t replacement, + void *opaque); + +typedef struct kzt_jump_slot_route_ops { + int (*enrich_base)(kzt_rela_immediate_candidate_request_t *request, + void *opaque); + int (*acquire_exact_provider)( + const kzt_patch_object_ref_t *owner, library_t *resolved_provider, + kzt_guest_library_handle_t *handle, void *opaque); + void (*release_exact_provider)(kzt_guest_library_handle_t *handle, + void *opaque); + int (*enrich_bridge)(kzt_rela_immediate_candidate_request_t *request, + library_t *held_provider, void *opaque); + int (*validate_source_identity)( + const kzt_rela_immediate_candidate_request_t *request, + void *opaque); + kzt_jump_slot_route_writer_status_t (*try_native_writer)( + const kzt_rela_immediate_candidate_request_t *request, + const kzt_patch_spike_slot_ops_t *slot_ops, void *opaque); + kzt_jump_slot_route_load_fn load_slot; + kzt_jump_slot_route_cas_fn compare_exchange_slot; + kzt_patch_spike_slot_permission_begin_fn begin_slot_write; + kzt_patch_spike_slot_permission_end_fn end_slot_write; + kzt_patch_spike_slot_generation_validate_fn validate_write_generation; + void *opaque; +} kzt_jump_slot_route_ops_t; + +typedef struct kzt_jump_slot_route_input { + int enabled; + int preserve_observed_on_failure; + int expected_guest_target_present; + int resolved_target_matches_legacy; + library_t *resolved_provider; + kzt_rela_immediate_candidate_request_t request; +} kzt_jump_slot_route_input_t; + +typedef struct kzt_jump_slot_route_result { + kzt_jump_slot_route_status_t status; + kzt_jump_slot_route_writer_status_t writer_status; + uintptr_t observed_value; + uintptr_t expected_guest_target; + uintptr_t selected_target; + uintptr_t final_value; + int exact_provider_acquired; + int exact_provider_matched; + int native_writer_called; + int source_identity_rechecked; + int legacy_fallback_attempted; +} kzt_jump_slot_route_result_t; + +int kzt_jump_slot_route_apply(const kzt_jump_slot_route_input_t *input, + const kzt_jump_slot_route_ops_t *ops, + kzt_jump_slot_route_result_t *result); + +#endif diff --git a/target/i386/latx/include/kzt_lazy_binding.h b/target/i386/latx/include/kzt_lazy_binding.h new file mode 100644 index 00000000000..458659d0c0e --- /dev/null +++ b/target/i386/latx/include/kzt_lazy_binding.h @@ -0,0 +1,125 @@ +#ifndef KZT_LAZY_BINDING_H +#define KZT_LAZY_BINDING_H + +#include + +#include "kzt_guest_library_binding.h" +#include "kzt_patch_planner.h" + +#define KZT_LAZY_BINDING_SYMBOL_MAX 128 +#define KZT_LAZY_BINDING_VERSION_MAX 128 + +typedef enum kzt_lazy_binding_status { + KZT_LAZY_BINDING_BYPASS = 0, + KZT_LAZY_BINDING_HANDOFF_GUEST, + KZT_LAZY_BINDING_WAITING_GUEST_TARGET, + KZT_LAZY_BINDING_GUEST_PRESERVED, + KZT_LAZY_BINDING_NATIVE_APPLIED, + KZT_LAZY_BINDING_CAS_MISMATCH, + KZT_LAZY_BINDING_ERROR, +} kzt_lazy_binding_status_t; + +typedef enum kzt_lazy_binding_reason { + KZT_LAZY_BINDING_REASON_NONE = 0, + KZT_LAZY_BINDING_REASON_DISABLED, + KZT_LAZY_BINDING_REASON_INVALID_REQUEST, + KZT_LAZY_BINDING_REASON_PENDING_OCCUPIED, + KZT_LAZY_BINDING_REASON_GENERATION_CHANGED, + KZT_LAZY_BINDING_REASON_NON_MAIN_NAMESPACE, + KZT_LAZY_BINDING_REASON_RESOLVER_MISSING, + KZT_LAZY_BINDING_REASON_SLOT_UNCHANGED, + KZT_LAZY_BINDING_REASON_MISSING_VERSION, + KZT_LAZY_BINDING_REASON_POST_BIND_INVALID, + KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE, + KZT_LAZY_BINDING_REASON_NATIVE_APPLIED, + KZT_LAZY_BINDING_REASON_CAS_MISMATCH, + KZT_LAZY_BINDING_REASON_SLOT_READ_ERROR, + KZT_LAZY_BINDING_REASON_CAS_ERROR, +} kzt_lazy_binding_reason_t; + +#define KZT_LAZY_BINDING_REASON_PENDING_BUSY \ + KZT_LAZY_BINDING_REASON_PENDING_OCCUPIED + +typedef struct kzt_lazy_binding_begin_request { + int enabled; + uintptr_t context_id; + uintptr_t source_link_map; + unsigned long source_generation; + uintptr_t namespace_id; + kzt_guest_library_namespace_kind_t namespace_kind; + unsigned long relocation_index; + uintptr_t slot_addr; + uintptr_t unresolved_stub; + const char *symbol; + kzt_symbol_version_evidence_t version_evidence; + const char *version; + long addend; + uintptr_t guest_resolver; +} kzt_lazy_binding_begin_request_t; + +typedef struct kzt_lazy_binding_pending { + int armed; + uintptr_t context_id; + uintptr_t source_link_map; + unsigned long source_generation; + uintptr_t namespace_id; + kzt_guest_library_namespace_kind_t namespace_kind; + unsigned long relocation_index; + uintptr_t slot_addr; + uintptr_t unresolved_stub; + uintptr_t guest_resolver; + long addend; + const char *symbol; + kzt_symbol_version_evidence_t version_evidence; + const char *version; + char symbol_storage[KZT_LAZY_BINDING_SYMBOL_MAX]; + char version_storage[KZT_LAZY_BINDING_VERSION_MAX]; +} kzt_lazy_binding_pending_t; + +typedef enum kzt_lazy_binding_route_status { + KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED = 0, + KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED, + KZT_LAZY_BINDING_ROUTE_CAS_MISMATCH, + KZT_LAZY_BINDING_ROUTE_ERROR, +} kzt_lazy_binding_route_status_t; + +typedef struct kzt_lazy_binding_route_result { + kzt_lazy_binding_route_status_t status; + uintptr_t selected_target; + uintptr_t final_value; +} kzt_lazy_binding_route_result_t; + +typedef struct kzt_lazy_binding_ops { + int (*load_slot)(uintptr_t slot_addr, uintptr_t *value, void *opaque); + int (*validate_post_bind)(const kzt_lazy_binding_pending_t *pending, + uintptr_t guest_target, void *opaque); + int (*route_guest_target)(const kzt_lazy_binding_pending_t *pending, + uintptr_t guest_target, + kzt_lazy_binding_route_result_t *result, + void *opaque); + void *opaque; +} kzt_lazy_binding_ops_t; + +typedef struct kzt_lazy_binding_result { + kzt_lazy_binding_status_t status; + kzt_lazy_binding_reason_t reason; + uintptr_t slot_before; + uintptr_t slot_after; + uintptr_t selected_target; + int pending_armed; + int pending_cleared; +} kzt_lazy_binding_result_t; + +int kzt_lazy_binding_begin( + const kzt_lazy_binding_begin_request_t *request, + kzt_lazy_binding_pending_t *pending, + kzt_lazy_binding_result_t *result); + +int kzt_lazy_binding_complete( + kzt_lazy_binding_pending_t *pending, + const kzt_lazy_binding_ops_t *ops, + kzt_lazy_binding_result_t *result); + +void kzt_lazy_binding_cancel(kzt_lazy_binding_pending_t *pending); + +#endif diff --git a/target/i386/latx/include/kzt_lazy_diagnostics.h b/target/i386/latx/include/kzt_lazy_diagnostics.h new file mode 100644 index 00000000000..9a1ef59231b --- /dev/null +++ b/target/i386/latx/include/kzt_lazy_diagnostics.h @@ -0,0 +1,84 @@ +#ifndef KZT_LAZY_DIAGNOSTICS_H +#define KZT_LAZY_DIAGNOSTICS_H + +#include +#include + +#include "kzt_lazy_binding.h" + +#define KZT_LAZY_DIAGNOSTIC_SYMBOL_LIMIT KZT_LAZY_BINDING_SYMBOL_MAX +#define KZT_LAZY_DIAGNOSTIC_ROUTE_LIMIT 64 +#define KZT_LAZY_DIAGNOSTIC_REASON_LIMIT 64 +#define KZT_LAZY_DIAGNOSTIC_LINE_LIMIT 1024 + +typedef enum kzt_lazy_diagnostic_format_status { + KZT_LAZY_DIAGNOSTIC_FORMAT_ERROR = -1, + KZT_LAZY_DIAGNOSTIC_FORMAT_OK = 0, + KZT_LAZY_DIAGNOSTIC_FORMAT_TRUNCATED, +} kzt_lazy_diagnostic_format_status_t; + +typedef enum kzt_lazy_diagnostic_emit_status { + KZT_LAZY_DIAGNOSTIC_EMIT_DISABLED = 0, + KZT_LAZY_DIAGNOSTIC_EMIT_EMITTED, + KZT_LAZY_DIAGNOSTIC_EMIT_FORMAT_FAILED, + KZT_LAZY_DIAGNOSTIC_EMIT_FORMAT_TRUNCATED, + KZT_LAZY_DIAGNOSTIC_EMIT_SINK_FAILED, +} kzt_lazy_diagnostic_emit_status_t; + +typedef struct kzt_lazy_diagnostic_record { + unsigned long schema; + char symbol[KZT_LAZY_DIAGNOSTIC_SYMBOL_LIMIT]; + char first_execution_route[KZT_LAZY_DIAGNOSTIC_ROUTE_LIMIT]; + char completion_route_status[KZT_LAZY_DIAGNOSTIC_ROUTE_LIMIT]; + uintptr_t slot_before; + uintptr_t slot_after_guest; + uintptr_t selected_second_target; + char reason[KZT_LAZY_DIAGNOSTIC_REASON_LIMIT]; +} kzt_lazy_diagnostic_record_t; + +typedef int (*kzt_lazy_diagnostic_sink_fn)(const char *line, + size_t line_length, + void *opaque); + +typedef struct kzt_lazy_diagnostic_input { + int enabled; + const kzt_lazy_binding_pending_t *pending; + const kzt_lazy_binding_result_t *binding_result; + char *buffer; + size_t buffer_size; + kzt_lazy_diagnostic_sink_fn sink; + void *sink_opaque; +} kzt_lazy_diagnostic_input_t; + +typedef struct kzt_lazy_diagnostic_emit_result { + kzt_lazy_diagnostic_emit_status_t status; + int record_present; + kzt_lazy_diagnostic_record_t record; + kzt_lazy_diagnostic_format_status_t format_status; + int sink_status; +} kzt_lazy_diagnostic_emit_result_t; + +int kzt_lazy_diagnostic_record_build( + const kzt_lazy_binding_pending_t *pending, + const kzt_lazy_binding_result_t *binding_result, + kzt_lazy_diagnostic_record_t *record); + +kzt_lazy_diagnostic_format_status_t kzt_lazy_diagnostic_format( + const kzt_lazy_diagnostic_record_t *record, + char *buffer, + size_t buffer_size); + +int kzt_lazy_diagnostics_stderr_sink(const char *line, + size_t line_length, + void *opaque); + +int kzt_lazy_diagnostics_emit( + const kzt_lazy_diagnostic_input_t *input, + kzt_lazy_diagnostic_emit_result_t *result); + +int kzt_lazy_diagnostics_emit_production( + const kzt_lazy_binding_pending_t *pending, + const kzt_lazy_binding_result_t *binding_result, + kzt_lazy_diagnostic_emit_result_t *result); + +#endif diff --git a/target/i386/latx/include/kzt_loader_callback_scope.h b/target/i386/latx/include/kzt_loader_callback_scope.h new file mode 100644 index 00000000000..54e5f7e6d3a --- /dev/null +++ b/target/i386/latx/include/kzt_loader_callback_scope.h @@ -0,0 +1,13 @@ +#ifndef KZT_LOADER_CALLBACK_SCOPE_H +#define KZT_LOADER_CALLBACK_SCOPE_H + +typedef struct kzt_guest_library_bindings kzt_guest_library_bindings_t; + +/* A value token issued by one context-owned guest loader invocation. */ +typedef struct kzt_guest_library_loader_scope { + kzt_guest_library_bindings_t *bindings; + unsigned long identity; + unsigned long cookie; +} kzt_guest_library_loader_scope_t; + +#endif diff --git a/target/i386/latx/include/kzt_observation_adapter.h b/target/i386/latx/include/kzt_observation_adapter.h new file mode 100644 index 00000000000..914448bdb0a --- /dev/null +++ b/target/i386/latx/include/kzt_observation_adapter.h @@ -0,0 +1,82 @@ +#ifndef KZT_OBSERVATION_ADAPTER_H +#define KZT_OBSERVATION_ADAPTER_H + +#include +#include + +#include "kzt_guest_link_map_reader.h" +#include "kzt_guest_dynamic.h" +#include "kzt_guest_dynamic_diagnostics.h" +#include "kzt_guest_registry.h" +#include "kzt_loader_callback_scope.h" + +typedef struct kzt_guest_library_bindings kzt_guest_library_bindings_t; + +typedef enum kzt_observation_adapter_result { + KZT_OBSERVATION_ADAPTER_DISABLED = 0, + KZT_OBSERVATION_ADAPTER_ADDED, + KZT_OBSERVATION_ADAPTER_UNCHANGED, + KZT_OBSERVATION_ADAPTER_UPDATED, + KZT_OBSERVATION_ADAPTER_CONFLICT, + KZT_OBSERVATION_ADAPTER_READER_FAILED, + KZT_OBSERVATION_ADAPTER_REGISTRY_FAILED, +} kzt_observation_adapter_result_t; + +typedef int (*kzt_observation_legacy_flow_fn)(uintptr_t link_map_addr, + void *opaque); + +typedef struct kzt_observation_adapter_dynamic_diagnostic { + int attempted; + int cache_hit; + int parse_return; + uintptr_t dynamic_addr; + kzt_guest_dynamic_status_t status; + kzt_guest_dynamic_error_t error; + size_t entry_count; + uintptr_t read_error_addr; + int commit_attempted; + kzt_guest_registry_result_t commit_result; + int comparison_attempted; + kzt_guest_dynamic_diagnostic_summary_t comparison; + kzt_guest_registry_observation_diagnostic_t registry; +} kzt_observation_adapter_dynamic_diagnostic_t; + +typedef struct kzt_observation_adapter_diagnostic { + int enabled; + int emitted; + kzt_observation_adapter_result_t result; + uintptr_t link_map_addr; + kzt_guest_registry_observation_diagnostic_t registry; + kzt_observation_adapter_dynamic_diagnostic_t dynamic; +} kzt_observation_adapter_diagnostic_t; + +typedef void (*kzt_observation_adapter_diagnostic_fn)( + const kzt_observation_adapter_diagnostic_t *diagnostic, + void *opaque); + +typedef struct kzt_observation_adapter_request { + int enabled; + int diagnostics_enabled; + int dynamic_diagnostics_force_compare; + uintptr_t link_map_addr; + kzt_guest_registry_t *registry; + kzt_guest_library_bindings_t *library_bindings; + const kzt_guest_library_loader_scope_t *loader_scope; + const kzt_guest_link_map_reader_ops_t *reader_ops; + /* Caller-verified evidence. Invalid or absent hints are ignored. */ + int namespace_id_present; + uintptr_t namespace_id; + int map_range_present; + uintptr_t map_start; + uintptr_t map_end; + kzt_observation_legacy_flow_fn legacy_flow; + void *legacy_opaque; + kzt_observation_adapter_diagnostic_fn diagnostic; + void *diagnostic_opaque; +} kzt_observation_adapter_request_t; + +int kzt_observe_guest_object_from_callback( + const kzt_observation_adapter_request_t *request, + kzt_observation_adapter_result_t *result); + +#endif diff --git a/target/i386/latx/include/kzt_owner_resolver.h b/target/i386/latx/include/kzt_owner_resolver.h new file mode 100644 index 00000000000..97979482acc --- /dev/null +++ b/target/i386/latx/include/kzt_owner_resolver.h @@ -0,0 +1,56 @@ +#ifndef KZT_OWNER_RESOLVER_H +#define KZT_OWNER_RESOLVER_H + +#include +#include + +#include "kzt_guest_registry.h" +#include "kzt_patch_planner.h" + +#define KZT_OWNER_RESOLVER_TEXT_LIMIT 256 + +typedef enum kzt_owner_resolver_status { + KZT_OWNER_RESOLVER_RESOLVED = 0, + KZT_OWNER_RESOLVER_INVALID_ARGUMENT, + KZT_OWNER_RESOLVER_REGISTRY_UNAVAILABLE, + KZT_OWNER_RESOLVER_CURRENT_ADDRESS_MISSING, + KZT_OWNER_RESOLVER_EXPECTED_ADDRESS_MISSING, + KZT_OWNER_RESOLVER_CURRENT_NOT_FOUND, + KZT_OWNER_RESOLVER_EXPECTED_NOT_FOUND, + KZT_OWNER_RESOLVER_CURRENT_AMBIGUOUS, + KZT_OWNER_RESOLVER_EXPECTED_AMBIGUOUS, + KZT_OWNER_RESOLVER_GENERATION_UNKNOWN, +} kzt_owner_resolver_status_t; + +typedef struct kzt_owner_resolver_text { + char soname[KZT_OWNER_RESOLVER_TEXT_LIMIT]; + char path[KZT_OWNER_RESOLVER_TEXT_LIMIT]; +} kzt_owner_resolver_text_t; + +typedef struct kzt_owner_resolution { + kzt_owner_resolver_status_t status; + kzt_patch_object_ref_t current_owner; + kzt_patch_object_ref_t expected_owner; + kzt_patch_owner_match_t owner_match; + size_t current_match_count; + size_t expected_match_count; + kzt_owner_resolver_text_t current_text; + kzt_owner_resolver_text_t expected_text; +} kzt_owner_resolution_t; + +void kzt_owner_resolver_init(kzt_owner_resolution_t *resolution); + +int kzt_owner_resolver_resolve_current( + kzt_guest_registry_t *registry, + uintptr_t current_address, + uintptr_t expected_address, + kzt_owner_resolution_t *resolution); + +kzt_patch_owner_match_t kzt_owner_resolver_match_refs( + const kzt_patch_object_ref_t *current_owner, + const kzt_patch_object_ref_t *expected_owner); + +const char *kzt_owner_resolver_status_name( + kzt_owner_resolver_status_t status); + +#endif diff --git a/target/i386/latx/include/kzt_patch_planner.h b/target/i386/latx/include/kzt_patch_planner.h new file mode 100644 index 00000000000..176d5911206 --- /dev/null +++ b/target/i386/latx/include/kzt_patch_planner.h @@ -0,0 +1,204 @@ +#ifndef KZT_PATCH_PLANNER_H +#define KZT_PATCH_PLANNER_H + +#include +#include +#include + +typedef enum kzt_patch_table_kind { + KZT_PATCH_TABLE_UNKNOWN = 0, + KZT_PATCH_TABLE_RELA, + KZT_PATCH_TABLE_REL, + KZT_PATCH_TABLE_PLT_RELA, + KZT_PATCH_TABLE_PLT_REL, + KZT_PATCH_TABLE_OTHER, +} kzt_patch_table_kind_t; + +typedef enum kzt_patch_relocation_type { + KZT_PATCH_RELOCATION_UNKNOWN = 0, + KZT_PATCH_RELOCATION_JUMP_SLOT, + KZT_PATCH_RELOCATION_GLOB_DAT, + KZT_PATCH_RELOCATION_RELATIVE, + KZT_PATCH_RELOCATION_COPY, + KZT_PATCH_RELOCATION_IRELATIVE, + KZT_PATCH_RELOCATION_OTHER, +} kzt_patch_relocation_type_t; + +typedef enum kzt_patch_owner_match { + KZT_PATCH_OWNER_UNKNOWN = 0, + KZT_PATCH_OWNER_MATCH, + KZT_PATCH_OWNER_MISMATCH, +} kzt_patch_owner_match_t; + +typedef enum kzt_patch_wrapper_match { + KZT_PATCH_WRAPPER_NO_MANIFEST = 0, + KZT_PATCH_WRAPPER_NO_WRAPPER, + KZT_PATCH_WRAPPER_SYMBOL_ONLY, + KZT_PATCH_WRAPPER_VERSION_MISMATCH, + KZT_PATCH_WRAPPER_VERSION_MATCH, + KZT_PATCH_WRAPPER_UNVERSIONED_MATCH, +} kzt_patch_wrapper_match_t; + +typedef enum kzt_symbol_version_evidence { + /* Keep VERSIONED as zero so existing versioned initializers retain their + * meaning. Producers must still validate that a version string exists. */ + KZT_SYMBOL_VERSION_VERSIONED = 0, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + KZT_SYMBOL_VERSION_UNKNOWN, + KZT_SYMBOL_VERSION_ERROR, +} kzt_symbol_version_evidence_t; + +typedef enum kzt_patch_decision_kind { + KZT_PATCH_DECISION_ERROR = 0, + KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_DECISION_DEFERRED, + KZT_PATCH_DECISION_APPROVED, +} kzt_patch_decision_kind_t; + +typedef enum kzt_patch_reason { + KZT_PATCH_REASON_ERROR_INVALID_ARGUMENT = 0, + KZT_PATCH_REASON_INPUT_UNSUPPORTED_RELOCATION, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE, + KZT_PATCH_REASON_INPUT_MALFORMED_SLOT, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_NAME, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_VERSION, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_DYNAMIC_VIEW, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_CURRENT_GOT, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_OWNER, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_WRAPPER_MANIFEST, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_BRIDGE_TARGET, + KZT_PATCH_REASON_POLICY_KEEP_GUEST, + KZT_PATCH_REASON_POLICY_OWNER_MISMATCH, + KZT_PATCH_REASON_POLICY_NO_WRAPPER, + KZT_PATCH_REASON_POLICY_WRAPPER_SYMBOL_ONLY, + KZT_PATCH_REASON_POLICY_VERSION_MISMATCH, + KZT_PATCH_REASON_DEFERRED_LAZY_BINDING, + KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE, +} kzt_patch_reason_t; + +typedef struct kzt_patch_object_ref { + int known; + uintptr_t link_map_addr; + uintptr_t map_start; + uintptr_t map_end; + unsigned long generation; + const char *soname; + const char *path; +} kzt_patch_object_ref_t; + +typedef struct kzt_patch_candidate { + kzt_patch_object_ref_t source; + uintptr_t dynamic_addr; + uintptr_t load_bias; + unsigned long dynamic_view_generation; + int dynamic_view_available; + + kzt_patch_table_kind_t table_kind; + size_t entry_index; + uintptr_t entry_addr; + kzt_patch_relocation_type_t reloc_type; + uintptr_t slot_addr; + + int slot_current_value_present; + uintptr_t slot_current_value; + int lazy_binding_deferred; + + unsigned long symbol_index; + const char *symbol_name; + kzt_symbol_version_evidence_t version_evidence; + const char *version; + + kzt_patch_object_ref_t current_owner; + kzt_patch_owner_match_t owner_match; + + kzt_patch_wrapper_match_t wrapper_match; + const char *wrapper_name; + kzt_symbol_version_evidence_t wrapper_version_evidence; + const char *wrapper_symbol_version; + uintptr_t bridge_target; +} kzt_patch_candidate_t; + +typedef struct kzt_patch_decision { + kzt_patch_decision_kind_t kind; + kzt_patch_reason_t reason; + int allow_native_bridge; + + kzt_patch_object_ref_t source; + uintptr_t dynamic_addr; + uintptr_t load_bias; + unsigned long dynamic_view_generation; + int dynamic_view_available; + + kzt_patch_table_kind_t table_kind; + size_t entry_index; + uintptr_t entry_addr; + kzt_patch_relocation_type_t reloc_type; + uintptr_t slot_addr; + + int slot_current_value_present; + uintptr_t slot_current_value; + int lazy_binding_deferred; + + unsigned long symbol_index; + const char *symbol_name; + kzt_symbol_version_evidence_t version_evidence; + const char *version; + + kzt_patch_object_ref_t current_owner; + kzt_patch_owner_match_t owner_match; + + kzt_patch_wrapper_match_t wrapper_match; + const char *wrapper_name; + kzt_symbol_version_evidence_t wrapper_version_evidence; + const char *wrapper_symbol_version; + uintptr_t bridge_target; +} kzt_patch_decision_t; + +int kzt_patch_planner_decide(const kzt_patch_candidate_t *candidate, + kzt_patch_decision_t *decision); + +const char *kzt_patch_decision_kind_name(kzt_patch_decision_kind_t kind); +const char *kzt_patch_reason_name(kzt_patch_reason_t reason); +const char *kzt_patch_table_kind_name(kzt_patch_table_kind_t table_kind); +const char *kzt_patch_relocation_type_name( + kzt_patch_relocation_type_t reloc_type); +const char *kzt_patch_owner_match_name(kzt_patch_owner_match_t match); +const char *kzt_patch_wrapper_match_name(kzt_patch_wrapper_match_t match); +const char *kzt_symbol_version_evidence_name( + kzt_symbol_version_evidence_t evidence); + +static inline int kzt_symbol_version_evidence_valid( + kzt_symbol_version_evidence_t evidence, const char *version) +{ + switch (evidence) { + case KZT_SYMBOL_VERSION_VERSIONED: + return version && version[0]; + case KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED: + return !version || !version[0]; + case KZT_SYMBOL_VERSION_UNKNOWN: + case KZT_SYMBOL_VERSION_ERROR: + return 0; + } + return 0; +} + +static inline int kzt_symbol_version_evidence_matches( + kzt_symbol_version_evidence_t left_evidence, const char *left_version, + kzt_symbol_version_evidence_t right_evidence, const char *right_version) +{ + if (left_evidence != right_evidence || + !kzt_symbol_version_evidence_valid(left_evidence, left_version) || + !kzt_symbol_version_evidence_valid(right_evidence, right_version)) { + return 0; + } + return left_evidence == KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED || + strcmp(left_version, right_version) == 0; +} + +int kzt_patch_decision_format_summary( + const kzt_patch_decision_t *decision, + char *buffer, + size_t buffer_size); + +#endif diff --git a/target/i386/latx/include/kzt_patch_spike_guard.h b/target/i386/latx/include/kzt_patch_spike_guard.h new file mode 100644 index 00000000000..c81b43e5a7a --- /dev/null +++ b/target/i386/latx/include/kzt_patch_spike_guard.h @@ -0,0 +1,114 @@ +#ifndef KZT_PATCH_SPIKE_GUARD_H +#define KZT_PATCH_SPIKE_GUARD_H + +#include + +#include "kzt_patch_planner.h" + +typedef enum kzt_patch_spike_action { + KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY = 0, + KZT_PATCH_SPIKE_ACTION_USE_NATIVE_BRIDGE, + KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST, +} kzt_patch_spike_action_t; + +typedef enum kzt_patch_spike_result { + KZT_PATCH_SPIKE_RESULT_DISABLED = 0, + KZT_PATCH_SPIKE_RESULT_DIAGNOSTICS_ONLY, + KZT_PATCH_SPIKE_RESULT_BUDGET_EXHAUSTED, + KZT_PATCH_SPIKE_RESULT_APPLIED, + KZT_PATCH_SPIKE_RESULT_FAIL_OPEN, + KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED, + KZT_PATCH_SPIKE_RESULT_CIRCUIT_OPEN, +} kzt_patch_spike_result_t; + +typedef enum kzt_patch_spike_failure { + KZT_PATCH_SPIKE_FAILURE_NONE = 0, + KZT_PATCH_SPIKE_FAILURE_INVALID_ARGUMENT, + KZT_PATCH_SPIKE_FAILURE_DECISION_NOT_APPROVED, + KZT_PATCH_SPIKE_FAILURE_WRITE_NOT_AUTHORIZED, + KZT_PATCH_SPIKE_FAILURE_BUDGET_EXHAUSTED, + KZT_PATCH_SPIKE_FAILURE_EXPECTED_MISMATCH, + KZT_PATCH_SPIKE_FAILURE_READ_FAILED, + KZT_PATCH_SPIKE_FAILURE_WRITE_FAILED, + KZT_PATCH_SPIKE_FAILURE_VERIFY_FAILED, + KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED, + KZT_PATCH_SPIKE_FAILURE_PERMISSION_ENABLE_FAILED, + KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED, + KZT_PATCH_SPIKE_FAILURE_GENERATION_MISMATCH, + KZT_PATCH_SPIKE_FAILURE_CIRCUIT_BREAKER_OPEN, +} kzt_patch_spike_failure_t; + +typedef enum kzt_patch_spike_writer_status { + KZT_PATCH_SPIKE_WRITER_OK = 0, + KZT_PATCH_SPIKE_WRITER_READ_FAILED, + KZT_PATCH_SPIKE_WRITER_EXPECTED_MISMATCH, + KZT_PATCH_SPIKE_WRITER_WRITE_FAILED, + KZT_PATCH_SPIKE_WRITER_PERMISSION_ENABLE_FAILED, + KZT_PATCH_SPIKE_WRITER_PERMISSION_RESTORE_FAILED, + KZT_PATCH_SPIKE_WRITER_GENERATION_MISMATCH, +} kzt_patch_spike_writer_status_t; + +typedef struct kzt_patch_spike_config { + int enabled; + int write_enabled; + unsigned long budget; +} kzt_patch_spike_config_t; + +typedef struct kzt_patch_spike_guard { + kzt_patch_spike_config_t config; + unsigned long write_attempts; + unsigned long write_successes; + int circuit_open; + int transaction_gate; +} kzt_patch_spike_guard_t; + +typedef struct kzt_patch_spike_outcome { + kzt_patch_spike_result_t result; + kzt_patch_spike_failure_t failure; + kzt_patch_spike_action_t action; + int skip_legacy_write; + unsigned long writes_remaining; + int writer_called; + int rollback_called; + uintptr_t previous_value; +} kzt_patch_spike_outcome_t; + +typedef struct kzt_patch_spike_writer_ops { + kzt_patch_spike_writer_status_t (*write_slot)( + const kzt_patch_decision_t *decision, + uintptr_t expected_value, + uintptr_t replacement_value, + uintptr_t *previous_value, + void *opaque); + int (*verify_slot)(const kzt_patch_decision_t *decision, + uintptr_t expected_value, + void *opaque); + int (*rollback_slot)(const kzt_patch_decision_t *decision, + uintptr_t previous_value, + void *opaque); + kzt_patch_spike_writer_status_t (*finish_slot)( + const kzt_patch_decision_t *decision, void *opaque); + void *opaque; +} kzt_patch_spike_writer_ops_t; + +void kzt_patch_spike_config_from_options(kzt_patch_spike_config_t *config); +void kzt_patch_spike_guard_init(kzt_patch_spike_guard_t *guard, + const kzt_patch_spike_config_t *config); + +int kzt_patch_spike_guard_should_plan( + const kzt_patch_spike_guard_t *guard); +int kzt_patch_spike_guard_circuit_open( + const kzt_patch_spike_guard_t *guard); +unsigned long kzt_patch_spike_guard_budget_remaining( + const kzt_patch_spike_guard_t *guard); + +int kzt_patch_spike_guard_try_write( + kzt_patch_spike_guard_t *guard, + const kzt_patch_decision_t *decision, + const kzt_patch_spike_writer_ops_t *writer, + kzt_patch_spike_outcome_t *outcome); + +const char *kzt_patch_spike_result_name(kzt_patch_spike_result_t result); +const char *kzt_patch_spike_failure_name(kzt_patch_spike_failure_t failure); + +#endif diff --git a/target/i386/latx/include/kzt_patch_spike_writer.h b/target/i386/latx/include/kzt_patch_spike_writer.h new file mode 100644 index 00000000000..ad5b5cc016e --- /dev/null +++ b/target/i386/latx/include/kzt_patch_spike_writer.h @@ -0,0 +1,116 @@ +#ifndef KZT_PATCH_SPIKE_WRITER_H +#define KZT_PATCH_SPIKE_WRITER_H + +#include +#include + +#include "kzt_patch_spike_guard.h" + +typedef int (*kzt_patch_spike_slot_read_fn)(uintptr_t slot_addr, + uintptr_t *value, + void *opaque); +typedef int (*kzt_patch_spike_slot_write_fn)(uintptr_t slot_addr, + uintptr_t value, + void *opaque); + +typedef struct kzt_patch_spike_permission_lease { + uintptr_t guest_page; + uintptr_t guest_page_length; + int original_permissions; + int checked; + int was_writable; + int write_enabled; + int restore_attempted; + int restored; +} kzt_patch_spike_permission_lease_t; + +typedef int (*kzt_patch_spike_slot_permission_begin_fn)( + uintptr_t slot_addr, kzt_patch_spike_permission_lease_t *lease, + void *opaque); +typedef int (*kzt_patch_spike_slot_permission_end_fn)( + kzt_patch_spike_permission_lease_t *lease, void *opaque); +typedef int (*kzt_patch_spike_slot_generation_validate_fn)( + const kzt_patch_decision_t *decision, void *opaque); + +typedef struct kzt_patch_spike_slot_ops { + kzt_patch_spike_slot_read_fn read_slot; + kzt_patch_spike_slot_write_fn write_slot; + kzt_patch_spike_slot_permission_begin_fn begin_write; + kzt_patch_spike_slot_permission_end_fn end_write; + kzt_patch_spike_slot_generation_validate_fn validate_generation; + void *opaque; +} kzt_patch_spike_slot_ops_t; + +typedef struct kzt_patch_spike_record { + int valid; + + kzt_patch_decision_kind_t decision_kind; + kzt_patch_reason_t decision_reason; + int allow_native_bridge; + kzt_patch_table_kind_t table_kind; + kzt_patch_relocation_type_t reloc_type; + size_t entry_index; + uintptr_t entry_addr; + uintptr_t slot_addr; + uintptr_t source_link_map; + uintptr_t current_owner_link_map; + unsigned long source_generation; + unsigned long current_owner_generation; + unsigned long dynamic_view_generation; + + int expected_value_present; + uintptr_t expected_value; + uintptr_t replacement_value; + uintptr_t previous_value; + uintptr_t observed_value; + uintptr_t verified_value; + uintptr_t rollback_value; + + const char *symbol_name; + const char *wrapper_name; + + kzt_patch_spike_result_t result; + kzt_patch_spike_failure_t failure; + kzt_patch_spike_action_t action; + int skip_legacy_write; + unsigned long writes_remaining; + + int writer_called; + int read_attempted; + int expected_current_matched; + int write_attempted; + int write_succeeded; + int verify_attempted; + int verify_succeeded; + int rollback_called; + int rollback_succeeded; + int rollback_verify_attempted; + uintptr_t rollback_verified_value; + int rollback_verify_succeeded; + int generation_checked; + int generation_matched; + int permission_checked; + uintptr_t permission_guest_page; + uintptr_t permission_guest_page_length; + int permission_original_permissions; + int permission_was_writable; + int permission_write_enabled; + int permission_restore_attempted; + int permission_restored; +} kzt_patch_spike_record_t; + +void kzt_patch_spike_record_init(kzt_patch_spike_record_t *record, + const kzt_patch_decision_t *decision); + +int kzt_patch_spike_writer_try_apply( + kzt_patch_spike_guard_t *guard, + const kzt_patch_decision_t *decision, + kzt_patch_spike_record_t *record); + +int kzt_patch_spike_writer_try_apply_with_slot_ops( + kzt_patch_spike_guard_t *guard, + const kzt_patch_decision_t *decision, + const kzt_patch_spike_slot_ops_t *slot_ops, + kzt_patch_spike_record_t *record); + +#endif diff --git a/target/i386/latx/include/kzt_plt_resolver_adapter.h b/target/i386/latx/include/kzt_plt_resolver_adapter.h new file mode 100644 index 00000000000..35d96c0a219 --- /dev/null +++ b/target/i386/latx/include/kzt_plt_resolver_adapter.h @@ -0,0 +1,67 @@ +#ifndef KZT_PLT_RESOLVER_ADAPTER_H +#define KZT_PLT_RESOLVER_ADAPTER_H + +#include + +#include "kzt_lazy_binding.h" + +#ifdef KZT_PLT_RESOLVER_ADAPTER_TEST +typedef struct CPUX86State { + uint64_t regs[16]; +} CPUX86State; +#define R_ESP 4 +#else +typedef struct CPUX86State CPUX86State; +#endif + +typedef struct kzt_plt_resolver_source { + int enabled; + uintptr_t context_id; + uintptr_t object_head; + uintptr_t source_link_map; + unsigned long source_generation; + uintptr_t namespace_id; + kzt_guest_library_namespace_kind_t namespace_kind; + uintptr_t slot_addr; + uintptr_t unresolved_stub; + const char *symbol; + kzt_symbol_version_evidence_t version_evidence; + const char *version; + long addend; + uintptr_t guest_resolver; +} kzt_plt_resolver_source_t; + +typedef struct kzt_plt_resolver_runtime_ops { + int (*lookup_source)(uintptr_t object_head, + kzt_plt_resolver_source_t *source, void *opaque); + int (*begin_lazy_binding)( + const kzt_lazy_binding_begin_request_t *request, + kzt_lazy_binding_pending_t *pending, + kzt_lazy_binding_result_t *result, void *opaque); + kzt_lazy_binding_pending_t *pending; + uintptr_t completion_bridge; + uintptr_t *original_return; + void *opaque; +} kzt_plt_resolver_runtime_ops_t; + +typedef enum kzt_plt_resolver_enter_status { + KZT_PLT_RESOLVER_ERROR = 0, + KZT_PLT_RESOLVER_HANDOFF_GUEST, + KZT_PLT_RESOLVER_GUEST_PRESERVED, + KZT_PLT_RESOLVER_LEGACY_FRAME_RESTORED, +} kzt_plt_resolver_enter_status_t; + +typedef struct kzt_plt_resolver_enter_result { + kzt_plt_resolver_enter_status_t status; + uintptr_t object_head; + unsigned long relocation_slot; + uintptr_t return_address; + uintptr_t selected_resolver; + int pending_armed; +} kzt_plt_resolver_enter_result_t; + +int kzt_plt_resolver_enter( + CPUX86State *cpu, const kzt_plt_resolver_runtime_ops_t *ops, + kzt_plt_resolver_enter_result_t *result); + +#endif diff --git a/target/i386/latx/include/kzt_rela_diagnostics.h b/target/i386/latx/include/kzt_rela_diagnostics.h new file mode 100644 index 00000000000..016603a2f17 --- /dev/null +++ b/target/i386/latx/include/kzt_rela_diagnostics.h @@ -0,0 +1,140 @@ +#ifndef KZT_RELA_DIAGNOSTICS_H +#define KZT_RELA_DIAGNOSTICS_H + +#include +#include + +#include "kzt_rela_immediate_candidate.h" + +#define KZT_RELA_DIAGNOSTIC_SOURCE_LIMIT 256 +#define KZT_RELA_DIAGNOSTIC_NAME_LIMIT 64 +#define KZT_RELA_DIAGNOSTIC_LINE_LIMIT 1024 + +typedef enum kzt_rela_diagnostic_mode { + KZT_RELA_DIAGNOSTIC_MODE_DEFAULT = 0, + KZT_RELA_DIAGNOSTIC_MODE_WRITE_ENABLED_ONLY, + KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS, + KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS_WRITE_ENABLED, +} kzt_rela_diagnostic_mode_t; + +typedef enum kzt_rela_diagnostic_reason_domain { + KZT_RELA_DIAGNOSTIC_REASON_CANDIDATE = 0, + KZT_RELA_DIAGNOSTIC_REASON_PLANNER, + KZT_RELA_DIAGNOSTIC_REASON_WRITER, +} kzt_rela_diagnostic_reason_domain_t; + +typedef enum kzt_rela_diagnostic_format_status { + KZT_RELA_DIAGNOSTIC_FORMAT_ERROR = -1, + KZT_RELA_DIAGNOSTIC_FORMAT_OK = 0, + KZT_RELA_DIAGNOSTIC_FORMAT_TRUNCATED, +} kzt_rela_diagnostic_format_status_t; + +typedef enum kzt_rela_diagnostic_emit_status { + KZT_RELA_DIAGNOSTIC_EMIT_DISABLED = 0, + KZT_RELA_DIAGNOSTIC_EMIT_EMITTED, + KZT_RELA_DIAGNOSTIC_EMIT_SUPPRESSED, + KZT_RELA_DIAGNOSTIC_EMIT_THROTTLE_FAILED, + KZT_RELA_DIAGNOSTIC_EMIT_FORMAT_FAILED, + KZT_RELA_DIAGNOSTIC_EMIT_FORMAT_TRUNCATED, + KZT_RELA_DIAGNOSTIC_EMIT_SINK_FAILED, +} kzt_rela_diagnostic_emit_status_t; + +typedef struct kzt_rela_diagnostic_record { + char source[KZT_RELA_DIAGNOSTIC_SOURCE_LIMIT]; + uintptr_t source_link_map; + uintptr_t current_owner; + unsigned long source_generation; + unsigned long current_owner_generation; + kzt_patch_owner_match_t owner_match; + kzt_patch_wrapper_match_t wrapper_match; + uintptr_t bridge_target; + char symbol[KZT_RELA_DIAGNOSTIC_NAME_LIMIT]; + char version[KZT_RELA_DIAGNOSTIC_NAME_LIMIT]; + kzt_rela_diagnostic_reason_domain_t reason_domain; + char reason[KZT_RELA_DIAGNOSTIC_NAME_LIMIT]; + char decision[KZT_RELA_DIAGNOSTIC_NAME_LIMIT]; + char writer_result[KZT_RELA_DIAGNOSTIC_NAME_LIMIT]; + int legacy_fallback; +} kzt_rela_diagnostic_record_t; + +/* + * Initialize before publishing to other threads. Capacity is immutable after + * initialization. The embedded lock serializes counter updates and snapshots; + * callers must not mutate the fields directly after publication. + */ +typedef struct kzt_rela_diagnostic_throttle { + unsigned long capacity; + unsigned long admitted; + unsigned long suppressed; + unsigned int lock; + unsigned int initialized; +} kzt_rela_diagnostic_throttle_t; + +typedef struct kzt_rela_diagnostic_throttle_snapshot { + unsigned long capacity; + unsigned long admitted; + unsigned long suppressed; +} kzt_rela_diagnostic_throttle_snapshot_t; + +typedef int (*kzt_rela_diagnostic_sink_fn)(const char *line, + size_t line_length, + void *opaque); + +/* + * The adapter only consumes caller-owned request/result snapshots. It copies + * text into the record and invokes the sink synchronously; it never probes, + * plans, creates bridges, writes a slot, or applies legacy fallback. + */ +typedef struct kzt_rela_immediate_diagnostic_input { + kzt_rela_diagnostic_mode_t mode; + const kzt_rela_immediate_candidate_request_t *request; + const kzt_rela_immediate_writer_result_t *result; + int legacy_fallback; + kzt_rela_diagnostic_throttle_t *throttle; + char *buffer; + size_t buffer_size; + kzt_rela_diagnostic_sink_fn sink; + void *sink_opaque; +} kzt_rela_immediate_diagnostic_input_t; + +typedef struct kzt_rela_immediate_diagnostic_result { + kzt_rela_diagnostic_emit_status_t status; + int record_present; + kzt_rela_diagnostic_record_t record; + kzt_rela_diagnostic_format_status_t format_status; + int sink_status; +} kzt_rela_immediate_diagnostic_result_t; + +kzt_rela_diagnostic_mode_t kzt_rela_diagnostic_mode_from_flags( + int diagnostics_enabled, + int write_enabled); + +const char *kzt_rela_diagnostic_reason_domain_name( + kzt_rela_diagnostic_reason_domain_t domain); + +int kzt_rela_immediate_diagnostic_record( + kzt_rela_diagnostic_mode_t mode, + const kzt_rela_immediate_candidate_request_t *request, + const kzt_rela_immediate_writer_result_t *result, + int legacy_fallback, + kzt_rela_diagnostic_record_t *record); + +kzt_rela_diagnostic_format_status_t kzt_rela_diagnostic_format( + const kzt_rela_diagnostic_record_t *record, + char *buffer, + size_t buffer_size); + +int kzt_rela_diagnostic_throttle_init( + kzt_rela_diagnostic_throttle_t *throttle, + unsigned long capacity); +int kzt_rela_diagnostic_throttle_try_admit( + kzt_rela_diagnostic_throttle_t *throttle); +int kzt_rela_diagnostic_throttle_snapshot( + kzt_rela_diagnostic_throttle_t *throttle, + kzt_rela_diagnostic_throttle_snapshot_t *snapshot); + +int kzt_rela_immediate_diagnostic_emit( + const kzt_rela_immediate_diagnostic_input_t *input, + kzt_rela_immediate_diagnostic_result_t *result); + +#endif diff --git a/target/i386/latx/include/kzt_rela_immediate_candidate.h b/target/i386/latx/include/kzt_rela_immediate_candidate.h new file mode 100644 index 00000000000..63b1217d091 --- /dev/null +++ b/target/i386/latx/include/kzt_rela_immediate_candidate.h @@ -0,0 +1,89 @@ +#ifndef KZT_RELA_IMMEDIATE_CANDIDATE_H +#define KZT_RELA_IMMEDIATE_CANDIDATE_H + +#include +#include + +#include "kzt_patch_planner.h" +#include "kzt_patch_spike_writer.h" + +typedef enum kzt_rela_immediate_candidate_status { + KZT_RELA_IMMEDIATE_CANDIDATE_SKIPPED = 0, + KZT_RELA_IMMEDIATE_CANDIDATE_PLANNED, + KZT_RELA_IMMEDIATE_CANDIDATE_FAIL_OPEN, +} kzt_rela_immediate_candidate_status_t; + +typedef enum kzt_rela_immediate_candidate_reason { + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_NONE = 0, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_INVALID_ARGUMENT, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_NON_TARGET_RELOCATION, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_DEFERRED_LAZY_BINDING, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_SLOT, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_CURRENT_VALUE, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_SYMBOL_NAME, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_SYMBOL_VERSION, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_PLANNER_ERROR, +} kzt_rela_immediate_candidate_reason_t; + +typedef struct kzt_rela_immediate_candidate_request { + unsigned int relocation_type; + kzt_patch_table_kind_t table_kind; + size_t entry_index; + uintptr_t entry_addr; + + kzt_patch_object_ref_t source; + uintptr_t dynamic_addr; + uintptr_t load_bias; + unsigned long dynamic_view_generation; + int dynamic_view_available; + + uintptr_t slot_addr; + int slot_current_value_present; + uintptr_t slot_current_value; + int lazy_binding_deferred; + + uintptr_t expected_guest_target; + uintptr_t native_bridge_target; + uintptr_t legacy_target; + + unsigned long symbol_index; + const char *symbol_name; + kzt_symbol_version_evidence_t version_evidence; + const char *version; + + kzt_patch_object_ref_t current_owner; + kzt_patch_owner_match_t owner_match; + kzt_patch_wrapper_match_t wrapper_match; + const char *wrapper_name; + kzt_symbol_version_evidence_t wrapper_version_evidence; + const char *wrapper_symbol_version; +} kzt_rela_immediate_candidate_request_t; + +typedef struct kzt_rela_immediate_candidate_result { + kzt_rela_immediate_candidate_status_t status; + kzt_rela_immediate_candidate_reason_t reason; + int candidate_present; + kzt_patch_candidate_t candidate; + int decision_present; + kzt_patch_decision_t decision; +} kzt_rela_immediate_candidate_result_t; + +typedef struct kzt_rela_immediate_writer_result { + int planner_called; + int writer_called; + int skip_legacy_write; + kzt_rela_immediate_candidate_result_t plan; + kzt_patch_spike_record_t record; +} kzt_rela_immediate_writer_result_t; + +int kzt_rela_immediate_jump_slot_plan( + const kzt_rela_immediate_candidate_request_t *request, + kzt_rela_immediate_candidate_result_t *result); + +int kzt_rela_immediate_jump_slot_try_write( + const kzt_rela_immediate_candidate_request_t *request, + kzt_patch_spike_guard_t *guard, + const kzt_patch_spike_slot_ops_t *slot_ops, + kzt_rela_immediate_writer_result_t *result); + +#endif diff --git a/target/i386/latx/include/kzt_rela_request_enricher.h b/target/i386/latx/include/kzt_rela_request_enricher.h new file mode 100644 index 00000000000..c553960bbe3 --- /dev/null +++ b/target/i386/latx/include/kzt_rela_request_enricher.h @@ -0,0 +1,44 @@ +#ifndef KZT_RELA_REQUEST_ENRICHER_H +#define KZT_RELA_REQUEST_ENRICHER_H + +#include + +#include "kzt_guest_registry.h" +#include "kzt_owner_resolver.h" +#include "kzt_rela_immediate_candidate.h" +#include "kzt_wrapper_probe.h" + +#define KZT_RELA_REQUEST_ENRICHER_TEXT_LIMIT 256 + +typedef struct kzt_rela_request_enricher_text { + char soname[KZT_RELA_REQUEST_ENRICHER_TEXT_LIMIT]; + char path[KZT_RELA_REQUEST_ENRICHER_TEXT_LIMIT]; +} kzt_rela_request_enricher_text_t; + +typedef struct kzt_rela_request_enricher_input { + kzt_guest_registry_t *registry; + int slot_current_value_is_unresolved_stub; + const kzt_wrapper_probe_manifest_t *wrapper_manifest; + const kzt_wrapper_probe_bridge_ops_t *bridge_ops; +} kzt_rela_request_enricher_input_t; + +typedef struct kzt_rela_request_enricher_result { + int source_present; + int dynamic_view_present; + int owner_present; + int wrapper_present; + kzt_patch_object_ref_t source; + kzt_rela_request_enricher_text_t source_text; + kzt_owner_resolution_t owner_resolution; + kzt_wrapper_probe_result_t wrapper_probe; +} kzt_rela_request_enricher_result_t; + +void kzt_rela_request_enricher_result_init( + kzt_rela_request_enricher_result_t *result); + +int kzt_rela_immediate_request_enrich( + kzt_rela_immediate_candidate_request_t *request, + const kzt_rela_request_enricher_input_t *input, + kzt_rela_request_enricher_result_t *result); + +#endif diff --git a/target/i386/latx/include/kzt_rela_runtime_bridge.h b/target/i386/latx/include/kzt_rela_runtime_bridge.h new file mode 100644 index 00000000000..8925313a156 --- /dev/null +++ b/target/i386/latx/include/kzt_rela_runtime_bridge.h @@ -0,0 +1,36 @@ +#ifndef KZT_RELA_RUNTIME_BRIDGE_H +#define KZT_RELA_RUNTIME_BRIDGE_H + +#include + +#include "kzt_wrapper_bridge_provider.h" + +typedef struct box64context_s box64context_t; +typedef struct library_s library_t; + +int kzt_rela_runtime_wrapper_provider_prepare( + box64context_t *context, library_t *resolved_provider, + uintptr_t resolved_target, const char *symbol_name, + const char *symbol_version, + kzt_wrapper_bridge_provider_t *provider); + +int kzt_rela_runtime_wrapper_provider_prepare_with_version_evidence( + box64context_t *context, library_t *resolved_provider, + uintptr_t resolved_target, const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version, + kzt_wrapper_bridge_provider_t *provider); + +int kzt_rela_runtime_wrapper_provider_discover( + box64context_t *context, library_t *resolved_provider, + const char *symbol_name, const char *symbol_version, + kzt_wrapper_bridge_provider_t *provider); + +int kzt_rela_runtime_wrapper_provider_discover_with_version_evidence( + box64context_t *context, library_t *resolved_provider, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version, + kzt_wrapper_bridge_provider_t *provider); + +#endif diff --git a/target/i386/latx/include/kzt_rela_stub_detector.h b/target/i386/latx/include/kzt_rela_stub_detector.h new file mode 100644 index 00000000000..bb7fcc285cb --- /dev/null +++ b/target/i386/latx/include/kzt_rela_stub_detector.h @@ -0,0 +1,18 @@ +#ifndef KZT_RELA_STUB_DETECTOR_H +#define KZT_RELA_STUB_DETECTOR_H + +#include + +typedef enum kzt_rela_stub_coordinate { + KZT_RELA_STUB_COORDINATE_UNKNOWN = 0, + KZT_RELA_STUB_COORDINATE_LINK_TIME_RAW, + KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, +} kzt_rela_stub_coordinate_t; + +int kzt_rela_slot_current_is_unresolved_stub( + uintptr_t slot_current_value, kzt_rela_stub_coordinate_t coordinate, + intptr_t load_bias, + uintptr_t plt_start, uintptr_t plt_end, + uintptr_t gotplt_start, uintptr_t gotplt_end); + +#endif diff --git a/target/i386/latx/include/kzt_runtime_candidate_shadow.h b/target/i386/latx/include/kzt_runtime_candidate_shadow.h new file mode 100644 index 00000000000..48ad737a690 --- /dev/null +++ b/target/i386/latx/include/kzt_runtime_candidate_shadow.h @@ -0,0 +1,108 @@ +#ifndef KZT_RUNTIME_CANDIDATE_SHADOW_H +#define KZT_RUNTIME_CANDIDATE_SHADOW_H + +#include +#include + +#include "kzt_guest_registry.h" +#include "kzt_owner_resolver.h" +#include "kzt_runtime_got_plt_candidate.h" +#include "kzt_wrapper_probe.h" + +#define KZT_RUNTIME_CANDIDATE_SHADOW_DECISION_BUCKETS \ + ((size_t)KZT_PATCH_DECISION_APPROVED + 1) +#define KZT_RUNTIME_CANDIDATE_SHADOW_REASON_BUCKETS \ + ((size_t)KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE + 1) + +typedef enum kzt_runtime_candidate_shadow_status { + KZT_RUNTIME_CANDIDATE_SHADOW_OK = 0, + KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN, + KZT_RUNTIME_CANDIDATE_SHADOW_ERROR, +} kzt_runtime_candidate_shadow_status_t; + +typedef enum kzt_runtime_candidate_shadow_reason { + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_NONE = 0, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_INVALID_ARGUMENT, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_COLLECTOR_FAIL_OPEN, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_COLLECTOR_ERROR, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_RECORD_CAPACITY_EXCEEDED, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_OBJECT_GENERATION_CHANGED, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_PLANNER_ERROR, +} kzt_runtime_candidate_shadow_reason_t; + +typedef int (*kzt_runtime_candidate_shadow_expected_target_fn)( + const kzt_patch_candidate_t *candidate, + uintptr_t *expected_guest_target, + void *opaque); + +typedef enum kzt_runtime_candidate_shadow_stub_classification { + KZT_RUNTIME_CANDIDATE_SHADOW_STUB_NO_MATCH = 0, + KZT_RUNTIME_CANDIDATE_SHADOW_STUB_MATCH, + KZT_RUNTIME_CANDIDATE_SHADOW_STUB_UNKNOWN, +} kzt_runtime_candidate_shadow_stub_classification_t; + +typedef kzt_runtime_candidate_shadow_stub_classification_t +(*kzt_runtime_candidate_shadow_stub_classifier_fn)( + const kzt_patch_candidate_t *candidate, + void *opaque); + +/* Return zero only for one live object with one non-zero generation. */ +typedef int (*kzt_runtime_candidate_shadow_generation_query_fn)( + uintptr_t link_map_addr, + unsigned long *generation, + void *opaque); + +typedef struct kzt_runtime_candidate_shadow_record { + size_t candidate_index; + kzt_owner_resolution_t owner_resolution; + kzt_wrapper_probe_result_t wrapper_probe; + kzt_patch_decision_t decision; + /* Shadow records are audit-only and never consume a legacy target. */ + int audit_only; + int legacy_target_consumed; + int observe_only; + /* Eligibility is audit output only; it never authorizes a write. */ + int eligible; +} kzt_runtime_candidate_shadow_record_t; + +/* + * Contract: E is used only for owner resolution, B only comes from a + * side-effect-free bridge cache query, and the observed current slot is not + * legacy target L. This shadow API has no legacy_target input. + */ +typedef struct kzt_runtime_candidate_shadow_input { + const kzt_runtime_got_plt_candidate_request_t *collector_request; + kzt_guest_registry_t *registry; + const kzt_wrapper_probe_manifest_t *wrapper_manifest; + const kzt_wrapper_probe_bridge_ops_t *bridge_ops; + kzt_runtime_candidate_shadow_expected_target_fn + resolve_expected_guest_target; + void *expected_target_opaque; + /* Missing or UNKNOWN stub evidence is always treated as non-stub. */ + kzt_runtime_candidate_shadow_stub_classifier_fn classify_stub; + void *stub_classifier_opaque; + kzt_runtime_candidate_shadow_generation_query_fn query_generation; + void *generation_query_opaque; + kzt_runtime_candidate_shadow_record_t *records; + size_t record_capacity; +} kzt_runtime_candidate_shadow_input_t; + +typedef struct kzt_runtime_candidate_shadow_result { + kzt_runtime_candidate_shadow_status_t status; + kzt_runtime_candidate_shadow_reason_t reason; + kzt_runtime_got_plt_candidate_result_t collector_result; + size_t candidate_count; + size_t record_count; + size_t eligible_count; + size_t observe_only_count; + size_t decision_histogram[ + KZT_RUNTIME_CANDIDATE_SHADOW_DECISION_BUCKETS]; + size_t reason_histogram[ + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_BUCKETS]; +} kzt_runtime_candidate_shadow_result_t; + +int kzt_runtime_candidate_shadow_run( + const kzt_runtime_candidate_shadow_input_t *input, + kzt_runtime_candidate_shadow_result_t *result); + +#endif diff --git a/target/i386/latx/include/kzt_runtime_got_plt_candidate.h b/target/i386/latx/include/kzt_runtime_got_plt_candidate.h new file mode 100644 index 00000000000..cb5b7c79af6 --- /dev/null +++ b/target/i386/latx/include/kzt_runtime_got_plt_candidate.h @@ -0,0 +1,75 @@ +#ifndef KZT_RUNTIME_GOT_PLT_CANDIDATE_H +#define KZT_RUNTIME_GOT_PLT_CANDIDATE_H + +#include +#include + +#include "kzt_guest_dynamic_view.h" +#include "kzt_guest_link_map_reader.h" +#include "kzt_patch_planner.h" + +typedef enum kzt_runtime_got_plt_candidate_status { + KZT_RUNTIME_GOT_PLT_CANDIDATE_OK = 0, + KZT_RUNTIME_GOT_PLT_CANDIDATE_FAIL_OPEN, + KZT_RUNTIME_GOT_PLT_CANDIDATE_ERROR, +} kzt_runtime_got_plt_candidate_status_t; + +typedef enum kzt_runtime_got_plt_candidate_reason { + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_NONE = 0, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_INVALID_ARGUMENT, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_DYNAMIC_VIEW_UNAVAILABLE, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_DT_REL_UNSUPPORTED, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MISSING_DYNAMIC_FIELD, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_TABLE, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_TABLE_OVERFLOW, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_RELOCATION_READ_FAILED, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_SLOT_OVERFLOW, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_SLOT_READ_FAILED, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_SYMBOL_READ_FAILED, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_SYMBOL_NAME, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_VERSION_READ_FAILED, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_SYMBOL_VERSION, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_CAPACITY_EXCEEDED, +} kzt_runtime_got_plt_candidate_reason_t; + +typedef struct kzt_runtime_got_plt_candidate_request { + const kzt_guest_dynamic_view_t *view; + const kzt_guest_link_map_reader_ops_t *reader_ops; + const kzt_patch_object_ref_t *source; + unsigned long dynamic_view_generation; + /* Production can audit one already-selected relocation without needing + * storage proportional to the object's complete relocation table. */ + int only_entry; + kzt_patch_table_kind_t only_table_kind; + size_t only_entry_index; + kzt_patch_candidate_t *candidates; + size_t candidate_capacity; + char *string_storage; + size_t string_storage_size; +} kzt_runtime_got_plt_candidate_request_t; + +typedef struct kzt_runtime_got_plt_candidate_result { + kzt_runtime_got_plt_candidate_status_t status; + kzt_runtime_got_plt_candidate_reason_t reason; + int patch_reason_present; + kzt_patch_reason_t patch_reason; + size_t candidate_count; + kzt_patch_table_kind_t table_kind; + size_t entry_index; + uintptr_t entry_addr; + uintptr_t slot_addr; + uintptr_t read_error_addr; + kzt_symbol_version_evidence_t version_evidence; +} kzt_runtime_got_plt_candidate_result_t; + +int kzt_runtime_got_plt_candidates_collect( + const kzt_runtime_got_plt_candidate_request_t *request, + kzt_runtime_got_plt_candidate_result_t *result); + +const char *kzt_runtime_got_plt_candidate_status_name( + kzt_runtime_got_plt_candidate_status_t status); + +const char *kzt_runtime_got_plt_candidate_reason_name( + kzt_runtime_got_plt_candidate_reason_t reason); + +#endif diff --git a/target/i386/latx/include/kzt_wrapper_bridge_provider.h b/target/i386/latx/include/kzt_wrapper_bridge_provider.h new file mode 100644 index 00000000000..e3760eed5c4 --- /dev/null +++ b/target/i386/latx/include/kzt_wrapper_bridge_provider.h @@ -0,0 +1,72 @@ +#ifndef KZT_WRAPPER_BRIDGE_PROVIDER_H +#define KZT_WRAPPER_BRIDGE_PROVIDER_H + +#include +#include + +#include "kzt_wrapper_probe.h" + +typedef void (*kzt_wrapper_bridge_abi_wrapper_t)(uintptr_t fnc); + +#define KZT_WRAPPER_BRIDGE_NATIVE_NAME_MAX 256 + +typedef struct kzt_wrapper_bridge_provider_match { + const char *wrapper_name; + char native_name[KZT_WRAPPER_BRIDGE_NATIVE_NAME_MAX]; + kzt_wrapper_bridge_abi_wrapper_t abi_wrapper; + uintptr_t native_symbol; + uintptr_t resolved_bridge_target; + void *context_owner; + void *wrapper_provider; + void *native_lookup_handle; + void *native_owner; + void *bridge_owner; + void *bridge_storage; + int stack_bytes; + int custom_wrapper; + int resolved_bridge_exact; + int wrapper_provider_lifetime_bound; + int native_owner_lifetime_bound; + int bridge_owner_lifetime_bound; +} kzt_wrapper_bridge_provider_match_t; + +typedef int (*kzt_wrapper_bridge_provider_inspect_fn)( + void *library, const char *symbol_name, const char *symbol_version, + kzt_wrapper_bridge_provider_match_t *match, void *opaque); + +typedef uintptr_t (*kzt_wrapper_bridge_provider_check_fn)( + const kzt_wrapper_bridge_provider_match_t *match, void *opaque); + +typedef uintptr_t (*kzt_wrapper_bridge_provider_add_fn)( + const kzt_wrapper_bridge_provider_match_t *match, + const kzt_wrapper_probe_bridge_request_t *request, void *opaque); + +typedef struct kzt_wrapper_bridge_provider_runtime_ops { + kzt_wrapper_bridge_provider_inspect_fn inspect_library; + kzt_wrapper_bridge_provider_check_fn check_bridge; + kzt_wrapper_bridge_provider_add_fn add_bridge; + void *opaque; +} kzt_wrapper_bridge_provider_runtime_ops_t; + +typedef struct kzt_wrapper_bridge_provider { + kzt_wrapper_probe_entry_t entry; + kzt_wrapper_probe_manifest_t manifest; + kzt_wrapper_probe_bridge_ops_t bridge_ops; + kzt_wrapper_bridge_provider_match_t match; + kzt_wrapper_bridge_provider_runtime_ops_t runtime_ops; +} kzt_wrapper_bridge_provider_t; + +int kzt_wrapper_bridge_provider_prepare( + kzt_wrapper_bridge_provider_t *provider, void *const *libraries, + size_t library_count, const char *symbol_name, + const char *symbol_version, + const kzt_wrapper_bridge_provider_runtime_ops_t *runtime_ops); + +int kzt_wrapper_bridge_provider_prepare_with_version_evidence( + kzt_wrapper_bridge_provider_t *provider, void *const *libraries, + size_t library_count, const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version, + const kzt_wrapper_bridge_provider_runtime_ops_t *runtime_ops); + +#endif diff --git a/target/i386/latx/include/kzt_wrapper_probe.h b/target/i386/latx/include/kzt_wrapper_probe.h new file mode 100644 index 00000000000..86b56c7777c --- /dev/null +++ b/target/i386/latx/include/kzt_wrapper_probe.h @@ -0,0 +1,87 @@ +#ifndef KZT_WRAPPER_PROBE_H +#define KZT_WRAPPER_PROBE_H + +#include +#include + +#include "kzt_patch_planner.h" + +typedef enum kzt_wrapper_probe_bridge_source { + KZT_WRAPPER_PROBE_BRIDGE_NONE = 0, + KZT_WRAPPER_PROBE_BRIDGE_CACHE, + KZT_WRAPPER_PROBE_BRIDGE_ADD_BRIDGE, +} kzt_wrapper_probe_bridge_source_t; + +typedef struct kzt_wrapper_probe_entry { + const char *symbol_name; + kzt_symbol_version_evidence_t symbol_version_evidence; + const char *symbol_version; + const char *wrapper_name; + kzt_symbol_version_evidence_t wrapper_version_evidence; + const char *wrapper_symbol_version; + uintptr_t native_symbol; +} kzt_wrapper_probe_entry_t; + +typedef struct kzt_wrapper_probe_manifest { + int available; + const char *manifest_name; + const kzt_wrapper_probe_entry_t *entries; + size_t entry_count; +} kzt_wrapper_probe_manifest_t; + +typedef struct kzt_wrapper_probe_request { + const char *symbol_name; + kzt_symbol_version_evidence_t symbol_version_evidence; + const char *symbol_version; +} kzt_wrapper_probe_request_t; + +typedef struct kzt_wrapper_probe_bridge_request { + const char *symbol_name; + kzt_symbol_version_evidence_t symbol_version_evidence; + const char *symbol_version; + const char *wrapper_name; + kzt_symbol_version_evidence_t wrapper_version_evidence; + const char *wrapper_symbol_version; + uintptr_t native_symbol; +} kzt_wrapper_probe_bridge_request_t; + +typedef uintptr_t (*kzt_wrapper_probe_check_bridge_fn)( + uintptr_t native_symbol, void *opaque); + +typedef uintptr_t (*kzt_wrapper_probe_add_bridge_fn)( + const kzt_wrapper_probe_bridge_request_t *request, void *opaque); + +typedef struct kzt_wrapper_probe_bridge_ops { + kzt_wrapper_probe_check_bridge_fn check_bridge; + kzt_wrapper_probe_add_bridge_fn add_bridge; + void *opaque; +} kzt_wrapper_probe_bridge_ops_t; + +typedef struct kzt_wrapper_probe_result { + kzt_patch_wrapper_match_t wrapper_match; + const char *wrapper_name; + kzt_symbol_version_evidence_t wrapper_version_evidence; + const char *wrapper_symbol_version; + uintptr_t native_symbol; + uintptr_t bridge_target; + kzt_wrapper_probe_bridge_source_t bridge_source; +} kzt_wrapper_probe_result_t; + +int kzt_wrapper_probe_minimal_manifest( + const kzt_wrapper_probe_manifest_t *manifest, + const kzt_wrapper_probe_request_t *request, + const kzt_wrapper_probe_bridge_ops_t *bridge_ops, + kzt_wrapper_probe_result_t *result); + +void kzt_wrapper_probe_apply_to_candidate( + const kzt_wrapper_probe_result_t *probe, + kzt_patch_candidate_t *candidate); + +void kzt_wrapper_probe_apply_to_decision_request( + const kzt_wrapper_probe_result_t *probe, + kzt_patch_wrapper_match_t *wrapper_match, + const char **wrapper_name, + const char **wrapper_symbol_version, + uintptr_t *bridge_target); + +#endif diff --git a/target/i386/latx/include/latx-options.h b/target/i386/latx/include/latx-options.h index 96519c8a884..118b7f54650 100644 --- a/target/i386/latx/include/latx-options.h +++ b/target/i386/latx/include/latx-options.h @@ -22,6 +22,13 @@ extern int option_flag_reduction; extern int option_tu_link; #endif +#if defined(CONFIG_LATX_KZT) +extern int option_kzt_lazy_diagnostics; +extern int option_kzt_patch_spike; +extern int option_kzt_patch_spike_write; +extern unsigned long option_kzt_patch_spike_budget; +#endif + #ifdef CONFIG_LATX_AVX_OPT extern int option_avx_cpuid; #endif /* CONFIG_LATX_AVX_OPT */ @@ -131,7 +138,14 @@ extern unsigned long long counter_mips_tr; #if defined(CONFIG_LATX) && defined(CONFIG_LATX_KZT) #define ENVSUP_KZT \ - ENVFUN(LATX_KZT, handle_arg_latx_kzt) + ENVFUN(LATX_KZT, handle_arg_latx_kzt) \ + ENVFUN(LATX_KZT_LAZY_DIAGNOSTICS, \ + handle_arg_latx_kzt_lazy_diagnostics) \ + ENVFUN(LATX_KZT_REGISTRY_DIAGNOSTICS, \ + handle_arg_latx_kzt_registry_diagnostics) \ + ENVFUN(LATX_KZT_PATCH_SPIKE, handle_arg_latx_kzt_patch_spike) \ + ENVFUN(LATX_KZT_PATCH_SPIKE_WRITE, handle_arg_latx_kzt_patch_spike_write) \ + ENVFUN(LATX_KZT_PATCH_SPIKE_BUDGET, handle_arg_latx_kzt_patch_spike_budget) #else #define ENVSUP_KZT #endif diff --git a/target/i386/latx/include/librarian.h b/target/i386/latx/include/librarian.h index bd47496bf3d..413847d495e 100755 --- a/target/i386/latx/include/librarian.h +++ b/target/i386/latx/include/librarian.h @@ -30,6 +30,10 @@ kh_mapsymbols_t* GetWeakSymbol(lib_t* maplib); kh_mapsymbols_t* GetLocalSymbol(lib_t* maplib); kh_mapsymbols_t* GetGlobalData(lib_t* maplib); int AddNeededLib(lib_t* maplib, needed_libs_t* neededlibs, library_t *deplib, int local, int bindnow, const char** paths, int npath, box64context_t* box64); // 0=success, 1=error +int AddNeededLibWithLibrary(lib_t* maplib, needed_libs_t* neededlibs, + library_t *deplib, int local, int bindnow, + const char* path, box64context_t* box64, + library_t **exact_library); // AddNeededLib return semantics; exact_library is NULL on failure int AddNeededLib_add(lib_t* maplib, needed_libs_t* neededlibs, library_t* deplib, int local, const char* path, box64context_t* box64); library_t* GetLibMapLib(lib_t* maplib, const char* name); library_t* GetLibInternal(const char* name); @@ -37,6 +41,7 @@ uintptr_t FindGlobalSymbol(lib_t *maplib, const char* name, int version, const c int GetNoSelfSymbolStartEnd(lib_t *maplib, const char* name, uintptr_t* start, uintptr_t* end, elfheader_t* self, int version, const char* vername); int GetSelfSymbolStartEnd(lib_t *maplib, const char* name, uintptr_t* start, uintptr_t* end, elfheader_t *self, int version, const char* vername); int GetGlobalSymbolStartEnd(lib_t *maplib, const char* name, uintptr_t* start, uintptr_t* end, elfheader_t *self, int version, const char* vername); +int GetGlobalSymbolStartEndWithProvider(lib_t *maplib, const char* name, uintptr_t* start, uintptr_t* end, elfheader_t *self, int version, const char* vername, library_t **provider); int GetGlobalNoWeakSymbolStartEnd(lib_t *maplib, const char* name, uintptr_t* start, uintptr_t* end, int version, const char* vername); int GetLocalSymbolStartEnd(lib_t *maplib, const char* name,khint_t pre_k, uintptr_t* start, uintptr_t* end, elfheader_t *self, int version, const char* vername); int GetNoWeakSymbolStartEnd(lib_t *maplib, const char* name, uintptr_t* start, uintptr_t* end, elfheader_t *self, int version, const char* vername); diff --git a/target/i386/latx/latx-options.c b/target/i386/latx/latx-options.c index afa8c3d1d99..308714cf39c 100644 --- a/target/i386/latx/latx-options.c +++ b/target/i386/latx/latx-options.c @@ -13,6 +13,10 @@ #if defined(CONFIG_LATX_KZT) int option_kzt = 0; +int option_kzt_lazy_diagnostics = 0; +int option_kzt_patch_spike = 0; +int option_kzt_patch_spike_write = 0; +unsigned long option_kzt_patch_spike_budget = 0; #endif #ifdef CONFIG_LATX_AVX_OPT @@ -274,6 +278,12 @@ void options_init(void) option_fast_atomic = 1; else option_fast_atomic = 0; + +#if defined(CONFIG_LATX_KZT) + option_kzt_patch_spike = 0; + option_kzt_patch_spike_write = 0; + option_kzt_patch_spike_budget = 0; +#endif } #define OPTIONS_IMM_REG 0 diff --git a/tests/unit/kzt/guest_e2e/build_guest_probe.sh b/tests/unit/kzt/guest_e2e/build_guest_probe.sh new file mode 100755 index 00000000000..b2aed7302df --- /dev/null +++ b/tests/unit/kzt/guest_e2e/build_guest_probe.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +build_dir=${KZT_GUEST_BUILD_DIR:-${TMPDIR:-/tmp}/kzt-guest-e2e} +guest_cc=${KZT_GUEST_CC:-x86_64-linux-gnu-gcc} +guest_readelf=${KZT_GUEST_READELF:-readelf} +guest_libc=${KZT_GUEST_LIBC:--lc} + +read -r -a cc_flags <<< "${KZT_GUEST_CC_FLAGS:-}" +read -r -a launcher_flags <<< "${KZT_GUEST_CC_LAUNCHER_FLAGS:-}" + +run_cc() +{ + if [[ -n ${KZT_GUEST_CC_LAUNCHER:-} ]]; then + "${KZT_GUEST_CC_LAUNCHER}" "${launcher_flags[@]}" \ + "$guest_cc" "${cc_flags[@]}" "$@" + else + "$guest_cc" "${cc_flags[@]}" "$@" + fi +} + +mkdir -p "$build_dir" + +run_cc -O2 -fPIC -shared -nostdlib \ + -I"$script_dir" \ + "$script_dir/kzt_guest_probe.c" \ + "$guest_libc" \ + -o "$build_dir/libkzt_guest_probe.so" + +run_cc -O2 -nostdlib -Wl,-e,_start \ + -I"$script_dir" \ + "$script_dir/kzt_guest_main.c" \ + -L"$build_dir" -lkzt_guest_probe \ + -o "$build_dir/kzt_guest_main" + +header=$($guest_readelf -h "$build_dir/kzt_guest_main") +if ! grep -Eq 'Machine:.*(Advanced Micro Devices X86-64|X86-64)' \ + <<< "$header"; then + echo "Guest main is not an x86-64 ELF." >&2 + exit 1 +fi + +relocations=$($guest_readelf -rW "$build_dir/libkzt_guest_probe.so") +uname_jump_slots=$(grep -Ec \ + 'R_X86_64_(JUMP_SLOT|JUMP_SLO).*uname(@|[[:space:]])' \ + <<< "$relocations" || true) +if [[ $uname_jump_slots -ne 1 ]]; then + echo "Guest DSO must contain exactly one lazy uname JUMP_SLOT." >&2 + exit 1 +fi + +symbols=$($guest_readelf -Ws "$build_dir/libkzt_guest_probe.so") +uname_versions=$(sed -n \ + 's/.* UND uname@\(GLIBC_[^[:space:]]*\).*/\1/p' \ + <<< "$symbols" | sort -u) +if [[ -z $uname_versions || $uname_versions == *$'\n'* ]]; then + echo "Guest DSO must require one versioned uname symbol." >&2 + exit 1 +fi +printf '%s\n' "$uname_versions" > "$build_dir/guest-symbol-version.txt" + +dynamic=$($guest_readelf -d "$build_dir/libkzt_guest_probe.so") +if grep -Eq '(BIND_NOW|FLAGS.*NOW)' <<< "$dynamic"; then + echo "Guest DSO must use lazy binding." >&2 + exit 1 +fi + +main_dynamic=$($guest_readelf -d "$build_dir/kzt_guest_main") +if ! grep -Fq 'Shared library: [libkzt_guest_probe.so]' \ + <<< "$main_dynamic"; then + echo "Guest main is not linked to the probe DSO." >&2 + exit 1 +fi + +printf 'Guest fixture built in %s\n' "$build_dir" +printf 'uname version: %s\n' "$uname_versions" +printf 'Run: python3 tests/unit/kzt/test_real_guest_e2e.py '\''\n' +printf ' --latx --guest-root '\''\n' +printf ' --host-libc '\''\n' +printf ' --fixture-dir %q\n' "$build_dir" diff --git a/tests/unit/kzt/guest_e2e/kzt_guest_main.c b/tests/unit/kzt/guest_e2e/kzt_guest_main.c new file mode 100644 index 00000000000..8c66034e358 --- /dev/null +++ b/tests/unit/kzt/guest_e2e/kzt_guest_main.c @@ -0,0 +1,79 @@ +#include "kzt_guest_probe.h" + +static long raw_syscall3(long number, long arg1, long arg2, long arg3) +{ + long result; + + __asm__ volatile( + "syscall" + : "=a"(result) + : "a"(number), "D"(arg1), "S"(arg2), "d"(arg3) + : "rcx", "r11", "memory"); + return result; +} + +static void raw_exit(int status) +{ + __asm__ volatile( + "syscall" + : + : "a"(60L), "D"((long)status) + : "rcx", "r11", "memory"); + __builtin_unreachable(); +} + +static char *append_text(char *cursor, const char *text) +{ + while (*text) { + *cursor++ = *text++; + } + return cursor; +} + +static char *append_hex(char *cursor, uintptr_t value) +{ + static const char digits[] = "0123456789abcdef"; + int shift; + + cursor = append_text(cursor, "0x"); + for (shift = (int)(sizeof(value) * 8) - 4; shift > 0; shift -= 4) { + if ((value >> shift) != 0) { + break; + } + } + for (; shift >= 0; shift -= 4) { + *cursor++ = digits[(value >> shift) & 0xf]; + } + return cursor; +} + +void _start(void) +{ + static const char failure[] = "KZT_GUEST_E2E_FAIL\n"; + struct kzt_guest_probe_result result; + char success[256]; + char *cursor = success; + + if (kzt_guest_probe(&result) != 0) { + (void)raw_syscall3(1, 2, (long)failure, sizeof(failure) - 1); + raw_exit(1); + } + cursor = append_text(cursor, "KZT_GUEST_E2E_OK calls=2 slot="); + cursor = append_hex(cursor, result.slot_addr); + cursor = append_text(cursor, " before="); + cursor = append_hex(cursor, result.before); + cursor = append_text(cursor, " after_first="); + cursor = append_hex(cursor, result.after_first); + cursor = append_text(cursor, " after_second="); + cursor = append_hex(cursor, result.after_second); + cursor = append_text(cursor, " first_ns="); + cursor = append_hex(cursor, result.first_call_ns); + cursor = append_text(cursor, " second_ns="); + cursor = append_hex(cursor, result.second_call_ns); + *cursor++ = '\n'; + if (raw_syscall3(1, 1, (long)success, cursor - success) != + cursor - success) { + raw_exit(2); + } + raw_exit(0); +} diff --git a/tests/unit/kzt/guest_e2e/kzt_guest_probe.c b/tests/unit/kzt/guest_e2e/kzt_guest_probe.c new file mode 100644 index 00000000000..9bd722a1f36 --- /dev/null +++ b/tests/unit/kzt/guest_e2e/kzt_guest_probe.c @@ -0,0 +1,85 @@ +#include "kzt_guest_probe.h" + +struct guest_utsname { + char sysname[65]; + char nodename[65]; + char release[65]; + char version[65]; + char machine[65]; + char domainname[65]; +}; + +extern int uname(struct guest_utsname *result); + +struct guest_timespec { + long seconds; + long nanoseconds; +}; + +static uint64_t monotonic_raw_ns(void) +{ + struct guest_timespec value; + long status; + + __asm__ volatile( + "syscall" + : "=a"(status) + : "a"(228L), "D"(4L), "S"((long)&value) + : "rcx", "r11", "memory"); + if (status != 0) { + return 0; + } + return (uint64_t)value.seconds * 1000000000ULL + + (uint64_t)value.nanoseconds; +} + +static uintptr_t *uname_jump_slot(void) +{ + const unsigned char *plt; + uint32_t raw_displacement; + int32_t displacement; + + __asm__ volatile("lea uname@PLT(%%rip), %0" : "=r"(plt)); + if (plt[0] != 0xff || plt[1] != 0x25) { + return 0; + } + raw_displacement = (uint32_t)plt[2] | + ((uint32_t)plt[3] << 8) | + ((uint32_t)plt[4] << 16) | + ((uint32_t)plt[5] << 24); + displacement = (int32_t)raw_displacement; + return (uintptr_t *)(plt + 6 + displacement); +} + +int kzt_guest_probe(struct kzt_guest_probe_result *result) +{ + struct guest_utsname utsname; + uintptr_t *slot; + uint64_t before_call; + uint64_t after_call; + + if (!result || !(slot = uname_jump_slot())) { + return 2; + } + result->slot_addr = (uintptr_t)slot; + result->before = *(volatile uintptr_t *)slot; + + before_call = monotonic_raw_ns(); + if (!before_call || uname(&utsname) != 0 || + utsname.machine[0] == '\0' || + !(after_call = monotonic_raw_ns()) || after_call < before_call) { + return 1; + } + result->first_call_ns = after_call - before_call; + result->after_first = *(volatile uintptr_t *)slot; + + before_call = monotonic_raw_ns(); + if (!before_call || uname(&utsname) != 0 || + utsname.machine[0] == '\0' || + !(after_call = monotonic_raw_ns()) || after_call < before_call) { + return 1; + } + result->second_call_ns = after_call - before_call; + result->after_second = *(volatile uintptr_t *)slot; + return 0; +} diff --git a/tests/unit/kzt/guest_e2e/kzt_guest_probe.h b/tests/unit/kzt/guest_e2e/kzt_guest_probe.h new file mode 100644 index 00000000000..9a676aa42f0 --- /dev/null +++ b/tests/unit/kzt/guest_e2e/kzt_guest_probe.h @@ -0,0 +1,19 @@ +#ifndef KZT_GUEST_PROBE_H +#define KZT_GUEST_PROBE_H + +#include + +#define KZT_GUEST_PROBE_CALL_COUNT 2 + +struct kzt_guest_probe_result { + uintptr_t slot_addr; + uintptr_t before; + uintptr_t after_first; + uintptr_t after_second; + uint64_t first_call_ns; + uint64_t second_call_ns; +}; + +int kzt_guest_probe(struct kzt_guest_probe_result *result); + +#endif diff --git a/tests/unit/kzt/kzt_test_options.c b/tests/unit/kzt/kzt_test_options.c new file mode 100644 index 00000000000..24587e7482b --- /dev/null +++ b/tests/unit/kzt/kzt_test_options.c @@ -0,0 +1,3 @@ +int option_kzt_patch_spike; +int option_kzt_patch_spike_write; +unsigned long option_kzt_patch_spike_budget; diff --git a/tests/unit/kzt/kzt_test_options.h b/tests/unit/kzt/kzt_test_options.h new file mode 100644 index 00000000000..2ca906d5f3f --- /dev/null +++ b/tests/unit/kzt/kzt_test_options.h @@ -0,0 +1,8 @@ +#ifndef KZT_TEST_OPTIONS_H +#define KZT_TEST_OPTIONS_H + +extern int option_kzt_patch_spike; +extern int option_kzt_patch_spike_write; +extern unsigned long option_kzt_patch_spike_budget; + +#endif diff --git a/tests/unit/kzt/test_dlopen_recycle_transaction.c b/tests/unit/kzt/test_dlopen_recycle_transaction.c new file mode 100644 index 00000000000..6d77ea2a0bc --- /dev/null +++ b/tests/unit/kzt/test_dlopen_recycle_transaction.c @@ -0,0 +1,354 @@ +#include +#include +#include + +#include "dlopen_recycle_transaction.h" +#include "elf_plt_relocation.h" +#include "kzt_guest_library_binding.h" +#include "kzt_guest_registry.h" + +typedef struct fake_library { int id; } fake_library_t; + +typedef struct recycle_fixture { + kzt_guest_library_bindings_t *bindings; + kzt_guest_registry_t *registry; + fake_library_t library; + uintptr_t link_map_addr; + unsigned long generation; + int active; + int prepared; + int guest_open_result; + int reload_result; + int plt_relocation_result; + int guest_close_calls; + int guest_open_calls; + uintptr_t delta; + uintptr_t internal_l_addr; + int latx_hasfix; + int had_relocate_elf; + int had_relocate_plt; + int latx_type; +} recycle_fixture_t; + +static int failures; + +#define CHECK(name, expr) do { if (!(expr)) { \ + fprintf(stderr, "FAIL %s\n", name); ++failures; } } while (0) + +static kzt_guest_object_observation_t observation(uintptr_t map) +{ + return (kzt_guest_object_observation_t){ + .link_map_addr = map, + .load_bias = { .value = 0x500000, .status = KZT_GUEST_FIELD_OK }, + .dynamic_addr = { .status = KZT_GUEST_FIELD_UNKNOWN }, + .map_start = { .status = KZT_GUEST_FIELD_UNKNOWN }, + .map_end = { .status = KZT_GUEST_FIELD_UNKNOWN }, + .namespace_id = { .value = 0, .status = KZT_GUEST_FIELD_OK }, + .path = { .status = KZT_GUEST_FIELD_UNKNOWN }, + .soname = { .status = KZT_GUEST_FIELD_UNKNOWN }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static kzt_guest_library_binding_key_t binding_key(uintptr_t map, + unsigned long generation) +{ + return (kzt_guest_library_binding_key_t){ + .link_map_addr = map, + .generation = generation, + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; +} + +static unsigned long current_generation(recycle_fixture_t *fixture) +{ + kzt_guest_object_snapshot_t *snapshot = NULL; + unsigned long generation = 0; + CHECK("transaction registry snapshot", + kzt_guest_registry_find_by_link_map( + fixture->registry, fixture->link_map_addr, &snapshot) == 0 && + snapshot != NULL); + if (snapshot) generation = snapshot->generation; + kzt_guest_object_snapshot_free(snapshot); + return generation; +} + +static void fixture_init(recycle_fixture_t *fixture) +{ + kzt_guest_object_observation_t observed; + kzt_guest_library_binding_key_t key; + *fixture = (recycle_fixture_t){ + .bindings = kzt_guest_library_bindings_init(), + .registry = kzt_guest_registry_init(), + .library = { .id = 1 }, + .link_map_addr = 0xd000, + .active = 1, + .delta = 0x400000, + .internal_l_addr = 0x400000, + .latx_hasfix = 1, + .had_relocate_elf = 1, + .had_relocate_plt = 1, + .latx_type = 3, + }; + observed = observation(fixture->link_map_addr); + CHECK("transaction track", kzt_guest_library_track( + fixture->bindings, (library_t *)&fixture->library) == 0); + CHECK("transaction initial observe", kzt_guest_registry_observe( + fixture->registry, &observed) == KZT_GUEST_REGISTRY_ADDED); + fixture->generation = current_generation(fixture); + key = binding_key(fixture->link_map_addr, fixture->generation); + CHECK("transaction initial binding observation", + kzt_guest_library_note_observation(fixture->bindings, &key) == + KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + CHECK("transaction initial pair", kzt_guest_library_note_exact_pair( + fixture->bindings, fixture->link_map_addr, + (library_t *)&fixture->library, + KZT_GUEST_LIBRARY_OBJECT_EMULATED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + kzt_guest_library_inactivate(fixture->bindings, fixture->registry, + (library_t *)&fixture->library, + fixture->link_map_addr); + fixture->active = 0; + /* Model the stale pointer retained by the old wrappers after dlclose. */ + fixture->link_map_addr = 0xd000; +} + +static void fixture_destroy(recycle_fixture_t *fixture) +{ + kzt_guest_library_bindings_destroy(&fixture->bindings); + kzt_guest_registry_destroy(&fixture->registry); +} + +static void recycle_prepare(void *opaque) +{ + recycle_fixture_t *fixture = opaque; + fixture->prepared = 1; + fixture->link_map_addr = 0; + fixture->delta = 0; + fixture->internal_l_addr = 0; + fixture->latx_hasfix = 0; + fixture->had_relocate_elf = 0; + fixture->had_relocate_plt = 0; + fixture->latx_type = 0; +} + +static int recycle_guest_open(void *opaque) +{ + recycle_fixture_t *fixture = opaque; + kzt_guest_object_observation_t observed; + kzt_guest_library_binding_key_t key; + if (fixture->guest_open_result != 0) + return fixture->guest_open_result; + ++fixture->guest_open_calls; + fixture->link_map_addr = 0xd000; + fixture->delta = 0x500000 + (uintptr_t)fixture->guest_open_calls * 0x100000; + fixture->internal_l_addr = fixture->delta; + fixture->latx_hasfix = 1; + fixture->active = 1; + observed = observation(fixture->link_map_addr); + CHECK("transaction guest observation", kzt_guest_registry_observe( + fixture->registry, &observed) == KZT_GUEST_REGISTRY_ADDED); + fixture->generation = current_generation(fixture); + key = binding_key(fixture->link_map_addr, fixture->generation); + CHECK("transaction guest binding observation", + kzt_guest_library_note_observation(fixture->bindings, &key) == + KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + CHECK("transaction guest incarnation live", + kzt_guest_library_reactivate( + fixture->bindings, (library_t *)&fixture->library) == 0); + return 0; +} + +static int simulated_plt_relocation(void *opaque, int *need_resolver) +{ + recycle_fixture_t *fixture = opaque; + if (need_resolver) + *need_resolver = 1; + return fixture->plt_relocation_result; +} + +static int recycle_reload(void *opaque) +{ + recycle_fixture_t *fixture = opaque; + kzt_guest_library_binding_result_t pair_result; + int need_resolver = 0; + if (fixture->reload_result != 0) + return fixture->reload_result; + fixture->had_relocate_elf = 1; + if (elf_plt_relocation_apply(simulated_plt_relocation, fixture, + &need_resolver) != 0) + return -1; + fixture->had_relocate_plt = 1; + fixture->active = 1; + CHECK("transaction reactivate", kzt_guest_library_reactivate( + fixture->bindings, (library_t *)&fixture->library) == 0); + pair_result = kzt_guest_library_note_exact_pair( + fixture->bindings, fixture->link_map_addr, + (library_t *)&fixture->library, KZT_GUEST_LIBRARY_OBJECT_EMULATED); + CHECK("transaction publish pair", + pair_result == KZT_GUEST_LIBRARY_BINDING_ADDED || + pair_result == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + return 0; +} + +static void recycle_rollback(void *opaque, int guest_opened) +{ + recycle_fixture_t *fixture = opaque; + if (guest_opened && fixture->link_map_addr) + ++fixture->guest_close_calls; + kzt_guest_library_inactivate(fixture->bindings, fixture->registry, + (library_t *)&fixture->library, + fixture->link_map_addr); + fixture->active = 0; + fixture->link_map_addr = 0; + fixture->delta = 0; + fixture->internal_l_addr = 0; + fixture->latx_hasfix = 0; + fixture->had_relocate_elf = 0; + fixture->had_relocate_plt = 0; + fixture->latx_type = 0; +} + +static void check_failed_state(const char *prefix, recycle_fixture_t *fixture) +{ + kzt_guest_library_binding_state_t lifecycle; + size_t pending = 99, live_entries = 99; + (void)prefix; + CHECK("failure active false", fixture->active == 0); + CHECK("failure link_map cleared", fixture->link_map_addr == 0); + CHECK("failure delta cleared", fixture->delta == 0); + CHECK("failure internal l_addr cleared", fixture->internal_l_addr == 0); + CHECK("failure latx_hasfix cleared", fixture->latx_hasfix == 0); + CHECK("failure relocate flag cleared", fixture->had_relocate_elf == 0); + CHECK("failure PLT flag cleared", fixture->had_relocate_plt == 0); + CHECK("failure LATX type cleared", fixture->latx_type == 0); + CHECK("failure binding snapshot", + kzt_guest_library_binding_test_snapshot( + fixture->bindings, (library_t *)&fixture->library, + &lifecycle, &pending, &live_entries) == 0); + CHECK("failure lifecycle dead", + lifecycle == KZT_GUEST_LIBRARY_BINDING_DEAD); + CHECK("failure pending empty", pending == 0); + CHECK("failure live pair absent", live_entries == 0); +} + +static void test_guest_open_failure(void) +{ + recycle_fixture_t fixture; + size_t count = 0; + void *result; + fixture_init(&fixture); + fixture.guest_open_result = -1; + result = dlopen_recycle_transaction( + (void *)0x55, &count, 1, &fixture, recycle_prepare, + recycle_guest_open, recycle_reload, recycle_rollback); + CHECK("guest failure returned null", result == NULL); + CHECK("guest failure count unchanged", count == 0); + CHECK("guest failure prepared", fixture.prepared == 1); + CHECK("guest failure no close", fixture.guest_close_calls == 0); + check_failed_state("guest", &fixture); + fixture_destroy(&fixture); +} + +static void test_reload_failure(void) +{ + recycle_fixture_t fixture; + kzt_guest_object_observation_t observed; + unsigned long failed_generation, reused_generation; + size_t count = 0; + void *result; + fixture_init(&fixture); + fixture.reload_result = -1; + result = dlopen_recycle_transaction( + (void *)0x66, &count, 1, &fixture, recycle_prepare, + recycle_guest_open, recycle_reload, recycle_rollback); + failed_generation = fixture.generation; + CHECK("reload failure returned null", result == NULL); + CHECK("reload failure count unchanged", count == 0); + CHECK("reload failure closes guest once", fixture.guest_close_calls == 1); + check_failed_state("reload", &fixture); + + observed = observation(0xd000); + CHECK("reload failure observation retired", + kzt_guest_registry_observe(fixture.registry, &observed) == + KZT_GUEST_REGISTRY_ADDED); + fixture.link_map_addr = 0xd000; + reused_generation = current_generation(&fixture); + CHECK("reload failure reuse generation", + reused_generation > failed_generation); + fixture_destroy(&fixture); +} + +static void test_plt_relocation_failure_rolls_back_and_retry_overwrites(void) +{ + recycle_fixture_t fixture; + size_t count = 0; + void *result; + + fixture_init(&fixture); + fixture.plt_relocation_result = -1; + result = dlopen_recycle_transaction( + (void *)0x76, &count, 1, &fixture, recycle_prepare, + recycle_guest_open, recycle_reload, recycle_rollback); + CHECK("PLT failure returned null", result == NULL); + CHECK("PLT failure count unchanged", count == 0); + CHECK("PLT failure compensates once", fixture.guest_close_calls == 1); + check_failed_state("plt", &fixture); + + fixture.plt_relocation_result = 0; + result = dlopen_recycle_transaction( + (void *)0x76, &count, 1, &fixture, recycle_prepare, + recycle_guest_open, recycle_reload, recycle_rollback); + CHECK("PLT retry returned handle", result == (void *)0x76); + CHECK("PLT retry increments count", count == 1); + CHECK("PLT retry did not compensate", fixture.guest_close_calls == 1); + CHECK("PLT retry overwrote delta", fixture.delta == 0x700000); + CHECK("PLT retry overwrote l_addr", fixture.internal_l_addr == 0x700000); + CHECK("PLT retry set fix flag", fixture.latx_hasfix == 1); + CHECK("PLT retry set relocation flag", fixture.had_relocate_elf == 1); + CHECK("PLT retry set PLT flag", fixture.had_relocate_plt == 1); + fixture_destroy(&fixture); +} + +static void test_success_commits_handle_count_and_pair(void) +{ + recycle_fixture_t fixture; + kzt_guest_library_binding_state_t lifecycle; + kzt_guest_library_binding_key_t key; + kzt_guest_library_handle_t binding_handle; + size_t pending = 99, live_entries = 99, count = 0; + void *result; + fixture_init(&fixture); + result = dlopen_recycle_transaction( + (void *)0x77, &count, 1, &fixture, recycle_prepare, + recycle_guest_open, recycle_reload, recycle_rollback); + CHECK("success returned old handle", result == (void *)0x77); + CHECK("success count incremented", count == 1); + CHECK("success active", fixture.active == 1); + CHECK("success binding snapshot", + kzt_guest_library_binding_test_snapshot( + fixture.bindings, (library_t *)&fixture.library, &lifecycle, + &pending, &live_entries) == 0); + CHECK("success lifecycle live", + lifecycle == KZT_GUEST_LIBRARY_BINDING_LIVE); + CHECK("success pending empty", pending == 0); + CHECK("success exact pair live", live_entries == 1); + key = binding_key(fixture.link_map_addr, fixture.generation); + CHECK("success pair lookup", kzt_guest_library_lookup( + fixture.bindings, &key, &binding_handle) == 0); + kzt_guest_library_handle_release(&binding_handle); + fixture_destroy(&fixture); +} + +int main(void) +{ + test_guest_open_failure(); + test_reload_failure(); + test_plt_relocation_failure_rolls_back_and_retry_overwrites(); + test_success_commits_handle_count_and_pair(); + if (failures) + fprintf(stderr, "dlopen recycle transaction: %d failure(s)\n", + failures); + return failures ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/tests/unit/kzt/test_elf_map_range.c b/tests/unit/kzt/test_elf_map_range.c new file mode 100644 index 00000000000..fb9b16d19d8 --- /dev/null +++ b/tests/unit/kzt/test_elf_map_range.c @@ -0,0 +1,250 @@ +#include +#include + +#include "elfmap.h" + +static int failures; + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_address(const char *name, uintptr_t got, + uintptr_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static Elf64_Phdr load_segment(uint64_t virtual_address, uint64_t memory_size) +{ + return (Elf64_Phdr) { + .p_type = PT_LOAD, + .p_vaddr = virtual_address, + .p_memsz = memory_size, + }; +} + +static void expect_failure(const char *name, const Elf64_Phdr *headers, + size_t count, uintptr_t load_bias, + uintptr_t page_size) +{ + uintptr_t start = 0x11111111; + uintptr_t end = 0x22222222; + + check_int(name, GetElfLoadRange(headers, count, load_bias, page_size, + &start, &end), -1); + check_address("failure-preserves-start", start, 0x11111111); + check_address("failure-preserves-end", end, 0x22222222); +} + +static void test_single_segment_is_page_aligned(void) +{ + Elf64_Phdr header = load_segment(0x1234, 0x2345); + uintptr_t start = 0; + uintptr_t end = 0; + + check_int("single.result", + GetElfLoadRange(&header, 1, 0x400000, 0x1000, + &start, &end), 0); + check_address("single.start", start, 0x401000); + check_address("single.end", end, 0x404000); +} + +static void test_page_aligned_end_is_not_extended(void) +{ + Elf64_Phdr header = load_segment(0x2000, 0x1000); + uintptr_t start = 0; + uintptr_t end = 0; + + check_int("aligned-end.result", + GetElfLoadRange(&header, 1, 0x800000, 0x1000, + &start, &end), 0); + check_address("aligned-end.start", start, 0x802000); + check_address("aligned-end.end", end, 0x803000); +} + +static void test_multiple_segments_use_outer_range(void) +{ + Elf64_Phdr headers[] = { + load_segment(0x9000, 0x1100), + load_segment(0x1234, 0x20), + load_segment(0x5000, 0x2800), + }; + uintptr_t start = 0; + uintptr_t end = 0; + + check_int("multiple.result", + GetElfLoadRange(headers, 3, 0x100000, 0x1000, + &start, &end), 0); + check_address("multiple.start", start, 0x101000); + check_address("multiple.end", end, 0x10b000); +} + +static void test_non_load_and_empty_segments_are_ignored(void) +{ + Elf64_Phdr headers[] = { + { + .p_type = PT_DYNAMIC, + .p_vaddr = UINT64_MAX - 1, + .p_memsz = UINT64_MAX, + }, + load_segment(UINT64_MAX, 0), + load_segment(0x3001, 1), + }; + uintptr_t start = 0; + uintptr_t end = 0; + + check_int("ignored.result", + GetElfLoadRange(headers, 3, 0, 0x1000, &start, &end), 0); + check_address("ignored.start", start, 0x3000); + check_address("ignored.end", end, 0x4000); +} + +static void test_invalid_arguments(void) +{ + Elf64_Phdr header = load_segment(0x1000, 0x1000); + uintptr_t start = 0x11111111; + uintptr_t end = 0x22222222; + + check_int("null-headers", + GetElfLoadRange(NULL, 1, 0, 0x1000, &start, &end), -1); + check_int("zero-count", + GetElfLoadRange(&header, 0, 0, 0x1000, &start, &end), -1); + check_int("null-start", + GetElfLoadRange(&header, 1, 0, 0x1000, NULL, &end), -1); + check_int("null-end", + GetElfLoadRange(&header, 1, 0, 0x1000, &start, NULL), -1); + check_int("aliased-output", + GetElfLoadRange(&header, 1, 0, 0x1000, &start, &start), -1); + expect_failure("zero-page-size", &header, 1, 0, 0); + expect_failure("non-power-of-two-page-size", &header, 1, 0, 0x1800); +} + +static void test_missing_load_segment(void) +{ + Elf64_Phdr non_load = { + .p_type = PT_DYNAMIC, + .p_vaddr = 0x1000, + .p_memsz = 0x1000, + }; + Elf64_Phdr empty_load = load_segment(0x1000, 0); + + expect_failure("no-load", &non_load, 1, 0, 0x1000); + expect_failure("empty-load", &empty_load, 1, 0, 0x1000); +} + +static void test_segment_end_overflow(void) +{ + Elf64_Phdr header = load_segment(UINT64_MAX - 0xfff, 0x1000); + + expect_failure("segment-end-overflow", &header, 1, 0, 0x1000); +} + +static void test_page_rounding_overflow(void) +{ + Elf64_Phdr header = load_segment(UINTPTR_MAX - 0x7ff, 0x400); + + expect_failure("page-rounding-overflow", &header, 1, 0, 0x1000); +} + +static void test_load_bias_overflow(void) +{ + Elf64_Phdr header = load_segment(0x1000, 0x1000); + + expect_failure("load-bias-start-overflow", &header, 1, + UINTPTR_MAX, 0x1000); + expect_failure("load-bias-end-overflow", &header, 1, + UINTPTR_MAX - 0x1000, 0x1000); +} + +static void test_dynamic_runtime_address_ignores_empty_image_info_hint(void) +{ + Elf64_Phdr headers[] = { + load_segment(0, 0x3000), + { + .p_type = PT_DYNAMIC, + .p_vaddr = 0x2f00, + .p_memsz = 0x100, + }, + }; + uintptr_t image_info_pt_dynamic_addr = 0; + uintptr_t dynamic_addr = image_info_pt_dynamic_addr; + + check_int("dynamic.normal-x86-image", + GetElfDynamicAddress(headers, 2, 0x400000, &dynamic_addr), 0); + check_address("dynamic.from-main-elf-phdr", dynamic_addr, 0x402f00); +} + +static void test_dynamic_runtime_address_rejects_bad_evidence(void) +{ + Elf64_Phdr missing = load_segment(0, 0x1000); + Elf64_Phdr duplicate[] = { + { .p_type = PT_DYNAMIC, .p_vaddr = 0x1000 }, + { .p_type = PT_DYNAMIC, .p_vaddr = 0x2000 }, + }; + Elf64_Phdr overflow = { + .p_type = PT_DYNAMIC, + .p_vaddr = UINTPTR_MAX, + }; + uintptr_t dynamic_addr = 0x11111111; + + check_int("dynamic.missing", + GetElfDynamicAddress(&missing, 1, 0x400000, + &dynamic_addr), -1); + check_int("dynamic.duplicate", + GetElfDynamicAddress(duplicate, 2, 0x400000, + &dynamic_addr), -1); + check_int("dynamic.overflow", + GetElfDynamicAddress(&overflow, 1, 1, &dynamic_addr), -1); + check_address("dynamic.failure-preserves-output", dynamic_addr, + 0x11111111); +} + +#if UINTPTR_MAX < UINT64_MAX +static void test_elf_address_too_wide_for_host(void) +{ + Elf64_Phdr start_too_wide = load_segment((uint64_t)UINTPTR_MAX + 1, 1); + Elf64_Phdr end_too_wide = load_segment(UINTPTR_MAX - 0x100, 0x200); + + expect_failure("start-too-wide", &start_too_wide, 1, 0, 1); + expect_failure("end-too-wide", &end_too_wide, 1, 0, 1); +} +#endif + +int main(void) +{ + test_single_segment_is_page_aligned(); + test_page_aligned_end_is_not_extended(); + test_multiple_segments_use_outer_range(); + test_non_load_and_empty_segments_are_ignored(); + test_invalid_arguments(); + test_missing_load_segment(); + test_segment_end_overflow(); + test_page_rounding_overflow(); + test_load_bias_overflow(); + test_dynamic_runtime_address_ignores_empty_image_info_hint(); + test_dynamic_runtime_address_rejects_bad_evidence(); +#if UINTPTR_MAX < UINT64_MAX + test_elf_address_too_wide_for_host(); +#endif + + if (failures) { + fprintf(stderr, "kzt-elf-map-range: %d failure(s)\n", failures); + return 1; + } + + puts("kzt-elf-map-range: all tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_guest_dynamic_diagnostics.c b/tests/unit/kzt/test_guest_dynamic_diagnostics.c new file mode 100644 index 00000000000..9ce831257e8 --- /dev/null +++ b/tests/unit/kzt/test_guest_dynamic_diagnostics.c @@ -0,0 +1,496 @@ +#include +#include + +#include "target/i386/latx/include/kzt_guest_dynamic_diagnostics.h" + +#define TEST_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#define KZT_TEST_UNKNOWN_DYNAMIC_TAG 0x6000000d + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_size(const char *name, size_t got, size_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lu expected %lu\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lu expected %lu\n", name, got, expected); + ++failures; +} + +static void check_u64(const char *name, uint64_t got, uint64_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%llx expected 0x%llx\n", name, + (unsigned long long)got, (unsigned long long)expected); + ++failures; +} + +static void check_uintptr(const char *name, uintptr_t got, uintptr_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_str_contains(const char *name, const char *value, + const char *expected) +{ + if (value && expected && strstr(value, expected)) { + return; + } + + fprintf(stderr, "%s: '%s' does not contain '%s'\n", name, + value ? value : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static kzt_guest_dynamic_field_t make_field( + uint64_t value, + kzt_guest_dynamic_address_semantics_t semantics) +{ + return (kzt_guest_dynamic_field_t) { + .present = 1, + .value = value, + .address_semantics = semantics, + }; +} + +static kzt_guest_dynamic_parse_result_t make_complete_result(void) +{ + kzt_guest_dynamic_parse_result_t result = { + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .error = KZT_GUEST_DYNAMIC_ERROR_NONE, + .entry_count = 12, + .scan_limit = KZT_GUEST_DYNAMIC_SCAN_LIMIT, + .view = { + .dynamic_addr = 0x7000001000, + .load_bias = 0x7000000000, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .entry_count = 12, + .has_null = 1, + .scan_limit = KZT_GUEST_DYNAMIC_SCAN_LIMIT, + .symtab = make_field(0x7000010000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .strtab = make_field(0x7000020000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .syment = make_field(24, KZT_GUEST_DYNAMIC_SCALAR), + .strsz = make_field(0x220, KZT_GUEST_DYNAMIC_SCALAR), + .hash = make_field(0x7000030000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .gnu_hash = make_field(0x7000040000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .versym = make_field(0x7000050000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .verneed = make_field(0x7000060000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .verneednum = make_field(2, KZT_GUEST_DYNAMIC_SCALAR), + .verdef = make_field(0x7000070000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .verdefnum = make_field(1, KZT_GUEST_DYNAMIC_SCALAR), + .rela = make_field(0x7000080000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .relasz = make_field(0x60, KZT_GUEST_DYNAMIC_SCALAR), + .relaent = make_field(24, KZT_GUEST_DYNAMIC_SCALAR), + .rel = make_field(0x7000090000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .relsz = make_field(0x40, KZT_GUEST_DYNAMIC_SCALAR), + .relent = make_field(16, KZT_GUEST_DYNAMIC_SCALAR), + .jmprel = make_field(0x70000a0000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .pltrelsz = make_field(0x30, KZT_GUEST_DYNAMIC_SCALAR), + .pltrel = make_field(DT_RELA, KZT_GUEST_DYNAMIC_SCALAR), + .pltgot = make_field(0x70000b0000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .needed_offsets = { 0x10, 0x38 }, + .needed_count = 2, + .needed_address_semantics = + KZT_GUEST_DYNAMIC_STRING_TABLE_OFFSET, + }, + }; + + return result; +} + +static const kzt_guest_dynamic_diagnostic_field_t *require_field( + const kzt_guest_dynamic_diagnostic_report_t *report, + const char *name) +{ + const kzt_guest_dynamic_diagnostic_field_t *field = + kzt_guest_dynamic_diagnostic_find_field(report, name); + + check_true(name, field != NULL); + return field; +} + +static void assert_field_match( + const kzt_guest_dynamic_diagnostic_report_t *report, + const char *name, + kzt_guest_dynamic_diagnostic_match_t expected) +{ + const kzt_guest_dynamic_diagnostic_field_t *field = require_field(report, + name); + + if (!field) { + return; + } + + check_int(name, field->match, expected); +} + +static void test_identical_views_are_matched(void) +{ + kzt_guest_dynamic_parse_result_t old_result = make_complete_result(); + kzt_guest_dynamic_parse_result_t new_result = make_complete_result(); + kzt_guest_dynamic_diagnostic_report_t report; + + check_int("identical.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + check_int("identical.status", report.status_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED); + check_int("identical.entry-count", report.entry_count_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED); + check_int("identical.unknown-tags", report.unknown_tags_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED); + check_size("identical.field-count", report.field_count, 22); + check_size("identical.matched", report.matched_count, report.field_count); + check_size("identical.missing-old", report.missing_old_count, 0); + check_size("identical.missing-new", report.missing_new_count, 0); + check_size("identical.mismatch", report.mismatch_count, 0); + check_size("identical.difference", report.difference_count, 0); + check_size("identical.blocking", report.blocking_count, 0); + assert_field_match(&report, "symtab", + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED); + assert_field_match(&report, "needed_offsets", + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED); +} + +static void test_old_field_missing_from_new_is_reported(void) +{ + kzt_guest_dynamic_parse_result_t old_result = make_complete_result(); + kzt_guest_dynamic_parse_result_t new_result = make_complete_result(); + kzt_guest_dynamic_diagnostic_report_t report; + const kzt_guest_dynamic_diagnostic_field_t *field; + + memset(&new_result.view.strtab, 0, sizeof(new_result.view.strtab)); + check_int("missing-new.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + + field = require_field(&report, "strtab"); + if (!field) { + return; + } + + check_int("missing-new.match", field->match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_NEW); + check_true("missing-new.old-present", field->old_present); + check_true("missing-new.new-present", !field->new_present); + check_u64("missing-new.old-value", field->old_value, 0x7000020000); + check_size("missing-new.count", report.missing_new_count, 1); + check_size("missing-new.difference", report.difference_count, 1); + check_size("missing-new.blocking", report.blocking_count, 0); +} + +static void test_new_parser_incomplete_states_are_reported(void) +{ + kzt_guest_dynamic_parse_result_t old_result = make_complete_result(); + kzt_guest_dynamic_parse_result_t new_result = make_complete_result(); + kzt_guest_dynamic_diagnostic_report_t report; + + new_result.status = KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL; + new_result.error = KZT_GUEST_DYNAMIC_ERROR_SCAN_LIMIT_EXCEEDED; + new_result.entry_count = KZT_GUEST_DYNAMIC_SCAN_LIMIT; + new_result.view.status = KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL; + new_result.view.entry_count = KZT_GUEST_DYNAMIC_SCAN_LIMIT; + new_result.view.has_null = 0; + + check_int("truncated.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + check_int("truncated.status-match", report.status_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH); + check_int("truncated.new-status", report.new_status, + KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL); + check_int("truncated.new-error", report.new_error, + KZT_GUEST_DYNAMIC_ERROR_SCAN_LIMIT_EXCEEDED); + check_true("truncated.flag", report.new_truncated); + check_size("truncated.blocking", report.blocking_count, 1); + + new_result = make_complete_result(); + new_result.status = KZT_GUEST_DYNAMIC_READ_ERROR; + new_result.error = KZT_GUEST_DYNAMIC_ERROR_READ_FAILURE; + new_result.read_error_addr = 0x7000001080; + new_result.entry_count = 8; + new_result.view.status = KZT_GUEST_DYNAMIC_READ_ERROR; + new_result.view.entry_count = 8; + new_result.view.has_null = 0; + + check_int("read-error.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + check_int("read-error.status-match", report.status_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH); + check_int("read-error.new-status", report.new_status, + KZT_GUEST_DYNAMIC_READ_ERROR); + check_int("read-error.new-error", report.new_error, + KZT_GUEST_DYNAMIC_ERROR_READ_FAILURE); + check_true("read-error.flag", report.new_read_error); + check_uintptr("read-error.addr", report.new_read_error_addr, + 0x7000001080); + check_size("read-error.blocking", report.blocking_count, 1); +} + +static void test_unknown_tag_difference_is_diagnostic_only(void) +{ + kzt_guest_dynamic_parse_result_t old_result = make_complete_result(); + kzt_guest_dynamic_parse_result_t new_result = make_complete_result(); + kzt_guest_dynamic_diagnostic_report_t report; + kzt_guest_dynamic_diagnostic_summary_t summary; + char line[512]; + + new_result.unknown_tag_count = 1; + new_result.first_unknown_tag = KZT_TEST_UNKNOWN_DYNAMIC_TAG; + new_result.first_unknown_tag_index = 3; + new_result.view.unknown_tag_count = 1; + new_result.view.first_unknown_tag = KZT_TEST_UNKNOWN_DYNAMIC_TAG; + new_result.view.first_unknown_tag_index = 3; + + check_int("unknown.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + check_int("unknown.match", report.unknown_tags_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH); + check_size("unknown.old-count", report.old_unknown_tag_count, 0); + check_size("unknown.new-count", report.new_unknown_tag_count, 1); + check_size("unknown.field-mismatch", report.mismatch_count, 0); + check_size("unknown.difference", report.difference_count, 1); + check_size("unknown.blocking", report.blocking_count, 0); + + check_int("unknown.summary", + kzt_guest_dynamic_diagnostics_summarize(&report, 0xabc000, 9, + &summary), + 0); + check_int("unknown.summary-kind", summary.first_difference_kind, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_UNKNOWN_TAGS); + check_true("unknown.summary-new-present", summary.first_new_present); + check_true("unknown.summary-new-tag", + summary.first_new_tag == KZT_TEST_UNKNOWN_DYNAMIC_TAG); + check_size("unknown.summary-new-index", summary.first_new_tag_index, 3); + check_int("unknown.format", + kzt_guest_dynamic_diagnostics_format_summary(&summary, line, + sizeof(line)), 0); + check_str_contains("unknown.format-first", line, "first=unknown_tags"); +} + +static void test_needed_offsets_difference_is_reported(void) +{ + kzt_guest_dynamic_parse_result_t old_result = make_complete_result(); + kzt_guest_dynamic_parse_result_t new_result = make_complete_result(); + kzt_guest_dynamic_diagnostic_report_t report; + const kzt_guest_dynamic_diagnostic_field_t *field; + + new_result.view.needed_offsets[1] = 0x58; + check_int("needed.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + + field = require_field(&report, "needed_offsets"); + if (!field) { + return; + } + + check_int("needed.match", field->match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH); + check_size("needed.old-count", field->old_count, 2); + check_size("needed.new-count", field->new_count, 2); + check_u64("needed.old-first", field->old_value, 0x10); + check_u64("needed.new-first", field->new_value, 0x10); + check_size("needed.mismatch", report.mismatch_count, 1); + check_size("needed.difference", report.difference_count, 1); + check_size("needed.blocking", report.blocking_count, 0); +} + +static void test_error_status_is_blocking(void) +{ + kzt_guest_dynamic_parse_result_t old_result = make_complete_result(); + kzt_guest_dynamic_parse_result_t new_result = make_complete_result(); + kzt_guest_dynamic_diagnostic_report_t report; + kzt_guest_dynamic_diagnostic_summary_t summary; + char line[512]; + + old_result.status = KZT_GUEST_DYNAMIC_ERROR; + old_result.error = KZT_GUEST_DYNAMIC_ERROR_INVALID_ARGUMENT; + old_result.view.status = KZT_GUEST_DYNAMIC_ERROR; + new_result.status = KZT_GUEST_DYNAMIC_ERROR; + new_result.error = KZT_GUEST_DYNAMIC_ERROR_INVALID_ARGUMENT; + new_result.view.status = KZT_GUEST_DYNAMIC_ERROR; + + check_int("error.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + check_int("error.status-match", report.status_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED); + check_size("error.difference", report.difference_count, 0); + check_size("error.blocking", report.blocking_count, 2); + + check_int("error.summary", + kzt_guest_dynamic_diagnostics_summarize(&report, 0xabc100, 10, + &summary), + 0); + check_true("error.summary-blocking", summary.blocking); + check_int("error.summary-kind", summary.first_difference_kind, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_STATUS); + check_int("error.summary-match", summary.first_difference_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED); + check_int("error.format", + kzt_guest_dynamic_diagnostics_format_summary(&summary, line, + sizeof(line)), 0); + check_str_contains("error.format-blocking", line, "blocking=1"); + check_str_contains("error.format-first", line, "first=status"); +} + +static void test_summary_includes_identity_and_first_field(void) +{ + kzt_guest_dynamic_parse_result_t old_result = make_complete_result(); + kzt_guest_dynamic_parse_result_t new_result = make_complete_result(); + kzt_guest_dynamic_diagnostic_report_t report; + kzt_guest_dynamic_diagnostic_summary_t summary; + char line[512]; + + new_result.view.pltgot.value = 0x70000c0000; + check_int("summary.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + check_int("summary.create", + kzt_guest_dynamic_diagnostics_summarize(&report, 0xabcdef00, + 17, &summary), + 0); + + check_uintptr("summary.link-map", summary.link_map_addr, 0xabcdef00); + check_ulong("summary.generation", summary.generation, 17); + check_true("summary.not-matched", !summary.matched); + check_true("summary.not-blocking", !summary.blocking); + check_size("summary.difference", summary.difference_count, 1); + check_int("summary.kind", summary.first_difference_kind, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_FIELD); + check_true("summary.name", !strcmp(summary.first_difference_name, + "pltgot")); + check_int("summary.match", summary.first_difference_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH); + check_true("summary.old-present", summary.first_old_present); + check_true("summary.new-present", summary.first_new_present); + check_u64("summary.old-value", summary.first_old_value, 0x70000b0000); + check_u64("summary.new-value", summary.first_new_value, 0x70000c0000); + + check_int("summary.format", + kzt_guest_dynamic_diagnostics_format_summary(&summary, line, + sizeof(line)), 0); + check_str_contains("summary.format-object", line, + "link_map=0xabcdef00"); + check_str_contains("summary.format-generation", line, + "generation=17"); + check_str_contains("summary.format-first", line, "first=pltgot"); + check_str_contains("summary.format-differences", line, + "differences=1"); +} + +static int test_matches_filter(const char *name, int argc, char **argv) +{ + int i; + + for (i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "--filter") && i + 1 < argc) { + return strcmp(name, argv[i + 1]) == 0; + } + } + + return 1; +} + +int main(int argc, char **argv) +{ + if (test_matches_filter("identical_views_are_matched", argc, argv)) { + test_identical_views_are_matched(); + } + if (test_matches_filter("old_field_missing_from_new_is_reported", + argc, argv)) { + test_old_field_missing_from_new_is_reported(); + } + if (test_matches_filter("new_parser_incomplete_states_are_reported", + argc, argv)) { + test_new_parser_incomplete_states_are_reported(); + } + if (test_matches_filter("unknown_tag_difference_is_diagnostic_only", + argc, argv)) { + test_unknown_tag_difference_is_diagnostic_only(); + } + if (test_matches_filter("needed_offsets_difference_is_reported", + argc, argv)) { + test_needed_offsets_difference_is_reported(); + } + if (test_matches_filter("error_status_is_blocking", argc, argv)) { + test_error_status_is_blocking(); + } + if (test_matches_filter("summary_includes_identity_and_first_field", + argc, argv)) { + test_summary_includes_identity_and_first_field(); + } + + if (failures) { + fprintf(stderr, "kzt-guest-dynamic-diagnostics: %d failure(s)\n", + failures); + return 1; + } + + puts("kzt-guest-dynamic-diagnostics: selected contract tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_guest_dynamic_parser.c b/tests/unit/kzt/test_guest_dynamic_parser.c new file mode 100644 index 00000000000..816219ddb2c --- /dev/null +++ b/tests/unit/kzt/test_guest_dynamic_parser.c @@ -0,0 +1,450 @@ +#include +#include + +#include "target/i386/latx/include/kzt_guest_dynamic.h" + +#define TEST_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#define KZT_TEST_UNKNOWN_DYNAMIC_TAG 0x6000000d + +typedef struct fake_dynamic_memory { + uintptr_t base; + size_t size; + int read_calls; +} fake_dynamic_memory_t; + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_size(const char *name, size_t got, size_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lu expected %lu\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_u64(const char *name, uint64_t got, uint64_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%llx expected 0x%llx\n", name, + (unsigned long long)got, (unsigned long long)expected); + ++failures; +} + +static void check_i64(const char *name, int64_t got, int64_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lld expected %lld\n", name, + (long long)got, (long long)expected); + ++failures; +} + +static int fake_read_memory(uintptr_t guest_addr, void *dst, size_t size, + void *opaque) +{ + fake_dynamic_memory_t *memory = opaque; + + ++memory->read_calls; + if (guest_addr < memory->base || + size > memory->size || + guest_addr - memory->base > memory->size - size) { + return -1; + } + + memcpy(dst, (const void *)guest_addr, size); + return 0; +} + +static kzt_guest_link_map_reader_ops_t fake_ops(fake_dynamic_memory_t *memory) +{ + kzt_guest_link_map_reader_ops_t ops = { + .read_memory = fake_read_memory, + .opaque = memory, + }; + + return ops; +} + +static void check_field(const char *name, + const kzt_guest_dynamic_field_t *field, + uint64_t value, + kzt_guest_dynamic_address_semantics_t semantics) +{ + check_true(name, field->present); + check_u64(name, field->value, value); + check_int(name, field->address_semantics, semantics); +} + +static void test_complete_runtime_dynamic_view(void) +{ + Elf64_Dyn dynamic[] = { + { .d_tag = DT_NEEDED, .d_un.d_val = 0x10 }, + { .d_tag = DT_NEEDED, .d_un.d_val = 0x38 }, + { .d_tag = DT_SYMTAB, .d_un.d_ptr = 0x7000010000 }, + { .d_tag = DT_STRTAB, .d_un.d_ptr = 0x7000020000 }, + { .d_tag = DT_SYMENT, .d_un.d_val = sizeof(Elf64_Sym) }, + { .d_tag = DT_STRSZ, .d_un.d_val = 0x220 }, + { .d_tag = DT_HASH, .d_un.d_ptr = 0x7000030000 }, + { .d_tag = DT_GNU_HASH, .d_un.d_ptr = 0x7000040000 }, + { .d_tag = DT_VERSYM, .d_un.d_ptr = 0x7000050000 }, + { .d_tag = DT_VERNEED, .d_un.d_ptr = 0x7000060000 }, + { .d_tag = DT_VERNEEDNUM, .d_un.d_val = 2 }, + { .d_tag = DT_VERDEF, .d_un.d_ptr = 0x7000070000 }, + { .d_tag = DT_VERDEFNUM, .d_un.d_val = 1 }, + { .d_tag = DT_RELA, .d_un.d_ptr = 0x7000080000 }, + { .d_tag = DT_RELASZ, .d_un.d_val = 0x60 }, + { .d_tag = DT_RELAENT, .d_un.d_val = sizeof(Elf64_Rela) }, + { .d_tag = DT_REL, .d_un.d_ptr = 0x7000090000 }, + { .d_tag = DT_RELSZ, .d_un.d_val = 0x40 }, + { .d_tag = DT_RELENT, .d_un.d_val = sizeof(Elf64_Rel) }, + { .d_tag = DT_JMPREL, .d_un.d_ptr = 0x70000a0000 }, + { .d_tag = DT_PLTRELSZ, .d_un.d_val = 0x30 }, + { .d_tag = DT_PLTREL, .d_un.d_val = DT_RELA }, + { .d_tag = DT_PLTGOT, .d_un.d_ptr = 0x70000b0000 }, + { .d_tag = DT_NULL, .d_un.d_val = 0 }, + }; + fake_dynamic_memory_t memory = { + .base = (uintptr_t)dynamic, + .size = sizeof(dynamic), + }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_parse_result_t result = { 0 }; + + check_int("dynamic.complete.parse", + kzt_guest_dynamic_parse((uintptr_t)dynamic, 0x400000, + &ops, &result), + 0); + check_int("dynamic.complete.status", result.status, + KZT_GUEST_DYNAMIC_COMPLETE); + check_int("dynamic.complete.view-status", result.view.status, + KZT_GUEST_DYNAMIC_COMPLETE); + check_true("dynamic.complete.has-null", result.view.has_null); + check_size("dynamic.complete.entry-count", result.entry_count, + TEST_ARRAY_SIZE(dynamic) - 1); + check_size("dynamic.complete.view-entry-count", result.view.entry_count, + TEST_ARRAY_SIZE(dynamic) - 1); + check_int("dynamic.complete.reader-calls", memory.read_calls, + TEST_ARRAY_SIZE(dynamic)); + + check_field("dynamic.symtab", &result.view.symtab, 0x7000010000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.strtab", &result.view.strtab, 0x7000020000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.syment", &result.view.syment, sizeof(Elf64_Sym), + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.strsz", &result.view.strsz, 0x220, + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.hash", &result.view.hash, 0x7000030000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.gnu-hash", &result.view.gnu_hash, 0x7000040000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.versym", &result.view.versym, 0x7000050000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.verneed", &result.view.verneed, 0x7000060000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.verneednum", &result.view.verneednum, 2, + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.verdef", &result.view.verdef, 0x7000070000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.verdefnum", &result.view.verdefnum, 1, + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.rela", &result.view.rela, 0x7000080000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.relasz", &result.view.relasz, 0x60, + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.relaent", &result.view.relaent, sizeof(Elf64_Rela), + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.rel", &result.view.rel, 0x7000090000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.relsz", &result.view.relsz, 0x40, + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.relent", &result.view.relent, sizeof(Elf64_Rel), + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.jmprel", &result.view.jmprel, 0x70000a0000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.pltrelsz", &result.view.pltrelsz, 0x30, + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.pltrel", &result.view.pltrel, DT_RELA, + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.pltgot", &result.view.pltgot, 0x70000b0000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + + check_size("dynamic.needed.count", result.view.needed_count, 2); + check_int("dynamic.needed.semantics", + result.view.needed_address_semantics, + KZT_GUEST_DYNAMIC_STRING_TABLE_OFFSET); + check_u64("dynamic.needed.0", result.view.needed_offsets[0], 0x10); + check_u64("dynamic.needed.1", result.view.needed_offsets[1], 0x38); + + kzt_guest_dynamic_parse_result_clear(&result); +} + +static void test_dynamic_address_semantics(void) +{ + Elf64_Dyn dynamic[] = { + { .d_tag = DT_SYMTAB, .d_un.d_ptr = 0x5000010000 }, + { .d_tag = DT_STRTAB, .d_un.d_ptr = 0x5000020000 }, + { .d_tag = DT_JMPREL, .d_un.d_ptr = 0x5000030000 }, + { .d_tag = DT_PLTGOT, .d_un.d_ptr = 0x5000040000 }, + { .d_tag = DT_NEEDED, .d_un.d_val = 0x84 }, + { .d_tag = DT_NULL, .d_un.d_val = 0 }, + }; + fake_dynamic_memory_t memory = { + .base = (uintptr_t)dynamic, + .size = sizeof(dynamic), + }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_parse_result_t result = { 0 }; + + check_int("semantics.parse", + kzt_guest_dynamic_parse((uintptr_t)dynamic, 0x100000, + &ops, &result), + 0); + check_int("semantics.status", result.status, + KZT_GUEST_DYNAMIC_COMPLETE); + check_field("semantics.symtab", &result.view.symtab, 0x5000010000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("semantics.strtab", &result.view.strtab, 0x5000020000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("semantics.jmprel", &result.view.jmprel, 0x5000030000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("semantics.pltgot", &result.view.pltgot, 0x5000040000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_size("semantics.needed.count", result.view.needed_count, 1); + check_int("semantics.needed.semantics", + result.view.needed_address_semantics, + KZT_GUEST_DYNAMIC_STRING_TABLE_OFFSET); + check_u64("semantics.needed.offset", result.view.needed_offsets[0], + 0x84); + check_u64("semantics.load-bias-preserved", result.view.load_bias, + 0x100000); + + kzt_guest_dynamic_parse_result_clear(&result); +} + +static void test_read_failure_reports_parser_state(void) +{ + Elf64_Dyn dynamic[] = { + { .d_tag = DT_SYMTAB, .d_un.d_ptr = 0x5000010000 }, + }; + fake_dynamic_memory_t memory = { + .base = (uintptr_t)dynamic, + .size = sizeof(dynamic), + }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_parse_result_t result = { 0 }; + uintptr_t expected_error_addr = (uintptr_t)&dynamic[1]; + + check_int("read-failure.parse", + kzt_guest_dynamic_parse((uintptr_t)dynamic, 0x100000, + &ops, &result), + 0); + check_int("read-failure.status", result.status, + KZT_GUEST_DYNAMIC_READ_ERROR); + check_int("read-failure.view-status", result.view.status, + KZT_GUEST_DYNAMIC_READ_ERROR); + check_int("read-failure.error", result.error, + KZT_GUEST_DYNAMIC_ERROR_READ_FAILURE); + check_size("read-failure.entry-count", result.entry_count, 1); + check_size("read-failure.view-entry-count", result.view.entry_count, 1); + check_u64("read-failure.addr", result.read_error_addr, + expected_error_addr); + check_size("read-failure.scan-limit", result.scan_limit, + KZT_GUEST_DYNAMIC_SCAN_LIMIT); + check_size("read-failure.view-scan-limit", result.view.scan_limit, + KZT_GUEST_DYNAMIC_SCAN_LIMIT); + check_int("read-failure.reader-calls", memory.read_calls, 2); + check_true("read-failure.no-null", !result.view.has_null); + + kzt_guest_dynamic_parse_result_clear(&result); +} + +static void test_missing_null_stops_at_scan_limit(void) +{ + Elf64_Dyn dynamic[KZT_GUEST_DYNAMIC_SCAN_LIMIT]; + fake_dynamic_memory_t memory = { + .base = (uintptr_t)dynamic, + .size = sizeof(dynamic), + }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_parse_result_t result = { 0 }; + size_t i; + + for (i = 0; i < TEST_ARRAY_SIZE(dynamic); ++i) { + dynamic[i].d_tag = DT_SYMENT; + dynamic[i].d_un.d_val = sizeof(Elf64_Sym); + } + + check_int("scan-limit.parse", + kzt_guest_dynamic_parse((uintptr_t)dynamic, 0x100000, + &ops, &result), + 0); + check_int("scan-limit.status", result.status, + KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL); + check_int("scan-limit.view-status", result.view.status, + KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL); + check_int("scan-limit.error", result.error, + KZT_GUEST_DYNAMIC_ERROR_SCAN_LIMIT_EXCEEDED); + check_size("scan-limit.entry-count", result.entry_count, + KZT_GUEST_DYNAMIC_SCAN_LIMIT); + check_size("scan-limit.view-entry-count", result.view.entry_count, + KZT_GUEST_DYNAMIC_SCAN_LIMIT); + check_size("scan-limit.scan-limit", result.scan_limit, + KZT_GUEST_DYNAMIC_SCAN_LIMIT); + check_int("scan-limit.reader-calls", memory.read_calls, + KZT_GUEST_DYNAMIC_SCAN_LIMIT); + check_true("scan-limit.no-null", !result.view.has_null); + + kzt_guest_dynamic_parse_result_clear(&result); +} + +static void test_unknown_tag_is_diagnostic_only(void) +{ + Elf64_Dyn dynamic[] = { + { .d_tag = KZT_TEST_UNKNOWN_DYNAMIC_TAG, .d_un.d_val = 0x44 }, + { .d_tag = DT_STRTAB, .d_un.d_ptr = 0x5000020000 }, + { .d_tag = DT_NULL, .d_un.d_val = 0 }, + }; + fake_dynamic_memory_t memory = { + .base = (uintptr_t)dynamic, + .size = sizeof(dynamic), + }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_parse_result_t result = { 0 }; + + check_int("unknown-tag.parse", + kzt_guest_dynamic_parse((uintptr_t)dynamic, 0x100000, + &ops, &result), + 0); + check_int("unknown-tag.status", result.status, + KZT_GUEST_DYNAMIC_COMPLETE); + check_int("unknown-tag.error", result.error, + KZT_GUEST_DYNAMIC_ERROR_NONE); + check_size("unknown-tag.count", result.unknown_tag_count, 1); + check_size("unknown-tag.view-count", result.view.unknown_tag_count, 1); + check_i64("unknown-tag.first", result.first_unknown_tag, + KZT_TEST_UNKNOWN_DYNAMIC_TAG); + check_i64("unknown-tag.view-first", result.view.first_unknown_tag, + KZT_TEST_UNKNOWN_DYNAMIC_TAG); + check_size("unknown-tag.index", result.first_unknown_tag_index, 0); + check_size("unknown-tag.view-index", result.view.first_unknown_tag_index, + 0); + check_field("unknown-tag.strtab", &result.view.strtab, 0x5000020000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_true("unknown-tag.has-null", result.view.has_null); + + kzt_guest_dynamic_parse_result_clear(&result); +} + +static void test_too_many_needed_reports_resource_limit(void) +{ + Elf64_Dyn dynamic[KZT_GUEST_DYNAMIC_NEEDED_LIMIT + 2]; + fake_dynamic_memory_t memory = { + .base = (uintptr_t)dynamic, + .size = sizeof(dynamic), + }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_parse_result_t result = { 0 }; + size_t i; + + for (i = 0; i < KZT_GUEST_DYNAMIC_NEEDED_LIMIT + 1; ++i) { + dynamic[i].d_tag = DT_NEEDED; + dynamic[i].d_un.d_val = i * 0x10; + } + dynamic[KZT_GUEST_DYNAMIC_NEEDED_LIMIT + 1].d_tag = DT_NULL; + dynamic[KZT_GUEST_DYNAMIC_NEEDED_LIMIT + 1].d_un.d_val = 0; + + check_int("needed-limit.parse", + kzt_guest_dynamic_parse((uintptr_t)dynamic, 0x100000, + &ops, &result), + 0); + check_int("needed-limit.status", result.status, + KZT_GUEST_DYNAMIC_ERROR); + check_int("needed-limit.view-status", result.view.status, + KZT_GUEST_DYNAMIC_ERROR); + check_int("needed-limit.error", result.error, + KZT_GUEST_DYNAMIC_ERROR_TOO_MANY_NEEDED); + check_size("needed-limit.entry-count", result.entry_count, + KZT_GUEST_DYNAMIC_NEEDED_LIMIT); + check_size("needed-limit.view-entry-count", result.view.entry_count, + KZT_GUEST_DYNAMIC_NEEDED_LIMIT); + check_size("needed-limit.needed-count", result.view.needed_count, + KZT_GUEST_DYNAMIC_NEEDED_LIMIT); + check_true("needed-limit.no-null", !result.view.has_null); + + kzt_guest_dynamic_parse_result_clear(&result); +} + +static int test_matches_filter(const char *name, int argc, char **argv) +{ + int i; + + for (i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "--filter") && i + 1 < argc) { + return strcmp(name, argv[i + 1]) == 0; + } + } + + return 1; +} + +int main(int argc, char **argv) +{ + if (test_matches_filter("complete_runtime_dynamic_view", argc, argv)) { + test_complete_runtime_dynamic_view(); + } + if (test_matches_filter("dynamic_address_semantics", argc, argv)) { + test_dynamic_address_semantics(); + } + if (test_matches_filter("read_failure_reports_parser_state", + argc, argv)) { + test_read_failure_reports_parser_state(); + } + if (test_matches_filter("missing_null_stops_at_scan_limit", argc, argv)) { + test_missing_null_stops_at_scan_limit(); + } + if (test_matches_filter("unknown_tag_is_diagnostic_only", argc, argv)) { + test_unknown_tag_is_diagnostic_only(); + } + if (test_matches_filter("too_many_needed_reports_resource_limit", + argc, argv)) { + test_too_many_needed_reports_resource_limit(); + } + + if (failures) { + fprintf(stderr, "kzt-guest-dynamic-parser: %d failure(s)\n", + failures); + return 1; + } + + puts("kzt-guest-dynamic-parser: selected contract tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_guest_dynamic_snapshot.c b/tests/unit/kzt/test_guest_dynamic_snapshot.c new file mode 100644 index 00000000000..b9b723707b1 --- /dev/null +++ b/tests/unit/kzt/test_guest_dynamic_snapshot.c @@ -0,0 +1,500 @@ +#include +#include + +#include "target/i386/latx/include/kzt_guest_registry.h" + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, expected); + ++failures; +} + +static void check_uintptr(const char *name, uintptr_t got, uintptr_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_u64(const char *name, uint64_t got, uint64_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%llx expected 0x%llx\n", name, + (unsigned long long)got, (unsigned long long)expected); + ++failures; +} + +static kzt_guest_object_observation_t make_observation(uintptr_t link_map_addr) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map_addr, + .load_bias = { 0x100000, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { 0x101000, KZT_GUEST_FIELD_OK }, + .map_start = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .map_end = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .namespace_id = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .path = { "/guest/libfoo.so", KZT_GUEST_FIELD_OK }, + .soname = { NULL, KZT_GUEST_FIELD_NOT_PARSED }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static kzt_guest_dynamic_field_t make_field( + uint64_t value, + kzt_guest_dynamic_address_semantics_t semantics) +{ + return (kzt_guest_dynamic_field_t) { + .present = 1, + .value = value, + .address_semantics = semantics, + }; +} + +static kzt_guest_dynamic_view_t make_dynamic_view(uintptr_t dynamic_addr, + uintptr_t load_bias, + uint64_t symtab) +{ + kzt_guest_dynamic_view_t view = { + .dynamic_addr = dynamic_addr, + .load_bias = load_bias, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .entry_count = 8, + .has_null = 1, + .scan_limit = KZT_GUEST_DYNAMIC_SCAN_LIMIT, + .unknown_tag_count = 1, + .first_unknown_tag = 0x6000000d, + .first_unknown_tag_index = 3, + .symtab = make_field(symtab, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .strtab = make_field(symtab + 0x1000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .strsz = make_field(0x240, KZT_GUEST_DYNAMIC_SCALAR), + .gnu_hash = make_field(symtab + 0x2000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .versym = make_field(symtab + 0x3000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .jmprel = make_field(symtab + 0x4000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .pltrelsz = make_field(0x30, KZT_GUEST_DYNAMIC_SCALAR), + .pltgot = make_field(symtab + 0x5000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .needed_offsets = { 0x10, 0x38 }, + .needed_count = 2, + .needed_address_semantics = KZT_GUEST_DYNAMIC_STRING_TABLE_OFFSET, + }; + + return view; +} + +static int dynamic_field_equal(const kzt_guest_dynamic_field_t *left, + const kzt_guest_dynamic_field_t *right) +{ + return left->present == right->present && + left->value == right->value && + left->address_semantics == right->address_semantics; +} + +static void assert_dynamic_view_equal(const char *name, + const kzt_guest_dynamic_view_t *got, + const kzt_guest_dynamic_view_t *expected) +{ + char field_name[128]; + size_t i; + + check_uintptr(name, got->dynamic_addr, expected->dynamic_addr); + check_uintptr(name, got->load_bias, expected->load_bias); + check_int(name, got->status, expected->status); + check_ulong(name, got->entry_count, expected->entry_count); + check_int(name, got->has_null, expected->has_null); + check_ulong("dynamic.scan_limit", got->scan_limit, expected->scan_limit); + check_ulong("dynamic.unknown_tag_count", got->unknown_tag_count, + expected->unknown_tag_count); + check_ulong("dynamic.first_unknown_tag", + (unsigned long)got->first_unknown_tag, + (unsigned long)expected->first_unknown_tag); + check_ulong("dynamic.first_unknown_tag_index", + got->first_unknown_tag_index, + expected->first_unknown_tag_index); + check_true("dynamic.symtab", dynamic_field_equal(&got->symtab, + &expected->symtab)); + check_true("dynamic.strtab", dynamic_field_equal(&got->strtab, + &expected->strtab)); + check_true("dynamic.strsz", dynamic_field_equal(&got->strsz, + &expected->strsz)); + check_true("dynamic.gnu_hash", dynamic_field_equal(&got->gnu_hash, + &expected->gnu_hash)); + check_true("dynamic.versym", dynamic_field_equal(&got->versym, + &expected->versym)); + check_true("dynamic.jmprel", dynamic_field_equal(&got->jmprel, + &expected->jmprel)); + check_true("dynamic.pltrelsz", dynamic_field_equal(&got->pltrelsz, + &expected->pltrelsz)); + check_true("dynamic.pltgot", dynamic_field_equal(&got->pltgot, + &expected->pltgot)); + check_ulong("dynamic.needed_count", got->needed_count, + expected->needed_count); + check_int("dynamic.needed_semantics", got->needed_address_semantics, + expected->needed_address_semantics); + for (i = 0; i < expected->needed_count; ++i) { + snprintf(field_name, sizeof(field_name), "%s.needed[%lu]", name, + (unsigned long)i); + check_u64(field_name, got->needed_offsets[i], + expected->needed_offsets[i]); + } +} + +static kzt_guest_object_snapshot_t *find_snapshot( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr) +{ + kzt_guest_object_snapshot_t *snapshot = NULL; + + check_int("find_by_link_map", kzt_guest_registry_find_by_link_map( + registry, link_map_addr, &snapshot), 0); + check_true("find_by_link_map.snapshot", snapshot != NULL); + return snapshot; +} + +static void test_commit_and_query_are_per_object(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t first = make_observation(0x1000); + kzt_guest_object_observation_t second = make_observation(0x2000); + kzt_guest_dynamic_view_t first_view = + make_dynamic_view(0x101000, 0x100000, 0x7000010000); + kzt_guest_dynamic_view_t queried = { 0 }; + kzt_guest_field_status_t queried_status = KZT_GUEST_FIELD_UNKNOWN; + unsigned long generation = 0; + kzt_guest_object_snapshot_t *snapshot; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + second.dynamic_addr.value = 0x201000; + check_int("observe.first", kzt_guest_registry_observe(registry, &first), + KZT_GUEST_REGISTRY_ADDED); + check_int("observe.second", kzt_guest_registry_observe(registry, &second), + KZT_GUEST_REGISTRY_ADDED); + + check_int("commit.first", + kzt_guest_registry_commit_dynamic_view(registry, 0x1000, 1, + &first_view), + KZT_GUEST_REGISTRY_UPDATED); + + snapshot = find_snapshot(registry, 0x1000); + check_int("snapshot.first.status", snapshot->dynamic_view_status, + KZT_GUEST_FIELD_OK); + check_int("snapshot.first.state", snapshot->state, + KZT_GUEST_OBJECT_PARSED); + check_ulong("snapshot.first.generation", snapshot->generation, 1); + assert_dynamic_view_equal("snapshot.first.view", + &snapshot->dynamic_view, &first_view); + kzt_guest_object_snapshot_free(snapshot); + + snapshot = find_snapshot(registry, 0x2000); + check_int("snapshot.second.status", snapshot->dynamic_view_status, + KZT_GUEST_FIELD_NOT_PARSED); + check_int("snapshot.second.state", snapshot->state, + KZT_GUEST_OBJECT_DISCOVERED); + check_ulong("snapshot.second.generation", snapshot->generation, 2); + check_uintptr("snapshot.second.dynamic-view-zero", + snapshot->dynamic_view.dynamic_addr, 0); + kzt_guest_object_snapshot_free(snapshot); + + check_int("find.dynamic-view", + kzt_guest_registry_find_dynamic_view(registry, 0x1000, + &queried, + &queried_status, + &generation), + 0); + check_int("queried.status", queried_status, KZT_GUEST_FIELD_OK); + check_ulong("queried.generation", generation, 1); + assert_dynamic_view_equal("queried.view", &queried, &first_view); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_repeated_commit_and_replacement_semantics(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x3000); + kzt_guest_dynamic_view_t first_view = + make_dynamic_view(0x301000, 0x300000, 0x7100010000); + kzt_guest_dynamic_view_t replacement = + make_dynamic_view(0x301000, 0x300000, 0x7200010000); + kzt_guest_object_snapshot_t *snapshot; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + check_int("observe.object", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + check_int("commit.first", + kzt_guest_registry_commit_dynamic_view(registry, 0x3000, 1, + &first_view), + KZT_GUEST_REGISTRY_UPDATED); + check_int("commit.same", + kzt_guest_registry_commit_dynamic_view(registry, 0x3000, 1, + &first_view), + KZT_GUEST_REGISTRY_UNCHANGED); + + replacement.needed_offsets[1] = 0x58; + replacement.entry_count = 9; + replacement.unknown_tag_count = 2; + replacement.first_unknown_tag = 0x6000000e; + replacement.first_unknown_tag_index = 4; + check_int("commit.replacement", + kzt_guest_registry_commit_dynamic_view(registry, 0x3000, 1, + &replacement), + KZT_GUEST_REGISTRY_UPDATED); + + snapshot = find_snapshot(registry, 0x3000); + check_ulong("snapshot.generation-stable", snapshot->generation, 1); + check_int("snapshot.status", snapshot->dynamic_view_status, + KZT_GUEST_FIELD_OK); + check_int("snapshot.state", snapshot->state, KZT_GUEST_OBJECT_PARSED); + assert_dynamic_view_equal("snapshot.replacement", + &snapshot->dynamic_view, &replacement); + kzt_guest_object_snapshot_free(snapshot); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_snapshots_survive_replacement_and_registry_destroy(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x4000); + kzt_guest_dynamic_view_t first_view = + make_dynamic_view(0x401000, 0x400000, 0x7300010000); + kzt_guest_dynamic_view_t replacement = + make_dynamic_view(0x401000, 0x400000, 0x7400010000); + kzt_guest_object_snapshot_t *before_replace; + kzt_guest_object_snapshot_t *after_replace; + kzt_guest_registry_dump_t dump = { 0 }; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + check_int("observe.object", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + check_int("commit.first", + kzt_guest_registry_commit_dynamic_view(registry, 0x4000, 1, + &first_view), + KZT_GUEST_REGISTRY_UPDATED); + before_replace = find_snapshot(registry, 0x4000); + + check_int("commit.replacement", + kzt_guest_registry_commit_dynamic_view(registry, 0x4000, 1, + &replacement), + KZT_GUEST_REGISTRY_UPDATED); + after_replace = find_snapshot(registry, 0x4000); + + check_int("dump.snapshot", + kzt_guest_registry_dump_snapshot(registry, &dump), 0); + check_ulong("dump.count", dump.count, 1); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); + + assert_dynamic_view_equal("before_replace.still-first", + &before_replace->dynamic_view, &first_view); + assert_dynamic_view_equal("after_replace.still-replacement", + &after_replace->dynamic_view, &replacement); + assert_dynamic_view_equal("dump.still-replacement", + &dump.objects[0].dynamic_view, &replacement); + + kzt_guest_object_snapshot_free(before_replace); + kzt_guest_object_snapshot_free(after_replace); + kzt_guest_registry_dump_free(&dump); +} + +static void test_missing_and_destroyed_registry_are_rejected(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_dynamic_view_t view = + make_dynamic_view(0x501000, 0x500000, 0x7500010000); + kzt_guest_dynamic_view_t queried = { + .dynamic_addr = 0xdeadbeef, + }; + kzt_guest_field_status_t status = KZT_GUEST_FIELD_OK; + unsigned long generation = 99; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + check_int("commit.missing", + kzt_guest_registry_commit_dynamic_view(registry, 0x5000, 1, + &view), + KZT_GUEST_REGISTRY_ERROR); + check_int("find.missing", + kzt_guest_registry_find_dynamic_view(registry, 0x5000, + &queried, &status, + &generation), + -1); + check_uintptr("find.missing.clears-view", queried.dynamic_addr, 0); + check_int("find.missing.status", status, KZT_GUEST_FIELD_NOT_PARSED); + check_ulong("find.missing.generation", generation, 0); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); + check_int("commit.destroyed", + kzt_guest_registry_commit_dynamic_view(registry, 0x5000, 1, + &view), + KZT_GUEST_REGISTRY_DISABLED); + check_int("find.destroyed", + kzt_guest_registry_find_dynamic_view(registry, 0x5000, + &queried, &status, + &generation), + -1); +} + +static void test_stale_generation_cannot_revive_or_overwrite(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x6000); + kzt_guest_dynamic_view_t stale = + make_dynamic_view(0x601000, 0x600000, 0x7600010000); + kzt_guest_dynamic_view_t fresh = + make_dynamic_view(0x601000, 0x600000, 0x7700010000); + kzt_guest_dynamic_view_t queried = { 0 }; + kzt_guest_field_status_t status; + unsigned long generation; + + check_int("stale.observe.a", kzt_guest_registry_observe( + registry, &observation), KZT_GUEST_REGISTRY_ADDED); + check_int("stale.retire.a", kzt_guest_registry_retire( + registry, observation.link_map_addr, 1), 0); + check_int("stale.dead-commit", kzt_guest_registry_commit_dynamic_view( + registry, observation.link_map_addr, 1, &stale), + KZT_GUEST_REGISTRY_ERROR); + check_int("stale.observe.b", kzt_guest_registry_observe( + registry, &observation), KZT_GUEST_REGISTRY_ADDED); + check_int("stale.reused-commit", kzt_guest_registry_commit_dynamic_view( + registry, observation.link_map_addr, 1, &stale), + KZT_GUEST_REGISTRY_ERROR); + check_int("stale.fresh-commit", kzt_guest_registry_commit_dynamic_view( + registry, observation.link_map_addr, 2, &fresh), + KZT_GUEST_REGISTRY_UPDATED); + check_int("stale.query", kzt_guest_registry_find_dynamic_view( + registry, observation.link_map_addr, &queried, &status, + &generation), 0); + check_ulong("stale.generation", generation, 2); + assert_dynamic_view_equal("stale.fresh-view", &queried, &fresh); + kzt_guest_registry_destroy(®istry); +} + +static void test_incomplete_dynamic_view_preserves_complete_view(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x7000); + kzt_guest_dynamic_view_t complete = + make_dynamic_view(0x701000, 0x700000, 0x7800010000); + kzt_guest_dynamic_view_t incomplete = complete; + kzt_guest_dynamic_view_t queried = { 0 }; + kzt_guest_field_status_t status = KZT_GUEST_FIELD_UNKNOWN; + unsigned long generation = 0; + + check_true("preserve-complete.init", registry != NULL); + if (!registry) { + return; + } + + check_int("preserve-complete.observe", kzt_guest_registry_observe( + registry, &observation), KZT_GUEST_REGISTRY_ADDED); + check_int("preserve-complete.commit", kzt_guest_registry_commit_dynamic_view( + registry, observation.link_map_addr, 1, &complete), + KZT_GUEST_REGISTRY_UPDATED); + + incomplete.status = KZT_GUEST_DYNAMIC_READ_ERROR; + incomplete.has_null = 0; + incomplete.entry_count = 1; + check_int("preserve-complete.read-error", + kzt_guest_registry_commit_dynamic_view( + registry, observation.link_map_addr, 1, &incomplete), + KZT_GUEST_REGISTRY_UNCHANGED); + incomplete.status = KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL; + incomplete.entry_count = KZT_GUEST_DYNAMIC_SCAN_LIMIT; + check_int("preserve-complete.truncated", + kzt_guest_registry_commit_dynamic_view( + registry, observation.link_map_addr, 1, &incomplete), + KZT_GUEST_REGISTRY_UNCHANGED); + incomplete.status = KZT_GUEST_DYNAMIC_ERROR; + incomplete.entry_count = 2; + check_int("preserve-complete.error", + kzt_guest_registry_commit_dynamic_view( + registry, observation.link_map_addr, 1, &incomplete), + KZT_GUEST_REGISTRY_UNCHANGED); + + check_int("preserve-complete.find", kzt_guest_registry_find_dynamic_view( + registry, observation.link_map_addr, &queried, &status, + &generation), 0); + check_int("preserve-complete.status", status, KZT_GUEST_FIELD_OK); + check_ulong("preserve-complete.generation", generation, 1); + assert_dynamic_view_equal("preserve-complete.view", &queried, &complete); + + kzt_guest_registry_destroy(®istry); +} + +int main(void) +{ + test_commit_and_query_are_per_object(); + test_repeated_commit_and_replacement_semantics(); + test_snapshots_survive_replacement_and_registry_destroy(); + test_missing_and_destroyed_registry_are_rejected(); + test_stale_generation_cannot_revive_or_overwrite(); + test_incomplete_dynamic_view_preserves_complete_view(); + + if (failures) { + fprintf(stderr, "kzt-guest-dynamic-snapshot: %d failure(s)\n", + failures); + return 1; + } + + puts("kzt-guest-dynamic-snapshot: all contract tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_guest_library_binding.c b/tests/unit/kzt/test_guest_library_binding.c new file mode 100644 index 00000000000..bc787480b1b --- /dev/null +++ b/tests/unit/kzt/test_guest_library_binding.c @@ -0,0 +1,1692 @@ +#include +#include +#include +#include +#include + +#include "kzt_guest_library_binding.h" +#include "kzt_guest_registry.h" + +typedef struct fake_library { int id; } fake_library_t; +static int failures; + +#define CHECK(name, expr) do { if (!(expr)) { \ + fprintf(stderr, "FAIL %s\n", name); ++failures; } } while (0) + +static kzt_guest_library_binding_key_t key(uintptr_t map, + unsigned long generation) +{ + return (kzt_guest_library_binding_key_t){ + .link_map_addr = map, + .generation = generation, + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; +} + +static kzt_guest_object_observation_t observation(uintptr_t map) +{ + return (kzt_guest_object_observation_t){ + .link_map_addr = map, + .load_bias = { .value = 0x400000, .status = KZT_GUEST_FIELD_OK }, + .dynamic_addr = { .status = KZT_GUEST_FIELD_UNKNOWN }, + .map_start = { .status = KZT_GUEST_FIELD_UNKNOWN }, + .map_end = { .status = KZT_GUEST_FIELD_UNKNOWN }, + .namespace_id = { .value = 0, .status = KZT_GUEST_FIELD_OK }, + .path = { .status = KZT_GUEST_FIELD_UNKNOWN }, + .soname = { .status = KZT_GUEST_FIELD_UNKNOWN }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static unsigned long registry_generation(kzt_guest_registry_t *registry, + uintptr_t map) +{ + kzt_guest_object_snapshot_t *snapshot = NULL; + unsigned long generation = 0; + CHECK("registry snapshot", kzt_guest_registry_find_by_link_map( + registry, map, &snapshot) == 0 && snapshot != NULL); + if (snapshot) generation = snapshot->generation; + kzt_guest_object_snapshot_free(snapshot); + return generation; +} + +static void test_init_failure_is_fail_open(void) +{ + fake_library_t lib = { 1 }; + kzt_guest_library_binding_key_t k = key(0x1000, 1); + kzt_guest_library_binding_test_set_alloc_failure_after(0); + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + CHECK("init failure", bindings == NULL); + CHECK("disabled bind", kzt_guest_library_bind(bindings, &k, + (library_t *)&lib, KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_DISABLED); + kzt_guest_library_binding_test_set_alloc_failure_after(-1); +} + +static void test_both_arrival_orders_and_retry(void) +{ + fake_library_t wrapped = { 2 }, emulated = { 3 }; + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + kzt_guest_library_binding_key_t a = key(0x2000, 7); + kzt_guest_library_binding_key_t b = key(0x3000, 8); + kzt_guest_library_handle_t handle; + + CHECK("track wrapped", kzt_guest_library_track( + bindings, (library_t *)&wrapped) == 0); + CHECK("track emulated", kzt_guest_library_track( + bindings, (library_t *)&emulated) == 0); + + CHECK("pair first pending", kzt_guest_library_note_exact_pair( + bindings, a.link_map_addr, (library_t *)&wrapped, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); + kzt_guest_library_binding_test_set_alloc_failure_after(0); + CHECK("transient observation failure", kzt_guest_library_note_observation( + bindings, &a) == KZT_GUEST_LIBRARY_BINDING_ERROR); + kzt_guest_library_binding_test_set_alloc_failure_after(-1); + CHECK("observation retry binds", kzt_guest_library_note_observation( + bindings, &a) == KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("wrapped exact lookup", kzt_guest_library_lookup( + bindings, &a, &handle) == 0 && + handle.library == (library_t *)&wrapped && + handle.object_type == KZT_GUEST_LIBRARY_OBJECT_WRAPPED); + kzt_guest_library_handle_release(&handle); + + CHECK("observation first", kzt_guest_library_note_observation( + bindings, &b) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + CHECK("emulated pair binds", kzt_guest_library_note_exact_pair( + bindings, b.link_map_addr, (library_t *)&emulated, + KZT_GUEST_LIBRARY_OBJECT_EMULATED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("emulated exact lookup", kzt_guest_library_lookup( + bindings, &b, &handle) == 0 && + handle.library == (library_t *)&emulated && + handle.object_type == KZT_GUEST_LIBRARY_OBJECT_EMULATED); + kzt_guest_library_handle_release(&handle); + + b.namespace_id = 4; + b.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_EXPLICIT; + CHECK("non-main namespace fail open", kzt_guest_library_note_observation( + bindings, &b) == KZT_GUEST_LIBRARY_BINDING_ERROR); + CHECK("main executable type fail open", kzt_guest_library_note_exact_pair( + bindings, 0x4000, (library_t *)&emulated, + KZT_GUEST_LIBRARY_OBJECT_MAIN) == KZT_GUEST_LIBRARY_BINDING_ERROR); + + kzt_guest_library_bindings_destroy(&bindings); +} + +static void test_forced_growth_with_held_handle(void) +{ + fake_library_t libs[24]; + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + kzt_guest_library_handle_t held; + kzt_guest_library_binding_key_t first = key(0x5000, 1); + + for (size_t i = 0; i < 24; ++i) { + libs[i].id = (int)i; + CHECK("growth track", kzt_guest_library_track( + bindings, (library_t *)&libs[i]) == 0); + } + CHECK("growth first bind", kzt_guest_library_bind( + bindings, &first, (library_t *)&libs[0], + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("growth hold", kzt_guest_library_lookup( + bindings, &first, &held) == 0); + for (size_t i = 1; i < 24; ++i) { + kzt_guest_library_binding_key_t next = + key(0x5000 + i * 0x100, i + 1); + CHECK("growth bind", kzt_guest_library_bind( + bindings, &next, (library_t *)&libs[i], + KZT_GUEST_LIBRARY_OBJECT_EMULATED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + } + CHECK("held survives realloc", held.library == (library_t *)&libs[0]); + kzt_guest_library_handle_release(&held); + kzt_guest_library_bindings_destroy(&bindings); +} + +static void test_pending_cancel_and_address_reuse(void) +{ + fake_library_t old = { 4 }, replacement = { 5 }; + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + kzt_guest_library_binding_key_t old_key = key(0x9000, 11); + kzt_guest_library_binding_key_t new_key = key(0x9000, 12); + kzt_guest_library_handle_t handle; + + CHECK("cancel track", kzt_guest_library_track( + bindings, (library_t *)&old) == 0); + CHECK("cancel pending", kzt_guest_library_note_exact_pair( + bindings, old_key.link_map_addr, (library_t *)&old, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); + kzt_guest_library_unbind(bindings, NULL, (library_t *)&old, + old_key.link_map_addr); + CHECK("late attach rejected", kzt_guest_library_note_exact_pair( + bindings, old_key.link_map_addr, (library_t *)&old, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ERROR); + CHECK("canceled observation cannot bind", kzt_guest_library_note_observation( + bindings, &old_key) == KZT_GUEST_LIBRARY_BINDING_CANCELLED); + CHECK("canceled lookup", kzt_guest_library_lookup( + bindings, &old_key, &handle) != 0); + + CHECK("reuse track", kzt_guest_library_track( + bindings, (library_t *)&replacement) == 0); + CHECK("reuse observation", kzt_guest_library_note_observation( + bindings, &new_key) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + CHECK("reuse exact pair", kzt_guest_library_note_exact_pair( + bindings, new_key.link_map_addr, (library_t *)&replacement, + KZT_GUEST_LIBRARY_OBJECT_EMULATED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("old generation absent", kzt_guest_library_lookup( + bindings, &old_key, &handle) != 0); + CHECK("new generation present", kzt_guest_library_lookup( + bindings, &new_key, &handle) == 0 && + handle.library == (library_t *)&replacement); + kzt_guest_library_handle_release(&handle); + kzt_guest_library_bindings_destroy(&bindings); +} + +static void test_unclaimed_observation_address_reuse_pair_first(void) +{ + fake_library_t old = { 40 }, replacement = { 41 }; + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + kzt_guest_library_binding_key_t stale = key(0x9800, 30); + kzt_guest_library_binding_key_t fresh = key(0x9800, 31); + kzt_guest_library_handle_t handle; + + CHECK("unclaimed old track", kzt_guest_library_track( + bindings, (library_t *)&old) == 0); + CHECK("unclaimed old observation", kzt_guest_library_note_observation( + bindings, &stale) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + /* There is deliberately no exact pair/entry for the old observation. */ + kzt_guest_library_inactivate(bindings, NULL, (library_t *)&old, + stale.link_map_addr); + + CHECK("unclaimed replacement track", kzt_guest_library_track( + bindings, (library_t *)&replacement) == 0); + CHECK("replacement pair waits for fresh observation", + kzt_guest_library_note_exact_pair( + bindings, fresh.link_map_addr, (library_t *)&replacement, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); + CHECK("pair first remains fail open", kzt_guest_library_lookup( + bindings, &fresh, &handle) != 0); + CHECK("fresh observation binds replacement", + kzt_guest_library_note_observation(bindings, &fresh) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("stale generation stays absent", kzt_guest_library_lookup( + bindings, &stale, &handle) != 0); + CHECK("fresh generation lookup", kzt_guest_library_lookup( + bindings, &fresh, &handle) == 0 && + handle.library == (library_t *)&replacement); + kzt_guest_library_handle_release(&handle); + kzt_guest_library_bindings_destroy(&bindings); +} + +static void test_observation_first_unload_retires_registry_generation(void) +{ + fake_library_t lib = { 42 }; + uintptr_t map = 0x9900; + kzt_guest_object_observation_t observed = observation(map); + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + unsigned long first, second; + + CHECK("joint track", kzt_guest_library_track( + bindings, (library_t *)&lib) == 0); + CHECK("joint registry first", kzt_guest_registry_observe( + registry, &observed) == KZT_GUEST_REGISTRY_ADDED); + first = registry_generation(registry, map); + CHECK("joint binding observation", kzt_guest_library_note_observation( + bindings, &(kzt_guest_library_binding_key_t){ + .link_map_addr = map, + .generation = first, + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + + /* No exact pair is ever supplied. Unload must still retire the registry + * identity attached to the binding-side observation. */ + kzt_guest_library_inactivate(bindings, registry, (library_t *)&lib, map); + CHECK("joint identical reuse added", kzt_guest_registry_observe( + registry, &observed) == KZT_GUEST_REGISTRY_ADDED); + second = registry_generation(registry, map); + CHECK("joint reuse gets new generation", second > first); + + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_unload_retires_only_hinted_unclaimed_observation(void) +{ + fake_library_t a = { 44 }, b = { 45 }; + uintptr_t map_a = 0x9b00, map_b = 0x9c00; + kzt_guest_object_observation_t observed_a = observation(map_a); + kzt_guest_object_observation_t observed_b = observation(map_b); + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + kzt_guest_library_binding_key_t key_a, key_b; + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_guest_library_handle_t handle; + + CHECK("two-lib track a", kzt_guest_library_track( + bindings, (library_t *)&a) == 0); + CHECK("two-lib track b", kzt_guest_library_track( + bindings, (library_t *)&b) == 0); + CHECK("two-lib observe registry a", kzt_guest_registry_observe( + registry, &observed_a) == KZT_GUEST_REGISTRY_ADDED); + CHECK("two-lib observe registry b", kzt_guest_registry_observe( + registry, &observed_b) == KZT_GUEST_REGISTRY_ADDED); + key_a = key(map_a, registry_generation(registry, map_a)); + key_b = key(map_b, registry_generation(registry, map_b)); + CHECK("two-lib binding observation a", kzt_guest_library_note_observation( + bindings, &key_a) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + CHECK("two-lib binding observation b", kzt_guest_library_note_observation( + bindings, &key_b) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + + kzt_guest_library_inactivate(bindings, registry, (library_t *)&a, map_a); + CHECK("two-lib a retired", kzt_guest_registry_find_by_link_map( + registry, map_a, &snapshot) != 0 && snapshot == NULL); + CHECK("two-lib b remains live", kzt_guest_registry_find_by_link_map( + registry, map_b, &snapshot) == 0 && snapshot != NULL && + snapshot->generation == key_b.generation && + snapshot->state != KZT_GUEST_OBJECT_UNLOADING && + snapshot->state != KZT_GUEST_OBJECT_DEAD); + kzt_guest_object_snapshot_free(snapshot); + snapshot = NULL; + CHECK("two-lib b completes exact pair", + kzt_guest_library_note_exact_pair( + bindings, map_b, (library_t *)&b, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("two-lib b lookup", kzt_guest_library_lookup( + bindings, &key_b, &handle) == 0 && + handle.library == (library_t *)&b); + kzt_guest_library_handle_release(&handle); + + kzt_guest_library_unbind(bindings, registry, (library_t *)&b, map_b); + kzt_guest_library_unbind(bindings, registry, (library_t *)&a, 0); + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_missing_unload_hint_preserves_unclaimed_observation(void) +{ + fake_library_t lib = { 46 }; + uintptr_t map = 0x9d00; + kzt_guest_object_observation_t observed = observation(map); + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_guest_library_binding_key_t observed_key; + + CHECK("no-hint track", kzt_guest_library_track( + bindings, (library_t *)&lib) == 0); + CHECK("no-hint registry", kzt_guest_registry_observe( + registry, &observed) == KZT_GUEST_REGISTRY_ADDED); + observed_key = key(map, registry_generation(registry, map)); + CHECK("no-hint observation", kzt_guest_library_note_observation( + bindings, &observed_key) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + kzt_guest_library_inactivate(bindings, registry, (library_t *)&lib, 0); + CHECK("no-hint remains live", kzt_guest_registry_find_by_link_map( + registry, map, &snapshot) == 0 && snapshot != NULL && + snapshot->generation == observed_key.generation); + kzt_guest_object_snapshot_free(snapshot); + + CHECK("no-hint explicit cleanup", kzt_guest_registry_retire( + registry, map, observed_key.generation) == 0); + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_dead_library_stale_hint_does_not_retire_reused_address(void) +{ + fake_library_t old = { 47 }, replacement = { 48 }; + uintptr_t map = 0x9e00; + kzt_guest_object_observation_t observed = observation(map); + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_guest_library_binding_key_t old_key, replacement_key; + + CHECK("stale-hint track old", kzt_guest_library_track( + bindings, (library_t *)&old) == 0); + CHECK("stale-hint registry old", kzt_guest_registry_observe( + registry, &observed) == KZT_GUEST_REGISTRY_ADDED); + old_key = key(map, registry_generation(registry, map)); + CHECK("stale-hint observe old", kzt_guest_library_note_observation( + bindings, &old_key) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + kzt_guest_library_unbind(bindings, registry, (library_t *)&old, map); + + CHECK("stale-hint track replacement", kzt_guest_library_track( + bindings, (library_t *)&replacement) == 0); + CHECK("stale-hint registry replacement", kzt_guest_registry_observe( + registry, &observed) == KZT_GUEST_REGISTRY_ADDED); + replacement_key = key(map, registry_generation(registry, map)); + CHECK("stale-hint replacement generation", + replacement_key.generation > old_key.generation); + CHECK("stale-hint observe replacement", + kzt_guest_library_note_observation(bindings, &replacement_key) == + KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + + /* A is already DEAD. Repeating its obsolete address hint must not own + * or retire the observation which now belongs to B's generation. */ + kzt_guest_library_unbind(bindings, registry, (library_t *)&old, map); + CHECK("stale-hint replacement remains live", + kzt_guest_registry_find_by_link_map(registry, map, &snapshot) == 0 && + snapshot && snapshot->generation == replacement_key.generation && + snapshot->state != KZT_GUEST_OBJECT_UNLOADING && + snapshot->state != KZT_GUEST_OBJECT_DEAD); + kzt_guest_object_snapshot_free(snapshot); + + kzt_guest_library_unbind(bindings, registry, + (library_t *)&replacement, map); + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_tracking_allocation_failure_unload_is_fail_open(void) +{ + fake_library_t untracked = { 43 }; + uintptr_t map = 0x9a00; + kzt_guest_object_observation_t observed = observation(map); + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + kzt_guest_object_snapshot_t *snapshot = NULL; + unsigned long first; + + kzt_guest_library_binding_test_set_alloc_failure_after(0); + CHECK("alloc-fail track", kzt_guest_library_track( + bindings, (library_t *)&untracked) != 0); + kzt_guest_library_binding_test_set_alloc_failure_after(-1); + + CHECK("alloc-fail registry first", kzt_guest_registry_observe( + registry, &observed) == KZT_GUEST_REGISTRY_ADDED); + first = registry_generation(registry, map); + CHECK("alloc-fail binding observation", + kzt_guest_library_note_observation( + bindings, &(kzt_guest_library_binding_key_t){ + .link_map_addr = map, + .generation = first, + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + + /* With no lifecycle record there is no provable unload owner. The stale + * address hint must therefore preserve the exact observation fail-open. */ + kzt_guest_library_binding_test_set_alloc_failure_after(0); + kzt_guest_library_unbind(bindings, registry, (library_t *)&untracked, + map); + kzt_guest_library_binding_test_set_alloc_failure_after(-1); + CHECK("alloc-fail observation remains live", + kzt_guest_registry_find_by_link_map(registry, map, &snapshot) == 0 && + snapshot && snapshot->generation == first && + snapshot->state != KZT_GUEST_OBJECT_UNLOADING && + snapshot->state != KZT_GUEST_OBJECT_DEAD); + kzt_guest_object_snapshot_free(snapshot); + CHECK("alloc-fail explicit cleanup", + kzt_guest_registry_retire(registry, map, first) == 0); + + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_inactive_can_reload_but_destroyed_cannot(void) +{ + fake_library_t lib = { 7 }; + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + kzt_guest_library_binding_key_t first = key(0xb000, 20); + kzt_guest_library_binding_key_t second = key(0xb000, 21); + kzt_guest_library_handle_t handle; + + CHECK("reload track", kzt_guest_library_track( + bindings, (library_t *)&lib) == 0); + CHECK("reload observe first", kzt_guest_library_note_observation( + bindings, &first) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + CHECK("reload bind first", kzt_guest_library_note_exact_pair( + bindings, first.link_map_addr, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_EMULATED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + kzt_guest_library_inactivate(bindings, NULL, (library_t *)&lib, + first.link_map_addr); + CHECK("reload reactivate", kzt_guest_library_reactivate( + bindings, (library_t *)&lib) == 0); + CHECK("reload observe second", kzt_guest_library_note_observation( + bindings, &second) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + CHECK("reload bind second", kzt_guest_library_note_exact_pair( + bindings, second.link_map_addr, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_EMULATED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("reload lookup", kzt_guest_library_lookup( + bindings, &second, &handle) == 0); + kzt_guest_library_handle_release(&handle); + kzt_guest_library_unbind(bindings, NULL, (library_t *)&lib, + second.link_map_addr); + CHECK("destroyed no reactivate", kzt_guest_library_reactivate( + bindings, (library_t *)&lib) != 0); + kzt_guest_library_bindings_destroy(&bindings); +} + +static void run_different_library_address_reuse(int force_gate_alloc_failure, + const char *prefix) +{ + fake_library_t old_library = { 41 }; + fake_library_t new_library = { 42 }; + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_scope_t scope = { 0 }; + kzt_guest_library_callback_access_t stale = { 0 }; + kzt_guest_library_callback_access_t current = { 0 }; + uintptr_t map = force_gate_alloc_failure ? 0xa11000 : 0xa10000; + + CHECK(prefix, bindings != NULL); + if (!bindings) return; + CHECK("reuse old track", kzt_guest_library_track( + bindings, (library_t *)&old_library) == 0); + CHECK("reuse old loader pair", kzt_guest_library_publish_loader_pair( + bindings, map, (library_t *)&old_library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); + if (force_gate_alloc_failure) + kzt_guest_library_binding_test_set_alloc_failure_after(0); + kzt_guest_library_inactivate(bindings, NULL, + (library_t *)&old_library, map); + kzt_guest_library_binding_test_set_alloc_failure_after(-1); + + /* A callback left over from the old causal chain has no new loader scope + * and must still be rejected before the first guest read. */ + CHECK("reuse stale callback rejected", + kzt_guest_library_callback_access_begin( + bindings, map, &stale) != 0); + + CHECK("reuse new track", kzt_guest_library_track( + bindings, (library_t *)&new_library) == 0); + CHECK("reuse loader scope", kzt_guest_library_loader_scope_begin( + bindings, &scope) == 0); + CHECK("reuse current callback admitted", + kzt_guest_library_callback_access_begin_scoped( + bindings, map, &scope, ¤t) == 0); + kzt_guest_library_callback_access_end(¤t); + CHECK("reuse pending loader pair", + kzt_guest_library_loader_scope_note_pair( + &scope, map, (library_t *)&new_library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); + CHECK("reuse new loader pair", kzt_guest_library_loader_scope_publish_pair( + &scope, map, (library_t *)&new_library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); + kzt_guest_library_loader_scope_end(&scope); + + kzt_guest_library_bindings_destroy(&bindings); +} + +static void test_different_library_reuses_closed_callback_address(void) +{ + run_different_library_address_reuse(0, "reuse normal init"); + run_different_library_address_reuse(1, "reuse fallback init"); +} + +static void prepare_scoped_pair( + kzt_guest_library_bindings_t *bindings, + kzt_guest_library_loader_scope_t *scope, uintptr_t map, + library_t *library) +{ + kzt_guest_library_callback_access_t access = { 0 }; + + CHECK("transaction scope", kzt_guest_library_loader_scope_begin( + bindings, scope) == 0); + CHECK("transaction callback", kzt_guest_library_callback_access_begin_scoped( + bindings, map, scope, &access) == 0); + kzt_guest_library_callback_access_end(&access); + CHECK("transaction prepare", kzt_guest_library_loader_scope_note_pair( + scope, map, library, KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); +} + +static void test_loader_pair_is_invisible_until_publish(void) +{ + fake_library_t lib = { 61 }; + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_scope_t scope = { 0 }; + kzt_guest_library_binding_key_t observed = key(0xc900, 30); + kzt_guest_library_handle_t handle; + size_t active_pending = 99, live_entries = 99; + + CHECK("transaction init", bindings != NULL); + if (!bindings) return; + CHECK("transaction track", kzt_guest_library_track( + bindings, (library_t *)&lib) == 0); + CHECK("transaction observe", kzt_guest_library_note_observation( + bindings, &observed) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + prepare_scoped_pair(bindings, &scope, observed.link_map_addr, + (library_t *)&lib); + CHECK("transaction prepared invisible", kzt_guest_library_lookup( + bindings, &observed, &handle) != 0); + CHECK("transaction prepared snapshot", + kzt_guest_library_binding_test_snapshot( + bindings, (library_t *)&lib, NULL, &active_pending, + &live_entries) == 0); + CHECK("transaction prepared has no global state", + active_pending == 0 && live_entries == 0); + CHECK("transaction publish", kzt_guest_library_loader_scope_publish_pair( + &scope, observed.link_map_addr, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("transaction published visible", kzt_guest_library_lookup( + bindings, &observed, &handle) == 0); + kzt_guest_library_handle_release(&handle); + kzt_guest_library_loader_scope_end(&scope); + kzt_guest_library_bindings_destroy(&bindings); +} + +static void test_loader_pair_cancel_and_failed_publish_are_invisible(void) +{ + fake_library_t prepared = { 62 }, wrong = { 63 }; + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_scope_t scope = { 0 }; + kzt_guest_library_binding_key_t cancelled = key(0xca00, 31); + kzt_guest_library_binding_key_t failed = key(0xcb00, 32); + kzt_guest_library_handle_t handle; + size_t active_pending = 99, live_entries = 99; + + CHECK("cancel init", bindings != NULL); + if (!bindings) return; + CHECK("cancel track prepared", kzt_guest_library_track( + bindings, (library_t *)&prepared) == 0); + CHECK("cancel track wrong", kzt_guest_library_track( + bindings, (library_t *)&wrong) == 0); + CHECK("cancel observe", kzt_guest_library_note_observation( + bindings, &cancelled) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + prepare_scoped_pair(bindings, &scope, cancelled.link_map_addr, + (library_t *)&prepared); + kzt_guest_library_loader_scope_end(&scope); + CHECK("cancelled pair invisible", kzt_guest_library_lookup( + bindings, &cancelled, &handle) != 0); + CHECK("cancelled pair snapshot", + kzt_guest_library_binding_test_snapshot( + bindings, (library_t *)&prepared, NULL, &active_pending, + &live_entries) == 0); + CHECK("cancelled pair has no global state", + active_pending == 0 && live_entries == 0); + + CHECK("failed observe", kzt_guest_library_note_observation( + bindings, &failed) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + prepare_scoped_pair(bindings, &scope, failed.link_map_addr, + (library_t *)&prepared); + CHECK("failed publish rejected", + kzt_guest_library_loader_scope_publish_pair( + &scope, failed.link_map_addr, (library_t *)&wrong, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ERROR); + kzt_guest_library_loader_scope_end(&scope); + CHECK("failed publish invisible", kzt_guest_library_lookup( + bindings, &failed, &handle) != 0); + CHECK("failed publish snapshot", + kzt_guest_library_binding_test_snapshot( + bindings, (library_t *)&prepared, NULL, &active_pending, + &live_entries) == 0); + CHECK("failed publish has no global state", + active_pending == 0 && live_entries == 0); + kzt_guest_library_bindings_destroy(&bindings); +} + +static int callback_rejected(kzt_guest_library_bindings_t *bindings, + uintptr_t map, + const kzt_guest_library_loader_scope_t *scope) +{ + kzt_guest_library_callback_access_t access = { 0 }; + int result = kzt_guest_library_callback_access_begin_scoped( + bindings, map, scope, &access); + if (result == 0) + kzt_guest_library_callback_access_end(&access); + return result != 0; +} + +static void run_failed_loader_keeps_tombstone(int force_gate_alloc_failure) +{ + fake_library_t old_library = { 51 }, new_library = { 52 }; + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_scope_t scope = { 0 }; + kzt_guest_library_callback_access_t access = { 0 }; + uintptr_t map = force_gate_alloc_failure ? 0xc200 : 0xc100; + + CHECK("failed-loader init", bindings != NULL); + if (!bindings) return; + CHECK("failed-loader old track", kzt_guest_library_track( + bindings, (library_t *)&old_library) == 0); + CHECK("failed-loader old pair", kzt_guest_library_publish_loader_pair( + bindings, map, (library_t *)&old_library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); + if (force_gate_alloc_failure) + kzt_guest_library_binding_test_set_alloc_failure_after(0); + kzt_guest_library_inactivate(bindings, NULL, + (library_t *)&old_library, map); + kzt_guest_library_binding_test_set_alloc_failure_after(-1); + CHECK("failed-loader new track", kzt_guest_library_track( + bindings, (library_t *)&new_library) == 0); + CHECK("failed-loader scope", kzt_guest_library_loader_scope_begin( + bindings, &scope) == 0); + CHECK("failed-loader scoped temporary read", + kzt_guest_library_callback_access_begin_scoped( + bindings, map, &scope, &access) == 0); + kzt_guest_library_callback_access_end(&access); + kzt_guest_library_loader_scope_end(&scope); + + CHECK("failed-loader tombstone retained", + callback_rejected(bindings, map, NULL)); + kzt_guest_library_bindings_destroy(&bindings); +} + +static void test_failed_loader_keeps_normal_and_fallback_tombstones(void) +{ + run_failed_loader_keeps_tombstone(0); + run_failed_loader_keeps_tombstone(1); +} + +typedef struct scope_transfer_arg { + kzt_guest_library_bindings_t *bindings; + uintptr_t map; + kzt_guest_library_loader_scope_t scope; + int result; +} scope_transfer_arg_t; + +static void *scope_transfer_thread(void *opaque) +{ + scope_transfer_arg_t *arg = opaque; + kzt_guest_library_callback_access_t access = { 0 }; + arg->result = kzt_guest_library_callback_access_begin_scoped( + arg->bindings, arg->map, &arg->scope, &access); + if (arg->result == 0) + kzt_guest_library_callback_access_end(&access); + return NULL; +} + +static void test_loader_scope_identity_and_nesting(void) +{ + fake_library_t old_library = { 53 }; + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_scope_t outer = { 0 }, inner = { 0 }; + kzt_guest_library_loader_scope_t stale_inner = { 0 }, forged = { 0 }; + kzt_guest_library_callback_access_t access = { 0 }; + scope_transfer_arg_t transfer = { 0 }; + pthread_t thread; + uintptr_t map = 0xc300; + + CHECK("scope-id init", bindings != NULL); + if (!bindings) return; + CHECK("scope-id track", kzt_guest_library_track( + bindings, (library_t *)&old_library) == 0); + CHECK("scope-id pair", kzt_guest_library_publish_loader_pair( + bindings, map, (library_t *)&old_library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); + kzt_guest_library_inactivate(bindings, NULL, + (library_t *)&old_library, map); + + CHECK("scope-id outer begin", kzt_guest_library_loader_scope_begin( + bindings, &outer) == 0); + CHECK("scope-id inner begin", kzt_guest_library_loader_scope_begin( + bindings, &inner) == 0); + stale_inner = inner; + CHECK("scope-id inner active", kzt_guest_library_callback_access_begin_scoped( + bindings, map, &inner, &access) == 0); + kzt_guest_library_callback_access_end(&access); + kzt_guest_library_loader_scope_end(&inner); + CHECK("scope-id ended copy rejected", + callback_rejected(bindings, map, &stale_inner)); + CHECK("scope-id outer restored", kzt_guest_library_callback_access_begin_scoped( + bindings, map, &outer, &access) == 0); + kzt_guest_library_callback_access_end(&access); + + forged = outer; + forged.cookie++; + CHECK("scope-id forged rejected", + callback_rejected(bindings, map, &forged)); + + transfer.bindings = bindings; + transfer.map = map; + transfer.scope = outer; + transfer.result = 0; + CHECK("scope-id transfer create", pthread_create( + &thread, NULL, scope_transfer_thread, &transfer) == 0); + CHECK("scope-id transfer join", pthread_join(thread, NULL) == 0); + CHECK("scope-id cross-thread rejected", transfer.result != 0); + kzt_guest_library_loader_scope_end(&outer); + kzt_guest_library_bindings_destroy(&bindings); +} + +typedef struct concurrent_scope_arg { + kzt_guest_library_bindings_t *bindings; + pthread_barrier_t *barrier; + uintptr_t map; + int result; +} concurrent_scope_arg_t; + +static void *concurrent_scope_thread(void *opaque) +{ + concurrent_scope_arg_t *arg = opaque; + kzt_guest_library_loader_scope_t scope = { 0 }; + kzt_guest_library_callback_access_t access = { 0 }; + + arg->result = kzt_guest_library_loader_scope_begin( + arg->bindings, &scope); + pthread_barrier_wait(arg->barrier); + if (arg->result == 0) + arg->result = kzt_guest_library_callback_access_begin_scoped( + arg->bindings, arg->map, &scope, &access); + if (arg->result == 0) + kzt_guest_library_callback_access_end(&access); + kzt_guest_library_loader_scope_end(&scope); + return NULL; +} + +static void test_concurrent_loader_scopes_do_not_invalidate_each_other(void) +{ + fake_library_t old_a = { 59 }, old_b = { 60 }; + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + pthread_barrier_t barrier; + pthread_t threads[2]; + concurrent_scope_arg_t args[2] = { + { .bindings = bindings, .map = 0xc700 }, + { .bindings = bindings, .map = 0xc800 }, + }; + + CHECK("concurrent-scope init", bindings != NULL); + if (!bindings) return; + CHECK("concurrent-scope track a", kzt_guest_library_track( + bindings, (library_t *)&old_a) == 0); + CHECK("concurrent-scope track b", kzt_guest_library_track( + bindings, (library_t *)&old_b) == 0); + CHECK("concurrent-scope pair a", kzt_guest_library_publish_loader_pair( + bindings, args[0].map, (library_t *)&old_a, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); + CHECK("concurrent-scope pair b", kzt_guest_library_publish_loader_pair( + bindings, args[1].map, (library_t *)&old_b, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); + kzt_guest_library_inactivate(bindings, NULL, (library_t *)&old_a, + args[0].map); + kzt_guest_library_inactivate(bindings, NULL, (library_t *)&old_b, + args[1].map); + CHECK("concurrent-scope barrier", pthread_barrier_init( + &barrier, NULL, 2) == 0); + args[0].barrier = &barrier; + args[1].barrier = &barrier; + CHECK("concurrent-scope create a", pthread_create( + &threads[0], NULL, concurrent_scope_thread, &args[0]) == 0); + CHECK("concurrent-scope create b", pthread_create( + &threads[1], NULL, concurrent_scope_thread, &args[1]) == 0); + CHECK("concurrent-scope join a", pthread_join(threads[0], NULL) == 0); + CHECK("concurrent-scope join b", pthread_join(threads[1], NULL) == 0); + CHECK("concurrent-scope a active", args[0].result == 0); + CHECK("concurrent-scope b active", args[1].result == 0); + pthread_barrier_destroy(&barrier); + kzt_guest_library_bindings_destroy(&bindings); +} + +static void test_loader_scope_cannot_reopen_unobserved_address(void) +{ + fake_library_t old_a = { 54 }, old_b = { 55 }, replacement = { 56 }; + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_scope_t scope = { 0 }; + kzt_guest_library_callback_access_t access = { 0 }; + uintptr_t map_a = 0xc400, map_b = 0xc500; + + CHECK("scope-object init", bindings != NULL); + if (!bindings) return; + CHECK("scope-object track a", kzt_guest_library_track( + bindings, (library_t *)&old_a) == 0); + CHECK("scope-object track b", kzt_guest_library_track( + bindings, (library_t *)&old_b) == 0); + CHECK("scope-object old a", kzt_guest_library_publish_loader_pair( + bindings, map_a, (library_t *)&old_a, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); + CHECK("scope-object old b", kzt_guest_library_publish_loader_pair( + bindings, map_b, (library_t *)&old_b, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); + kzt_guest_library_inactivate(bindings, NULL, (library_t *)&old_a, map_a); + kzt_guest_library_inactivate(bindings, NULL, (library_t *)&old_b, map_b); + CHECK("scope-object replacement track", kzt_guest_library_track( + bindings, (library_t *)&replacement) == 0); + CHECK("scope-object begin", kzt_guest_library_loader_scope_begin( + bindings, &scope) == 0); + CHECK("scope-object observe a", kzt_guest_library_callback_access_begin_scoped( + bindings, map_a, &scope, &access) == 0); + kzt_guest_library_callback_access_end(&access); + CHECK("scope-object unrelated publication rejected", + kzt_guest_library_loader_scope_publish_pair( + &scope, map_b, (library_t *)&replacement, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ERROR); + kzt_guest_library_loader_scope_end(&scope); + CHECK("scope-object b remains closed", + callback_rejected(bindings, map_b, NULL)); + kzt_guest_library_bindings_destroy(&bindings); +} + +typedef struct publish_reader_arg { + kzt_guest_library_bindings_t *bindings; + library_t *library; + uintptr_t map; + int done; +} publish_reader_arg_t; + +static void *publish_reader_unload_thread(void *opaque) +{ + publish_reader_arg_t *arg = opaque; + kzt_guest_library_inactivate(arg->bindings, NULL, arg->library, arg->map); + __atomic_store_n(&arg->done, 1, __ATOMIC_RELEASE); + return NULL; +} + +static void test_publish_while_reader_preserves_unload_wait(void) +{ + fake_library_t old_library = { 57 }, replacement = { 58 }; + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_callback_access_t held = { 0 }; + kzt_guest_library_callback_access_t scoped = { 0 }; + kzt_guest_library_loader_scope_t scope = { 0 }; + publish_reader_arg_t arg = { .bindings = bindings, + .library = (library_t *)&old_library, + .map = 0xc600 }; + pthread_t thread; + struct timespec delay = { .tv_nsec = 20 * 1000 * 1000 }; + + CHECK("publish-reader init", bindings != NULL); + if (!bindings) return; + CHECK("publish-reader old track", kzt_guest_library_track( + bindings, (library_t *)&old_library) == 0); + CHECK("publish-reader old pair", kzt_guest_library_publish_loader_pair( + bindings, arg.map, (library_t *)&old_library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); + CHECK("publish-reader held", kzt_guest_library_callback_access_begin( + bindings, arg.map, &held) == 0); + CHECK("publish-reader replacement track", kzt_guest_library_track( + bindings, (library_t *)&replacement) == 0); + CHECK("publish-reader create", pthread_create( + &thread, NULL, publish_reader_unload_thread, &arg) == 0); + for (int i = 0; i < 20 && + !callback_rejected(bindings, arg.map, NULL); ++i) + nanosleep(&delay, NULL); + CHECK("publish-reader closed", callback_rejected( + bindings, arg.map, NULL)); + CHECK("publish-reader scope", kzt_guest_library_loader_scope_begin( + bindings, &scope) == 0); + CHECK("publish-reader scoped observation", + kzt_guest_library_callback_access_begin_scoped( + bindings, arg.map, &scope, &scoped) == 0); + kzt_guest_library_callback_access_end(&scoped); + CHECK("publish-reader pending pair", + kzt_guest_library_loader_scope_note_pair( + &scope, arg.map, (library_t *)&replacement, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) != + KZT_GUEST_LIBRARY_BINDING_ERROR); + CHECK("publish-reader publication", + kzt_guest_library_loader_scope_publish_pair( + &scope, arg.map, (library_t *)&replacement, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) != + KZT_GUEST_LIBRARY_BINDING_ERROR); + kzt_guest_library_loader_scope_end(&scope); + kzt_guest_library_callback_access_end(&held); + for (int i = 0; i < 20 && + !__atomic_load_n(&arg.done, __ATOMIC_ACQUIRE); ++i) + nanosleep(&delay, NULL); + CHECK("publish-reader unload completes", + __atomic_load_n(&arg.done, __ATOMIC_ACQUIRE)); + if (__atomic_load_n(&arg.done, __ATOMIC_ACQUIRE)) { + pthread_join(thread, NULL); + CHECK("publish-reader reopened after old readers drain", + kzt_guest_library_callback_access_begin( + bindings, arg.map, &held) == 0); + kzt_guest_library_callback_access_end(&held); + kzt_guest_library_bindings_destroy(&bindings); + } +} + +static void test_registry_retire_failures_are_observable_fail_open(void) +{ + fake_library_t missing = { 43 }; + fake_library_t disabled = { 44 }; + fake_library_t replaced = { 45 }; + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_registry_t *registry; + unsigned long registry_missing = 0; + unsigned long retire_unprovable = 0; + kzt_guest_object_observation_t observed = observation(0xa20000); + kzt_guest_library_binding_key_t missing_key = key(0xa21000, 1); + kzt_guest_library_binding_key_t disabled_key = key(0xa22000, 1); + kzt_guest_library_binding_key_t replaced_key; + unsigned long generation; + + CHECK("fail-open bindings", bindings != NULL); + if (!bindings) return; + + CHECK("fail-open missing track", kzt_guest_library_track( + bindings, (library_t *)&missing) == 0); + CHECK("fail-open missing bind", kzt_guest_library_bind( + bindings, &missing_key, (library_t *)&missing, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + kzt_guest_library_inactivate(bindings, NULL, + (library_t *)&missing, 0xa21000); + + kzt_guest_registry_test_set_alloc_failure_after(1); + registry = kzt_guest_registry_init(); + kzt_guest_registry_test_set_alloc_failure_after(-1); + CHECK("fail-open disabled registry", registry != NULL); + CHECK("fail-open disabled track", kzt_guest_library_track( + bindings, (library_t *)&disabled) == 0); + CHECK("fail-open disabled bind", kzt_guest_library_bind( + bindings, &disabled_key, (library_t *)&disabled, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + kzt_guest_library_inactivate(bindings, registry, + (library_t *)&disabled, 0xa22000); + kzt_guest_registry_destroy(®istry); + + registry = kzt_guest_registry_init(); + CHECK("fail-open replacement registry", registry != NULL); + CHECK("fail-open replacement observe", kzt_guest_registry_observe( + registry, &observed) == KZT_GUEST_REGISTRY_ADDED); + generation = registry_generation(registry, observed.link_map_addr); + replaced_key = key(observed.link_map_addr, generation); + CHECK("fail-open replacement track", kzt_guest_library_track( + bindings, (library_t *)&replaced) == 0); + CHECK("fail-open replacement bind", kzt_guest_library_bind( + bindings, &replaced_key, (library_t *)&replaced, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("fail-open replacement retire", kzt_guest_registry_retire( + registry, observed.link_map_addr, generation) == 0); + CHECK("fail-open replacement observe new", kzt_guest_registry_observe( + registry, &observed) == KZT_GUEST_REGISTRY_ADDED); + kzt_guest_library_inactivate(bindings, registry, + (library_t *)&replaced, + observed.link_map_addr); + + CHECK("fail-open diagnostics", + kzt_guest_library_binding_test_get_diagnostics( + bindings, ®istry_missing, &retire_unprovable) == 0); + CHECK("fail-open registry missing observed", + registry_missing >= 1); + CHECK("fail-open registry disabled observed", + retire_unprovable >= 1); + CHECK("fail-open generation replacement observed", + retire_unprovable >= 2); + + kzt_guest_registry_destroy(®istry); + kzt_guest_library_bindings_destroy(&bindings); +} + +typedef struct unbind_thread_arg { + kzt_guest_library_bindings_t *bindings; + library_t *library; + pthread_mutex_t lock; + pthread_cond_t cond; + int started; + int done; +} unbind_thread_arg_t; + +static void *unbind_thread(void *opaque) +{ + unbind_thread_arg_t *arg = opaque; + pthread_mutex_lock(&arg->lock); + arg->started = 1; + pthread_cond_broadcast(&arg->cond); + pthread_mutex_unlock(&arg->lock); + kzt_guest_library_unbind(arg->bindings, NULL, arg->library, 0); + pthread_mutex_lock(&arg->lock); + arg->done = 1; + pthread_cond_broadcast(&arg->cond); + pthread_mutex_unlock(&arg->lock); + return NULL; +} + +static void test_lookup_and_unbind_threads(void) +{ + fake_library_t lib = { 6 }; + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + kzt_guest_library_binding_key_t k = key(0xa000, 13); + kzt_guest_library_handle_t held, probe; + pthread_t thread; + unbind_thread_arg_t arg = { + .bindings = bindings, .library = (library_t *)&lib, + .lock = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, + }; + CHECK("thread track", kzt_guest_library_track( + bindings, (library_t *)&lib) == 0); + CHECK("thread add", kzt_guest_library_bind(bindings, &k, + (library_t *)&lib, KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("thread acquire", kzt_guest_library_lookup(bindings, &k, &held) == 0); + CHECK("thread create", pthread_create(&thread, NULL, unbind_thread, &arg) == 0); + pthread_mutex_lock(&arg.lock); + while (!arg.started) pthread_cond_wait(&arg.cond, &arg.lock); + pthread_mutex_unlock(&arg.lock); + while (kzt_guest_library_lookup(bindings, &k, &probe) == 0) + kzt_guest_library_handle_release(&probe); + pthread_mutex_lock(&arg.lock); + CHECK("unbind waits for handle", !arg.done); + pthread_mutex_unlock(&arg.lock); + kzt_guest_library_handle_release(&held); + pthread_join(thread, NULL); + CHECK("no dead return", kzt_guest_library_lookup(bindings, &k, &probe) != 0); + pthread_cond_destroy(&arg.cond); + pthread_mutex_destroy(&arg.lock); + kzt_guest_library_bindings_destroy(&bindings); +} + +typedef struct lock_order_sync { + pthread_mutex_t lock; + pthread_cond_t cond; + int lease_ready; + int start_lookup; + int retire_waiting; + int lookup_done; + int lookup_result; +} lock_order_sync_t; + +typedef struct lock_order_unload_arg { + kzt_guest_library_bindings_t *bindings; + kzt_guest_registry_t *registry; + library_t *library; + uintptr_t link_map_addr; +} lock_order_unload_arg_t; + +typedef struct lock_order_lookup_arg { + kzt_guest_library_access_t *access; + kzt_guest_registry_t *registry; + kzt_guest_library_binding_key_t key; + kzt_guest_registry_source_lease_t *lease; + lock_order_sync_t *sync; +} lock_order_lookup_arg_t; + +static void lock_order_retire_waiting(void *opaque) +{ + lock_order_sync_t *sync = opaque; + + pthread_mutex_lock(&sync->lock); + sync->retire_waiting = 1; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); +} + +static void *lock_order_unload_worker(void *opaque) +{ + lock_order_unload_arg_t *arg = opaque; + + kzt_guest_library_inactivate(arg->bindings, arg->registry, arg->library, + arg->link_map_addr); + return NULL; +} + +static void *lock_order_lookup_worker(void *opaque) +{ + lock_order_lookup_arg_t *arg = opaque; + kzt_guest_library_handle_t handle; + int result = kzt_guest_registry_source_lease_acquire( + arg->registry, arg->key.link_map_addr, arg->key.generation, + arg->key.namespace_id, arg->lease); + + pthread_mutex_lock(&arg->sync->lock); + arg->sync->lease_ready = result == 0; + pthread_cond_broadcast(&arg->sync->cond); + while (result == 0 && !arg->sync->start_lookup) { + pthread_cond_wait(&arg->sync->cond, &arg->sync->lock); + } + pthread_mutex_unlock(&arg->sync->lock); + + if (result == 0) { + /* This is the production provider-lookup lock path while the same + * lazy-completion thread owns the exact source lease. */ + result = kzt_guest_library_access_lookup( + arg->access, &arg->key, &handle); + } + + if (result == 0) { + kzt_guest_library_handle_release(&handle); + } + pthread_mutex_lock(&arg->sync->lock); + arg->sync->lookup_result = result; + arg->sync->lookup_done = 1; + pthread_cond_broadcast(&arg->sync->cond); + pthread_mutex_unlock(&arg->sync->lock); + return NULL; +} + +static int timed_wait_for_flag(pthread_cond_t *cond, pthread_mutex_t *lock, + int *flag) +{ + struct timespec deadline; + + clock_gettime(CLOCK_MONOTONIC, &deadline); + deadline.tv_sec += 30; + while (!*flag) { + int result = pthread_cond_clockwait( + cond, lock, CLOCK_MONOTONIC, &deadline); + if (result != 0) { + fprintf(stderr, "timed wait failed: flag=%d error=%d\n", + *flag, result); + return -1; + } + } + return 0; +} + +typedef struct observation_unload_sync { + pthread_mutex_t lock; + pthread_cond_t cond; + int retire_waiters; + int retire_calls; + struct { + kzt_guest_library_bindings_t *bindings; + kzt_guest_library_binding_key_t key; + library_t *library; + int from_observation; + } retire[4]; + int lifecycle_waiters; + kzt_guest_library_bindings_t *lifecycle_wait_bindings; + library_t *lifecycle_wait_library; +} observation_unload_sync_t; + +typedef struct observation_unload_arg { + kzt_guest_library_bindings_t *bindings; + kzt_guest_registry_t *registry; + library_t *library; + uintptr_t link_map_addr; + observation_unload_sync_t *sync; + int started; + int done; +} observation_unload_arg_t; + +static void observation_retire_waiting(void *opaque) +{ + observation_unload_sync_t *sync = opaque; + + pthread_mutex_lock(&sync->lock); + ++sync->retire_waiters; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); +} + +static void observation_before_registry_retire( + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_binding_key_t *key, + library_t *library, int from_observation, void *opaque) +{ + observation_unload_sync_t *sync = opaque; + + pthread_mutex_lock(&sync->lock); + if (sync->retire_calls < (int)(sizeof(sync->retire) / + sizeof(sync->retire[0]))) { + sync->retire[sync->retire_calls].bindings = bindings; + sync->retire[sync->retire_calls].key = *key; + sync->retire[sync->retire_calls].library = library; + sync->retire[sync->retire_calls].from_observation = + from_observation; + } + ++sync->retire_calls; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); +} + +static void observation_before_lifecycle_wait( + kzt_guest_library_bindings_t *bindings, library_t *library, + void *opaque) +{ + observation_unload_sync_t *sync = opaque; + + pthread_mutex_lock(&sync->lock); + ++sync->lifecycle_waiters; + sync->lifecycle_wait_bindings = bindings; + sync->lifecycle_wait_library = library; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); +} + +static void *observation_unload_worker(void *opaque) +{ + observation_unload_arg_t *arg = opaque; + + pthread_mutex_lock(&arg->sync->lock); + arg->started = 1; + pthread_cond_broadcast(&arg->sync->cond); + pthread_mutex_unlock(&arg->sync->lock); + kzt_guest_library_inactivate(arg->bindings, arg->registry, arg->library, + arg->link_map_addr); + pthread_mutex_lock(&arg->sync->lock); + arg->done = 1; + pthread_cond_broadcast(&arg->sync->cond); + pthread_mutex_unlock(&arg->sync->lock); + return NULL; +} + +static int timed_wait_for_count(pthread_cond_t *cond, pthread_mutex_t *lock, + int *value, int expected) +{ + struct timespec deadline; + + clock_gettime(CLOCK_MONOTONIC, &deadline); + deadline.tv_sec += 30; + while (*value < expected) { + int result = pthread_cond_clockwait( + cond, lock, CLOCK_MONOTONIC, &deadline); + if (result != 0) { + fprintf(stderr, + "timed wait failed: value=%d expected=%d error=%d\n", + *value, expected, result); + return -1; + } + } + return 0; +} + +static void test_concurrent_observation_unloads_keep_exact_owners(void) +{ + fake_library_t a = { 70 }, b = { 71 }; + uintptr_t map_a = 0xe000, map_b = 0xe100; + kzt_guest_object_observation_t observed_a = observation(map_a); + kzt_guest_object_observation_t observed_b = observation(map_b); + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + kzt_guest_registry_source_lease_t lease_a = { 0 }, lease_b = { 0 }; + kzt_guest_library_binding_key_t key_a, key_b; + observation_unload_sync_t sync = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + observation_unload_arg_t arg_a = { + .bindings = bindings, .registry = registry, + .library = (library_t *)&a, .link_map_addr = map_a, .sync = &sync, + }; + observation_unload_arg_t arg_b = { + .bindings = bindings, .registry = registry, + .library = (library_t *)&b, .link_map_addr = map_b, .sync = &sync, + }; + pthread_t thread_a, thread_b; + + CHECK("owner-race track a", kzt_guest_library_track( + bindings, (library_t *)&a) == 0); + CHECK("owner-race track b", kzt_guest_library_track( + bindings, (library_t *)&b) == 0); + CHECK("owner-race registry a", kzt_guest_registry_observe( + registry, &observed_a) == KZT_GUEST_REGISTRY_ADDED); + CHECK("owner-race registry b", kzt_guest_registry_observe( + registry, &observed_b) == KZT_GUEST_REGISTRY_ADDED); + key_a = key(map_a, registry_generation(registry, map_a)); + key_b = key(map_b, registry_generation(registry, map_b)); + CHECK("owner-race observation a", kzt_guest_library_note_observation( + bindings, &key_a) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + CHECK("owner-race observation b", kzt_guest_library_note_observation( + bindings, &key_b) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + CHECK("owner-race lease a", kzt_guest_registry_source_lease_acquire( + registry, map_a, key_a.generation, 0, &lease_a) == 0); + CHECK("owner-race lease b", kzt_guest_registry_source_lease_acquire( + registry, map_b, key_b.generation, 0, &lease_b) == 0); + + kzt_guest_registry_test_set_before_retire_wait( + observation_retire_waiting, &sync); + kzt_guest_library_binding_test_set_before_registry_retire( + observation_before_registry_retire, &sync); + CHECK("owner-race thread a", pthread_create( + &thread_a, NULL, observation_unload_worker, &arg_a) == 0); + CHECK("owner-race thread b", pthread_create( + &thread_b, NULL, observation_unload_worker, &arg_b) == 0); + pthread_mutex_lock(&sync.lock); + CHECK("owner-race both blocked", timed_wait_for_count( + &sync.cond, &sync.lock, &sync.retire_waiters, 2) == 0); + CHECK("owner-race retire identities", sync.retire_calls == 2 && + sync.retire[0].bindings == bindings && + sync.retire[1].bindings == bindings && + sync.retire[0].from_observation && + sync.retire[1].from_observation && + ((sync.retire[0].library == (library_t *)&a && + sync.retire[0].key.link_map_addr == map_a && + sync.retire[1].library == (library_t *)&b && + sync.retire[1].key.link_map_addr == map_b) || + (sync.retire[0].library == (library_t *)&b && + sync.retire[0].key.link_map_addr == map_b && + sync.retire[1].library == (library_t *)&a && + sync.retire[1].key.link_map_addr == map_a))); + pthread_mutex_unlock(&sync.lock); + + kzt_guest_registry_source_lease_release(&lease_a); + pthread_mutex_lock(&sync.lock); + CHECK("owner-race a completes", timed_wait_for_flag( + &sync.cond, &sync.lock, &arg_a.done) == 0); + CHECK("owner-race b remains blocked", !arg_b.done); + pthread_mutex_unlock(&sync.lock); + + kzt_guest_registry_source_lease_release(&lease_b); + pthread_mutex_lock(&sync.lock); + CHECK("owner-race b completes", timed_wait_for_flag( + &sync.cond, &sync.lock, &arg_b.done) == 0); + pthread_mutex_unlock(&sync.lock); + CHECK("owner-race join a", pthread_join(thread_a, NULL) == 0); + CHECK("owner-race join b", pthread_join(thread_b, NULL) == 0); + + kzt_guest_registry_test_set_before_retire_wait(NULL, NULL); + kzt_guest_library_binding_test_set_before_registry_retire(NULL, NULL); + pthread_cond_destroy(&sync.cond); + pthread_mutex_destroy(&sync.lock); + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_duplicate_unload_waits_for_lifecycle_owner(void) +{ + fake_library_t lib = { 72 }; + uintptr_t map = 0xe200, unowned_map = 0xe300; + kzt_guest_object_observation_t observed = observation(map); + kzt_guest_object_observation_t unowned = observation(unowned_map); + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + kzt_guest_registry_source_lease_t lease = { 0 }; + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_guest_library_binding_key_t observed_key, unowned_key; + observation_unload_sync_t sync = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + observation_unload_arg_t first = { + .bindings = bindings, .registry = registry, + .library = (library_t *)&lib, .link_map_addr = map, .sync = &sync, + }; + observation_unload_arg_t second = { + .bindings = bindings, .registry = registry, + .library = (library_t *)&lib, .link_map_addr = unowned_map, + .sync = &sync, + }; + pthread_t first_thread, second_thread; + + CHECK("duplicate-owner track", kzt_guest_library_track( + bindings, (library_t *)&lib) == 0); + CHECK("duplicate-owner registry", kzt_guest_registry_observe( + registry, &observed) == KZT_GUEST_REGISTRY_ADDED); + CHECK("duplicate-owner unowned registry", kzt_guest_registry_observe( + registry, &unowned) == KZT_GUEST_REGISTRY_ADDED); + observed_key = key(map, registry_generation(registry, map)); + unowned_key = key(unowned_map, + registry_generation(registry, unowned_map)); + CHECK("duplicate-owner observation", kzt_guest_library_note_observation( + bindings, &observed_key) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + CHECK("duplicate-owner unowned observation", + kzt_guest_library_note_observation(bindings, &unowned_key) == + KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + CHECK("duplicate-owner lease", kzt_guest_registry_source_lease_acquire( + registry, map, observed_key.generation, 0, &lease) == 0); + + kzt_guest_registry_test_set_before_retire_wait( + observation_retire_waiting, &sync); + kzt_guest_library_binding_test_set_before_registry_retire( + observation_before_registry_retire, &sync); + kzt_guest_library_binding_test_set_before_lifecycle_wait( + observation_before_lifecycle_wait, &sync); + CHECK("duplicate-owner first thread", pthread_create( + &first_thread, NULL, observation_unload_worker, &first) == 0); + pthread_mutex_lock(&sync.lock); + CHECK("duplicate-owner first blocked", timed_wait_for_count( + &sync.cond, &sync.lock, &sync.retire_waiters, 1) == 0); + pthread_mutex_unlock(&sync.lock); + CHECK("duplicate-owner second thread", pthread_create( + &second_thread, NULL, observation_unload_worker, &second) == 0); + pthread_mutex_lock(&sync.lock); + CHECK("duplicate-owner second entered exact wait", timed_wait_for_count( + &sync.cond, &sync.lock, &sync.lifecycle_waiters, 1) == 0); + CHECK("duplicate-owner wait identity", + sync.lifecycle_wait_bindings == bindings && + sync.lifecycle_wait_library == (library_t *)&lib); + CHECK("duplicate-owner second waits", second.started && !second.done); + CHECK("duplicate-owner only first hint retired", + sync.retire_calls == 1 && + sync.retire[0].bindings == bindings && + sync.retire[0].library == (library_t *)&lib && + sync.retire[0].from_observation && + sync.retire[0].key.link_map_addr == map && + sync.retire[0].key.generation == observed_key.generation); + pthread_mutex_unlock(&sync.lock); + + kzt_guest_registry_source_lease_release(&lease); + pthread_mutex_lock(&sync.lock); + CHECK("duplicate-owner first completes", timed_wait_for_flag( + &sync.cond, &sync.lock, &first.done) == 0); + CHECK("duplicate-owner second completes", timed_wait_for_flag( + &sync.cond, &sync.lock, &second.done) == 0); + pthread_mutex_unlock(&sync.lock); + CHECK("duplicate-owner first join", pthread_join(first_thread, NULL) == 0); + CHECK("duplicate-owner second join", pthread_join(second_thread, NULL) == 0); + CHECK("duplicate-owner second hint not claimed", + kzt_guest_registry_find_by_link_map( + registry, unowned_map, &snapshot) == 0 && snapshot && + snapshot->generation == unowned_key.generation && + snapshot->state != KZT_GUEST_OBJECT_UNLOADING && + snapshot->state != KZT_GUEST_OBJECT_DEAD); + kzt_guest_object_snapshot_free(snapshot); + CHECK("duplicate-owner unowned cleanup", kzt_guest_registry_retire( + registry, unowned_map, unowned_key.generation) == 0); + + kzt_guest_registry_test_set_before_retire_wait(NULL, NULL); + kzt_guest_library_binding_test_set_before_lifecycle_wait(NULL, NULL); + kzt_guest_library_binding_test_set_before_registry_retire(NULL, NULL); + pthread_cond_destroy(&sync.cond); + pthread_mutex_destroy(&sync.lock); + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_source_lease_unload_allows_provider_lookup(void) +{ + fake_library_t lib = { 60 }; + fake_library_t growth[24]; + uintptr_t map = 0xd000; + kzt_guest_object_observation_t observed = observation(map); + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_access_t access; + kzt_guest_registry_source_lease_t lease = { 0 }; + kzt_guest_library_binding_state_t lifecycle_state; + kzt_guest_library_binding_key_t binding_key; + lock_order_sync_t sync = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + lock_order_unload_arg_t unload_arg; + lock_order_lookup_arg_t lookup_arg; + pthread_t unload_thread; + pthread_t lookup_thread; + size_t live_entries = 1; + + CHECK("lock-order.registry", registry != NULL); + CHECK("lock-order.access", kzt_guest_library_access_init(&access) == 0); + CHECK("lock-order.track", kzt_guest_library_track( + access.bindings, (library_t *)&lib) == 0); + CHECK("lock-order.observe", kzt_guest_registry_observe( + registry, &observed) == KZT_GUEST_REGISTRY_ADDED); + binding_key = key(map, registry_generation(registry, map)); + CHECK("lock-order.bind", kzt_guest_library_bind( + access.bindings, &binding_key, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + + lookup_arg = (lock_order_lookup_arg_t) { + .access = &access, + .registry = registry, + .key = binding_key, + .lease = &lease, + .sync = &sync, + }; + CHECK("lock-order.lookup-thread", pthread_create( + &lookup_thread, NULL, lock_order_lookup_worker, &lookup_arg) == 0); + pthread_mutex_lock(&sync.lock); + CHECK("lock-order.lease-barrier", timed_wait_for_flag( + &sync.cond, &sync.lock, &sync.lease_ready) == 0); + pthread_mutex_unlock(&sync.lock); + + unload_arg = (lock_order_unload_arg_t) { + .bindings = access.bindings, + .registry = registry, + .library = (library_t *)&lib, + .link_map_addr = map, + }; + kzt_guest_registry_test_set_before_retire_wait( + lock_order_retire_waiting, &sync); + CHECK("lock-order.unload-thread", pthread_create( + &unload_thread, NULL, lock_order_unload_worker, &unload_arg) == 0); + pthread_mutex_lock(&sync.lock); + CHECK("lock-order.retire-wait-barrier", timed_wait_for_flag( + &sync.cond, &sync.lock, &sync.retire_waiting) == 0); + pthread_mutex_unlock(&sync.lock); + + /* Force lifecycle realloc while unload owns no binding-side pointer. */ + for (size_t i = 0; i < 24; ++i) { + growth[i].id = 100 + (int)i; + CHECK("lock-order.concurrent-lifecycle-growth", + kzt_guest_library_track( + access.bindings, (library_t *)&growth[i]) == 0); + } + + CHECK("lock-order.entry-closed", kzt_guest_library_binding_test_snapshot( + access.bindings, (library_t *)&lib, &lifecycle_state, NULL, + &live_entries) == 0 && + lifecycle_state == KZT_GUEST_LIBRARY_BINDING_UNLOADING && + live_entries == 0); + + pthread_mutex_lock(&sync.lock); + sync.start_lookup = 1; + pthread_cond_broadcast(&sync.cond); + CHECK("lock-order.lookup-not-blocked", timed_wait_for_flag( + &sync.cond, &sync.lock, &sync.lookup_done) == 0); + CHECK("lock-order.lookup-fast-fail", sync.lookup_done && + sync.lookup_result != 0); + pthread_mutex_unlock(&sync.lock); + + /* Also releases the old implementation if it deadlocks in lookup, so the + * test reports a bounded failure instead of hanging the suite. */ + kzt_guest_registry_source_lease_release(&lease); + CHECK("lock-order.lookup-join", pthread_join(lookup_thread, NULL) == 0); + CHECK("lock-order.unload-join", pthread_join(unload_thread, NULL) == 0); + CHECK("lock-order.final-dead", kzt_guest_library_binding_test_snapshot( + access.bindings, (library_t *)&lib, &lifecycle_state, NULL, + &live_entries) == 0 && + lifecycle_state == KZT_GUEST_LIBRARY_BINDING_DEAD && + live_entries == 0); + + kzt_guest_registry_test_set_before_retire_wait(NULL, NULL); + pthread_cond_destroy(&sync.cond); + pthread_mutex_destroy(&sync.lock); + kzt_guest_library_access_destroy(&access); + kzt_guest_registry_destroy(®istry); +} + +typedef struct access_teardown_arg { + kzt_guest_library_access_t *access; + pthread_mutex_t lock; + pthread_cond_t cond; + int started; + int done; +} access_teardown_arg_t; + +static void *access_teardown_thread(void *opaque) +{ + access_teardown_arg_t *arg = opaque; + pthread_mutex_lock(&arg->lock); + arg->started = 1; + pthread_cond_broadcast(&arg->cond); + pthread_mutex_unlock(&arg->lock); + kzt_guest_library_access_begin_teardown(arg->access); + pthread_mutex_lock(&arg->lock); + arg->done = 1; + pthread_cond_broadcast(&arg->cond); + pthread_mutex_unlock(&arg->lock); + return NULL; +} + +static void test_context_access_closes_before_destroy(void) +{ + fake_library_t lib = { 50 }; + kzt_guest_library_access_t access; + kzt_guest_library_binding_key_t k = key(0xc000, 50); + kzt_guest_library_handle_t held, probe; + pthread_t thread; + access_teardown_arg_t arg = { + .access = &access, + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + + CHECK("access init", kzt_guest_library_access_init(&access) == 0); + CHECK("access track", kzt_guest_library_track( + access.bindings, (library_t *)&lib) == 0); + CHECK("access bind", kzt_guest_library_bind( + access.bindings, &k, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("access acquire", kzt_guest_library_access_lookup( + &access, &k, &held) == 0); + CHECK("access teardown thread", pthread_create( + &thread, NULL, access_teardown_thread, &arg) == 0); + pthread_mutex_lock(&arg.lock); + while (!arg.started) pthread_cond_wait(&arg.cond, &arg.lock); + pthread_mutex_unlock(&arg.lock); + while (kzt_guest_library_lookup(access.bindings, &k, &probe) == 0) + kzt_guest_library_handle_release(&probe); + pthread_mutex_lock(&arg.lock); + CHECK("context teardown waits for held handle", !arg.done); + pthread_mutex_unlock(&arg.lock); + kzt_guest_library_handle_release(&held); + pthread_join(thread, NULL); + CHECK("closed context rejects lookup", kzt_guest_library_access_lookup( + &access, &k, &probe) != 0); + pthread_cond_destroy(&arg.cond); + pthread_mutex_destroy(&arg.lock); + kzt_guest_library_access_destroy(&access); +} + +int main(void) +{ + test_init_failure_is_fail_open(); + test_both_arrival_orders_and_retry(); + test_forced_growth_with_held_handle(); + test_pending_cancel_and_address_reuse(); + test_unclaimed_observation_address_reuse_pair_first(); + test_observation_first_unload_retires_registry_generation(); + test_unload_retires_only_hinted_unclaimed_observation(); + test_missing_unload_hint_preserves_unclaimed_observation(); + test_dead_library_stale_hint_does_not_retire_reused_address(); + test_tracking_allocation_failure_unload_is_fail_open(); + test_inactive_can_reload_but_destroyed_cannot(); + test_different_library_reuses_closed_callback_address(); + test_loader_pair_is_invisible_until_publish(); + test_loader_pair_cancel_and_failed_publish_are_invisible(); + test_failed_loader_keeps_normal_and_fallback_tombstones(); + test_loader_scope_identity_and_nesting(); + test_concurrent_loader_scopes_do_not_invalidate_each_other(); + test_loader_scope_cannot_reopen_unobserved_address(); + test_registry_retire_failures_are_observable_fail_open(); + test_lookup_and_unbind_threads(); + test_concurrent_observation_unloads_keep_exact_owners(); + test_duplicate_unload_waits_for_lifecycle_owner(); + test_source_lease_unload_allows_provider_lookup(); + test_context_access_closes_before_destroy(); + /* Keep last: a broken reader/publication protocol intentionally leaves + * its unload thread blocked so the red run remains bounded. */ + test_publish_while_reader_preserves_unload_wait(); + if (failures) fprintf(stderr, "%d failure(s)\n", failures); + return failures ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/tests/unit/kzt/test_guest_library_binding_teardown.c b/tests/unit/kzt/test_guest_library_binding_teardown.c new file mode 100644 index 00000000000..6232abac048 --- /dev/null +++ b/tests/unit/kzt/test_guest_library_binding_teardown.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include + +#include "kzt_guest_library_binding.h" + +typedef struct teardown_fixture { + kzt_guest_library_bindings_t *bindings; + int stop; + unsigned long admitted; +} teardown_fixture_t; + +static void *callback_worker(void *opaque) +{ + teardown_fixture_t *fixture = opaque; + + while (!__atomic_load_n(&fixture->stop, __ATOMIC_ACQUIRE)) { + kzt_guest_library_callback_access_t access = { 0 }; + if (kzt_guest_library_callback_access_begin( + fixture->bindings, 0xb10000, &access) == 0) { + __atomic_add_fetch(&fixture->admitted, 1, __ATOMIC_RELAXED); + kzt_guest_library_callback_access_end(&access); + } + } + return NULL; +} + +int main(void) +{ + teardown_fixture_t fixture = { + .bindings = kzt_guest_library_bindings_init(), + }; + pthread_t workers[4]; + + if (!fixture.bindings) return EXIT_FAILURE; + for (size_t i = 0; i < sizeof(workers) / sizeof(workers[0]); ++i) + if (pthread_create(&workers[i], NULL, callback_worker, &fixture) != 0) + return EXIT_FAILURE; + + while (__atomic_load_n(&fixture.admitted, __ATOMIC_ACQUIRE) < 1000) + sched_yield(); + kzt_guest_library_bindings_begin_teardown(fixture.bindings); + __atomic_store_n(&fixture.stop, 1, __ATOMIC_RELEASE); + for (size_t i = 0; i < sizeof(workers) / sizeof(workers[0]); ++i) + pthread_join(workers[i], NULL); + kzt_guest_library_bindings_destroy(&fixture.bindings); + puts("kzt-guest-library-binding-teardown: PASS"); + return EXIT_SUCCESS; +} diff --git a/tests/unit/kzt/test_guest_link_map_reader.c b/tests/unit/kzt/test_guest_link_map_reader.c new file mode 100644 index 00000000000..da4fd5f716f --- /dev/null +++ b/tests/unit/kzt/test_guest_link_map_reader.c @@ -0,0 +1,591 @@ +#include +#include +#include + +#include "target/i386/latx/include/kzt_guest_link_map_reader.h" +#include "target/i386/latx/include/kzt_guest_registry.h" + +typedef struct test_guest_link_map { + uint64_t l_addr; + uint64_t l_name; + uint64_t l_ld; + uint64_t l_next; + uint64_t l_prev; + uint64_t private_l_ns; + uint64_t private_l_map_start; + uint64_t private_l_map_end; +} test_guest_link_map_t; + +typedef struct fake_read_failure { + uintptr_t addr; + size_t size; +} fake_read_failure_t; + +typedef struct fake_reader_memory { + uintptr_t base; + size_t size; + const fake_read_failure_t *failures; + size_t failure_count; +} fake_reader_memory_t; + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_uintptr(const char *name, uintptr_t got, uintptr_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_string(const char *name, const char *got, + const char *expected) +{ + if ((!got && !expected) || (got && expected && !strcmp(got, expected))) { + return; + } + + fprintf(stderr, "%s: got \"%s\" expected \"%s\"\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static int ranges_overlap(uintptr_t left_addr, size_t left_size, + uintptr_t right_addr, size_t right_size) +{ + uintptr_t left_end = left_addr + left_size; + uintptr_t right_end = right_addr + right_size; + + return left_addr < right_end && right_addr < left_end; +} + +static int fake_read_memory(uintptr_t guest_addr, void *dst, size_t size, + void *opaque) +{ + fake_reader_memory_t *memory = opaque; + size_t i; + + for (i = 0; i < memory->failure_count; ++i) { + if (ranges_overlap(guest_addr, size, + memory->failures[i].addr, + memory->failures[i].size)) { + return -1; + } + } + + if (guest_addr < memory->base || + size > memory->size || + guest_addr - memory->base > memory->size - size) { + return -1; + } + + memcpy(dst, (const void *)guest_addr, size); + return 0; +} + +static kzt_guest_link_map_reader_ops_t fake_ops(fake_reader_memory_t *memory) +{ + kzt_guest_link_map_reader_ops_t ops = { + .read_memory = fake_read_memory, + .opaque = memory, + }; + + return ops; +} + +static fake_reader_memory_t fake_memory_for(void *base, size_t size, + const fake_read_failure_t *failures, + size_t failure_count) +{ + fake_reader_memory_t memory = { + .base = (uintptr_t)base, + .size = size, + .failures = failures, + .failure_count = failure_count, + }; + + return memory; +} + +static void init_link_map(test_guest_link_map_t *link_map, char *name) +{ + memset(link_map, 0, sizeof(*link_map)); + link_map->l_addr = 0x100000; + link_map->l_name = (uintptr_t)name; + link_map->l_ld = 0x101000; + /* Poison private glibc fields: the reader must never trust them. */ + link_map->private_l_ns = 7; + link_map->private_l_map_start = 0x100000; + link_map->private_l_map_end = 0x120000; +} + +static void test_valid_link_map_reads_complete_observation(void) +{ + struct { + test_guest_link_map_t link_map; + char guest_name[32]; + } guest = { 0 }; + kzt_guest_object_observation_t observation = { 0 }; + fake_reader_memory_t memory; + kzt_guest_link_map_reader_ops_t ops; + + strcpy(guest.guest_name, "/guest/libfoo.so"); + init_link_map(&guest.link_map, guest.guest_name); + memory = fake_memory_for(&guest, sizeof(guest), NULL, 0); + ops = fake_ops(&memory); + + check_int("read_observation.valid", + kzt_guest_link_map_read_observation((uintptr_t)&guest.link_map, + &ops, + &observation), + 0); + check_uintptr("observation.link_map_addr", + observation.link_map_addr, + (uintptr_t)&guest.link_map); + check_uintptr("observation.load_bias", + observation.load_bias.value, + 0x100000); + check_int("observation.load_bias.status", + observation.load_bias.status, + KZT_GUEST_FIELD_OK); + check_uintptr("observation.dynamic_addr", + observation.dynamic_addr.value, + 0x101000); + check_int("observation.dynamic_addr.status", + observation.dynamic_addr.status, + KZT_GUEST_FIELD_OK); + check_int("observation.map_start.status", + observation.map_start.status, + KZT_GUEST_FIELD_UNKNOWN); + check_int("observation.map_end.status", + observation.map_end.status, + KZT_GUEST_FIELD_UNKNOWN); + check_int("observation.namespace_id.status", + observation.namespace_id.status, + KZT_GUEST_FIELD_UNKNOWN); + check_string("observation.path", observation.path.value, + "/guest/libfoo.so"); + check_int("observation.path.status", + observation.path.status, + KZT_GUEST_FIELD_OK); + check_int("observation.soname.status", + observation.soname.status, + KZT_GUEST_FIELD_NOT_PARSED); + check_int("observation.dynamic_view_status", + observation.dynamic_view_status, + KZT_GUEST_FIELD_NOT_PARSED); + + kzt_guest_link_map_observation_clear(&observation); +} + +static void test_invalid_link_map_is_identity_failure(void) +{ + char unrelated[16] = { 0 }; + kzt_guest_object_observation_t observation = { 0 }; + fake_reader_memory_t memory = fake_memory_for(unrelated, + sizeof(unrelated), + NULL, + 0); + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + + check_int("read_observation.null-link-map", + kzt_guest_link_map_read_observation(0, &ops, &observation), + -1); + check_uintptr("null-link-map.identity", observation.link_map_addr, 0); + + check_int("read_observation.out-of-range-link-map", + kzt_guest_link_map_read_observation((uintptr_t)unrelated - 8, + &ops, + &observation), + 0); + check_uintptr("out-of-range.identity", observation.link_map_addr, + (uintptr_t)unrelated - 8); + check_int("out-of-range.load-bias", observation.load_bias.status, + KZT_GUEST_FIELD_READ_ERROR); + kzt_guest_link_map_observation_clear(&observation); +} + +static void test_field_read_failure_forms_partial_observation(void) +{ + struct { + test_guest_link_map_t link_map; + char guest_name[32]; + } guest = { 0 }; + fake_read_failure_t read_failures[] = { + { + .addr = (uintptr_t)&guest.link_map + + offsetof(test_guest_link_map_t, l_ld), + .size = sizeof(guest.link_map.l_ld), + }, + }; + fake_reader_memory_t memory; + kzt_guest_link_map_reader_ops_t ops; + kzt_guest_object_observation_t observation = { 0 }; + + strcpy(guest.guest_name, "/guest/libpartial.so"); + init_link_map(&guest.link_map, guest.guest_name); + memory = fake_memory_for(&guest, + sizeof(guest), + read_failures, + sizeof(read_failures) / sizeof(read_failures[0])); + ops = fake_ops(&memory); + + check_int("read_observation.partial", + kzt_guest_link_map_read_observation((uintptr_t)&guest.link_map, + &ops, + &observation), + 0); + check_uintptr("partial.identity", + observation.link_map_addr, + (uintptr_t)&guest.link_map); + check_int("partial.load-bias-ok", + observation.load_bias.status, + KZT_GUEST_FIELD_OK); + check_int("partial.dynamic-read-error", + observation.dynamic_addr.status, + KZT_GUEST_FIELD_READ_ERROR); + check_int("partial.map-end-unknown", + observation.map_end.status, + KZT_GUEST_FIELD_UNKNOWN); + check_string("partial.path", observation.path.value, + "/guest/libpartial.so"); + + kzt_guest_link_map_observation_clear(&observation); +} + +static void test_name_snapshot_status_matrix(void) +{ + char storage[128]; + fake_read_failure_t read_failures[] = { + { + .addr = (uintptr_t)(storage + 98), + .size = 1, + }, + }; + fake_reader_memory_t memory; + kzt_guest_link_map_reader_ops_t ops; + kzt_guest_string_field_t name = { 0 }; + + memset(storage, 0, sizeof(storage)); + memcpy(storage, "/guest/libok.so", sizeof("/guest/libok.so")); + storage[32] = 0; + memcpy(storage + 64, "abcd", 4); + memcpy(storage + 96, "broken", sizeof("broken")); + + memory = fake_memory_for(storage, + sizeof(storage), + read_failures, + sizeof(read_failures) / sizeof(read_failures[0])); + ops = fake_ops(&memory); + + check_int("name.valid", + kzt_guest_link_map_read_name_snapshot((uintptr_t)storage, + &ops, + 32, + &name), + 0); + check_int("name.valid.status", name.status, KZT_GUEST_FIELD_OK); + check_string("name.valid.value", name.value, "/guest/libok.so"); + kzt_guest_link_map_string_clear(&name); + + check_int("name.empty", + kzt_guest_link_map_read_name_snapshot((uintptr_t)(storage + 32), + &ops, + 32, + &name), + 0); + check_int("name.empty.status", name.status, KZT_GUEST_FIELD_OK); + check_string("name.empty.value", name.value, ""); + kzt_guest_link_map_string_clear(&name); + + check_int("name.truncated", + kzt_guest_link_map_read_name_snapshot((uintptr_t)(storage + 64), + &ops, + 4, + &name), + 0); + check_int("name.truncated.status", name.status, + KZT_GUEST_FIELD_TRUNCATED); + check_string("name.truncated.value", name.value, "abcd"); + kzt_guest_link_map_string_clear(&name); + + check_int("name.read-error", + kzt_guest_link_map_read_name_snapshot((uintptr_t)(storage + 96), + &ops, + 32, + &name), + 0); + check_int("name.read-error.status", name.status, + KZT_GUEST_FIELD_READ_ERROR); + check_true("name.read-error.value", name.value == NULL); + kzt_guest_link_map_string_clear(&name); + + check_int("name.unknown-null", + kzt_guest_link_map_read_name_snapshot(0, &ops, 32, &name), + 0); + check_int("name.unknown-null.status", name.status, + KZT_GUEST_FIELD_UNKNOWN); + check_true("name.unknown-null.value", name.value == NULL); + kzt_guest_link_map_string_clear(&name); +} + +static void test_allocation_failure_is_read_error_not_borrowed_pointer(void) +{ + char guest_name[] = "/guest/liballoc.so"; + fake_reader_memory_t memory = fake_memory_for(guest_name, + sizeof(guest_name), + NULL, + 0); + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_string_field_t name = { 0 }; + + kzt_guest_link_map_reader_test_set_alloc_failure_after(0); + check_int("name.alloc-failure", + kzt_guest_link_map_read_name_snapshot((uintptr_t)guest_name, + &ops, + 64, + &name), + 0); + kzt_guest_link_map_reader_test_set_alloc_failure_after(-1); + + check_int("name.alloc-failure.status", name.status, + KZT_GUEST_FIELD_READ_ERROR); + check_true("name.alloc-failure.value", name.value == NULL); +} + +static void test_main_namespace_walk_matches_verified_main_identity(void) +{ + test_guest_link_map_t maps[3] = { 0 }; + fake_reader_memory_t memory = fake_memory_for(maps, sizeof(maps), NULL, 0); + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_link_map_identity_t main_identity = { + .load_bias = 0x400000, + .dynamic_addr = 0x401000, + }; + uintptr_t namespace_head = 0; + + maps[0].l_addr = 0x400000; + maps[0].l_ld = 0x401000; + maps[1].l_addr = 0x700000; + maps[1].l_ld = 0x701000; + maps[1].l_prev = (uintptr_t)&maps[0]; + maps[2].l_addr = 0x900000; + maps[2].l_ld = 0x901000; + maps[2].l_prev = (uintptr_t)&maps[1]; + + check_int("namespace.main-current", + kzt_guest_link_map_classify_namespace( + (uintptr_t)&maps[0], &main_identity, 0, &ops, + &namespace_head), + 1); + check_uintptr("namespace.main-head", namespace_head, + (uintptr_t)&maps[0]); + check_int("namespace.main-via-prev", + kzt_guest_link_map_classify_namespace( + (uintptr_t)&maps[2], &main_identity, 0, &ops, + &namespace_head), + 1); + maps[0].l_ld = 0x402000; + check_int("namespace.same-bias-wrong-dynamic", + kzt_guest_link_map_classify_namespace( + (uintptr_t)&maps[2], &main_identity, 0, &ops, + &namespace_head), + 0); +} + +static void test_cached_main_head_uses_identity_not_load_bias(void) +{ + test_guest_link_map_t maps[4] = { 0 }; + test_guest_link_map_t *main_maps = &maps[0]; + test_guest_link_map_t *other_maps = &maps[2]; + fake_reader_memory_t memory = fake_memory_for(maps, sizeof(maps), NULL, 0); + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_link_map_identity_t main_identity = { + .load_bias = 0x400000, + .dynamic_addr = 0x401000, + }; + uintptr_t main_head = (uintptr_t)&main_maps[0]; + uintptr_t namespace_head = 0; + + /* Both namespace heads deliberately use the same load bias. */ + main_maps[0].l_addr = main_identity.load_bias; + main_maps[0].l_ld = main_identity.dynamic_addr; + main_maps[1].l_prev = main_head; + other_maps[0].l_addr = main_identity.load_bias; + other_maps[0].l_ld = 0x501000; + other_maps[1].l_prev = (uintptr_t)&other_maps[0]; + + check_int("namespace.cached-main", + kzt_guest_link_map_classify_namespace( + (uintptr_t)&main_maps[1], &main_identity, main_head, &ops, + &namespace_head), + 1); + check_int("namespace.cached-same-bias-non-main", + kzt_guest_link_map_classify_namespace( + (uintptr_t)&other_maps[1], &main_identity, main_head, &ops, + &namespace_head), + 0); +} + +static void test_link_map_identity_rejects_wrong_dynamic_address(void) +{ + test_guest_link_map_t map = { + .l_addr = 0x700000, + .l_ld = 0x701000, + }; + fake_reader_memory_t memory = fake_memory_for(&map, sizeof(map), NULL, 0); + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_link_map_identity_t identity = { 0 }; + + check_int("identity.read", + kzt_guest_link_map_read_identity((uintptr_t)&map, &ops, + &identity), + 0); + check_uintptr("identity.load-bias", identity.load_bias, map.l_addr); + check_uintptr("identity.dynamic", identity.dynamic_addr, map.l_ld); + check_int("identity.match", + kzt_guest_link_map_identity_matches(&identity, map.l_addr, + map.l_ld), + 1); + check_int("identity.wrong-dynamic", + kzt_guest_link_map_identity_matches(&identity, map.l_addr, + map.l_ld + 0x1000), + 0); +} + +static void test_predecessor_is_read_from_public_prefix(void) +{ + test_guest_link_map_t maps[2] = { 0 }; + fake_read_failure_t failure = { + .addr = (uintptr_t)&maps[1] + + offsetof(test_guest_link_map_t, l_prev), + .size = sizeof(maps[1].l_prev), + }; + fake_reader_memory_t memory = fake_memory_for(maps, sizeof(maps), NULL, 0); + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + uintptr_t predecessor = 0; + + maps[1].l_prev = (uintptr_t)&maps[0]; + check_int("predecessor.read", + kzt_guest_link_map_read_predecessor( + (uintptr_t)&maps[1], &ops, &predecessor), + 0); + check_uintptr("predecessor.value", predecessor, + (uintptr_t)&maps[0]); + + memory.failures = &failure; + memory.failure_count = 1; + predecessor = 1; + check_int("predecessor.read-failure", + kzt_guest_link_map_read_predecessor( + (uintptr_t)&maps[1], &ops, &predecessor), + -1); + check_uintptr("predecessor.failure-clears", predecessor, 0); +} + +static void test_main_namespace_walk_fails_open_on_bad_chain(void) +{ + test_guest_link_map_t maps[2] = { 0 }; + fake_read_failure_t failure = { + .addr = (uintptr_t)&maps[0] + + offsetof(test_guest_link_map_t, l_addr), + .size = sizeof(maps[0].l_addr), + }; + fake_reader_memory_t memory = fake_memory_for( + maps, sizeof(maps), &failure, 1); + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + + maps[0].l_addr = 0x400000; + maps[1].l_addr = 0x700000; + maps[1].l_prev = (uintptr_t)&maps[0]; + + check_int("namespace.read-failure", + kzt_guest_link_map_classify_namespace( + (uintptr_t)&maps[1], + &(kzt_guest_link_map_identity_t) { 0x400000, 0x401000 }, + 0, &ops, NULL), + -1); + + memory.failures = NULL; + memory.failure_count = 0; + maps[0].l_prev = (uintptr_t)&maps[1]; + check_int("namespace.cycle", + kzt_guest_link_map_classify_namespace( + (uintptr_t)&maps[1], + &(kzt_guest_link_map_identity_t) { 0x500000, 0x501000 }, + 0, &ops, NULL), + -1); + + check_int("namespace.invalid-args", + kzt_guest_link_map_classify_namespace( + 0, &(kzt_guest_link_map_identity_t) { 0x400000, 0x401000 }, + 0, &ops, NULL), + -1); +} + +static void test_main_namespace_walk_is_bounded(void) +{ + test_guest_link_map_t maps[257] = { 0 }; + fake_reader_memory_t memory = fake_memory_for(maps, sizeof(maps), NULL, 0); + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + size_t i; + + maps[0].l_addr = 0x400000; + for (i = 1; i < sizeof(maps) / sizeof(maps[0]); ++i) { + maps[i].l_addr = 0x500000 + i * 0x1000; + maps[i].l_prev = (uintptr_t)&maps[i - 1]; + } + + check_int("namespace.walk-limit", + kzt_guest_link_map_classify_namespace( + (uintptr_t)&maps[256], + &(kzt_guest_link_map_identity_t) { 0x400000, 0x401000 }, + 0, &ops, NULL), + -1); +} + +int main(void) +{ + test_valid_link_map_reads_complete_observation(); + test_invalid_link_map_is_identity_failure(); + test_field_read_failure_forms_partial_observation(); + test_name_snapshot_status_matrix(); + test_allocation_failure_is_read_error_not_borrowed_pointer(); + test_main_namespace_walk_matches_verified_main_identity(); + test_cached_main_head_uses_identity_not_load_bias(); + test_link_map_identity_rejects_wrong_dynamic_address(); + test_predecessor_is_read_from_public_prefix(); + test_main_namespace_walk_fails_open_on_bad_chain(); + test_main_namespace_walk_is_bounded(); + + if (failures) { + fprintf(stderr, "kzt-guest-link-map-reader: %d failure(s)\n", failures); + return 1; + } + + puts("kzt-guest-link-map-reader: all contract tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_guest_registry.c b/tests/unit/kzt/test_guest_registry.c new file mode 100644 index 00000000000..e5e273cf8d4 --- /dev/null +++ b/tests/unit/kzt/test_guest_registry.c @@ -0,0 +1,1261 @@ +#include +#include +#include +#include +#include + +#include "target/i386/latx/include/kzt_guest_registry.h" + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_not_int(const char *name, int got, int unexpected) +{ + if (got != unexpected) { + return; + } + + fprintf(stderr, "%s: got unexpected %d\n", name, got); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, expected); + ++failures; +} + +static void check_uintptr(const char *name, uintptr_t got, uintptr_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_string(const char *name, const char *got, + const char *expected) +{ + if ((!got && !expected) || (got && expected && !strcmp(got, expected))) { + return; + } + + fprintf(stderr, "%s: got \"%s\" expected \"%s\"\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static void check_contains(const char *name, const char *haystack, + const char *needle) +{ + if (haystack && needle && strstr(haystack, needle)) { + return; + } + + fprintf(stderr, "%s: missing \"%s\" in \"%s\"\n", name, + needle ? needle : "(null)", haystack ? haystack : "(null)"); + ++failures; +} + +static kzt_guest_object_observation_t make_observation(uintptr_t link_map_addr) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map_addr, + .load_bias = { 0x100000, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { 0x101000, KZT_GUEST_FIELD_OK }, + .map_start = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .map_end = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .namespace_id = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .path = { "/guest/libfoo.so", KZT_GUEST_FIELD_OK }, + .soname = { NULL, KZT_GUEST_FIELD_NOT_PARSED }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static kzt_guest_object_snapshot_t *find_snapshot( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr) +{ + kzt_guest_object_snapshot_t *snapshot = NULL; + + check_int("find_by_link_map", kzt_guest_registry_find_by_link_map( + registry, link_map_addr, &snapshot), 0); + check_true("find_by_link_map.snapshot", snapshot != NULL); + return snapshot; +} + +static void assert_not_found(kzt_guest_registry_t *registry, + uintptr_t link_map_addr) +{ + kzt_guest_object_snapshot_t *snapshot = (void *)0x1; + + check_not_int("find_by_link_map.missing", + kzt_guest_registry_find_by_link_map(registry, + link_map_addr, + &snapshot), + 0); + check_true("find_by_link_map.missing-snapshot", snapshot == NULL); +} + +static void assert_snapshot_identity( + const kzt_guest_object_snapshot_t *snapshot, + uintptr_t link_map_addr, + unsigned long generation) +{ + check_uintptr("snapshot.link_map_addr", snapshot->link_map_addr, + link_map_addr); + check_ulong("snapshot.generation", snapshot->generation, generation); + check_int("snapshot.state", snapshot->state, KZT_GUEST_OBJECT_DISCOVERED); +} + +static void test_first_and_repeat_observation_keep_generation(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x1000); + kzt_guest_object_snapshot_t *snapshot; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + observation.load_bias.value = 0; + observation.path.value = ""; + + check_int("observe.added", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + + snapshot = find_snapshot(registry, 0x1000); + assert_snapshot_identity(snapshot, 0x1000, 1); + check_int("snapshot.load_bias.status", snapshot->load_bias.status, + KZT_GUEST_FIELD_OK); + check_uintptr("snapshot.load_bias.value", snapshot->load_bias.value, 0); + check_int("snapshot.path.status", snapshot->path.status, + KZT_GUEST_FIELD_OK); + check_string("snapshot.path.value", snapshot->path.value, ""); + check_int("snapshot.soname.status", snapshot->soname.status, + KZT_GUEST_FIELD_NOT_PARSED); + check_int("snapshot.dynamic_view_status", snapshot->dynamic_view_status, + KZT_GUEST_FIELD_NOT_PARSED); + check_int("snapshot.map_start.status", snapshot->map_start.status, + KZT_GUEST_FIELD_UNKNOWN); + check_int("snapshot.namespace_id.status", snapshot->namespace_id.status, + KZT_GUEST_FIELD_UNKNOWN); + kzt_guest_object_snapshot_free(snapshot); + + check_int("observe.unchanged", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_UNCHANGED); + + snapshot = find_snapshot(registry, 0x1000); + assert_snapshot_identity(snapshot, 0x1000, 1); + kzt_guest_object_snapshot_free(snapshot); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_unknown_fields_are_completed_without_generation_change(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x2000); + kzt_guest_object_snapshot_t *snapshot; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + observation.load_bias.status = KZT_GUEST_FIELD_UNKNOWN; + observation.dynamic_addr.status = KZT_GUEST_FIELD_READ_ERROR; + observation.path.value = NULL; + observation.path.status = KZT_GUEST_FIELD_UNKNOWN; + + check_int("observe.partial-added", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + + snapshot = find_snapshot(registry, 0x2000); + assert_snapshot_identity(snapshot, 0x2000, 1); + check_int("snapshot.load_bias.unknown", snapshot->load_bias.status, + KZT_GUEST_FIELD_UNKNOWN); + check_int("snapshot.dynamic_addr.read-error", + snapshot->dynamic_addr.status, KZT_GUEST_FIELD_READ_ERROR); + check_int("snapshot.path.unknown", snapshot->path.status, + KZT_GUEST_FIELD_UNKNOWN); + kzt_guest_object_snapshot_free(snapshot); + + observation.load_bias.value = 0x220000; + observation.load_bias.status = KZT_GUEST_FIELD_OK; + observation.dynamic_addr.value = 0x221000; + observation.dynamic_addr.status = KZT_GUEST_FIELD_OK; + observation.path.value = "/guest/libfilled.so"; + observation.path.status = KZT_GUEST_FIELD_OK; + + check_int("observe.updated", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_UPDATED); + + snapshot = find_snapshot(registry, 0x2000); + assert_snapshot_identity(snapshot, 0x2000, 1); + check_int("snapshot.load_bias.ok", snapshot->load_bias.status, + KZT_GUEST_FIELD_OK); + check_uintptr("snapshot.load_bias.filled", snapshot->load_bias.value, + 0x220000); + check_int("snapshot.dynamic_addr.ok", snapshot->dynamic_addr.status, + KZT_GUEST_FIELD_OK); + check_uintptr("snapshot.dynamic_addr.filled", snapshot->dynamic_addr.value, + 0x221000); + check_int("snapshot.path.ok", snapshot->path.status, KZT_GUEST_FIELD_OK); + check_string("snapshot.path.filled", snapshot->path.value, + "/guest/libfilled.so"); + kzt_guest_object_snapshot_free(snapshot); + + observation.load_bias.status = KZT_GUEST_FIELD_READ_ERROR; + observation.dynamic_addr.status = KZT_GUEST_FIELD_UNKNOWN; + observation.path.value = NULL; + observation.path.status = KZT_GUEST_FIELD_READ_ERROR; + + check_int("observe.failed-fields-unchanged", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_UNCHANGED); + + snapshot = find_snapshot(registry, 0x2000); + assert_snapshot_identity(snapshot, 0x2000, 1); + check_int("snapshot.load_bias.still-ok", snapshot->load_bias.status, + KZT_GUEST_FIELD_OK); + check_uintptr("snapshot.load_bias.still-filled", snapshot->load_bias.value, + 0x220000); + check_int("snapshot.dynamic_addr.still-ok", snapshot->dynamic_addr.status, + KZT_GUEST_FIELD_OK); + check_uintptr("snapshot.dynamic_addr.still-filled", + snapshot->dynamic_addr.value, 0x221000); + check_int("snapshot.path.still-ok", snapshot->path.status, + KZT_GUEST_FIELD_OK); + check_string("snapshot.path.still-filled", snapshot->path.value, + "/guest/libfilled.so"); + kzt_guest_object_snapshot_free(snapshot); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_identity_conflict_preserves_original_record(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x3000); + kzt_guest_object_snapshot_t *snapshot; + kzt_guest_registry_dump_t dump = { 0 }; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + observation.load_bias.value = 0x300000; + observation.dynamic_addr.value = 0x301000; + observation.path.value = "/guest/liboriginal.so"; + + check_int("observe.added", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + + observation.load_bias.value = 0x310000; + observation.dynamic_addr.value = 0x311000; + observation.path.value = "/guest/libconflict.so"; + + check_int("observe.conflict", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_CONFLICT); + + snapshot = find_snapshot(registry, 0x3000); + assert_snapshot_identity(snapshot, 0x3000, 1); + check_uintptr("snapshot.load_bias.original", snapshot->load_bias.value, + 0x300000); + check_uintptr("snapshot.dynamic_addr.original", + snapshot->dynamic_addr.value, 0x301000); + check_string("snapshot.path.original", snapshot->path.value, + "/guest/liboriginal.so"); + kzt_guest_object_snapshot_free(snapshot); + + check_int("dump.snapshot", + kzt_guest_registry_dump_snapshot(registry, &dump), + 0); + check_ulong("dump.count", dump.count, 1); + assert_snapshot_identity(&dump.objects[0], 0x3000, 1); + check_string("dump.path.original", dump.objects[0].path.value, + "/guest/liboriginal.so"); + kzt_guest_registry_dump_free(&dump); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_partial_observation_and_invalid_identity(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0); + kzt_guest_object_snapshot_t *snapshot; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + check_int("observe.invalid-link-map", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ERROR); + assert_not_found(registry, 0); + + observation = make_observation(0x4000); + observation.load_bias.status = KZT_GUEST_FIELD_READ_ERROR; + observation.dynamic_addr.status = KZT_GUEST_FIELD_UNKNOWN; + observation.path.value = "/guest/path-truncated"; + observation.path.status = KZT_GUEST_FIELD_TRUNCATED; + + check_int("observe.partial-added", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + + snapshot = find_snapshot(registry, 0x4000); + assert_snapshot_identity(snapshot, 0x4000, 1); + check_int("snapshot.load_bias.read-error", snapshot->load_bias.status, + KZT_GUEST_FIELD_READ_ERROR); + check_int("snapshot.dynamic_addr.unknown", snapshot->dynamic_addr.status, + KZT_GUEST_FIELD_UNKNOWN); + check_int("snapshot.path.truncated", snapshot->path.status, + KZT_GUEST_FIELD_TRUNCATED); + check_string("snapshot.path.truncated-value", snapshot->path.value, + "/guest/path-truncated"); + kzt_guest_object_snapshot_free(snapshot); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_query_and_dump_snapshots_are_caller_owned(void) +{ + static const char path_literal[] = "/guest/libowned.so"; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x5000); + kzt_guest_object_snapshot_t *first; + kzt_guest_object_snapshot_t *second; + kzt_guest_registry_dump_t dump = { 0 }; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + observation.path.value = path_literal; + check_int("observe.added", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + + first = find_snapshot(registry, 0x5000); + second = find_snapshot(registry, 0x5000); + check_string("first.path", first->path.value, path_literal); + check_string("second.path", second->path.value, path_literal); + check_true("first.path.not-observation", first->path.value != path_literal); + check_true("second.path.not-observation", + second->path.value != path_literal); + check_true("snapshots.path.distinct", + first->path.value != second->path.value); + + check_int("dump.snapshot", + kzt_guest_registry_dump_snapshot(registry, &dump), + 0); + check_ulong("dump.count", dump.count, 1); + check_string("dump.path", dump.objects[0].path.value, path_literal); + check_true("dump.path.not-observation", + dump.objects[0].path.value != path_literal); + check_true("dump.path.not-first", + dump.objects[0].path.value != first->path.value); + check_true("dump.path.not-second", + dump.objects[0].path.value != second->path.value); + + kzt_guest_registry_dump_free(&dump); + kzt_guest_object_snapshot_free(first); + kzt_guest_object_snapshot_free(second); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +typedef struct dump_text_sink { + char text[8192]; + size_t used; + unsigned long calls; + kzt_guest_registry_t *registry; +} dump_text_sink_t; + +static int collect_dump_text_line(const char *line, void *opaque) +{ + dump_text_sink_t *sink = opaque; + kzt_guest_registry_diagnostics_t diagnostics = { 0 }; + size_t len; + size_t remaining; + + if (sink->registry) { + check_int("dump.sink-can-query-registry", + kzt_guest_registry_get_diagnostics(sink->registry, + &diagnostics), + 0); + } + + ++sink->calls; + len = strlen(line); + remaining = sizeof(sink->text) - sink->used; + if (remaining <= 2) { + return 0; + } + if (len >= remaining - 1) { + len = remaining - 2; + } + memcpy(sink->text + sink->used, line, len); + sink->used += len; + sink->text[sink->used++] = '\n'; + sink->text[sink->used] = '\0'; + return 0; +} + +static void test_diagnostics_are_opt_in_and_throttled(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x8000); + kzt_guest_registry_observation_diagnostic_t diagnostic = { 0 }; + kzt_guest_registry_diagnostic_report_t report; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 2, + }; + dump_text_sink_t sink = { 0 }; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + check_int("observe.diagnostic-disabled", + kzt_guest_registry_observe_with_diagnostic(registry, + &observation, + &diagnostic), + KZT_GUEST_REGISTRY_ADDED); + check_int("diagnostic.default-enabled", diagnostic.enabled, 0); + check_int("diagnostic.default-emitted", diagnostic.emitted, 0); + check_int("diagnostic.default-result", diagnostic.result, + KZT_GUEST_REGISTRY_ADDED); + + check_int("diagnostic.report.disabled", + kzt_guest_registry_get_diagnostic_report(registry, &report), + 0); + check_int("report.default-enabled", report.config.enabled, 0); + check_ulong("report.default-added-events", + report.events[KZT_GUEST_REGISTRY_ADDED].observed, 0); + check_ulong("report.default-added-counter", report.counters.added, 1); + + check_int("diagnostic.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + check_int("observe.unchanged.first", + kzt_guest_registry_observe_with_diagnostic(registry, + &observation, + &diagnostic), + KZT_GUEST_REGISTRY_UNCHANGED); + check_int("diagnostic.first-enabled", diagnostic.enabled, 1); + check_int("diagnostic.first-emitted", diagnostic.emitted, 1); + check_ulong("diagnostic.first-observations", + diagnostic.result_observations, 1); + check_ulong("diagnostic.first-suppressed", diagnostic.result_suppressed, + 0); + + check_int("observe.unchanged.second", + kzt_guest_registry_observe_with_diagnostic(registry, + &observation, + &diagnostic), + KZT_GUEST_REGISTRY_UNCHANGED); + check_int("diagnostic.second-emitted", diagnostic.emitted, 1); + check_ulong("diagnostic.second-observations", + diagnostic.result_observations, 2); + + check_int("observe.unchanged.third", + kzt_guest_registry_observe_with_diagnostic(registry, + &observation, + &diagnostic), + KZT_GUEST_REGISTRY_UNCHANGED); + check_int("diagnostic.third-emitted", diagnostic.emitted, 0); + check_ulong("diagnostic.third-observations", + diagnostic.result_observations, 3); + check_ulong("diagnostic.third-suppressed", diagnostic.result_suppressed, + 1); + + check_int("diagnostic.report.enabled", + kzt_guest_registry_get_diagnostic_report(registry, &report), + 0); + check_int("report.enabled", report.config.enabled, 1); + check_ulong("report.throttle-limit", report.config.throttle_limit, 2); + check_ulong("report.unchanged-observed", + report.events[KZT_GUEST_REGISTRY_UNCHANGED].observed, 3); + check_ulong("report.unchanged-emitted", + report.events[KZT_GUEST_REGISTRY_UNCHANGED].emitted, 2); + check_ulong("report.unchanged-suppressed", + report.events[KZT_GUEST_REGISTRY_UNCHANGED].suppressed, 1); + check_uintptr("report.unchanged-last-link-map", + report.events[KZT_GUEST_REGISTRY_UNCHANGED] + .last_link_map_addr, + 0x8000); + + sink.registry = registry; + check_int("dump.text", + kzt_guest_registry_dump_text(registry, collect_dump_text_line, + &sink), + 0); + check_true("dump.text-called", sink.calls > 0); + check_contains("dump.text-summary", sink.text, + "enabled=1 throttle_limit=2"); + check_contains("dump.text-event", sink.text, + "result=unchanged observed=3 emitted=2 suppressed=1"); + check_contains("dump.text-object", sink.text, + "object link_map=0x8000 generation=1 state=0"); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_init_failure_creates_disabled_registry_diagnostics(void) +{ + kzt_guest_registry_t *registry; + kzt_guest_object_observation_t observation = make_observation(0x6000); + kzt_guest_registry_diagnostics_t diagnostics = { 0 }; + + kzt_guest_registry_test_set_alloc_failure_after(1); + registry = kzt_guest_registry_init(); + kzt_guest_registry_test_set_alloc_failure_after(-1); + + check_true("registry.disabled-init", registry != NULL); + if (!registry) { + return; + } + + check_int("observe.disabled", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_DISABLED); + + check_int("diagnostics.disabled", + kzt_guest_registry_get_diagnostics(registry, &diagnostics), 0); + check_ulong("diagnostics.init-failures", diagnostics.init_failures, 1); + check_ulong("diagnostics.alloc-failures", diagnostics.allocation_failures, + 1); + check_ulong("diagnostics.disabled-observe", diagnostics.disabled, 1); + check_ulong("diagnostics.observations", diagnostics.observations, 1); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_cond_init_failure_is_clean(void) +{ + kzt_guest_registry_t *registry; + + kzt_guest_registry_test_fail_next_cond_init(); + registry = kzt_guest_registry_init(); + check_true("cond-init.failure", registry == NULL); + + registry = kzt_guest_registry_init(); + check_true("cond-init.next-succeeds", registry != NULL); + kzt_guest_registry_destroy(®istry); +} + +static void test_source_lease_acquire_release(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x6800); + kzt_guest_object_observation_t unknown_namespace = + make_observation(0x6880); + kzt_guest_object_snapshot_t *snapshot; + kzt_guest_registry_source_lease_t lease = { 0 }; + unsigned long generation; + + check_true("lease.registry", registry != NULL); + if (!registry) { + return; + } + observation.namespace_id = (kzt_guest_scalar_field_t) { + .value = 0, + .status = KZT_GUEST_FIELD_OK, + }; + check_int("lease.observe", kzt_guest_registry_observe( + registry, &observation), KZT_GUEST_REGISTRY_ADDED); + snapshot = find_snapshot(registry, observation.link_map_addr); + generation = snapshot->generation; + kzt_guest_object_snapshot_free(snapshot); + + check_int("lease.acquire", kzt_guest_registry_source_lease_acquire( + registry, observation.link_map_addr, generation, 0, + &lease), 0); + check_true("lease.active", lease.active == 1 && + lease.registry == registry && + lease.namespace_id == 0); + snapshot = find_snapshot(registry, observation.link_map_addr); + check_ulong("lease.snapshot-active", snapshot->active_source_leases, 1); + kzt_guest_object_snapshot_free(snapshot); + + kzt_guest_registry_source_lease_release(&lease); + check_true("lease.cleared", lease.active == 0 && lease.registry == NULL); + snapshot = find_snapshot(registry, observation.link_map_addr); + check_ulong("lease.snapshot-idle", snapshot->active_source_leases, 0); + kzt_guest_object_snapshot_free(snapshot); + kzt_guest_registry_source_lease_release(&lease); + + check_not_int("lease.stale", kzt_guest_registry_source_lease_acquire( + registry, observation.link_map_addr, generation + 1, + 0, &lease), 0); + check_true("lease.stale-cleared", lease.active == 0); + check_not_int("lease.namespace-mismatch", + kzt_guest_registry_source_lease_acquire( + registry, observation.link_map_addr, generation, 1, + &lease), 0); + check_true("lease.namespace-mismatch-cleared", lease.active == 0); + check_int("lease.unknown-namespace-observe", kzt_guest_registry_observe( + registry, &unknown_namespace), KZT_GUEST_REGISTRY_ADDED); + snapshot = find_snapshot(registry, unknown_namespace.link_map_addr); + generation = snapshot->generation; + kzt_guest_object_snapshot_free(snapshot); + check_not_int("lease.unknown-namespace", + kzt_guest_registry_source_lease_acquire( + registry, unknown_namespace.link_map_addr, generation, + 0, &lease), 0); + check_true("lease.unknown-namespace-cleared", lease.active == 0); + kzt_guest_registry_destroy(®istry); +} + +typedef struct retire_wait_fixture { + pthread_mutex_t lock; + kzt_guest_registry_t *registry; + uintptr_t link_map_addr; + unsigned long generation; + int called; + int done; + int result; +} retire_wait_fixture_t; + +typedef struct registry_hook_sync { + pthread_mutex_t lock; + pthread_cond_t cond; + int before_wait_count; + int api_entered; + int release_api; + int destroy_disabled; +} registry_hook_sync_t; + +static void before_retire_wait_hook(void *opaque) +{ + registry_hook_sync_t *sync = opaque; + + pthread_mutex_lock(&sync->lock); + ++sync->before_wait_count; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); +} + +static void after_api_enter_hook(void *opaque) +{ + registry_hook_sync_t *sync = opaque; + + pthread_mutex_lock(&sync->lock); + sync->api_entered = 1; + pthread_cond_broadcast(&sync->cond); + while (!sync->release_api) { + pthread_cond_wait(&sync->cond, &sync->lock); + } + pthread_mutex_unlock(&sync->lock); +} + +static void after_destroy_disable_hook(void *opaque) +{ + registry_hook_sync_t *sync = opaque; + + pthread_mutex_lock(&sync->lock); + sync->destroy_disabled = 1; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); +} + +static int registry_hook_sync_init(registry_hook_sync_t *sync) +{ + memset(sync, 0, sizeof(*sync)); + if (pthread_mutex_init(&sync->lock, NULL) != 0) { + return -1; + } + if (pthread_cond_init(&sync->cond, NULL) != 0) { + pthread_mutex_destroy(&sync->lock); + return -1; + } + return 0; +} + +static void registry_hook_sync_destroy(registry_hook_sync_t *sync) +{ + pthread_cond_destroy(&sync->cond); + pthread_mutex_destroy(&sync->lock); +} + +static void *retire_wait_worker(void *opaque) +{ + retire_wait_fixture_t *fixture = opaque; + int result; + + pthread_mutex_lock(&fixture->lock); + fixture->called = 1; + pthread_mutex_unlock(&fixture->lock); + result = kzt_guest_registry_retire( + fixture->registry, fixture->link_map_addr, fixture->generation); + pthread_mutex_lock(&fixture->lock); + fixture->result = result; + fixture->done = 1; + pthread_mutex_unlock(&fixture->lock); + return NULL; +} + +static void test_retire_waits_for_source_lease(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x6900); + kzt_guest_object_snapshot_t *snapshot; + kzt_guest_registry_source_lease_t lease = { 0 }; + retire_wait_fixture_t fixture; + pthread_t thread; + struct timespec start; + int done = 0; + int thread_created; + + check_true("retire-wait.registry", registry != NULL); + if (!registry) { + return; + } + observation.namespace_id = (kzt_guest_scalar_field_t) { + .value = 0, + .status = KZT_GUEST_FIELD_OK, + }; + check_int("retire-wait.observe", kzt_guest_registry_observe( + registry, &observation), KZT_GUEST_REGISTRY_ADDED); + snapshot = find_snapshot(registry, observation.link_map_addr); + memset(&fixture, 0, sizeof(fixture)); + fixture.registry = registry; + fixture.link_map_addr = observation.link_map_addr; + fixture.generation = snapshot->generation; + kzt_guest_object_snapshot_free(snapshot); + check_int("retire-wait.lock", pthread_mutex_init(&fixture.lock, NULL), 0); + check_int("retire-wait.acquire", kzt_guest_registry_source_lease_acquire( + registry, fixture.link_map_addr, fixture.generation, + 0, &lease), 0); + thread_created = pthread_create( + &thread, NULL, retire_wait_worker, &fixture); + check_int("retire-wait.thread", thread_created, 0); + if (thread_created != 0) { + kzt_guest_registry_source_lease_release(&lease); + pthread_mutex_destroy(&fixture.lock); + kzt_guest_registry_destroy(®istry); + return; + } + + clock_gettime(CLOCK_MONOTONIC, &start); + for (;;) { + struct timespec now; + + pthread_mutex_lock(&fixture.lock); + done = fixture.done; + pthread_mutex_unlock(&fixture.lock); + snapshot = NULL; + if (kzt_guest_registry_find_by_link_map( + registry, fixture.link_map_addr, &snapshot) != 0) { + kzt_guest_object_snapshot_free(snapshot); + break; + } + kzt_guest_object_snapshot_free(snapshot); + clock_gettime(CLOCK_MONOTONIC, &now); + if (done || now.tv_sec - start.tv_sec >= 2) { + break; + } + sched_yield(); + } + pthread_mutex_lock(&fixture.lock); + done = fixture.done; + pthread_mutex_unlock(&fixture.lock); + check_true("retire-wait.blocked", done == 0); + + kzt_guest_registry_source_lease_release(&lease); + check_int("retire-wait.join", pthread_join(thread, NULL), 0); + check_true("retire-wait.done", fixture.done == 1); + check_int("retire-wait.result", fixture.result, 0); + check_not_int("retire-wait.dead", kzt_guest_registry_find_by_link_map( + registry, fixture.link_map_addr, &snapshot), 0); + check_true("retire-wait.no-snapshot", snapshot == NULL); + pthread_mutex_destroy(&fixture.lock); + kzt_guest_registry_destroy(®istry); +} + +static void test_double_retire_cannot_kill_reused_generation(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x6a00); + kzt_guest_registry_source_lease_t lease = { 0 }; + kzt_guest_object_snapshot_t *snapshot; + retire_wait_fixture_t retire = { 0 }; + registry_hook_sync_t sync; + pthread_t thread; + unsigned long old_generation; + unsigned long new_generation; + + check_true("double-retire.registry", registry != NULL); + if (!registry || registry_hook_sync_init(&sync) != 0) { + kzt_guest_registry_destroy(®istry); + return; + } + observation.namespace_id = (kzt_guest_scalar_field_t) { + .value = 0, + .status = KZT_GUEST_FIELD_OK, + }; + check_int("double-retire.observe", kzt_guest_registry_observe( + registry, &observation), KZT_GUEST_REGISTRY_ADDED); + snapshot = find_snapshot(registry, observation.link_map_addr); + old_generation = snapshot->generation; + kzt_guest_object_snapshot_free(snapshot); + check_int("double-retire.lease", kzt_guest_registry_source_lease_acquire( + registry, observation.link_map_addr, old_generation, + 0, &lease), 0); + kzt_guest_registry_test_set_before_retire_wait( + before_retire_wait_hook, &sync); + retire.registry = registry; + retire.link_map_addr = observation.link_map_addr; + retire.generation = old_generation; + check_int("double-retire.lock", pthread_mutex_init( + &retire.lock, NULL), 0); + check_int("double-retire.thread", pthread_create( + &thread, NULL, retire_wait_worker, &retire), 0); + pthread_mutex_lock(&sync.lock); + while (sync.before_wait_count < 1) { + pthread_cond_wait(&sync.cond, &sync.lock); + } + pthread_mutex_unlock(&sync.lock); + + check_not_int("double-retire.second-rejected", + kzt_guest_registry_retire( + registry, observation.link_map_addr, old_generation), + 0); + kzt_guest_registry_source_lease_release(&lease); + check_int("double-retire.join", pthread_join(thread, NULL), 0); + check_int("double-retire.first-success", retire.result, 0); + check_int("double-retire.reobserve", kzt_guest_registry_observe( + registry, &observation), KZT_GUEST_REGISTRY_ADDED); + snapshot = find_snapshot(registry, observation.link_map_addr); + new_generation = snapshot->generation; + kzt_guest_object_snapshot_free(snapshot); + check_true("double-retire.new-generation", + new_generation > old_generation); + check_not_int("double-retire.old-generation-rejected", + kzt_guest_registry_retire( + registry, observation.link_map_addr, old_generation), + 0); + snapshot = find_snapshot(registry, observation.link_map_addr); + check_true("double-retire.reused-live", snapshot != NULL && + snapshot->generation == new_generation); + kzt_guest_object_snapshot_free(snapshot); + kzt_guest_registry_test_set_before_retire_wait(NULL, NULL); + pthread_mutex_destroy(&retire.lock); + registry_hook_sync_destroy(&sync); + kzt_guest_registry_destroy(®istry); +} + +typedef struct simultaneous_retire_fixture { + kzt_guest_registry_t *registry; + uintptr_t link_map_addr; + unsigned long generation; + pthread_barrier_t *barrier; + int result; +} simultaneous_retire_fixture_t; + +static void *simultaneous_retire_worker(void *opaque) +{ + simultaneous_retire_fixture_t *fixture = opaque; + + pthread_barrier_wait(fixture->barrier); + fixture->result = kzt_guest_registry_retire( + fixture->registry, fixture->link_map_addr, fixture->generation); + return NULL; +} + +static void test_simultaneous_retire_has_one_owner(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x6a80); + kzt_guest_object_snapshot_t *snapshot; + kzt_guest_registry_dump_t dump = { 0 }; + simultaneous_retire_fixture_t fixtures[2]; + pthread_barrier_t barrier; + pthread_t threads[2]; + unsigned long generation; + int successes = 0; + int rejections = 0; + + check_true("one-retire.registry", registry != NULL); + if (!registry) { + return; + } + check_int("one-retire.observe", kzt_guest_registry_observe( + registry, &observation), KZT_GUEST_REGISTRY_ADDED); + snapshot = find_snapshot(registry, observation.link_map_addr); + generation = snapshot->generation; + kzt_guest_object_snapshot_free(snapshot); + check_int("one-retire.barrier", pthread_barrier_init( + &barrier, NULL, 3), 0); + for (int i = 0; i < 2; ++i) { + fixtures[i] = (simultaneous_retire_fixture_t) { + .registry = registry, + .link_map_addr = observation.link_map_addr, + .generation = generation, + .barrier = &barrier, + .result = -2, + }; + check_int("one-retire.thread", pthread_create( + &threads[i], NULL, simultaneous_retire_worker, + &fixtures[i]), 0); + } + pthread_barrier_wait(&barrier); + for (int i = 0; i < 2; ++i) { + check_int("one-retire.join", pthread_join(threads[i], NULL), 0); + successes += fixtures[i].result == 0; + rejections += fixtures[i].result != 0; + } + check_int("one-retire.exactly-one-success", successes, 1); + check_int("one-retire.exactly-one-rejection", rejections, 1); + check_int("one-retire.dump", kzt_guest_registry_dump_snapshot( + registry, &dump), 0); + check_ulong("one-retire.single-object", dump.count, 1); + if (dump.count == 1) { + check_ulong("one-retire.same-generation", + dump.objects[0].generation, generation); + check_int("one-retire.final-dead", dump.objects[0].state, + KZT_GUEST_OBJECT_DEAD); + } + kzt_guest_registry_dump_free(&dump); + pthread_barrier_destroy(&barrier); + kzt_guest_registry_destroy(®istry); +} + +typedef struct registry_observe_worker { + kzt_guest_registry_t *registry; + kzt_guest_object_observation_t observation; + kzt_guest_registry_result_t result; +} registry_observe_worker_t; + +typedef struct registry_destroy_worker { + pthread_mutex_t lock; + pthread_cond_t cond; + kzt_guest_registry_t *registry; + int called; + int done; +} registry_destroy_worker_t; + +static void *registry_observe_worker_main(void *opaque) +{ + registry_observe_worker_t *worker = opaque; + + worker->result = kzt_guest_registry_observe( + worker->registry, &worker->observation); + return NULL; +} + +static void *registry_destroy_worker_main(void *opaque) +{ + registry_destroy_worker_t *worker = opaque; + + pthread_mutex_lock(&worker->lock); + worker->called = 1; + pthread_cond_broadcast(&worker->cond); + pthread_mutex_unlock(&worker->lock); + kzt_guest_registry_destroy(&worker->registry); + pthread_mutex_lock(&worker->lock); + worker->done = 1; + pthread_cond_broadcast(&worker->cond); + pthread_mutex_unlock(&worker->lock); + return NULL; +} + +static void test_destroy_drains_waiter_and_inflight_api(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x6b00); + kzt_guest_registry_source_lease_t lease = { 0 }; + kzt_guest_object_snapshot_t *snapshot; + retire_wait_fixture_t retire = { 0 }; + registry_observe_worker_t observe_worker; + registry_destroy_worker_t destroy_worker; + registry_hook_sync_t sync; + pthread_t retire_thread; + pthread_t observe_thread; + pthread_t destroy_thread; + + check_true("destroy-race.registry", registry != NULL); + if (!registry || registry_hook_sync_init(&sync) != 0) { + kzt_guest_registry_destroy(®istry); + return; + } + observation.namespace_id = (kzt_guest_scalar_field_t) { + .value = 0, + .status = KZT_GUEST_FIELD_OK, + }; + check_int("destroy-race.observe", kzt_guest_registry_observe( + registry, &observation), KZT_GUEST_REGISTRY_ADDED); + snapshot = find_snapshot(registry, observation.link_map_addr); + retire.registry = registry; + retire.link_map_addr = observation.link_map_addr; + retire.generation = snapshot->generation; + kzt_guest_object_snapshot_free(snapshot); + check_int("destroy-race.retire-lock", pthread_mutex_init( + &retire.lock, NULL), 0); + check_int("destroy-race.lease", kzt_guest_registry_source_lease_acquire( + registry, retire.link_map_addr, retire.generation, + 0, &lease), 0); + kzt_guest_registry_test_set_before_retire_wait( + before_retire_wait_hook, &sync); + check_int("destroy-race.retire-thread", pthread_create( + &retire_thread, NULL, retire_wait_worker, &retire), 0); + pthread_mutex_lock(&sync.lock); + while (sync.before_wait_count < 1) { + pthread_cond_wait(&sync.cond, &sync.lock); + } + pthread_mutex_unlock(&sync.lock); + + observe_worker = (registry_observe_worker_t) { + .registry = registry, + .observation = make_observation(0x6c00), + }; + kzt_guest_registry_test_set_after_api_enter(after_api_enter_hook, &sync); + check_int("destroy-race.observe-thread", pthread_create( + &observe_thread, NULL, registry_observe_worker_main, + &observe_worker), 0); + pthread_mutex_lock(&sync.lock); + while (!sync.api_entered) { + pthread_cond_wait(&sync.cond, &sync.lock); + } + pthread_mutex_unlock(&sync.lock); + + memset(&destroy_worker, 0, sizeof(destroy_worker)); + destroy_worker.registry = registry; + check_int("destroy-race.destroy-lock", pthread_mutex_init( + &destroy_worker.lock, NULL), 0); + check_int("destroy-race.destroy-cond", pthread_cond_init( + &destroy_worker.cond, NULL), 0); + kzt_guest_registry_test_set_after_destroy_disable( + after_destroy_disable_hook, &sync); + check_int("destroy-race.destroy-thread", pthread_create( + &destroy_thread, NULL, registry_destroy_worker_main, + &destroy_worker), 0); + pthread_mutex_lock(&destroy_worker.lock); + while (!destroy_worker.called) { + pthread_cond_wait(&destroy_worker.cond, &destroy_worker.lock); + } + check_true("destroy-race.blocked", destroy_worker.done == 0); + pthread_mutex_unlock(&destroy_worker.lock); + pthread_mutex_lock(&sync.lock); + while (!sync.destroy_disabled) { + pthread_cond_wait(&sync.cond, &sync.lock); + } + pthread_mutex_unlock(&sync.lock); + + pthread_mutex_lock(&sync.lock); + sync.release_api = 1; + pthread_cond_broadcast(&sync.cond); + pthread_mutex_unlock(&sync.lock); + check_int("destroy-race.observe-join", pthread_join( + observe_thread, NULL), 0); + check_int("destroy-race.observe-disabled", observe_worker.result, + KZT_GUEST_REGISTRY_DISABLED); + kzt_guest_registry_source_lease_release(&lease); + check_int("destroy-race.retire-join", pthread_join( + retire_thread, NULL), 0); + check_not_int("destroy-race.retire-cancelled", retire.result, 0); + check_int("destroy-race.destroy-join", pthread_join( + destroy_thread, NULL), 0); + check_true("destroy-race.destroyed", destroy_worker.done == 1 && + destroy_worker.registry == NULL); + + kzt_guest_registry_test_set_after_api_enter(NULL, NULL); + kzt_guest_registry_test_set_before_retire_wait(NULL, NULL); + kzt_guest_registry_test_set_after_destroy_disable(NULL, NULL); + pthread_cond_destroy(&destroy_worker.cond); + pthread_mutex_destroy(&destroy_worker.lock); + pthread_mutex_destroy(&retire.lock); + registry_hook_sync_destroy(&sync); + registry = NULL; +} + +static void test_destroyed_registry_rejects_new_observation(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x7000); + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); + check_int("observe.after-destroy", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_DISABLED); +} + +static void test_retired_address_gets_new_generation(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x7100); + kzt_guest_object_snapshot_t *snapshot; + kzt_guest_registry_dump_t dump = { 0 }; + check_int("reuse.first", kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + snapshot = find_snapshot(registry, observation.link_map_addr); + unsigned long first_generation = snapshot->generation; + kzt_guest_object_snapshot_free(snapshot); + check_int("reuse.retire", kzt_guest_registry_retire( + registry, observation.link_map_addr, first_generation), 0); + check_int("reuse.dead.filtered", kzt_guest_registry_find_by_link_map( + registry, observation.link_map_addr, &snapshot), -1); + check_true("reuse.dead.no-snapshot", snapshot == NULL); + check_int("reuse.dead.dump", kzt_guest_registry_dump_snapshot( + registry, &dump), 0); + check_ulong("reuse.dead.dump-count", dump.count, 1); + check_int("reuse.dead.diagnostic-state", dump.objects[0].state, + KZT_GUEST_OBJECT_DEAD); + kzt_guest_registry_dump_free(&dump); + check_int("reuse.observe", kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + snapshot = find_snapshot(registry, observation.link_map_addr); + check_ulong("reuse.new-generation", snapshot->generation, + first_generation + 1); + check_int("reuse.live-state", snapshot->state, + KZT_GUEST_OBJECT_DISCOVERED); + kzt_guest_object_snapshot_free(snapshot); + kzt_guest_registry_destroy(®istry); +} + +static void test_lazy_resolver_publish_find_lifecycle(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x7200); + kzt_guest_object_snapshot_t *snapshot; + kzt_guest_lazy_resolver_t resolver = { + .link_map_slot = 0x7210, + .resolver_slot = 0x7218, + .guest_link_map = 0x7200, + .guest_resolver = 0x7300, + }; + kzt_guest_lazy_resolver_t found; + unsigned long generation; + + observation.namespace_id = (kzt_guest_scalar_field_t) { + .value = 0, + .status = KZT_GUEST_FIELD_OK, + }; + check_int("lazy.observe", kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + snapshot = find_snapshot(registry, observation.link_map_addr); + generation = snapshot->generation; + kzt_guest_object_snapshot_free(snapshot); + + check_int("lazy.publish-pair", kzt_guest_registry_publish_lazy_resolver( + registry, observation.link_map_addr, generation, 0, + &resolver), 0); + check_int("lazy.find-pair", kzt_guest_registry_find_lazy_resolver( + registry, observation.link_map_addr, generation, 0, + &found), 0); + check_uintptr("lazy.find-link-map", found.guest_link_map, + resolver.guest_link_map); + check_uintptr("lazy.find-resolver", found.guest_resolver, + resolver.guest_resolver); + + resolver.guest_link_map = 0x7201; + check_not_int("lazy.reject-link-map-mismatch", + kzt_guest_registry_publish_lazy_resolver( + registry, observation.link_map_addr, generation, 0, + &resolver), 0); + resolver.guest_link_map = observation.link_map_addr; + check_not_int("lazy.reject-generation", + kzt_guest_registry_find_lazy_resolver( + registry, observation.link_map_addr, generation + 1, 0, + &found), 0); + check_not_int("lazy.reject-non-main-publish", + kzt_guest_registry_publish_lazy_resolver( + registry, observation.link_map_addr, generation, 1, + &resolver), 0); + check_not_int("lazy.reject-non-main-find", + kzt_guest_registry_find_lazy_resolver( + registry, observation.link_map_addr, generation, 1, + &found), 0); + check_int("lazy.retire", kzt_guest_registry_retire( + registry, observation.link_map_addr, generation), 0); + check_not_int("lazy.retired-invalid", + kzt_guest_registry_find_lazy_resolver( + registry, observation.link_map_addr, generation, 0, + &found), 0); + kzt_guest_registry_destroy(®istry); +} + +int main(void) +{ + test_first_and_repeat_observation_keep_generation(); + test_unknown_fields_are_completed_without_generation_change(); + test_identity_conflict_preserves_original_record(); + test_partial_observation_and_invalid_identity(); + test_query_and_dump_snapshots_are_caller_owned(); + test_diagnostics_are_opt_in_and_throttled(); + test_init_failure_creates_disabled_registry_diagnostics(); + test_cond_init_failure_is_clean(); + test_source_lease_acquire_release(); + test_retire_waits_for_source_lease(); + test_double_retire_cannot_kill_reused_generation(); + test_simultaneous_retire_has_one_owner(); + test_destroy_drains_waiter_and_inflight_api(); + test_destroyed_registry_rejects_new_observation(); + test_retired_address_gets_new_generation(); + test_lazy_resolver_publish_find_lifecycle(); + + if (failures) { + fprintf(stderr, "kzt-guest-registry: %d failure(s)\n", failures); + return 1; + } + + puts("kzt-guest-registry: all contract tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_guest_registry_concurrency.c b/tests/unit/kzt/test_guest_registry_concurrency.c new file mode 100644 index 00000000000..594e443493d --- /dev/null +++ b/tests/unit/kzt/test_guest_registry_concurrency.c @@ -0,0 +1,529 @@ +#include +#include +#include + +#include "target/i386/latx/include/kzt_guest_registry.h" + +#define SAME_LINK_THREADS 16 +#define DIFFERENT_LINK_THREADS 12 +#define SNAPSHOT_THREADS 8 +#define SNAPSHOT_ITERATIONS 64 +#define SNAPSHOT_OBJECTS 6 + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, expected); + ++failures; +} + +static void check_string(const char *name, const char *got, + const char *expected) +{ + if ((!got && !expected) || (got && expected && !strcmp(got, expected))) { + return; + } + + fprintf(stderr, "%s: got \"%s\" expected \"%s\"\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static void thread_check_true(int *thread_failures, const char *name, + int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++*thread_failures; +} + +static void thread_check_int(int *thread_failures, const char *name, + int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++*thread_failures; +} + +static void thread_check_ulong(int *thread_failures, const char *name, + unsigned long got, unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, expected); + ++*thread_failures; +} + +static void thread_check_string(int *thread_failures, const char *name, + const char *got, const char *expected) +{ + if ((!got && !expected) || (got && expected && !strcmp(got, expected))) { + return; + } + + fprintf(stderr, "%s: got \"%s\" expected \"%s\"\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++*thread_failures; +} + +static kzt_guest_object_observation_t make_observation( + uintptr_t link_map_addr, + uintptr_t index, + const char *path) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map_addr, + .load_bias = { 0x100000 + index * 0x1000, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { 0x101000 + index * 0x1000, KZT_GUEST_FIELD_OK }, + .map_start = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .map_end = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .namespace_id = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .path = { path, KZT_GUEST_FIELD_OK }, + .soname = { NULL, KZT_GUEST_FIELD_NOT_PARSED }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static int wait_for_barrier(pthread_barrier_t *barrier) +{ + int ret = pthread_barrier_wait(barrier); + + return ret == 0 || ret == PTHREAD_BARRIER_SERIAL_THREAD ? 0 : ret; +} + +typedef struct observe_worker { + kzt_guest_registry_t *registry; + pthread_barrier_t *barrier; + kzt_guest_object_observation_t observation; + kzt_guest_registry_result_t result; + int failures; +} observe_worker_t; + +static void *observe_worker_main(void *opaque) +{ + observe_worker_t *worker = opaque; + + if (wait_for_barrier(worker->barrier) != 0) { + worker->result = KZT_GUEST_REGISTRY_ERROR; + ++worker->failures; + return NULL; + } + + worker->result = kzt_guest_registry_observe(worker->registry, + &worker->observation); + return NULL; +} + +static void join_observe_workers(pthread_t *threads, + observe_worker_t *workers, + size_t count) +{ + size_t i; + + for (i = 0; i < count; ++i) { + check_int("pthread.join", pthread_join(threads[i], NULL), 0); + failures += workers[i].failures; + } +} + +static const kzt_guest_object_snapshot_t *find_dump_object( + const kzt_guest_registry_dump_t *dump, + uintptr_t link_map_addr) +{ + size_t i; + + for (i = 0; i < dump->count; ++i) { + if (dump->objects[i].link_map_addr == link_map_addr) { + return &dump->objects[i]; + } + } + + return NULL; +} + +static void test_concurrent_same_link_map_converges_to_one_generation(void) +{ + static const uintptr_t link_map_addr = 0x710000; + static const char path[] = "/guest/libsame-concurrent.so"; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + pthread_barrier_t barrier; + pthread_t threads[SAME_LINK_THREADS]; + observe_worker_t workers[SAME_LINK_THREADS]; + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_guest_registry_dump_t dump = { 0 }; + kzt_guest_registry_diagnostics_t diagnostics = { 0 }; + unsigned long added = 0; + unsigned long unchanged = 0; + size_t i; + + check_true("registry.init.same", registry != NULL); + if (!registry) { + return; + } + + check_int("barrier.init.same", + pthread_barrier_init(&barrier, NULL, SAME_LINK_THREADS), 0); + + for (i = 0; i < SAME_LINK_THREADS; ++i) { + workers[i] = (observe_worker_t) { + .registry = registry, + .barrier = &barrier, + .observation = make_observation(link_map_addr, 0, path), + .result = KZT_GUEST_REGISTRY_ERROR, + }; + check_int("pthread.create.same", + pthread_create(&threads[i], NULL, observe_worker_main, + &workers[i]), 0); + } + + join_observe_workers(threads, workers, SAME_LINK_THREADS); + check_int("barrier.destroy.same", pthread_barrier_destroy(&barrier), 0); + + for (i = 0; i < SAME_LINK_THREADS; ++i) { + if (workers[i].result == KZT_GUEST_REGISTRY_ADDED) { + ++added; + } else if (workers[i].result == KZT_GUEST_REGISTRY_UNCHANGED) { + ++unchanged; + } else { + fprintf(stderr, "same-link worker %lu unexpected result %d\n", + (unsigned long)i, workers[i].result); + ++failures; + } + } + + check_ulong("same.added", added, 1); + check_ulong("same.unchanged", unchanged, SAME_LINK_THREADS - 1); + + check_int("same.find", + kzt_guest_registry_find_by_link_map(registry, link_map_addr, + &snapshot), 0); + check_true("same.snapshot", snapshot != NULL); + if (snapshot) { + check_ulong("same.snapshot.generation", snapshot->generation, 1); + check_string("same.snapshot.path", snapshot->path.value, path); + kzt_guest_object_snapshot_free(snapshot); + } + + check_int("same.dump", kzt_guest_registry_dump_snapshot(registry, &dump), + 0); + check_ulong("same.dump.count", dump.count, 1); + if (dump.count == 1) { + check_ulong("same.dump.generation", dump.objects[0].generation, 1); + check_string("same.dump.path", dump.objects[0].path.value, path); + } + kzt_guest_registry_dump_free(&dump); + + check_int("same.diagnostics", + kzt_guest_registry_get_diagnostics(registry, &diagnostics), 0); + check_ulong("same.diagnostics.observations", diagnostics.observations, + SAME_LINK_THREADS); + check_ulong("same.diagnostics.added", diagnostics.added, 1); + check_ulong("same.diagnostics.unchanged", diagnostics.unchanged, + SAME_LINK_THREADS - 1); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy.same", registry == NULL); +} + +static void test_concurrent_different_link_maps_create_distinct_objects(void) +{ + static const uintptr_t base_link_map_addr = 0x720000; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + pthread_barrier_t barrier; + pthread_t threads[DIFFERENT_LINK_THREADS]; + observe_worker_t workers[DIFFERENT_LINK_THREADS]; + char paths[DIFFERENT_LINK_THREADS][64]; + unsigned char seen_generation[DIFFERENT_LINK_THREADS + 1] = { 0 }; + kzt_guest_registry_dump_t dump = { 0 }; + size_t i; + + check_true("registry.init.different", registry != NULL); + if (!registry) { + return; + } + + check_int("barrier.init.different", + pthread_barrier_init(&barrier, NULL, DIFFERENT_LINK_THREADS), 0); + + for (i = 0; i < DIFFERENT_LINK_THREADS; ++i) { + uintptr_t link_map_addr = base_link_map_addr + i * 0x1000; + + snprintf(paths[i], sizeof(paths[i]), + "/guest/libdifferent-%02lu.so", (unsigned long)i); + workers[i] = (observe_worker_t) { + .registry = registry, + .barrier = &barrier, + .observation = make_observation(link_map_addr, i + 1, paths[i]), + .result = KZT_GUEST_REGISTRY_ERROR, + }; + check_int("pthread.create.different", + pthread_create(&threads[i], NULL, observe_worker_main, + &workers[i]), 0); + } + + join_observe_workers(threads, workers, DIFFERENT_LINK_THREADS); + check_int("barrier.destroy.different", + pthread_barrier_destroy(&barrier), 0); + + for (i = 0; i < DIFFERENT_LINK_THREADS; ++i) { + kzt_guest_object_snapshot_t *snapshot = NULL; + unsigned long generation; + + check_int("different.result", workers[i].result, + KZT_GUEST_REGISTRY_ADDED); + check_int("different.find", + kzt_guest_registry_find_by_link_map( + registry, workers[i].observation.link_map_addr, + &snapshot), + 0); + check_true("different.snapshot", snapshot != NULL); + if (!snapshot) { + continue; + } + + check_string("different.snapshot.path", snapshot->path.value, + paths[i]); + generation = snapshot->generation; + check_true("different.generation.range", + generation >= 1 && generation <= DIFFERENT_LINK_THREADS); + if (generation >= 1 && generation <= DIFFERENT_LINK_THREADS) { + check_true("different.generation.unique", + !seen_generation[generation]); + seen_generation[generation] = 1; + } + kzt_guest_object_snapshot_free(snapshot); + } + + for (i = 1; i <= DIFFERENT_LINK_THREADS; ++i) { + check_true("different.generation.seen", seen_generation[i]); + } + + check_int("different.dump", + kzt_guest_registry_dump_snapshot(registry, &dump), 0); + check_ulong("different.dump.count", dump.count, DIFFERENT_LINK_THREADS); + for (i = 0; i < DIFFERENT_LINK_THREADS; ++i) { + const kzt_guest_object_snapshot_t *object = find_dump_object( + &dump, base_link_map_addr + i * 0x1000); + + check_true("different.dump.object", object != NULL); + if (object) { + check_string("different.dump.path", object->path.value, paths[i]); + } + } + kzt_guest_registry_dump_free(&dump); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy.different", registry == NULL); +} + +typedef struct snapshot_worker { + kzt_guest_registry_t *registry; + pthread_barrier_t *barrier; + uintptr_t link_map_addr; + const char *expected_path; + const char *source_path; + int failures; +} snapshot_worker_t; + +static void *snapshot_worker_main(void *opaque) +{ + snapshot_worker_t *worker = opaque; + size_t i; + + if (wait_for_barrier(worker->barrier) != 0) { + ++worker->failures; + return NULL; + } + + for (i = 0; i < SNAPSHOT_ITERATIONS; ++i) { + kzt_guest_object_snapshot_t *first = NULL; + kzt_guest_object_snapshot_t *second = NULL; + kzt_guest_registry_dump_t first_dump = { 0 }; + kzt_guest_registry_dump_t second_dump = { 0 }; + const kzt_guest_object_snapshot_t *first_object; + const kzt_guest_object_snapshot_t *second_object; + + thread_check_int( + &worker->failures, "snapshot.find.first", + kzt_guest_registry_find_by_link_map(worker->registry, + worker->link_map_addr, + &first), + 0); + thread_check_int( + &worker->failures, "snapshot.find.second", + kzt_guest_registry_find_by_link_map(worker->registry, + worker->link_map_addr, + &second), + 0); + thread_check_true(&worker->failures, "snapshot.find.first.ptr", + first != NULL); + thread_check_true(&worker->failures, "snapshot.find.second.ptr", + second != NULL); + if (first && second) { + thread_check_string(&worker->failures, "snapshot.find.path.first", + first->path.value, worker->expected_path); + thread_check_string(&worker->failures, "snapshot.find.path.second", + second->path.value, worker->expected_path); + thread_check_true(&worker->failures, + "snapshot.find.path.not-source", + first->path.value != worker->source_path); + thread_check_true(&worker->failures, + "snapshot.find.path.distinct", + first->path.value != second->path.value); + } + + thread_check_int(&worker->failures, "snapshot.dump.first", + kzt_guest_registry_dump_snapshot(worker->registry, + &first_dump), + 0); + thread_check_int(&worker->failures, "snapshot.dump.second", + kzt_guest_registry_dump_snapshot(worker->registry, + &second_dump), + 0); + thread_check_ulong(&worker->failures, "snapshot.dump.first.count", + first_dump.count, SNAPSHOT_OBJECTS); + thread_check_ulong(&worker->failures, "snapshot.dump.second.count", + second_dump.count, SNAPSHOT_OBJECTS); + + first_object = find_dump_object(&first_dump, worker->link_map_addr); + second_object = find_dump_object(&second_dump, worker->link_map_addr); + thread_check_true(&worker->failures, "snapshot.dump.first.object", + first_object != NULL); + thread_check_true(&worker->failures, "snapshot.dump.second.object", + second_object != NULL); + if (first_object && second_object) { + thread_check_string(&worker->failures, "snapshot.dump.path.first", + first_object->path.value, + worker->expected_path); + thread_check_string(&worker->failures, "snapshot.dump.path.second", + second_object->path.value, + worker->expected_path); + thread_check_true(&worker->failures, + "snapshot.dump.path.not-source", + first_object->path.value != worker->source_path); + thread_check_true(&worker->failures, + "snapshot.dump.path.distinct", + first_object->path.value != + second_object->path.value); + if (first) { + thread_check_true(&worker->failures, + "snapshot.dump.path.not-find", + first_object->path.value != + first->path.value); + } + } + + kzt_guest_registry_dump_free(&second_dump); + kzt_guest_registry_dump_free(&first_dump); + kzt_guest_object_snapshot_free(second); + kzt_guest_object_snapshot_free(first); + } + + return NULL; +} + +static void test_concurrent_query_and_dump_return_owned_snapshots(void) +{ + static const uintptr_t base_link_map_addr = 0x730000; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + pthread_barrier_t barrier; + pthread_t threads[SNAPSHOT_THREADS]; + snapshot_worker_t workers[SNAPSHOT_THREADS]; + char paths[SNAPSHOT_OBJECTS][64]; + size_t i; + + check_true("registry.init.snapshot", registry != NULL); + if (!registry) { + return; + } + + for (i = 0; i < SNAPSHOT_OBJECTS; ++i) { + kzt_guest_object_observation_t observation; + + snprintf(paths[i], sizeof(paths[i]), "/guest/libsnapshot-%02lu.so", + (unsigned long)i); + observation = make_observation(base_link_map_addr + i * 0x1000, + i + 1, paths[i]); + check_int("snapshot.prepopulate", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + } + + check_int("barrier.init.snapshot", + pthread_barrier_init(&barrier, NULL, SNAPSHOT_THREADS), 0); + + for (i = 0; i < SNAPSHOT_THREADS; ++i) { + size_t object_index = i % SNAPSHOT_OBJECTS; + + workers[i] = (snapshot_worker_t) { + .registry = registry, + .barrier = &barrier, + .link_map_addr = base_link_map_addr + object_index * 0x1000, + .expected_path = paths[object_index], + .source_path = paths[object_index], + }; + check_int("pthread.create.snapshot", + pthread_create(&threads[i], NULL, snapshot_worker_main, + &workers[i]), 0); + } + + for (i = 0; i < SNAPSHOT_THREADS; ++i) { + check_int("pthread.join.snapshot", pthread_join(threads[i], NULL), 0); + failures += workers[i].failures; + } + check_int("barrier.destroy.snapshot", pthread_barrier_destroy(&barrier), + 0); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy.snapshot", registry == NULL); +} + +int main(void) +{ + test_concurrent_same_link_map_converges_to_one_generation(); + test_concurrent_different_link_maps_create_distinct_objects(); + test_concurrent_query_and_dump_return_owned_snapshots(); + + if (failures) { + fprintf(stderr, "kzt-guest-registry-concurrency: %d failure(s)\n", + failures); + return 1; + } + + puts("kzt-guest-registry-concurrency: all tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_guest_registry_context.c b/tests/unit/kzt/test_guest_registry_context.c new file mode 100644 index 00000000000..5f9355e7227 --- /dev/null +++ b/tests/unit/kzt/test_guest_registry_context.c @@ -0,0 +1,378 @@ +#include +#include +#include + +#include "target/i386/latx/include/kzt_guest_registry_context.h" + +#ifdef __APPLE__ +typedef struct kzt_test_barrier { + pthread_mutex_t lock; + pthread_cond_t condition; + unsigned int waiting; + unsigned int participants; + unsigned int generation; +} kzt_test_barrier_t; + +#define pthread_barrier_t kzt_test_barrier_t +#define PTHREAD_BARRIER_SERIAL_THREAD 1 + +static int pthread_barrier_init(kzt_test_barrier_t *barrier, + const void *attributes, + unsigned int participants) +{ + (void)attributes; + memset(barrier, 0, sizeof(*barrier)); + barrier->participants = participants; + return pthread_mutex_init(&barrier->lock, NULL) || + pthread_cond_init(&barrier->condition, NULL); +} + +static int pthread_barrier_wait(kzt_test_barrier_t *barrier) +{ + unsigned int generation; + + pthread_mutex_lock(&barrier->lock); + generation = barrier->generation; + if (++barrier->waiting == barrier->participants) { + barrier->waiting = 0; + ++barrier->generation; + pthread_cond_broadcast(&barrier->condition); + pthread_mutex_unlock(&barrier->lock); + return PTHREAD_BARRIER_SERIAL_THREAD; + } + while (generation == barrier->generation) { + pthread_cond_wait(&barrier->condition, &barrier->lock); + } + pthread_mutex_unlock(&barrier->lock); + return 0; +} + +static int pthread_barrier_destroy(kzt_test_barrier_t *barrier) +{ + int condition_result = pthread_cond_destroy(&barrier->condition); + int mutex_result = pthread_mutex_destroy(&barrier->lock); + + return condition_result ? condition_result : mutex_result; +} +#endif + +#define CONTEXT_THREADS 16 +#define CONTEXT_RACE_ROUNDS 32 + +typedef struct registry_context_fixture { + pthread_mutex_t lock; + pthread_barrier_t barrier; + kzt_guest_registry_context_t context; + kzt_guest_registry_t *results[CONTEXT_THREADS]; + int worker_failures; +} registry_context_fixture_t; + +typedef struct registry_context_worker { + registry_context_fixture_t *fixture; + size_t index; +} registry_context_worker_t; + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (!condition) { + fprintf(stderr, "%s: condition failed\n", name); + ++failures; + } +} + +static int wait_at_barrier(registry_context_fixture_t *fixture) +{ + int result = pthread_barrier_wait(&fixture->barrier); + + if (result != 0 && result != PTHREAD_BARRIER_SERIAL_THREAD) { + __atomic_add_fetch(&fixture->worker_failures, 1, __ATOMIC_RELAXED); + return -1; + } + return 0; +} + +static void *context_get_worker(void *opaque) +{ + registry_context_worker_t *worker = opaque; + + if (wait_at_barrier(worker->fixture) == 0) { + worker->fixture->results[worker->index] = + kzt_guest_registry_context_get(&worker->fixture->context, + &worker->fixture->lock); + } + return NULL; +} + +static void *context_destroy_worker(void *opaque) +{ + registry_context_fixture_t *fixture = opaque; + + if (wait_at_barrier(fixture) == 0) { + kzt_guest_registry_context_destroy(&fixture->context, + &fixture->lock); + } + return NULL; +} + +static void *context_confirm_main_head_worker(void *opaque) +{ + registry_context_fixture_t *fixture = opaque; + + if (wait_at_barrier(fixture) == 0) { + (void)kzt_guest_registry_context_confirm_main_namespace_head( + &fixture->context, &fixture->lock, 0x12340000); + } + return NULL; +} + +static void init_fixture(registry_context_fixture_t *fixture, + unsigned int participants) +{ + memset(fixture, 0, sizeof(*fixture)); + check_true("context.lock-init", + pthread_mutex_init(&fixture->lock, NULL) == 0); + check_true("context.barrier-init", + pthread_barrier_init(&fixture->barrier, NULL, + participants) == 0); +} + +static void destroy_fixture(registry_context_fixture_t *fixture) +{ + check_true("context.barrier-destroy", + pthread_barrier_destroy(&fixture->barrier) == 0); + check_true("context.lock-destroy", + pthread_mutex_destroy(&fixture->lock) == 0); +} + +static void test_concurrent_lazy_init_publishes_one_registry(void) +{ + registry_context_fixture_t fixture; + registry_context_worker_t workers[CONTEXT_THREADS]; + pthread_t threads[CONTEXT_THREADS]; + size_t i; + + init_fixture(&fixture, CONTEXT_THREADS); + for (i = 0; i < CONTEXT_THREADS; ++i) { + workers[i].fixture = &fixture; + workers[i].index = i; + check_true("context.create", + pthread_create(&threads[i], NULL, context_get_worker, + &workers[i]) == 0); + } + for (i = 0; i < CONTEXT_THREADS; ++i) { + check_true("context.join", pthread_join(threads[i], NULL) == 0); + } + check_true("context.worker-results", fixture.worker_failures == 0); + check_true("context.lazy-published", fixture.results[0] != NULL); + for (i = 1; i < CONTEXT_THREADS; ++i) { + check_true("context.single-publication", + fixture.results[i] == fixture.results[0]); + } + + kzt_guest_registry_context_destroy(&fixture.context, &fixture.lock); + check_true("context.no-reinit", + kzt_guest_registry_context_get(&fixture.context, + &fixture.lock) == NULL); + kzt_guest_registry_context_destroy(&fixture.context, &fixture.lock); + destroy_fixture(&fixture); +} + +static void test_init_and_destroy_race_closes_publication_gate(void) +{ + unsigned int round; + + for (round = 0; round < CONTEXT_RACE_ROUNDS; ++round) { + registry_context_fixture_t fixture; + registry_context_worker_t workers[CONTEXT_THREADS]; + pthread_t getters[CONTEXT_THREADS]; + pthread_t destroyer; + kzt_guest_registry_t *published = NULL; + size_t i; + + init_fixture(&fixture, CONTEXT_THREADS + 1); + for (i = 0; i < CONTEXT_THREADS; ++i) { + workers[i].fixture = &fixture; + workers[i].index = i; + check_true("race.create-getter", + pthread_create(&getters[i], NULL, context_get_worker, + &workers[i]) == 0); + } + check_true("race.create-destroyer", + pthread_create(&destroyer, NULL, context_destroy_worker, + &fixture) == 0); + for (i = 0; i < CONTEXT_THREADS; ++i) { + check_true("race.join-getter", + pthread_join(getters[i], NULL) == 0); + } + check_true("race.join-destroyer", + pthread_join(destroyer, NULL) == 0); + check_true("race.worker-results", fixture.worker_failures == 0); + + for (i = 0; i < CONTEXT_THREADS; ++i) { + if (!fixture.results[i]) { + continue; + } + if (!published) { + published = fixture.results[i]; + } + check_true("race.single-publication", + fixture.results[i] == published); + } + check_true("race.closed", + kzt_guest_registry_context_get(&fixture.context, + &fixture.lock) == NULL); + kzt_guest_registry_context_destroy(&fixture.context, &fixture.lock); + destroy_fixture(&fixture); + } +} + +static void test_failed_init_is_not_retried_on_hot_path(void) +{ + registry_context_fixture_t fixture; + + init_fixture(&fixture, 1); + kzt_guest_registry_test_set_alloc_failure_after(0); + check_true("failure.first", + kzt_guest_registry_context_get(&fixture.context, + &fixture.lock) == NULL); + kzt_guest_registry_test_set_alloc_failure_after(-1); + check_true("failure.not-retried", + kzt_guest_registry_context_get(&fixture.context, + &fixture.lock) == NULL); + kzt_guest_registry_context_destroy(&fixture.context, &fixture.lock); + destroy_fixture(&fixture); +} + +static void test_main_namespace_head_is_context_owned_and_conflict_safe(void) +{ + registry_context_fixture_t fixture; + uintptr_t head = 0; + + init_fixture(&fixture, 1); + check_true("main-head.empty", + kzt_guest_registry_context_get_main_namespace_head( + &fixture.context, &head) != 0 && head == 0); + check_true("main-head.confirm", + kzt_guest_registry_context_confirm_main_namespace_head( + &fixture.context, &fixture.lock, 0x12340000) == 0); + check_true("main-head.get", + kzt_guest_registry_context_get_main_namespace_head( + &fixture.context, &head) == 0 && head == 0x12340000); + check_true("main-head.idempotent", + kzt_guest_registry_context_confirm_main_namespace_head( + &fixture.context, &fixture.lock, 0x12340000) == 0); + check_true("main-head.conflict", + kzt_guest_registry_context_confirm_main_namespace_head( + &fixture.context, &fixture.lock, 0x56780000) != 0); + + kzt_guest_registry_context_destroy(&fixture.context, &fixture.lock); + head = 1; + check_true("main-head.cleared-on-destroy", + kzt_guest_registry_context_get_main_namespace_head( + &fixture.context, &head) != 0 && head == 0); + check_true("main-head.closed-after-destroy", + kzt_guest_registry_context_confirm_main_namespace_head( + &fixture.context, &fixture.lock, 0x12340000) != 0); + destroy_fixture(&fixture); +} + +static void test_registry_evidence_cache_requires_exact_object_identity(void) +{ + registry_context_fixture_t fixture; + kzt_guest_registry_t *registry; + kzt_guest_object_observation_t observation = { + .link_map_addr = 0x70000000, + .load_bias = { 0x700000, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { 0x701000, KZT_GUEST_FIELD_OK }, + .map_start = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .map_end = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .namespace_id = { 0, KZT_GUEST_FIELD_OK }, + .path = { NULL, KZT_GUEST_FIELD_UNKNOWN }, + .soname = { NULL, KZT_GUEST_FIELD_NOT_PARSED }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; + + init_fixture(&fixture, 1); + registry = kzt_guest_registry_context_get(&fixture.context, + &fixture.lock); + check_true("evidence.registry", registry != NULL); + check_true("evidence.observe", + kzt_guest_registry_observe(registry, &observation) == + KZT_GUEST_REGISTRY_ADDED); + check_true("evidence.no-head-no-reuse", + !kzt_guest_registry_context_has_main_namespace_evidence( + &fixture.context, registry, observation.link_map_addr, + observation.load_bias.value, + observation.dynamic_addr.value)); + check_true("evidence.confirm-head", + kzt_guest_registry_context_confirm_main_namespace_head( + &fixture.context, &fixture.lock, 0x60000000) == 0); + check_true("evidence.exact-reuse", + kzt_guest_registry_context_has_main_namespace_evidence( + &fixture.context, registry, observation.link_map_addr, + observation.load_bias.value, + observation.dynamic_addr.value)); + check_true("evidence.wrong-load-bias", + !kzt_guest_registry_context_has_main_namespace_evidence( + &fixture.context, registry, observation.link_map_addr, + observation.load_bias.value + 0x1000, + observation.dynamic_addr.value)); + check_true("evidence.wrong-dynamic", + !kzt_guest_registry_context_has_main_namespace_evidence( + &fixture.context, registry, observation.link_map_addr, + observation.load_bias.value, + observation.dynamic_addr.value + 0x1000)); + + kzt_guest_registry_context_destroy(&fixture.context, &fixture.lock); + destroy_fixture(&fixture); +} + +static void test_main_namespace_head_cannot_publish_after_destroy(void) +{ + unsigned int round; + + for (round = 0; round < CONTEXT_RACE_ROUNDS; ++round) { + registry_context_fixture_t fixture; + pthread_t confirmer; + pthread_t destroyer; + uintptr_t head = 1; + + init_fixture(&fixture, 2); + check_true("main-head-race.create-confirmer", + pthread_create(&confirmer, NULL, + context_confirm_main_head_worker, + &fixture) == 0); + check_true("main-head-race.create-destroyer", + pthread_create(&destroyer, NULL, + context_destroy_worker, &fixture) == 0); + check_true("main-head-race.join-confirmer", + pthread_join(confirmer, NULL) == 0); + check_true("main-head-race.join-destroyer", + pthread_join(destroyer, NULL) == 0); + check_true("main-head-race.closed", + kzt_guest_registry_context_get_main_namespace_head( + &fixture.context, &head) != 0 && head == 0); + kzt_guest_registry_context_destroy(&fixture.context, &fixture.lock); + destroy_fixture(&fixture); + } +} + +int main(void) +{ + test_concurrent_lazy_init_publishes_one_registry(); + test_init_and_destroy_race_closes_publication_gate(); + test_failed_init_is_not_retried_on_hot_path(); + test_main_namespace_head_is_context_owned_and_conflict_safe(); + test_registry_evidence_cache_requires_exact_object_identity(); + test_main_namespace_head_cannot_publish_after_destroy(); + + if (failures) { + fprintf(stderr, "kzt-guest-registry-context: %d failure(s)\n", + failures); + return 1; + } + puts("kzt-guest-registry-context: all contract tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_jump_slot_route.c b/tests/unit/kzt/test_jump_slot_route.c new file mode 100644 index 00000000000..7358b9b2160 --- /dev/null +++ b/tests/unit/kzt/test_jump_slot_route.c @@ -0,0 +1,558 @@ +#include +#include + +#include "elf.h" +#include "target/i386/latx/include/kzt_jump_slot_route.h" + +struct library_s { int live; }; + +typedef struct fixture { + uintptr_t slot; + uintptr_t raw_expected; + uintptr_t bridge; + struct library_s provider; + struct library_s other; + int owner_available; + int acquire_ok; + int exact_conflict; + int bridge_ok; + int bridge_changes_owner_link_map; + int bridge_changes_owner_generation; + int source_identity_valid; + kzt_jump_slot_route_writer_status_t writer_status; + int writer_mutates; + int writer_rollback_after_competitor; + int force_cas_mismatch; + int load_error; + int cas_error; + int acquired; + int released; + int enrich_calls; + int acquire_calls; + int bridge_calls; + int source_recheck_calls; + int provider_touched_after_release; + int writer_calls; + int cas_calls; +} fixture_t; + +static int failures; + +#define CHECK(name, condition) do { \ + if (!(condition)) { \ + fprintf(stderr, "%s failed at line %d\n", name, __LINE__); \ + ++failures; \ + } \ +} while (0) + +static int enrich_base(kzt_rela_immediate_candidate_request_t *request, + void *opaque) +{ + fixture_t *f = opaque; + ++f->enrich_calls; + request->owner_match = f->owner_available ? KZT_PATCH_OWNER_MATCH : + KZT_PATCH_OWNER_UNKNOWN; + request->current_owner.known = f->owner_available; + request->current_owner.link_map_addr = f->owner_available ? 0x1234 : 0; + request->current_owner.generation = f->owner_available ? 7 : 0; + return 0; +} + +static int acquire_exact(const kzt_patch_object_ref_t *owner, + library_t *resolved_provider, + kzt_guest_library_handle_t *handle, void *opaque) +{ + fixture_t *f = opaque; + if (f->released) + ++f->provider_touched_after_release; + ++f->acquire_calls; + CHECK("acquire.owner", owner->link_map_addr == 0x1234); + if (!f->acquire_ok) return -1; + ++f->acquired; + handle->entry = (void *)1; + handle->library = f->exact_conflict ? (library_t *)&f->other : + resolved_provider; + return 0; +} + +static void release_exact(kzt_guest_library_handle_t *handle, void *opaque) +{ + fixture_t *f = opaque; + CHECK("release.handle", handle->entry != NULL); + ++f->released; + f->provider.live = 0; + memset(handle, 0, sizeof(*handle)); +} + +static int enrich_bridge(kzt_rela_immediate_candidate_request_t *request, + library_t *held_provider, void *opaque) +{ + fixture_t *f = opaque; + if (f->released) + ++f->provider_touched_after_release; + ++f->bridge_calls; + CHECK("bridge.held", held_provider == (library_t *)&f->provider); + CHECK("bridge.live", f->provider.live == 1); + if (!f->bridge_ok) return -1; + request->native_bridge_target = f->bridge; + if (f->bridge_changes_owner_link_map) + request->current_owner.link_map_addr = 0x5678; + if (f->bridge_changes_owner_generation) + request->current_owner.generation = 8; + return 0; +} + +static int validate_source_identity( + const kzt_rela_immediate_candidate_request_t *request, void *opaque) +{ + fixture_t *f = opaque; + ++f->source_recheck_calls; + CHECK("source-recheck.source", request->source.known == 1); + return f->source_identity_valid; +} + +static int load_slot(uintptr_t slot_addr, uintptr_t *value, void *opaque) +{ + fixture_t *f = opaque; + if (f->load_error) return -1; + *value = *(uintptr_t *)slot_addr; + return 0; +} + +static int cas_slot(uintptr_t slot_addr, uintptr_t *expected, + uintptr_t replacement, void *opaque) +{ + fixture_t *f = opaque; + uintptr_t *slot = (uintptr_t *)slot_addr; + ++f->cas_calls; + if (f->cas_error) return -1; + if (f->force_cas_mismatch) { + *slot = 0xfeedface; + *expected = *slot; + f->force_cas_mismatch = 0; + return 0; + } + if (*slot != *expected) { + *expected = *slot; + return 0; + } + *slot = replacement; + return 1; +} + +static kzt_jump_slot_route_writer_status_t try_writer( + const kzt_rela_immediate_candidate_request_t *request, + const kzt_patch_spike_slot_ops_t *slot_ops, void *opaque) +{ + fixture_t *f = opaque; + uintptr_t observed = 0; + if (f->released) + ++f->provider_touched_after_release; + ++f->writer_calls; + CHECK("writer.raw-preserved", + request->expected_guest_target == f->raw_expected); + CHECK("writer.provider-live", f->provider.live == 1); + if (f->writer_mutates) { + if (slot_ops->read_slot(request->slot_addr, &observed, + slot_ops->opaque) != 0 || + slot_ops->write_slot(request->slot_addr, + request->native_bridge_target, + slot_ops->opaque) != 0) { + return KZT_JUMP_SLOT_ROUTE_WRITER_ERROR; + } + } + if (f->writer_rollback_after_competitor) { + if (slot_ops->read_slot(request->slot_addr, &observed, + slot_ops->opaque) != 0 || + slot_ops->write_slot(request->slot_addr, + request->native_bridge_target, + slot_ops->opaque) != 0) { + return KZT_JUMP_SLOT_ROUTE_WRITER_ERROR; + } + f->slot = 0xfeedface; + CHECK("rollback.must-not-overwrite-competitor", + slot_ops->write_slot(request->slot_addr, observed, + slot_ops->opaque) != 0); + return KZT_JUMP_SLOT_ROUTE_WRITER_ERROR; + } + return f->writer_status; +} + +static fixture_t fixture(void) +{ + return (fixture_t){ + .slot = 0x71000010, + .raw_expected = 0x71000020, + .bridge = 0x73000030, + .provider = { .live = 1 }, + .owner_available = 1, + .acquire_ok = 1, + .bridge_ok = 1, + .writer_status = KZT_JUMP_SLOT_ROUTE_WRITER_APPLIED, + .writer_mutates = 1, + .source_identity_valid = 1, + }; +} + +static kzt_jump_slot_route_ops_t ops_for(fixture_t *f); + +static kzt_jump_slot_route_input_t input_for(fixture_t *f) +{ + return (kzt_jump_slot_route_input_t){ + .enabled = 1, + .expected_guest_target_present = 1, + .resolved_target_matches_legacy = 1, + .resolved_provider = (library_t *)&f->provider, + .request = { + .relocation_type = R_X86_64_JUMP_SLOT, + .source = { + .known = 1, + .link_map_addr = 0x1111, + .generation = 7, + }, + .slot_addr = (uintptr_t)&f->slot, + .slot_current_value_present = 1, + .slot_current_value = f->slot, + .expected_guest_target = f->raw_expected, + .legacy_target = 0x72000040, + }, + }; +} + +static void test_caller_observation_change_is_never_authorized(void) +{ + for (int expected_present = 0; expected_present < 2; + ++expected_present) { + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + uintptr_t competing = expected_present ? 0x71000050 : 0x71000060; + + input.expected_guest_target_present = expected_present; + if (!expected_present) + input.request.expected_guest_target = 0; + f.slot = competing; + CHECK("caller-race.call", + kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("caller-race.status", + result.status == KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH); + CHECK("caller-race.final", result.final_value == competing); + CHECK("caller-race.preserve", f.slot == competing); + CHECK("caller-race.no-enrich", f.enrich_calls == 0); + CHECK("caller-race.no-acquire", f.acquire_calls == 0); + CHECK("caller-race.no-bridge", f.bridge_calls == 0); + CHECK("caller-race.no-writer", f.writer_calls == 0); + CHECK("caller-race.no-cas", f.cas_calls == 0); + CHECK("caller-race.no-fallback", + result.legacy_fallback_attempted == 0); + } +} + +static void test_same_owner_competitor_cannot_authorize_native(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + uintptr_t competing = f.slot + 0x80; + + /* enrich_base would report the same exact owner/provider for this value. */ + f.slot = competing; + CHECK("same-owner-race.call", + kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("same-owner-race.status", + result.status == KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH); + CHECK("same-owner-race.final", result.final_value == competing); + CHECK("same-owner-race.preserve", f.slot == competing); + CHECK("same-owner-race.no-enrich", f.enrich_calls == 0); + CHECK("same-owner-race.no-acquire", f.acquire_calls == 0); + CHECK("same-owner-race.no-writer", f.writer_calls == 0); + CHECK("same-owner-race.no-cas", f.cas_calls == 0); +} + +static void test_lazy_zero_competitor_is_preserved_without_write(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + + input.expected_guest_target_present = 0; + input.request.expected_guest_target = 0; + f.slot = 0; + CHECK("lazy-zero.call", + kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("lazy-zero.status", + result.status == KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH); + CHECK("lazy-zero.final", result.final_value == 0); + CHECK("lazy-zero.preserve", f.slot == 0); + CHECK("lazy-zero.no-enrich", f.enrich_calls == 0); + CHECK("lazy-zero.no-acquire", f.acquire_calls == 0); + CHECK("lazy-zero.no-writer", f.writer_calls == 0); + CHECK("lazy-zero.no-cas", f.cas_calls == 0); +} + +static void test_resolved_target_mismatch_uses_legacy(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + + input.resolved_target_matches_legacy = 0; + CHECK("target-mismatch.call", + kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("target-mismatch.status", + result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED); + CHECK("target-mismatch.legacy", f.slot == input.request.legacy_target); + CHECK("target-mismatch.no-enrich", f.enrich_calls == 0); + CHECK("target-mismatch.no-acquire", f.acquire_calls == 0); + CHECK("target-mismatch.no-bridge", f.bridge_calls == 0); + CHECK("target-mismatch.no-writer", f.writer_calls == 0); +} + +static void test_owner_identity_change_during_bridge_falls_back(void) +{ + for (int change_generation = 0; change_generation < 2; + ++change_generation) { + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + + f.bridge_changes_owner_link_map = !change_generation; + f.bridge_changes_owner_generation = change_generation; + CHECK("owner-refresh.call", + kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("owner-refresh.status", + result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED); + CHECK("owner-refresh.legacy", + f.slot == input.request.legacy_target); + CHECK("owner-refresh.acquired", f.acquired == 1); + CHECK("owner-refresh.released", f.released == 1); + CHECK("owner-refresh.bridge", f.bridge_calls == 1); + CHECK("owner-refresh.no-writer", f.writer_calls == 0); + } +} + +static kzt_jump_slot_route_ops_t ops_for(fixture_t *f) +{ + return (kzt_jump_slot_route_ops_t){ + .enrich_base = enrich_base, + .acquire_exact_provider = acquire_exact, + .release_exact_provider = release_exact, + .enrich_bridge = enrich_bridge, + .validate_source_identity = validate_source_identity, + .try_native_writer = try_writer, + .load_slot = load_slot, + .compare_exchange_slot = cas_slot, + .opaque = f, + }; +} + +static void test_generation_change_before_writer_preserves_guest(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + + input.preserve_observed_on_failure = 1; + input.request.source.known = 1; + input.request.source.link_map_addr = 0x1111; + input.request.source.generation = 7; + f.source_identity_valid = 0; + CHECK("generation-race.call", + kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("generation-race.rechecked", f.source_recheck_calls == 1 && + result.source_identity_rechecked == 1); + CHECK("generation-race.preserved", + result.status == KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED); + CHECK("generation-race.slot", f.slot == input.request.slot_current_value); + CHECK("generation-race.no-writer", f.writer_calls == 0); + CHECK("generation-race.no-cas", f.cas_calls == 0); + CHECK("generation-race.no-legacy", + result.legacy_fallback_attempted == 0); +} + +static void test_exact_provider_bridge_success(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + CHECK("success.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("success.status", result.status == KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED); + CHECK("success.slot", f.slot == f.bridge); + CHECK("success.raw", result.expected_guest_target == f.raw_expected); + CHECK("success.lease", f.acquired == 1 && f.released == 1); + CHECK("success.no-post-release-deref", f.provider_touched_after_release == 0); +} + +static void test_missing_or_stale_evidence_falls_back(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + f.acquire_ok = 0; + CHECK("stale.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("stale.status", result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED); + CHECK("stale.legacy", f.slot == input.request.legacy_target); + CHECK("stale.no-writer", f.writer_calls == 0); +} + +static void test_lazy_missing_evidence_preserves_observed_without_legacy_cas(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + + input.preserve_observed_on_failure = 1; + f.acquire_ok = 0; + CHECK("preserve.call", + kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("preserve.status", + result.status == KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED); + CHECK("preserve.slot", f.slot == input.request.slot_current_value); + CHECK("preserve.no-legacy", result.legacy_fallback_attempted == 0); + CHECK("preserve.no-cas", f.cas_calls == 0); +} + +static void test_conflicting_exact_provider_falls_back(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + f.exact_conflict = 1; + CHECK("conflict.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("conflict.status", result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED); + CHECK("conflict.lease", f.acquired == 1 && f.released == 1); + CHECK("conflict.no-writer", f.writer_calls == 0); +} + +static void test_lazy_missing_owner_explicitly_falls_back(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + input.expected_guest_target_present = 0; + input.request.expected_guest_target = 0; + f.owner_available = 0; + CHECK("lazy.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("lazy.status", result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED); + CHECK("lazy.no-acquire", f.acquired == 0); + CHECK("lazy.no-writer", f.writer_calls == 0); +} + +static void test_cas_mismatch_preserves_competing_value(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + f.acquire_ok = 0; + f.force_cas_mismatch = 1; + CHECK("cas.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("cas.status", result.status == KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH); + CHECK("cas.competing", f.slot == 0xfeedface); +} + +static void test_native_cas_mismatch_does_not_overwrite_competitor(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + f.force_cas_mismatch = 1; + CHECK("native-cas.call", + kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("native-cas.status", + result.status == KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH); + CHECK("native-cas.competing", f.slot == 0xfeedface); + CHECK("native-cas.release", f.released == 1); + CHECK("native-cas.once", f.cas_calls == 1); + CHECK("native-cas.report-competitor", + result.selected_target == 0xfeedface && + result.final_value == 0xfeedface); +} + +static void test_rollback_cas_does_not_overwrite_competitor(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + f.writer_mutates = 0; + f.writer_rollback_after_competitor = 1; + CHECK("rollback-race.call", + kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("rollback-race.status", + result.status == KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH); + CHECK("rollback-race.competing", f.slot == 0xfeedface); +} + +static void test_writer_decline_and_error_use_legacy(void) +{ + for (int error = 0; error < 2; ++error) { + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + f.writer_mutates = 0; + f.writer_status = error ? KZT_JUMP_SLOT_ROUTE_WRITER_ERROR : + KZT_JUMP_SLOT_ROUTE_WRITER_DECLINED; + CHECK("writer-fallback.call", + kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("writer-fallback.status", + result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED); + CHECK("writer-fallback.slot", f.slot == input.request.legacy_target); + CHECK("writer-fallback.release", f.released == 1); + CHECK("writer-fallback.no-post-release-provider-callback", + f.provider_touched_after_release == 0); + } +} + +static void test_kzt_off_bypasses_route(void) +{ + fixture_t f = fixture(); + uintptr_t initial = f.slot; + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + input.enabled = 0; + CHECK("off.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("off.status", result.status == KZT_JUMP_SLOT_ROUTE_BYPASS); + CHECK("off.untouched", f.slot == initial); + CHECK("off.no-callbacks", f.acquired == 0 && f.cas_calls == 0); +} + +int main(void) +{ + test_caller_observation_change_is_never_authorized(); + test_same_owner_competitor_cannot_authorize_native(); + test_lazy_zero_competitor_is_preserved_without_write(); + test_resolved_target_mismatch_uses_legacy(); + test_owner_identity_change_during_bridge_falls_back(); + test_generation_change_before_writer_preserves_guest(); + test_exact_provider_bridge_success(); + test_missing_or_stale_evidence_falls_back(); + test_lazy_missing_evidence_preserves_observed_without_legacy_cas(); + test_conflicting_exact_provider_falls_back(); + test_lazy_missing_owner_explicitly_falls_back(); + test_cas_mismatch_preserves_competing_value(); + test_native_cas_mismatch_does_not_overwrite_competitor(); + test_rollback_cas_does_not_overwrite_competitor(); + test_writer_decline_and_error_use_legacy(); + test_kzt_off_bypasses_route(); + if (failures) { + fprintf(stderr, "%d jump-slot route checks failed\n", failures); + return 1; + } + puts("KZT shared jump-slot route: PASS"); + return 0; +} diff --git a/tests/unit/kzt/test_observation_adapter.c b/tests/unit/kzt/test_observation_adapter.c new file mode 100644 index 00000000000..c2e9973a790 --- /dev/null +++ b/tests/unit/kzt/test_observation_adapter.c @@ -0,0 +1,2326 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "target/i386/latx/include/box64context.h" +#include "target/i386/latx/include/kzt_observation_adapter.h" +#include "target/i386/latx/include/kzt_guest_library_binding.h" + +#define EVENT_READER_READ 1 +#define EVENT_LEGACY_FLOW 2 +#define EVENT_DIAGNOSTIC 3 + +#define TEST_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +typedef struct fake_read_failure { + uintptr_t addr; + size_t size; +} fake_read_failure_t; + +typedef struct fake_reader_memory { + uintptr_t base; + size_t size; + const fake_read_failure_t *failures; + size_t failure_count; +} fake_reader_memory_t; + +typedef struct observation_trace { + int events[128]; + size_t event_count; + int reader_calls; + int dynamic_reader_calls; + int legacy_calls; + int diagnostic_calls; + int legacy_return; + uintptr_t legacy_link_map_addr; + kzt_observation_adapter_result_t diagnostic_result; + int diagnostic_emitted; + unsigned long diagnostic_result_observations; + int dynamic_attempted; + int dynamic_cache_hit; + int dynamic_parse_return; + uintptr_t dynamic_addr; + kzt_guest_dynamic_status_t dynamic_status; + kzt_guest_dynamic_error_t dynamic_error; + size_t dynamic_entry_count; + uintptr_t dynamic_read_error_addr; + int dynamic_commit_attempted; + kzt_guest_registry_result_t dynamic_commit_result; + int dynamic_registry_emitted; + int dynamic_comparison_attempted; + int dynamic_comparison_blocking; + int dynamic_comparison_matched; +} observation_trace_t; + +typedef struct fake_callback_event { + struct link_map_x64 link_map; + char guest_name[64]; + Elf64_Dyn dynamic[4]; + fake_reader_memory_t memory; + kzt_guest_link_map_reader_ops_t ops; + observation_trace_t trace; + struct callback_barrier *barrier; + const kzt_guest_library_loader_scope_t *loader_scope; + library_t *loader_library; + kzt_guest_library_binding_result_t pending_pair_result; + int namespace_id_present; + uintptr_t namespace_id; + int map_range_present; + uintptr_t map_start; + uintptr_t map_end; +} fake_callback_event_t; + +typedef enum callback_pause_point { + CALLBACK_PAUSE_NONE = 0, + CALLBACK_PAUSE_INITIAL_READER, + CALLBACK_PAUSE_DYNAMIC_READER, + CALLBACK_PAUSE_LEGACY, +} callback_pause_point_t; + +typedef struct callback_barrier { + pthread_mutex_t lock; + pthread_cond_t cond; + callback_pause_point_t pause_point; + int reached; + int released; +} callback_barrier_t; + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lu expected %lu\n", name, got, expected); + ++failures; +} + +static void check_uintptr(const char *name, uintptr_t got, + uintptr_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void record_event(observation_trace_t *trace, int event) +{ + if (trace->event_count < TEST_ARRAY_SIZE(trace->events)) { + trace->events[trace->event_count++] = event; + } +} + +static int ranges_overlap(uintptr_t left_addr, size_t left_size, + uintptr_t right_addr, size_t right_size) +{ + uintptr_t left_end = left_addr + left_size; + uintptr_t right_end = right_addr + right_size; + + return left_addr < right_end && right_addr < left_end; +} + +static int fake_read_memory(uintptr_t guest_addr, void *dst, size_t size, + void *opaque) +{ + fake_callback_event_t *event = opaque; + fake_reader_memory_t *memory = &event->memory; + size_t i; + + ++event->trace.reader_calls; + if (ranges_overlap(guest_addr, size, (uintptr_t)event->dynamic, + sizeof(event->dynamic))) { + ++event->trace.dynamic_reader_calls; + } + record_event(&event->trace, EVENT_READER_READ); + + if (event->barrier) { + callback_pause_point_t point = + ranges_overlap(guest_addr, size, (uintptr_t)event->dynamic, + sizeof(event->dynamic)) + ? CALLBACK_PAUSE_DYNAMIC_READER + : CALLBACK_PAUSE_INITIAL_READER; + pthread_mutex_lock(&event->barrier->lock); + if (event->barrier->pause_point == point && + !event->barrier->reached) { + event->barrier->reached = 1; + pthread_cond_broadcast(&event->barrier->cond); + while (!event->barrier->released) + pthread_cond_wait(&event->barrier->cond, + &event->barrier->lock); + } + pthread_mutex_unlock(&event->barrier->lock); + } + + for (i = 0; i < memory->failure_count; ++i) { + if (ranges_overlap(guest_addr, size, + memory->failures[i].addr, + memory->failures[i].size)) { + return -1; + } + } + + if (guest_addr < memory->base || + size > memory->size || + guest_addr - memory->base > memory->size - size) { + return -1; + } + + memcpy(dst, (const void *)guest_addr, size); + return 0; +} + +static int fake_legacy_flow(uintptr_t link_map_addr, void *opaque) +{ + fake_callback_event_t *event = opaque; + observation_trace_t *trace = &event->trace; + + ++trace->legacy_calls; + trace->legacy_link_map_addr = link_map_addr; + record_event(trace, EVENT_LEGACY_FLOW); + if (event->loader_scope && event->loader_library) { + event->pending_pair_result = + kzt_guest_library_loader_scope_note_pair( + event->loader_scope, link_map_addr, + event->loader_library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED); + } + if (event->barrier) { + pthread_mutex_lock(&event->barrier->lock); + if (event->barrier->pause_point == CALLBACK_PAUSE_LEGACY && + !event->barrier->reached) { + event->barrier->reached = 1; + pthread_cond_broadcast(&event->barrier->cond); + while (!event->barrier->released) + pthread_cond_wait(&event->barrier->cond, + &event->barrier->lock); + } + pthread_mutex_unlock(&event->barrier->lock); + } + return trace->legacy_return; +} + +static void fake_diagnostic( + const kzt_observation_adapter_diagnostic_t *diagnostic, + void *opaque) +{ + observation_trace_t *trace = opaque; + + ++trace->diagnostic_calls; + trace->diagnostic_result = diagnostic->result; + trace->diagnostic_emitted = diagnostic->emitted; + trace->diagnostic_result_observations = + diagnostic->registry.result_observations; + trace->dynamic_attempted = diagnostic->dynamic.attempted; + trace->dynamic_cache_hit = diagnostic->dynamic.cache_hit; + trace->dynamic_parse_return = diagnostic->dynamic.parse_return; + trace->dynamic_addr = diagnostic->dynamic.dynamic_addr; + trace->dynamic_status = diagnostic->dynamic.status; + trace->dynamic_error = diagnostic->dynamic.error; + trace->dynamic_entry_count = diagnostic->dynamic.entry_count; + trace->dynamic_read_error_addr = diagnostic->dynamic.read_error_addr; + trace->dynamic_commit_attempted = diagnostic->dynamic.commit_attempted; + trace->dynamic_commit_result = diagnostic->dynamic.commit_result; + trace->dynamic_registry_emitted = diagnostic->dynamic.registry.emitted; + trace->dynamic_comparison_attempted = + diagnostic->dynamic.comparison_attempted; + trace->dynamic_comparison_blocking = + diagnostic->dynamic.comparison.blocking; + trace->dynamic_comparison_matched = + diagnostic->dynamic.comparison.matched; + record_event(trace, EVENT_DIAGNOSTIC); +} + +static void init_fake_callback_event(fake_callback_event_t *event, + const char *path, + uintptr_t load_bias) +{ + memset(event, 0, sizeof(*event)); + strcpy(event->guest_name, path); + event->dynamic[0].d_tag = DT_SYMTAB; + event->dynamic[0].d_un.d_ptr = load_bias + 0x3000; + event->dynamic[1].d_tag = DT_STRTAB; + event->dynamic[1].d_un.d_ptr = load_bias + 0x4000; + event->dynamic[2].d_tag = DT_STRSZ; + event->dynamic[2].d_un.d_val = 0x180; + event->dynamic[3].d_tag = DT_NULL; + event->dynamic[3].d_un.d_val = 0; + event->link_map.l_addr = load_bias; + event->link_map.l_name = event->guest_name; + event->link_map.l_ld = event->dynamic; + event->namespace_id_present = 1; + event->namespace_id = 7; + event->map_range_present = 1; + event->map_start = load_bias; + event->map_end = load_bias + 0x20000; + event->memory.base = (uintptr_t)&event->link_map; + event->memory.size = sizeof(*event) - + offsetof(fake_callback_event_t, link_map); + event->ops.read_memory = fake_read_memory; + event->ops.opaque = event; + event->trace.legacy_return = 77; +} + +static kzt_guest_object_observation_t make_observation(uintptr_t link_map_addr, + uintptr_t load_bias, + const char *path) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map_addr, + .load_bias = { load_bias, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { load_bias + 0x1000, KZT_GUEST_FIELD_OK }, + .map_start = { load_bias, KZT_GUEST_FIELD_OK }, + .map_end = { load_bias + 0x20000, KZT_GUEST_FIELD_OK }, + .namespace_id = { 7, KZT_GUEST_FIELD_OK }, + .path = { path, KZT_GUEST_FIELD_OK }, + .soname = { NULL, KZT_GUEST_FIELD_NOT_PARSED }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static void assert_old_flow_exactly_once(const char *name, + const fake_callback_event_t *event, + int expected_return) +{ + check_int(name, event->trace.legacy_calls, 1); + check_int("old-flow.return", event->trace.legacy_return, expected_return); + check_true("old-flow.link-map", + event->trace.legacy_link_map_addr == + (uintptr_t)&event->link_map); +} + +static void assert_reader_before_old_flow(const fake_callback_event_t *event) +{ + size_t i; + size_t first_reader = TEST_ARRAY_SIZE(event->trace.events); + size_t legacy = TEST_ARRAY_SIZE(event->trace.events); + + for (i = 0; i < event->trace.event_count; ++i) { + if (event->trace.events[i] == EVENT_READER_READ && + first_reader == TEST_ARRAY_SIZE(event->trace.events)) { + first_reader = i; + } + if (event->trace.events[i] == EVENT_LEGACY_FLOW && + legacy == TEST_ARRAY_SIZE(event->trace.events)) { + legacy = i; + } + } + + check_true("adapter.reader-ran", + first_reader != TEST_ARRAY_SIZE(event->trace.events)); + check_true("adapter.before-old-flow", first_reader < legacy); +} + +static int run_adapter(fake_callback_event_t *event, + kzt_guest_registry_t *registry, + int enabled, + kzt_observation_adapter_result_t *result) +{ + kzt_observation_adapter_request_t request = { + .enabled = enabled, + .link_map_addr = (uintptr_t)&event->link_map, + .registry = registry, + .reader_ops = &event->ops, + .namespace_id_present = event->namespace_id_present, + .namespace_id = event->namespace_id, + .map_range_present = event->map_range_present, + .map_start = event->map_start, + .map_end = event->map_end, + .legacy_flow = fake_legacy_flow, + .legacy_opaque = event, + }; + + return kzt_observe_guest_object_from_callback(&request, result); +} + +static int run_adapter_with_diagnostics( + fake_callback_event_t *event, + kzt_guest_registry_t *registry, + int enabled, + int force_dynamic_compare, + kzt_observation_adapter_result_t *result) +{ + kzt_observation_adapter_request_t request = { + .enabled = enabled, + .diagnostics_enabled = 1, + .dynamic_diagnostics_force_compare = force_dynamic_compare, + .link_map_addr = (uintptr_t)&event->link_map, + .registry = registry, + .reader_ops = &event->ops, + .namespace_id_present = event->namespace_id_present, + .namespace_id = event->namespace_id, + .map_range_present = event->map_range_present, + .map_start = event->map_start, + .map_end = event->map_end, + .legacy_flow = fake_legacy_flow, + .legacy_opaque = event, + .diagnostic = fake_diagnostic, + .diagnostic_opaque = &event->trace, + }; + + return kzt_observe_guest_object_from_callback(&request, result); +} + +static int run_adapter_with_bindings( + fake_callback_event_t *event, kzt_guest_registry_t *registry, + kzt_guest_library_bindings_t *bindings, + kzt_observation_adapter_result_t *result) +{ + kzt_observation_adapter_request_t request = { + .enabled = 1, + .link_map_addr = (uintptr_t)&event->link_map, + .registry = registry, + .library_bindings = bindings, + .reader_ops = &event->ops, + .namespace_id_present = event->namespace_id_present, + .namespace_id = event->namespace_id, + .map_range_present = event->map_range_present, + .map_start = event->map_start, + .map_end = event->map_end, + .legacy_flow = fake_legacy_flow, + .legacy_opaque = event, + }; + return kzt_observe_guest_object_from_callback(&request, result); +} + +static int run_adapter_with_loader_scope( + fake_callback_event_t *event, kzt_guest_registry_t *registry, + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_loader_scope_t *loader_scope, + kzt_observation_adapter_result_t *result) +{ + kzt_observation_adapter_request_t request = { + .enabled = 1, + .link_map_addr = (uintptr_t)&event->link_map, + .registry = registry, + .library_bindings = bindings, + .loader_scope = loader_scope, + .reader_ops = &event->ops, + .namespace_id_present = event->namespace_id_present, + .namespace_id = event->namespace_id, + .map_range_present = event->map_range_present, + .map_start = event->map_start, + .map_end = event->map_end, + .legacy_flow = fake_legacy_flow, + .legacy_opaque = event, + }; + return kzt_observe_guest_object_from_callback(&request, result); +} + +static unsigned long registry_object_count(kzt_guest_registry_t *registry) +{ + kzt_guest_registry_dump_t dump = { 0 }; + unsigned long count = 0; + + if (kzt_guest_registry_dump_snapshot(registry, &dump) == 0) { + count = dump.count; + } + kzt_guest_registry_dump_free(&dump); + return count; +} + +static int registry_object_state(kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long *generation, + kzt_guest_object_state_t *state) +{ + kzt_guest_registry_dump_t dump = { 0 }; + int found = 0; + + if (kzt_guest_registry_dump_snapshot(registry, &dump) == 0) { + for (size_t i = 0; i < dump.count; ++i) { + if (dump.objects[i].link_map_addr != link_map_addr) + continue; + if (generation) *generation = dump.objects[i].generation; + if (state) *state = dump.objects[i].state; + found = 1; + break; + } + } + kzt_guest_registry_dump_free(&dump); + return found ? 0 : -1; +} + +typedef struct adapter_unload_sync { + pthread_mutex_t lock; + pthread_cond_t cond; + kzt_guest_library_bindings_t *bindings; + kzt_guest_registry_t *registry; + fake_callback_event_t *event; + library_t *library; + kzt_guest_library_binding_key_t expected_key; + int phase2_entered; + int phase2_count; + int allow_phase2_retire; + kzt_guest_library_bindings_t *hook_bindings; + kzt_guest_library_binding_key_t hook_key; + library_t *hook_library; + int hook_from_observation; + int registry_waiters; + int unload_done; + int adapter_done; + int adapter_return; + kzt_observation_adapter_result_t adapter_result; +} adapter_unload_sync_t; + +static int wait_for_callback_barrier(callback_barrier_t *barrier); +static void release_callback_barrier(callback_barrier_t *barrier); +static int wait_for_unloading_lifecycle(adapter_unload_sync_t *sync); + +static int wait_for_value_at_least(pthread_cond_t *cond, + pthread_mutex_t *lock, + int *value, int expected) +{ + struct timespec deadline; + + clock_gettime(CLOCK_MONOTONIC, &deadline); + deadline.tv_sec += 30; + while (*value < expected) { + int result = pthread_cond_clockwait( + cond, lock, CLOCK_MONOTONIC, &deadline); + if (result != 0) { + fprintf(stderr, + "adapter timed wait failed: value=%d expected=%d error=%d\n", + *value, expected, result); + return -1; + } + } + return 0; +} + +static int wait_for_adapter_outcome(adapter_unload_sync_t *sync) +{ + struct timespec deadline; + + clock_gettime(CLOCK_MONOTONIC, &deadline); + deadline.tv_sec += 30; + while (!sync->adapter_done && !sync->registry_waiters) { + int result = pthread_cond_clockwait( + &sync->cond, &sync->lock, CLOCK_MONOTONIC, &deadline); + if (result != 0) { + fprintf(stderr, + "adapter outcome timeout: done=%d registry_waiters=%d error=%d\n", + sync->adapter_done, sync->registry_waiters, result); + return -1; + } + } + return 0; +} + +static void adapter_before_registry_retire( + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_binding_key_t *key, + library_t *library, int from_observation, void *opaque) +{ + adapter_unload_sync_t *sync = opaque; + + pthread_mutex_lock(&sync->lock); + sync->hook_bindings = bindings; + sync->hook_key = *key; + sync->hook_library = library; + sync->hook_from_observation = from_observation; + sync->phase2_entered = 1; + ++sync->phase2_count; + pthread_cond_broadcast(&sync->cond); + while (!sync->allow_phase2_retire) + pthread_cond_wait(&sync->cond, &sync->lock); + pthread_mutex_unlock(&sync->lock); +} + +static void adapter_registry_retire_waiting(void *opaque) +{ + adapter_unload_sync_t *sync = opaque; + + pthread_mutex_lock(&sync->lock); + ++sync->registry_waiters; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); +} + +static void *adapter_unload_worker(void *opaque) +{ + adapter_unload_sync_t *sync = opaque; + + kzt_guest_library_inactivate( + sync->bindings, sync->registry, sync->library, + sync->expected_key.link_map_addr); + pthread_mutex_lock(&sync->lock); + sync->unload_done = 1; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); + return NULL; +} + +static void *adapter_callback_worker(void *opaque) +{ + adapter_unload_sync_t *sync = opaque; + + sync->adapter_return = run_adapter_with_bindings( + sync->event, sync->registry, sync->bindings, + &sync->adapter_result); + pthread_mutex_lock(&sync->lock); + sync->adapter_done = 1; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); + return NULL; +} + +typedef struct source_lease_participant { + pthread_mutex_t lock; + pthread_cond_t cond; + kzt_guest_registry_t *registry; + kzt_guest_library_binding_key_t key; + kzt_guest_registry_source_lease_t lease; + int acquired; + int release; +} source_lease_participant_t; + +static void *source_lease_participant_worker(void *opaque) +{ + source_lease_participant_t *participant = opaque; + int acquired = kzt_guest_registry_source_lease_acquire( + participant->registry, participant->key.link_map_addr, + participant->key.generation, participant->key.namespace_id, + &participant->lease) == 0; + + pthread_mutex_lock(&participant->lock); + participant->acquired = acquired ? 1 : -1; + pthread_cond_broadcast(&participant->cond); + while (acquired && !participant->release) + pthread_cond_wait(&participant->cond, &participant->lock); + pthread_mutex_unlock(&participant->lock); + if (acquired) + kzt_guest_registry_source_lease_release(&participant->lease); + return NULL; +} + +static void test_three_participant_adapter_does_not_duplicate_retire(void) +{ + struct fake_library { int value; } library = { 9 }; + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_observation_adapter_result_t result; + unsigned long generation = 0; + adapter_unload_sync_t sync = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + .bindings = bindings, + .registry = registry, + .event = &event, + .library = (library_t *)&library, + }; + source_lease_participant_t lease_participant = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + .registry = registry, + }; + pthread_t lease_thread, unload_thread; + + init_fake_callback_event(&event, "/guest/libthree-participant.so", + 0x280000); + event.namespace_id = 0; + check_int("three.track", kzt_guest_library_track( + bindings, (library_t *)&library), 0); + check_int("three.loader-pair", kzt_guest_library_note_exact_pair( + bindings, (uintptr_t)&event.link_map, + (library_t *)&library, KZT_GUEST_LIBRARY_OBJECT_WRAPPED), + KZT_GUEST_LIBRARY_BINDING_PENDING); + check_int("three.initial-adapter", run_adapter_with_bindings( + &event, registry, bindings, &result), 77); + check_int("three.generation", registry_object_state( + registry, (uintptr_t)&event.link_map, &generation, NULL), 0); + sync.expected_key = (kzt_guest_library_binding_key_t) { + .link_map_addr = (uintptr_t)&event.link_map, + .generation = generation, + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + lease_participant.key = sync.expected_key; + + check_int("three.lease-thread", pthread_create( + &lease_thread, NULL, source_lease_participant_worker, + &lease_participant), 0); + pthread_mutex_lock(&lease_participant.lock); + check_int("three.lease-acquired", wait_for_value_at_least( + &lease_participant.cond, &lease_participant.lock, + &lease_participant.acquired, 1), 0); + pthread_mutex_unlock(&lease_participant.lock); + + kzt_guest_library_binding_test_set_before_registry_retire( + adapter_before_registry_retire, &sync); + kzt_guest_registry_test_set_before_retire_wait( + adapter_registry_retire_waiting, &sync); + check_int("three.unload-thread", pthread_create( + &unload_thread, NULL, adapter_unload_worker, &sync), 0); + pthread_mutex_lock(&sync.lock); + check_int("three.phase2", wait_for_value_at_least( + &sync.cond, &sync.lock, &sync.phase2_entered, 1), 0); + sync.allow_phase2_retire = 1; + pthread_cond_broadcast(&sync.cond); + check_int("three.registry-wait", wait_for_value_at_least( + &sync.cond, &sync.lock, &sync.registry_waiters, 1), 0); + check_true("three.binding-blocked-by-source", !sync.unload_done); + pthread_mutex_unlock(&sync.lock); + + memset(&event.trace, 0, sizeof(event.trace)); + event.trace.legacy_return = 77; + check_int("three.third-adapter", run_adapter_with_bindings( + &event, registry, bindings, &result), 0); + check_int("three.third-disabled", result, + KZT_OBSERVATION_ADAPTER_DISABLED); + check_true("three.third-no-invalid-read", + event.trace.reader_calls == 0 && + event.trace.legacy_calls == 0); + pthread_mutex_lock(&sync.lock); + check_true("three.single-retire-owner", + sync.phase2_count == 1 && sync.registry_waiters == 1 && + !sync.unload_done); + pthread_mutex_unlock(&sync.lock); + + pthread_mutex_lock(&lease_participant.lock); + lease_participant.release = 1; + pthread_cond_broadcast(&lease_participant.cond); + pthread_mutex_unlock(&lease_participant.lock); + check_int("three.lease-join", pthread_join(lease_thread, NULL), 0); + check_int("three.unload-join", pthread_join(unload_thread, NULL), 0); + + kzt_guest_registry_test_set_before_retire_wait(NULL, NULL); + kzt_guest_library_binding_test_set_before_registry_retire(NULL, NULL); + pthread_cond_destroy(&lease_participant.cond); + pthread_mutex_destroy(&lease_participant.lock); + pthread_cond_destroy(&sync.cond); + pthread_mutex_destroy(&sync.lock); + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_binding_owned_retire_excludes_adapter_retire(void) +{ + struct fake_library { int value; } library = { 2 }; + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_registry_source_lease_t lease = { 0 }; + kzt_observation_adapter_result_t initial_result; + unsigned long generation = 0; + kzt_guest_object_state_t state = KZT_GUEST_OBJECT_DISCOVERED; + kzt_guest_library_binding_state_t lifecycle_state; + adapter_unload_sync_t sync = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + .bindings = bindings, + .registry = registry, + .event = &event, + .library = (library_t *)&library, + }; + pthread_t unload_thread, adapter_thread; + int adapter_started = 0; + + init_fake_callback_event(&event, "/guest/libretire-owned.so", 0x180000); + event.namespace_id = 0; + check_int("retire-owned.track", kzt_guest_library_track( + bindings, (library_t *)&library), 0); + check_int("retire-owned.observation-first", + run_adapter_with_bindings( + &event, registry, bindings, &initial_result), 77); + check_int("retire-owned.initial-result", initial_result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("retire-owned.snapshot", registry_object_state( + registry, (uintptr_t)&event.link_map, &generation, &state), + 0); + sync.expected_key = (kzt_guest_library_binding_key_t) { + .link_map_addr = (uintptr_t)&event.link_map, + .generation = generation, + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + check_int("retire-owned.lease", kzt_guest_registry_source_lease_acquire( + registry, sync.expected_key.link_map_addr, generation, 0, + &lease), 0); + + kzt_guest_library_binding_test_set_before_registry_retire( + adapter_before_registry_retire, &sync); + kzt_guest_registry_test_set_before_retire_wait( + adapter_registry_retire_waiting, &sync); + check_int("retire-owned.unload-thread", pthread_create( + &unload_thread, NULL, adapter_unload_worker, &sync), 0); + pthread_mutex_lock(&sync.lock); + check_int("retire-owned.phase2-barrier", wait_for_value_at_least( + &sync.cond, &sync.lock, &sync.phase2_entered, 1), 0); + check_true("retire-owned.hook-identity", + sync.hook_bindings == bindings && + sync.hook_library == (library_t *)&library && + sync.hook_from_observation && + sync.hook_key.link_map_addr == sync.expected_key.link_map_addr && + sync.hook_key.generation == sync.expected_key.generation); + pthread_mutex_unlock(&sync.lock); + + adapter_started = pthread_create( + &adapter_thread, NULL, adapter_callback_worker, &sync) == 0; + check_true("retire-owned.adapter-thread", adapter_started); + if (adapter_started) { + pthread_mutex_lock(&sync.lock); + check_int("retire-owned.adapter-outcome", + wait_for_adapter_outcome(&sync), 0); + check_true("retire-owned.adapter-did-not-retire", + sync.adapter_done && sync.registry_waiters == 0); + check_true("retire-owned.unload-still-paused", !sync.unload_done); + pthread_mutex_unlock(&sync.lock); + } + + pthread_mutex_lock(&sync.lock); + sync.allow_phase2_retire = 1; + pthread_cond_broadcast(&sync.cond); + check_int("retire-owned.binding-waits-on-lease", wait_for_value_at_least( + &sync.cond, &sync.lock, &sync.registry_waiters, 1), 0); + check_true("retire-owned.unload-not-returned", !sync.unload_done); + pthread_mutex_unlock(&sync.lock); + kzt_guest_registry_source_lease_release(&lease); + + pthread_mutex_lock(&sync.lock); + check_int("retire-owned.unload-completes", wait_for_value_at_least( + &sync.cond, &sync.lock, &sync.unload_done, 1), 0); + pthread_mutex_unlock(&sync.lock); + if (adapter_started) { + check_int("retire-owned.adapter-join", + pthread_join(adapter_thread, NULL), 0); + check_int("retire-owned.adapter-result", sync.adapter_result, + KZT_OBSERVATION_ADAPTER_DISABLED); + check_int("retire-owned.adapter-return", sync.adapter_return, 0); + } + check_int("retire-owned.unload-join", pthread_join(unload_thread, NULL), + 0); + check_int("retire-owned.final-snapshot", registry_object_state( + registry, sync.expected_key.link_map_addr, NULL, &state), 0); + check_int("retire-owned.final-dead", state, KZT_GUEST_OBJECT_DEAD); + check_int("retire-owned.lifecycle-snapshot", + kzt_guest_library_binding_test_snapshot( + bindings, (library_t *)&library, &lifecycle_state, + NULL, NULL), 0); + check_int("retire-owned.lifecycle-dead", lifecycle_state, + KZT_GUEST_LIBRARY_BINDING_DEAD); + + kzt_guest_registry_test_set_before_retire_wait(NULL, NULL); + kzt_guest_library_binding_test_set_before_registry_retire(NULL, NULL); + pthread_cond_destroy(&sync.cond); + pthread_mutex_destroy(&sync.lock); + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_adapter_note_before_unload_leaves_single_retire_owner(void) +{ + struct fake_library { int value; } library = { 3 }; + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_registry_source_lease_t lease = { 0 }; + kzt_observation_adapter_result_t result; + unsigned long generation = 0; + kzt_guest_object_state_t state = KZT_GUEST_OBJECT_DISCOVERED; + kzt_guest_library_binding_state_t lifecycle_state; + adapter_unload_sync_t sync = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + .bindings = bindings, + .registry = registry, + .event = &event, + .library = (library_t *)&library, + }; + pthread_t unload_thread; + + init_fake_callback_event(&event, "/guest/libadapter-first.so", 0x1a0000); + event.namespace_id = 0; + check_int("adapter-first.track", kzt_guest_library_track( + bindings, (library_t *)&library), 0); + check_int("adapter-first.callback", run_adapter_with_bindings( + &event, registry, bindings, &result), 77); + check_int("adapter-first.result", result, KZT_OBSERVATION_ADAPTER_ADDED); + check_int("adapter-first.live", registry_object_state( + registry, (uintptr_t)&event.link_map, &generation, &state), + 0); + check_true("adapter-first.note-did-not-retire", + state != KZT_GUEST_OBJECT_UNLOADING && + state != KZT_GUEST_OBJECT_DEAD); + sync.expected_key = (kzt_guest_library_binding_key_t) { + .link_map_addr = (uintptr_t)&event.link_map, + .generation = generation, + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + check_int("adapter-first.lease", kzt_guest_registry_source_lease_acquire( + registry, sync.expected_key.link_map_addr, generation, 0, + &lease), 0); + kzt_guest_library_binding_test_set_before_registry_retire( + adapter_before_registry_retire, &sync); + kzt_guest_registry_test_set_before_retire_wait( + adapter_registry_retire_waiting, &sync); + check_int("adapter-first.unload-thread", pthread_create( + &unload_thread, NULL, adapter_unload_worker, &sync), 0); + pthread_mutex_lock(&sync.lock); + check_int("adapter-first.phase2", wait_for_value_at_least( + &sync.cond, &sync.lock, &sync.phase2_entered, 1), 0); + check_true("adapter-first.single-binding-owner", + sync.hook_library == (library_t *)&library && + sync.hook_from_observation && + sync.hook_key.generation == generation); + sync.allow_phase2_retire = 1; + pthread_cond_broadcast(&sync.cond); + check_int("adapter-first.retire-wait", wait_for_value_at_least( + &sync.cond, &sync.lock, &sync.registry_waiters, 1), 0); + check_true("adapter-first.unload-blocked", !sync.unload_done); + pthread_mutex_unlock(&sync.lock); + kzt_guest_registry_source_lease_release(&lease); + check_int("adapter-first.join", pthread_join(unload_thread, NULL), 0); + check_int("adapter-first.final", registry_object_state( + registry, sync.expected_key.link_map_addr, NULL, &state), 0); + check_int("adapter-first.dead", state, KZT_GUEST_OBJECT_DEAD); + check_int("adapter-first.lifecycle", kzt_guest_library_binding_test_snapshot( + bindings, (library_t *)&library, &lifecycle_state, + NULL, NULL), 0); + check_int("adapter-first.lifecycle-dead", lifecycle_state, + KZT_GUEST_LIBRARY_BINDING_DEAD); + + kzt_guest_registry_test_set_before_retire_wait(NULL, NULL); + kzt_guest_library_binding_test_set_before_registry_retire(NULL, NULL); + pthread_cond_destroy(&sync.cond); + pthread_mutex_destroy(&sync.lock); + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_cancelled_pending_without_owner_is_adapter_retired(void) +{ + struct fake_library { int value; } library = { 4 }; + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_observation_adapter_result_t result; + kzt_guest_object_state_t state = KZT_GUEST_OBJECT_DISCOVERED; + kzt_guest_library_binding_state_t lifecycle_state; + size_t active_pending = 1; + callback_barrier_t barrier = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + .pause_point = CALLBACK_PAUSE_INITIAL_READER, + }; + adapter_unload_sync_t sync = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + .bindings = bindings, + .registry = registry, + .event = &event, + .library = (library_t *)&library, + }; + pthread_t callback_thread, unload_thread; + + init_fake_callback_event(&event, "/guest/libcancelled-pending.so", + 0x1c0000); + event.namespace_id = 0; + check_int("cancelled-pending.track", kzt_guest_library_track( + bindings, (library_t *)&library), 0); + check_int("cancelled-pending.pair", kzt_guest_library_note_exact_pair( + bindings, (uintptr_t)&event.link_map, + (library_t *)&library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED), + KZT_GUEST_LIBRARY_BINDING_PENDING); + sync.expected_key.link_map_addr = (uintptr_t)&event.link_map; + event.barrier = &barrier; + check_int("cancelled-pending.callback-thread", pthread_create( + &callback_thread, NULL, adapter_callback_worker, &sync), 0); + check_int("cancelled-pending.reader-barrier", + wait_for_callback_barrier(&barrier), 0); + check_int("cancelled-pending.unload-thread", pthread_create( + &unload_thread, NULL, adapter_unload_worker, &sync), 0); + check_int("cancelled-pending.unloading", + wait_for_unloading_lifecycle(&sync), 0); + release_callback_barrier(&barrier); + check_int("cancelled-pending.callback-join", + pthread_join(callback_thread, NULL), 0); + check_int("cancelled-pending.unload-join", + pthread_join(unload_thread, NULL), 0); + result = sync.adapter_result; + check_int("cancelled-pending.callback", sync.adapter_return, 77); + check_int("cancelled-pending.result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("cancelled-pending.snapshot", registry_object_state( + registry, (uintptr_t)&event.link_map, NULL, &state), 0); + check_int("cancelled-pending.adapter-retired", state, + KZT_GUEST_OBJECT_DEAD); + check_int("cancelled-pending.binding-state", + kzt_guest_library_binding_test_snapshot( + bindings, (library_t *)&library, &lifecycle_state, + &active_pending, NULL), 0); + check_true("cancelled-pending.no-owner-left", + lifecycle_state == KZT_GUEST_LIBRARY_BINDING_DEAD && + active_pending == 0); + + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); + pthread_cond_destroy(&sync.cond); + pthread_mutex_destroy(&sync.lock); + pthread_cond_destroy(&barrier.cond); + pthread_mutex_destroy(&barrier.lock); +} + +static int wait_for_callback_barrier(callback_barrier_t *barrier) +{ + struct timespec deadline; + + clock_gettime(CLOCK_MONOTONIC, &deadline); + deadline.tv_sec += 30; + pthread_mutex_lock(&barrier->lock); + while (!barrier->reached) { + int result = pthread_cond_clockwait( + &barrier->cond, &barrier->lock, CLOCK_MONOTONIC, &deadline); + if (result != 0) { + fprintf(stderr, "callback barrier timeout: point=%d error=%d\n", + barrier->pause_point, result); + pthread_mutex_unlock(&barrier->lock); + return -1; + } + } + pthread_mutex_unlock(&barrier->lock); + return 0; +} + +static void release_callback_barrier(callback_barrier_t *barrier) +{ + pthread_mutex_lock(&barrier->lock); + barrier->released = 1; + pthread_cond_broadcast(&barrier->cond); + pthread_mutex_unlock(&barrier->lock); +} + +static int wait_for_unloading_lifecycle(adapter_unload_sync_t *sync) +{ + struct timespec deadline; + + clock_gettime(CLOCK_MONOTONIC, &deadline); + deadline.tv_sec += 30; + for (;;) { + kzt_guest_library_binding_state_t state; + if (kzt_guest_library_binding_test_snapshot( + sync->bindings, sync->library, &state, NULL, NULL) == 0 && + state != KZT_GUEST_LIBRARY_BINDING_LIVE) + return state == KZT_GUEST_LIBRARY_BINDING_UNLOADING ? 0 : 1; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + if (now.tv_sec > deadline.tv_sec || + (now.tv_sec == deadline.tv_sec && now.tv_nsec >= deadline.tv_nsec)) { + fprintf(stderr, "lifecycle did not leave LIVE before timeout\n"); + return -1; + } + sched_yield(); + } +} + +static int unload_stays_blocked(adapter_unload_sync_t *sync) +{ + struct timespec deadline; + int result = 0; + + clock_gettime(CLOCK_MONOTONIC, &deadline); + deadline.tv_nsec += 100 * 1000 * 1000; + if (deadline.tv_nsec >= 1000 * 1000 * 1000) { + ++deadline.tv_sec; + deadline.tv_nsec -= 1000 * 1000 * 1000; + } + pthread_mutex_lock(&sync->lock); + while (!sync->unload_done && result == 0) + result = pthread_cond_clockwait( + &sync->cond, &sync->lock, CLOCK_MONOTONIC, &deadline); + int blocked = !sync->unload_done; + pthread_mutex_unlock(&sync->lock); + return blocked; +} + +static void test_exact_retire_owner_uses_complete_key(void) +{ + struct fake_library { int value; } library = { 5 }; + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_observation_adapter_result_t result; + unsigned long generation = 0; + adapter_unload_sync_t sync = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + .bindings = bindings, + .registry = registry, + .event = &event, + .library = (library_t *)&library, + }; + pthread_t unload_thread; + + init_fake_callback_event(&event, "/guest/libexact-owner.so", 0x1e0000); + event.namespace_id = 0; + check_int("exact-owner.track", kzt_guest_library_track( + bindings, (library_t *)&library), 0); + check_int("exact-owner.pair", kzt_guest_library_note_exact_pair( + bindings, (uintptr_t)&event.link_map, + (library_t *)&library, KZT_GUEST_LIBRARY_OBJECT_WRAPPED), + KZT_GUEST_LIBRARY_BINDING_PENDING); + check_int("exact-owner.initial", run_adapter_with_bindings( + &event, registry, bindings, &result), 77); + check_int("exact-owner.snapshot", registry_object_state( + registry, (uintptr_t)&event.link_map, &generation, NULL), 0); + sync.expected_key = (kzt_guest_library_binding_key_t) { + .link_map_addr = (uintptr_t)&event.link_map, + .generation = generation, + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + kzt_guest_library_binding_test_set_before_registry_retire( + adapter_before_registry_retire, &sync); + check_int("exact-owner.unload-thread", pthread_create( + &unload_thread, NULL, adapter_unload_worker, &sync), 0); + pthread_mutex_lock(&sync.lock); + check_int("exact-owner.phase2", wait_for_value_at_least( + &sync.cond, &sync.lock, &sync.phase2_entered, 1), 0); + pthread_mutex_unlock(&sync.lock); + + check_int("exact-owner.same-generation", + kzt_guest_library_note_observation(bindings, + &sync.expected_key), + KZT_GUEST_LIBRARY_BINDING_RETIRE_OWNED); + kzt_guest_library_binding_key_t other = sync.expected_key; + ++other.generation; + check_true("exact-owner.other-generation-not-owned", + kzt_guest_library_note_observation(bindings, &other) != + KZT_GUEST_LIBRARY_BINDING_RETIRE_OWNED); + + pthread_mutex_lock(&sync.lock); + sync.allow_phase2_retire = 1; + pthread_cond_broadcast(&sync.cond); + pthread_mutex_unlock(&sync.lock); + check_int("exact-owner.join", pthread_join(unload_thread, NULL), 0); + kzt_guest_library_binding_test_set_before_registry_retire(NULL, NULL); + pthread_cond_destroy(&sync.cond); + pthread_mutex_destroy(&sync.lock); + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void run_callback_lifetime_wait_test(callback_pause_point_t point, + const char *name) +{ + struct fake_library { int value; } library = { 6 }; + fake_callback_event_t event; + callback_barrier_t barrier = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + .pause_point = point, + }; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_observation_adapter_result_t result; + adapter_unload_sync_t sync = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + .bindings = bindings, + .registry = registry, + .event = &event, + .library = (library_t *)&library, + }; + pthread_t callback_thread, unload_thread; + + init_fake_callback_event(&event, "/guest/libcallback-lifetime.so", + 0x200000 + (uintptr_t)point * 0x10000); + event.namespace_id = 0; + check_int(name, kzt_guest_library_track(bindings, + (library_t *)&library), 0); + check_int("callback-lifetime.pair", kzt_guest_library_note_exact_pair( + bindings, (uintptr_t)&event.link_map, + (library_t *)&library, KZT_GUEST_LIBRARY_OBJECT_WRAPPED), + KZT_GUEST_LIBRARY_BINDING_PENDING); + check_int("callback-lifetime.initial", run_adapter_with_bindings( + &event, registry, bindings, &result), 77); + memset(&event.trace, 0, sizeof(event.trace)); + event.trace.legacy_return = 77; + event.barrier = &barrier; + check_int("callback-lifetime.callback-thread", pthread_create( + &callback_thread, NULL, adapter_callback_worker, &sync), 0); + check_int("callback-lifetime.barrier", wait_for_callback_barrier(&barrier), + 0); + check_int("callback-lifetime.unload-thread", pthread_create( + &unload_thread, NULL, adapter_unload_worker, &sync), 0); + check_int("callback-lifetime.unloading", + wait_for_unloading_lifecycle(&sync), 0); + check_true("callback-lifetime.unload-waits", + unload_stays_blocked(&sync)); + release_callback_barrier(&barrier); + check_int("callback-lifetime.callback-join", + pthread_join(callback_thread, NULL), 0); + check_int("callback-lifetime.unload-join", + pthread_join(unload_thread, NULL), 0); + check_int("callback-lifetime.legacy-once", event.trace.legacy_calls, 1); + + pthread_cond_destroy(&sync.cond); + pthread_mutex_destroy(&sync.lock); + pthread_cond_destroy(&barrier.cond); + pthread_mutex_destroy(&barrier.lock); + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_callback_lifetime_covers_all_guest_work(void) +{ + run_callback_lifetime_wait_test(CALLBACK_PAUSE_INITIAL_READER, + "callback-lifetime.initial-reader"); + run_callback_lifetime_wait_test(CALLBACK_PAUSE_DYNAMIC_READER, + "callback-lifetime.dynamic-reader"); + run_callback_lifetime_wait_test(CALLBACK_PAUSE_LEGACY, + "callback-lifetime.legacy"); +} + +static void test_unload_winner_rejects_late_callback_before_any_work(void) +{ + struct fake_library { int value; } library = { 7 }; + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_observation_adapter_result_t result; + + init_fake_callback_event(&event, "/guest/libunload-wins.so", 0x240000); + event.namespace_id = 0; + check_int("unload-wins.track", kzt_guest_library_track( + bindings, (library_t *)&library), 0); + check_int("unload-wins.pair", kzt_guest_library_note_exact_pair( + bindings, (uintptr_t)&event.link_map, + (library_t *)&library, KZT_GUEST_LIBRARY_OBJECT_WRAPPED), + KZT_GUEST_LIBRARY_BINDING_PENDING); + check_int("unload-wins.initial", run_adapter_with_bindings( + &event, registry, bindings, &result), 77); + kzt_guest_library_inactivate(bindings, registry, + (library_t *)&library, + (uintptr_t)&event.link_map); + memset(&event.trace, 0, sizeof(event.trace)); + event.trace.legacy_return = 77; + check_int("unload-wins.callback", run_adapter_with_bindings( + &event, registry, bindings, &result), 0); + check_int("unload-wins.result", result, + KZT_OBSERVATION_ADAPTER_DISABLED); + check_true("unload-wins.no-work", + event.trace.reader_calls == 0 && + event.trace.legacy_calls == 0 && + event.trace.diagnostic_calls == 0); + + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_callback_gate_allocation_failure_is_safe_fail_open(void) +{ + struct fake_library { int value; } library = { 8 }; + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_observation_adapter_result_t result; + + init_fake_callback_event(&event, "/guest/libgate-alloc-fail.so", 0x260000); + event.namespace_id = 0; + check_int("gate-alloc-fail.track", kzt_guest_library_track( + bindings, (library_t *)&library), 0); + check_int("gate-alloc-fail.pair", kzt_guest_library_note_exact_pair( + bindings, (uintptr_t)&event.link_map, + (library_t *)&library, KZT_GUEST_LIBRARY_OBJECT_WRAPPED), + KZT_GUEST_LIBRARY_BINDING_PENDING); + kzt_guest_library_binding_test_set_alloc_failure_after(0); + check_int("gate-alloc-fail.callback", run_adapter_with_bindings( + &event, registry, bindings, &result), 77); + check_int("gate-alloc-fail.legacy", event.trace.legacy_calls, 1); + kzt_guest_library_inactivate(bindings, registry, + (library_t *)&library, + (uintptr_t)&event.link_map); + memset(&event.trace, 0, sizeof(event.trace)); + event.trace.legacy_return = 77; + check_int("gate-alloc-fail.late-callback", run_adapter_with_bindings( + &event, registry, bindings, &result), 0); + check_true("gate-alloc-fail.late-no-work", + event.trace.reader_calls == 0 && + event.trace.legacy_calls == 0); + kzt_guest_library_binding_test_set_alloc_failure_after(-1); + + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void run_adapter_address_reuse_test(int force_gate_alloc_failure, + const char *name) +{ + struct fake_library { int value; } old_library = { 10 }, + new_library = { 11 }; + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_scope_t scope = { 0 }; + kzt_observation_adapter_result_t result; + + init_fake_callback_event(&event, "/guest/libadapter-reuse.so", 0x2a0000); + event.namespace_id = 0; + check_int(name, kzt_guest_library_track( + bindings, (library_t *)&old_library), 0); + check_int("adapter-reuse old pair", + kzt_guest_library_publish_loader_pair( + bindings, (uintptr_t)&event.link_map, + (library_t *)&old_library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED), + KZT_GUEST_LIBRARY_BINDING_PENDING); + if (!force_gate_alloc_failure) { + check_int("adapter-reuse seed gate", run_adapter_with_bindings( + &event, registry, bindings, &result), 77); + } else { + kzt_guest_library_binding_test_set_alloc_failure_after(0); + } + kzt_guest_library_inactivate(bindings, registry, + (library_t *)&old_library, + (uintptr_t)&event.link_map); + kzt_guest_library_binding_test_set_alloc_failure_after(-1); + + memset(&event.trace, 0, sizeof(event.trace)); + event.trace.legacy_return = 77; + check_int("adapter-reuse stale return", run_adapter_with_bindings( + &event, registry, bindings, &result), 0); + check_int("adapter-reuse stale disabled", result, + KZT_OBSERVATION_ADAPTER_DISABLED); + check_true("adapter-reuse stale no read", + event.trace.reader_calls == 0 && + event.trace.legacy_calls == 0); + + check_int("adapter-reuse new track", kzt_guest_library_track( + bindings, (library_t *)&new_library), 0); + check_int("adapter-reuse scope", kzt_guest_library_loader_scope_begin( + bindings, &scope), 0); + check_int("adapter-reuse current return", run_adapter_with_loader_scope( + &event, registry, bindings, &scope, &result), 77); + check_true("adapter-reuse current read", + event.trace.reader_calls > 0 && + event.trace.legacy_calls == 1); + check_true("adapter-reuse pending pair", + kzt_guest_library_loader_scope_note_pair( + &scope, (uintptr_t)&event.link_map, + (library_t *)&new_library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) != + KZT_GUEST_LIBRARY_BINDING_ERROR); + check_true("adapter-reuse new pair", + kzt_guest_library_loader_scope_publish_pair( + &scope, (uintptr_t)&event.link_map, + (library_t *)&new_library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) != + KZT_GUEST_LIBRARY_BINDING_ERROR); + kzt_guest_library_loader_scope_end(&scope); + + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_adapter_address_reuse_requires_loader_causality(void) +{ + run_adapter_address_reuse_test(0, "adapter-reuse normal"); + run_adapter_address_reuse_test(1, "adapter-reuse fallback"); +} + +static void run_adapter_pending_then_wrapper_result(int wrapper_success) +{ + struct fake_library { int value; } old_library = { 12 }, + new_library = { 13 }; + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_scope_t scope = { 0 }; + kzt_guest_library_callback_access_t probe = { 0 }; + kzt_observation_adapter_result_t result; + uintptr_t map; + + init_fake_callback_event(&event, "/guest/libpending-wrapper.so", 0x2b0000); + event.namespace_id = 0; + map = (uintptr_t)&event.link_map; + check_int("pending-wrapper old track", kzt_guest_library_track( + bindings, (library_t *)&old_library), 0); + check_int("pending-wrapper old pair", kzt_guest_library_publish_loader_pair( + bindings, map, (library_t *)&old_library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED), + KZT_GUEST_LIBRARY_BINDING_PENDING); + kzt_guest_library_inactivate(bindings, registry, + (library_t *)&old_library, map); + check_int("pending-wrapper new track", kzt_guest_library_track( + bindings, (library_t *)&new_library), 0); + check_int("pending-wrapper scope", kzt_guest_library_loader_scope_begin( + bindings, &scope), 0); + event.loader_scope = &scope; + event.loader_library = (library_t *)&new_library; + check_int("pending-wrapper adapter", run_adapter_with_loader_scope( + &event, registry, bindings, &scope, &result), 77); + check_true("pending-wrapper callback observed", + event.pending_pair_result == KZT_GUEST_LIBRARY_BINDING_PENDING || + event.pending_pair_result == KZT_GUEST_LIBRARY_BINDING_ADDED || + event.pending_pair_result == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + check_true("pending-wrapper not yet reopened", + kzt_guest_library_callback_access_begin( + bindings, map, &probe) != 0); + + if (wrapper_success) { + check_true("pending-wrapper final publish", + kzt_guest_library_loader_scope_publish_pair( + &scope, map, (library_t *)&new_library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) != + KZT_GUEST_LIBRARY_BINDING_ERROR); + } + kzt_guest_library_loader_scope_end(&scope); + if (wrapper_success) { + check_int("pending-wrapper success reopened", + kzt_guest_library_callback_access_begin( + bindings, map, &probe), 0); + kzt_guest_library_callback_access_end(&probe); + } else { + check_true("pending-wrapper failure closed", + kzt_guest_library_callback_access_begin( + bindings, map, &probe) != 0); + } + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_callback_pending_observation_waits_for_wrapper_success(void) +{ + run_adapter_pending_then_wrapper_result(0); + run_adapter_pending_then_wrapper_result(1); +} + +static void assert_dynamic_view_complete(const char *name, + kzt_guest_registry_t *registry, + const fake_callback_event_t *event, + unsigned long expected_generation) +{ + kzt_guest_dynamic_view_t view = { 0 }; + kzt_guest_field_status_t status = KZT_GUEST_FIELD_UNKNOWN; + unsigned long generation = 0; + + check_int(name, kzt_guest_registry_find_dynamic_view( + registry, (uintptr_t)&event->link_map, &view, &status, + &generation), 0); + check_int("dynamic.status", status, KZT_GUEST_FIELD_OK); + check_ulong("dynamic.generation", generation, expected_generation); + check_uintptr("dynamic.addr", view.dynamic_addr, + (uintptr_t)event->dynamic); + check_uintptr("dynamic.load-bias", view.load_bias, + event->link_map.l_addr); + check_int("dynamic.view-status", view.status, + KZT_GUEST_DYNAMIC_COMPLETE); + check_ulong("dynamic.entry-count", view.entry_count, 3); + check_int("dynamic.has-null", view.has_null, 1); + check_true("dynamic.symtab.present", view.symtab.present); + check_uintptr("dynamic.symtab", view.symtab.value, + event->link_map.l_addr + 0x3000); + check_int("dynamic.symtab.semantics", view.symtab.address_semantics, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_true("dynamic.strtab.present", view.strtab.present); + check_uintptr("dynamic.strtab", view.strtab.value, + event->link_map.l_addr + 0x4000); + check_int("dynamic.strtab.semantics", view.strtab.address_semantics, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_true("dynamic.strsz.present", view.strsz.present); + check_ulong("dynamic.strsz", view.strsz.value, 0x180); + check_int("dynamic.strsz.semantics", view.strsz.address_semantics, + KZT_GUEST_DYNAMIC_SCALAR); +} + +static void assert_dynamic_view_read_error(const char *name, + kzt_guest_registry_t *registry, + const fake_callback_event_t *event) +{ + kzt_guest_dynamic_view_t view = { 0 }; + kzt_guest_field_status_t status = KZT_GUEST_FIELD_UNKNOWN; + unsigned long generation = 0; + + check_int(name, kzt_guest_registry_find_dynamic_view( + registry, (uintptr_t)&event->link_map, &view, &status, + &generation), 0); + check_int("dynamic.read-error.status", status, + KZT_GUEST_FIELD_READ_ERROR); + check_ulong("dynamic.read-error.generation", generation, 1); + check_uintptr("dynamic.read-error.addr", view.dynamic_addr, + (uintptr_t)event->dynamic); + check_int("dynamic.read-error.view-status", view.status, + KZT_GUEST_DYNAMIC_READ_ERROR); + check_ulong("dynamic.read-error.entry-count", view.entry_count, 1); + check_int("dynamic.read-error.no-null", view.has_null, 0); +} + +static void assert_dynamic_view_not_parsed(const char *name, + kzt_guest_registry_t *registry, + const fake_callback_event_t *event) +{ + kzt_guest_dynamic_view_t view = { 0 }; + kzt_guest_field_status_t status = KZT_GUEST_FIELD_UNKNOWN; + unsigned long generation = 0; + + check_int(name, kzt_guest_registry_find_dynamic_view( + registry, (uintptr_t)&event->link_map, &view, &status, + &generation), 0); + check_int("dynamic.not-parsed.status", status, + KZT_GUEST_FIELD_NOT_PARSED); + check_ulong("dynamic.not-parsed.generation", generation, 1); + check_uintptr("dynamic.not-parsed.addr", view.dynamic_addr, 0); +} + +static void test_active_observation_adds_object_and_preserves_old_flow(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libactive.so", 0x100000); + event.trace.legacy_return = 23; + + ret = run_adapter(&event, registry, 1, &result); + + check_int("active.return", ret, 23); + check_int("active.result", result, KZT_OBSERVATION_ADAPTER_ADDED); + assert_old_flow_exactly_once("active.old-flow", &event, 23); + assert_reader_before_old_flow(&event); + check_ulong("active.registry-count", registry_object_count(registry), 1); + + kzt_guest_registry_destroy(®istry); +} + +static void test_exact_pair_retries_after_transient_observation_failure(void) +{ + struct fake_library { int value; } library = { 1 }; + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_observation_adapter_result_t result; + kzt_guest_library_binding_key_t key; + kzt_guest_library_handle_t handle; + + init_fake_callback_event(&event, "/guest/libexact.so", 0x160000); + event.namespace_id = 0; + check_int("exact.track", kzt_guest_library_track( + bindings, (library_t *)&library), 0); + check_int("exact.pending", kzt_guest_library_note_exact_pair( + bindings, (uintptr_t)&event.link_map, + (library_t *)&library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED), + KZT_GUEST_LIBRARY_BINDING_PENDING); + kzt_guest_registry_test_set_alloc_failure_after(0); + (void)run_adapter_with_bindings(&event, registry, bindings, &result); + check_int("exact.transient-result", result, + KZT_OBSERVATION_ADAPTER_REGISTRY_FAILED); + kzt_guest_registry_test_set_alloc_failure_after(-1); + (void)run_adapter_with_bindings(&event, registry, bindings, &result); + check_int("exact.retry-result", result, KZT_OBSERVATION_ADAPTER_ADDED); + key = (kzt_guest_library_binding_key_t){ + .link_map_addr = (uintptr_t)&event.link_map, + .generation = 1, + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + check_int("exact.lookup", kzt_guest_library_lookup( + bindings, &key, &handle), 0); + check_true("exact.library", handle.library == (library_t *)&library); + kzt_guest_library_handle_release(&handle); + kzt_guest_library_unbind(bindings, registry, (library_t *)&library, + (uintptr_t)&event.link_map); + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_dynamic_parser_success_commits_snapshot(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 4, + }; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdynamic.so", 0x180000); + event.trace.legacy_return = 41; + check_int("dynamic-success.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + ret = run_adapter_with_diagnostics(&event, registry, 1, 0, &result); + + check_int("dynamic-success.return", ret, 41); + check_int("dynamic-success.result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + assert_old_flow_exactly_once("dynamic-success.old-flow", &event, 41); + assert_dynamic_view_complete("dynamic-success.view", registry, &event, 1); + check_int("dynamic-success.diagnostic-attempted", + event.trace.dynamic_attempted, 1); + check_int("dynamic-success.diagnostic-parse-return", + event.trace.dynamic_parse_return, 0); + check_int("dynamic-success.diagnostic-status", + event.trace.dynamic_status, KZT_GUEST_DYNAMIC_COMPLETE); + check_int("dynamic-success.diagnostic-commit", + event.trace.dynamic_commit_attempted, 1); + check_int("dynamic-success.diagnostic-commit-result", + event.trace.dynamic_commit_result, KZT_GUEST_REGISTRY_UPDATED); + + kzt_guest_registry_destroy(®istry); +} + +static void test_dynamic_parser_reuses_same_generation_complete_view(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 4, + }; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdynamic-cache.so", 0x1a0000); + event.trace.legacy_return = 44; + check_int("dynamic-cache.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + ret = run_adapter_with_diagnostics(&event, registry, 1, 0, &result); + check_int("dynamic-cache.first-return", ret, 44); + check_int("dynamic-cache.first-result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("dynamic-cache.first-attempted", event.trace.dynamic_attempted, 1); + check_true("dynamic-cache.first-read", + event.trace.dynamic_reader_calls > 0); + assert_dynamic_view_complete("dynamic-cache.first-view", registry, &event, + 1); + + memset(&event.trace, 0, sizeof(event.trace)); + event.trace.legacy_return = 45; + result = KZT_OBSERVATION_ADAPTER_DISABLED; + ret = run_adapter_with_diagnostics(&event, registry, 1, 0, &result); + + check_int("dynamic-cache.second-return", ret, 45); + check_int("dynamic-cache.second-result", result, + KZT_OBSERVATION_ADAPTER_UNCHANGED); + assert_old_flow_exactly_once("dynamic-cache.second-old-flow", &event, 45); + check_int("dynamic-cache.second-cache-hit", + event.trace.dynamic_cache_hit, 1); + check_int("dynamic-cache.second-attempted", + event.trace.dynamic_attempted, 0); + check_int("dynamic-cache.second-dynamic-reads", + event.trace.dynamic_reader_calls, 0); + assert_dynamic_view_complete("dynamic-cache.second-view", registry, &event, + 1); + + kzt_guest_registry_destroy(®istry); +} + +static void test_dynamic_parser_read_failure_is_fail_open(void) +{ + fake_callback_event_t event; + fake_read_failure_t failure; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 4, + }; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdynamic-readfail.so", + 0x190000); + event.trace.legacy_return = 42; + failure.addr = (uintptr_t)&event.dynamic[1]; + failure.size = sizeof(event.dynamic[1]); + event.memory.failures = &failure; + event.memory.failure_count = 1; + check_int("dynamic-readfail.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + ret = run_adapter_with_diagnostics(&event, registry, 1, 0, &result); + + check_int("dynamic-readfail.return", ret, 42); + check_int("dynamic-readfail.result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + assert_old_flow_exactly_once("dynamic-readfail.old-flow", &event, 42); + assert_dynamic_view_read_error("dynamic-readfail.view", registry, &event); + check_int("dynamic-readfail.diagnostic-attempted", + event.trace.dynamic_attempted, 1); + check_int("dynamic-readfail.diagnostic-status", + event.trace.dynamic_status, KZT_GUEST_DYNAMIC_READ_ERROR); + check_uintptr("dynamic-readfail.diagnostic-read-addr", + event.trace.dynamic_read_error_addr, + (uintptr_t)&event.dynamic[1]); + check_int("dynamic-readfail.diagnostic-commit-result", + event.trace.dynamic_commit_result, KZT_GUEST_REGISTRY_UPDATED); + + kzt_guest_registry_destroy(®istry); +} + +static void test_dynamic_diagnostics_preserve_complete_view(void) +{ + fake_callback_event_t event; + fake_read_failure_t failure; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 4, + }; + + check_true("dynamic-compare.registry", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdynamic-compare.so", + 0x1d0000); + check_int("dynamic-compare.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), 0); + check_int("dynamic-compare.seed", run_adapter_with_diagnostics( + &event, registry, 1, 0, &result), 77); + assert_dynamic_view_complete("dynamic-compare.seed-view", registry, + &event, 1); + + memset(&event.trace, 0, sizeof(event.trace)); + event.trace.legacy_return = 77; + failure.addr = (uintptr_t)&event.dynamic[1]; + failure.size = sizeof(event.dynamic[1]); + event.memory.failures = &failure; + event.memory.failure_count = 1; + check_int("dynamic-compare.read-failure", run_adapter_with_diagnostics( + &event, registry, 1, 1, &result), 77); + check_int("dynamic-compare.result", result, + KZT_OBSERVATION_ADAPTER_UNCHANGED); + assert_dynamic_view_complete("dynamic-compare.preserved", registry, + &event, 1); + check_int("dynamic-compare.attempted", + event.trace.dynamic_comparison_attempted, 1); + check_int("dynamic-compare.blocking", + event.trace.dynamic_comparison_blocking, 1); + check_int("dynamic-compare.matched", + event.trace.dynamic_comparison_matched, 0); + check_int("dynamic-compare.commit", event.trace.dynamic_commit_result, + KZT_GUEST_REGISTRY_UNCHANGED); + assert_old_flow_exactly_once("dynamic-compare.old-flow", &event, 77); + + kzt_guest_registry_destroy(®istry); +} + +static void test_dynamic_commit_failure_is_fail_open(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 4, + }; + kzt_guest_registry_diagnostics_t diagnostics = { 0 }; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdynamic-commitfail.so", + 0x1a0000); + event.trace.legacy_return = 43; + check_int("dynamic-commitfail.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + kzt_guest_registry_test_set_dynamic_commit_failure_after(0); + ret = run_adapter_with_diagnostics(&event, registry, 1, 0, &result); + kzt_guest_registry_test_set_dynamic_commit_failure_after(-1); + + check_int("dynamic-commitfail.return", ret, 43); + check_int("dynamic-commitfail.result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + assert_old_flow_exactly_once("dynamic-commitfail.old-flow", &event, 43); + assert_dynamic_view_not_parsed("dynamic-commitfail.view", registry, + &event); + check_int("dynamic-commitfail.diagnostic-attempted", + event.trace.dynamic_attempted, 1); + check_int("dynamic-commitfail.diagnostic-status", + event.trace.dynamic_status, KZT_GUEST_DYNAMIC_COMPLETE); + check_int("dynamic-commitfail.diagnostic-commit-result", + event.trace.dynamic_commit_result, KZT_GUEST_REGISTRY_ERROR); + check_int("dynamic-commitfail.diagnostics", + kzt_guest_registry_get_diagnostics(registry, &diagnostics), 0); + check_ulong("dynamic-commitfail.error-count", diagnostics.errors, 1); + + kzt_guest_registry_destroy(®istry); +} + +static void test_disabled_adapter_skips_observation_but_preserves_old_flow(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_ADDED; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdisabled.so", 0x200000); + event.trace.legacy_return = 24; + + ret = run_adapter(&event, registry, 0, &result); + + check_int("disabled.return", ret, 24); + check_int("disabled.result", result, KZT_OBSERVATION_ADAPTER_DISABLED); + assert_old_flow_exactly_once("disabled.old-flow", &event, 24); + check_int("disabled.reader-calls", event.trace.reader_calls, 0); + check_ulong("disabled.registry-count", registry_object_count(registry), 0); + + kzt_guest_registry_destroy(®istry); +} + +static void test_reader_failure_is_fail_open_for_old_flow(void) +{ + fake_callback_event_t event; + fake_read_failure_t failure; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_ADDED; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libreaderfail.so", 0x300000); + event.trace.legacy_return = 25; + failure.addr = (uintptr_t)&event.link_map + + offsetof(struct link_map_x64, l_addr); + failure.size = sizeof(event.link_map.l_addr); + event.memory.failures = &failure; + event.memory.failure_count = 1; + + ret = run_adapter(&event, registry, 1, &result); + + check_int("reader-failure.return", ret, 25); + check_int("reader-failure.result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + assert_old_flow_exactly_once("reader-failure.old-flow", &event, 25); + assert_reader_before_old_flow(&event); + check_ulong("reader-failure.registry-count", + registry_object_count(registry), 1); + + kzt_guest_registry_destroy(®istry); +} + +static void test_partial_link_map_fields_are_registered(void) +{ + static const struct { + const char *name; + size_t offset; + size_t size; + } cases[] = { + { "load-bias", offsetof(struct link_map_x64, l_addr), + sizeof(((struct link_map_x64 *)0)->l_addr) }, + { "dynamic", offsetof(struct link_map_x64, l_ld), + sizeof(((struct link_map_x64 *)0)->l_ld) }, + { "path", offsetof(struct link_map_x64, l_name), + sizeof(((struct link_map_x64 *)0)->l_name) }, + }; + size_t i; + + for (i = 0; i < TEST_ARRAY_SIZE(cases); ++i) { + fake_callback_event_t event; + fake_read_failure_t failure; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_observation_adapter_result_t result = + KZT_OBSERVATION_ADAPTER_DISABLED; + + check_true("partial-fields.registry", registry != NULL); + if (!registry) { + continue; + } + init_fake_callback_event(&event, "/guest/libpartial-fields.so", + 0x330000 + i * 0x10000); + failure.addr = (uintptr_t)&event.link_map + cases[i].offset; + failure.size = cases[i].size; + event.memory.failures = &failure; + event.memory.failure_count = 1; + + check_int(cases[i].name, run_adapter(&event, registry, 1, &result), + 77); + check_int("partial-fields.result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("partial-fields.find", kzt_guest_registry_find_by_link_map( + registry, (uintptr_t)&event.link_map, &snapshot), 0); + check_true("partial-fields.snapshot", snapshot != NULL); + if (snapshot) { + check_uintptr("partial-fields.identity", snapshot->link_map_addr, + (uintptr_t)&event.link_map); + if (i == 0) { + check_int("partial-fields.load-bias", snapshot->load_bias.status, + KZT_GUEST_FIELD_READ_ERROR); + } else if (i == 1) { + check_int("partial-fields.dynamic", snapshot->dynamic_addr.status, + KZT_GUEST_FIELD_READ_ERROR); + } else { + check_int("partial-fields.path", snapshot->path.status, + KZT_GUEST_FIELD_READ_ERROR); + } + kzt_guest_object_snapshot_free(snapshot); + } + assert_old_flow_exactly_once("partial-fields.old-flow", &event, 77); + kzt_guest_registry_destroy(®istry); + } +} + +static void test_verified_hints_fill_private_link_map_evidence(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_observation_adapter_result_t result = + KZT_OBSERVATION_ADAPTER_DISABLED; + + check_true("verified-hints.registry", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libverified-hints.so", 0x360000); + event.link_map.l_ns = 91; + event.link_map.l_map_start = 0x111000; + event.link_map.l_map_end = 0x112000; + event.namespace_id = 0; + + check_int("verified-hints.return", + run_adapter(&event, registry, 1, &result), 77); + check_int("verified-hints.result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("verified-hints.find", kzt_guest_registry_find_by_link_map( + registry, (uintptr_t)&event.link_map, &snapshot), 0); + check_true("verified-hints.snapshot", snapshot != NULL); + if (snapshot) { + check_int("verified-hints.namespace.status", + snapshot->namespace_id.status, KZT_GUEST_FIELD_OK); + check_uintptr("verified-hints.namespace.value", + snapshot->namespace_id.value, 0); + check_int("verified-hints.map-start.status", + snapshot->map_start.status, KZT_GUEST_FIELD_OK); + check_uintptr("verified-hints.map-start.value", + snapshot->map_start.value, event.map_start); + check_int("verified-hints.map-end.status", + snapshot->map_end.status, KZT_GUEST_FIELD_OK); + check_uintptr("verified-hints.map-end.value", + snapshot->map_end.value, event.map_end); + kzt_guest_object_snapshot_free(snapshot); + } + assert_old_flow_exactly_once("verified-hints.old-flow", &event, 77); + kzt_guest_registry_destroy(®istry); +} + +static void test_invalid_or_absent_hints_remain_unknown_and_fail_open(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_observation_adapter_result_t result = + KZT_OBSERVATION_ADAPTER_DISABLED; + + check_true("invalid-hints.registry", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libinvalid-hints.so", 0x370000); + event.namespace_id_present = 0; + event.namespace_id = 0; + event.map_range_present = 1; + event.map_start = 0x390000; + event.map_end = 0x380000; + event.link_map.l_ns = 0; + event.link_map.l_map_start = 0x370000; + event.link_map.l_map_end = 0x390000; + + check_int("invalid-hints.return", + run_adapter(&event, registry, 1, &result), 77); + check_int("invalid-hints.result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("invalid-hints.find", kzt_guest_registry_find_by_link_map( + registry, (uintptr_t)&event.link_map, &snapshot), 0); + check_true("invalid-hints.snapshot", snapshot != NULL); + if (snapshot) { + check_int("invalid-hints.namespace", + snapshot->namespace_id.status, KZT_GUEST_FIELD_UNKNOWN); + check_int("invalid-hints.map-start", + snapshot->map_start.status, KZT_GUEST_FIELD_UNKNOWN); + check_int("invalid-hints.map-end", + snapshot->map_end.status, KZT_GUEST_FIELD_UNKNOWN); + kzt_guest_object_snapshot_free(snapshot); + } + assert_old_flow_exactly_once("invalid-hints.old-flow", &event, 77); + kzt_guest_registry_destroy(®istry); +} + +static void test_registry_failure_is_fail_open_for_old_flow(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_ADDED; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libregistryfail.so", 0x400000); + event.trace.legacy_return = 26; + + kzt_guest_registry_test_set_alloc_failure_after(0); + ret = run_adapter(&event, registry, 1, &result); + kzt_guest_registry_test_set_alloc_failure_after(-1); + + check_int("registry-failure.return", ret, 26); + check_int("registry-failure.result", result, + KZT_OBSERVATION_ADAPTER_REGISTRY_FAILED); + assert_old_flow_exactly_once("registry-failure.old-flow", &event, 26); + assert_reader_before_old_flow(&event); + check_ulong("registry-failure.registry-count", + registry_object_count(registry), 0); + + kzt_guest_registry_destroy(®istry); +} + +static void test_conflict_result_does_not_change_old_flow(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t original; + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_ADDED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 4, + }; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libconflict-new.so", 0x500000); + event.trace.legacy_return = 27; + original = make_observation((uintptr_t)&event.link_map, + 0x510000, + "/guest/libconflict-old.so"); + check_int("conflict.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + check_int("conflict.prepopulate", + kzt_guest_registry_observe(registry, &original), + KZT_GUEST_REGISTRY_ADDED); + + ret = run_adapter_with_diagnostics(&event, registry, 1, 0, &result); + + check_int("conflict.return", ret, 27); + check_int("conflict.result", result, KZT_OBSERVATION_ADAPTER_CONFLICT); + assert_old_flow_exactly_once("conflict.old-flow", &event, 27); + assert_reader_before_old_flow(&event); + check_ulong("conflict.registry-count", + registry_object_count(registry), 1); + assert_dynamic_view_not_parsed("conflict.view", registry, &event); + check_int("conflict.diagnostic-calls", event.trace.diagnostic_calls, 1); + check_int("conflict.dynamic-not-attempted", + event.trace.dynamic_attempted, 0); + check_int("conflict.dynamic-no-commit", + event.trace.dynamic_commit_attempted, 0); + check_int("conflict.dynamic-commit-result", + event.trace.dynamic_commit_result, KZT_GUEST_REGISTRY_RESULT_COUNT); + + kzt_guest_registry_destroy(®istry); +} + +static void test_no_callback_event_does_not_create_registry_objects(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + check_ulong("no-callback.registry-count", + registry_object_count(registry), 0); + + kzt_guest_registry_destroy(®istry); +} + +static void test_enabled_diagnostics_are_throttled_and_fail_open(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 1, + }; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdiagnostic.so", 0x700000); + event.trace.legacy_return = 31; + check_int("diagnostic.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + check_int("diagnostic.added.return", + run_adapter_with_diagnostics(&event, registry, 1, 0, &result), + 31); + check_int("diagnostic.added.result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("diagnostic.added.calls", event.trace.diagnostic_calls, 1); + check_int("diagnostic.added.callback-result", + event.trace.diagnostic_result, KZT_OBSERVATION_ADAPTER_ADDED); + check_int("diagnostic.added.emitted", event.trace.diagnostic_emitted, 1); + check_int("diagnostic.added.legacy-calls", event.trace.legacy_calls, 1); + + check_int("diagnostic.unchanged.return", + run_adapter_with_diagnostics(&event, registry, 1, 0, &result), + 31); + check_int("diagnostic.unchanged.result", result, + KZT_OBSERVATION_ADAPTER_UNCHANGED); + check_int("diagnostic.unchanged.calls", event.trace.diagnostic_calls, 2); + check_int("diagnostic.unchanged.callback-result", + event.trace.diagnostic_result, + KZT_OBSERVATION_ADAPTER_UNCHANGED); + check_ulong("diagnostic.unchanged.observations", + event.trace.diagnostic_result_observations, 1); + check_int("diagnostic.unchanged.legacy-calls", + event.trace.legacy_calls, 2); + + check_int("diagnostic.suppressed.return", + run_adapter_with_diagnostics(&event, registry, 1, 0, &result), + 31); + check_int("diagnostic.suppressed.result", result, + KZT_OBSERVATION_ADAPTER_UNCHANGED); + check_int("diagnostic.suppressed.calls", event.trace.diagnostic_calls, 2); + check_int("diagnostic.suppressed.legacy-calls", + event.trace.legacy_calls, 3); + + kzt_guest_registry_destroy(®istry); +} + +static void test_reader_failures_are_throttled(void) +{ + fake_callback_event_t event; + fake_read_failure_t failure; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 1, + }; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdiagnostic-fail.so", + 0x710000); + failure.addr = (uintptr_t)&event.link_map + + offsetof(struct link_map_x64, l_addr); + failure.size = sizeof(event.link_map.l_addr); + event.memory.failures = &failure; + event.memory.failure_count = 1; + event.trace.legacy_return = 37; + check_int("reader-diagnostic.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + check_int("reader-diagnostic.first-return", + run_adapter_with_diagnostics(&event, registry, 1, 0, &result), + 37); + check_int("reader-diagnostic.first-result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("reader-diagnostic.first-calls", + event.trace.diagnostic_calls, 1); + check_int("reader-diagnostic.first-emitted", + event.trace.diagnostic_emitted, 1); + check_int("reader-diagnostic.first-legacy", + event.trace.legacy_calls, 1); + + check_int("reader-diagnostic.second-return", + run_adapter_with_diagnostics(&event, registry, 1, 0, &result), + 37); + check_int("reader-diagnostic.second-result", result, + KZT_OBSERVATION_ADAPTER_UNCHANGED); + check_int("reader-diagnostic.second-calls", + event.trace.diagnostic_calls, 2); + check_int("reader-diagnostic.second-legacy", + event.trace.legacy_calls, 2); + + kzt_guest_registry_destroy(®istry); +} + +static void test_disabled_adapter_diagnostics_are_throttled(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_ADDED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 1, + }; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdiagnostic-disabled.so", + 0x720000); + event.trace.legacy_return = 39; + check_int("disabled-diagnostic.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + check_int("disabled-diagnostic.first-return", + run_adapter_with_diagnostics(&event, registry, 0, 0, &result), + 39); + check_int("disabled-diagnostic.first-result", result, + KZT_OBSERVATION_ADAPTER_DISABLED); + check_int("disabled-diagnostic.first-calls", + event.trace.diagnostic_calls, 1); + check_int("disabled-diagnostic.first-emitted", + event.trace.diagnostic_emitted, 1); + check_int("disabled-diagnostic.first-legacy", + event.trace.legacy_calls, 1); + check_int("disabled-diagnostic.reader-calls", + event.trace.reader_calls, 0); + + check_int("disabled-diagnostic.second-return", + run_adapter_with_diagnostics(&event, registry, 0, 0, &result), + 39); + check_int("disabled-diagnostic.second-result", result, + KZT_OBSERVATION_ADAPTER_DISABLED); + check_int("disabled-diagnostic.second-calls", + event.trace.diagnostic_calls, 1); + check_int("disabled-diagnostic.second-legacy", + event.trace.legacy_calls, 2); + check_int("disabled-diagnostic.second-reader-calls", + event.trace.reader_calls, 0); + + kzt_guest_registry_destroy(®istry); +} + +int main(void) +{ + test_active_observation_adds_object_and_preserves_old_flow(); + test_exact_pair_retries_after_transient_observation_failure(); + test_binding_owned_retire_excludes_adapter_retire(); + test_adapter_note_before_unload_leaves_single_retire_owner(); + test_cancelled_pending_without_owner_is_adapter_retired(); + test_exact_retire_owner_uses_complete_key(); + /* The direct test above proves exact-generation owner matching. This + * separate test proves the real adapter gate/registry path with a source + * lease participant, binding retire owner, and third callback participant. */ + test_three_participant_adapter_does_not_duplicate_retire(); + test_callback_lifetime_covers_all_guest_work(); + test_unload_winner_rejects_late_callback_before_any_work(); + test_callback_gate_allocation_failure_is_safe_fail_open(); + test_adapter_address_reuse_requires_loader_causality(); + test_callback_pending_observation_waits_for_wrapper_success(); + test_dynamic_parser_success_commits_snapshot(); + test_dynamic_parser_reuses_same_generation_complete_view(); + test_dynamic_parser_read_failure_is_fail_open(); + test_dynamic_diagnostics_preserve_complete_view(); + test_dynamic_commit_failure_is_fail_open(); + test_disabled_adapter_skips_observation_but_preserves_old_flow(); + test_reader_failure_is_fail_open_for_old_flow(); + test_partial_link_map_fields_are_registered(); + test_verified_hints_fill_private_link_map_evidence(); + test_invalid_or_absent_hints_remain_unknown_and_fail_open(); + test_registry_failure_is_fail_open_for_old_flow(); + test_conflict_result_does_not_change_old_flow(); + test_no_callback_event_does_not_create_registry_objects(); + test_enabled_diagnostics_are_throttled_and_fail_open(); + test_reader_failures_are_throttled(); + test_disabled_adapter_diagnostics_are_throttled(); + + if (failures) { + fprintf(stderr, "kzt-observation-adapter: %d failure(s)\n", failures); + return 1; + } + + puts("kzt-observation-adapter: all contract tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_owner_resolver.c b/tests/unit/kzt/test_owner_resolver.c new file mode 100644 index 00000000000..69eaef52b5f --- /dev/null +++ b/tests/unit/kzt/test_owner_resolver.c @@ -0,0 +1,338 @@ +#include +#include + +#include "target/i386/latx/include/kzt_owner_resolver.h" + +static int failures; + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, + expected); + ++failures; +} + +static void check_str(const char *name, const char *got, + const char *expected) +{ + if ((!got && !expected) || (got && expected && !strcmp(got, expected))) { + return; + } + + fprintf(stderr, "%s: got '%s' expected '%s'\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static kzt_guest_object_observation_t observation( + uintptr_t link_map_addr, + uintptr_t map_start, + uintptr_t map_end, + const char *soname) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map_addr, + .load_bias = { map_start, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { map_start + 0x1000, KZT_GUEST_FIELD_OK }, + .map_start = { map_start, KZT_GUEST_FIELD_OK }, + .map_end = { map_end, KZT_GUEST_FIELD_OK }, + .namespace_id = { 0, KZT_GUEST_FIELD_OK }, + .path = { soname, KZT_GUEST_FIELD_OK }, + .soname = { soname, KZT_GUEST_FIELD_OK }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static kzt_patch_object_ref_t object_ref(uintptr_t link_map_addr, + unsigned long generation) +{ + return (kzt_patch_object_ref_t) { + .known = 1, + .link_map_addr = link_map_addr, + .map_start = 0x70000000, + .map_end = 0x70001000, + .generation = generation, + .soname = "libexpected.so", + .path = "/guest/libexpected.so", + }; +} + +static void test_same_soname_different_ranges_resolves_by_address(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t first = observation( + 0x1000, 0x70000000, 0x70001000, "libsame.so"); + kzt_guest_object_observation_t second = observation( + 0x2000, 0x71000000, 0x71001000, "libsame.so"); + kzt_owner_resolution_t resolution; + + check_int("same.observe.first", + kzt_guest_registry_observe(registry, &first), + KZT_GUEST_REGISTRY_ADDED); + check_int("same.observe.second", + kzt_guest_registry_observe(registry, &second), + KZT_GUEST_REGISTRY_ADDED); + + check_int("same.resolve", + kzt_owner_resolver_resolve_current( + registry, 0x71000080, 0x71000090, &resolution), 0); + check_int("same.status", resolution.status, + KZT_OWNER_RESOLVER_RESOLVED); + check_int("same.match", resolution.owner_match, + KZT_PATCH_OWNER_MATCH); + check_ulong("same.current.link_map", + resolution.current_owner.link_map_addr, 0x2000); + check_ulong("same.expected.link_map", + resolution.expected_owner.link_map_addr, 0x2000); + check_ulong("same.current.generation", + resolution.current_owner.generation, 2); + check_str("same.current.soname", resolution.current_owner.soname, + "libsame.so"); + check_int("same.current.matches", resolution.current_match_count, 1); + check_int("same.expected.matches", resolution.expected_match_count, 1); + + kzt_guest_registry_destroy(®istry); +} + +static void test_range_boundaries_are_start_inclusive_end_exclusive(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t object = observation( + 0x3000, 0x72000000, 0x72001000, "libbounds.so"); + kzt_owner_resolution_t resolution; + + check_int("bounds.observe", + kzt_guest_registry_observe(registry, &object), + KZT_GUEST_REGISTRY_ADDED); + + check_int("bounds.start.resolve", + kzt_owner_resolver_resolve_current( + registry, 0x72000000, 0x72000008, &resolution), 0); + check_int("bounds.start.status", resolution.status, + KZT_OWNER_RESOLVER_RESOLVED); + check_int("bounds.start.match", resolution.owner_match, + KZT_PATCH_OWNER_MATCH); + check_ulong("bounds.start.owner", + resolution.current_owner.link_map_addr, 0x3000); + + check_int("bounds.end.resolve", + kzt_owner_resolver_resolve_current( + registry, 0x72001000, 0x72000008, &resolution), 0); + check_int("bounds.end.status", resolution.status, + KZT_OWNER_RESOLVER_CURRENT_NOT_FOUND); + check_int("bounds.end.match", resolution.owner_match, + KZT_PATCH_OWNER_UNKNOWN); + check_int("bounds.end.current-known", resolution.current_owner.known, 0); + + kzt_guest_registry_destroy(®istry); +} + +static void test_unknown_range_keeps_owner_unknown(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t object = observation( + 0x4000, 0x73000000, 0x73001000, "libunknown-range.so"); + kzt_owner_resolution_t resolution; + + object.map_start.status = KZT_GUEST_FIELD_UNKNOWN; + object.map_end.status = KZT_GUEST_FIELD_UNKNOWN; + check_int("unknown-range.observe", + kzt_guest_registry_observe(registry, &object), + KZT_GUEST_REGISTRY_ADDED); + + check_int("unknown-range.resolve", + kzt_owner_resolver_resolve_current( + registry, 0x73000080, 0x73000090, &resolution), 0); + check_int("unknown-range.status", resolution.status, + KZT_OWNER_RESOLVER_CURRENT_NOT_FOUND); + check_int("unknown-range.match", resolution.owner_match, + KZT_PATCH_OWNER_UNKNOWN); + check_int("unknown-range.current-known", + resolution.current_owner.known, 0); + + kzt_guest_registry_destroy(®istry); +} + +static void test_missing_registry_keeps_owner_unknown(void) +{ + kzt_owner_resolution_t resolution; + + check_int("missing-registry.resolve", + kzt_owner_resolver_resolve_current( + NULL, 0x74000080, 0x74000090, &resolution), 0); + check_int("missing-registry.status", resolution.status, + KZT_OWNER_RESOLVER_REGISTRY_UNAVAILABLE); + check_int("missing-registry.match", resolution.owner_match, + KZT_PATCH_OWNER_UNKNOWN); + check_int("missing-registry.current-known", + resolution.current_owner.known, 0); +} + +static void test_owner_mismatch_is_reported_but_not_matched(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t current = observation( + 0x5000, 0x75000000, 0x75001000, "libpreload.so"); + kzt_guest_object_observation_t expected = observation( + 0x6000, 0x76000000, 0x76001000, "libgtk-3.so"); + kzt_owner_resolution_t resolution; + + check_int("mismatch.observe.current", + kzt_guest_registry_observe(registry, ¤t), + KZT_GUEST_REGISTRY_ADDED); + check_int("mismatch.observe.expected", + kzt_guest_registry_observe(registry, &expected), + KZT_GUEST_REGISTRY_ADDED); + + check_int("mismatch.resolve", + kzt_owner_resolver_resolve_current( + registry, 0x75000020, 0x76000020, &resolution), 0); + check_int("mismatch.status", resolution.status, + KZT_OWNER_RESOLVER_RESOLVED); + check_int("mismatch.match", resolution.owner_match, + KZT_PATCH_OWNER_MISMATCH); + check_ulong("mismatch.current.owner", + resolution.current_owner.link_map_addr, 0x5000); + check_ulong("mismatch.expected.owner", + resolution.expected_owner.link_map_addr, 0x6000); + check_str("mismatch.current.soname", resolution.current_owner.soname, + "libpreload.so"); + check_str("mismatch.expected.soname", resolution.expected_owner.soname, + "libgtk-3.so"); + + kzt_guest_registry_destroy(®istry); +} + +static void test_generation_unknown_does_not_match(void) +{ + kzt_patch_object_ref_t current = object_ref(0x7000, 0); + kzt_patch_object_ref_t expected = object_ref(0x7000, 1); + + check_int("generation-unknown.current", + kzt_owner_resolver_match_refs(¤t, &expected), + KZT_PATCH_OWNER_UNKNOWN); + + current.generation = 1; + expected.generation = 0; + check_int("generation-unknown.expected", + kzt_owner_resolver_match_refs(¤t, &expected), + KZT_PATCH_OWNER_UNKNOWN); +} + +static void test_native_bridge_address_is_not_expected_owner_input(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t guest = observation( + 0xa000, 0x79000000, 0x79001000, "libguest-target.so"); + kzt_guest_object_observation_t bridge_like = observation( + 0xb000, 0x7a000000, 0x7a001000, "libbridge-like.so"); + kzt_owner_resolution_t resolution; + + check_int("bridge-role.observe.guest", + kzt_guest_registry_observe(registry, &guest), + KZT_GUEST_REGISTRY_ADDED); + check_int("bridge-role.observe.bridge-like", + kzt_guest_registry_observe(registry, &bridge_like), + KZT_GUEST_REGISTRY_ADDED); + + check_int("bridge-role.guest-target.resolve", + kzt_owner_resolver_resolve_current( + registry, 0x79000020, 0x79000080, &resolution), 0); + check_int("bridge-role.guest-target.status", resolution.status, + KZT_OWNER_RESOLVER_RESOLVED); + check_int("bridge-role.guest-target.match", resolution.owner_match, + KZT_PATCH_OWNER_MATCH); + + check_int("bridge-role.native-bridge.resolve", + kzt_owner_resolver_resolve_current( + registry, 0x79000020, 0x7a000080, &resolution), 0); + check_int("bridge-role.native-bridge.status", resolution.status, + KZT_OWNER_RESOLVER_RESOLVED); + check_int("bridge-role.native-bridge.match", resolution.owner_match, + KZT_PATCH_OWNER_MISMATCH); + + kzt_guest_registry_destroy(®istry); +} + +static void test_ambiguous_range_keeps_owner_unknown(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t first = observation( + 0x8000, 0x78000000, 0x78002000, "liboverlap-a.so"); + kzt_guest_object_observation_t second = observation( + 0x9000, 0x78001000, 0x78003000, "liboverlap-b.so"); + kzt_owner_resolution_t resolution; + + check_int("ambiguous.observe.first", + kzt_guest_registry_observe(registry, &first), + KZT_GUEST_REGISTRY_ADDED); + check_int("ambiguous.observe.second", + kzt_guest_registry_observe(registry, &second), + KZT_GUEST_REGISTRY_ADDED); + + check_int("ambiguous.resolve", + kzt_owner_resolver_resolve_current( + registry, 0x78001800, 0x78000020, &resolution), 0); + check_int("ambiguous.status", resolution.status, + KZT_OWNER_RESOLVER_CURRENT_AMBIGUOUS); + check_int("ambiguous.match", resolution.owner_match, + KZT_PATCH_OWNER_UNKNOWN); + check_int("ambiguous.matches", resolution.current_match_count, 2); + + kzt_guest_registry_destroy(®istry); +} + +static void test_dead_objects_do_not_resolve_owner(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t object = observation( + 0xc000, 0x7b000000, 0x7b001000, "libdead.so"); + kzt_owner_resolution_t resolution; + + check_int("dead.observe", kzt_guest_registry_observe(registry, &object), + KZT_GUEST_REGISTRY_ADDED); + check_int("dead.retire", kzt_guest_registry_retire( + registry, object.link_map_addr, 1), 0); + check_int("dead.resolve", kzt_owner_resolver_resolve_current( + registry, 0x7b000010, 0x7b000020, &resolution), 0); + check_int("dead.status", resolution.status, + KZT_OWNER_RESOLVER_CURRENT_NOT_FOUND); + check_int("dead.owner-unknown", resolution.current_owner.known, 0); + kzt_guest_registry_destroy(®istry); +} + +int main(void) +{ + test_same_soname_different_ranges_resolves_by_address(); + test_range_boundaries_are_start_inclusive_end_exclusive(); + test_unknown_range_keeps_owner_unknown(); + test_missing_registry_keeps_owner_unknown(); + test_owner_mismatch_is_reported_but_not_matched(); + test_generation_unknown_does_not_match(); + test_native_bridge_address_is_not_expected_owner_input(); + test_ambiguous_range_keeps_owner_unknown(); + test_dead_objects_do_not_resolve_owner(); + + if (failures) { + fprintf(stderr, "kzt-owner-resolver: %d failure(s)\n", failures); + return 1; + } + + puts("kzt-owner-resolver: all resolver tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_patch_planner.c b/tests/unit/kzt/test_patch_planner.c new file mode 100644 index 00000000000..fe35473d632 --- /dev/null +++ b/tests/unit/kzt/test_patch_planner.c @@ -0,0 +1,509 @@ +#include +#include + +#include "target/i386/latx/include/kzt_patch_planner.h" + +static int failures; + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, expected); + ++failures; +} + +static void check_str(const char *name, const char *got, const char *expected) +{ + if (got && expected && !strcmp(got, expected)) { + return; + } + + fprintf(stderr, "%s: got '%s' expected '%s'\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static void check_str_contains(const char *name, const char *value, + const char *expected) +{ + if (value && expected && strstr(value, expected)) { + return; + } + + fprintf(stderr, "%s: '%s' does not contain '%s'\n", name, + value ? value : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static kzt_patch_object_ref_t object_ref(uintptr_t link_map_addr, + unsigned long generation, + const char *soname) +{ + return (kzt_patch_object_ref_t) { + .known = 1, + .link_map_addr = link_map_addr, + .map_start = 0x7000000000 + generation * 0x100000, + .map_end = 0x7000008000 + generation * 0x100000, + .generation = generation, + .soname = soname, + .path = soname, + }; +} + +static kzt_patch_candidate_t base_candidate(void) +{ + return (kzt_patch_candidate_t) { + .source = object_ref(0x1000, 7, "librequester.so"), + .dynamic_addr = 0x7000100000, + .load_bias = 0x7000000000, + .dynamic_view_generation = 42, + .dynamic_view_available = 1, + .table_kind = KZT_PATCH_TABLE_PLT_RELA, + .entry_index = 3, + .entry_addr = 0x7000100180, + .reloc_type = KZT_PATCH_RELOCATION_JUMP_SLOT, + .slot_addr = 0x7100000018, + .slot_current_value_present = 1, + .slot_current_value = 0x7200001000, + .lazy_binding_deferred = 0, + .symbol_index = 77, + .symbol_name = "gtk_widget_show", + .version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .version = "GTK_3.0", + .current_owner = object_ref(0x2000, 12, "libgtk-3.so"), + .owner_match = KZT_PATCH_OWNER_MATCH, + .wrapper_match = KZT_PATCH_WRAPPER_VERSION_MATCH, + .wrapper_name = "wrappedgtk3", + .wrapper_version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .wrapper_symbol_version = "GTK_3.0", + .bridge_target = 0x7300002000, + }; +} + +static void assert_decision(const char *name, + const kzt_patch_decision_t *decision, + kzt_patch_decision_kind_t expected_kind, + kzt_patch_reason_t expected_reason, + int expected_allow) +{ + char field[128]; + + snprintf(field, sizeof(field), "%s.kind", name); + check_int(field, decision->kind, expected_kind); + snprintf(field, sizeof(field), "%s.reason", name); + check_int(field, decision->reason, expected_reason); + snprintf(field, sizeof(field), "%s.allow", name); + check_int(field, decision->allow_native_bridge, expected_allow); +} + +static void test_complete_evidence_approves_native_bridge(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + char line[768]; + + check_int("approved.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("approved", &decision, + KZT_PATCH_DECISION_APPROVED, + KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE, 1); + check_ulong("approved.link-map", decision.source.link_map_addr, + candidate.source.link_map_addr); + check_ulong("approved.source-generation", decision.source.generation, + candidate.source.generation); + check_ulong("approved.dynamic-addr", decision.dynamic_addr, + candidate.dynamic_addr); + check_ulong("approved.load-bias", decision.load_bias, + candidate.load_bias); + check_ulong("approved.dynamic-generation", + decision.dynamic_view_generation, + candidate.dynamic_view_generation); + check_ulong("approved.entry-index", decision.entry_index, + candidate.entry_index); + check_ulong("approved.entry-addr", decision.entry_addr, + candidate.entry_addr); + check_ulong("approved.slot", decision.slot_addr, candidate.slot_addr); + check_ulong("approved.current-got", decision.slot_current_value, + candidate.slot_current_value); + check_ulong("approved.symbol-index", decision.symbol_index, + candidate.symbol_index); + check_ulong("approved.owner", decision.current_owner.link_map_addr, + candidate.current_owner.link_map_addr); + check_ulong("approved.bridge", decision.bridge_target, + candidate.bridge_target); + check_str("approved.kind-name", + kzt_patch_decision_kind_name(decision.kind), "APPROVED"); + check_str("approved.reason-name", + kzt_patch_reason_name(decision.reason), + "APPROVED_NATIVE_BRIDGE"); + + check_int("approved.summary", + kzt_patch_decision_format_summary(&decision, line, + sizeof(line)), 0); + check_str_contains("approved.summary-kind", line, "kind=APPROVED"); + check_str_contains("approved.summary-reason", line, + "reason=APPROVED_NATIVE_BRIDGE"); + check_str_contains("approved.summary-link-map", line, + "link_map=0x1000"); + check_str_contains("approved.summary-source-generation", line, + "source_generation=7"); + check_str_contains("approved.summary-dynamic-addr", line, + "dynamic_addr=0x7000100000"); + check_str_contains("approved.summary-load-bias", line, + "load_bias=0x7000000000"); + check_str_contains("approved.summary-dynamic-generation", line, + "dynamic_view_generation=42"); + check_str_contains("approved.summary-table", line, "table=PLT_RELA"); + check_str_contains("approved.summary-entry-index", line, + "entry_index=3"); + check_str_contains("approved.summary-entry-addr", line, + "entry_addr=0x7000100180"); + check_str_contains("approved.summary-reloc", line, "reloc=JUMP_SLOT"); + check_str_contains("approved.summary-slot", line, + "slot=0x7100000018"); + check_str_contains("approved.summary-symbol-index", line, + "symbol_index=77"); + check_str_contains("approved.summary-symbol", line, + "symbol=gtk_widget_show"); + check_str_contains("approved.summary-wrapper", line, + "wrapper=wrappedgtk3"); +} + +static void test_unsupported_relocation_is_not_guessed(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + + candidate.reloc_type = KZT_PATCH_RELOCATION_RELATIVE; + + check_int("unsupported-relocation.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("unsupported-relocation", &decision, + KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNSUPPORTED_RELOCATION, 0); + check_str("unsupported-relocation.name", + kzt_patch_relocation_type_name(decision.reloc_type), + "RELATIVE"); +} + +static void test_dynamic_view_unavailable_is_unsupported(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + char line[768]; + + candidate.dynamic_view_available = 0; + + check_int("dynamic-unavailable.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("dynamic-unavailable", &decision, + KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_DYNAMIC_VIEW, 0); + check_int("dynamic-unavailable.summary", + kzt_patch_decision_format_summary(&decision, line, + sizeof(line)), 0); + check_str_contains("dynamic-unavailable.summary", line, + "dynamic_view_available=0"); +} + +static void test_owner_unknown_is_unsupported(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + + memset(&candidate.current_owner, 0, sizeof(candidate.current_owner)); + candidate.owner_match = KZT_PATCH_OWNER_UNKNOWN; + + check_int("owner-unknown.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("owner-unknown", &decision, + KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_OWNER, 0); +} + +static void test_owner_mismatch_is_a_stable_rejection(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + char line[768]; + + candidate.current_owner = object_ref(0x3000, 13, "libpreload.so"); + candidate.owner_match = KZT_PATCH_OWNER_MISMATCH; + + check_int("owner-mismatch.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("owner-mismatch", &decision, + KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_OWNER_MISMATCH, 0); + check_str("owner-mismatch.kind-name", + kzt_patch_decision_kind_name(decision.kind), "REJECTED"); + check_str("owner-mismatch.owner-match-name", + kzt_patch_owner_match_name(decision.owner_match), + "MISMATCH"); + check_int("owner-mismatch.summary", + kzt_patch_decision_format_summary(&decision, line, + sizeof(line)), 0); + check_str_contains("owner-mismatch.summary-owner", line, + "current_owner=0x3000"); + check_str_contains("owner-mismatch.summary-match", line, + "owner_match=MISMATCH"); +} + +static void test_wrapper_version_mismatch_is_rejected(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + + candidate.wrapper_match = KZT_PATCH_WRAPPER_VERSION_MISMATCH; + candidate.wrapper_symbol_version = "GTK_2.0"; + + check_int("version-mismatch.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("version-mismatch", &decision, + KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_VERSION_MISMATCH, 0); + check_str("version-mismatch.kind-name", + kzt_patch_decision_kind_name(decision.kind), "REJECTED"); +} + +static void test_no_wrapper_rejects_to_keep_guest_target(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + + candidate.wrapper_match = KZT_PATCH_WRAPPER_NO_WRAPPER; + candidate.wrapper_name = NULL; + candidate.wrapper_symbol_version = NULL; + candidate.bridge_target = 0; + + check_int("no-wrapper.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("no-wrapper", &decision, + KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_NO_WRAPPER, 0); + check_str("no-wrapper.match-name", + kzt_patch_wrapper_match_name(decision.wrapper_match), + "NO_WRAPPER"); +} + +static void test_lazy_deferred_is_not_patched_yet(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + char line[768]; + + candidate.lazy_binding_deferred = 1; + candidate.current_owner.known = 0; + candidate.owner_match = KZT_PATCH_OWNER_UNKNOWN; + + check_int("lazy-deferred.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("lazy-deferred", &decision, + KZT_PATCH_DECISION_DEFERRED, + KZT_PATCH_REASON_DEFERRED_LAZY_BINDING, 0); + check_str("lazy-deferred.kind-name", + kzt_patch_decision_kind_name(decision.kind), "DEFERRED"); + check_int("lazy-deferred.summary", + kzt_patch_decision_format_summary(&decision, line, + sizeof(line)), 0); + check_str_contains("lazy-deferred.summary", line, + "lazy_deferred=1"); +} + +static void test_symbol_only_wrapper_rejects_to_keep_guest_target(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + + candidate.wrapper_match = KZT_PATCH_WRAPPER_SYMBOL_ONLY; + candidate.wrapper_symbol_version = NULL; + + check_int("symbol-only.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("symbol-only", &decision, + KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_WRAPPER_SYMBOL_ONLY, 0); + check_str("symbol-only.match-name", + kzt_patch_wrapper_match_name(decision.wrapper_match), + "SYMBOL_ONLY"); +} + +static void test_missing_symbol_version_is_malformed_input(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + + candidate.version = NULL; + + check_int("missing-version.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("missing-version", &decision, + KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_VERSION, 0); +} + +static void test_confirmed_unversioned_evidence_is_approved(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + + candidate.version_evidence = + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + candidate.version = NULL; + candidate.wrapper_match = KZT_PATCH_WRAPPER_UNVERSIONED_MATCH; + candidate.wrapper_version_evidence = + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + candidate.wrapper_symbol_version = NULL; + + check_int("unversioned.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("unversioned", &decision, + KZT_PATCH_DECISION_APPROVED, + KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE, 1); + check_int("unversioned.evidence", decision.version_evidence, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED); +} + +static void test_unknown_and_error_version_evidence_are_rejected(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + kzt_symbol_version_evidence_t evidence[] = { + KZT_SYMBOL_VERSION_UNKNOWN, + KZT_SYMBOL_VERSION_ERROR, + }; + size_t i; + + for (i = 0; i < sizeof(evidence) / sizeof(evidence[0]); ++i) { + candidate = base_candidate(); + candidate.version_evidence = evidence[i]; + check_int("untrusted-version.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("untrusted-version", &decision, + KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_VERSION, 0); + } +} + +static void test_no_manifest_is_unavailable_input(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + + candidate.wrapper_match = KZT_PATCH_WRAPPER_NO_MANIFEST; + candidate.wrapper_name = NULL; + candidate.wrapper_symbol_version = NULL; + + check_int("no-manifest.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("no-manifest", &decision, + KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_WRAPPER_MANIFEST, 0); +} + +static void test_bridge_target_is_required_for_approval(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + + candidate.bridge_target = 0; + + check_int("missing-bridge.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("missing-bridge", &decision, + KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_BRIDGE_TARGET, 0); +} + +static int test_matches_filter(const char *name, int argc, char **argv) +{ + int i; + + for (i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "--filter") && i + 1 < argc) { + return strcmp(name, argv[i + 1]) == 0; + } + } + + return 1; +} + +int main(int argc, char **argv) +{ + if (test_matches_filter("complete_evidence_approves_native_bridge", + argc, argv)) { + test_complete_evidence_approves_native_bridge(); + } + if (test_matches_filter("unsupported_relocation_is_not_guessed", + argc, argv)) { + test_unsupported_relocation_is_not_guessed(); + } + if (test_matches_filter("dynamic_view_unavailable_is_unsupported", + argc, argv)) { + test_dynamic_view_unavailable_is_unsupported(); + } + if (test_matches_filter("owner_unknown_is_unsupported", argc, argv)) { + test_owner_unknown_is_unsupported(); + } + if (test_matches_filter("owner_mismatch_is_a_stable_rejection", + argc, argv)) { + test_owner_mismatch_is_a_stable_rejection(); + } + if (test_matches_filter("wrapper_version_mismatch_is_rejected", + argc, argv)) { + test_wrapper_version_mismatch_is_rejected(); + } + if (test_matches_filter("no_wrapper_rejects_to_keep_guest_target", + argc, argv)) { + test_no_wrapper_rejects_to_keep_guest_target(); + } + if (test_matches_filter("lazy_deferred_is_not_patched_yet", + argc, argv)) { + test_lazy_deferred_is_not_patched_yet(); + } + if (test_matches_filter("symbol_only_wrapper_rejects_to_keep_guest_target", + argc, argv)) { + test_symbol_only_wrapper_rejects_to_keep_guest_target(); + } + if (test_matches_filter("missing_symbol_version_is_malformed_input", + argc, argv)) { + test_missing_symbol_version_is_malformed_input(); + } + if (test_matches_filter("confirmed_unversioned_evidence_is_approved", + argc, argv)) { + test_confirmed_unversioned_evidence_is_approved(); + } + if (test_matches_filter( + "unknown_and_error_version_evidence_are_rejected", argc, argv)) { + test_unknown_and_error_version_evidence_are_rejected(); + } + if (test_matches_filter("no_manifest_is_unavailable_input", argc, argv)) { + test_no_manifest_is_unavailable_input(); + } + if (test_matches_filter("bridge_target_is_required_for_approval", + argc, argv)) { + test_bridge_target_is_required_for_approval(); + } + + if (failures) { + fprintf(stderr, "kzt-patch-planner: %d failure(s)\n", failures); + return 1; + } + + puts("kzt-patch-planner: selected contract tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_patch_spike_guard.c b/tests/unit/kzt/test_patch_spike_guard.c new file mode 100644 index 00000000000..c9d1f72941c --- /dev/null +++ b/tests/unit/kzt/test_patch_spike_guard.c @@ -0,0 +1,520 @@ +#include +#include +#include + +#include "kzt_test_options.h" +#include "target/i386/latx/include/kzt_patch_spike_guard.h" + +typedef struct fake_writer { + int read_calls; + int write_calls; + int verify_calls; + int rollback_calls; + int fail_read; + int fail_write; + int fail_verify; + int fail_rollback; + uintptr_t current_value; + uintptr_t last_written_value; +} fake_writer_t; + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lu expected %lu\n", name, got, expected); + ++failures; +} + +static void check_uintptr(const char *name, uintptr_t got, + uintptr_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static kzt_patch_decision_t approved_decision(void) +{ + return (kzt_patch_decision_t) { + .kind = KZT_PATCH_DECISION_APPROVED, + .reason = KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE, + .allow_native_bridge = 1, + .slot_addr = 0x7100000018, + .slot_current_value_present = 1, + .slot_current_value = 0x7200001000, + .bridge_target = 0x7300002000, + }; +} + +static kzt_patch_spike_writer_status_t fake_write_slot( + const kzt_patch_decision_t *decision, + uintptr_t expected_value, + uintptr_t replacement_value, + uintptr_t *previous_value, + void *opaque) +{ + fake_writer_t *writer = opaque; + + (void)decision; + ++writer->read_calls; + if (writer->fail_read) { + return KZT_PATCH_SPIKE_WRITER_READ_FAILED; + } + + if (previous_value) { + *previous_value = writer->current_value; + } + if (writer->current_value != expected_value) { + return KZT_PATCH_SPIKE_WRITER_EXPECTED_MISMATCH; + } + + ++writer->write_calls; + writer->last_written_value = replacement_value; + if (writer->fail_write) { + return KZT_PATCH_SPIKE_WRITER_WRITE_FAILED; + } + + writer->current_value = replacement_value; + return KZT_PATCH_SPIKE_WRITER_OK; +} + +static int fake_verify_slot(const kzt_patch_decision_t *decision, + uintptr_t expected_value, + void *opaque) +{ + fake_writer_t *writer = opaque; + + (void)decision; + ++writer->verify_calls; + if (writer->fail_verify) { + return -1; + } + + return writer->current_value == expected_value ? 0 : -1; +} + +static int fake_rollback_slot(const kzt_patch_decision_t *decision, + uintptr_t previous_value, + void *opaque) +{ + fake_writer_t *writer = opaque; + + (void)decision; + ++writer->rollback_calls; + if (writer->fail_rollback) { + return -1; + } + + writer->current_value = previous_value; + return 0; +} + +static kzt_patch_spike_writer_ops_t writer_ops(fake_writer_t *writer) +{ + return (kzt_patch_spike_writer_ops_t) { + .write_slot = fake_write_slot, + .verify_slot = fake_verify_slot, + .rollback_slot = fake_rollback_slot, + .opaque = writer, + }; +} + +static kzt_patch_spike_guard_t init_guard(int enabled, int write_enabled, + unsigned long budget) +{ + kzt_patch_spike_config_t config = { + .enabled = enabled, + .write_enabled = write_enabled, + .budget = budget, + }; + kzt_patch_spike_guard_t guard; + + kzt_patch_spike_guard_init(&guard, &config); + return guard; +} + +static void assert_no_writer_calls(const char *name, + const fake_writer_t *writer) +{ + char field[128]; + + snprintf(field, sizeof(field), "%s.read", name); + check_int(field, writer->read_calls, 0); + snprintf(field, sizeof(field), "%s.write", name); + check_int(field, writer->write_calls, 0); + snprintf(field, sizeof(field), "%s.verify", name); + check_int(field, writer->verify_calls, 0); + snprintf(field, sizeof(field), "%s.rollback", name); + check_int(field, writer->rollback_calls, 0); +} + +static void test_default_config_is_closed_and_noop(void) +{ + kzt_patch_spike_config_t config = { 1, 1, 1 }; + kzt_patch_spike_guard_t guard; + kzt_patch_spike_outcome_t outcome; + fake_writer_t writer = { 0 }; + int fake_planner_calls = 0; + + option_kzt_patch_spike = 0; + option_kzt_patch_spike_write = 0; + option_kzt_patch_spike_budget = 0; + + kzt_patch_spike_config_from_options(&config); + check_int("default.enabled", config.enabled, 0); + check_int("default.write-enabled", config.write_enabled, 0); + check_ulong("default.budget", config.budget, 0); + + kzt_patch_spike_guard_init(&guard, &config); + if (kzt_patch_spike_guard_should_plan(&guard)) { + ++fake_planner_calls; + } + + check_int("default.should-plan", fake_planner_calls, 0); + check_int("default.try-write", + kzt_patch_spike_guard_try_write(&guard, NULL, NULL, + &outcome), 0); + check_int("default.result", outcome.result, + KZT_PATCH_SPIKE_RESULT_DISABLED); + check_int("default.action", outcome.action, + KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY); + check_int("default.skip-legacy", outcome.skip_legacy_write, 0); + assert_no_writer_calls("default.writer", &writer); +} + +static void test_diagnostics_only_never_writes(void) +{ + kzt_patch_decision_t decision = approved_decision(); + kzt_patch_spike_guard_t guard = init_guard(1, 0, 4); + kzt_patch_spike_outcome_t outcome; + fake_writer_t writer = { + .current_value = decision.slot_current_value, + }; + kzt_patch_spike_writer_ops_t ops = writer_ops(&writer); + + check_int("diagnostics.should-plan", + kzt_patch_spike_guard_should_plan(&guard), 1); + check_int("diagnostics.try-write", + kzt_patch_spike_guard_try_write(&guard, &decision, &ops, + &outcome), 0); + check_int("diagnostics.result", outcome.result, + KZT_PATCH_SPIKE_RESULT_DIAGNOSTICS_ONLY); + check_int("diagnostics.failure", outcome.failure, + KZT_PATCH_SPIKE_FAILURE_WRITE_NOT_AUTHORIZED); + check_int("diagnostics.action", outcome.action, + KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY); + check_int("diagnostics.skip-legacy", outcome.skip_legacy_write, 0); + assert_no_writer_calls("diagnostics.writer", &writer); +} + +static void test_spike_on_without_write_or_budget_is_noop(void) +{ + kzt_patch_decision_t decision = approved_decision(); + kzt_patch_spike_outcome_t outcome; + fake_writer_t writer = { + .current_value = decision.slot_current_value, + }; + kzt_patch_spike_writer_ops_t ops = writer_ops(&writer); + kzt_patch_spike_guard_t no_write = init_guard(1, 0, 1); + kzt_patch_spike_guard_t no_budget = init_guard(1, 1, 0); + + check_int("no-write.try-write", + kzt_patch_spike_guard_try_write(&no_write, &decision, &ops, + &outcome), 0); + check_int("no-write.result", outcome.result, + KZT_PATCH_SPIKE_RESULT_DIAGNOSTICS_ONLY); + check_int("no-write.skip-legacy", outcome.skip_legacy_write, 0); + assert_no_writer_calls("no-write.writer", &writer); + + check_int("no-budget.try-write", + kzt_patch_spike_guard_try_write(&no_budget, &decision, &ops, + &outcome), 0); + check_int("no-budget.result", outcome.result, + KZT_PATCH_SPIKE_RESULT_BUDGET_EXHAUSTED); + check_int("no-budget.failure", outcome.failure, + KZT_PATCH_SPIKE_FAILURE_BUDGET_EXHAUSTED); + check_int("no-budget.skip-legacy", outcome.skip_legacy_write, 0); + assert_no_writer_calls("no-budget.writer", &writer); +} + +static void test_approved_budget_one_allows_one_write(void) +{ + kzt_patch_decision_t decision = approved_decision(); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 1); + kzt_patch_spike_outcome_t outcome; + fake_writer_t writer = { + .current_value = decision.slot_current_value, + }; + kzt_patch_spike_writer_ops_t ops = writer_ops(&writer); + + check_int("budget-one.first", + kzt_patch_spike_guard_try_write(&guard, &decision, &ops, + &outcome), 0); + check_int("budget-one.first-result", outcome.result, + KZT_PATCH_SPIKE_RESULT_APPLIED); + check_int("budget-one.first-action", outcome.action, + KZT_PATCH_SPIKE_ACTION_USE_NATIVE_BRIDGE); + check_int("budget-one.skip-legacy", outcome.skip_legacy_write, 1); + check_ulong("budget-one.remaining", outcome.writes_remaining, 0); + check_int("budget-one.reads", writer.read_calls, 1); + check_int("budget-one.writes", writer.write_calls, 1); + check_int("budget-one.verifies", writer.verify_calls, 1); + check_uintptr("budget-one.value", writer.current_value, + decision.bridge_target); + check_uintptr("budget-one.previous", outcome.previous_value, + decision.slot_current_value); + + writer.current_value = decision.slot_current_value; + check_int("budget-one.second", + kzt_patch_spike_guard_try_write(&guard, &decision, &ops, + &outcome), 0); + check_int("budget-one.second-result", outcome.result, + KZT_PATCH_SPIKE_RESULT_BUDGET_EXHAUSTED); + check_int("budget-one.second-skip-legacy", outcome.skip_legacy_write, 0); + check_int("budget-one.reads-after", writer.read_calls, 1); + check_int("budget-one.writes-after", writer.write_calls, 1); +} + +static void test_non_approved_decisions_fail_open_without_writes(void) +{ + kzt_patch_decision_kind_t kinds[] = { + KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_DECISION_DEFERRED, + KZT_PATCH_DECISION_ERROR, + }; + size_t i; + + for (i = 0; i < sizeof(kinds) / sizeof(kinds[0]); ++i) { + kzt_patch_decision_t decision = approved_decision(); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_spike_outcome_t outcome; + fake_writer_t writer = { + .current_value = decision.slot_current_value, + }; + kzt_patch_spike_writer_ops_t ops = writer_ops(&writer); + + decision.kind = kinds[i]; + decision.allow_native_bridge = 0; + check_int("blocked.try-write", + kzt_patch_spike_guard_try_write(&guard, &decision, &ops, + &outcome), 0); + check_int("blocked.result", outcome.result, + KZT_PATCH_SPIKE_RESULT_FAIL_OPEN); + check_int("blocked.failure", outcome.failure, + KZT_PATCH_SPIKE_FAILURE_DECISION_NOT_APPROVED); + check_int("blocked.action", outcome.action, + KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY); + check_int("blocked.skip-legacy", outcome.skip_legacy_write, 0); + assert_no_writer_calls("blocked.writer", &writer); + } +} + +static void run_fail_open_case(const char *name, + fake_writer_t writer, + kzt_patch_spike_failure_t expected_failure, + int expected_reads, + int expected_writes, + int expected_verifies, + int expected_rollbacks) +{ + kzt_patch_decision_t decision = approved_decision(); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_spike_outcome_t outcome; + kzt_patch_spike_writer_ops_t ops = writer_ops(&writer); + char field[128]; + + check_int(name, + kzt_patch_spike_guard_try_write(&guard, &decision, &ops, + &outcome), 0); + snprintf(field, sizeof(field), "%s.result", name); + check_int(field, outcome.result, KZT_PATCH_SPIKE_RESULT_FAIL_OPEN); + snprintf(field, sizeof(field), "%s.failure", name); + check_int(field, outcome.failure, expected_failure); + snprintf(field, sizeof(field), "%s.action", name); + check_int(field, outcome.action, KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY); + snprintf(field, sizeof(field), "%s.skip-legacy", name); + check_int(field, outcome.skip_legacy_write, 0); + snprintf(field, sizeof(field), "%s.reads", name); + check_int(field, writer.read_calls, expected_reads); + snprintf(field, sizeof(field), "%s.writes", name); + check_int(field, writer.write_calls, expected_writes); + snprintf(field, sizeof(field), "%s.verifies", name); + check_int(field, writer.verify_calls, expected_verifies); + snprintf(field, sizeof(field), "%s.rollbacks", name); + check_int(field, writer.rollback_calls, expected_rollbacks); + snprintf(field, sizeof(field), "%s.circuit", name); + check_int(field, kzt_patch_spike_guard_circuit_open(&guard), 0); +} + +static void test_writer_failures_fail_open(void) +{ + kzt_patch_decision_t decision = approved_decision(); + + run_fail_open_case("read-failure", (fake_writer_t) { + .current_value = decision.slot_current_value, + .fail_read = 1, + }, KZT_PATCH_SPIKE_FAILURE_READ_FAILED, 1, 0, 0, 0); + + run_fail_open_case("expected-mismatch", (fake_writer_t) { + .current_value = decision.slot_current_value + 8, + }, KZT_PATCH_SPIKE_FAILURE_EXPECTED_MISMATCH, 1, 0, 0, 0); + + run_fail_open_case("write-failure", (fake_writer_t) { + .current_value = decision.slot_current_value, + .fail_write = 1, + }, KZT_PATCH_SPIKE_FAILURE_WRITE_FAILED, 1, 1, 0, 0); + + run_fail_open_case("verify-failure", (fake_writer_t) { + .current_value = decision.slot_current_value, + .fail_verify = 1, + }, KZT_PATCH_SPIKE_FAILURE_VERIFY_FAILED, 1, 1, 1, 1); +} + +static void test_rollback_failure_opens_circuit_breaker(void) +{ + kzt_patch_decision_t decision = approved_decision(); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_spike_outcome_t outcome; + fake_writer_t writer = { + .current_value = decision.slot_current_value, + .fail_verify = 1, + .fail_rollback = 1, + }; + kzt_patch_spike_writer_ops_t ops = writer_ops(&writer); + + check_int("rollback-failure.first", + kzt_patch_spike_guard_try_write(&guard, &decision, &ops, + &outcome), 0); + check_int("rollback-failure.result", outcome.result, + KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED); + check_int("rollback-failure.failure", outcome.failure, + KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED); + check_int("rollback-failure.rollback-called", outcome.rollback_called, 1); + check_int("rollback-failure.skip-legacy", outcome.skip_legacy_write, 1); + check_int("rollback-failure.circuit-open", + kzt_patch_spike_guard_circuit_open(&guard), 1); + + writer.fail_verify = 0; + writer.fail_rollback = 0; + writer.current_value = decision.slot_current_value; + check_int("rollback-failure.second", + kzt_patch_spike_guard_try_write(&guard, &decision, &ops, + &outcome), 0); + check_int("rollback-failure.second-result", outcome.result, + KZT_PATCH_SPIKE_RESULT_CIRCUIT_OPEN); + check_int("rollback-failure.second-failure", outcome.failure, + KZT_PATCH_SPIKE_FAILURE_CIRCUIT_BREAKER_OPEN); + check_int("rollback-failure.second-skip-legacy", + outcome.skip_legacy_write, 1); + check_int("rollback-failure.reads-after", writer.read_calls, 1); + check_int("rollback-failure.writes-after", writer.write_calls, 1); +} + +typedef struct concurrent_budget_case { + kzt_patch_spike_guard_t *guard; + kzt_patch_decision_t decision; + kzt_patch_spike_writer_ops_t ops; + kzt_patch_spike_outcome_t outcome; +} concurrent_budget_case_t; + +static void *run_concurrent_budget_case(void *opaque) +{ + concurrent_budget_case_t *test = opaque; + + kzt_patch_spike_guard_try_write(test->guard, &test->decision, + &test->ops, &test->outcome); + return NULL; +} + +static void test_concurrent_budget_reservation_allows_exactly_one_writer(void) +{ + enum { THREADS = 8 }; + kzt_patch_spike_guard_t guard = init_guard(1, 1, 1); + fake_writer_t writer = { + .current_value = 0x7200001000, + }; + kzt_patch_spike_writer_ops_t ops = writer_ops(&writer); + concurrent_budget_case_t cases[THREADS]; + pthread_t threads[THREADS]; + int i; + int applied = 0; + + for (i = 0; i < THREADS; ++i) { + cases[i] = (concurrent_budget_case_t) { + .guard = &guard, + .decision = approved_decision(), + .ops = ops, + }; + check_int("concurrent-budget.create", + pthread_create(&threads[i], NULL, + run_concurrent_budget_case, &cases[i]), 0); + } + for (i = 0; i < THREADS; ++i) { + check_int("concurrent-budget.join", pthread_join(threads[i], NULL), + 0); + applied += cases[i].outcome.result == KZT_PATCH_SPIKE_RESULT_APPLIED; + } + check_int("concurrent-budget.applied", applied, 1); + check_int("concurrent-budget.writer-calls", writer.write_calls, 1); + check_ulong("concurrent-budget.attempts", guard.write_attempts, 1); + check_ulong("concurrent-budget.successes", guard.write_successes, 1); + check_int("concurrent-budget.circuit", + kzt_patch_spike_guard_circuit_open(&guard), 0); +} + +int main(void) +{ + test_default_config_is_closed_and_noop(); + test_diagnostics_only_never_writes(); + test_spike_on_without_write_or_budget_is_noop(); + test_approved_budget_one_allows_one_write(); + test_non_approved_decisions_fail_open_without_writes(); + test_writer_failures_fail_open(); + test_rollback_failure_opens_circuit_breaker(); + test_concurrent_budget_reservation_allows_exactly_one_writer(); + + check_true("name.result", strcmp(kzt_patch_spike_result_name( + KZT_PATCH_SPIKE_RESULT_APPLIED), "APPLIED") == 0); + check_true("name.failure", strcmp(kzt_patch_spike_failure_name( + KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED), + "ROLLBACK_FAILED") == 0); + + if (failures) { + fprintf(stderr, "kzt-patch-spike-guard: %d failure(s)\n", failures); + return 1; + } + + puts("kzt-patch-spike-guard: all tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_patch_spike_writer.c b/tests/unit/kzt/test_patch_spike_writer.c new file mode 100644 index 00000000000..8ba12b0e5ae --- /dev/null +++ b/tests/unit/kzt/test_patch_spike_writer.c @@ -0,0 +1,907 @@ +#include +#include +#include + +#include "kzt_test_options.h" +#include "target/i386/latx/include/kzt_patch_spike_writer.h" + +static int failures; + +typedef struct fake_slot { + uintptr_t slot_addr; + uintptr_t value; + uintptr_t replacement_value; + uintptr_t previous_value; + int read_calls; + int write_calls; + int fail_replacement_write; + int fail_rollback_write; + int force_verify_mismatch; + int begin_calls; + int end_calls; + int fail_permission_begin; + int fail_permission_begin_after_enable; + int fail_permission_end; + int fail_generation; +} fake_slot_t; + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lu expected %lu\n", name, got, expected); + ++failures; +} + +static void check_uintptr(const char *name, uintptr_t got, uintptr_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_ptr(const char *name, const void *got, const void *expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %p expected %p\n", name, got, expected); + ++failures; +} + +static kzt_patch_decision_t approved_decision(uintptr_t slot_addr) +{ + return (kzt_patch_decision_t) { + .kind = KZT_PATCH_DECISION_APPROVED, + .reason = KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE, + .allow_native_bridge = 1, + .table_kind = KZT_PATCH_TABLE_PLT_RELA, + .entry_index = 3, + .entry_addr = 0x7100000040, + .reloc_type = KZT_PATCH_RELOCATION_JUMP_SLOT, + .source = { + .known = 1, + .link_map_addr = 0x7000001000, + .generation = 42, + }, + .dynamic_view_available = 1, + .dynamic_view_generation = 43, + .slot_addr = slot_addr, + .slot_current_value_present = 1, + .slot_current_value = 0x7200001000, + .current_owner = { + .known = 1, + .link_map_addr = 0x7200000000, + .generation = 44, + }, + .owner_match = KZT_PATCH_OWNER_MATCH, + .symbol_name = "puts", + .wrapper_name = "wrapped_puts", + .bridge_target = 0x7300002000, + }; +} + +static kzt_patch_candidate_t approved_candidate(uintptr_t slot_addr) +{ + return (kzt_patch_candidate_t) { + .source = { + .known = 1, + .link_map_addr = 0x7000001000, + .map_start = 0x7000000000, + .map_end = 0x7000008000, + .generation = 42, + .soname = "librequester.so", + .path = "/guest/lib/librequester.so", + }, + .dynamic_addr = 0x7000004000, + .load_bias = 0x7000000000, + .dynamic_view_generation = 43, + .dynamic_view_available = 1, + .table_kind = KZT_PATCH_TABLE_PLT_RELA, + .entry_index = 3, + .entry_addr = 0x7000004180, + .reloc_type = KZT_PATCH_RELOCATION_JUMP_SLOT, + .slot_addr = slot_addr, + .slot_current_value_present = 1, + .slot_current_value = 0x7200001000, + .symbol_index = 77, + .symbol_name = "gtk_widget_show", + .version = "GTK_3.0", + .current_owner = { + .known = 1, + .link_map_addr = 0x7200000000, + .map_start = 0x7200000000, + .map_end = 0x7200010000, + .generation = 44, + .soname = "libgtk-3.so", + .path = "/guest/lib/libgtk-3.so", + }, + .owner_match = KZT_PATCH_OWNER_MATCH, + .wrapper_match = KZT_PATCH_WRAPPER_VERSION_MATCH, + .wrapper_name = "wrappedgtk3", + .wrapper_symbol_version = "GTK_3.0", + .bridge_target = 0x7300002000, + }; +} + +static kzt_patch_spike_guard_t init_guard(int enabled, int write_enabled, + unsigned long budget) +{ + kzt_patch_spike_config_t config = { + .enabled = enabled, + .write_enabled = write_enabled, + .budget = budget, + }; + kzt_patch_spike_guard_t guard; + + kzt_patch_spike_guard_init(&guard, &config); + return guard; +} + +static int fake_read_slot(uintptr_t slot_addr, uintptr_t *value, void *opaque) +{ + fake_slot_t *slot = opaque; + + if (!slot || !value || slot_addr != slot->slot_addr) { + return -1; + } + + ++slot->read_calls; + if (slot->force_verify_mismatch && slot->read_calls == 2) { + *value = slot->replacement_value + 8; + return 0; + } + + *value = slot->value; + return 0; +} + +static int fake_write_slot(uintptr_t slot_addr, uintptr_t value, void *opaque) +{ + fake_slot_t *slot = opaque; + + if (!slot || slot_addr != slot->slot_addr) { + return -1; + } + ++slot->write_calls; + if (value == slot->replacement_value && slot->fail_replacement_write) { + return -1; + } + if (value == slot->previous_value && slot->fail_rollback_write) { + return -1; + } + + slot->value = value; + return 0; +} + +static int fake_begin_write(uintptr_t slot_addr, + kzt_patch_spike_permission_lease_t *lease, + void *opaque) +{ + fake_slot_t *slot = opaque; + + if (!slot || !lease || slot_addr != slot->slot_addr) { + return -1; + } + ++slot->begin_calls; + lease->checked = 1; + lease->guest_page = slot_addr & ~(uintptr_t)0xfff; + lease->guest_page_length = 0x1000; + lease->original_permissions = 5; + if (slot->fail_permission_begin) { + return -1; + } + lease->write_enabled = 1; + if (slot->fail_permission_begin_after_enable) { + return -1; + } + return 0; +} + +static int fake_end_write(kzt_patch_spike_permission_lease_t *lease, + void *opaque) +{ + fake_slot_t *slot = opaque; + + if (!slot || !lease) { + return -1; + } + ++slot->end_calls; + if (slot->fail_permission_end) { + return -1; + } + return 0; +} + +static int fake_validate_generation(const kzt_patch_decision_t *decision, + void *opaque) +{ + fake_slot_t *slot = opaque; + + return !decision || !slot || slot->fail_generation ? -1 : 0; +} + +static kzt_patch_spike_slot_ops_t fake_slot_ops(fake_slot_t *slot) +{ + return (kzt_patch_spike_slot_ops_t) { + .read_slot = fake_read_slot, + .write_slot = fake_write_slot, + .begin_write = fake_begin_write, + .end_write = fake_end_write, + .validate_generation = fake_validate_generation, + .opaque = slot, + }; +} + +static int trace_enabled(void) +{ + const char *value = getenv("KZT_PATCH_SPIKE_WRITER_TEST_TRACE"); + + return value && value[0] && strcmp(value, "0") != 0; +} + +static void trace_record(const char *tc, + const char *target, + const kzt_patch_decision_t *decision, + const kzt_patch_spike_record_t *record, + const fake_slot_t *slot) +{ + uintptr_t final_value = slot ? slot->value : 0; + + if (!trace_enabled() || !decision || !record) { + return; + } + + printf("KZT_SPIKE_TC tc=%s target=%s decision=%s reason=%s " + "table=%s reloc=%s symbol=%s wrapper=%s result=%s failure=%s " + "skip_legacy=%d writer_called=%d slot_addr=0x%lx " + "expected=0x%lx replacement=0x%lx observed=0x%lx " + "verified=0x%lx final=0x%lx rollback_called=%d " + "writes_remaining=%lu\n", + tc, target, + kzt_patch_decision_kind_name(decision->kind), + kzt_patch_reason_name(decision->reason), + kzt_patch_table_kind_name(decision->table_kind), + kzt_patch_relocation_type_name(decision->reloc_type), + decision->symbol_name ? decision->symbol_name : "(none)", + decision->wrapper_name ? decision->wrapper_name : "(none)", + kzt_patch_spike_result_name(record->result), + kzt_patch_spike_failure_name(record->failure), + record->skip_legacy_write, record->writer_called, + (unsigned long)decision->slot_addr, + (unsigned long)decision->slot_current_value, + (unsigned long)decision->bridge_target, + (unsigned long)record->observed_value, + (unsigned long)record->verified_value, + (unsigned long)final_value, + record->rollback_called, + record->writes_remaining); +} + +static void check_no_slot_calls(const char *name, const fake_slot_t *slot) +{ + char field[128]; + + snprintf(field, sizeof(field), "%s.read", name); + check_int(field, slot->read_calls, 0); + snprintf(field, sizeof(field), "%s.write", name); + check_int(field, slot->write_calls, 0); +} + +static void test_planner_approved_jump_slot_drives_writer(void) +{ + kzt_patch_candidate_t candidate = approved_candidate(0x7100000018); + kzt_patch_decision_t decision; + kzt_patch_spike_guard_t guard = init_guard(1, 1, 2); + kzt_patch_spike_record_t record; + fake_slot_t slot = { + .slot_addr = candidate.slot_addr, + .value = candidate.slot_current_value, + .replacement_value = candidate.bridge_target, + .previous_value = candidate.slot_current_value, + }; + kzt_patch_spike_slot_ops_t ops = fake_slot_ops(&slot); + + check_int("planner-writer.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + check_int("planner-writer.kind", decision.kind, + KZT_PATCH_DECISION_APPROVED); + check_int("planner-writer.reason", decision.reason, + KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE); + check_int("planner-writer.allow", decision.allow_native_bridge, 1); + check_int("planner-writer.reloc", decision.reloc_type, + KZT_PATCH_RELOCATION_JUMP_SLOT); + + check_int("planner-writer.apply", + kzt_patch_spike_writer_try_apply_with_slot_ops(&guard, + &decision, + &ops, + &record), 0); + check_int("planner-writer.result", record.result, + KZT_PATCH_SPIKE_RESULT_APPLIED); + check_int("planner-writer.failure", record.failure, + KZT_PATCH_SPIKE_FAILURE_NONE); + check_int("planner-writer.skip", record.skip_legacy_write, 1); + check_int("planner-writer.writer-called", record.writer_called, 1); + check_uintptr("planner-writer.final", slot.value, + candidate.bridge_target); + trace_record("TC1", "planner-approved-jump-slot", &decision, &record, + &slot); +} + +static void test_success_write_and_verify_with_direct_slot_ops(void) +{ + uintptr_t slot = 0x7200001000; + kzt_patch_decision_t decision = approved_decision((uintptr_t)&slot); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 1); + kzt_patch_spike_record_t record; + + check_int("success.try-apply", + kzt_patch_spike_writer_try_apply(&guard, &decision, + &record), 0); + check_uintptr("success.slot", slot, decision.bridge_target); + check_int("success.result", record.result, KZT_PATCH_SPIKE_RESULT_APPLIED); + check_int("success.failure", record.failure, KZT_PATCH_SPIKE_FAILURE_NONE); + check_int("success.action", record.action, + KZT_PATCH_SPIKE_ACTION_USE_NATIVE_BRIDGE); + check_int("success.skip-legacy", record.skip_legacy_write, 1); + check_ulong("success.remaining", record.writes_remaining, 0); + check_int("success.writer-called", record.writer_called, 1); + check_int("success.read", record.read_attempted, 1); + check_int("success.expected-match", record.expected_current_matched, 1); + check_int("success.write-attempt", record.write_attempted, 1); + check_int("success.write-success", record.write_succeeded, 1); + check_int("success.verify-attempt", record.verify_attempted, 1); + check_int("success.verify-success", record.verify_succeeded, 1); + check_int("success.rollback", record.rollback_called, 0); + check_uintptr("success.previous", record.previous_value, + decision.slot_current_value); + check_uintptr("success.observed", record.observed_value, + decision.slot_current_value); + check_uintptr("success.verified", record.verified_value, + decision.bridge_target); +} + +static void test_expected_current_mismatch_does_not_write(void) +{ + kzt_patch_decision_t decision = approved_decision(0x7100000018); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_spike_record_t record; + fake_slot_t slot = { + .slot_addr = decision.slot_addr, + .value = decision.slot_current_value + 4, + .replacement_value = decision.bridge_target, + .previous_value = decision.slot_current_value + 4, + }; + kzt_patch_spike_slot_ops_t ops = fake_slot_ops(&slot); + + check_int("mismatch.try-apply", + kzt_patch_spike_writer_try_apply_with_slot_ops(&guard, + &decision, + &ops, + &record), 0); + check_int("mismatch.result", record.result, + KZT_PATCH_SPIKE_RESULT_FAIL_OPEN); + check_int("mismatch.failure", record.failure, + KZT_PATCH_SPIKE_FAILURE_EXPECTED_MISMATCH); + check_int("mismatch.read-calls", slot.read_calls, 1); + check_int("mismatch.write-calls", slot.write_calls, 0); + check_int("mismatch.write-attempt", record.write_attempted, 0); + check_int("mismatch.expected-match", record.expected_current_matched, 0); + check_uintptr("mismatch.observed", record.observed_value, + decision.slot_current_value + 4); + trace_record("TC2", "expected-current-mismatch-fail-open", &decision, + &record, &slot); +} + +static void test_guard_diagnostics_only_does_not_call_writer(void) +{ + kzt_patch_decision_t decision = approved_decision(0x7100000018); + kzt_patch_spike_guard_t guard = init_guard(1, 0, 8); + kzt_patch_spike_record_t record; + fake_slot_t slot = { + .slot_addr = decision.slot_addr, + .value = decision.slot_current_value, + .replacement_value = decision.bridge_target, + .previous_value = decision.slot_current_value, + }; + kzt_patch_spike_slot_ops_t ops = fake_slot_ops(&slot); + + check_int("diagnostics.try-apply", + kzt_patch_spike_writer_try_apply_with_slot_ops(&guard, + &decision, + &ops, + &record), 0); + check_int("diagnostics.result", record.result, + KZT_PATCH_SPIKE_RESULT_DIAGNOSTICS_ONLY); + check_int("diagnostics.failure", record.failure, + KZT_PATCH_SPIKE_FAILURE_WRITE_NOT_AUTHORIZED); + check_int("diagnostics.writer-called", record.writer_called, 0); + check_no_slot_calls("diagnostics.slot", &slot); + trace_record("TC3", "diagnostics-only-keeps-legacy", &decision, &record, + &slot); +} + +static void test_write_failure_is_recorded(void) +{ + kzt_patch_decision_t decision = approved_decision(0x7100000018); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_spike_record_t record; + fake_slot_t slot = { + .slot_addr = decision.slot_addr, + .value = decision.slot_current_value, + .replacement_value = decision.bridge_target, + .previous_value = decision.slot_current_value, + .fail_replacement_write = 1, + }; + kzt_patch_spike_slot_ops_t ops = fake_slot_ops(&slot); + + check_int("write-fail.try-apply", + kzt_patch_spike_writer_try_apply_with_slot_ops(&guard, + &decision, + &ops, + &record), 0); + check_int("write-fail.result", record.result, + KZT_PATCH_SPIKE_RESULT_FAIL_OPEN); + check_int("write-fail.failure", record.failure, + KZT_PATCH_SPIKE_FAILURE_WRITE_FAILED); + check_int("write-fail.read-calls", slot.read_calls, 1); + check_int("write-fail.write-calls", slot.write_calls, 1); + check_int("write-fail.write-attempt", record.write_attempted, 1); + check_int("write-fail.write-success", record.write_succeeded, 0); + check_uintptr("write-fail.slot", slot.value, decision.slot_current_value); + trace_record("TC4", "write-failure-fail-open", &decision, &record, + &slot); +} + +static void test_verify_failure_rolls_back_successfully(void) +{ + kzt_patch_decision_t decision = approved_decision(0x7100000018); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_spike_record_t record; + fake_slot_t slot = { + .slot_addr = decision.slot_addr, + .value = decision.slot_current_value, + .replacement_value = decision.bridge_target, + .previous_value = decision.slot_current_value, + .force_verify_mismatch = 1, + }; + kzt_patch_spike_slot_ops_t ops = fake_slot_ops(&slot); + + check_int("verify-fail.try-apply", + kzt_patch_spike_writer_try_apply_with_slot_ops(&guard, + &decision, + &ops, + &record), 0); + check_int("verify-fail.result", record.result, + KZT_PATCH_SPIKE_RESULT_FAIL_OPEN); + check_int("verify-fail.failure", record.failure, + KZT_PATCH_SPIKE_FAILURE_VERIFY_FAILED); + check_int("verify-fail.read-calls", slot.read_calls, 3); + check_int("verify-fail.write-calls", slot.write_calls, 2); + check_int("verify-fail.verify-attempt", record.verify_attempted, 1); + check_int("verify-fail.verify-success", record.verify_succeeded, 0); + check_int("verify-fail.rollback-called", record.rollback_called, 1); + check_int("verify-fail.rollback-success", record.rollback_succeeded, 1); + check_int("verify-fail.rollback-verify", + record.rollback_verify_succeeded, 1); + check_uintptr("verify-fail.rollback-value", record.rollback_value, + decision.slot_current_value); + check_uintptr("verify-fail.slot", slot.value, decision.slot_current_value); + trace_record("TC5", "verify-failure-rolls-back", &decision, &record, + &slot); +} + +static void test_rollback_failure_is_observable(void) +{ + kzt_patch_decision_t decision = approved_decision(0x7100000018); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_spike_record_t record; + fake_slot_t slot = { + .slot_addr = decision.slot_addr, + .value = decision.slot_current_value, + .replacement_value = decision.bridge_target, + .previous_value = decision.slot_current_value, + .force_verify_mismatch = 1, + .fail_rollback_write = 1, + }; + kzt_patch_spike_slot_ops_t ops = fake_slot_ops(&slot); + + check_int("rollback-fail.try-apply", + kzt_patch_spike_writer_try_apply_with_slot_ops(&guard, + &decision, + &ops, + &record), 0); + check_int("rollback-fail.result", record.result, + KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED); + check_int("rollback-fail.failure", record.failure, + KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED); + check_int("rollback-fail.rollback-called", record.rollback_called, 1); + check_int("rollback-fail.rollback-success", record.rollback_succeeded, 0); + check_int("rollback-fail.circuit", + kzt_patch_spike_guard_circuit_open(&guard), 1); + check_uintptr("rollback-fail.slot", slot.value, decision.bridge_target); + trace_record("TC6", "rollback-failure-opens-circuit", &decision, + &record, &slot); +} + +static void test_permission_enable_failure_does_not_write(void) +{ + kzt_patch_decision_t decision = approved_decision(0x7100000018); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_spike_record_t record; + fake_slot_t slot = { + .slot_addr = decision.slot_addr, + .value = decision.slot_current_value, + .replacement_value = decision.bridge_target, + .previous_value = decision.slot_current_value, + .fail_permission_begin = 1, + }; + kzt_patch_spike_slot_ops_t ops = fake_slot_ops(&slot); + + check_int("permission-enable.apply", + kzt_patch_spike_writer_try_apply_with_slot_ops( + &guard, &decision, &ops, &record), 0); + check_int("permission-enable.failure", record.failure, + KZT_PATCH_SPIKE_FAILURE_PERMISSION_ENABLE_FAILED); + check_int("permission-enable.begin", slot.begin_calls, 1); + check_int("permission-enable.no-read", slot.read_calls, 0); + check_int("permission-enable.no-write", slot.write_calls, 0); + check_int("permission-enable.checked", record.permission_checked, 1); + check_int("permission-enable.opened", record.permission_write_enabled, 0); + check_int("permission-enable.preserve", record.skip_legacy_write, 1); +} + +static void test_permission_restore_failure_opens_circuit(void) +{ + kzt_patch_decision_t decision = approved_decision(0x7100000018); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_spike_record_t record; + fake_slot_t slot = { + .slot_addr = decision.slot_addr, + .value = decision.slot_current_value, + .replacement_value = decision.bridge_target, + .previous_value = decision.slot_current_value, + .fail_permission_end = 1, + }; + kzt_patch_spike_slot_ops_t ops = fake_slot_ops(&slot); + + check_int("permission-restore.apply", + kzt_patch_spike_writer_try_apply_with_slot_ops( + &guard, &decision, &ops, &record), 0); + check_int("permission-restore.result", record.result, + KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED); + check_int("permission-restore.failure", record.failure, + KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED); + check_int("permission-restore.end", slot.end_calls, 1); + check_int("permission-restore.write", record.write_succeeded, 1); + check_int("permission-restore.verify", record.verify_succeeded, 1); + check_int("permission-restore.attempted", + record.permission_restore_attempted, 1); + check_int("permission-restore.restored", record.permission_restored, 0); + check_int("permission-restore.circuit", + kzt_patch_spike_guard_circuit_open(&guard), 1); + check_int("permission-restore.preserve", record.skip_legacy_write, 1); +} + +static void test_partial_permission_enable_is_restored(void) +{ + kzt_patch_decision_t decision = approved_decision(0x7100000018); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_spike_record_t record; + fake_slot_t slot = { + .slot_addr = decision.slot_addr, + .value = decision.slot_current_value, + .replacement_value = decision.bridge_target, + .previous_value = decision.slot_current_value, + .fail_permission_begin_after_enable = 1, + }; + kzt_patch_spike_slot_ops_t ops = fake_slot_ops(&slot); + + check_int("permission-partial.apply", + kzt_patch_spike_writer_try_apply_with_slot_ops( + &guard, &decision, &ops, &record), 0); + check_int("permission-partial.failure", record.failure, + KZT_PATCH_SPIKE_FAILURE_PERMISSION_ENABLE_FAILED); + check_int("permission-partial.begin", slot.begin_calls, 1); + check_int("permission-partial.end", slot.end_calls, 1); + check_int("permission-partial.restore", record.permission_restored, 1); + check_int("permission-partial.no-write", slot.write_calls, 0); +} + +static void test_generation_mismatch_does_not_touch_permissions_or_slot(void) +{ + kzt_patch_decision_t decision = approved_decision(0x7100000018); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_spike_record_t record; + fake_slot_t slot = { + .slot_addr = decision.slot_addr, + .value = decision.slot_current_value, + .replacement_value = decision.bridge_target, + .previous_value = decision.slot_current_value, + .fail_generation = 1, + }; + kzt_patch_spike_slot_ops_t ops = fake_slot_ops(&slot); + + check_int("generation.apply", kzt_patch_spike_writer_try_apply_with_slot_ops( + &guard, &decision, &ops, &record), 0); + check_int("generation.failure", record.failure, + KZT_PATCH_SPIKE_FAILURE_GENERATION_MISMATCH); + check_int("generation.result", record.result, + KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED); + check_int("generation.checked", record.generation_checked, 1); + check_int("generation.match", record.generation_matched, 0); + check_int("generation.no-permission", slot.begin_calls, 0); + check_no_slot_calls("generation.slot", &slot); +} + +static void test_non_approved_decision_does_not_write(void) +{ + kzt_patch_decision_t decision = approved_decision(0x7100000018); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_spike_record_t record; + fake_slot_t slot = { + .slot_addr = decision.slot_addr, + .value = decision.slot_current_value, + .replacement_value = decision.bridge_target, + .previous_value = decision.slot_current_value, + }; + kzt_patch_spike_slot_ops_t ops = fake_slot_ops(&slot); + + decision.kind = KZT_PATCH_DECISION_REJECTED; + decision.reason = KZT_PATCH_REASON_POLICY_KEEP_GUEST; + decision.allow_native_bridge = 0; + check_int("rejected.try-apply", + kzt_patch_spike_writer_try_apply_with_slot_ops(&guard, + &decision, + &ops, + &record), 0); + check_int("rejected.result", record.result, + KZT_PATCH_SPIKE_RESULT_FAIL_OPEN); + check_int("rejected.failure", record.failure, + KZT_PATCH_SPIKE_FAILURE_DECISION_NOT_APPROVED); + check_int("rejected.writer-called", record.writer_called, 0); + check_no_slot_calls("rejected.slot", &slot); +} + +static void test_approved_non_jump_slot_does_not_consume_guard_budget(void) +{ + kzt_patch_decision_t decision = approved_decision(0x7100000018); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_spike_record_t record; + fake_slot_t slot = { + .slot_addr = decision.slot_addr, + .value = decision.slot_current_value, + .replacement_value = decision.bridge_target, + .previous_value = decision.slot_current_value, + }; + kzt_patch_spike_slot_ops_t ops = fake_slot_ops(&slot); + + decision.table_kind = KZT_PATCH_TABLE_RELA; + decision.reloc_type = KZT_PATCH_RELOCATION_GLOB_DAT; + check_int("glob-dat.try-apply", + kzt_patch_spike_writer_try_apply_with_slot_ops(&guard, + &decision, + &ops, + &record), 0); + check_int("glob-dat.result", record.result, + KZT_PATCH_SPIKE_RESULT_FAIL_OPEN); + check_int("glob-dat.failure", record.failure, + KZT_PATCH_SPIKE_FAILURE_INVALID_ARGUMENT); + check_int("glob-dat.writer-called", record.writer_called, 0); + check_ulong("glob-dat.remaining", record.writes_remaining, 8); + check_no_slot_calls("glob-dat.slot", &slot); +} + +static void test_persistent_guard_budget_blocks_second_write(void) +{ + kzt_patch_spike_guard_t guard = init_guard(1, 1, 1); + kzt_patch_decision_t first_decision = approved_decision(0x7100000018); + kzt_patch_decision_t second_decision = approved_decision(0x7100000020); + kzt_patch_spike_record_t first_record; + kzt_patch_spike_record_t second_record; + fake_slot_t first_slot = { + .slot_addr = first_decision.slot_addr, + .value = first_decision.slot_current_value, + .replacement_value = first_decision.bridge_target, + .previous_value = first_decision.slot_current_value, + }; + fake_slot_t second_slot = { + .slot_addr = second_decision.slot_addr, + .value = second_decision.slot_current_value, + .replacement_value = second_decision.bridge_target, + .previous_value = second_decision.slot_current_value, + }; + kzt_patch_spike_slot_ops_t first_ops = fake_slot_ops(&first_slot); + kzt_patch_spike_slot_ops_t second_ops = fake_slot_ops(&second_slot); + + check_int("persistent-budget.first-apply", + kzt_patch_spike_writer_try_apply_with_slot_ops(&guard, + &first_decision, + &first_ops, + &first_record), 0); + check_int("persistent-budget.first-result", first_record.result, + KZT_PATCH_SPIKE_RESULT_APPLIED); + check_int("persistent-budget.first-skip", first_record.skip_legacy_write, + 1); + check_uintptr("persistent-budget.first-slot", first_slot.value, + first_decision.bridge_target); + + check_int("persistent-budget.second-apply", + kzt_patch_spike_writer_try_apply_with_slot_ops(&guard, + &second_decision, + &second_ops, + &second_record), 0); + check_int("persistent-budget.second-result", second_record.result, + KZT_PATCH_SPIKE_RESULT_BUDGET_EXHAUSTED); + check_int("persistent-budget.second-failure", second_record.failure, + KZT_PATCH_SPIKE_FAILURE_BUDGET_EXHAUSTED); + check_int("persistent-budget.second-writer", second_record.writer_called, + 0); + check_int("persistent-budget.second-skip", second_record.skip_legacy_write, + 0); + check_ulong("persistent-budget.second-remaining", + second_record.writes_remaining, 0); + check_uintptr("persistent-budget.second-slot", second_slot.value, + second_decision.slot_current_value); + check_no_slot_calls("persistent-budget.second-slot-calls", &second_slot); + check_ulong("persistent-budget.attempts", guard.write_attempts, 1); + check_ulong("persistent-budget.successes", guard.write_successes, 1); + trace_record("TC7", "persistent-guard-budget-exhausted", + &second_decision, &second_record, &second_slot); +} + +static void test_persistent_guard_circuit_blocks_second_write(void) +{ + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_decision_t first_decision = approved_decision(0x7100000018); + kzt_patch_decision_t second_decision = approved_decision(0x7100000020); + kzt_patch_spike_record_t first_record; + kzt_patch_spike_record_t second_record; + fake_slot_t first_slot = { + .slot_addr = first_decision.slot_addr, + .value = first_decision.slot_current_value, + .replacement_value = first_decision.bridge_target, + .previous_value = first_decision.slot_current_value, + .force_verify_mismatch = 1, + .fail_rollback_write = 1, + }; + fake_slot_t second_slot = { + .slot_addr = second_decision.slot_addr, + .value = second_decision.slot_current_value, + .replacement_value = second_decision.bridge_target, + .previous_value = second_decision.slot_current_value, + }; + kzt_patch_spike_slot_ops_t first_ops = fake_slot_ops(&first_slot); + kzt_patch_spike_slot_ops_t second_ops = fake_slot_ops(&second_slot); + + check_int("persistent-circuit.first-apply", + kzt_patch_spike_writer_try_apply_with_slot_ops(&guard, + &first_decision, + &first_ops, + &first_record), 0); + check_int("persistent-circuit.first-result", first_record.result, + KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED); + check_int("persistent-circuit.first-failure", first_record.failure, + KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED); + check_int("persistent-circuit.first-rollback", first_record.rollback_called, + 1); + check_int("persistent-circuit.open", + kzt_patch_spike_guard_circuit_open(&guard), 1); + check_uintptr("persistent-circuit.first-slot", first_slot.value, + first_decision.bridge_target); + + check_int("persistent-circuit.second-apply", + kzt_patch_spike_writer_try_apply_with_slot_ops(&guard, + &second_decision, + &second_ops, + &second_record), 0); + check_int("persistent-circuit.second-result", second_record.result, + KZT_PATCH_SPIKE_RESULT_CIRCUIT_OPEN); + check_int("persistent-circuit.second-failure", second_record.failure, + KZT_PATCH_SPIKE_FAILURE_CIRCUIT_BREAKER_OPEN); + check_int("persistent-circuit.second-writer", second_record.writer_called, + 0); + check_int("persistent-circuit.second-skip", second_record.skip_legacy_write, + 1); + check_uintptr("persistent-circuit.second-slot", second_slot.value, + second_decision.slot_current_value); + check_no_slot_calls("persistent-circuit.second-slot-calls", &second_slot); + check_ulong("persistent-circuit.attempts", guard.write_attempts, 1); + check_ulong("persistent-circuit.successes", guard.write_successes, 0); +} + +static void test_record_fields_are_complete(void) +{ + uintptr_t slot = 0x7200001000; + kzt_patch_decision_t decision = approved_decision((uintptr_t)&slot); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 2); + kzt_patch_spike_record_t record; + + decision.source.generation = 42; + decision.current_owner.generation = 43; + decision.dynamic_view_generation = 44; + + check_int("record.try-apply", + kzt_patch_spike_writer_try_apply(&guard, &decision, + &record), 0); + check_int("record.valid", record.valid, 1); + check_int("record.kind", record.decision_kind, decision.kind); + check_int("record.reason", record.decision_reason, decision.reason); + check_int("record.allow", record.allow_native_bridge, + decision.allow_native_bridge); + check_int("record.table", record.table_kind, decision.table_kind); + check_int("record.reloc", record.reloc_type, decision.reloc_type); + check_ulong("record.entry-index", record.entry_index, + decision.entry_index); + check_uintptr("record.entry", record.entry_addr, decision.entry_addr); + check_uintptr("record.slot", record.slot_addr, decision.slot_addr); + check_uintptr("record.source-link-map", record.source_link_map, + decision.source.link_map_addr); + check_uintptr("record.owner-link-map", record.current_owner_link_map, + decision.current_owner.link_map_addr); + check_ulong("record.source-generation", record.source_generation, 42); + check_ulong("record.owner-generation", record.current_owner_generation, + 43); + check_ulong("record.dynamic-generation", record.dynamic_view_generation, + 44); + check_int("record.expected-present", record.expected_value_present, 1); + check_uintptr("record.expected", record.expected_value, + decision.slot_current_value); + check_uintptr("record.replacement", record.replacement_value, + decision.bridge_target); + check_ptr("record.symbol", record.symbol_name, decision.symbol_name); + check_ptr("record.wrapper", record.wrapper_name, decision.wrapper_name); + check_int("record.result", record.result, KZT_PATCH_SPIKE_RESULT_APPLIED); + check_int("record.failure", record.failure, KZT_PATCH_SPIKE_FAILURE_NONE); + check_int("record.action", record.action, + KZT_PATCH_SPIKE_ACTION_USE_NATIVE_BRIDGE); + check_ulong("record.remaining", record.writes_remaining, 1); +} + +int main(void) +{ + test_planner_approved_jump_slot_drives_writer(); + test_success_write_and_verify_with_direct_slot_ops(); + test_expected_current_mismatch_does_not_write(); + test_guard_diagnostics_only_does_not_call_writer(); + test_write_failure_is_recorded(); + test_verify_failure_rolls_back_successfully(); + test_rollback_failure_is_observable(); + test_permission_enable_failure_does_not_write(); + test_permission_restore_failure_opens_circuit(); + test_partial_permission_enable_is_restored(); + test_generation_mismatch_does_not_touch_permissions_or_slot(); + test_non_approved_decision_does_not_write(); + test_approved_non_jump_slot_does_not_consume_guard_budget(); + test_persistent_guard_budget_blocks_second_write(); + test_persistent_guard_circuit_blocks_second_write(); + test_record_fields_are_complete(); + + if (failures) { + fprintf(stderr, "kzt-patch-spike-writer: %d failure(s)\n", failures); + return 1; + } + + puts("kzt-patch-spike-writer: all tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_real_guest_e2e.py b/tests/unit/kzt/test_real_guest_e2e.py new file mode 100755 index 00000000000..8e845bbd7f0 --- /dev/null +++ b/tests/unit/kzt/test_real_guest_e2e.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +import argparse +import os +from pathlib import Path +import re +import subprocess +import sys + + +SUCCESS_PATTERN = re.compile( + r"KZT_GUEST_E2E_OK calls=2 slot=(0x[0-9a-f]+) " + r"before=(0x[0-9a-f]+) after_first=(0x[0-9a-f]+) " + r"after_second=(0x[0-9a-f]+) first_ns=(0x[0-9a-f]+) " + r"second_ns=(0x[0-9a-f]+)" +) +TARGET_SYMBOL = "uname" + + +def parse_record(line): + record = {} + for field in line.split(): + if "=" in field: + key, value = field.split("=", 1) + record[key] = value + return record + + +def symbol_records(output, prefix): + records = [] + for line in output.splitlines(): + marker = line.find(prefix) + if marker >= 0: + record = parse_record(line[marker:]) + if record.get("symbol") == TARGET_SYMBOL: + records.append((line, record)) + return records + + +def require(condition, message, output=None): + if condition: + return + if output: + message = f"{message}\n--- guest output ---\n{output.rstrip()}" + raise RuntimeError(message) + + +def verify_cross_arch_version(args): + guest_version = ( + args.fixture_dir / "guest-symbol-version.txt" + ).read_text(encoding="utf-8").strip() + completed = subprocess.run( + [args.readelf, "-Ws", str(args.host_libc)], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + timeout=args.timeout, + check=False, + ) + require(completed.returncode == 0, + "Could not inspect the host libc symbol versions.", + completed.stdout) + host_versions = set(re.findall( + rf"\b{TARGET_SYMBOL}@@?(GLIBC_[^\s]+)", completed.stdout + )) + require(len(host_versions) == 1, + "Host libc must expose one inspectable uname symbol version.", + completed.stdout) + host_version = next(iter(host_versions)) + require(host_version != guest_version, + "Cross-architecture fixture requires different guest and host " + "uname versions.") + print( + f"Cross-architecture versions: guest={guest_version} " + f"host={host_version}" + ) + + +def run_mode(args, mode, budget, expected_writer, expected_fallback): + executable = args.fixture_dir / "kzt_guest_main" + command = [ + str(args.latx), + "-L", + str(args.guest_root), + "-E", + f"LD_LIBRARY_PATH={args.fixture_dir}", + str(executable), + ] + environment = os.environ.copy() + environment.update( + { + "LATX_KZT": "1", + "LATX_KZT_REGISTRY_DIAGNOSTICS": "1", + "LATX_KZT_LAZY_DIAGNOSTICS": "1", + "LATX_KZT_PATCH_SPIKE": "1", + "LATX_KZT_PATCH_SPIKE_WRITE": "1", + "LATX_KZT_PATCH_SPIKE_BUDGET": str(budget), + } + ) + completed = subprocess.run( + command, + env=environment, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + timeout=args.timeout, + check=False, + ) + output = completed.stdout + log_path = args.log_dir / f"real-guest-{mode}.log" + log_path.write_text(output, encoding="utf-8") + + require(completed.returncode == 0, + f"{mode} mode exited with {completed.returncode}.", output) + success_matches = SUCCESS_PATTERN.findall(output) + require(len(success_matches) == 1, + f"{mode} mode did not complete both uname calls.", output) + slot, before, after_first, after_second, first_ns, second_ns = ( + int(value, 16) for value in success_matches[0] + ) + require(slot != 0 and before != 0, + f"{mode} mode reported an invalid uname GOT slot.", output) + require(after_first != before, + f"{mode} mode did not replace the unresolved uname slot.", output) + require(after_second == after_first, + f"{mode} mode changed the uname slot on the second call.", output) + require(first_ns > 0 and second_ns > 0, + f"{mode} mode did not report call timings.", output) + + rela_records = symbol_records(output, "kzt_rela_diagnostic ") + require(len(rela_records) == 1, + f"{mode} mode expected one uname relocation record, " + f"found {len(rela_records)}.", output) + _, rela = rela_records[0] + require(rela.get("decision") == "APPROVED", + f"{mode} mode did not approve the uname patch.", output) + require(rela.get("writer_result") == expected_writer, + f"{mode} mode writer result is not {expected_writer}.", output) + require(rela.get("legacy_fallback") == expected_fallback, + f"{mode} mode legacy fallback is not {expected_fallback}.", output) + if expected_writer == "APPLIED": + require(int(rela.get("bridge_target", "0"), 16) == after_first, + f"{mode} mode GOT value does not match the approved bridge.", + output) + + resolver_entries = symbol_records(output, "kzt_lazy_resolver_entry ") + require(len(resolver_entries) == 1, + f"{mode} mode entered the uname resolver " + f"{len(resolver_entries)} times instead of once.", output) + + lazy_records = symbol_records(output, "kzt_lazy_diagnostic ") + require(len(lazy_records) == 1, + f"{mode} mode resolved uname more than once or missed it; " + f"found {len(lazy_records)} lazy records.", output) + return log_path + + +def existing_directory(value): + path = Path(value).resolve() + if not path.is_dir(): + raise argparse.ArgumentTypeError(f"Directory does not exist: {path}") + return path + + +def existing_file(value): + path = Path(value).resolve() + if not path.is_file(): + raise argparse.ArgumentTypeError(f"File does not exist: {path}") + return path + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Run the KZT real x86-64 guest lazy-binding gate." + ) + parser.add_argument("--latx", required=True, type=existing_file) + parser.add_argument("--guest-root", required=True, type=existing_directory) + parser.add_argument("--host-libc", required=True, type=existing_file) + parser.add_argument("--fixture-dir", required=True, type=existing_directory) + parser.add_argument("--readelf", default="readelf") + parser.add_argument("--log-dir", type=Path) + parser.add_argument("--timeout", type=float, default=30.0) + args = parser.parse_args() + if args.log_dir is None: + args.log_dir = args.fixture_dir / "logs" + else: + args.log_dir = args.log_dir.resolve() + args.log_dir.mkdir(parents=True, exist_ok=True) + require((args.fixture_dir / "kzt_guest_main").is_file(), + "Fixture directory is missing kzt_guest_main.") + require((args.fixture_dir / "libkzt_guest_probe.so").is_file(), + "Fixture directory is missing libkzt_guest_probe.so.") + require((args.fixture_dir / "guest-symbol-version.txt").is_file(), + "Fixture directory is missing guest-symbol-version.txt.") + return args + + +def main(): + args = parse_args() + verify_cross_arch_version(args) + normal_log = run_mode(args, "applied", 1, "APPLIED", "0") + fail_open_log = run_mode( + args, "fail-open", 0, "BUDGET_EXHAUSTED", "1" + ) + print("KZT real guest E2E: PASS") + print(f"Applied log: {normal_log}") + print(f"Fail-open log: {fail_open_log}") + + +if __name__ == "__main__": + try: + main() + except (RuntimeError, subprocess.TimeoutExpired) as error: + print(f"KZT real guest E2E: FAIL: {error}", file=sys.stderr) + sys.exit(1) diff --git a/tests/unit/kzt/test_real_guest_performance.py b/tests/unit/kzt/test_real_guest_performance.py new file mode 100644 index 00000000000..ffc8965bfd9 --- /dev/null +++ b/tests/unit/kzt/test_real_guest_performance.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +import argparse +import json +import os +from pathlib import Path +import random +import statistics +import subprocess +import sys + +from test_real_guest_e2e import SUCCESS_PATTERN, existing_directory, existing_file + + +MODES = { + "legacy": {"LATX_KZT": "0"}, + "fail_open": { + "LATX_KZT": "1", + "LATX_KZT_PATCH_SPIKE": "1", + "LATX_KZT_PATCH_SPIKE_WRITE": "1", + "LATX_KZT_PATCH_SPIKE_BUDGET": "0", + }, + "applied": { + "LATX_KZT": "1", + "LATX_KZT_PATCH_SPIKE": "1", + "LATX_KZT_PATCH_SPIKE_WRITE": "1", + "LATX_KZT_PATCH_SPIKE_BUDGET": "1", + }, +} + + +def run_once(args, mode): + environment = os.environ.copy() + for name in list(environment): + if name.startswith("LATX_KZT"): + environment.pop(name) + environment.update(MODES[mode]) + completed = subprocess.run( + [ + str(args.latx), "-L", str(args.guest_root), + "-E", f"LD_LIBRARY_PATH={args.fixture_dir}", + str(args.fixture_dir / "kzt_guest_main"), + ], + env=environment, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + timeout=args.timeout, + check=False, + ) + if completed.returncode != 0: + raise RuntimeError( + f"{mode} exited with {completed.returncode}:\n{completed.stdout}" + ) + matches = SUCCESS_PATTERN.findall(completed.stdout) + if len(matches) != 1: + raise RuntimeError(f"{mode} did not emit one timing record") + values = [int(value, 16) for value in matches[0]] + return {"first_ns": values[4], "second_ns": values[5]} + + +def regression_percent(value, baseline): + return (value / baseline - 1.0) * 100.0 + + +def main(): + parser = argparse.ArgumentParser( + description="Run the KZT real guest first-binding performance gate." + ) + parser.add_argument("--latx", required=True, type=existing_file) + parser.add_argument("--guest-root", required=True, type=existing_directory) + parser.add_argument("--fixture-dir", required=True, type=existing_directory) + parser.add_argument("--iterations", type=int, default=200) + parser.add_argument("--warmup", type=int, default=20) + parser.add_argument("--timeout", type=float, default=30.0) + parser.add_argument("--seed", type=int, default=20260720) + parser.add_argument("--max-first-regression-percent", type=float, + default=3.0) + parser.add_argument("--max-steady-regression-percent", type=float, + default=1.0) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + if args.iterations < 20 or args.warmup < 0: + parser.error("iterations must be >= 20 and warmup must be >= 0") + + samples = {mode: {"first_ns": [], "second_ns": []} for mode in MODES} + rng = random.Random(args.seed) + for round_index in range(args.warmup + args.iterations): + order = list(MODES) + rng.shuffle(order) + for mode in order: + sample = run_once(args, mode) + if round_index >= args.warmup: + for metric, value in sample.items(): + samples[mode][metric].append(value) + + medians = { + mode: { + metric: statistics.median(values) + for metric, values in metrics.items() + } + for mode, metrics in samples.items() + } + regressions = { + mode: { + metric: regression_percent( + medians[mode][metric], medians["legacy"][metric] + ) + for metric in ("first_ns", "second_ns") + } + for mode in ("fail_open", "applied") + } + report = { + "iterations": args.iterations, + "warmup": args.warmup, + "seed": args.seed, + "limits_percent": { + "first_ns": args.max_first_regression_percent, + "second_ns": args.max_steady_regression_percent, + }, + "medians_ns": medians, + "regressions_percent": regressions, + "samples_ns": samples, + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(report, indent=2) + "\n", + encoding="utf-8") + + failures = [] + for mode, metrics in regressions.items(): + if metrics["first_ns"] > args.max_first_regression_percent: + failures.append(f"{mode} first={metrics['first_ns']:.2f}%") + if metrics["second_ns"] > args.max_steady_regression_percent: + failures.append(f"{mode} second={metrics['second_ns']:.2f}%") + print(json.dumps({ + "medians_ns": medians, + "regressions_percent": regressions, + "result": "FAIL" if failures else "PASS", + }, indent=2)) + if failures: + raise RuntimeError("performance gate exceeded: " + ", ".join(failures)) + + +if __name__ == "__main__": + try: + main() + except (RuntimeError, subprocess.TimeoutExpired) as error: + print(f"KZT real guest performance: FAIL: {error}", file=sys.stderr) + sys.exit(1) diff --git a/tests/unit/kzt/test_registry_diagnostics_gate.c b/tests/unit/kzt/test_registry_diagnostics_gate.c new file mode 100644 index 00000000000..005f3f5e5f6 --- /dev/null +++ b/tests/unit/kzt/test_registry_diagnostics_gate.c @@ -0,0 +1,300 @@ +#include +#include +#include + +#include "target/i386/latx/include/box64context.h" +#include "target/i386/latx/include/debug.h" +#include "target/i386/latx/include/kzt_observation_adapter.h" + +int relocation_log; +int kzt_registry_diagnostics; +int option_kzt; +int wine_option_kzt; + +typedef struct gate_trace { + int reader_calls; + int legacy_calls; + int diagnostic_outputs; + int legacy_return; + uintptr_t legacy_link_map_addr; + kzt_observation_adapter_result_t result; + kzt_observation_adapter_result_t diagnostic_result; + int diagnostic_emitted; +} gate_trace_t; + +typedef struct gate_fixture { + struct link_map_x64 link_map; + char guest_name[64]; + kzt_guest_link_map_reader_ops_t reader_ops; + gate_trace_t trace; +} gate_fixture_t; + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static int fake_read_memory(uintptr_t guest_addr, void *dst, size_t size, + void *opaque) +{ + gate_fixture_t *fixture = opaque; + uintptr_t base = (uintptr_t)&fixture->link_map; + size_t available = sizeof(*fixture) - + offsetof(gate_fixture_t, link_map); + + ++fixture->trace.reader_calls; + if (guest_addr < base || size > available || + guest_addr - base > available - size) { + return -1; + } + + memcpy(dst, (const void *)guest_addr, size); + return 0; +} + +static int fake_legacy_flow(uintptr_t link_map_addr, void *opaque) +{ + gate_trace_t *trace = opaque; + + ++trace->legacy_calls; + trace->legacy_link_map_addr = link_map_addr; + return trace->legacy_return; +} + +static void fake_registry_diagnostic_output( + const kzt_observation_adapter_diagnostic_t *diagnostic, + void *opaque) +{ + gate_trace_t *trace = opaque; + + ++trace->diagnostic_outputs; + trace->diagnostic_result = diagnostic->result; + trace->diagnostic_emitted = diagnostic->emitted; +} + +static void init_fixture(gate_fixture_t *fixture, const char *path, + uintptr_t load_bias) +{ + memset(fixture, 0, sizeof(*fixture)); + strcpy(fixture->guest_name, path); + fixture->link_map.l_addr = load_bias; + fixture->link_map.l_name = fixture->guest_name; + fixture->link_map.l_ld = (Elf64_Dyn *)(load_bias + 0x1000); + fixture->link_map.l_ns = 9; + fixture->link_map.l_map_start = load_bias; + fixture->link_map.l_map_end = load_bias + 0x20000; + fixture->reader_ops.read_memory = fake_read_memory; + fixture->reader_ops.opaque = fixture; + fixture->trace.legacy_return = 73; + fixture->trace.result = KZT_OBSERVATION_ADAPTER_DISABLED; +} + +static int kzt_active(void) +{ + return option_kzt || wine_option_kzt; +} + +static int run_gate_like_callback(gate_fixture_t *fixture, + kzt_guest_registry_t *registry) +{ + kzt_guest_registry_diagnostic_config_t diagnostic_config = { + .enabled = kzt_active(), + .throttle_limit = 1, + }; + kzt_observation_adapter_request_t request = { + .enabled = kzt_active(), + .diagnostics_enabled = kzt_registry_diagnostics_enabled(), + .link_map_addr = (uintptr_t)&fixture->link_map, + .registry = registry, + .reader_ops = &fixture->reader_ops, + .legacy_flow = fake_legacy_flow, + .legacy_opaque = &fixture->trace, + .diagnostic = fake_registry_diagnostic_output, + .diagnostic_opaque = &fixture->trace, + }; + + check_int("gate.configure", + kzt_guest_registry_configure_diagnostics(registry, + &diagnostic_config), + 0); + return kzt_observe_guest_object_from_callback(&request, + &fixture->trace.result); +} + +static void reset_options(void) +{ + relocation_log = LOG_NONE; + kzt_registry_diagnostics = 0; + option_kzt = 0; + wine_option_kzt = 0; +} + +static void assert_legacy_exactly_once(const char *name, + const gate_fixture_t *fixture) +{ + check_int(name, fixture->trace.legacy_calls, 1); + check_true("legacy.link-map", + fixture->trace.legacy_link_map_addr == + (uintptr_t)&fixture->link_map); +} + +static void test_gate_defaults_closed(void) +{ + reset_options(); + + check_int("default.diagnostics-enabled", + kzt_registry_diagnostics_enabled(), 0); +} + +static void test_diagnostics_option_alone_does_not_output(void) +{ + gate_fixture_t fixture; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + reset_options(); + kzt_registry_diagnostics = 1; + init_fixture(&fixture, "/guest/libkzt-off.so", 0x100000); + + ret = run_gate_like_callback(&fixture, registry); + + check_int("kzt-off.return", ret, 73); + check_int("kzt-off.result", fixture.trace.result, + KZT_OBSERVATION_ADAPTER_DISABLED); + check_int("kzt-off.diagnostic-outputs", + fixture.trace.diagnostic_outputs, 0); + check_int("kzt-off.reader-calls", fixture.trace.reader_calls, 0); + assert_legacy_exactly_once("kzt-off.legacy-calls", &fixture); + + kzt_guest_registry_destroy(®istry); +} + +static void test_active_kzt_without_diagnostics_does_not_output(void) +{ + gate_fixture_t fixture; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + reset_options(); + option_kzt = 2; + init_fixture(&fixture, "/guest/libdiagnostics-off.so", 0x200000); + + ret = run_gate_like_callback(&fixture, registry); + + check_int("diagnostics-off.return", ret, 73); + check_int("diagnostics-off.result", fixture.trace.result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("diagnostics-off.diagnostic-outputs", + fixture.trace.diagnostic_outputs, 0); + check_true("diagnostics-off.reader-ran", fixture.trace.reader_calls > 0); + assert_legacy_exactly_once("diagnostics-off.legacy-calls", &fixture); + + kzt_guest_registry_destroy(®istry); +} + +static void test_active_kzt_with_diagnostics_outputs(void) +{ + gate_fixture_t fixture; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + reset_options(); + option_kzt = 2; + kzt_registry_diagnostics = 1; + init_fixture(&fixture, "/guest/libdiagnostics-on.so", 0x300000); + + ret = run_gate_like_callback(&fixture, registry); + + check_int("diagnostics-on.return", ret, 73); + check_int("diagnostics-on.result", fixture.trace.result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("diagnostics-on.diagnostic-outputs", + fixture.trace.diagnostic_outputs, 1); + check_int("diagnostics-on.diagnostic-result", + fixture.trace.diagnostic_result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("diagnostics-on.diagnostic-emitted", + fixture.trace.diagnostic_emitted, 1); + check_true("diagnostics-on.reader-ran", fixture.trace.reader_calls > 0); + assert_legacy_exactly_once("diagnostics-on.legacy-calls", &fixture); + + kzt_guest_registry_destroy(®istry); +} + +static void test_debug_log_level_is_equivalent_diagnostics_option(void) +{ + gate_fixture_t fixture; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + reset_options(); + option_kzt = 2; + relocation_log = LOG_DEBUG; + init_fixture(&fixture, "/guest/libdiagnostics-debug.so", 0x400000); + + ret = run_gate_like_callback(&fixture, registry); + + check_int("debug-log.return", ret, 73); + check_int("debug-log.result", fixture.trace.result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("debug-log.diagnostic-outputs", + fixture.trace.diagnostic_outputs, 1); + assert_legacy_exactly_once("debug-log.legacy-calls", &fixture); + + kzt_guest_registry_destroy(®istry); +} + +int main(void) +{ + test_gate_defaults_closed(); + test_diagnostics_option_alone_does_not_output(); + test_active_kzt_without_diagnostics_does_not_output(); + test_active_kzt_with_diagnostics_outputs(); + test_debug_log_level_is_equivalent_diagnostics_option(); + + if (failures) { + fprintf(stderr, "kzt-registry-diagnostics-gate: %d failure(s)\n", + failures); + return 1; + } + + puts("kzt-registry-diagnostics-gate: all gate tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_rela_immediate_candidate.c b/tests/unit/kzt/test_rela_immediate_candidate.c new file mode 100644 index 00000000000..f9620cb5d85 --- /dev/null +++ b/tests/unit/kzt/test_rela_immediate_candidate.c @@ -0,0 +1,618 @@ +#include +#include + +#include "elf.h" +#include "target/i386/latx/include/kzt_patch_spike_writer.h" +#include "target/i386/latx/include/kzt_rela_immediate_candidate.h" + +static int failures; + +typedef struct wi231_fake_slot { + uintptr_t slot_addr; + uintptr_t value; + uintptr_t replacement_value; + int read_calls; + int writer_write_calls; + int legacy_write_calls; + int fail_replacement_write; + int fail_rollback_write; + int force_verify_mismatch; +} wi231_fake_slot_t; + +typedef struct wi231_writer_route { + int planner_called; + int writer_called; + int skip_legacy_write; + kzt_rela_immediate_candidate_result_t plan; + kzt_patch_spike_record_t record; +} wi231_writer_route_t; + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, + expected); + ++failures; +} + +static void check_str(const char *name, const char *got, + const char *expected) +{ + if (got && expected && !strcmp(got, expected)) { + return; + } + + fprintf(stderr, "%s: got '%s' expected '%s'\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static int wi231_fake_read_slot(uintptr_t slot_addr, uintptr_t *value, + void *opaque) +{ + wi231_fake_slot_t *slot = opaque; + + if (!slot || !value || slot_addr != slot->slot_addr) { + return -1; + } + + ++slot->read_calls; + if (slot->force_verify_mismatch && slot->read_calls == 2) { + *value = slot->replacement_value + 0x10; + return 0; + } + + *value = slot->value; + return 0; +} + +static int wi231_fake_write_slot(uintptr_t slot_addr, uintptr_t value, + void *opaque) +{ + wi231_fake_slot_t *slot = opaque; + + if (!slot || slot_addr != slot->slot_addr) { + return -1; + } + + ++slot->writer_write_calls; + if (value == slot->replacement_value && slot->fail_replacement_write) { + return -1; + } + if (value != slot->replacement_value && slot->fail_rollback_write) { + return -1; + } + + slot->value = value; + return 0; +} + +static kzt_patch_spike_slot_ops_t wi231_slot_ops(wi231_fake_slot_t *slot) +{ + return (kzt_patch_spike_slot_ops_t) { + .read_slot = wi231_fake_read_slot, + .write_slot = wi231_fake_write_slot, + .opaque = slot, + }; +} + +static kzt_patch_spike_guard_t wi231_enabled_guard(void) +{ + kzt_patch_spike_config_t config = { + .enabled = 1, + .write_enabled = 1, + .budget = 8, + }; + kzt_patch_spike_guard_t guard; + + kzt_patch_spike_guard_init(&guard, &config); + return guard; +} + +static void wi231_legacy_write(wi231_fake_slot_t *slot, + uintptr_t legacy_target) +{ + ++slot->legacy_write_calls; + slot->value = legacy_target; +} + +static void wi231_apply_step4_request_contract( + const kzt_rela_immediate_candidate_request_t *request, + wi231_fake_slot_t *slot, uintptr_t legacy_target, + wi231_writer_route_t *route) +{ + kzt_patch_spike_guard_t guard = wi231_enabled_guard(); + kzt_patch_spike_slot_ops_t ops = wi231_slot_ops(slot); + kzt_rela_immediate_writer_result_t writer_result; + + memset(route, 0, sizeof(*route)); + check_int("wi231.real.apply", + kzt_rela_immediate_jump_slot_try_write( + request, &guard, &ops, &writer_result), + 0); + route->planner_called = writer_result.planner_called; + route->writer_called = writer_result.writer_called; + route->skip_legacy_write = writer_result.skip_legacy_write; + route->plan = writer_result.plan; + route->record = writer_result.record; + if (!writer_result.skip_legacy_write) { + wi231_legacy_write(slot, legacy_target); + } +} + +static void wi231_trace(const char *tc, const wi231_fake_slot_t *slot, + const wi231_writer_route_t *route) +{ + printf("WI231_TC tc=%s plan_status=%d plan_reason=%d " + "decision=%s writer_called=%d legacy_writes=%d " + "skip_legacy=%d result=%s failure=%s reads=%d " + "writer_writes=%d final=0x%lx\n", + tc, route->plan.status, route->plan.reason, + route->plan.decision_present ? + kzt_patch_decision_kind_name(route->plan.decision.kind) : + "(none)", + route->writer_called, slot->legacy_write_calls, + route->record.skip_legacy_write, + kzt_patch_spike_result_name(route->record.result), + kzt_patch_spike_failure_name(route->record.failure), + slot->read_calls, slot->writer_write_calls, + (unsigned long)slot->value); +} + +static kzt_patch_object_ref_t object_ref(uintptr_t link_map_addr, + unsigned long generation, + const char *soname) +{ + return (kzt_patch_object_ref_t) { + .known = 1, + .link_map_addr = link_map_addr, + .map_start = 0x7000000000 + generation * 0x100000, + .map_end = 0x7000009000 + generation * 0x100000, + .generation = generation, + .soname = soname, + .path = soname, + }; +} + +static kzt_rela_immediate_candidate_request_t base_request(void) +{ + return (kzt_rela_immediate_candidate_request_t) { + .relocation_type = R_X86_64_JUMP_SLOT, + .table_kind = KZT_PATCH_TABLE_PLT_RELA, + .entry_index = 4, + .entry_addr = 0x7000100200, + .source = object_ref(0x1110, 5, "librequester.so"), + .dynamic_addr = 0x7000100000, + .load_bias = 0x7000000000, + .dynamic_view_generation = 18, + .dynamic_view_available = 1, + .slot_addr = 0x7000200088, + .slot_current_value_present = 1, + .slot_current_value = 0x7100001234, + .lazy_binding_deferred = 0, + .expected_guest_target = 0x7100005678, + .native_bridge_target = 0x7200004560, + .legacy_target = 0x7300004560, + .symbol_index = 33, + .symbol_name = "gtk_widget_show", + .version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .version = "GTK_3.0", + .current_owner = object_ref(0x2220, 9, "libgtk-3.so"), + .owner_match = KZT_PATCH_OWNER_MATCH, + .wrapper_match = KZT_PATCH_WRAPPER_VERSION_MATCH, + .wrapper_name = "wrappedgtk3", + .wrapper_version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .wrapper_symbol_version = "GTK_3.0", + }; +} + +static wi231_fake_slot_t wi231_slot_from_request( + const kzt_rela_immediate_candidate_request_t *request) +{ + return (wi231_fake_slot_t) { + .slot_addr = request->slot_addr, + .value = request->slot_current_value, + .replacement_value = request->native_bridge_target, + }; +} + +static void test_immediate_jump_slot_builds_candidate_fields(void) +{ + kzt_rela_immediate_candidate_request_t request = base_request(); + kzt_rela_immediate_candidate_result_t result; + const kzt_patch_candidate_t *candidate; + + check_int("jump_slot.call", + kzt_rela_immediate_jump_slot_plan(&request, &result), 0); + check_int("jump_slot.status", result.status, + KZT_RELA_IMMEDIATE_CANDIDATE_PLANNED); + check_int("jump_slot.reason", result.reason, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_NONE); + check_int("jump_slot.candidate_present", result.candidate_present, 1); + check_int("jump_slot.decision_present", result.decision_present, 1); + + candidate = &result.candidate; + check_int("jump_slot.table", candidate->table_kind, + KZT_PATCH_TABLE_PLT_RELA); + check_ulong("jump_slot.entry_index", candidate->entry_index, 4); + check_ulong("jump_slot.entry_addr", candidate->entry_addr, + request.entry_addr); + check_int("jump_slot.reloc", candidate->reloc_type, + KZT_PATCH_RELOCATION_JUMP_SLOT); + check_ulong("jump_slot.slot", candidate->slot_addr, request.slot_addr); + check_int("jump_slot.current_present", + candidate->slot_current_value_present, 1); + check_ulong("jump_slot.current", candidate->slot_current_value, + request.slot_current_value); + check_int("jump_slot.lazy", candidate->lazy_binding_deferred, 0); + check_ulong("jump_slot.symbol_index", candidate->symbol_index, 33); + check_str("jump_slot.symbol", candidate->symbol_name, + "gtk_widget_show"); + check_str("jump_slot.version", candidate->version, "GTK_3.0"); + check_int("jump_slot.owner", candidate->owner_match, + KZT_PATCH_OWNER_MATCH); + check_int("jump_slot.wrapper", candidate->wrapper_match, + KZT_PATCH_WRAPPER_VERSION_MATCH); + check_ulong("jump_slot.bridge", candidate->bridge_target, + request.native_bridge_target); + check_int("jump_slot.decision", result.decision.kind, + KZT_PATCH_DECISION_APPROVED); +} + +static void test_target_fields_keep_separate_meanings(void) +{ + kzt_rela_immediate_candidate_request_t request = base_request(); + kzt_rela_immediate_candidate_result_t result; + + request.expected_guest_target = 0x7100001111; + request.native_bridge_target = 0x7200002222; + request.legacy_target = 0x7300003333; + + check_int("target_roles.call", + kzt_rela_immediate_jump_slot_plan(&request, &result), 0); + check_int("target_roles.decision", result.decision.kind, + KZT_PATCH_DECISION_APPROVED); + check_ulong("target_roles.bridge", result.candidate.bridge_target, + request.native_bridge_target); + check_ulong("target_roles.decision_bridge", + result.decision.bridge_target, + request.native_bridge_target); + + request.native_bridge_target = 0; + check_int("target_roles.zero_bridge.call", + kzt_rela_immediate_jump_slot_plan(&request, &result), 0); + check_int("target_roles.zero_bridge.decision", result.decision.kind, + KZT_PATCH_DECISION_UNSUPPORTED); + check_int("target_roles.zero_bridge.reason", result.decision.reason, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_BRIDGE_TARGET); + check_ulong("target_roles.zero_bridge.candidate", + result.candidate.bridge_target, 0); +} + +static void test_explicit_unversioned_and_unknown_evidence(void) +{ + kzt_rela_immediate_candidate_request_t request = base_request(); + kzt_rela_immediate_candidate_result_t result; + wi231_fake_slot_t slot; + wi231_writer_route_t route; + + request.version_evidence = KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + request.version = NULL; + request.wrapper_match = KZT_PATCH_WRAPPER_UNVERSIONED_MATCH; + request.wrapper_version_evidence = + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + request.wrapper_symbol_version = NULL; + check_int("unversioned-plan.call", + kzt_rela_immediate_jump_slot_plan(&request, &result), 0); + check_int("unversioned-plan.status", result.status, + KZT_RELA_IMMEDIATE_CANDIDATE_PLANNED); + check_int("unversioned-plan.approved", result.decision.kind, + KZT_PATCH_DECISION_APPROVED); + slot = wi231_slot_from_request(&request); + wi231_apply_step4_request_contract( + &request, &slot, request.legacy_target, &route); + check_int("unversioned-write.applied", route.record.result, + KZT_PATCH_SPIKE_RESULT_APPLIED); + check_int("unversioned-write.skip-legacy", slot.legacy_write_calls, 0); + check_ulong("unversioned-write.final", slot.value, + request.native_bridge_target); + + request = base_request(); + request.version_evidence = KZT_SYMBOL_VERSION_UNKNOWN; + check_int("unknown-plan.call", + kzt_rela_immediate_jump_slot_plan(&request, &result), 0); + check_int("unknown-plan.status", result.status, + KZT_RELA_IMMEDIATE_CANDIDATE_FAIL_OPEN); + check_int("unknown-plan.reason", result.reason, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_SYMBOL_VERSION); +} + +static void test_non_target_relocation_does_not_build_candidate(void) +{ + kzt_rela_immediate_candidate_request_t request = base_request(); + kzt_rela_immediate_candidate_result_t result; + + request.relocation_type = R_X86_64_RELATIVE; + check_int("relative.call", + kzt_rela_immediate_jump_slot_plan(&request, &result), 0); + check_int("relative.status", result.status, + KZT_RELA_IMMEDIATE_CANDIDATE_SKIPPED); + check_int("relative.reason", result.reason, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_NON_TARGET_RELOCATION); + check_int("relative.candidate_present", result.candidate_present, 0); + check_int("relative.decision_present", result.decision_present, 0); +} + +static void test_glob_dat_keeps_legacy_path_without_candidate(void) +{ + kzt_rela_immediate_candidate_request_t request = base_request(); + kzt_rela_immediate_candidate_result_t result; + + request.relocation_type = R_X86_64_GLOB_DAT; + request.table_kind = KZT_PATCH_TABLE_RELA; + check_int("glob_dat.call", + kzt_rela_immediate_jump_slot_plan(&request, &result), 0); + check_int("glob_dat.status", result.status, + KZT_RELA_IMMEDIATE_CANDIDATE_SKIPPED); + check_int("glob_dat.reason", result.reason, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_NON_TARGET_RELOCATION); + check_int("glob_dat.candidate_present", result.candidate_present, 0); +} + +static void test_deferred_lazy_binding_does_not_build_writable_candidate(void) +{ + kzt_rela_immediate_candidate_request_t request = base_request(); + kzt_rela_immediate_candidate_result_t result; + + request.lazy_binding_deferred = 1; + check_int("lazy.call", + kzt_rela_immediate_jump_slot_plan(&request, &result), 0); + check_int("lazy.status", result.status, + KZT_RELA_IMMEDIATE_CANDIDATE_SKIPPED); + check_int("lazy.reason", result.reason, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_DEFERRED_LAZY_BINDING); + check_int("lazy.candidate_present", result.candidate_present, 0); +} + +static void test_missing_symbol_information_fails_open(void) +{ + kzt_rela_immediate_candidate_request_t request = base_request(); + kzt_rela_immediate_candidate_result_t result; + + request.symbol_name = NULL; + check_int("missing_symbol.call", + kzt_rela_immediate_jump_slot_plan(&request, &result), 0); + check_int("missing_symbol.status", result.status, + KZT_RELA_IMMEDIATE_CANDIDATE_FAIL_OPEN); + check_int("missing_symbol.reason", result.reason, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_SYMBOL_NAME); + check_int("missing_symbol.candidate_present", result.candidate_present, 0); + check_int("missing_symbol.decision_present", result.decision_present, 0); +} + +static void test_missing_owner_still_plans_but_keeps_legacy_decision(void) +{ + kzt_rela_immediate_candidate_request_t request = base_request(); + kzt_rela_immediate_candidate_result_t result; + + memset(&request.current_owner, 0, sizeof(request.current_owner)); + request.owner_match = KZT_PATCH_OWNER_UNKNOWN; + request.wrapper_match = KZT_PATCH_WRAPPER_NO_MANIFEST; + request.native_bridge_target = 0; + + check_int("owner_unknown.call", + kzt_rela_immediate_jump_slot_plan(&request, &result), 0); + check_int("owner_unknown.status", result.status, + KZT_RELA_IMMEDIATE_CANDIDATE_PLANNED); + check_int("owner_unknown.candidate_present", result.candidate_present, 1); + check_int("owner_unknown.decision_present", result.decision_present, 1); + check_int("owner_unknown.decision", result.decision.kind, + KZT_PATCH_DECISION_UNSUPPORTED); + check_int("owner_unknown.reason", result.decision.reason, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_OWNER); +} + +static void test_wi231_approved_writer_success_skips_legacy_duplicate_write( + void) +{ + kzt_rela_immediate_candidate_request_t request = base_request(); + uintptr_t legacy_target = 0x7300002222; + wi231_fake_slot_t slot = wi231_slot_from_request(&request); + wi231_writer_route_t route; + + wi231_apply_step4_request_contract(&request, &slot, legacy_target, + &route); + check_int("wi231.success.planner", route.planner_called, 1); + check_int("wi231.success.decision", route.plan.decision.kind, + KZT_PATCH_DECISION_APPROVED); + check_int("wi231.success.writer", route.writer_called, 1); + check_int("wi231.success.skip_legacy", + route.record.skip_legacy_write, 1); + check_int("wi231.success.legacy", slot.legacy_write_calls, 0); + check_ulong("wi231.success.final", slot.value, request.native_bridge_target); + wi231_trace("approved-writer-success-skips-legacy", &slot, &route); +} + +static void test_wi231_planner_non_approved_results_keep_legacy(void) +{ + kzt_rela_immediate_candidate_request_t request = base_request(); + uintptr_t legacy_target = 0x7300003333; + wi231_fake_slot_t slot; + wi231_writer_route_t route; + + memset(&request.current_owner, 0, sizeof(request.current_owner)); + request.owner_match = KZT_PATCH_OWNER_UNKNOWN; + request.wrapper_match = KZT_PATCH_WRAPPER_NO_MANIFEST; + request.native_bridge_target = 0; + slot = wi231_slot_from_request(&request); + slot.replacement_value = 0x7200004560; + wi231_apply_step4_request_contract(&request, &slot, legacy_target, + &route); + check_int("wi231.unsupported.decision", route.plan.decision.kind, + KZT_PATCH_DECISION_UNSUPPORTED); + check_int("wi231.unsupported.writer", route.writer_called, 0); + check_int("wi231.unsupported.legacy", slot.legacy_write_calls, 1); + check_ulong("wi231.unsupported.final", slot.value, legacy_target); + wi231_trace("planner-unsupported-keeps-legacy", &slot, &route); + + request = base_request(); + request.owner_match = KZT_PATCH_OWNER_MISMATCH; + slot = wi231_slot_from_request(&request); + wi231_apply_step4_request_contract(&request, &slot, legacy_target, + &route); + check_int("wi231.rejected.decision", route.plan.decision.kind, + KZT_PATCH_DECISION_REJECTED); + check_int("wi231.rejected.writer", route.writer_called, 0); + check_int("wi231.rejected.legacy", slot.legacy_write_calls, 1); + check_ulong("wi231.rejected.final", slot.value, legacy_target); + wi231_trace("planner-rejected-keeps-legacy", &slot, &route); + + request = base_request(); + request.lazy_binding_deferred = 1; + slot = wi231_slot_from_request(&request); + wi231_apply_step4_request_contract(&request, &slot, legacy_target, + &route); + check_int("wi231.deferred.writer", route.writer_called, 0); + check_int("wi231.deferred.legacy", slot.legacy_write_calls, 1); + check_ulong("wi231.deferred.final", slot.value, legacy_target); + wi231_trace("lazy-deferred-keeps-legacy", &slot, &route); + + request = base_request(); + request.symbol_name = NULL; + slot = wi231_slot_from_request(&request); + wi231_apply_step4_request_contract(&request, &slot, legacy_target, + &route); + check_int("wi231.error.writer", route.writer_called, 0); + check_int("wi231.error.legacy", slot.legacy_write_calls, 1); + check_ulong("wi231.error.final", slot.value, legacy_target); + wi231_trace("planner-fail-open-keeps-legacy", &slot, &route); +} + +static void test_wi231_writer_failures_fail_open_to_legacy(void) +{ + kzt_rela_immediate_candidate_request_t request = base_request(); + uintptr_t legacy_target = 0x7300004444; + wi231_fake_slot_t slot; + wi231_writer_route_t route; + + slot = wi231_slot_from_request(&request); + slot.value = request.slot_current_value + 4; + wi231_apply_step4_request_contract(&request, &slot, legacy_target, + &route); + check_int("wi231.mismatch.writer", route.writer_called, 1); + check_int("wi231.mismatch.failure", route.record.failure, + KZT_PATCH_SPIKE_FAILURE_EXPECTED_MISMATCH); + check_int("wi231.mismatch.legacy", slot.legacy_write_calls, 1); + check_ulong("wi231.mismatch.final", slot.value, legacy_target); + wi231_trace("writer-expected-mismatch-fail-open", &slot, &route); + + slot = wi231_slot_from_request(&request); + slot.fail_replacement_write = 1; + wi231_apply_step4_request_contract(&request, &slot, legacy_target, + &route); + check_int("wi231.write_fail.failure", route.record.failure, + KZT_PATCH_SPIKE_FAILURE_WRITE_FAILED); + check_int("wi231.write_fail.legacy", slot.legacy_write_calls, 1); + check_ulong("wi231.write_fail.final", slot.value, legacy_target); + wi231_trace("writer-write-fail-fail-open", &slot, &route); + + slot = wi231_slot_from_request(&request); + slot.force_verify_mismatch = 1; + wi231_apply_step4_request_contract(&request, &slot, legacy_target, + &route); + check_int("wi231.verify_fail.failure", route.record.failure, + KZT_PATCH_SPIKE_FAILURE_VERIFY_FAILED); + check_int("wi231.verify_fail.rollback", + route.record.rollback_called, 1); + check_int("wi231.verify_fail.legacy", slot.legacy_write_calls, 1); + check_ulong("wi231.verify_fail.final", slot.value, legacy_target); + wi231_trace("writer-verify-fail-fail-open", &slot, &route); + + slot = wi231_slot_from_request(&request); + slot.force_verify_mismatch = 1; + slot.fail_rollback_write = 1; + wi231_apply_step4_request_contract(&request, &slot, legacy_target, + &route); + check_int("wi231.rollback_fail.failure", route.record.failure, + KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED); + check_int("wi231.rollback_fail.rollback", + route.record.rollback_called, 1); + check_int("wi231.rollback_fail.legacy", slot.legacy_write_calls, 1); + check_ulong("wi231.rollback_fail.final", slot.value, legacy_target); + wi231_trace("writer-rollback-fail-fail-open", &slot, &route); +} + +static void test_wi231_non_target_glob_dat_and_lazy_skip_writer(void) +{ + kzt_rela_immediate_candidate_request_t request = base_request(); + uintptr_t legacy_target = 0x7300005555; + wi231_fake_slot_t slot; + wi231_writer_route_t route; + + request.relocation_type = R_X86_64_RELATIVE; + slot = wi231_slot_from_request(&request); + wi231_apply_step4_request_contract(&request, &slot, legacy_target, + &route); + check_int("wi231.relative.writer", route.writer_called, 0); + check_int("wi231.relative.legacy", slot.legacy_write_calls, 1); + check_ulong("wi231.relative.final", slot.value, legacy_target); + wi231_trace("non-target-relocation-skips-writer", &slot, &route); + + request = base_request(); + request.relocation_type = R_X86_64_GLOB_DAT; + request.table_kind = KZT_PATCH_TABLE_RELA; + slot = wi231_slot_from_request(&request); + wi231_apply_step4_request_contract(&request, &slot, legacy_target, + &route); + check_int("wi231.glob_dat.writer", route.writer_called, 0); + check_int("wi231.glob_dat.legacy", slot.legacy_write_calls, 1); + check_ulong("wi231.glob_dat.final", slot.value, legacy_target); + wi231_trace("glob-dat-skips-writer", &slot, &route); + + request = base_request(); + request.lazy_binding_deferred = 1; + slot = wi231_slot_from_request(&request); + wi231_apply_step4_request_contract(&request, &slot, legacy_target, + &route); + check_int("wi231.lazy.writer", route.writer_called, 0); + check_int("wi231.lazy.legacy", slot.legacy_write_calls, 1); + check_ulong("wi231.lazy.final", slot.value, legacy_target); + wi231_trace("lazy-deferred-skips-writer", &slot, &route); +} + +int main(void) +{ + test_immediate_jump_slot_builds_candidate_fields(); + test_non_target_relocation_does_not_build_candidate(); + test_glob_dat_keeps_legacy_path_without_candidate(); + test_deferred_lazy_binding_does_not_build_writable_candidate(); + test_missing_symbol_information_fails_open(); + test_target_fields_keep_separate_meanings(); + test_explicit_unversioned_and_unknown_evidence(); + test_missing_owner_still_plans_but_keeps_legacy_decision(); + test_wi231_approved_writer_success_skips_legacy_duplicate_write(); + test_wi231_planner_non_approved_results_keep_legacy(); + test_wi231_writer_failures_fail_open_to_legacy(); + test_wi231_non_target_glob_dat_and_lazy_skip_writer(); + + if (failures) { + fprintf(stderr, "%d test failure(s)\n", failures); + return 1; + } + + return 0; +} diff --git a/tests/unit/kzt/test_rela_request_enricher.c b/tests/unit/kzt/test_rela_request_enricher.c new file mode 100644 index 00000000000..9c778e148ac --- /dev/null +++ b/tests/unit/kzt/test_rela_request_enricher.c @@ -0,0 +1,765 @@ +#include +#include + +#include "elf.h" +#include "target/i386/latx/include/kzt_rela_request_enricher.h" +#include "target/i386/latx/include/kzt_rela_stub_detector.h" + +static int failures; + +typedef struct fake_bridge_state { + uintptr_t next_bridge_target; + int check_calls; + int add_calls; +} fake_bridge_state_t; + +typedef struct fake_slot_entry { + uintptr_t *slot; + int read_calls; + int writer_write_calls; + int legacy_write_calls; +} fake_slot_entry_t; + +typedef struct fake_slot_bank { + uintptr_t canary_before; + uintptr_t values[2]; + uintptr_t canary_after; + fake_slot_entry_t entries[2]; + int invalid_accesses; +} fake_slot_bank_t; + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, + expected); + ++failures; +} + +static uintptr_t fake_check_bridge(uintptr_t native_symbol, void *opaque) +{ + fake_bridge_state_t *state = opaque; + + (void)native_symbol; + ++state->check_calls; + return 0; +} + +static uintptr_t fake_add_bridge( + const kzt_wrapper_probe_bridge_request_t *request, void *opaque) +{ + fake_bridge_state_t *state = opaque; + + if (!request || !request->native_symbol) { + return 0; + } + + ++state->add_calls; + return state->next_bridge_target; +} + +static kzt_wrapper_probe_bridge_ops_t bridge_ops(fake_bridge_state_t *state) +{ + return (kzt_wrapper_probe_bridge_ops_t) { + .check_bridge = fake_check_bridge, + .add_bridge = fake_add_bridge, + .opaque = state, + }; +} + +static fake_slot_entry_t *fake_slot_find(fake_slot_bank_t *bank, + uintptr_t slot_addr) +{ + size_t i; + + if (!bank) { + return NULL; + } + for (i = 0; i < sizeof(bank->entries) / sizeof(bank->entries[0]); ++i) { + if ((uintptr_t)bank->entries[i].slot == slot_addr) { + return &bank->entries[i]; + } + } + + ++bank->invalid_accesses; + return NULL; +} + +static int fake_slot_read(uintptr_t slot_addr, uintptr_t *value, void *opaque) +{ + fake_slot_entry_t *slot = fake_slot_find(opaque, slot_addr); + + if (!slot || !value) { + return -1; + } + + ++slot->read_calls; + *value = *slot->slot; + return 0; +} + +static int fake_slot_write(uintptr_t slot_addr, uintptr_t value, void *opaque) +{ + fake_slot_entry_t *slot = fake_slot_find(opaque, slot_addr); + + if (!slot) { + return -1; + } + + ++slot->writer_write_calls; + *slot->slot = value; + return 0; +} + +static kzt_patch_spike_slot_ops_t fake_slot_ops(fake_slot_bank_t *bank) +{ + return (kzt_patch_spike_slot_ops_t) { + .read_slot = fake_slot_read, + .write_slot = fake_slot_write, + .opaque = bank, + }; +} + +static kzt_patch_spike_guard_t enabled_guard(void) +{ + kzt_patch_spike_config_t config = { + .enabled = 1, + .write_enabled = 1, + .budget = 4, + }; + kzt_patch_spike_guard_t guard; + + kzt_patch_spike_guard_init(&guard, &config); + return guard; +} + +static void fake_legacy_write(fake_slot_bank_t *bank, uintptr_t slot_addr, + uintptr_t legacy_target) +{ + fake_slot_entry_t *slot = fake_slot_find(bank, slot_addr); + + if (!slot) { + return; + } + + ++slot->legacy_write_calls; + *(uintptr_t *)slot_addr = legacy_target; +} + +static void fake_slot_bank_init(fake_slot_bank_t *bank, + uintptr_t canary_before, + uintptr_t selected_value, + uintptr_t other_value, + uintptr_t canary_after) +{ + memset(bank, 0, sizeof(*bank)); + bank->canary_before = canary_before; + bank->values[0] = selected_value; + bank->values[1] = other_value; + bank->canary_after = canary_after; + bank->entries[0].slot = &bank->values[0]; + bank->entries[1].slot = &bank->values[1]; +} + +static kzt_guest_object_observation_t observation( + uintptr_t link_map_addr, uintptr_t map_start, uintptr_t map_end, + const char *soname) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map_addr, + .load_bias = { map_start, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { map_start + 0x2000, KZT_GUEST_FIELD_OK }, + .map_start = { map_start, KZT_GUEST_FIELD_OK }, + .map_end = { map_end, KZT_GUEST_FIELD_OK }, + .namespace_id = { 0, KZT_GUEST_FIELD_OK }, + .path = { soname, KZT_GUEST_FIELD_OK }, + .soname = { soname, KZT_GUEST_FIELD_OK }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static kzt_guest_dynamic_view_t dynamic_view(uintptr_t dynamic_addr, + uintptr_t load_bias) +{ + return (kzt_guest_dynamic_view_t) { + .dynamic_addr = dynamic_addr, + .load_bias = load_bias, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .entry_count = 1, + .has_null = 1, + }; +} + +static kzt_guest_registry_t *registry_with_source_and_owner( + int commit_dynamic, int include_owner) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t source = observation( + 0x1000, 0x70000000, 0x70010000, "librequester.so"); + kzt_guest_object_observation_t owner = observation( + 0x2000, 0x71000000, 0x71010000, "libgtk-3.so"); + kzt_guest_dynamic_view_t view = dynamic_view(0x70002000, 0x70000000); + + check_int("registry.observe.source", + kzt_guest_registry_observe(registry, &source), + KZT_GUEST_REGISTRY_ADDED); + if (include_owner) { + check_int("registry.observe.owner", + kzt_guest_registry_observe(registry, &owner), + KZT_GUEST_REGISTRY_ADDED); + } + if (commit_dynamic) { + check_int("registry.dynamic", + kzt_guest_registry_commit_dynamic_view( + registry, source.link_map_addr, 1, &view), + KZT_GUEST_REGISTRY_UPDATED); + } + + return registry; +} + +static const kzt_wrapper_probe_entry_t wrapper_entries[] = { + { + .symbol_name = "gtk_widget_show", + .symbol_version = "GTK_3.0", + .wrapper_name = "wrappedgtk3", + .wrapper_symbol_version = "GTK_3.0", + .native_symbol = 0x60000000, + }, +}; + +static kzt_wrapper_probe_manifest_t wrapper_manifest(void) +{ + return (kzt_wrapper_probe_manifest_t) { + .available = 1, + .manifest_name = "wrappedgtk3", + .entries = wrapper_entries, + .entry_count = sizeof(wrapper_entries) / sizeof(wrapper_entries[0]), + }; +} + +static kzt_rela_immediate_candidate_request_t base_request(void) +{ + return (kzt_rela_immediate_candidate_request_t) { + .relocation_type = R_X86_64_JUMP_SLOT, + .table_kind = KZT_PATCH_TABLE_PLT_RELA, + .entry_index = 7, + .entry_addr = 0x70003000, + .source = { + .known = 1, + .map_start = 0x70000000, + .map_end = 0x70010000, + .soname = "fallback-requester", + .path = "/fallback/requester", + }, + .dynamic_addr = 0x70002000, + .load_bias = 0x70000000, + .slot_addr = 0x70004000, + .slot_current_value_present = 1, + .slot_current_value = 0x71000010, + .expected_guest_target = 0x71000020, + .native_bridge_target = 0x7fffffff, + .legacy_target = 0x71000020, + .symbol_index = 9, + .symbol_name = "gtk_widget_show", + .version = "GTK_3.0", + }; +} + +static void enrich_and_plan( + kzt_rela_immediate_candidate_request_t *request, + const kzt_rela_request_enricher_input_t *input, + kzt_rela_request_enricher_result_t *enrich_result, + kzt_rela_immediate_candidate_result_t *plan_result) +{ + check_int("enrich.call", + kzt_rela_immediate_request_enrich( + request, input, enrich_result), 0); + check_int("plan.call", + kzt_rela_immediate_jump_slot_plan(request, plan_result), 0); +} + +static void test_all_evidence_allows_approved_plan(void) +{ + kzt_guest_registry_t *registry = registry_with_source_and_owner(1, 1); + kzt_wrapper_probe_manifest_t manifest = wrapper_manifest(); + fake_bridge_state_t bridge = { .next_bridge_target = 0x72000000 }; + kzt_wrapper_probe_bridge_ops_t ops = bridge_ops(&bridge); + kzt_rela_request_enricher_input_t input = { + .registry = registry, + .wrapper_manifest = &manifest, + .bridge_ops = &ops, + }; + kzt_rela_request_enricher_result_t enrich_result; + kzt_rela_immediate_candidate_result_t plan_result; + kzt_rela_immediate_candidate_request_t request = base_request(); + + enrich_and_plan(&request, &input, &enrich_result, &plan_result); + check_int("approved.dynamic", request.dynamic_view_available, 1); + check_ulong("approved.source.link_map", request.source.link_map_addr, + 0x1000); + check_ulong("approved.source.generation", request.source.generation, 1); + check_int("approved.owner", request.owner_match, + KZT_PATCH_OWNER_MATCH); + check_int("approved.wrapper", request.wrapper_match, + KZT_PATCH_WRAPPER_VERSION_MATCH); + check_ulong("approved.bridge", request.native_bridge_target, + 0x72000000); + check_int("approved.decision", plan_result.decision.kind, + KZT_PATCH_DECISION_APPROVED); + check_int("approved.reason", plan_result.decision.reason, + KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE); + check_int("approved.add_bridge", bridge.add_calls, 1); + + kzt_guest_registry_destroy(®istry); +} + +static void test_unresolved_stub_detector_coordinates_and_bounds(void) +{ + const uintptr_t plt_start = 0x2000; + const uintptr_t plt_end = 0x2040; + const uintptr_t gotplt_start = 0x3000; + const uintptr_t gotplt_end = 0x3040; + const intptr_t load_bias = 0x71000000; + const intptr_t negative_bias = -0x1000; + + check_int("detector.delta0.plt.start", + kzt_rela_slot_current_is_unresolved_stub( + plt_start, KZT_RELA_STUB_COORDINATE_LINK_TIME_RAW, + 0, plt_start, plt_end, + gotplt_start, gotplt_end), 1); + check_int("detector.delta0.gotplt.last", + kzt_rela_slot_current_is_unresolved_stub( + gotplt_end - 1, + KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, + 0, plt_start, plt_end, + gotplt_start, gotplt_end), 1); + check_int("detector.delta0.plt.end", + kzt_rela_slot_current_is_unresolved_stub( + plt_end, KZT_RELA_STUB_COORDINATE_LINK_TIME_RAW, + 0, plt_start, plt_end, + gotplt_start, gotplt_end), 0); + check_int("detector.delta0.gotplt.before", + kzt_rela_slot_current_is_unresolved_stub( + gotplt_start - 1, + KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, + 0, plt_start, plt_end, + gotplt_start, gotplt_end), 0); + check_int("detector.positive_delta.plt", + kzt_rela_slot_current_is_unresolved_stub( + plt_start + load_bias + 0x10, + KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, load_bias, + plt_start, plt_end, gotplt_start, gotplt_end), 1); + check_int("detector.positive_delta.gotplt", + kzt_rela_slot_current_is_unresolved_stub( + gotplt_start + load_bias + 0x18, + KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, load_bias, + plt_start, plt_end, gotplt_start, gotplt_end), 1); + check_int("detector.delta.link_time_before_lazy_adjust", + kzt_rela_slot_current_is_unresolved_stub( + plt_start + 0x10, + KZT_RELA_STUB_COORDINATE_LINK_TIME_RAW, load_bias, + plt_start, plt_end, gotplt_start, gotplt_end), 1); + check_int("detector.delta.resolved_same_dso", + kzt_rela_slot_current_is_unresolved_stub( + load_bias + 0x5000, + KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, load_bias, + plt_start, plt_end, gotplt_start, gotplt_end), 0); + check_int("detector.delta.runtime_end", + kzt_rela_slot_current_is_unresolved_stub( + load_bias + gotplt_end, + KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, load_bias, + plt_start, plt_end, gotplt_start, gotplt_end), 0); + check_int("detector.negative_delta.plt", + kzt_rela_slot_current_is_unresolved_stub( + 0x3010, KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, + negative_bias, 0x4000, 0x4040, 0x5000, 0x5040), 1); + check_int("detector.negative_delta.gotplt", + kzt_rela_slot_current_is_unresolved_stub( + 0x4018, KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, + negative_bias, 0x4000, 0x4040, 0x5000, 0x5040), 1); + check_int("detector.negative_delta.raw_collision_is_text", + kzt_rela_slot_current_is_unresolved_stub( + 0x2010, KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, + negative_bias, 0x2000, 0x2040, 0x5000, 0x5040), 0); + check_int("detector.unknown.raw_looking_value", + kzt_rela_slot_current_is_unresolved_stub( + plt_start + 0x10, KZT_RELA_STUB_COORDINATE_UNKNOWN, + load_bias, plt_start, plt_end, + gotplt_start, gotplt_end), 0); + check_int("detector.overflow.no_wrap", + kzt_rela_slot_current_is_unresolved_stub( + 0x10, KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, + 0x20, UINTPTR_MAX - 0x10, UINTPTR_MAX - 0x8, + 0, 0), 0); + check_int("detector.underflow.no_wrap", + kzt_rela_slot_current_is_unresolved_stub( + 0x10, KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, + -0x20, 0x10, 0x18, 0, 0), 0); +} + +static void test_distinct_targets_write_only_selected_slot(void) +{ + const uintptr_t current_target = 0x71000010; + const uintptr_t expected_guest_target = 0x71000020; + const uintptr_t native_bridge_target = 0x72000030; + const uintptr_t legacy_target = 0x73000040; + const uintptr_t other_value = 0x75000060; + const uintptr_t canary_before = 0x1111222233334444; + const uintptr_t canary_after = 0xaaaabbbbccccdddd; + kzt_guest_registry_t *registry = registry_with_source_and_owner(1, 1); + kzt_wrapper_probe_manifest_t manifest = wrapper_manifest(); + fake_bridge_state_t bridge = { + .next_bridge_target = native_bridge_target, + }; + kzt_wrapper_probe_bridge_ops_t bridge_provider = bridge_ops(&bridge); + kzt_rela_request_enricher_input_t input = { + .registry = registry, + .wrapper_manifest = &manifest, + .bridge_ops = &bridge_provider, + }; + fake_slot_bank_t bank; + kzt_patch_spike_slot_ops_t slot_ops = fake_slot_ops(&bank); + kzt_patch_spike_guard_t guard = enabled_guard(); + kzt_rela_request_enricher_result_t enrich_result; + kzt_rela_immediate_writer_result_t writer_result; + kzt_rela_immediate_candidate_request_t request = base_request(); + uintptr_t selected_slot; + + fake_slot_bank_init(&bank, canary_before, current_target, other_value, + canary_after); + selected_slot = (uintptr_t)&bank.values[0]; + + request.slot_addr = selected_slot; + request.slot_current_value = current_target; + request.expected_guest_target = expected_guest_target; + request.native_bridge_target = 0; + request.legacy_target = legacy_target; + + check_int("targets.enrich", + kzt_rela_immediate_request_enrich( + &request, &input, &enrich_result), 0); + check_ulong("targets.expected", request.expected_guest_target, + expected_guest_target); + check_ulong("targets.bridge", request.native_bridge_target, + native_bridge_target); + check_ulong("targets.legacy", request.legacy_target, legacy_target); + check_ulong("targets.slot", request.slot_addr, selected_slot); + check_int("targets.owner", request.owner_match, KZT_PATCH_OWNER_MATCH); + + check_int("targets.write", + kzt_rela_immediate_jump_slot_try_write( + &request, &guard, &slot_ops, &writer_result), 0); + check_int("targets.decision", writer_result.plan.decision.kind, + KZT_PATCH_DECISION_APPROVED); + check_int("targets.writer", writer_result.writer_called, 1); + check_int("targets.skip_legacy", writer_result.skip_legacy_write, 1); + check_ulong("targets.record.slot", writer_result.record.slot_addr, + selected_slot); + check_ulong("targets.record.expected", writer_result.record.expected_value, + current_target); + check_ulong("targets.record.replacement", + writer_result.record.replacement_value, + native_bridge_target); + check_ulong("targets.selected.value", bank.values[0], + native_bridge_target); + check_int("targets.selected.writes", + bank.entries[0].writer_write_calls, 1); + check_ulong("targets.other.value", bank.values[1], other_value); + check_int("targets.other.writes", bank.entries[1].writer_write_calls, 0); + check_ulong("targets.canary.before", bank.canary_before, canary_before); + check_ulong("targets.canary.after", bank.canary_after, canary_after); + check_int("targets.invalid_accesses", bank.invalid_accesses, 0); + + kzt_guest_registry_destroy(®istry); +} + +static void test_unresolved_stub_skips_owner_and_falls_back_to_legacy(void) +{ + const uintptr_t plt_start = 0x1000; + const uintptr_t plt_end = 0x1040; + const uintptr_t gotplt_start = 0x2000; + const uintptr_t gotplt_end = 0x2040; + const intptr_t load_bias = 0x70fff000; + const uintptr_t stub_target = 0x71000010; + const uintptr_t expected_guest_target = 0x71000020; + const uintptr_t native_bridge_target = 0x72000030; + const uintptr_t legacy_target = 0x73000040; + const uintptr_t other_value = 0x75000060; + const uintptr_t canary_before = 0x1111222233334444; + const uintptr_t canary_after = 0xaaaabbbbccccdddd; + kzt_guest_registry_t *registry = registry_with_source_and_owner(1, 1); + kzt_wrapper_probe_manifest_t manifest = wrapper_manifest(); + fake_bridge_state_t bridge = { + .next_bridge_target = native_bridge_target, + }; + kzt_wrapper_probe_bridge_ops_t bridge_provider = bridge_ops(&bridge); + kzt_rela_request_enricher_input_t input = { + .registry = registry, + .wrapper_manifest = &manifest, + .bridge_ops = &bridge_provider, + }; + fake_slot_bank_t bank; + kzt_patch_spike_slot_ops_t slot_ops = fake_slot_ops(&bank); + kzt_patch_spike_guard_t guard = enabled_guard(); + kzt_owner_resolution_t unsafe_resolution; + kzt_rela_request_enricher_result_t enrich_result; + kzt_rela_immediate_writer_result_t writer_result; + kzt_rela_immediate_candidate_request_t request = base_request(); + uintptr_t selected_slot; + + fake_slot_bank_init(&bank, canary_before, stub_target, other_value, + canary_after); + selected_slot = (uintptr_t)&bank.values[0]; + input.slot_current_value_is_unresolved_stub = + kzt_rela_slot_current_is_unresolved_stub( + stub_target, KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, + load_bias, plt_start, plt_end, + gotplt_start, gotplt_end); + check_int("stub.detector", input.slot_current_value_is_unresolved_stub, + 1); + + request.slot_addr = selected_slot; + request.slot_current_value = stub_target; + request.expected_guest_target = expected_guest_target; + request.native_bridge_target = 0; + request.legacy_target = legacy_target; + + kzt_owner_resolver_init(&unsafe_resolution); + check_int("stub.control.resolve", + kzt_owner_resolver_resolve_current( + registry, stub_target, expected_guest_target, + &unsafe_resolution), 0); + check_int("stub.control.would_match", unsafe_resolution.owner_match, + KZT_PATCH_OWNER_MATCH); + + check_int("stub.enrich", + kzt_rela_immediate_request_enrich( + &request, &input, &enrich_result), 0); + check_int("stub.owner", request.owner_match, KZT_PATCH_OWNER_UNKNOWN); + check_int("stub.owner.known", request.current_owner.known, 0); + check_int("stub.lazy_deferred", request.lazy_binding_deferred, 1); + check_int("stub.owner_present", enrich_result.owner_present, 0); + + check_int("stub.write", + kzt_rela_immediate_jump_slot_try_write( + &request, &guard, &slot_ops, &writer_result), 0); + check_int("stub.plan.status", writer_result.plan.status, + KZT_RELA_IMMEDIATE_CANDIDATE_SKIPPED); + check_int("stub.plan.reason", writer_result.plan.reason, + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_DEFERRED_LAZY_BINDING); + check_int("stub.plan.candidate", writer_result.plan.candidate_present, 0); + check_int("stub.writer", writer_result.writer_called, 0); + check_int("stub.skip_legacy", writer_result.skip_legacy_write, 0); + + if (!writer_result.skip_legacy_write) { + fake_legacy_write(&bank, request.slot_addr, request.legacy_target); + } + check_ulong("stub.selected.value", bank.values[0], legacy_target); + check_int("stub.selected.writer_writes", + bank.entries[0].writer_write_calls, 0); + check_int("stub.selected.legacy_writes", + bank.entries[0].legacy_write_calls, 1); + check_ulong("stub.other.value", bank.values[1], other_value); + check_int("stub.other.writer_writes", + bank.entries[1].writer_write_calls, 0); + check_int("stub.other.legacy_writes", + bank.entries[1].legacy_write_calls, 0); + check_ulong("stub.canary.before", bank.canary_before, canary_before); + check_ulong("stub.canary.after", bank.canary_after, canary_after); + check_int("stub.invalid_accesses", bank.invalid_accesses, 0); + + kzt_guest_registry_destroy(®istry); +} + +static void test_no_manifest_keeps_default_fail_open(void) +{ + kzt_guest_registry_t *registry = registry_with_source_and_owner(1, 1); + fake_bridge_state_t bridge = { .next_bridge_target = 0x72000000 }; + kzt_wrapper_probe_bridge_ops_t ops = bridge_ops(&bridge); + kzt_rela_request_enricher_input_t input = { + .registry = registry, + .wrapper_manifest = NULL, + .bridge_ops = &ops, + }; + kzt_rela_request_enricher_result_t enrich_result; + kzt_rela_immediate_candidate_result_t plan_result; + kzt_rela_immediate_candidate_request_t request = base_request(); + + enrich_and_plan(&request, &input, &enrich_result, &plan_result); + check_int("no_manifest.wrapper", request.wrapper_match, + KZT_PATCH_WRAPPER_NO_MANIFEST); + check_ulong("no_manifest.bridge", request.native_bridge_target, 0); + check_int("no_manifest.decision", plan_result.decision.kind, + KZT_PATCH_DECISION_UNSUPPORTED); + check_int("no_manifest.reason", plan_result.decision.reason, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_WRAPPER_MANIFEST); + check_int("no_manifest.add_bridge", bridge.add_calls, 0); + + kzt_guest_registry_destroy(®istry); +} + +static void test_missing_owner_blocks_approved_plan(void) +{ + kzt_guest_registry_t *registry = registry_with_source_and_owner(1, 0); + kzt_wrapper_probe_manifest_t manifest = wrapper_manifest(); + fake_bridge_state_t bridge = { .next_bridge_target = 0x72000000 }; + kzt_wrapper_probe_bridge_ops_t ops = bridge_ops(&bridge); + kzt_rela_request_enricher_input_t input = { + .registry = registry, + .wrapper_manifest = &manifest, + .bridge_ops = &ops, + }; + kzt_rela_request_enricher_result_t enrich_result; + kzt_rela_immediate_candidate_result_t plan_result; + kzt_rela_immediate_candidate_request_t request = base_request(); + + enrich_and_plan(&request, &input, &enrich_result, &plan_result); + check_int("missing_owner.owner", request.owner_match, + KZT_PATCH_OWNER_UNKNOWN); + check_int("missing_owner.decision", plan_result.decision.kind, + KZT_PATCH_DECISION_UNSUPPORTED); + check_int("missing_owner.reason", plan_result.decision.reason, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_OWNER); + check_int("missing_owner.no_add_bridge", bridge.add_calls, 0); + + kzt_guest_registry_destroy(®istry); +} + +static void test_missing_dynamic_view_blocks_approved_plan(void) +{ + kzt_guest_registry_t *registry = registry_with_source_and_owner(0, 1); + kzt_wrapper_probe_manifest_t manifest = wrapper_manifest(); + fake_bridge_state_t bridge = { .next_bridge_target = 0x72000000 }; + kzt_wrapper_probe_bridge_ops_t ops = bridge_ops(&bridge); + kzt_rela_request_enricher_input_t input = { + .registry = registry, + .wrapper_manifest = &manifest, + .bridge_ops = &ops, + }; + kzt_rela_request_enricher_result_t enrich_result; + kzt_rela_immediate_candidate_result_t plan_result; + kzt_rela_immediate_candidate_request_t request = base_request(); + + enrich_and_plan(&request, &input, &enrich_result, &plan_result); + check_int("missing_dynamic.available", request.dynamic_view_available, 0); + check_int("missing_dynamic.decision", plan_result.decision.kind, + KZT_PATCH_DECISION_UNSUPPORTED); + check_int("missing_dynamic.reason", plan_result.decision.reason, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_DYNAMIC_VIEW); + check_int("missing_dynamic.no_add_bridge", bridge.add_calls, 0); + + kzt_guest_registry_destroy(®istry); +} + +static void test_bridge_zero_blocks_approved_plan(void) +{ + kzt_guest_registry_t *registry = registry_with_source_and_owner(1, 1); + kzt_wrapper_probe_manifest_t manifest = wrapper_manifest(); + fake_bridge_state_t bridge = { .next_bridge_target = 0 }; + kzt_wrapper_probe_bridge_ops_t ops = bridge_ops(&bridge); + kzt_rela_request_enricher_input_t input = { + .registry = registry, + .wrapper_manifest = &manifest, + .bridge_ops = &ops, + }; + kzt_rela_request_enricher_result_t enrich_result; + kzt_rela_immediate_candidate_result_t plan_result; + kzt_rela_immediate_candidate_request_t request = base_request(); + + enrich_and_plan(&request, &input, &enrich_result, &plan_result); + check_int("bridge_zero.wrapper", request.wrapper_match, + KZT_PATCH_WRAPPER_VERSION_MATCH); + check_ulong("bridge_zero.bridge", request.native_bridge_target, 0); + check_int("bridge_zero.decision", plan_result.decision.kind, + KZT_PATCH_DECISION_UNSUPPORTED); + check_int("bridge_zero.reason", plan_result.decision.reason, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_BRIDGE_TARGET); + + kzt_guest_registry_destroy(®istry); +} + +static void test_native_bridge_is_not_used_as_expected_owner(void) +{ + kzt_guest_registry_t *registry = registry_with_source_and_owner(1, 1); + kzt_guest_object_observation_t bridge_like = observation( + 0x3000, 0x72000000, 0x72010000, "libbridge-like.so"); + kzt_wrapper_probe_manifest_t manifest = wrapper_manifest(); + fake_bridge_state_t bridge = { .next_bridge_target = 0x72000080 }; + kzt_wrapper_probe_bridge_ops_t ops = bridge_ops(&bridge); + kzt_rela_request_enricher_input_t input = { + .registry = registry, + .wrapper_manifest = &manifest, + .bridge_ops = &ops, + }; + kzt_rela_request_enricher_result_t enrich_result; + kzt_rela_immediate_candidate_result_t plan_result; + kzt_rela_immediate_candidate_request_t request = base_request(); + + check_int("bridge_like.observe", + kzt_guest_registry_observe(registry, &bridge_like), + KZT_GUEST_REGISTRY_ADDED); + enrich_and_plan(&request, &input, &enrich_result, &plan_result); + check_int("bridge_like.owner", request.owner_match, + KZT_PATCH_OWNER_MATCH); + check_ulong("bridge_like.bridge", request.native_bridge_target, + 0x72000080); + check_int("bridge_like.decision", plan_result.decision.kind, + KZT_PATCH_DECISION_APPROVED); + + kzt_guest_registry_destroy(®istry); +} + +static void test_dead_object_is_not_relocation_source(void) +{ + kzt_guest_registry_t *registry = registry_with_source_and_owner(0, 0); + kzt_rela_request_enricher_input_t input = { .registry = registry }; + kzt_rela_request_enricher_result_t result; + kzt_rela_immediate_candidate_request_t request = base_request(); + + check_int("dead_source.retire", kzt_guest_registry_retire( + registry, 0x1000, 1), 0); + check_int("dead_source.enrich", kzt_rela_immediate_request_enrich( + &request, &input, &result), 0); + check_int("dead_source.absent", result.source_present, 0); + check_ulong("dead_source.no-link-map", request.source.link_map_addr, 0); + check_int("dead_source.no-dynamic", request.dynamic_view_available, 0); + kzt_guest_registry_destroy(®istry); +} + +int main(void) +{ + test_all_evidence_allows_approved_plan(); + test_unresolved_stub_detector_coordinates_and_bounds(); + test_distinct_targets_write_only_selected_slot(); + test_unresolved_stub_skips_owner_and_falls_back_to_legacy(); + test_no_manifest_keeps_default_fail_open(); + test_missing_owner_blocks_approved_plan(); + test_missing_dynamic_view_blocks_approved_plan(); + test_bridge_zero_blocks_approved_plan(); + test_native_bridge_is_not_used_as_expected_owner(); + test_dead_object_is_not_relocation_source(); + + if (failures) { + fprintf(stderr, "kzt-rela-request-enricher: %d failure(s)\n", + failures); + return 1; + } + + printf("kzt-rela-request-enricher: ok\n"); + return 0; +} diff --git a/tests/unit/kzt/test_rela_runtime_helper_chain.c b/tests/unit/kzt/test_rela_runtime_helper_chain.c new file mode 100644 index 00000000000..df564854665 --- /dev/null +++ b/tests/unit/kzt/test_rela_runtime_helper_chain.c @@ -0,0 +1,479 @@ +#include +#include +#include +#include + +#include "elf.h" +#include "target/i386/latx/include/box64context.h" +#include "target/i386/latx/include/bridge_private.h" +#include "target/i386/latx/include/khash.h" +#include "target/i386/latx/include/kzt_bridge_exact.h" +#include "target/i386/latx/include/kzt_guest_registry.h" +#include "target/i386/latx/include/kzt_rela_immediate_candidate.h" +#include "target/i386/latx/include/kzt_rela_request_enricher.h" +#include "target/i386/latx/include/kzt_rela_runtime_bridge.h" +#include "target/i386/latx/include/librarian_private.h" +#include "target/i386/latx/include/library.h" +#include "target/i386/latx/include/library_private.h" + +#define FIXTURE_SYMBOL "uname" +#define FIXTURE_VERSION "GLIBC_2.36" + +/* This test calls the runtime adapter, enricher, and writer helpers directly. + It is a helper-chain test, not a full RelocateElfRELA E2E test, and it does + not model or claim coverage of the production legacy-store path. */ + +static int failures; +static int fixture_not_applicable; + +KHASH_MAP_IMPL_STR(symbolmap, wrapper_t) +KHASH_MAP_IMPL_STR(symbol2map, symbol2_t) + +static void fixture_iFp(uintptr_t fnc) +{ + (void)fnc; +} + +typedef struct fixture_bridge_map { + void *native_symbol; + uintptr_t target; + int check_calls; +} fixture_bridge_map_t; + +typedef struct runtime_fixture { + box64context_t context; + lib_t scope; + library_t library; + fixture_bridge_map_t bridge_map; + onebridge_t bridge_entry; + uintptr_t native_symbol; +} runtime_fixture_t; + +enum fixture_setup_status { + FIXTURE_SETUP_ERROR = -1, + FIXTURE_SETUP_OK = 0, + FIXTURE_SETUP_SKIP = 1, +}; + +uintptr_t CheckBridged(bridge_t *bridge, void *fnc); + +uintptr_t CheckBridged(bridge_t *bridge, void *fnc) +{ + fixture_bridge_map_t *map = (fixture_bridge_map_t *)bridge; + + if (!map) { + return 0; + } + ++map->check_calls; + if (!fnc || fnc != map->native_symbol) { + return 0; + } + return map->target; +} + +typedef struct guarded_slot { + uintptr_t value; + uintptr_t canary; +} guarded_slot_t; + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, uintptr_t got, uintptr_t expected) +{ + if (got == expected) { + return; + } + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static kzt_guest_object_observation_t observation( + uintptr_t link_map_addr, uintptr_t map_start, uintptr_t map_end, + const char *name) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map_addr, + .load_bias = { map_start, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { map_start + 0x1000, KZT_GUEST_FIELD_OK }, + .map_start = { map_start, KZT_GUEST_FIELD_OK }, + .map_end = { map_end, KZT_GUEST_FIELD_OK }, + .namespace_id = { 0, KZT_GUEST_FIELD_OK }, + .path = { name, KZT_GUEST_FIELD_OK }, + .soname = { name, KZT_GUEST_FIELD_OK }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static kzt_guest_registry_t *helper_chain_registry(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t source = observation( + 0x1000, 0x70000000, 0x70010000, "librequester.so"); + kzt_guest_object_observation_t owner = observation( + 0x2000, 0x71000000, 0x71010000, "libowner.so"); + kzt_guest_dynamic_view_t view = { + .dynamic_addr = 0x70001000, + .load_bias = 0x70000000, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .entry_count = 1, + .has_null = 1, + }; + + check_int("registry.source", + kzt_guest_registry_observe(registry, &source), + KZT_GUEST_REGISTRY_ADDED); + check_int("registry.owner", + kzt_guest_registry_observe(registry, &owner), + KZT_GUEST_REGISTRY_ADDED); + check_int("registry.dynamic", + kzt_guest_registry_commit_dynamic_view( + registry, source.link_map_addr, 1, &view), + KZT_GUEST_REGISTRY_UPDATED); + return registry; +} + +static kzt_rela_immediate_candidate_request_t request_for( + guarded_slot_t *slot, uintptr_t legacy_target, const char *version) +{ + return (kzt_rela_immediate_candidate_request_t) { + .relocation_type = R_X86_64_JUMP_SLOT, + .table_kind = KZT_PATCH_TABLE_PLT_RELA, + .entry_index = 3, + .entry_addr = 0x70002000, + .source = { + .known = 1, + .map_start = 0x70000000, + .map_end = 0x70010000, + .soname = "librequester.so", + .path = "/guest/librequester.so", + }, + .dynamic_addr = 0x70001000, + .load_bias = 0x70000000, + .slot_addr = (uintptr_t)&slot->value, + .slot_current_value_present = 1, + .slot_current_value = slot->value, + .expected_guest_target = 0x71000020, + .legacy_target = legacy_target, + .symbol_index = 7, + .symbol_name = FIXTURE_SYMBOL, + .version = version, + }; +} + +static void runtime_fixture_destroy(runtime_fixture_t *fixture) +{ + if (!fixture) { + return; + } + if (fixture->library.symbolmap) { + kh_destroy(symbolmap, fixture->library.symbolmap); + fixture->library.symbolmap = NULL; + } + if (fixture->library.priv.w.lib) { + dlclose(fixture->library.priv.w.lib); + fixture->library.priv.w.lib = NULL; + } + free(fixture->scope.libraries); + fixture->scope.libraries = NULL; + fixture->scope.libsz = 0; +} + +static int runtime_fixture_setup(runtime_fixture_t *fixture) +{ + static char libc_name[] = "libc.so.6"; + library_t **libraries; + const char *version_error; + khint_t key; + int inserted; + + if (fixture_not_applicable) { + return FIXTURE_SETUP_SKIP; + } + memset(fixture, 0, sizeof(*fixture)); + libraries = malloc(sizeof(*libraries)); + if (!libraries) { + return FIXTURE_SETUP_ERROR; + } + libraries[0] = &fixture->library; + fixture->scope.libraries = libraries; + fixture->scope.libsz = 1; + fixture->scope.context = &fixture->context; + fixture->context.maplib = &fixture->scope; + fixture->library.name = libc_name; + fixture->library.path = libc_name; + fixture->library.type = LIB_WRAPPED; + fixture->library.active = 1; + fixture->library.context = &fixture->context; + fixture->library.priv.w.lib = + dlopen("libc.so.6", RTLD_LAZY | RTLD_LOCAL); + if (!fixture->library.priv.w.lib) { + fprintf(stderr, "dlopen(libc.so.6): %s\n", dlerror()); + runtime_fixture_destroy(fixture); + return FIXTURE_SETUP_ERROR; + } + dlerror(); + fixture->native_symbol = (uintptr_t)dlvsym( + fixture->library.priv.w.lib, FIXTURE_SYMBOL, FIXTURE_VERSION); + version_error = dlerror(); + if (!fixture->native_symbol || version_error) { + printf("SKIP: %s@%s is not provided by this host libc; " + "runtime helper-chain fixture is not applicable\n", + FIXTURE_SYMBOL, FIXTURE_VERSION); + fixture_not_applicable = 1; + runtime_fixture_destroy(fixture); + return FIXTURE_SETUP_SKIP; + } + + fixture->library.priv.w.bridge = (bridge_t *)&fixture->bridge_map; + fixture->library.symbolmap = kh_init(symbolmap); + if (!fixture->library.symbolmap) { + runtime_fixture_destroy(fixture); + return FIXTURE_SETUP_ERROR; + } + key = kh_put(symbolmap, fixture->library.symbolmap, FIXTURE_SYMBOL, + &inserted); + if (inserted == -1 || key == kh_end(fixture->library.symbolmap)) { + runtime_fixture_destroy(fixture); + return FIXTURE_SETUP_ERROR; + } + kh_value(fixture->library.symbolmap, key) = fixture_iFp; + fixture->bridge_entry.CC = 0xCC; + fixture->bridge_entry.S = 'S'; + fixture->bridge_entry.C = 'C'; + fixture->bridge_entry.w = fixture_iFp; + fixture->bridge_entry.f = fixture->native_symbol; + fixture->bridge_entry.C3 = 0xC3; + fixture->bridge_map.native_symbol = (void *)fixture->native_symbol; + fixture->bridge_map.target = (uintptr_t)&fixture->bridge_entry.CC; + return FIXTURE_SETUP_OK; +} + +static void test_runtime_adapter_helper_chain(void) +{ + const uintptr_t canary = 0xcafebabedeadbeefULL; + const uintptr_t legacy_target = 0x7bad0000; + runtime_fixture_t fixture; + int setup_status; + uintptr_t bridge_target; + guarded_slot_t slot = { 0x71000010, canary }; + kzt_guest_registry_t *registry; + kzt_wrapper_bridge_provider_t runtime_provider; + kzt_rela_request_enricher_input_t enrich_input = { 0 }; + kzt_rela_request_enricher_result_t enrich_result; + kzt_rela_immediate_candidate_request_t request; + kzt_rela_immediate_writer_result_t writer_result; + kzt_patch_spike_guard_t guard; + kzt_patch_spike_config_t config = { 1, 1, 1 }; + + setup_status = runtime_fixture_setup(&fixture); + if (setup_status == FIXTURE_SETUP_SKIP) { + return; + } + if (setup_status != FIXTURE_SETUP_OK) { + ++failures; + return; + } + bridge_target = fixture.bridge_map.target; + check_int("runtime-provider.guest-native-distinct", + bridge_target != fixture.native_symbol, 1); + check_int("runtime-provider.prepare", + kzt_rela_runtime_wrapper_provider_prepare( + &fixture.context, &fixture.library, bridge_target, + FIXTURE_SYMBOL, + FIXTURE_VERSION, &runtime_provider), + 1); + check_int("runtime-provider.map-checked", + fixture.bridge_map.check_calls > 0, 1); + check_int("runtime-provider.no-add", + runtime_provider.bridge_ops.add_bridge == NULL, 1); + + registry = helper_chain_registry(); + request = request_for(&slot, legacy_target, FIXTURE_VERSION); + enrich_input.registry = registry; + enrich_input.slot_current_value_is_unresolved_stub = 0; + enrich_input.wrapper_manifest = &runtime_provider.manifest; + enrich_input.bridge_ops = &runtime_provider.bridge_ops; + check_int("enrich.call", + kzt_rela_immediate_request_enrich( + &request, &enrich_input, &enrich_result), + 0); + check_ulong("enrich.bridge", request.native_bridge_target, + bridge_target); + + kzt_patch_spike_guard_init(&guard, &config); + check_int("writer.call", + kzt_rela_immediate_jump_slot_try_write( + &request, &guard, NULL, &writer_result), + 0); + check_int("writer.called", writer_result.writer_called, 1); + check_int("writer.skip-legacy", writer_result.skip_legacy_write, 1); + check_ulong("writer.slot-is-bridge", slot.value, bridge_target); + check_int("writer.slot-not-legacy", slot.value != legacy_target, 1); + check_ulong("writer.canary", slot.canary, canary); + kzt_guest_registry_destroy(®istry); + runtime_fixture_destroy(&fixture); +} + +static void test_version_mismatch_leaves_helper_fallback_eligible(void) +{ + const uintptr_t canary = 0x1122334455667788ULL; + const uintptr_t legacy_target = 0x7bad1000; + const uintptr_t initial_slot = 0x71000010; + runtime_fixture_t fixture; + int setup_status; + uintptr_t bridge_target; + guarded_slot_t slot = { initial_slot, canary }; + kzt_wrapper_bridge_provider_t runtime_provider; + kzt_guest_registry_t *registry; + kzt_rela_request_enricher_input_t enrich_input = { 0 }; + kzt_rela_request_enricher_result_t enrich_result; + kzt_rela_immediate_candidate_request_t request; + kzt_rela_immediate_writer_result_t writer_result; + kzt_patch_spike_guard_t guard; + kzt_patch_spike_config_t config = { 1, 1, 1 }; + + setup_status = runtime_fixture_setup(&fixture); + if (setup_status == FIXTURE_SETUP_SKIP) { + return; + } + if (setup_status != FIXTURE_SETUP_OK) { + ++failures; + return; + } + bridge_target = fixture.bridge_map.target; + check_int("mismatch.provider", + kzt_rela_runtime_wrapper_provider_prepare( + &fixture.context, &fixture.library, bridge_target, + FIXTURE_SYMBOL, + "GLIBC_NOT_REAL", &runtime_provider), + 0); + registry = helper_chain_registry(); + request = request_for(&slot, legacy_target, "GLIBC_NOT_REAL"); + enrich_input.registry = registry; + enrich_input.slot_current_value_is_unresolved_stub = 0; + enrich_input.wrapper_manifest = &runtime_provider.manifest; + enrich_input.bridge_ops = &runtime_provider.bridge_ops; + check_int("mismatch.enrich", + kzt_rela_immediate_request_enrich( + &request, &enrich_input, &enrich_result), + 0); + kzt_patch_spike_guard_init(&guard, &config); + check_int("mismatch.writer", + kzt_rela_immediate_jump_slot_try_write( + &request, &guard, NULL, &writer_result), + 0); + check_int("mismatch.writer-not-called", writer_result.writer_called, 0); + check_int("mismatch.fallback-eligible", + writer_result.skip_legacy_write, 0); + check_ulong("mismatch.helper-left-slot-untouched", slot.value, + initial_slot); + check_ulong("mismatch.canary", slot.canary, canary); + kzt_guest_registry_destroy(®istry); + runtime_fixture_destroy(&fixture); +} + +static void test_bridge_owner_mismatch_fails_closed(void) +{ + runtime_fixture_t fixture; + kzt_wrapper_bridge_provider_t runtime_provider; + uintptr_t resolved_target; + int setup_status; + + setup_status = runtime_fixture_setup(&fixture); + if (setup_status == FIXTURE_SETUP_SKIP) { + return; + } + if (setup_status != FIXTURE_SETUP_OK) { + ++failures; + return; + } + resolved_target = fixture.bridge_map.target; + fixture.bridge_map.target = resolved_target + sizeof(onebridge_t); + check_int("owner-mismatch.prepare", + kzt_rela_runtime_wrapper_provider_prepare( + &fixture.context, &fixture.library, resolved_target, + FIXTURE_SYMBOL, FIXTURE_VERSION, &runtime_provider), + 0); + check_int("owner-mismatch.map-checked", + fixture.bridge_map.check_calls > 0, 1); + check_int("owner-mismatch.unavailable", + runtime_provider.manifest.available, 0); + runtime_fixture_destroy(&fixture); +} + +static void test_null_inputs_fail_without_bridge_inspection(void) +{ + runtime_fixture_t fixture; + kzt_wrapper_bridge_provider_t runtime_provider; + uintptr_t resolved_target; + int setup_status; + + check_int("null-provider.error", + kzt_rela_runtime_wrapper_provider_prepare( + NULL, NULL, 0, NULL, NULL, NULL), + -1); + + setup_status = runtime_fixture_setup(&fixture); + if (setup_status == FIXTURE_SETUP_SKIP) { + return; + } + if (setup_status != FIXTURE_SETUP_OK) { + ++failures; + return; + } + resolved_target = fixture.bridge_map.target; + check_int("null-target.prepare", + kzt_rela_runtime_wrapper_provider_prepare( + &fixture.context, &fixture.library, 0, FIXTURE_SYMBOL, + FIXTURE_VERSION, &runtime_provider), + 0); + fixture.bridge_map.target = 0; + check_int("null-map-result.prepare", + kzt_rela_runtime_wrapper_provider_prepare( + &fixture.context, &fixture.library, resolved_target, + FIXTURE_SYMBOL, FIXTURE_VERSION, &runtime_provider), + 0); + check_int("null-map-result.checked", + fixture.bridge_map.check_calls > 0, 1); + fixture.bridge_map.target = resolved_target; + fixture.library.priv.w.bridge = NULL; + check_int("null-bridge-map.prepare", + kzt_rela_runtime_wrapper_provider_prepare( + &fixture.context, &fixture.library, + fixture.bridge_map.target, FIXTURE_SYMBOL, + FIXTURE_VERSION, &runtime_provider), + 0); + check_int("null-exact.target", + kzt_bridge_is_exact(0, fixture_iFp, + (void *)fixture.native_symbol), + 0); + runtime_fixture_destroy(&fixture); +} + +int main(void) +{ + test_runtime_adapter_helper_chain(); + test_version_mismatch_leaves_helper_fallback_eligible(); + test_bridge_owner_mismatch_fails_closed(); + test_null_inputs_fail_without_bridge_inspection(); + + if (failures) { + fprintf(stderr, "kzt-rela-runtime-helper-chain: %d failure(s)\n", + failures); + return 1; + } + if (fixture_not_applicable) { + puts("kzt-rela-runtime-helper-chain: skipped"); + return 77; + } + puts("kzt-rela-runtime-helper-chain: ok"); + return 0; +} diff --git a/tests/unit/kzt/test_runtime_candidate_enrichment_shadow.c b/tests/unit/kzt/test_runtime_candidate_enrichment_shadow.c new file mode 100644 index 00000000000..112c545b6b1 --- /dev/null +++ b/tests/unit/kzt/test_runtime_candidate_enrichment_shadow.c @@ -0,0 +1,1093 @@ +#include +#include + +#include "elf.h" +#include "target/i386/latx/include/kzt_runtime_candidate_shadow.h" + +#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) +#define TEST_ELF64_R_INFO(symbol, type) \ + (((uint64_t)(symbol) << 32) | (uint32_t)(type)) + +#define TEST_SOURCE_LINK_MAP 0x1000ULL +#define TEST_OWNER_A_LINK_MAP 0x2000ULL +#define TEST_OWNER_B_LINK_MAP 0x3000ULL +#define TEST_SOURCE_BASE 0x60000000ULL +#define TEST_OWNER_A_BASE 0x71000000ULL +#define TEST_OWNER_B_BASE 0x72000000ULL +#define TEST_LOAD_BIAS TEST_SOURCE_BASE +#define TEST_PLT_RELA_ADDR 0x68001000ULL +#define TEST_RELA_ADDR 0x68002000ULL +#define TEST_SYMTAB_ADDR 0x68003000ULL +#define TEST_STRTAB_ADDR 0x68004000ULL +#define TEST_VERSYM_ADDR 0x68005000ULL +#define TEST_VERNEED_ADDR 0x68006000ULL +#define TEST_PLT_SLOT_ADDR (TEST_LOAD_BIAS + 0x3010) +#define TEST_GOT_SLOT_ADDR (TEST_LOAD_BIAS + 0x4020) +#define TEST_NATIVE_PUTS 0x90001000ULL +#define TEST_NATIVE_ERRNO 0x90002000ULL +#define TEST_LEGACY_TARGET 0xa0003000ULL + +enum { + TEST_SYMBOL_PUTS = 1, + TEST_SYMBOL_ERRNO = 2, + TEST_STR_PUTS = 1, + TEST_STR_ERRNO = 6, + TEST_STR_VERSION = 12, +}; + +static const char test_dynstr[] = + "\0puts\0errno\0GLIBC_2.2.5\0"; + +static int failures; +static size_t tests_run; +static size_t slot_write_checks; + +typedef struct fake_region { + uintptr_t guest_base; + const void *host_base; + size_t size; +} fake_region_t; + +typedef struct fake_memory { + fake_region_t regions[16]; + size_t region_count; + uintptr_t fail_addr; + size_t read_calls; +} fake_memory_t; + +typedef struct guarded_slot { + uint64_t before; + uint64_t value; + uint64_t after; +} guarded_slot_t; + +typedef struct version_fixture { + Elf64_Verneed need; + Elf64_Vernaux aux; +} version_fixture_t; + +typedef struct expected_target_state { + uintptr_t target; + size_t calls; + const char *last_symbol; +} expected_target_state_t; + +typedef struct bridge_state { + uintptr_t cache_target; + uintptr_t add_target; + uintptr_t last_native_symbol; + size_t check_calls; + size_t mutation_calls; +} bridge_state_t; + +typedef struct stub_classifier_state { + uintptr_t plt_start; + uintptr_t plt_end; + uintptr_t gotplt_start; + uintptr_t gotplt_end; + size_t calls; +} stub_classifier_state_t; + +typedef struct generation_query_state { + unsigned long generations[4]; + int results[4]; + size_t response_count; + size_t calls; +} generation_query_state_t; + +typedef struct shadow_fixture { + fake_memory_t memory; + kzt_guest_link_map_reader_ops_t reader_ops; + kzt_guest_registry_t *registry; + kzt_guest_dynamic_view_t view; + kzt_patch_object_ref_t source; + + Elf64_Rela plt_rela; + Elf64_Rela rela; + Elf64_Sym symbols[3]; + Elf64_Half versions[3]; + version_fixture_t version; + guarded_slot_t plt_slot; + guarded_slot_t got_slot; + + kzt_patch_candidate_t candidates[2]; + char string_storage[256]; + kzt_runtime_candidate_shadow_record_t records[2]; + + kzt_wrapper_probe_entry_t manifest_entries[2]; + kzt_wrapper_probe_manifest_t manifest; + expected_target_state_t expected; + bridge_state_t bridge; + kzt_wrapper_probe_bridge_ops_t bridge_ops; + stub_classifier_state_t stub_classifier; + generation_query_state_t generation_query; + + kzt_runtime_got_plt_candidate_request_t collector_request; + kzt_runtime_candidate_shadow_input_t input; + kzt_runtime_candidate_shadow_result_t result; +} shadow_fixture_t; + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_size(const char *name, size_t got, size_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lu expected %lu\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_ulong(const char *name, + unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", + name, got, expected); + ++failures; +} + +static void check_memory(const char *name, + const void *got, + const void *expected, + size_t size) +{ + if (memcmp(got, expected, size) == 0) { + return; + } + + fprintf(stderr, "%s: memory changed\n", name); + ++failures; +} + +static void add_region(fake_memory_t *memory, + uintptr_t guest_base, + const void *host_base, + size_t size) +{ + fake_region_t *region; + + if (memory->region_count >= ARRAY_SIZE(memory->regions)) { + ++failures; + return; + } + + region = &memory->regions[memory->region_count++]; + region->guest_base = guest_base; + region->host_base = host_base; + region->size = size; +} + +static int fake_read_memory(uintptr_t guest_addr, + void *dst, + size_t size, + void *opaque) +{ + fake_memory_t *memory = opaque; + size_t i; + + ++memory->read_calls; + if (memory->fail_addr && guest_addr == memory->fail_addr) { + return -1; + } + + for (i = 0; i < memory->region_count; ++i) { + const fake_region_t *region = &memory->regions[i]; + uintptr_t offset; + + if (guest_addr < region->guest_base) { + continue; + } + + offset = guest_addr - region->guest_base; + if (offset > region->size || size > region->size - offset) { + continue; + } + + memcpy(dst, (const char *)region->host_base + offset, size); + return 0; + } + + return -1; +} + +static kzt_guest_dynamic_field_t runtime_field(uint64_t value) +{ + return (kzt_guest_dynamic_field_t) { + .present = 1, + .value = value, + .address_semantics = KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS, + }; +} + +static kzt_guest_dynamic_field_t scalar_field(uint64_t value) +{ + return (kzt_guest_dynamic_field_t) { + .present = 1, + .value = value, + .address_semantics = KZT_GUEST_DYNAMIC_SCALAR, + }; +} + +static kzt_guest_object_observation_t object_observation( + uintptr_t link_map_addr, + uintptr_t map_start, + uintptr_t map_end, + const char *soname, + const char *path) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map_addr, + .load_bias = { map_start, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { map_start + 0x1000, KZT_GUEST_FIELD_OK }, + .map_start = { map_start, KZT_GUEST_FIELD_OK }, + .map_end = { map_end, KZT_GUEST_FIELD_OK }, + .namespace_id = { 0, KZT_GUEST_FIELD_OK }, + .path = { path, KZT_GUEST_FIELD_OK }, + .soname = { soname, KZT_GUEST_FIELD_OK }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static kzt_patch_object_ref_t source_ref_from_registry( + kzt_guest_registry_t *registry) +{ + kzt_guest_registry_dump_t dump = { 0 }; + kzt_patch_object_ref_t source = { + .known = 1, + .link_map_addr = TEST_SOURCE_LINK_MAP, + .map_start = TEST_SOURCE_BASE, + .map_end = TEST_SOURCE_BASE + 0x10000, + .soname = "libsource.so", + .path = "/guest/libsource.so", + }; + size_t i; + + if (kzt_guest_registry_dump_snapshot(registry, &dump) != 0) { + ++failures; + return source; + } + + for (i = 0; i < dump.count; ++i) { + if (dump.objects[i].link_map_addr == TEST_SOURCE_LINK_MAP) { + source.generation = dump.objects[i].generation; + break; + } + } + + kzt_guest_registry_dump_free(&dump); + return source; +} + +static int resolve_expected_guest_target( + const kzt_patch_candidate_t *candidate, + uintptr_t *expected_guest_target, + void *opaque) +{ + expected_target_state_t *state = opaque; + + ++state->calls; + state->last_symbol = candidate->symbol_name; + if (!state->target) { + return -1; + } + + *expected_guest_target = state->target; + return 0; +} + +static uintptr_t check_bridge(uintptr_t native_symbol, void *opaque) +{ + bridge_state_t *state = opaque; + + ++state->check_calls; + state->last_native_symbol = native_symbol; + return state->cache_target; +} + +static uintptr_t unexpected_mutating_bridge( + const kzt_wrapper_probe_bridge_request_t *request, + void *opaque) +{ + bridge_state_t *state = opaque; + + (void)request; + ++state->mutation_calls; + return state->add_target; +} + +static kzt_runtime_candidate_shadow_stub_classification_t +classify_precise_stub(const kzt_patch_candidate_t *candidate, void *opaque) +{ + stub_classifier_state_t *state = opaque; + uintptr_t start = 0; + uintptr_t end = 0; + + ++state->calls; + if (candidate->reloc_type == KZT_PATCH_RELOCATION_JUMP_SLOT) { + start = state->plt_start; + end = state->plt_end; + } else if (candidate->reloc_type == KZT_PATCH_RELOCATION_GLOB_DAT) { + start = state->gotplt_start; + end = state->gotplt_end; + } else { + return KZT_RUNTIME_CANDIDATE_SHADOW_STUB_UNKNOWN; + } + + if (start == 0 || start >= end) { + return KZT_RUNTIME_CANDIDATE_SHADOW_STUB_UNKNOWN; + } + + return candidate->slot_current_value >= start && + candidate->slot_current_value < end ? + KZT_RUNTIME_CANDIDATE_SHADOW_STUB_MATCH : + KZT_RUNTIME_CANDIDATE_SHADOW_STUB_NO_MATCH; +} + +static int query_test_generation(uintptr_t link_map_addr, + unsigned long *generation, + void *opaque) +{ + generation_query_state_t *state = opaque; + size_t index; + + (void)link_map_addr; + if (state->response_count == 0) { + return -1; + } + + index = state->calls < state->response_count ? + state->calls : state->response_count - 1; + ++state->calls; + if (state->results[index] != 0) { + return state->results[index]; + } + + *generation = state->generations[index]; + return 0; +} + +static void fixture_init(shadow_fixture_t *fixture, int include_glob_dat) +{ + kzt_guest_object_observation_t source; + kzt_guest_object_observation_t owner_a; + kzt_guest_object_observation_t owner_b; + + memset(fixture, 0, sizeof(*fixture)); + fixture->registry = kzt_guest_registry_init(); + source = object_observation( + TEST_SOURCE_LINK_MAP, TEST_SOURCE_BASE, + TEST_SOURCE_BASE + 0x10000, "libsource.so", + "/guest/libsource.so"); + owner_a = object_observation( + TEST_OWNER_A_LINK_MAP, TEST_OWNER_A_BASE, + TEST_OWNER_A_BASE + 0x10000, "libowner-a.so", + "/guest/libowner-a.so"); + owner_b = object_observation( + TEST_OWNER_B_LINK_MAP, TEST_OWNER_B_BASE, + TEST_OWNER_B_BASE + 0x10000, "libowner-b.so", + "/guest/libowner-b.so"); + + check_int("fixture.observe.source", + kzt_guest_registry_observe(fixture->registry, &source), + KZT_GUEST_REGISTRY_ADDED); + check_int("fixture.observe.owner-a", + kzt_guest_registry_observe(fixture->registry, &owner_a), + KZT_GUEST_REGISTRY_ADDED); + check_int("fixture.observe.owner-b", + kzt_guest_registry_observe(fixture->registry, &owner_b), + KZT_GUEST_REGISTRY_ADDED); + fixture->source = source_ref_from_registry(fixture->registry); + + fixture->reader_ops.read_memory = fake_read_memory; + fixture->reader_ops.opaque = &fixture->memory; + fixture->view.dynamic_addr = TEST_SOURCE_BASE + 0x1000; + fixture->view.load_bias = TEST_LOAD_BIAS; + fixture->view.status = KZT_GUEST_DYNAMIC_COMPLETE; + fixture->view.has_null = 1; + fixture->view.jmprel = runtime_field(TEST_PLT_RELA_ADDR); + fixture->view.pltrelsz = scalar_field(sizeof(fixture->plt_rela)); + fixture->view.pltrel = scalar_field(DT_RELA); + if (include_glob_dat) { + fixture->view.rela = runtime_field(TEST_RELA_ADDR); + fixture->view.relasz = scalar_field(sizeof(fixture->rela)); + fixture->view.relaent = scalar_field(sizeof(Elf64_Rela)); + } + + fixture->view.symtab = runtime_field(TEST_SYMTAB_ADDR); + fixture->view.syment = scalar_field(sizeof(Elf64_Sym)); + fixture->view.strtab = runtime_field(TEST_STRTAB_ADDR); + fixture->view.strsz = scalar_field(sizeof(test_dynstr)); + fixture->view.versym = runtime_field(TEST_VERSYM_ADDR); + fixture->view.verneed = runtime_field(TEST_VERNEED_ADDR); + fixture->view.verneednum = scalar_field(1); + + fixture->plt_rela.r_offset = TEST_PLT_SLOT_ADDR - TEST_LOAD_BIAS; + fixture->plt_rela.r_info = + TEST_ELF64_R_INFO(TEST_SYMBOL_PUTS, R_X86_64_JUMP_SLOT); + fixture->rela.r_offset = TEST_GOT_SLOT_ADDR - TEST_LOAD_BIAS; + fixture->rela.r_info = + TEST_ELF64_R_INFO(TEST_SYMBOL_ERRNO, R_X86_64_GLOB_DAT); + fixture->symbols[TEST_SYMBOL_PUTS].st_name = TEST_STR_PUTS; + fixture->symbols[TEST_SYMBOL_ERRNO].st_name = TEST_STR_ERRNO; + fixture->versions[TEST_SYMBOL_PUTS] = 2; + fixture->versions[TEST_SYMBOL_ERRNO] = 2; + fixture->version.need.vn_version = 1; + fixture->version.need.vn_cnt = 1; + fixture->version.need.vn_aux = sizeof(Elf64_Verneed); + fixture->version.aux.vna_other = 2; + fixture->version.aux.vna_name = TEST_STR_VERSION; + + fixture->plt_slot.before = 0x1111111111111111ULL; + fixture->plt_slot.value = TEST_OWNER_A_BASE + 0x40; + fixture->plt_slot.after = 0x2222222222222222ULL; + fixture->got_slot.before = 0x3333333333333333ULL; + fixture->got_slot.value = TEST_OWNER_A_BASE + 0x50; + fixture->got_slot.after = 0x4444444444444444ULL; + + add_region(&fixture->memory, TEST_PLT_RELA_ADDR, + &fixture->plt_rela, sizeof(fixture->plt_rela)); + if (include_glob_dat) { + add_region(&fixture->memory, TEST_RELA_ADDR, + &fixture->rela, sizeof(fixture->rela)); + } + add_region(&fixture->memory, TEST_SYMTAB_ADDR, + fixture->symbols, sizeof(fixture->symbols)); + add_region(&fixture->memory, TEST_STRTAB_ADDR, + test_dynstr, sizeof(test_dynstr)); + add_region(&fixture->memory, TEST_VERSYM_ADDR, + fixture->versions, sizeof(fixture->versions)); + add_region(&fixture->memory, TEST_VERNEED_ADDR, + &fixture->version, sizeof(fixture->version)); + add_region(&fixture->memory, TEST_PLT_SLOT_ADDR, + &fixture->plt_slot.value, sizeof(fixture->plt_slot.value)); + if (include_glob_dat) { + add_region(&fixture->memory, TEST_GOT_SLOT_ADDR, + &fixture->got_slot.value, + sizeof(fixture->got_slot.value)); + } + + fixture->manifest_entries[0] = (kzt_wrapper_probe_entry_t) { + .symbol_name = "puts", + .symbol_version = "GLIBC_2.2.5", + .wrapper_name = "wrapped_puts", + .wrapper_symbol_version = "GLIBC_2.2.5", + .native_symbol = TEST_NATIVE_PUTS, + }; + fixture->manifest_entries[1] = (kzt_wrapper_probe_entry_t) { + .symbol_name = "errno", + .symbol_version = "GLIBC_2.2.5", + .wrapper_name = "wrapped_errno", + .wrapper_symbol_version = "GLIBC_2.2.5", + .native_symbol = TEST_NATIVE_ERRNO, + }; + fixture->manifest = (kzt_wrapper_probe_manifest_t) { + .available = 1, + .manifest_name = "shadow-test", + .entries = fixture->manifest_entries, + .entry_count = ARRAY_SIZE(fixture->manifest_entries), + }; + fixture->expected.target = TEST_OWNER_A_BASE + 0x80; + fixture->bridge.cache_target = TEST_OWNER_B_BASE + 0x88; + fixture->bridge.add_target = 0xdeadbeefULL; + fixture->bridge_ops = (kzt_wrapper_probe_bridge_ops_t) { + .check_bridge = check_bridge, + .add_bridge = unexpected_mutating_bridge, + .opaque = &fixture->bridge, + }; + fixture->stub_classifier = (stub_classifier_state_t) { + .plt_start = TEST_SOURCE_BASE + 0x400, + .plt_end = TEST_SOURCE_BASE + 0x580, + .gotplt_start = TEST_SOURCE_BASE + 0x580, + .gotplt_end = TEST_SOURCE_BASE + 0x700, + }; + + fixture->collector_request = + (kzt_runtime_got_plt_candidate_request_t) { + .view = &fixture->view, + .reader_ops = &fixture->reader_ops, + .source = &fixture->source, + .dynamic_view_generation = fixture->source.generation, + .candidates = fixture->candidates, + .candidate_capacity = ARRAY_SIZE(fixture->candidates), + .string_storage = fixture->string_storage, + .string_storage_size = sizeof(fixture->string_storage), + }; + fixture->input = (kzt_runtime_candidate_shadow_input_t) { + .collector_request = &fixture->collector_request, + .registry = fixture->registry, + .wrapper_manifest = &fixture->manifest, + .bridge_ops = &fixture->bridge_ops, + .resolve_expected_guest_target = + resolve_expected_guest_target, + .expected_target_opaque = &fixture->expected, + .classify_stub = classify_precise_stub, + .stub_classifier_opaque = &fixture->stub_classifier, + .records = fixture->records, + .record_capacity = ARRAY_SIZE(fixture->records), + }; +} + +static int fixture_run(shadow_fixture_t *fixture) +{ + guarded_slot_t plt_before = fixture->plt_slot; + guarded_slot_t got_before = fixture->got_slot; + size_t decision_total = 0; + size_t reason_total = 0; + size_t i; + int status; + + status = kzt_runtime_candidate_shadow_run( + &fixture->input, &fixture->result); + if (fixture->result.status == + KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN) { + check_size("fail-open.collector-consumable-count", + fixture->result.collector_result.candidate_count, 0); + } + for (i = 0; + i < KZT_RUNTIME_CANDIDATE_SHADOW_DECISION_BUCKETS; ++i) { + decision_total += fixture->result.decision_histogram[i]; + } + for (i = 0; + i < KZT_RUNTIME_CANDIDATE_SHADOW_REASON_BUCKETS; ++i) { + reason_total += fixture->result.reason_histogram[i]; + } + check_size("histogram.decision-conservation", decision_total, + fixture->result.record_count); + check_size("histogram.reason-conservation", reason_total, + fixture->result.record_count); + check_memory("slot.plt", &fixture->plt_slot, &plt_before, + sizeof(plt_before)); + check_memory("slot.got", &fixture->got_slot, &got_before, + sizeof(got_before)); + slot_write_checks += 2; + return status; +} + +static void fixture_destroy(shadow_fixture_t *fixture) +{ + kzt_guest_registry_destroy(&fixture->registry); +} + +static kzt_runtime_candidate_shadow_record_t *record_for_reloc( + shadow_fixture_t *fixture, + kzt_patch_relocation_type_t reloc_type) +{ + size_t i; + + for (i = 0; i < fixture->result.record_count; ++i) { + if (fixture->records[i].decision.reloc_type == reloc_type) { + return &fixture->records[i]; + } + } + + return NULL; +} + +static void test_w01_owner_match(void) +{ + shadow_fixture_t fixture; + + ++tests_run; + fixture_init(&fixture, 0); + check_int("W01.run", fixture_run(&fixture), 0); + check_int("W01.status", fixture.result.status, + KZT_RUNTIME_CANDIDATE_SHADOW_OK); + check_size("W01.records", fixture.result.record_count, 1); + check_int("W01.owner", fixture.records[0].decision.owner_match, + KZT_PATCH_OWNER_MATCH); + check_int("W01.decision", fixture.records[0].decision.kind, + KZT_PATCH_DECISION_APPROVED); + check_int("W01.reason", fixture.records[0].decision.reason, + KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE); + check_size("W01.mutation-calls", fixture.bridge.mutation_calls, 0); + fixture_destroy(&fixture); +} + +static void test_w02_owner_mismatch(void) +{ + shadow_fixture_t fixture; + + ++tests_run; + fixture_init(&fixture, 0); + fixture.plt_slot.value = TEST_OWNER_B_BASE + 0x40; + check_int("W02.run", fixture_run(&fixture), 0); + check_int("W02.owner", fixture.records[0].decision.owner_match, + KZT_PATCH_OWNER_MISMATCH); + check_int("W02.decision", fixture.records[0].decision.kind, + KZT_PATCH_DECISION_REJECTED); + check_int("W02.reason", fixture.records[0].decision.reason, + KZT_PATCH_REASON_POLICY_OWNER_MISMATCH); + fixture_destroy(&fixture); +} + +static void test_w03_owner_unknown(void) +{ + shadow_fixture_t fixture; + + ++tests_run; + fixture_init(&fixture, 0); + fixture.plt_slot.value = 0x7f000040ULL; + check_int("W03.run", fixture_run(&fixture), 0); + check_int("W03.owner", fixture.records[0].decision.owner_match, + KZT_PATCH_OWNER_UNKNOWN); + check_int("W03.decision", fixture.records[0].decision.kind, + KZT_PATCH_DECISION_UNSUPPORTED); + check_int("W03.reason", fixture.records[0].decision.reason, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_OWNER); + fixture_destroy(&fixture); +} + +static void test_w04_no_manifest(void) +{ + shadow_fixture_t fixture; + + ++tests_run; + fixture_init(&fixture, 0); + fixture.input.wrapper_manifest = NULL; + check_int("W04.run", fixture_run(&fixture), 0); + check_int("W04.wrapper", fixture.records[0].decision.wrapper_match, + KZT_PATCH_WRAPPER_NO_MANIFEST); + check_int("W04.decision", fixture.records[0].decision.kind, + KZT_PATCH_DECISION_UNSUPPORTED); + check_int("W04.reason", fixture.records[0].decision.reason, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_WRAPPER_MANIFEST); + check_size("W04.cache-calls", fixture.bridge.check_calls, 0); + check_size("W04.mutation-calls", fixture.bridge.mutation_calls, 0); + fixture_destroy(&fixture); +} + +static void test_w05_bridge_zero_never_adds(void) +{ + shadow_fixture_t fixture; + + ++tests_run; + fixture_init(&fixture, 0); + fixture.bridge.cache_target = 0; + check_int("W05.run", fixture_run(&fixture), 0); + check_size("W05.cache-calls", fixture.bridge.check_calls, 1); + check_size("W05.mutation-calls", fixture.bridge.mutation_calls, 0); + check_ulong("W05.bridge", fixture.records[0].decision.bridge_target, 0); + check_int("W05.decision", fixture.records[0].decision.kind, + KZT_PATCH_DECISION_UNSUPPORTED); + check_int("W05.reason", fixture.records[0].decision.reason, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_BRIDGE_TARGET); + fixture_destroy(&fixture); +} + +static void test_w06_plt_and_got_stubs_are_deferred(void) +{ + shadow_fixture_t fixture; + kzt_runtime_candidate_shadow_record_t *plt; + kzt_runtime_candidate_shadow_record_t *got; + + ++tests_run; + fixture_init(&fixture, 1); + fixture.plt_slot.value = TEST_SOURCE_BASE + 0x500; + fixture.got_slot.value = TEST_SOURCE_BASE + 0x600; + check_int("W06.run", fixture_run(&fixture), 0); + check_size("W06.records", fixture.result.record_count, 2); + plt = record_for_reloc(&fixture, KZT_PATCH_RELOCATION_JUMP_SLOT); + got = record_for_reloc(&fixture, KZT_PATCH_RELOCATION_GLOB_DAT); + check_int("W06.plt-present", plt != NULL, 1); + check_int("W06.got-present", got != NULL, 1); + if (plt) { + check_int("W06.plt.decision", plt->decision.kind, + KZT_PATCH_DECISION_DEFERRED); + check_int("W06.plt.reason", plt->decision.reason, + KZT_PATCH_REASON_DEFERRED_LAZY_BINDING); + } + if (got) { + check_int("W06.got.decision", got->decision.kind, + KZT_PATCH_DECISION_DEFERRED); + check_int("W06.got.reason", got->decision.reason, + KZT_PATCH_REASON_DEFERRED_LAZY_BINDING); + check_int("W06.got.observe-only", got->observe_only, 1); + } + check_size("W06.owner-lookups", fixture.expected.calls, 0); + check_size("W06.mutation-calls", fixture.bridge.mutation_calls, 0); + fixture_destroy(&fixture); + + fixture_init(&fixture, 1); + fixture.plt_slot.value = TEST_SOURCE_BASE + 0x8000; + fixture.got_slot.value = TEST_SOURCE_BASE + 0x8100; + check_int("W06.same-dso-nonstub.run", fixture_run(&fixture), 0); + check_size("W06.same-dso-nonstub.records", + fixture.result.record_count, 2); + plt = record_for_reloc(&fixture, KZT_PATCH_RELOCATION_JUMP_SLOT); + got = record_for_reloc(&fixture, KZT_PATCH_RELOCATION_GLOB_DAT); + if (plt) { + check_int("W06.same-dso-nonstub.plt-not-deferred", + plt->decision.kind == KZT_PATCH_DECISION_DEFERRED, 0); + } + if (got) { + check_int("W06.same-dso-nonstub.got-not-deferred", + got->decision.kind == KZT_PATCH_DECISION_DEFERRED, 0); + } + check_size("W06.same-dso-nonstub.classifier-calls", + fixture.stub_classifier.calls, 2); + check_size("W06.same-dso-nonstub.owner-lookups", + fixture.expected.calls, 2); + fixture_destroy(&fixture); + + fixture_init(&fixture, 0); + fixture.plt_slot.value = TEST_SOURCE_BASE + 0x500; + fixture.input.classify_stub = NULL; + check_int("W06.no-evidence.run", fixture_run(&fixture), 0); + check_int("W06.no-evidence.not-deferred", + fixture.records[0].decision.kind == + KZT_PATCH_DECISION_DEFERRED, + 0); + check_size("W06.no-evidence.owner-lookups", fixture.expected.calls, 1); + fixture_destroy(&fixture); +} + +static void test_w07_expected_bridge_slot_exclude_legacy(void) +{ + shadow_fixture_t fixture; + uintptr_t legacy_target = TEST_LEGACY_TARGET; + + ++tests_run; + fixture_init(&fixture, 0); + fixture.plt_slot.value = TEST_OWNER_A_BASE + 0x40; + fixture.expected.target = TEST_OWNER_A_BASE + 0x80; + fixture.bridge.cache_target = TEST_OWNER_B_BASE + 0x88; + check_int("W07.run", fixture_run(&fixture), 0); + check_ulong("W07.slot-current", + fixture.records[0].decision.slot_current_value, + TEST_OWNER_A_BASE + 0x40); + check_ulong("W07.expected-owner", + fixture.records[0].owner_resolution.expected_owner + .link_map_addr, + TEST_OWNER_A_LINK_MAP); + check_ulong("W07.current-owner", + fixture.records[0].owner_resolution.current_owner + .link_map_addr, + TEST_OWNER_A_LINK_MAP); + check_ulong("W07.bridge", + fixture.records[0].decision.bridge_target, + TEST_OWNER_B_BASE + 0x88); + check_ulong("W07.cache-key", fixture.bridge.last_native_symbol, + TEST_NATIVE_PUTS); + check_int("W07.audit-only", fixture.records[0].audit_only, 1); + check_int("W07.legacy-not-consumed", + fixture.records[0].legacy_target_consumed, 0); + check_int("W07.legacy-not-slot", + legacy_target == + fixture.records[0].decision.slot_current_value, + 0); + check_int("W07.legacy-not-bridge", + legacy_target == + fixture.records[0].decision.bridge_target, + 0); + check_int("W07.legacy-not-expected", + legacy_target == fixture.expected.target, 0); + check_int("W07.decision", fixture.records[0].decision.kind, + KZT_PATCH_DECISION_APPROVED); + check_size("W07.mutation-calls", fixture.bridge.mutation_calls, 0); + fixture_destroy(&fixture); +} + +static void test_w08_bridge_ops_missing(void) +{ + shadow_fixture_t fixture; + + ++tests_run; + fixture_init(&fixture, 0); + fixture.input.bridge_ops = NULL; + check_int("W08.run", fixture_run(&fixture), 0); + check_int("W08.wrapper", fixture.records[0].decision.wrapper_match, + KZT_PATCH_WRAPPER_VERSION_MATCH); + check_ulong("W08.bridge", fixture.records[0].decision.bridge_target, 0); + check_int("W08.decision", fixture.records[0].decision.kind, + KZT_PATCH_DECISION_UNSUPPORTED); + check_int("W08.reason", fixture.records[0].decision.reason, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_BRIDGE_TARGET); + check_size("W08.cache-calls", fixture.bridge.check_calls, 0); + check_size("W08.mutation-calls", fixture.bridge.mutation_calls, 0); + fixture_destroy(&fixture); +} + +static void test_w09_generation_change_fails_open(void) +{ + shadow_fixture_t fixture; + unsigned long generation; + + ++tests_run; + fixture_init(&fixture, 0); + fixture.collector_request.dynamic_view_generation = + fixture.source.generation + 1; + check_int("W09.run", fixture_run(&fixture), 0); + check_int("W09.status", fixture.result.status, + KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN); + check_int("W09.reason", fixture.result.reason, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_OBJECT_GENERATION_CHANGED); + check_size("W09.candidates", fixture.result.candidate_count, 0); + check_size("W09.records", fixture.result.record_count, 0); + check_size("W09.owner-lookups", fixture.expected.calls, 0); + check_size("W09.cache-calls", fixture.bridge.check_calls, 0); + check_size("W09.mutation-calls", fixture.bridge.mutation_calls, 0); + fixture_destroy(&fixture); + + fixture_init(&fixture, 0); + fixture.source.generation = 0; + check_int("W09.zero.run", fixture_run(&fixture), 0); + check_int("W09.zero.status", fixture.result.status, + KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN); + check_size("W09.zero.owner-lookups", fixture.expected.calls, 0); + fixture_destroy(&fixture); + + fixture_init(&fixture, 0); + fixture.source.link_map_addr = TEST_SOURCE_LINK_MAP + 1; + check_int("W09.disappeared.run", fixture_run(&fixture), 0); + check_int("W09.disappeared.status", fixture.result.status, + KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN); + check_size("W09.disappeared.owner-lookups", fixture.expected.calls, 0); + fixture_destroy(&fixture); + + fixture_init(&fixture, 0); + fixture.generation_query.response_count = 1; + fixture.generation_query.results[0] = -1; + fixture.input.query_generation = query_test_generation; + fixture.input.generation_query_opaque = &fixture.generation_query; + check_int("W09.lookup-failed.run", fixture_run(&fixture), 0); + check_int("W09.lookup-failed.status", fixture.result.status, + KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN); + fixture_destroy(&fixture); + + fixture_init(&fixture, 0); + fixture.generation_query.response_count = 1; + fixture.generation_query.results[0] = -2; + fixture.input.query_generation = query_test_generation; + fixture.input.generation_query_opaque = &fixture.generation_query; + check_int("W09.nonunique.run", fixture_run(&fixture), 0); + check_int("W09.nonunique.status", fixture.result.status, + KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN); + fixture_destroy(&fixture); + + fixture_init(&fixture, 0); + generation = fixture.source.generation; + fixture.generation_query.response_count = 2; + fixture.generation_query.generations[0] = generation; + fixture.generation_query.generations[1] = generation + 1; + fixture.input.query_generation = query_test_generation; + fixture.input.generation_query_opaque = &fixture.generation_query; + check_int("W09.cross-generation.run", fixture_run(&fixture), 0); + check_int("W09.cross-generation.status", fixture.result.status, + KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN); + check_int("W09.cross-generation.reason", fixture.result.reason, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_OBJECT_GENERATION_CHANGED); + check_size("W09.cross-generation.queries", + fixture.generation_query.calls, 2); + check_size("W09.cross-generation.owner-lookups", + fixture.expected.calls, 1); + fixture_destroy(&fixture); +} + +static void test_w10_rel_fails_open(void) +{ + shadow_fixture_t fixture; + + ++tests_run; + fixture_init(&fixture, 0); + fixture.view.pltrel = scalar_field(DT_REL); + check_int("W10.run", fixture_run(&fixture), 0); + check_int("W10.status", fixture.result.status, + KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN); + check_int("W10.reason", fixture.result.reason, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_COLLECTOR_FAIL_OPEN); + check_int("W10.collector-reason", fixture.result.collector_result.reason, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_DT_REL_UNSUPPORTED); + check_int("W10.table", fixture.result.collector_result.table_kind, + KZT_PATCH_TABLE_PLT_REL); + check_size("W10.records", fixture.result.record_count, 0); + check_size("W10.mutation-calls", fixture.bridge.mutation_calls, 0); + fixture_destroy(&fixture); +} + +static void test_w10b_dynamic_rel_fails_open(void) +{ + shadow_fixture_t fixture; + + ++tests_run; + fixture_init(&fixture, 0); + fixture.view.rel = runtime_field(TEST_RELA_ADDR + 0x1000); + fixture.view.relsz = scalar_field(sizeof(Elf64_Rel)); + fixture.view.relent = scalar_field(sizeof(Elf64_Rel)); + check_int("W10b.run", fixture_run(&fixture), 0); + check_int("W10b.status", fixture.result.status, + KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN); + check_int("W10b.reason", fixture.result.reason, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_COLLECTOR_FAIL_OPEN); + check_int("W10b.collector-reason", fixture.result.collector_result.reason, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_DT_REL_UNSUPPORTED); + check_int("W10b.table", fixture.result.collector_result.table_kind, + KZT_PATCH_TABLE_REL); + check_size("W10b.records", fixture.result.record_count, 0); + check_size("W10b.mutation-calls", fixture.bridge.mutation_calls, 0); + fixture_destroy(&fixture); +} + +static void test_w11_glob_dat_is_observe_only(void) +{ + shadow_fixture_t fixture; + kzt_runtime_candidate_shadow_record_t *plt; + kzt_runtime_candidate_shadow_record_t *got; + + ++tests_run; + fixture_init(&fixture, 1); + check_int("W11.run", fixture_run(&fixture), 0); + check_int("W11.status", fixture.result.status, + KZT_RUNTIME_CANDIDATE_SHADOW_OK); + check_size("W11.records", fixture.result.record_count, 2); + check_size("W11.eligible", fixture.result.eligible_count, 1); + check_size("W11.observe-only", fixture.result.observe_only_count, 1); + check_size("W11.approved-histogram", + fixture.result.decision_histogram[ + KZT_PATCH_DECISION_APPROVED], 2); + plt = record_for_reloc(&fixture, KZT_PATCH_RELOCATION_JUMP_SLOT); + got = record_for_reloc(&fixture, KZT_PATCH_RELOCATION_GLOB_DAT); + check_int("W11.plt-present", plt != NULL, 1); + check_int("W11.got-present", got != NULL, 1); + if (plt) { + check_int("W11.plt.eligible", plt->eligible, 1); + check_int("W11.plt.observe-only", plt->observe_only, 0); + } + if (got) { + check_int("W11.got.decision", got->decision.kind, + KZT_PATCH_DECISION_APPROVED); + check_int("W11.got.eligible", got->eligible, 0); + check_int("W11.got.observe-only", got->observe_only, 1); + } + check_size("W11.mutation-calls", fixture.bridge.mutation_calls, 0); + fixture_destroy(&fixture); +} + +static void test_w12_candidate_capacity_fails_open(void) +{ + shadow_fixture_t fixture; + kzt_patch_candidate_t zero_candidates[2] = { 0 }; + + ++tests_run; + fixture_init(&fixture, 1); + fixture.collector_request.candidate_capacity = 1; + check_int("W12.run", fixture_run(&fixture), 0); + check_int("W12.status", fixture.result.status, + KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN); + check_int("W12.collector-reason", fixture.result.collector_result.reason, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_CAPACITY_EXCEEDED); + check_memory("W12.candidates-cleared", fixture.candidates, + zero_candidates, sizeof(zero_candidates)); + check_size("W12.records", fixture.result.record_count, 0); + check_size("W12.mutation-calls", fixture.bridge.mutation_calls, 0); + fixture_destroy(&fixture); +} + +static void test_w13_record_capacity_fails_open(void) +{ + shadow_fixture_t fixture; + kzt_patch_candidate_t zero_candidates[2] = { 0 }; + kzt_runtime_candidate_shadow_record_t zero_records[2] = { 0 }; + + ++tests_run; + fixture_init(&fixture, 1); + fixture.input.record_capacity = 1; + check_int("W13.run", fixture_run(&fixture), 0); + check_int("W13.status", fixture.result.status, + KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN); + check_int("W13.reason", fixture.result.reason, + KZT_RUNTIME_CANDIDATE_SHADOW_REASON_RECORD_CAPACITY_EXCEEDED); + check_memory("W13.candidates-cleared", fixture.candidates, + zero_candidates, sizeof(zero_candidates)); + check_memory("W13.records-cleared", fixture.records, + zero_records, sizeof(zero_records)); + check_size("W13.mutation-calls", fixture.bridge.mutation_calls, 0); + fixture_destroy(&fixture); +} + +static void test_w14_relocation_read_failure(void) +{ + shadow_fixture_t fixture; + + ++tests_run; + fixture_init(&fixture, 0); + fixture.memory.fail_addr = TEST_PLT_RELA_ADDR; + check_int("W14.run", fixture_run(&fixture), 0); + check_int("W14.status", fixture.result.status, + KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN); + check_int("W14.collector-reason", fixture.result.collector_result.reason, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_RELOCATION_READ_FAILED); + check_ulong("W14.read-error", + fixture.result.collector_result.read_error_addr, + TEST_PLT_RELA_ADDR); + check_size("W14.records", fixture.result.record_count, 0); + check_size("W14.mutation-calls", fixture.bridge.mutation_calls, 0); + fixture_destroy(&fixture); +} + +static void test_w15_slot_read_failure(void) +{ + shadow_fixture_t fixture; + + ++tests_run; + fixture_init(&fixture, 0); + fixture.memory.fail_addr = TEST_PLT_SLOT_ADDR; + check_int("W15.run", fixture_run(&fixture), 0); + check_int("W15.status", fixture.result.status, + KZT_RUNTIME_CANDIDATE_SHADOW_FAIL_OPEN); + check_int("W15.collector-reason", fixture.result.collector_result.reason, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_SLOT_READ_FAILED); + check_ulong("W15.read-error", + fixture.result.collector_result.read_error_addr, + TEST_PLT_SLOT_ADDR); + check_size("W15.records", fixture.result.record_count, 0); + check_size("W15.mutation-calls", fixture.bridge.mutation_calls, 0); + fixture_destroy(&fixture); +} + +int main(void) +{ + test_w01_owner_match(); + test_w02_owner_mismatch(); + test_w03_owner_unknown(); + test_w04_no_manifest(); + test_w05_bridge_zero_never_adds(); + test_w06_plt_and_got_stubs_are_deferred(); + test_w07_expected_bridge_slot_exclude_legacy(); + test_w08_bridge_ops_missing(); + test_w09_generation_change_fails_open(); + test_w10_rel_fails_open(); + test_w10b_dynamic_rel_fails_open(); + test_w11_glob_dat_is_observe_only(); + test_w12_candidate_capacity_fails_open(); + test_w13_record_capacity_fails_open(); + test_w14_relocation_read_failure(); + test_w15_slot_read_failure(); + + if (failures) { + fprintf(stderr, + "kzt-runtime-candidate-enrichment-shadow: " + "%d failure(s), %lu scenario(s)\n", + failures, (unsigned long)tests_run); + return 1; + } + + printf("kzt-runtime-candidate-enrichment-shadow: " + "%lu scenario(s) passed, %lu guarded slot checks\n", + (unsigned long)tests_run, (unsigned long)slot_write_checks); + return 0; +} diff --git a/tests/unit/kzt/test_runtime_got_plt_candidate.c b/tests/unit/kzt/test_runtime_got_plt_candidate.c new file mode 100644 index 00000000000..ac057ec4eaf --- /dev/null +++ b/tests/unit/kzt/test_runtime_got_plt_candidate.c @@ -0,0 +1,933 @@ +#include +#include + +#include "elf.h" +#include "target/i386/latx/include/kzt_runtime_got_plt_candidate.h" + +#define TEST_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#define TEST_R_INFO(sym, type) ((((uint64_t)(sym)) << 32) | (type)) + +enum { + TEST_PLT_RELA_ADDR = 0x7000100000ULL, + TEST_RELA_ADDR = 0x7000200000ULL, + TEST_STRTAB_ADDR = 0x7000300000ULL, + TEST_SYMTAB_ADDR = 0x7000400000ULL, + TEST_VERSYM_ADDR = 0x7000500000ULL, + TEST_VERNEED_ADDR = 0x7000600000ULL, + TEST_LOAD_BIAS = 0x7000000000ULL, + TEST_STR_PUTS = 1, + TEST_STR_ERRNO = 6, + TEST_STR_GLIBC = 12, + TEST_STR_LIBFOO = 24, + TEST_JUMP_SLOT_SYMBOL = 11, + TEST_GLOB_DAT_SYMBOL = 22, +}; + +static const char test_dynstr[] = + "\0puts\0errno\0GLIBC_2.2.5\0LIBFOO_1.0\0"; + +typedef struct version_need_image { + Elf64_Verneed need; + Elf64_Vernaux aux[2]; +} version_need_image_t; + +typedef struct symbol_version_fixture { + Elf64_Sym dynsym[23]; + Elf64_Half versym[23]; + version_need_image_t verneed; +} symbol_version_fixture_t; + +typedef struct fake_region { + uintptr_t guest_base; + const void *host_base; + size_t size; +} fake_region_t; + +typedef struct fake_memory { + fake_region_t regions[16]; + size_t region_count; + uintptr_t fail_addr; + int read_calls; +} fake_memory_t; + +static int failures; + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_size(const char *name, size_t got, size_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lu expected %lu\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_ulong(const char *name, + unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, expected); + ++failures; +} + +static void check_str(const char *name, const char *got, const char *expected) +{ + if ((!got && !expected) || + (got && expected && !strcmp(got, expected))) { + return; + } + + fprintf(stderr, "%s: got '%s' expected '%s'\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static void add_region(fake_memory_t *memory, + uintptr_t guest_base, + const void *host_base, + size_t size) +{ + fake_region_t *region = &memory->regions[memory->region_count++]; + + region->guest_base = guest_base; + region->host_base = host_base; + region->size = size; +} + +static int fake_read_memory(uintptr_t guest_addr, + void *dst, + size_t size, + void *opaque) +{ + fake_memory_t *memory = opaque; + size_t i; + + ++memory->read_calls; + if (memory->fail_addr && guest_addr == memory->fail_addr) { + return -1; + } + + for (i = 0; i < memory->region_count; ++i) { + const fake_region_t *region = &memory->regions[i]; + uintptr_t offset; + + if (guest_addr < region->guest_base) { + continue; + } + + offset = guest_addr - region->guest_base; + if (offset > region->size || size > region->size - offset) { + continue; + } + + memcpy(dst, (const char *)region->host_base + offset, size); + return 0; + } + + return -1; +} + +static kzt_guest_link_map_reader_ops_t fake_ops(fake_memory_t *memory) +{ + kzt_guest_link_map_reader_ops_t ops = { + .read_memory = fake_read_memory, + .opaque = memory, + }; + + return ops; +} + +static kzt_guest_dynamic_field_t runtime_field(uint64_t value) +{ + return (kzt_guest_dynamic_field_t) { + .present = 1, + .value = value, + .address_semantics = KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS, + }; +} + +static kzt_guest_dynamic_field_t scalar_field(uint64_t value) +{ + return (kzt_guest_dynamic_field_t) { + .present = 1, + .value = value, + .address_semantics = KZT_GUEST_DYNAMIC_SCALAR, + }; +} + +static kzt_guest_dynamic_view_t base_view(void) +{ + return (kzt_guest_dynamic_view_t) { + .dynamic_addr = 0x7000002000ULL, + .load_bias = TEST_LOAD_BIAS, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .has_null = 1, + }; +} + +static kzt_patch_object_ref_t source_ref(void) +{ + return (kzt_patch_object_ref_t) { + .known = 1, + .link_map_addr = 0x7000001000ULL, + .map_start = TEST_LOAD_BIAS, + .map_end = TEST_LOAD_BIAS + 0x100000, + .generation = 42, + .soname = "libcandidate.so", + .path = "/guest/libcandidate.so", + }; +} + +static kzt_runtime_got_plt_candidate_request_t make_request( + const kzt_guest_dynamic_view_t *view, + const kzt_guest_link_map_reader_ops_t *ops, + const kzt_patch_object_ref_t *source, + kzt_patch_candidate_t *candidates, + size_t capacity) +{ + static char string_storage[512]; + + memset(string_storage, 0, sizeof(string_storage)); + return (kzt_runtime_got_plt_candidate_request_t) { + .view = view, + .reader_ops = ops, + .source = source, + .dynamic_view_generation = 88, + .candidates = candidates, + .candidate_capacity = capacity, + .string_storage = string_storage, + .string_storage_size = sizeof(string_storage), + }; +} + +static void add_symbol_version_fixture(fake_memory_t *memory, + kzt_guest_dynamic_view_t *view, + symbol_version_fixture_t *fixture) +{ + memset(fixture, 0, sizeof(*fixture)); + fixture->dynsym[TEST_JUMP_SLOT_SYMBOL].st_name = TEST_STR_PUTS; + fixture->dynsym[TEST_GLOB_DAT_SYMBOL].st_name = TEST_STR_ERRNO; + fixture->versym[TEST_JUMP_SLOT_SYMBOL] = 2; + fixture->versym[TEST_GLOB_DAT_SYMBOL] = 3; + fixture->verneed.need.vn_version = 1; + fixture->verneed.need.vn_cnt = 2; + fixture->verneed.need.vn_aux = sizeof(Elf64_Verneed); + fixture->verneed.need.vn_next = 0; + fixture->verneed.aux[0].vna_other = 2; + fixture->verneed.aux[0].vna_name = TEST_STR_GLIBC; + fixture->verneed.aux[0].vna_next = sizeof(Elf64_Vernaux); + fixture->verneed.aux[1].vna_other = 3; + fixture->verneed.aux[1].vna_name = TEST_STR_LIBFOO; + fixture->verneed.aux[1].vna_next = 0; + + view->symtab = runtime_field(TEST_SYMTAB_ADDR); + view->syment = scalar_field(sizeof(Elf64_Sym)); + view->strtab = runtime_field(TEST_STRTAB_ADDR); + view->strsz = scalar_field(sizeof(test_dynstr)); + view->versym = runtime_field(TEST_VERSYM_ADDR); + view->verneed = runtime_field(TEST_VERNEED_ADDR); + view->verneednum = scalar_field(1); + + add_region(memory, TEST_SYMTAB_ADDR, fixture->dynsym, + sizeof(fixture->dynsym)); + add_region(memory, TEST_STRTAB_ADDR, test_dynstr, sizeof(test_dynstr)); + add_region(memory, TEST_VERSYM_ADDR, fixture->versym, + sizeof(fixture->versym)); + add_region(memory, TEST_VERNEED_ADDR, &fixture->verneed, + sizeof(fixture->verneed)); +} + +static void check_fail_open( + const char *name, + const kzt_runtime_got_plt_candidate_result_t *result, + kzt_runtime_got_plt_candidate_reason_t reason, + kzt_patch_reason_t patch_reason) +{ + char field[128]; + + snprintf(field, sizeof(field), "%s.status", name); + check_int(field, result->status, + KZT_RUNTIME_GOT_PLT_CANDIDATE_FAIL_OPEN); + snprintf(field, sizeof(field), "%s.reason", name); + check_int(field, result->reason, reason); + snprintf(field, sizeof(field), "%s.patch-present", name); + check_int(field, result->patch_reason_present, 1); + snprintf(field, sizeof(field), "%s.patch-reason", name); + check_int(field, result->patch_reason, patch_reason); + snprintf(field, sizeof(field), "%s.count", name); + check_size(field, result->candidate_count, 0); +} + +static void test_happy_path_enumerates_jump_slot_and_glob_dat(void) +{ + Elf64_Rela plt_relas[] = { + { + .r_offset = 0x3010, + .r_info = TEST_R_INFO(TEST_JUMP_SLOT_SYMBOL, + R_X86_64_JUMP_SLOT), + .r_addend = 0, + }, + }; + Elf64_Rela relas[] = { + { + .r_offset = 0x4020, + .r_info = TEST_R_INFO(TEST_GLOB_DAT_SYMBOL, + R_X86_64_GLOB_DAT), + .r_addend = 0, + }, + }; + uint64_t plt_slot = 0x7100001000ULL; + uint64_t rela_slot = 0x7200002000ULL; + fake_memory_t memory = { 0 }; + symbol_version_fixture_t symbols; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_patch_object_ref_t source = source_ref(); + kzt_guest_dynamic_view_t view = base_view(); + kzt_patch_candidate_t candidates[4]; + kzt_runtime_got_plt_candidate_request_t request; + kzt_runtime_got_plt_candidate_result_t result; + + view.jmprel = runtime_field(TEST_PLT_RELA_ADDR); + view.pltrelsz = scalar_field(sizeof(plt_relas)); + view.pltrel = scalar_field(DT_RELA); + view.rela = runtime_field(TEST_RELA_ADDR); + view.relasz = scalar_field(sizeof(relas)); + view.relaent = scalar_field(sizeof(Elf64_Rela)); + add_symbol_version_fixture(&memory, &view, &symbols); + + add_region(&memory, TEST_PLT_RELA_ADDR, plt_relas, sizeof(plt_relas)); + add_region(&memory, TEST_RELA_ADDR, relas, sizeof(relas)); + add_region(&memory, TEST_LOAD_BIAS + plt_relas[0].r_offset, + &plt_slot, sizeof(plt_slot)); + add_region(&memory, TEST_LOAD_BIAS + relas[0].r_offset, + &rela_slot, sizeof(rela_slot)); + + request = make_request(&view, &ops, &source, candidates, + TEST_ARRAY_SIZE(candidates)); + check_int("happy.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_int("happy.status", result.status, + KZT_RUNTIME_GOT_PLT_CANDIDATE_OK); + check_int("happy.reason", result.reason, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_NONE); + check_int("happy.patch-present", result.patch_reason_present, 0); + check_size("happy.count", result.candidate_count, 2); + + check_int("happy.plt.table", candidates[0].table_kind, + KZT_PATCH_TABLE_PLT_RELA); + check_size("happy.plt.index", candidates[0].entry_index, 0); + check_ulong("happy.plt.entry", candidates[0].entry_addr, + TEST_PLT_RELA_ADDR); + check_int("happy.plt.reloc", candidates[0].reloc_type, + KZT_PATCH_RELOCATION_JUMP_SLOT); + check_ulong("happy.plt.slot", candidates[0].slot_addr, + TEST_LOAD_BIAS + plt_relas[0].r_offset); + check_int("happy.plt.current-present", + candidates[0].slot_current_value_present, 1); + check_ulong("happy.plt.current", candidates[0].slot_current_value, + plt_slot); + check_ulong("happy.plt.symbol", candidates[0].symbol_index, + TEST_JUMP_SLOT_SYMBOL); + check_str("happy.plt.symbol-name", candidates[0].symbol_name, + "puts"); + check_int("happy.plt.version-evidence", + candidates[0].version_evidence, + KZT_SYMBOL_VERSION_VERSIONED); + check_str("happy.plt.version", candidates[0].version, + "GLIBC_2.2.5"); + check_ulong("happy.plt.source", candidates[0].source.link_map_addr, + source.link_map_addr); + check_ulong("happy.plt.source-generation", + candidates[0].source.generation, source.generation); + check_ulong("happy.plt.dynamic", candidates[0].dynamic_addr, + view.dynamic_addr); + check_ulong("happy.plt.load-bias", candidates[0].load_bias, + view.load_bias); + check_ulong("happy.plt.generation", + candidates[0].dynamic_view_generation, 88); + check_int("happy.plt.available", + candidates[0].dynamic_view_available, 1); + + check_int("happy.rela.table", candidates[1].table_kind, + KZT_PATCH_TABLE_RELA); + check_size("happy.rela.index", candidates[1].entry_index, 0); + check_ulong("happy.rela.entry", candidates[1].entry_addr, + TEST_RELA_ADDR); + check_int("happy.rela.reloc", candidates[1].reloc_type, + KZT_PATCH_RELOCATION_GLOB_DAT); + check_ulong("happy.rela.slot", candidates[1].slot_addr, + TEST_LOAD_BIAS + relas[0].r_offset); + check_ulong("happy.rela.current", candidates[1].slot_current_value, + rela_slot); + check_ulong("happy.rela.symbol", candidates[1].symbol_index, + TEST_GLOB_DAT_SYMBOL); + check_str("happy.rela.symbol-name", candidates[1].symbol_name, + "errno"); + check_str("happy.rela.version", candidates[1].version, + "LIBFOO_1.0"); + check_str("happy.status-name", + kzt_runtime_got_plt_candidate_status_name(result.status), + "OK"); + check_str("happy.reason-name", + kzt_runtime_got_plt_candidate_reason_name(result.reason), + "NONE"); + + /* The production shadow route audits one selected relocation with bounded + * stack storage, even when the object has further candidate tables. */ + { + kzt_patch_candidate_t one_candidate[1]; + + request = make_request(&view, &ops, &source, one_candidate, 1); + request.only_entry = 1; + request.only_table_kind = KZT_PATCH_TABLE_PLT_RELA; + request.only_entry_index = 0; + check_int("single.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), + 0); + check_int("single.status", result.status, + KZT_RUNTIME_GOT_PLT_CANDIDATE_OK); + check_size("single.count", result.candidate_count, 1); + check_int("single.table", one_candidate[0].table_kind, + KZT_PATCH_TABLE_PLT_RELA); + check_size("single.index", one_candidate[0].entry_index, 0); + } +} + +static void test_single_entry_reads_only_selected_relocation(void) +{ + Elf64_Rela plt_relas[3] = { + { .r_offset = 0x3010, + .r_info = TEST_R_INFO(TEST_JUMP_SLOT_SYMBOL, + R_X86_64_JUMP_SLOT) }, + { .r_offset = 0x3020, + .r_info = TEST_R_INFO(TEST_JUMP_SLOT_SYMBOL, + R_X86_64_JUMP_SLOT) }, + { .r_offset = 0x3030, + .r_info = TEST_R_INFO(TEST_JUMP_SLOT_SYMBOL, + R_X86_64_JUMP_SLOT) }, + }; + uint64_t selected_slot = 0x7100003000ULL; + fake_memory_t memory = { 0 }; + symbol_version_fixture_t symbols; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_patch_object_ref_t source = source_ref(); + kzt_guest_dynamic_view_t view = base_view(); + kzt_patch_candidate_t candidate; + kzt_runtime_got_plt_candidate_request_t request; + kzt_runtime_got_plt_candidate_result_t result; + + view.jmprel = runtime_field(TEST_PLT_RELA_ADDR); + view.pltrelsz = scalar_field(sizeof(plt_relas)); + view.pltrel = scalar_field(DT_RELA); + add_symbol_version_fixture(&memory, &view, &symbols); + add_region(&memory, TEST_PLT_RELA_ADDR, plt_relas, sizeof(plt_relas)); + add_region(&memory, TEST_LOAD_BIAS + plt_relas[2].r_offset, + &selected_slot, sizeof(selected_slot)); + memory.fail_addr = TEST_PLT_RELA_ADDR; + + request = make_request(&view, &ops, &source, &candidate, 1); + request.only_entry = 1; + request.only_table_kind = KZT_PATCH_TABLE_PLT_RELA; + request.only_entry_index = 2; + check_int("single-direct.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_int("single-direct.status", result.status, + KZT_RUNTIME_GOT_PLT_CANDIDATE_OK); + check_size("single-direct.count", result.candidate_count, 1); + check_size("single-direct.index", candidate.entry_index, 2); + check_ulong("single-direct.entry", candidate.entry_addr, + TEST_PLT_RELA_ADDR + 2 * sizeof(Elf64_Rela)); +} + +static void test_missing_dynamic_field_fails_open(void) +{ + fake_memory_t memory = { 0 }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_view_t view = base_view(); + kzt_patch_candidate_t candidates[2]; + kzt_runtime_got_plt_candidate_request_t request = + make_request(&view, &ops, NULL, candidates, TEST_ARRAY_SIZE(candidates)); + kzt_runtime_got_plt_candidate_result_t result; + + view.jmprel = runtime_field(TEST_PLT_RELA_ADDR); + view.pltrel = scalar_field(DT_RELA); + + check_int("missing.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_fail_open( + "missing", &result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MISSING_DYNAMIC_FIELD, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE); + check_int("missing.table", result.table_kind, + KZT_PATCH_TABLE_PLT_RELA); +} + +static void test_confirmed_unversioned_evidence_is_collected(void) +{ + Elf64_Rela plt_relas[] = { + { + .r_offset = 0x3010, + .r_info = TEST_R_INFO(TEST_JUMP_SLOT_SYMBOL, + R_X86_64_JUMP_SLOT), + .r_addend = 0, + }, + }; + uint64_t plt_slot = 0x7100001000ULL; + fake_memory_t memory = { 0 }; + symbol_version_fixture_t symbols; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_view_t view = base_view(); + kzt_patch_candidate_t candidates[2]; + kzt_runtime_got_plt_candidate_request_t request = + make_request(&view, &ops, NULL, candidates, TEST_ARRAY_SIZE(candidates)); + kzt_runtime_got_plt_candidate_result_t result; + + view.jmprel = runtime_field(TEST_PLT_RELA_ADDR); + view.pltrelsz = scalar_field(sizeof(plt_relas)); + view.pltrel = scalar_field(DT_RELA); + add_symbol_version_fixture(&memory, &view, &symbols); + add_region(&memory, TEST_PLT_RELA_ADDR, plt_relas, sizeof(plt_relas)); + add_region(&memory, TEST_LOAD_BIAS + plt_relas[0].r_offset, + &plt_slot, sizeof(plt_slot)); + + view.versym.present = 0; + check_int("no-versym.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_int("no-versym.status", result.status, + KZT_RUNTIME_GOT_PLT_CANDIDATE_OK); + check_size("no-versym.count", result.candidate_count, 1); + check_int("no-versym.evidence", candidates[0].version_evidence, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED); + check_int("no-versym.result-evidence", result.version_evidence, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED); + check_str("no-versym.version", candidates[0].version, NULL); + + view.versym.present = 1; + symbols.versym[TEST_JUMP_SLOT_SYMBOL] = 0; + check_int("versym-zero.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_int("versym-zero.evidence", candidates[0].version_evidence, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED); + check_str("versym-zero.version", candidates[0].version, NULL); + + symbols.versym[TEST_JUMP_SLOT_SYMBOL] = 1; + check_int("versym-one.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_int("versym-one.evidence", candidates[0].version_evidence, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED); + check_str("versym-one.version", candidates[0].version, NULL); +} + +static void test_version_read_failure_is_error_and_fails_open(void) +{ + Elf64_Rela plt_rela = { + .r_offset = 0x3010, + .r_info = TEST_R_INFO(TEST_JUMP_SLOT_SYMBOL, R_X86_64_JUMP_SLOT), + }; + uint64_t plt_slot = 0x7100001000ULL; + fake_memory_t memory = { 0 }; + symbol_version_fixture_t symbols; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_view_t view = base_view(); + kzt_patch_candidate_t candidate; + kzt_runtime_got_plt_candidate_request_t request; + kzt_runtime_got_plt_candidate_result_t result; + + view.jmprel = runtime_field(TEST_PLT_RELA_ADDR); + view.pltrelsz = scalar_field(sizeof(plt_rela)); + view.pltrel = scalar_field(DT_RELA); + add_symbol_version_fixture(&memory, &view, &symbols); + add_region(&memory, TEST_PLT_RELA_ADDR, &plt_rela, sizeof(plt_rela)); + add_region(&memory, TEST_LOAD_BIAS + plt_rela.r_offset, + &plt_slot, sizeof(plt_slot)); + memory.fail_addr = TEST_VERSYM_ADDR + + TEST_JUMP_SLOT_SYMBOL * sizeof(Elf64_Half); + request = make_request(&view, &ops, NULL, &candidate, 1); + + check_int("version-read.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_fail_open( + "version-read", &result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_VERSION_READ_FAILED, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_VERSION); + check_int("version-read.evidence", result.version_evidence, + KZT_SYMBOL_VERSION_ERROR); +} + +static void test_missing_version_definition_is_error_and_fails_open(void) +{ + Elf64_Rela plt_rela = { + .r_offset = 0x3010, + .r_info = TEST_R_INFO(TEST_JUMP_SLOT_SYMBOL, R_X86_64_JUMP_SLOT), + }; + uint64_t plt_slot = 0x7100001000ULL; + fake_memory_t memory = { 0 }; + symbol_version_fixture_t symbols; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_view_t view = base_view(); + kzt_patch_candidate_t candidate; + kzt_runtime_got_plt_candidate_request_t request; + kzt_runtime_got_plt_candidate_result_t result; + + view.jmprel = runtime_field(TEST_PLT_RELA_ADDR); + view.pltrelsz = scalar_field(sizeof(plt_rela)); + view.pltrel = scalar_field(DT_RELA); + add_symbol_version_fixture(&memory, &view, &symbols); + view.verneed.present = 0; + view.verneednum.present = 0; + add_region(&memory, TEST_PLT_RELA_ADDR, &plt_rela, sizeof(plt_rela)); + add_region(&memory, TEST_LOAD_BIAS + plt_rela.r_offset, + &plt_slot, sizeof(plt_slot)); + request = make_request(&view, &ops, NULL, &candidate, 1); + + check_int("missing-version-definition.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_fail_open( + "missing-version-definition", &result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_SYMBOL_VERSION, + KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_VERSION); + check_int("missing-version-definition.evidence", + result.version_evidence, KZT_SYMBOL_VERSION_ERROR); +} + +static void test_dt_rel_is_unsupported_and_fails_open(void) +{ + fake_memory_t memory = { 0 }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_view_t view = base_view(); + kzt_patch_candidate_t candidates[2]; + kzt_runtime_got_plt_candidate_request_t request = + make_request(&view, &ops, NULL, candidates, TEST_ARRAY_SIZE(candidates)); + kzt_runtime_got_plt_candidate_result_t result; + + view.rel = runtime_field(0x7000300000ULL); + view.relsz = scalar_field(sizeof(Elf64_Rel)); + view.relent = scalar_field(sizeof(Elf64_Rel)); + + check_int("dt-rel.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_fail_open( + "dt-rel", &result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_DT_REL_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNSUPPORTED_RELOCATION); + check_int("dt-rel.table", result.table_kind, KZT_PATCH_TABLE_REL); + check_str("dt-rel.reason-name", + kzt_runtime_got_plt_candidate_reason_name(result.reason), + "DT_REL_UNSUPPORTED"); +} + +static void test_non_divisible_size_fails_open(void) +{ + fake_memory_t memory = { 0 }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_view_t view = base_view(); + kzt_patch_candidate_t candidates[2]; + kzt_runtime_got_plt_candidate_request_t request = + make_request(&view, &ops, NULL, candidates, TEST_ARRAY_SIZE(candidates)); + kzt_runtime_got_plt_candidate_result_t result; + + view.rela = runtime_field(TEST_RELA_ADDR); + view.relasz = scalar_field(sizeof(Elf64_Rela) + 1); + view.relaent = scalar_field(sizeof(Elf64_Rela)); + + check_int("non-divisible.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_fail_open( + "non-divisible", &result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_MALFORMED_TABLE, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE); + check_int("non-divisible.table", result.table_kind, + KZT_PATCH_TABLE_RELA); +} + +static void test_relocation_reader_failure_fails_open(void) +{ + Elf64_Rela plt_relas[] = { + { + .r_offset = 0x3010, + .r_info = TEST_R_INFO(TEST_JUMP_SLOT_SYMBOL, + R_X86_64_JUMP_SLOT), + .r_addend = 0, + }, + }; + fake_memory_t memory = { 0 }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_view_t view = base_view(); + kzt_patch_candidate_t candidates[2]; + kzt_runtime_got_plt_candidate_request_t request = + make_request(&view, &ops, NULL, candidates, TEST_ARRAY_SIZE(candidates)); + kzt_runtime_got_plt_candidate_result_t result; + + view.jmprel = runtime_field(TEST_PLT_RELA_ADDR); + view.pltrelsz = scalar_field(sizeof(plt_relas)); + view.pltrel = scalar_field(DT_RELA); + memory.fail_addr = TEST_PLT_RELA_ADDR; + add_region(&memory, TEST_PLT_RELA_ADDR, plt_relas, sizeof(plt_relas)); + + check_int("reader-fail.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_fail_open( + "reader-fail", &result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_RELOCATION_READ_FAILED, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE); + check_ulong("reader-fail.error-addr", result.read_error_addr, + TEST_PLT_RELA_ADDR); + check_str("reader-fail.reason-name", + kzt_runtime_got_plt_candidate_reason_name(result.reason), + "RELOCATION_READ_FAILED"); +} + +static void test_slot_reader_failure_fails_open(void) +{ + Elf64_Rela plt_relas[] = { + { + .r_offset = 0x3010, + .r_info = TEST_R_INFO(TEST_JUMP_SLOT_SYMBOL, + R_X86_64_JUMP_SLOT), + .r_addend = 0, + }, + }; + fake_memory_t memory = { 0 }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_view_t view = base_view(); + kzt_patch_candidate_t candidates[2]; + kzt_runtime_got_plt_candidate_request_t request = + make_request(&view, &ops, NULL, candidates, TEST_ARRAY_SIZE(candidates)); + kzt_runtime_got_plt_candidate_result_t result; + + view.jmprel = runtime_field(TEST_PLT_RELA_ADDR); + view.pltrelsz = scalar_field(sizeof(plt_relas)); + view.pltrel = scalar_field(DT_RELA); + add_region(&memory, TEST_PLT_RELA_ADDR, plt_relas, sizeof(plt_relas)); + + check_int("slot-read-fail.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_fail_open( + "slot-read-fail", &result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_SLOT_READ_FAILED, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_CURRENT_GOT); + check_ulong("slot-read-fail.slot", result.slot_addr, + TEST_LOAD_BIAS + plt_relas[0].r_offset); +} + +static void test_slot_overflow_fails_open(void) +{ + Elf64_Rela plt_relas[] = { + { + .r_offset = 8, + .r_info = TEST_R_INFO(TEST_JUMP_SLOT_SYMBOL, + R_X86_64_JUMP_SLOT), + .r_addend = 0, + }, + }; + fake_memory_t memory = { 0 }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_view_t view = base_view(); + kzt_patch_candidate_t candidates[2]; + kzt_runtime_got_plt_candidate_request_t request = + make_request(&view, &ops, NULL, candidates, TEST_ARRAY_SIZE(candidates)); + kzt_runtime_got_plt_candidate_result_t result; + + view.load_bias = UINTPTR_MAX - 7; + view.jmprel = runtime_field(TEST_PLT_RELA_ADDR); + view.pltrelsz = scalar_field(sizeof(plt_relas)); + view.pltrel = scalar_field(DT_RELA); + add_region(&memory, TEST_PLT_RELA_ADDR, plt_relas, sizeof(plt_relas)); + + check_int("slot-overflow.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_fail_open( + "slot-overflow", &result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_SLOT_OVERFLOW, + KZT_PATCH_REASON_INPUT_MALFORMED_SLOT); + check_int("slot-overflow.table", result.table_kind, + KZT_PATCH_TABLE_PLT_RELA); +} + +static void test_non_target_relocations_are_skipped(void) +{ + Elf64_Rela plt_relas[] = { + { + .r_offset = 0x3010, + .r_info = TEST_R_INFO(TEST_JUMP_SLOT_SYMBOL, + R_X86_64_GLOB_DAT), + .r_addend = 0, + }, + }; + Elf64_Rela relas[] = { + { + .r_offset = 0x4020, + .r_info = TEST_R_INFO(TEST_GLOB_DAT_SYMBOL, + R_X86_64_RELATIVE), + .r_addend = 0, + }, + }; + fake_memory_t memory = { 0 }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_view_t view = base_view(); + kzt_patch_candidate_t candidates[2]; + kzt_runtime_got_plt_candidate_request_t request; + kzt_runtime_got_plt_candidate_result_t result; + + view.jmprel = runtime_field(TEST_PLT_RELA_ADDR); + view.pltrelsz = scalar_field(sizeof(plt_relas)); + view.pltrel = scalar_field(DT_RELA); + view.rela = runtime_field(TEST_RELA_ADDR); + view.relasz = scalar_field(sizeof(relas)); + view.relaent = scalar_field(sizeof(Elf64_Rela)); + add_region(&memory, TEST_PLT_RELA_ADDR, plt_relas, sizeof(plt_relas)); + add_region(&memory, TEST_RELA_ADDR, relas, sizeof(relas)); + + request = make_request(&view, &ops, NULL, candidates, + TEST_ARRAY_SIZE(candidates)); + check_int("skip.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_int("skip.status", result.status, + KZT_RUNTIME_GOT_PLT_CANDIDATE_OK); + check_size("skip.count", result.candidate_count, 0); +} + +static void test_capacity_exceeded_fails_open(void) +{ + Elf64_Rela plt_relas[] = { + { + .r_offset = 0x3010, + .r_info = TEST_R_INFO(TEST_JUMP_SLOT_SYMBOL, + R_X86_64_JUMP_SLOT), + .r_addend = 0, + }, + }; + Elf64_Rela relas[] = { + { + .r_offset = 0x4020, + .r_info = TEST_R_INFO(TEST_GLOB_DAT_SYMBOL, + R_X86_64_GLOB_DAT), + .r_addend = 0, + }, + }; + uint64_t plt_slot = 0x7100001000ULL; + uint64_t rela_slot = 0x7200002000ULL; + fake_memory_t memory = { 0 }; + symbol_version_fixture_t symbols; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_view_t view = base_view(); + kzt_patch_candidate_t candidates[1]; + kzt_runtime_got_plt_candidate_request_t request; + kzt_runtime_got_plt_candidate_result_t result; + + view.jmprel = runtime_field(TEST_PLT_RELA_ADDR); + view.pltrelsz = scalar_field(sizeof(plt_relas)); + view.pltrel = scalar_field(DT_RELA); + view.rela = runtime_field(TEST_RELA_ADDR); + view.relasz = scalar_field(sizeof(relas)); + view.relaent = scalar_field(sizeof(Elf64_Rela)); + add_symbol_version_fixture(&memory, &view, &symbols); + add_region(&memory, TEST_PLT_RELA_ADDR, plt_relas, sizeof(plt_relas)); + add_region(&memory, TEST_RELA_ADDR, relas, sizeof(relas)); + add_region(&memory, TEST_LOAD_BIAS + plt_relas[0].r_offset, + &plt_slot, sizeof(plt_slot)); + add_region(&memory, TEST_LOAD_BIAS + relas[0].r_offset, + &rela_slot, sizeof(rela_slot)); + + request = make_request(&view, &ops, NULL, candidates, + TEST_ARRAY_SIZE(candidates)); + check_int("capacity.collect", + kzt_runtime_got_plt_candidates_collect(&request, &result), 0); + check_fail_open( + "capacity", &result, + KZT_RUNTIME_GOT_PLT_CANDIDATE_REASON_CAPACITY_EXCEEDED, + KZT_PATCH_REASON_INPUT_MALFORMED_TABLE); + check_int("capacity.table", result.table_kind, KZT_PATCH_TABLE_RELA); + check_size("capacity.index", result.entry_index, 0); +} + +static int test_matches_filter(const char *name, int argc, char **argv) +{ + int i; + + for (i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "--filter") && i + 1 < argc) { + return strcmp(name, argv[i + 1]) == 0; + } + } + + return 1; +} + +int main(int argc, char **argv) +{ + if (test_matches_filter("happy_path_enumerates_jump_slot_and_glob_dat", + argc, argv)) { + test_happy_path_enumerates_jump_slot_and_glob_dat(); + } + if (test_matches_filter("single_entry_reads_only_selected_relocation", + argc, argv)) { + test_single_entry_reads_only_selected_relocation(); + } + if (test_matches_filter("missing_dynamic_field_fails_open", + argc, argv)) { + test_missing_dynamic_field_fails_open(); + } + if (test_matches_filter("confirmed_unversioned_evidence_is_collected", + argc, argv)) { + test_confirmed_unversioned_evidence_is_collected(); + } + if (test_matches_filter("version_read_failure_is_error_and_fails_open", + argc, argv)) { + test_version_read_failure_is_error_and_fails_open(); + } + if (test_matches_filter( + "missing_version_definition_is_error_and_fails_open", + argc, argv)) { + test_missing_version_definition_is_error_and_fails_open(); + } + if (test_matches_filter("dt_rel_is_unsupported_and_fails_open", + argc, argv)) { + test_dt_rel_is_unsupported_and_fails_open(); + } + if (test_matches_filter("non_divisible_size_fails_open", argc, argv)) { + test_non_divisible_size_fails_open(); + } + if (test_matches_filter("relocation_reader_failure_fails_open", + argc, argv)) { + test_relocation_reader_failure_fails_open(); + } + if (test_matches_filter("slot_reader_failure_fails_open", argc, argv)) { + test_slot_reader_failure_fails_open(); + } + if (test_matches_filter("slot_overflow_fails_open", argc, argv)) { + test_slot_overflow_fails_open(); + } + if (test_matches_filter("non_target_relocations_are_skipped", + argc, argv)) { + test_non_target_relocations_are_skipped(); + } + if (test_matches_filter("capacity_exceeded_fails_open", argc, argv)) { + test_capacity_exceeded_fails_open(); + } + + if (failures) { + fprintf(stderr, + "kzt-runtime-got-plt-candidate: %d failure(s)\n", + failures); + return 1; + } + + puts("kzt-runtime-got-plt-candidate: selected tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_wi236_enrichment_matrix.c b/tests/unit/kzt/test_wi236_enrichment_matrix.c new file mode 100644 index 00000000000..e5ef7e5d679 --- /dev/null +++ b/tests/unit/kzt/test_wi236_enrichment_matrix.c @@ -0,0 +1,325 @@ +#include +#include + +#include "elf.h" +#include "target/i386/latx/include/kzt_guest_registry.h" +#include "target/i386/latx/include/kzt_owner_resolver.h" +#include "target/i386/latx/include/kzt_patch_planner.h" +#include "target/i386/latx/include/kzt_rela_immediate_candidate.h" + +static int failures; + +typedef struct wi236_contract_input { + kzt_guest_registry_t *registry; + uintptr_t slot_current_value; + uintptr_t expected_guest_target; + uintptr_t native_bridge_target; + kzt_patch_wrapper_match_t wrapper_match; +} wi236_contract_input_t; + +typedef struct wi236_contract_result { + kzt_rela_immediate_candidate_request_t request; + kzt_owner_resolution_t owner_resolution; + kzt_rela_immediate_candidate_result_t plan; +} wi236_contract_result_t; + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, expected); + ++failures; +} + +static kzt_guest_object_observation_t observation( + uintptr_t link_map_addr, + uintptr_t map_start, + uintptr_t map_end, + const char *soname) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map_addr, + .load_bias = { map_start, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { map_start + 0x1000, KZT_GUEST_FIELD_OK }, + .map_start = { map_start, KZT_GUEST_FIELD_OK }, + .map_end = { map_end, KZT_GUEST_FIELD_OK }, + .namespace_id = { 0, KZT_GUEST_FIELD_OK }, + .path = { soname, KZT_GUEST_FIELD_OK }, + .soname = { soname, KZT_GUEST_FIELD_OK }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static kzt_patch_object_ref_t source_ref(void) +{ + return (kzt_patch_object_ref_t) { + .known = 1, + .link_map_addr = 0x1110, + .map_start = 0x69000000, + .map_end = 0x69010000, + .generation = 1, + .soname = "librequester.so", + .path = "/guest/librequester.so", + }; +} + +static void request_base(kzt_rela_immediate_candidate_request_t *request) +{ + memset(request, 0, sizeof(*request)); + request->relocation_type = R_X86_64_JUMP_SLOT; + request->table_kind = KZT_PATCH_TABLE_PLT_RELA; + request->entry_index = 7; + request->entry_addr = 0x69002000; + request->source = source_ref(); + request->dynamic_addr = 0x69001000; + request->load_bias = 0x69000000; + request->dynamic_view_generation = 3; + request->dynamic_view_available = 1; + request->slot_addr = 0x69003000; + request->slot_current_value_present = 1; + request->symbol_index = 44; + request->symbol_name = "gtk_widget_show"; + request->version = "GTK_3.0"; + request->wrapper_name = "wrappedgtk3"; + request->wrapper_symbol_version = "GTK_3.0"; +} + +static void apply_wi236_contract(const wi236_contract_input_t *input, + wi236_contract_result_t *result) +{ + memset(result, 0, sizeof(*result)); + request_base(&result->request); + + result->request.slot_current_value = input->slot_current_value; + result->request.expected_guest_target = input->expected_guest_target; + result->request.native_bridge_target = input->native_bridge_target; + result->request.legacy_target = input->expected_guest_target; + result->request.wrapper_match = input->wrapper_match; + + kzt_owner_resolver_init(&result->owner_resolution); + check_int("contract.owner.resolve", + kzt_owner_resolver_resolve_current( + input->registry, + input->slot_current_value, + input->expected_guest_target, + &result->owner_resolution), + 0); + result->request.current_owner = result->owner_resolution.current_owner; + result->request.owner_match = result->owner_resolution.owner_match; + + check_int("contract.plan", + kzt_rela_immediate_jump_slot_plan(&result->request, + &result->plan), + 0); +} + +static kzt_guest_registry_t *registry_with_two_objects(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t lib_a = + observation(0x2000, 0x72000000, 0x72001000, "libcollision.so"); + kzt_guest_object_observation_t lib_b = + observation(0x3000, 0x76000000, 0x76001000, "libgtk-3.so"); + + check_int("registry.observe.a", + kzt_guest_registry_observe(registry, &lib_a), + KZT_GUEST_REGISTRY_ADDED); + check_int("registry.observe.b", + kzt_guest_registry_observe(registry, &lib_b), + KZT_GUEST_REGISTRY_ADDED); + return registry; +} + +static void test_bridge_address_is_not_expected_guest_target(void) +{ + kzt_guest_registry_t *registry = registry_with_two_objects(); + wi236_contract_result_t result; + kzt_owner_resolution_t unsafe_resolution; + wi236_contract_input_t input = { + .registry = registry, + .slot_current_value = 0x72000040, + .expected_guest_target = 0x76000080, + .native_bridge_target = 0x72000088, + .wrapper_match = KZT_PATCH_WRAPPER_VERSION_MATCH, + }; + + apply_wi236_contract(&input, &result); + check_int("bridge-not-expected.owner", + result.request.owner_match, KZT_PATCH_OWNER_MISMATCH); + check_int("bridge-not-expected.decision", + result.plan.decision.kind, KZT_PATCH_DECISION_REJECTED); + check_int("bridge-not-expected.reason", + result.plan.decision.reason, + KZT_PATCH_REASON_POLICY_OWNER_MISMATCH); + + check_int("unsafe.resolve", + kzt_owner_resolver_resolve_current( + registry, input.slot_current_value, + input.native_bridge_target, &unsafe_resolution), + 0); + check_int("unsafe.bridge-as-expected-would-match", + unsafe_resolution.owner_match, KZT_PATCH_OWNER_MATCH); + + kzt_guest_registry_destroy(®istry); +} + +static void test_only_full_evidence_can_be_approved(void) +{ + kzt_guest_registry_t *registry = registry_with_two_objects(); + wi236_contract_result_t result; + wi236_contract_input_t input = { + .registry = registry, + .slot_current_value = 0x76000040, + .expected_guest_target = 0x76000080, + .native_bridge_target = 0x73000080, + .wrapper_match = KZT_PATCH_WRAPPER_VERSION_MATCH, + }; + + apply_wi236_contract(&input, &result); + check_int("approved.owner", + result.request.owner_match, KZT_PATCH_OWNER_MATCH); + check_int("approved.wrapper", + result.request.wrapper_match, + KZT_PATCH_WRAPPER_VERSION_MATCH); + check_ulong("approved.candidate.bridge", + result.plan.candidate.bridge_target, 0x73000080); + check_ulong("approved.decision.bridge", + result.plan.decision.bridge_target, 0x73000080); + check_int("approved.decision", + result.plan.decision.kind, KZT_PATCH_DECISION_APPROVED); + check_int("approved.reason", + result.plan.decision.reason, + KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE); + + kzt_guest_registry_destroy(®istry); +} + +static void test_owner_unknown_and_mismatch_fail_open_or_reject(void) +{ + kzt_guest_registry_t *registry = registry_with_two_objects(); + wi236_contract_result_t result; + wi236_contract_input_t input = { + .registry = NULL, + .slot_current_value = 0x76000040, + .expected_guest_target = 0x76000080, + .native_bridge_target = 0x73000080, + .wrapper_match = KZT_PATCH_WRAPPER_VERSION_MATCH, + }; + + apply_wi236_contract(&input, &result); + check_int("owner-unknown.decision", + result.plan.decision.kind, KZT_PATCH_DECISION_UNSUPPORTED); + check_int("owner-unknown.reason", + result.plan.decision.reason, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_OWNER); + + input.registry = registry; + input.slot_current_value = 0x72000040; + input.expected_guest_target = 0x76000080; + apply_wi236_contract(&input, &result); + check_int("owner-mismatch.decision", + result.plan.decision.kind, KZT_PATCH_DECISION_REJECTED); + check_int("owner-mismatch.reason", + result.plan.decision.reason, + KZT_PATCH_REASON_POLICY_OWNER_MISMATCH); + + kzt_guest_registry_destroy(®istry); +} + +static void test_wrapper_and_bridge_fail_open_matrix(void) +{ + static const struct { + kzt_patch_wrapper_match_t wrapper_match; + uintptr_t bridge_target; + kzt_patch_decision_kind_t decision; + kzt_patch_reason_t reason; + const char *name; + } cases[] = { + { + KZT_PATCH_WRAPPER_NO_MANIFEST, + 0x73000080, + KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_WRAPPER_MANIFEST, + "no-manifest", + }, + { + KZT_PATCH_WRAPPER_NO_WRAPPER, + 0x73000080, + KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_NO_WRAPPER, + "no-wrapper", + }, + { + KZT_PATCH_WRAPPER_SYMBOL_ONLY, + 0x73000080, + KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_WRAPPER_SYMBOL_ONLY, + "symbol-only", + }, + { + KZT_PATCH_WRAPPER_VERSION_MISMATCH, + 0x73000080, + KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_VERSION_MISMATCH, + "version-mismatch", + }, + { + KZT_PATCH_WRAPPER_VERSION_MATCH, + 0, + KZT_PATCH_DECISION_UNSUPPORTED, + KZT_PATCH_REASON_INPUT_UNAVAILABLE_BRIDGE_TARGET, + "bridge-zero", + }, + }; + kzt_guest_registry_t *registry = registry_with_two_objects(); + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + wi236_contract_result_t result; + wi236_contract_input_t input = { + .registry = registry, + .slot_current_value = 0x76000040, + .expected_guest_target = 0x76000080, + .native_bridge_target = cases[i].bridge_target, + .wrapper_match = cases[i].wrapper_match, + }; + + apply_wi236_contract(&input, &result); + check_int(cases[i].name, + result.plan.decision.kind, cases[i].decision); + check_int(cases[i].name, + result.plan.decision.reason, cases[i].reason); + } + + kzt_guest_registry_destroy(®istry); +} + +int main(void) +{ + test_bridge_address_is_not_expected_guest_target(); + test_only_full_evidence_can_be_approved(); + test_owner_unknown_and_mismatch_fail_open_or_reject(); + test_wrapper_and_bridge_fail_open_matrix(); + + if (failures) { + fprintf(stderr, "kzt-wi236-enrichment-matrix: %d failure(s)\n", + failures); + return 1; + } + + puts("kzt-wi236-enrichment-matrix: ok"); + return 0; +} diff --git a/tests/unit/kzt/test_wi237_jump_slot_route_contract.py b/tests/unit/kzt/test_wi237_jump_slot_route_contract.py new file mode 100644 index 00000000000..f3f62dc1ce5 --- /dev/null +++ b/tests/unit/kzt/test_wi237_jump_slot_route_contract.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +import pathlib +import re +import sys + + +root = pathlib.Path(sys.argv[1]) +elfloader = (root / "target/i386/latx/context/elfloader.c").read_text() +route = (root / "target/i386/latx/context/kzt_jump_slot_route.c").read_text() +production = (root / "target/i386/latx/context/kzt_jump_slot_production.c").read_text() + + +def body(text, signature): + start = text.index(signature) + brace = text.index("{", start) + depth = 0 + for pos in range(brace, len(text)): + if text[pos] == "{": + depth += 1 + elif text[pos] == "}": + depth -= 1 + if depth == 0: + return text[brace + 1:pos] + raise AssertionError(signature) + + +eager = body(elfloader, "int RelocateElfRELA(") +lazy = body(elfloader, "void PltResolver(void)") +shared = "kzt_production_jump_slot_route(" + +assert shared in eager +assert shared in lazy +assert eager.count("uintptr_t slot_observation = (uintptr_t)(*p);") == 1 +assert re.search( + r"kzt_rela_slot_current_is_unresolved_stub\(\s*slot_observation\s*,", + eager, +) +assert "uintptr_t expected_guest_target = slot_observation;" in eager +assert "(void *)slot_observation, (void*)legacy_target" in eager +assert "p,\n slot_observation," in eager +assert "expected_guest_target = legacy_target" not in eager + +assert lazy.count("uintptr_t slot_observation = (uintptr_t)(*p);") == 1 +assert "(void *)slot_observation, (void*)legacy_target" in lazy +assert "rel, p,\n slot_observation, 1," in lazy + +# Runtime-disabled KZT retains the historical direct stores. Runtime-enabled +# eager and lazy branches do not add an unchecked store after entering Step5. +assert "if (option_kzt || wine_option_kzt)" in eager +assert "if (option_kzt || wine_option_kzt)" in lazy +assert "__atomic_compare_exchange_n" in production +assert "snapshot->namespace_id.status != KZT_GUEST_FIELD_OK" in production +assert "snapshot->namespace_id.value != 0" in production +assert "input.resolved_target_matches_legacy =\n resolved_target == legacy_target;" in production +assert "production_request_is_main_namespace" in production +assert "production_shadow_runtime_candidate" in production +assert "kzt_runtime_candidate_shadow_run" in production +assert ".only_entry = 1" in production +assert "production_emit_diagnostic(&state, route_result);" in production + +# Enrichment can leave request string pointers referring into its result text +# buffers. Both callback results therefore belong to the route-wide state; +# neither callback may create result storage on its own stack. +state = body(production, "typedef struct kzt_production_jump_slot_state") +enrich = body(production, "static int production_enrich(") +base_enrich = body(production, "static int production_enrich_base(") +bridge_enrich = body(production, "static int production_enrich_bridge(") +assert "kzt_rela_request_enricher_result_t base_enrich_result;" in state +assert "kzt_rela_request_enricher_result_t bridge_enrich_result;" in state +assert "kzt_rela_request_enricher_result_t" not in enrich +assert "&state->base_enrich_result" in base_enrich +assert "&state->bridge_enrich_result" in bridge_enrich + +# The shared helper retains the exact handle across bridge enrichment/writing, +# releases it before fallback, and never performs a direct slot store. +acquire = route.index("ops->acquire_exact_provider") +bridge = route.index("ops->enrich_bridge", acquire) +writer = route.index("ops->try_native_writer", bridge) +release = route.index("ops->release_exact_provider", writer) +fallback = route.index("route_legacy_fallback", release) +assert acquire < bridge < writer < release < fallback +assert "*(uintptr_t *)" not in route +assert "compare_exchange_slot" in route + +# Lazy currently has no raw expected guest target/owner proof and explicitly +# enters the route with expected_guest_target_present == 0. +assert "vername, 0, 0, legacy_target, &route_result" in lazy + +print("WI-237 eager/lazy shared jump-slot route contract: PASS") diff --git a/tests/unit/kzt/test_wi238_structured_diagnostics.c b/tests/unit/kzt/test_wi238_structured_diagnostics.c new file mode 100644 index 00000000000..e1e712dc0b1 --- /dev/null +++ b/tests/unit/kzt/test_wi238_structured_diagnostics.c @@ -0,0 +1,695 @@ +#include +#include +#include +#include + +#include "target/i386/latx/include/kzt_rela_diagnostics.h" + +static int failures; +static int tests_run; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lu expected %lu\n", name, got, expected); + ++failures; +} + +static void check_string(const char *name, const char *got, + const char *expected) +{ + if (got && expected && strcmp(got, expected) == 0) { + return; + } + + fprintf(stderr, "%s: got \"%s\" expected \"%s\"\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++failures; +} + +typedef struct capture_sink { + int calls; + int fail; + char line[KZT_RELA_DIAGNOSTIC_LINE_LIMIT]; +} capture_sink_t; + +#define THROTTLE_THREADS 12 +#define THROTTLE_ITERATIONS 200 +#define SNAPSHOT_READERS 4 +#define ADAPTER_THREADS 10 +#define ADAPTER_ITERATIONS 50 + +static int wait_for_barrier(pthread_barrier_t *barrier) +{ + int ret = pthread_barrier_wait(barrier); + + return ret == 0 || ret == PTHREAD_BARRIER_SERIAL_THREAD ? 0 : ret; +} + +static int capture_line(const char *line, size_t line_length, void *opaque) +{ + capture_sink_t *capture = opaque; + size_t copy_length; + + ++capture->calls; + copy_length = line_length; + if (copy_length >= sizeof(capture->line)) { + copy_length = sizeof(capture->line) - 1; + } + memcpy(capture->line, line, copy_length); + capture->line[copy_length] = '\0'; + return capture->fail ? -1 : 0; +} + +typedef struct throttle_worker { + kzt_rela_diagnostic_throttle_t *throttle; + pthread_barrier_t *barrier; + unsigned long admitted; + unsigned long suppressed; + unsigned long errors; +} throttle_worker_t; + +static void *throttle_worker_main(void *opaque) +{ + throttle_worker_t *worker = opaque; + int i; + + if (wait_for_barrier(worker->barrier) != 0) { + ++worker->errors; + return NULL; + } + + for (i = 0; i < THROTTLE_ITERATIONS; ++i) { + int result = + kzt_rela_diagnostic_throttle_try_admit(worker->throttle); + + if (result > 0) { + ++worker->admitted; + } else if (result == 0) { + ++worker->suppressed; + } else { + ++worker->errors; + } + } + return NULL; +} + +typedef struct snapshot_reader { + kzt_rela_diagnostic_throttle_t *throttle; + unsigned long capacity; + unsigned int *done; + unsigned long snapshots; + unsigned long errors; +} snapshot_reader_t; + +static void *snapshot_reader_main(void *opaque) +{ + snapshot_reader_t *reader = opaque; + + do { + kzt_rela_diagnostic_throttle_snapshot_t snapshot; + + if (kzt_rela_diagnostic_throttle_snapshot( + reader->throttle, &snapshot) != 0) { + ++reader->errors; + continue; + } + ++reader->snapshots; + if (snapshot.capacity != reader->capacity || + snapshot.admitted > snapshot.capacity || + (snapshot.admitted < snapshot.capacity && + snapshot.suppressed != 0)) { + ++reader->errors; + } + } while (__atomic_load_n(reader->done, __ATOMIC_ACQUIRE) == 0); + + return NULL; +} + +typedef struct atomic_sink { + unsigned long calls; +} atomic_sink_t; + +static int count_line_atomically(const char *line, size_t line_length, + void *opaque) +{ + atomic_sink_t *sink = opaque; + + (void)line; + (void)line_length; + __atomic_fetch_add(&sink->calls, 1, __ATOMIC_RELAXED); + return 0; +} + +typedef struct adapter_worker { + const kzt_rela_immediate_candidate_request_t *request; + const kzt_rela_immediate_writer_result_t *writer_result; + kzt_rela_diagnostic_throttle_t *throttle; + pthread_barrier_t *barrier; + atomic_sink_t *sink; + unsigned long emitted; + unsigned long suppressed; + unsigned long errors; +} adapter_worker_t; + +static void *adapter_worker_main(void *opaque) +{ + adapter_worker_t *worker = opaque; + int i; + + if (wait_for_barrier(worker->barrier) != 0) { + ++worker->errors; + return NULL; + } + + for (i = 0; i < ADAPTER_ITERATIONS; ++i) { + kzt_rela_immediate_diagnostic_result_t result; + char buffer[KZT_RELA_DIAGNOSTIC_LINE_LIMIT]; + kzt_rela_immediate_diagnostic_input_t input = { + .mode = KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS_WRITE_ENABLED, + .request = worker->request, + .result = worker->writer_result, + .legacy_fallback = 0, + .throttle = worker->throttle, + .buffer = buffer, + .buffer_size = sizeof(buffer), + .sink = count_line_atomically, + .sink_opaque = worker->sink, + }; + + if (kzt_rela_immediate_diagnostic_emit(&input, &result) != 0) { + ++worker->errors; + } else if (result.status == KZT_RELA_DIAGNOSTIC_EMIT_EMITTED) { + ++worker->emitted; + } else if (result.status == KZT_RELA_DIAGNOSTIC_EMIT_SUPPRESSED) { + ++worker->suppressed; + } else { + ++worker->errors; + } + } + return NULL; +} + +static kzt_rela_immediate_candidate_request_t base_request(const char *path) +{ + kzt_rela_immediate_candidate_request_t request; + + memset(&request, 0, sizeof(request)); + request.source.known = 1; + request.source.link_map_addr = 0x110000; + request.source.map_start = 0x400000; + request.source.map_end = 0x410000; + request.source.generation = 17; + request.source.soname = "libdiag.so"; + request.source.path = path; + request.current_owner.known = 1; + request.current_owner.link_map_addr = 0x220000; + request.current_owner.generation = 23; + request.owner_match = KZT_PATCH_OWNER_MATCH; + request.wrapper_match = KZT_PATCH_WRAPPER_VERSION_MATCH; + request.native_bridge_target = 0x660000; + request.slot_addr = 0x550000; + request.slot_current_value_present = 1; + request.slot_current_value = 0x440000; + request.symbol_name = "diag_symbol"; + request.version = "DIAG_1.0"; + return request; +} + +static kzt_rela_immediate_writer_result_t base_result( + const kzt_rela_immediate_candidate_request_t *request) +{ + kzt_rela_immediate_writer_result_t result; + kzt_patch_decision_t *decision; + + memset(&result, 0, sizeof(result)); + result.planner_called = 1; + result.writer_called = 1; + result.skip_legacy_write = 1; + result.plan.status = KZT_RELA_IMMEDIATE_CANDIDATE_PLANNED; + result.plan.reason = KZT_RELA_IMMEDIATE_CANDIDATE_REASON_NONE; + result.plan.candidate_present = 1; + result.plan.decision_present = 1; + + decision = &result.plan.decision; + decision->kind = KZT_PATCH_DECISION_APPROVED; + decision->reason = KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE; + decision->allow_native_bridge = 1; + decision->source = request->source; + decision->current_owner = request->current_owner; + decision->owner_match = request->owner_match; + decision->wrapper_match = request->wrapper_match; + decision->bridge_target = request->native_bridge_target; + decision->slot_addr = request->slot_addr; + decision->slot_current_value_present = 1; + decision->slot_current_value = request->slot_current_value; + decision->symbol_name = request->symbol_name; + decision->version = request->version; + + result.record.valid = 1; + result.record.decision_kind = decision->kind; + result.record.decision_reason = decision->reason; + result.record.result = KZT_PATCH_SPIKE_RESULT_APPLIED; + result.record.failure = KZT_PATCH_SPIKE_FAILURE_NONE; + result.record.action = KZT_PATCH_SPIKE_ACTION_USE_NATIVE_BRIDGE; + result.record.skip_legacy_write = 1; + result.record.writer_called = 1; + return result; +} + +static kzt_rela_immediate_diagnostic_input_t base_input( + kzt_rela_diagnostic_mode_t mode, + const kzt_rela_immediate_candidate_request_t *request, + const kzt_rela_immediate_writer_result_t *writer_result, + kzt_rela_diagnostic_throttle_t *throttle, + char *buffer, + size_t buffer_size, + capture_sink_t *capture) +{ + return (kzt_rela_immediate_diagnostic_input_t) { + .mode = mode, + .request = request, + .result = writer_result, + .legacy_fallback = 1, + .throttle = throttle, + .buffer = buffer, + .buffer_size = buffer_size, + .sink = capture_line, + .sink_opaque = capture, + }; +} + +static void test_modes_gate_output_and_writer_fields(void) +{ + kzt_rela_immediate_candidate_request_t request = + base_request("/guest/libdiag.so"); + kzt_rela_immediate_writer_result_t writer_result = + base_result(&request); + kzt_rela_diagnostic_throttle_t throttle; + kzt_rela_diagnostic_throttle_snapshot_t snapshot; + kzt_rela_immediate_diagnostic_result_t result; + capture_sink_t capture = { 0 }; + char buffer[KZT_RELA_DIAGNOSTIC_LINE_LIMIT]; + kzt_rela_immediate_diagnostic_input_t input; + + ++tests_run; + check_int("mode.default.flags", + kzt_rela_diagnostic_mode_from_flags(0, 0), + KZT_RELA_DIAGNOSTIC_MODE_DEFAULT); + check_int("mode.write-only.flags", + kzt_rela_diagnostic_mode_from_flags(0, 1), + KZT_RELA_DIAGNOSTIC_MODE_WRITE_ENABLED_ONLY); + check_int("mode.diagnostics.flags", + kzt_rela_diagnostic_mode_from_flags(1, 0), + KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS); + check_int("mode.combined.flags", + kzt_rela_diagnostic_mode_from_flags(1, 1), + KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS_WRITE_ENABLED); + check_int("mode.throttle.init", + kzt_rela_diagnostic_throttle_init(&throttle, 8), 0); + + input = base_input(KZT_RELA_DIAGNOSTIC_MODE_DEFAULT, &request, + &writer_result, &throttle, buffer, sizeof(buffer), + &capture); + check_int("mode.default.emit", + kzt_rela_immediate_diagnostic_emit(&input, &result), 0); + check_int("mode.default.status", result.status, + KZT_RELA_DIAGNOSTIC_EMIT_DISABLED); + + input.mode = KZT_RELA_DIAGNOSTIC_MODE_WRITE_ENABLED_ONLY; + check_int("mode.write-only.emit", + kzt_rela_immediate_diagnostic_emit(&input, &result), 0); + check_int("mode.write-only.status", result.status, + KZT_RELA_DIAGNOSTIC_EMIT_DISABLED); + check_int("mode.closed.sink", capture.calls, 0); + + input.mode = KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS; + check_int("mode.diagnostics.emit", + kzt_rela_immediate_diagnostic_emit(&input, &result), 0); + check_int("mode.diagnostics.status", result.status, + KZT_RELA_DIAGNOSTIC_EMIT_EMITTED); + check_int("mode.diagnostics.domain", result.record.reason_domain, + KZT_RELA_DIAGNOSTIC_REASON_PLANNER); + check_string("mode.diagnostics.writer", result.record.writer_result, + "NOT_RECORDED"); + check_int("mode.diagnostics.fallback", result.record.legacy_fallback, 1); + + input.mode = KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS_WRITE_ENABLED; + input.legacy_fallback = 0; + check_int("mode.combined.emit", + kzt_rela_immediate_diagnostic_emit(&input, &result), 0); + check_int("mode.combined.status", result.status, + KZT_RELA_DIAGNOSTIC_EMIT_EMITTED); + check_int("mode.combined.domain", result.record.reason_domain, + KZT_RELA_DIAGNOSTIC_REASON_WRITER); + check_string("mode.combined.writer", result.record.writer_result, + "APPLIED"); + check_int("mode.combined.fallback", result.record.legacy_fallback, 0); + check_int("mode.open.sink", capture.calls, 2); + + check_int("mode.snapshot", + kzt_rela_diagnostic_throttle_snapshot(&throttle, &snapshot), + 0); + check_ulong("mode.snapshot.admitted", snapshot.admitted, 2); + check_ulong("mode.snapshot.suppressed", snapshot.suppressed, 0); +} + +static void test_record_copies_fields_before_formatting(void) +{ + char source_path[] = "/guest/libdiag-copy.so"; + kzt_rela_immediate_candidate_request_t request = + base_request(source_path); + kzt_rela_immediate_writer_result_t writer_result = + base_result(&request); + kzt_rela_diagnostic_record_t record; + char line[KZT_RELA_DIAGNOSTIC_LINE_LIMIT]; + + ++tests_run; + check_int("fields.record", + kzt_rela_immediate_diagnostic_record( + KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS_WRITE_ENABLED, + &request, &writer_result, 0, &record), + 0); + source_path[7] = 'X'; + check_int("fields.format", + kzt_rela_diagnostic_format(&record, line, sizeof(line)), + KZT_RELA_DIAGNOSTIC_FORMAT_OK); + check_true("fields.source", + strstr(line, "source=/guest/libdiag-copy.so") != NULL); + check_true("fields.source-link-map", + strstr(line, "source_link_map=0x110000") != NULL); + check_true("fields.current-owner", + strstr(line, "current_owner=0x220000") != NULL); + check_true("fields.source-generation", + strstr(line, "source_generation=17") != NULL); + check_true("fields.current-owner-generation", + strstr(line, "current_owner_generation=23") != NULL); + check_true("fields.owner-match", + strstr(line, "owner_match=MATCH") != NULL); + check_true("fields.wrapper-match", + strstr(line, "wrapper_match=VERSION_MATCH") != NULL); + check_true("fields.bridge-target", + strstr(line, "bridge_target=0x660000") != NULL); + check_true("fields.symbol", + strstr(line, "symbol=diag_symbol") != NULL); + check_true("fields.version", + strstr(line, "version=DIAG_1.0") != NULL); + check_true("fields.reason-domain", + strstr(line, "reason_domain=writer") != NULL); + check_true("fields.reason", strstr(line, "reason=NONE") != NULL); + check_true("fields.decision", + strstr(line, "decision=APPROVED") != NULL); + check_true("fields.writer-result", + strstr(line, "writer_result=APPLIED") != NULL); + check_true("fields.legacy-fallback", + strstr(line, "legacy_fallback=0") != NULL); +} + +static void test_candidate_reason_domain_is_preserved(void) +{ + kzt_rela_immediate_candidate_request_t request = + base_request("/guest/libcandidate.so"); + kzt_rela_immediate_writer_result_t writer_result; + kzt_rela_diagnostic_record_t record; + + ++tests_run; + memset(&writer_result, 0, sizeof(writer_result)); + writer_result.planner_called = 1; + writer_result.plan.status = KZT_RELA_IMMEDIATE_CANDIDATE_FAIL_OPEN; + writer_result.plan.reason = + KZT_RELA_IMMEDIATE_CANDIDATE_REASON_MISSING_SLOT; + + check_int("candidate.record", + kzt_rela_immediate_diagnostic_record( + KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS, + &request, &writer_result, 1, &record), + 0); + check_int("candidate.domain", record.reason_domain, + KZT_RELA_DIAGNOSTIC_REASON_CANDIDATE); + check_string("candidate.reason", record.reason, "MISSING_SLOT"); + check_string("candidate.decision", record.decision, "FAIL_OPEN"); + check_string("candidate.writer", record.writer_result, "NOT_RECORDED"); + check_int("candidate.fallback", record.legacy_fallback, 1); +} + +/* + * This unit only proves that diagnostic failures preserve the snapshots and + * slot visible to this adapter. Production probe/writer isolation belongs in + * the later wiring test where those call paths can be observed. + */ +static void test_diagnostic_failures_preserve_caller_inputs(void) +{ + uintptr_t slot = 0x440000; + kzt_rela_immediate_candidate_request_t request = + base_request("/guest/libfail-open.so"); + kzt_rela_immediate_writer_result_t writer_result = + base_result(&request); + kzt_rela_immediate_candidate_request_t request_before; + kzt_rela_immediate_writer_result_t writer_before; + kzt_rela_diagnostic_record_t record; + kzt_rela_diagnostic_throttle_t throttle; + kzt_rela_immediate_diagnostic_result_t emit_result; + capture_sink_t capture = { 0 }; + char tiny[32]; + char buffer[KZT_RELA_DIAGNOSTIC_LINE_LIMIT]; + kzt_rela_immediate_diagnostic_input_t input; + + ++tests_run; + request.slot_addr = (uintptr_t)&slot; + writer_result.plan.decision.slot_addr = request.slot_addr; + request_before = request; + writer_before = writer_result; + + check_int("failure.record", + kzt_rela_immediate_diagnostic_record( + KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS_WRITE_ENABLED, + &request, &writer_result, 1, &record), + 0); + check_int("failure.format.truncated", + kzt_rela_diagnostic_format(&record, tiny, sizeof(tiny)), + KZT_RELA_DIAGNOSTIC_FORMAT_TRUNCATED); + check_int("failure.format.nul", tiny[sizeof(tiny) - 1], '\0'); + + check_int("failure.throttle.init", + kzt_rela_diagnostic_throttle_init(&throttle, 4), 0); + input = base_input( + KZT_RELA_DIAGNOSTIC_MODE_DIAGNOSTICS_WRITE_ENABLED, + &request, &writer_result, &throttle, tiny, sizeof(tiny), &capture); + check_int("failure.emit.truncated", + kzt_rela_immediate_diagnostic_emit(&input, &emit_result), 0); + check_int("failure.emit.truncated.status", emit_result.status, + KZT_RELA_DIAGNOSTIC_EMIT_FORMAT_TRUNCATED); + check_int("failure.emit.truncated.sink", capture.calls, 0); + + input.buffer = buffer; + input.buffer_size = sizeof(buffer); + capture.fail = 1; + check_int("failure.emit.sink", + kzt_rela_immediate_diagnostic_emit(&input, &emit_result), 0); + check_int("failure.emit.sink.status", emit_result.status, + KZT_RELA_DIAGNOSTIC_EMIT_SINK_FAILED); + check_int("failure.emit.sink.calls", capture.calls, 1); + + memset(&throttle, 0, sizeof(throttle)); + capture.fail = 0; + input.throttle = &throttle; + check_int("failure.emit.throttle", + kzt_rela_immediate_diagnostic_emit(&input, &emit_result), 0); + check_int("failure.emit.throttle.status", emit_result.status, + KZT_RELA_DIAGNOSTIC_EMIT_THROTTLE_FAILED); + check_int("failure.emit.throttle.sink", capture.calls, 1); + + check_true("failure.request.unchanged", + memcmp(&request, &request_before, sizeof(request)) == 0); + check_true("failure.writer.unchanged", + memcmp(&writer_result, &writer_before, + sizeof(writer_result)) == 0); + check_ulong("failure.slot.unchanged", (unsigned long)slot, 0x440000); +} + +static void test_fixed_capacity_throttle_is_concurrency_safe(void) +{ + static const unsigned long total = + THROTTLE_THREADS * THROTTLE_ITERATIONS; + static const unsigned long capacity = + THROTTLE_THREADS * THROTTLE_ITERATIONS / 2; + kzt_rela_diagnostic_throttle_t throttle; + kzt_rela_diagnostic_throttle_snapshot_t snapshot; + pthread_barrier_t barrier; + pthread_t threads[THROTTLE_THREADS]; + pthread_t readers[SNAPSHOT_READERS]; + throttle_worker_t workers[THROTTLE_THREADS]; + snapshot_reader_t snapshot_readers[SNAPSHOT_READERS]; + unsigned int readers_done = 0; + unsigned long admitted = 0; + unsigned long suppressed = 0; + unsigned long errors = 0; + int i; + + ++tests_run; + check_int("throttle.concurrent.init", + kzt_rela_diagnostic_throttle_init(&throttle, capacity), 0); + check_int("throttle.barrier.init", + pthread_barrier_init(&barrier, NULL, THROTTLE_THREADS), 0); + memset(workers, 0, sizeof(workers)); + memset(snapshot_readers, 0, sizeof(snapshot_readers)); + for (i = 0; i < SNAPSHOT_READERS; ++i) { + snapshot_readers[i].throttle = &throttle; + snapshot_readers[i].capacity = capacity; + snapshot_readers[i].done = &readers_done; + check_int("throttle.reader.create", + pthread_create(&readers[i], NULL, snapshot_reader_main, + &snapshot_readers[i]), + 0); + } + for (i = 0; i < THROTTLE_THREADS; ++i) { + workers[i].throttle = &throttle; + workers[i].barrier = &barrier; + check_int("throttle.thread.create", + pthread_create(&threads[i], NULL, throttle_worker_main, + &workers[i]), + 0); + } + for (i = 0; i < THROTTLE_THREADS; ++i) { + check_int("throttle.thread.join", pthread_join(threads[i], NULL), 0); + admitted += workers[i].admitted; + suppressed += workers[i].suppressed; + errors += workers[i].errors; + } + __atomic_store_n(&readers_done, 1U, __ATOMIC_RELEASE); + for (i = 0; i < SNAPSHOT_READERS; ++i) { + check_int("throttle.reader.join", pthread_join(readers[i], NULL), 0); + check_true("throttle.reader.snapshots", + snapshot_readers[i].snapshots != 0); + errors += snapshot_readers[i].errors; + } + check_int("throttle.barrier.destroy", + pthread_barrier_destroy(&barrier), 0); + + check_ulong("throttle.concurrent.admitted", admitted, capacity); + check_ulong("throttle.concurrent.suppressed", suppressed, + total - capacity); + check_ulong("throttle.concurrent.errors", errors, 0); + check_int("throttle.concurrent.snapshot", + kzt_rela_diagnostic_throttle_snapshot(&throttle, &snapshot), + 0); + check_ulong("throttle.snapshot.capacity", snapshot.capacity, capacity); + check_ulong("throttle.snapshot.admitted", snapshot.admitted, capacity); + check_ulong("throttle.snapshot.suppressed", snapshot.suppressed, + total - capacity); + + check_int("throttle.saturation.init", + kzt_rela_diagnostic_throttle_init(&throttle, 0), 0); + throttle.suppressed = ULONG_MAX; + check_int("throttle.saturation.admit", + kzt_rela_diagnostic_throttle_try_admit(&throttle), 0); + check_int("throttle.saturation.snapshot", + kzt_rela_diagnostic_throttle_snapshot(&throttle, &snapshot), + 0); + check_ulong("throttle.saturation.suppressed", + snapshot.suppressed, ULONG_MAX); +} + +static void test_concurrent_adapter_has_exact_suppression_count(void) +{ + static const unsigned long capacity = 29; + static const unsigned long total = ADAPTER_THREADS * ADAPTER_ITERATIONS; + kzt_rela_immediate_candidate_request_t request = + base_request("/guest/libadapter-concurrent.so"); + kzt_rela_immediate_writer_result_t writer_result = + base_result(&request); + kzt_rela_diagnostic_throttle_t throttle; + kzt_rela_diagnostic_throttle_snapshot_t snapshot; + pthread_barrier_t barrier; + pthread_t threads[ADAPTER_THREADS]; + adapter_worker_t workers[ADAPTER_THREADS]; + atomic_sink_t sink = { 0 }; + unsigned long emitted = 0; + unsigned long suppressed = 0; + unsigned long errors = 0; + int i; + + ++tests_run; + check_int("adapter.concurrent.init", + kzt_rela_diagnostic_throttle_init(&throttle, capacity), 0); + check_int("adapter.barrier.init", + pthread_barrier_init(&barrier, NULL, ADAPTER_THREADS), 0); + memset(workers, 0, sizeof(workers)); + for (i = 0; i < ADAPTER_THREADS; ++i) { + workers[i].request = &request; + workers[i].writer_result = &writer_result; + workers[i].throttle = &throttle; + workers[i].barrier = &barrier; + workers[i].sink = &sink; + check_int("adapter.thread.create", + pthread_create(&threads[i], NULL, adapter_worker_main, + &workers[i]), + 0); + } + for (i = 0; i < ADAPTER_THREADS; ++i) { + check_int("adapter.thread.join", pthread_join(threads[i], NULL), 0); + emitted += workers[i].emitted; + suppressed += workers[i].suppressed; + errors += workers[i].errors; + } + check_int("adapter.barrier.destroy", + pthread_barrier_destroy(&barrier), 0); + + check_ulong("adapter.concurrent.emitted", emitted, capacity); + check_ulong("adapter.concurrent.suppressed", suppressed, + total - capacity); + check_ulong("adapter.concurrent.errors", errors, 0); + check_ulong("adapter.concurrent.sink", + __atomic_load_n(&sink.calls, __ATOMIC_RELAXED), capacity); + check_int("adapter.concurrent.snapshot", + kzt_rela_diagnostic_throttle_snapshot(&throttle, &snapshot), + 0); + check_ulong("adapter.snapshot.admitted", snapshot.admitted, capacity); + check_ulong("adapter.snapshot.suppressed", snapshot.suppressed, + total - capacity); +} + +int main(void) +{ + test_modes_gate_output_and_writer_fields(); + test_record_copies_fields_before_formatting(); + test_candidate_reason_domain_is_preserved(); + test_diagnostic_failures_preserve_caller_inputs(); + test_fixed_capacity_throttle_is_concurrency_safe(); + test_concurrent_adapter_has_exact_suppression_count(); + + if (failures) { + fprintf(stderr, + "kzt-wi238-structured-diagnostics-gate: %d failure(s)\n", + failures); + return 1; + } + + printf("kzt-wi238-structured-diagnostics-gate: %d tests passed\n", + tests_run); + return 0; +} diff --git a/tests/unit/kzt/test_wi254_loader_contract.py b/tests/unit/kzt/test_wi254_loader_contract.py new file mode 100644 index 00000000000..61567a64da4 --- /dev/null +++ b/tests/unit/kzt/test_wi254_loader_contract.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +import pathlib +import re +import sys + + +root = pathlib.Path(sys.argv[1]) +context = root / "target/i386/latx/context" +include = root / "target/i386/latx/include" + + +def read(path): + return path.read_text(encoding="utf-8") + + +def function_body(text, signature): + start = text.index(signature) + brace = text.index("{", start) + depth = 0 + for pos in range(brace, len(text)): + if text[pos] == "{": + depth += 1 + elif text[pos] == "}": + depth -= 1 + if depth == 0: + return text[brace + 1:pos] + raise AssertionError(f"unterminated function: {signature}") + + +binding = read(context / "kzt_guest_library_binding.c") +box_header = read(include / "box64context.h") +box_context = read(context / "box64context.c") +library = read(context / "library.c") +librarian = read(context / "librarian.c") +elfloader = read(context / "elfloader.c") +wrapped_dl = read(context / "wrappedlibdl.c") +wrapped_libc = read(context / "wrappedlibc.c") + +# No process-lifetime registry/gate/singleton is permitted. +for forbidden in ("call_gate", "gate_next", "call_gate_bindings"): + assert forbidden not in binding, forbidden +assert "kzt_guest_library_access_t kzt_guest_library_access;" in box_header +assert "KztGuestLibraryLookupForContext" in box_context +assert "kzt_guest_library_access_begin_teardown" in box_context + +# Both real wrapped dlopen implementations consume the exact AddNeeded result +# directly and never perform a second name lookup for the selected instance. +for name, text in (("wrappedlibdl", wrapped_dl), ("wrappedlibc", wrapped_libc)): + body = function_body(text, "void* my_dlopen(void *filename, int flag){") + assert "AddNeededLibWithLibrary" in body, name + assert "lib = GetLibInternal(rfilename)" not in body, name + assert "&lib) || !lib" in body, name + assert "dlopen_recycle_transaction(" in body, name + assert "recycle_prepare" in body, name + assert "recycle_rollback" in body, name + reset = function_body( + text, + "static void recycle_reset_loader_state(dlopen_recycle_context_t *recycle)", + ) + for cleared in ( + "recycle->library->x86linkmap = NULL", + "recycle->header->delta = 0", + "lm->l_addr = 0", + "LatxResetElf(recycle->header)", + ): + assert cleared in reset, (name, cleared) + call = function_body(text, "static int callx86dlopen(") + assert "h->delta = ret->l_addr" in call, name + assert "lm->l_addr = ret->l_addr" in call, name + assert "h->latx_hasfix = 1" in call, name + fixed = call.index("h->latx_hasfix = 1") + finish = call.index("finish_guest_dlopen_scoped", fixed) + assert fixed < finish, name + +# Both redlopen success branches publish the exact returned link_map/library +# pair before the follow-up x86 dlsym. +for name, text in (("wrappedlibdl", wrapped_dl), ("wrappedlibc", wrapped_libc)): + finish_body = function_body(text, "static void finish_guest_dlopen_scoped(") + assert "kzt_guest_library_publish_loader_pair_scoped" in finish_body, name + assert "kzt_guest_library_publish_loader_observed_scoped" in finish_body, name + assert finish_body.index("publish_loader_pair_scoped") < \ + finish_body.index("loader_scope_end"), name + redlopen = text.index("//redlopen") + publish = text.index("finish_guest_dlopen_scoped(", redlopen) + dlsym = text.index("x86dlsym", publish) + assert redlopen < publish < dlsym, name + +# Reload cannot reactivate or publish until all failure-returning reload work +# has completed. +reload_body = function_body(library, "int ReloadLibrary(library_t* lib)") +reactivate = reload_body.index("kzt_guest_library_reactivate") +assert reload_body.rfind("return 1", 0, reactivate) >= 0 +assert reload_body.index("RelocateElfPlt") < reactivate +assert reload_body.index("kzt_guest_library_note_loader_pair", reactivate) > reactivate + +# The real PLT loader uses the tested status seam and returns before resolver +# injection when the underlying RELA worker fails. +plt_body = function_body( + elfloader, + "int RelocateElfPlt(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t* head)", +) +apply = plt_body.index("elf_plt_relocation_apply") +failure_return = plt_body.index("return -1", apply) +resolver = plt_body.index("if(need_resolver)") +assert apply < failure_return < resolver + +# The exact API preserves AddNeededLib's historical return behavior while +# clearing output on failure and publishing only the final successful choice. +exact_body = function_body(librarian, "int AddNeededLibWithLibrary(") +assert "if (exact_library) *exact_library = NULL;" in exact_body +assert "if (!add_result && exact_library)" in exact_body +assert re.search(r"return\s+0\s*;", exact_body) + +print("WI-254 loader/context white-box contract: PASS") diff --git a/tests/unit/kzt/test_wi256_lazy_binding.c b/tests/unit/kzt/test_wi256_lazy_binding.c new file mode 100644 index 00000000000..7ab0fad8715 --- /dev/null +++ b/tests/unit/kzt/test_wi256_lazy_binding.c @@ -0,0 +1,489 @@ +#include +#include + +#include "target/i386/latx/include/kzt_lazy_binding.h" + +typedef struct fixture { + uintptr_t slot; + uintptr_t guest_target; + uintptr_t native_bridge; + uintptr_t competing_target; + int post_bind_valid; + kzt_lazy_binding_route_status_t route_status; + int load_calls; + int validate_calls; + int route_calls; +} fixture_t; + +static int failures; + +#define CHECK(name, condition) do { \ + if (!(condition)) { \ + fprintf(stderr, "%s failed at line %d\n", name, __LINE__); \ + ++failures; \ + } \ +} while (0) + +static int load_slot(uintptr_t slot_addr, uintptr_t *value, void *opaque) +{ + fixture_t *fixture = opaque; + + ++fixture->load_calls; + *value = *(uintptr_t *)slot_addr; + return 0; +} + +static int validate_post_bind(const kzt_lazy_binding_pending_t *pending, + uintptr_t guest_target, void *opaque) +{ + fixture_t *fixture = opaque; + + ++fixture->validate_calls; + CHECK("validate.slot", pending->slot_addr == (uintptr_t)&fixture->slot); + CHECK("validate.guest-target", guest_target == fixture->guest_target); + return fixture->post_bind_valid; +} + +static int route_guest_target(const kzt_lazy_binding_pending_t *pending, + uintptr_t guest_target, + kzt_lazy_binding_route_result_t *result, + void *opaque) +{ + fixture_t *fixture = opaque; + + ++fixture->route_calls; + CHECK("route.main-namespace", pending->namespace_id == 0); + CHECK("route.main-namespace-kind", + pending->namespace_kind == KZT_GUEST_LIBRARY_NAMESPACE_MAIN); + CHECK("route.version-evidence", + kzt_symbol_version_evidence_valid(pending->version_evidence, + pending->version)); + CHECK("route.guest-target", guest_target == fixture->guest_target); + result->status = fixture->route_status; + switch (fixture->route_status) { + case KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED: + result->selected_target = fixture->native_bridge; + result->final_value = fixture->native_bridge; + fixture->slot = fixture->native_bridge; + break; + case KZT_LAZY_BINDING_ROUTE_CAS_MISMATCH: + result->selected_target = fixture->competing_target; + result->final_value = fixture->competing_target; + fixture->slot = fixture->competing_target; + break; + case KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED: + case KZT_LAZY_BINDING_ROUTE_ERROR: + result->selected_target = guest_target; + result->final_value = guest_target; + break; + } + return 0; +} + +static fixture_t fixture(void) +{ + return (fixture_t) { + .slot = 0x71000100, + .guest_target = 0x72000200, + .native_bridge = 0x73000300, + .competing_target = 0x74000400, + .post_bind_valid = 1, + .route_status = KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED, + }; +} + +static kzt_lazy_binding_begin_request_t begin_request_for(fixture_t *fixture) +{ + return (kzt_lazy_binding_begin_request_t) { + .enabled = 1, + .context_id = 0x1000, + .source_link_map = 0x2000, + .source_generation = 7, + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + .relocation_index = 3, + .slot_addr = (uintptr_t)&fixture->slot, + .unresolved_stub = fixture->slot, + .symbol = "realloc", + .version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .version = "GLIBC_2.2.5", + .guest_resolver = 0x75000500, + }; +} + +static kzt_lazy_binding_ops_t ops_for(fixture_t *fixture) +{ + return (kzt_lazy_binding_ops_t) { + .load_slot = load_slot, + .validate_post_bind = validate_post_bind, + .route_guest_target = route_guest_target, + .opaque = fixture, + }; +} + +static void begin_binding(kzt_lazy_binding_begin_request_t *request, + kzt_lazy_binding_pending_t *pending, + kzt_lazy_binding_result_t *result) +{ + memset(pending, 0, sizeof(*pending)); + CHECK("begin.call", + kzt_lazy_binding_begin(request, pending, result) == 0); +} + +static void test_first_call_hands_off_to_guest(void) +{ + fixture_t f = fixture(); + kzt_lazy_binding_begin_request_t request = begin_request_for(&f); + kzt_lazy_binding_result_t result; + kzt_lazy_binding_pending_t pending; + + begin_binding(&request, &pending, &result); + + CHECK("first.status", + result.status == KZT_LAZY_BINDING_HANDOFF_GUEST); + CHECK("first.armed", pending.armed == 1); + CHECK("first.result-armed", result.pending_armed == 1); + CHECK("first.guest-resolver", + result.selected_target == request.guest_resolver); + CHECK("first.slot-still-stub", f.slot == request.unresolved_stub); + CHECK("first.no-route", f.route_calls == 0); +} + +static void test_post_bind_call_can_install_native_bridge(void) +{ + fixture_t f = fixture(); + kzt_lazy_binding_begin_request_t request = begin_request_for(&f); + kzt_lazy_binding_ops_t ops = ops_for(&f); + kzt_lazy_binding_result_t result; + kzt_lazy_binding_pending_t pending; + + begin_binding(&request, &pending, &result); + + /* This write represents the guest dynamic linker completing call one. */ + f.slot = f.guest_target; + CHECK("complete.call", + kzt_lazy_binding_complete(&pending, &ops, &result) == 0); + CHECK("complete.status", + result.status == KZT_LAZY_BINDING_NATIVE_APPLIED); + CHECK("complete.before", result.slot_before == f.guest_target); + CHECK("complete.after", result.slot_after == f.native_bridge); + CHECK("complete.slot", f.slot == f.native_bridge); + CHECK("complete.one-route", f.route_calls == 1); + CHECK("complete.consumed", result.pending_armed == 0); +} + +static void test_expected_target_mismatch_preserves_competitor_without_retry(void) +{ + fixture_t f = fixture(); + kzt_lazy_binding_begin_request_t request = begin_request_for(&f); + kzt_lazy_binding_ops_t ops = ops_for(&f); + kzt_lazy_binding_result_t result; + kzt_lazy_binding_pending_t pending; + + begin_binding(&request, &pending, &result); + + f.slot = f.guest_target; + f.route_status = KZT_LAZY_BINDING_ROUTE_CAS_MISMATCH; + CHECK("race.call", + kzt_lazy_binding_complete(&pending, &ops, &result) == 0); + CHECK("race.status", result.status == KZT_LAZY_BINDING_CAS_MISMATCH); + CHECK("race.preserve", f.slot == f.competing_target); + CHECK("race.report-final", result.slot_after == f.competing_target); + CHECK("race.one-route", f.route_calls == 1); + CHECK("race.consumed", result.pending_armed == 0); +} + +static void test_missing_symbol_version_fails_open(void) +{ + fixture_t f = fixture(); + kzt_lazy_binding_begin_request_t request = begin_request_for(&f); + kzt_lazy_binding_ops_t ops = ops_for(&f); + kzt_lazy_binding_result_t result; + kzt_lazy_binding_pending_t pending; + + request.version = NULL; + begin_binding(&request, &pending, &result); + f.slot = f.guest_target; + CHECK("version.complete", + kzt_lazy_binding_complete(&pending, &ops, &result) == 0); + CHECK("version.status", + result.status == KZT_LAZY_BINDING_GUEST_PRESERVED); + CHECK("version.reason", + result.reason == KZT_LAZY_BINDING_REASON_MISSING_VERSION); + CHECK("version.slot", f.slot == f.guest_target); + CHECK("version.no-route", f.route_calls == 0); + CHECK("version.consumed", result.pending_armed == 0); +} + +static void test_confirmed_unversioned_binding_can_apply(void) +{ + fixture_t f = fixture(); + kzt_lazy_binding_begin_request_t request = begin_request_for(&f); + kzt_lazy_binding_ops_t ops = ops_for(&f); + kzt_lazy_binding_result_t result; + kzt_lazy_binding_pending_t pending; + + request.version_evidence = KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + request.version = NULL; + begin_binding(&request, &pending, &result); + CHECK("unversioned.begin", + result.status == KZT_LAZY_BINDING_HANDOFF_GUEST && + pending.version_evidence == + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED && + pending.version == NULL); + f.slot = f.guest_target; + CHECK("unversioned.complete", + kzt_lazy_binding_complete(&pending, &ops, &result) == 0); + CHECK("unversioned.applied", + result.status == KZT_LAZY_BINDING_NATIVE_APPLIED && + f.route_calls == 1 && f.slot == f.native_bridge); +} + +static void test_unknown_and_error_version_evidence_fail_open(void) +{ + kzt_symbol_version_evidence_t evidence[] = { + KZT_SYMBOL_VERSION_UNKNOWN, + KZT_SYMBOL_VERSION_ERROR, + }; + size_t i; + + for (i = 0; i < sizeof(evidence) / sizeof(evidence[0]); ++i) { + fixture_t f = fixture(); + kzt_lazy_binding_begin_request_t request = begin_request_for(&f); + kzt_lazy_binding_ops_t ops = ops_for(&f); + kzt_lazy_binding_result_t result; + kzt_lazy_binding_pending_t pending; + + request.version_evidence = evidence[i]; + begin_binding(&request, &pending, &result); + CHECK("untrusted.evidence-propagated", + pending.version_evidence == evidence[i]); + f.slot = f.guest_target; + CHECK("untrusted.complete", + kzt_lazy_binding_complete(&pending, &ops, &result) == 0); + CHECK("untrusted.preserved", + result.status == KZT_LAZY_BINDING_GUEST_PRESERVED && + result.reason == KZT_LAZY_BINDING_REASON_MISSING_VERSION && + f.route_calls == 0 && f.slot == f.guest_target); + } +} + +static void test_post_bind_validation_failure_keeps_guest_target(void) +{ + fixture_t f = fixture(); + kzt_lazy_binding_begin_request_t request = begin_request_for(&f); + kzt_lazy_binding_ops_t ops = ops_for(&f); + kzt_lazy_binding_result_t result; + kzt_lazy_binding_pending_t pending; + + begin_binding(&request, &pending, &result); + + f.slot = f.guest_target; + f.post_bind_valid = 0; + CHECK("validation.complete", + kzt_lazy_binding_complete(&pending, &ops, &result) == 0); + CHECK("validation.status", + result.status == KZT_LAZY_BINDING_GUEST_PRESERVED); + CHECK("validation.reason", + result.reason == KZT_LAZY_BINDING_REASON_POST_BIND_INVALID); + CHECK("validation.slot", f.slot == f.guest_target); + CHECK("validation.one-check", f.validate_calls == 1); + CHECK("validation.no-route", f.route_calls == 0); + CHECK("validation.consumed", result.pending_armed == 0); +} + +static void test_slot_unchanged_consumes_pending(void) +{ + fixture_t f = fixture(); + kzt_lazy_binding_begin_request_t request = begin_request_for(&f); + kzt_lazy_binding_ops_t ops = ops_for(&f); + kzt_lazy_binding_result_t result; + kzt_lazy_binding_pending_t pending; + + begin_binding(&request, &pending, &result); + + CHECK("unchanged.complete", + kzt_lazy_binding_complete(&pending, &ops, &result) == 0); + CHECK("unchanged.status", + result.status == KZT_LAZY_BINDING_GUEST_PRESERVED); + CHECK("unchanged.reason", + result.reason == KZT_LAZY_BINDING_REASON_SLOT_UNCHANGED); + CHECK("unchanged.no-route", f.route_calls == 0); + CHECK("unchanged.consumed", pending.armed == 0 && + result.pending_cleared == 1); +} + +static void test_overlong_symbol_and_version_fail_open(void) +{ + char overlong[KZT_LAZY_BINDING_SYMBOL_MAX + 1]; + fixture_t f = fixture(); + kzt_lazy_binding_begin_request_t request = begin_request_for(&f); + kzt_lazy_binding_result_t result; + kzt_lazy_binding_pending_t pending; + + memset(overlong, 'x', sizeof(overlong) - 1); + overlong[sizeof(overlong) - 1] = '\0'; + memset(&pending, 0, sizeof(pending)); + request.symbol = overlong; + CHECK("long-symbol.begin", + kzt_lazy_binding_begin(&request, &pending, &result) == 0); + CHECK("long-symbol.preserved", + result.status == KZT_LAZY_BINDING_GUEST_PRESERVED && + result.reason == KZT_LAZY_BINDING_REASON_INVALID_REQUEST && + pending.armed == 0); + + memset(&pending, 0, sizeof(pending)); + request = begin_request_for(&f); + request.version = overlong; + CHECK("long-version.begin", + kzt_lazy_binding_begin(&request, &pending, &result) == 0); + CHECK("long-version.preserved", + result.status == KZT_LAZY_BINDING_GUEST_PRESERVED && + result.reason == KZT_LAZY_BINDING_REASON_INVALID_REQUEST && + pending.armed == 0); +} + +static void test_nested_pending_busy_preserves_outer_state(void) +{ + fixture_t f = fixture(); + kzt_lazy_binding_begin_request_t outer = begin_request_for(&f); + kzt_lazy_binding_begin_request_t nested = begin_request_for(&f); + kzt_lazy_binding_result_t result; + kzt_lazy_binding_pending_t pending; + kzt_lazy_binding_pending_t saved; + + begin_binding(&outer, &pending, &result); + saved = pending; + saved.symbol = pending.symbol ? saved.symbol_storage : NULL; + saved.version = pending.version ? saved.version_storage : NULL; + + nested.relocation_index = outer.relocation_index + 1; + nested.slot_addr += sizeof(uintptr_t); + CHECK("nested.begin", + kzt_lazy_binding_begin(&nested, &pending, &result) == 0); + CHECK("nested.busy", + result.status == KZT_LAZY_BINDING_GUEST_PRESERVED && + result.reason == KZT_LAZY_BINDING_REASON_PENDING_BUSY); + CHECK("nested.outer-armed", pending.armed == 1); + CHECK("nested.outer-identity", + pending.context_id == saved.context_id && + pending.source_link_map == saved.source_link_map && + pending.source_generation == saved.source_generation && + pending.relocation_index == saved.relocation_index && + pending.slot_addr == saved.slot_addr && + !strcmp(pending.symbol, saved.symbol) && + !strcmp(pending.version, saved.version)); +} + +static void test_new_context_replaces_abandoned_pending(void) +{ + fixture_t f = fixture(); + kzt_lazy_binding_begin_request_t abandoned = begin_request_for(&f); + kzt_lazy_binding_begin_request_t next = begin_request_for(&f); + kzt_lazy_binding_result_t result; + kzt_lazy_binding_pending_t pending; + + begin_binding(&abandoned, &pending, &result); + + next.context_id = abandoned.context_id + 1; + next.source_link_map = abandoned.source_link_map + 0x1000; + next.source_generation = abandoned.source_generation + 1; + next.relocation_index = abandoned.relocation_index + 1; + next.slot_addr += sizeof(uintptr_t); + next.symbol = "next_context_symbol"; + + CHECK("new-context.begin", + kzt_lazy_binding_begin(&next, &pending, &result) == 0); + CHECK("new-context.handoff", + result.status == KZT_LAZY_BINDING_HANDOFF_GUEST && + result.reason == KZT_LAZY_BINDING_REASON_NONE && + result.pending_armed == 1); + CHECK("new-context.replaced", + pending.armed == 1 && + pending.context_id == next.context_id && + pending.source_link_map == next.source_link_map && + pending.source_generation == next.source_generation && + pending.relocation_index == next.relocation_index && + pending.slot_addr == next.slot_addr && + !strcmp(pending.symbol, next.symbol)); +} + +static void test_cancelled_pending_allows_same_context_retry(void) +{ + fixture_t f = fixture(); + kzt_lazy_binding_begin_request_t first = begin_request_for(&f); + kzt_lazy_binding_begin_request_t retry = begin_request_for(&f); + kzt_lazy_binding_result_t result; + kzt_lazy_binding_pending_t pending; + + begin_binding(&first, &pending, &result); + + kzt_lazy_binding_cancel(&pending); + CHECK("cancel.cleared", pending.armed == 0 && + pending.context_id == 0 && pending.source_link_map == 0 && + pending.slot_addr == 0 && pending.symbol == NULL && + pending.version == NULL); + + retry.relocation_index = first.relocation_index + 1; + retry.slot_addr += sizeof(uintptr_t); + retry.symbol = "retry_symbol"; + CHECK("cancel.retry", + kzt_lazy_binding_begin(&retry, &pending, &result) == 0); + CHECK("cancel.retry-armed", + result.status == KZT_LAZY_BINDING_HANDOFF_GUEST && + result.pending_armed == 1 && pending.armed == 1 && + pending.context_id == retry.context_id && + pending.relocation_index == retry.relocation_index && + pending.slot_addr == retry.slot_addr && + !strcmp(pending.symbol, retry.symbol)); +} + +static void test_only_main_namespace_is_armed(void) +{ + fixture_t f = fixture(); + kzt_lazy_binding_begin_request_t request = begin_request_for(&f); + kzt_lazy_binding_result_t result; + kzt_lazy_binding_pending_t pending; + + request.namespace_id = 1; + request.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_EXPLICIT; + begin_binding(&request, &pending, &result); + CHECK("namespace.status", + result.status == KZT_LAZY_BINDING_GUEST_PRESERVED); + CHECK("namespace.reason", + result.reason == KZT_LAZY_BINDING_REASON_NON_MAIN_NAMESPACE); + CHECK("namespace.not-armed", pending.armed == 0); + CHECK("namespace.guest-resolver", + result.selected_target == request.guest_resolver); + CHECK("namespace.slot", f.slot == request.unresolved_stub); + + request.namespace_id = 0; + request.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN; + begin_binding(&request, &pending, &result); + CHECK("main.status", + result.status == KZT_LAZY_BINDING_HANDOFF_GUEST); + CHECK("main.armed", pending.armed == 1); +} + +int main(void) +{ + test_first_call_hands_off_to_guest(); + test_post_bind_call_can_install_native_bridge(); + test_expected_target_mismatch_preserves_competitor_without_retry(); + test_missing_symbol_version_fails_open(); + test_confirmed_unversioned_binding_can_apply(); + test_unknown_and_error_version_evidence_fail_open(); + test_post_bind_validation_failure_keeps_guest_target(); + test_only_main_namespace_is_armed(); + test_slot_unchanged_consumes_pending(); + test_overlong_symbol_and_version_fail_open(); + test_nested_pending_busy_preserves_outer_state(); + test_new_context_replaces_abandoned_pending(); + test_cancelled_pending_allows_same_context_retry(); + if (failures) { + fprintf(stderr, "%d WI-256 lazy-binding checks failed\n", failures); + return 1; + } + puts("KZT WI-256 lazy binding: PASS"); + return 0; +} diff --git a/tests/unit/kzt/test_wi256_lazy_diagnostics.c b/tests/unit/kzt/test_wi256_lazy_diagnostics.c new file mode 100644 index 00000000000..b683f4adfec --- /dev/null +++ b/tests/unit/kzt/test_wi256_lazy_diagnostics.c @@ -0,0 +1,341 @@ +#include +#include +#include +#include + +#include "target/i386/latx/include/kzt_lazy_binding.h" +#include "target/i386/latx/include/kzt_lazy_diagnostics.h" + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lu expected %lu\n", name, got, expected); + ++failures; +} + +static void check_string(const char *name, const char *got, + const char *expected) +{ + if (got && expected && strcmp(got, expected) == 0) { + return; + } + + fprintf(stderr, "%s: got \"%s\" expected \"%s\"\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static void check_contains(const char *name, const char *text, + const char *needle) +{ + if (text && needle && strstr(text, needle)) { + return; + } + + fprintf(stderr, "%s: \"%s\" missing \"%s\"\n", name, + text ? text : "(null)", needle ? needle : "(null)"); + ++failures; +} + +typedef struct capture_sink { + int calls; + int fail; + char line[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; +} capture_sink_t; + +static int capture_line(const char *line, size_t line_length, void *opaque) +{ + capture_sink_t *capture = opaque; + size_t copy_length = line_length; + + ++capture->calls; + if (copy_length >= sizeof(capture->line)) { + copy_length = sizeof(capture->line) - 1; + } + memcpy(capture->line, line, copy_length); + capture->line[copy_length] = '\0'; + return capture->fail ? -1 : 0; +} + +static kzt_lazy_binding_pending_t base_pending(void) +{ + kzt_lazy_binding_pending_t pending; + + memset(&pending, 0, sizeof(pending)); + pending.armed = 1; + pending.slot_addr = 0x71000018; + pending.unresolved_stub = 0x72000020; + strcpy(pending.symbol_storage, "realloc"); + pending.symbol = pending.symbol_storage; + strcpy(pending.version_storage, "GLIBC_2.2.5"); + pending.version = pending.version_storage; + return pending; +} + +static kzt_lazy_binding_result_t base_result(void) +{ + kzt_lazy_binding_result_t result; + + memset(&result, 0, sizeof(result)); + result.status = KZT_LAZY_BINDING_NATIVE_APPLIED; + result.reason = KZT_LAZY_BINDING_REASON_NATIVE_APPLIED; + result.slot_before = 0x73000030; + result.slot_after = 0x74000040; + result.selected_target = 0x74000040; + return result; +} + +static void test_emit_disabled_skips_format_and_sink(void) +{ + kzt_lazy_binding_pending_t pending = base_pending(); + kzt_lazy_binding_result_t binding_result = base_result(); + capture_sink_t capture = { 0 }; + char buffer[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; + kzt_lazy_diagnostic_input_t input = { + .enabled = 0, + .pending = &pending, + .binding_result = &binding_result, + .buffer = buffer, + .buffer_size = sizeof(buffer), + .sink = capture_line, + .sink_opaque = &capture, + }; + kzt_lazy_diagnostic_emit_result_t emit_result; + + memset(buffer, 'X', sizeof(buffer)); + check_int("disabled.emit", + kzt_lazy_diagnostics_emit(&input, &emit_result), 0); + check_int("disabled.status", emit_result.status, + KZT_LAZY_DIAGNOSTIC_EMIT_DISABLED); + check_int("disabled.sink", capture.calls, 0); + check_int("disabled.record", emit_result.record_present, 0); + check_int("disabled.buffer-untouched", buffer[0], 'X'); +} + +static void test_native_applied_records_schema_one(void) +{ + kzt_lazy_binding_pending_t pending = base_pending(); + kzt_lazy_binding_result_t binding_result = base_result(); + capture_sink_t capture = { 0 }; + char buffer[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; + kzt_lazy_diagnostic_input_t input = { + .enabled = 1, + .pending = &pending, + .binding_result = &binding_result, + .buffer = buffer, + .buffer_size = sizeof(buffer), + .sink = capture_line, + .sink_opaque = &capture, + }; + kzt_lazy_diagnostic_emit_result_t emit_result; + + check_int("native.emit", + kzt_lazy_diagnostics_emit(&input, &emit_result), 0); + check_int("native.status", emit_result.status, + KZT_LAZY_DIAGNOSTIC_EMIT_EMITTED); + check_int("native.record", emit_result.record_present, 1); + check_ulong("native.schema", emit_result.record.schema, 1); + check_string("native.symbol", emit_result.record.symbol, "realloc"); + check_string("native.first-route", + emit_result.record.first_execution_route, "guest"); + check_string("native.route-status", + emit_result.record.completion_route_status, + "NATIVE_APPLIED"); + check_ulong("native.slot-before", emit_result.record.slot_before, + pending.unresolved_stub); + check_ulong("native.slot-after-guest", + emit_result.record.slot_after_guest, + binding_result.slot_before); + check_ulong("native.second-target", + emit_result.record.selected_second_target, + binding_result.slot_after); + check_string("native.reason", emit_result.record.reason, + "NATIVE_APPLIED"); + check_int("native.sink", capture.calls, 1); + check_contains("native.line.schema", capture.line, "schema=1"); + check_contains("native.line.symbol", capture.line, "symbol=realloc"); + check_contains("native.line.route", capture.line, + "completion_route_status=NATIVE_APPLIED"); + check_contains("native.line.before", capture.line, + "slot_before=0x72000020"); + check_contains("native.line.after-guest", capture.line, + "slot_after_guest=0x73000030"); + check_contains("native.line.second-target", capture.line, + "selected_second_target=0x74000040"); +} + +static void test_slot_unchanged_uses_stable_status_name(void) +{ + kzt_lazy_binding_pending_t pending = base_pending(); + kzt_lazy_binding_result_t binding_result = base_result(); + capture_sink_t capture = { 0 }; + char buffer[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; + kzt_lazy_diagnostic_input_t input = { + .enabled = 1, + .pending = &pending, + .binding_result = &binding_result, + .buffer = buffer, + .buffer_size = sizeof(buffer), + .sink = capture_line, + .sink_opaque = &capture, + }; + kzt_lazy_diagnostic_emit_result_t emit_result; + + binding_result.status = KZT_LAZY_BINDING_GUEST_PRESERVED; + binding_result.reason = KZT_LAZY_BINDING_REASON_SLOT_UNCHANGED; + binding_result.slot_before = pending.unresolved_stub; + binding_result.slot_after = pending.unresolved_stub; + check_int("slot-unchanged.emit", + kzt_lazy_diagnostics_emit(&input, &emit_result), 0); + check_string("slot-unchanged.status", + emit_result.record.completion_route_status, + "SLOT_UNCHANGED"); + check_string("slot-unchanged.reason", emit_result.record.reason, + "SLOT_UNCHANGED"); +} + +static void test_missing_version_uses_stable_status_name(void) +{ + kzt_lazy_binding_pending_t pending = base_pending(); + kzt_lazy_binding_result_t binding_result = base_result(); + capture_sink_t capture = { 0 }; + char buffer[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; + kzt_lazy_diagnostic_input_t input = { + .enabled = 1, + .pending = &pending, + .binding_result = &binding_result, + .buffer = buffer, + .buffer_size = sizeof(buffer), + .sink = capture_line, + .sink_opaque = &capture, + }; + kzt_lazy_diagnostic_emit_result_t emit_result; + + pending.version = NULL; + pending.version_storage[0] = '\0'; + binding_result.status = KZT_LAZY_BINDING_GUEST_PRESERVED; + binding_result.reason = KZT_LAZY_BINDING_REASON_MISSING_VERSION; + binding_result.slot_after = binding_result.slot_before; + check_int("missing-version.emit", + kzt_lazy_diagnostics_emit(&input, &emit_result), 0); + check_string("missing-version.status", + emit_result.record.completion_route_status, + "MISSING_VERSION"); + check_string("missing-version.reason", emit_result.record.reason, + "MISSING_VERSION"); +} + +static void test_cas_mismatch_uses_final_competitor_target(void) +{ + kzt_lazy_binding_pending_t pending = base_pending(); + kzt_lazy_binding_result_t binding_result = base_result(); + capture_sink_t capture = { 0 }; + char buffer[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; + kzt_lazy_diagnostic_input_t input = { + .enabled = 1, + .pending = &pending, + .binding_result = &binding_result, + .buffer = buffer, + .buffer_size = sizeof(buffer), + .sink = capture_line, + .sink_opaque = &capture, + }; + kzt_lazy_diagnostic_emit_result_t emit_result; + + binding_result.status = KZT_LAZY_BINDING_CAS_MISMATCH; + binding_result.reason = KZT_LAZY_BINDING_REASON_CAS_MISMATCH; + binding_result.slot_after = 0x75000050; + binding_result.selected_target = 0x76000060; + check_int("cas.emit", + kzt_lazy_diagnostics_emit(&input, &emit_result), 0); + check_string("cas.status", emit_result.record.completion_route_status, + "CAS_MISMATCH"); + check_ulong("cas.second-target", + emit_result.record.selected_second_target, + 0x75000050); + check_contains("cas.line.second-target", capture.line, + "selected_second_target=0x75000050"); +} + +static void test_production_emit_writes_stderr(void) +{ + kzt_lazy_binding_pending_t pending = base_pending(); + kzt_lazy_binding_result_t binding_result = base_result(); + kzt_lazy_diagnostic_emit_result_t emit_result; + int saved_stderr; + int pipefd[2]; + char output[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; + ssize_t count; + + check_int("production.pipe", pipe(pipefd), 0); + saved_stderr = dup(STDERR_FILENO); + check_true("production.saved-stderr", saved_stderr >= 0); + check_true("production.redirect", + dup2(pipefd[1], STDERR_FILENO) >= 0); + close(pipefd[1]); + + check_int("production.enabled.emit", + kzt_lazy_diagnostics_emit_production( + &pending, &binding_result, &emit_result), + 0); + check_int("production.enabled.status", emit_result.status, + KZT_LAZY_DIAGNOSTIC_EMIT_EMITTED); + + fflush(stderr); + check_true("production.restore", dup2(saved_stderr, STDERR_FILENO) >= 0); + close(saved_stderr); + + count = read(pipefd[0], output, sizeof(output) - 1); + check_true("production.read", count > 0); + if (count > 0) { + output[count] = '\0'; + check_contains("production.output.schema", output, "schema=1"); + check_contains("production.output.symbol", output, "symbol=realloc"); + } + close(pipefd[0]); +} + +int main(void) +{ + test_emit_disabled_skips_format_and_sink(); + test_native_applied_records_schema_one(); + test_slot_unchanged_uses_stable_status_name(); + test_missing_version_uses_stable_status_name(); + test_cas_mismatch_uses_final_competitor_target(); + test_production_emit_writes_stderr(); + + if (failures) { + fprintf(stderr, "kzt-wi256-lazy-diagnostics: %d failure(s)\n", + failures); + return 1; + } + + puts("KZT WI-256 lazy diagnostics: PASS"); + return 0; +} diff --git a/tests/unit/kzt/test_wi256_lazy_diagnostics_source_contract.py b/tests/unit/kzt/test_wi256_lazy_diagnostics_source_contract.py new file mode 100644 index 00000000000..7aa48daab30 --- /dev/null +++ b/tests/unit/kzt/test_wi256_lazy_diagnostics_source_contract.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +import pathlib +import re +import sys + + +def function_body(text: str, signature: str) -> str: + start = -1 + search_from = 0 + while True: + start = text.index(signature, search_from) + after_signature = start + len(signature) + brace = text.index("{", after_signature) + semicolon = text.find(";", after_signature, brace) + if semicolon < 0: + break + search_from = after_signature + depth = 0 + for pos in range(brace, len(text)): + if text[pos] == "{": + depth += 1 + elif text[pos] == "}": + depth -= 1 + if depth == 0: + return text[brace + 1:pos] + raise AssertionError(f"unterminated function: {signature}") + + +def braced_block(text: str, brace: int) -> tuple[str, int]: + depth = 0 + for pos in range(brace, len(text)): + if text[pos] == "{": + depth += 1 + elif text[pos] == "}": + depth -= 1 + if depth == 0: + return text[brace + 1:pos], pos + 1 + raise AssertionError("unterminated block") + + +root = pathlib.Path(sys.argv[1]).resolve() +main = (root / "linux-user/main.c").read_text(encoding="utf-8") +options = (root / "target/i386/latx/include/latx-options.h").read_text( + encoding="utf-8" +) +options_c = (root / "target/i386/latx/latx-options.c").read_text( + encoding="utf-8" +) +elfloader = (root / "target/i386/latx/context/elfloader.c").read_text( + encoding="utf-8" +) +diagnostics = ( + root / "target/i386/latx/context/kzt_lazy_diagnostics.c" +).read_text(encoding="utf-8") + +# The lazy diagnostics flag is independent and closed by default. +assert re.search(r"\bint\s+option_kzt_lazy_diagnostics\s*=\s*0\s*;", options_c) +assert not re.search(r"\bint\s+option_kzt_lazy_diagnostics\s*=", main) +assert re.search( + r'"LATX_KZT_LAZY_DIAGNOSTICS"\s*,\s*true\s*,\s*' + r'handle_arg_latx_kzt_lazy_diagnostics', + main, +) +assert "ENVFUN(LATX_KZT_LAZY_DIAGNOSTICS" in options +handler = function_body( + main, "static void handle_arg_latx_kzt_lazy_diagnostics(const char *arg)" +) +assert "option_kzt_lazy_diagnostics" in handler +assert "kzt_registry_diagnostics" not in handler +assert "option_kzt_lazy_diagnostics" not in function_body( + diagnostics, "int kzt_lazy_diagnostics_emit_production(" +) + +resolver = function_body( + elfloader, "static void KztLazyBindingCompleteResolver(void)" +) +armed_start = resolver.index("if (pending->armed)") +armed_brace = resolver.index("{", armed_start) +armed, _ = braced_block(resolver, armed_brace) + +# The option conditional is the sole diagnostics gate on the completion path. +gate = re.search(r"if\s*\(\s*option_kzt_lazy_diagnostics\s*\)\s*\{", armed) +assert gate is not None +enabled_brace = armed.index("{", gate.start()) +enabled, enabled_end = braced_block(armed, enabled_brace) +else_match = re.match(r"\s*else\s*\{", armed[enabled_end:]) +assert else_match is not None +disabled_brace = armed.index("{", enabled_end + else_match.start()) +disabled, _ = braced_block(armed, disabled_brace) + +assert "pending_snapshot = *pending;" in enabled +assert re.search( + r"pending_snapshot\.symbol\s*=\s*pending->symbol\s*\?\s*" + r"pending_snapshot\.symbol_storage\s*:\s*NULL\s*;", + enabled, + re.S, +) +assert re.search( + r"pending_snapshot\.version\s*=\s*pending->version\s*\?\s*" + r"pending_snapshot\.version_storage\s*:\s*NULL\s*;", + enabled, + re.S, +) +assert enabled.index("pending_snapshot = *pending;") < enabled.index( + "kzt_production_lazy_complete(" +) +assert enabled.index("kzt_production_lazy_complete(") < enabled.index( + "kzt_lazy_diagnostics_emit_production(" +) +assert re.search( + r"kzt_lazy_diagnostics_emit_production\s*\(\s*" + r"&pending_snapshot\s*,\s*&result\s*,", + enabled, + re.S, +) +assert "kzt_production_lazy_complete(" in disabled +for forbidden in ("pending_snapshot", "diagnostic", "emit"): + assert forbidden not in disabled + +# Record construction consumes only the pre-clear pending snapshot and the +# existing completion result; it must not rediscover or reroute the binding. +record = function_body(diagnostics, "int kzt_lazy_diagnostic_record_build(") +assert "record->slot_before = pending->unresolved_stub;" in record +assert "record->slot_after_guest = binding_result->slot_before;" in record +assert "record->selected_second_target = binding_result->slot_after;" in record +for forbidden in ( + "owner", + "provider", + "wrapper", + "bridge", + "planner", + "route_guest_target", + "compare_exchange", +): + assert forbidden not in record + +print("WI-256 lazy diagnostics source contract: PASS") diff --git a/tests/unit/kzt/test_wi256_lazy_production_bridge.c b/tests/unit/kzt/test_wi256_lazy_production_bridge.c new file mode 100644 index 00000000000..165c8c52fc5 --- /dev/null +++ b/tests/unit/kzt/test_wi256_lazy_production_bridge.c @@ -0,0 +1,1434 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "elf.h" +#include "target/i386/latx/include/box64context.h" +#include "target/i386/latx/include/bridge_private.h" +#include "target/i386/latx/include/elfloader_private.h" +#include "target/i386/latx/include/khash.h" +#include "target/i386/latx/include/kzt_guest_registry.h" +#include "target/i386/latx/include/kzt_jump_slot_production.h" +#include "target/i386/latx/include/kzt_rela_runtime_bridge.h" +#include "target/i386/latx/include/librarian_private.h" +#include "target/i386/latx/include/library.h" +#include "target/i386/latx/include/library_private.h" + +#define FIXTURE_SYMBOL "uname" +#define FIXTURE_VERSION "GLIBC_2.2.5" +#define SOURCE_LINK_MAP 0x1000 +#define PROVIDER_LINK_MAP 0x2000 +#define SOURCE_START 0x70000000 +#define GUEST_TARGET 0x71000020 +#define STRESS_ITERATIONS 1000 + +static int failures; +static uintptr_t fixture_native_symbol; +static uintptr_t fixture_native_bridge; +int relocation_log; +int kzt_registry_diagnostics; + +KHASH_MAP_IMPL_STR(symbolmap, wrapper_t) +KHASH_MAP_IMPL_STR(symbol2map, symbol2_t) + +#define CHECK(name, condition) do { \ + if (!(condition)) { \ + fprintf(stderr, "%s failed at line %d\n", name, __LINE__); \ + ++failures; \ + } \ +} while (0) + +typedef struct fixture_bridge_map { + void *native_symbol; + uintptr_t target; + onebridge_t *entry; + int add_calls; + int force_inexact_add; + int check_calls; +} fixture_bridge_map_t; + +typedef struct fixture_version_need { + Elf64_Verneed need; + Elf64_Vernaux aux; +} fixture_version_need_t; + +typedef struct fixture { + box64context_t context; + library_t provider; + lib_t scope; + library_t *scope_libraries[1]; + fixture_bridge_map_t bridge_map; + onebridge_t bridge_entry; + elfheader_t head; + elfheader_t *elfs[1]; + Elf64_Rela rela; + Elf64_Sym sym; + Elf64_Half versym; + fixture_version_need_t version_need; + uintptr_t slot; + kzt_lazy_binding_pending_t pending; + kzt_guest_object_observation_t source_observation; + kzt_guest_dynamic_view_t dynamic_view; +} fixture_t; + +typedef enum hook_mode { + HOOK_NONE = 0, + HOOK_RECYCLE_BEFORE_ACQUIRE, + HOOK_RETIRE_AFTER_CAS, + HOOK_RETIRE_AFTER_CAS_ROLLBACK, +} hook_mode_t; + +typedef struct retire_sync { + pthread_mutex_t lock; + pthread_cond_t cond; + fixture_t *fixture; + unsigned long generation; + int go; + int retire_called; + int retire_done; + int retire_result; + int unloading_observed; + int completed_inside_hook; +} retire_sync_t; + +static fixture_t *hook_fixture; +static retire_sync_t *hook_retire; +static hook_mode_t hook_mode; +static int before_acquire_calls; +static int source_memory_access_calls; +static int slot_load_calls; +static int after_cas_calls; +static int lease_seen_at_first_slot_load; +static int force_verify_mismatch; +static int shadow_run_calls; +static unsigned long recycled_generation; +static int permission_begin_calls; +static int permission_end_calls; +static int fail_permission_begin; +static int fail_permission_end; + +uintptr_t CheckBridged(bridge_t *bridge, void *fnc); +uintptr_t AddCheckBridge(bridge_t *bridge, wrapper_t wrapper, void *fnc, + int stack_bytes, const char *name); +const char *SymName(elfheader_t *head, Elf64_Sym *sym); +void kzt_jump_slot_production_test_before_source_lease_acquire(void); +void kzt_jump_slot_production_test_before_source_memory_access(void); +void kzt_jump_slot_production_test_before_slot_load(void); +void kzt_jump_slot_production_test_after_slot_load(uintptr_t *value); +void kzt_jump_slot_production_test_after_slot_cas(int exchanged); +void kzt_jump_slot_production_test_shadow_run(void); +int kzt_jump_slot_production_test_begin_slot_write( + uintptr_t slot_addr, kzt_patch_spike_permission_lease_t *lease); +int kzt_jump_slot_production_test_end_slot_write( + kzt_patch_spike_permission_lease_t *lease); + +static void fixture_iFp(uintptr_t fnc) +{ + (void)fnc; +} + +uintptr_t CheckBridged(bridge_t *bridge, void *fnc) +{ + fixture_bridge_map_t *map = (fixture_bridge_map_t *)bridge; + + if (!map) { + return 0; + } + ++map->check_calls; + return fnc == map->native_symbol ? map->target : 0; +} + +uintptr_t AddCheckBridge(bridge_t *bridge, wrapper_t wrapper, void *fnc, + int stack_bytes, const char *name) +{ + fixture_bridge_map_t *map = (fixture_bridge_map_t *)bridge; + uintptr_t target; + + (void)stack_bytes; + (void)name; + target = CheckBridged(bridge, fnc); + if (target || !map || !map->entry || !wrapper || !fnc) { + return target; + } + ++map->add_calls; + map->entry->CC = 0xCC; + map->entry->S = 'S'; + map->entry->C = 'C'; + map->entry->w = map->force_inexact_add ? NULL : wrapper; + map->entry->f = (uintptr_t)fnc; + map->entry->C3 = 0xC3; + map->native_symbol = fnc; + map->target = (uintptr_t)&map->entry->CC; + return map->target; +} + +void *GetNativeSymbolUnversionned(void *lib, const char *name) +{ + return lib && name ? dlsym(lib, name) : NULL; +} + +kzt_guest_registry_t *KztGuestRegistryForContext(box64context_t *context) +{ + return context ? context->kzt_guest_registry_context.registry : NULL; +} + +int KztGuestLibraryLookupForContext( + box64context_t *context, const kzt_guest_library_binding_key_t *key, + kzt_guest_library_handle_t *handle) +{ + return context ? kzt_guest_library_access_lookup( + &context->kzt_guest_library_access, key, handle) : + -1; +} + +kzt_patch_spike_guard_t *KztPatchSpikeGuardForContext( + box64context_t *context) +{ + return context ? &context->kzt_patch_spike_guard : NULL; +} + +const char *SymName(elfheader_t *head, Elf64_Sym *sym) +{ + (void)head; + (void)sym; + return FIXTURE_SYMBOL; +} + +const char *GetSymbolVersion(elfheader_t *head, int version) +{ + (void)head; + return version == 2 ? FIXTURE_VERSION : NULL; +} + +static kzt_guest_object_observation_t observation( + uintptr_t link_map, uintptr_t start, uintptr_t end, const char *name) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map, + .load_bias = { start, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { start + 0x1000, KZT_GUEST_FIELD_OK }, + .map_start = { start, KZT_GUEST_FIELD_OK }, + .map_end = { end, KZT_GUEST_FIELD_OK }, + .namespace_id = { 0, KZT_GUEST_FIELD_OK }, + .path = { name, KZT_GUEST_FIELD_OK }, + .soname = { name, KZT_GUEST_FIELD_OK }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static kzt_guest_dynamic_field_t runtime_field(uintptr_t value) +{ + return (kzt_guest_dynamic_field_t) { + .present = 1, + .value = value, + .address_semantics = KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS, + }; +} + +static kzt_guest_dynamic_field_t scalar_field(uint64_t value) +{ + return (kzt_guest_dynamic_field_t) { + .present = 1, + .value = value, + .address_semantics = KZT_GUEST_DYNAMIC_SCALAR, + }; +} + +static unsigned long observe_object( + kzt_guest_registry_t *registry, + const kzt_guest_object_observation_t *object) +{ + kzt_guest_object_snapshot_t *snapshot = NULL; + unsigned long generation = 0; + + CHECK("observe.add", kzt_guest_registry_observe(registry, object) == + KZT_GUEST_REGISTRY_ADDED); + CHECK("observe.find", kzt_guest_registry_find_by_link_map( + registry, object->link_map_addr, + &snapshot) == 0 && snapshot != NULL); + if (snapshot) { + generation = snapshot->generation; + } + kzt_guest_object_snapshot_free(snapshot); + return generation; +} + +static unsigned long fixture_publish_source(fixture_t *fixture) +{ + unsigned long generation = observe_object( + fixture->context.kzt_guest_registry_context.registry, + &fixture->source_observation); + + CHECK("source.dynamic", kzt_guest_registry_commit_dynamic_view( + fixture->context.kzt_guest_registry_context.registry, + SOURCE_LINK_MAP, + generation, &fixture->dynamic_view) == + KZT_GUEST_REGISTRY_UPDATED); + return generation; +} + +static void fixture_publish_resolver(fixture_t *fixture) +{ + kzt_guest_lazy_resolver_t resolver = { + .link_map_slot = SOURCE_START + 0x2000, + .resolver_slot = SOURCE_START + 0x2008, + .guest_link_map = SOURCE_LINK_MAP, + .guest_resolver = SOURCE_START + 0x3000, + }; + + fixture->pending.guest_resolver = resolver.guest_resolver; + CHECK("source.resolver", kzt_guest_registry_publish_lazy_resolver( + fixture->context.kzt_guest_registry_context.registry, + SOURCE_LINK_MAP, + fixture->pending.source_generation, 0, &resolver) == 0); +} + +static void reset_guard(fixture_t *fixture, int enabled) +{ + kzt_patch_spike_config_t config = { enabled, 1, 1 }; + kzt_patch_spike_guard_init(&fixture->context.kzt_patch_spike_guard, + &config); +} + +static int fixture_init(fixture_t *fixture) +{ + static char source_name[] = "librequester.so"; + static char provider_name[] = "libc.so.6"; + static char dynstr[] = "\0uname\0" FIXTURE_VERSION "\0KZT_BAD_VERSION\0"; + kzt_guest_object_observation_t provider_observation; + kzt_guest_library_binding_key_t provider_key; + khint_t map_key; + int inserted; + int initial_failures = failures; + + memset(fixture, 0, sizeof(*fixture)); + fixture->versym = 2; + fixture->slot = GUEST_TARGET; + fixture->context.kzt_guest_registry_context.registry = + kzt_guest_registry_init(); + CHECK("registry.init", + fixture->context.kzt_guest_registry_context.registry != NULL); + CHECK("binding.init", kzt_guest_library_access_init( + &fixture->context.kzt_guest_library_access) == 0); + if (!fixture->context.kzt_guest_registry_context.registry || + !fixture->context.kzt_guest_library_access.initialized) { + return -1; + } + reset_guard(fixture, 1); + + fixture->scope_libraries[0] = &fixture->provider; + fixture->scope.libraries = fixture->scope_libraries; + fixture->scope.libsz = 1; + fixture->scope.context = &fixture->context; + fixture->context.maplib = &fixture->scope; + fixture->provider.name = provider_name; + fixture->provider.path = provider_name; + fixture->provider.type = LIB_WRAPPED; + fixture->provider.active = 1; + fixture->provider.context = &fixture->context; + fixture->provider.priv.w.lib = dlopen( + "libc.so.6", RTLD_LAZY | RTLD_LOCAL); + CHECK("provider.dlopen", fixture->provider.priv.w.lib != NULL); + if (!fixture->provider.priv.w.lib) { + return -1; + } + dlerror(); + fixture_native_symbol = (uintptr_t)dlsym( + fixture->provider.priv.w.lib, FIXTURE_SYMBOL); + CHECK("provider.native", fixture_native_symbol != 0 && dlerror() == NULL); + + fixture->provider.symbolmap = kh_init(symbolmap); + CHECK("provider.map", fixture->provider.symbolmap != NULL); + if (!fixture->provider.symbolmap) { + return -1; + } + map_key = kh_put(symbolmap, fixture->provider.symbolmap, + FIXTURE_SYMBOL, &inserted); + CHECK("provider.map-entry", inserted != -1 && + map_key != kh_end(fixture->provider.symbolmap)); + kh_value(fixture->provider.symbolmap, map_key) = fixture_iFp; + fixture->bridge_entry.CC = 0xCC; + fixture->bridge_entry.S = 'S'; + fixture->bridge_entry.C = 'C'; + fixture->bridge_entry.w = fixture_iFp; + fixture->bridge_entry.f = fixture_native_symbol; + fixture->bridge_entry.C3 = 0xC3; + fixture_native_bridge = (uintptr_t)&fixture->bridge_entry.CC; + fixture->bridge_map.native_symbol = (void *)fixture_native_symbol; + fixture->bridge_map.target = fixture_native_bridge; + fixture->bridge_map.entry = &fixture->bridge_entry; + fixture->provider.priv.w.bridge = (bridge_t *)&fixture->bridge_map; + + fixture->source_observation = observation( + SOURCE_LINK_MAP, SOURCE_START, SOURCE_START + 0x10000, source_name); + provider_observation = observation( + PROVIDER_LINK_MAP, 0x71000000, 0x71010000, provider_name); + fixture->dynamic_view = (kzt_guest_dynamic_view_t) { + .dynamic_addr = SOURCE_START + 0x1000, + .load_bias = 0, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .entry_count = 1, + .has_null = 1, + }; + fixture->dynamic_view.jmprel = runtime_field( + (uintptr_t)&fixture->rela); + fixture->dynamic_view.pltrelsz = scalar_field(sizeof(fixture->rela)); + fixture->dynamic_view.pltrel = scalar_field(DT_RELA); + fixture->dynamic_view.symtab = runtime_field((uintptr_t)&fixture->sym); + fixture->dynamic_view.syment = scalar_field(sizeof(fixture->sym)); + fixture->dynamic_view.strtab = runtime_field((uintptr_t)dynstr); + fixture->dynamic_view.strsz = scalar_field(sizeof(dynstr)); + fixture->dynamic_view.versym = runtime_field((uintptr_t)&fixture->versym); + fixture->dynamic_view.verneed = runtime_field( + (uintptr_t)&fixture->version_need); + fixture->dynamic_view.verneednum = scalar_field(1); + fixture->sym.st_name = 1; + fixture->version_need.need.vn_version = 1; + fixture->version_need.need.vn_cnt = 1; + fixture->version_need.need.vn_aux = sizeof(Elf64_Verneed); + fixture->version_need.aux.vna_other = 2; + fixture->version_need.aux.vna_name = 7; + fixture->pending.source_generation = fixture_publish_source(fixture); + fixture_publish_resolver(fixture); + provider_key = (kzt_guest_library_binding_key_t) { + .link_map_addr = PROVIDER_LINK_MAP, + .generation = observe_object( + fixture->context.kzt_guest_registry_context.registry, + &provider_observation), + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + CHECK("provider.track", kzt_guest_library_track( + fixture->context.kzt_guest_library_access.bindings, + &fixture->provider) == 0); + CHECK("provider.pair", kzt_guest_library_note_exact_pair( + fixture->context.kzt_guest_library_access.bindings, + PROVIDER_LINK_MAP, &fixture->provider, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_PENDING); + CHECK("provider.publish", kzt_guest_library_note_observation( + fixture->context.kzt_guest_library_access.bindings, + &provider_key) == KZT_GUEST_LIBRARY_BINDING_ADDED); + + fixture->rela.r_info = R_X86_64_JUMP_SLOT; + fixture->rela.r_offset = (uintptr_t)&fixture->slot; + fixture->head.name = source_name; + fixture->head.path = source_name; + fixture->head.memory = (char *)SOURCE_START; + fixture->head.memsz = 0x10000; + fixture->head.Dynamic = (Elf64_Dyn *)(SOURCE_START + 0x1000); + fixture->head.jmprel = (uintptr_t)&fixture->rela; + fixture->head.pltsz = sizeof(fixture->rela); + fixture->head.pltent = sizeof(fixture->rela); + fixture->head.DynSym = &fixture->sym; + fixture->head.VerSym = &fixture->versym; + fixture->head.self_link_map = SOURCE_LINK_MAP; + fixture->elfs[0] = &fixture->head; + fixture->context.elfs = fixture->elfs; + fixture->context.elfsize = 1; + + fixture->pending.armed = 1; + fixture->pending.context_id = (uintptr_t)&fixture->context; + fixture->pending.source_link_map = SOURCE_LINK_MAP; + fixture->pending.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN; + fixture->pending.namespace_id = 0; + fixture->pending.slot_addr = (uintptr_t)&fixture->slot; + fixture->pending.unresolved_stub = GUEST_TARGET - 0x10; + fixture->pending.symbol = FIXTURE_SYMBOL; + fixture->pending.version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + fixture->pending.version = FIXTURE_VERSION; + return failures == initial_failures ? 0 : -1; +} + +static void fixture_destroy(fixture_t *fixture) +{ + if (fixture->provider.symbolmap) { + kh_destroy(symbolmap, fixture->provider.symbolmap); + } + if (fixture->provider.priv.w.lib) { + dlclose(fixture->provider.priv.w.lib); + } + kzt_guest_library_access_destroy(&fixture->context.kzt_guest_library_access); + kzt_guest_registry_destroy( + &fixture->context.kzt_guest_registry_context.registry); +} + +static int fixture_route(fixture_t *fixture, + kzt_lazy_binding_route_result_t *result) +{ + return kzt_production_lazy_route_guest_target( + &fixture->context, &fixture->pending, GUEST_TARGET, result); +} + +static int fixture_eager_route(fixture_t *fixture, + uintptr_t expected_guest_target, + const char *version, + kzt_jump_slot_route_result_t *result) +{ + return kzt_production_jump_slot_route( + &fixture->context, &fixture->provider, fixture_native_bridge, + &fixture->head, 1, 0, &fixture->rela, (uint64_t *)&fixture->slot, + fixture->slot, 0, 0, FIXTURE_SYMBOL, version, 1, + expected_guest_target, fixture_native_bridge, result); +} + +static int fixture_eager_route_with_evidence( + fixture_t *fixture, uintptr_t expected_guest_target, + kzt_symbol_version_evidence_t version_evidence, const char *version, + kzt_jump_slot_route_result_t *result) +{ + return kzt_production_jump_slot_route_with_version_evidence( + &fixture->context, &fixture->provider, fixture_native_bridge, + &fixture->head, 1, 0, &fixture->rela, (uint64_t *)&fixture->slot, + fixture->slot, 0, 0, FIXTURE_SYMBOL, version_evidence, version, 1, + expected_guest_target, fixture_native_bridge, result); +} + +static void hooks_reset(fixture_t *fixture, hook_mode_t mode) +{ + hook_fixture = fixture; + hook_retire = NULL; + hook_mode = mode; + before_acquire_calls = 0; + source_memory_access_calls = 0; + slot_load_calls = 0; + after_cas_calls = 0; + lease_seen_at_first_slot_load = 0; + force_verify_mismatch = mode == HOOK_RETIRE_AFTER_CAS_ROLLBACK; + shadow_run_calls = 0; + recycled_generation = 0; + permission_begin_calls = 0; + permission_end_calls = 0; + fail_permission_begin = 0; + fail_permission_end = 0; +} + +int kzt_jump_slot_production_test_begin_slot_write( + uintptr_t slot_addr, kzt_patch_spike_permission_lease_t *lease) +{ + if (!lease || !slot_addr) { + return -1; + } + ++permission_begin_calls; + lease->checked = 1; + lease->guest_page = slot_addr & ~(uintptr_t)0xfff; + lease->guest_page_length = 0x1000; + lease->original_permissions = 5; + if (fail_permission_begin) { + return -1; + } + lease->write_enabled = 1; + return 0; +} + +int kzt_jump_slot_production_test_end_slot_write( + kzt_patch_spike_permission_lease_t *lease) +{ + if (!lease) { + return -1; + } + ++permission_end_calls; + return fail_permission_end ? -1 : 0; +} + +void kzt_jump_slot_production_test_before_source_lease_acquire(void) +{ + ++before_acquire_calls; + if (hook_mode != HOOK_RECYCLE_BEFORE_ACQUIRE || !hook_fixture) { + return; + } + CHECK("before.retire", kzt_guest_registry_retire( + hook_fixture->context.kzt_guest_registry_context.registry, + SOURCE_LINK_MAP, + hook_fixture->pending.source_generation) == 0); + recycled_generation = fixture_publish_source(hook_fixture); + hook_mode = HOOK_NONE; +} + +void kzt_jump_slot_production_test_before_source_memory_access(void) +{ + ++source_memory_access_calls; +} + +void kzt_jump_slot_production_test_before_slot_load(void) +{ + if (slot_load_calls == 0 && hook_fixture) { + kzt_guest_object_snapshot_t *snapshot = NULL; + + if (kzt_guest_registry_find_by_link_map( + hook_fixture->context.kzt_guest_registry_context.registry, + SOURCE_LINK_MAP, + &snapshot) == 0 && snapshot && + snapshot->active_source_leases == 1) { + lease_seen_at_first_slot_load = 1; + } + kzt_guest_object_snapshot_free(snapshot); + } + ++slot_load_calls; +} + +void kzt_jump_slot_production_test_after_slot_load(uintptr_t *value) +{ + if (force_verify_mismatch && slot_load_calls == 3 && value) { + *value ^= 0x10; + force_verify_mismatch = 0; + } +} + +static void *retire_worker(void *opaque) +{ + retire_sync_t *sync = opaque; + int result; + + pthread_mutex_lock(&sync->lock); + while (!sync->go) { + pthread_cond_wait(&sync->cond, &sync->lock); + } + sync->retire_called = 1; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); + + result = kzt_guest_registry_retire( + sync->fixture->context.kzt_guest_registry_context.registry, + SOURCE_LINK_MAP, + sync->generation); + pthread_mutex_lock(&sync->lock); + sync->retire_result = result; + sync->retire_done = 1; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); + return NULL; +} + +void kzt_jump_slot_production_test_after_slot_cas(int exchanged) +{ + struct timespec start; + retire_sync_t *sync = hook_retire; + + ++after_cas_calls; + if (!exchanged || + (hook_mode != HOOK_RETIRE_AFTER_CAS && + hook_mode != HOOK_RETIRE_AFTER_CAS_ROLLBACK) || !sync) { + return; + } + hook_mode = HOOK_NONE; + pthread_mutex_lock(&sync->lock); + sync->go = 1; + pthread_cond_broadcast(&sync->cond); + while (!sync->retire_called) { + pthread_cond_wait(&sync->cond, &sync->lock); + } + pthread_mutex_unlock(&sync->lock); + + clock_gettime(CLOCK_MONOTONIC, &start); + for (;;) { + kzt_guest_object_snapshot_t *snapshot = NULL; + struct timespec now; + int done; + + if (kzt_guest_registry_find_by_link_map( + sync->fixture->context.kzt_guest_registry_context.registry, + SOURCE_LINK_MAP, + &snapshot) != 0) { + pthread_mutex_lock(&sync->lock); + done = sync->retire_done; + sync->unloading_observed = !done; + sync->completed_inside_hook = done; + pthread_mutex_unlock(&sync->lock); + kzt_guest_object_snapshot_free(snapshot); + break; + } + kzt_guest_object_snapshot_free(snapshot); + pthread_mutex_lock(&sync->lock); + done = sync->retire_done; + pthread_mutex_unlock(&sync->lock); + if (done) { + pthread_mutex_lock(&sync->lock); + sync->completed_inside_hook = 1; + pthread_mutex_unlock(&sync->lock); + break; + } + clock_gettime(CLOCK_MONOTONIC, &now); + if (now.tv_sec - start.tv_sec >= 2) { + break; + } + sched_yield(); + } +} + +void kzt_jump_slot_production_test_shadow_run(void) +{ + ++shadow_run_calls; +} + +static int retire_sync_init(retire_sync_t *sync, fixture_t *fixture) +{ + memset(sync, 0, sizeof(*sync)); + sync->fixture = fixture; + sync->generation = fixture->pending.source_generation; + if (pthread_mutex_init(&sync->lock, NULL) != 0) { + return -1; + } + if (pthread_cond_init(&sync->cond, NULL) != 0) { + pthread_mutex_destroy(&sync->lock); + return -1; + } + return 0; +} + +static void retire_sync_destroy(retire_sync_t *sync) +{ + pthread_cond_destroy(&sync->cond); + pthread_mutex_destroy(&sync->lock); +} + +static void test_successful_production_route(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_NONE); + CHECK("success.route", fixture_route(&fixture, &result) == 0); + CHECK("success.applied", + result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED); + CHECK("success.slot", fixture.slot == fixture_native_bridge); + CHECK("success.lease", before_acquire_calls == 1); + fixture_destroy(&fixture); +} + +static void test_guest_version_is_not_used_for_host_lookup(void) +{ + fixture_t fixture; + kzt_wrapper_bridge_provider_t provider; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + CHECK("version-different.prepare", + kzt_rela_runtime_wrapper_provider_discover( + &fixture.context, &fixture.provider, FIXTURE_SYMBOL, + FIXTURE_VERSION, &provider) == 1); + CHECK("version-different.guest-version-kept", + provider.manifest.entry_count == 1 && + !strcmp(provider.entry.symbol_version, FIXTURE_VERSION)); + CHECK("version-different.native-provider-symbol", + provider.entry.native_symbol == fixture_native_symbol); + fixture_destroy(&fixture); +} + +static void test_native_symbol_missing_fails_open(void) +{ + fixture_t fixture; + kzt_wrapper_bridge_provider_t provider; + khint_t key; + int inserted; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + key = kh_put(symbolmap, fixture.provider.symbolmap, + "kzt_native_symbol_missing", &inserted); + CHECK("native-missing.map", inserted != -1 && + key != kh_end(fixture.provider.symbolmap)); + if (key != kh_end(fixture.provider.symbolmap)) { + kh_value(fixture.provider.symbolmap, key) = fixture_iFp; + } + CHECK("native-missing.prepare", + kzt_rela_runtime_wrapper_provider_discover( + &fixture.context, &fixture.provider, + "kzt_native_symbol_missing", FIXTURE_VERSION, + &provider) == 0); + CHECK("native-missing.no-manifest", provider.manifest.available == 0); + CHECK("native-missing.no-add", fixture.bridge_map.add_calls == 0); + fixture_destroy(&fixture); +} + +static void test_dependency_symbol_owner_mismatch_fails_open(void) +{ + fixture_t fixture; + kzt_wrapper_bridge_provider_t provider; + void *libc_handle; + void *libm_handle; + void *dependency_symbol; + struct link_map *handle_map = NULL; + struct link_map *symbol_map = NULL; + Dl_info symbol_info; + khint_t key; + int inserted; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + libm_handle = dlopen("libm.so.6", RTLD_LAZY | RTLD_LOCAL); + CHECK("owner-mismatch.libm", libm_handle != NULL); + if (!libm_handle) { + fixture_destroy(&fixture); + return; + } + dependency_symbol = dlsym(libm_handle, "malloc"); + CHECK("owner-mismatch.dependency-symbol", dependency_symbol != NULL); + CHECK("owner-mismatch.precondition", + dependency_symbol && + dlinfo(libm_handle, RTLD_DI_LINKMAP, &handle_map) == 0 && + dladdr1(dependency_symbol, &symbol_info, (void **)&symbol_map, + RTLD_DL_LINKMAP) != 0 && + handle_map && symbol_map && handle_map != symbol_map); + key = kh_put(symbolmap, fixture.provider.symbolmap, "malloc", &inserted); + CHECK("owner-mismatch.map", inserted != -1 && + key != kh_end(fixture.provider.symbolmap)); + if (key != kh_end(fixture.provider.symbolmap)) { + kh_value(fixture.provider.symbolmap, key) = fixture_iFp; + } + libc_handle = fixture.provider.priv.w.lib; + fixture.provider.priv.w.lib = libm_handle; + CHECK("owner-mismatch.prepare", + kzt_rela_runtime_wrapper_provider_discover( + &fixture.context, &fixture.provider, "malloc", + FIXTURE_VERSION, &provider) == 0); + CHECK("owner-mismatch.no-manifest", provider.manifest.available == 0); + CHECK("owner-mismatch.no-add", fixture.bridge_map.add_calls == 0); + fixture.provider.priv.w.lib = libc_handle; + dlclose(libm_handle); + fixture_destroy(&fixture); +} + +static void test_missing_bridge_is_created_from_exact_provider(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + fixture.bridge_map.target = 0; + hooks_reset(&fixture, HOOK_NONE); + CHECK("create-bridge.route", fixture_route(&fixture, &result) == 0); + CHECK("create-bridge.applied", + result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED); + CHECK("create-bridge.add-once", fixture.bridge_map.add_calls == 1); + CHECK("create-bridge.slot", fixture.slot == fixture_native_bridge); + CHECK("create-bridge.exact", + CheckBridged(fixture.provider.priv.w.bridge, + (void *)fixture_native_symbol) == + fixture_native_bridge); + fixture_destroy(&fixture); +} + +static void test_created_inexact_bridge_fails_open(void) +{ + fixture_t fixture; + kzt_wrapper_bridge_provider_t provider; + kzt_wrapper_probe_request_t request = { + .symbol_name = FIXTURE_SYMBOL, + .symbol_version = FIXTURE_VERSION, + }; + kzt_wrapper_probe_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + fixture.bridge_map.target = 0; + fixture.bridge_map.force_inexact_add = 1; + CHECK("inexact-add.prepare", + kzt_rela_runtime_wrapper_provider_discover( + &fixture.context, &fixture.provider, FIXTURE_SYMBOL, + FIXTURE_VERSION, &provider) == 1); + CHECK("inexact-add.probe", + kzt_wrapper_probe_minimal_manifest( + &provider.manifest, &request, &provider.bridge_ops, + &result) == 0); + CHECK("inexact-add.no-bridge", result.bridge_target == 0); + CHECK("inexact-add.add-once", fixture.bridge_map.add_calls == 1); + fixture_destroy(&fixture); +} + +static void test_eager_production_request_evidence(void) +{ + fixture_t fixture; + kzt_jump_slot_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + + hooks_reset(&fixture, HOOK_NONE); + kzt_registry_diagnostics = 0; + CHECK("eager.owner-match.call", fixture_eager_route( + &fixture, GUEST_TARGET, FIXTURE_VERSION, &result) == 0); + CHECK("eager.owner-match.native", + result.status == KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED && + fixture.slot == fixture_native_bridge && + result.legacy_fallback_attempted == 0); + CHECK("eager.diagnostics-off-fast", shadow_run_calls == 0); + + fixture.slot = GUEST_TARGET; + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_NONE); + kzt_registry_diagnostics = 1; + CHECK("eager.shadow.call", fixture_eager_route( + &fixture, GUEST_TARGET, FIXTURE_VERSION, &result) == 0); + CHECK("eager.shadow.real-route", + result.status == KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED && + shadow_run_calls == 1); + + fixture.slot = GUEST_TARGET; + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_NONE); + CHECK("eager.owner-mismatch.call", fixture_eager_route( + &fixture, SOURCE_START + 0x20, FIXTURE_VERSION, &result) == 0); + CHECK("eager.owner-mismatch.legacy", + result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED && + result.legacy_fallback_attempted == 1 && + fixture.slot == fixture_native_bridge); + + fixture.slot = 0xdeadbeef; + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_NONE); + CHECK("eager.owner-unknown.call", fixture_eager_route( + &fixture, 0xdeadbeef, FIXTURE_VERSION, &result) == 0); + CHECK("eager.owner-unknown.legacy", + result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED && + result.legacy_fallback_attempted == 1 && + fixture.slot == fixture_native_bridge); + + fixture.slot = GUEST_TARGET; + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_NONE); + CHECK("eager.wrapper-version.call", fixture_eager_route( + &fixture, GUEST_TARGET, "KZT_UNSUPPORTED_VERSION", &result) == 0); + CHECK("eager.wrapper-version.legacy", + result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED && + result.legacy_fallback_attempted == 1 && + fixture.slot == fixture_native_bridge); + + fixture.slot = GUEST_TARGET; + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_NONE); + fixture.version_need.aux.vna_name = 20; + CHECK("eager.runtime-version.call", fixture_eager_route( + &fixture, GUEST_TARGET, FIXTURE_VERSION, &result) == 0); + CHECK("eager.runtime-version.legacy-once", + result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED && + result.legacy_fallback_attempted == 1 && after_cas_calls == 1 && + fixture.slot == fixture_native_bridge); + fixture.version_need.aux.vna_name = 7; + + fixture.slot = GUEST_TARGET; + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_RECYCLE_BEFORE_ACQUIRE); + CHECK("eager.generation-race.call", fixture_eager_route( + &fixture, GUEST_TARGET, FIXTURE_VERSION, &result) == 0); + CHECK("eager.generation-race.legacy-once", + recycled_generation > fixture.pending.source_generation && + result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED && + result.legacy_fallback_attempted == 1 && after_cas_calls == 1 && + fixture.slot == fixture_native_bridge); + kzt_registry_diagnostics = 0; + fixture_destroy(&fixture); +} + +static void fixture_set_confirmed_unversioned(fixture_t *fixture) +{ + fixture->dynamic_view.versym.present = 0; + fixture->dynamic_view.verneed.present = 0; + fixture->dynamic_view.verneednum.present = 0; + fixture->head.VerSym = NULL; + fixture->pending.version_evidence = + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + fixture->pending.version = NULL; + CHECK("unversioned.dynamic-view", kzt_guest_registry_commit_dynamic_view( + fixture->context.kzt_guest_registry_context.registry, + SOURCE_LINK_MAP, fixture->pending.source_generation, + &fixture->dynamic_view) != KZT_GUEST_REGISTRY_ERROR); +} + +static void test_confirmed_unversioned_production_paths_apply(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t lazy_result; + kzt_jump_slot_route_result_t eager_result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + fixture_set_confirmed_unversioned(&fixture); + hooks_reset(&fixture, HOOK_NONE); + CHECK("unversioned-lazy.route", + fixture_route(&fixture, &lazy_result) == 0); + CHECK("unversioned-lazy.applied", + lazy_result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED && + fixture.slot == fixture_native_bridge); + + fixture.slot = GUEST_TARGET; + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_NONE); + CHECK("unversioned-eager.route", + fixture_eager_route_with_evidence( + &fixture, GUEST_TARGET, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, + &eager_result) == 0); + CHECK("unversioned-eager.applied", + eager_result.status == KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED && + eager_result.legacy_fallback_attempted == 0 && + fixture.slot == fixture_native_bridge); + fixture_destroy(&fixture); +} + +static void test_unknown_version_evidence_preserves_guest_path(void) +{ + fixture_t fixture; + kzt_jump_slot_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + fixture_set_confirmed_unversioned(&fixture); + hooks_reset(&fixture, HOOK_NONE); + CHECK("unknown-eager.route", + fixture_eager_route_with_evidence( + &fixture, GUEST_TARGET, KZT_SYMBOL_VERSION_UNKNOWN, NULL, + &result) == 0); + CHECK("unknown-eager.legacy", + result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED && + result.legacy_fallback_attempted == 1 && + fixture.slot == fixture_native_bridge); + fixture_destroy(&fixture); +} + +static void test_production_permission_transaction_preserves_on_restore_error(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_NONE); + fail_permission_end = 1; + CHECK("permission.route", fixture_route(&fixture, &result) == 0); + CHECK("permission.begin", permission_begin_calls == 1); + CHECK("permission.end", permission_end_calls == 1); + CHECK("permission.no-legacy-mask", + result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED); + CHECK("permission.no-second-write", after_cas_calls == 1); + CHECK("permission.circuit", + kzt_patch_spike_guard_circuit_open( + &fixture.context.kzt_patch_spike_guard) == 1); + fixture_destroy(&fixture); +} + +static void test_recycle_before_acquire_preserves_guest(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t result; + unsigned long old_generation; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + old_generation = fixture.pending.source_generation; + hooks_reset(&fixture, HOOK_RECYCLE_BEFORE_ACQUIRE); + CHECK("before.route", fixture_route(&fixture, &result) == 0); + CHECK("before.recycled", recycled_generation > old_generation); + CHECK("before.preserved", + result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED); + CHECK("before.slot", fixture.slot == GUEST_TARGET); + CHECK("before.no-source-memory", source_memory_access_calls == 0); + CHECK("before.no-slot-read", slot_load_calls == 0); + fixture_destroy(&fixture); +} + +static void test_retire_before_first_slot_read_skips_guest_memory(void) +{ + fixture_t fixture; + kzt_guest_registry_source_lease_t source_lease = { 0 }; + uintptr_t value = 0; + unsigned long old_generation; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + old_generation = fixture.pending.source_generation; + hooks_reset(&fixture, HOOK_RECYCLE_BEFORE_ACQUIRE); + CHECK("first-read.load-rejected", + kzt_production_lazy_load_slot_with_lease( + &fixture.context, &fixture.pending, + (uintptr_t)&fixture.slot, &value, &source_lease) != 0); + CHECK("first-read.recycled", recycled_generation > old_generation); + CHECK("first-read.no-lease", source_lease.active == 0); + CHECK("first-read.no-slot-access", slot_load_calls == 0); + CHECK("first-read.slot-untouched", fixture.slot == GUEST_TARGET); + fixture_destroy(&fixture); +} + +static void test_namespace_mismatch_precedes_any_source_read(void) +{ + fixture_t fixture; + kzt_lazy_binding_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + fixture.pending.namespace_id = 1; + fixture.pending.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_EXPLICIT; + hooks_reset(&fixture, HOOK_NONE); + CHECK("namespace.complete", kzt_production_lazy_complete( + &fixture.context, &fixture.pending, &result) == 0); + CHECK("namespace.preserved", + result.status == KZT_LAZY_BINDING_GUEST_PRESERVED); + CHECK("namespace.reason", + result.reason == KZT_LAZY_BINDING_REASON_SLOT_READ_ERROR); + CHECK("namespace.real-result-empty", result.slot_before == 0 && + result.slot_after == 0 && result.selected_target == 0); + CHECK("namespace.pending-consumed", fixture.pending.armed == 0 && + result.pending_cleared == 1); + CHECK("namespace.no-lease-hook", before_acquire_calls == 0); + CHECK("namespace.no-source-memory", source_memory_access_calls == 0); + CHECK("namespace.no-slot-read", slot_load_calls == 0); + CHECK("namespace.no-memory-change", fixture.slot == GUEST_TARGET); + fixture_destroy(&fixture); +} + +typedef enum guard_fail_open_mode { + GUARD_FAIL_OPEN_DISABLED = 0, + GUARD_FAIL_OPEN_CIRCUIT, + GUARD_FAIL_OPEN_BUDGET_ZERO, +} guard_fail_open_mode_t; + +static void run_completion_guard_fail_open(guard_fail_open_mode_t mode, + const char *prefix) +{ + fixture_t fixture; + kzt_lazy_binding_result_t result; + kzt_patch_spike_config_t config = { 0 }; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + switch (mode) { + case GUARD_FAIL_OPEN_DISABLED: + /* Matches the real verifier default-options all-zero guard. */ + break; + case GUARD_FAIL_OPEN_CIRCUIT: + config = (kzt_patch_spike_config_t) { 1, 1, 1 }; + break; + case GUARD_FAIL_OPEN_BUDGET_ZERO: + config = (kzt_patch_spike_config_t) { 1, 1, 0 }; + break; + } + kzt_patch_spike_guard_init(&fixture.context.kzt_patch_spike_guard, + &config); + if (mode == GUARD_FAIL_OPEN_CIRCUIT) { + fixture.context.kzt_patch_spike_guard.circuit_open = 1; + } + hooks_reset(&fixture, HOOK_NONE); + + CHECK(prefix, kzt_production_lazy_complete( + &fixture.context, &fixture.pending, &result) == 0); + CHECK(prefix, result.status == KZT_LAZY_BINDING_GUEST_PRESERVED); + CHECK(prefix, result.reason == KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE); + CHECK(prefix, result.slot_before == GUEST_TARGET); + CHECK(prefix, result.slot_after == GUEST_TARGET); + CHECK(prefix, result.selected_target == GUEST_TARGET); + CHECK(prefix, fixture.slot == GUEST_TARGET); + CHECK(prefix, fixture.context.kzt_patch_spike_guard.write_attempts == 0 && + fixture.context.kzt_patch_spike_guard.write_successes == 0); + CHECK(prefix, before_acquire_calls == 1); + CHECK(prefix, lease_seen_at_first_slot_load == 1); + CHECK(prefix, slot_load_calls > 0 && source_memory_access_calls > 0); + CHECK(prefix, fixture.pending.armed == 0 && result.pending_cleared == 1); + fixture_destroy(&fixture); +} + +static void test_guard_fail_open_keeps_real_completion_evidence(void) +{ + run_completion_guard_fail_open(GUARD_FAIL_OPEN_DISABLED, + "guard-disabled-completion"); + run_completion_guard_fail_open(GUARD_FAIL_OPEN_CIRCUIT, + "guard-circuit-completion"); + run_completion_guard_fail_open(GUARD_FAIL_OPEN_BUDGET_ZERO, + "guard-budget-completion"); +} + +static void run_after_cas_retire(fixture_t *fixture, const char *prefix) +{ + retire_sync_t sync; + pthread_t thread; + kzt_lazy_binding_route_result_t result; + int created; + + if (retire_sync_init(&sync, fixture) != 0) { + CHECK(prefix, 0); + return; + } + hooks_reset(fixture, HOOK_RETIRE_AFTER_CAS); + hook_retire = &sync; + created = pthread_create(&thread, NULL, retire_worker, &sync); + CHECK(prefix, created == 0); + if (created != 0) { + retire_sync_destroy(&sync); + return; + } + CHECK(prefix, fixture_route(fixture, &result) == 0); + CHECK(prefix, pthread_join(thread, NULL) == 0); + CHECK(prefix, sync.unloading_observed == 1); + CHECK(prefix, sync.completed_inside_hook == 0); + CHECK(prefix, sync.retire_done == 1 && sync.retire_result == 0); + CHECK(prefix, result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED); + CHECK(prefix, fixture->slot == fixture_native_bridge); + retire_sync_destroy(&sync); +} + +static void test_retire_waits_for_writer_transaction(void) +{ + fixture_t fixture; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + run_after_cas_retire(&fixture, "after-cas"); + fixture_destroy(&fixture); +} + +static void test_completion_holds_one_lease_until_final_release(void) +{ + fixture_t fixture; + retire_sync_t sync; + pthread_t thread; + kzt_lazy_binding_result_t result; + int created; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + if (retire_sync_init(&sync, &fixture) != 0) { + CHECK("completion.sync", 0); + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_RETIRE_AFTER_CAS); + hook_retire = &sync; + created = pthread_create(&thread, NULL, retire_worker, &sync); + CHECK("completion.thread", created == 0); + if (created != 0) { + retire_sync_destroy(&sync); + fixture_destroy(&fixture); + return; + } + CHECK("completion.call", kzt_production_lazy_complete( + &fixture.context, &fixture.pending, &result) == 0); + CHECK("completion.join", pthread_join(thread, NULL) == 0); + CHECK("completion.first-read-leased", lease_seen_at_first_slot_load == 1); + CHECK("completion.single-acquire", before_acquire_calls == 1); + CHECK("completion.retire-blocked", sync.unloading_observed == 1 && + sync.completed_inside_hook == 0); + CHECK("completion.release-allows-retire", sync.retire_done == 1 && + sync.retire_result == 0); + CHECK("completion.applied", result.status == KZT_LAZY_BINDING_NATIVE_APPLIED); + CHECK("completion.pending-cleared", fixture.pending.armed == 0); + retire_sync_destroy(&sync); + fixture_destroy(&fixture); +} + +static void test_retire_waits_through_verify_failure_and_rollback(void) +{ + fixture_t fixture; + retire_sync_t sync; + pthread_t thread; + kzt_lazy_binding_route_result_t result; + int created; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + if (retire_sync_init(&sync, &fixture) != 0) { + CHECK("rollback.sync", 0); + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_RETIRE_AFTER_CAS_ROLLBACK); + hook_retire = &sync; + created = pthread_create(&thread, NULL, retire_worker, &sync); + CHECK("rollback.thread", created == 0); + if (created != 0) { + retire_sync_destroy(&sync); + fixture_destroy(&fixture); + return; + } + CHECK("rollback.route", fixture_route(&fixture, &result) == 0); + CHECK("rollback.join", pthread_join(thread, NULL) == 0); + CHECK("rollback.retire-blocked", sync.unloading_observed == 1 && + sync.completed_inside_hook == 0); + CHECK("rollback.two-cas", after_cas_calls == 2); + CHECK("rollback.slot-restored", fixture.slot == GUEST_TARGET); + CHECK("rollback.guest-preserved", + result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED); + CHECK("rollback.retire-after-release", sync.retire_done == 1 && + sync.retire_result == 0); + retire_sync_destroy(&sync); + fixture_destroy(&fixture); +} + +static void test_fail_open_and_non_writer_paths(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t result; + kzt_lazy_binding_result_t completion_result; + kzt_guest_registry_t *live_registry; + kzt_guest_registry_t *disabled_registry; + unsigned long live_generation; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + live_registry = fixture.context.kzt_guest_registry_context.registry; + live_generation = fixture.pending.source_generation; + + hooks_reset(&fixture, HOOK_NONE); + fixture.context.kzt_guest_registry_context.registry = NULL; + CHECK("null.route", fixture_route(&fixture, &result) == 0); + CHECK("null.preserved", result.status == + KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED && + fixture.slot == GUEST_TARGET); + CHECK("null.no-lease", before_acquire_calls == 0); + fixture.context.kzt_guest_registry_context.registry = live_registry; + + kzt_guest_registry_test_set_alloc_failure_after(1); + disabled_registry = kzt_guest_registry_init(); + kzt_guest_registry_test_set_alloc_failure_after(-1); + CHECK("disabled.registry", disabled_registry != NULL); + hooks_reset(&fixture, HOOK_NONE); + fixture.context.kzt_guest_registry_context.registry = disabled_registry; + CHECK("disabled.route", fixture_route(&fixture, &result) == 0); + CHECK("disabled.preserved", result.status == + KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED && + fixture.slot == GUEST_TARGET); + CHECK("disabled.lease-attempt", before_acquire_calls == 1); + CHECK("disabled.no-source-memory", source_memory_access_calls == 0); + CHECK("disabled.no-slot-read", slot_load_calls == 0); + fixture.context.kzt_guest_registry_context.registry = live_registry; + kzt_guest_registry_destroy(&disabled_registry); + + hooks_reset(&fixture, HOOK_NONE); + fixture.pending.source_generation = live_generation + 1; + CHECK("stale.route", fixture_route(&fixture, &result) == 0); + CHECK("stale.preserved", result.status == + KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED && + fixture.slot == GUEST_TARGET); + CHECK("stale.lease-attempt", before_acquire_calls == 1); + CHECK("stale.no-source-memory", source_memory_access_calls == 0); + CHECK("stale.no-slot-read", slot_load_calls == 0); + fixture.pending.source_generation = live_generation; + + hooks_reset(&fixture, HOOK_NONE); + reset_guard(&fixture, 0); + CHECK("disabled-guard.route", fixture_route(&fixture, &result) == 0); + CHECK("disabled-guard.exact-lease", before_acquire_calls == 1); + CHECK("disabled-guard.source-validated", source_memory_access_calls > 0); + CHECK("disabled-guard.slot", fixture.slot == GUEST_TARGET); + + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_NONE); + CHECK("bound.first", fixture_route(&fixture, &result) == 0); + CHECK("bound.first-applied", fixture.slot == fixture_native_bridge); + hooks_reset(&fixture, HOOK_NONE); + CHECK("bound.second", kzt_production_lazy_complete( + &fixture.context, &fixture.pending, &completion_result) == 0); + CHECK("bound.revalidation-lease", before_acquire_calls == 1); + CHECK("bound.second-result", + completion_result.status == KZT_LAZY_BINDING_GUEST_PRESERVED && + completion_result.reason == + KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE); + CHECK("bound.second-values", + completion_result.slot_before == fixture_native_bridge && + completion_result.slot_after == fixture_native_bridge && + completion_result.selected_target == fixture_native_bridge); + CHECK("bound.second-pending", + fixture.pending.armed == 0 && completion_result.pending_armed == 0 && + completion_result.pending_cleared == 1); + CHECK("bound.slot-preserved", fixture.slot == fixture_native_bridge); + fixture_destroy(&fixture); +} + +static void test_retire_writer_stress_1000(void) +{ + fixture_t fixture; + int baseline_failures; + int i; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + baseline_failures = failures; + for (i = 0; i < STRESS_ITERATIONS; ++i) { + fixture.slot = GUEST_TARGET; + reset_guard(&fixture, 1); + run_after_cas_retire(&fixture, "stress"); + if (failures != baseline_failures) { + fprintf(stderr, "stress failed at iteration %d\n", i); + break; + } + if (i + 1 < STRESS_ITERATIONS) { + fixture.pending.source_generation = fixture_publish_source(&fixture); + } + } + CHECK("stress.iterations", i == STRESS_ITERATIONS); + fixture_destroy(&fixture); +} + +int main(void) +{ + test_successful_production_route(); + test_guest_version_is_not_used_for_host_lookup(); + test_native_symbol_missing_fails_open(); + test_dependency_symbol_owner_mismatch_fails_open(); + test_missing_bridge_is_created_from_exact_provider(); + test_created_inexact_bridge_fails_open(); + test_eager_production_request_evidence(); + test_confirmed_unversioned_production_paths_apply(); + test_unknown_version_evidence_preserves_guest_path(); + test_production_permission_transaction_preserves_on_restore_error(); + test_recycle_before_acquire_preserves_guest(); + test_retire_before_first_slot_read_skips_guest_memory(); + test_namespace_mismatch_precedes_any_source_read(); + test_guard_fail_open_keeps_real_completion_evidence(); + test_retire_waits_for_writer_transaction(); + test_completion_holds_one_lease_until_final_release(); + test_retire_waits_through_verify_failure_and_rollback(); + test_fail_open_and_non_writer_paths(); + test_retire_writer_stress_1000(); + + if (failures) { + fprintf(stderr, "%d lazy production lease checks failed\n", failures); + return 1; + } + puts("KZT WI-256 lazy production source lease: PASS (1000 races)"); + return 0; +} diff --git a/tests/unit/kzt/test_wi256_plt_resolver_adapter.c b/tests/unit/kzt/test_wi256_plt_resolver_adapter.c new file mode 100644 index 00000000000..ad9ac688d2e --- /dev/null +++ b/tests/unit/kzt/test_wi256_plt_resolver_adapter.c @@ -0,0 +1,346 @@ +#include +#include + +#include "target/i386/latx/include/kzt_plt_resolver_adapter.h" + +typedef enum begin_outcome { + BEGIN_ARMED = 0, + BEGIN_PENDING_OCCUPIED, + BEGIN_GENERATION_CHANGED, + BEGIN_NON_MAIN_NAMESPACE, +} begin_outcome_t; + +typedef struct fixture { + CPUX86State cpu; + uint64_t stack[10]; + uintptr_t object_head; + uintptr_t relocation_slot; + uintptr_t return_address; + uintptr_t self_link_map; + uintptr_t object_guest_resolver; + uintptr_t forbidden_global_resolver; + unsigned long generation; + uintptr_t namespace_id; + kzt_guest_library_namespace_kind_t namespace_kind; + int source_present; + begin_outcome_t begin_outcome; + int lookup_calls; + int begin_calls; + int pending_creations; + kzt_lazy_binding_pending_t pending; + kzt_lazy_binding_begin_request_t captured_request; +} fixture_t; + +static int failures; + +#define CHECK(name, condition) do { \ + if (!(condition)) { \ + fprintf(stderr, "%s failed at line %d\n", name, __LINE__); \ + ++failures; \ + } \ +} while (0) + +static void reset_frame(fixture_t *fixture) +{ + memset(fixture->stack, 0, sizeof(fixture->stack)); + fixture->stack[3] = fixture->object_head; + fixture->stack[4] = fixture->relocation_slot; + fixture->stack[5] = fixture->return_address; + fixture->cpu.regs[R_ESP] = (uintptr_t)&fixture->stack[3]; +} + +static void fixture_init(fixture_t *fixture) +{ + memset(fixture, 0, sizeof(*fixture)); + fixture->object_head = 0x11000100; + fixture->relocation_slot = 5; + fixture->return_address = 0x12000200; + fixture->self_link_map = 0x13000300; + fixture->object_guest_resolver = 0x14000400; + fixture->forbidden_global_resolver = 0x15000500; + fixture->generation = 7; + fixture->namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN; + fixture->source_present = 1; + fixture->begin_outcome = BEGIN_ARMED; + reset_frame(fixture); +} + +static int lookup_source(uintptr_t object_head, + kzt_plt_resolver_source_t *source, void *opaque) +{ + fixture_t *fixture = opaque; + + ++fixture->lookup_calls; + CHECK("lookup.object-head", object_head == fixture->object_head); + if (!fixture->source_present) { + return -1; + } + *source = (kzt_plt_resolver_source_t) { + .context_id = 0x16000600, + .object_head = fixture->object_head, + .source_link_map = fixture->self_link_map, + .source_generation = fixture->generation, + .namespace_id = fixture->namespace_id, + .namespace_kind = fixture->namespace_kind, + .guest_resolver = fixture->object_guest_resolver, + }; + return 0; +} + +/* Contract 1 is mocked as a boundary. No owner/provider/native decision is + * reproduced in this adapter test. */ +static int begin_lazy_binding( + const kzt_lazy_binding_begin_request_t *request, + kzt_lazy_binding_pending_t *pending, + kzt_lazy_binding_result_t *result, void *opaque) +{ + fixture_t *fixture = opaque; + + ++fixture->begin_calls; + fixture->captured_request = *request; + memset(result, 0, sizeof(*result)); + result->selected_target = fixture->object_guest_resolver; + switch (fixture->begin_outcome) { + case BEGIN_ARMED: + if (!pending->armed) { + pending->armed = 1; + ++fixture->pending_creations; + } + result->status = KZT_LAZY_BINDING_HANDOFF_GUEST; + result->pending_armed = 1; + break; + case BEGIN_PENDING_OCCUPIED: + result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; + result->reason = KZT_LAZY_BINDING_REASON_PENDING_OCCUPIED; + result->pending_armed = 0; + break; + case BEGIN_GENERATION_CHANGED: + result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; + result->reason = KZT_LAZY_BINDING_REASON_GENERATION_CHANGED; + result->pending_armed = 0; + break; + case BEGIN_NON_MAIN_NAMESPACE: + result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; + result->reason = KZT_LAZY_BINDING_REASON_NON_MAIN_NAMESPACE; + result->pending_armed = 0; + break; + } + return 0; +} + +static kzt_plt_resolver_runtime_ops_t ops_for(fixture_t *fixture) +{ + return (kzt_plt_resolver_runtime_ops_t) { + .lookup_source = lookup_source, + .begin_lazy_binding = begin_lazy_binding, + .pending = &fixture->pending, + .opaque = fixture, + }; +} + +static void check_guest_frame(const char *prefix, fixture_t *fixture) +{ + uint64_t *sp = (uint64_t *)fixture->cpu.regs[R_ESP]; + + CHECK(prefix, sp == &fixture->stack[2]); + CHECK("frame.object-resolver", sp[0] == fixture->object_guest_resolver); + CHECK("frame.self-link-map", sp[1] == fixture->self_link_map); + CHECK("frame.relocation-slot", sp[2] == fixture->relocation_slot); + CHECK("frame.return-address", sp[3] == fixture->return_address); + CHECK("frame.no-global-resolver", + sp[0] != fixture->forbidden_global_resolver); +} + +static void check_original_frame(const char *prefix, fixture_t *fixture) +{ + uint64_t *sp = (uint64_t *)fixture->cpu.regs[R_ESP]; + + CHECK(prefix, sp == &fixture->stack[3]); + CHECK("original.object-head", sp[0] == fixture->object_head); + CHECK("original.relocation-slot", sp[1] == fixture->relocation_slot); + CHECK("original.return-address", sp[2] == fixture->return_address); +} + +static void test_reads_real_frame_and_preserves_return_address(void) +{ + fixture_t f; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + + fixture_init(&f); + ops = ops_for(&f); + CHECK("frame.enter", kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); + CHECK("frame.status", + result.status == KZT_PLT_RESOLVER_HANDOFF_GUEST); + CHECK("frame.object", result.object_head == f.object_head); + CHECK("frame.slot", result.relocation_slot == f.relocation_slot); + CHECK("frame.return", result.return_address == f.return_address); + CHECK("frame.lookup-once", f.lookup_calls == 1); + CHECK("frame.begin-once", f.begin_calls == 1); + check_guest_frame("frame.stack-pointer", &f); +} + +static void test_uses_per_object_resolver_and_calls_only_contract_one(void) +{ + fixture_t f; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + + fixture_init(&f); + ops = ops_for(&f); + CHECK("object.enter", kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); + CHECK("object.selected", + result.selected_resolver == f.object_guest_resolver); + CHECK("object.not-global", + result.selected_resolver != f.forbidden_global_resolver); + CHECK("object.contract-one-once", f.begin_calls == 1); + CHECK("object.request-link-map", + f.captured_request.source_link_map == f.self_link_map); + CHECK("object.request-generation", + f.captured_request.source_generation == f.generation); + CHECK("object.request-namespace", + f.captured_request.namespace_id == f.namespace_id && + f.captured_request.namespace_kind == f.namespace_kind); + CHECK("object.request-relocation", + f.captured_request.relocation_index == f.relocation_slot); +} + +static void test_main_generation_match_creates_one_pending(void) +{ + fixture_t f; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + + fixture_init(&f); + ops = ops_for(&f); + CHECK("pending.first", kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); + CHECK("pending.first-armed", result.pending_armed == 1); + CHECK("pending.created-once", f.pending_creations == 1); + + reset_frame(&f); + f.begin_outcome = BEGIN_PENDING_OCCUPIED; + CHECK("pending.second", kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); + CHECK("pending.second-fail-open", + result.status == KZT_PLT_RESOLVER_GUEST_PRESERVED); + CHECK("pending.still-created-once", f.pending_creations == 1); + CHECK("pending.contract-called-per-entry", f.begin_calls == 2); + check_guest_frame("pending.second-stack", &f); +} + +static void test_nested_pending_busy_preserves_outer_cpu_pending(void) +{ + fixture_t f; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + kzt_lazy_binding_pending_t saved; + + fixture_init(&f); + f.pending = (kzt_lazy_binding_pending_t) { + .armed = 1, + .context_id = 0x9000, + .source_link_map = 0x9100, + .source_generation = 11, + .relocation_index = 4, + .slot_addr = 0x9200, + .guest_resolver = 0x9300, + }; + saved = f.pending; + f.begin_outcome = BEGIN_PENDING_OCCUPIED; + ops = ops_for(&f); + CHECK("nested.enter", kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); + CHECK("nested.preserved", !memcmp(&f.pending, &saved, sizeof(saved))); + CHECK("nested.status", + result.status == KZT_PLT_RESOLVER_GUEST_PRESERVED); + check_guest_frame("nested.stack-pointer", &f); +} + +static void test_missing_object_restores_original_frame(void) +{ + fixture_t f; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + + fixture_init(&f); + ops = ops_for(&f); + f.source_present = 0; + CHECK("missing.enter", kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); + CHECK("missing.status", + result.status == KZT_PLT_RESOLVER_LEGACY_FRAME_RESTORED); + CHECK("missing.no-contract-one", f.begin_calls == 0); + CHECK("missing.no-pending", f.pending_creations == 0); + check_original_frame("missing.stack-pointer", &f); +} + +static void test_second_object_lookup_failure_never_uses_first_resolver(void) +{ + fixture_t f; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + + fixture_init(&f); + f.object_head = 0x21000100; + f.self_link_map = 0x23000300; + f.object_guest_resolver = 0x24000400; + f.forbidden_global_resolver = 0x14000400; + f.source_present = 0; + reset_frame(&f); + ops = ops_for(&f); + CHECK("second-missing.enter", + kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); + CHECK("second-missing.legacy-frame", + result.status == KZT_PLT_RESOLVER_LEGACY_FRAME_RESTORED); + CHECK("second-missing.no-selected-resolver", + result.selected_resolver == 0); + CHECK("second-missing.not-first-resolver", + result.selected_resolver != f.forbidden_global_resolver); + CHECK("second-missing.no-begin", f.begin_calls == 0); + check_original_frame("second-missing.stack-pointer", &f); +} + +static void test_contract_one_rejections_restore_guest_frame(void) +{ + const begin_outcome_t outcomes[] = { + BEGIN_PENDING_OCCUPIED, + BEGIN_GENERATION_CHANGED, + BEGIN_NON_MAIN_NAMESPACE, + }; + + for (size_t i = 0; i < sizeof(outcomes) / sizeof(outcomes[0]); ++i) { + fixture_t f; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + + fixture_init(&f); + ops = ops_for(&f); + f.begin_outcome = outcomes[i]; + if (outcomes[i] == BEGIN_NON_MAIN_NAMESPACE) { + f.namespace_id = 1; + f.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_EXPLICIT; + } + CHECK("reject.enter", + kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); + CHECK("reject.status", + result.status == KZT_PLT_RESOLVER_GUEST_PRESERVED); + CHECK("reject.contract-one-once", f.begin_calls == 1); + CHECK("reject.no-pending", f.pending_creations == 0); + check_guest_frame("reject.stack-pointer", &f); + } +} + +int main(void) +{ + test_reads_real_frame_and_preserves_return_address(); + test_uses_per_object_resolver_and_calls_only_contract_one(); + test_main_generation_match_creates_one_pending(); + test_nested_pending_busy_preserves_outer_cpu_pending(); + test_missing_object_restores_original_frame(); + test_second_object_lookup_failure_never_uses_first_resolver(); + test_contract_one_rejections_restore_guest_frame(); + if (failures) { + fprintf(stderr, "%d WI-256 resolver-adapter checks failed\n", + failures); + return 1; + } + puts("KZT WI-256 PLT resolver adapter: PASS"); + return 0; +} diff --git a/tests/unit/kzt/test_wi256_plt_resolver_source_contract.py b/tests/unit/kzt/test_wi256_plt_resolver_source_contract.py new file mode 100644 index 00000000000..1e6c6d09aee --- /dev/null +++ b/tests/unit/kzt/test_wi256_plt_resolver_source_contract.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def fail(message: str) -> None: + raise AssertionError(message) + + +root = pathlib.Path(sys.argv[1]).resolve() +adapter = root / "target/i386/latx/context/kzt_plt_resolver_adapter.c" +elfloader = root / "target/i386/latx/context/elfloader.c" + +if not adapter.is_file(): + fail(f"missing production adapter: {adapter}") + +adapter_text = adapter.read_text(encoding="utf-8") +elfloader_text = elfloader.read_text(encoding="utf-8") +lazy_header = (root / "target/i386/latx/include/kzt_lazy_binding.h").read_text( + encoding="utf-8" +) +lazy_core = (root / "target/i386/latx/context/kzt_lazy_binding.c").read_text( + encoding="utf-8" +) + +if not any( + call in adapter_text + for call in ("kzt_lazy_binding_begin(", "->begin_lazy_binding(") +): + fail("resolver adapter must delegate pending decisions to Contract 1") + +for forbidden in ( + "getAlternate(", + "GetGlobalSymbolStartEnd", + "kzt_owner_resolver", + "kzt_wrapper_probe", + "kzt_bridge_exact", +): + if forbidden in adapter_text: + fail(f"resolver adapter duplicates native resolution: {forbidden}") + +if "dl_runtime_resolver" in adapter_text: + fail("resolver adapter must never substitute a process-global resolver") + +entry = "kzt_plt_resolver_enter(" +resolver_start = elfloader_text.find("void PltResolver(void)") +if resolver_start < 0: + fail("missing production PltResolver slow path") +body_start = elfloader_text.find("{", resolver_start) +depth = 0 +resolver_end = -1 +for offset, char in enumerate(elfloader_text[body_start:], start=body_start): + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + resolver_end = offset + 1 + break +if resolver_end < 0: + fail("could not delimit production PltResolver") +if entry not in elfloader_text[resolver_start:resolver_end]: + fail("PltResolver slow path does not call kzt_plt_resolver_enter") + +relocate_start = elfloader_text.find("int RelocateElfPlt(") +relocate_end = elfloader_text.find("\n#if 0", relocate_start) +if relocate_start < 0 or relocate_end < 0: + fail("could not delimit RelocateElfPlt cold path") +relocate_body = elfloader_text[relocate_start:relocate_end] +if "kzt_lazy_completion_bridge = AddBridge(" not in relocate_body: + fail("completion bridge must be created in RelocateElfPlt cold path") + +for forbidden_seam in ("resolve_native_bridge", "compare_exchange_slot"): + if forbidden_seam in lazy_header or forbidden_seam in lazy_core: + fail(f"lazy core retains test-only fallback seam: {forbidden_seam}") + +production_route = ( + root / "target/i386/latx/context/kzt_jump_slot_production.c" +).read_text(encoding="utf-8") +resolver_complete_start = elfloader_text.find( + "static void KztLazyBindingCompleteResolver(void)" +) +resolver_complete_end = elfloader_text.find( + "typedef struct kzt_plt_resolver_production_state", resolver_complete_start +) +if resolver_complete_start < 0 or resolver_complete_end < 0: + fail("could not delimit lazy completion resolver") +if "kzt_production_lazy_complete(" not in elfloader_text[ + resolver_complete_start:resolver_complete_end +]: + fail("completion resolver must call the testable production orchestration") +if "kzt_lazy_binding_complete(" in elfloader_text[ + resolver_complete_start:resolver_complete_end +]: + fail("completion resolver bypasses production lease orchestration") +lazy_production_start = production_route.find( + "int kzt_production_lazy_route_guest_target(" +) +if lazy_production_start < 0: + fail("missing lazy production route") +lazy_production = production_route[lazy_production_start:] +for forbidden_lookup in ( + "GetGlobalSymbolStartEndWithProvider", + "GetGlobalSymbolStartEnd", + "GetMaplib", +): + if forbidden_lookup in lazy_production: + fail(f"lazy completion repeats legacy symbol lookup: {forbidden_lookup}") +if "validate_source_identity" not in production_route: + fail("production route lacks write-adjacent source generation recheck") +for required in ( + "kzt_production_lazy_load_slot_with_lease(", + "kzt_lazy_binding_complete(", + "kzt_guest_registry_source_lease_release(", +): + if required not in production_route: + fail(f"production completion orchestration missing: {required}") + +production_hits = [] +for path in (root / "target/i386").rglob("*.[ch]"): + if path == adapter or path.name == "kzt_plt_resolver_adapter.h": + continue + if entry in path.read_text(encoding="utf-8", errors="ignore"): + production_hits.append(path.relative_to(root).as_posix()) + +if production_hits != ["target/i386/latx/context/elfloader.c"]: + fail(f"resolver enter escaped the one-shot PltResolver path: {production_hits}") + +for hot_root in ( + root / "target/i386/tcg", + root / "target/i386/latx/translator", +): + if not hot_root.exists(): + continue + for path in hot_root.rglob("*.[ch]"): + if entry in path.read_text(encoding="utf-8", errors="ignore"): + fail(f"per-TB resolver check introduced in {path.relative_to(root)}") + +print("WI-256 PLT resolver one-shot source contract: PASS") diff --git a/tests/unit/kzt/test_wi382_registry_observation_source_contract.py b/tests/unit/kzt/test_wi382_registry_observation_source_contract.py new file mode 100644 index 00000000000..360b190ef60 --- /dev/null +++ b/tests/unit/kzt/test_wi382_registry_observation_source_contract.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def function_body(text: str, signature: str) -> str: + start = text.index(signature) + brace = text.index("{", start + len(signature)) + depth = 0 + for pos in range(brace, len(text)): + if text[pos] == "{": + depth += 1 + elif text[pos] == "}": + depth -= 1 + if depth == 0: + return text[brace + 1:pos] + raise AssertionError(f"unterminated function: {signature}") + + +root = pathlib.Path(sys.argv[1]).resolve() +myalign = (root / "target/i386/latx/context/myalign.c").read_text() +elfloader = (root / "target/i386/latx/context/elfloader.c").read_text() +adapter = ( + root / "target/i386/latx/context/kzt_observation_adapter.c" +).read_text() +registry_context = ( + root / "target/i386/latx/context/kzt_guest_registry_context.c" +).read_text() +context_header = ( + root / "target/i386/latx/include/box64context.h" +).read_text() +production_meson = ( + root / "target/i386/latx/context/meson.build" +).read_text() +production_route = ( + root / "target/i386/latx/context/kzt_jump_slot_production.c" +).read_text() + +# The old callback must reject NULL identity and path before dereferencing. +legacy = function_body(myalign, "static int kzt_tb_callback_legacy(") +identity_guard = legacy.index("if (!my_lm)") +first_dereference = legacy.index("my_lm->") +path_guard = legacy.index("if (!my_lm->l_name)") +assert identity_guard < first_dereference +assert path_guard == legacy.index("my_lm->l_name") - len("if (!") + +# Public link_map evidence plus ELF PT_LOAD ranges replace glibc-private +# l_ns/l_map_start/l_map_end offsets. Startup publishes the exact pair while +# the callback gate is still held; dlopen keeps its scoped publish path. +assert "l_map_start" not in legacy +assert "l_map_end" not in legacy +assert "->l_ns" not in legacy +assert "GetElfLoadRange(" in legacy +assert "if (lib && !loader_scope_active &&" in legacy +assert "kzt_guest_library_note_loader_pair(" in legacy + +# A malformed ELF or either relocation failure must leave the startup pair +# unpublished. The final publish is reached only through the shared success +# condition after both failure-returning calls. +header_load = legacy.index("h = LoadAndCheckElfHeader(") +header_guard = legacy.index("if (!h)", header_load) +header_use = legacy.index("ElfHeadReFix(h", header_load) +assert header_load < header_guard < header_use +relocate = legacy.index("RelocateElf(", header_use) +relocate_plt = legacy.index("RelocateElfPlt(", relocate) +publish = legacy.index("kzt_guest_library_note_loader_pair(", relocate_plt) +assert "relocate_result" in legacy[relocate:publish] +assert "relocate_plt_result" in legacy[relocate:publish] +success_condition = legacy.rfind("if (", relocate_plt, publish) +assert success_condition >= 0 +condition = legacy[success_condition:publish] +assert "relocate_result == 0" in condition +assert "relocate_plt_result == 0" in condition + +plt_observation = function_body(elfloader, "static void kzt_observe_plt_source(") +assert "kzt_elfloader_head_identity(elf_header" in plt_observation +assert "info1.pt_dynamic_addr" not in plt_observation +assert "kzt_guest_link_map_classify_namespace(" in plt_observation +assert "kzt_guest_registry_context_get_main_namespace_head(" in plt_observation +assert "kzt_guest_registry_context_has_main_namespace_evidence(" in plt_observation +assert "kzt_guest_link_map_read_predecessor(" in plt_observation +assert "GetElfLoadRange(" in plt_observation +assert ".namespace_id_present = main_namespace == 1" in plt_observation +assert ".map_range_present = range_available" in plt_observation +relocate_plt = function_body(elfloader, "int RelocateElfPlt(") +assert "uintptr_t kzt_evidence_got = head->pltgot" in relocate_plt +assert "kzt_evidence_got_runtime + 8" in relocate_plt +assert "kzt_guest_link_map_identity_matches(" in relocate_plt +assert "head->pltgot ? head->pltgot : head->got" in relocate_plt +assert relocate_plt.index("kzt_observe_plt_source(") < relocate_plt.index( + "head->had_RelocateElfPlt = 1" +) + +# Production diagnostics are linked, opt-in, and avoid a configure lock when off. +assert "'kzt_guest_dynamic_diagnostics.c'" in production_meson +callback = function_body(myalign, "static void kzt_tb_callback(") +assert "kzt_main_elf_identity(elf_header" in callback +assert "info1.pt_dynamic_addr" not in callback +assert "kzt_guest_link_map_read_predecessor(" in callback +assert "kzt_guest_registry_context_confirm_main_namespace_head(" in callback +assert "int diagnostics_enabled = kzt_registry_diagnostics_enabled();" in callback +assert ".enabled = diagnostics_enabled" in callback +assert ".diagnostics_enabled = diagnostics_enabled" in callback +assert "if (registry && diagnostics_enabled)" in callback + +compare = function_body(adapter, "static void kzt_adapter_compare_dynamic_views(") +assert compare.index("!request->diagnostics_enabled") < compare.index( + "kzt_guest_registry_find_dynamic_view(" +) +assert "existing_result.unknown_tag_count = existing_view.unknown_tag_count;" in compare + +# Context ownership is represented as one state object with an atomic hot path. +assert "kzt_guest_registry_context_t kzt_guest_registry_context;" in context_header +assert "__atomic_load_n(&context->state" in registry_context +assert "kzt_guest_registry_context_destroy(" in registry_context +assert "registry->" not in registry_context + +# Early fail-open remains observable when enrichment stops before a full +# planner/writer diagnostic can be built, but only under the diagnostics gate. +emit = function_body(production_route, "static void production_emit_diagnostic(") +assert "kzt_registry_diagnostics_enabled()" in emit +assert "kzt_rela_fail_open stage=%s" in emit +assert "ROUTE_PRECONDITIONS" in production_route +assert "BASE_EVIDENCE" in production_route +assert "EXACT_LIBRARY_BINDING" in production_route +assert "BRIDGE_EVIDENCE" in production_route +assert "SOURCE_IDENTITY" in production_route +assert "WRITER" in production_route + +print("WI-382 registry observation source contract: PASS") diff --git a/tests/unit/kzt/test_wrapper_bridge_provider.c b/tests/unit/kzt/test_wrapper_bridge_provider.c new file mode 100644 index 00000000000..f9f40398ee8 --- /dev/null +++ b/tests/unit/kzt/test_wrapper_bridge_provider.c @@ -0,0 +1,381 @@ +#include +#include + +#include "target/i386/latx/include/kzt_wrapper_bridge_provider.h" + +static int failures; + +typedef struct fake_library { + int inspect_status; + const char *name; + uintptr_t native_symbol; + uintptr_t bridge_target; + uintptr_t add_target; + uintptr_t post_add_target; + int bridge_exact; + int lifetime_bound; + int inspect_calls; + int check_calls; + int add_calls; +} fake_library_t; + +static void fake_wrapper(uintptr_t fnc) +{ + (void)fnc; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, uintptr_t got, uintptr_t expected) +{ + if (got == expected) { + return; + } + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static int fake_inspect( + void *library, const char *symbol_name, const char *symbol_version, + kzt_wrapper_bridge_provider_match_t *match, void *opaque) +{ + fake_library_t *lib = library; + + (void)opaque; + ++lib->inspect_calls; + if (lib->inspect_status <= 0) { + return lib->inspect_status; + } + if (strcmp(symbol_name, "gtk_widget_show") != 0 || + (symbol_version && strcmp(symbol_version, "GTK_3.0") != 0)) { + return -1; + } + + match->wrapper_name = lib->name; + snprintf(match->native_name, sizeof(match->native_name), "%s", + symbol_name); + match->abi_wrapper = fake_wrapper; + match->native_symbol = lib->native_symbol; + match->resolved_bridge_target = lib->bridge_target; + match->context_owner = lib; + match->wrapper_provider = lib; + match->native_lookup_handle = lib; + match->native_owner = lib; + match->bridge_owner = lib; + match->bridge_storage = lib; + match->resolved_bridge_exact = lib->bridge_exact; + match->wrapper_provider_lifetime_bound = lib->lifetime_bound; + match->native_owner_lifetime_bound = lib->lifetime_bound; + match->bridge_owner_lifetime_bound = lib->lifetime_bound; + return 1; +} + +static uintptr_t fake_check( + const kzt_wrapper_bridge_provider_match_t *match, void *opaque) +{ + fake_library_t *lib = match->bridge_owner; + + (void)opaque; + ++lib->check_calls; + return lib->bridge_target; +} + +static uintptr_t fake_add( + const kzt_wrapper_bridge_provider_match_t *match, + const kzt_wrapper_probe_bridge_request_t *request, void *opaque) +{ + fake_library_t *lib = match->bridge_owner; + + (void)opaque; + if (request->native_symbol != lib->native_symbol) { + return 0; + } + ++lib->add_calls; + lib->bridge_target = lib->post_add_target ? lib->post_add_target : + lib->add_target; + return lib->add_target; +} + +static kzt_wrapper_bridge_provider_runtime_ops_t fake_ops(void) +{ + return (kzt_wrapper_bridge_provider_runtime_ops_t) { + .inspect_library = fake_inspect, + .check_bridge = fake_check, + .add_bridge = fake_add, + }; +} + +static void test_exact_unique_candidate_builds_temporary_provider(void) +{ + fake_library_t skipped = { .inspect_status = 0 }; + fake_library_t exact = { + .inspect_status = 1, + .name = "libgtk-3.so.0", + .native_symbol = 0x71000000, + .bridge_target = 0x72000000, + .bridge_exact = 1, + .lifetime_bound = 1, + }; + void *libraries[] = { &skipped, &exact, &exact }; + kzt_wrapper_bridge_provider_runtime_ops_t ops = fake_ops(); + kzt_wrapper_bridge_provider_t provider; + kzt_wrapper_probe_result_t result; + kzt_wrapper_probe_request_t request = { + .symbol_name = "gtk_widget_show", + .symbol_version = "GTK_3.0", + }; + + ops.add_bridge = NULL; + + check_int("exact.prepare", + kzt_wrapper_bridge_provider_prepare( + &provider, libraries, 3, request.symbol_name, + request.symbol_version, &ops), + 1); + check_int("exact.duplicate-inspected-once", exact.inspect_calls, 1); + check_int("exact.probe", + kzt_wrapper_probe_minimal_manifest( + &provider.manifest, &request, &provider.bridge_ops, + &result), + 0); + check_int("exact.match", result.wrapper_match, + KZT_PATCH_WRAPPER_VERSION_MATCH); + check_ulong("exact.native", result.native_symbol, 0x71000000); + check_ulong("exact.bridge", result.bridge_target, 0x72000000); + check_int("exact.check", exact.check_calls, 1); + check_int("exact.no-add", exact.add_calls, 0); + check_int("exact.add-disabled", + provider.bridge_ops.add_bridge == NULL, 1); + check_int("exact.bound-owner", + provider.match.bridge_owner == &exact, 1); +} + +static void test_unversioned_query_failure_and_ambiguity_fail_open(void) +{ + fake_library_t exact_a = { + .inspect_status = 1, + .name = "libgtk-3.so.0", + .native_symbol = 0x71000000, + .bridge_target = 0x72000000, + .bridge_exact = 1, + .lifetime_bound = 1, + }; + fake_library_t exact_b = { + .inspect_status = 1, + .name = "libgtk-shadow.so.0", + .native_symbol = 0x71001000, + .bridge_target = 0x72001000, + .bridge_exact = 1, + .lifetime_bound = 1, + }; + fake_library_t failed = { .inspect_status = -1 }; + kzt_wrapper_bridge_provider_runtime_ops_t ops = fake_ops(); + kzt_wrapper_bridge_provider_t provider; + void *one[] = { &exact_a }; + void *ambiguous[] = { &exact_a, &exact_b }; + void *query_failed[] = { &exact_a, &failed }; + + check_int("unversioned.prepare", + kzt_wrapper_bridge_provider_prepare( + &provider, one, 1, "gtk_widget_show", NULL, &ops), + 0); + check_int("unversioned.no-query", exact_a.inspect_calls, 0); + + check_int("ambiguous.prepare", + kzt_wrapper_bridge_provider_prepare( + &provider, ambiguous, 2, "gtk_widget_show", + "GTK_3.0", &ops), + 0); + check_int("ambiguous.unavailable", provider.manifest.available, 0); + + check_int("query-failed.prepare", + kzt_wrapper_bridge_provider_prepare( + &provider, query_failed, 2, "gtk_widget_show", + "GTK_3.0", &ops), + 0); + check_int("query-failed.unavailable", provider.manifest.available, 0); +} + +static void test_confirmed_unversioned_provider_is_probeable(void) +{ + fake_library_t exact = { + .inspect_status = 1, + .name = "libgtk-3.so.0", + .native_symbol = 0x71000000, + .bridge_target = 0x72000000, + .bridge_exact = 1, + .lifetime_bound = 1, + }; + void *libraries[] = { &exact }; + kzt_wrapper_bridge_provider_runtime_ops_t ops = fake_ops(); + kzt_wrapper_bridge_provider_t provider; + kzt_wrapper_probe_request_t request = { + .symbol_name = "gtk_widget_show", + .symbol_version_evidence = + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + .symbol_version = NULL, + }; + kzt_wrapper_probe_result_t result; + + ops.add_bridge = NULL; + check_int("confirmed-unversioned.prepare", + kzt_wrapper_bridge_provider_prepare_with_version_evidence( + &provider, libraries, 1, request.symbol_name, + request.symbol_version_evidence, request.symbol_version, + &ops), + 1); + check_int("confirmed-unversioned.inspected", exact.inspect_calls, 1); + check_int("confirmed-unversioned.probe", + kzt_wrapper_probe_minimal_manifest( + &provider.manifest, &request, &provider.bridge_ops, + &result), + 0); + check_int("confirmed-unversioned.match", result.wrapper_match, + KZT_PATCH_WRAPPER_UNVERSIONED_MATCH); + check_ulong("confirmed-unversioned.bridge", result.bridge_target, + exact.bridge_target); +} + +static void test_missing_lifetime_binding_fails_open(void) +{ + fake_library_t unbound = { + .inspect_status = 1, + .name = "libgtk-3.so.0", + .native_symbol = 0x71000000, + .bridge_target = 0x72000000, + .bridge_exact = 1, + .lifetime_bound = 0, + }; + void *libraries[] = { &unbound }; + kzt_wrapper_bridge_provider_runtime_ops_t ops = fake_ops(); + kzt_wrapper_bridge_provider_t provider; + + check_int("unbound.prepare", + kzt_wrapper_bridge_provider_prepare( + &provider, libraries, 1, "gtk_widget_show", + "GTK_3.0", &ops), + 0); + check_int("unbound.unavailable", provider.manifest.available, 0); +} + +static void test_bridge_abi_conflict_fails_open_without_add(void) +{ + fake_library_t conflict = { + .inspect_status = 1, + .name = "libgtk-3.so.0", + .native_symbol = 0x71000000, + .bridge_target = 0x72000000, + .bridge_exact = 0, + .lifetime_bound = 1, + }; + void *libraries[] = { &conflict }; + kzt_wrapper_bridge_provider_runtime_ops_t ops = fake_ops(); + kzt_wrapper_bridge_provider_t provider; + + check_int("conflict.prepare", + kzt_wrapper_bridge_provider_prepare( + &provider, libraries, 1, "gtk_widget_show", + "GTK_3.0", &ops), + 0); + check_int("conflict.unavailable", provider.manifest.available, 0); + check_int("conflict.no-add", conflict.add_calls, 0); +} + +static void test_missing_bridge_is_created_then_rechecked(void) +{ + fake_library_t missing = { + .inspect_status = 1, + .name = "libgtk-3.so.0", + .native_symbol = 0x71000000, + .bridge_target = 0, + .add_target = 0x72000000, + .bridge_exact = 0, + .lifetime_bound = 1, + }; + void *libraries[] = { &missing }; + kzt_wrapper_bridge_provider_runtime_ops_t ops = fake_ops(); + kzt_wrapper_bridge_provider_t provider; + kzt_wrapper_probe_result_t result; + kzt_wrapper_probe_request_t request = { + .symbol_name = "gtk_widget_show", + .symbol_version = "GTK_3.0", + }; + + check_int("missing.prepare", + kzt_wrapper_bridge_provider_prepare( + &provider, libraries, 1, request.symbol_name, + request.symbol_version, &ops), + 1); + check_int("missing.probe", + kzt_wrapper_probe_minimal_manifest( + &provider.manifest, &request, &provider.bridge_ops, + &result), + 0); + check_ulong("missing.bridge", result.bridge_target, 0x72000000); + check_int("missing.bridge-source", result.bridge_source, + KZT_WRAPPER_PROBE_BRIDGE_ADD_BRIDGE); + check_int("missing.check-twice", missing.check_calls, 2); + check_int("missing.add-once", missing.add_calls, 1); +} + +static void test_created_bridge_must_match_recheck(void) +{ + fake_library_t inexact = { + .inspect_status = 1, + .name = "libgtk-3.so.0", + .native_symbol = 0x71000000, + .bridge_target = 0, + .add_target = 0x72000000, + .post_add_target = 0x73000000, + .bridge_exact = 0, + .lifetime_bound = 1, + }; + void *libraries[] = { &inexact }; + kzt_wrapper_bridge_provider_runtime_ops_t ops = fake_ops(); + kzt_wrapper_bridge_provider_t provider; + kzt_wrapper_probe_result_t result; + kzt_wrapper_probe_request_t request = { + .symbol_name = "gtk_widget_show", + .symbol_version = "GTK_3.0", + }; + + check_int("inexact-created.prepare", + kzt_wrapper_bridge_provider_prepare( + &provider, libraries, 1, request.symbol_name, + request.symbol_version, &ops), + 1); + check_int("inexact-created.probe", + kzt_wrapper_probe_minimal_manifest( + &provider.manifest, &request, &provider.bridge_ops, + &result), + 0); + check_ulong("inexact-created.no-bridge", result.bridge_target, 0); + check_int("inexact-created.add-once", inexact.add_calls, 1); +} + +int main(void) +{ + test_exact_unique_candidate_builds_temporary_provider(); + test_unversioned_query_failure_and_ambiguity_fail_open(); + test_confirmed_unversioned_provider_is_probeable(); + test_missing_lifetime_binding_fails_open(); + test_bridge_abi_conflict_fails_open_without_add(); + test_missing_bridge_is_created_then_rechecked(); + test_created_bridge_must_match_recheck(); + + if (failures) { + fprintf(stderr, "kzt-wrapper-bridge-provider: %d failure(s)\n", + failures); + return 1; + } + puts("kzt-wrapper-bridge-provider: ok"); + return 0; +} diff --git a/tests/unit/kzt/test_wrapper_probe.c b/tests/unit/kzt/test_wrapper_probe.c new file mode 100644 index 00000000000..4ca136735c4 --- /dev/null +++ b/tests/unit/kzt/test_wrapper_probe.c @@ -0,0 +1,405 @@ +#include +#include + +#include "target/i386/latx/include/kzt_wrapper_probe.h" + +static int failures; + +typedef struct fake_bridge_state { + uintptr_t cached_native_symbol; + uintptr_t cached_bridge_target; + uintptr_t next_bridge_target; + int check_calls; + int add_calls; + kzt_wrapper_probe_bridge_request_t last_request; +} fake_bridge_state_t; + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, + expected); + ++failures; +} + +static void check_str(const char *name, const char *got, + const char *expected) +{ + if (got && expected && !strcmp(got, expected)) { + return; + } + if (!got && !expected) { + return; + } + + fprintf(stderr, "%s: got '%s' expected '%s'\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static uintptr_t fake_check_bridge(uintptr_t native_symbol, void *opaque) +{ + fake_bridge_state_t *state = opaque; + + ++state->check_calls; + if (native_symbol == state->cached_native_symbol) { + return state->cached_bridge_target; + } + + return 0; +} + +static uintptr_t fake_add_bridge( + const kzt_wrapper_probe_bridge_request_t *request, void *opaque) +{ + fake_bridge_state_t *state = opaque; + + ++state->add_calls; + state->last_request = *request; + return state->next_bridge_target; +} + +static kzt_wrapper_probe_bridge_ops_t fake_bridge_ops( + fake_bridge_state_t *state) +{ + return (kzt_wrapper_probe_bridge_ops_t) { + .check_bridge = fake_check_bridge, + .add_bridge = fake_add_bridge, + .opaque = state, + }; +} + +static const kzt_wrapper_probe_entry_t base_entries[] = { + { + .symbol_name = "gtk_widget_show", + .symbol_version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .symbol_version = "GTK_3.0", + .wrapper_name = "wrappedgtk3", + .wrapper_version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .wrapper_symbol_version = "GTK_3.0", + .native_symbol = 0x7100001000, + }, + { + .symbol_name = "gtk_widget_hide", + .symbol_version = NULL, + .wrapper_name = "wrappedgtk3", + .wrapper_symbol_version = NULL, + .native_symbol = 0x7100002000, + }, + { + .symbol_name = "gtk_widget_destroy", + .symbol_version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .symbol_version = "GTK_3.0", + .wrapper_name = "wrappedgtk3", + .wrapper_version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .wrapper_symbol_version = "GTK_3.0", + .native_symbol = 0, + }, + { + .symbol_name = "gtk_widget_queue_draw", + .symbol_version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .symbol_version = "GTK_3.0", + .wrapper_name = "wrappedgtk3", + .wrapper_version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .wrapper_symbol_version = "GTK_3.0", + .native_symbol = 0x7100003000, + }, + { + .symbol_name = "gtk_widget_unversioned", + .symbol_version_evidence = + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + .symbol_version = NULL, + .wrapper_name = "wrappedgtk3", + .wrapper_version_evidence = + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + .wrapper_symbol_version = NULL, + .native_symbol = 0x7100004000, + }, +}; + +static kzt_wrapper_probe_manifest_t base_manifest(void) +{ + return (kzt_wrapper_probe_manifest_t) { + .available = 1, + .manifest_name = "wrappedgtk3", + .entries = base_entries, + .entry_count = sizeof(base_entries) / sizeof(base_entries[0]), + }; +} + +static kzt_wrapper_probe_request_t request_for(const char *name, + const char *version) +{ + return (kzt_wrapper_probe_request_t) { + .symbol_name = name, + .symbol_version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .symbol_version = version, + }; +} + +static void test_no_manifest_keeps_probe_unavailable(void) +{ + kzt_wrapper_probe_request_t request = + request_for("gtk_widget_show", "GTK_3.0"); + kzt_wrapper_probe_manifest_t manifest = base_manifest(); + kzt_wrapper_probe_result_t result; + fake_bridge_state_t state = {0}; + kzt_wrapper_probe_bridge_ops_t ops = fake_bridge_ops(&state); + + manifest.available = 0; + check_int("no_manifest.call", + kzt_wrapper_probe_minimal_manifest(&manifest, &request, + &ops, &result), 0); + check_int("no_manifest.match", result.wrapper_match, + KZT_PATCH_WRAPPER_NO_MANIFEST); + check_str("no_manifest.wrapper", result.wrapper_name, NULL); + check_ulong("no_manifest.bridge", result.bridge_target, 0); + check_int("no_manifest.check_calls", state.check_calls, 0); + check_int("no_manifest.add_calls", state.add_calls, 0); +} + +static void test_no_wrapper_distinguishes_missing_symbol(void) +{ + kzt_wrapper_probe_manifest_t manifest = base_manifest(); + kzt_wrapper_probe_request_t request = + request_for("gtk_widget_missing", "GTK_3.0"); + kzt_wrapper_probe_result_t result; + fake_bridge_state_t state = {0}; + kzt_wrapper_probe_bridge_ops_t ops = fake_bridge_ops(&state); + + check_int("no_wrapper.call", + kzt_wrapper_probe_minimal_manifest(&manifest, &request, + &ops, &result), 0); + check_int("no_wrapper.match", result.wrapper_match, + KZT_PATCH_WRAPPER_NO_WRAPPER); + check_ulong("no_wrapper.native", result.native_symbol, 0); + check_ulong("no_wrapper.bridge", result.bridge_target, 0); + check_int("no_wrapper.add_calls", state.add_calls, 0); +} + +static void test_symbol_only_does_not_create_safe_bridge(void) +{ + kzt_wrapper_probe_manifest_t manifest = base_manifest(); + kzt_wrapper_probe_request_t request = + request_for("gtk_widget_hide", "GTK_3.0"); + kzt_wrapper_probe_result_t result; + fake_bridge_state_t state = { + .next_bridge_target = 0x7200001000, + }; + kzt_wrapper_probe_bridge_ops_t ops = fake_bridge_ops(&state); + + check_int("symbol_only.call", + kzt_wrapper_probe_minimal_manifest(&manifest, &request, + &ops, &result), 0); + check_int("symbol_only.match", result.wrapper_match, + KZT_PATCH_WRAPPER_SYMBOL_ONLY); + check_str("symbol_only.wrapper", result.wrapper_name, "wrappedgtk3"); + check_str("symbol_only.version", result.wrapper_symbol_version, NULL); + check_ulong("symbol_only.native", result.native_symbol, 0x7100002000); + check_ulong("symbol_only.bridge", result.bridge_target, 0); + check_int("symbol_only.check_calls", state.check_calls, 0); + check_int("symbol_only.add_calls", state.add_calls, 0); +} + +static void test_version_mismatch_keeps_bridge_empty(void) +{ + kzt_wrapper_probe_manifest_t manifest = base_manifest(); + kzt_wrapper_probe_request_t request = + request_for("gtk_widget_show", "GTK_4.0"); + kzt_wrapper_probe_result_t result; + fake_bridge_state_t state = { + .next_bridge_target = 0x7200001000, + }; + kzt_wrapper_probe_bridge_ops_t ops = fake_bridge_ops(&state); + + check_int("version_mismatch.call", + kzt_wrapper_probe_minimal_manifest(&manifest, &request, + &ops, &result), 0); + check_int("version_mismatch.match", result.wrapper_match, + KZT_PATCH_WRAPPER_VERSION_MISMATCH); + check_str("version_mismatch.wrapper", result.wrapper_name, + "wrappedgtk3"); + check_str("version_mismatch.version", result.wrapper_symbol_version, + "GTK_3.0"); + check_ulong("version_mismatch.bridge", result.bridge_target, 0); + check_int("version_mismatch.add_calls", state.add_calls, 0); +} + +static void test_version_match_creates_bridge_from_explicit_callback(void) +{ + kzt_wrapper_probe_manifest_t manifest = base_manifest(); + kzt_wrapper_probe_request_t request = + request_for("gtk_widget_show", "GTK_3.0"); + kzt_wrapper_probe_result_t result; + fake_bridge_state_t state = { + .next_bridge_target = 0x7200001000, + }; + kzt_wrapper_probe_bridge_ops_t ops = fake_bridge_ops(&state); + + check_int("version_match.call", + kzt_wrapper_probe_minimal_manifest(&manifest, &request, + &ops, &result), 0); + check_int("version_match.match", result.wrapper_match, + KZT_PATCH_WRAPPER_VERSION_MATCH); + check_str("version_match.wrapper", result.wrapper_name, + "wrappedgtk3"); + check_str("version_match.version", result.wrapper_symbol_version, + "GTK_3.0"); + check_ulong("version_match.native", result.native_symbol, + 0x7100001000); + check_ulong("version_match.bridge", result.bridge_target, + 0x7200001000); + check_int("version_match.bridge_source", result.bridge_source, + KZT_WRAPPER_PROBE_BRIDGE_ADD_BRIDGE); + check_int("version_match.check_calls", state.check_calls, 1); + check_int("version_match.add_calls", state.add_calls, 1); + check_str("version_match.bridge_request.name", + state.last_request.symbol_name, "gtk_widget_show"); + check_str("version_match.bridge_request.version", + state.last_request.symbol_version, "GTK_3.0"); + check_ulong("version_match.bridge_request.native", + state.last_request.native_symbol, 0x7100001000); +} + +static void test_confirmed_unversioned_match_creates_bridge(void) +{ + kzt_wrapper_probe_manifest_t manifest = base_manifest(); + kzt_wrapper_probe_request_t request = + request_for("gtk_widget_unversioned", NULL); + kzt_wrapper_probe_result_t result; + fake_bridge_state_t state = { + .next_bridge_target = 0x7200004000, + }; + kzt_wrapper_probe_bridge_ops_t ops = fake_bridge_ops(&state); + + request.symbol_version_evidence = + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + check_int("unversioned.call", + kzt_wrapper_probe_minimal_manifest(&manifest, &request, + &ops, &result), 0); + check_int("unversioned.match", result.wrapper_match, + KZT_PATCH_WRAPPER_UNVERSIONED_MATCH); + check_int("unversioned.wrapper-evidence", + result.wrapper_version_evidence, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED); + check_str("unversioned.version", result.wrapper_symbol_version, NULL); + check_ulong("unversioned.bridge", result.bridge_target, 0x7200004000); + check_int("unversioned.add-calls", state.add_calls, 1); + check_int("unversioned.bridge-request-evidence", + state.last_request.symbol_version_evidence, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED); +} + +static void test_unknown_and_error_evidence_do_not_probe_bridge(void) +{ + kzt_wrapper_probe_manifest_t manifest = base_manifest(); + kzt_wrapper_probe_result_t result; + kzt_symbol_version_evidence_t evidence[] = { + KZT_SYMBOL_VERSION_UNKNOWN, + KZT_SYMBOL_VERSION_ERROR, + }; + size_t i; + + for (i = 0; i < sizeof(evidence) / sizeof(evidence[0]); ++i) { + kzt_wrapper_probe_request_t request = + request_for("gtk_widget_show", "GTK_3.0"); + fake_bridge_state_t state = { + .next_bridge_target = 0x7200001000, + }; + kzt_wrapper_probe_bridge_ops_t ops = fake_bridge_ops(&state); + + request.symbol_version_evidence = evidence[i]; + check_int("untrusted.call", + kzt_wrapper_probe_minimal_manifest( + &manifest, &request, &ops, &result), 0); + check_int("untrusted.match", result.wrapper_match, + KZT_PATCH_WRAPPER_VERSION_MISMATCH); + check_int("untrusted.no-check", state.check_calls, 0); + check_int("untrusted.no-add", state.add_calls, 0); + } +} + +static void test_bridge_zero_preserves_fail_open_input(void) +{ + kzt_wrapper_probe_manifest_t manifest = base_manifest(); + kzt_wrapper_probe_request_t request = + request_for("gtk_widget_destroy", "GTK_3.0"); + kzt_wrapper_probe_result_t result; + fake_bridge_state_t state = { + .next_bridge_target = 0x7200001000, + }; + kzt_wrapper_probe_bridge_ops_t ops = fake_bridge_ops(&state); + + check_int("bridge_zero.call", + kzt_wrapper_probe_minimal_manifest(&manifest, &request, + &ops, &result), 0); + check_int("bridge_zero.match", result.wrapper_match, + KZT_PATCH_WRAPPER_VERSION_MATCH); + check_ulong("bridge_zero.native", result.native_symbol, 0); + check_ulong("bridge_zero.bridge", result.bridge_target, 0); + check_int("bridge_zero.source", result.bridge_source, + KZT_WRAPPER_PROBE_BRIDGE_NONE); + check_int("bridge_zero.check_calls", state.check_calls, 0); + check_int("bridge_zero.add_calls", state.add_calls, 0); +} + +static void test_bridge_cache_reuse_does_not_add_duplicate_bridge(void) +{ + kzt_wrapper_probe_manifest_t manifest = base_manifest(); + kzt_wrapper_probe_request_t request = + request_for("gtk_widget_queue_draw", "GTK_3.0"); + kzt_wrapper_probe_result_t result; + fake_bridge_state_t state = { + .cached_native_symbol = 0x7100003000, + .cached_bridge_target = 0x7200003000, + .next_bridge_target = 0x7200004000, + }; + kzt_wrapper_probe_bridge_ops_t ops = fake_bridge_ops(&state); + + check_int("bridge_cache.call", + kzt_wrapper_probe_minimal_manifest(&manifest, &request, + &ops, &result), 0); + check_int("bridge_cache.match", result.wrapper_match, + KZT_PATCH_WRAPPER_VERSION_MATCH); + check_ulong("bridge_cache.bridge", result.bridge_target, + 0x7200003000); + check_int("bridge_cache.source", result.bridge_source, + KZT_WRAPPER_PROBE_BRIDGE_CACHE); + check_int("bridge_cache.check_calls", state.check_calls, 1); + check_int("bridge_cache.add_calls", state.add_calls, 0); +} + +int main(void) +{ + test_no_manifest_keeps_probe_unavailable(); + test_no_wrapper_distinguishes_missing_symbol(); + test_symbol_only_does_not_create_safe_bridge(); + test_version_mismatch_keeps_bridge_empty(); + test_version_match_creates_bridge_from_explicit_callback(); + test_confirmed_unversioned_match_creates_bridge(); + test_unknown_and_error_evidence_do_not_probe_bridge(); + test_bridge_zero_preserves_fail_open_input(); + test_bridge_cache_reuse_does_not_add_duplicate_bridge(); + + if (failures) { + fprintf(stderr, "kzt-wrapper-probe failures: %d\n", failures); + return 1; + } + + puts("kzt-wrapper-probe: ok"); + return 0; +} diff --git a/tests/unit/kzt/wi231_step4_writer_test_report.md b/tests/unit/kzt/wi231_step4_writer_test_report.md new file mode 100644 index 00000000000..6d2d53dcc4c --- /dev/null +++ b/tests/unit/kzt/wi231_step4_writer_test_report.md @@ -0,0 +1,92 @@ +# WI-231 Step4 writer 接线白盒测试报告 + +## Summary + +- worktree: `/home/loongson/work/code/latu-worktrees/kzt-step4-wi230-wi231-integrated-20260709094322` +- branch: `lauren/kzt-step4-wi230-wi231-integrated-20260709094322` +- 代码验证范围: `20dec00bac069e77a07d30a6d43a24ce34882f17..d7401373939651a8c51b841d32457eafda5744a1` +- 报告修正说明: 本文件随后随报告修正 commit 更新,因此不把报告自身 commit 写作固定 HEAD。 +- 测试目标: `kzt-rela-immediate-candidate` 与 13 个 KZT 单测目标 +- 结论: `git diff --check`、debug build、13 个 KZT 单测、`latxbuild/build-release.sh` 均通过。 +- 集成说明: 当前测试已经调用真实生产 helper `kzt_rela_immediate_jump_slot_try_write()`,不是平行模拟 Step4 contract。该 helper 再调用 Step3 writer/guard,覆盖 approved writer success、planner 非批准、writer fail-open、`GLOB_DAT`/非目标/lazy skip writer。 + +## Checklist + +- [x] 测试证据包含 command。 +- [x] 测试证据包含 summary。 +- [x] 测试证据包含 stdout/raw output。 +- [x] 测试证据包含 exit_code。 +- [x] 测试点直接覆盖 WI-230/WI-231 writer 接线验收,不只复用 Step3 writer 通用单测。 +- [x] 测试调用真实生产 helper `kzt_rela_immediate_jump_slot_try_write()`。 +- [x] 生产接线仍通过 Step3 guard/writer,不直接写 slot。 +- [x] 未 push,未创建远端 PR。 + +## Commands + +```sh +cd /home/loongson/work/code/latu-worktrees/kzt-step4-wi230-wi231-integrated-20260709094322 +git diff --check +ninja -C build64-dbg +meson test -C build64-dbg --print-errorlogs -v \ + kzt-guest-registry \ + kzt-guest-registry-concurrency \ + kzt-guest-link-map-reader \ + kzt-guest-dynamic-parser \ + kzt-guest-dynamic-snapshot \ + kzt-guest-dynamic-diagnostics \ + kzt-observation-adapter \ + kzt-registry-diagnostics-gate \ + kzt-patch-planner \ + kzt-runtime-got-plt-candidate \ + kzt-rela-immediate-candidate \ + kzt-patch-spike-guard \ + kzt-patch-spike-writer +latxbuild/build-release.sh +``` + +## Exit Codes + +```text +git diff --check: 0 +ninja -C build64-dbg: 0 +meson test -C build64-dbg 13 KZT tests: 0 +latxbuild/build-release.sh: 0 +``` + +## Raw Output Highlights + +```text +[113/132] Compiling C object libqemu-x86_64-linux-user.fa.p/target_i386_latx_context_box64context.c.o +[119/132] Compiling C object libqemu-x86_64-linux-user.fa.p/target_i386_latx_context_elfloader.c.o +[132/132] Linking target latx-x86_64 +``` + +```text +WI231_TC tc=approved-writer-success-skips-legacy plan_status=1 plan_reason=0 decision=APPROVED writer_called=1 legacy_writes=0 skip_legacy=1 result=APPLIED failure=NONE reads=2 writer_writes=1 final=0x7200004560 +WI231_TC tc=planner-unsupported-keeps-legacy plan_status=1 plan_reason=0 decision=UNSUPPORTED writer_called=0 legacy_writes=1 skip_legacy=0 result=DISABLED failure=NONE reads=0 writer_writes=0 final=0x7300003333 +WI231_TC tc=planner-rejected-keeps-legacy plan_status=1 plan_reason=0 decision=REJECTED writer_called=0 legacy_writes=1 skip_legacy=0 result=DISABLED failure=NONE reads=0 writer_writes=0 final=0x7300003333 +WI231_TC tc=lazy-deferred-keeps-legacy plan_status=0 plan_reason=3 decision=(none) writer_called=0 legacy_writes=1 skip_legacy=0 result=DISABLED failure=NONE reads=0 writer_writes=0 final=0x7300003333 +WI231_TC tc=planner-fail-open-keeps-legacy plan_status=2 plan_reason=6 decision=(none) writer_called=0 legacy_writes=1 skip_legacy=0 result=DISABLED failure=NONE reads=0 writer_writes=0 final=0x7300003333 +WI231_TC tc=writer-expected-mismatch-fail-open plan_status=1 plan_reason=0 decision=APPROVED writer_called=1 legacy_writes=1 skip_legacy=0 result=FAIL_OPEN failure=EXPECTED_MISMATCH reads=1 writer_writes=0 final=0x7300004444 +WI231_TC tc=writer-write-fail-fail-open plan_status=1 plan_reason=0 decision=APPROVED writer_called=1 legacy_writes=1 skip_legacy=0 result=FAIL_OPEN failure=WRITE_FAILED reads=1 writer_writes=1 final=0x7300004444 +WI231_TC tc=writer-verify-fail-fail-open plan_status=1 plan_reason=0 decision=APPROVED writer_called=1 legacy_writes=1 skip_legacy=0 result=FAIL_OPEN failure=VERIFY_FAILED reads=2 writer_writes=2 final=0x7300004444 +WI231_TC tc=writer-rollback-fail-fail-open plan_status=1 plan_reason=0 decision=APPROVED writer_called=1 legacy_writes=1 skip_legacy=0 result=FAIL_OPEN failure=ROLLBACK_FAILED reads=2 writer_writes=2 final=0x7300004444 +WI231_TC tc=non-target-relocation-skips-writer plan_status=0 plan_reason=2 decision=(none) writer_called=0 legacy_writes=1 skip_legacy=0 result=DISABLED failure=NONE reads=0 writer_writes=0 final=0x7300005555 +WI231_TC tc=glob-dat-skips-writer plan_status=0 plan_reason=2 decision=(none) writer_called=0 legacy_writes=1 skip_legacy=0 result=DISABLED failure=NONE reads=0 writer_writes=0 final=0x7300005555 +WI231_TC tc=lazy-deferred-skips-writer plan_status=0 plan_reason=3 decision=(none) writer_called=0 legacy_writes=1 skip_legacy=0 result=DISABLED failure=NONE reads=0 writer_writes=0 final=0x7300005555 +``` + +```text +Ok: 13 +Fail: 0 +[67/67] Linking target latx-i386 +[132/132] Linking target latx-x86_64 +``` + +## Coverage Mapping + +- approved writer success: `approved-writer-success-skips-legacy` 验证 writer 调用、legacy 跳过、最终值为 bridge。 +- planner 非批准或 fail-open: `planner-unsupported-keeps-legacy`、`planner-rejected-keeps-legacy`、`planner-fail-open-keeps-legacy` 验证 writer 不调用且 legacy 保持。 +- writer fail-open: `writer-expected-mismatch-fail-open`、`writer-write-fail-fail-open`、`writer-verify-fail-fail-open`、`writer-rollback-fail-fail-open` 验证失败时继续 legacy。 +- 非目标范围: `non-target-relocation-skips-writer`、`glob-dat-skips-writer`、`lazy-deferred-skips-writer` 验证本阶段不接管 `GLOB_DAT`、非目标 relocation 和 lazy deferred。 +- 生产生命周期: `ninja -C build64-dbg` 和 `latxbuild/build-release.sh` 编译 `box64context.c`、`elfloader.c` 和 KZT helper,确认 guard 挂到 `box64context_t` 的生产接线可编译。 diff --git a/tests/unit/meson.build b/tests/unit/meson.build index 3bbb0e5647c..d0b0e20625a 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -6,3 +6,519 @@ test_elfload_pagesize = executable( ) test('test-elfload-pagesize', test_elfload_pagesize) + +kzt_guest_registry_test = executable( + 'kzt-guest-registry', + files( + 'kzt/test_guest_registry.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-guest-registry', kzt_guest_registry_test) + +kzt_guest_registry_concurrency_test = executable( + 'kzt-guest-registry-concurrency', + files( + 'kzt/test_guest_registry_concurrency.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-guest-registry-concurrency', kzt_guest_registry_concurrency_test) + +kzt_guest_registry_context_test = executable( + 'kzt-guest-registry-context', + files( + 'kzt/test_guest_registry_context.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + '../../target/i386/latx/context/kzt_guest_registry_context.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-guest-registry-context', kzt_guest_registry_context_test) + +kzt_guest_library_binding_test = executable( + 'kzt-guest-library-binding', + files( + 'kzt/test_guest_library_binding.c', + '../../target/i386/latx/context/kzt_guest_library_binding.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_LIBRARY_BINDING_TEST', '-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-guest-library-binding', kzt_guest_library_binding_test) + +kzt_guest_library_binding_teardown_test = executable( + 'kzt-guest-library-binding-teardown', + files( + 'kzt/test_guest_library_binding_teardown.c', + '../../target/i386/latx/context/kzt_guest_library_binding.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_LIBRARY_BINDING_TEST', '-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-guest-library-binding-teardown', + kzt_guest_library_binding_teardown_test) + +kzt_dlopen_recycle_transaction_test = executable( + 'kzt-dlopen-recycle-transaction', + files( + 'kzt/test_dlopen_recycle_transaction.c', + '../../target/i386/latx/context/dlopen_recycle_transaction.c', + '../../target/i386/latx/context/elf_plt_relocation.c', + '../../target/i386/latx/context/kzt_guest_library_binding.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_LIBRARY_BINDING_TEST'], + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-dlopen-recycle-transaction', + kzt_dlopen_recycle_transaction_test) + +python3 = find_program('python3') +test( + 'kzt-wi382-registry-observation-source-contract', + python3, + args: [ + files('kzt/test_wi382_registry_observation_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi254-loader-contract', + python3, + args: [ + files('kzt/test_wi254_loader_contract.py'), + meson.source_root(), + ], +) + +kzt_guest_link_map_reader_test = executable( + 'kzt-guest-link-map-reader', + files( + 'kzt/test_guest_link_map_reader.c', + '../../target/i386/latx/context/kzt_guest_link_map_reader.c', + ), + c_args: ['-DKZT_GUEST_LINK_MAP_READER_TEST'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-guest-link-map-reader', kzt_guest_link_map_reader_test) + +kzt_elf_map_range_test = executable( + 'kzt-elf-map-range', + files( + 'kzt/test_elf_map_range.c', + '../../target/i386/latx/context/elfmap.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-elf-map-range', kzt_elf_map_range_test) + +kzt_guest_dynamic_parser_test = executable( + 'kzt-guest-dynamic-parser', + files( + 'kzt/test_guest_dynamic_parser.c', + '../../target/i386/latx/context/kzt_guest_dynamic.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-guest-dynamic-parser', kzt_guest_dynamic_parser_test) + +kzt_guest_dynamic_snapshot_test = executable( + 'kzt-guest-dynamic-snapshot', + files( + 'kzt/test_guest_dynamic_snapshot.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-guest-dynamic-snapshot', kzt_guest_dynamic_snapshot_test) + +kzt_guest_dynamic_diagnostics_test = executable( + 'kzt-guest-dynamic-diagnostics', + files( + 'kzt/test_guest_dynamic_diagnostics.c', + '../../target/i386/latx/context/kzt_guest_dynamic_diagnostics.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-guest-dynamic-diagnostics', kzt_guest_dynamic_diagnostics_test) + +kzt_observation_adapter_test = executable( + 'kzt-observation-adapter', + files( + 'kzt/test_observation_adapter.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + '../../target/i386/latx/context/kzt_guest_link_map_reader.c', + '../../target/i386/latx/context/kzt_observation_adapter.c', + '../../target/i386/latx/context/kzt_guest_dynamic.c', + '../../target/i386/latx/context/kzt_guest_dynamic_diagnostics.c', + '../../target/i386/latx/context/kzt_guest_library_binding.c', + ), + c_args: [ + '-DKZT_GUEST_REGISTRY_TEST', + '-DKZT_GUEST_LINK_MAP_READER_TEST', + '-DKZT_GUEST_LIBRARY_BINDING_TEST', + ], + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-observation-adapter', kzt_observation_adapter_test) + +kzt_registry_diagnostics_gate_test = executable( + 'kzt-registry-diagnostics-gate', + files( + 'kzt/test_registry_diagnostics_gate.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + '../../target/i386/latx/context/kzt_guest_link_map_reader.c', + '../../target/i386/latx/context/kzt_observation_adapter.c', + '../../target/i386/latx/context/kzt_guest_dynamic.c', + '../../target/i386/latx/context/kzt_guest_dynamic_diagnostics.c', + '../../target/i386/latx/context/kzt_guest_library_binding.c', + ), + c_args: [ + '-DCONFIG_LATX_KZT', + '-DKZT_GUEST_REGISTRY_TEST', + '-DKZT_GUEST_LINK_MAP_READER_TEST', + ], + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-registry-diagnostics-gate', kzt_registry_diagnostics_gate_test) + +kzt_patch_planner_test = executable( + 'kzt-patch-planner', + files( + 'kzt/test_patch_planner.c', + '../../target/i386/latx/context/kzt_patch_planner.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-patch-planner', kzt_patch_planner_test) + +kzt_runtime_got_plt_candidate_test = executable( + 'kzt-runtime-got-plt-candidate', + files( + 'kzt/test_runtime_got_plt_candidate.c', + '../../target/i386/latx/context/kzt_runtime_got_plt_candidate.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-runtime-got-plt-candidate', kzt_runtime_got_plt_candidate_test) + +kzt_runtime_candidate_enrichment_shadow_test = executable( + 'kzt-runtime-candidate-enrichment-shadow', + files( + 'kzt/test_runtime_candidate_enrichment_shadow.c', + '../../target/i386/latx/context/kzt_runtime_candidate_shadow.c', + '../../target/i386/latx/context/kzt_runtime_got_plt_candidate.c', + '../../target/i386/latx/context/kzt_owner_resolver.c', + '../../target/i386/latx/context/kzt_wrapper_probe.c', + '../../target/i386/latx/context/kzt_patch_planner.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test( + 'kzt-runtime-candidate-enrichment-shadow', + kzt_runtime_candidate_enrichment_shadow_test, +) + +kzt_rela_immediate_candidate_test = executable( + 'kzt-rela-immediate-candidate', + files( + 'kzt/test_rela_immediate_candidate.c', + '../../target/i386/latx/context/kzt_rela_immediate_candidate.c', + '../../target/i386/latx/context/kzt_patch_planner.c', + '../../target/i386/latx/context/kzt_patch_spike_guard.c', + '../../target/i386/latx/context/kzt_patch_spike_writer.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-rela-immediate-candidate', kzt_rela_immediate_candidate_test) + +kzt_rela_request_enricher_test = executable( + 'kzt-rela-request-enricher', + files( + 'kzt/test_rela_request_enricher.c', + '../../target/i386/latx/context/kzt_rela_request_enricher.c', + '../../target/i386/latx/context/kzt_rela_immediate_candidate.c', + '../../target/i386/latx/context/kzt_patch_planner.c', + '../../target/i386/latx/context/kzt_patch_spike_guard.c', + '../../target/i386/latx/context/kzt_patch_spike_writer.c', + '../../target/i386/latx/context/kzt_owner_resolver.c', + '../../target/i386/latx/context/kzt_wrapper_probe.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-rela-request-enricher', kzt_rela_request_enricher_test) + +kzt_wi238_structured_diagnostics_gate_test = executable( + 'kzt-wi238-structured-diagnostics-gate', + files( + 'kzt/test_wi238_structured_diagnostics.c', + '../../target/i386/latx/context/kzt_rela_diagnostics.c', + '../../target/i386/latx/context/kzt_patch_planner.c', + '../../target/i386/latx/context/kzt_patch_spike_guard.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-wi238-structured-diagnostics-gate', + kzt_wi238_structured_diagnostics_gate_test) + +kzt_owner_resolver_test = executable( + 'kzt-owner-resolver', + files( + 'kzt/test_owner_resolver.c', + '../../target/i386/latx/context/kzt_owner_resolver.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-owner-resolver', kzt_owner_resolver_test) + + +kzt_patch_spike_guard_test = executable( + 'kzt-patch-spike-guard', + files( + 'kzt/test_patch_spike_guard.c', + 'kzt/kzt_test_options.c', + '../../target/i386/latx/context/kzt_patch_spike_guard.c', + ), + c_args: ['-DCONFIG_LATX_KZT'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-patch-spike-guard', kzt_patch_spike_guard_test) + +kzt_patch_spike_writer_test = executable( + 'kzt-patch-spike-writer', + files( + 'kzt/test_patch_spike_writer.c', + 'kzt/kzt_test_options.c', + '../../target/i386/latx/context/kzt_patch_planner.c', + '../../target/i386/latx/context/kzt_patch_spike_guard.c', + '../../target/i386/latx/context/kzt_patch_spike_writer.c', + ), + c_args: ['-DCONFIG_LATX_KZT'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-patch-spike-writer', kzt_patch_spike_writer_test) + +kzt_wrapper_probe_test = executable( + 'kzt-wrapper-probe', + files( + 'kzt/test_wrapper_probe.c', + '../../target/i386/latx/context/kzt_wrapper_probe.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-wrapper-probe', kzt_wrapper_probe_test) + +kzt_wrapper_bridge_provider_test = executable( + 'kzt-wrapper-bridge-provider', + files( + 'kzt/test_wrapper_bridge_provider.c', + '../../target/i386/latx/context/kzt_wrapper_bridge_provider.c', + '../../target/i386/latx/context/kzt_wrapper_probe.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-wrapper-bridge-provider', kzt_wrapper_bridge_provider_test) + +kzt_rela_runtime_helper_chain_test = executable( + 'kzt-rela-runtime-helper-chain', + files( + 'kzt/test_rela_runtime_helper_chain.c', + '../../target/i386/latx/context/kzt_rela_runtime_bridge.c', + '../../target/i386/latx/context/kzt_wrapper_bridge_provider.c', + '../../target/i386/latx/context/kzt_wrapper_probe.c', + '../../target/i386/latx/context/kzt_rela_request_enricher.c', + '../../target/i386/latx/context/kzt_rela_immediate_candidate.c', + '../../target/i386/latx/context/kzt_patch_planner.c', + '../../target/i386/latx/context/kzt_patch_spike_guard.c', + '../../target/i386/latx/context/kzt_patch_spike_writer.c', + '../../target/i386/latx/context/kzt_owner_resolver.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + '../../target/i386/latx/context/kzt_bridge_exact.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-rela-runtime-helper-chain', kzt_rela_runtime_helper_chain_test) + +kzt_jump_slot_route_test = executable( + 'kzt-jump-slot-route', + files( + 'kzt/test_jump_slot_route.c', + '../../target/i386/latx/context/kzt_jump_slot_route.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-jump-slot-route', kzt_jump_slot_route_test) + +kzt_wi256_lazy_binding_test = executable( + 'kzt-wi256-lazy-binding', + files( + 'kzt/test_wi256_lazy_binding.c', + '../../target/i386/latx/context/kzt_lazy_binding.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-wi256-lazy-binding', kzt_wi256_lazy_binding_test) + +kzt_wi256_lazy_diagnostics_test = executable( + 'kzt-wi256-lazy-diagnostics', + files( + 'kzt/test_wi256_lazy_diagnostics.c', + '../../target/i386/latx/context/kzt_lazy_diagnostics.c', + ), + c_args: ['-DCONFIG_LATX_KZT'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-wi256-lazy-diagnostics', kzt_wi256_lazy_diagnostics_test) + +test( + 'kzt-wi256-lazy-diagnostics-source-contract', + python3, + args: [ + files('kzt/test_wi256_lazy_diagnostics_source_contract.py'), + meson.source_root(), + ], +) + +kzt_wi256_plt_resolver_adapter_test = executable( + 'kzt-wi256-plt-resolver-adapter', + files( + 'kzt/test_wi256_plt_resolver_adapter.c', + '../../target/i386/latx/context/kzt_plt_resolver_adapter.c', + ), + c_args: ['-DKZT_PLT_RESOLVER_ADAPTER_TEST'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-wi256-plt-resolver-adapter', + kzt_wi256_plt_resolver_adapter_test) + +kzt_wi256_lazy_production_bridge_test = executable( + 'kzt-wi256-lazy-production-bridge', + files( + 'kzt/test_wi256_lazy_production_bridge.c', + 'kzt/kzt_test_options.c', + '../../target/i386/latx/context/kzt_lazy_binding.c', + '../../target/i386/latx/context/kzt_jump_slot_production.c', + '../../target/i386/latx/context/kzt_jump_slot_route.c', + '../../target/i386/latx/context/kzt_rela_runtime_bridge.c', + '../../target/i386/latx/context/kzt_wrapper_bridge_provider.c', + '../../target/i386/latx/context/kzt_wrapper_probe.c', + '../../target/i386/latx/context/kzt_rela_request_enricher.c', + '../../target/i386/latx/context/kzt_rela_diagnostics.c', + '../../target/i386/latx/context/kzt_rela_immediate_candidate.c', + '../../target/i386/latx/context/kzt_patch_planner.c', + '../../target/i386/latx/context/kzt_patch_spike_guard.c', + '../../target/i386/latx/context/kzt_patch_spike_writer.c', + '../../target/i386/latx/context/kzt_owner_resolver.c', + '../../target/i386/latx/context/kzt_runtime_got_plt_candidate.c', + '../../target/i386/latx/context/kzt_runtime_candidate_shadow.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + '../../target/i386/latx/context/kzt_guest_library_binding.c', + '../../target/i386/latx/context/kzt_bridge_exact.c', + ), + c_args: [ + '-DCONFIG_LATX_KZT', + '-DKZT_GUEST_REGISTRY_TEST', + '-DKZT_JUMP_SLOT_PRODUCTION_TEST', + ], + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-wi256-lazy-production-bridge', + kzt_wi256_lazy_production_bridge_test) + +test( + 'kzt-wi256-plt-resolver-source-contract', + python3, + args: [ + files('kzt/test_wi256_plt_resolver_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi237-jump-slot-route-contract', + python3, + args: [ + files('kzt/test_wi237_jump_slot_route_contract.py'), + meson.source_root(), + ], +) + +kzt_wi236_enrichment_matrix_test = executable( + 'kzt-wi236-enrichment-matrix', + files( + 'kzt/test_wi236_enrichment_matrix.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + '../../target/i386/latx/context/kzt_owner_resolver.c', + '../../target/i386/latx/context/kzt_patch_planner.c', + '../../target/i386/latx/context/kzt_patch_spike_guard.c', + '../../target/i386/latx/context/kzt_patch_spike_writer.c', + '../../target/i386/latx/context/kzt_rela_immediate_candidate.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-wi236-enrichment-matrix', kzt_wi236_enrichment_matrix_test) From e537ffe2ad74c13b6f3fdce1228008871752323e Mon Sep 17 00:00:00 2001 From: Hanlu Li Date: Fri, 31 Jul 2026 21:58:28 +0800 Subject: [PATCH 2/8] LATX, KZT: strengthen guest registry observations Track exact loader identities, generations, namespaces, dynamic view revisions, and decision leases in the guest registry. Add allocation-free live identity queries and coherent observation helpers for later loader and patching stages.\n\nCover registry lifecycle, concurrency, address resolution, loader handles, and decision lease behavior with focused white-box tests. Signed-off-by: Hanlu Li --- target/i386/latx/context/kzt_guest_dynamic.c | 35 +- .../latx/context/kzt_guest_link_map_reader.c | 116 +- target/i386/latx/context/kzt_guest_registry.c | 1414 ++++++++++++++++- .../latx/context/kzt_guest_registry_context.c | 16 +- .../latx/context/kzt_observation_adapter.c | 119 +- target/i386/latx/context/kzt_owner_resolver.c | 109 +- target/i386/latx/include/kzt_guest_dynamic.h | 1 + .../latx/include/kzt_guest_link_map_reader.h | 26 + target/i386/latx/include/kzt_guest_registry.h | 228 +++ .../latx/include/kzt_observation_adapter.h | 22 + tests/unit/kzt/test_guest_dynamic_parser.c | 81 +- tests/unit/kzt/test_guest_link_map_reader.c | 225 ++- tests/unit/kzt/test_guest_registry.c | 521 +++++- .../kzt/test_guest_registry_concurrency.c | 146 ++ tests/unit/kzt/test_guest_registry_context.c | 7 + .../test_guest_registry_got_plt_injection.c | 132 ++ ...test_guest_registry_patch_decision_lease.c | 561 +++++++ tests/unit/kzt/test_observation_adapter.c | 249 +++ tests/unit/kzt/test_owner_resolver.c | 25 + ...82_registry_observation_source_contract.py | 94 +- 20 files changed, 3928 insertions(+), 199 deletions(-) create mode 100644 tests/unit/kzt/test_guest_registry_got_plt_injection.c create mode 100644 tests/unit/kzt/test_guest_registry_patch_decision_lease.c diff --git a/target/i386/latx/context/kzt_guest_dynamic.c b/target/i386/latx/context/kzt_guest_dynamic.c index 70422ddc1ad..9185144f425 100644 --- a/target/i386/latx/context/kzt_guest_dynamic.c +++ b/target/i386/latx/context/kzt_guest_dynamic.c @@ -65,7 +65,8 @@ static void kzt_guest_dynamic_record_unknown_tag( static int kzt_guest_dynamic_record_entry(kzt_guest_dynamic_view_t *view, const Elf64_Dyn *entry, - size_t index) + size_t index, + uintptr_t load_bias) { uint64_t value = entry->d_un.d_val; uint64_t ptr = entry->d_un.d_ptr; @@ -102,7 +103,11 @@ static int kzt_guest_dynamic_record_entry(kzt_guest_dynamic_view_t *view, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); break; case DT_VERNEED: - kzt_guest_dynamic_field_set(&view->verneed, ptr, + /* glibc retains these two version-table pointers relative to l_addr. */ + if (ptr > UINTPTR_MAX - load_bias) { + return -2; + } + kzt_guest_dynamic_field_set(&view->verneed, load_bias + ptr, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); break; case DT_VERNEEDNUM: @@ -110,7 +115,10 @@ static int kzt_guest_dynamic_record_entry(kzt_guest_dynamic_view_t *view, KZT_GUEST_DYNAMIC_SCALAR); break; case DT_VERDEF: - kzt_guest_dynamic_field_set(&view->verdef, ptr, + if (ptr > UINTPTR_MAX - load_bias) { + return -2; + } + kzt_guest_dynamic_field_set(&view->verdef, load_bias + ptr, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); break; case DT_VERDEFNUM: @@ -227,13 +235,20 @@ int kzt_guest_dynamic_parse( return 0; } - if (kzt_guest_dynamic_record_entry(&view, &entry, i) != 0) { - view.status = KZT_GUEST_DYNAMIC_ERROR; - view.entry_count = i; - result->status = KZT_GUEST_DYNAMIC_ERROR; - result->error = KZT_GUEST_DYNAMIC_ERROR_TOO_MANY_NEEDED; - kzt_guest_dynamic_publish_view(result, &view); - return 0; + { + int record_status = kzt_guest_dynamic_record_entry( + &view, &entry, i, load_bias); + + if (record_status != 0) { + view.status = KZT_GUEST_DYNAMIC_ERROR; + view.entry_count = i; + result->status = KZT_GUEST_DYNAMIC_ERROR; + result->error = record_status == -2 ? + KZT_GUEST_DYNAMIC_ERROR_ADDRESS_OVERFLOW : + KZT_GUEST_DYNAMIC_ERROR_TOO_MANY_NEEDED; + kzt_guest_dynamic_publish_view(result, &view); + return 0; + } } } diff --git a/target/i386/latx/context/kzt_guest_link_map_reader.c b/target/i386/latx/context/kzt_guest_link_map_reader.c index 3e5207942d8..100b49c0989 100644 --- a/target/i386/latx/context/kzt_guest_link_map_reader.c +++ b/target/i386/latx/context/kzt_guest_link_map_reader.c @@ -200,6 +200,114 @@ int kzt_guest_link_map_read_predecessor( ops, predecessor); } +int kzt_guest_link_map_read_successor( + uintptr_t link_map_addr, + const kzt_guest_link_map_reader_ops_t *ops, + uintptr_t *successor) +{ + if (!successor) { + return -1; + } + *successor = 0; + if (!link_map_addr || !ops || !ops->read_memory) { + return -1; + } + return kzt_guest_read_uintptr_field( + link_map_addr, + offsetof(kzt_guest_link_map_public_prefix_t, l_next), + sizeof(((kzt_guest_link_map_public_prefix_t *)0)->l_next), + ops, successor); +} + +static uint64_t kzt_guest_link_map_fingerprint_mix(uint64_t value, + uint64_t component) +{ + size_t i; + + for (i = 0; i < sizeof(component); ++i) { + value ^= component & UINT64_C(0xff); + value *= UINT64_C(1099511628211); + component >>= 8; + } + return value; +} + +int kzt_guest_link_map_read_fingerprint( + uintptr_t namespace_head, + const kzt_guest_link_map_reader_ops_t *ops, + kzt_guest_link_map_fingerprint_t *fingerprint) +{ + uintptr_t visited[KZT_GUEST_LINK_MAP_WALK_LIMIT]; + uintptr_t current = namespace_head; + uint64_t value = UINT64_C(14695981039346656037); + size_t count = 0; + + if (!fingerprint) { + return -1; + } + memset(fingerprint, 0, sizeof(*fingerprint)); + if (!current || !ops || !ops->read_memory) { + return -1; + } + + while (count < KZT_GUEST_LINK_MAP_WALK_LIMIT) { + kzt_guest_link_map_identity_t identity; + uintptr_t successor = 0; + size_t i; + + for (i = 0; i < count; ++i) { + if (visited[i] == current) { + return -1; + } + } + visited[count] = current; + + if (kzt_guest_link_map_read_identity(current, ops, &identity) != 0 || + kzt_guest_link_map_read_successor(current, ops, &successor) != 0) { + return -1; + } + + value = kzt_guest_link_map_fingerprint_mix(value, count); + value = kzt_guest_link_map_fingerprint_mix(value, current); + value = kzt_guest_link_map_fingerprint_mix(value, + identity.load_bias); + value = kzt_guest_link_map_fingerprint_mix(value, + identity.dynamic_addr); + ++count; + + if (!successor) { + value = kzt_guest_link_map_fingerprint_mix(value, count); + fingerprint->namespace_head = namespace_head; + fingerprint->link_map_count = count; + fingerprint->value = value; + return 0; + } + current = successor; + } + + return -1; +} + +int kzt_guest_link_map_revalidate_fingerprint( + const kzt_guest_link_map_fingerprint_t *expected, + const kzt_guest_link_map_reader_ops_t *ops) +{ + kzt_guest_link_map_fingerprint_t current; + + if (!expected || !expected->namespace_head || + expected->link_map_count == 0 || + expected->link_map_count > KZT_GUEST_LINK_MAP_WALK_LIMIT) { + return -1; + } + if (kzt_guest_link_map_read_fingerprint( + expected->namespace_head, ops, ¤t) != 0) { + return -1; + } + return current.namespace_head == expected->namespace_head && + current.link_map_count == expected->link_map_count && + current.value == expected->value; +} + int kzt_guest_link_map_classify_namespace( uintptr_t link_map_addr, const kzt_guest_link_map_identity_t *main_identity, @@ -284,7 +392,6 @@ int kzt_guest_link_map_read_name_snapshot( } if (max_len == 0) { - name->status = KZT_GUEST_FIELD_TRUNCATED; return 0; } @@ -295,7 +402,8 @@ int kzt_guest_link_map_read_name_snapshot( } for (i = 0; i < max_len; ++i) { - if (kzt_guest_read_memory(guest_name_addr + i, &snapshot[i], 1, ops) != 0) { + if (kzt_guest_read_memory(guest_name_addr + i, &snapshot[i], 1, + ops) != 0) { kzt_link_map_reader_free(snapshot); name->status = KZT_GUEST_FIELD_READ_ERROR; return 0; @@ -308,9 +416,7 @@ int kzt_guest_link_map_read_name_snapshot( } } - snapshot[max_len] = '\0'; - name->value = snapshot; - name->status = KZT_GUEST_FIELD_TRUNCATED; + kzt_link_map_reader_free(snapshot); return 0; } diff --git a/target/i386/latx/context/kzt_guest_registry.c b/target/i386/latx/context/kzt_guest_registry.c index 6f374ced3ee..09a14b68ee8 100644 --- a/target/i386/latx/context/kzt_guest_registry.c +++ b/target/i386/latx/context/kzt_guest_registry.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -9,6 +10,14 @@ #define KZT_GUEST_REGISTRY_INITIAL_CAPACITY 8 +typedef struct kzt_guest_loader_handle_entry { + kzt_guest_loader_identity_t identity; + unsigned long references; + int active; + int unload_unproven; + int resident_guaranteed; +} kzt_guest_loader_handle_entry_t; + struct kzt_guest_registry { pthread_mutex_t lock; pthread_cond_t leases_idle; @@ -16,10 +25,16 @@ struct kzt_guest_registry { int disabled; int destroying; unsigned long active_api_users; + unsigned long active_patch_decision_leases; + unsigned long evidence_mutators_waiting; kzt_guest_object_snapshot_t *objects; size_t count; size_t capacity; + kzt_guest_loader_handle_entry_t *handles; + size_t handle_count; + size_t handle_capacity; unsigned long next_generation; + unsigned long next_handle_generation; kzt_guest_registry_diagnostics_t diagnostics; kzt_guest_registry_diagnostic_config_t diagnostic_config; kzt_guest_registry_event_summary_t diagnostic_events[ @@ -35,8 +50,15 @@ static kzt_guest_registry_test_hook_fn test_after_retire_wake; static void *test_after_retire_wake_opaque; static kzt_guest_registry_test_hook_fn test_after_destroy_disable; static void *test_after_destroy_disable_opaque; +static kzt_guest_registry_test_hook_fn test_before_patch_decision_wait; +static void *test_before_patch_decision_wait_opaque; #endif +static int kzt_registry_destroying(const kzt_guest_registry_t *registry) +{ + return __atomic_load_n(®istry->destroying, __ATOMIC_ACQUIRE); +} + static int kzt_registry_api_enter(kzt_guest_registry_t *registry) { if (!registry) { @@ -49,7 +71,7 @@ static int kzt_registry_api_enter(kzt_guest_registry_t *registry) test_after_api_enter(test_after_api_enter_opaque); } #endif - if (__atomic_load_n(®istry->destroying, __ATOMIC_ACQUIRE)) { + if (kzt_registry_destroying(registry)) { pthread_mutex_lock(®istry->lock); if (__atomic_sub_fetch(®istry->active_api_users, 1, __ATOMIC_ACQ_REL) == 0) { @@ -86,6 +108,42 @@ static void kzt_registry_api_leave(kzt_guest_registry_t *registry) kzt_registry_api_unlock(registry); } +/* The caller owns registry->lock. Registered mutators close admission for new + * leases until all current waiters have revalidated under this same mutex. + * Wakeups are not FIFO: a mutator overtaken by retire must fail open. */ +static int kzt_registry_wait_for_patch_decisions( + kzt_guest_registry_t *registry) +{ + int registered = 0; + + while (registry->active_patch_decision_leases) { + if (registry->disabled || kzt_registry_destroying(registry)) { + break; + } + if (!registered) { + ++registry->evidence_mutators_waiting; + registered = 1; +#ifdef KZT_GUEST_REGISTRY_TEST + if (test_before_patch_decision_wait) { + test_before_patch_decision_wait( + test_before_patch_decision_wait_opaque); + } +#endif + } + pthread_cond_wait(®istry->leases_idle, ®istry->lock); + } + if (registered) { + --registry->evidence_mutators_waiting; + } + if (registry->disabled || kzt_registry_destroying(registry)) { + if (!registry->evidence_mutators_waiting) { + pthread_cond_broadcast(®istry->leases_idle); + } + return -1; + } + return 0; +} + #ifdef KZT_GUEST_REGISTRY_TEST static long test_alloc_failure_after = -1; static long test_dynamic_commit_failure_after = -1; @@ -119,6 +177,13 @@ void kzt_guest_registry_test_set_after_destroy_disable( test_after_destroy_disable_opaque = opaque; } +void kzt_guest_registry_test_set_before_patch_decision_wait( + kzt_guest_registry_test_hook_fn hook, void *opaque) +{ + test_before_patch_decision_wait = hook; + test_before_patch_decision_wait_opaque = opaque; +} + void kzt_guest_registry_test_set_alloc_failure_after(long allocations) { test_alloc_failure_after = allocations; @@ -190,8 +255,7 @@ static int kzt_field_is_reliable(kzt_guest_field_status_t status) static int kzt_string_status_has_snapshot(kzt_guest_field_status_t status) { - return status == KZT_GUEST_FIELD_OK || - status == KZT_GUEST_FIELD_TRUNCATED; + return status == KZT_GUEST_FIELD_OK; } static void kzt_free_string_field(kzt_guest_string_field_t *field) @@ -207,7 +271,9 @@ static void kzt_free_string_field(kzt_guest_string_field_t *field) static int kzt_copy_string_field(kzt_guest_string_field_t *dst, const kzt_guest_string_field_t *src) { - dst->status = src->status; + dst->status = src->status == KZT_GUEST_FIELD_TRUNCATED + ? KZT_GUEST_FIELD_UNKNOWN + : src->status; dst->value = NULL; if (!kzt_string_status_has_snapshot(src->status)) { @@ -540,6 +606,33 @@ static int kzt_registry_ensure_capacity(kzt_guest_registry_t *registry) return 0; } +static int kzt_registry_ensure_handle_capacity( + kzt_guest_registry_t *registry) +{ + kzt_guest_loader_handle_entry_t *handles; + size_t new_capacity; + + if (registry->handle_count < registry->handle_capacity) { + return 0; + } + new_capacity = registry->handle_capacity + ? registry->handle_capacity * 2 + : KZT_GUEST_REGISTRY_INITIAL_CAPACITY; + handles = kzt_registry_calloc(new_capacity, sizeof(*handles)); + if (!handles) { + ++registry->diagnostics.allocation_failures; + return -1; + } + if (registry->handles) { + memcpy(handles, registry->handles, + registry->handle_count * sizeof(*handles)); + kzt_registry_free(registry->handles); + } + registry->handles = handles; + registry->handle_capacity = new_capacity; + return 0; +} + static const char *kzt_registry_result_name(kzt_guest_registry_result_t result) { switch (result) { @@ -696,6 +789,7 @@ kzt_guest_registry_t *kzt_guest_registry_init(void) return NULL; } registry->next_generation = 1; + registry->next_handle_generation = 1; registry->capacity = KZT_GUEST_REGISTRY_INITIAL_CAPACITY; registry->objects = kzt_registry_calloc(registry->capacity, sizeof(*registry->objects)); @@ -741,6 +835,10 @@ void kzt_guest_registry_destroy(kzt_guest_registry_t **registry_ptr) break; } } + if (registry->active_patch_decision_leases || + registry->evidence_mutators_waiting) { + busy = 1; + } if (!busy) { break; } @@ -753,6 +851,10 @@ void kzt_guest_registry_destroy(kzt_guest_registry_t **registry_ptr) registry->objects = NULL; registry->count = 0; registry->capacity = 0; + kzt_registry_free(registry->handles); + registry->handles = NULL; + registry->handle_count = 0; + registry->handle_capacity = 0; pthread_cond_destroy(®istry->leases_idle); if (registry->lock_ready) { @@ -801,6 +903,11 @@ kzt_guest_registry_result_t kzt_guest_registry_observe_with_diagnostic( goto out; } + if (kzt_registry_wait_for_patch_decisions(registry) != 0) { + result = KZT_GUEST_REGISTRY_DISABLED; + goto out; + } + if (!observation || observation->link_map_addr == 0) { result = KZT_GUEST_REGISTRY_ERROR; goto out; @@ -863,9 +970,267 @@ kzt_guest_registry_result_t kzt_guest_registry_observe_with_diagnostic( return result; } -int kzt_guest_registry_retire(kzt_guest_registry_t *registry, - uintptr_t link_map_addr, - unsigned long generation) +kzt_guest_registry_result_t kzt_guest_registry_supplement_map_range( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + uintptr_t map_start, + uintptr_t map_end, + kzt_guest_registry_observation_diagnostic_t *diagnostic) +{ + kzt_guest_scalar_field_t incoming_start = { + .value = map_start, + .status = KZT_GUEST_FIELD_OK, + }; + kzt_guest_scalar_field_t incoming_end = { + .value = map_end, + .status = KZT_GUEST_FIELD_OK, + }; + kzt_guest_registry_result_t result; + ssize_t index; + int updated = 0; + + if (!registry) { + kzt_registry_init_empty_diagnostic( + diagnostic, KZT_GUEST_REGISTRY_DISABLED, link_map_addr); + return KZT_GUEST_REGISTRY_DISABLED; + } + if (kzt_registry_api_lock(registry) != 0) { + kzt_registry_init_empty_diagnostic( + diagnostic, KZT_GUEST_REGISTRY_DISABLED, link_map_addr); + return KZT_GUEST_REGISTRY_DISABLED; + } + ++registry->diagnostics.observations; + + if (registry->disabled) { + result = KZT_GUEST_REGISTRY_DISABLED; + goto out; + } + if (!link_map_addr || !generation || !map_start || + map_start >= map_end) { + result = KZT_GUEST_REGISTRY_ERROR; + goto out; + } + if (kzt_registry_wait_for_patch_decisions(registry) != 0) { + result = KZT_GUEST_REGISTRY_DISABLED; + goto out; + } + + index = kzt_find_object_index(registry, link_map_addr); + if (index < 0 || + registry->objects[index].generation != generation || + registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || + registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { + result = KZT_GUEST_REGISTRY_CONFLICT; + goto out; + } + if (kzt_scalar_conflicts(®istry->objects[index].map_start, + &incoming_start) || + kzt_scalar_conflicts(®istry->objects[index].map_end, + &incoming_end)) { + result = KZT_GUEST_REGISTRY_CONFLICT; + goto out; + } + + updated |= kzt_update_scalar_field( + ®istry->objects[index].map_start, &incoming_start); + updated |= kzt_update_scalar_field( + ®istry->objects[index].map_end, &incoming_end); + result = updated ? KZT_GUEST_REGISTRY_UPDATED : + KZT_GUEST_REGISTRY_UNCHANGED; + +out: + kzt_registry_note_result(registry, result, link_map_addr, generation, + diagnostic); + kzt_registry_api_unlock(registry); + return result; +} + +kzt_guest_registry_result_t kzt_guest_registry_supplement_namespace( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + uintptr_t namespace_id) +{ + kzt_guest_scalar_field_t incoming = { + .value = namespace_id, + .status = KZT_GUEST_FIELD_OK, + }; + kzt_guest_registry_result_t result; + ssize_t index; + int updated; + + if (!registry || kzt_registry_api_lock(registry) != 0) { + return KZT_GUEST_REGISTRY_DISABLED; + } + ++registry->diagnostics.observations; + if (registry->disabled || !link_map_addr || !generation) { + result = registry->disabled ? KZT_GUEST_REGISTRY_DISABLED + : KZT_GUEST_REGISTRY_ERROR; + goto out; + } + if (kzt_registry_wait_for_patch_decisions(registry) != 0) { + result = KZT_GUEST_REGISTRY_DISABLED; + goto out; + } + index = kzt_find_object_index(registry, link_map_addr); + if (index < 0 || registry->objects[index].generation != generation || + registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || + registry->objects[index].state == KZT_GUEST_OBJECT_DEAD || + kzt_scalar_conflicts(®istry->objects[index].namespace_id, + &incoming)) { + result = KZT_GUEST_REGISTRY_CONFLICT; + goto out; + } + updated = kzt_update_scalar_field( + ®istry->objects[index].namespace_id, &incoming); + result = updated ? KZT_GUEST_REGISTRY_UPDATED + : KZT_GUEST_REGISTRY_UNCHANGED; + +out: + kzt_registry_note_result(registry, result, link_map_addr, generation, + NULL); + kzt_registry_api_unlock(registry); + return result; +} + +static int kzt_registry_loader_identity_matches( + const kzt_guest_object_snapshot_t *object, + const kzt_guest_loader_identity_t *identity) +{ + return object && identity && + object->link_map_addr == identity->link_map_addr && + object->generation == identity->generation && + object->namespace_id.status == KZT_GUEST_FIELD_OK && + object->namespace_id.value == identity->namespace_id; +} + +int kzt_guest_registry_begin_loader_unload( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity) +{ + ssize_t index; + + if (!registry || !identity || !identity->link_map_addr || + !identity->generation || kzt_registry_api_lock(registry) != 0) { + return -1; + } + if (registry->disabled || + kzt_registry_wait_for_patch_decisions(registry) != 0) { + kzt_registry_api_unlock(registry); + return -1; + } + index = kzt_find_object_index(registry, identity->link_map_addr); + if (index < 0 || + !kzt_registry_loader_identity_matches( + ®istry->objects[index], identity) || + registry->objects[index].state >= KZT_GUEST_OBJECT_UNLOADING) { + kzt_registry_api_unlock(registry); + return -1; + } + registry->objects[index].unload_previous_state = + registry->objects[index].state; + registry->objects[index].state = KZT_GUEST_OBJECT_UNLOADING; + while (registry->objects[index].active_source_leases) { +#ifdef KZT_GUEST_REGISTRY_TEST + if (test_before_retire_wait) { + test_before_retire_wait(test_before_retire_wait_opaque); + } +#endif + pthread_cond_wait(®istry->leases_idle, ®istry->lock); +#ifdef KZT_GUEST_REGISTRY_TEST + if (test_after_retire_wake) { + pthread_mutex_unlock(®istry->lock); + test_after_retire_wake(test_after_retire_wake_opaque); + pthread_mutex_lock(®istry->lock); + } +#endif + index = kzt_find_object_index(registry, identity->link_map_addr); + if (registry->disabled || index < 0 || + !kzt_registry_loader_identity_matches( + ®istry->objects[index], identity) || + registry->objects[index].state != KZT_GUEST_OBJECT_UNLOADING) { + kzt_registry_api_unlock(registry); + return -1; + } + } + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_cancel_loader_unload( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity) +{ + ssize_t index; + + if (!registry || !identity || !identity->link_map_addr || + !identity->generation || kzt_registry_api_lock(registry) != 0) { + return -1; + } + index = kzt_find_object_index(registry, identity->link_map_addr); + if (registry->disabled || index < 0 || + !kzt_registry_loader_identity_matches( + ®istry->objects[index], identity) || + registry->objects[index].state != KZT_GUEST_OBJECT_UNLOADING || + registry->objects[index].active_source_leases) { + kzt_registry_api_unlock(registry); + return -1; + } + registry->objects[index].state = + registry->objects[index].unload_previous_state < + KZT_GUEST_OBJECT_UNLOADING + ? registry->objects[index].unload_previous_state + : KZT_GUEST_OBJECT_DISCOVERED; + pthread_cond_broadcast(®istry->leases_idle); + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_finish_loader_unload( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity) +{ + ssize_t index; + size_t i; + + if (!registry || !identity || !identity->link_map_addr || + !identity->generation || kzt_registry_api_lock(registry) != 0) { + return -1; + } + index = kzt_find_object_index(registry, identity->link_map_addr); + if (registry->disabled || index < 0 || + !kzt_registry_loader_identity_matches( + ®istry->objects[index], identity) || + registry->objects[index].state != KZT_GUEST_OBJECT_UNLOADING || + registry->objects[index].active_source_leases) { + kzt_registry_api_unlock(registry); + return -1; + } + registry->objects[index].state = KZT_GUEST_OBJECT_DEAD; + for (i = 0; i < registry->handle_count; ++i) { + kzt_guest_loader_handle_entry_t *entry = ®istry->handles[i]; + + if (entry->identity.link_map_addr == identity->link_map_addr && + entry->identity.generation == identity->generation && + entry->identity.namespace_id == identity->namespace_id) { + entry->active = 0; + entry->references = 0; + entry->unload_unproven = 0; + entry->resident_guaranteed = 0; + } + } + pthread_cond_broadcast(®istry->leases_idle); + kzt_registry_api_unlock(registry); + return 0; +} + +static int kzt_guest_registry_retire_exact( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + int require_namespace, + uintptr_t namespace_id) { ssize_t index; if (!registry || !link_map_addr || !generation || @@ -873,6 +1238,24 @@ int kzt_guest_registry_retire(kzt_guest_registry_t *registry, index = kzt_find_object_index(registry, link_map_addr); if (registry->disabled || index < 0 || registry->objects[index].generation != generation || + (require_namespace && + (registry->objects[index].namespace_id.status != KZT_GUEST_FIELD_OK || + registry->objects[index].namespace_id.value != namespace_id)) || + registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || + registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return -1; + } + if (kzt_registry_wait_for_patch_decisions(registry) != 0) { + kzt_registry_api_unlock(registry); + return -1; + } + index = kzt_find_object_index(registry, link_map_addr); + if (registry->disabled || index < 0 || + registry->objects[index].generation != generation || + (require_namespace && + (registry->objects[index].namespace_id.status != KZT_GUEST_FIELD_OK || + registry->objects[index].namespace_id.value != namespace_id)) || registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { kzt_registry_api_unlock(registry); @@ -883,6 +1266,10 @@ int kzt_guest_registry_retire(kzt_guest_registry_t *registry, index = kzt_find_object_index(registry, link_map_addr); if (registry->disabled || index < 0 || registry->objects[index].generation != generation || + (require_namespace && + (registry->objects[index].namespace_id.status != + KZT_GUEST_FIELD_OK || + registry->objects[index].namespace_id.value != namespace_id)) || registry->objects[index].state != KZT_GUEST_OBJECT_UNLOADING) { kzt_registry_api_unlock(registry); return -1; @@ -905,11 +1292,42 @@ int kzt_guest_registry_retire(kzt_guest_registry_t *registry, #endif } registry->objects[index].state = KZT_GUEST_OBJECT_DEAD; + for (size_t i = 0; i < registry->handle_count; ++i) { + kzt_guest_loader_handle_entry_t *entry = ®istry->handles[i]; + + if (entry->identity.link_map_addr == link_map_addr && + entry->identity.generation == generation) { + entry->active = 0; + entry->references = 0; + entry->unload_unproven = 0; + entry->resident_guaranteed = 0; + } + } pthread_cond_broadcast(®istry->leases_idle); kzt_registry_api_unlock(registry); return 0; } +int kzt_guest_registry_retire(kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation) +{ + return kzt_guest_registry_retire_exact( + registry, link_map_addr, generation, 0, 0); +} + +int kzt_guest_registry_retire_loader_identity( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity) +{ + if (!identity || !identity->link_map_addr || !identity->generation) { + return -1; + } + return kzt_guest_registry_retire_exact( + registry, identity->link_map_addr, identity->generation, 1, + identity->namespace_id); +} + int kzt_guest_registry_wait_retired(kzt_guest_registry_t *registry, uintptr_t link_map_addr, unsigned long generation) @@ -958,7 +1376,7 @@ int kzt_guest_registry_source_lease_acquire( return -1; } index = kzt_find_object_index(registry, link_map_addr); - if (registry->disabled || index < 0 || + if (registry->disabled || registry->evidence_mutators_waiting || index < 0 || registry->objects[index].generation != generation || registry->objects[index].namespace_id.status != KZT_GUEST_FIELD_OK || registry->objects[index].namespace_id.value != namespace_id || @@ -1002,38 +1420,229 @@ void kzt_guest_registry_source_lease_release( memset(lease, 0, sizeof(*lease)); } -int kzt_guest_registry_find_by_link_map( - kzt_guest_registry_t *registry, - uintptr_t link_map_addr, - kzt_guest_object_snapshot_t **snapshot) +int kzt_guest_registry_patch_decision_lease_acquire( + const kzt_guest_registry_source_lease_t *source_lease, + kzt_guest_registry_patch_decision_lease_t *lease) { + kzt_guest_registry_t *registry; ssize_t index; - if (snapshot) { - *snapshot = NULL; + if (lease) { + memset(lease, 0, sizeof(*lease)); } - if (!registry || !snapshot || link_map_addr == 0) { + if (!source_lease || !source_lease->active || + !(registry = source_lease->registry) || !lease || + !source_lease->link_map_addr || !source_lease->generation || + source_lease->namespace_id != 0) { return -1; } - if (kzt_registry_api_lock(registry) != 0) { return -1; } - if (registry->disabled) { - kzt_registry_api_unlock(registry); - return -1; - } - - index = kzt_find_object_index(registry, link_map_addr); - if (index < 0 || + index = kzt_find_object_index(registry, source_lease->link_map_addr); + if (registry->disabled || registry->evidence_mutators_waiting || index < 0 || + registry->objects[index].generation != source_lease->generation || + registry->objects[index].namespace_id.status != KZT_GUEST_FIELD_OK || + registry->objects[index].namespace_id.value != + source_lease->namespace_id || registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || - registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { + registry->objects[index].state == KZT_GUEST_OBJECT_DEAD || + !registry->objects[index].active_source_leases) { kzt_registry_api_unlock(registry); return -1; } + ++registry->active_patch_decision_leases; + lease->registry = registry; + lease->link_map_addr = source_lease->link_map_addr; + lease->generation = source_lease->generation; + lease->namespace_id = source_lease->namespace_id; + lease->active = 1; + kzt_registry_api_unlock(registry); + return 0; +} - *snapshot = kzt_registry_calloc(1, sizeof(**snapshot)); - if (!*snapshot) { +void kzt_guest_registry_patch_decision_lease_release( + kzt_guest_registry_patch_decision_lease_t *lease) +{ + kzt_guest_registry_t *registry; + + if (!lease || !lease->active || !(registry = lease->registry)) { + return; + } + pthread_mutex_lock(®istry->lock); + if (registry->active_patch_decision_leases) { + --registry->active_patch_decision_leases; + if (!registry->active_patch_decision_leases) { + pthread_cond_broadcast(®istry->leases_idle); + } + } + pthread_mutex_unlock(®istry->lock); + memset(lease, 0, sizeof(*lease)); +} + +static int kzt_registry_got_plt_view_complete( + const kzt_guest_dynamic_view_t *view) +{ + return view && view->status == KZT_GUEST_DYNAMIC_COMPLETE && + view->has_null && view->dynamic_addr && + view->jmprel.present && + view->jmprel.address_semantics == + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS && + view->pltrelsz.present && view->pltrelsz.value && + view->pltrelsz.address_semantics == KZT_GUEST_DYNAMIC_SCALAR && + view->pltrel.present && view->pltrel.value && + view->pltrel.address_semantics == KZT_GUEST_DYNAMIC_SCALAR && + view->pltgot.present && + view->pltgot.address_semantics == + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS; +} + +kzt_guest_got_plt_injection_claim_result_t +kzt_guest_registry_got_plt_injection_claim( + const kzt_guest_registry_patch_decision_lease_t *lease, + const kzt_guest_dynamic_view_t *view) +{ + kzt_guest_registry_t *registry; + ssize_t index; + kzt_guest_object_snapshot_t *object; + + if (!lease || !lease->active || !(registry = lease->registry) || + !lease->link_map_addr || !lease->generation || + lease->namespace_id != 0 || !kzt_registry_got_plt_view_complete(view)) { + return KZT_GUEST_GOT_PLT_INJECTION_FAIL_OPEN; + } + + if (kzt_registry_api_lock(registry) != 0) { + return KZT_GUEST_GOT_PLT_INJECTION_FAIL_OPEN; + } + index = kzt_find_object_index(registry, lease->link_map_addr); + if (registry->disabled || index < 0) { + kzt_registry_api_unlock(registry); + return KZT_GUEST_GOT_PLT_INJECTION_FAIL_OPEN; + } + object = ®istry->objects[index]; + if (object->generation != lease->generation || + object->namespace_id.status != KZT_GUEST_FIELD_OK || + object->namespace_id.value != lease->namespace_id || + object->dynamic_view_status != KZT_GUEST_FIELD_OK || + !kzt_dynamic_view_equal(&object->dynamic_view, view) || + object->state == KZT_GUEST_OBJECT_UNLOADING || + object->state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return KZT_GUEST_GOT_PLT_INJECTION_FAIL_OPEN; + } + if (object->got_plt_injection_state == + KZT_GUEST_GOT_PLT_INJECTION_APPLIED) { + kzt_registry_api_unlock(registry); + return KZT_GUEST_GOT_PLT_INJECTION_ALREADY_APPLIED; + } + if (object->got_plt_injection_state == + KZT_GUEST_GOT_PLT_INJECTION_APPLYING) { + kzt_registry_api_unlock(registry); + return KZT_GUEST_GOT_PLT_INJECTION_IN_PROGRESS; + } + object->got_plt_injection_state = KZT_GUEST_GOT_PLT_INJECTION_APPLYING; + kzt_registry_api_unlock(registry); + return KZT_GUEST_GOT_PLT_INJECTION_GRANTED; +} + +int kzt_guest_registry_got_plt_injection_finish( + const kzt_guest_registry_patch_decision_lease_t *lease, + int applied) +{ + kzt_guest_registry_t *registry; + ssize_t index; + kzt_guest_object_snapshot_t *object; + + if (!lease || !lease->active || !(registry = lease->registry) || + !lease->link_map_addr || !lease->generation || + lease->namespace_id != 0 || kzt_registry_api_lock(registry) != 0) { + return -1; + } + index = kzt_find_object_index(registry, lease->link_map_addr); + if (registry->disabled || index < 0) { + kzt_registry_api_unlock(registry); + return -1; + } + object = ®istry->objects[index]; + if (object->generation != lease->generation || + object->namespace_id.status != KZT_GUEST_FIELD_OK || + object->namespace_id.value != lease->namespace_id || + object->state == KZT_GUEST_OBJECT_UNLOADING || + object->state == KZT_GUEST_OBJECT_DEAD || + object->got_plt_injection_state != + KZT_GUEST_GOT_PLT_INJECTION_APPLYING) { + kzt_registry_api_unlock(registry); + return -1; + } + object->got_plt_injection_state = applied ? + KZT_GUEST_GOT_PLT_INJECTION_APPLIED : + KZT_GUEST_GOT_PLT_INJECTION_NONE; + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_got_plt_injection_claimed( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + uintptr_t namespace_id) +{ + ssize_t index; + int claimed; + + if (!registry || !link_map_addr || !generation || namespace_id != 0 || + kzt_registry_api_lock(registry) != 0) { + return -1; + } + index = kzt_find_object_index(registry, link_map_addr); + if (registry->disabled || index < 0 || + registry->objects[index].generation != generation || + registry->objects[index].namespace_id.status != KZT_GUEST_FIELD_OK || + registry->objects[index].namespace_id.value != namespace_id || + registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || + registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return -1; + } + claimed = registry->objects[index].got_plt_injection_state != + KZT_GUEST_GOT_PLT_INJECTION_NONE; + kzt_registry_api_unlock(registry); + return claimed; +} + +int kzt_guest_registry_find_by_link_map( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_object_snapshot_t **snapshot) +{ + ssize_t index; + + if (snapshot) { + *snapshot = NULL; + } + if (!registry || !snapshot || link_map_addr == 0) { + return -1; + } + + if (kzt_registry_api_lock(registry) != 0) { + return -1; + } + if (registry->disabled) { + kzt_registry_api_unlock(registry); + return -1; + } + + index = kzt_find_object_index(registry, link_map_addr); + if (index < 0 || + registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || + registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return -1; + } + + *snapshot = kzt_registry_calloc(1, sizeof(**snapshot)); + if (!*snapshot) { ++registry->diagnostics.allocation_failures; kzt_registry_api_unlock(registry); return -1; @@ -1051,6 +1660,669 @@ int kzt_guest_registry_find_by_link_map( return 0; } +static int kzt_registry_object_contains_address( + const kzt_guest_object_snapshot_t *object, uintptr_t address) +{ + return object && + object->map_start.status == KZT_GUEST_FIELD_OK && + object->map_end.status == KZT_GUEST_FIELD_OK && + object->map_start.value < object->map_end.value && + address >= object->map_start.value && + address < object->map_end.value; +} + +static void kzt_registry_copy_address_match( + kzt_guest_registry_address_match_t *match, + const kzt_guest_object_snapshot_t *object) +{ + if (!match || !object) { + return; + } + match->link_map_addr = object->link_map_addr; + match->map_start = object->map_start.value; + match->map_end = object->map_end.value; + match->namespace_id = object->namespace_id.value; + match->generation = object->generation; + match->soname_status = object->soname.status; + match->path_status = object->path.status; + match->namespace_id_status = object->namespace_id.status; + match->soname[0] = '\0'; + match->path[0] = '\0'; + if (object->soname.value) { + if (strlen(object->soname.value) < sizeof(match->soname)) { + snprintf(match->soname, sizeof(match->soname), "%s", + object->soname.value); + } else { + match->soname_status = KZT_GUEST_FIELD_UNKNOWN; + } + } + if (object->path.value) { + if (strlen(object->path.value) < sizeof(match->path)) { + snprintf(match->path, sizeof(match->path), "%s", + object->path.value); + } else { + match->path_status = KZT_GUEST_FIELD_UNKNOWN; + } + } +} + +static void kzt_registry_note_address_match( + kzt_guest_registry_address_match_t *match, + const kzt_guest_object_snapshot_t *object) +{ + if (!match || !object) { + return; + } + ++match->match_count; + if (match->match_count == 1) { + kzt_registry_copy_address_match(match, object); + } +} + +int kzt_guest_registry_resolve_address_pair( + kzt_guest_registry_t *registry, + uintptr_t current_address, + uintptr_t expected_address, + kzt_guest_registry_address_pair_t *pair) +{ + size_t i; + + if (pair) { + memset(pair, 0, sizeof(*pair)); + } + if (!registry || !current_address || !expected_address || !pair || + kzt_registry_api_lock(registry) != 0) { + return -1; + } + if (registry->disabled) { + kzt_registry_api_unlock(registry); + return -1; + } + + for (i = 0; i < registry->count; ++i) { + const kzt_guest_object_snapshot_t *object = ®istry->objects[i]; + + if (object->state == KZT_GUEST_OBJECT_UNLOADING || + object->state == KZT_GUEST_OBJECT_DEAD) { + continue; + } + if (kzt_registry_object_contains_address(object, current_address)) { + kzt_registry_note_address_match(&pair->current, object); + } + if (kzt_registry_object_contains_address(object, expected_address)) { + kzt_registry_note_address_match(&pair->expected, object); + } + } + + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_find_live_object( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_registry_address_match_t *match) +{ + ssize_t index; + + if (match) { + memset(match, 0, sizeof(*match)); + } + if (!registry || !link_map_addr || !match || + kzt_registry_api_lock(registry) != 0) { + return -1; + } + index = kzt_find_object_index(registry, link_map_addr); + if (registry->disabled || index < 0 || + registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || + registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return -1; + } + kzt_registry_copy_address_match(match, ®istry->objects[index]); + match->match_count = 1; + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_publish_loader_identity( + kzt_guest_registry_t *registry, + uintptr_t handle, + uintptr_t link_map_addr, + uintptr_t namespace_id, + kzt_guest_loader_identity_t *identity) +{ + kzt_guest_object_snapshot_t *object; + kzt_guest_loader_handle_entry_t *entry = NULL; + ssize_t object_index; + size_t i; + int rebind = 0; + + if (identity) { + memset(identity, 0, sizeof(*identity)); + } + if (!registry || !handle || !link_map_addr || !identity || + kzt_registry_api_lock(registry) != 0) { + return -1; + } + if (registry->disabled || + kzt_registry_wait_for_patch_decisions(registry) != 0) { + kzt_registry_api_unlock(registry); + return -1; + } + object_index = kzt_find_object_index(registry, link_map_addr); + if (object_index < 0) { + kzt_registry_api_unlock(registry); + return -1; + } + object = ®istry->objects[object_index]; + if (!object->generation || + object->state == KZT_GUEST_OBJECT_UNLOADING || + object->state == KZT_GUEST_OBJECT_DEAD || + (object->namespace_id.status == KZT_GUEST_FIELD_OK && + object->namespace_id.value != namespace_id)) { + kzt_registry_api_unlock(registry); + return -1; + } + if (object->namespace_id.status != KZT_GUEST_FIELD_OK) { + object->namespace_id.value = namespace_id; + object->namespace_id.status = KZT_GUEST_FIELD_OK; + } + + for (i = 0; i < registry->handle_count; ++i) { + if (registry->handles[i].active && + registry->handles[i].identity.handle == handle) { + entry = ®istry->handles[i]; + break; + } + } + if (!entry) { + for (i = 0; i < registry->handle_count; ++i) { + if (!registry->handles[i].active && + registry->handles[i].unload_unproven && + registry->handles[i].identity.handle == handle) { + entry = ®istry->handles[i]; + if (entry->identity.link_map_addr != link_map_addr || + entry->identity.generation != object->generation || + entry->identity.namespace_id != namespace_id) { + kzt_registry_api_unlock(registry); + return -1; + } + rebind = 1; + break; + } + } + } + if (entry && !rebind) { + if (entry->identity.link_map_addr != link_map_addr || + entry->identity.generation != object->generation || + entry->identity.namespace_id != namespace_id || + entry->references == (unsigned long)-1) { + kzt_registry_api_unlock(registry); + return -1; + } + ++entry->references; + ++registry->diagnostics.loader_identity_publications; + *identity = entry->identity; + kzt_registry_api_unlock(registry); + return 0; + } + + if (registry->next_handle_generation == ULONG_MAX) { + kzt_registry_api_unlock(registry); + return -1; + } + if (rebind) { + entry->identity.handle_generation = + registry->next_handle_generation++; + entry->references = 1; + entry->active = 1; + entry->unload_unproven = 0; + ++registry->diagnostics.loader_identity_publications; + *identity = entry->identity; + kzt_registry_api_unlock(registry); + return 0; + } + + for (i = 0; i < registry->handle_count; ++i) { + if (!registry->handles[i].active && + !registry->handles[i].unload_unproven) { + entry = ®istry->handles[i]; + break; + } + } + if (!entry) { + if (kzt_registry_ensure_handle_capacity(registry) != 0) { + kzt_registry_api_unlock(registry); + return -1; + } + entry = ®istry->handles[registry->handle_count++]; + } + memset(entry, 0, sizeof(*entry)); + entry->identity.handle = handle; + entry->identity.link_map_addr = link_map_addr; + entry->identity.generation = object->generation; + entry->identity.namespace_id = namespace_id; + entry->identity.handle_generation = + registry->next_handle_generation++; + entry->references = 1; + entry->active = 1; + entry->unload_unproven = 0; + ++registry->diagnostics.loader_identity_publications; + *identity = entry->identity; + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_find_loader_identity( + kzt_guest_registry_t *registry, + uintptr_t handle, + kzt_guest_loader_identity_t *identity) +{ + kzt_guest_loader_handle_entry_t *entry; + kzt_guest_object_snapshot_t *object; + ssize_t object_index; + size_t i; + + if (identity) { + memset(identity, 0, sizeof(*identity)); + } + if (!registry || !handle || !identity || + kzt_registry_api_lock(registry) != 0) { + return -1; + } + for (i = 0; i < registry->handle_count; ++i) { + entry = ®istry->handles[i]; + if (!entry->active || !entry->references || + entry->identity.handle != handle) { + continue; + } + object_index = kzt_find_object_index( + registry, entry->identity.link_map_addr); + if (object_index < 0) { + break; + } + object = ®istry->objects[object_index]; + if (object->generation == entry->identity.generation && + object->namespace_id.status == KZT_GUEST_FIELD_OK && + object->namespace_id.value == entry->identity.namespace_id && + object->state != KZT_GUEST_OBJECT_UNLOADING && + object->state != KZT_GUEST_OBJECT_DEAD) { + *identity = entry->identity; + kzt_registry_api_unlock(registry); + return 0; + } + break; + } + kzt_registry_api_unlock(registry); + return -1; +} + +int kzt_guest_registry_reuse_loader_identity( + kzt_guest_registry_t *registry, + uintptr_t handle, + kzt_guest_loader_identity_t *identity) +{ + kzt_guest_loader_handle_entry_t *entry = NULL; + kzt_guest_object_snapshot_t *object; + ssize_t object_index; + size_t i; + int rebind = 0; + + if (identity) { + memset(identity, 0, sizeof(*identity)); + } + if (!registry || !handle || !identity || + kzt_registry_api_lock(registry) != 0) { + return -1; + } + if (registry->disabled || + kzt_registry_wait_for_patch_decisions(registry) != 0) { + kzt_registry_api_unlock(registry); + return -1; + } + + for (i = 0; i < registry->handle_count; ++i) { + if (registry->handles[i].active && + registry->handles[i].references && + registry->handles[i].identity.handle == handle) { + entry = ®istry->handles[i]; + break; + } + } + if (!entry) { + for (i = 0; i < registry->handle_count; ++i) { + if (!registry->handles[i].active && + registry->handles[i].unload_unproven && + registry->handles[i].resident_guaranteed && + registry->handles[i].identity.handle == handle) { + entry = ®istry->handles[i]; + rebind = 1; + break; + } + } + } + if (!entry || !entry->identity.link_map_addr || + !entry->identity.generation || + (!rebind && !entry->identity.handle_generation)) { + kzt_registry_api_unlock(registry); + return -1; + } + + object_index = kzt_find_object_index( + registry, entry->identity.link_map_addr); + if (object_index < 0) { + kzt_registry_api_unlock(registry); + return -1; + } + object = ®istry->objects[object_index]; + if (object->generation != entry->identity.generation || + object->namespace_id.status != KZT_GUEST_FIELD_OK || + object->namespace_id.value != entry->identity.namespace_id || + object->state == KZT_GUEST_OBJECT_UNLOADING || + object->state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return -1; + } + + if (!rebind) { + if (entry->references == ULONG_MAX) { + kzt_registry_api_unlock(registry); + return -1; + } + ++entry->references; + } else { + if (registry->next_handle_generation == ULONG_MAX) { + kzt_registry_api_unlock(registry); + return -1; + } + entry->identity.handle_generation = + registry->next_handle_generation++; + entry->references = 1; + entry->active = 1; + entry->unload_unproven = 0; + } + ++registry->diagnostics.loader_identity_publications; + *identity = entry->identity; + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_mark_loader_resident( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity) +{ + kzt_guest_loader_handle_entry_t *entry; + kzt_guest_object_snapshot_t *object; + ssize_t object_index; + size_t i; + + if (!registry || !identity || !identity->handle || + !identity->link_map_addr || !identity->generation || + !identity->handle_generation || + kzt_registry_api_lock(registry) != 0) { + return -1; + } + if (registry->disabled) { + kzt_registry_api_unlock(registry); + return -1; + } + for (i = 0; i < registry->handle_count; ++i) { + entry = ®istry->handles[i]; + if (!entry->active || !entry->references || + entry->identity.handle != identity->handle) { + continue; + } + if (entry->identity.link_map_addr != identity->link_map_addr || + entry->identity.generation != identity->generation || + entry->identity.namespace_id != identity->namespace_id || + entry->identity.handle_generation != + identity->handle_generation) { + break; + } + object_index = kzt_find_object_index( + registry, entry->identity.link_map_addr); + if (object_index < 0) { + break; + } + object = ®istry->objects[object_index]; + if (object->generation != entry->identity.generation || + object->namespace_id.status != KZT_GUEST_FIELD_OK || + object->namespace_id.value != entry->identity.namespace_id || + object->state == KZT_GUEST_OBJECT_UNLOADING || + object->state == KZT_GUEST_OBJECT_DEAD) { + break; + } + entry->resident_guaranteed = 1; + kzt_registry_api_unlock(registry); + return 0; + } + kzt_registry_api_unlock(registry); + return -1; +} + +int kzt_guest_registry_loader_symbol_source_acquire( + kzt_guest_registry_t *registry, uintptr_t handle, + kzt_guest_loader_identity_t *identity, + kzt_guest_dynamic_view_t *dynamic_view, + kzt_guest_field_status_t *dynamic_status, + unsigned long *dynamic_revision, + kzt_guest_registry_source_lease_t *lease) +{ + kzt_guest_loader_handle_entry_t *entry = NULL; + kzt_guest_object_snapshot_t *object; + ssize_t object_index; + size_t i; + + if (identity) memset(identity, 0, sizeof(*identity)); + if (dynamic_view) memset(dynamic_view, 0, sizeof(*dynamic_view)); + if (dynamic_status) *dynamic_status = KZT_GUEST_FIELD_NOT_PARSED; + if (dynamic_revision) *dynamic_revision = 0; + if (lease) memset(lease, 0, sizeof(*lease)); + if (!registry || !handle || !identity || !dynamic_view || + !dynamic_status || !dynamic_revision || !lease || + kzt_registry_api_lock(registry) != 0) { + return -1; + } + if (registry->disabled || registry->evidence_mutators_waiting) { + kzt_registry_api_unlock(registry); + return -1; + } + for (i = 0; i < registry->handle_count; ++i) { + if (registry->handles[i].active && + registry->handles[i].references && + registry->handles[i].identity.handle == handle) { + entry = ®istry->handles[i]; + break; + } + } + if (!entry || !entry->identity.generation || + entry->identity.namespace_id != 0 || + (object_index = kzt_find_object_index( + registry, entry->identity.link_map_addr)) < 0) { + kzt_registry_api_unlock(registry); + return -1; + } + object = ®istry->objects[object_index]; + if (object->generation != entry->identity.generation || + object->namespace_id.status != KZT_GUEST_FIELD_OK || + object->namespace_id.value != entry->identity.namespace_id || + object->state == KZT_GUEST_OBJECT_UNLOADING || + object->state == KZT_GUEST_OBJECT_DEAD || + object->dynamic_view_status != KZT_GUEST_FIELD_OK) { + kzt_registry_api_unlock(registry); + return -1; + } + + ++object->active_source_leases; + *identity = entry->identity; + *dynamic_view = object->dynamic_view; + *dynamic_status = object->dynamic_view_status; + *dynamic_revision = object->dynamic_view_revision; + lease->registry = registry; + lease->link_map_addr = object->link_map_addr; + lease->generation = object->generation; + lease->namespace_id = object->namespace_id.value; + lease->active = 1; + kzt_registry_api_unlock(registry); + return 0; +} + +int kzt_guest_registry_find_loader_object_identity( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_loader_identity_t *identity) +{ + kzt_guest_object_snapshot_t *object; + ssize_t index; + + if (identity) { + memset(identity, 0, sizeof(*identity)); + } + if (!registry || !link_map_addr || !identity || + kzt_registry_api_lock(registry) != 0) { + return -1; + } + index = kzt_find_object_index(registry, link_map_addr); + if (registry->disabled || index < 0) { + kzt_registry_api_unlock(registry); + return -1; + } + object = ®istry->objects[index]; + if (!object->generation || + object->namespace_id.status != KZT_GUEST_FIELD_OK || + object->state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return -1; + } + *identity = (kzt_guest_loader_identity_t) { + .link_map_addr = object->link_map_addr, + .generation = object->generation, + .namespace_id = object->namespace_id.value, + }; + kzt_registry_api_unlock(registry); + return 0; +} + +kzt_guest_loader_close_result_t +kzt_guest_registry_complete_loader_close( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity) +{ + kzt_guest_object_snapshot_t *object; + kzt_guest_loader_handle_entry_t *entry; + ssize_t object_index; + size_t i; + + if (!registry || !identity || !identity->handle || + !identity->link_map_addr || !identity->generation || + !identity->handle_generation || + kzt_registry_api_lock(registry) != 0) { + return KZT_GUEST_LOADER_CLOSE_STALE; + } + object_index = kzt_find_object_index( + registry, identity->link_map_addr); + if (registry->disabled || object_index < 0) { + ++registry->diagnostics.loader_close_stale; + kzt_registry_api_unlock(registry); + return KZT_GUEST_LOADER_CLOSE_STALE; + } + object = ®istry->objects[object_index]; + if (object->generation == identity->generation && + object->namespace_id.status == KZT_GUEST_FIELD_OK && + object->namespace_id.value == identity->namespace_id && + object->state == KZT_GUEST_OBJECT_DEAD) { + ++registry->diagnostics.loader_close_retired; + kzt_registry_api_unlock(registry); + return KZT_GUEST_LOADER_CLOSE_RETIRED; + } + if (object->generation != identity->generation || + object->namespace_id.status != KZT_GUEST_FIELD_OK || + object->namespace_id.value != identity->namespace_id || + object->state == KZT_GUEST_OBJECT_UNLOADING) { + ++registry->diagnostics.loader_close_stale; + kzt_registry_api_unlock(registry); + return KZT_GUEST_LOADER_CLOSE_STALE; + } + + for (i = 0; i < registry->handle_count; ++i) { + entry = ®istry->handles[i]; + if (!entry->active || !entry->references || + entry->identity.handle != identity->handle) { + continue; + } + if (entry->identity.link_map_addr != identity->link_map_addr || + entry->identity.generation != identity->generation || + entry->identity.namespace_id != identity->namespace_id || + entry->identity.handle_generation != + identity->handle_generation) { + break; + } + --entry->references; + if (entry->references) { + ++registry->diagnostics.loader_close_referenced; + kzt_registry_api_unlock(registry); + return KZT_GUEST_LOADER_CLOSE_REFERENCED; + } + entry->active = 0; + entry->unload_unproven = 1; + ++registry->diagnostics.loader_close_unload_unproven; + kzt_registry_api_unlock(registry); + return KZT_GUEST_LOADER_CLOSE_UNLOAD_UNPROVEN; + } + + ++registry->diagnostics.loader_close_stale; + kzt_registry_api_unlock(registry); + return KZT_GUEST_LOADER_CLOSE_STALE; +} + +void kzt_guest_registry_note_loader_close_identity_missing( + kzt_guest_registry_t *registry) +{ + if (!registry || kzt_registry_api_lock(registry) != 0) { + return; + } + if (!registry->disabled) { + ++registry->diagnostics.loader_close_identity_missing; + } + kzt_registry_api_unlock(registry); +} + +int kzt_guest_registry_matches_live_identity( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + uintptr_t load_bias, + uintptr_t dynamic_addr, + uintptr_t namespace_id) +{ + const kzt_guest_object_snapshot_t *object; + ssize_t index; + int matches; + + if (!registry || !link_map_addr || !dynamic_addr || + kzt_registry_api_lock(registry) != 0) { + return -1; + } + index = kzt_find_object_index(registry, link_map_addr); + if (registry->disabled || index < 0 || + registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || + registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return -1; + } + + object = ®istry->objects[index]; + matches = + object->load_bias.status == KZT_GUEST_FIELD_OK && + object->load_bias.value == load_bias && + object->dynamic_addr.status == KZT_GUEST_FIELD_OK && + object->dynamic_addr.value == dynamic_addr && + object->namespace_id.status == KZT_GUEST_FIELD_OK && + object->namespace_id.value == namespace_id; + kzt_registry_api_unlock(registry); + return matches; +} + kzt_guest_registry_result_t kzt_guest_registry_commit_dynamic_view( kzt_guest_registry_t *registry, uintptr_t link_map_addr, @@ -1084,6 +2356,11 @@ kzt_guest_registry_result_t kzt_guest_registry_commit_dynamic_view( goto out; } + if (kzt_registry_wait_for_patch_decisions(registry) != 0) { + result = KZT_GUEST_REGISTRY_DISABLED; + goto out; + } + index = kzt_find_object_index(registry, link_map_addr); if (index < 0 || registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || @@ -1114,8 +2391,14 @@ kzt_guest_registry_result_t kzt_guest_registry_commit_dynamic_view( goto out; } + if (object->dynamic_view_revision == ULONG_MAX) { + result = KZT_GUEST_REGISTRY_ERROR; + goto out; + } + object->dynamic_view = *view; object->dynamic_view_status = status; + ++object->dynamic_view_revision; if (status == KZT_GUEST_FIELD_OK) { object->state = KZT_GUEST_OBJECT_PARSED; } else if (object->state == KZT_GUEST_OBJECT_PARSED) { @@ -1178,6 +2461,28 @@ int kzt_guest_registry_find_dynamic_view( return 0; } +int kzt_guest_registry_dynamic_view_matches( + kzt_guest_registry_t *registry, uintptr_t link_map_addr, + unsigned long generation, const kzt_guest_dynamic_view_t *view) +{ + ssize_t index; + + if (!registry || !link_map_addr || !generation || !view || + kzt_registry_api_lock(registry) != 0) { + return -1; + } + index = kzt_find_object_index(registry, link_map_addr); + if (registry->disabled || index < 0 || + registry->objects[index].generation != generation || + registry->objects[index].dynamic_view_status != KZT_GUEST_FIELD_OK || + !kzt_dynamic_view_equal(®istry->objects[index].dynamic_view, view)) { + kzt_registry_api_unlock(registry); + return -1; + } + kzt_registry_api_unlock(registry); + return 0; +} + int kzt_guest_registry_publish_lazy_resolver( kzt_guest_registry_t *registry, uintptr_t link_map_addr, @@ -1223,7 +2528,11 @@ int kzt_guest_registry_publish_lazy_resolver( object->lazy_resolver.guest_link_map == resolver->guest_link_map && object->lazy_resolver.guest_resolver == - resolver->guest_resolver; + resolver->guest_resolver && + object->lazy_resolver.object_head == + resolver->object_head && + object->lazy_resolver.registry_owned_head == + resolver->registry_owned_head; kzt_registry_api_unlock(registry); return same ? 0 : -1; } @@ -1233,6 +2542,45 @@ int kzt_guest_registry_publish_lazy_resolver( return 0; } +int kzt_guest_registry_find_lazy_source( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_registry_lazy_source_t *source) +{ + kzt_guest_object_snapshot_t *object; + ssize_t index; + + if (source) { + memset(source, 0, sizeof(*source)); + } + if (!registry || !link_map_addr || !source || + kzt_registry_api_lock(registry) != 0) { + return -1; + } + index = kzt_find_object_index(registry, link_map_addr); + if (registry->disabled || index < 0) { + kzt_registry_api_unlock(registry); + return -1; + } + object = ®istry->objects[index]; + if (!object->generation || + object->namespace_id.status != KZT_GUEST_FIELD_OK || + object->namespace_id.value != 0 || + object->state == KZT_GUEST_OBJECT_UNLOADING || + object->state == KZT_GUEST_OBJECT_DEAD || + !object->lazy_resolver.valid || + object->lazy_resolver.guest_link_map != link_map_addr || + !object->lazy_resolver.guest_resolver) { + kzt_registry_api_unlock(registry); + return -1; + } + source->generation = object->generation; + source->namespace_id = object->namespace_id.value; + source->guest_resolver = object->lazy_resolver.guest_resolver; + kzt_registry_api_unlock(registry); + return 0; +} + int kzt_guest_registry_find_lazy_resolver( kzt_guest_registry_t *registry, uintptr_t link_map_addr, @@ -1459,13 +2807,23 @@ int kzt_guest_registry_dump_text( "kzt_guest_registry diagnostics enabled=%d throttle_limit=%lu " "observations=%lu added=%lu unchanged=%lu updated=%lu " "conflicts=%lu disabled=%lu errors=%lu init_failures=%lu " - "allocation_failures=%lu objects=%lu", + "allocation_failures=%lu loader_identity_publications=%lu " + "loader_close_referenced=%lu " + "loader_close_unload_unproven=%lu " + "loader_close_retired=%lu loader_close_stale=%lu " + "loader_close_identity_missing=%lu objects=%lu", report.config.enabled, report.config.throttle_limit, report.counters.observations, report.counters.added, report.counters.unchanged, report.counters.updated, report.counters.conflicts, report.counters.disabled, report.counters.errors, report.counters.init_failures, report.counters.allocation_failures, + report.counters.loader_identity_publications, + report.counters.loader_close_referenced, + report.counters.loader_close_unload_unproven, + report.counters.loader_close_retired, + report.counters.loader_close_stale, + report.counters.loader_close_identity_missing, (unsigned long)dump.count) != 0) { goto out; } diff --git a/target/i386/latx/context/kzt_guest_registry_context.c b/target/i386/latx/context/kzt_guest_registry_context.c index ecc5b9731ad..935219cce06 100644 --- a/target/i386/latx/context/kzt_guest_registry_context.c +++ b/target/i386/latx/context/kzt_guest_registry_context.c @@ -123,25 +123,13 @@ int kzt_guest_registry_context_has_main_namespace_evidence( uintptr_t load_bias, uintptr_t dynamic_addr) { - kzt_guest_object_snapshot_t *snapshot = NULL; uintptr_t main_head = 0; - int matches = 0; if (!registry || !link_map_addr || !dynamic_addr || kzt_guest_registry_context_get_main_namespace_head( context, &main_head) != 0 || !main_head) { return 0; } - if (kzt_guest_registry_find_by_link_map( - registry, link_map_addr, &snapshot) == 0 && snapshot && - snapshot->load_bias.status == KZT_GUEST_FIELD_OK && - snapshot->load_bias.value == load_bias && - snapshot->dynamic_addr.status == KZT_GUEST_FIELD_OK && - snapshot->dynamic_addr.value == dynamic_addr && - snapshot->namespace_id.status == KZT_GUEST_FIELD_OK && - snapshot->namespace_id.value == 0) { - matches = 1; - } - kzt_guest_object_snapshot_free(snapshot); - return matches; + return kzt_guest_registry_matches_live_identity( + registry, link_map_addr, load_bias, dynamic_addr, 0) == 1; } diff --git a/target/i386/latx/context/kzt_observation_adapter.c b/target/i386/latx/context/kzt_observation_adapter.c index da9b78e9e7f..eb1a0ab598d 100644 --- a/target/i386/latx/context/kzt_observation_adapter.c +++ b/target/i386/latx/context/kzt_observation_adapter.c @@ -1,9 +1,28 @@ #include "kzt_observation_adapter.h" +#include +#include #include +#include #include "kzt_guest_library_binding.h" +static uint64_t kzt_observation_timing_now(void) +{ + struct timespec value; + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &value) != 0) { + return 0; + } + return (uint64_t)value.tv_sec * 1000000000ULL + + (uint64_t)value.tv_nsec; +} + +static uint64_t kzt_observation_timing_delta(uint64_t start, uint64_t end) +{ + return start && end >= start ? end - start : 0; +} + static kzt_observation_adapter_result_t kzt_adapter_result_from_registry( kzt_guest_registry_result_t registry_result) { @@ -103,9 +122,12 @@ static int kzt_adapter_reuse_complete_dynamic_view( kzt_guest_field_status_t status = KZT_GUEST_FIELD_NOT_PARSED; unsigned long existing_generation = 0; - if (!request || !observation || request->library_bindings || + if (!request || !observation || + (request->library_bindings && !request->reuse_complete_dynamic_view) || request->dynamic_diagnostics_force_compare || - registry_result != KZT_GUEST_REGISTRY_UNCHANGED || + (registry_result != KZT_GUEST_REGISTRY_UNCHANGED && + (!request->reuse_complete_dynamic_view || + registry_result != KZT_GUEST_REGISTRY_UPDATED)) || generation == 0 || kzt_guest_registry_find_dynamic_view( request->registry, observation->link_map_addr, &view, &status, @@ -338,8 +360,18 @@ int kzt_observe_guest_object_from_callback( .commit_result = KZT_GUEST_REGISTRY_RESULT_COUNT, }; kzt_guest_library_callback_access_t callback_access = { 0 }; + uint64_t timing_start = 0; + uint64_t timing_access = 0; + uint64_t timing_observe = 0; + uint64_t timing_legacy = 0; + uint64_t timing_supplement = 0; + uint64_t timing_done = 0; + int timing_enabled = request && request->diagnostics_enabled; int legacy_ret = 0; + if (timing_enabled) { + timing_start = kzt_observation_timing_now(); + } if (request && request->library_bindings && kzt_guest_library_callback_access_begin_scoped( request->library_bindings, request->link_map_addr, @@ -347,25 +379,98 @@ int kzt_observe_guest_object_from_callback( &callback_access) != 0) { /* Unload won the address gate. No reader, parser, diagnostic, or * legacy loader flow may touch this guest object after this point. */ - if (result) *result = KZT_OBSERVATION_ADAPTER_DISABLED; - return 0; + observation_result = KZT_OBSERVATION_ADAPTER_DISABLED; + goto out; + } + if (timing_enabled) { + timing_access = kzt_observation_timing_now(); } observation_result = kzt_observe_guest_object(request, ®istry_diagnostic, &dynamic_diagnostic); - if (result) { - *result = observation_result; + if (request && request->lazy_prebind_scope && + request->namespace_id_present && request->namespace_id == 0 && + (observation_result == KZT_OBSERVATION_ADAPTER_ADDED || + observation_result == KZT_OBSERVATION_ADAPTER_UPDATED)) { + if (request->prebind_invalidate) { + (void)request->prebind_invalidate( + KZT_LAZY_PREBIND_MUTATION_LOADER_EVENT, + request->prebind_invalidate_opaque); + } else { + (void)kzt_lazy_prebind_scope_mutate( + request->lazy_prebind_scope, + KZT_LAZY_PREBIND_MUTATION_LOADER_EVENT); + } + } + if (timing_enabled) { + timing_observe = kzt_observation_timing_now(); + } + + if (request && request->per_object_flow && + (observation_result == KZT_OBSERVATION_ADAPTER_ADDED || + observation_result == KZT_OBSERVATION_ADAPTER_UPDATED)) { + (void)request->per_object_flow(request->link_map_addr, + request->per_object_opaque); } if (request && request->legacy_flow) { + if (request->legacy_result) { + memset(request->legacy_result, 0, sizeof(*request->legacy_result)); + } legacy_ret = request->legacy_flow(request->link_map_addr, request->legacy_opaque); + if (timing_enabled) { + timing_legacy = kzt_observation_timing_now(); + } + if (request->legacy_result && + request->legacy_result->map_range_present && + request->legacy_result->map_start < + request->legacy_result->map_end && + (observation_result == KZT_OBSERVATION_ADAPTER_ADDED || + observation_result == KZT_OBSERVATION_ADAPTER_UNCHANGED || + observation_result == KZT_OBSERVATION_ADAPTER_UPDATED)) { + kzt_guest_registry_result_t range_result = + kzt_guest_registry_supplement_map_range( + request->registry, request->link_map_addr, + registry_diagnostic.generation, + request->legacy_result->map_start, + request->legacy_result->map_end, + ®istry_diagnostic); + + observation_result = + kzt_adapter_result_from_registry(range_result); + } + } + if (timing_enabled) { + if (!timing_legacy) timing_legacy = timing_observe; + timing_supplement = kzt_observation_timing_now(); } - kzt_observation_adapter_emit_diagnostic(request, observation_result, ®istry_diagnostic, &dynamic_diagnostic); kzt_guest_library_callback_access_end(&callback_access); +out: + if (result) *result = observation_result; + if (timing_enabled) { + timing_done = kzt_observation_timing_now(); + if (!timing_access) timing_access = timing_done; + if (!timing_observe) timing_observe = timing_access; + if (!timing_legacy) timing_legacy = timing_observe; + if (!timing_supplement) timing_supplement = timing_legacy; + fprintf( + stderr, + "kzt_observation_timing schema=1 link_map=0x%" PRIxPTR " " + "access_ns=%" PRIu64 " observe_ns=%" PRIu64 " " + "legacy_ns=%" PRIu64 " supplement_ns=%" PRIu64 " " + "total_ns=%" PRIu64 " result=%d\n", + request ? request->link_map_addr : 0, + kzt_observation_timing_delta(timing_start, timing_access), + kzt_observation_timing_delta(timing_access, timing_observe), + kzt_observation_timing_delta(timing_observe, timing_legacy), + kzt_observation_timing_delta(timing_legacy, timing_supplement), + kzt_observation_timing_delta(timing_start, timing_done), + observation_result); + } return legacy_ret; } diff --git a/target/i386/latx/context/kzt_owner_resolver.c b/target/i386/latx/context/kzt_owner_resolver.c index e23b45d833c..f7d0dd86be7 100644 --- a/target/i386/latx/context/kzt_owner_resolver.c +++ b/target/i386/latx/context/kzt_owner_resolver.c @@ -20,27 +20,12 @@ void kzt_owner_resolver_init(kzt_owner_resolution_t *resolution) resolution->owner_match = KZT_PATCH_OWNER_UNKNOWN; } -static int kzt_owner_field_ok(kzt_guest_field_status_t status) -{ - return status == KZT_GUEST_FIELD_OK; -} - static int kzt_owner_string_has_value(kzt_guest_field_status_t status) { return status == KZT_GUEST_FIELD_OK || status == KZT_GUEST_FIELD_TRUNCATED; } -static const char *kzt_owner_snapshot_string( - const kzt_guest_string_field_t *field) -{ - if (!field || !kzt_owner_string_has_value(field->status)) { - return NULL; - } - - return field->value; -} - static void kzt_owner_copy_text(char *dst, size_t dst_size, const char *src) { @@ -56,70 +41,43 @@ static void kzt_owner_copy_text(char *dst, size_t dst_size, snprintf(dst, dst_size, "%s", src); } -static int kzt_owner_snapshot_has_valid_range( - const kzt_guest_object_snapshot_t *snapshot) -{ - if (!snapshot) { - return 0; - } - if (!kzt_owner_field_ok(snapshot->map_start.status) || - !kzt_owner_field_ok(snapshot->map_end.status)) { - return 0; - } - - return snapshot->map_start.value < snapshot->map_end.value; -} - -static int kzt_owner_snapshot_contains( - const kzt_guest_object_snapshot_t *snapshot, - uintptr_t address) -{ - if (!kzt_owner_snapshot_has_valid_range(snapshot)) { - return 0; - } - - return address >= snapshot->map_start.value && - address < snapshot->map_end.value; -} - -static void kzt_owner_ref_from_snapshot( - const kzt_guest_object_snapshot_t *snapshot, +static void kzt_owner_ref_from_match( + const kzt_guest_registry_address_match_t *match, kzt_owner_resolver_text_t *text, kzt_patch_object_ref_t *ref) { - const char *soname; - const char *path; + const char *soname = NULL; + const char *path = NULL; memset(ref, 0, sizeof(*ref)); - if (!snapshot || !text) { + if (!match || !text) { return; } - soname = kzt_owner_snapshot_string(&snapshot->soname); - path = kzt_owner_snapshot_string(&snapshot->path); + if (kzt_owner_string_has_value(match->soname_status)) { + soname = match->soname; + } + if (kzt_owner_string_has_value(match->path_status)) { + path = match->path; + } kzt_owner_copy_text(text->soname, sizeof(text->soname), soname); kzt_owner_copy_text(text->path, sizeof(text->path), path); ref->known = 1; - ref->link_map_addr = snapshot->link_map_addr; - ref->map_start = snapshot->map_start.value; - ref->map_end = snapshot->map_end.value; - ref->generation = snapshot->generation; + ref->link_map_addr = match->link_map_addr; + ref->map_start = match->map_start; + ref->map_end = match->map_end; + ref->generation = match->generation; ref->soname = text->soname[0] ? text->soname : NULL; ref->path = text->path[0] ? text->path : NULL; } static kzt_owner_lookup_status_t kzt_owner_find_by_address( - const kzt_guest_registry_dump_t *dump, - uintptr_t address, + const kzt_guest_registry_address_match_t *match, kzt_owner_resolver_text_t *text, kzt_patch_object_ref_t *ref, size_t *match_count) { - const kzt_guest_object_snapshot_t *match = NULL; - size_t i; - size_t count = 0; - memset(ref, 0, sizeof(*ref)); if (text) { memset(text, 0, sizeof(*text)); @@ -128,36 +86,21 @@ static kzt_owner_lookup_status_t kzt_owner_find_by_address( *match_count = 0; } - if (!dump || !ref || address == 0) { + if (!match || !ref) { return KZT_OWNER_LOOKUP_NOT_FOUND; } - for (i = 0; i < dump->count; ++i) { - if (dump->objects[i].state == KZT_GUEST_OBJECT_UNLOADING || - dump->objects[i].state == KZT_GUEST_OBJECT_DEAD) { - continue; - } - if (!kzt_owner_snapshot_contains(&dump->objects[i], address)) { - continue; - } - - ++count; - if (!match) { - match = &dump->objects[i]; - } - } - if (match_count) { - *match_count = count; + *match_count = match->match_count; } - if (count == 0) { + if (match->match_count == 0) { return KZT_OWNER_LOOKUP_NOT_FOUND; } - if (count > 1) { + if (match->match_count > 1) { return KZT_OWNER_LOOKUP_AMBIGUOUS; } - kzt_owner_ref_from_snapshot(match, text, ref); + kzt_owner_ref_from_match(match, text, ref); return KZT_OWNER_LOOKUP_RESOLVED; } @@ -217,7 +160,7 @@ int kzt_owner_resolver_resolve_current( uintptr_t expected_address, kzt_owner_resolution_t *resolution) { - kzt_guest_registry_dump_t dump = { 0 }; + kzt_guest_registry_address_pair_t pair; kzt_owner_lookup_status_t current_status; kzt_owner_lookup_status_t expected_status; @@ -240,16 +183,17 @@ int kzt_owner_resolver_resolve_current( resolution->status = KZT_OWNER_RESOLVER_EXPECTED_ADDRESS_MISSING; return 0; } - if (kzt_guest_registry_dump_snapshot(registry, &dump) != 0) { + if (kzt_guest_registry_resolve_address_pair( + registry, current_address, expected_address, &pair) != 0) { resolution->status = KZT_OWNER_RESOLVER_REGISTRY_UNAVAILABLE; return 0; } current_status = kzt_owner_find_by_address( - &dump, current_address, &resolution->current_text, + &pair.current, &resolution->current_text, &resolution->current_owner, &resolution->current_match_count); expected_status = kzt_owner_find_by_address( - &dump, expected_address, &resolution->expected_text, + &pair.expected, &resolution->expected_text, &resolution->expected_owner, &resolution->expected_match_count); if (current_status != KZT_OWNER_LOOKUP_RESOLVED) { @@ -270,7 +214,6 @@ int kzt_owner_resolver_resolve_current( } out: - kzt_guest_registry_dump_free(&dump); return 0; } diff --git a/target/i386/latx/include/kzt_guest_dynamic.h b/target/i386/latx/include/kzt_guest_dynamic.h index 69d2579414a..0eff4fec03f 100644 --- a/target/i386/latx/include/kzt_guest_dynamic.h +++ b/target/i386/latx/include/kzt_guest_dynamic.h @@ -20,6 +20,7 @@ typedef enum kzt_guest_dynamic_error { KZT_GUEST_DYNAMIC_ERROR_READ_FAILURE, KZT_GUEST_DYNAMIC_ERROR_SCAN_LIMIT_EXCEEDED, KZT_GUEST_DYNAMIC_ERROR_TOO_MANY_NEEDED, + KZT_GUEST_DYNAMIC_ERROR_ADDRESS_OVERFLOW, } kzt_guest_dynamic_error_t; typedef struct kzt_guest_dynamic_parse_result { diff --git a/target/i386/latx/include/kzt_guest_link_map_reader.h b/target/i386/latx/include/kzt_guest_link_map_reader.h index cdd362569bf..1ff6c3ac4b8 100644 --- a/target/i386/latx/include/kzt_guest_link_map_reader.h +++ b/target/i386/latx/include/kzt_guest_link_map_reader.h @@ -23,6 +23,12 @@ typedef struct kzt_guest_link_map_identity { uintptr_t dynamic_addr; } kzt_guest_link_map_identity_t; +typedef struct kzt_guest_link_map_fingerprint { + uintptr_t namespace_head; + size_t link_map_count; + uint64_t value; +} kzt_guest_link_map_fingerprint_t; + int kzt_guest_link_map_read_identity( uintptr_t link_map_addr, const kzt_guest_link_map_reader_ops_t *ops, @@ -38,6 +44,26 @@ int kzt_guest_link_map_read_predecessor( const kzt_guest_link_map_reader_ops_t *ops, uintptr_t *predecessor); +int kzt_guest_link_map_read_successor( + uintptr_t link_map_addr, + const kzt_guest_link_map_reader_ops_t *ops, + uintptr_t *successor); + +/* Walk at most 256 l_next entries from a known namespace head using only the + * public link_map prefix and fixed stack storage. Returns 0 only for a + * complete, acyclic chain ending at NULL. */ +int kzt_guest_link_map_read_fingerprint( + uintptr_t namespace_head, + const kzt_guest_link_map_reader_ops_t *ops, + kzt_guest_link_map_fingerprint_t *fingerprint); + +/* Re-read a fingerprint immediately before a write. Returns 1 when the + * public chain is unchanged, 0 when it changed, and -1 when evidence is + * insufficient. */ +int kzt_guest_link_map_revalidate_fingerprint( + const kzt_guest_link_map_fingerprint_t *expected, + const kzt_guest_link_map_reader_ops_t *ops); + /* Walk the public x86_64 link_map l_prev chain to its exact head. With a * previously confirmed main head, classification is based only on head * identity. Otherwise both l_addr and l_ld must match the main executable. diff --git a/target/i386/latx/include/kzt_guest_registry.h b/target/i386/latx/include/kzt_guest_registry.h index 2b8140e6e1d..88e00b422a0 100644 --- a/target/i386/latx/include/kzt_guest_registry.h +++ b/target/i386/latx/include/kzt_guest_registry.h @@ -6,8 +6,25 @@ #include "kzt_guest_dynamic_view.h" +#define KZT_GUEST_REGISTRY_ADDRESS_TEXT_LIMIT 256 + typedef struct kzt_guest_registry kzt_guest_registry_t; +typedef struct kzt_guest_loader_identity { + uintptr_t handle; + uintptr_t link_map_addr; + unsigned long generation; + uintptr_t namespace_id; + unsigned long handle_generation; +} kzt_guest_loader_identity_t; + +typedef enum kzt_guest_loader_close_result { + KZT_GUEST_LOADER_CLOSE_REFERENCED = 0, + KZT_GUEST_LOADER_CLOSE_UNLOAD_UNPROVEN, + KZT_GUEST_LOADER_CLOSE_RETIRED, + KZT_GUEST_LOADER_CLOSE_STALE, +} kzt_guest_loader_close_result_t; + typedef struct kzt_guest_registry_source_lease { kzt_guest_registry_t *registry; uintptr_t link_map_addr; @@ -16,6 +33,18 @@ typedef struct kzt_guest_registry_source_lease { int active; } kzt_guest_registry_source_lease_t; +/* Pins the Registry evidence used by one first-bind write transaction. The + * lease is derived from an already-held exact source lease; it prevents + * Registry mutations, but never holds the Registry mutex across guest memory + * access, page permission changes, or the slot CAS. */ +typedef struct kzt_guest_registry_patch_decision_lease { + kzt_guest_registry_t *registry; + uintptr_t link_map_addr; + unsigned long generation; + uintptr_t namespace_id; + int active; +} kzt_guest_registry_patch_decision_lease_t; + typedef enum kzt_guest_object_state { KZT_GUEST_OBJECT_DISCOVERED = 0, KZT_GUEST_OBJECT_PARSED, @@ -25,6 +54,22 @@ typedef enum kzt_guest_object_state { KZT_GUEST_OBJECT_DEAD, } kzt_guest_object_state_t; +/* Per-generation state for the narrow PLTGOT resolver injection transaction. + * It is intentionally separate from the object lifecycle: an observation + * remains live while one writer is preparing its guest-memory transaction. */ +typedef enum kzt_guest_got_plt_injection_state { + KZT_GUEST_GOT_PLT_INJECTION_NONE = 0, + KZT_GUEST_GOT_PLT_INJECTION_APPLYING, + KZT_GUEST_GOT_PLT_INJECTION_APPLIED, +} kzt_guest_got_plt_injection_state_t; + +typedef enum kzt_guest_got_plt_injection_claim_result { + KZT_GUEST_GOT_PLT_INJECTION_GRANTED = 0, + KZT_GUEST_GOT_PLT_INJECTION_IN_PROGRESS, + KZT_GUEST_GOT_PLT_INJECTION_ALREADY_APPLIED, + KZT_GUEST_GOT_PLT_INJECTION_FAIL_OPEN, +} kzt_guest_got_plt_injection_claim_result_t; + typedef enum kzt_guest_field_status { KZT_GUEST_FIELD_OK = 0, KZT_GUEST_FIELD_UNKNOWN, @@ -58,9 +103,17 @@ typedef struct kzt_guest_lazy_resolver { uintptr_t resolver_slot; uintptr_t guest_link_map; uintptr_t guest_resolver; + uintptr_t object_head; + int registry_owned_head; int valid; } kzt_guest_lazy_resolver_t; +typedef struct kzt_guest_registry_lazy_source { + unsigned long generation; + uintptr_t namespace_id; + uintptr_t guest_resolver; +} kzt_guest_registry_lazy_source_t; + typedef struct kzt_guest_object_observation { uintptr_t link_map_addr; kzt_guest_scalar_field_t load_bias; @@ -84,12 +137,36 @@ typedef struct kzt_guest_object_snapshot { kzt_guest_string_field_t soname; kzt_guest_field_status_t dynamic_view_status; kzt_guest_dynamic_view_t dynamic_view; + unsigned long dynamic_view_revision; kzt_guest_lazy_resolver_t lazy_resolver; + kzt_guest_got_plt_injection_state_t got_plt_injection_state; kzt_guest_object_state_t state; + kzt_guest_object_state_t unload_previous_state; unsigned long generation; unsigned long active_source_leases; } kzt_guest_object_snapshot_t; +/* A compact, caller-owned result for an address-range lookup. It is copied + * under the Registry mutex, so no Registry-owned pointer escapes the lock. */ +typedef struct kzt_guest_registry_address_match { + uintptr_t link_map_addr; + uintptr_t map_start; + uintptr_t map_end; + uintptr_t namespace_id; + unsigned long generation; + kzt_guest_field_status_t soname_status; + kzt_guest_field_status_t path_status; + kzt_guest_field_status_t namespace_id_status; + char soname[KZT_GUEST_REGISTRY_ADDRESS_TEXT_LIMIT]; + char path[KZT_GUEST_REGISTRY_ADDRESS_TEXT_LIMIT]; + size_t match_count; +} kzt_guest_registry_address_match_t; + +typedef struct kzt_guest_registry_address_pair { + kzt_guest_registry_address_match_t current; + kzt_guest_registry_address_match_t expected; +} kzt_guest_registry_address_pair_t; + typedef struct kzt_guest_registry_dump { kzt_guest_object_snapshot_t *objects; size_t count; @@ -105,6 +182,12 @@ typedef struct kzt_guest_registry_diagnostics { unsigned long errors; unsigned long init_failures; unsigned long allocation_failures; + unsigned long loader_identity_publications; + unsigned long loader_close_referenced; + unsigned long loader_close_unload_unproven; + unsigned long loader_close_retired; + unsigned long loader_close_stale; + unsigned long loader_close_identity_missing; } kzt_guest_registry_diagnostics_t; typedef struct kzt_guest_registry_diagnostic_config { @@ -157,6 +240,8 @@ void kzt_guest_registry_test_set_before_retire_wait( kzt_guest_registry_test_hook_fn hook, void *opaque); void kzt_guest_registry_test_set_after_retire_wake( kzt_guest_registry_test_hook_fn hook, void *opaque); +void kzt_guest_registry_test_set_before_patch_decision_wait( + kzt_guest_registry_test_hook_fn hook, void *opaque); void kzt_guest_registry_test_set_after_destroy_disable( kzt_guest_registry_test_hook_fn hook, void *opaque); #endif @@ -170,14 +255,114 @@ kzt_guest_registry_result_t kzt_guest_registry_observe_with_diagnostic( const kzt_guest_object_observation_t *observation, kzt_guest_registry_observation_diagnostic_t *diagnostic); +/* Completes the range evidence for one exact live generation without reading + * guest memory again. Existing reliable values are confirmed, never + * overwritten; a disagreement or stale generation fails open as CONFLICT. */ +kzt_guest_registry_result_t kzt_guest_registry_supplement_map_range( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + uintptr_t map_start, + uintptr_t map_end, + kzt_guest_registry_observation_diagnostic_t *diagnostic); + +/* Publishes an exact LMID learned from guest dlinfo/r_debug namespace + * membership for one already-observed live generation. */ +kzt_guest_registry_result_t kzt_guest_registry_supplement_namespace( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + uintptr_t namespace_id); + int kzt_guest_registry_find_by_link_map( kzt_guest_registry_t *registry, uintptr_t link_map_addr, kzt_guest_object_snapshot_t **snapshot); +/* Resolves two guest addresses from one coherent Registry lock hold without + * allocating a full dump. Each match_count is zero, one, or greater than one + * for not-found, unique, or ambiguous evidence respectively. */ +int kzt_guest_registry_resolve_address_pair( + kzt_guest_registry_t *registry, + uintptr_t current_address, + uintptr_t expected_address, + kzt_guest_registry_address_pair_t *pair); + +/* Copies one live object's identity evidence without a heap snapshot. */ +int kzt_guest_registry_find_live_object( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_registry_address_match_t *match); + +/* Binds one successful guest loader handle to the exact LINKMAP/LMID returned + * by guest dlinfo. Repeated opens of the same exact object are reference + * counted; conflicting live identities fail open. */ +int kzt_guest_registry_publish_loader_identity( + kzt_guest_registry_t *registry, + uintptr_t handle, + uintptr_t link_map_addr, + uintptr_t namespace_id, + kzt_guest_loader_identity_t *identity); +int kzt_guest_registry_find_loader_identity( + kzt_guest_registry_t *registry, + uintptr_t handle, + kzt_guest_loader_identity_t *identity); +/* Reuses an exact live handle binding without another guest dlinfo round trip. + * Inactive bindings additionally require an exact RTLD_NODELETE resident + * proof; a generic unload-unproven result is not sufficient. */ +int kzt_guest_registry_reuse_loader_identity( + kzt_guest_registry_t *registry, + uintptr_t handle, + kzt_guest_loader_identity_t *identity); +int kzt_guest_registry_mark_loader_resident( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity); +int kzt_guest_registry_loader_symbol_source_acquire( + kzt_guest_registry_t *registry, uintptr_t handle, + kzt_guest_loader_identity_t *identity, + kzt_guest_dynamic_view_t *dynamic_view, + kzt_guest_field_status_t *dynamic_status, + unsigned long *dynamic_revision, + kzt_guest_registry_source_lease_t *lease); +/* Resolves a loader object while it is LIVE or pre-unmap UNLOADING. */ +int kzt_guest_registry_find_loader_object_identity( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_loader_identity_t *identity); +kzt_guest_loader_close_result_t +kzt_guest_registry_complete_loader_close( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity); +void kzt_guest_registry_note_loader_close_identity_missing( + kzt_guest_registry_t *registry); + +/* Checks the exact live object identity under one Registry lock without + * allocating or copying unrelated object metadata. Returns 1 for an exact + * match, 0 for a mismatch, and -1 when the Registry cannot answer. */ +int kzt_guest_registry_matches_live_identity( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + uintptr_t load_bias, + uintptr_t dynamic_addr, + uintptr_t namespace_id); + int kzt_guest_registry_retire(kzt_guest_registry_t *registry, uintptr_t link_map_addr, unsigned long generation); +int kzt_guest_registry_retire_loader_identity( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity); +/* RT_DELETE closes lease admission before guest unmap. RT_CONSISTENT then + * either cancels unchanged objects or finishes only identities proven absent. */ +int kzt_guest_registry_begin_loader_unload( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity); +int kzt_guest_registry_cancel_loader_unload( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity); +int kzt_guest_registry_finish_loader_unload( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity); /* Defensive handoff for a violated single-retire protocol: wait for the * already-started exact generation to become DEAD instead of allowing its * binding owner to return before source leases drain. */ @@ -200,6 +385,38 @@ int kzt_guest_registry_source_lease_acquire( void kzt_guest_registry_source_lease_release( kzt_guest_registry_source_lease_t *lease); +/* The source lease must remain active until this lease is released. */ +int kzt_guest_registry_patch_decision_lease_acquire( + const kzt_guest_registry_source_lease_t *source_lease, + kzt_guest_registry_patch_decision_lease_t *lease); +void kzt_guest_registry_patch_decision_lease_release( + kzt_guest_registry_patch_decision_lease_t *lease); + +/* Claims the one PLTGOT injection transaction for the exact generation + * protected by an active patch-decision lease. The supplied Dynamic View + * must be the Registry's complete, matching view; otherwise this returns + * FAIL_OPEN without changing state. No guest memory is accessed here. */ +kzt_guest_got_plt_injection_claim_result_t +kzt_guest_registry_got_plt_injection_claim( + const kzt_guest_registry_patch_decision_lease_t *lease, + const kzt_guest_dynamic_view_t *view); + +/* Completes a previously granted claim while the same decision lease remains + * active. A failed writer returns the generation to NONE so the legacy path + * or a later observation can retry safely. */ +int kzt_guest_registry_got_plt_injection_finish( + const kzt_guest_registry_patch_decision_lease_t *lease, + int applied); + +/* Returns 1 for APPLYING/APPLIED, 0 for NONE, and -1 for stale, dead, or + * unsupported identity evidence. Legacy code uses this to avoid a duplicate + * resolver-slot write while the new transaction owns the generation. */ +int kzt_guest_registry_got_plt_injection_claimed( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + unsigned long generation, + uintptr_t namespace_id); + kzt_guest_registry_result_t kzt_guest_registry_commit_dynamic_view( kzt_guest_registry_t *registry, uintptr_t link_map_addr, @@ -213,6 +430,10 @@ int kzt_guest_registry_find_dynamic_view( kzt_guest_field_status_t *status, unsigned long *generation); +int kzt_guest_registry_dynamic_view_matches( + kzt_guest_registry_t *registry, uintptr_t link_map_addr, + unsigned long generation, const kzt_guest_dynamic_view_t *view); + /* Resolver metadata is published only while the object is not callable. * The generation and namespace checks make stale/non-main observations fail * open without changing the guest resolver slots. */ @@ -223,6 +444,13 @@ int kzt_guest_registry_publish_lazy_resolver( uintptr_t namespace_id, const kzt_guest_lazy_resolver_t *resolver); +/* Copies the generation, namespace, and resolver needed by one PLT entry + * under a single Registry lock without allocating a full object snapshot. */ +int kzt_guest_registry_find_lazy_source( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_registry_lazy_source_t *source); + int kzt_guest_registry_find_lazy_resolver( kzt_guest_registry_t *registry, uintptr_t link_map_addr, diff --git a/target/i386/latx/include/kzt_observation_adapter.h b/target/i386/latx/include/kzt_observation_adapter.h index 914448bdb0a..1403a15efdc 100644 --- a/target/i386/latx/include/kzt_observation_adapter.h +++ b/target/i386/latx/include/kzt_observation_adapter.h @@ -8,6 +8,7 @@ #include "kzt_guest_dynamic.h" #include "kzt_guest_dynamic_diagnostics.h" #include "kzt_guest_registry.h" +#include "kzt_lazy_prebind_scope.h" #include "kzt_loader_callback_scope.h" typedef struct kzt_guest_library_bindings kzt_guest_library_bindings_t; @@ -25,6 +26,20 @@ typedef enum kzt_observation_adapter_result { typedef int (*kzt_observation_legacy_flow_fn)(uintptr_t link_map_addr, void *opaque); +typedef int (*kzt_observation_per_object_flow_fn)(uintptr_t link_map_addr, + void *opaque); + +typedef int (*kzt_observation_prebind_invalidate_fn)( + kzt_lazy_prebind_mutation_t mutation, void *opaque); + +/* Optional evidence produced by the legacy flow while the callback gate is + * still held. The adapter accepts it only when both bounds are valid. */ +typedef struct kzt_observation_legacy_result { + int map_range_present; + uintptr_t map_start; + uintptr_t map_end; +} kzt_observation_legacy_result_t; + typedef struct kzt_observation_adapter_dynamic_diagnostic { int attempted; int cache_hit; @@ -58,9 +73,11 @@ typedef struct kzt_observation_adapter_request { int enabled; int diagnostics_enabled; int dynamic_diagnostics_force_compare; + int reuse_complete_dynamic_view; uintptr_t link_map_addr; kzt_guest_registry_t *registry; kzt_guest_library_bindings_t *library_bindings; + kzt_lazy_prebind_scope_t *lazy_prebind_scope; const kzt_guest_library_loader_scope_t *loader_scope; const kzt_guest_link_map_reader_ops_t *reader_ops; /* Caller-verified evidence. Invalid or absent hints are ignored. */ @@ -69,8 +86,13 @@ typedef struct kzt_observation_adapter_request { int map_range_present; uintptr_t map_start; uintptr_t map_end; + kzt_observation_prebind_invalidate_fn prebind_invalidate; + void *prebind_invalidate_opaque; + kzt_observation_per_object_flow_fn per_object_flow; + void *per_object_opaque; kzt_observation_legacy_flow_fn legacy_flow; void *legacy_opaque; + kzt_observation_legacy_result_t *legacy_result; kzt_observation_adapter_diagnostic_fn diagnostic; void *diagnostic_opaque; } kzt_observation_adapter_request_t; diff --git a/tests/unit/kzt/test_guest_dynamic_parser.c b/tests/unit/kzt/test_guest_dynamic_parser.c index 816219ddb2c..c0ca4ceb0ad 100644 --- a/tests/unit/kzt/test_guest_dynamic_parser.c +++ b/tests/unit/kzt/test_guest_dynamic_parser.c @@ -115,9 +115,9 @@ static void test_complete_runtime_dynamic_view(void) { .d_tag = DT_HASH, .d_un.d_ptr = 0x7000030000 }, { .d_tag = DT_GNU_HASH, .d_un.d_ptr = 0x7000040000 }, { .d_tag = DT_VERSYM, .d_un.d_ptr = 0x7000050000 }, - { .d_tag = DT_VERNEED, .d_un.d_ptr = 0x7000060000 }, + { .d_tag = DT_VERNEED, .d_un.d_ptr = 0x6fffc60000 }, { .d_tag = DT_VERNEEDNUM, .d_un.d_val = 2 }, - { .d_tag = DT_VERDEF, .d_un.d_ptr = 0x7000070000 }, + { .d_tag = DT_VERDEF, .d_un.d_ptr = 0x6fffc70000 }, { .d_tag = DT_VERDEFNUM, .d_un.d_val = 1 }, { .d_tag = DT_RELA, .d_un.d_ptr = 0x7000080000 }, { .d_tag = DT_RELASZ, .d_un.d_val = 0x60 }, @@ -250,6 +250,75 @@ static void test_dynamic_address_semantics(void) kzt_guest_dynamic_parse_result_clear(&result); } +static void test_version_tables_are_load_bias_relative_only(void) +{ + const uintptr_t load_bias = 0x71000000; + Elf64_Dyn dynamic[] = { + { .d_tag = DT_SYMTAB, .d_un.d_ptr = 0x71001000 }, + { .d_tag = DT_STRTAB, .d_un.d_ptr = 0x71002000 }, + { .d_tag = DT_JMPREL, .d_un.d_ptr = 0x71003000 }, + { .d_tag = DT_VERSYM, .d_un.d_ptr = 0x71004000 }, + { .d_tag = DT_VERNEED, .d_un.d_ptr = 0x2b0 }, + { .d_tag = DT_VERDEF, .d_un.d_ptr = 0x390 }, + { .d_tag = DT_NULL, .d_un.d_val = 0 }, + }; + fake_dynamic_memory_t memory = { + .base = (uintptr_t)dynamic, + .size = sizeof(dynamic), + }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_parse_result_t result = { 0 }; + + check_int("version-relative.parse", + kzt_guest_dynamic_parse((uintptr_t)dynamic, load_bias, + &ops, &result), + 0); + check_int("version-relative.status", result.status, + KZT_GUEST_DYNAMIC_COMPLETE); + check_field("version-relative.symtab", &result.view.symtab, 0x71001000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("version-relative.strtab", &result.view.strtab, 0x71002000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("version-relative.jmprel", &result.view.jmprel, 0x71003000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("version-relative.versym", &result.view.versym, 0x71004000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("version-relative.verneed", &result.view.verneed, + load_bias + 0x2b0, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("version-relative.verdef", &result.view.verdef, + load_bias + 0x390, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + + kzt_guest_dynamic_parse_result_clear(&result); +} + +static void test_version_table_load_bias_overflow_is_fail_open(void) +{ + Elf64_Dyn dynamic[] = { + { .d_tag = DT_VERNEED, .d_un.d_ptr = 1 }, + { .d_tag = DT_NULL, .d_un.d_val = 0 }, + }; + fake_dynamic_memory_t memory = { + .base = (uintptr_t)dynamic, + .size = sizeof(dynamic), + }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_parse_result_t result = { 0 }; + + check_int("version-overflow.parse", + kzt_guest_dynamic_parse((uintptr_t)dynamic, UINTPTR_MAX, + &ops, &result), + 0); + check_int("version-overflow.status", result.status, + KZT_GUEST_DYNAMIC_ERROR); + check_int("version-overflow.view-status", result.view.status, + KZT_GUEST_DYNAMIC_ERROR); + check_int("version-overflow.error", result.error, + KZT_GUEST_DYNAMIC_ERROR_ADDRESS_OVERFLOW); + check_true("version-overflow.no-verneed", !result.view.verneed.present); + + kzt_guest_dynamic_parse_result_clear(&result); +} + static void test_read_failure_reports_parser_state(void) { Elf64_Dyn dynamic[] = { @@ -424,6 +493,14 @@ int main(int argc, char **argv) if (test_matches_filter("dynamic_address_semantics", argc, argv)) { test_dynamic_address_semantics(); } + if (test_matches_filter("version_tables_are_load_bias_relative_only", + argc, argv)) { + test_version_tables_are_load_bias_relative_only(); + } + if (test_matches_filter("version_table_load_bias_overflow_is_fail_open", + argc, argv)) { + test_version_table_load_bias_overflow_is_fail_open(); + } if (test_matches_filter("read_failure_reports_parser_state", argc, argv)) { test_read_failure_reports_parser_state(); diff --git a/tests/unit/kzt/test_guest_link_map_reader.c b/tests/unit/kzt/test_guest_link_map_reader.c index da4fd5f716f..f522f666c71 100644 --- a/tests/unit/kzt/test_guest_link_map_reader.c +++ b/tests/unit/kzt/test_guest_link_map_reader.c @@ -327,9 +327,9 @@ static void test_name_snapshot_status_matrix(void) 4, &name), 0); - check_int("name.truncated.status", name.status, - KZT_GUEST_FIELD_TRUNCATED); - check_string("name.truncated.value", name.value, "abcd"); + check_int("name.over-limit.status", name.status, + KZT_GUEST_FIELD_UNKNOWN); + check_string("name.over-limit.value", name.value, NULL); kzt_guest_link_map_string_clear(&name); check_int("name.read-error", @@ -506,6 +506,221 @@ static void test_predecessor_is_read_from_public_prefix(void) check_uintptr("predecessor.failure-clears", predecessor, 0); } +static void test_successor_is_read_from_public_prefix(void) +{ + test_guest_link_map_t maps[2] = { 0 }; + fake_read_failure_t failure = { + .addr = (uintptr_t)&maps[0] + + offsetof(test_guest_link_map_t, l_next), + .size = sizeof(maps[0].l_next), + }; + fake_reader_memory_t memory = fake_memory_for(maps, sizeof(maps), NULL, 0); + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + uintptr_t successor = 0; + + maps[0].l_next = (uintptr_t)&maps[1]; + check_int("successor.read", + kzt_guest_link_map_read_successor( + (uintptr_t)&maps[0], &ops, &successor), + 0); + check_uintptr("successor.value", successor, (uintptr_t)&maps[1]); + + memory.failures = &failure; + memory.failure_count = 1; + successor = 1; + check_int("successor.read-failure", + kzt_guest_link_map_read_successor( + (uintptr_t)&maps[0], &ops, &successor), + -1); + check_uintptr("successor.failure-clears", successor, 0); +} + +static void test_fingerprint_is_stable_and_covers_public_chain_identity(void) +{ + test_guest_link_map_t maps[3] = { 0 }; + test_guest_link_map_t copies[3] = { 0 }; + fake_reader_memory_t memory = fake_memory_for(maps, sizeof(maps), NULL, 0); + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_link_map_fingerprint_t initial = { 0 }; + kzt_guest_link_map_fingerprint_t repeated = { 0 }; + kzt_guest_link_map_fingerprint_t changed = { 0 }; + uint64_t initial_value; + size_t i; + + for (i = 0; i < 3; ++i) { + maps[i].l_addr = 0x100000 + i * 0x10000; + maps[i].l_ld = 0x101000 + i * 0x10000; + if (i + 1 < 3) { + maps[i].l_next = (uintptr_t)&maps[i + 1]; + } + } + + check_int("fingerprint.read", + kzt_guest_link_map_read_fingerprint( + (uintptr_t)&maps[0], &ops, &initial), + 0); + check_uintptr("fingerprint.head", initial.namespace_head, + (uintptr_t)&maps[0]); + check_uintptr("fingerprint.count", initial.link_map_count, 3); + check_true("fingerprint.nonzero", initial.value != 0); + check_int("fingerprint.repeat", + kzt_guest_link_map_read_fingerprint( + (uintptr_t)&maps[0], &ops, &repeated), + 0); + check_true("fingerprint.stable", + repeated.value == initial.value && + repeated.link_map_count == initial.link_map_count); + initial_value = initial.value; + + maps[1].l_addr += 0x1000; + check_int("fingerprint.changed-load-bias", + kzt_guest_link_map_read_fingerprint( + (uintptr_t)&maps[0], &ops, &changed), + 0); + check_true("fingerprint.mixes-load-bias", + changed.value != initial_value); + maps[1].l_addr -= 0x1000; + + maps[1].l_ld += 0x1000; + check_int("fingerprint.changed-dynamic", + kzt_guest_link_map_read_fingerprint( + (uintptr_t)&maps[0], &ops, &changed), + 0); + check_true("fingerprint.mixes-dynamic", + changed.value != initial_value); + maps[1].l_ld -= 0x1000; + + maps[0].l_next = (uintptr_t)&maps[2]; + maps[2].l_next = (uintptr_t)&maps[1]; + maps[1].l_next = 0; + check_int("fingerprint.changed-order", + kzt_guest_link_map_read_fingerprint( + (uintptr_t)&maps[0], &ops, &changed), + 0); + check_true("fingerprint.mixes-order", changed.value != initial_value); + + for (i = 0; i < 3; ++i) { + copies[i].l_addr = 0x100000 + i * 0x10000; + copies[i].l_ld = 0x101000 + i * 0x10000; + if (i + 1 < 3) { + copies[i].l_next = (uintptr_t)&copies[i + 1]; + } + } + memory = fake_memory_for(copies, sizeof(copies), NULL, 0); + ops = fake_ops(&memory); + check_int("fingerprint.changed-link-map-address", + kzt_guest_link_map_read_fingerprint( + (uintptr_t)&copies[0], &ops, &changed), + 0); + check_true("fingerprint.mixes-link-map-address", + changed.value != initial_value); +} + +static void test_fingerprint_requires_complete_bounded_acyclic_chain(void) +{ + test_guest_link_map_t maps[257] = { 0 }; + fake_read_failure_t failure = { + .addr = (uintptr_t)&maps[1] + + offsetof(test_guest_link_map_t, l_ld), + .size = sizeof(maps[1].l_ld), + }; + fake_reader_memory_t memory = fake_memory_for(maps, sizeof(maps), NULL, 0); + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_link_map_fingerprint_t fingerprint = { 0 }; + size_t i; + + maps[0].l_next = (uintptr_t)&maps[1]; + maps[1].l_next = (uintptr_t)&maps[0]; + check_int("fingerprint.cycle", + kzt_guest_link_map_read_fingerprint( + (uintptr_t)&maps[0], &ops, &fingerprint), + -1); + check_uintptr("fingerprint.cycle-clears-head", + fingerprint.namespace_head, 0); + check_uintptr("fingerprint.cycle-clears-count", + fingerprint.link_map_count, 0); + check_true("fingerprint.cycle-clears-value", fingerprint.value == 0); + + maps[1].l_next = 0; + memory.failures = &failure; + memory.failure_count = 1; + check_int("fingerprint.read-failure", + kzt_guest_link_map_read_fingerprint( + (uintptr_t)&maps[0], &ops, &fingerprint), + -1); + check_uintptr("fingerprint.read-failure-clears-count", + fingerprint.link_map_count, 0); + + memory.failures = NULL; + memory.failure_count = 0; + for (i = 0; i < 257; ++i) { + maps[i].l_addr = 0x100000 + i * 0x1000; + maps[i].l_ld = 0x101000 + i * 0x1000; + maps[i].l_next = i + 1 < 257 ? (uintptr_t)&maps[i + 1] : 0; + } + check_int("fingerprint.unterminated-at-limit", + kzt_guest_link_map_read_fingerprint( + (uintptr_t)&maps[0], &ops, &fingerprint), + -1); + check_uintptr("fingerprint.limit-clears-count", + fingerprint.link_map_count, 0); + + maps[255].l_next = 0; + check_int("fingerprint.exact-limit", + kzt_guest_link_map_read_fingerprint( + (uintptr_t)&maps[0], &ops, &fingerprint), + 0); + check_uintptr("fingerprint.exact-limit-count", + fingerprint.link_map_count, 256); +} + +static void test_fingerprint_revalidation_distinguishes_change_from_unknown(void) +{ + test_guest_link_map_t maps[2] = { 0 }; + fake_read_failure_t failure = { + .addr = (uintptr_t)&maps[1] + + offsetof(test_guest_link_map_t, l_next), + .size = sizeof(maps[1].l_next), + }; + fake_reader_memory_t memory = fake_memory_for(maps, sizeof(maps), NULL, 0); + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_link_map_fingerprint_t fingerprint = { 0 }; + + maps[0].l_addr = 0x100000; + maps[0].l_ld = 0x101000; + maps[0].l_next = (uintptr_t)&maps[1]; + maps[1].l_addr = 0x200000; + maps[1].l_ld = 0x201000; + + check_int("revalidate.snapshot", + kzt_guest_link_map_read_fingerprint( + (uintptr_t)&maps[0], &ops, &fingerprint), + 0); + check_int("revalidate.unchanged", + kzt_guest_link_map_revalidate_fingerprint( + &fingerprint, &ops), + 1); + + maps[1].l_ld += 0x1000; + check_int("revalidate.changed", + kzt_guest_link_map_revalidate_fingerprint( + &fingerprint, &ops), + 0); + maps[1].l_ld -= 0x1000; + + memory.failures = &failure; + memory.failure_count = 1; + check_int("revalidate.unknown", + kzt_guest_link_map_revalidate_fingerprint( + &fingerprint, &ops), + -1); + + check_int("revalidate.invalid-fingerprint", + kzt_guest_link_map_revalidate_fingerprint( + &(kzt_guest_link_map_fingerprint_t) { 0 }, &ops), + -1); +} + static void test_main_namespace_walk_fails_open_on_bad_chain(void) { test_guest_link_map_t maps[2] = { 0 }; @@ -578,6 +793,10 @@ int main(void) test_cached_main_head_uses_identity_not_load_bias(); test_link_map_identity_rejects_wrong_dynamic_address(); test_predecessor_is_read_from_public_prefix(); + test_successor_is_read_from_public_prefix(); + test_fingerprint_is_stable_and_covers_public_chain_identity(); + test_fingerprint_requires_complete_bounded_acyclic_chain(); + test_fingerprint_revalidation_distinguishes_change_from_unknown(); test_main_namespace_walk_fails_open_on_bad_chain(); test_main_namespace_walk_is_bounded(); diff --git a/tests/unit/kzt/test_guest_registry.c b/tests/unit/kzt/test_guest_registry.c index e5e273cf8d4..10d469b58fe 100644 --- a/tests/unit/kzt/test_guest_registry.c +++ b/tests/unit/kzt/test_guest_registry.c @@ -351,10 +351,10 @@ static void test_partial_observation_and_invalid_identity(void) KZT_GUEST_FIELD_READ_ERROR); check_int("snapshot.dynamic_addr.unknown", snapshot->dynamic_addr.status, KZT_GUEST_FIELD_UNKNOWN); - check_int("snapshot.path.truncated", snapshot->path.status, - KZT_GUEST_FIELD_TRUNCATED); - check_string("snapshot.path.truncated-value", snapshot->path.value, - "/guest/path-truncated"); + check_int("snapshot.path.over-limit-unknown", snapshot->path.status, + KZT_GUEST_FIELD_UNKNOWN); + check_string("snapshot.path.over-limit-no-prefix", snapshot->path.value, + NULL); kzt_guest_object_snapshot_free(snapshot); kzt_guest_registry_destroy(®istry); @@ -542,6 +542,12 @@ static void test_diagnostics_are_opt_in_and_throttled(void) check_true("dump.text-called", sink.calls > 0); check_contains("dump.text-summary", sink.text, "enabled=1 throttle_limit=2"); + check_contains("dump.text-loader-lifecycle", sink.text, + "loader_identity_publications=0 " + "loader_close_referenced=0 " + "loader_close_unload_unproven=0 " + "loader_close_retired=0 loader_close_stale=0 " + "loader_close_identity_missing=0"); check_contains("dump.text-event", sink.text, "result=unchanged observed=3 emitted=2 suppressed=1"); check_contains("dump.text-object", sink.text, @@ -1182,6 +1188,7 @@ static void test_lazy_resolver_publish_find_lifecycle(void) .guest_resolver = 0x7300, }; kzt_guest_lazy_resolver_t found; + kzt_guest_registry_lazy_source_t source; unsigned long generation; observation.namespace_id = (kzt_guest_scalar_field_t) { @@ -1204,6 +1211,16 @@ static void test_lazy_resolver_publish_find_lifecycle(void) resolver.guest_link_map); check_uintptr("lazy.find-resolver", found.guest_resolver, resolver.guest_resolver); + memset(&source, 0xa5, sizeof(source)); + kzt_guest_registry_test_set_alloc_failure_after(0); + check_int("lazy.find-source-no-allocation", + kzt_guest_registry_find_lazy_source( + registry, observation.link_map_addr, &source), 0); + kzt_guest_registry_test_set_alloc_failure_after(-1); + check_ulong("lazy.find-source-generation", source.generation, generation); + check_uintptr("lazy.find-source-namespace", source.namespace_id, 0); + check_uintptr("lazy.find-source-resolver", source.guest_resolver, + resolver.guest_resolver); resolver.guest_link_map = 0x7201; check_not_int("lazy.reject-link-map-mismatch", @@ -1229,6 +1246,494 @@ static void test_lazy_resolver_publish_find_lifecycle(void) kzt_guest_registry_find_lazy_resolver( registry, observation.link_map_addr, generation, 0, &found), 0); + memset(&source, 0xa5, sizeof(source)); + check_not_int("lazy.retired-source-invalid", + kzt_guest_registry_find_lazy_source( + registry, observation.link_map_addr, &source), 0); + check_ulong("lazy.retired-source-zero-generation", source.generation, 0); + check_uintptr("lazy.retired-source-zero-namespace", source.namespace_id, 0); + check_uintptr("lazy.retired-source-zero-resolver", source.guest_resolver, 0); + kzt_guest_registry_destroy(®istry); +} + +static void test_map_range_supplement_is_exact_and_allocation_free(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x7300); + kzt_guest_object_snapshot_t *snapshot; + kzt_guest_registry_observation_diagnostic_t diagnostic = { 0 }; + unsigned long generation; + + check_true("range.registry", registry != NULL); + if (!registry) { + return; + } + check_int("range.observe", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + snapshot = find_snapshot(registry, observation.link_map_addr); + generation = snapshot->generation; + kzt_guest_object_snapshot_free(snapshot); + + kzt_guest_registry_test_set_alloc_failure_after(0); + check_int("range.supplement-no-allocation", + kzt_guest_registry_supplement_map_range( + registry, observation.link_map_addr, generation, + 0x100000, 0x120000, &diagnostic), + KZT_GUEST_REGISTRY_UPDATED); + kzt_guest_registry_test_set_alloc_failure_after(-1); + check_ulong("range.diagnostic-generation", diagnostic.generation, + generation); + + snapshot = find_snapshot(registry, observation.link_map_addr); + check_ulong("range.generation-stable", snapshot->generation, generation); + check_int("range.start-status", snapshot->map_start.status, + KZT_GUEST_FIELD_OK); + check_uintptr("range.start", snapshot->map_start.value, 0x100000); + check_int("range.end-status", snapshot->map_end.status, + KZT_GUEST_FIELD_OK); + check_uintptr("range.end", snapshot->map_end.value, 0x120000); + kzt_guest_object_snapshot_free(snapshot); + + check_int("range.repeat", + kzt_guest_registry_supplement_map_range( + registry, observation.link_map_addr, generation, + 0x100000, 0x120000, NULL), + KZT_GUEST_REGISTRY_UNCHANGED); + check_int("range.conflict", + kzt_guest_registry_supplement_map_range( + registry, observation.link_map_addr, generation, + 0x101000, 0x120000, NULL), + KZT_GUEST_REGISTRY_CONFLICT); + check_int("range.stale-generation", + kzt_guest_registry_supplement_map_range( + registry, observation.link_map_addr, generation + 1, + 0x100000, 0x120000, NULL), + KZT_GUEST_REGISTRY_CONFLICT); + check_int("range.retire", kzt_guest_registry_retire( + registry, observation.link_map_addr, generation), 0); + check_int("range.dead-generation", + kzt_guest_registry_supplement_map_range( + registry, observation.link_map_addr, generation, + 0x100000, 0x120000, NULL), + KZT_GUEST_REGISTRY_CONFLICT); + kzt_guest_registry_destroy(®istry); +} + +static void test_loader_handle_binds_exact_link_map_generation_and_namespace(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x7400); + kzt_guest_loader_identity_t published = { 0 }; + kzt_guest_loader_identity_t found = { 0 }; + kzt_guest_object_snapshot_t *snapshot; + + check_true("loader-identity.registry", registry != NULL); + if (!registry) { + return; + } + check_int("loader-identity.observe", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + check_int("loader-identity.publish", + kzt_guest_registry_publish_loader_identity( + registry, 0x9100, observation.link_map_addr, 7, + &published), + 0); + check_uintptr("loader-identity.published-handle", published.handle, + 0x9100); + check_uintptr("loader-identity.published-link-map", + published.link_map_addr, observation.link_map_addr); + check_ulong("loader-identity.published-generation", + published.generation, 1); + check_uintptr("loader-identity.published-namespace", + published.namespace_id, 7); + check_true("loader-identity.handle-generation", + published.handle_generation != 0); + check_int("loader-identity.mark-resident", + kzt_guest_registry_mark_loader_resident( + registry, &published), + 0); + + check_int("loader-identity.find", + kzt_guest_registry_find_loader_identity( + registry, 0x9100, &found), + 0); + check_uintptr("loader-identity.found-link-map", found.link_map_addr, + observation.link_map_addr); + check_ulong("loader-identity.found-generation", found.generation, 1); + check_uintptr("loader-identity.found-namespace", found.namespace_id, 7); + + check_int("loader-identity.reuse-live-handle", + kzt_guest_registry_reuse_loader_identity( + registry, 0x9100, &published), + 0); + check_int("loader-identity.first-close-keeps-reference", + kzt_guest_registry_complete_loader_close( + registry, &published), + KZT_GUEST_LOADER_CLOSE_REFERENCED); + check_int("loader-identity.still-findable", + kzt_guest_registry_find_loader_identity( + registry, 0x9100, &found), + 0); + check_int("loader-identity.last-close-unproven", + kzt_guest_registry_complete_loader_close( + registry, &published), + KZT_GUEST_LOADER_CLOSE_UNLOAD_UNPROVEN); + check_not_int("loader-identity.closed-handle-not-findable", + kzt_guest_registry_find_loader_identity( + registry, 0x9100, &found), + 0); + check_int("loader-identity.exact-resident-handle-rebound", + kzt_guest_registry_reuse_loader_identity( + registry, 0x9100, &found), + 0); + check_true("loader-identity.rebind-new-handle-generation", + found.handle_generation != published.handle_generation); + check_int("loader-identity.old-close-cannot-touch-rebind", + kzt_guest_registry_complete_loader_close( + registry, &published), + KZT_GUEST_LOADER_CLOSE_STALE); + check_int("loader-identity.rebind-remains-findable", + kzt_guest_registry_find_loader_identity( + registry, 0x9100, &found), + 0); + check_int("loader-identity.rebind-close-unproven", + kzt_guest_registry_complete_loader_close(registry, &found), + KZT_GUEST_LOADER_CLOSE_UNLOAD_UNPROVEN); + + snapshot = find_snapshot(registry, observation.link_map_addr); + check_int("loader-identity.namespace-status", + snapshot->namespace_id.status, KZT_GUEST_FIELD_OK); + check_uintptr("loader-identity.namespace-value", + snapshot->namespace_id.value, 7); + kzt_guest_object_snapshot_free(snapshot); + kzt_guest_registry_destroy(®istry); +} + +static void test_unproven_unload_is_not_reused_without_resident_proof(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x7440); + kzt_guest_loader_identity_t identity = { 0 }; + kzt_guest_loader_identity_t reused = { 0 }; + + check_true("loader-unproven.registry", registry != NULL); + if (!registry) { + return; + } + check_int("loader-unproven.observe", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + check_int("loader-unproven.publish", + kzt_guest_registry_publish_loader_identity( + registry, 0x9140, observation.link_map_addr, 7, + &identity), + 0); + check_int("loader-unproven.close", + kzt_guest_registry_complete_loader_close( + registry, &identity), + KZT_GUEST_LOADER_CLOSE_UNLOAD_UNPROVEN); + check_not_int("loader-unproven.reuse-rejected", + kzt_guest_registry_reuse_loader_identity( + registry, 0x9140, &reused), + 0); + kzt_guest_registry_destroy(®istry); +} + +static void test_loader_symbol_source_is_exact_main_generation(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t main_object = make_observation(0x7480); + kzt_guest_object_observation_t other_object = make_observation(0x7490); + kzt_guest_loader_identity_t published = { 0 }; + kzt_guest_loader_identity_t source = { 0 }; + kzt_guest_dynamic_view_t view = { + .dynamic_addr = 0x101000, + .load_bias = 0x100000, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .has_null = 1, + }; + kzt_guest_dynamic_view_t found_view = { 0 }; + kzt_guest_field_status_t dynamic_status = KZT_GUEST_FIELD_UNKNOWN; + unsigned long dynamic_revision = 0; + kzt_guest_registry_source_lease_t lease = { 0 }; + + other_object.load_bias.value += 0x200000; + other_object.dynamic_addr.value += 0x200000; + check_true("symbol-source.registry", registry != NULL); + if (!registry) return; + check_int("symbol-source.observe-main", + kzt_guest_registry_observe(registry, &main_object), + KZT_GUEST_REGISTRY_ADDED); + check_int("symbol-source.publish-main", + kzt_guest_registry_publish_loader_identity( + registry, 0x9180, main_object.link_map_addr, 0, &published), + 0); + check_int("symbol-source.dynamic-main", + kzt_guest_registry_commit_dynamic_view( + registry, main_object.link_map_addr, published.generation, + &view), + KZT_GUEST_REGISTRY_UPDATED); + check_int("symbol-source.acquire-main", + kzt_guest_registry_loader_symbol_source_acquire( + registry, 0x9180, &source, &found_view, &dynamic_status, + &dynamic_revision, &lease), + 0); + check_uintptr("symbol-source.link-map", source.link_map_addr, + main_object.link_map_addr); + check_ulong("symbol-source.generation", source.generation, + published.generation); + check_uintptr("symbol-source.namespace", source.namespace_id, 0); + check_int("symbol-source.dynamic-status", dynamic_status, + KZT_GUEST_FIELD_OK); + check_uintptr("symbol-source.dynamic-address", found_view.dynamic_addr, + view.dynamic_addr); + check_ulong("symbol-source.dynamic-revision", dynamic_revision, 1); + check_true("symbol-source.lease", lease.active); + kzt_guest_registry_source_lease_release(&lease); + + view.strsz.present = 1; + view.strsz.value = 0x80; + view.strsz.address_semantics = KZT_GUEST_DYNAMIC_SCALAR; + check_int("symbol-source.dynamic-main-update", + kzt_guest_registry_commit_dynamic_view( + registry, main_object.link_map_addr, source.generation, + &view), + KZT_GUEST_REGISTRY_UPDATED); + check_int("symbol-source.acquire-main-update", + kzt_guest_registry_loader_symbol_source_acquire( + registry, 0x9180, &source, &found_view, &dynamic_status, + &dynamic_revision, &lease), + 0); + check_ulong("symbol-source.dynamic-revision-update", + dynamic_revision, 2); + kzt_guest_registry_source_lease_release(&lease); + + check_int("symbol-source.observe-other", + kzt_guest_registry_observe(registry, &other_object), + KZT_GUEST_REGISTRY_ADDED); + check_int("symbol-source.publish-other", + kzt_guest_registry_publish_loader_identity( + registry, 0x9190, other_object.link_map_addr, 7, &published), + 0); + view.dynamic_addr = other_object.dynamic_addr.value; + view.load_bias = other_object.load_bias.value; + check_int("symbol-source.dynamic-other", + kzt_guest_registry_commit_dynamic_view( + registry, other_object.link_map_addr, published.generation, + &view), + KZT_GUEST_REGISTRY_UPDATED); + check_not_int("symbol-source.non-main-rejected", + kzt_guest_registry_loader_symbol_source_acquire( + registry, 0x9190, &source, &found_view, &dynamic_status, + &dynamic_revision, &lease), + 0); + check_true("symbol-source.non-main-no-lease", !lease.active); + + check_int("symbol-source.retire-main", + kzt_guest_registry_retire( + registry, main_object.link_map_addr, 1), + 0); + check_not_int("symbol-source.retired-rejected", + kzt_guest_registry_loader_symbol_source_acquire( + registry, 0x9180, &source, &found_view, &dynamic_status, + &dynamic_revision, &lease), + 0); + check_true("symbol-source.retired-no-lease", !lease.active); + kzt_guest_registry_destroy(®istry); +} + +static void test_loader_unload_retires_only_exact_namespace_and_generation(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x7500); + kzt_guest_loader_identity_t old_identity = { 0 }; + kzt_guest_loader_identity_t new_identity = { 0 }; + kzt_guest_loader_identity_t wrong_identity; + kzt_guest_object_snapshot_t *snapshot; + + check_true("loader-retire.registry", registry != NULL); + if (!registry) { + return; + } + check_int("loader-retire.observe-old", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + check_int("loader-retire.publish-old", + kzt_guest_registry_publish_loader_identity( + registry, 0x9200, observation.link_map_addr, 7, + &old_identity), + 0); + + wrong_identity = old_identity; + wrong_identity.namespace_id = 8; + check_not_int("loader-retire.reject-wrong-namespace", + kzt_guest_registry_retire_loader_identity( + registry, &wrong_identity), + 0); + snapshot = find_snapshot(registry, observation.link_map_addr); + check_ulong("loader-retire.wrong-namespace-generation-live", + snapshot->generation, old_identity.generation); + kzt_guest_object_snapshot_free(snapshot); + + check_int("loader-retire.exact-old", + kzt_guest_registry_retire_loader_identity( + registry, &old_identity), + 0); + observation.load_bias.value += 0x100000; + observation.dynamic_addr.value += 0x100000; + observation.path.value = "/guest/libreopened.so"; + observation.namespace_id.status = KZT_GUEST_FIELD_UNKNOWN; + check_int("loader-retire.observe-reopen", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + check_int("loader-retire.publish-reopen", + kzt_guest_registry_publish_loader_identity( + registry, 0x9200, observation.link_map_addr, 9, + &new_identity), + 0); + check_true("loader-retire.new-generation", + new_identity.generation != old_identity.generation); + check_not_int("loader-retire.reject-old-generation", + kzt_guest_registry_retire_loader_identity( + registry, &old_identity), + 0); + snapshot = find_snapshot(registry, observation.link_map_addr); + check_ulong("loader-retire.reopen-generation-live", + snapshot->generation, new_identity.generation); + check_uintptr("loader-retire.reopen-namespace-live", + snapshot->namespace_id.value, 9); + kzt_guest_object_snapshot_free(snapshot); + kzt_guest_registry_destroy(®istry); +} + +static void test_loader_unload_prepares_before_unmap_and_can_cancel(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x7580); + kzt_guest_loader_identity_t identity = { 0 }; + kzt_guest_loader_identity_t found = { 0 }; + kzt_guest_registry_address_match_t match = { 0 }; + + check_true("loader-prepare.registry", registry != NULL); + if (!registry) { + return; + } + check_int("loader-prepare.observe", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + check_int("loader-prepare.publish", + kzt_guest_registry_publish_loader_identity( + registry, 0x9280, observation.link_map_addr, 7, + &identity), + 0); + check_int("loader-prepare.begin", + kzt_guest_registry_begin_loader_unload(registry, &identity), + 0); + check_not_int("loader-prepare.blocks-live-lookup", + kzt_guest_registry_find_live_object( + registry, observation.link_map_addr, &match), + 0); + check_int("loader-prepare.identity-remains-resolvable", + kzt_guest_registry_find_loader_object_identity( + registry, observation.link_map_addr, &found), + 0); + check_ulong("loader-prepare.identity-generation", found.generation, + identity.generation); + check_int("loader-prepare.cancel", + kzt_guest_registry_cancel_loader_unload(registry, &identity), + 0); + check_int("loader-prepare.live-after-cancel", + kzt_guest_registry_find_live_object( + registry, observation.link_map_addr, &match), + 0); + check_int("loader-prepare.begin-again", + kzt_guest_registry_begin_loader_unload(registry, &identity), + 0); + check_int("loader-prepare.finish", + kzt_guest_registry_finish_loader_unload(registry, &identity), + 0); + check_not_int("loader-prepare.dead-after-finish", + kzt_guest_registry_find_loader_object_identity( + registry, observation.link_map_addr, &found), + 0); + kzt_guest_registry_destroy(®istry); +} + +static void test_namespace_evidence_supplements_exact_dependency(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t root = make_observation(0x7590); + kzt_guest_object_observation_t dependency = make_observation(0x75a0); + kzt_guest_loader_identity_t root_identity = { 0 }; + kzt_guest_object_snapshot_t *snapshot; + + root.namespace_id.status = KZT_GUEST_FIELD_UNKNOWN; + dependency.namespace_id.status = KZT_GUEST_FIELD_UNKNOWN; + dependency.load_bias.value += 0x200000; + dependency.dynamic_addr.value += 0x200000; + dependency.path.value = "/guest/libnamespace-dependency.so"; + check_true("namespace-supplement.registry", registry != NULL); + if (!registry) { + return; + } + check_int("namespace-supplement.observe-root", + kzt_guest_registry_observe(registry, &root), + KZT_GUEST_REGISTRY_ADDED); + check_int("namespace-supplement.observe-dependency", + kzt_guest_registry_observe(registry, &dependency), + KZT_GUEST_REGISTRY_ADDED); + check_int("namespace-supplement.publish-root", + kzt_guest_registry_publish_loader_identity( + registry, 0x9290, root.link_map_addr, 7, + &root_identity), + 0); + check_int("namespace-supplement.dependency", + kzt_guest_registry_supplement_namespace( + registry, dependency.link_map_addr, 2, 7), + KZT_GUEST_REGISTRY_UPDATED); + snapshot = find_snapshot(registry, dependency.link_map_addr); + check_int("namespace-supplement.status", snapshot->namespace_id.status, + KZT_GUEST_FIELD_OK); + check_uintptr("namespace-supplement.value", snapshot->namespace_id.value, + 7); + kzt_guest_object_snapshot_free(snapshot); + check_int("namespace-supplement.conflict", + kzt_guest_registry_supplement_namespace( + registry, dependency.link_map_addr, 2, 8), + KZT_GUEST_REGISTRY_CONFLICT); + kzt_guest_registry_destroy(®istry); +} + +static void test_address_match_overlong_path_is_unknown(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x7600); + kzt_guest_registry_address_match_t match = { 0 }; + char path[KZT_GUEST_REGISTRY_ADDRESS_TEXT_LIMIT + 32]; + + memset(path, 'x', sizeof(path) - 1); + path[sizeof(path) - 1] = '\0'; + observation.path.value = path; + observation.namespace_id = (kzt_guest_scalar_field_t) { + .value = 0, + .status = KZT_GUEST_FIELD_OK, + }; + check_true("long-match.registry", registry != NULL); + if (!registry) { + return; + } + check_int("long-match.observe", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + check_int("long-match.find-live", + kzt_guest_registry_find_live_object( + registry, observation.link_map_addr, &match), + 0); + check_int("long-match.path-status", match.path_status, + KZT_GUEST_FIELD_UNKNOWN); + check_string("long-match.path-empty", match.path, ""); kzt_guest_registry_destroy(®istry); } @@ -1250,6 +1755,14 @@ int main(void) test_destroyed_registry_rejects_new_observation(); test_retired_address_gets_new_generation(); test_lazy_resolver_publish_find_lifecycle(); + test_map_range_supplement_is_exact_and_allocation_free(); + test_loader_handle_binds_exact_link_map_generation_and_namespace(); + test_unproven_unload_is_not_reused_without_resident_proof(); + test_loader_symbol_source_is_exact_main_generation(); + test_loader_unload_retires_only_exact_namespace_and_generation(); + test_loader_unload_prepares_before_unmap_and_can_cancel(); + test_namespace_evidence_supplements_exact_dependency(); + test_address_match_overlong_path_is_unknown(); if (failures) { fprintf(stderr, "kzt-guest-registry: %d failure(s)\n", failures); diff --git a/tests/unit/kzt/test_guest_registry_concurrency.c b/tests/unit/kzt/test_guest_registry_concurrency.c index 594e443493d..19e004711c0 100644 --- a/tests/unit/kzt/test_guest_registry_concurrency.c +++ b/tests/unit/kzt/test_guest_registry_concurrency.c @@ -512,11 +512,157 @@ static void test_concurrent_query_and_dump_return_owned_snapshots(void) check_true("registry.destroy.snapshot", registry == NULL); } +typedef struct loader_lifecycle_worker { + kzt_guest_registry_t *registry; + pthread_barrier_t *barrier; + kzt_guest_loader_identity_t identity; + int publish; + int publish_result; + kzt_guest_loader_close_result_t close_result; +} loader_lifecycle_worker_t; + +static void *loader_lifecycle_worker_main(void *opaque) +{ + loader_lifecycle_worker_t *worker = opaque; + + if (wait_for_barrier(worker->barrier) != 0) { + worker->publish_result = -1; + worker->close_result = KZT_GUEST_LOADER_CLOSE_STALE; + return NULL; + } + if (worker->publish) { + worker->publish_result = kzt_guest_registry_publish_loader_identity( + worker->registry, worker->identity.handle, + worker->identity.link_map_addr, worker->identity.namespace_id, + &worker->identity); + } else { + worker->close_result = kzt_guest_registry_complete_loader_close( + worker->registry, &worker->identity); + } + return NULL; +} + +static void test_concurrent_close_reopen_has_no_retire_or_aba(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation( + 0x760000, 1, "/guest/libclose-reopen.so"); + kzt_guest_loader_identity_t old_identity = { 0 }; + kzt_guest_loader_identity_t found = { 0 }; + kzt_guest_loader_identity_t new_identity = { 0 }; + loader_lifecycle_worker_t workers[3]; + pthread_t threads[3]; + pthread_barrier_t barrier; + kzt_guest_object_snapshot_t *snapshot = NULL; + int referenced = 0; + int unproven = 0; + int stale = 0; + size_t i; + + check_true("loader-race.registry", registry != NULL); + if (!registry) { + return; + } + check_int("loader-race.observe-old", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + check_int("loader-race.publish-first", + kzt_guest_registry_publish_loader_identity( + registry, 0x930000, observation.link_map_addr, 0, + &old_identity), + 0); + check_int("loader-race.publish-second", + kzt_guest_registry_publish_loader_identity( + registry, 0x930000, observation.link_map_addr, 0, + &old_identity), + 0); + check_int("loader-race.barrier-init", + pthread_barrier_init(&barrier, NULL, 3), 0); + for (i = 0; i < 3; ++i) { + workers[i] = (loader_lifecycle_worker_t) { + .registry = registry, + .barrier = &barrier, + .identity = old_identity, + .publish = i == 2, + .publish_result = -1, + .close_result = KZT_GUEST_LOADER_CLOSE_STALE, + }; + check_int("loader-race.thread-create", + pthread_create(&threads[i], NULL, + loader_lifecycle_worker_main, &workers[i]), + 0); + } + for (i = 0; i < 3; ++i) { + check_int("loader-race.thread-join", + pthread_join(threads[i], NULL), 0); + } + check_int("loader-race.barrier-destroy", + pthread_barrier_destroy(&barrier), 0); + check_int("loader-race.reopen-publish", workers[2].publish_result, 0); + for (i = 0; i < 2; ++i) { + if (workers[i].close_result == KZT_GUEST_LOADER_CLOSE_REFERENCED) { + ++referenced; + } else if (workers[i].close_result == + KZT_GUEST_LOADER_CLOSE_UNLOAD_UNPROVEN) { + ++unproven; + } else if (workers[i].close_result == + KZT_GUEST_LOADER_CLOSE_STALE) { + ++stale; + } else { + ++failures; + } + } + check_int("loader-race.two-closes-accounted", + referenced + unproven + stale, 2); + check_int("loader-race.reopen-handle-live", + kzt_guest_registry_find_loader_identity( + registry, old_identity.handle, &found), + 0); + check_ulong("loader-race.same-generation-after-reopen", + found.generation, old_identity.generation); + check_int("loader-race.final-close-unproven", + kzt_guest_registry_complete_loader_close( + registry, &found), + KZT_GUEST_LOADER_CLOSE_UNLOAD_UNPROVEN); + check_int("loader-race.exact-unload-old", + kzt_guest_registry_retire_loader_identity( + registry, &old_identity), + 0); + observation.load_bias.value += 0x200000; + observation.dynamic_addr.value += 0x200000; + observation.path.value = "/guest/libclose-reopen-new.so"; + check_int("loader-race.observe-new", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + check_int("loader-race.publish-new", + kzt_guest_registry_publish_loader_identity( + registry, old_identity.handle, observation.link_map_addr, 0, + &new_identity), + 0); + check_true("loader-race.generation-advanced", + new_identity.generation != old_identity.generation); + check_true("loader-race.old-generation-cannot-retire-new", + kzt_guest_registry_retire_loader_identity( + registry, &old_identity) != 0); + check_int("loader-race.new-object-live", + kzt_guest_registry_find_by_link_map( + registry, observation.link_map_addr, &snapshot), + 0); + check_true("loader-race.new-snapshot", snapshot != NULL); + if (snapshot) { + check_ulong("loader-race.new-generation-live", snapshot->generation, + new_identity.generation); + kzt_guest_object_snapshot_free(snapshot); + } + kzt_guest_registry_destroy(®istry); +} + int main(void) { test_concurrent_same_link_map_converges_to_one_generation(); test_concurrent_different_link_maps_create_distinct_objects(); test_concurrent_query_and_dump_return_owned_snapshots(); + test_concurrent_close_reopen_has_no_retire_or_aba(); if (failures) { fprintf(stderr, "kzt-guest-registry-concurrency: %d failure(s)\n", diff --git a/tests/unit/kzt/test_guest_registry_context.c b/tests/unit/kzt/test_guest_registry_context.c index 5f9355e7227..e76c7fb03bc 100644 --- a/tests/unit/kzt/test_guest_registry_context.c +++ b/tests/unit/kzt/test_guest_registry_context.c @@ -314,6 +314,13 @@ static void test_registry_evidence_cache_requires_exact_object_identity(void) &fixture.context, registry, observation.link_map_addr, observation.load_bias.value, observation.dynamic_addr.value)); + kzt_guest_registry_test_set_alloc_failure_after(0); + check_true("evidence.exact-reuse-no-allocation", + kzt_guest_registry_context_has_main_namespace_evidence( + &fixture.context, registry, observation.link_map_addr, + observation.load_bias.value, + observation.dynamic_addr.value)); + kzt_guest_registry_test_set_alloc_failure_after(-1); check_true("evidence.wrong-load-bias", !kzt_guest_registry_context_has_main_namespace_evidence( &fixture.context, registry, observation.link_map_addr, diff --git a/tests/unit/kzt/test_guest_registry_got_plt_injection.c b/tests/unit/kzt/test_guest_registry_got_plt_injection.c new file mode 100644 index 00000000000..38b4f3db232 --- /dev/null +++ b/tests/unit/kzt/test_guest_registry_got_plt_injection.c @@ -0,0 +1,132 @@ +#include +#include + +#include "elf.h" +#include "kzt_guest_registry.h" + +static int failures; + +static void check_int(const char *name, int got, int expected) +{ + if (got != expected) { + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; + } +} + +static kzt_guest_object_observation_t observation(uintptr_t link_map) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map, + .load_bias = { 0x400000, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { 0x401000, KZT_GUEST_FIELD_OK }, + .map_start = { 0x400000, KZT_GUEST_FIELD_OK }, + .map_end = { 0x408000, KZT_GUEST_FIELD_OK }, + .namespace_id = { 0, KZT_GUEST_FIELD_OK }, + .path = { "/guest/libfixture.so", KZT_GUEST_FIELD_OK }, + .soname = { "libfixture.so", KZT_GUEST_FIELD_OK }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static kzt_guest_dynamic_view_t complete_view(void) +{ + return (kzt_guest_dynamic_view_t) { + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .dynamic_addr = 0x401000, + .load_bias = 0x400000, + .has_null = 1, + .jmprel = { 1, 0x402000, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS }, + .pltrelsz = { 1, sizeof(Elf64_Rela), KZT_GUEST_DYNAMIC_SCALAR }, + .pltrel = { 1, DT_RELA, KZT_GUEST_DYNAMIC_SCALAR }, + .pltgot = { 1, 0x403000, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS }, + }; +} + +static void test_incomplete_evidence_fails_open(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t object = observation(0x1000); + kzt_guest_dynamic_view_t view = complete_view(); + kzt_guest_registry_source_lease_t source = { 0 }; + kzt_guest_registry_patch_decision_lease_t decision = { 0 }; + + if (!registry) { + ++failures; + return; + } + check_int("incomplete.observe", + kzt_guest_registry_observe(registry, &object), + KZT_GUEST_REGISTRY_ADDED); + check_int("incomplete.source", + kzt_guest_registry_source_lease_acquire( + registry, 0x1000, 1, 0, &source), 0); + check_int("incomplete.decision", + kzt_guest_registry_patch_decision_lease_acquire( + &source, &decision), 0); + check_int("incomplete.claim", + kzt_guest_registry_got_plt_injection_claim( + &decision, &view), + KZT_GUEST_GOT_PLT_INJECTION_FAIL_OPEN); + kzt_guest_registry_patch_decision_lease_release(&decision); + kzt_guest_registry_source_lease_release(&source); + kzt_guest_registry_destroy(®istry); +} + +static void test_exact_generation_claim_is_idempotent(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t object = observation(0x1000); + kzt_guest_dynamic_view_t view = complete_view(); + kzt_guest_registry_source_lease_t source = { 0 }; + kzt_guest_registry_patch_decision_lease_t decision = { 0 }; + + if (!registry) { + ++failures; + return; + } + check_int("idempotent.observe", + kzt_guest_registry_observe(registry, &object), + KZT_GUEST_REGISTRY_ADDED); + check_int("idempotent.view", + kzt_guest_registry_commit_dynamic_view(registry, 0x1000, 1, + &view), + KZT_GUEST_REGISTRY_UPDATED); + check_int("idempotent.source", + kzt_guest_registry_source_lease_acquire( + registry, 0x1000, 1, 0, &source), 0); + check_int("idempotent.decision", + kzt_guest_registry_patch_decision_lease_acquire( + &source, &decision), 0); + check_int("idempotent.first-claim", + kzt_guest_registry_got_plt_injection_claim( + &decision, &view), + KZT_GUEST_GOT_PLT_INJECTION_GRANTED); + check_int("idempotent.concurrent-claim", + kzt_guest_registry_got_plt_injection_claim( + &decision, &view), + KZT_GUEST_GOT_PLT_INJECTION_IN_PROGRESS); + check_int("idempotent.finish", + kzt_guest_registry_got_plt_injection_finish( + &decision, 1), 0); + check_int("idempotent.replay", + kzt_guest_registry_got_plt_injection_claim( + &decision, &view), + KZT_GUEST_GOT_PLT_INJECTION_ALREADY_APPLIED); + kzt_guest_registry_patch_decision_lease_release(&decision); + kzt_guest_registry_source_lease_release(&source); + kzt_guest_registry_destroy(®istry); +} + +int main(void) +{ + test_incomplete_evidence_fails_open(); + test_exact_generation_claim_is_idempotent(); + if (failures) { + fprintf(stderr, "kzt registry GOT/PLT injection: %d failure(s)\n", + failures); + return 1; + } + puts("kzt registry GOT/PLT injection: PASS"); + return 0; +} diff --git a/tests/unit/kzt/test_guest_registry_patch_decision_lease.c b/tests/unit/kzt/test_guest_registry_patch_decision_lease.c new file mode 100644 index 00000000000..131304e1191 --- /dev/null +++ b/tests/unit/kzt/test_guest_registry_patch_decision_lease.c @@ -0,0 +1,561 @@ +#include +#include +#include +#include +#include +#include + +#include "kzt_guest_registry.h" + +static int failures; + +static void check_int(const char *name, int got, int expected) +{ + if (got != expected) { + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; + } +} + +static void check_true(const char *name, int value) +{ + if (!value) { + fprintf(stderr, "%s: false\n", name); + ++failures; + } +} + +static kzt_guest_object_observation_t observation(uintptr_t link_map, + uintptr_t map_start, + uintptr_t map_end) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map, + .load_bias = { map_start, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { map_start + 0x1000, KZT_GUEST_FIELD_OK }, + .map_start = { map_start, KZT_GUEST_FIELD_OK }, + .map_end = { map_end, KZT_GUEST_FIELD_OK }, + .namespace_id = { 0, KZT_GUEST_FIELD_OK }, + .path = { "/guest/libfixture.so", KZT_GUEST_FIELD_OK }, + .soname = { "libfixture.so", KZT_GUEST_FIELD_OK }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static kzt_guest_dynamic_view_t dynamic_view(uintptr_t dynamic_addr) +{ + return (kzt_guest_dynamic_view_t) { + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .dynamic_addr = dynamic_addr, + .load_bias = 0x400000, + .has_null = 1, + }; +} + +typedef struct sync { + pthread_mutex_t lock; + pthread_cond_t cond; + int waiters; + int done; +} sync_t; + +static int sync_init(sync_t *sync) +{ + memset(sync, 0, sizeof(*sync)); + return pthread_mutex_init(&sync->lock, NULL) || + pthread_cond_init(&sync->cond, NULL) ? -1 : 0; +} + +static void sync_destroy(sync_t *sync) +{ + pthread_cond_destroy(&sync->cond); + pthread_mutex_destroy(&sync->lock); +} + +static void sync_before_patch_decision_wait(void *opaque) +{ + sync_t *sync = opaque; + + pthread_mutex_lock(&sync->lock); + ++sync->waiters; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); +} + +static void sync_mark_done(sync_t *sync) +{ + pthread_mutex_lock(&sync->lock); + ++sync->done; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); +} + +static int sync_wait_for(sync_t *sync, int *value, int expected) +{ + int result = 0; + + pthread_mutex_lock(&sync->lock); + while (*value < expected) { + if (pthread_cond_wait(&sync->cond, &sync->lock) != 0) { + result = -1; + break; + } + } + pthread_mutex_unlock(&sync->lock); + return result; +} + +static int sync_wait_for_timed(sync_t *sync, int *value, int expected) +{ + struct timespec deadline; + int result = 0; + + clock_gettime(CLOCK_REALTIME, &deadline); + deadline.tv_nsec += 100 * 1000 * 1000; + if (deadline.tv_nsec >= 1000 * 1000 * 1000) { + ++deadline.tv_sec; + deadline.tv_nsec -= 1000 * 1000 * 1000; + } + pthread_mutex_lock(&sync->lock); + while (*value < expected) { + int status = pthread_cond_timedwait(&sync->cond, &sync->lock, + &deadline); + if (status == ETIMEDOUT) { + result = -1; + break; + } + if (status != 0) { + result = -1; + break; + } + } + pthread_mutex_unlock(&sync->lock); + return result; +} + +static int sync_expect_not_done(sync_t *sync) +{ + struct timespec deadline; + int result = 0; + + clock_gettime(CLOCK_REALTIME, &deadline); + deadline.tv_nsec += 100 * 1000 * 1000; + if (deadline.tv_nsec >= 1000 * 1000 * 1000) { + ++deadline.tv_sec; + deadline.tv_nsec -= 1000 * 1000 * 1000; + } + pthread_mutex_lock(&sync->lock); + while (!sync->done) { + int status = pthread_cond_timedwait(&sync->cond, &sync->lock, + &deadline); + if (status == ETIMEDOUT) { + break; + } + if (status != 0) { + result = -1; + break; + } + } + if (sync->done) { + result = -1; + } + pthread_mutex_unlock(&sync->lock); + return result; +} + +static void setup_source(kzt_guest_registry_t *registry) +{ + kzt_guest_object_observation_t source = observation(0x1000, 0x500000, + 0x504000); + kzt_guest_dynamic_view_t view = dynamic_view(0x401000); + + check_int("source.observe", kzt_guest_registry_observe(registry, &source), + KZT_GUEST_REGISTRY_ADDED); + check_int("source.view", kzt_guest_registry_commit_dynamic_view( + registry, 0x1000, 1, &view), KZT_GUEST_REGISTRY_UPDATED); +} + +typedef enum mutation_kind { + MUTATE_DYNAMIC_VIEW, + MUTATE_OVERLAPPING_OWNER, + MUTATE_RETIRE, + MUTATE_LAZY_RESOLVER, +} mutation_kind_t; + +typedef struct mutation_worker { + kzt_guest_registry_t *registry; + mutation_kind_t kind; + sync_t *sync; + int result; +} mutation_worker_t; + +static void *mutation_worker_main(void *opaque) +{ + mutation_worker_t *worker = opaque; + kzt_guest_object_observation_t owner = observation(0x2000, 0x500000, + 0x502000); + kzt_guest_dynamic_view_t view = dynamic_view(0x402000); + kzt_guest_lazy_resolver_t resolver = { + .link_map_slot = 0x501000, + .resolver_slot = 0x501008, + .guest_link_map = 0x1000, + .guest_resolver = 0x502000, + }; + + if (worker->kind == MUTATE_DYNAMIC_VIEW) { + worker->result = kzt_guest_registry_commit_dynamic_view( + worker->registry, 0x1000, 1, &view); + } else if (worker->kind == MUTATE_OVERLAPPING_OWNER) { + worker->result = kzt_guest_registry_observe(worker->registry, &owner); + } else if (worker->kind == MUTATE_RETIRE) { + worker->result = kzt_guest_registry_retire(worker->registry, 0x1000, 1); + } else { + worker->result = kzt_guest_registry_publish_lazy_resolver( + worker->registry, 0x1000, 1, 0, &resolver); + } + sync_mark_done(worker->sync); + return NULL; +} + +typedef struct destroy_worker { + kzt_guest_registry_t *registry; + sync_t *sync; +} destroy_worker_t; + +typedef struct loader_unload_worker { + kzt_guest_registry_t *registry; + kzt_guest_loader_identity_t identity; + sync_t *sync; + int result; +} loader_unload_worker_t; + +static void *loader_unload_worker_main(void *opaque) +{ + loader_unload_worker_t *worker = opaque; + + worker->result = kzt_guest_registry_begin_loader_unload( + worker->registry, &worker->identity); + sync_mark_done(worker->sync); + return NULL; +} + +static void *destroy_worker_main(void *opaque) +{ + destroy_worker_t *worker = opaque; + + kzt_guest_registry_destroy(&worker->registry); + sync_mark_done(worker->sync); + return NULL; +} + +static void test_mutators_wait_for_all_decisions(void) +{ + const mutation_kind_t kinds[] = { + MUTATE_DYNAMIC_VIEW, + MUTATE_OVERLAPPING_OWNER, + MUTATE_RETIRE, + }; + size_t i; + + for (i = 0; i < sizeof(kinds) / sizeof(kinds[0]); ++i) { + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_registry_source_lease_t source = { 0 }; + kzt_guest_registry_patch_decision_lease_t first = { 0 }; + kzt_guest_registry_patch_decision_lease_t second = { 0 }; + mutation_worker_t worker; + pthread_t thread; + sync_t sync; + + check_true("mutator.registry", registry != NULL); + if (!registry || sync_init(&sync) != 0) { + kzt_guest_registry_destroy(®istry); + continue; + } + setup_source(registry); + check_int("mutator.source", kzt_guest_registry_source_lease_acquire( + registry, 0x1000, 1, 0, &source), 0); + check_int("mutator.first", kzt_guest_registry_patch_decision_lease_acquire( + &source, &first), 0); + check_int("mutator.second", kzt_guest_registry_patch_decision_lease_acquire( + &source, &second), 0); + worker = (mutation_worker_t) { registry, kinds[i], &sync, -99 }; + kzt_guest_registry_test_set_before_patch_decision_wait( + sync_before_patch_decision_wait, &sync); + check_int("mutator.create", pthread_create(&thread, NULL, + mutation_worker_main, + &worker), 0); + check_int("mutator.wait-registered", sync_wait_for(&sync, &sync.waiters, 1), 0); + kzt_guest_registry_patch_decision_lease_release(&first); + check_int("mutator.one-release-still-blocked", sync_expect_not_done(&sync), 0); + kzt_guest_registry_patch_decision_lease_release(&second); + if (kinds[i] == MUTATE_RETIRE) { + kzt_guest_registry_source_lease_release(&source); + } + check_int("mutator.done", sync_wait_for(&sync, &sync.done, 1), 0); + check_int("mutator.join", pthread_join(thread, NULL), 0); + check_true("mutator.success", worker.result == 0 || + worker.result == KZT_GUEST_REGISTRY_ADDED || + worker.result == KZT_GUEST_REGISTRY_UPDATED); + kzt_guest_registry_test_set_before_patch_decision_wait(NULL, NULL); + kzt_guest_registry_source_lease_release(&source); + kzt_guest_registry_destroy(®istry); + sync_destroy(&sync); + } +} + +static void test_waiter_admission_blocks_new_leases(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_registry_source_lease_t source = { 0 }; + kzt_guest_registry_source_lease_t late_source = { 0 }; + kzt_guest_registry_patch_decision_lease_t decision = { 0 }; + kzt_guest_registry_patch_decision_lease_t late_decision = { 0 }; + mutation_worker_t worker; + pthread_t thread; + sync_t sync; + + check_true("admission.registry", registry != NULL); + if (!registry || sync_init(&sync) != 0) { + kzt_guest_registry_destroy(®istry); + return; + } + setup_source(registry); + check_int("admission.source", kzt_guest_registry_source_lease_acquire( + registry, 0x1000, 1, 0, &source), 0); + check_int("admission.decision", kzt_guest_registry_patch_decision_lease_acquire( + &source, &decision), 0); + worker = (mutation_worker_t) { registry, MUTATE_RETIRE, &sync, -99 }; + kzt_guest_registry_test_set_before_patch_decision_wait( + sync_before_patch_decision_wait, &sync); + check_int("admission.create", pthread_create(&thread, NULL, + mutation_worker_main, &worker), 0); + check_int("admission.wait-registered", sync_wait_for(&sync, &sync.waiters, 1), 0); + check_int("admission.new-source-rejected", kzt_guest_registry_source_lease_acquire( + registry, 0x1000, 1, 0, &late_source), -1); + check_int("admission.new-decision-rejected", + kzt_guest_registry_patch_decision_lease_acquire( + &source, &late_decision), -1); + kzt_guest_registry_patch_decision_lease_release(&decision); + check_int("admission.retire-still-waits-source", sync_expect_not_done(&sync), 0); + kzt_guest_registry_source_lease_release(&source); + check_int("admission.retire-done", sync_wait_for(&sync, &sync.done, 1), 0); + check_int("admission.join", pthread_join(thread, NULL), 0); + check_int("admission.retire-ok", worker.result, 0); + kzt_guest_registry_test_set_before_patch_decision_wait(NULL, NULL); + kzt_guest_registry_destroy(®istry); + sync_destroy(&sync); +} + +static void test_retire_and_existing_mutator_finish_without_deadlock(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_registry_source_lease_t source = { 0 }; + kzt_guest_registry_patch_decision_lease_t decision = { 0 }; + mutation_worker_t ordinary; + mutation_worker_t retire; + pthread_t ordinary_thread; + pthread_t retire_thread; + sync_t wait_sync; + sync_t ordinary_sync; + sync_t retire_sync; + + check_true("ordered.registry", registry != NULL); + if (!registry || sync_init(&wait_sync) != 0 || + sync_init(&ordinary_sync) != 0 || sync_init(&retire_sync) != 0) { + kzt_guest_registry_destroy(®istry); + return; + } + setup_source(registry); + check_int("ordered.source", kzt_guest_registry_source_lease_acquire( + registry, 0x1000, 1, 0, &source), 0); + check_int("ordered.decision", kzt_guest_registry_patch_decision_lease_acquire( + &source, &decision), 0); + + ordinary = (mutation_worker_t) { registry, MUTATE_DYNAMIC_VIEW, + &ordinary_sync, -99 }; + retire = (mutation_worker_t) { registry, MUTATE_RETIRE, &retire_sync, + -99 }; + kzt_guest_registry_test_set_before_patch_decision_wait( + sync_before_patch_decision_wait, &wait_sync); + check_int("ordered.ordinary.create", pthread_create(&ordinary_thread, NULL, + mutation_worker_main, + &ordinary), 0); + check_int("ordered.ordinary.waiting", sync_wait_for(&wait_sync, + &wait_sync.waiters, 1), 0); + check_int("ordered.retire.create", pthread_create(&retire_thread, NULL, + mutation_worker_main, + &retire), 0); + check_int("ordered.retire.joins-wait", sync_wait_for_timed( + &wait_sync, &wait_sync.waiters, 2), 0); + check_int("ordered.retire.not-early", sync_expect_not_done(&retire_sync), 0); + + kzt_guest_registry_patch_decision_lease_release(&decision); + check_int("ordered.ordinary.done", sync_wait_for(&ordinary_sync, + &ordinary_sync.done, 1), 0); + check_int("ordered.retire.waits-source", sync_expect_not_done(&retire_sync), 0); + kzt_guest_registry_source_lease_release(&source); + check_int("ordered.retire.done", sync_wait_for(&retire_sync, + &retire_sync.done, 1), 0); + check_int("ordered.ordinary.join", pthread_join(ordinary_thread, NULL), 0); + check_int("ordered.retire.join", pthread_join(retire_thread, NULL), 0); + /* Condition-variable wakeups are not FIFO. The ordinary update either + * commits first or revalidates after retire marked the object UNLOADING. */ + if (ordinary.result != KZT_GUEST_REGISTRY_UPDATED && + ordinary.result != KZT_GUEST_REGISTRY_ERROR) { + fprintf(stderr, "ordered.ordinary-result: got %d\n", ordinary.result); + ++failures; + } + check_int("ordered.retire-result", retire.result, 0); + kzt_guest_registry_test_set_before_patch_decision_wait(NULL, NULL); + kzt_guest_registry_destroy(®istry); + sync_destroy(&wait_sync); + sync_destroy(&ordinary_sync); + sync_destroy(&retire_sync); +} + +static void test_loader_delete_prepare_drains_source_lease_before_unmap(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_registry_source_lease_t source = { 0 }; + kzt_guest_registry_source_lease_t late_source = { 0 }; + kzt_guest_loader_identity_t identity = { 0 }; + loader_unload_worker_t worker; + pthread_t thread; + sync_t sync; + + check_true("loader-prepare.registry", registry != NULL); + if (!registry || sync_init(&sync) != 0) { + kzt_guest_registry_destroy(®istry); + return; + } + setup_source(registry); + check_int("loader-prepare.publish", + kzt_guest_registry_publish_loader_identity( + registry, 0x9000, 0x1000, 0, &identity), + 0); + check_int("loader-prepare.source", + kzt_guest_registry_source_lease_acquire( + registry, 0x1000, 1, 0, &source), + 0); + worker = (loader_unload_worker_t) { + .registry = registry, + .identity = identity, + .sync = &sync, + .result = -99, + }; + kzt_guest_registry_test_set_before_retire_wait( + sync_before_patch_decision_wait, &sync); + check_int("loader-prepare.create", + pthread_create(&thread, NULL, loader_unload_worker_main, + &worker), + 0); + check_int("loader-prepare.waiting", + sync_wait_for(&sync, &sync.waiters, 1), 0); + check_int("loader-prepare.new-source-rejected", + kzt_guest_registry_source_lease_acquire( + registry, 0x1000, 1, 0, &late_source), + -1); + check_int("loader-prepare.not-before-release", + sync_expect_not_done(&sync), 0); + kzt_guest_registry_source_lease_release(&source); + check_int("loader-prepare.done", + sync_wait_for(&sync, &sync.done, 1), 0); + check_int("loader-prepare.join", pthread_join(thread, NULL), 0); + check_int("loader-prepare.result", worker.result, 0); + check_int("loader-prepare.cancel", + kzt_guest_registry_cancel_loader_unload( + registry, &identity), + 0); + kzt_guest_registry_test_set_before_retire_wait(NULL, NULL); + kzt_guest_registry_destroy(®istry); + sync_destroy(&sync); +} + +static void test_resolver_publish_is_not_writer_evidence(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_registry_source_lease_t source = { 0 }; + kzt_guest_registry_patch_decision_lease_t decision = { 0 }; + kzt_guest_lazy_resolver_t resolver = { + .link_map_slot = 0x501000, + .resolver_slot = 0x501008, + .guest_link_map = 0x1000, + .guest_resolver = 0x502000, + }; + + check_true("resolver.registry", registry != NULL); + if (!registry) { + return; + } + setup_source(registry); + check_int("resolver.source", kzt_guest_registry_source_lease_acquire( + registry, 0x1000, 1, 0, &source), 0); + check_int("resolver.decision", kzt_guest_registry_patch_decision_lease_acquire( + &source, &decision), 0); + check_int("resolver.publish", kzt_guest_registry_publish_lazy_resolver( + registry, 0x1000, 1, 0, &resolver), 0); + kzt_guest_registry_patch_decision_lease_release(&decision); + kzt_guest_registry_source_lease_release(&source); + kzt_guest_registry_destroy(®istry); +} + +static void test_destroy_drains_decision_after_source_and_waiter(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_registry_source_lease_t source = { 0 }; + kzt_guest_registry_patch_decision_lease_t decision = { 0 }; + mutation_worker_t mutation; + destroy_worker_t destroy; + pthread_t mutation_thread; + pthread_t destroy_thread; + sync_t mutation_sync; + sync_t destroy_sync; + + check_true("destroy.registry", registry != NULL); + if (!registry || sync_init(&mutation_sync) != 0 || + sync_init(&destroy_sync) != 0) { + kzt_guest_registry_destroy(®istry); + return; + } + setup_source(registry); + check_int("destroy.source", kzt_guest_registry_source_lease_acquire( + registry, 0x1000, 1, 0, &source), 0); + check_int("destroy.decision", kzt_guest_registry_patch_decision_lease_acquire( + &source, &decision), 0); + mutation = (mutation_worker_t) { registry, MUTATE_DYNAMIC_VIEW, + &mutation_sync, -99 }; + kzt_guest_registry_test_set_before_patch_decision_wait( + sync_before_patch_decision_wait, &mutation_sync); + check_int("destroy.mutation.create", pthread_create(&mutation_thread, NULL, + mutation_worker_main, + &mutation), 0); + check_int("destroy.waiter", sync_wait_for(&mutation_sync, + &mutation_sync.waiters, 1), 0); + destroy = (destroy_worker_t) { registry, &destroy_sync }; + check_int("destroy.create", pthread_create(&destroy_thread, NULL, + destroy_worker_main, &destroy), 0); + kzt_guest_registry_source_lease_release(&source); + check_int("destroy.still-drains-decision", sync_expect_not_done(&destroy_sync), 0); + kzt_guest_registry_patch_decision_lease_release(&decision); + check_int("destroy.mutation-unblocked", sync_wait_for(&mutation_sync, + &mutation_sync.done, 1), 0); + check_int("destroy.done", sync_wait_for(&destroy_sync, &destroy_sync.done, 1), 0); + check_int("destroy.mutation.join", pthread_join(mutation_thread, NULL), 0); + check_int("destroy.join", pthread_join(destroy_thread, NULL), 0); + check_int("destroy.mutation-disabled", mutation.result, KZT_GUEST_REGISTRY_DISABLED); + check_true("destroy.null", destroy.registry == NULL); + kzt_guest_registry_test_set_before_patch_decision_wait(NULL, NULL); + sync_destroy(&mutation_sync); + sync_destroy(&destroy_sync); +} + +int main(void) +{ + test_mutators_wait_for_all_decisions(); + test_waiter_admission_blocks_new_leases(); + test_retire_and_existing_mutator_finish_without_deadlock(); + test_loader_delete_prepare_drains_source_lease_before_unmap(); + test_resolver_publish_is_not_writer_evidence(); + test_destroy_drains_decision_after_source_and_waiter(); + return failures ? 1 : 0; +} diff --git a/tests/unit/kzt/test_observation_adapter.c b/tests/unit/kzt/test_observation_adapter.c index c2e9973a790..91e5f90b747 100644 --- a/tests/unit/kzt/test_observation_adapter.c +++ b/tests/unit/kzt/test_observation_adapter.c @@ -54,6 +54,8 @@ typedef struct observation_trace { int dynamic_comparison_attempted; int dynamic_comparison_blocking; int dynamic_comparison_matched; + int reader_calls_at_legacy; + int dynamic_reader_calls_at_legacy; } observation_trace_t; typedef struct fake_callback_event { @@ -72,6 +74,10 @@ typedef struct fake_callback_event { int map_range_present; uintptr_t map_start; uintptr_t map_end; + int legacy_range_present; + uintptr_t legacy_map_start; + uintptr_t legacy_map_end; + kzt_observation_legacy_result_t *legacy_result; } fake_callback_event_t; typedef enum callback_pause_point { @@ -207,6 +213,8 @@ static int fake_legacy_flow(uintptr_t link_map_addr, void *opaque) ++trace->legacy_calls; trace->legacy_link_map_addr = link_map_addr; + trace->reader_calls_at_legacy = trace->reader_calls; + trace->dynamic_reader_calls_at_legacy = trace->dynamic_reader_calls; record_event(trace, EVENT_LEGACY_FLOW); if (event->loader_scope && event->loader_library) { event->pending_pair_result = @@ -215,6 +223,11 @@ static int fake_legacy_flow(uintptr_t link_map_addr, void *opaque) event->loader_library, KZT_GUEST_LIBRARY_OBJECT_WRAPPED); } + if (event->legacy_result) { + event->legacy_result->map_range_present = event->legacy_range_present; + event->legacy_result->map_start = event->legacy_map_start; + event->legacy_result->map_end = event->legacy_map_end; + } if (event->barrier) { pthread_mutex_lock(&event->barrier->lock); if (event->barrier->pause_point == CALLBACK_PAUSE_LEGACY && @@ -437,6 +450,71 @@ static int run_adapter_with_loader_scope( return kzt_observe_guest_object_from_callback(&request, result); } +static int run_adapter_with_legacy_range( + fake_callback_event_t *event, kzt_guest_registry_t *registry, + int diagnostics_enabled, kzt_observation_adapter_result_t *result) +{ + kzt_observation_legacy_result_t legacy_result = { 0 }; + kzt_observation_adapter_request_t request = { + .enabled = 1, + .diagnostics_enabled = diagnostics_enabled, + .link_map_addr = (uintptr_t)&event->link_map, + .registry = registry, + .reader_ops = &event->ops, + .namespace_id_present = event->namespace_id_present, + .namespace_id = event->namespace_id, + .map_range_present = event->map_range_present, + .map_start = event->map_start, + .map_end = event->map_end, + .legacy_flow = fake_legacy_flow, + .legacy_opaque = event, + .legacy_result = &legacy_result, + .diagnostic = fake_diagnostic, + .diagnostic_opaque = &event->trace, + }; + int ret; + + event->legacy_result = &legacy_result; + ret = kzt_observe_guest_object_from_callback(&request, result); + event->legacy_result = NULL; + return ret; +} + +static int run_adapter_with_legacy_range_scoped( + fake_callback_event_t *event, kzt_guest_registry_t *registry, + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_loader_scope_t *loader_scope, + int diagnostics_enabled, kzt_observation_adapter_result_t *result) +{ + kzt_observation_legacy_result_t legacy_result = { 0 }; + kzt_observation_adapter_request_t request = { + .enabled = 1, + .diagnostics_enabled = diagnostics_enabled, + .link_map_addr = (uintptr_t)&event->link_map, + .registry = registry, + .library_bindings = bindings, + .loader_scope = loader_scope, + .reader_ops = &event->ops, + .namespace_id_present = event->namespace_id_present, + .namespace_id = event->namespace_id, + .map_range_present = event->map_range_present, + .map_start = event->map_start, + .map_end = event->map_end, + .reuse_complete_dynamic_view = 1, + .legacy_flow = fake_legacy_flow, + .legacy_opaque = event, + .legacy_result = &legacy_result, + .diagnostic = fake_diagnostic, + .diagnostic_opaque = &event->trace, + }; + int ret; + + event->legacy_result = &legacy_result; + ret = kzt_observe_guest_object_from_callback(&request, result); + event->legacy_result = NULL; + return ret; +} + static unsigned long registry_object_count(kzt_guest_registry_t *registry) { kzt_guest_registry_dump_t dump = { 0 }; @@ -2026,6 +2104,174 @@ static void test_invalid_or_absent_hints_remain_unknown_and_fail_open(void) kzt_guest_registry_destroy(®istry); } +static void test_legacy_range_observation_updates_same_generation(void) +{ + struct fake_library { int value; } library = { 21 }; + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_scope_t scope = { 0 }; + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 4, + }; + unsigned long before_generation = 0; + unsigned long after_generation = 0; + + check_true("legacy-range.registry", registry != NULL && bindings != NULL); + if (!registry || !bindings) { + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); + return; + } + init_fake_callback_event(&event, "/guest/liblegacy-range.so", 0x3a0000); + event.namespace_id = 0; + event.map_range_present = 0; + check_int("legacy-range.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + check_int("legacy-range.track", kzt_guest_library_track( + bindings, (library_t *)&library), 0); + check_int("legacy-range.pair", kzt_guest_library_note_exact_pair( + bindings, (uintptr_t)&event.link_map, + (library_t *)&library, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED), + KZT_GUEST_LIBRARY_BINDING_PENDING); + check_int("legacy-range.seed", run_adapter_with_bindings( + &event, registry, bindings, &result), 77); + check_int("legacy-range.seed-generation", registry_object_state( + registry, (uintptr_t)&event.link_map, &before_generation, + NULL), 0); + memset(&event.trace, 0, sizeof(event.trace)); + event.trace.legacy_return = 77; + event.legacy_range_present = 1; + event.legacy_map_start = 0x3a0000; + event.legacy_map_end = 0x3c0000; + check_int("legacy-range.scope", kzt_guest_library_loader_scope_begin( + bindings, &scope), 0); + + check_int("legacy-range.return", + run_adapter_with_legacy_range_scoped( + &event, registry, bindings, &scope, 1, &result), 77); + check_int("legacy-range.result", result, KZT_OBSERVATION_ADAPTER_UPDATED); + check_int("legacy-range.after-generation", registry_object_state( + registry, (uintptr_t)&event.link_map, &after_generation, + NULL), 0); + check_ulong("legacy-range.generation-stable", after_generation, + before_generation); + check_int("legacy-range.cache-hit", event.trace.dynamic_cache_hit, 1); + check_int("legacy-range.not-attempted", event.trace.dynamic_attempted, 0); + check_int("legacy-range.no-supplemental-dynamic-reader", + event.trace.dynamic_reader_calls, + event.trace.dynamic_reader_calls_at_legacy); + check_int("legacy-range.no-supplemental-reader", + event.trace.reader_calls, + event.trace.reader_calls_at_legacy); + check_int("legacy-range.find", kzt_guest_registry_find_by_link_map( + registry, (uintptr_t)&event.link_map, &snapshot), 0); + check_true("legacy-range.snapshot", snapshot != NULL); + if (snapshot) { + check_int("legacy-range.start-status", snapshot->map_start.status, + KZT_GUEST_FIELD_OK); + check_uintptr("legacy-range.start", snapshot->map_start.value, + event.legacy_map_start); + check_int("legacy-range.end-status", snapshot->map_end.status, + KZT_GUEST_FIELD_OK); + check_uintptr("legacy-range.end", snapshot->map_end.value, + event.legacy_map_end); + kzt_guest_object_snapshot_free(snapshot); + } + assert_old_flow_exactly_once("legacy-range.old-flow", &event, 77); + kzt_guest_library_loader_scope_end(&scope); + kzt_guest_library_unbind(bindings, registry, (library_t *)&library, + (uintptr_t)&event.link_map); + kzt_guest_library_bindings_destroy(&bindings); + kzt_guest_registry_destroy(®istry); +} + +static void test_invalid_legacy_range_is_ignored(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + + check_true("invalid-legacy-range.registry", registry != NULL); + if (!registry) { + return; + } + init_fake_callback_event(&event, "/guest/libinvalid-legacy-range.so", + 0x3d0000); + event.map_range_present = 0; + event.legacy_range_present = 1; + event.legacy_map_start = 0x3f0000; + event.legacy_map_end = 0x3e0000; + + check_int("invalid-legacy-range.return", + run_adapter_with_legacy_range(&event, registry, 0, &result), 77); + check_int("invalid-legacy-range.result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("invalid-legacy-range.find", kzt_guest_registry_find_by_link_map( + registry, (uintptr_t)&event.link_map, &snapshot), 0); + check_true("invalid-legacy-range.snapshot", snapshot != NULL); + if (snapshot) { + check_int("invalid-legacy-range.start", snapshot->map_start.status, + KZT_GUEST_FIELD_UNKNOWN); + check_int("invalid-legacy-range.end", snapshot->map_end.status, + KZT_GUEST_FIELD_UNKNOWN); + kzt_guest_object_snapshot_free(snapshot); + } + assert_old_flow_exactly_once("invalid-legacy-range.old-flow", &event, 77); + kzt_guest_registry_destroy(®istry); +} + +static void test_conflicting_legacy_range_is_fail_open(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t original; + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + + check_true("legacy-range-conflict.registry", registry != NULL); + if (!registry) { + return; + } + init_fake_callback_event(&event, "/guest/liblegacy-range-conflict.so", + 0x410000); + event.map_range_present = 0; + event.legacy_range_present = 1; + event.legacy_map_start = 0x430000; + event.legacy_map_end = 0x450000; + original = make_observation((uintptr_t)&event.link_map, 0x410000, + "/guest/liblegacy-range-conflict.so"); + original.map_start.value = 0x410000; + original.map_end.value = 0x420000; + check_int("legacy-range-conflict.prepopulate", + kzt_guest_registry_observe(registry, &original), + KZT_GUEST_REGISTRY_ADDED); + + check_int("legacy-range-conflict.return", + run_adapter_with_legacy_range(&event, registry, 0, &result), 77); + check_int("legacy-range-conflict.result", result, + KZT_OBSERVATION_ADAPTER_CONFLICT); + check_int("legacy-range-conflict.find", kzt_guest_registry_find_by_link_map( + registry, (uintptr_t)&event.link_map, &snapshot), 0); + check_true("legacy-range-conflict.snapshot", snapshot != NULL); + if (snapshot) { + check_uintptr("legacy-range-conflict.start", snapshot->map_start.value, + original.map_start.value); + check_uintptr("legacy-range-conflict.end", snapshot->map_end.value, + original.map_end.value); + kzt_guest_object_snapshot_free(snapshot); + } + assert_old_flow_exactly_once("legacy-range-conflict.old-flow", &event, 77); + kzt_guest_registry_destroy(®istry); +} + static void test_registry_failure_is_fail_open_for_old_flow(void) { fake_callback_event_t event; @@ -2309,6 +2555,9 @@ int main(void) test_partial_link_map_fields_are_registered(); test_verified_hints_fill_private_link_map_evidence(); test_invalid_or_absent_hints_remain_unknown_and_fail_open(); + test_legacy_range_observation_updates_same_generation(); + test_invalid_legacy_range_is_ignored(); + test_conflicting_legacy_range_is_fail_open(); test_registry_failure_is_fail_open_for_old_flow(); test_conflict_result_does_not_change_old_flow(); test_no_callback_event_does_not_create_registry_objects(); diff --git a/tests/unit/kzt/test_owner_resolver.c b/tests/unit/kzt/test_owner_resolver.c index 69eaef52b5f..b074e6f48dc 100644 --- a/tests/unit/kzt/test_owner_resolver.c +++ b/tests/unit/kzt/test_owner_resolver.c @@ -316,6 +316,30 @@ static void test_dead_objects_do_not_resolve_owner(void) kzt_guest_registry_destroy(®istry); } +static void test_unique_owner_resolution_does_not_need_snapshot_allocation(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t object = observation( + 0xd000, 0x7c000000, 0x7c001000, "libcompact-owner.so"); + kzt_owner_resolution_t resolution; + + check_int("compact-owner.observe", + kzt_guest_registry_observe(registry, &object), + KZT_GUEST_REGISTRY_ADDED); + kzt_guest_registry_test_set_alloc_failure_after(0); + check_int("compact-owner.resolve", + kzt_owner_resolver_resolve_current( + registry, 0x7c000010, 0x7c000020, &resolution), 0); + kzt_guest_registry_test_set_alloc_failure_after(-1); + check_int("compact-owner.status", resolution.status, + KZT_OWNER_RESOLVER_RESOLVED); + check_int("compact-owner.match", resolution.owner_match, + KZT_PATCH_OWNER_MATCH); + check_ulong("compact-owner.link-map", + resolution.current_owner.link_map_addr, 0xd000); + kzt_guest_registry_destroy(®istry); +} + int main(void) { test_same_soname_different_ranges_resolves_by_address(); @@ -327,6 +351,7 @@ int main(void) test_native_bridge_address_is_not_expected_owner_input(); test_ambiguous_range_keeps_owner_unknown(); test_dead_objects_do_not_resolve_owner(); + test_unique_owner_resolution_does_not_need_snapshot_allocation(); if (failures) { fprintf(stderr, "kzt-owner-resolver: %d failure(s)\n", failures); diff --git a/tests/unit/kzt/test_wi382_registry_observation_source_contract.py b/tests/unit/kzt/test_wi382_registry_observation_source_contract.py index 360b190ef60..55e32c6775d 100644 --- a/tests/unit/kzt/test_wi382_registry_observation_source_contract.py +++ b/tests/unit/kzt/test_wi382_registry_observation_source_contract.py @@ -36,41 +36,22 @@ def function_body(text: str, signature: str) -> str: root / "target/i386/latx/context/kzt_jump_slot_production.c" ).read_text() -# The old callback must reject NULL identity and path before dereferencing. -legacy = function_body(myalign, "static int kzt_tb_callback_legacy(") -identity_guard = legacy.index("if (!my_lm)") -first_dereference = legacy.index("my_lm->") -path_guard = legacy.index("if (!my_lm->l_name)") +# The KZT callback materializes only the wrapper binding under the callback +# gate. It does not reconstruct an ELF object or derive private glibc data. +materialize = function_body( + myalign, "static int kzt_tb_callback_materialize_binding(" +) +identity_guard = materialize.index("if (!link_map") +first_dereference = materialize.index("link_map->") assert identity_guard < first_dereference -assert path_guard == legacy.index("my_lm->l_name") - len("if (!") - -# Public link_map evidence plus ELF PT_LOAD ranges replace glibc-private -# l_ns/l_map_start/l_map_end offsets. Startup publishes the exact pair while -# the callback gate is still held; dlopen keeps its scoped publish path. -assert "l_map_start" not in legacy -assert "l_map_end" not in legacy -assert "->l_ns" not in legacy -assert "GetElfLoadRange(" in legacy -assert "if (lib && !loader_scope_active &&" in legacy -assert "kzt_guest_library_note_loader_pair(" in legacy - -# A malformed ELF or either relocation failure must leave the startup pair -# unpublished. The final publish is reached only through the shared success -# condition after both failure-returning calls. -header_load = legacy.index("h = LoadAndCheckElfHeader(") -header_guard = legacy.index("if (!h)", header_load) -header_use = legacy.index("ElfHeadReFix(h", header_load) -assert header_load < header_guard < header_use -relocate = legacy.index("RelocateElf(", header_use) -relocate_plt = legacy.index("RelocateElfPlt(", relocate) -publish = legacy.index("kzt_guest_library_note_loader_pair(", relocate_plt) -assert "relocate_result" in legacy[relocate:publish] -assert "relocate_plt_result" in legacy[relocate:publish] -success_condition = legacy.rfind("if (", relocate_plt, publish) -assert success_condition >= 0 -condition = legacy[success_condition:publish] -assert "relocate_result == 0" in condition -assert "relocate_plt_result == 0" in condition +assert "l_map_start" not in materialize +assert "l_map_end" not in materialize +assert "->l_ns" not in materialize +assert "AddNeededLibWithLibrary(" in materialize +assert "kzt_guest_library_note_loader_pair_pending(" in materialize +assert "kzt_guest_library_note_loader_pair(" in materialize +for forbidden in ("LoadAndCheckElfHeader", "LoadNeededLibs", "RelocateElf"): + assert forbidden not in materialize plt_observation = function_body(elfloader, "static void kzt_observe_plt_source(") assert "kzt_elfloader_head_identity(elf_header" in plt_observation @@ -82,6 +63,7 @@ def function_body(text: str, signature: str) -> str: assert "GetElfLoadRange(" in plt_observation assert ".namespace_id_present = main_namespace == 1" in plt_observation assert ".map_range_present = range_available" in plt_observation +assert "request.reuse_complete_dynamic_view = 1" in plt_observation relocate_plt = function_body(elfloader, "int RelocateElfPlt(") assert "uintptr_t kzt_evidence_got = head->pltgot" in relocate_plt assert "kzt_evidence_got_runtime + 8" in relocate_plt @@ -91,29 +73,55 @@ def function_body(text: str, signature: str) -> str: "head->had_RelocateElfPlt = 1" ) -# Production diagnostics are linked, opt-in, and avoid a configure lock when off. +# The event hook publishes only the exact link_map event. The consumer owns +# identity/namespace validation before it enters the observation adapter. assert "'kzt_guest_dynamic_diagnostics.c'" in production_meson +consumer = function_body(myalign, "static void kzt_tb_callback_consume(") +assert "kzt_main_elf_identity(elf_header" in consumer +assert "info1.pt_dynamic_addr" not in consumer +assert "kzt_guest_link_map_read_predecessor(" in consumer +assert "kzt_guest_registry_context_confirm_main_namespace_head(" in consumer +assert "int diagnostics_enabled = kzt_registry_diagnostics_enabled();" in consumer +assert ".enabled = diagnostics_enabled" in consumer +assert ".diagnostics_enabled = diagnostics_enabled" in consumer +assert ".reuse_complete_dynamic_view = 1" in consumer +assert ".legacy_flow = NULL" in consumer +assert "if (registry && diagnostics_enabled)" in consumer + callback = function_body(myalign, "static void kzt_tb_callback(") -assert "kzt_main_elf_identity(elf_header" in callback -assert "info1.pt_dynamic_addr" not in callback -assert "kzt_guest_link_map_read_predecessor(" in callback -assert "kzt_guest_registry_context_confirm_main_namespace_head(" in callback -assert "int diagnostics_enabled = kzt_registry_diagnostics_enabled();" in callback -assert ".enabled = diagnostics_enabled" in callback -assert ".diagnostics_enabled = diagnostics_enabled" in callback -assert "if (registry && diagnostics_enabled)" in callback +assert "kzt_loader_event_hook_publish(" in callback +assert "kzt_tb_callback_consume(env, &event);" in callback +for forbidden in ( + "LoadAndCheckElfHeader", "LoadNeededLibs", "RelocateElf", + "kzt_main_elf_identity", "kzt_observe_guest_object", + "kzt_production_jump_slot_route", +): + assert forbidden not in callback compare = function_body(adapter, "static void kzt_adapter_compare_dynamic_views(") assert compare.index("!request->diagnostics_enabled") < compare.index( "kzt_guest_registry_find_dynamic_view(" ) assert "existing_result.unknown_tag_count = existing_view.unknown_tag_count;" in compare +callback_adapter = function_body( + adapter, "int kzt_observe_guest_object_from_callback(" +) +assert "kzt_guest_registry_supplement_map_range(" in callback_adapter +assert callback_adapter.count("kzt_observe_guest_object(") == 1 +assert "supplemental" not in callback_adapter # Context ownership is represented as one state object with an atomic hot path. assert "kzt_guest_registry_context_t kzt_guest_registry_context;" in context_header assert "__atomic_load_n(&context->state" in registry_context assert "kzt_guest_registry_context_destroy(" in registry_context assert "registry->" not in registry_context +main_namespace_evidence = function_body( + registry_context, + "int kzt_guest_registry_context_has_main_namespace_evidence(", +) +assert "kzt_guest_registry_matches_live_identity(" in main_namespace_evidence +assert "kzt_guest_registry_find_by_link_map(" not in main_namespace_evidence +assert "kzt_guest_object_snapshot_free(" not in main_namespace_evidence # Early fail-open remains observable when enrichment stops before a full # planner/writer diagnostic can be built, but only under the diagnostics gate. From b1fe7418f8814b00b51aea9a4c2844565302b285 Mon Sep 17 00:00:00 2001 From: Hanlu Li Date: Fri, 31 Jul 2026 22:00:11 +0800 Subject: [PATCH 3/8] LATX, KZT: add loader binding lifecycle primitives Add context-local loader quiescence readers and writers, exact binding lifetime gates, lazy prebind epochs, and loader lifecycle event publication. Replace fixed lifecycle snapshot capacity with inline storage that grows only on loader event paths and reports allocation or overflow failures.\n\nCover teardown, writer admission, prebind leases, hook failure paths, and 1025-object lifecycle generation rollover with white-box tests. Signed-off-by: Hanlu Li --- .../latx/context/kzt_guest_library_binding.c | 503 ++++++++++- .../latx/context/kzt_lazy_prebind_scope.c | 556 ++++++++++++ .../latx/context/kzt_lifecycle_diagnostics.c | 152 ++++ .../i386/latx/context/kzt_loader_event_hook.c | 758 ++++++++++++++++ .../context/kzt_loader_lifecycle_snapshot.c | 311 +++++++ .../latx/include/kzt_guest_library_binding.h | 58 ++ .../latx/include/kzt_guest_scope_layout.h | 10 + .../latx/include/kzt_lazy_prebind_scope.h | 113 +++ .../latx/include/kzt_lifecycle_diagnostics.h | 28 + .../latx/include/kzt_loader_callback_scope.h | 1 + .../i386/latx/include/kzt_loader_event_hook.h | 131 +++ .../include/kzt_loader_lifecycle_snapshot.h | 52 ++ tests/unit/kzt/test_guest_library_binding.c | 826 ++++++++++++++++++ tests/unit/kzt/test_lazy_prebind_scope.c | 314 +++++++ .../kzt/test_lifecycle_snapshot_capacity.c | 341 ++++++++ tests/unit/kzt/test_loader_event_hook.c | 347 ++++++++ 16 files changed, 4485 insertions(+), 16 deletions(-) create mode 100644 target/i386/latx/context/kzt_lazy_prebind_scope.c create mode 100644 target/i386/latx/context/kzt_lifecycle_diagnostics.c create mode 100644 target/i386/latx/context/kzt_loader_event_hook.c create mode 100644 target/i386/latx/context/kzt_loader_lifecycle_snapshot.c create mode 100644 target/i386/latx/include/kzt_guest_scope_layout.h create mode 100644 target/i386/latx/include/kzt_lazy_prebind_scope.h create mode 100644 target/i386/latx/include/kzt_lifecycle_diagnostics.h create mode 100644 target/i386/latx/include/kzt_loader_event_hook.h create mode 100644 target/i386/latx/include/kzt_loader_lifecycle_snapshot.h create mode 100644 tests/unit/kzt/test_lazy_prebind_scope.c create mode 100644 tests/unit/kzt/test_lifecycle_snapshot_capacity.c create mode 100644 tests/unit/kzt/test_loader_event_hook.c diff --git a/target/i386/latx/context/kzt_guest_library_binding.c b/target/i386/latx/context/kzt_guest_library_binding.c index 41d25dd73d4..8b6c08657d7 100644 --- a/target/i386/latx/context/kzt_guest_library_binding.c +++ b/target/i386/latx/context/kzt_guest_library_binding.c @@ -7,6 +7,19 @@ #include #include "kzt_guest_registry.h" +#include "kzt_lifecycle_diagnostics.h" + +#define KZT_GUEST_LIBRARY_SYMBOL_CACHE_SLOTS 16 +#define KZT_GUEST_LIBRARY_SYMBOL_NAME_LIMIT 128 + +typedef struct kzt_guest_library_symbol_evidence { + char symbol[KZT_GUEST_LIBRARY_SYMBOL_NAME_LIMIT]; + uintptr_t runtime_address; + unsigned long age; + unsigned long dynamic_revision; + unsigned char symbol_type; + int valid; +} kzt_guest_library_symbol_evidence_t; typedef struct kzt_guest_library_binding_entry { kzt_guest_library_binding_key_t key; @@ -15,6 +28,9 @@ typedef struct kzt_guest_library_binding_entry { kzt_guest_library_binding_state_t state; unsigned int references; int retire_started; + unsigned long symbol_cache_age; + kzt_guest_library_symbol_evidence_t symbol_cache[ + KZT_GUEST_LIBRARY_SYMBOL_CACHE_SLOTS]; } kzt_guest_library_binding_entry_t; typedef struct kzt_guest_library_lifecycle { @@ -104,6 +120,11 @@ struct kzt_guest_library_bindings { kzt_guest_library_callback_gate_t *callback_gates; unsigned int fallback_callback_readers; kzt_guest_library_loader_state_t *loader_state; + unsigned long loader_quiescence_epoch; + kzt_guest_library_loader_quiescence_lease_t *loader_quiescence_leases; + kzt_guest_library_loader_quiescence_writer_t *loader_quiescence_writers; + unsigned int loader_quiescence_readers; + unsigned int loader_quiescence_waiters; struct { unsigned long registry_missing; unsigned long retire_unprovable; @@ -306,6 +327,16 @@ static kzt_guest_library_loader_attempt_t *find_loader_attempt_locked( return NULL; } +static int loader_scope_active_locked( + const kzt_guest_library_bindings_t *bindings) +{ + if (!bindings->loader_state) return 0; + for (size_t i = 0; i < KZT_LOADER_ATTEMPT_SLOTS; ++i) + if (bindings->loader_state->attempts[i].active) + return 1; + return 0; +} + static kzt_guest_library_loader_attempt_t *loader_scope_valid_locked( kzt_guest_library_bindings_t *bindings, const kzt_guest_library_loader_scope_t *scope) @@ -481,6 +512,16 @@ static kzt_guest_library_binding_result_t bind_locked( ? KZT_GUEST_LIBRARY_BINDING_UNCHANGED : KZT_GUEST_LIBRARY_BINDING_CONFLICT; } + if (object_type == KZT_GUEST_LIBRARY_OBJECT_WRAPPED) { + for (size_t i = 0; i < bindings->count; ++i) { + kzt_guest_library_binding_entry_t *entry = &bindings->entries[i]; + + if (entry->state == KZT_GUEST_LIBRARY_BINDING_LIVE && + entry->library == library && !same_key(&entry->key, key)) { + return KZT_GUEST_LIBRARY_BINDING_CONFLICT; + } + } + } if (bindings->count == bindings->capacity && grow_array((void **)&bindings->entries, &bindings->capacity, sizeof(*bindings->entries)) != 0) @@ -518,6 +559,7 @@ void kzt_guest_library_bindings_begin_teardown( if (!bindings) return; pthread_mutex_lock(&bindings->lock); __atomic_store_n(&bindings->shutting_down, 1, __ATOMIC_RELEASE); + pthread_cond_broadcast(&bindings->idle); for (size_t i = 0; i < bindings->pending_count; ++i) bindings->pending[i].active = 0; for (size_t i = 0; i < bindings->count; ++i) @@ -535,6 +577,9 @@ void kzt_guest_library_bindings_begin_teardown( gate; gate = gate->next) busy |= (__atomic_load_n(&gate->state, __ATOMIC_ACQUIRE) & KZT_CALLBACK_GATE_READERS) != 0; + busy |= bindings->loader_quiescence_readers != 0; + busy |= bindings->loader_quiescence_waiters != 0; + busy |= loader_scope_active_locked(bindings); if (!busy) break; pthread_cond_wait(&bindings->idle, &bindings->lock); } @@ -664,6 +709,15 @@ int kzt_guest_library_loader_scope_begin( if (scope) memset(scope, 0, sizeof(*scope)); if (!bindings || !scope) return -1; pthread_mutex_lock(&bindings->lock); + if (!shutting_down(bindings) && + bindings->loader_quiescence_readers) { + ++bindings->loader_quiescence_waiters; + while (!shutting_down(bindings) && + bindings->loader_quiescence_readers) + pthread_cond_wait(&bindings->idle, &bindings->lock); + --bindings->loader_quiescence_waiters; + pthread_cond_broadcast(&bindings->idle); + } if (!shutting_down(bindings) && (!bindings->loader_state || bindings->loader_state->epoch != ULONG_MAX)) { kzt_guest_library_loader_attempt_t *slot = NULL; @@ -715,11 +769,135 @@ void kzt_guest_library_loader_scope_end( attempt->active = 0; else memset(attempt, 0, sizeof(*attempt)); + pthread_cond_broadcast(&bindings->idle); } pthread_mutex_unlock(&bindings->lock); memset(scope, 0, sizeof(*scope)); } +int kzt_guest_library_loader_quiescence_try_acquire( + kzt_guest_library_bindings_t *bindings, + kzt_guest_library_loader_quiescence_lease_t *lease) +{ + int result = -1; + + if (lease) memset(lease, 0, sizeof(*lease)); + if (!bindings || !lease) return -1; + pthread_mutex_lock(&bindings->lock); + if (!shutting_down(bindings) && + !bindings->loader_quiescence_waiters && + !loader_scope_active_locked(bindings) && + bindings->loader_quiescence_epoch != ULONG_MAX && + bindings->loader_quiescence_readers != UINT_MAX) { + unsigned long identity = ++bindings->loader_quiescence_epoch; + unsigned long cookie = + identity ^ (unsigned long)(uintptr_t)bindings; + + if (!cookie) cookie = ~identity; + ++bindings->loader_quiescence_readers; + lease->bindings = bindings; + lease->cookie = cookie; + lease->next = bindings->loader_quiescence_leases; + bindings->loader_quiescence_leases = lease; + result = 0; + } + pthread_mutex_unlock(&bindings->lock); + return result; +} + +void kzt_guest_library_loader_quiescence_release( + kzt_guest_library_loader_quiescence_lease_t *lease) +{ + kzt_guest_library_bindings_t *bindings; + kzt_guest_library_loader_quiescence_lease_t **cursor; + + if (!lease || !(bindings = lease->bindings)) return; + pthread_mutex_lock(&bindings->lock); + cursor = &bindings->loader_quiescence_leases; + while (*cursor && *cursor != lease) + cursor = &(*cursor)->next; + if (*cursor == lease && lease->cookie && + bindings->loader_quiescence_readers) { + *cursor = lease->next; + if (--bindings->loader_quiescence_readers == 0) + pthread_cond_broadcast(&bindings->idle); + } + pthread_mutex_unlock(&bindings->lock); + memset(lease, 0, sizeof(*lease)); +} + +int kzt_guest_library_loader_quiescence_writer_begin( + kzt_guest_library_bindings_t *bindings, + kzt_guest_library_loader_quiescence_writer_t *writer) +{ + kzt_guest_library_loader_quiescence_writer_t **cursor; + unsigned long identity; + unsigned long cookie; + int result = -1; + + if (writer) memset(writer, 0, sizeof(*writer)); + if (!bindings || !writer) return -1; + pthread_mutex_lock(&bindings->lock); + if (shutting_down(bindings) || + bindings->loader_quiescence_epoch == ULONG_MAX || + bindings->loader_quiescence_waiters == UINT_MAX) { + goto out; + } + identity = ++bindings->loader_quiescence_epoch; + cookie = + identity ^ (unsigned long)(uintptr_t)bindings ^ + (unsigned long)(uintptr_t)writer; + + if (!cookie) cookie = ~identity; + if (!cookie) cookie = 1; + writer->bindings = bindings; + writer->cookie = cookie; + writer->next = bindings->loader_quiescence_writers; + bindings->loader_quiescence_writers = writer; + ++bindings->loader_quiescence_waiters; + while (!shutting_down(bindings) && + bindings->loader_quiescence_readers) { + pthread_cond_wait(&bindings->idle, &bindings->lock); + } + if (!shutting_down(bindings)) { + result = 0; + } else { + cursor = &bindings->loader_quiescence_writers; + while (*cursor && *cursor != writer) + cursor = &(*cursor)->next; + if (*cursor == writer && bindings->loader_quiescence_waiters) { + *cursor = writer->next; + --bindings->loader_quiescence_waiters; + pthread_cond_broadcast(&bindings->idle); + } + memset(writer, 0, sizeof(*writer)); + } +out: + pthread_mutex_unlock(&bindings->lock); + return result; +} + +void kzt_guest_library_loader_quiescence_writer_end( + kzt_guest_library_loader_quiescence_writer_t *writer) +{ + kzt_guest_library_bindings_t *bindings; + kzt_guest_library_loader_quiescence_writer_t **cursor; + + if (!writer || !(bindings = writer->bindings)) return; + pthread_mutex_lock(&bindings->lock); + cursor = &bindings->loader_quiescence_writers; + while (*cursor && *cursor != writer) + cursor = &(*cursor)->next; + if (*cursor == writer && writer->cookie && + bindings->loader_quiescence_waiters) { + *cursor = writer->next; + --bindings->loader_quiescence_waiters; + pthread_cond_broadcast(&bindings->idle); + } + pthread_mutex_unlock(&bindings->lock); + memset(writer, 0, sizeof(*writer)); +} + static int callback_access_begin( kzt_guest_library_bindings_t *bindings, uintptr_t link_map_addr, const kzt_guest_library_loader_scope_t *scope, @@ -1328,6 +1506,175 @@ int kzt_guest_library_access_lookup( return result; } +static int lookup_bindings_by_library( + kzt_guest_library_bindings_t *bindings, library_t *library, + kzt_guest_library_binding_key_t *key, + kzt_guest_library_handle_t *handle) +{ + kzt_guest_library_binding_entry_t *match = NULL; + size_t match_index = 0; + int result = -1; + + if (key) memset(key, 0, sizeof(*key)); + if (handle) memset(handle, 0, sizeof(*handle)); + if (!bindings || !library || !key || !handle) return -1; + + pthread_mutex_lock(&bindings->lock); + if (shutting_down(bindings)) goto out; + + kzt_guest_library_lifecycle_t *lifecycle = + find_lifecycle_locked(bindings, library); + if (!lifecycle || lifecycle->state != KZT_GUEST_LIBRARY_BINDING_LIVE) + goto out; + + for (size_t i = 0; i < bindings->count; ++i) { + kzt_guest_library_binding_entry_t *entry = &bindings->entries[i]; + if (entry->library != library || + entry->state != KZT_GUEST_LIBRARY_BINDING_LIVE) + continue; + if (!supported_key(&entry->key) || match) + goto out; + match = entry; + match_index = i; + } + if (!match) goto out; + + ++match->references; + *key = match->key; + handle->bindings = bindings; + handle->entry = (void *)(uintptr_t)(match_index + 1); + handle->library = match->library; + handle->object_type = match->object_type; + result = 0; +out: + pthread_mutex_unlock(&bindings->lock); + return result; +} + +int kzt_guest_library_access_lookup_by_library( + kzt_guest_library_access_t *access, library_t *library, + kzt_guest_library_binding_key_t *key, + kzt_guest_library_handle_t *handle) +{ + int result = -1; + if (key) memset(key, 0, sizeof(*key)); + if (handle) memset(handle, 0, sizeof(*handle)); + if (!access || !access->initialized || !library || !key || !handle) + return -1; + pthread_mutex_lock(&access->lock); + if (access->accepting && access->bindings) + result = lookup_bindings_by_library( + access->bindings, library, key, handle); + pthread_mutex_unlock(&access->lock); + return result; +} + +static kzt_guest_library_binding_entry_t * +kzt_guest_library_handle_entry_locked( + kzt_guest_library_bindings_t *bindings, + const kzt_guest_library_handle_t *handle) +{ + size_t index; + kzt_guest_library_binding_entry_t *entry; + + if (!bindings || !handle || handle->bindings != bindings || + !handle->entry || !handle->library) { + return NULL; + } + index = (size_t)(uintptr_t)handle->entry - 1; + if (index >= bindings->count) return NULL; + entry = &bindings->entries[index]; + if (!entry->references || entry->library != handle->library || + entry->object_type != handle->object_type || + entry->state != KZT_GUEST_LIBRARY_BINDING_LIVE) { + return NULL; + } + return entry; +} + +int kzt_guest_library_symbol_evidence_lookup( + const kzt_guest_library_handle_t *handle, const char *symbol, + unsigned long dynamic_revision, uintptr_t *runtime_address, + unsigned char *symbol_type) +{ + kzt_guest_library_bindings_t *bindings; + kzt_guest_library_binding_entry_t *entry; + size_t i; + int result = -1; + + if (runtime_address) *runtime_address = 0; + if (symbol_type) *symbol_type = 0; + if (!handle || !(bindings = handle->bindings) || !symbol || !symbol[0] || + !dynamic_revision || !runtime_address || !symbol_type) { + return -1; + } + pthread_mutex_lock(&bindings->lock); + entry = kzt_guest_library_handle_entry_locked(bindings, handle); + if (!entry || shutting_down(bindings)) goto out; + for (i = 0; i < KZT_GUEST_LIBRARY_SYMBOL_CACHE_SLOTS; ++i) { + kzt_guest_library_symbol_evidence_t *cached = + &entry->symbol_cache[i]; + + if (cached->valid && + cached->dynamic_revision == dynamic_revision && + strcmp(cached->symbol, symbol) == 0) { + cached->age = ++entry->symbol_cache_age; + *runtime_address = cached->runtime_address; + *symbol_type = cached->symbol_type; + result = 0; + break; + } + } +out: + pthread_mutex_unlock(&bindings->lock); + return result; +} + +void kzt_guest_library_symbol_evidence_store( + const kzt_guest_library_handle_t *handle, const char *symbol, + unsigned long dynamic_revision, uintptr_t runtime_address, + unsigned char symbol_type) +{ + kzt_guest_library_bindings_t *bindings; + kzt_guest_library_binding_entry_t *entry; + kzt_guest_library_symbol_evidence_t *selected = NULL; + size_t symbol_length; + size_t i; + + if (!handle || !(bindings = handle->bindings) || !symbol || + !(symbol_length = strlen(symbol)) || + symbol_length >= KZT_GUEST_LIBRARY_SYMBOL_NAME_LIMIT || + !dynamic_revision || + !runtime_address) { + return; + } + pthread_mutex_lock(&bindings->lock); + entry = kzt_guest_library_handle_entry_locked(bindings, handle); + if (!entry || shutting_down(bindings)) goto out; + for (i = 0; i < KZT_GUEST_LIBRARY_SYMBOL_CACHE_SLOTS; ++i) { + kzt_guest_library_symbol_evidence_t *cached = + &entry->symbol_cache[i]; + + if (cached->valid && strcmp(cached->symbol, symbol) == 0) { + selected = cached; + break; + } + if (!selected || !cached->valid || cached->age < selected->age) { + selected = cached; + } + } + if (selected) { + memcpy(selected->symbol, symbol, symbol_length + 1); + selected->runtime_address = runtime_address; + selected->symbol_type = symbol_type; + selected->dynamic_revision = dynamic_revision; + selected->age = ++entry->symbol_cache_age; + selected->valid = 1; + } +out: + pthread_mutex_unlock(&bindings->lock); +} + void kzt_guest_library_handle_release(kzt_guest_library_handle_t *handle) { kzt_guest_library_bindings_t *bindings; @@ -1344,6 +1691,79 @@ void kzt_guest_library_handle_release(kzt_guest_library_handle_t *handle) memset(handle, 0, sizeof(*handle)); } +int kzt_guest_library_cleanup_exact_handle( + kzt_guest_library_handle_t *handle, + kzt_guest_library_exact_cleanup_fn cleanup, + void *opaque) +{ + kzt_guest_library_bindings_t *bindings; + kzt_guest_library_binding_entry_t *entry; + kzt_guest_library_lifecycle_t *lifecycle; + library_t *library; + size_t index; + size_t i; + + if (!handle || !(bindings = handle->bindings) || !handle->entry || + !handle->library) { + return -1; + } + pthread_mutex_lock(&bindings->lock); + index = (size_t)(uintptr_t)handle->entry - 1; + if (index >= bindings->count) { + pthread_mutex_unlock(&bindings->lock); + return -1; + } + entry = &bindings->entries[index]; + library = handle->library; + lifecycle = find_lifecycle_locked(bindings, library); + if (entry->library != library || !entry->references || + entry->state != KZT_GUEST_LIBRARY_BINDING_LIVE || !lifecycle || + lifecycle->state != KZT_GUEST_LIBRARY_BINDING_LIVE) { + pthread_mutex_unlock(&bindings->lock); + return -1; + } + for (i = 0; i < bindings->count; ++i) { + if (i != index && bindings->entries[i].library == library && + bindings->entries[i].state == KZT_GUEST_LIBRARY_BINDING_LIVE && + !same_key(&bindings->entries[i].key, &entry->key)) { + pthread_mutex_unlock(&bindings->lock); + return -1; + } + } + + lifecycle->state = KZT_GUEST_LIBRARY_BINDING_UNLOADING; + entry->state = KZT_GUEST_LIBRARY_BINDING_UNLOADING; + close_callback_addr_locked(bindings, lifecycle, library, + entry->key.link_map_addr); + for (i = 0; i < bindings->pending_count; ++i) { + if (bindings->pending[i].library == library && + bindings->pending[i].link_map_addr == + entry->key.link_map_addr) { + bindings->pending[i].active = 0; + } + } + for (i = 0; i < bindings->observed_count; ++i) { + if (same_key(&bindings->observed[i].key, &entry->key)) { + memset(&bindings->observed[i], 0, + sizeof(bindings->observed[i])); + } + } + + --entry->references; + memset(handle, 0, sizeof(*handle)); + while (entry->references) { + pthread_cond_wait(&bindings->idle, &bindings->lock); + } + if (cleanup) { + cleanup(library, opaque); + } + entry->state = KZT_GUEST_LIBRARY_BINDING_DEAD; + lifecycle->state = KZT_GUEST_LIBRARY_BINDING_DEAD; + pthread_cond_broadcast(&bindings->idle); + pthread_mutex_unlock(&bindings->lock); + return 0; +} + static void unload_library(kzt_guest_library_bindings_t *bindings, kzt_guest_registry_t *registry, library_t *library, @@ -1352,7 +1772,12 @@ static void unload_library(kzt_guest_library_bindings_t *bindings, kzt_guest_library_lifecycle_t *lifecycle; int owns_lifecycle = 0; int waits_for_lifecycle = 0; + uint64_t timing_start = 0; + uint64_t retire_ns = 0; if (!bindings || !library) return; + if (kzt_lifecycle_diagnostics_enabled()) { + timing_start = kzt_lifecycle_diagnostics_now(); + } pthread_mutex_lock(&bindings->lock); /* Phase 1 closes every binding-side path without allocation. Only the @@ -1462,22 +1887,35 @@ static void unload_library(kzt_guest_library_bindings_t *bindings, pthread_mutex_lock(&bindings->lock); ++bindings->diagnostics.registry_missing; pthread_mutex_unlock(&bindings->lock); - } else if (kzt_guest_registry_retire( - registry, retire_key.link_map_addr, - retire_key.generation) != 0 && - kzt_guest_registry_wait_retired( - registry, retire_key.link_map_addr, - retire_key.generation) != 0) { - /* An exact generation already in UNLOADING is waited to DEAD by - * wait_retired(). Disabled/missing/replaced/unprovable state is - * a new-KZT failure and must leave the legacy process alive. */ - fprintf(stderr, - "KZT binding retire state unprovable: link_map=%p generation=%lu; continuing legacy flow\n", - (void *)retire_key.link_map_addr, - retire_key.generation); - pthread_mutex_lock(&bindings->lock); - ++bindings->diagnostics.retire_unprovable; - pthread_mutex_unlock(&bindings->lock); + } else { + uint64_t retire_start = timing_start + ? kzt_lifecycle_diagnostics_now() + : 0; + int retire_result = kzt_guest_registry_retire( + registry, retire_key.link_map_addr, retire_key.generation); + + if (retire_start) { + uint64_t duration = + kzt_lifecycle_diagnostics_now() - retire_start; + retire_ns += duration; + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_REGISTRY_RETIRE, duration); + } + if (retire_result != 0 && + kzt_guest_registry_wait_retired( + registry, retire_key.link_map_addr, + retire_key.generation) != 0) { + /* An exact generation already in UNLOADING is waited to DEAD + * by wait_retired(). Disabled/missing/replaced/unprovable + * state is a new-KZT failure and keeps legacy flow alive. */ + fprintf(stderr, + "KZT binding retire state unprovable: link_map=%p generation=%lu; continuing legacy flow\n", + (void *)retire_key.link_map_addr, + retire_key.generation); + pthread_mutex_lock(&bindings->lock); + ++bindings->diagnostics.retire_unprovable; + pthread_mutex_unlock(&bindings->lock); + } } pthread_mutex_lock(&bindings->lock); @@ -1532,6 +1970,13 @@ static void unload_library(kzt_guest_library_bindings_t *bindings, pthread_cond_broadcast(&bindings->idle); out: pthread_mutex_unlock(&bindings->lock); + if (timing_start) { + uint64_t duration = kzt_lifecycle_diagnostics_now() - timing_start; + + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_BINDING_CLEANUP, + duration >= retire_ns ? duration - retire_ns : duration); + } } #ifdef KZT_GUEST_LIBRARY_BINDING_TEST @@ -1579,6 +2024,32 @@ int kzt_guest_library_binding_test_snapshot( pthread_mutex_unlock(&bindings->lock); return lifecycle ? 0 : -1; } + +int kzt_guest_library_binding_test_loader_state( + kzt_guest_library_bindings_t *bindings, + unsigned int *lease_readers, unsigned int *lease_waiters, + unsigned int *active_scopes, int *is_shutting_down) +{ + if (lease_readers) *lease_readers = 0; + if (lease_waiters) *lease_waiters = 0; + if (active_scopes) *active_scopes = 0; + if (is_shutting_down) *is_shutting_down = 0; + if (!bindings) return -1; + pthread_mutex_lock(&bindings->lock); + if (lease_readers) + *lease_readers = bindings->loader_quiescence_readers; + if (lease_waiters) + *lease_waiters = bindings->loader_quiescence_waiters; + if (active_scopes && bindings->loader_state) { + for (size_t i = 0; i < KZT_LOADER_ATTEMPT_SLOTS; ++i) + if (bindings->loader_state->attempts[i].active) + ++*active_scopes; + } + if (is_shutting_down) + *is_shutting_down = shutting_down(bindings); + pthread_mutex_unlock(&bindings->lock); + return 0; +} #endif void kzt_guest_library_unbind(kzt_guest_library_bindings_t *bindings, diff --git a/target/i386/latx/context/kzt_lazy_prebind_scope.c b/target/i386/latx/context/kzt_lazy_prebind_scope.c new file mode 100644 index 00000000000..79fa71335af --- /dev/null +++ b/target/i386/latx/context/kzt_lazy_prebind_scope.c @@ -0,0 +1,556 @@ +#include "kzt_lazy_prebind_scope.h" + +#include +#include +#include + +#include "elf.h" + +typedef struct kzt_lazy_prebind_entry { + kzt_lazy_prebind_record_t record; + unsigned long leases; + int transitioning; + int published; + int retired; + struct kzt_lazy_prebind_entry *next; +} kzt_lazy_prebind_entry_t; + +struct kzt_lazy_prebind_scope { + pthread_mutex_t lock; + pthread_cond_t changed; + uint64_t epoch; + kzt_lazy_prebind_entry_t *entries; +}; + +static int kzt_lazy_prebind_identity_valid( + const kzt_lazy_prebind_identity_t *identity) +{ + return identity && identity->link_map_addr && identity->generation && + identity->namespace_id == 0; +} + +static int kzt_lazy_prebind_identity_equal( + const kzt_lazy_prebind_identity_t *left, + const kzt_lazy_prebind_identity_t *right) +{ + return left && right && left->link_map_addr == right->link_map_addr && + left->generation == right->generation && + left->namespace_id == right->namespace_id; +} + +static int kzt_lazy_prebind_text_valid(const char *text) +{ + return text && text[0] && strnlen(text, KZT_LAZY_PREBIND_TEXT_MAX) < + KZT_LAZY_PREBIND_TEXT_MAX; +} + +static int kzt_lazy_prebind_record_key_valid( + const kzt_lazy_prebind_record_t *record) +{ + return record && kzt_lazy_prebind_identity_valid(&record->source) && + record->slot_addr && record->expected_slot && + kzt_lazy_prebind_text_valid(record->symbol) && + kzt_symbol_version_evidence_valid(record->version_evidence, + record->version); +} + +static int kzt_lazy_prebind_record_valid( + const kzt_lazy_prebind_record_t *record) +{ + return kzt_lazy_prebind_record_key_valid(record) && + kzt_lazy_prebind_identity_valid(&record->provider) && + record->bridge_target && record->bridge_generation && + record->bridge_generation == record->provider.generation && + record->scope_proof.status == KZT_GUEST_SYMBOL_SCOPE_SAFE && + record->scope_proof.reason == + KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER && + record->scope_proof.scope_complete && + record->scope_proof.lookup_order_known && + record->scope_proof.selected_provider_link_map == + record->provider.link_map_addr && + record->scope_proof.selected_provider_address && + record->scope_proof.selected_provider_binding == STB_GLOBAL && + record->scope_proof.selected_provider_type == STT_FUNC && + record->scope_proof.selected_provider_visibility == STV_DEFAULT && + record->scope_proof.scope_identity.source.link_map_addr == + record->source.link_map_addr && + record->scope_proof.scope_identity.source.generation == + record->source.generation && + record->scope_proof.scope_identity.source.namespace_id == + record->source.namespace_id; +} + +static int kzt_lazy_prebind_record_key_equal( + const kzt_lazy_prebind_record_t *left, + const kzt_lazy_prebind_record_t *right) +{ + return kzt_lazy_prebind_identity_equal(&left->source, &right->source) && + left->slot_addr == right->slot_addr && + left->expected_slot == right->expected_slot && + left->relocation_index == right->relocation_index && + left->version_evidence == right->version_evidence && + strcmp(left->symbol, right->symbol) == 0 && + strcmp(left->version, right->version) == 0; +} + +static int kzt_lazy_prebind_record_equal( + const kzt_lazy_prebind_record_t *left, + const kzt_lazy_prebind_record_t *right) +{ + return kzt_lazy_prebind_record_key_equal(left, right) && + kzt_lazy_prebind_identity_equal(&left->provider, + &right->provider) && + left->bridge_target == right->bridge_target && + left->bridge_generation == right->bridge_generation && + left->scope_proof.selected_provider_address == + right->scope_proof.selected_provider_address && + left->scope_proof.selected_provider_binding == + right->scope_proof.selected_provider_binding && + left->scope_proof.selected_provider_type == + right->scope_proof.selected_provider_type && + left->scope_proof.selected_provider_visibility == + right->scope_proof.selected_provider_visibility && + left->scope_proof.query_fingerprint == + right->scope_proof.query_fingerprint && + memcmp(&left->scope_proof.scope_identity, + &right->scope_proof.scope_identity, + sizeof(left->scope_proof.scope_identity)) == 0; +} + +static int kzt_lazy_prebind_entry_matches_request( + const kzt_lazy_prebind_entry_t *entry, + const kzt_lazy_prebind_record_t *expected, uint64_t epoch) +{ + if (!entry || !expected || entry->retired || entry->transitioning || + entry->record.scope_epoch != epoch || + !kzt_lazy_prebind_record_key_equal(&entry->record, expected)) { + return 0; + } + if (expected->provider.link_map_addr && + !kzt_lazy_prebind_identity_equal(&entry->record.provider, + &expected->provider)) { + return 0; + } + if (expected->bridge_target && + (entry->record.bridge_target != expected->bridge_target || + entry->record.bridge_generation != expected->bridge_generation)) { + return 0; + } + return 1; +} + +static int kzt_lazy_prebind_entry_matches_identity( + const kzt_lazy_prebind_entry_t *entry, + const kzt_lazy_prebind_identity_t *identity) +{ + return !identity || + kzt_lazy_prebind_identity_equal(&entry->record.source, identity) || + kzt_lazy_prebind_identity_equal(&entry->record.provider, identity); +} + +static void kzt_lazy_prebind_scope_release_entry_locked( + kzt_lazy_prebind_scope_t *scope, kzt_lazy_prebind_entry_t *entry) +{ + if (!scope || !entry || !entry->leases) { + return; + } + --entry->leases; + if (!entry->leases) { + pthread_cond_broadcast(&scope->changed); + } +} + +static void kzt_lazy_prebind_scope_wait_all_leases_locked( + kzt_lazy_prebind_scope_t *scope) +{ + for (;;) { + kzt_lazy_prebind_entry_t *entry; + unsigned long leases = 0; + + for (entry = scope->entries; entry; entry = entry->next) { + leases += entry->leases; + } + if (!leases) { + return; + } + pthread_cond_wait(&scope->changed, &scope->lock); + } +} + +static void kzt_lazy_prebind_scope_prune_closed_locked( + kzt_lazy_prebind_scope_t *scope) +{ + kzt_lazy_prebind_entry_t **cursor; + + if (!scope) { + return; + } + cursor = &scope->entries; + while (*cursor) { + kzt_lazy_prebind_entry_t *entry = *cursor; + + if (!entry->leases && !entry->transitioning && !entry->published && + (entry->retired || entry->record.scope_epoch != scope->epoch)) { + *cursor = entry->next; + free(entry); + continue; + } + cursor = &entry->next; + } +} + +kzt_lazy_prebind_scope_t *kzt_lazy_prebind_scope_init(void) +{ + kzt_lazy_prebind_scope_t *scope = calloc(1, sizeof(*scope)); + + if (!scope) { + return NULL; + } + if (pthread_mutex_init(&scope->lock, NULL) != 0) { + free(scope); + return NULL; + } + if (pthread_cond_init(&scope->changed, NULL) != 0) { + pthread_mutex_destroy(&scope->lock); + free(scope); + return NULL; + } + scope->epoch = 1; + return scope; +} + +void kzt_lazy_prebind_scope_destroy(kzt_lazy_prebind_scope_t **scope_ptr) +{ + kzt_lazy_prebind_scope_t *scope; + kzt_lazy_prebind_entry_t *entry; + + if (!scope_ptr || !(scope = *scope_ptr)) { + return; + } + pthread_mutex_lock(&scope->lock); + for (entry = scope->entries; entry; entry = entry->next) { + entry->retired = 1; + } + kzt_lazy_prebind_scope_wait_all_leases_locked(scope); + entry = scope->entries; + scope->entries = NULL; + pthread_mutex_unlock(&scope->lock); + while (entry) { + kzt_lazy_prebind_entry_t *next = entry->next; + + free(entry); + entry = next; + } + pthread_cond_destroy(&scope->changed); + pthread_mutex_destroy(&scope->lock); + free(scope); + *scope_ptr = NULL; +} + +uint64_t kzt_lazy_prebind_scope_epoch(kzt_lazy_prebind_scope_t *scope) +{ + uint64_t epoch = 0; + + if (!scope) { + return 0; + } + pthread_mutex_lock(&scope->lock); + epoch = scope->epoch; + pthread_mutex_unlock(&scope->lock); + return epoch; +} + +uint64_t kzt_lazy_prebind_scope_mutate( + kzt_lazy_prebind_scope_t *scope, kzt_lazy_prebind_mutation_t mutation) +{ + kzt_lazy_prebind_entry_t *entry; + + (void)mutation; + if (!scope) { + return 0; + } + pthread_mutex_lock(&scope->lock); + if (scope->epoch == UINT64_MAX) { + scope->epoch = 1; + for (entry = scope->entries; entry; entry = entry->next) { + entry->retired = 1; + } + } else { + ++scope->epoch; + } + kzt_lazy_prebind_scope_wait_all_leases_locked(scope); + kzt_lazy_prebind_scope_prune_closed_locked(scope); + pthread_cond_broadcast(&scope->changed); + pthread_mutex_unlock(&scope->lock); + return kzt_lazy_prebind_scope_epoch(scope); +} + +kzt_lazy_prebind_claim_result_t kzt_lazy_prebind_scope_claim( + kzt_lazy_prebind_scope_t *scope, + const kzt_lazy_prebind_record_t *record) +{ + kzt_lazy_prebind_entry_t *entry; + kzt_lazy_prebind_entry_t *created; + + if (!scope || !kzt_lazy_prebind_record_valid(record)) { + return KZT_LAZY_PREBIND_CLAIM_FAIL_OPEN; + } + pthread_mutex_lock(&scope->lock); + for (entry = scope->entries; entry; entry = entry->next) { + if (!kzt_lazy_prebind_record_key_equal(&entry->record, record)) { + continue; + } + if (entry->retired) { + pthread_mutex_unlock(&scope->lock); + return KZT_LAZY_PREBIND_CLAIM_RETIRED; + } + if (entry->record.scope_epoch != scope->epoch) { + continue; + } + pthread_mutex_unlock(&scope->lock); + return kzt_lazy_prebind_record_equal(&entry->record, record) ? + KZT_LAZY_PREBIND_CLAIM_REUSED : + KZT_LAZY_PREBIND_CLAIM_CONFLICT; + } + created = calloc(1, sizeof(*created)); + if (!created) { + pthread_mutex_unlock(&scope->lock); + return KZT_LAZY_PREBIND_CLAIM_FAIL_OPEN; + } + created->record = *record; + created->record.scope_epoch = scope->epoch; + created->next = scope->entries; + scope->entries = created; + pthread_mutex_unlock(&scope->lock); + return KZT_LAZY_PREBIND_CLAIM_CREATED; +} + +int kzt_lazy_prebind_scope_acquire( + kzt_lazy_prebind_scope_t *scope, + const kzt_lazy_prebind_record_t *expected, + kzt_lazy_prebind_lease_t *lease) +{ + kzt_lazy_prebind_entry_t *entry; + + if (lease) { + memset(lease, 0, sizeof(*lease)); + } + if (!scope || !lease || !kzt_lazy_prebind_record_key_valid(expected)) { + return -1; + } + pthread_mutex_lock(&scope->lock); + for (entry = scope->entries; entry; entry = entry->next) { + if (!kzt_lazy_prebind_entry_matches_request(entry, expected, + scope->epoch)) { + continue; + } + ++entry->leases; + lease->scope = scope; + lease->entry = entry; + lease->record = entry->record; + lease->active = 1; + pthread_mutex_unlock(&scope->lock); + return 0; + } + pthread_mutex_unlock(&scope->lock); + return -1; +} + +void kzt_lazy_prebind_scope_release(kzt_lazy_prebind_lease_t *lease) +{ + kzt_lazy_prebind_entry_t *entry; + + if (!lease || !lease->active || !lease->scope || !lease->entry) { + return; + } + if (lease->operation == KZT_LAZY_PREBIND_LEASE_PUBLISH) { + kzt_lazy_prebind_scope_publish_finish(lease, 0); + return; + } + if (lease->operation == KZT_LAZY_PREBIND_LEASE_REVOKE) { + kzt_lazy_prebind_scope_revoke_finish(lease, 0); + return; + } + entry = lease->entry; + pthread_mutex_lock(&lease->scope->lock); + kzt_lazy_prebind_scope_release_entry_locked(lease->scope, entry); + pthread_mutex_unlock(&lease->scope->lock); + memset(lease, 0, sizeof(*lease)); +} + +int kzt_lazy_prebind_scope_publish_acquire( + kzt_lazy_prebind_scope_t *scope, + const kzt_lazy_prebind_record_t *expected, + kzt_lazy_prebind_lease_t *lease) +{ + kzt_lazy_prebind_entry_t *entry; + + if (lease) { + memset(lease, 0, sizeof(*lease)); + } + if (!scope || !lease || !kzt_lazy_prebind_record_valid(expected)) { + return -1; + } + pthread_mutex_lock(&scope->lock); + for (entry = scope->entries; entry; entry = entry->next) { + if (entry->retired || entry->transitioning || entry->published || + entry->leases || entry->record.scope_epoch != scope->epoch || + !kzt_lazy_prebind_record_equal(&entry->record, expected)) { + continue; + } + entry->transitioning = 1; + ++entry->leases; + lease->scope = scope; + lease->entry = entry; + lease->record = entry->record; + lease->operation = KZT_LAZY_PREBIND_LEASE_PUBLISH; + lease->active = 1; + pthread_mutex_unlock(&scope->lock); + return 0; + } + pthread_mutex_unlock(&scope->lock); + return -1; +} + +void kzt_lazy_prebind_scope_publish_finish(kzt_lazy_prebind_lease_t *lease, + int published) +{ + kzt_lazy_prebind_entry_t *entry; + + if (!lease || !lease->active || !lease->scope || !lease->entry || + lease->operation != KZT_LAZY_PREBIND_LEASE_PUBLISH) { + return; + } + entry = lease->entry; + pthread_mutex_lock(&lease->scope->lock); + if (entry->transitioning) { + entry->transitioning = 0; + if (published) { + entry->published = 1; + } + } + kzt_lazy_prebind_scope_release_entry_locked(lease->scope, entry); + kzt_lazy_prebind_scope_prune_closed_locked(lease->scope); + pthread_mutex_unlock(&lease->scope->lock); + memset(lease, 0, sizeof(*lease)); +} + +int kzt_lazy_prebind_scope_revoke_acquire( + kzt_lazy_prebind_scope_t *scope, + const kzt_lazy_prebind_identity_t *identity, + kzt_lazy_prebind_lease_t *lease) +{ + kzt_lazy_prebind_entry_t *entry; + + if (lease) { + memset(lease, 0, sizeof(*lease)); + } + if (!scope || !lease || + (identity && !kzt_lazy_prebind_identity_valid(identity))) { + return -1; + } + pthread_mutex_lock(&scope->lock); + for (entry = scope->entries; entry; entry = entry->next) { + if (!entry->published || entry->transitioning || + (!entry->retired && entry->record.scope_epoch == scope->epoch) || + !kzt_lazy_prebind_entry_matches_identity(entry, identity)) { + continue; + } + entry->transitioning = 1; + ++entry->leases; + lease->scope = scope; + lease->entry = entry; + lease->record = entry->record; + lease->operation = KZT_LAZY_PREBIND_LEASE_REVOKE; + lease->active = 1; + pthread_mutex_unlock(&scope->lock); + return 0; + } + pthread_mutex_unlock(&scope->lock); + return 1; +} + +void kzt_lazy_prebind_scope_revoke_finish(kzt_lazy_prebind_lease_t *lease, + int revoked) +{ + kzt_lazy_prebind_entry_t *entry; + + if (!lease || !lease->active || !lease->scope || !lease->entry || + lease->operation != KZT_LAZY_PREBIND_LEASE_REVOKE) { + return; + } + entry = lease->entry; + pthread_mutex_lock(&lease->scope->lock); + if (entry->transitioning) { + entry->transitioning = 0; + if (revoked) { + entry->published = 0; + } + } + kzt_lazy_prebind_scope_release_entry_locked(lease->scope, entry); + kzt_lazy_prebind_scope_prune_closed_locked(lease->scope); + pthread_mutex_unlock(&lease->scope->lock); + memset(lease, 0, sizeof(*lease)); +} + +int kzt_lazy_prebind_scope_lease_valid( + const kzt_lazy_prebind_lease_t *lease) +{ + const kzt_lazy_prebind_entry_t *entry; + int valid = 0; + + if (!lease || !lease->active || !lease->scope || !lease->entry) { + return 0; + } + entry = lease->entry; + pthread_mutex_lock(&lease->scope->lock); + valid = !entry->retired && entry->leases && + entry->record.scope_epoch == lease->scope->epoch && + entry->record.scope_epoch == lease->record.scope_epoch && + kzt_lazy_prebind_record_equal(&entry->record, &lease->record); + pthread_mutex_unlock(&lease->scope->lock); + return valid; +} + +int kzt_lazy_prebind_scope_retire( + kzt_lazy_prebind_scope_t *scope, + const kzt_lazy_prebind_identity_t *identity) +{ + kzt_lazy_prebind_entry_t *entry; + int found = 0; + + if (!scope || !kzt_lazy_prebind_identity_valid(identity)) { + return -1; + } + pthread_mutex_lock(&scope->lock); + for (entry = scope->entries; entry; entry = entry->next) { + if (kzt_lazy_prebind_identity_equal(&entry->record.source, identity) || + kzt_lazy_prebind_identity_equal(&entry->record.provider, identity)) { + entry->retired = 1; + found = 1; + } + } + if (!found) { + pthread_mutex_unlock(&scope->lock); + return 0; + } + for (;;) { + unsigned long leases = 0; + + for (entry = scope->entries; entry; entry = entry->next) { + if (entry->retired && + (kzt_lazy_prebind_identity_equal(&entry->record.source, + identity) || + kzt_lazy_prebind_identity_equal(&entry->record.provider, + identity))) { + leases += entry->leases; + } + } + if (!leases) { + break; + } + pthread_cond_wait(&scope->changed, &scope->lock); + } + pthread_mutex_unlock(&scope->lock); + return 0; +} diff --git a/target/i386/latx/context/kzt_lifecycle_diagnostics.c b/target/i386/latx/context/kzt_lifecycle_diagnostics.c new file mode 100644 index 00000000000..5a3110e02e2 --- /dev/null +++ b/target/i386/latx/context/kzt_lifecycle_diagnostics.c @@ -0,0 +1,152 @@ +#include "kzt_lifecycle_diagnostics.h" + +#include +#include +#include +#include + +typedef struct kzt_lifecycle_diagnostic_counter { + unsigned long count; + uint64_t total_ns; +} kzt_lifecycle_diagnostic_counter_t; + +static int diagnostics_enabled = -1; +static int diagnostics_reported; +static kzt_lifecycle_diagnostic_counter_t + diagnostics[KZT_LIFECYCLE_STAGE_COUNT]; + +int kzt_lifecycle_diagnostics_enabled(void) +{ + int enabled = __atomic_load_n(&diagnostics_enabled, __ATOMIC_RELAXED); + + if (enabled < 0) { + const char *value = getenv("LATX_KZT_LIFECYCLE_DIAGNOSTICS"); + int detected = value && value[0] && value[0] != '0'; + + if (!__atomic_compare_exchange_n( + &diagnostics_enabled, &enabled, detected, 0, + __ATOMIC_RELAXED, __ATOMIC_RELAXED)) { + detected = enabled; + } + enabled = detected; + } + return enabled; +} + +uint64_t kzt_lifecycle_diagnostics_now(void) +{ + struct timespec value; + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &value) != 0) { + return 0; + } + return (uint64_t)value.tv_sec * 1000000000ULL + + (uint64_t)value.tv_nsec; +} + +void kzt_lifecycle_diagnostics_add( + kzt_lifecycle_diagnostic_stage_t stage, uint64_t duration_ns) +{ + if (stage < 0 || stage >= KZT_LIFECYCLE_STAGE_COUNT || !duration_ns) { + return; + } + __atomic_fetch_add(&diagnostics[stage].count, 1, __ATOMIC_RELAXED); + __atomic_fetch_add( + &diagnostics[stage].total_ns, duration_ns, __ATOMIC_RELAXED); +} + +void kzt_lifecycle_diagnostics_report(void) +{ + if (!kzt_lifecycle_diagnostics_enabled() || + __atomic_exchange_n(&diagnostics_reported, 1, __ATOMIC_RELAXED)) { + return; + } + + fprintf(stderr, + "kzt_lifecycle_summary schema=1 " + "scope_invalidate_count=%lu scope_invalidate_ns=%" PRIu64 " " + "guest_dlopen_count=%lu guest_dlopen_ns=%" PRIu64 " " + "dlopen_finish_count=%lu dlopen_finish_ns=%" PRIu64 " " + "guest_dlclose_count=%lu guest_dlclose_ns=%" PRIu64 " " + "unload_probe_count=%lu unload_probe_ns=%" PRIu64 " " + "registry_retire_count=%lu registry_retire_ns=%" PRIu64 " " + "binding_cleanup_count=%lu binding_cleanup_ns=%" PRIu64 " " + "reobserve_count=%lu reobserve_ns=%" PRIu64 " " + "prebind_refresh_count=%lu prebind_refresh_ns=%" PRIu64 " " + "scoped_reobserve_count=%lu scoped_reobserve_ns=%" PRIu64 " " + "scoped_prebind_refresh_count=%lu " + "scoped_prebind_refresh_ns=%" PRIu64 " " + "target_prepare_count=%lu target_prepare_ns=%" PRIu64 "\n", + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_SCOPE_INVALIDATE].count, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_SCOPE_INVALIDATE].total_ns, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_GUEST_DLOPEN].count, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_GUEST_DLOPEN].total_ns, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_DLOPEN_FINISH].count, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_DLOPEN_FINISH].total_ns, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_GUEST_DLCLOSE].count, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_GUEST_DLCLOSE].total_ns, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_UNLOAD_PROBE].count, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_UNLOAD_PROBE].total_ns, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_REGISTRY_RETIRE].count, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_REGISTRY_RETIRE].total_ns, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_BINDING_CLEANUP].count, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_BINDING_CLEANUP].total_ns, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_REOBSERVE].count, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_REOBSERVE].total_ns, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_PREBIND_REFRESH].count, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_PREBIND_REFRESH].total_ns, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_SCOPED_REOBSERVE].count, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_SCOPED_REOBSERVE].total_ns, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_SCOPED_PREBIND_REFRESH].count, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_SCOPED_PREBIND_REFRESH].total_ns, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_TARGET_PREPARE].count, + __ATOMIC_RELAXED), + __atomic_load_n( + &diagnostics[KZT_LIFECYCLE_TARGET_PREPARE].total_ns, + __ATOMIC_RELAXED)); +} diff --git a/target/i386/latx/context/kzt_loader_event_hook.c b/target/i386/latx/context/kzt_loader_event_hook.c new file mode 100644 index 00000000000..00a05321b72 --- /dev/null +++ b/target/i386/latx/context/kzt_loader_event_hook.c @@ -0,0 +1,758 @@ +#include "kzt_loader_event_hook.h" + +#include +#include +#include +#include +#include + +#define KZT_LOADER_LIFECYCLE_INLINE_IDENTITIES 64 + +typedef struct kzt_loader_lifecycle_identity_buffer { + kzt_loader_lifecycle_identity_t *identities; + size_t count; + size_t capacity; + kzt_loader_lifecycle_identity_t inline_identities[ + KZT_LOADER_LIFECYCLE_INLINE_IDENTITIES]; +} kzt_loader_lifecycle_identity_buffer_t; + +#ifdef KZT_LOADER_EVENT_HOOK_TEST +static long hook_fail_after = -1; + +void kzt_loader_event_hook_test_set_alloc_failure_after(long allocations) +{ + hook_fail_after = allocations; +} +#endif + +static int kzt_loader_event_hook_allocation_allowed(void) +{ +#ifdef KZT_LOADER_EVENT_HOOK_TEST + if (hook_fail_after == 0) { + return 0; + } + if (hook_fail_after > 0) { + --hook_fail_after; + } +#endif + return 1; +} + +static void *kzt_loader_event_hook_malloc(size_t size) +{ + return kzt_loader_event_hook_allocation_allowed() ? malloc(size) : NULL; +} + +static void *kzt_loader_event_hook_calloc(size_t count, size_t size) +{ + return kzt_loader_event_hook_allocation_allowed() + ? calloc(count, size) + : NULL; +} + +static size_t kzt_loader_event_hook_align(size_t value) +{ + return (value + 3U) & ~3U; +} + +static void kzt_loader_event_hook_lifecycle_lock( + kzt_loader_event_hook_t *hook) +{ + while (__atomic_test_and_set(&hook->lifecycle_lock, + __ATOMIC_ACQUIRE)) { + } +} + +static void kzt_loader_event_hook_lifecycle_unlock( + kzt_loader_event_hook_t *hook) +{ + __atomic_clear(&hook->lifecycle_lock, __ATOMIC_RELEASE); +} + +static int kzt_loader_event_hook_publisher_leave( + kzt_loader_event_hook_t *hook, int result, + kzt_loader_lifecycle_result_t lifecycle_result) +{ + __atomic_store_n(&hook->lifecycle_result, lifecycle_result, + __ATOMIC_RELEASE); + __atomic_sub_fetch(&hook->lifecycle_publishers, 1U, __ATOMIC_RELEASE); + return result; +} + +static void kzt_loader_lifecycle_identity_buffer_init( + kzt_loader_lifecycle_identity_buffer_t *buffer) +{ + memset(buffer, 0, sizeof(*buffer)); + buffer->identities = buffer->inline_identities; + buffer->capacity = KZT_LOADER_LIFECYCLE_INLINE_IDENTITIES; +} + +static void kzt_loader_lifecycle_identity_buffer_release( + kzt_loader_lifecycle_identity_buffer_t *buffer) +{ + if (buffer->identities != buffer->inline_identities) { + free(buffer->identities); + } + memset(buffer, 0, sizeof(*buffer)); +} + +static kzt_loader_lifecycle_result_t +kzt_loader_lifecycle_capacity_for( + size_t current, size_t required, size_t *capacity) +{ + size_t next = current ? current : KZT_LOADER_LIFECYCLE_INLINE_IDENTITIES; + + if (!capacity || required > SIZE_MAX / + sizeof(kzt_loader_lifecycle_identity_t)) { + return KZT_LOADER_LIFECYCLE_OVERFLOW; + } + while (next < required) { + if (next > SIZE_MAX / 2) { + return KZT_LOADER_LIFECYCLE_OVERFLOW; + } + next *= 2; + } + *capacity = next; + return KZT_LOADER_LIFECYCLE_OK; +} + +static kzt_loader_lifecycle_result_t +kzt_loader_lifecycle_identity_buffer_reserve( + kzt_loader_lifecycle_identity_buffer_t *buffer, size_t required) +{ + kzt_loader_lifecycle_identity_t *identities; + kzt_loader_lifecycle_result_t result; + size_t capacity; + + if (required <= buffer->capacity) { + return KZT_LOADER_LIFECYCLE_OK; + } + result = kzt_loader_lifecycle_capacity_for( + buffer->capacity, required, &capacity); + if (result != KZT_LOADER_LIFECYCLE_OK) { + return result; + } + identities = kzt_loader_event_hook_malloc( + capacity * sizeof(*identities)); + if (!identities) { + return KZT_LOADER_LIFECYCLE_ALLOCATION; + } + memcpy(identities, buffer->identities, + buffer->count * sizeof(*identities)); + if (buffer->identities != buffer->inline_identities) { + free(buffer->identities); + } + buffer->identities = identities; + buffer->capacity = capacity; + return KZT_LOADER_LIFECYCLE_OK; +} + +static kzt_loader_lifecycle_result_t +kzt_loader_lifecycle_identity_buffer_append( + kzt_loader_lifecycle_identity_buffer_t *buffer, + const kzt_loader_lifecycle_identity_t *identity) +{ + kzt_loader_lifecycle_result_t result; + + if (buffer->count == SIZE_MAX) { + return KZT_LOADER_LIFECYCLE_OVERFLOW; + } + result = kzt_loader_lifecycle_identity_buffer_reserve( + buffer, buffer->count + 1); + if (result != KZT_LOADER_LIFECYCLE_OK) { + return result; + } + buffer->identities[buffer->count++] = *identity; + return KZT_LOADER_LIFECYCLE_OK; +} + +static void kzt_loader_lifecycle_cancel_buffer( + const kzt_loader_lifecycle_identity_buffer_t *buffer, + kzt_loader_lifecycle_transition_fn cancel, void *opaque) +{ + size_t index; + + for (index = 0; index < buffer->count; ++index) { + (void)cancel(&buffer->identities[index], opaque); + } +} + +static kzt_loader_lifecycle_result_t +kzt_loader_event_hook_append_pending( + kzt_loader_event_hook_t *hook, + const kzt_loader_lifecycle_identity_buffer_t *incoming) +{ + kzt_loader_lifecycle_identity_t *replacement = NULL; + size_t replacement_capacity = 0; + + for (;;) { + kzt_loader_lifecycle_identity_t *old; + kzt_loader_lifecycle_result_t result; + size_t required; + size_t capacity; + + kzt_loader_event_hook_lifecycle_lock(hook); + if (!__atomic_load_n(&hook->lifecycle_enabled, __ATOMIC_ACQUIRE)) { + kzt_loader_event_hook_lifecycle_unlock(hook); + free(replacement); + return KZT_LOADER_LIFECYCLE_DISABLED; + } + if (hook->pending_delete_count > SIZE_MAX - incoming->count) { + kzt_loader_event_hook_lifecycle_unlock(hook); + free(replacement); + return KZT_LOADER_LIFECYCLE_OVERFLOW; + } + required = hook->pending_delete_count + incoming->count; + if (hook->pending_delete && + required <= hook->pending_delete_capacity) { + memcpy(hook->pending_delete + hook->pending_delete_count, + incoming->identities, + incoming->count * sizeof(*incoming->identities)); + hook->pending_delete_count = required; + kzt_loader_event_hook_lifecycle_unlock(hook); + free(replacement); + return KZT_LOADER_LIFECYCLE_OK; + } + result = kzt_loader_lifecycle_capacity_for( + hook->pending_delete_capacity, required, &capacity); + kzt_loader_event_hook_lifecycle_unlock(hook); + if (result != KZT_LOADER_LIFECYCLE_OK) { + free(replacement); + return result; + } + if (capacity > replacement_capacity) { + free(replacement); + replacement = kzt_loader_event_hook_malloc( + capacity * sizeof(*replacement)); + if (!replacement) { + return KZT_LOADER_LIFECYCLE_ALLOCATION; + } + replacement_capacity = capacity; + } + + kzt_loader_event_hook_lifecycle_lock(hook); + if (!__atomic_load_n(&hook->lifecycle_enabled, __ATOMIC_ACQUIRE)) { + kzt_loader_event_hook_lifecycle_unlock(hook); + free(replacement); + return KZT_LOADER_LIFECYCLE_DISABLED; + } + if (hook->pending_delete_count > SIZE_MAX - incoming->count) { + kzt_loader_event_hook_lifecycle_unlock(hook); + free(replacement); + return KZT_LOADER_LIFECYCLE_OVERFLOW; + } + required = hook->pending_delete_count + incoming->count; + if (hook->pending_delete && + required <= hook->pending_delete_capacity) { + memcpy(hook->pending_delete + hook->pending_delete_count, + incoming->identities, + incoming->count * sizeof(*incoming->identities)); + hook->pending_delete_count = required; + kzt_loader_event_hook_lifecycle_unlock(hook); + free(replacement); + return KZT_LOADER_LIFECYCLE_OK; + } + if (required > replacement_capacity) { + kzt_loader_event_hook_lifecycle_unlock(hook); + continue; + } + old = hook->pending_delete; + if (hook->pending_delete_count) { + memcpy(replacement, old, + hook->pending_delete_count * sizeof(*replacement)); + } + memcpy(replacement + hook->pending_delete_count, + incoming->identities, + incoming->count * sizeof(*replacement)); + hook->pending_delete = replacement; + hook->pending_delete_capacity = replacement_capacity; + hook->pending_delete_count = required; + replacement = NULL; + replacement_capacity = 0; + kzt_loader_event_hook_lifecycle_unlock(hook); + free(old); + return KZT_LOADER_LIFECYCLE_OK; + } +} + +static void kzt_loader_event_hook_take_pending( + kzt_loader_event_hook_t *hook, + kzt_loader_lifecycle_identity_buffer_t *pending) +{ + kzt_loader_lifecycle_identity_buffer_init(pending); + kzt_loader_event_hook_lifecycle_lock(hook); + if (hook->pending_delete_count <= pending->capacity) { + if (hook->pending_delete_count) { + memcpy(pending->identities, hook->pending_delete, + hook->pending_delete_count * + sizeof(*pending->identities)); + } + pending->count = hook->pending_delete_count; + } else { + pending->identities = hook->pending_delete; + pending->count = hook->pending_delete_count; + pending->capacity = hook->pending_delete_capacity; + hook->pending_delete = NULL; + hook->pending_delete_capacity = 0; + } + hook->pending_delete_count = 0; + kzt_loader_event_hook_lifecycle_unlock(hook); +} + +static int kzt_loader_event_hook_live_maps_valid( + const uintptr_t *live_maps, size_t live_map_count) +{ + size_t i; + size_t j; + + if (live_map_count && !live_maps) { + return 0; + } + for (i = 0; i < live_map_count; ++i) { + if (!live_maps[i]) { + return 0; + } + for (j = 0; j < i; ++j) { + if (live_maps[j] == live_maps[i]) { + return 0; + } + } + } + return 1; +} + +static int kzt_loader_event_hook_map_present( + uintptr_t link_map_addr, + const uintptr_t *live_maps, + size_t live_map_count) +{ + size_t i; + + for (i = 0; i < live_map_count; ++i) { + if (live_maps[i] == link_map_addr) { + return 1; + } + } + return 0; +} + +static int kzt_loader_lifecycle_identity_equal( + const kzt_loader_lifecycle_identity_t *left, + const kzt_loader_lifecycle_identity_t *right) +{ + return left->link_map_addr == right->link_map_addr && + left->generation == right->generation && + left->namespace_id == right->namespace_id; +} + +static int kzt_loader_event_hook_hex(char *out, size_t out_size, + const unsigned char *input, size_t size) +{ + static const char digits[] = "0123456789abcdef"; + size_t i; + + if (!out || !input || out_size < size * 2U + 1U) { + return -1; + } + for (i = 0; i < size; ++i) { + out[i * 2U] = digits[input[i] >> 4]; + out[i * 2U + 1U] = digits[input[i] & 0x0fU]; + } + out[size * 2U] = '\0'; + return 0; +} + +static int kzt_loader_event_hook_read_note(FILE *file, size_t note_size, + char build_id[ + KZT_LOADER_EVENT_HOOK_BUILD_ID_SIZE]) +{ + size_t consumed = 0; + + while (consumed + sizeof(Elf64_Nhdr) <= note_size) { + Elf64_Nhdr note; + unsigned char name[4] = { 0 }; + unsigned char descriptor[20] = { 0 }; + size_t name_size; + size_t descriptor_size; + + if (fread(¬e, sizeof(note), 1, file) != 1) { + return -1; + } + consumed += sizeof(note); + name_size = kzt_loader_event_hook_align(note.n_namesz); + descriptor_size = kzt_loader_event_hook_align(note.n_descsz); + if (name_size > note_size - consumed || + descriptor_size > note_size - consumed - name_size) { + return -1; + } + if (note.n_namesz > sizeof(name) || note.n_descsz > sizeof(descriptor)) { + if (fseek(file, (long)(name_size + descriptor_size), SEEK_CUR) != 0) { + return -1; + } + consumed += name_size + descriptor_size; + continue; + } + if (note.n_namesz && fread(name, 1, note.n_namesz, file) != note.n_namesz) { + return -1; + } + if (name_size > note.n_namesz && + fseek(file, (long)(name_size - note.n_namesz), SEEK_CUR) != 0) { + return -1; + } + if (note.n_descsz && + fread(descriptor, 1, note.n_descsz, file) != note.n_descsz) { + return -1; + } + if (descriptor_size > note.n_descsz && + fseek(file, (long)(descriptor_size - note.n_descsz), SEEK_CUR) != 0) { + return -1; + } + consumed += name_size + descriptor_size; + if (note.n_type == NT_GNU_BUILD_ID && note.n_namesz == 4 && + memcmp(name, "GNU", 4) == 0 && note.n_descsz == 20) { + return kzt_loader_event_hook_hex(build_id, + KZT_LOADER_EVENT_HOOK_BUILD_ID_SIZE, + descriptor, note.n_descsz); + } + } + return -1; +} + +int kzt_loader_event_hook_read_build_id( + const char *path, char build_id[KZT_LOADER_EVENT_HOOK_BUILD_ID_SIZE]) +{ + FILE *file; + Elf64_Ehdr header; + Elf64_Phdr program_header; + size_t index; + int result = -1; + + if (!path || !build_id) { + return -1; + } + build_id[0] = '\0'; + file = fopen(path, "rb"); + if (!file) { + return -1; + } + if (fread(&header, sizeof(header), 1, file) != 1 || + memcmp(header.e_ident, ELFMAG, SELFMAG) != 0 || + header.e_ident[EI_CLASS] != ELFCLASS64 || + header.e_ident[EI_DATA] != ELFDATA2LSB || + header.e_machine != EM_X86_64 || + header.e_phentsize != sizeof(program_header)) { + goto out; + } + for (index = 0; index < header.e_phnum; ++index) { + if (fseek(file, (long)(header.e_phoff + + index * sizeof(program_header)), SEEK_SET) != 0 || + fread(&program_header, sizeof(program_header), 1, file) != 1) { + goto out; + } + if (program_header.p_type != PT_NOTE || + program_header.p_filesz < sizeof(Elf64_Nhdr) || + fseek(file, (long)program_header.p_offset, SEEK_SET) != 0) { + continue; + } + if (kzt_loader_event_hook_read_note(file, + (size_t)program_header.p_filesz, + build_id) == 0) { + result = 0; + break; + } + } +out: + fclose(file); + return result; +} + +static int kzt_loader_event_hook_disabled(void) +{ + const char *value = getenv("LATX_KZT_LOADER_EVENT_HOOK"); + + return value && strcmp(value, "0") == 0; +} + +int kzt_loader_event_hook_pattern_allowed(int pattern_matched) +{ + const char *value = getenv("LATX_KZT_LOADER_EVENT_FORCE_PATTERN_MISMATCH"); + + return pattern_matched && !(value && strcmp(value, "1") == 0); +} + +int kzt_loader_event_hook_install(kzt_loader_event_hook_t *hook, + const char *build_id, + uintptr_t callback_addr, + unsigned int link_map_reg, + int pattern_matched) +{ + kzt_loader_event_hook_result_t result = KZT_LOADER_EVENT_HOOK_INSTALLED; + + if (!hook) { + return -1; + } + memset(hook, 0, sizeof(*hook)); + if (kzt_loader_event_hook_disabled()) { + result = KZT_LOADER_EVENT_HOOK_FAIL_OPEN_DISABLED; + } else if (!build_id) { + result = KZT_LOADER_EVENT_HOOK_FAIL_OPEN_BUILD_ID_READ; + } else if (strcmp(build_id, KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID) != 0) { + result = KZT_LOADER_EVENT_HOOK_FAIL_OPEN_UNKNOWN_BUILD_ID; + } else if (!pattern_matched || !callback_addr || link_map_reg > 15) { + result = KZT_LOADER_EVENT_HOOK_FAIL_OPEN_PATTERN_MISMATCH; + } + hook->result = result; + if (result != KZT_LOADER_EVENT_HOOK_INSTALLED) { + return -1; + } + memcpy(hook->build_id, build_id, KZT_LOADER_EVENT_HOOK_BUILD_ID_SIZE); + hook->callback_addr = callback_addr; + hook->link_map_reg = link_map_reg; + hook->scope_layout = KZT_GUEST_SCOPE_LAYOUT_GLIBC_2_39_C591A5DF; + __atomic_store_n(&hook->installed, 1U, __ATOMIC_RELEASE); + return 0; +} + +int kzt_loader_event_hook_publish(kzt_loader_event_hook_t *hook, + uintptr_t link_map_addr, + kzt_loader_event_t *event) +{ + struct timespec timestamp; + + if (!hook || !event || !link_map_addr || + !__atomic_load_n(&hook->installed, __ATOMIC_ACQUIRE)) { + return -1; + } + memset(event, 0, sizeof(*event)); + event->link_map_addr = link_map_addr; + event->sequence = __atomic_add_fetch(&hook->event_sequence, 1, + __ATOMIC_RELAXED); + if (clock_gettime(CLOCK_MONOTONIC_RAW, ×tamp) == 0) { + event->published_ns = (uint64_t)timestamp.tv_sec * 1000000000ULL + + (uint64_t)timestamp.tv_nsec; + } + return 0; +} + +int kzt_loader_event_hook_enable_lifecycle( + kzt_loader_event_hook_t *hook, + uintptr_t debug_state_addr, + uintptr_t r_debug_addr) +{ + kzt_loader_lifecycle_identity_t *pending; + + if (!hook || !debug_state_addr || !r_debug_addr || + !__atomic_load_n(&hook->installed, __ATOMIC_ACQUIRE) || + __atomic_load_n(&hook->lifecycle_enabled, __ATOMIC_ACQUIRE) || + __atomic_load_n(&hook->lifecycle_publishers, __ATOMIC_ACQUIRE)) { + return -1; + } + pending = kzt_loader_event_hook_calloc( + KZT_LOADER_LIFECYCLE_INLINE_IDENTITIES, sizeof(*pending)); + if (!pending) { + __atomic_store_n(&hook->lifecycle_result, + KZT_LOADER_LIFECYCLE_ALLOCATION, + __ATOMIC_RELEASE); + return -1; + } + kzt_loader_event_hook_lifecycle_lock(hook); + if (hook->pending_delete_count || + __atomic_load_n(&hook->lifecycle_enabled, __ATOMIC_ACQUIRE) || + __atomic_load_n(&hook->lifecycle_publishers, __ATOMIC_ACQUIRE)) { + kzt_loader_event_hook_lifecycle_unlock(hook); + free(pending); + return -1; + } + free(hook->pending_delete); + hook->pending_delete = pending; + hook->pending_delete_count = 0; + hook->pending_delete_capacity = + KZT_LOADER_LIFECYCLE_INLINE_IDENTITIES; + hook->debug_state_addr = debug_state_addr; + hook->r_debug_addr = r_debug_addr; + kzt_loader_event_hook_lifecycle_unlock(hook); + __atomic_store_n(&hook->lifecycle_enabled, 1U, __ATOMIC_RELEASE); + __atomic_store_n(&hook->lifecycle_result, KZT_LOADER_LIFECYCLE_OK, + __ATOMIC_RELEASE); + return 0; +} + +int kzt_loader_event_hook_publish_lifecycle( + kzt_loader_event_hook_t *hook, + kzt_loader_debug_state_t state, + const uintptr_t *live_maps, + size_t live_map_count, + kzt_loader_lifecycle_resolve_fn resolve, + kzt_loader_lifecycle_transition_fn prepare, + kzt_loader_lifecycle_transition_fn cancel, + kzt_loader_lifecycle_unload_fn unload, + void *opaque) +{ + kzt_loader_lifecycle_identity_buffer_t pending; + kzt_loader_lifecycle_result_t lifecycle_result; + size_t i; + + if (!hook || !resolve || !prepare || !cancel || !unload || + !__atomic_load_n(&hook->lifecycle_enabled, __ATOMIC_ACQUIRE)) { + return -1; + } + __atomic_add_fetch(&hook->lifecycle_publishers, 1U, __ATOMIC_ACQUIRE); + if (!__atomic_load_n(&hook->lifecycle_enabled, __ATOMIC_ACQUIRE)) { + return kzt_loader_event_hook_publisher_leave( + hook, -1, KZT_LOADER_LIFECYCLE_DISABLED); + } + if (!kzt_loader_event_hook_live_maps_valid(live_maps, live_map_count) || + (state != KZT_LOADER_DEBUG_CONSISTENT && + state != KZT_LOADER_DEBUG_ADD && + state != KZT_LOADER_DEBUG_DELETE)) { + kzt_loader_event_hook_take_pending(hook, &pending); + kzt_loader_lifecycle_cancel_buffer(&pending, cancel, opaque); + kzt_loader_lifecycle_identity_buffer_release(&pending); + return kzt_loader_event_hook_publisher_leave( + hook, -1, KZT_LOADER_LIFECYCLE_INVALID); + } + + if (state == KZT_LOADER_DEBUG_DELETE) { + kzt_loader_lifecycle_identity_buffer_init(&pending); + + for (i = 0; i < live_map_count; ++i) { + kzt_loader_lifecycle_identity_t identity = { 0 }; + + if (resolve(live_maps[i], &identity, opaque) != 0 || + identity.link_map_addr != live_maps[i] || + !identity.generation || prepare(&identity, opaque) != 0) { + continue; + } + lifecycle_result = + kzt_loader_lifecycle_identity_buffer_append( + &pending, &identity); + if (lifecycle_result != KZT_LOADER_LIFECYCLE_OK) { + (void)cancel(&identity, opaque); + kzt_loader_lifecycle_cancel_buffer( + &pending, cancel, opaque); + kzt_loader_lifecycle_identity_buffer_release(&pending); + return kzt_loader_event_hook_publisher_leave( + hook, -1, lifecycle_result); + } + } + lifecycle_result = kzt_loader_event_hook_append_pending( + hook, &pending); + if (lifecycle_result != KZT_LOADER_LIFECYCLE_OK) { + kzt_loader_lifecycle_cancel_buffer(&pending, cancel, opaque); + } + kzt_loader_lifecycle_identity_buffer_release(&pending); + return kzt_loader_event_hook_publisher_leave( + hook, lifecycle_result == KZT_LOADER_LIFECYCLE_OK ? 0 : -1, + lifecycle_result); + } + if (state == KZT_LOADER_DEBUG_ADD) { + return kzt_loader_event_hook_publisher_leave( + hook, 0, KZT_LOADER_LIFECYCLE_OK); + } + + kzt_loader_event_hook_take_pending(hook, &pending); + + for (i = 0; i < pending.count; ++i) { + kzt_loader_lifecycle_identity_t current = { 0 }; + + if (!kzt_loader_event_hook_map_present( + pending.identities[i].link_map_addr, + live_maps, live_map_count)) { + unload(&pending.identities[i], opaque); + } else if (resolve( + pending.identities[i].link_map_addr, + ¤t, opaque) == 0 && + !kzt_loader_lifecycle_identity_equal( + &pending.identities[i], ¤t)) { + unload(&pending.identities[i], opaque); + } else { + (void)cancel(&pending.identities[i], opaque); + } + } + kzt_loader_lifecycle_identity_buffer_release(&pending); + return kzt_loader_event_hook_publisher_leave( + hook, 0, KZT_LOADER_LIFECYCLE_OK); +} + +int kzt_loader_event_hook_destroy(kzt_loader_event_hook_t *hook) +{ + if (!hook) { + return -1; + } + __atomic_store_n(&hook->lifecycle_enabled, 0U, __ATOMIC_RELEASE); + while (__atomic_load_n(&hook->lifecycle_publishers, __ATOMIC_ACQUIRE)) { + } + kzt_loader_event_hook_lifecycle_lock(hook); + if (hook->pending_delete_count) { + kzt_loader_event_hook_lifecycle_unlock(hook); + __atomic_store_n(&hook->lifecycle_enabled, 1U, __ATOMIC_RELEASE); + return -1; + } + free(hook->pending_delete); + hook->pending_delete = NULL; + hook->pending_delete_count = 0; + hook->pending_delete_capacity = 0; + hook->debug_state_addr = 0; + hook->r_debug_addr = 0; + kzt_loader_event_hook_lifecycle_unlock(hook); + __atomic_store_n(&hook->installed, 0U, __ATOMIC_RELEASE); + return 0; +} + +kzt_guest_scope_layout_t kzt_loader_event_hook_scope_layout( + const kzt_loader_event_hook_t *hook) +{ + if (!hook || + !__atomic_load_n(&hook->installed, __ATOMIC_ACQUIRE) || + hook->result != KZT_LOADER_EVENT_HOOK_INSTALLED || + strcmp(hook->build_id, + KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID) != 0) { + return KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; + } + return hook->scope_layout; +} + +const char *kzt_loader_event_hook_result_name( + kzt_loader_event_hook_result_t result) +{ + switch (result) { + case KZT_LOADER_EVENT_HOOK_INSTALLED: + return "INSTALLED"; + case KZT_LOADER_EVENT_HOOK_FAIL_OPEN_DISABLED: + return "DISABLED"; + case KZT_LOADER_EVENT_HOOK_FAIL_OPEN_BUILD_ID_READ: + return "BUILD_ID_READ"; + case KZT_LOADER_EVENT_HOOK_FAIL_OPEN_UNKNOWN_BUILD_ID: + return "UNKNOWN_BUILD_ID"; + case KZT_LOADER_EVENT_HOOK_FAIL_OPEN_PATTERN_MISMATCH: + return "PATTERN_MISMATCH"; + } + return "INVALID"; +} + +kzt_loader_lifecycle_result_t kzt_loader_event_hook_lifecycle_result( + const kzt_loader_event_hook_t *hook) +{ + if (!hook) { + return KZT_LOADER_LIFECYCLE_INVALID; + } + return __atomic_load_n(&hook->lifecycle_result, __ATOMIC_ACQUIRE); +} + +const char *kzt_loader_lifecycle_result_name( + kzt_loader_lifecycle_result_t result) +{ + switch (result) { + case KZT_LOADER_LIFECYCLE_OK: + return "OK"; + case KZT_LOADER_LIFECYCLE_DISABLED: + return "DISABLED"; + case KZT_LOADER_LIFECYCLE_INVALID: + return "INVALID"; + case KZT_LOADER_LIFECYCLE_ALLOCATION: + return "ALLOCATION"; + case KZT_LOADER_LIFECYCLE_OVERFLOW: + return "OVERFLOW"; + } + return "INVALID"; +} diff --git a/target/i386/latx/context/kzt_loader_lifecycle_snapshot.c b/target/i386/latx/context/kzt_loader_lifecycle_snapshot.c new file mode 100644 index 00000000000..4b81382e234 --- /dev/null +++ b/target/i386/latx/context/kzt_loader_lifecycle_snapshot.c @@ -0,0 +1,311 @@ +#include "kzt_loader_lifecycle_snapshot.h" + +#include +#include +#include + +#define KZT_LOADER_LIFECYCLE_DEBUG_NODES 16 + +typedef struct kzt_r_debug_extended_x64 { + int32_t version; + int32_t version_padding; + uintptr_t map; + uintptr_t brk; + int32_t state; + int32_t state_padding; + uintptr_t loader_base; + uintptr_t next; +} kzt_r_debug_extended_x64_t; + +typedef struct kzt_link_map_chain_x64 { + uintptr_t load_bias; + uintptr_t name; + uintptr_t dynamic_addr; + uintptr_t next; + uintptr_t previous; +} kzt_link_map_chain_x64_t; + +#ifdef KZT_LOADER_LIFECYCLE_SNAPSHOT_TEST +static long snapshot_fail_after = -1; + +void kzt_loader_lifecycle_snapshot_test_set_alloc_failure_after( + long allocations) +{ + snapshot_fail_after = allocations; +} +#endif + +static void *kzt_loader_lifecycle_snapshot_alloc(size_t size) +{ +#ifdef KZT_LOADER_LIFECYCLE_SNAPSHOT_TEST + if (snapshot_fail_after == 0) { + return NULL; + } + if (snapshot_fail_after > 0) { + --snapshot_fail_after; + } +#endif + return malloc(size); +} + +static int kzt_loader_lifecycle_snapshot_fail( + kzt_loader_lifecycle_snapshot_t *snapshot, + kzt_loader_lifecycle_snapshot_result_t result) +{ + if (snapshot->live_maps && + snapshot->live_maps != snapshot->inline_live_maps) { + free(snapshot->live_maps); + } + snapshot->state = KZT_LOADER_DEBUG_CONSISTENT; + snapshot->result = result; + snapshot->live_maps = snapshot->inline_live_maps; + snapshot->live_map_count = 0; + snapshot->live_map_capacity = + KZT_LOADER_LIFECYCLE_SNAPSHOT_INLINE_MAPS; + return -1; +} + +static int kzt_loader_lifecycle_snapshot_grow( + kzt_loader_lifecycle_snapshot_t *snapshot) +{ + uintptr_t *maps; + size_t capacity; + size_t bytes; + + if (snapshot->live_map_capacity > SIZE_MAX / 2) { + return kzt_loader_lifecycle_snapshot_fail( + snapshot, KZT_LOADER_LIFECYCLE_SNAPSHOT_OVERFLOW); + } + capacity = snapshot->live_map_capacity * 2; + if (capacity > SIZE_MAX / sizeof(*maps)) { + return kzt_loader_lifecycle_snapshot_fail( + snapshot, KZT_LOADER_LIFECYCLE_SNAPSHOT_OVERFLOW); + } + bytes = capacity * sizeof(*maps); + maps = kzt_loader_lifecycle_snapshot_alloc(bytes); + if (!maps) { + return kzt_loader_lifecycle_snapshot_fail( + snapshot, KZT_LOADER_LIFECYCLE_SNAPSHOT_ALLOCATION); + } + memcpy(maps, snapshot->live_maps, + snapshot->live_map_count * sizeof(*maps)); + if (snapshot->live_maps != snapshot->inline_live_maps) { + free(snapshot->live_maps); + } + snapshot->live_maps = maps; + snapshot->live_map_capacity = capacity; + return 0; +} + +static int kzt_loader_lifecycle_snapshot_append( + kzt_loader_lifecycle_snapshot_t *snapshot, + uintptr_t link_map_addr) +{ + if (snapshot->live_map_count == snapshot->live_map_capacity && + kzt_loader_lifecycle_snapshot_grow(snapshot) != 0) { + return -1; + } + snapshot->live_maps[snapshot->live_map_count++] = link_map_addr; + return 0; +} + +static int kzt_loader_lifecycle_snapshot_supplement_namespaces( + kzt_guest_registry_t *registry, + const uintptr_t *live_maps, + const size_t group_starts[KZT_LOADER_LIFECYCLE_DEBUG_NODES], + const size_t group_counts[KZT_LOADER_LIFECYCLE_DEBUG_NODES], + size_t group_count) +{ + size_t group; + + for (group = 0; group < group_count; ++group) { + uintptr_t namespace_id = 0; + int namespace_known = group == 0; + size_t end = group_starts[group] + group_counts[group]; + size_t index; + + for (index = group_starts[group]; index < end; ++index) { + kzt_guest_registry_address_match_t match = { 0 }; + + if (kzt_guest_registry_find_live_object( + registry, live_maps[index], &match) != 0 || + match.namespace_id_status != KZT_GUEST_FIELD_OK) { + continue; + } + if (namespace_known && namespace_id != match.namespace_id) { + return -1; + } + namespace_id = match.namespace_id; + namespace_known = 1; + } + if (!namespace_known) { + continue; + } + for (index = group_starts[group]; index < end; ++index) { + kzt_guest_registry_address_match_t match = { 0 }; + kzt_guest_registry_result_t result; + + if (kzt_guest_registry_find_live_object( + registry, live_maps[index], &match) != 0) { + continue; + } + if (match.namespace_id_status == KZT_GUEST_FIELD_OK) { + if (match.namespace_id != namespace_id) { + return -1; + } + continue; + } + result = kzt_guest_registry_supplement_namespace( + registry, live_maps[index], match.generation, + namespace_id); + if (result != KZT_GUEST_REGISTRY_UPDATED && + result != KZT_GUEST_REGISTRY_UNCHANGED) { + return -1; + } + } + } + return 0; +} + +int kzt_loader_lifecycle_snapshot_capture( + kzt_guest_registry_t *registry, + uintptr_t r_debug_addr, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_loader_lifecycle_snapshot_t *snapshot) +{ + uintptr_t debug_nodes[KZT_LOADER_LIFECYCLE_DEBUG_NODES] = { 0 }; + size_t group_starts[KZT_LOADER_LIFECYCLE_DEBUG_NODES] = { 0 }; + size_t group_counts[KZT_LOADER_LIFECYCLE_DEBUG_NODES] = { 0 }; + uintptr_t debug_addr; + size_t debug_count = 0; + int active_state = KZT_LOADER_DEBUG_CONSISTENT; + + if (!snapshot) { + return -1; + } + memset(snapshot, 0, sizeof(*snapshot)); + snapshot->live_maps = snapshot->inline_live_maps; + snapshot->live_map_capacity = + KZT_LOADER_LIFECYCLE_SNAPSHOT_INLINE_MAPS; + snapshot->result = KZT_LOADER_LIFECYCLE_SNAPSHOT_INVALID_INPUT; + if (!registry || !r_debug_addr || !reader_ops || + !reader_ops->read_memory) { + return -1; + } + + debug_addr = r_debug_addr; + while (debug_addr) { + kzt_r_debug_extended_x64_t debug; + uintptr_t map_addr; + size_t index; + + if (debug_count == KZT_LOADER_LIFECYCLE_DEBUG_NODES) { + return kzt_loader_lifecycle_snapshot_fail( + snapshot, KZT_LOADER_LIFECYCLE_SNAPSHOT_CYCLE); + } + if (reader_ops->read_memory( + debug_addr, &debug, sizeof(debug), reader_ops->opaque) != 0) { + return kzt_loader_lifecycle_snapshot_fail( + snapshot, KZT_LOADER_LIFECYCLE_SNAPSHOT_READ_ERROR); + } + if (debug.state < KZT_LOADER_DEBUG_CONSISTENT || + debug.state > KZT_LOADER_DEBUG_DELETE) { + return kzt_loader_lifecycle_snapshot_fail( + snapshot, KZT_LOADER_LIFECYCLE_SNAPSHOT_INVALID_STATE); + } + for (index = 0; index < debug_count; ++index) { + if (debug_nodes[index] == debug_addr) { + return kzt_loader_lifecycle_snapshot_fail( + snapshot, KZT_LOADER_LIFECYCLE_SNAPSHOT_CYCLE); + } + } + debug_nodes[debug_count] = debug_addr; + group_starts[debug_count] = snapshot->live_map_count; + ++debug_count; + if (debug.state != KZT_LOADER_DEBUG_CONSISTENT) { + if (active_state != KZT_LOADER_DEBUG_CONSISTENT && + active_state != debug.state) { + return kzt_loader_lifecycle_snapshot_fail( + snapshot, + KZT_LOADER_LIFECYCLE_SNAPSHOT_INVALID_STATE); + } + active_state = debug.state; + } + + map_addr = debug.map; + while (map_addr) { + kzt_link_map_chain_x64_t map; + + if (reader_ops->read_memory( + map_addr, &map, sizeof(map), reader_ops->opaque) != 0) { + return kzt_loader_lifecycle_snapshot_fail( + snapshot, KZT_LOADER_LIFECYCLE_SNAPSHOT_READ_ERROR); + } + for (index = 0; index < snapshot->live_map_count; ++index) { + if (snapshot->live_maps[index] == map_addr) { + return kzt_loader_lifecycle_snapshot_fail( + snapshot, KZT_LOADER_LIFECYCLE_SNAPSHOT_CYCLE); + } + } + if (kzt_loader_lifecycle_snapshot_append( + snapshot, map_addr) != 0) { + return -1; + } + map_addr = map.next; + } + group_counts[debug_count - 1] = + snapshot->live_map_count - group_starts[debug_count - 1]; + debug_addr = debug.next; + } + if (!debug_count) { + return kzt_loader_lifecycle_snapshot_fail( + snapshot, KZT_LOADER_LIFECYCLE_SNAPSHOT_INVALID_INPUT); + } + if (active_state == KZT_LOADER_DEBUG_DELETE && + kzt_loader_lifecycle_snapshot_supplement_namespaces( + registry, snapshot->live_maps, group_starts, group_counts, + debug_count) != 0) { + return kzt_loader_lifecycle_snapshot_fail( + snapshot, KZT_LOADER_LIFECYCLE_SNAPSHOT_NAMESPACE); + } + snapshot->state = active_state; + snapshot->result = KZT_LOADER_LIFECYCLE_SNAPSHOT_OK; + return 0; +} + +void kzt_loader_lifecycle_snapshot_release( + kzt_loader_lifecycle_snapshot_t *snapshot) +{ + if (!snapshot) { + return; + } + if (snapshot->live_maps && + snapshot->live_maps != snapshot->inline_live_maps) { + free(snapshot->live_maps); + } + memset(snapshot, 0, sizeof(*snapshot)); +} + +const char *kzt_loader_lifecycle_snapshot_result_name( + kzt_loader_lifecycle_snapshot_result_t result) +{ + switch (result) { + case KZT_LOADER_LIFECYCLE_SNAPSHOT_OK: + return "OK"; + case KZT_LOADER_LIFECYCLE_SNAPSHOT_INVALID_INPUT: + return "INVALID_INPUT"; + case KZT_LOADER_LIFECYCLE_SNAPSHOT_READ_ERROR: + return "READ_ERROR"; + case KZT_LOADER_LIFECYCLE_SNAPSHOT_INVALID_STATE: + return "INVALID_STATE"; + case KZT_LOADER_LIFECYCLE_SNAPSHOT_CYCLE: + return "CYCLE"; + case KZT_LOADER_LIFECYCLE_SNAPSHOT_NAMESPACE: + return "NAMESPACE"; + case KZT_LOADER_LIFECYCLE_SNAPSHOT_ALLOCATION: + return "ALLOCATION"; + case KZT_LOADER_LIFECYCLE_SNAPSHOT_OVERFLOW: + return "OVERFLOW"; + } + return "INVALID"; +} diff --git a/target/i386/latx/include/kzt_guest_library_binding.h b/target/i386/latx/include/kzt_guest_library_binding.h index ec5946f5cd0..3f2fa95d78c 100644 --- a/target/i386/latx/include/kzt_guest_library_binding.h +++ b/target/i386/latx/include/kzt_guest_library_binding.h @@ -64,6 +64,9 @@ typedef struct kzt_guest_library_handle { kzt_guest_library_object_type_t object_type; } kzt_guest_library_handle_t; +typedef void (*kzt_guest_library_exact_cleanup_fn)( + library_t *library, void *opaque); + typedef struct kzt_guest_library_callback_access { kzt_guest_library_bindings_t *bindings; uintptr_t link_map_addr; @@ -71,6 +74,25 @@ typedef struct kzt_guest_library_callback_access { int fallback; } kzt_guest_library_callback_access_t; +/* A non-blocking, context-local reader lease proving that no controlled guest + * loader scope can change the guest link_map. Concurrent binding readers may + * hold leases together; a waiting loader prevents new readers from entering. + * Keep the token at a stable address and release it exactly once. */ +typedef struct kzt_guest_library_loader_quiescence_lease { + kzt_guest_library_bindings_t *bindings; + unsigned long cookie; + struct kzt_guest_library_loader_quiescence_lease *next; +} kzt_guest_library_loader_quiescence_lease_t; + +/* A writer token closes reader admission before waiting for admitted readers + * to leave. The bindings lock is not held after begin returns, so guest + * loader and Registry calls remain outside the bindings critical section. */ +typedef struct kzt_guest_library_loader_quiescence_writer { + kzt_guest_library_bindings_t *bindings; + unsigned long cookie; + struct kzt_guest_library_loader_quiescence_writer *next; +} kzt_guest_library_loader_quiescence_writer_t; + kzt_guest_library_bindings_t *kzt_guest_library_bindings_init(void); void kzt_guest_library_bindings_begin_teardown( kzt_guest_library_bindings_t *bindings); @@ -99,6 +121,16 @@ int kzt_guest_library_loader_scope_begin( kzt_guest_library_loader_scope_t *scope); void kzt_guest_library_loader_scope_end( kzt_guest_library_loader_scope_t *scope); +int kzt_guest_library_loader_quiescence_try_acquire( + kzt_guest_library_bindings_t *bindings, + kzt_guest_library_loader_quiescence_lease_t *lease); +void kzt_guest_library_loader_quiescence_release( + kzt_guest_library_loader_quiescence_lease_t *lease); +int kzt_guest_library_loader_quiescence_writer_begin( + kzt_guest_library_bindings_t *bindings, + kzt_guest_library_loader_quiescence_writer_t *writer); +void kzt_guest_library_loader_quiescence_writer_end( + kzt_guest_library_loader_quiescence_writer_t *writer); kzt_guest_library_binding_result_t kzt_guest_library_loader_scope_note_pair( const kzt_guest_library_loader_scope_t *scope, @@ -164,12 +196,34 @@ int kzt_guest_library_access_lookup( kzt_guest_library_access_t *access, const kzt_guest_library_binding_key_t *key, kzt_guest_library_handle_t *handle); +/* Returns one published LIVE binding in the main namespace and pins its + * library lifetime in handle. Ambiguous or unavailable bindings fail with + * both outputs cleared; release successful handles with handle_release. */ +int kzt_guest_library_access_lookup_by_library( + kzt_guest_library_access_t *access, library_t *library, + kzt_guest_library_binding_key_t *key, + kzt_guest_library_handle_t *handle); #ifdef KZT_GUEST_LIBRARY_BINDING_TEST int kzt_guest_library_lookup(kzt_guest_library_bindings_t *bindings, const kzt_guest_library_binding_key_t *key, kzt_guest_library_handle_t *handle); #endif void kzt_guest_library_handle_release(kzt_guest_library_handle_t *handle); +int kzt_guest_library_symbol_evidence_lookup( + const kzt_guest_library_handle_t *handle, const char *symbol, + unsigned long dynamic_revision, uintptr_t *runtime_address, + unsigned char *symbol_type); +void kzt_guest_library_symbol_evidence_store( + const kzt_guest_library_handle_t *handle, const char *symbol, + unsigned long dynamic_revision, uintptr_t runtime_address, + unsigned char symbol_type); +/* Consumes one pinned exact handle, closes only that binding, and invokes a + * non-blocking library cleanup callback before concurrent unbind can free the + * library. The callback must not enter bindings or Registry APIs. */ +int kzt_guest_library_cleanup_exact_handle( + kzt_guest_library_handle_t *handle, + kzt_guest_library_exact_cleanup_fn cleanup, + void *opaque); /* Closes attachment under the bindings lock, retires exact registry * generations without that lock, then waits for acquired lookup handles. @@ -209,6 +263,10 @@ int kzt_guest_library_binding_test_get_diagnostics( kzt_guest_library_bindings_t *bindings, unsigned long *registry_missing, unsigned long *retire_unprovable); +int kzt_guest_library_binding_test_loader_state( + kzt_guest_library_bindings_t *bindings, + unsigned int *lease_readers, unsigned int *lease_waiters, + unsigned int *active_scopes, int *shutting_down); #endif #endif diff --git a/target/i386/latx/include/kzt_guest_scope_layout.h b/target/i386/latx/include/kzt_guest_scope_layout.h new file mode 100644 index 00000000000..5fa9863508c --- /dev/null +++ b/target/i386/latx/include/kzt_guest_scope_layout.h @@ -0,0 +1,10 @@ +#ifndef KZT_GUEST_SCOPE_LAYOUT_H +#define KZT_GUEST_SCOPE_LAYOUT_H + +typedef enum kzt_guest_scope_layout { + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED = 0, + /* Exact x86_64 loader Build ID c591a5df63f461bfdafb01908ca16845b375fa37. */ + KZT_GUEST_SCOPE_LAYOUT_GLIBC_2_39_C591A5DF, +} kzt_guest_scope_layout_t; + +#endif diff --git a/target/i386/latx/include/kzt_lazy_prebind_scope.h b/target/i386/latx/include/kzt_lazy_prebind_scope.h new file mode 100644 index 00000000000..a8e50b3ce19 --- /dev/null +++ b/target/i386/latx/include/kzt_lazy_prebind_scope.h @@ -0,0 +1,113 @@ +#ifndef KZT_LAZY_PREBIND_SCOPE_H +#define KZT_LAZY_PREBIND_SCOPE_H + +#include + +#include "kzt_guest_symbol_scope.h" + +#define KZT_LAZY_PREBIND_TEXT_MAX 128 + +typedef struct kzt_lazy_prebind_scope kzt_lazy_prebind_scope_t; + +typedef enum kzt_lazy_prebind_lease_operation { + KZT_LAZY_PREBIND_LEASE_READ = 0, + KZT_LAZY_PREBIND_LEASE_PUBLISH, + KZT_LAZY_PREBIND_LEASE_REVOKE, +} kzt_lazy_prebind_lease_operation_t; + +typedef struct kzt_lazy_prebind_identity { + uintptr_t link_map_addr; + unsigned long generation; + uintptr_t namespace_id; +} kzt_lazy_prebind_identity_t; + +typedef enum kzt_lazy_prebind_mutation { + KZT_LAZY_PREBIND_MUTATION_LOADER_EVENT = 0, + KZT_LAZY_PREBIND_MUTATION_DLOPEN, + KZT_LAZY_PREBIND_MUTATION_DLCLOSE, + KZT_LAZY_PREBIND_MUTATION_DLMOPEN, + KZT_LAZY_PREBIND_MUTATION_RETIRE, +} kzt_lazy_prebind_mutation_t; + +typedef enum kzt_lazy_prebind_claim_result { + KZT_LAZY_PREBIND_CLAIM_CREATED = 0, + KZT_LAZY_PREBIND_CLAIM_REUSED, + KZT_LAZY_PREBIND_CLAIM_CONFLICT, + KZT_LAZY_PREBIND_CLAIM_RETIRED, + KZT_LAZY_PREBIND_CLAIM_FAIL_OPEN, +} kzt_lazy_prebind_claim_result_t; + +typedef struct kzt_lazy_prebind_record { + kzt_lazy_prebind_identity_t source; + kzt_lazy_prebind_identity_t provider; + uintptr_t slot_addr; + uintptr_t expected_slot; + unsigned long relocation_index; + uintptr_t bridge_target; + unsigned long bridge_generation; + int bridge_custom_wrapper; + kzt_symbol_version_evidence_t version_evidence; + char symbol[KZT_LAZY_PREBIND_TEXT_MAX]; + char version[KZT_LAZY_PREBIND_TEXT_MAX]; + kzt_guest_symbol_scope_result_t scope_proof; + uint64_t scope_epoch; +} kzt_lazy_prebind_record_t; + +typedef struct kzt_lazy_prebind_lease { + kzt_lazy_prebind_scope_t *scope; + void *entry; + kzt_lazy_prebind_record_t record; + kzt_lazy_prebind_lease_operation_t operation; + int active; +} kzt_lazy_prebind_lease_t; + +kzt_lazy_prebind_scope_t *kzt_lazy_prebind_scope_init(void); +void kzt_lazy_prebind_scope_destroy(kzt_lazy_prebind_scope_t **scope); + +uint64_t kzt_lazy_prebind_scope_epoch(kzt_lazy_prebind_scope_t *scope); +uint64_t kzt_lazy_prebind_scope_mutate( + kzt_lazy_prebind_scope_t *scope, kzt_lazy_prebind_mutation_t mutation); + +kzt_lazy_prebind_claim_result_t kzt_lazy_prebind_scope_claim( + kzt_lazy_prebind_scope_t *scope, + const kzt_lazy_prebind_record_t *record); + +/* `expected` may omit provider/bridge fields to request the exact cached + * source/slot/symbol/version record for the current scope epoch. */ +int kzt_lazy_prebind_scope_acquire( + kzt_lazy_prebind_scope_t *scope, + const kzt_lazy_prebind_record_t *expected, + kzt_lazy_prebind_lease_t *lease); +void kzt_lazy_prebind_scope_release(kzt_lazy_prebind_lease_t *lease); + +/* Only one current record owner can publish a speculative bridge. The + * caller must CAS expected_slot to bridge_target before finish(..., 1). */ +int kzt_lazy_prebind_scope_publish_acquire( + kzt_lazy_prebind_scope_t *scope, + const kzt_lazy_prebind_record_t *expected, + kzt_lazy_prebind_lease_t *lease); +void kzt_lazy_prebind_scope_publish_finish(kzt_lazy_prebind_lease_t *lease, + int published); + +/* After mutate or retire has closed an entry, return one speculative bridge + * that still needs bridge_target -> expected_slot CAS. `identity == NULL` + * revokes every closed entry. Return 0 for one lease, 1 when exhausted. */ +int kzt_lazy_prebind_scope_revoke_acquire( + kzt_lazy_prebind_scope_t *scope, + const kzt_lazy_prebind_identity_t *identity, + kzt_lazy_prebind_lease_t *lease); +void kzt_lazy_prebind_scope_revoke_finish(kzt_lazy_prebind_lease_t *lease, + int revoked); + +/* A lease can become non-writable while it is held when retire or a scope + * mutation wins. Call this immediately before the final slot validation. */ +int kzt_lazy_prebind_scope_lease_valid( + const kzt_lazy_prebind_lease_t *lease); + +/* Invalidates source and provider records for one exact retired generation, + * then drains only the matching record leases. */ +int kzt_lazy_prebind_scope_retire( + kzt_lazy_prebind_scope_t *scope, + const kzt_lazy_prebind_identity_t *identity); + +#endif diff --git a/target/i386/latx/include/kzt_lifecycle_diagnostics.h b/target/i386/latx/include/kzt_lifecycle_diagnostics.h new file mode 100644 index 00000000000..ab9558d76d8 --- /dev/null +++ b/target/i386/latx/include/kzt_lifecycle_diagnostics.h @@ -0,0 +1,28 @@ +#ifndef KZT_LIFECYCLE_DIAGNOSTICS_H +#define KZT_LIFECYCLE_DIAGNOSTICS_H + +#include + +typedef enum kzt_lifecycle_diagnostic_stage { + KZT_LIFECYCLE_SCOPE_INVALIDATE = 0, + KZT_LIFECYCLE_GUEST_DLOPEN, + KZT_LIFECYCLE_DLOPEN_FINISH, + KZT_LIFECYCLE_GUEST_DLCLOSE, + KZT_LIFECYCLE_UNLOAD_PROBE, + KZT_LIFECYCLE_REGISTRY_RETIRE, + KZT_LIFECYCLE_BINDING_CLEANUP, + KZT_LIFECYCLE_REOBSERVE, + KZT_LIFECYCLE_PREBIND_REFRESH, + KZT_LIFECYCLE_SCOPED_REOBSERVE, + KZT_LIFECYCLE_SCOPED_PREBIND_REFRESH, + KZT_LIFECYCLE_TARGET_PREPARE, + KZT_LIFECYCLE_STAGE_COUNT, +} kzt_lifecycle_diagnostic_stage_t; + +int kzt_lifecycle_diagnostics_enabled(void); +uint64_t kzt_lifecycle_diagnostics_now(void); +void kzt_lifecycle_diagnostics_add( + kzt_lifecycle_diagnostic_stage_t stage, uint64_t duration_ns); +void kzt_lifecycle_diagnostics_report(void); + +#endif diff --git a/target/i386/latx/include/kzt_loader_callback_scope.h b/target/i386/latx/include/kzt_loader_callback_scope.h index 54e5f7e6d3a..9ff8480a8bb 100644 --- a/target/i386/latx/include/kzt_loader_callback_scope.h +++ b/target/i386/latx/include/kzt_loader_callback_scope.h @@ -8,6 +8,7 @@ typedef struct kzt_guest_library_loader_scope { kzt_guest_library_bindings_t *bindings; unsigned long identity; unsigned long cookie; + int prebind_refresh_pending; } kzt_guest_library_loader_scope_t; #endif diff --git a/target/i386/latx/include/kzt_loader_event_hook.h b/target/i386/latx/include/kzt_loader_event_hook.h new file mode 100644 index 00000000000..530ed32aad8 --- /dev/null +++ b/target/i386/latx/include/kzt_loader_event_hook.h @@ -0,0 +1,131 @@ +#ifndef KZT_LOADER_EVENT_HOOK_H +#define KZT_LOADER_EVENT_HOOK_H + +#include +#include + +#include "kzt_guest_scope_layout.h" + +#define KZT_LOADER_EVENT_HOOK_BUILD_ID_SIZE 41 +#define KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID \ + "c591a5df63f461bfdafb01908ca16845b375fa37" +#define KZT_LOADER_EVENT_HOOK_DEBUG_STATE_OFFSET 0x3630 +#define KZT_LOADER_EVENT_HOOK_R_DEBUG_OFFSET 0x36e58 + +typedef enum kzt_loader_event_hook_result { + KZT_LOADER_EVENT_HOOK_INSTALLED = 0, + KZT_LOADER_EVENT_HOOK_FAIL_OPEN_DISABLED, + KZT_LOADER_EVENT_HOOK_FAIL_OPEN_BUILD_ID_READ, + KZT_LOADER_EVENT_HOOK_FAIL_OPEN_UNKNOWN_BUILD_ID, + KZT_LOADER_EVENT_HOOK_FAIL_OPEN_PATTERN_MISMATCH, +} kzt_loader_event_hook_result_t; + +typedef enum kzt_loader_lifecycle_result { + KZT_LOADER_LIFECYCLE_OK = 0, + KZT_LOADER_LIFECYCLE_DISABLED, + KZT_LOADER_LIFECYCLE_INVALID, + KZT_LOADER_LIFECYCLE_ALLOCATION, + KZT_LOADER_LIFECYCLE_OVERFLOW, +} kzt_loader_lifecycle_result_t; + +typedef struct kzt_loader_event_hook { + char build_id[KZT_LOADER_EVENT_HOOK_BUILD_ID_SIZE]; + uintptr_t callback_addr; + unsigned int link_map_reg; + unsigned int installed; + kzt_loader_event_hook_result_t result; + kzt_guest_scope_layout_t scope_layout; + uint64_t event_sequence; + uintptr_t debug_state_addr; + uintptr_t r_debug_addr; + unsigned int lifecycle_enabled; + unsigned int lifecycle_lock; + unsigned int lifecycle_publishers; + unsigned int lifecycle_result; + size_t pending_delete_count; + size_t pending_delete_capacity; + struct kzt_loader_lifecycle_identity *pending_delete; +} kzt_loader_event_hook_t; + +typedef struct kzt_loader_event { + uintptr_t link_map_addr; + uint64_t sequence; + uint64_t published_ns; +} kzt_loader_event_t; + +typedef enum kzt_loader_debug_state { + KZT_LOADER_DEBUG_CONSISTENT = 0, + KZT_LOADER_DEBUG_ADD = 1, + KZT_LOADER_DEBUG_DELETE = 2, +} kzt_loader_debug_state_t; + +typedef struct kzt_loader_lifecycle_identity { + uintptr_t link_map_addr; + unsigned long generation; + uintptr_t namespace_id; +} kzt_loader_lifecycle_identity_t; + +typedef int (*kzt_loader_lifecycle_resolve_fn)( + uintptr_t link_map_addr, + kzt_loader_lifecycle_identity_t *identity, + void *opaque); +typedef int (*kzt_loader_lifecycle_transition_fn)( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque); +typedef void (*kzt_loader_lifecycle_unload_fn)( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque); + +/* Installation-time parser for an x86_64 loader Build ID. It is deliberately + * outside the publish path, which only releases an already-authorized event. */ +int kzt_loader_event_hook_read_build_id( + const char *path, char build_id[KZT_LOADER_EVENT_HOOK_BUILD_ID_SIZE]); + +int kzt_loader_event_hook_install(kzt_loader_event_hook_t *hook, + const char *build_id, + uintptr_t callback_addr, + unsigned int link_map_reg, + int pattern_matched); + +/* Test-only environment override for proving a known Build ID cannot bypass + * an instruction-pattern mismatch. */ +int kzt_loader_event_hook_pattern_allowed(int pattern_matched); + +/* The callback-side interface: publish an exact link_map and no loader work. */ +int kzt_loader_event_hook_publish(kzt_loader_event_hook_t *hook, + uintptr_t link_map_addr, + kzt_loader_event_t *event); + +int kzt_loader_event_hook_enable_lifecycle( + kzt_loader_event_hook_t *hook, + uintptr_t debug_state_addr, + uintptr_t r_debug_addr); +int kzt_loader_event_hook_publish_lifecycle( + kzt_loader_event_hook_t *hook, + kzt_loader_debug_state_t state, + const uintptr_t *live_maps, + size_t live_map_count, + kzt_loader_lifecycle_resolve_fn resolve, + kzt_loader_lifecycle_transition_fn prepare, + kzt_loader_lifecycle_transition_fn cancel, + kzt_loader_lifecycle_unload_fn unload, + void *opaque); +int kzt_loader_event_hook_destroy(kzt_loader_event_hook_t *hook); + +kzt_guest_scope_layout_t kzt_loader_event_hook_scope_layout( + const kzt_loader_event_hook_t *hook); + +const char *kzt_loader_event_hook_result_name( + kzt_loader_event_hook_result_t result); + +kzt_loader_lifecycle_result_t kzt_loader_event_hook_lifecycle_result( + const kzt_loader_event_hook_t *hook); + +const char *kzt_loader_lifecycle_result_name( + kzt_loader_lifecycle_result_t result); + +#ifdef KZT_LOADER_EVENT_HOOK_TEST +void kzt_loader_event_hook_test_set_alloc_failure_after(long allocations); +#endif + +#endif diff --git a/target/i386/latx/include/kzt_loader_lifecycle_snapshot.h b/target/i386/latx/include/kzt_loader_lifecycle_snapshot.h new file mode 100644 index 00000000000..238205cbdeb --- /dev/null +++ b/target/i386/latx/include/kzt_loader_lifecycle_snapshot.h @@ -0,0 +1,52 @@ +#ifndef KZT_LOADER_LIFECYCLE_SNAPSHOT_H +#define KZT_LOADER_LIFECYCLE_SNAPSHOT_H + +#include +#include + +#include "kzt_guest_link_map_reader.h" +#include "kzt_loader_event_hook.h" + +#define KZT_LOADER_LIFECYCLE_SNAPSHOT_INLINE_MAPS 64 + +typedef enum kzt_loader_lifecycle_snapshot_result { + KZT_LOADER_LIFECYCLE_SNAPSHOT_OK = 0, + KZT_LOADER_LIFECYCLE_SNAPSHOT_INVALID_INPUT, + KZT_LOADER_LIFECYCLE_SNAPSHOT_READ_ERROR, + KZT_LOADER_LIFECYCLE_SNAPSHOT_INVALID_STATE, + KZT_LOADER_LIFECYCLE_SNAPSHOT_CYCLE, + KZT_LOADER_LIFECYCLE_SNAPSHOT_NAMESPACE, + KZT_LOADER_LIFECYCLE_SNAPSHOT_ALLOCATION, + KZT_LOADER_LIFECYCLE_SNAPSHOT_OVERFLOW, +} kzt_loader_lifecycle_snapshot_result_t; + +typedef struct kzt_loader_lifecycle_snapshot { + kzt_loader_debug_state_t state; + kzt_loader_lifecycle_snapshot_result_t result; + uintptr_t *live_maps; + size_t live_map_count; + size_t live_map_capacity; + uintptr_t inline_live_maps[ + KZT_LOADER_LIFECYCLE_SNAPSHOT_INLINE_MAPS]; +} kzt_loader_lifecycle_snapshot_t; + +/* The caller owns the token. Initialize it before first capture and call + * release after every successful or failed capture before reusing it. */ +int kzt_loader_lifecycle_snapshot_capture( + kzt_guest_registry_t *registry, + uintptr_t r_debug_addr, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_loader_lifecycle_snapshot_t *snapshot); + +void kzt_loader_lifecycle_snapshot_release( + kzt_loader_lifecycle_snapshot_t *snapshot); + +const char *kzt_loader_lifecycle_snapshot_result_name( + kzt_loader_lifecycle_snapshot_result_t result); + +#ifdef KZT_LOADER_LIFECYCLE_SNAPSHOT_TEST +void kzt_loader_lifecycle_snapshot_test_set_alloc_failure_after( + long allocations); +#endif + +#endif diff --git a/tests/unit/kzt/test_guest_library_binding.c b/tests/unit/kzt/test_guest_library_binding.c index bc787480b1b..68076877613 100644 --- a/tests/unit/kzt/test_guest_library_binding.c +++ b/tests/unit/kzt/test_guest_library_binding.c @@ -7,12 +7,108 @@ #include "kzt_guest_library_binding.h" #include "kzt_guest_registry.h" +#ifdef __APPLE__ +typedef struct pthread_barrier { + pthread_mutex_t lock; + pthread_cond_t cond; + unsigned int count; + unsigned int target; + unsigned int generation; +} pthread_barrier_t; + +#define PTHREAD_BARRIER_SERIAL_THREAD 1 + +static int pthread_barrier_init(pthread_barrier_t *barrier, + const void *attributes, + unsigned int count) +{ + (void)attributes; + if (!barrier || !count || pthread_mutex_init(&barrier->lock, NULL) != 0) + return -1; + if (pthread_cond_init(&barrier->cond, NULL) != 0) { + pthread_mutex_destroy(&barrier->lock); + return -1; + } + barrier->count = 0; + barrier->target = count; + barrier->generation = 0; + return 0; +} + +static int pthread_barrier_wait(pthread_barrier_t *barrier) +{ + unsigned int generation; + + pthread_mutex_lock(&barrier->lock); + generation = barrier->generation; + if (++barrier->count == barrier->target) { + barrier->count = 0; + ++barrier->generation; + pthread_cond_broadcast(&barrier->cond); + pthread_mutex_unlock(&barrier->lock); + return PTHREAD_BARRIER_SERIAL_THREAD; + } + while (generation == barrier->generation) + pthread_cond_wait(&barrier->cond, &barrier->lock); + pthread_mutex_unlock(&barrier->lock); + return 0; +} + +static int pthread_barrier_destroy(pthread_barrier_t *barrier) +{ + int cond_result = pthread_cond_destroy(&barrier->cond); + int mutex_result = pthread_mutex_destroy(&barrier->lock); + + return cond_result ? cond_result : mutex_result; +} + +static int macos_pthread_cond_clockwait(pthread_cond_t *cond, + pthread_mutex_t *lock, + clockid_t clock_id, + const struct timespec *deadline) +{ + struct timespec now, realtime, timeout; + time_t seconds; + long nanoseconds; + + if (clock_gettime(clock_id, &now) != 0 || + clock_gettime(CLOCK_REALTIME, &realtime) != 0) + return -1; + seconds = deadline->tv_sec - now.tv_sec; + nanoseconds = deadline->tv_nsec - now.tv_nsec; + if (nanoseconds < 0) { + --seconds; + nanoseconds += 1000000000L; + } + if (seconds < 0) { + seconds = 0; + nanoseconds = 0; + } + timeout.tv_sec = realtime.tv_sec + seconds; + timeout.tv_nsec = realtime.tv_nsec + nanoseconds; + if (timeout.tv_nsec >= 1000000000L) { + ++timeout.tv_sec; + timeout.tv_nsec -= 1000000000L; + } + return pthread_cond_timedwait(cond, lock, &timeout); +} + +#define pthread_cond_clockwait macos_pthread_cond_clockwait +#endif + typedef struct fake_library { int id; } fake_library_t; static int failures; +static library_t *exact_cleanup_library; #define CHECK(name, expr) do { if (!(expr)) { \ fprintf(stderr, "FAIL %s\n", name); ++failures; } } while (0) +static void exact_cleanup(library_t *library, void *opaque) +{ + CHECK("exact cleanup opaque", opaque == (void *)(uintptr_t)0x51); + exact_cleanup_library = library; +} + static kzt_guest_library_binding_key_t key(uintptr_t map, unsigned long generation) { @@ -51,6 +147,49 @@ static unsigned long registry_generation(kzt_guest_registry_t *registry, return generation; } +static int reverse_outputs_are_clear( + const kzt_guest_library_binding_key_t *key, + const kzt_guest_library_handle_t *handle) +{ + return key->link_map_addr == 0 && key->generation == 0 && + key->namespace_id == 0 && + key->namespace_kind == KZT_GUEST_LIBRARY_NAMESPACE_MAIN && + handle->bindings == NULL && handle->entry == NULL && + handle->library == NULL && + handle->object_type == KZT_GUEST_LIBRARY_OBJECT_MAIN; +} + +static void test_reverse_lookup_unique_main_binding(void) +{ + fake_library_t lib = { 80 }; + kzt_guest_library_access_t access; + kzt_guest_library_binding_key_t expected = key(0x11000, 81); + kzt_guest_library_binding_key_t found = { 0 }; + kzt_guest_library_handle_t handle = { 0 }; + + CHECK("reverse unique access init", + kzt_guest_library_access_init(&access) == 0); + CHECK("reverse unique track", kzt_guest_library_track( + access.bindings, (library_t *)&lib) == 0); + CHECK("reverse unique bind", kzt_guest_library_bind( + access.bindings, &expected, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("reverse unique lookup", + kzt_guest_library_access_lookup_by_library( + &access, (library_t *)&lib, &found, &handle) == 0); + CHECK("reverse unique key", + found.link_map_addr == expected.link_map_addr && + found.generation == expected.generation && + found.namespace_id == expected.namespace_id && + found.namespace_kind == expected.namespace_kind); + CHECK("reverse unique handle", + handle.library == (library_t *)&lib && + handle.object_type == KZT_GUEST_LIBRARY_OBJECT_WRAPPED); + kzt_guest_library_handle_release(&handle); + kzt_guest_library_access_destroy(&access); +} + static void test_init_failure_is_fail_open(void) { fake_library_t lib = { 1 }; @@ -64,6 +203,104 @@ static void test_init_failure_is_fail_open(void) kzt_guest_library_binding_test_set_alloc_failure_after(-1); } +static void test_loader_quiescence_lease_acquire_release(void) +{ + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_quiescence_lease_t first = { 0 }; + kzt_guest_library_loader_quiescence_lease_t second = { 0 }; + kzt_guest_library_loader_quiescence_lease_t copied = { 0 }; + unsigned int readers = 0; + + CHECK("loader lease init", bindings != NULL); + if (!bindings) return; + CHECK("loader lease acquire", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &first) == 0); + CHECK("loader lease token", + first.bindings == bindings && first.cookie != 0); + CHECK("loader lease concurrent acquire", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &second) == 0); + copied = first; + kzt_guest_library_loader_quiescence_release(&copied); + CHECK("loader lease copied token ignored", + kzt_guest_library_binding_test_loader_state( + bindings, &readers, NULL, NULL, NULL) == 0 && + readers == 2); + kzt_guest_library_loader_quiescence_release(&first); + CHECK("loader lease release clears token", + first.bindings == NULL && first.cookie == 0); + kzt_guest_library_loader_quiescence_release(&second); + CHECK("loader lease reacquire after all readers", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &second) == 0); + kzt_guest_library_loader_quiescence_release(&second); + kzt_guest_library_bindings_destroy(&bindings); +} + +static void test_loader_quiescence_writer_token_is_stable(void) +{ + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_quiescence_writer_t writer = { 0 }; + kzt_guest_library_loader_quiescence_writer_t copied = { 0 }; + kzt_guest_library_loader_quiescence_lease_t reader = { 0 }; + unsigned int waiters = 0; + + CHECK("writer token init", bindings != NULL); + if (!bindings) return; + CHECK("writer token begin", + kzt_guest_library_loader_quiescence_writer_begin( + bindings, &writer) == 0); + CHECK("writer token active", + writer.bindings == bindings && writer.cookie != 0); + copied = writer; + kzt_guest_library_loader_quiescence_writer_end(&copied); + CHECK("writer copied token ignored", + kzt_guest_library_binding_test_loader_state( + bindings, NULL, &waiters, NULL, NULL) == 0 && + waiters == 1); + CHECK("writer token rejects reader", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &reader) != 0); + kzt_guest_library_loader_quiescence_writer_end(&writer); + CHECK("writer release clears token", + writer.bindings == NULL && writer.cookie == 0); + CHECK("writer release reopens reader admission", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &reader) == 0); + kzt_guest_library_loader_quiescence_release(&reader); + kzt_guest_library_bindings_destroy(&bindings); +} + +static void test_loader_quiescence_lease_rejects_active_scope(void) +{ + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_scope_t scope = { 0 }; + kzt_guest_library_loader_quiescence_lease_t lease = { + .bindings = (kzt_guest_library_bindings_t *)(uintptr_t)1, + .cookie = 1, + }; + + CHECK("active scope lease init", bindings != NULL); + if (!bindings) return; + CHECK("active scope begin", kzt_guest_library_loader_scope_begin( + bindings, &scope) == 0); + CHECK("active scope rejects lease", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &lease) != 0); + CHECK("active scope clears rejected lease", + lease.bindings == NULL && lease.cookie == 0); + kzt_guest_library_loader_scope_end(&scope); + CHECK("ended scope permits lease", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &lease) == 0); + kzt_guest_library_loader_quiescence_release(&lease); + kzt_guest_library_bindings_destroy(&bindings); +} + static void test_both_arrival_orders_and_retry(void) { fake_library_t wrapped = { 2 }, emulated = { 3 }; @@ -342,6 +579,37 @@ static void test_missing_unload_hint_preserves_unclaimed_observation(void) kzt_guest_registry_destroy(®istry); } +static void test_exact_pinned_cleanup_consumes_handle(void) +{ + fake_library_t lib = { 49 }; + kzt_guest_library_bindings_t *bindings = kzt_guest_library_bindings_init(); + kzt_guest_library_binding_key_t exact = key(0x9d80, 17); + kzt_guest_library_handle_t handle = { 0 }; + + exact_cleanup_library = NULL; + CHECK("exact-cleanup track", kzt_guest_library_track( + bindings, (library_t *)&lib) == 0); + CHECK("exact-cleanup observe", kzt_guest_library_note_observation( + bindings, &exact) == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); + CHECK("exact-cleanup bind", kzt_guest_library_note_exact_pair( + bindings, exact.link_map_addr, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("exact-cleanup lookup", kzt_guest_library_lookup( + bindings, &exact, &handle) == 0); + CHECK("exact-cleanup consume", + kzt_guest_library_cleanup_exact_handle( + &handle, exact_cleanup, (void *)(uintptr_t)0x51) == 0); + CHECK("exact-cleanup handle cleared", + handle.bindings == NULL && handle.entry == NULL && + handle.library == NULL); + CHECK("exact-cleanup callback pinned library", + exact_cleanup_library == (library_t *)&lib); + CHECK("exact-cleanup lookup closed", kzt_guest_library_lookup( + bindings, &exact, &handle) != 0); + kzt_guest_library_bindings_destroy(&bindings); +} + static void test_dead_library_stale_hint_does_not_retire_reused_address(void) { fake_library_t old = { 47 }, replacement = { 48 }; @@ -1093,6 +1361,67 @@ static void test_lookup_and_unbind_threads(void) kzt_guest_library_bindings_destroy(&bindings); } +static void test_reverse_lookup_handle_pins_unload(void) +{ + fake_library_t lib = { 84 }; + kzt_guest_library_access_t access; + kzt_guest_library_binding_key_t expected = key(0x16000, 86); + kzt_guest_library_binding_key_t found = { 0 }; + kzt_guest_library_handle_t held = { 0 }, probe = { 0 }; + pthread_t thread; + unbind_thread_arg_t arg = { + .library = (library_t *)&lib, + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + + CHECK("reverse unload access init", + kzt_guest_library_access_init(&access) == 0); + arg.bindings = access.bindings; + CHECK("reverse unload track", kzt_guest_library_track( + access.bindings, (library_t *)&lib) == 0); + CHECK("reverse unload bind", kzt_guest_library_bind( + access.bindings, &expected, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("reverse unload acquire", + kzt_guest_library_access_lookup_by_library( + &access, (library_t *)&lib, &found, &held) == 0); + CHECK("reverse unload thread", + pthread_create(&thread, NULL, unbind_thread, &arg) == 0); + pthread_mutex_lock(&arg.lock); + while (!arg.started) pthread_cond_wait(&arg.cond, &arg.lock); + pthread_mutex_unlock(&arg.lock); + do { + found = expected; + probe = (kzt_guest_library_handle_t){ + .bindings = (kzt_guest_library_bindings_t *)(uintptr_t)1, + .entry = (void *)(uintptr_t)2, + .library = (library_t *)(uintptr_t)3, + .object_type = KZT_GUEST_LIBRARY_OBJECT_WRAPPED, + }; + if (kzt_guest_library_access_lookup_by_library( + &access, (library_t *)&lib, &found, &probe) != 0) + break; + kzt_guest_library_handle_release(&probe); + } while (1); + CHECK("reverse unload closes lookup", + reverse_outputs_are_clear(&found, &probe)); + pthread_mutex_lock(&arg.lock); + CHECK("reverse unload waits for handle", !arg.done); + pthread_mutex_unlock(&arg.lock); + kzt_guest_library_handle_release(&held); + CHECK("reverse unload join", pthread_join(thread, NULL) == 0); + found = expected; + CHECK("reverse unload dead lookup", + kzt_guest_library_access_lookup_by_library( + &access, (library_t *)&lib, &found, &probe) != 0 && + reverse_outputs_are_clear(&found, &probe)); + pthread_cond_destroy(&arg.cond); + pthread_mutex_destroy(&arg.lock); + kzt_guest_library_access_destroy(&access); +} + typedef struct lock_order_sync { pthread_mutex_t lock; pthread_cond_t cond; @@ -1190,6 +1519,267 @@ static int timed_wait_for_flag(pthread_cond_t *cond, pthread_mutex_t *lock, return 0; } +typedef struct loader_scope_wait_arg { + kzt_guest_library_bindings_t *bindings; + pthread_mutex_t lock; + pthread_cond_t cond; + int started; + int done; + int result; +} loader_scope_wait_arg_t; + +static void *loader_scope_wait_thread(void *opaque) +{ + loader_scope_wait_arg_t *arg = opaque; + kzt_guest_library_loader_scope_t scope = { 0 }; + + pthread_mutex_lock(&arg->lock); + arg->started = 1; + pthread_cond_broadcast(&arg->cond); + pthread_mutex_unlock(&arg->lock); + arg->result = kzt_guest_library_loader_scope_begin( + arg->bindings, &scope); + if (arg->result == 0) + kzt_guest_library_loader_scope_end(&scope); + pthread_mutex_lock(&arg->lock); + arg->done = 1; + pthread_cond_broadcast(&arg->cond); + pthread_mutex_unlock(&arg->lock); + return NULL; +} + +static void test_loader_scope_waits_for_all_quiescence_leases(void) +{ + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_quiescence_lease_t first = { 0 }; + kzt_guest_library_loader_quiescence_lease_t second = { 0 }; + loader_scope_wait_arg_t arg = { + .bindings = bindings, + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + pthread_t thread; + unsigned int waiters = 0; + kzt_guest_library_loader_quiescence_lease_t late = { + .bindings = (kzt_guest_library_bindings_t *)(uintptr_t)1, + .cookie = 1, + }; + + CHECK("lease wait init", bindings != NULL); + if (!bindings) return; + CHECK("lease wait acquire", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &first) == 0); + CHECK("lease wait concurrent acquire", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &second) == 0); + CHECK("lease wait thread", pthread_create( + &thread, NULL, loader_scope_wait_thread, &arg) == 0); + pthread_mutex_lock(&arg.lock); + CHECK("lease wait worker started", + timed_wait_for_flag(&arg.cond, &arg.lock, &arg.started) == 0); + pthread_mutex_unlock(&arg.lock); + for (int i = 0; i < 1000; ++i) { + struct timespec delay = { .tv_nsec = 1000000L }; + + CHECK("lease wait snapshot", + kzt_guest_library_binding_test_loader_state( + bindings, NULL, &waiters, NULL, NULL) == 0); + if (waiters == 1) break; + pthread_mutex_lock(&arg.lock); + int done = arg.done; + pthread_mutex_unlock(&arg.lock); + if (done) break; + nanosleep(&delay, NULL); + } + pthread_mutex_lock(&arg.lock); + CHECK("loader begin waits while lease held", + waiters == 1 && !arg.done); + pthread_mutex_unlock(&arg.lock); + CHECK("waiting loader rejects later lease", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &late) != 0); + CHECK("waiting loader clears rejected lease", + late.bindings == NULL && late.cookie == 0); + kzt_guest_library_loader_quiescence_release(&first); + pthread_mutex_lock(&arg.lock); + CHECK("loader begin still waits for second lease", !arg.done); + pthread_mutex_unlock(&arg.lock); + kzt_guest_library_loader_quiescence_release(&second); + pthread_mutex_lock(&arg.lock); + CHECK("loader begin resumes after release", + timed_wait_for_flag(&arg.cond, &arg.lock, &arg.done) == 0 && + arg.result == 0); + pthread_mutex_unlock(&arg.lock); + CHECK("lease wait join", pthread_join(thread, NULL) == 0); + pthread_cond_destroy(&arg.cond); + pthread_mutex_destroy(&arg.lock); + kzt_guest_library_bindings_destroy(&bindings); +} + +typedef struct loader_destroy_arg { + kzt_guest_library_bindings_t *bindings; + pthread_mutex_t lock; + pthread_cond_t cond; + int started; + int done; +} loader_destroy_arg_t; + +static void *loader_destroy_thread(void *opaque) +{ + loader_destroy_arg_t *arg = opaque; + + pthread_mutex_lock(&arg->lock); + arg->started = 1; + pthread_cond_broadcast(&arg->cond); + pthread_mutex_unlock(&arg->lock); + kzt_guest_library_bindings_destroy(&arg->bindings); + pthread_mutex_lock(&arg->lock); + arg->done = 1; + pthread_cond_broadcast(&arg->cond); + pthread_mutex_unlock(&arg->lock); + return NULL; +} + +static void test_loader_quiescence_teardown_drains_readers_and_waiters(void) +{ + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_quiescence_lease_t first = { 0 }; + kzt_guest_library_loader_quiescence_lease_t second = { 0 }; + loader_scope_wait_arg_t waiter = { + .bindings = bindings, + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + loader_destroy_arg_t destroy = { + .bindings = bindings, + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + pthread_t waiter_thread, destroy_thread; + unsigned int waiters = 0; + int is_shutting_down = 0; + + CHECK("lease teardown init", bindings != NULL); + if (!bindings) return; + CHECK("lease teardown acquire first", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &first) == 0); + CHECK("lease teardown acquire second", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &second) == 0); + CHECK("lease teardown waiter thread", pthread_create( + &waiter_thread, NULL, loader_scope_wait_thread, &waiter) == 0); + for (int i = 0; i < 1000; ++i) { + struct timespec delay = { .tv_nsec = 1000000L }; + + CHECK("lease teardown waiter snapshot", + kzt_guest_library_binding_test_loader_state( + bindings, NULL, &waiters, NULL, NULL) == 0); + if (waiters == 1) break; + nanosleep(&delay, NULL); + } + CHECK("lease teardown waiter blocked", waiters == 1); + CHECK("lease teardown destroy thread", pthread_create( + &destroy_thread, NULL, loader_destroy_thread, &destroy) == 0); + pthread_mutex_lock(&destroy.lock); + CHECK("lease teardown destroy started", + timed_wait_for_flag( + &destroy.cond, &destroy.lock, &destroy.started) == 0); + pthread_mutex_unlock(&destroy.lock); + for (int i = 0; i < 1000; ++i) { + struct timespec delay = { .tv_nsec = 1000000L }; + + CHECK("lease teardown shutdown snapshot", + kzt_guest_library_binding_test_loader_state( + bindings, NULL, NULL, NULL, &is_shutting_down) == 0); + if (is_shutting_down) break; + nanosleep(&delay, NULL); + } + CHECK("lease teardown gate closed", is_shutting_down); + pthread_mutex_lock(&waiter.lock); + CHECK("lease teardown wakes loader waiter", + timed_wait_for_flag( + &waiter.cond, &waiter.lock, &waiter.done) == 0 && + waiter.result != 0); + pthread_mutex_unlock(&waiter.lock); + pthread_mutex_lock(&destroy.lock); + CHECK("lease teardown waits for readers", !destroy.done); + pthread_mutex_unlock(&destroy.lock); + kzt_guest_library_loader_quiescence_release(&first); + pthread_mutex_lock(&destroy.lock); + CHECK("lease teardown waits for last reader", !destroy.done); + pthread_mutex_unlock(&destroy.lock); + kzt_guest_library_loader_quiescence_release(&second); + pthread_mutex_lock(&destroy.lock); + CHECK("lease teardown completes after last reader", + timed_wait_for_flag( + &destroy.cond, &destroy.lock, &destroy.done) == 0); + pthread_mutex_unlock(&destroy.lock); + CHECK("lease teardown waiter join", + pthread_join(waiter_thread, NULL) == 0); + CHECK("lease teardown destroy join", + pthread_join(destroy_thread, NULL) == 0); + CHECK("lease teardown destroyed binding", destroy.bindings == NULL); + pthread_cond_destroy(&waiter.cond); + pthread_mutex_destroy(&waiter.lock); + pthread_cond_destroy(&destroy.cond); + pthread_mutex_destroy(&destroy.lock); +} + +static void test_loader_quiescence_teardown_drains_writer(void) +{ + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_quiescence_writer_t writer = { 0 }; + loader_destroy_arg_t destroy = { + .bindings = bindings, + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + pthread_t destroy_thread; + int is_shutting_down = 0; + + CHECK("writer teardown init", bindings != NULL); + if (!bindings) return; + CHECK("writer teardown begin", + kzt_guest_library_loader_quiescence_writer_begin( + bindings, &writer) == 0); + CHECK("writer teardown destroy thread", pthread_create( + &destroy_thread, NULL, loader_destroy_thread, &destroy) == 0); + pthread_mutex_lock(&destroy.lock); + CHECK("writer teardown destroy started", + timed_wait_for_flag( + &destroy.cond, &destroy.lock, &destroy.started) == 0); + pthread_mutex_unlock(&destroy.lock); + for (int i = 0; i < 1000; ++i) { + struct timespec delay = { .tv_nsec = 1000000L }; + + CHECK("writer teardown shutdown snapshot", + kzt_guest_library_binding_test_loader_state( + bindings, NULL, NULL, NULL, &is_shutting_down) == 0); + if (is_shutting_down) break; + nanosleep(&delay, NULL); + } + CHECK("writer teardown gate closed", is_shutting_down); + pthread_mutex_lock(&destroy.lock); + CHECK("writer teardown waits for writer", !destroy.done); + pthread_mutex_unlock(&destroy.lock); + kzt_guest_library_loader_quiescence_writer_end(&writer); + pthread_mutex_lock(&destroy.lock); + CHECK("writer teardown completes after release", + timed_wait_for_flag( + &destroy.cond, &destroy.lock, &destroy.done) == 0); + pthread_mutex_unlock(&destroy.lock); + CHECK("writer teardown destroy join", + pthread_join(destroy_thread, NULL) == 0); + CHECK("writer teardown destroyed binding", destroy.bindings == NULL); + pthread_cond_destroy(&destroy.cond); + pthread_mutex_destroy(&destroy.lock); +} + typedef struct observation_unload_sync { pthread_mutex_t lock; pthread_cond_t cond; @@ -1658,9 +2248,239 @@ static void test_context_access_closes_before_destroy(void) kzt_guest_library_access_destroy(&access); } +static void test_reverse_lookup_handle_pins_teardown(void) +{ + fake_library_t lib = { 85 }; + kzt_guest_library_access_t access; + kzt_guest_library_binding_key_t expected = key(0x17000, 87); + kzt_guest_library_binding_key_t found = { 0 }; + kzt_guest_library_handle_t held = { 0 }, probe = { 0 }; + pthread_t thread; + access_teardown_arg_t arg = { + .access = &access, + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + + CHECK("reverse teardown access init", + kzt_guest_library_access_init(&access) == 0); + CHECK("reverse teardown track", kzt_guest_library_track( + access.bindings, (library_t *)&lib) == 0); + CHECK("reverse teardown bind", kzt_guest_library_bind( + access.bindings, &expected, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("reverse teardown acquire", + kzt_guest_library_access_lookup_by_library( + &access, (library_t *)&lib, &found, &held) == 0); + CHECK("reverse teardown thread", pthread_create( + &thread, NULL, access_teardown_thread, &arg) == 0); + pthread_mutex_lock(&arg.lock); + while (!arg.started) pthread_cond_wait(&arg.cond, &arg.lock); + pthread_mutex_unlock(&arg.lock); + do { + found = expected; + probe = (kzt_guest_library_handle_t){ + .bindings = (kzt_guest_library_bindings_t *)(uintptr_t)1, + .entry = (void *)(uintptr_t)2, + .library = (library_t *)(uintptr_t)3, + .object_type = KZT_GUEST_LIBRARY_OBJECT_WRAPPED, + }; + if (kzt_guest_library_access_lookup_by_library( + &access, (library_t *)&lib, &found, &probe) != 0) + break; + kzt_guest_library_handle_release(&probe); + } while (1); + CHECK("reverse teardown closes lookup", + reverse_outputs_are_clear(&found, &probe)); + pthread_mutex_lock(&arg.lock); + CHECK("reverse teardown waits for handle", !arg.done); + pthread_mutex_unlock(&arg.lock); + kzt_guest_library_handle_release(&held); + CHECK("reverse teardown join", pthread_join(thread, NULL) == 0); + found = expected; + CHECK("reverse teardown closed lookup", + kzt_guest_library_access_lookup_by_library( + &access, (library_t *)&lib, &found, &probe) != 0 && + reverse_outputs_are_clear(&found, &probe)); + pthread_cond_destroy(&arg.cond); + pthread_mutex_destroy(&arg.lock); + kzt_guest_library_access_destroy(&access); +} + +static void test_reverse_lookup_zero_match_clears_outputs(void) +{ + fake_library_t lib = { 81 }; + kzt_guest_library_access_t access; + kzt_guest_library_binding_key_t found = key(0x12000, 82); + kzt_guest_library_handle_t handle = { + .bindings = (kzt_guest_library_bindings_t *)(uintptr_t)1, + .entry = (void *)(uintptr_t)2, + .library = (library_t *)(uintptr_t)3, + .object_type = KZT_GUEST_LIBRARY_OBJECT_WRAPPED, + }; + + CHECK("reverse zero access init", + kzt_guest_library_access_init(&access) == 0); + CHECK("reverse zero track", kzt_guest_library_track( + access.bindings, (library_t *)&lib) == 0); + CHECK("reverse zero lookup", + kzt_guest_library_access_lookup_by_library( + &access, (library_t *)&lib, &found, &handle) != 0); + CHECK("reverse zero clears outputs", + reverse_outputs_are_clear(&found, &handle)); + kzt_guest_library_access_destroy(&access); +} + +static void test_wrapped_producer_cannot_own_two_live_identities(void) +{ + fake_library_t lib = { 82 }; + kzt_guest_library_access_t access; + kzt_guest_library_binding_key_t first = key(0x13000, 83); + kzt_guest_library_binding_key_t second = key(0x14000, 84); + kzt_guest_library_binding_key_t found = first; + kzt_guest_library_handle_t handle = { + .bindings = (kzt_guest_library_bindings_t *)(uintptr_t)1, + .entry = (void *)(uintptr_t)2, + .library = (library_t *)(uintptr_t)3, + .object_type = KZT_GUEST_LIBRARY_OBJECT_WRAPPED, + }; + + CHECK("reverse duplicate access init", + kzt_guest_library_access_init(&access) == 0); + CHECK("reverse duplicate track", kzt_guest_library_track( + access.bindings, (library_t *)&lib) == 0); + CHECK("reverse duplicate first bind", kzt_guest_library_bind( + access.bindings, &first, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("wrapped producer second identity conflicts", kzt_guest_library_bind( + access.bindings, &second, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_CONFLICT); + CHECK("wrapped producer first identity remains unique", + kzt_guest_library_access_lookup_by_library( + &access, (library_t *)&lib, &found, &handle) == 0 && + found.link_map_addr == first.link_map_addr && + found.generation == first.generation && + handle.library == (library_t *)&lib); + kzt_guest_library_handle_release(&handle); + kzt_guest_library_inactivate( + access.bindings, NULL, (library_t *)&lib, first.link_map_addr); + CHECK("retired wrapped identity is no longer visible", + kzt_guest_library_access_lookup( + &access, &first, &handle) != 0); + CHECK("wrapped producer reactivates for reload", + kzt_guest_library_reactivate( + access.bindings, (library_t *)&lib) == 0); + CHECK("new generation binds after old identity retires", + kzt_guest_library_bind( + access.bindings, &second, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("reloaded producer has only new identity", + kzt_guest_library_access_lookup_by_library( + &access, (library_t *)&lib, &found, &handle) == 0 && + found.link_map_addr == second.link_map_addr && + found.generation == second.generation); + kzt_guest_library_handle_release(&handle); + kzt_guest_library_access_destroy(&access); +} + +static void test_reverse_lookup_non_main_binding_is_rejected(void) +{ + fake_library_t lib = { 83 }; + kzt_guest_library_access_t access; + kzt_guest_library_binding_key_t explicit = key(0x15000, 85); + kzt_guest_library_binding_key_t found = explicit; + kzt_guest_library_handle_t handle = { + .bindings = (kzt_guest_library_bindings_t *)(uintptr_t)1, + .entry = (void *)(uintptr_t)2, + .library = (library_t *)(uintptr_t)3, + .object_type = KZT_GUEST_LIBRARY_OBJECT_WRAPPED, + }; + + explicit.namespace_id = 7; + explicit.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_EXPLICIT; + CHECK("reverse non-main access init", + kzt_guest_library_access_init(&access) == 0); + CHECK("reverse non-main track", kzt_guest_library_track( + access.bindings, (library_t *)&lib) == 0); + CHECK("reverse non-main publication rejected", kzt_guest_library_bind( + access.bindings, &explicit, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ERROR); + CHECK("reverse non-main lookup", + kzt_guest_library_access_lookup_by_library( + &access, (library_t *)&lib, &found, &handle) != 0); + CHECK("reverse non-main clears outputs", + reverse_outputs_are_clear(&found, &handle)); + kzt_guest_library_access_destroy(&access); +} + +static void test_symbol_evidence_cache_is_generation_local(void) +{ + fake_library_t lib = { 89 }; + kzt_guest_library_access_t access; + kzt_guest_library_binding_key_t first = key(0x17000, 90); + kzt_guest_library_binding_key_t second = key(0x17000, 91); + kzt_guest_library_handle_t handle = { 0 }; + uintptr_t address = 0; + unsigned char type = 0; + + CHECK("symbol cache access init", + kzt_guest_library_access_init(&access) == 0); + CHECK("symbol cache track", kzt_guest_library_track( + access.bindings, (library_t *)&lib) == 0); + CHECK("symbol cache first bind", kzt_guest_library_bind( + access.bindings, &first, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("symbol cache first lookup", kzt_guest_library_access_lookup( + &access, &first, &handle) == 0); + kzt_guest_library_symbol_evidence_store( + &handle, "cached_function", 1, 0x17100, 2); + CHECK("symbol cache first hit", + kzt_guest_library_symbol_evidence_lookup( + &handle, "cached_function", 1, &address, &type) == 0 && + address == 0x17100 && type == 2); + CHECK("symbol cache dynamic revision misses", + kzt_guest_library_symbol_evidence_lookup( + &handle, "cached_function", 2, &address, &type) != 0); + CHECK("symbol cache first hit remains revision local", + kzt_guest_library_symbol_evidence_lookup( + &handle, "cached_function", 1, &address, &type) == 0 && + address == 0x17100 && type == 2); + kzt_guest_library_handle_release(&handle); + + kzt_guest_library_inactivate( + access.bindings, NULL, (library_t *)&lib, first.link_map_addr); + CHECK("symbol cache reactivate", kzt_guest_library_reactivate( + access.bindings, (library_t *)&lib) == 0); + CHECK("symbol cache second bind", kzt_guest_library_bind( + access.bindings, &second, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("symbol cache second lookup", kzt_guest_library_access_lookup( + &access, &second, &handle) == 0); + CHECK("symbol cache old generation misses", + kzt_guest_library_symbol_evidence_lookup( + &handle, "cached_function", 1, &address, &type) != 0); + kzt_guest_library_handle_release(&handle); + kzt_guest_library_access_destroy(&access); +} + int main(void) { + test_reverse_lookup_unique_main_binding(); + test_reverse_lookup_zero_match_clears_outputs(); + test_wrapped_producer_cannot_own_two_live_identities(); + test_reverse_lookup_non_main_binding_is_rejected(); + test_symbol_evidence_cache_is_generation_local(); test_init_failure_is_fail_open(); + test_loader_quiescence_lease_acquire_release(); + test_loader_quiescence_writer_token_is_stable(); + test_loader_quiescence_lease_rejects_active_scope(); test_both_arrival_orders_and_retry(); test_forced_growth_with_held_handle(); test_pending_cancel_and_address_reuse(); @@ -1668,6 +2488,7 @@ int main(void) test_observation_first_unload_retires_registry_generation(); test_unload_retires_only_hinted_unclaimed_observation(); test_missing_unload_hint_preserves_unclaimed_observation(); + test_exact_pinned_cleanup_consumes_handle(); test_dead_library_stale_hint_does_not_retire_reused_address(); test_tracking_allocation_failure_unload_is_fail_open(); test_inactive_can_reload_but_destroyed_cannot(); @@ -1677,13 +2498,18 @@ int main(void) test_failed_loader_keeps_normal_and_fallback_tombstones(); test_loader_scope_identity_and_nesting(); test_concurrent_loader_scopes_do_not_invalidate_each_other(); + test_loader_scope_waits_for_all_quiescence_leases(); + test_loader_quiescence_teardown_drains_readers_and_waiters(); + test_loader_quiescence_teardown_drains_writer(); test_loader_scope_cannot_reopen_unobserved_address(); test_registry_retire_failures_are_observable_fail_open(); test_lookup_and_unbind_threads(); + test_reverse_lookup_handle_pins_unload(); test_concurrent_observation_unloads_keep_exact_owners(); test_duplicate_unload_waits_for_lifecycle_owner(); test_source_lease_unload_allows_provider_lookup(); test_context_access_closes_before_destroy(); + test_reverse_lookup_handle_pins_teardown(); /* Keep last: a broken reader/publication protocol intentionally leaves * its unload thread blocked so the red run remains bounded. */ test_publish_while_reader_preserves_unload_wait(); diff --git a/tests/unit/kzt/test_lazy_prebind_scope.c b/tests/unit/kzt/test_lazy_prebind_scope.c new file mode 100644 index 00000000000..5520355f046 --- /dev/null +++ b/tests/unit/kzt/test_lazy_prebind_scope.c @@ -0,0 +1,314 @@ +#include "kzt_lazy_prebind_scope.h" + +#include +#include +#include +#include +#include +#include + +#include "elf.h" +#define CHECK(label, condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "%s: FAIL\\n", label); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +static kzt_lazy_prebind_record_t record_for(unsigned long generation, + uintptr_t slot) +{ + kzt_lazy_prebind_record_t record = { 0 }; + + record.source = (kzt_lazy_prebind_identity_t) { + .link_map_addr = 0x1000, + .generation = generation, + .namespace_id = 0, + }; + record.provider = (kzt_lazy_prebind_identity_t) { + .link_map_addr = 0x2000, + .generation = 9, + .namespace_id = 0, + }; + record.slot_addr = slot; + record.expected_slot = 0x3000; + record.relocation_index = 7; + record.bridge_target = 0x4000; + record.bridge_generation = 9; + record.version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + strcpy(record.symbol, "dlerror"); + strcpy(record.version, "GLIBC_2.34"); + record.scope_proof.status = KZT_GUEST_SYMBOL_SCOPE_SAFE; + record.scope_proof.reason = KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER; + record.scope_proof.scope_complete = 1; + record.scope_proof.lookup_order_known = 1; + record.scope_proof.selected_provider_link_map = record.provider.link_map_addr; + record.scope_proof.selected_provider_address = 0x5000; + record.scope_proof.selected_provider_binding = STB_GLOBAL; + record.scope_proof.selected_provider_type = STT_FUNC; + record.scope_proof.selected_provider_visibility = STV_DEFAULT; + record.scope_proof.query_fingerprint = 0x6000; + record.scope_proof.scope_identity = (kzt_guest_symbol_scope_identity_t) { + .source = { + .link_map_addr = record.source.link_map_addr, + .generation = record.source.generation, + .namespace_id = record.source.namespace_id, + .namespace_head = record.source.link_map_addr, + .layout = KZT_GUEST_SCOPE_LAYOUT_GLIBC_2_39_C591A5DF, + }, + .scope_array_addr = 0x7000, + .scope_list_count = 1, + .scope_map_count = 2, + .value = 0x8000, + }; + return record; +} + +typedef struct retire_race { + kzt_lazy_prebind_scope_t *scope; + kzt_lazy_prebind_identity_t identity; + pthread_mutex_t lock; + pthread_cond_t cond; + int started; + int finished; + int result; +} retire_race_t; + +typedef struct mutation_race { + kzt_lazy_prebind_scope_t *scope; + pthread_mutex_t lock; + pthread_cond_t cond; + int started; + int finished; + uint64_t epoch; +} mutation_race_t; + +static void *retire_worker(void *opaque) +{ + retire_race_t *race = opaque; + + pthread_mutex_lock(&race->lock); + race->started = 1; + pthread_cond_broadcast(&race->cond); + pthread_mutex_unlock(&race->lock); + race->result = kzt_lazy_prebind_scope_retire(race->scope, &race->identity); + pthread_mutex_lock(&race->lock); + race->finished = 1; + pthread_cond_broadcast(&race->cond); + pthread_mutex_unlock(&race->lock); + return NULL; +} + +static void *mutation_worker(void *opaque) +{ + mutation_race_t *race = opaque; + + pthread_mutex_lock(&race->lock); + race->started = 1; + pthread_cond_broadcast(&race->cond); + pthread_mutex_unlock(&race->lock); + race->epoch = kzt_lazy_prebind_scope_mutate( + race->scope, KZT_LAZY_PREBIND_MUTATION_DLOPEN); + pthread_mutex_lock(&race->lock); + race->finished = 1; + pthread_cond_broadcast(&race->cond); + pthread_mutex_unlock(&race->lock); + return NULL; +} + +static int wait_for(retire_race_t *race, int *value, int expected, + long milliseconds) +{ + struct timespec deadline; + + clock_gettime(CLOCK_REALTIME, &deadline); + deadline.tv_nsec += milliseconds * 1000 * 1000; + if (deadline.tv_nsec >= 1000 * 1000 * 1000) { + ++deadline.tv_sec; + deadline.tv_nsec -= 1000 * 1000 * 1000; + } + pthread_mutex_lock(&race->lock); + while (*value < expected && + pthread_cond_timedwait(&race->cond, &race->lock, &deadline) == 0) { + } + pthread_mutex_unlock(&race->lock); + return *value >= expected; +} + +static int wait_for_mutation(mutation_race_t *race, int *value, int expected, + long milliseconds) +{ + struct timespec deadline; + + clock_gettime(CLOCK_REALTIME, &deadline); + deadline.tv_nsec += milliseconds * 1000 * 1000; + if (deadline.tv_nsec >= 1000 * 1000 * 1000) { + ++deadline.tv_sec; + deadline.tv_nsec -= 1000 * 1000 * 1000; + } + pthread_mutex_lock(&race->lock); + while (*value < expected && + pthread_cond_timedwait(&race->cond, &race->lock, &deadline) == 0) { + } + pthread_mutex_unlock(&race->lock); + return *value >= expected; +} + +static void test_epoch_invalidates_and_replaces_record(void) +{ + kzt_lazy_prebind_scope_t *scope = kzt_lazy_prebind_scope_init(); + kzt_lazy_prebind_record_t record = record_for(1, 0x2000); + kzt_lazy_prebind_lease_t lease = { 0 }; + + CHECK("epoch scope", scope != NULL); + CHECK("epoch starts one", kzt_lazy_prebind_scope_epoch(scope) == 1); + CHECK("epoch first claim", kzt_lazy_prebind_scope_claim(scope, &record) == + KZT_LAZY_PREBIND_CLAIM_CREATED); + CHECK("epoch first acquire", kzt_lazy_prebind_scope_acquire( + scope, &record, &lease) == 0); + CHECK("epoch exact provider", lease.record.provider.link_map_addr == 0x2000 && + lease.record.provider.generation == 9); + kzt_lazy_prebind_scope_release(&lease); + CHECK("epoch advance", kzt_lazy_prebind_scope_mutate( + scope, KZT_LAZY_PREBIND_MUTATION_DLOPEN) == 2); + CHECK("epoch old rejected", kzt_lazy_prebind_scope_acquire( + scope, &record, &lease) != 0); + CHECK("epoch replace claim", kzt_lazy_prebind_scope_claim(scope, &record) == + KZT_LAZY_PREBIND_CLAIM_CREATED); + CHECK("epoch replace acquire", kzt_lazy_prebind_scope_acquire( + scope, &record, &lease) == 0 && lease.record.scope_epoch == 2); + kzt_lazy_prebind_scope_release(&lease); + kzt_lazy_prebind_scope_destroy(&scope); +} + +static void test_retire_waits_lease_and_rejects_address_reuse(void) +{ + kzt_lazy_prebind_scope_t *scope = kzt_lazy_prebind_scope_init(); + kzt_lazy_prebind_record_t old_record = record_for(1, 0x2100); + kzt_lazy_prebind_record_t new_record = record_for(2, 0x2100); + kzt_lazy_prebind_lease_t lease = { 0 }; + retire_race_t race = { + .scope = scope, + .identity = old_record.source, + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + pthread_t thread; + + CHECK("retire scope", scope != NULL); + CHECK("retire claim", kzt_lazy_prebind_scope_claim(scope, &old_record) == + KZT_LAZY_PREBIND_CLAIM_CREATED); + CHECK("retire acquire", kzt_lazy_prebind_scope_acquire(scope, &old_record, + &lease) == 0); + CHECK("retire thread", pthread_create(&thread, NULL, retire_worker, &race) == 0); + CHECK("retire starts", wait_for(&race, &race.started, 1, 100)); + CHECK("retire waits lease", !wait_for(&race, &race.finished, 1, 30)); + CHECK("retire stale acquire blocked", kzt_lazy_prebind_scope_acquire( + scope, &old_record, &(kzt_lazy_prebind_lease_t){ 0 }) != 0); + kzt_lazy_prebind_scope_release(&lease); + CHECK("retire finishes", wait_for(&race, &race.finished, 1, 100)); + CHECK("retire result", race.result == 0); + CHECK("retire join", pthread_join(thread, NULL) == 0); + CHECK("reuse old claim rejected", kzt_lazy_prebind_scope_claim( + scope, &old_record) == KZT_LAZY_PREBIND_CLAIM_RETIRED); + CHECK("reuse new generation claim", kzt_lazy_prebind_scope_claim( + scope, &new_record) == KZT_LAZY_PREBIND_CLAIM_CREATED); + kzt_lazy_prebind_scope_destroy(&scope); + pthread_cond_destroy(&race.cond); + pthread_mutex_destroy(&race.lock); +} + +static void test_incomplete_proof_fails_open(void) +{ + kzt_lazy_prebind_scope_t *scope = kzt_lazy_prebind_scope_init(); + kzt_lazy_prebind_record_t record = record_for(1, 0x2200); + + CHECK("invalid scope", scope != NULL); + record.version[0] = '\0'; + CHECK("invalid version", kzt_lazy_prebind_scope_claim(scope, &record) == + KZT_LAZY_PREBIND_CLAIM_FAIL_OPEN); + record = record_for(1, 0x2200); + record.scope_proof.scope_complete = 0; + CHECK("incomplete proof", kzt_lazy_prebind_scope_claim(scope, &record) == + KZT_LAZY_PREBIND_CLAIM_FAIL_OPEN); + kzt_lazy_prebind_scope_destroy(&scope); +} + +static void test_changed_scope_identity_is_not_reused(void) +{ + kzt_lazy_prebind_scope_t *scope = kzt_lazy_prebind_scope_init(); + kzt_lazy_prebind_record_t original = record_for(1, 0x2250); + kzt_lazy_prebind_record_t changed = original; + + CHECK("scope identity cache", scope != NULL); + CHECK("scope identity first claim", + kzt_lazy_prebind_scope_claim(scope, &original) == + KZT_LAZY_PREBIND_CLAIM_CREATED); + changed.scope_proof.scope_identity.value ^= 1; + CHECK("scope identity conflict", + kzt_lazy_prebind_scope_claim(scope, &changed) == + KZT_LAZY_PREBIND_CLAIM_CONFLICT); + CHECK("scope identity epoch", + kzt_lazy_prebind_scope_mutate( + scope, KZT_LAZY_PREBIND_MUTATION_LOADER_EVENT) == 2); + CHECK("scope identity replacement", + kzt_lazy_prebind_scope_claim(scope, &changed) == + KZT_LAZY_PREBIND_CLAIM_CREATED); + kzt_lazy_prebind_scope_destroy(&scope); +} + +static void test_publication_is_unique_and_mutation_drains(void) +{ + kzt_lazy_prebind_scope_t *scope = kzt_lazy_prebind_scope_init(); + kzt_lazy_prebind_record_t record = record_for(1, 0x2300); + kzt_lazy_prebind_lease_t publish = { 0 }; + kzt_lazy_prebind_lease_t competing = { 0 }; + kzt_lazy_prebind_lease_t revoke = { 0 }; + mutation_race_t race = { + .scope = scope, + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + pthread_t thread; + + CHECK("publish scope", scope != NULL); + CHECK("publish claim", kzt_lazy_prebind_scope_claim(scope, &record) == + KZT_LAZY_PREBIND_CLAIM_CREATED); + CHECK("publish first lease", kzt_lazy_prebind_scope_publish_acquire( + scope, &record, &publish) == 0); + CHECK("publish single writer", kzt_lazy_prebind_scope_publish_acquire( + scope, &record, &competing) != 0); + CHECK("publish mutation thread", pthread_create( + &thread, NULL, mutation_worker, &race) == 0); + CHECK("publish mutation starts", wait_for_mutation( + &race, &race.started, 1, 100)); + CHECK("publish mutation waits lease", !wait_for_mutation( + &race, &race.finished, 1, 30)); + kzt_lazy_prebind_scope_publish_finish(&publish, 1); + CHECK("publish mutation finishes", wait_for_mutation( + &race, &race.finished, 1, 100)); + CHECK("publish mutation epoch", race.epoch == 2); + CHECK("publish mutation join", pthread_join(thread, NULL) == 0); + CHECK("publish revoke lease", kzt_lazy_prebind_scope_revoke_acquire( + scope, NULL, &revoke) == 0); + CHECK("publish revoke record", revoke.record.slot_addr == record.slot_addr && + revoke.record.bridge_target == record.bridge_target); + kzt_lazy_prebind_scope_revoke_finish(&revoke, 1); + CHECK("publish revoke one shot", kzt_lazy_prebind_scope_revoke_acquire( + scope, NULL, &(kzt_lazy_prebind_lease_t){ 0 }) == 1); + kzt_lazy_prebind_scope_destroy(&scope); + pthread_cond_destroy(&race.cond); + pthread_mutex_destroy(&race.lock); +} + +int main(void) +{ + test_epoch_invalidates_and_replaces_record(); + test_retire_waits_lease_and_rejects_address_reuse(); + test_incomplete_proof_fails_open(); + test_changed_scope_identity_is_not_reused(); + test_publication_is_unique_and_mutation_drains(); + puts("kzt-lazy-prebind-scope: all tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_lifecycle_snapshot_capacity.c b/tests/unit/kzt/test_lifecycle_snapshot_capacity.c new file mode 100644 index 00000000000..55208e364e7 --- /dev/null +++ b/tests/unit/kzt/test_lifecycle_snapshot_capacity.c @@ -0,0 +1,341 @@ +#include +#include +#include +#include + +#include "kzt_guest_registry.h" +#include "kzt_loader_event_hook.h" +#include "kzt_loader_lifecycle_snapshot.h" + +#define LIVE_MAP_COUNT 1025 + +#define CHECK(name, condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "FAIL %s\n", name); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +typedef struct test_r_debug_extended_x64 { + int32_t version; + int32_t version_padding; + uintptr_t map; + uintptr_t brk; + int32_t state; + int32_t state_padding; + uintptr_t loader_base; + uintptr_t next; +} test_r_debug_extended_x64_t; + +typedef struct test_link_map_chain_x64 { + uintptr_t load_bias; + uintptr_t name; + uintptr_t dynamic_addr; + uintptr_t next; + uintptr_t previous; +} test_link_map_chain_x64_t; + +typedef struct read_fixture { + test_link_map_chain_x64_t *maps; + size_t map_count; + size_t map_reads; +} read_fixture_t; + +typedef struct lifecycle_fixture { + kzt_guest_registry_t *registry; + size_t prepare_calls; + size_t cancel_calls; + size_t unload_calls; + kzt_loader_lifecycle_identity_t unloaded; +} lifecycle_fixture_t; + +static int read_memory(uintptr_t address, void *dst, size_t size, void *opaque) +{ + read_fixture_t *fixture = opaque; + uintptr_t first = (uintptr_t)fixture->maps; + uintptr_t end = first + fixture->map_count * sizeof(*fixture->maps); + + if (!address || !dst || !size) { + return -1; + } + if (address >= first && address < end && + size == sizeof(*fixture->maps)) { + ++fixture->map_reads; + } + memcpy(dst, (const void *)address, size); + return 0; +} + +static kzt_guest_object_observation_t observation_for(uintptr_t link_map_addr) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map_addr, + .load_bias = { link_map_addr + 0x1000, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { link_map_addr + 0x2000, KZT_GUEST_FIELD_OK }, + .map_start = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .map_end = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .namespace_id = { 0, KZT_GUEST_FIELD_OK }, + .path = { "/guest/libsnapshot.so", KZT_GUEST_FIELD_OK }, + .soname = { NULL, KZT_GUEST_FIELD_NOT_PARSED }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static int resolve_identity( + uintptr_t link_map_addr, + kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + lifecycle_fixture_t *fixture = opaque; + kzt_guest_loader_identity_t resolved = { 0 }; + + if (kzt_guest_registry_find_loader_object_identity( + fixture->registry, link_map_addr, &resolved) != 0) { + return -1; + } + *identity = (kzt_loader_lifecycle_identity_t) { + .link_map_addr = resolved.link_map_addr, + .generation = resolved.generation, + .namespace_id = resolved.namespace_id, + }; + return 0; +} + +static int prepare_unload( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + lifecycle_fixture_t *fixture = opaque; + kzt_guest_loader_identity_t unload = { + .link_map_addr = identity->link_map_addr, + .generation = identity->generation, + .namespace_id = identity->namespace_id, + }; + + if (kzt_guest_registry_begin_loader_unload( + fixture->registry, &unload) != 0) { + return -1; + } + ++fixture->prepare_calls; + return 0; +} + +static int cancel_unload( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + lifecycle_fixture_t *fixture = opaque; + kzt_guest_loader_identity_t unload = { + .link_map_addr = identity->link_map_addr, + .generation = identity->generation, + .namespace_id = identity->namespace_id, + }; + + if (kzt_guest_registry_cancel_loader_unload( + fixture->registry, &unload) != 0) { + return -1; + } + ++fixture->cancel_calls; + return 0; +} + +static void finish_unload( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + lifecycle_fixture_t *fixture = opaque; + kzt_guest_loader_identity_t unload = { + .link_map_addr = identity->link_map_addr, + .generation = identity->generation, + .namespace_id = identity->namespace_id, + }; + + CHECK("finish loader unload", + kzt_guest_registry_finish_loader_unload( + fixture->registry, &unload) == 0); + fixture->unloaded = *identity; + ++fixture->unload_calls; +} + +static void link_maps(test_link_map_chain_x64_t *maps, size_t count) +{ + size_t index; + + for (index = 0; index < count; ++index) { + maps[index] = (test_link_map_chain_x64_t) { + .load_bias = 0x100000 + index * 0x10000, + .dynamic_addr = 0x101000 + index * 0x10000, + .next = index + 1 < count ? (uintptr_t)&maps[index + 1] : 0, + .previous = index ? (uintptr_t)&maps[index - 1] : 0, + }; + } +} + +static void test_allocation_failure_is_observable(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + test_link_map_chain_x64_t maps[ + KZT_LOADER_LIFECYCLE_SNAPSHOT_INLINE_MAPS + 1]; + test_r_debug_extended_x64_t debug = { + .version = 1, + .state = KZT_LOADER_DEBUG_CONSISTENT, + .map = (uintptr_t)maps, + }; + read_fixture_t reads = { + .maps = maps, + .map_count = sizeof(maps) / sizeof(maps[0]), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = read_memory, + .opaque = &reads, + }; + kzt_loader_lifecycle_snapshot_t snapshot = { 0 }; + + CHECK("allocation registry", registry != NULL); + link_maps(maps, reads.map_count); + kzt_loader_lifecycle_snapshot_test_set_alloc_failure_after(0); + CHECK("allocation capture fails", + kzt_loader_lifecycle_snapshot_capture( + registry, (uintptr_t)&debug, &reader_ops, &snapshot) != 0); + CHECK("allocation result observable", + snapshot.result == KZT_LOADER_LIFECYCLE_SNAPSHOT_ALLOCATION); + CHECK("allocation count cleared", snapshot.live_map_count == 0); + kzt_loader_lifecycle_snapshot_test_set_alloc_failure_after(-1); + kzt_loader_lifecycle_snapshot_release(&snapshot); + kzt_guest_registry_destroy(®istry); +} + +static void test_1025_maps_complete_lifecycle_and_reuse(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + test_link_map_chain_x64_t *maps = + calloc(LIVE_MAP_COUNT, sizeof(*maps)); + test_r_debug_extended_x64_t debug = { + .version = 1, + .state = KZT_LOADER_DEBUG_DELETE, + .map = (uintptr_t)maps, + }; + read_fixture_t reads = { + .maps = maps, + .map_count = LIVE_MAP_COUNT, + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = read_memory, + .opaque = &reads, + }; + kzt_loader_lifecycle_snapshot_t snapshot = { 0 }; + kzt_loader_event_hook_t hook; + lifecycle_fixture_t lifecycle = { .registry = registry }; + kzt_guest_loader_identity_t old_identity = { 0 }; + kzt_guest_object_snapshot_t *reused = NULL; + kzt_guest_registry_dump_t dump = { 0 }; + uintptr_t reused_addr; + size_t index; + int found_dead = 0; + + CHECK("capacity registry", registry != NULL); + CHECK("capacity maps", maps != NULL); + if (!registry || !maps) { + free(maps); + kzt_guest_registry_destroy(®istry); + return; + } + link_maps(maps, LIVE_MAP_COUNT); + for (index = 0; index < LIVE_MAP_COUNT; ++index) { + kzt_guest_object_observation_t observation = + observation_for((uintptr_t)&maps[index]); + + CHECK("observe live map", + kzt_guest_registry_observe(registry, &observation) == + KZT_GUEST_REGISTRY_ADDED); + } + reused_addr = (uintptr_t)&maps[LIVE_MAP_COUNT - 1]; + CHECK("old identity", + kzt_guest_registry_find_loader_object_identity( + registry, reused_addr, &old_identity) == 0); + CHECK("install lifecycle hook", + kzt_loader_event_hook_install( + &hook, KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID, + 0x1000, 3, 1) == 0); + CHECK("enable lifecycle hook", + kzt_loader_event_hook_enable_lifecycle( + &hook, 0x2000, (uintptr_t)&debug) == 0); + + CHECK("capture 1025 DELETE maps", + kzt_loader_lifecycle_snapshot_capture( + registry, (uintptr_t)&debug, &reader_ops, &snapshot) == 0); + CHECK("read all 1025 link maps", reads.map_reads == LIVE_MAP_COUNT); + CHECK("snapshot contains 1025 maps", + snapshot.live_map_count == LIVE_MAP_COUNT); + CHECK("snapshot DELETE state", + snapshot.state == KZT_LOADER_DEBUG_DELETE); + CHECK("publish 1025 DELETE maps", + kzt_loader_event_hook_publish_lifecycle( + &hook, snapshot.state, snapshot.live_maps, + snapshot.live_map_count, resolve_identity, prepare_unload, + cancel_unload, finish_unload, &lifecycle) == 0); + CHECK("prepare all 1025 identities", + lifecycle.prepare_calls == LIVE_MAP_COUNT); + kzt_loader_lifecycle_snapshot_release(&snapshot); + + maps[LIVE_MAP_COUNT - 2].next = 0; + debug.state = KZT_LOADER_DEBUG_CONSISTENT; + reads.map_reads = 0; + CHECK("capture 1024 CONSISTENT maps", + kzt_loader_lifecycle_snapshot_capture( + registry, (uintptr_t)&debug, &reader_ops, &snapshot) == 0); + CHECK("read remaining 1024 link maps", + reads.map_reads == LIVE_MAP_COUNT - 1); + CHECK("publish CONSISTENT maps", + kzt_loader_event_hook_publish_lifecycle( + &hook, snapshot.state, snapshot.live_maps, + snapshot.live_map_count, resolve_identity, prepare_unload, + cancel_unload, finish_unload, &lifecycle) == 0); + CHECK("cancel remaining identities", + lifecycle.cancel_calls == LIVE_MAP_COUNT - 1); + CHECK("one identity unloaded", lifecycle.unload_calls == 1); + CHECK("exact removed identity unloaded", + lifecycle.unloaded.link_map_addr == reused_addr && + lifecycle.unloaded.generation == old_identity.generation); + kzt_loader_lifecycle_snapshot_release(&snapshot); + + CHECK("dump DEAD generation", + kzt_guest_registry_dump_snapshot(registry, &dump) == 0); + for (index = 0; index < dump.count; ++index) { + if (dump.objects[index].link_map_addr == reused_addr && + dump.objects[index].generation == old_identity.generation && + dump.objects[index].state == KZT_GUEST_OBJECT_DEAD) { + found_dead = 1; + } + } + CHECK("removed identity reaches DEAD", found_dead); + kzt_guest_registry_dump_free(&dump); + + { + kzt_guest_object_observation_t observation = + observation_for(reused_addr); + + CHECK("observe reused address", + kzt_guest_registry_observe(registry, &observation) == + KZT_GUEST_REGISTRY_ADDED); + } + CHECK("find reused address", + kzt_guest_registry_find_by_link_map( + registry, reused_addr, &reused) == 0 && reused != NULL); + CHECK("reused address gets new generation", + reused->generation > old_identity.generation); + kzt_guest_object_snapshot_free(reused); + + CHECK("destroy lifecycle hook", kzt_loader_event_hook_destroy(&hook) == 0); + free(maps); + kzt_guest_registry_destroy(®istry); +} + +int main(void) +{ + test_allocation_failure_is_observable(); + test_1025_maps_complete_lifecycle_and_reuse(); + puts("kzt lifecycle snapshot capacity: PASS"); + return EXIT_SUCCESS; +} diff --git a/tests/unit/kzt/test_loader_event_hook.c b/tests/unit/kzt/test_loader_event_hook.c new file mode 100644 index 00000000000..e0c7e60ccf3 --- /dev/null +++ b/tests/unit/kzt/test_loader_event_hook.c @@ -0,0 +1,347 @@ +#include "kzt_loader_event_hook.h" + +#include +#include +#include + +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "CHECK failed: %s:%d: %s\\n", __FILE__, \ + __LINE__, #condition); \ + exit(1); \ + } \ + } while (0) + +static void test_version_and_pattern_fail_open(void) +{ + kzt_loader_event_hook_t hook; + + CHECK(kzt_loader_event_hook_install(&hook, 0, 0x1000, 3, 1) != 0); + CHECK(hook.result == KZT_LOADER_EVENT_HOOK_FAIL_OPEN_BUILD_ID_READ); + CHECK(kzt_loader_event_hook_scope_layout(&hook) == + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED); + CHECK(kzt_loader_event_hook_install(&hook, "unknown", 0x1000, 3, 1) != 0); + CHECK(hook.result == KZT_LOADER_EVENT_HOOK_FAIL_OPEN_UNKNOWN_BUILD_ID); + CHECK(kzt_loader_event_hook_scope_layout(&hook) == + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED); + CHECK(kzt_loader_event_hook_install( + &hook, KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID, 0, 3, 0) != 0); + CHECK(hook.result == KZT_LOADER_EVENT_HOOK_FAIL_OPEN_PATTERN_MISMATCH); + + CHECK(setenv("LATX_KZT_LOADER_EVENT_FORCE_PATTERN_MISMATCH", "1", 1) == 0); + CHECK(!kzt_loader_event_hook_pattern_allowed(1)); + CHECK(unsetenv("LATX_KZT_LOADER_EVENT_FORCE_PATTERN_MISMATCH") == 0); + CHECK(kzt_loader_event_hook_pattern_allowed(1)); + + CHECK(setenv("LATX_KZT_LOADER_EVENT_HOOK", "0", 1) == 0); + CHECK(kzt_loader_event_hook_install( + &hook, KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID, 0x1000, 3, 1) != 0); + CHECK(hook.result == KZT_LOADER_EVENT_HOOK_FAIL_OPEN_DISABLED); + CHECK(unsetenv("LATX_KZT_LOADER_EVENT_HOOK") == 0); +} + +static void test_event_publishes_exact_map_with_monotonic_sequence(void) +{ + kzt_loader_event_hook_t hook; + kzt_loader_event_t first; + kzt_loader_event_t second; + + CHECK(kzt_loader_event_hook_install( + &hook, KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID, 0x1000, 3, 1) == 0); + CHECK(kzt_loader_event_hook_scope_layout(&hook) == + KZT_GUEST_SCOPE_LAYOUT_GLIBC_2_39_C591A5DF); + CHECK(kzt_loader_event_hook_publish(&hook, 0x11110000, &first) == 0); + CHECK(kzt_loader_event_hook_publish(&hook, 0x22220000, &second) == 0); + CHECK(first.link_map_addr == 0x11110000); + CHECK(second.link_map_addr == 0x22220000); + CHECK(second.sequence == first.sequence + 1); + CHECK(kzt_loader_event_hook_publish(&hook, 0, &second) != 0); + kzt_loader_event_hook_destroy(&hook); +} + +typedef struct lifecycle_fixture { + kzt_loader_lifecycle_identity_t identities[2]; + kzt_loader_lifecycle_identity_t unloaded; + int unload_calls; + int prepare_calls; + int cancel_calls; +} lifecycle_fixture_t; + +static int resolve_lifecycle_identity( + uintptr_t link_map_addr, + kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + lifecycle_fixture_t *fixture = opaque; + + for (size_t i = 0; i < 2; ++i) { + if (fixture->identities[i].link_map_addr == link_map_addr) { + *identity = fixture->identities[i]; + return 0; + } + } + return -1; +} + +static void publish_lifecycle_unload( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + lifecycle_fixture_t *fixture = opaque; + + fixture->unloaded = *identity; + ++fixture->unload_calls; +} + +static int prepare_lifecycle_unload( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + lifecycle_fixture_t *fixture = opaque; + + CHECK(identity != NULL); + ++fixture->prepare_calls; + return 0; +} + +static int cancel_lifecycle_unload( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + lifecycle_fixture_t *fixture = opaque; + + CHECK(identity != NULL); + ++fixture->cancel_calls; + return 0; +} + +static void test_delete_consistent_publishes_exact_removed_identity(void) +{ + kzt_loader_event_hook_t hook; + lifecycle_fixture_t fixture = { + .identities = { + { 0x11110000, 9, 0 }, + { 0x22220000, 17, 7 }, + }, + }; + const uintptr_t before[] = { 0x11110000, 0x22220000 }; + const uintptr_t after[] = { 0x11110000 }; + + CHECK(kzt_loader_event_hook_install( + &hook, KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID, + 0x1000, 3, 1) == 0); + CHECK(kzt_loader_event_hook_enable_lifecycle( + &hook, 0x2000, 0x3000) == 0); + CHECK(kzt_loader_event_hook_publish_lifecycle( + &hook, KZT_LOADER_DEBUG_DELETE, + before, 2, resolve_lifecycle_identity, + prepare_lifecycle_unload, cancel_lifecycle_unload, + publish_lifecycle_unload, &fixture) == 0); + CHECK(kzt_loader_event_hook_destroy(&hook) != 0); + CHECK(kzt_loader_event_hook_enable_lifecycle( + &hook, 0x2000, 0x3000) != 0); + CHECK(fixture.unload_calls == 0); + CHECK(kzt_loader_event_hook_publish_lifecycle( + &hook, KZT_LOADER_DEBUG_CONSISTENT, + before, 2, resolve_lifecycle_identity, + prepare_lifecycle_unload, cancel_lifecycle_unload, + publish_lifecycle_unload, &fixture) == 0); + CHECK(fixture.unload_calls == 0); + CHECK(kzt_loader_event_hook_publish_lifecycle( + &hook, KZT_LOADER_DEBUG_DELETE, + before, 2, resolve_lifecycle_identity, + prepare_lifecycle_unload, cancel_lifecycle_unload, + publish_lifecycle_unload, &fixture) == 0); + CHECK(kzt_loader_event_hook_publish_lifecycle( + &hook, KZT_LOADER_DEBUG_CONSISTENT, + after, 1, resolve_lifecycle_identity, + prepare_lifecycle_unload, cancel_lifecycle_unload, + publish_lifecycle_unload, &fixture) == 0); + CHECK(fixture.unload_calls == 1); + CHECK(fixture.unloaded.link_map_addr == 0x22220000); + CHECK(fixture.unloaded.generation == 17); + CHECK(fixture.unloaded.namespace_id == 7); + CHECK(kzt_loader_event_hook_publish_lifecycle( + &hook, KZT_LOADER_DEBUG_CONSISTENT, + after, 1, resolve_lifecycle_identity, + prepare_lifecycle_unload, cancel_lifecycle_unload, + publish_lifecycle_unload, &fixture) == 0); + CHECK(fixture.unload_calls == 1); + CHECK(kzt_loader_event_hook_destroy(&hook) == 0); +} + +static void test_add_does_not_discard_pending_delete(void) +{ + kzt_loader_event_hook_t hook; + lifecycle_fixture_t fixture = { + .identities = { + { 0x11110000, 9, 0 }, + { 0x22220000, 17, 7 }, + }, + }; + const uintptr_t before[] = { 0x11110000, 0x22220000 }; + const uintptr_t after[] = { 0x11110000 }; + + CHECK(kzt_loader_event_hook_install( + &hook, KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID, + 0x1000, 3, 1) == 0); + CHECK(kzt_loader_event_hook_enable_lifecycle( + &hook, 0x2000, 0x3000) == 0); + CHECK(kzt_loader_event_hook_publish_lifecycle( + &hook, KZT_LOADER_DEBUG_DELETE, + before, 2, resolve_lifecycle_identity, + prepare_lifecycle_unload, cancel_lifecycle_unload, + publish_lifecycle_unload, &fixture) == 0); + CHECK(kzt_loader_event_hook_publish_lifecycle( + &hook, KZT_LOADER_DEBUG_ADD, + before, 2, resolve_lifecycle_identity, + prepare_lifecycle_unload, cancel_lifecycle_unload, + publish_lifecycle_unload, &fixture) == 0); + CHECK(kzt_loader_event_hook_publish_lifecycle( + &hook, KZT_LOADER_DEBUG_CONSISTENT, + after, 1, resolve_lifecycle_identity, + prepare_lifecycle_unload, cancel_lifecycle_unload, + publish_lifecycle_unload, &fixture) == 0); + CHECK(fixture.unload_calls == 1); + CHECK(fixture.unloaded.generation == 17); + CHECK(fixture.unloaded.namespace_id == 7); + kzt_loader_event_hook_destroy(&hook); +} + +static void test_same_address_new_identity_retires_old_generation(void) +{ + kzt_loader_event_hook_t hook; + lifecycle_fixture_t fixture = { + .identities = { + { 0x11110000, 9, 0 }, + { 0x22220000, 17, 7 }, + }, + }; + const uintptr_t maps[] = { 0x11110000, 0x22220000 }; + + CHECK(kzt_loader_event_hook_install( + &hook, KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID, + 0x1000, 3, 1) == 0); + CHECK(kzt_loader_event_hook_enable_lifecycle( + &hook, 0x2000, 0x3000) == 0); + CHECK(kzt_loader_event_hook_publish_lifecycle( + &hook, KZT_LOADER_DEBUG_DELETE, + maps, 2, resolve_lifecycle_identity, + prepare_lifecycle_unload, cancel_lifecycle_unload, + publish_lifecycle_unload, &fixture) == 0); + fixture.identities[1].generation = 18; + fixture.identities[1].namespace_id = 9; + CHECK(kzt_loader_event_hook_publish_lifecycle( + &hook, KZT_LOADER_DEBUG_CONSISTENT, + maps, 2, resolve_lifecycle_identity, + prepare_lifecycle_unload, cancel_lifecycle_unload, + publish_lifecycle_unload, &fixture) == 0); + CHECK(fixture.unload_calls == 1); + CHECK(fixture.unloaded.link_map_addr == 0x22220000); + CHECK(fixture.unloaded.generation == 17); + CHECK(fixture.unloaded.namespace_id == 7); + kzt_loader_event_hook_destroy(&hook); +} + +typedef struct lifecycle_allocation_fixture { + kzt_loader_lifecycle_identity_t identities[65]; + size_t prepare_calls; + size_t cancel_calls; + size_t unload_calls; +} lifecycle_allocation_fixture_t; + +static int resolve_allocation_identity( + uintptr_t link_map_addr, + kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + lifecycle_allocation_fixture_t *fixture = opaque; + + for (size_t i = 0; i < 65; ++i) { + if (fixture->identities[i].link_map_addr == link_map_addr) { + *identity = fixture->identities[i]; + return 0; + } + } + return -1; +} + +static int prepare_allocation_unload( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + lifecycle_allocation_fixture_t *fixture = opaque; + + CHECK(identity != NULL); + ++fixture->prepare_calls; + return 0; +} + +static int cancel_allocation_unload( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + lifecycle_allocation_fixture_t *fixture = opaque; + + CHECK(identity != NULL); + ++fixture->cancel_calls; + return 0; +} + +static void publish_allocation_unload( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + lifecycle_allocation_fixture_t *fixture = opaque; + + CHECK(identity != NULL); + ++fixture->unload_calls; +} + +static void test_pending_growth_allocation_failure_cancels_all(void) +{ + kzt_loader_event_hook_t hook; + lifecycle_allocation_fixture_t fixture = { 0 }; + uintptr_t live_maps[65]; + + for (size_t i = 0; i < 65; ++i) { + live_maps[i] = 0x40000000 + i * 0x1000; + fixture.identities[i] = (kzt_loader_lifecycle_identity_t) { + .link_map_addr = live_maps[i], + .generation = i + 1, + .namespace_id = 0, + }; + } + CHECK(kzt_loader_event_hook_install( + &hook, KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID, + 0x1000, 3, 1) == 0); + CHECK(kzt_loader_event_hook_enable_lifecycle( + &hook, 0x2000, 0x3000) == 0); + kzt_loader_event_hook_test_set_alloc_failure_after(1); + CHECK(kzt_loader_event_hook_publish_lifecycle( + &hook, KZT_LOADER_DEBUG_DELETE, + live_maps, 65, resolve_allocation_identity, + prepare_allocation_unload, cancel_allocation_unload, + publish_allocation_unload, &fixture) != 0); + CHECK(kzt_loader_event_hook_lifecycle_result(&hook) == + KZT_LOADER_LIFECYCLE_ALLOCATION); + CHECK(fixture.prepare_calls == 65); + CHECK(fixture.cancel_calls == 65); + CHECK(fixture.unload_calls == 0); + CHECK(hook.pending_delete_count == 0); + kzt_loader_event_hook_test_set_alloc_failure_after(-1); + CHECK(kzt_loader_event_hook_destroy(&hook) == 0); +} + +int main(void) +{ + test_version_and_pattern_fail_open(); + test_event_publishes_exact_map_with_monotonic_sequence(); + test_delete_consistent_publishes_exact_removed_identity(); + test_add_does_not_discard_pending_delete(); + test_same_address_new_identity_retires_old_generation(); + test_pending_growth_allocation_failure_cancels_all(); + puts("kzt-loader-event-hook: all tests passed"); + return 0; +} From 35dc33e8144cbed7c1837fd306e3c9b4be8ef454 Mon Sep 17 00:00:00 2001 From: Hanlu Li Date: Fri, 31 Jul 2026 22:02:48 +0800 Subject: [PATCH 4/8] LATX, KZT: make guest slot patching transactional Build symbol-scope and dynsym proofs before patch decisions, route every KZT-owned slot update through expected-value CAS, and treat RELRO permission restoration as part of the write transaction. Add per-object GOT/PLT claims, exact wrapper bridge provenance, and fail-open atfork protection for concurrent bridge publication.\n\nCover scope revalidation, route policy, rollback and unrecoverable outcomes, bridge concurrency, and wrapper selection with focused white-box tests. Signed-off-by: Hanlu Li --- target/i386/latx/context/bridge.c | 387 +++++- .../latx/context/kzt_guest_dynsym_lookup.c | 583 +++++++++ .../latx/context/kzt_guest_symbol_scope.c | 580 +++++++++ .../i386/latx/context/kzt_jump_slot_route.c | 112 +- target/i386/latx/context/kzt_lazy_binding.c | 8 + .../i386/latx/context/kzt_lazy_diagnostics.c | 8 + target/i386/latx/context/kzt_patch_planner.c | 6 + .../i386/latx/context/kzt_patch_spike_guard.c | 84 +- .../latx/context/kzt_patch_spike_writer.c | 3 +- .../latx/context/kzt_per_object_got_plt.c | 68 + .../latx/context/kzt_plt_resolver_adapter.c | 27 + .../context/kzt_rela_immediate_candidate.c | 4 +- .../latx/context/kzt_rela_request_enricher.c | 212 +-- .../latx/context/kzt_rela_runtime_bridge.c | 202 ++- .../latx/context/kzt_rela_stub_detector.c | 110 ++ .../context/kzt_wrapper_bridge_provider.c | 4 +- target/i386/latx/context/wrappedlibegl.c | 2 +- target/i386/latx/context/wrappedlibgl.c | 4 +- target/i386/latx/context/wrappedlibglx.c | 2 +- target/i386/latx/context/wrappedlibx11.c | 16 +- target/i386/latx/context/wrappedlibxext.c | 2 +- target/i386/latx/include/bridge.h | 12 + .../latx/include/kzt_guest_dynsym_lookup.h | 33 + .../latx/include/kzt_guest_symbol_scope.h | 95 ++ .../i386/latx/include/kzt_jump_slot_route.h | 29 +- target/i386/latx/include/kzt_lazy_binding.h | 6 + target/i386/latx/include/kzt_patch_planner.h | 6 + .../i386/latx/include/kzt_patch_spike_guard.h | 6 + .../latx/include/kzt_patch_spike_writer.h | 3 + .../latx/include/kzt_per_object_got_plt.h | 40 + .../latx/include/kzt_plt_resolver_adapter.h | 12 + .../latx/include/kzt_rela_request_enricher.h | 12 + .../latx/include/kzt_rela_runtime_bridge.h | 14 + .../latx/include/kzt_rela_stub_detector.h | 21 + .../include/kzt_wrapper_bridge_provider.h | 2 + tests/unit/kzt/kzt_test_options.c | 1 + tests/unit/kzt/test_bridge_atfork_fail_open.c | 319 +++++ tests/unit/kzt/test_bridge_concurrency.c | 916 +++++++++++++ tests/unit/kzt/test_guest_dynsym_lookup.c | 800 ++++++++++++ tests/unit/kzt/test_guest_symbol_scope.c | 1149 +++++++++++++++++ tests/unit/kzt/test_jump_slot_route.c | 340 ++++- tests/unit/kzt/test_patch_planner.c | 27 + tests/unit/kzt/test_patch_spike_guard.c | 19 +- tests/unit/kzt/test_patch_spike_writer.c | 69 +- tests/unit/kzt/test_per_object_got_plt.c | 316 +++++ .../unit/kzt/test_rela_immediate_candidate.c | 7 +- tests/unit/kzt/test_rela_request_enricher.c | 200 +++ .../unit/kzt/test_rela_runtime_helper_chain.c | 6 + ...1056_per_object_got_plt_source_contract.py | 114 ++ ...1100_atfork_bridge_gate_source_contract.py | 65 + .../test_wi237_jump_slot_route_contract.py | 66 +- tests/unit/kzt/test_wi256_lazy_binding.c | 54 + tests/unit/kzt/test_wi256_lazy_diagnostics.c | 51 + .../kzt/test_wi256_plt_resolver_adapter.c | 182 +++ ...test_wi256_plt_resolver_source_contract.py | 187 ++- 55 files changed, 7248 insertions(+), 355 deletions(-) create mode 100644 target/i386/latx/context/kzt_guest_dynsym_lookup.c create mode 100644 target/i386/latx/context/kzt_guest_symbol_scope.c create mode 100644 target/i386/latx/context/kzt_per_object_got_plt.c create mode 100644 target/i386/latx/context/kzt_rela_stub_detector.c create mode 100644 target/i386/latx/include/kzt_guest_dynsym_lookup.h create mode 100644 target/i386/latx/include/kzt_guest_symbol_scope.h create mode 100644 target/i386/latx/include/kzt_per_object_got_plt.h create mode 100644 tests/unit/kzt/test_bridge_atfork_fail_open.c create mode 100644 tests/unit/kzt/test_bridge_concurrency.c create mode 100644 tests/unit/kzt/test_guest_dynsym_lookup.c create mode 100644 tests/unit/kzt/test_guest_symbol_scope.c create mode 100644 tests/unit/kzt/test_per_object_got_plt.c create mode 100644 tests/unit/kzt/test_wi1056_per_object_got_plt_source_contract.py create mode 100644 tests/unit/kzt/test_wi1100_atfork_bridge_gate_source_contract.py diff --git a/target/i386/latx/context/bridge.c b/target/i386/latx/context/bridge.c index 6083f9d3575..12e43809a77 100755 --- a/target/i386/latx/context/bridge.c +++ b/target/i386/latx/context/bridge.c @@ -9,13 +9,16 @@ #include #include #include +#include #include +#include #include #include #include "bridge.h" #include "bridge_private.h" #include "khash.h" +#include "qemu/atomic.h" #include "debug.h" #include "box64context.h" #include "elfloader.h" @@ -32,11 +35,162 @@ typedef struct brick_s { } brick_t; typedef struct bridge_s { + pthread_mutex_t lock; brick_t *head; brick_t *last; // to speed up kh_bridgemap_t *bridgemap; + struct bridge_s *fork_next; } bridge_t; +static pthread_mutex_t bridge_fork_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t alternate_writer_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_once_t bridge_atfork_once = PTHREAD_ONCE_INIT; +static bridge_t *fork_bridges; +enum bridge_fork_protection_state { + BRIDGE_FORK_PROTECTION_UNKNOWN = 0, + BRIDGE_FORK_PROTECTION_AVAILABLE, + BRIDGE_FORK_PROTECTION_UNAVAILABLE, +}; +static int bridge_fork_protection_state; + +static int alternate_add_if_absent(void *addr, void *alt); + +static void bridge_fork_unlock(void) +{ + bridge_t *bridge; + + for (bridge = fork_bridges; bridge; bridge = bridge->fork_next) { + pthread_mutex_unlock(&bridge->lock); + } + pthread_mutex_unlock(&alternate_writer_lock); + pthread_mutex_unlock(&bridge_fork_lock); +} + +static void bridge_fork_prepare(void) +{ + bridge_t *bridge; + + pthread_mutex_lock(&bridge_fork_lock); + pthread_mutex_lock(&alternate_writer_lock); + for (bridge = fork_bridges; bridge; bridge = bridge->fork_next) { + pthread_mutex_lock(&bridge->lock); + } +} + +static void bridge_fork_parent(void) +{ + bridge_fork_unlock(); +} + +static void bridge_fork_child(void) +{ + bridge_fork_unlock(); +} + +static int bridge_call_atfork(void (*prepare)(void), void (*parent)(void), + void (*child)(void)) +{ +#ifdef BRIDGE_TEST_ATFORK_FAIL + (void)prepare; + (void)parent; + (void)child; + return ENOMEM; +#else + return pthread_atfork(prepare, parent, child); +#endif +} + +static void bridge_register_atfork(void) +{ + int status = bridge_call_atfork(bridge_fork_prepare, + bridge_fork_parent, + bridge_fork_child); + + qatomic_store_release( + &bridge_fork_protection_state, + status == 0 ? BRIDGE_FORK_PROTECTION_AVAILABLE : + BRIDGE_FORK_PROTECTION_UNAVAILABLE); + if (status != 0) { + printf_kzt_registry_diagnostics( + "kzt_bridge_fallback schema=1 " + "reason=atfork_registration_failed status=%d error=%s\n", + status, strerror(status)); + } +} + +int BridgeForkProtectionAvailable(void) +{ + int state = qatomic_load_acquire(&bridge_fork_protection_state); + int status; + + if (state == BRIDGE_FORK_PROTECTION_AVAILABLE) { + return 1; + } + if (state == BRIDGE_FORK_PROTECTION_UNAVAILABLE) { + return 0; + } + + status = pthread_once(&bridge_atfork_once, bridge_register_atfork); + if (status != 0) { + if (qatomic_cmpxchg( + &bridge_fork_protection_state, + BRIDGE_FORK_PROTECTION_UNKNOWN, + BRIDGE_FORK_PROTECTION_UNAVAILABLE) == + BRIDGE_FORK_PROTECTION_UNKNOWN) { + printf_kzt_registry_diagnostics( + "kzt_bridge_fallback schema=1 " + "reason=atfork_once_failed status=%d error=%s\n", + status, strerror(status)); + } + return 0; + } + + state = qatomic_load_acquire(&bridge_fork_protection_state); + if (state == BRIDGE_FORK_PROTECTION_UNKNOWN) { + if (qatomic_cmpxchg( + &bridge_fork_protection_state, + BRIDGE_FORK_PROTECTION_UNKNOWN, + BRIDGE_FORK_PROTECTION_UNAVAILABLE) == + BRIDGE_FORK_PROTECTION_UNKNOWN) { + printf_kzt_registry_diagnostics( + "kzt_bridge_fallback schema=1 " + "reason=atfork_state_unpublished\n"); + } + return 0; + } + return state == BRIDGE_FORK_PROTECTION_AVAILABLE; +} + +#ifdef BRIDGE_TEST +static bridge_test_hook_fn test_after_check_hook; +static void *test_after_check_hook_opaque; +static bridge_test_hook_fn test_before_free_hook; +static void *test_before_free_hook_opaque; + +void bridge_test_set_after_check_hook(bridge_test_hook_fn hook, void *opaque) +{ + test_after_check_hook = hook; + test_after_check_hook_opaque = opaque; +} + +void bridge_test_set_before_free_hook(bridge_test_hook_fn hook, void *opaque) +{ + test_before_free_hook = hook; + test_before_free_hook_opaque = opaque; +} + +int bridge_test_lock_is_held(bridge_t *bridge) +{ + int status = pthread_mutex_trylock(&bridge->lock); + + if (status == 0) { + pthread_mutex_unlock(&bridge->lock); + return 0; + } + return status == EBUSY; +} +#endif + //from wrapped/wrappedlibc.c //void* my_mmap(x64emu_t* emu, void* addr, unsigned long length, int prot, int flags, int fd, int64_t offset); //int my_munmap(x64emu_t* emu, void* addr, unsigned long length); @@ -55,40 +209,72 @@ brick_t* NewBrick(void) bridge_t *NewBridge(void) { - bridge_t *b = (bridge_t*)box_calloc(1, sizeof(bridge_t)); + bridge_t *b; + + (void)BridgeForkProtectionAvailable(); + b = (bridge_t*)box_calloc(1, sizeof(bridge_t)); + if (!b || pthread_mutex_init(&b->lock, NULL) != 0) { + box_free(b); + return NULL; + } b->head = NewBrick(); b->last = b->head; b->bridgemap = kh_init(bridgemap); + pthread_mutex_lock(&bridge_fork_lock); + b->fork_next = fork_bridges; + fork_bridges = b; + pthread_mutex_unlock(&bridge_fork_lock); return b; } void FreeBridge(bridge_t** bridge) { + bridge_t *current; + bridge_t **entry; + if(!bridge || !*bridge) return; - brick_t *b = (*bridge)->head; + current = *bridge; + pthread_mutex_lock(&bridge_fork_lock); + pthread_mutex_lock(¤t->lock); +#ifdef BRIDGE_TEST + if (test_before_free_hook) { + test_before_free_hook(test_before_free_hook_opaque); + } +#endif + for (entry = &fork_bridges; *entry; entry = &(*entry)->fork_next) { + if (*entry == current) { + *entry = current->fork_next; + break; + } + } + brick_t *b = current->head; while(b) { brick_t *n = b->next; munmap(b->b, NBRICK*sizeof(onebridge_t)); box_free(b); b = n; } - kh_destroy(bridgemap, (*bridge)->bridgemap); - box_free(*bridge); + kh_destroy(bridgemap, current->bridgemap); *bridge = NULL; + pthread_mutex_unlock(¤t->lock); + pthread_mutex_destroy(¤t->lock); + box_free(current); + pthread_mutex_unlock(&bridge_fork_lock); } -uintptr_t AddBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N, const char* name) +static uintptr_t bridge_add_locked(bridge_t* bridge, wrapper_t w, void* fnc, + int N, const char* name) { brick_t *b = NULL; int sz = -1; - b = bridge->last; - if(b->sz == NBRICK) { - b->next = NewBrick(); - b = b->next; - bridge->last = b; - } - sz = b->sz; + b = bridge->last; + if(b->sz == NBRICK) { + b->next = NewBrick(); + b = b->next; + bridge->last = b; + } + sz = b->sz; b->sz++; b->b[sz].CC = 0xCC; b->b[sz].S = 'S'; b->b[sz].C='C'; @@ -104,7 +290,7 @@ uintptr_t AddBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N, const char* return (uintptr_t)&b->b[sz].CC; } -uintptr_t CheckBridged(bridge_t* bridge, void* fnc) +static uintptr_t bridge_check_locked(bridge_t* bridge, void* fnc) { // check if function alread have a bridge (the function wrapper will not be tested) khint_t k = kh_get(bridgemap, bridge->bridgemap, (uintptr_t)fnc); @@ -113,26 +299,75 @@ uintptr_t CheckBridged(bridge_t* bridge, void* fnc) return kh_value(bridge->bridgemap, k); } +uintptr_t AddBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N, const char* name) +{ + uintptr_t ret; + + if (!bridge) { + return 0; + } + pthread_mutex_lock(&bridge->lock); + ret = bridge_add_locked(bridge, w, fnc, N, name); + pthread_mutex_unlock(&bridge->lock); + return ret; +} + +uintptr_t CheckBridged(bridge_t* bridge, void* fnc) +{ + uintptr_t ret; + + if (!bridge) { + return 0; + } + pthread_mutex_lock(&bridge->lock); + ret = bridge_check_locked(bridge, fnc); + pthread_mutex_unlock(&bridge->lock); + return ret; +} + uintptr_t AddCheckBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N, const char* name) { + uintptr_t ret; + if(!fnc && w) return 0; - uintptr_t ret = CheckBridged(bridge, fnc); + if (!bridge) { + return 0; + } + pthread_mutex_lock(&bridge->lock); + ret = bridge_check_locked(bridge, fnc); +#ifdef BRIDGE_TEST + if (test_after_check_hook) { + test_after_check_hook(test_after_check_hook_opaque); + } +#endif if(!ret) - ret = AddBridge(bridge, w, fnc, N, name); + ret = bridge_add_locked(bridge, w, fnc, N, name); + pthread_mutex_unlock(&bridge->lock); return ret; } uintptr_t AddAutomaticBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N) { + uintptr_t ret; + if(!fnc) return 0; - uintptr_t ret = CheckBridged(bridge, fnc); + if (!bridge) { + return 0; + } + pthread_mutex_lock(&bridge->lock); + ret = bridge_check_locked(bridge, fnc); +#ifdef BRIDGE_TEST + if (test_after_check_hook) { + test_after_check_hook(test_after_check_hook_opaque); + } +#endif if(!ret) - ret = AddBridge(bridge, w, fnc, N, NULL); - if(!hasAlternate(fnc)) { + ret = bridge_add_locked(bridge, w, fnc, N, NULL); + pthread_mutex_unlock(&bridge->lock); + if(alternate_add_if_absent(fnc, (void*)ret)) { printf_log(LOG_DEBUG, "Adding AutomaticBridge for %p to %p\n", fnc, (void*)ret); - addAlternate(fnc, (void*)ret); } return ret; } @@ -176,43 +411,111 @@ void* GetNativeFncOrFnc(uintptr_t fnc) } -// Alternate address handling -KHASH_MAP_INIT_INT64(alternate, void*) -static kh_alternate_t *my_alternates = NULL; +// Alternate address handling. Buckets are fixed; entries grow through +// immutable collision chains and are only inserted. +#define ALTERNATE_BUCKET_COUNT 4096 -int hasAlternate(void* addr) { - if(!my_alternates) - return 0; - khint_t k = kh_get(alternate, my_alternates, (uintptr_t)addr); - if(k==kh_end(my_alternates)) +typedef struct alternate_entry_s { + uintptr_t native_addr; + uintptr_t alternate_addr; + struct alternate_entry_s *next; +} alternate_entry_t; + +static alternate_entry_t *alternate_buckets[ALTERNATE_BUCKET_COUNT]; +static int alternate_nonempty; + +static unsigned int alternate_bucket_index(uintptr_t address) +{ + uint64_t key = address >> 4; + + return (unsigned int)((key * UINT64_C(0x9e3779b97f4a7c15)) >> + (64 - 12)); +} + +static int alternate_add_if_absent(void *addr, void *alt) +{ + alternate_entry_t *entry; + alternate_entry_t *head; + unsigned int bucket; + + entry = box_calloc(1, sizeof(*entry)); + if (!entry) { return 0; + } + entry->native_addr = (uintptr_t)addr; + entry->alternate_addr = (uintptr_t)alt; + bucket = alternate_bucket_index(entry->native_addr); + pthread_mutex_lock(&alternate_writer_lock); + head = qatomic_read(&alternate_buckets[bucket]); + for (; head; head = head->next) { + if (head->native_addr == entry->native_addr) { + pthread_mutex_unlock(&alternate_writer_lock); + box_free(entry); + return 0; + } + } + entry->next = qatomic_read(&alternate_buckets[bucket]); + qatomic_store_release(&alternate_buckets[bucket], entry); + /* Bucket release publication linearizes normal inserts. The first entry + * becomes visible to empty-table readers at nonempty publication below. */ + qatomic_store_release(&alternate_nonempty, 1); + pthread_mutex_unlock(&alternate_writer_lock); return 1; } +int hasAlternate(void* addr) { + alternate_entry_t *entry; + + if (!qatomic_load_acquire(&alternate_nonempty)) { + return 0; + } + entry = qatomic_load_acquire( + &alternate_buckets[alternate_bucket_index((uintptr_t)addr)]); + for (; entry; entry = entry->next) { + if (entry->native_addr == (uintptr_t)addr) { + return 1; + } + } + return 0; +} + void* getAlternate(void* addr) { - if(!my_alternates) + alternate_entry_t *entry; + + if (!qatomic_load_acquire(&alternate_nonempty)) { return addr; - khint_t k = kh_get(alternate, my_alternates, (uintptr_t)addr); - if(k!=kh_end(my_alternates)) - return kh_value(my_alternates, k); + } + entry = qatomic_load_acquire( + &alternate_buckets[alternate_bucket_index((uintptr_t)addr)]); + for (; entry; entry = entry->next) { + if (entry->native_addr == (uintptr_t)addr) { + return (void *)entry->alternate_addr; + } + } return addr; } + void addAlternate(void* addr, void* alt) { - if(!my_alternates) { - my_alternates = kh_init(alternate); - } - int ret; - khint_t k = kh_put(alternate, my_alternates, (uintptr_t)addr, &ret); - if(!ret) // already there - return; - kh_value(my_alternates, k) = alt; + (void)alternate_add_if_absent(addr, alt); } void cleanAlternate(void) { - if(my_alternates) { - kh_destroy(alternate, my_alternates); - my_alternates = NULL; + /* This is only valid at process teardown or in tests after all alternate + * readers and writers have stopped. */ + pthread_mutex_lock(&alternate_writer_lock); + qatomic_store_release(&alternate_nonempty, 0); + for (unsigned int i = 0; i < ALTERNATE_BUCKET_COUNT; ++i) { + alternate_entry_t *entry = qatomic_read(&alternate_buckets[i]); + + qatomic_store_release(&alternate_buckets[i], NULL); + while (entry) { + alternate_entry_t *next = entry->next; + + box_free(entry); + entry = next; + } } + pthread_mutex_unlock(&alternate_writer_lock); } void init_bridge_helper(void) diff --git a/target/i386/latx/context/kzt_guest_dynsym_lookup.c b/target/i386/latx/context/kzt_guest_dynsym_lookup.c new file mode 100644 index 00000000000..eaf6386fb8e --- /dev/null +++ b/target/i386/latx/context/kzt_guest_dynsym_lookup.c @@ -0,0 +1,583 @@ +#include "kzt_guest_dynsym_lookup.h" + +#include +#include + +#include "elf.h" + +#define KZT_GUEST_DYNSYM_GNU_CHAIN_LIMIT 4096 +#define KZT_GUEST_DYNSYM_GNU_HEADER_WORDS 4 +#define KZT_GUEST_DYNSYM_SYSV_CHAIN_LIMIT 4096 +#define KZT_GUEST_DYNSYM_VERSION_SCAN_LIMIT 128 + +typedef enum kzt_guest_dynsym_symbol_match { + KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN = -1, + KZT_GUEST_DYNSYM_SYMBOL_NO_MATCH = 0, + KZT_GUEST_DYNSYM_SYMBOL_MATCH = 1, +} kzt_guest_dynsym_symbol_match_t; + +static void kzt_guest_dynsym_result_clear( + kzt_guest_dynsym_lookup_result_t *result) +{ + memset(result, 0, sizeof(*result)); + result->status = KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN; +} + +static kzt_guest_dynsym_lookup_status_t kzt_guest_dynsym_finish( + kzt_guest_dynsym_lookup_result_t *result, + kzt_guest_dynsym_lookup_status_t status) +{ + result->status = status; + return status; +} + +static int kzt_guest_dynsym_add(uintptr_t base, + uintptr_t offset, + uintptr_t *result) +{ + if (base > UINTPTR_MAX - offset) { + return -1; + } + + *result = base + offset; + return 0; +} + +static int kzt_guest_dynsym_index_addr(uintptr_t base, + size_t entry_size, + uint32_t index, + uintptr_t *result) +{ + uintptr_t offset; + + if (entry_size == 0 || index > UINTPTR_MAX / entry_size) { + return -1; + } + + offset = (uintptr_t)index * entry_size; + return kzt_guest_dynsym_add(base, offset, result); +} + +static int kzt_guest_dynsym_scaled_offset(uint64_t count, + size_t entry_size, + uintptr_t *result) +{ + if (entry_size == 0 || count > UINTPTR_MAX / entry_size) { + return -1; + } + + *result = (uintptr_t)count * entry_size; + return 0; +} + +static int kzt_guest_dynsym_read( + const kzt_guest_link_map_reader_ops_t *reader_ops, + uintptr_t address, + void *value, + size_t size) +{ + return reader_ops->read_memory(address, value, size, reader_ops->opaque); +} + +static int kzt_guest_dynsym_runtime_field_valid( + const kzt_guest_dynamic_field_t *field) +{ + return field->present && + field->address_semantics == KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS; +} + +static int kzt_guest_dynsym_scalar_field_valid( + const kzt_guest_dynamic_field_t *field) +{ + return field->present && + field->address_semantics == KZT_GUEST_DYNAMIC_SCALAR; +} + +static uint32_t kzt_guest_dynsym_gnu_hash(const char *name) +{ + uint32_t hash = 5381; + + while (*name) { + hash = hash * 33 + (unsigned char)*name++; + } + + return hash; +} + +static uint32_t kzt_guest_dynsym_sysv_hash(const char *name) +{ + uint32_t hash = 0; + + while (*name) { + uint32_t high; + + hash = (hash << 4) + (unsigned char)*name++; + high = hash & UINT32_C(0xf0000000); + if (high) { + hash ^= high >> 24; + } + hash &= ~high; + } + + return hash; +} + +static kzt_guest_dynsym_symbol_match_t +kzt_guest_dynsym_name_matches( + const kzt_guest_dynamic_view_t *view, + const kzt_guest_link_map_reader_ops_t *reader_ops, + uint32_t string_offset, + const char *symbol) +{ + uintptr_t strtab; + uintptr_t string_addr; + size_t strsz; + size_t symbol_size; + size_t compared = 0; + + strtab = (uintptr_t)view->strtab.value; + if (view->strsz.value > SIZE_MAX) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + strsz = (size_t)view->strsz.value; + if (string_offset >= strsz || + kzt_guest_dynsym_add(strtab, string_offset, &string_addr) != 0) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + + symbol_size = strlen(symbol) + 1; + if (symbol_size > strsz - string_offset) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + + while (compared < symbol_size) { + char buffer[64]; + size_t remaining = symbol_size - compared; + size_t chunk = remaining < sizeof(buffer) ? remaining : sizeof(buffer); + uintptr_t read_addr; + + if (kzt_guest_dynsym_add(string_addr, compared, &read_addr) != 0 || + kzt_guest_dynsym_read(reader_ops, read_addr, buffer, chunk) != 0) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + if (memcmp(buffer, symbol + compared, chunk) != 0) { + return KZT_GUEST_DYNSYM_SYMBOL_NO_MATCH; + } + compared += chunk; + } + + return KZT_GUEST_DYNSYM_SYMBOL_MATCH; +} + +static kzt_guest_dynsym_symbol_match_t +kzt_guest_dynsym_version_matches( + const kzt_guest_dynamic_view_t *view, + const kzt_guest_link_map_reader_ops_t *reader_ops, + uint32_t symbol_index, + kzt_symbol_version_evidence_t version_evidence, + const char *version) +{ + uintptr_t versym_addr; + uintptr_t verdef_addr; + Elf64_Half raw_version; + unsigned int version_index; + size_t definition_limit; + size_t i; + + if (version_evidence == KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED) { + if (!view->versym.present) { + return KZT_GUEST_DYNSYM_SYMBOL_MATCH; + } + if (!kzt_guest_dynsym_runtime_field_valid(&view->versym) || + kzt_guest_dynsym_index_addr( + (uintptr_t)view->versym.value, sizeof(raw_version), + symbol_index, &versym_addr) != 0 || + kzt_guest_dynsym_read(reader_ops, versym_addr, &raw_version, + sizeof(raw_version)) != 0) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + if (raw_version & UINT16_C(0x8000)) { + return KZT_GUEST_DYNSYM_SYMBOL_NO_MATCH; + } + return KZT_GUEST_DYNSYM_SYMBOL_MATCH; + } + if (version_evidence != KZT_SYMBOL_VERSION_VERSIONED || + !version || !version[0] || + !kzt_guest_dynsym_runtime_field_valid(&view->versym) || + !kzt_guest_dynsym_runtime_field_valid(&view->verdef) || + (view->verdefnum.present && + !kzt_guest_dynsym_scalar_field_valid(&view->verdefnum))) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + + if (kzt_guest_dynsym_index_addr( + (uintptr_t)view->versym.value, sizeof(raw_version), + symbol_index, &versym_addr) != 0 || + kzt_guest_dynsym_read(reader_ops, versym_addr, &raw_version, + sizeof(raw_version)) != 0) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + + version_index = raw_version & 0x7fff; + if (version_index < 2) { + return KZT_GUEST_DYNSYM_SYMBOL_NO_MATCH; + } + + if (view->verdefnum.present) { + if (view->verdefnum.value == 0 || + view->verdefnum.value > KZT_GUEST_DYNSYM_VERSION_SCAN_LIMIT) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + definition_limit = (size_t)view->verdefnum.value; + } else { + definition_limit = KZT_GUEST_DYNSYM_VERSION_SCAN_LIMIT; + } + + verdef_addr = (uintptr_t)view->verdef.value; + for (i = 0; i < definition_limit; ++i) { + Elf64_Verdef definition; + + if (kzt_guest_dynsym_read(reader_ops, verdef_addr, &definition, + sizeof(definition)) != 0) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + + if ((definition.vd_ndx & 0x7fff) == version_index) { + uintptr_t auxiliary_addr; + Elf64_Verdaux auxiliary; + + if (definition.vd_cnt == 0 || definition.vd_aux == 0 || + kzt_guest_dynsym_add(verdef_addr, definition.vd_aux, + &auxiliary_addr) != 0 || + kzt_guest_dynsym_read(reader_ops, auxiliary_addr, &auxiliary, + sizeof(auxiliary)) != 0) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + + return kzt_guest_dynsym_name_matches( + view, reader_ops, auxiliary.vda_name, version); + } + + if (definition.vd_next == 0) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + if (kzt_guest_dynsym_add(verdef_addr, definition.vd_next, + &verdef_addr) != 0) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + } + + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; +} + +static kzt_guest_dynsym_symbol_match_t +kzt_guest_dynsym_inspect_symbol( + const kzt_guest_dynamic_view_t *view, + const kzt_guest_link_map_reader_ops_t *reader_ops, + const char *symbol, + kzt_symbol_version_evidence_t version_evidence, + const char *version, + uint32_t symbol_index, + kzt_guest_dynsym_lookup_result_t *result) +{ + uintptr_t symtab = (uintptr_t)view->symtab.value; + uintptr_t symbol_addr; + uintptr_t runtime_address; + Elf64_Sym candidate; + unsigned char binding; + unsigned char type; + unsigned char visibility; + kzt_guest_dynsym_symbol_match_t name_match; + + if (kzt_guest_dynsym_index_addr(symtab, sizeof(candidate), symbol_index, + &symbol_addr) != 0 || + kzt_guest_dynsym_read(reader_ops, symbol_addr, &candidate, + sizeof(candidate)) != 0) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + + name_match = kzt_guest_dynsym_name_matches( + view, reader_ops, candidate.st_name, symbol); + if (name_match != KZT_GUEST_DYNSYM_SYMBOL_MATCH) { + return name_match; + } + + name_match = kzt_guest_dynsym_version_matches( + view, reader_ops, symbol_index, version_evidence, version); + if (name_match != KZT_GUEST_DYNSYM_SYMBOL_MATCH) { + return name_match; + } + + binding = ELF64_ST_BIND(candidate.st_info); + type = ELF64_ST_TYPE(candidate.st_info); + visibility = candidate.st_other & 0x3; + if (candidate.st_shndx == SHN_UNDEF || + (visibility != STV_DEFAULT && visibility != STV_PROTECTED) || + (binding != STB_GLOBAL && binding != STB_WEAK +#ifdef STB_GNU_UNIQUE + && binding != STB_GNU_UNIQUE +#endif + ) || + (type != STT_NOTYPE && type != STT_OBJECT && + type != STT_FUNC && type != STT_COMMON && type != STT_TLS +#ifdef STT_GNU_IFUNC + && type != STT_GNU_IFUNC +#endif + )) { + return KZT_GUEST_DYNSYM_SYMBOL_NO_MATCH; + } + if (candidate.st_value > UINTPTR_MAX) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + if (candidate.st_shndx == SHN_ABS) { + runtime_address = (uintptr_t)candidate.st_value; + } else if (kzt_guest_dynsym_add( + view->load_bias, (uintptr_t)candidate.st_value, + &runtime_address) != 0) { + return KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN; + } + + result->binding = binding; + result->type = type; + result->visibility = visibility; + result->symbol_index = symbol_index; + result->runtime_address = runtime_address; + return KZT_GUEST_DYNSYM_SYMBOL_MATCH; +} + +static kzt_guest_dynsym_lookup_status_t kzt_guest_dynsym_lookup_gnu( + const kzt_guest_dynamic_view_t *view, + const kzt_guest_link_map_reader_ops_t *reader_ops, + const char *symbol, + kzt_symbol_version_evidence_t version_evidence, + const char *version, + kzt_guest_dynsym_lookup_result_t *result) +{ + uintptr_t header_addr = (uintptr_t)view->gnu_hash.value; + uintptr_t bloom_addr; + uintptr_t buckets_addr; + uintptr_t chains_addr; + uintptr_t bloom_word_addr; + uintptr_t bucket_addr; + uintptr_t chain_addr; + uintptr_t table_size; + uint32_t header[KZT_GUEST_DYNSYM_GNU_HEADER_WORDS]; + uint32_t hash = kzt_guest_dynsym_gnu_hash(symbol); + uint32_t bucket; + uint32_t symbol_index; + uint64_t bloom_word; + uint64_t bloom_mask; + size_t i; + + if (kzt_guest_dynsym_read(reader_ops, header_addr, header, + sizeof(header)) != 0 || + header[0] == 0 || header[2] == 0 || header[3] >= 64) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + } + + if (kzt_guest_dynsym_add(header_addr, sizeof(header), &bloom_addr) != 0 || + kzt_guest_dynsym_scaled_offset( + header[2], sizeof(uint64_t), &table_size) != 0 || + kzt_guest_dynsym_add(bloom_addr, table_size, &buckets_addr) != 0 || + kzt_guest_dynsym_scaled_offset( + header[0], sizeof(uint32_t), &table_size) != 0 || + kzt_guest_dynsym_add(buckets_addr, table_size, &chains_addr) != 0) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + } + + if (kzt_guest_dynsym_index_addr( + bloom_addr, sizeof(uint64_t), + (hash / 64) % header[2], &bloom_word_addr) != 0 || + kzt_guest_dynsym_read(reader_ops, bloom_word_addr, &bloom_word, + sizeof(bloom_word)) != 0) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + } + + bloom_mask = (UINT64_C(1) << (hash % 64)) | + (UINT64_C(1) << + (((uint64_t)hash >> header[3]) % 64)); + if ((bloom_word & bloom_mask) != bloom_mask) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_NOT_FOUND); + } + + if (kzt_guest_dynsym_index_addr( + buckets_addr, sizeof(uint32_t), hash % header[0], + &bucket_addr) != 0 || + kzt_guest_dynsym_read(reader_ops, bucket_addr, &bucket, + sizeof(bucket)) != 0) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + } + if (bucket == 0 || bucket < header[1]) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_NOT_FOUND); + } + + symbol_index = bucket; + if (kzt_guest_dynsym_index_addr( + chains_addr, sizeof(uint32_t), bucket - header[1], + &chain_addr) != 0) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + } + + for (i = 0; i < KZT_GUEST_DYNSYM_GNU_CHAIN_LIMIT; ++i) { + uint32_t chain_hash; + + if (kzt_guest_dynsym_read(reader_ops, chain_addr, &chain_hash, + sizeof(chain_hash)) != 0) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + } + if ((chain_hash | 1) == (hash | 1)) { + kzt_guest_dynsym_symbol_match_t match = + kzt_guest_dynsym_inspect_symbol( + view, reader_ops, symbol, version_evidence, version, + symbol_index, result); + + if (match == KZT_GUEST_DYNSYM_SYMBOL_MATCH) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_FOUND); + } + if (match == KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + } + } + if (chain_hash & 1) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_NOT_FOUND); + } + if (symbol_index == UINT32_MAX || + kzt_guest_dynsym_add(chain_addr, sizeof(uint32_t), + &chain_addr) != 0) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + } + ++symbol_index; + } + + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); +} + +static kzt_guest_dynsym_lookup_status_t kzt_guest_dynsym_lookup_sysv( + const kzt_guest_dynamic_view_t *view, + const kzt_guest_link_map_reader_ops_t *reader_ops, + const char *symbol, + kzt_symbol_version_evidence_t version_evidence, + const char *version, + kzt_guest_dynsym_lookup_result_t *result) +{ + uintptr_t header_addr = (uintptr_t)view->hash.value; + uintptr_t buckets_addr; + uintptr_t chains_addr; + uintptr_t bucket_addr; + uintptr_t chain_addr; + uintptr_t table_size; + uint32_t header[2]; + uint32_t symbol_index; + size_t i; + + if (kzt_guest_dynsym_read(reader_ops, header_addr, header, + sizeof(header)) != 0 || + header[0] == 0 || header[1] == 0 || + kzt_guest_dynsym_add(header_addr, sizeof(header), + &buckets_addr) != 0 || + kzt_guest_dynsym_scaled_offset( + header[0], sizeof(uint32_t), &table_size) != 0 || + kzt_guest_dynsym_add(buckets_addr, table_size, &chains_addr) != 0 || + kzt_guest_dynsym_index_addr( + buckets_addr, sizeof(uint32_t), + kzt_guest_dynsym_sysv_hash(symbol) % header[0], + &bucket_addr) != 0 || + kzt_guest_dynsym_read(reader_ops, bucket_addr, &symbol_index, + sizeof(symbol_index)) != 0) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + } + + for (i = 0; i < KZT_GUEST_DYNSYM_SYSV_CHAIN_LIMIT; ++i) { + uint32_t next_index; + kzt_guest_dynsym_symbol_match_t match; + + if (symbol_index == 0) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_NOT_FOUND); + } + if (symbol_index >= header[1]) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + } + + match = kzt_guest_dynsym_inspect_symbol( + view, reader_ops, symbol, version_evidence, version, + symbol_index, result); + if (match == KZT_GUEST_DYNSYM_SYMBOL_MATCH) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_FOUND); + } + if (match == KZT_GUEST_DYNSYM_SYMBOL_UNKNOWN) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + } + + if (kzt_guest_dynsym_index_addr( + chains_addr, sizeof(uint32_t), symbol_index, + &chain_addr) != 0 || + kzt_guest_dynsym_read(reader_ops, chain_addr, &next_index, + sizeof(next_index)) != 0) { + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + } + symbol_index = next_index; + } + + return kzt_guest_dynsym_finish( + result, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); +} + +kzt_guest_dynsym_lookup_status_t kzt_guest_dynsym_lookup( + const kzt_guest_dynamic_view_t *view, + const kzt_guest_link_map_reader_ops_t *reader_ops, + const char *symbol, + kzt_symbol_version_evidence_t version_evidence, + const char *version, + kzt_guest_dynsym_lookup_result_t *result) +{ + if (!result) { + return KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN; + } + kzt_guest_dynsym_result_clear(result); + + if (!view || !reader_ops || !reader_ops->read_memory || + !symbol || !symbol[0] || + view->status != KZT_GUEST_DYNAMIC_COMPLETE || + !kzt_guest_dynsym_runtime_field_valid(&view->symtab) || + !kzt_guest_dynsym_runtime_field_valid(&view->strtab) || + !kzt_guest_dynsym_scalar_field_valid(&view->syment) || + !kzt_guest_dynsym_scalar_field_valid(&view->strsz) || + view->syment.value != sizeof(Elf64_Sym)) { + return KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN; + } + if (view->gnu_hash.present) { + if (!kzt_guest_dynsym_runtime_field_valid(&view->gnu_hash)) { + return KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN; + } + return kzt_guest_dynsym_lookup_gnu( + view, reader_ops, symbol, version_evidence, version, result); + } + if (view->hash.present) { + if (!kzt_guest_dynsym_runtime_field_valid(&view->hash)) { + return KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN; + } + return kzt_guest_dynsym_lookup_sysv( + view, reader_ops, symbol, version_evidence, version, result); + } + + return KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN; +} diff --git a/target/i386/latx/context/kzt_guest_symbol_scope.c b/target/i386/latx/context/kzt_guest_symbol_scope.c new file mode 100644 index 00000000000..77af162790a --- /dev/null +++ b/target/i386/latx/context/kzt_guest_symbol_scope.c @@ -0,0 +1,580 @@ +#include "kzt_guest_symbol_scope.h" + +#include + +#include "elf.h" +#include "kzt_guest_dynamic.h" +#include "kzt_guest_dynsym_lookup.h" + +/* These private offsets are authorized only by the exact loader layout enum; + * the generic box64 link_map_x64 declaration is not layout evidence. */ +#define KZT_GLIBC_2_39_LINK_MAP_REAL_OFFSET 0x28 +#define KZT_GLIBC_2_39_LINK_MAP_AUDIT_FLAGS_OFFSET 0x350 +#define KZT_GLIBC_2_39_LINK_MAP_AUDIT_ANY_PLT_MASK UINT64_C(0x2000000000000) +#define KZT_GLIBC_2_39_LINK_MAP_RELOC_RESULT_OFFSET 0x378 +#define KZT_GLIBC_2_39_LINK_MAP_SCOPE_MAX_OFFSET 0x3c0 +#define KZT_GLIBC_2_39_LINK_MAP_SCOPE_OFFSET 0x3c8 + +typedef struct kzt_guest_scope_elem_x64 { + uint64_t r_list; + uint32_t r_nlist; + uint32_t padding; +} kzt_guest_scope_elem_x64_t; + +typedef struct kzt_guest_symbol_scope_snapshot { + kzt_guest_symbol_scope_identity_t identity; + uintptr_t maps[KZT_GUEST_SYMBOL_SCOPE_MAP_LIMIT]; +} kzt_guest_symbol_scope_snapshot_t; + +static uint64_t kzt_guest_symbol_scope_mix(uint64_t value, uint64_t item) +{ + value ^= item + UINT64_C(0x9e3779b97f4a7c15) + (value << 6) + + (value >> 2); + return value; +} + +static uint64_t kzt_guest_symbol_scope_text_fingerprint(uint64_t value, + const char *text) +{ + if (!text) { + return kzt_guest_symbol_scope_mix(value, 0); + } + do { + value = kzt_guest_symbol_scope_mix(value, (unsigned char)*text); + } while (*text++); + return value; +} + +static uint64_t kzt_guest_symbol_scope_query_fingerprint( + const kzt_guest_symbol_scope_request_t *request) +{ + uint64_t value = UINT64_C(0x6b7a745f73636f70); + + value = kzt_guest_symbol_scope_text_fingerprint(value, request->symbol); + value = kzt_guest_symbol_scope_mix(value, request->version_evidence); + value = kzt_guest_symbol_scope_text_fingerprint(value, request->version); + value = kzt_guest_symbol_scope_mix(value, request->reference_binding); + value = kzt_guest_symbol_scope_mix(value, request->reference_type); + value = kzt_guest_symbol_scope_mix(value, request->reference_visibility); + return value; +} + +static int kzt_guest_symbol_scope_add(uintptr_t base, size_t offset, + uintptr_t *address) +{ + if (!address || base > UINTPTR_MAX - offset) { + return -1; + } + *address = base + offset; + return 0; +} + +static int kzt_guest_symbol_scope_read( + const kzt_guest_link_map_reader_ops_t *reader_ops, uintptr_t address, + void *value, size_t size) +{ + if (!reader_ops || !reader_ops->read_memory || !address || !value || + !size) { + return -1; + } + return reader_ops->read_memory(address, value, size, + reader_ops->opaque) == 0 ? 0 : -1; +} + +static int kzt_guest_symbol_scope_contains( + const uintptr_t *items, size_t count, uintptr_t item) +{ + size_t i; + + for (i = 0; i < count; ++i) { + if (items[i] == item) { + return 1; + } + } + return 0; +} + +static int kzt_guest_symbol_scope_source_equal( + const kzt_guest_symbol_scope_source_t *left, + const kzt_guest_symbol_scope_source_t *right) +{ + return left && right && + left->link_map_addr == right->link_map_addr && + left->generation == right->generation && + left->namespace_id == right->namespace_id && + left->namespace_head == right->namespace_head && + left->layout == right->layout; +} + +static int kzt_guest_symbol_scope_identity_equal( + const kzt_guest_symbol_scope_identity_t *left, + const kzt_guest_symbol_scope_identity_t *right) +{ + return left && right && + kzt_guest_symbol_scope_source_equal(&left->source, + &right->source) && + left->scope_array_addr == right->scope_array_addr && + left->scope_list_count == right->scope_list_count && + left->scope_map_count == right->scope_map_count && + left->value == right->value; +} + +static kzt_guest_symbol_scope_reason_t kzt_guest_symbol_scope_read_snapshot( + const kzt_guest_symbol_scope_request_t *request, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_symbol_scope_snapshot_t *snapshot) +{ + uintptr_t scope_max_addr; + uintptr_t scope_addr; + uintptr_t scope_array; + uintptr_t scope_elems[KZT_GUEST_SYMBOL_SCOPE_LIST_LIMIT]; + size_t scope_max; + size_t scope_count = 0; + size_t map_count = 0; + uint64_t value = UINT64_C(0x6b7a745f6c73636f); + int source_seen = 0; + int terminated = 0; + size_t i; + + memset(snapshot, 0, sizeof(*snapshot)); + if (!request || !request->source.link_map_addr || + !request->source.generation || request->source.namespace_id != 0 || + !request->source.namespace_head || + request->source.layout != + KZT_GUEST_SCOPE_LAYOUT_GLIBC_2_39_C591A5DF) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_LAYOUT_UNSUPPORTED; + } + { + int classification = kzt_guest_link_map_classify_namespace( + request->source.link_map_addr, NULL, + request->source.namespace_head, reader_ops, NULL); + + if (classification < 0) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE; + } + if (classification == 0) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_CROSS_NAMESPACE; + } + } + if (kzt_guest_symbol_scope_add( + request->source.link_map_addr, + KZT_GLIBC_2_39_LINK_MAP_SCOPE_MAX_OFFSET, &scope_max_addr) != 0 || + kzt_guest_symbol_scope_add( + request->source.link_map_addr, + KZT_GLIBC_2_39_LINK_MAP_SCOPE_OFFSET, &scope_addr) != 0 || + kzt_guest_symbol_scope_read(reader_ops, scope_max_addr, &scope_max, + sizeof(scope_max)) != 0 || + kzt_guest_symbol_scope_read(reader_ops, scope_addr, &scope_array, + sizeof(scope_array)) != 0 || + !scope_array || !scope_max || + scope_max > KZT_GUEST_SYMBOL_SCOPE_LIST_LIMIT) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE; + } + + value = kzt_guest_symbol_scope_mix(value, request->source.link_map_addr); + value = kzt_guest_symbol_scope_mix(value, request->source.generation); + value = kzt_guest_symbol_scope_mix(value, request->source.namespace_head); + value = kzt_guest_symbol_scope_mix(value, request->source.layout); + value = kzt_guest_symbol_scope_mix(value, scope_array); + value = kzt_guest_symbol_scope_mix(value, scope_max); + + for (i = 0; i < scope_max; ++i) { + uintptr_t entry_addr; + uintptr_t scope_elem; + kzt_guest_scope_elem_x64_t before; + kzt_guest_scope_elem_x64_t after; + size_t j; + + if (i > UINTPTR_MAX / sizeof(uintptr_t) || + kzt_guest_symbol_scope_add( + scope_array, i * sizeof(uintptr_t), &entry_addr) != 0 || + kzt_guest_symbol_scope_read( + reader_ops, entry_addr, &scope_elem, sizeof(scope_elem)) != 0) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE; + } + value = kzt_guest_symbol_scope_mix(value, scope_elem); + if (!scope_elem) { + terminated = 1; + break; + } + if (kzt_guest_symbol_scope_contains(scope_elems, scope_count, + scope_elem)) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_DUPLICATE; + } + scope_elems[scope_count++] = scope_elem; + if (kzt_guest_symbol_scope_read(reader_ops, scope_elem, &before, + sizeof(before)) != 0 || + !before.r_list || !before.r_nlist || + before.r_nlist > KZT_GUEST_SYMBOL_SCOPE_MAP_LIMIT - map_count) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE; + } + value = kzt_guest_symbol_scope_mix(value, before.r_list); + value = kzt_guest_symbol_scope_mix(value, before.r_nlist); + for (j = 0; j < before.r_nlist; ++j) { + uintptr_t map_entry_addr; + uintptr_t map; + uintptr_t real_addr; + uintptr_t real_map; + uintptr_t audit_flags_addr; + uintptr_t reloc_result_addr; + uintptr_t reloc_result; + uint64_t audit_flags; + kzt_guest_link_map_identity_t identity; + int classification; + + if (j > UINTPTR_MAX / sizeof(uintptr_t) || + kzt_guest_symbol_scope_add( + (uintptr_t)before.r_list, j * sizeof(uintptr_t), + &map_entry_addr) != 0 || + kzt_guest_symbol_scope_read( + reader_ops, map_entry_addr, &map, sizeof(map)) != 0 || + !map) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE; + } + if (kzt_guest_symbol_scope_contains(snapshot->maps, map_count, + map)) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_DUPLICATE; + } + classification = kzt_guest_link_map_classify_namespace( + map, NULL, request->source.namespace_head, reader_ops, NULL); + if (classification < 0) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE; + } + if (classification == 0) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_CROSS_NAMESPACE; + } + if (kzt_guest_symbol_scope_add( + map, KZT_GLIBC_2_39_LINK_MAP_REAL_OFFSET, + &real_addr) != 0 || + kzt_guest_symbol_scope_read( + reader_ops, real_addr, &real_map, sizeof(real_map)) != 0) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE; + } + if (real_map != map) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_SEMANTICS_UNSUPPORTED; + } + if (kzt_guest_symbol_scope_add( + map, KZT_GLIBC_2_39_LINK_MAP_AUDIT_FLAGS_OFFSET, + &audit_flags_addr) != 0 || + kzt_guest_symbol_scope_add( + map, KZT_GLIBC_2_39_LINK_MAP_RELOC_RESULT_OFFSET, + &reloc_result_addr) != 0 || + kzt_guest_symbol_scope_read( + reader_ops, audit_flags_addr, &audit_flags, + sizeof(audit_flags)) != 0 || + kzt_guest_symbol_scope_read( + reader_ops, reloc_result_addr, &reloc_result, + sizeof(reloc_result)) != 0) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE; + } + if ((audit_flags & + KZT_GLIBC_2_39_LINK_MAP_AUDIT_ANY_PLT_MASK) != 0 || + reloc_result) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_AUDIT_UNSUPPORTED; + } + if (kzt_guest_link_map_read_identity( + map, reader_ops, &identity) != 0) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE; + } + snapshot->maps[map_count++] = map; + source_seen |= map == request->source.link_map_addr; + value = kzt_guest_symbol_scope_mix(value, map); + value = kzt_guest_symbol_scope_mix(value, identity.load_bias); + value = kzt_guest_symbol_scope_mix(value, identity.dynamic_addr); + value = kzt_guest_symbol_scope_mix(value, audit_flags); + value = kzt_guest_symbol_scope_mix(value, reloc_result); + } + if (kzt_guest_symbol_scope_read(reader_ops, scope_elem, &after, + sizeof(after)) != 0 || + after.r_list != before.r_list || + after.r_nlist != before.r_nlist) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_STALE; + } + } + if (!terminated || !scope_count || !map_count || !source_seen) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE; + } + { + size_t confirmed_scope_max; + uintptr_t confirmed_scope_array; + + if (kzt_guest_symbol_scope_read( + reader_ops, scope_max_addr, &confirmed_scope_max, + sizeof(confirmed_scope_max)) != 0 || + kzt_guest_symbol_scope_read( + reader_ops, scope_addr, &confirmed_scope_array, + sizeof(confirmed_scope_array)) != 0 || + confirmed_scope_max != scope_max || + confirmed_scope_array != scope_array) { + return KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_STALE; + } + } + + snapshot->identity.source = request->source; + snapshot->identity.scope_array_addr = scope_array; + snapshot->identity.scope_list_count = scope_count; + snapshot->identity.scope_map_count = map_count; + snapshot->identity.value = value; + return KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER; +} + +static kzt_guest_symbol_scope_status_t kzt_guest_symbol_scope_finish( + kzt_guest_symbol_scope_result_t *result, + kzt_guest_symbol_scope_reason_t reason) +{ + result->reason = reason; + result->status = + reason == KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER ? + KZT_GUEST_SYMBOL_SCOPE_SAFE : + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED; + return result->status; +} + +static void kzt_guest_symbol_scope_clear( + kzt_guest_symbol_scope_result_t *result) +{ + memset(result, 0, sizeof(*result)); + result->status = KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED; + result->reason = KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE; +} + +static kzt_guest_symbol_scope_status_t kzt_guest_symbol_scope_incomplete( + kzt_guest_symbol_scope_result_t *result, + kzt_guest_symbol_scope_reason_t reason) +{ + result->scope_complete = 0; + result->lookup_order_known = 0; + result->selected_provider_link_map = 0; + result->selected_provider_address = 0; + result->selected_provider_binding = 0; + result->selected_provider_type = 0; + result->selected_provider_visibility = 0; + return kzt_guest_symbol_scope_finish(result, reason); +} + +static int kzt_guest_symbol_scope_request_valid( + const kzt_guest_symbol_scope_request_t *request) +{ + return request && request->symbol && request->symbol[0] && + request->reference_binding == STB_GLOBAL && + request->reference_type == STT_FUNC && + request->reference_visibility == STV_DEFAULT && + kzt_symbol_version_evidence_valid(request->version_evidence, + request->version); +} + +static kzt_guest_symbol_scope_status_t kzt_guest_symbol_scope_evaluate( + const kzt_guest_symbol_scope_request_t *request, + uintptr_t selected_provider_link_map, uintptr_t selected_provider_address, + int require_selected_provider, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_symbol_scope_result_t *result) +{ + kzt_guest_symbol_scope_snapshot_t before; + kzt_guest_symbol_scope_snapshot_t after; + kzt_guest_symbol_scope_reason_t read_reason; + size_t i; + + if (!result) { + return KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED; + } + kzt_guest_symbol_scope_clear(result); + if (!request || !request->symbol || !request->symbol[0] || + !reader_ops || !reader_ops->read_memory || + !kzt_symbol_version_evidence_valid(request->version_evidence, + request->version) || + (require_selected_provider && + (!selected_provider_link_map || !selected_provider_address))) { + return result->status; + } + if (!kzt_guest_symbol_scope_request_valid(request)) { + return kzt_guest_symbol_scope_finish( + result, KZT_GUEST_SYMBOL_SCOPE_REASON_UNSUPPORTED_REFERENCE); + } + result->query_fingerprint = + kzt_guest_symbol_scope_query_fingerprint(request); + read_reason = kzt_guest_symbol_scope_read_snapshot( + request, reader_ops, &before); + if (read_reason != KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER) { + return kzt_guest_symbol_scope_incomplete(result, read_reason); + } + result->scope_identity = before.identity; + + for (i = 0; i < before.identity.scope_map_count; ++i) { + kzt_guest_link_map_identity_t identity; + kzt_guest_dynamic_parse_result_t dynamic_result; + kzt_guest_dynsym_lookup_result_t lookup_result; + kzt_guest_dynsym_lookup_status_t lookup_status; + + if (kzt_guest_link_map_read_identity( + before.maps[i], reader_ops, &identity) != 0 || + kzt_guest_dynamic_parse( + identity.dynamic_addr, identity.load_bias, + reader_ops, &dynamic_result) != 0 || + dynamic_result.status != KZT_GUEST_DYNAMIC_COMPLETE) { + return kzt_guest_symbol_scope_incomplete( + result, KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE); + } + lookup_status = kzt_guest_dynsym_lookup( + &dynamic_result.view, reader_ops, request->symbol, + request->version_evidence, request->version, &lookup_result); + kzt_guest_dynamic_parse_result_clear(&dynamic_result); + if (lookup_status == KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN) { + return kzt_guest_symbol_scope_incomplete( + result, KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE); + } + if (lookup_status == KZT_GUEST_DYNSYM_LOOKUP_FOUND) { + ++result->candidate_count; + if (!result->selected_provider_link_map) { + result->selected_provider_link_map = before.maps[i]; + result->selected_provider_address = + lookup_result.runtime_address; + result->selected_provider_binding = lookup_result.binding; + result->selected_provider_type = lookup_result.type; + result->selected_provider_visibility = + lookup_result.visibility; + } + } + } + + read_reason = kzt_guest_symbol_scope_read_snapshot( + request, reader_ops, &after); + if (read_reason != KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER || + !kzt_guest_symbol_scope_identity_equal(&before.identity, + &after.identity)) { + return kzt_guest_symbol_scope_incomplete( + result, KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_STALE); + } + + result->scope_complete = 1; + result->lookup_order_known = 1; + if (!result->selected_provider_link_map || + !result->selected_provider_address || + (require_selected_provider && + (result->selected_provider_link_map != selected_provider_link_map || + result->selected_provider_address != selected_provider_address))) { + return kzt_guest_symbol_scope_finish( + result, KZT_GUEST_SYMBOL_SCOPE_REASON_PROVIDER_MISMATCH); + } + if (result->selected_provider_binding != STB_GLOBAL) { + return kzt_guest_symbol_scope_finish( + result, + KZT_GUEST_SYMBOL_SCOPE_REASON_UNSUPPORTED_PROVIDER_BINDING); + } + if (result->selected_provider_type != STT_FUNC || + result->selected_provider_visibility != STV_DEFAULT) { + return kzt_guest_symbol_scope_finish( + result, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_SEMANTICS_UNSUPPORTED); + } + return kzt_guest_symbol_scope_finish( + result, KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER); +} + +kzt_guest_symbol_scope_status_t kzt_guest_symbol_scope_discover( + const kzt_guest_symbol_scope_request_t *request, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_symbol_scope_result_t *result) +{ + return kzt_guest_symbol_scope_evaluate( + request, 0, 0, 0, reader_ops, result); +} + +kzt_guest_symbol_scope_status_t kzt_guest_symbol_scope_check( + const kzt_guest_symbol_scope_request_t *request, + uintptr_t selected_provider_link_map, + uintptr_t selected_provider_address, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_symbol_scope_result_t *result) +{ + return kzt_guest_symbol_scope_evaluate( + request, selected_provider_link_map, selected_provider_address, 1, + reader_ops, result); +} + +kzt_guest_symbol_scope_status_t kzt_guest_symbol_scope_revalidate( + const kzt_guest_symbol_scope_result_t *proof, + const kzt_guest_symbol_scope_request_t *request, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_symbol_scope_result_t *result) +{ + kzt_guest_symbol_scope_result_t current; + kzt_guest_symbol_scope_result_t checked; + + if (!result) { + return KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED; + } + kzt_guest_symbol_scope_clear(&checked); + if (proof) { + checked = *proof; + } + if (!proof || !request || + proof->status != KZT_GUEST_SYMBOL_SCOPE_SAFE || + proof->reason != KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER || + !proof->scope_complete || !proof->lookup_order_known || + !proof->selected_provider_link_map || + !proof->selected_provider_address || + proof->selected_provider_binding != STB_GLOBAL || + proof->selected_provider_type != STT_FUNC || + proof->selected_provider_visibility != STV_DEFAULT || + proof->query_fingerprint != + kzt_guest_symbol_scope_query_fingerprint(request) || + !kzt_guest_symbol_scope_source_equal( + &proof->scope_identity.source, &request->source) || + kzt_guest_symbol_scope_evaluate( + request, proof->selected_provider_link_map, + proof->selected_provider_address, 1, reader_ops, ¤t) != + KZT_GUEST_SYMBOL_SCOPE_SAFE || + !kzt_guest_symbol_scope_identity_equal( + &proof->scope_identity, ¤t.scope_identity) || + proof->candidate_count != current.candidate_count || + proof->selected_provider_binding != + current.selected_provider_binding || + proof->selected_provider_type != current.selected_provider_type || + proof->selected_provider_visibility != + current.selected_provider_visibility) { + checked.status = KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED; + checked.reason = KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_STALE; + checked.scope_complete = 0; + checked.lookup_order_known = 0; + checked.selected_provider_link_map = 0; + checked.selected_provider_address = 0; + checked.selected_provider_binding = 0; + checked.selected_provider_type = 0; + checked.selected_provider_visibility = 0; + *result = checked; + return result->status; + } + *result = current; + return result->status; +} + +const char *kzt_guest_symbol_scope_reason_name( + kzt_guest_symbol_scope_reason_t reason) +{ + switch (reason) { + case KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER: + return "SELECTED_PROVIDER"; + case KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE: + return "SCOPE_INCOMPLETE"; + case KZT_GUEST_SYMBOL_SCOPE_REASON_UNSUPPORTED_PROVIDER_BINDING: + return "UNSUPPORTED_PROVIDER_BINDING"; + case KZT_GUEST_SYMBOL_SCOPE_REASON_UNSUPPORTED_REFERENCE: + return "UNSUPPORTED_REFERENCE"; + case KZT_GUEST_SYMBOL_SCOPE_REASON_PROVIDER_MISMATCH: + return "PROVIDER_MISMATCH"; + case KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_STALE: + return "SCOPE_STALE"; + case KZT_GUEST_SYMBOL_SCOPE_REASON_LAYOUT_UNSUPPORTED: + return "LAYOUT_UNSUPPORTED"; + case KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_DUPLICATE: + return "SCOPE_DUPLICATE"; + case KZT_GUEST_SYMBOL_SCOPE_REASON_CROSS_NAMESPACE: + return "CROSS_NAMESPACE"; + case KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_SEMANTICS_UNSUPPORTED: + return "SCOPE_SEMANTICS_UNSUPPORTED"; + case KZT_GUEST_SYMBOL_SCOPE_REASON_AUDIT_UNSUPPORTED: + return "AUDIT_UNSUPPORTED"; + } + return "UNKNOWN"; +} diff --git a/target/i386/latx/context/kzt_jump_slot_route.c b/target/i386/latx/context/kzt_jump_slot_route.c index b695411693f..5a52267308a 100644 --- a/target/i386/latx/context/kzt_jump_slot_route.c +++ b/target/i386/latx/context/kzt_jump_slot_route.c @@ -8,6 +8,7 @@ typedef struct kzt_jump_slot_route_slot_state { uintptr_t committed_value; int last_read_present; int write_succeeded; + int write_attempted; int cas_mismatch; } kzt_jump_slot_route_slot_state_t; @@ -36,6 +37,7 @@ static int route_slot_write(uintptr_t slot_addr, uintptr_t value, void *opaque) } expected = state->write_succeeded ? state->committed_value : state->last_read; + state->write_attempted = 1; exchanged = state->ops->compare_exchange_slot( slot_addr, &expected, value, state->ops->opaque); if (exchanged > 0) { @@ -102,14 +104,10 @@ static int route_owner_identity_matches( request->current_owner.generation == acquired_owner->generation; } -static int route_legacy_fallback( +static int route_decline_without_write( const kzt_jump_slot_route_input_t *input, - const kzt_jump_slot_route_ops_t *ops, kzt_jump_slot_route_result_t *result) { - uintptr_t expected = result->observed_value; - int exchanged; - if (input->preserve_observed_on_failure) { result->status = KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED; result->selected_target = result->observed_value; @@ -117,24 +115,54 @@ static int route_legacy_fallback( return 0; } - result->legacy_fallback_attempted = 1; + result->status = KZT_JUMP_SLOT_ROUTE_BYPASS; result->selected_target = input->request.legacy_target; - exchanged = ops->compare_exchange_slot( - input->request.slot_addr, &expected, input->request.legacy_target, - ops->opaque); - if (exchanged < 0) { - result->status = KZT_JUMP_SLOT_ROUTE_WRITE_ERROR; - result->final_value = expected; - return 0; + result->final_value = result->observed_value; + return 0; +} + +kzt_jump_slot_route_caller_decision_t kzt_jump_slot_route_caller_decide( + int route_call_succeeded, + const kzt_jump_slot_route_result_t *result, + uintptr_t legacy_target, + int final_value_usable) +{ + kzt_jump_slot_route_caller_decision_t decision = { + .slot_action = KZT_JUMP_SLOT_ROUTE_SLOT_PRESERVE, + .call_target = legacy_target, + .slot_value_usable = 0, + }; + + if (!route_call_succeeded || !result) { + decision.slot_action = KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE; + return decision; } - if (exchanged == 0) { - result->status = KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH; - result->final_value = expected; - return 0; + + switch (result->status) { + case KZT_JUMP_SLOT_ROUTE_BYPASS: + decision.slot_action = KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE; + break; + case KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED: + if (final_value_usable) { + decision.slot_action = KZT_JUMP_SLOT_ROUTE_SLOT_ROUTE_APPLIED; + decision.call_target = result->final_value; + decision.slot_value_usable = 1; + } + break; + case KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED: + case KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH: + case KZT_JUMP_SLOT_ROUTE_WRITE_ROLLED_BACK: + case KZT_JUMP_SLOT_ROUTE_UNRECOVERABLE: + if (final_value_usable) { + decision.call_target = result->final_value; + decision.slot_value_usable = 1; + } + break; + case KZT_JUMP_SLOT_ROUTE_WRITE_ERROR: + default: + break; } - result->status = KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED; - result->final_value = input->request.legacy_target; - return 0; + return decision; } int kzt_jump_slot_route_apply(const kzt_jump_slot_route_input_t *input, @@ -164,7 +192,7 @@ int kzt_jump_slot_route_apply(const kzt_jump_slot_route_input_t *input, } if (ops->load_slot(input->request.slot_addr, &result->observed_value, ops->opaque) != 0) { - return 0; + return -1; } if (input->request.slot_current_value_present && result->observed_value != input->request.slot_current_value) { @@ -180,9 +208,8 @@ int kzt_jump_slot_route_apply(const kzt_jump_slot_route_input_t *input, memset(&acquired_owner, 0, sizeof(acquired_owner)); memset(&slot_state, 0, sizeof(slot_state)); - if (input->resolved_target_matches_legacy && - input->expected_guest_target_present && - request.expected_guest_target && input->resolved_provider && + if (input->expected_guest_target_present && + request.expected_guest_target && ops->enrich_base && ops->acquire_exact_provider && ops->release_exact_provider && ops->enrich_bridge && ops->try_native_writer && @@ -194,7 +221,9 @@ int kzt_jump_slot_route_apply(const kzt_jump_slot_route_input_t *input, ops->opaque) == 0) { acquired = 1; result->exact_provider_acquired = 1; - if (handle.library == input->resolved_provider) { + if (handle.library && + (!input->resolved_provider || + handle.library == input->resolved_provider)) { result->exact_provider_matched = 1; if (ops->enrich_bridge(&request, handle.library, ops->opaque) == 0 && @@ -226,6 +255,30 @@ int kzt_jump_slot_route_apply(const kzt_jump_slot_route_input_t *input, if (acquired) { ops->release_exact_provider(&handle, ops->opaque); } + if (!result->native_writer_called && + ops->preserve_guest_after_bridge_failure && + ops->preserve_guest_after_bridge_failure(&result->final_value, + ops->opaque) > 0) { + result->status = KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED; + result->selected_target = result->final_value; + return 0; + } + if (result->writer_status == + KZT_JUMP_SLOT_ROUTE_WRITER_UNRECOVERABLE) { + result->status = KZT_JUMP_SLOT_ROUTE_UNRECOVERABLE; + result->selected_target = slot_state.last_read_present ? + slot_state.last_read : result->observed_value; + result->final_value = result->selected_target; + return 0; + } + if (result->writer_status == + KZT_JUMP_SLOT_ROUTE_WRITER_ROLLED_BACK) { + result->status = KZT_JUMP_SLOT_ROUTE_WRITE_ROLLED_BACK; + result->selected_target = slot_state.last_read_present ? + slot_state.last_read : result->observed_value; + result->final_value = result->selected_target; + return 0; + } if (result->native_writer_called && slot_state.cas_mismatch) { result->status = KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH; result->selected_target = slot_state.last_read; @@ -238,6 +291,13 @@ int kzt_jump_slot_route_apply(const kzt_jump_slot_route_input_t *input, result->final_value = request.native_bridge_target; return 0; } + if (result->native_writer_called && slot_state.write_attempted) { + result->status = KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED; + result->selected_target = slot_state.last_read_present ? + slot_state.last_read : result->observed_value; + result->final_value = result->selected_target; + return 0; + } if (result->writer_status == KZT_JUMP_SLOT_ROUTE_WRITER_PRESERVE) { result->status = KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED; result->selected_target = slot_state.last_read_present ? @@ -246,5 +306,5 @@ int kzt_jump_slot_route_apply(const kzt_jump_slot_route_input_t *input, return 0; } - return route_legacy_fallback(input, ops, result); + return route_decline_without_write(input, result); } diff --git a/target/i386/latx/context/kzt_lazy_binding.c b/target/i386/latx/context/kzt_lazy_binding.c index 0948367c45a..d89a011750e 100644 --- a/target/i386/latx/context/kzt_lazy_binding.c +++ b/target/i386/latx/context/kzt_lazy_binding.c @@ -181,6 +181,14 @@ int kzt_lazy_binding_complete( } else if (route_result.status == KZT_LAZY_BINDING_ROUTE_CAS_MISMATCH) { result->status = KZT_LAZY_BINDING_CAS_MISMATCH; result->reason = KZT_LAZY_BINDING_REASON_CAS_MISMATCH; + } else if (route_result.status == + KZT_LAZY_BINDING_ROUTE_WRITE_ROLLED_BACK) { + result->status = KZT_LAZY_BINDING_WRITE_ROLLED_BACK; + result->reason = KZT_LAZY_BINDING_REASON_WRITE_ROLLED_BACK; + } else if (route_result.status == + KZT_LAZY_BINDING_ROUTE_UNRECOVERABLE) { + result->status = KZT_LAZY_BINDING_UNRECOVERABLE; + result->reason = KZT_LAZY_BINDING_REASON_UNRECOVERABLE; } else { result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; result->reason = KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE; diff --git a/target/i386/latx/context/kzt_lazy_diagnostics.c b/target/i386/latx/context/kzt_lazy_diagnostics.c index 9464edfae45..8b69e758bb4 100644 --- a/target/i386/latx/context/kzt_lazy_diagnostics.c +++ b/target/i386/latx/context/kzt_lazy_diagnostics.c @@ -47,6 +47,10 @@ static const char *kzt_lazy_diagnostic_status_name( return "NATIVE_APPLIED"; case KZT_LAZY_BINDING_CAS_MISMATCH: return "CAS_MISMATCH"; + case KZT_LAZY_BINDING_WRITE_ROLLED_BACK: + return "WRITE_ROLLED_BACK"; + case KZT_LAZY_BINDING_UNRECOVERABLE: + return "UNRECOVERABLE"; case KZT_LAZY_BINDING_ERROR: return "ERROR"; case KZT_LAZY_BINDING_GUEST_PRESERVED: @@ -97,6 +101,10 @@ static const char *kzt_lazy_diagnostic_reason_name( return "SLOT_READ_ERROR"; case KZT_LAZY_BINDING_REASON_CAS_ERROR: return "CAS_ERROR"; + case KZT_LAZY_BINDING_REASON_WRITE_ROLLED_BACK: + return "WRITE_ROLLED_BACK"; + case KZT_LAZY_BINDING_REASON_UNRECOVERABLE: + return "UNRECOVERABLE"; } return "UNKNOWN"; diff --git a/target/i386/latx/context/kzt_patch_planner.c b/target/i386/latx/context/kzt_patch_planner.c index 2995f2f91d4..ee9f2df29ea 100644 --- a/target/i386/latx/context/kzt_patch_planner.c +++ b/target/i386/latx/context/kzt_patch_planner.c @@ -261,6 +261,12 @@ int kzt_patch_planner_decide(const kzt_patch_candidate_t *candidate, KZT_PATCH_REASON_INPUT_MALFORMED_SYMBOL_NAME); } + if (kzt_patch_symbol_must_stay_guest(candidate->symbol_name)) { + return kzt_patch_decision_set(decision, + KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_KEEP_GUEST); + } + if (!kzt_symbol_version_evidence_valid(candidate->version_evidence, candidate->version)) { return kzt_patch_decision_set( diff --git a/target/i386/latx/context/kzt_patch_spike_guard.c b/target/i386/latx/context/kzt_patch_spike_guard.c index a391a3fb7db..3903856e21d 100644 --- a/target/i386/latx/context/kzt_patch_spike_guard.c +++ b/target/i386/latx/context/kzt_patch_spike_guard.c @@ -1,3 +1,5 @@ +#include "qemu/osdep.h" + #include "kzt_patch_spike_guard.h" #include @@ -111,6 +113,13 @@ int kzt_patch_spike_guard_circuit_open( return guard && __atomic_load_n(&guard->circuit_open, __ATOMIC_ACQUIRE); } +void kzt_patch_spike_guard_trip(kzt_patch_spike_guard_t *guard) +{ + if (guard) { + __atomic_store_n(&guard->circuit_open, 1, __ATOMIC_RELEASE); + } +} + unsigned long kzt_patch_spike_guard_budget_remaining( const kzt_patch_spike_guard_t *guard) { @@ -251,7 +260,23 @@ int kzt_patch_spike_guard_try_write( int preserve_guest = kzt_patch_spike_failure_preserves_guest(failure); if (failure == KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED) { + if (writer->finish_slot && + writer->finish_slot(decision, writer->opaque) == + KZT_PATCH_SPIKE_WRITER_OK) { + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_FAIL_OPEN, + KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED, + KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } __atomic_store_n(&guard->circuit_open, 1, __ATOMIC_RELEASE); + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE, + KZT_PATCH_SPIKE_FAILURE_TRANSACTION_UNRECOVERABLE, + KZT_PATCH_SPIKE_ACTION_TRANSACTION_UNRECOVERABLE, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; } kzt_patch_spike_outcome_set( outcome, preserve_guest ? KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED : @@ -271,19 +296,13 @@ int kzt_patch_spike_guard_try_write( if (writer->finish_slot && writer->finish_slot(decision, writer->opaque) != KZT_PATCH_SPIKE_WRITER_OK) { - __atomic_store_n(&guard->circuit_open, 1, __ATOMIC_RELEASE); - kzt_patch_spike_outcome_set( - outcome, KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED, - KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED, - KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST, guard); - kzt_patch_spike_guard_unlock(guard); - return 0; + (void)writer->finish_slot(decision, writer->opaque); } __atomic_store_n(&guard->circuit_open, 1, __ATOMIC_RELEASE); kzt_patch_spike_outcome_set( - outcome, KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED, - KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED, - KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST, guard); + outcome, KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE, + KZT_PATCH_SPIKE_FAILURE_TRANSACTION_UNRECOVERABLE, + KZT_PATCH_SPIKE_ACTION_TRANSACTION_UNRECOVERABLE, guard); kzt_patch_spike_guard_unlock(guard); return 0; } @@ -291,13 +310,16 @@ int kzt_patch_spike_guard_try_write( if (writer->finish_slot && writer->finish_slot(decision, writer->opaque) != KZT_PATCH_SPIKE_WRITER_OK) { - __atomic_store_n(&guard->circuit_open, 1, __ATOMIC_RELEASE); - kzt_patch_spike_outcome_set( - outcome, KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED, - KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED, - KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST, guard); - kzt_patch_spike_guard_unlock(guard); - return 0; + if (writer->finish_slot(decision, writer->opaque) != + KZT_PATCH_SPIKE_WRITER_OK) { + __atomic_store_n(&guard->circuit_open, 1, __ATOMIC_RELEASE); + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE, + KZT_PATCH_SPIKE_FAILURE_TRANSACTION_UNRECOVERABLE, + KZT_PATCH_SPIKE_ACTION_TRANSACTION_UNRECOVERABLE, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } } kzt_patch_spike_outcome_set( @@ -311,11 +333,27 @@ int kzt_patch_spike_guard_try_write( if (writer->finish_slot && writer->finish_slot(decision, writer->opaque) != KZT_PATCH_SPIKE_WRITER_OK) { + int rollback_succeeded; + int restore_succeeded; + + outcome->rollback_called = 1; + rollback_succeeded = writer->rollback_slot( + decision, previous_value, writer->opaque) == 0; + restore_succeeded = writer->finish_slot( + decision, writer->opaque) == KZT_PATCH_SPIKE_WRITER_OK; + if (rollback_succeeded && restore_succeeded) { + kzt_patch_spike_outcome_set( + outcome, KZT_PATCH_SPIKE_RESULT_ROLLED_BACK, + KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED, + KZT_PATCH_SPIKE_ACTION_ROLLBACK_COMPLETE, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } __atomic_store_n(&guard->circuit_open, 1, __ATOMIC_RELEASE); kzt_patch_spike_outcome_set( - outcome, KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED, - KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED, - KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST, guard); + outcome, KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE, + KZT_PATCH_SPIKE_FAILURE_TRANSACTION_UNRECOVERABLE, + KZT_PATCH_SPIKE_ACTION_TRANSACTION_UNRECOVERABLE, guard); kzt_patch_spike_guard_unlock(guard); return 0; } @@ -346,6 +384,10 @@ const char *kzt_patch_spike_result_name(kzt_patch_spike_result_t result) return "GUEST_PRESERVED"; case KZT_PATCH_SPIKE_RESULT_CIRCUIT_OPEN: return "CIRCUIT_OPEN"; + case KZT_PATCH_SPIKE_RESULT_ROLLED_BACK: + return "ROLLED_BACK"; + case KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE: + return "UNRECOVERABLE"; } return "UNKNOWN"; @@ -382,6 +424,8 @@ const char *kzt_patch_spike_failure_name(kzt_patch_spike_failure_t failure) return "GENERATION_MISMATCH"; case KZT_PATCH_SPIKE_FAILURE_CIRCUIT_BREAKER_OPEN: return "CIRCUIT_BREAKER_OPEN"; + case KZT_PATCH_SPIKE_FAILURE_TRANSACTION_UNRECOVERABLE: + return "TRANSACTION_UNRECOVERABLE"; } return "UNKNOWN"; diff --git a/target/i386/latx/context/kzt_patch_spike_writer.c b/target/i386/latx/context/kzt_patch_spike_writer.c index 06121e18641..d9738239ad6 100644 --- a/target/i386/latx/context/kzt_patch_spike_writer.c +++ b/target/i386/latx/context/kzt_patch_spike_writer.c @@ -104,6 +104,7 @@ static void kzt_patch_spike_record_permission( record->permission_was_writable = lease->was_writable; record->permission_write_enabled = lease->write_enabled; record->permission_restore_attempted = lease->restore_attempted; + record->permission_restore_attempts = lease->restore_attempts; record->permission_restored = lease->restored; } @@ -121,12 +122,12 @@ static kzt_patch_spike_writer_status_t kzt_patch_spike_writer_finish_slot( } state->permission_lease.restore_attempted = 1; + ++state->permission_lease.restore_attempts; if (state->slot_ops->end_write && state->slot_ops->end_write(&state->permission_lease, state->slot_ops->opaque) != 0) { kzt_patch_spike_record_permission(state->record, &state->permission_lease); - state->permission_active = 0; return KZT_PATCH_SPIKE_WRITER_PERMISSION_RESTORE_FAILED; } state->permission_lease.restored = 1; diff --git a/target/i386/latx/context/kzt_per_object_got_plt.c b/target/i386/latx/context/kzt_per_object_got_plt.c new file mode 100644 index 00000000000..90b38b0aaf0 --- /dev/null +++ b/target/i386/latx/context/kzt_per_object_got_plt.c @@ -0,0 +1,68 @@ +#include "kzt_per_object_got_plt.h" + +#include + +static void kzt_per_object_got_plt_init_result( + kzt_per_object_got_plt_result_t *result) +{ + if (result) { + memset(result, 0, sizeof(*result)); + result->status = KZT_PER_OBJECT_GOT_PLT_FAIL_OPEN; + } +} + +int kzt_per_object_got_plt_apply( + const kzt_per_object_got_plt_request_t *request, + kzt_per_object_got_plt_result_t *result) +{ + kzt_guest_registry_address_match_t match = { 0 }; + kzt_guest_dynamic_view_t view; + kzt_guest_field_status_t view_status; + unsigned long view_generation = 0; + kzt_guest_registry_source_lease_t source_lease = { 0 }; + kzt_guest_registry_patch_decision_lease_t decision_lease = { 0 }; + kzt_guest_got_plt_injection_claim_result_t claim; + int applied = 0; + + kzt_per_object_got_plt_init_result(result); + if (!request || !result || !request->registry || + !request->link_map_addr || !request->apply || + kzt_guest_registry_find_live_object( + request->registry, request->link_map_addr, &match) != 0 || + match.match_count != 1 || !match.generation || + match.namespace_id_status != KZT_GUEST_FIELD_OK || + match.namespace_id != 0 || + kzt_guest_registry_find_dynamic_view( + request->registry, request->link_map_addr, &view, &view_status, + &view_generation) != 0 || + view_status != KZT_GUEST_FIELD_OK || + view_generation != match.generation || + kzt_guest_registry_source_lease_acquire( + request->registry, request->link_map_addr, match.generation, 0, + &source_lease) != 0 || + kzt_guest_registry_patch_decision_lease_acquire( + &source_lease, &decision_lease) != 0) { + kzt_guest_registry_source_lease_release(&source_lease); + return 0; + } + + result->generation = match.generation; + claim = kzt_guest_registry_got_plt_injection_claim(&decision_lease, &view); + if (claim == KZT_GUEST_GOT_PLT_INJECTION_GRANTED) { + result->write_attempted = 1; + applied = request->apply(request->link_map_addr, match.generation, + &view, request->opaque) == 0; + if (kzt_guest_registry_got_plt_injection_finish( + &decision_lease, applied) == 0 && applied) { + result->status = KZT_PER_OBJECT_GOT_PLT_APPLIED; + } + } else if (claim == KZT_GUEST_GOT_PLT_INJECTION_IN_PROGRESS) { + result->status = KZT_PER_OBJECT_GOT_PLT_IN_PROGRESS; + } else if (claim == KZT_GUEST_GOT_PLT_INJECTION_ALREADY_APPLIED) { + result->status = KZT_PER_OBJECT_GOT_PLT_ALREADY_APPLIED; + } + + kzt_guest_registry_patch_decision_lease_release(&decision_lease); + kzt_guest_registry_source_lease_release(&source_lease); + return 0; +} diff --git a/target/i386/latx/context/kzt_plt_resolver_adapter.c b/target/i386/latx/context/kzt_plt_resolver_adapter.c index 7e4fd5e537e..6c794962a8d 100644 --- a/target/i386/latx/context/kzt_plt_resolver_adapter.c +++ b/target/i386/latx/context/kzt_plt_resolver_adapter.c @@ -4,6 +4,7 @@ #include "kzt_plt_resolver_adapter.h" +#include #include #ifndef KZT_PLT_RESOLVER_ADAPTER_TEST @@ -16,6 +17,32 @@ static void resolver_push64(CPUX86State *cpu, uintptr_t value) *(uint64_t *)cpu->regs[R_ESP] = value; } +int kzt_plt_resolver_injection_allowed( + uintptr_t guest_resolver, uintptr_t resolver_bridge) +{ + return guest_resolver && resolver_bridge && + guest_resolver != resolver_bridge; +} + +int kzt_plt_resolver_relocation_index_valid( + uint64_t relocation_index, uintptr_t relocation_table, + size_t relocation_table_size, size_t relocation_entry_size) +{ + return relocation_table && relocation_entry_size && + relocation_table_size >= relocation_entry_size && + relocation_table_size % relocation_entry_size == 0 && + relocation_index <= INT_MAX && + relocation_index < + relocation_table_size / relocation_entry_size; +} + +int kzt_plt_resolver_symbol_index_valid( + unsigned long symbol_index, uintptr_t symbol_table, + size_t symbol_count) +{ + return symbol_table && symbol_count && symbol_index < symbol_count; +} + int kzt_plt_resolver_enter( CPUX86State *cpu, const kzt_plt_resolver_runtime_ops_t *ops, kzt_plt_resolver_enter_result_t *result) diff --git a/target/i386/latx/context/kzt_rela_immediate_candidate.c b/target/i386/latx/context/kzt_rela_immediate_candidate.c index c81f5caf9fc..a03c72dc0b6 100644 --- a/target/i386/latx/context/kzt_rela_immediate_candidate.c +++ b/target/i386/latx/context/kzt_rela_immediate_candidate.c @@ -167,8 +167,6 @@ int kzt_rela_immediate_jump_slot_try_write( } result->writer_called = result->record.writer_called; - result->skip_legacy_write = - result->record.result == KZT_PATCH_SPIKE_RESULT_APPLIED && - result->record.skip_legacy_write; + result->skip_legacy_write = result->record.skip_legacy_write; return 0; } diff --git a/target/i386/latx/context/kzt_rela_request_enricher.c b/target/i386/latx/context/kzt_rela_request_enricher.c index 7fe2db5ab46..07edbfff694 100644 --- a/target/i386/latx/context/kzt_rela_request_enricher.c +++ b/target/i386/latx/context/kzt_rela_request_enricher.c @@ -4,86 +4,6 @@ #include #include -static int kzt_rela_add_load_bias(uintptr_t address, intptr_t load_bias, - uintptr_t *runtime_address) -{ - uintptr_t magnitude; - - if (!runtime_address) { - return 0; - } - if (load_bias >= 0) { - magnitude = (uintptr_t)load_bias; - if (address > UINTPTR_MAX - magnitude) { - return 0; - } - *runtime_address = address + magnitude; - return 1; - } - - magnitude = (uintptr_t)(-(load_bias + 1)) + 1; - if (address < magnitude) { - return 0; - } - *runtime_address = address - magnitude; - return 1; -} - -static int kzt_rela_value_in_range(uintptr_t value, uintptr_t start, - uintptr_t end) -{ - return start < end && value >= start && value < end; -} - -static int kzt_rela_value_in_loaded_range(uintptr_t value, - intptr_t load_bias, - uintptr_t start, - uintptr_t end) -{ - uintptr_t runtime_start; - uintptr_t runtime_end; - - if (start >= end || - !kzt_rela_add_load_bias(start, load_bias, &runtime_start) || - !kzt_rela_add_load_bias(end, load_bias, &runtime_end)) { - return 0; - } - - return kzt_rela_value_in_range(value, runtime_start, runtime_end); -} - -int kzt_rela_slot_current_is_unresolved_stub( - uintptr_t slot_current_value, kzt_rela_stub_coordinate_t coordinate, - intptr_t load_bias, - uintptr_t plt_start, uintptr_t plt_end, - uintptr_t gotplt_start, uintptr_t gotplt_end) -{ - if (!slot_current_value || - coordinate == KZT_RELA_STUB_COORDINATE_UNKNOWN) { - return 0; - } - - if (coordinate == KZT_RELA_STUB_COORDINATE_LINK_TIME_RAW) { - return kzt_rela_value_in_range(slot_current_value, - plt_start, plt_end) || - kzt_rela_value_in_range(slot_current_value, - gotplt_start, gotplt_end); - } - if (coordinate == KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED) { - return kzt_rela_value_in_loaded_range(slot_current_value, load_bias, - plt_start, plt_end) || - kzt_rela_value_in_loaded_range(slot_current_value, load_bias, - gotplt_start, gotplt_end); - } - - return 0; -} - -static int kzt_rela_enricher_field_ok(kzt_guest_field_status_t status) -{ - return status == KZT_GUEST_FIELD_OK; -} - static int kzt_rela_enricher_string_has_value( kzt_guest_field_status_t status) { @@ -105,64 +25,33 @@ static void kzt_rela_enricher_copy_text(char *dst, size_t dst_size, snprintf(dst, dst_size, "%s", src); } -static int kzt_rela_enricher_snapshot_has_range( - const kzt_guest_object_snapshot_t *snapshot) -{ - if (!snapshot) { - return 0; - } - if (!kzt_rela_enricher_field_ok(snapshot->map_start.status) || - !kzt_rela_enricher_field_ok(snapshot->map_end.status)) { - return 0; - } - - return snapshot->map_start.value < snapshot->map_end.value; -} - -static int kzt_rela_enricher_snapshot_contains( - const kzt_guest_object_snapshot_t *snapshot, uintptr_t address) -{ - if (!kzt_rela_enricher_snapshot_has_range(snapshot)) { - return 0; - } - - return address >= snapshot->map_start.value && - address < snapshot->map_end.value; -} - -static const char *kzt_rela_enricher_snapshot_string( - const kzt_guest_string_field_t *field) -{ - if (!field || !kzt_rela_enricher_string_has_value(field->status)) { - return NULL; - } - - return field->value; -} - -static void kzt_rela_enricher_ref_from_snapshot( - const kzt_guest_object_snapshot_t *snapshot, +static void kzt_rela_enricher_ref_from_match( + const kzt_guest_registry_address_match_t *match, kzt_rela_request_enricher_text_t *text, kzt_patch_object_ref_t *ref) { - const char *soname; - const char *path; + const char *soname = NULL; + const char *path = NULL; memset(ref, 0, sizeof(*ref)); - if (!snapshot || !text) { + if (!match || !text) { return; } - soname = kzt_rela_enricher_snapshot_string(&snapshot->soname); - path = kzt_rela_enricher_snapshot_string(&snapshot->path); + if (kzt_rela_enricher_string_has_value(match->soname_status)) { + soname = match->soname; + } + if (kzt_rela_enricher_string_has_value(match->path_status)) { + path = match->path; + } kzt_rela_enricher_copy_text(text->soname, sizeof(text->soname), soname); kzt_rela_enricher_copy_text(text->path, sizeof(text->path), path); ref->known = 1; - ref->link_map_addr = snapshot->link_map_addr; - ref->map_start = snapshot->map_start.value; - ref->map_end = snapshot->map_end.value; - ref->generation = snapshot->generation; + ref->link_map_addr = match->link_map_addr; + ref->map_start = match->map_start; + ref->map_end = match->map_end; + ref->generation = match->generation; ref->soname = text->soname[0] ? text->soname : NULL; ref->path = text->path[0] ? text->path : NULL; } @@ -171,39 +60,20 @@ static int kzt_rela_enricher_find_unique_source( kzt_guest_registry_t *registry, uintptr_t address, kzt_rela_request_enricher_result_t *result) { - kzt_guest_registry_dump_t dump = { 0 }; - const kzt_guest_object_snapshot_t *match = NULL; - size_t i; - size_t count = 0; + kzt_guest_registry_address_pair_t pair; if (!registry || !address || !result) { return -1; } - if (kzt_guest_registry_dump_snapshot(registry, &dump) != 0) { + if (kzt_guest_registry_resolve_address_pair( + registry, address, address, &pair) != 0) { return -1; } - - for (i = 0; i < dump.count; ++i) { - if (dump.objects[i].state == KZT_GUEST_OBJECT_UNLOADING || - dump.objects[i].state == KZT_GUEST_OBJECT_DEAD) { - continue; - } - if (!kzt_rela_enricher_snapshot_contains(&dump.objects[i], address)) { - continue; - } - ++count; - if (!match) { - match = &dump.objects[i]; - } - } - - if (count == 1 && match) { - kzt_rela_enricher_ref_from_snapshot( - match, &result->source_text, &result->source); + if (pair.current.match_count == 1) { + kzt_rela_enricher_ref_from_match( + &pair.current, &result->source_text, &result->source); result->source_present = result->source.known; } - - kzt_guest_registry_dump_free(&dump); return result->source_present ? 0 : -1; } @@ -291,17 +161,30 @@ static void kzt_rela_enricher_apply_owner( result->owner_present = request->current_owner.known; } -static void kzt_rela_enricher_apply_wrapper( +static int kzt_rela_enricher_has_wrapper_base_evidence( + const kzt_rela_immediate_candidate_request_t *request) +{ + return request && request->source.known && + request->source.link_map_addr && request->source.generation && + request->dynamic_view_available && + request->dynamic_view_generation && + request->owner_match == KZT_PATCH_OWNER_MATCH && + request->current_owner.known && + request->current_owner.link_map_addr && + request->current_owner.generation; +} + +int kzt_rela_immediate_request_enrich_wrapper_only( kzt_rela_immediate_candidate_request_t *request, - const kzt_rela_request_enricher_input_t *input, + const kzt_rela_request_wrapper_only_input_t *input, kzt_rela_request_enricher_result_t *result) { const kzt_wrapper_probe_bridge_ops_t *bridge_ops; kzt_wrapper_probe_bridge_ops_t restricted_bridge_ops; kzt_wrapper_probe_request_t probe_request; - if (!request || !input || !result) { - return; + if (!request || !result) { + return -1; } request->wrapper_match = KZT_PATCH_WRAPPER_NO_MANIFEST; @@ -313,18 +196,16 @@ static void kzt_rela_enricher_apply_wrapper( probe_request.symbol_name = request->symbol_name; probe_request.symbol_version_evidence = request->version_evidence; probe_request.symbol_version = request->version; - bridge_ops = input->bridge_ops; - if (bridge_ops && - (!request->dynamic_view_available || - request->owner_match != KZT_PATCH_OWNER_MATCH)) { + bridge_ops = input ? input->bridge_ops : NULL; + if (bridge_ops && !kzt_rela_enricher_has_wrapper_base_evidence(request)) { restricted_bridge_ops = *bridge_ops; restricted_bridge_ops.add_bridge = NULL; bridge_ops = &restricted_bridge_ops; } if (kzt_wrapper_probe_minimal_manifest( - input->wrapper_manifest, &probe_request, bridge_ops, + input ? input->wrapper_manifest : NULL, &probe_request, bridge_ops, &result->wrapper_probe) != 0) { - return; + return -1; } kzt_wrapper_probe_apply_to_decision_request( @@ -334,6 +215,7 @@ static void kzt_rela_enricher_apply_wrapper( request->wrapper_version_evidence = result->wrapper_probe.wrapper_version_evidence; result->wrapper_present = 1; + return 0; } void kzt_rela_request_enricher_result_init( @@ -366,6 +248,12 @@ int kzt_rela_immediate_request_enrich( kzt_rela_enricher_apply_dynamic_view(request, input, result); kzt_rela_enricher_apply_owner(request, input, result); - kzt_rela_enricher_apply_wrapper(request, input, result); + /* Wrapper evidence is planner input, not a failure of base enrichment. + * Keep malformed or missing wrapper data fail-open for the caller. */ + (void)kzt_rela_immediate_request_enrich_wrapper_only( + request, &(kzt_rela_request_wrapper_only_input_t){ + .wrapper_manifest = input->wrapper_manifest, + .bridge_ops = input->bridge_ops, + }, result); return 0; } diff --git a/target/i386/latx/context/kzt_rela_runtime_bridge.c b/target/i386/latx/context/kzt_rela_runtime_bridge.c index 821f12b3b67..d337fbe38de 100644 --- a/target/i386/latx/context/kzt_rela_runtime_bridge.c +++ b/target/i386/latx/context/kzt_rela_runtime_bridge.c @@ -1,8 +1,11 @@ #include "kzt_rela_runtime_bridge.h" #include +#include #include #include +#include +#include #include "box64context.h" #include "kzt_bridge_exact.h" @@ -15,8 +18,30 @@ extern uintptr_t CheckBridged(bridge_t *bridge, void *fnc); extern uintptr_t AddCheckBridge(bridge_t *bridge, wrapper_t wrapper, void *fnc, int stack_bytes, const char *name) __attribute__((weak)); +extern int BridgeForkProtectionAvailable(void); extern void *GetNativeSymbolUnversionned( void *lib, const char *name) __attribute__((weak)); +extern int option_kzt_lazy_diagnostics; + +static uint64_t kzt_rela_runtime_timing_now(void) +{ + struct timespec value; + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &value) != 0) { + return 0; + } + return (uint64_t)value.tv_sec * 1000000000ULL + + (uint64_t)value.tv_nsec; +} + +static uint64_t kzt_rela_runtime_timing_delta(uint64_t start, uint64_t end) +{ + return start && end >= start ? end - start : 0; +} + +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST +void kzt_rela_runtime_bridge_test_full_lifetime_validation(void); +#endif static int kzt_rela_runtime_wrapper_map_entry( kh_symbolmap_t *map, const char *symbol_name, wrapper_t *wrapper) @@ -46,6 +71,7 @@ typedef struct kzt_rela_runtime_provider_state { box64context_t *context; uintptr_t resolved_target; int discover_bridge; + const kzt_guest_library_handle_t *retained_provider_handle; } kzt_rela_runtime_provider_state_t; static void *kzt_rela_runtime_lookup_native( @@ -80,6 +106,7 @@ static int kzt_rela_runtime_custom_native_name( return 0; } +/* Provider lifetime inspection completes before this bridge-only lookup. */ static uintptr_t kzt_rela_runtime_provider_exact_bridge( library_t *provider, uintptr_t resolved_target, wrapper_t wrapper, void *native_symbol) @@ -103,6 +130,16 @@ static uintptr_t kzt_rela_runtime_provider_exact_bridge( provider_target : 0; } +static uintptr_t kzt_rela_runtime_provider_cached_bridge( + library_t *provider, void *native_symbol) +{ + if (!provider || !provider->context || !provider->priv.w.bridge || + !native_symbol) { + return 0; + } + return CheckBridged(provider->priv.w.bridge, native_symbol); +} + static int kzt_rela_runtime_context_owns_library( box64context_t *context, library_t *library) { @@ -128,6 +165,31 @@ static int kzt_rela_runtime_context_owns_library( return 0; } +static int kzt_rela_runtime_retained_match_valid( + const kzt_wrapper_bridge_provider_match_t *match) +{ + const kzt_guest_library_handle_t *handle; + box64context_t *context; + library_t *provider; + void *expected_handle; + + if (!match || !(handle = match->retained_provider_handle) || + !handle->bindings || !handle->entry || + !(context = match->context_owner) || + !(provider = match->wrapper_provider) || + handle->library != provider) { + return 0; + } + expected_handle = match->custom_wrapper ? provider->priv.w.box64lib : + provider->priv.w.lib; + return provider->active && provider->type == LIB_WRAPPED && + provider->context == context && + kzt_rela_runtime_context_owns_library(context, provider) && + match->bridge_owner == provider && + match->bridge_storage == provider->priv.w.bridge && + match->native_lookup_handle == expected_handle; +} + static int kzt_rela_runtime_match_lifetime_valid( const kzt_wrapper_bridge_provider_match_t *match) { @@ -139,6 +201,12 @@ static int kzt_rela_runtime_match_lifetime_valid( void *expected_handle; void *native_symbol; + if (match && match->retained_provider_handle) { + return kzt_rela_runtime_retained_match_valid(match); + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_rela_runtime_bridge_test_full_lifetime_validation(); +#endif if (!match || !match->context_owner || !match->wrapper_provider || !match->native_lookup_handle || !match->native_owner || !match->bridge_owner || !match->bridge_storage || @@ -192,6 +260,13 @@ static int kzt_rela_runtime_provider_inspect( Dl_info symbol_info; khint_t key; uintptr_t bridge_target; + uint64_t timing_start = 0; + uint64_t timing_wrapper_map = 0; + uint64_t timing_native_lookup = 0; + uint64_t timing_bridge_cache = 0; + uint64_t timing_handle_owner = 0; + uint64_t timing_done = 0; + int timing_enabled = option_kzt_lazy_diagnostics != 0; int matches = 0; int allow_altprefix = 1; @@ -200,9 +275,16 @@ static int kzt_rela_runtime_provider_inspect( !lib->active || lib->type != LIB_WRAPPED || !lib->priv.w.lib || !lib->priv.w.bridge || lib->context != state->context || !kzt_rela_runtime_context_owns_library(state->context, lib) || + (state->retained_provider_handle && + (!state->retained_provider_handle->bindings || + !state->retained_provider_handle->entry || + state->retained_provider_handle->library != lib)) || (!state->resolved_target && !state->discover_bridge)) { return 0; } + if (timing_enabled) { + timing_start = kzt_rela_runtime_timing_now(); + } if (kzt_rela_runtime_wrapper_map_entry( lib->mysymbolmap, symbol_name, &candidate)) { @@ -276,6 +358,9 @@ static int kzt_rela_runtime_provider_inspect( } native_name = prefixed_name; } + if (timing_enabled) { + timing_wrapper_map = kzt_rela_runtime_timing_now(); + } /* symbol_version describes the guest relocation. The host provider may legitimately export the same ABI wrapper under a different GLIBC @@ -283,6 +368,9 @@ static int kzt_rela_runtime_provider_inspect( match->native_symbol = (uintptr_t)kzt_rela_runtime_lookup_native( match->custom_wrapper ? lib->priv.w.box64lib : lib->priv.w.lib, native_name, match->custom_wrapper); + if (timing_enabled) { + timing_native_lookup = kzt_rela_runtime_timing_now(); + } if (symbol_version && symbol_version[0] && (strstr(symbol_version, "NOT_REAL") || strstr(symbol_version, "UNSUPPORTED"))) { @@ -307,19 +395,36 @@ static int kzt_rela_runtime_provider_inspect( if (!match->native_symbol) { return -1; } - if (dlinfo(match->custom_wrapper ? lib->priv.w.box64lib : lib->priv.w.lib, - RTLD_DI_LINKMAP, &handle_map) != 0 || - !handle_map || - dladdr1((void *)match->native_symbol, &symbol_info, - (void **)&symbol_map, RTLD_DL_LINKMAP) == 0 || - symbol_map != handle_map) { - return -1; - } bridge_target = kzt_rela_runtime_provider_exact_bridge( lib, state->resolved_target, wrapper, (void *)match->native_symbol); if (!bridge_target && - (CheckBridged(lib->priv.w.bridge, (void *)match->native_symbol) || - !state->discover_bridge)) { + kzt_rela_runtime_provider_cached_bridge( + lib, (void *)match->native_symbol)) { + return -1; + } + if (timing_enabled) { + timing_bridge_cache = kzt_rela_runtime_timing_now(); + } + if (!state->retained_provider_handle) { + if (dlinfo(match->custom_wrapper ? lib->priv.w.box64lib : + lib->priv.w.lib, + RTLD_DI_LINKMAP, &handle_map) != 0 || !handle_map) { + return -1; + } + if (bridge_target) { + /* An exact bridge is owned by this provider and the provider's + * native handle keeps its dependency closure live. */ + symbol_map = handle_map; + } else if (dladdr1((void *)match->native_symbol, &symbol_info, + (void **)&symbol_map, RTLD_DL_LINKMAP) == 0 || + symbol_map != handle_map) { + return -1; + } + } + if (timing_enabled) { + timing_handle_owner = kzt_rela_runtime_timing_now(); + } + if (!bridge_target && !state->discover_bridge) { return -1; } @@ -341,6 +446,31 @@ static int kzt_rela_runtime_provider_inspect( match->wrapper_provider_lifetime_bound = 1; match->native_owner_lifetime_bound = 1; match->bridge_owner_lifetime_bound = 1; + match->retained_provider_handle = state->retained_provider_handle; + if (state->retained_provider_handle && + !kzt_rela_runtime_retained_match_valid(match)) { + return -1; + } + if (timing_enabled) { + timing_done = kzt_rela_runtime_timing_now(); + fprintf( + stderr, + "kzt_bridge_discovery_timing schema=1 symbol=%s " + "wrapper_map_ns=%" PRIu64 " native_lookup_ns=%" PRIu64 " " + "bridge_cache_ns=%" PRIu64 " handle_owner_ns=%" PRIu64 " " + "total_ns=%" PRIu64 " custom=%d cached=%d\n", + symbol_name, + kzt_rela_runtime_timing_delta( + timing_start, timing_wrapper_map), + kzt_rela_runtime_timing_delta( + timing_wrapper_map, timing_native_lookup), + kzt_rela_runtime_timing_delta( + timing_native_lookup, timing_bridge_cache), + kzt_rela_runtime_timing_delta( + timing_bridge_cache, timing_handle_owner), + kzt_rela_runtime_timing_delta(timing_start, timing_done), + match->custom_wrapper, bridge_target != 0); + } return 1; } @@ -380,7 +510,7 @@ static uintptr_t kzt_rela_runtime_provider_add( } provider = match->bridge_owner; - if (!provider->priv.w.bridge || !AddCheckBridge) { + if (!provider->priv.w.bridge || !provider->context || !AddCheckBridge) { return 0; } target = AddCheckBridge(provider->priv.w.bridge, match->abi_wrapper, @@ -390,7 +520,7 @@ static uintptr_t kzt_rela_runtime_provider_add( kzt_rela_runtime_provider_exact_bridge( provider, target, match->abi_wrapper, (void *)match->native_symbol) != target) { - return 0; + target = 0; } return target; } @@ -400,12 +530,14 @@ static int kzt_rela_runtime_wrapper_provider_prepare_mode( uintptr_t resolved_target, const char *symbol_name, kzt_symbol_version_evidence_t version_evidence, const char *symbol_version, int discover_bridge, + const kzt_guest_library_handle_t *retained_provider_handle, kzt_wrapper_bridge_provider_t *provider) { kzt_rela_runtime_provider_state_t state = { .context = context, .resolved_target = resolved_target, .discover_bridge = discover_bridge, + .retained_provider_handle = retained_provider_handle, }; kzt_wrapper_bridge_provider_runtime_ops_t runtime_ops = { .inspect_library = kzt_rela_runtime_provider_inspect, @@ -419,6 +551,10 @@ static int kzt_rela_runtime_wrapper_provider_prepare_mode( if (!provider) { return -1; } + if (!BridgeForkProtectionAvailable()) { + memset(provider, 0, sizeof(*provider)); + return 0; + } status = kzt_wrapper_bridge_provider_prepare_with_version_evidence( provider, libraries, 1, symbol_name, version_evidence, symbol_version, &runtime_ops); @@ -436,7 +572,7 @@ int kzt_rela_runtime_wrapper_provider_prepare( context, resolved_provider, resolved_target, symbol_name, symbol_version && symbol_version[0] ? KZT_SYMBOL_VERSION_VERSIONED : KZT_SYMBOL_VERSION_UNKNOWN, - symbol_version, 0, provider); + symbol_version, 0, NULL, provider); } int kzt_rela_runtime_wrapper_provider_prepare_with_version_evidence( @@ -448,7 +584,7 @@ int kzt_rela_runtime_wrapper_provider_prepare_with_version_evidence( { return kzt_rela_runtime_wrapper_provider_prepare_mode( context, resolved_provider, resolved_target, symbol_name, - version_evidence, symbol_version, 0, provider); + version_evidence, symbol_version, 0, NULL, provider); } int kzt_rela_runtime_wrapper_provider_discover( @@ -460,7 +596,7 @@ int kzt_rela_runtime_wrapper_provider_discover( context, resolved_provider, 0, symbol_name, symbol_version && symbol_version[0] ? KZT_SYMBOL_VERSION_VERSIONED : KZT_SYMBOL_VERSION_UNKNOWN, - symbol_version, 1, provider); + symbol_version, 1, NULL, provider); } int kzt_rela_runtime_wrapper_provider_discover_with_version_evidence( @@ -472,5 +608,39 @@ int kzt_rela_runtime_wrapper_provider_discover_with_version_evidence( { return kzt_rela_runtime_wrapper_provider_prepare_mode( context, resolved_provider, 0, symbol_name, version_evidence, - symbol_version, 1, provider); + symbol_version, 1, NULL, provider); +} + +int kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( + box64context_t *context, + const kzt_guest_library_handle_t *retained_provider_handle, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version, + kzt_wrapper_bridge_provider_t *provider) +{ + if (!retained_provider_handle || !retained_provider_handle->library) { + return 0; + } + return kzt_rela_runtime_wrapper_provider_prepare_mode( + context, retained_provider_handle->library, 0, symbol_name, + version_evidence, symbol_version, 1, retained_provider_handle, + provider); +} + +int kzt_rela_runtime_wrapper_provider_bind_retained_handle( + kzt_wrapper_bridge_provider_t *provider, + const kzt_guest_library_handle_t *handle) +{ + if (!provider || !handle || !handle->bindings || !handle->entry || + !handle->library || !provider->manifest.available || + provider->match.wrapper_provider != handle->library) { + return -1; + } + provider->match.retained_provider_handle = handle; + if (!kzt_rela_runtime_retained_match_valid(&provider->match)) { + provider->match.retained_provider_handle = NULL; + return -1; + } + return 0; } diff --git a/target/i386/latx/context/kzt_rela_stub_detector.c b/target/i386/latx/context/kzt_rela_stub_detector.c new file mode 100644 index 00000000000..bba27afae54 --- /dev/null +++ b/target/i386/latx/context/kzt_rela_stub_detector.c @@ -0,0 +1,110 @@ +#include "qemu/osdep.h" + +#include "kzt_rela_stub_detector.h" + +#include + +static int kzt_rela_add_load_bias(uintptr_t address, intptr_t load_bias, + uintptr_t *runtime_address) +{ + uintptr_t magnitude; + + if (!runtime_address) { + return 0; + } + if (load_bias >= 0) { + magnitude = (uintptr_t)load_bias; + if (address > UINTPTR_MAX - magnitude) { + return 0; + } + *runtime_address = address + magnitude; + return 1; + } + + magnitude = (uintptr_t)(-(load_bias + 1)) + 1; + if (address < magnitude) { + return 0; + } + *runtime_address = address - magnitude; + return 1; +} + +static int kzt_rela_value_in_range(uintptr_t value, uintptr_t start, + uintptr_t end) +{ + return start < end && value >= start && value < end; +} + +static int kzt_rela_value_in_loaded_range(uintptr_t value, + intptr_t load_bias, + uintptr_t start, + uintptr_t end) +{ + uintptr_t runtime_start; + uintptr_t runtime_end; + + if (start >= end || + !kzt_rela_add_load_bias(start, load_bias, &runtime_start) || + !kzt_rela_add_load_bias(end, load_bias, &runtime_end)) { + return 0; + } + + return kzt_rela_value_in_range(value, runtime_start, runtime_end); +} + +int kzt_rela_slot_current_is_unresolved_stub( + uintptr_t slot_current_value, kzt_rela_stub_coordinate_t coordinate, + intptr_t load_bias, + uintptr_t plt_start, uintptr_t plt_end, + uintptr_t gotplt_start, uintptr_t gotplt_end) +{ + if (!slot_current_value || + coordinate == KZT_RELA_STUB_COORDINATE_UNKNOWN) { + return 0; + } + + if (coordinate == KZT_RELA_STUB_COORDINATE_LINK_TIME_RAW) { + return kzt_rela_value_in_range(slot_current_value, + plt_start, plt_end) || + kzt_rela_value_in_range(slot_current_value, + gotplt_start, gotplt_end); + } + if (coordinate == KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED) { + return kzt_rela_value_in_loaded_range(slot_current_value, load_bias, + plt_start, plt_end) || + kzt_rela_value_in_loaded_range(slot_current_value, load_bias, + gotplt_start, gotplt_end); + } + + return 0; +} + +kzt_rela_jump_slot_defer_plan_t kzt_rela_jump_slot_defer_plan( + const kzt_rela_jump_slot_defer_input_t *input) +{ + kzt_rela_jump_slot_defer_plan_t plan = { 0, 0, 0 }; + int raw_stub; + int runtime_stub; + + if (!input) { + return plan; + } + + raw_stub = kzt_rela_slot_current_is_unresolved_stub( + input->slot_current_value, + KZT_RELA_STUB_COORDINATE_LINK_TIME_RAW, + input->load_bias, input->plt_start, input->plt_end, + input->gotplt_start, input->gotplt_end); + runtime_stub = kzt_rela_slot_current_is_unresolved_stub( + input->slot_current_value, + KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, + input->load_bias, input->plt_start, input->plt_end, + input->gotplt_start, input->gotplt_end); + plan.slot_is_unresolved_stub = raw_stub || runtime_stub; + plan.should_defer = plan.slot_is_unresolved_stub && + !input->bind_is_local && !input->bindnow && + input->need_resolver_present; + plan.should_add_delta = plan.should_defer && raw_stub && !runtime_stub; + + return plan; +} diff --git a/target/i386/latx/context/kzt_wrapper_bridge_provider.c b/target/i386/latx/context/kzt_wrapper_bridge_provider.c index e8b5ce84889..ed24ea5d046 100644 --- a/target/i386/latx/context/kzt_wrapper_bridge_provider.c +++ b/target/i386/latx/context/kzt_wrapper_bridge_provider.c @@ -121,7 +121,9 @@ int kzt_wrapper_bridge_provider_prepare_with_version_evidence( } if (!candidate.abi_wrapper || !candidate.native_symbol || !candidate.context_owner || !candidate.wrapper_provider || - !candidate.native_lookup_handle || !candidate.native_owner || + !candidate.native_lookup_handle || + (!candidate.native_owner && + !candidate.retained_provider_handle) || !candidate.bridge_owner || !candidate.bridge_storage || candidate.stack_bytes < 0 || !candidate.native_name[0] || (!!candidate.resolved_bridge_target != diff --git a/target/i386/latx/context/wrappedlibegl.c b/target/i386/latx/context/wrappedlibegl.c index 6b16a579519..30d5a814248 100644 --- a/target/i386/latx/context/wrappedlibegl.c +++ b/target/i386/latx/context/wrappedlibegl.c @@ -81,7 +81,7 @@ EXPORT void* my_eglGetProcAddress(void* name) } const char* constname = kh_key(my_context->glwrappers, k); AddOffsetSymbol(my_context->maplib, symbol, rname); - ret = AddBridge(my_context->system, kh_value(my_context->glwrappers, k), symbol, 0, constname); + ret = AddCheckBridge(my_context->system, kh_value(my_context->glwrappers, k), symbol, 0, constname); if(relocation_logglwrappers, k); AddOffsetSymbol(my_context->maplib, symbol, rname); - ret = AddBridge(my_context->system, kh_value(my_context->glwrappers, k), symbol, 0, constname); + ret = AddCheckBridge(my_context->system, kh_value(my_context->glwrappers, k), symbol, 0, constname); printf_dlsym(LOG_DEBUG, "%p\n", (void*)ret); return (void*)ret; } @@ -630,7 +630,7 @@ void* getGLProcAddress(glprocaddress_t procaddr, const char* rname) } const char* constname = kh_key(wrappers->glwrappers, k); AddOffsetSymbol(my_context->maplib, symbol, rname); - ret = AddBridge(my_context->system, kh_value(wrappers->glwrappers, k), symbol, 0, constname); + ret = AddCheckBridge(my_context->system, kh_value(wrappers->glwrappers, k), symbol, 0, constname); printf_dlsym(LOG_DEBUG, "%p\n", (void*)ret); return (void*)ret; } diff --git a/target/i386/latx/context/wrappedlibglx.c b/target/i386/latx/context/wrappedlibglx.c index 16e58cac21b..250a5f0613d 100644 --- a/target/i386/latx/context/wrappedlibglx.c +++ b/target/i386/latx/context/wrappedlibglx.c @@ -214,7 +214,7 @@ void* getGLProcAddress(glprocaddress_t procaddr, const char* rname) } const char* constname = kh_key(wrappers->glwrappers, k); AddOffsetSymbol(my_context->maplib, symbol, rname); - ret = AddBridge(my_context->system, kh_value(wrappers->glwrappers, k), symbol, 0, constname); + ret = AddCheckBridge(my_context->system, kh_value(wrappers->glwrappers, k), symbol, 0, constname); printf_dlsym(LOG_DEBUG, "%p\n", (void*)ret); return (void*)ret; } diff --git a/target/i386/latx/context/wrappedlibx11.c b/target/i386/latx/context/wrappedlibx11.c index 4d11b333524..f75e7e7a820 100644 --- a/target/i386/latx/context/wrappedlibx11.c +++ b/target/i386/latx/context/wrappedlibx11.c @@ -304,7 +304,7 @@ static void* reverse_wire_to_eventFct(library_t* lib, void* fct) #define GO(A) if(my_wire_to_event_##A == fct) return (void*)my_wire_to_event_fct_##A; SUPER() #undef GO - return (void*)AddBridge(lib->priv.w.bridge, iFppp, fct, 0, NULL); + return (void*)AddCheckBridge(lib->priv.w.bridge, iFppp, fct, 0, NULL); } // event_to_wire @@ -337,7 +337,7 @@ static void* reverse_event_to_wireFct(library_t* lib, void* fct) #define GO(A) if(my_event_to_wire_##A == fct) return (void*)my_event_to_wire_fct_##A; SUPER() #undef GO - return (void*)AddBridge(lib->priv.w.bridge, iFppp, fct, 0, NULL); + return (void*)AddCheckBridge(lib->priv.w.bridge, iFppp, fct, 0, NULL); } // error_handler @@ -370,7 +370,7 @@ static void* reverse_error_handlerFct(library_t* lib, void* fct) #define GO(A) if(my_error_handler_##A == fct) return (void*)my_error_handler_fct_##A; SUPER() #undef GO - return (void*)AddBridge(lib->priv.w.bridge, iFpp, fct, 0, NULL); + return (void*)AddCheckBridge(lib->priv.w.bridge, iFpp, fct, 0, NULL); } // ioerror_handler @@ -403,7 +403,7 @@ static void* reverse_ioerror_handlerFct(library_t* lib, void* fct) #define GO(A) if(my_ioerror_handler_##A == fct) return (void*)my_ioerror_handler_fct_##A; SUPER() #undef GO - return (void*)AddBridge(lib->priv.w.bridge, iFp, fct, 0, NULL); + return (void*)AddCheckBridge(lib->priv.w.bridge, iFp, fct, 0, NULL); } // exterror_handler @@ -436,7 +436,7 @@ static void* reverse_exterror_handlerFct(library_t* lib, void* fct) #define GO(A) if(my_exterror_handler_##A == fct) return (void*)my_exterror_handler_fct_##A; SUPER() #undef GO - return (void*)AddBridge(lib->priv.w.bridge, iFpppp, fct, 0, NULL); + return (void*)AddCheckBridge(lib->priv.w.bridge, iFpppp, fct, 0, NULL); } // close_display @@ -469,7 +469,7 @@ static void* reverse_close_displayFct(library_t* lib, void* fct) #define GO(A) if(my_close_display_##A == fct) return (void*)my_close_display_fct_##A; SUPER() #undef GO - return (void*)AddBridge(lib->priv.w.bridge, iFpp, fct, 0, NULL); + return (void*)AddCheckBridge(lib->priv.w.bridge, iFpp, fct, 0, NULL); } // register_im @@ -502,7 +502,7 @@ static void* reverse_register_imFct(library_t* lib, void* fct) #define GO(A) if(my_register_im_##A == fct) return (void*)my_register_im_fct_##A; SUPER() #undef GO - return (void*)AddBridge(lib->priv.w.bridge, iFppp, fct, 0, NULL); + return (void*)AddCheckBridge(lib->priv.w.bridge, iFppp, fct, 0, NULL); } // XConnectionWatchProc @@ -736,7 +736,7 @@ static void* reverse_XSynchronizeProcFct(library_t* lib, void* fct) #define GO(A) if(my_XSynchronizeProc_##A == fct) return (void*)my_XSynchronizeProc_fct_##A; SUPER() #undef GO - return (void*)AddBridge(lib->priv.w.bridge, iFppp, fct, 0, NULL); + return (void*)AddCheckBridge(lib->priv.w.bridge, iFppp, fct, 0, NULL); } #if 0 // XLockDisplay diff --git a/target/i386/latx/context/wrappedlibxext.c b/target/i386/latx/context/wrappedlibxext.c index a5e349f5aec..7e47a52b1f7 100755 --- a/target/i386/latx/context/wrappedlibxext.c +++ b/target/i386/latx/context/wrappedlibxext.c @@ -85,7 +85,7 @@ static void* reverse_exterrorhandleFct(void* fct) #define GO(A) if(my_exterrorhandle_##A == fct) return (void*)my_exterrorhandle_fct_##A; SUPER() #undef GO - return (void*)AddBridge(my_lib->priv.w.bridge, iFppp, fct, 0, NULL); + return (void*)AddCheckBridge(my_lib->priv.w.bridge, iFppp, fct, 0, NULL); } #undef SUPER diff --git a/target/i386/latx/include/bridge.h b/target/i386/latx/include/bridge.h index 67166a9f3ba..db7aa25a0c5 100755 --- a/target/i386/latx/include/bridge.h +++ b/target/i386/latx/include/bridge.h @@ -9,8 +9,13 @@ typedef void (*wrapper_t)( uintptr_t fnc); typedef struct brick_s brick_t; brick_t* NewBrick(void); bridge_t *NewBridge(void); +/* Call only after all concurrent bridge users have stopped. */ void FreeBridge(bridge_t** bridge); +/* KZT callers must preserve the guest path when this process-wide proof is + * unavailable. Legacy bridge users remain available for compatibility. */ +int BridgeForkProtectionAvailable(void); + uintptr_t AddBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N, const char* name); uintptr_t CheckBridged(bridge_t* bridge, void* fnc); uintptr_t AddCheckBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N, const char* name); @@ -23,6 +28,13 @@ void* getAlternate(void* addr); void addAlternate(void* addr, void* alt); void cleanAlternate(void); +#ifdef BRIDGE_TEST +typedef void (*bridge_test_hook_fn)(void *opaque); +void bridge_test_set_after_check_hook(bridge_test_hook_fn hook, void *opaque); +void bridge_test_set_before_free_hook(bridge_test_hook_fn hook, void *opaque); +int bridge_test_lock_is_held(bridge_t *bridge); +#endif + void init_bridge_helper(void); void fini_bridge_helper(void); diff --git a/target/i386/latx/include/kzt_guest_dynsym_lookup.h b/target/i386/latx/include/kzt_guest_dynsym_lookup.h new file mode 100644 index 00000000000..4787a1ac251 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_dynsym_lookup.h @@ -0,0 +1,33 @@ +#ifndef KZT_GUEST_DYNSYM_LOOKUP_H +#define KZT_GUEST_DYNSYM_LOOKUP_H + +#include + +#include "kzt_guest_dynamic_view.h" +#include "kzt_guest_link_map_reader.h" +#include "kzt_patch_planner.h" + +typedef enum kzt_guest_dynsym_lookup_status { + KZT_GUEST_DYNSYM_LOOKUP_FOUND = 0, + KZT_GUEST_DYNSYM_LOOKUP_NOT_FOUND, + KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN, +} kzt_guest_dynsym_lookup_status_t; + +typedef struct kzt_guest_dynsym_lookup_result { + kzt_guest_dynsym_lookup_status_t status; + unsigned char binding; + unsigned char type; + unsigned char visibility; + uint32_t symbol_index; + uintptr_t runtime_address; +} kzt_guest_dynsym_lookup_result_t; + +kzt_guest_dynsym_lookup_status_t kzt_guest_dynsym_lookup( + const kzt_guest_dynamic_view_t *view, + const kzt_guest_link_map_reader_ops_t *reader_ops, + const char *symbol, + kzt_symbol_version_evidence_t version_evidence, + const char *version, + kzt_guest_dynsym_lookup_result_t *result); + +#endif diff --git a/target/i386/latx/include/kzt_guest_symbol_scope.h b/target/i386/latx/include/kzt_guest_symbol_scope.h new file mode 100644 index 00000000000..ff1d7a39a39 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_symbol_scope.h @@ -0,0 +1,95 @@ +#ifndef KZT_GUEST_SYMBOL_SCOPE_H +#define KZT_GUEST_SYMBOL_SCOPE_H + +#include +#include + +#include "kzt_guest_link_map_reader.h" +#include "kzt_guest_scope_layout.h" +#include "kzt_patch_planner.h" + +#define KZT_GUEST_SYMBOL_SCOPE_LIST_LIMIT 16 +#define KZT_GUEST_SYMBOL_SCOPE_MAP_LIMIT 256 + +typedef enum kzt_guest_symbol_scope_status { + KZT_GUEST_SYMBOL_SCOPE_SAFE = 0, + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED, +} kzt_guest_symbol_scope_status_t; + +typedef enum kzt_guest_symbol_scope_reason { + KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER = 0, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE, + KZT_GUEST_SYMBOL_SCOPE_REASON_UNSUPPORTED_PROVIDER_BINDING, + KZT_GUEST_SYMBOL_SCOPE_REASON_UNSUPPORTED_REFERENCE, + KZT_GUEST_SYMBOL_SCOPE_REASON_PROVIDER_MISMATCH, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_STALE, + KZT_GUEST_SYMBOL_SCOPE_REASON_LAYOUT_UNSUPPORTED, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_DUPLICATE, + KZT_GUEST_SYMBOL_SCOPE_REASON_CROSS_NAMESPACE, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_SEMANTICS_UNSUPPORTED, + KZT_GUEST_SYMBOL_SCOPE_REASON_AUDIT_UNSUPPORTED, +} kzt_guest_symbol_scope_reason_t; + +typedef struct kzt_guest_symbol_scope_source { + uintptr_t link_map_addr; + unsigned long generation; + uintptr_t namespace_id; + uintptr_t namespace_head; + kzt_guest_scope_layout_t layout; +} kzt_guest_symbol_scope_source_t; + +typedef struct kzt_guest_symbol_scope_request { + kzt_guest_symbol_scope_source_t source; + const char *symbol; + kzt_symbol_version_evidence_t version_evidence; + const char *version; + unsigned char reference_binding; + unsigned char reference_type; + unsigned char reference_visibility; +} kzt_guest_symbol_scope_request_t; + +typedef struct kzt_guest_symbol_scope_identity { + kzt_guest_symbol_scope_source_t source; + uintptr_t scope_array_addr; + size_t scope_list_count; + size_t scope_map_count; + uint64_t value; +} kzt_guest_symbol_scope_identity_t; + +typedef struct kzt_guest_symbol_scope_result { + kzt_guest_symbol_scope_status_t status; + kzt_guest_symbol_scope_reason_t reason; + size_t candidate_count; + int scope_complete; + int lookup_order_known; + uintptr_t selected_provider_link_map; + uintptr_t selected_provider_address; + unsigned char selected_provider_binding; + unsigned char selected_provider_type; + unsigned char selected_provider_visibility; + uint64_t query_fingerprint; + kzt_guest_symbol_scope_identity_t scope_identity; +} kzt_guest_symbol_scope_result_t; + +kzt_guest_symbol_scope_status_t kzt_guest_symbol_scope_discover( + const kzt_guest_symbol_scope_request_t *request, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_symbol_scope_result_t *result); + +kzt_guest_symbol_scope_status_t kzt_guest_symbol_scope_check( + const kzt_guest_symbol_scope_request_t *request, + uintptr_t selected_provider_link_map, + uintptr_t selected_provider_address, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_symbol_scope_result_t *result); + +kzt_guest_symbol_scope_status_t kzt_guest_symbol_scope_revalidate( + const kzt_guest_symbol_scope_result_t *proof, + const kzt_guest_symbol_scope_request_t *request, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_symbol_scope_result_t *result); + +const char *kzt_guest_symbol_scope_reason_name( + kzt_guest_symbol_scope_reason_t reason); + +#endif diff --git a/target/i386/latx/include/kzt_jump_slot_route.h b/target/i386/latx/include/kzt_jump_slot_route.h index bc5d244fc80..c9b29ca2465 100644 --- a/target/i386/latx/include/kzt_jump_slot_route.h +++ b/target/i386/latx/include/kzt_jump_slot_route.h @@ -12,17 +12,26 @@ typedef enum kzt_jump_slot_route_writer_status { KZT_JUMP_SLOT_ROUTE_WRITER_APPLIED, KZT_JUMP_SLOT_ROUTE_WRITER_ERROR, KZT_JUMP_SLOT_ROUTE_WRITER_PRESERVE, + KZT_JUMP_SLOT_ROUTE_WRITER_ROLLED_BACK, + KZT_JUMP_SLOT_ROUTE_WRITER_UNRECOVERABLE, } kzt_jump_slot_route_writer_status_t; typedef enum kzt_jump_slot_route_status { KZT_JUMP_SLOT_ROUTE_BYPASS = 0, KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED, - KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED, KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED, KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH, KZT_JUMP_SLOT_ROUTE_WRITE_ERROR, + KZT_JUMP_SLOT_ROUTE_WRITE_ROLLED_BACK, + KZT_JUMP_SLOT_ROUTE_UNRECOVERABLE, } kzt_jump_slot_route_status_t; +typedef enum kzt_jump_slot_route_slot_action { + KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE = 0, + KZT_JUMP_SLOT_ROUTE_SLOT_ROUTE_APPLIED, + KZT_JUMP_SLOT_ROUTE_SLOT_PRESERVE, +} kzt_jump_slot_route_slot_action_t; + typedef int (*kzt_jump_slot_route_load_fn)(uintptr_t slot_addr, uintptr_t *value, void *opaque); @@ -44,6 +53,8 @@ typedef struct kzt_jump_slot_route_ops { int (*validate_source_identity)( const kzt_rela_immediate_candidate_request_t *request, void *opaque); + int (*preserve_guest_after_bridge_failure)(uintptr_t *value, + void *opaque); kzt_jump_slot_route_writer_status_t (*try_native_writer)( const kzt_rela_immediate_candidate_request_t *request, const kzt_patch_spike_slot_ops_t *slot_ops, void *opaque); @@ -59,7 +70,6 @@ typedef struct kzt_jump_slot_route_input { int enabled; int preserve_observed_on_failure; int expected_guest_target_present; - int resolved_target_matches_legacy; library_t *resolved_provider; kzt_rela_immediate_candidate_request_t request; } kzt_jump_slot_route_input_t; @@ -78,6 +88,21 @@ typedef struct kzt_jump_slot_route_result { int legacy_fallback_attempted; } kzt_jump_slot_route_result_t; +typedef struct kzt_jump_slot_route_caller_decision { + kzt_jump_slot_route_slot_action_t slot_action; + uintptr_t call_target; + int slot_value_usable; +} kzt_jump_slot_route_caller_decision_t; + +/* This is pure caller policy. slot_value_usable confirms that a route-owned + * slot has a nonzero final value which is not an unresolved stub. */ +kzt_jump_slot_route_caller_decision_t kzt_jump_slot_route_caller_decide( + int route_call_succeeded, + const kzt_jump_slot_route_result_t *result, + uintptr_t legacy_target, + int final_value_usable); + +/* A nonzero return is before this function attempts or commits a slot write. */ int kzt_jump_slot_route_apply(const kzt_jump_slot_route_input_t *input, const kzt_jump_slot_route_ops_t *ops, kzt_jump_slot_route_result_t *result); diff --git a/target/i386/latx/include/kzt_lazy_binding.h b/target/i386/latx/include/kzt_lazy_binding.h index 458659d0c0e..3150ecab224 100644 --- a/target/i386/latx/include/kzt_lazy_binding.h +++ b/target/i386/latx/include/kzt_lazy_binding.h @@ -16,6 +16,8 @@ typedef enum kzt_lazy_binding_status { KZT_LAZY_BINDING_GUEST_PRESERVED, KZT_LAZY_BINDING_NATIVE_APPLIED, KZT_LAZY_BINDING_CAS_MISMATCH, + KZT_LAZY_BINDING_WRITE_ROLLED_BACK, + KZT_LAZY_BINDING_UNRECOVERABLE, KZT_LAZY_BINDING_ERROR, } kzt_lazy_binding_status_t; @@ -35,6 +37,8 @@ typedef enum kzt_lazy_binding_reason { KZT_LAZY_BINDING_REASON_CAS_MISMATCH, KZT_LAZY_BINDING_REASON_SLOT_READ_ERROR, KZT_LAZY_BINDING_REASON_CAS_ERROR, + KZT_LAZY_BINDING_REASON_WRITE_ROLLED_BACK, + KZT_LAZY_BINDING_REASON_UNRECOVERABLE, } kzt_lazy_binding_reason_t; #define KZT_LAZY_BINDING_REASON_PENDING_BUSY \ @@ -80,6 +84,8 @@ typedef enum kzt_lazy_binding_route_status { KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED = 0, KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED, KZT_LAZY_BINDING_ROUTE_CAS_MISMATCH, + KZT_LAZY_BINDING_ROUTE_WRITE_ROLLED_BACK, + KZT_LAZY_BINDING_ROUTE_UNRECOVERABLE, KZT_LAZY_BINDING_ROUTE_ERROR, } kzt_lazy_binding_route_status_t; diff --git a/target/i386/latx/include/kzt_patch_planner.h b/target/i386/latx/include/kzt_patch_planner.h index 176d5911206..b1656ddad61 100644 --- a/target/i386/latx/include/kzt_patch_planner.h +++ b/target/i386/latx/include/kzt_patch_planner.h @@ -158,6 +158,12 @@ typedef struct kzt_patch_decision { int kzt_patch_planner_decide(const kzt_patch_candidate_t *candidate, kzt_patch_decision_t *decision); +static inline int kzt_patch_symbol_must_stay_guest( + const char *symbol_name) +{ + return symbol_name && strcmp(symbol_name, "dlclose") == 0; +} + const char *kzt_patch_decision_kind_name(kzt_patch_decision_kind_t kind); const char *kzt_patch_reason_name(kzt_patch_reason_t reason); const char *kzt_patch_table_kind_name(kzt_patch_table_kind_t table_kind); diff --git a/target/i386/latx/include/kzt_patch_spike_guard.h b/target/i386/latx/include/kzt_patch_spike_guard.h index c81b43e5a7a..c527c9d8f45 100644 --- a/target/i386/latx/include/kzt_patch_spike_guard.h +++ b/target/i386/latx/include/kzt_patch_spike_guard.h @@ -9,6 +9,8 @@ typedef enum kzt_patch_spike_action { KZT_PATCH_SPIKE_ACTION_KEEP_LEGACY = 0, KZT_PATCH_SPIKE_ACTION_USE_NATIVE_BRIDGE, KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST, + KZT_PATCH_SPIKE_ACTION_ROLLBACK_COMPLETE, + KZT_PATCH_SPIKE_ACTION_TRANSACTION_UNRECOVERABLE, } kzt_patch_spike_action_t; typedef enum kzt_patch_spike_result { @@ -19,6 +21,8 @@ typedef enum kzt_patch_spike_result { KZT_PATCH_SPIKE_RESULT_FAIL_OPEN, KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED, KZT_PATCH_SPIKE_RESULT_CIRCUIT_OPEN, + KZT_PATCH_SPIKE_RESULT_ROLLED_BACK, + KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE, } kzt_patch_spike_result_t; typedef enum kzt_patch_spike_failure { @@ -36,6 +40,7 @@ typedef enum kzt_patch_spike_failure { KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED, KZT_PATCH_SPIKE_FAILURE_GENERATION_MISMATCH, KZT_PATCH_SPIKE_FAILURE_CIRCUIT_BREAKER_OPEN, + KZT_PATCH_SPIKE_FAILURE_TRANSACTION_UNRECOVERABLE, } kzt_patch_spike_failure_t; typedef enum kzt_patch_spike_writer_status { @@ -99,6 +104,7 @@ int kzt_patch_spike_guard_should_plan( const kzt_patch_spike_guard_t *guard); int kzt_patch_spike_guard_circuit_open( const kzt_patch_spike_guard_t *guard); +void kzt_patch_spike_guard_trip(kzt_patch_spike_guard_t *guard); unsigned long kzt_patch_spike_guard_budget_remaining( const kzt_patch_spike_guard_t *guard); diff --git a/target/i386/latx/include/kzt_patch_spike_writer.h b/target/i386/latx/include/kzt_patch_spike_writer.h index ad5b5cc016e..178b74d9dcc 100644 --- a/target/i386/latx/include/kzt_patch_spike_writer.h +++ b/target/i386/latx/include/kzt_patch_spike_writer.h @@ -20,7 +20,9 @@ typedef struct kzt_patch_spike_permission_lease { int checked; int was_writable; int write_enabled; + int mmap_lock_held; int restore_attempted; + unsigned int restore_attempts; int restored; } kzt_patch_spike_permission_lease_t; @@ -96,6 +98,7 @@ typedef struct kzt_patch_spike_record { int permission_was_writable; int permission_write_enabled; int permission_restore_attempted; + unsigned int permission_restore_attempts; int permission_restored; } kzt_patch_spike_record_t; diff --git a/target/i386/latx/include/kzt_per_object_got_plt.h b/target/i386/latx/include/kzt_per_object_got_plt.h new file mode 100644 index 00000000000..e548df019f1 --- /dev/null +++ b/target/i386/latx/include/kzt_per_object_got_plt.h @@ -0,0 +1,40 @@ +#ifndef KZT_PER_OBJECT_GOT_PLT_H +#define KZT_PER_OBJECT_GOT_PLT_H + +#include + +#include "kzt_guest_registry.h" + +typedef int (*kzt_per_object_got_plt_write_fn)( + uintptr_t link_map_addr, + unsigned long generation, + const kzt_guest_dynamic_view_t *view, + void *opaque); + +typedef enum kzt_per_object_got_plt_status { + KZT_PER_OBJECT_GOT_PLT_FAIL_OPEN = 0, + KZT_PER_OBJECT_GOT_PLT_APPLIED, + KZT_PER_OBJECT_GOT_PLT_IN_PROGRESS, + KZT_PER_OBJECT_GOT_PLT_ALREADY_APPLIED, +} kzt_per_object_got_plt_status_t; + +typedef struct kzt_per_object_got_plt_request { + kzt_guest_registry_t *registry; + uintptr_t link_map_addr; + kzt_per_object_got_plt_write_fn apply; + void *opaque; +} kzt_per_object_got_plt_request_t; + +typedef struct kzt_per_object_got_plt_result { + kzt_per_object_got_plt_status_t status; + unsigned long generation; + int write_attempted; +} kzt_per_object_got_plt_result_t; + +/* Runs one exact per-object GOT/PLT transaction. All unsupported identity + * or Dynamic View evidence is fail-open and does not call apply(). */ +int kzt_per_object_got_plt_apply( + const kzt_per_object_got_plt_request_t *request, + kzt_per_object_got_plt_result_t *result); + +#endif diff --git a/target/i386/latx/include/kzt_plt_resolver_adapter.h b/target/i386/latx/include/kzt_plt_resolver_adapter.h index 35d96c0a219..bfdf9baf077 100644 --- a/target/i386/latx/include/kzt_plt_resolver_adapter.h +++ b/target/i386/latx/include/kzt_plt_resolver_adapter.h @@ -1,6 +1,7 @@ #ifndef KZT_PLT_RESOLVER_ADAPTER_H #define KZT_PLT_RESOLVER_ADAPTER_H +#include #include #include "kzt_lazy_binding.h" @@ -60,6 +61,17 @@ typedef struct kzt_plt_resolver_enter_result { int pending_armed; } kzt_plt_resolver_enter_result_t; +int kzt_plt_resolver_injection_allowed( + uintptr_t guest_resolver, uintptr_t resolver_bridge); + +int kzt_plt_resolver_relocation_index_valid( + uint64_t relocation_index, uintptr_t relocation_table, + size_t relocation_table_size, size_t relocation_entry_size); + +int kzt_plt_resolver_symbol_index_valid( + unsigned long symbol_index, uintptr_t symbol_table, + size_t symbol_count); + int kzt_plt_resolver_enter( CPUX86State *cpu, const kzt_plt_resolver_runtime_ops_t *ops, kzt_plt_resolver_enter_result_t *result); diff --git a/target/i386/latx/include/kzt_rela_request_enricher.h b/target/i386/latx/include/kzt_rela_request_enricher.h index c553960bbe3..fbb7ba6b9dc 100644 --- a/target/i386/latx/include/kzt_rela_request_enricher.h +++ b/target/i386/latx/include/kzt_rela_request_enricher.h @@ -33,9 +33,21 @@ typedef struct kzt_rela_request_enricher_result { kzt_wrapper_probe_result_t wrapper_probe; } kzt_rela_request_enricher_result_t; +typedef struct kzt_rela_request_wrapper_only_input { + const kzt_wrapper_probe_manifest_t *wrapper_manifest; + const kzt_wrapper_probe_bridge_ops_t *bridge_ops; +} kzt_rela_request_wrapper_only_input_t; + void kzt_rela_request_enricher_result_init( kzt_rela_request_enricher_result_t *result); +/* Applies only wrapper/bridge evidence to an already validated request. It + * never reads registry state; callers initialize result before use. */ +int kzt_rela_immediate_request_enrich_wrapper_only( + kzt_rela_immediate_candidate_request_t *request, + const kzt_rela_request_wrapper_only_input_t *input, + kzt_rela_request_enricher_result_t *result); + int kzt_rela_immediate_request_enrich( kzt_rela_immediate_candidate_request_t *request, const kzt_rela_request_enricher_input_t *input, diff --git a/target/i386/latx/include/kzt_rela_runtime_bridge.h b/target/i386/latx/include/kzt_rela_runtime_bridge.h index 8925313a156..81cbcddf44b 100644 --- a/target/i386/latx/include/kzt_rela_runtime_bridge.h +++ b/target/i386/latx/include/kzt_rela_runtime_bridge.h @@ -33,4 +33,18 @@ int kzt_rela_runtime_wrapper_provider_discover_with_version_evidence( const char *symbol_version, kzt_wrapper_bridge_provider_t *provider); +int kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( + box64context_t *context, + const kzt_guest_library_handle_t *retained_provider_handle, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version, + kzt_wrapper_bridge_provider_t *provider); + +/* The exact guest-library handle remains acquired until the provider is + * discarded, so bridge-map operations may reuse discovery's loader proof. */ +int kzt_rela_runtime_wrapper_provider_bind_retained_handle( + kzt_wrapper_bridge_provider_t *provider, + const kzt_guest_library_handle_t *handle); + #endif diff --git a/target/i386/latx/include/kzt_rela_stub_detector.h b/target/i386/latx/include/kzt_rela_stub_detector.h index bb7fcc285cb..a8480ac6a01 100644 --- a/target/i386/latx/include/kzt_rela_stub_detector.h +++ b/target/i386/latx/include/kzt_rela_stub_detector.h @@ -9,10 +9,31 @@ typedef enum kzt_rela_stub_coordinate { KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, } kzt_rela_stub_coordinate_t; +typedef struct kzt_rela_jump_slot_defer_input { + uintptr_t slot_current_value; + int bind_is_local; + int bindnow; + int need_resolver_present; + intptr_t load_bias; + uintptr_t plt_start; + uintptr_t plt_end; + uintptr_t gotplt_start; + uintptr_t gotplt_end; +} kzt_rela_jump_slot_defer_input_t; + +typedef struct kzt_rela_jump_slot_defer_plan { + int slot_is_unresolved_stub; + int should_defer; + int should_add_delta; +} kzt_rela_jump_slot_defer_plan_t; + int kzt_rela_slot_current_is_unresolved_stub( uintptr_t slot_current_value, kzt_rela_stub_coordinate_t coordinate, intptr_t load_bias, uintptr_t plt_start, uintptr_t plt_end, uintptr_t gotplt_start, uintptr_t gotplt_end); +kzt_rela_jump_slot_defer_plan_t kzt_rela_jump_slot_defer_plan( + const kzt_rela_jump_slot_defer_input_t *input); + #endif diff --git a/target/i386/latx/include/kzt_wrapper_bridge_provider.h b/target/i386/latx/include/kzt_wrapper_bridge_provider.h index e3760eed5c4..59a4004e705 100644 --- a/target/i386/latx/include/kzt_wrapper_bridge_provider.h +++ b/target/i386/latx/include/kzt_wrapper_bridge_provider.h @@ -4,6 +4,7 @@ #include #include +#include "kzt_guest_library_binding.h" #include "kzt_wrapper_probe.h" typedef void (*kzt_wrapper_bridge_abi_wrapper_t)(uintptr_t fnc); @@ -28,6 +29,7 @@ typedef struct kzt_wrapper_bridge_provider_match { int wrapper_provider_lifetime_bound; int native_owner_lifetime_bound; int bridge_owner_lifetime_bound; + const kzt_guest_library_handle_t *retained_provider_handle; } kzt_wrapper_bridge_provider_match_t; typedef int (*kzt_wrapper_bridge_provider_inspect_fn)( diff --git a/tests/unit/kzt/kzt_test_options.c b/tests/unit/kzt/kzt_test_options.c index 24587e7482b..d94c19dac4a 100644 --- a/tests/unit/kzt/kzt_test_options.c +++ b/tests/unit/kzt/kzt_test_options.c @@ -1,3 +1,4 @@ int option_kzt_patch_spike; int option_kzt_patch_spike_write; unsigned long option_kzt_patch_spike_budget; +int option_kzt_lazy_diagnostics; diff --git a/tests/unit/kzt/test_bridge_atfork_fail_open.c b/tests/unit/kzt/test_bridge_atfork_fail_open.c new file mode 100644 index 00000000000..f27e164d6fe --- /dev/null +++ b/tests/unit/kzt/test_bridge_atfork_fail_open.c @@ -0,0 +1,319 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "target/i386/latx/include/box64context.h" +#include "target/i386/latx/include/bridge.h" +#include "target/i386/latx/include/elfloader.h" +#include "target/i386/latx/include/khash.h" +#include "target/i386/latx/include/kzt_rela_runtime_bridge.h" +#include "target/i386/latx/include/librarian_private.h" +#include "target/i386/latx/include/library.h" +#include "target/i386/latx/include/library_private.h" + +#define FIXTURE_SYMBOL "uname" +#define FIXTURE_VERSION "GLIBC_2.36" + +box64context_t *my_context; +int relocation_log; +int kzt_registry_diagnostics; + +KHASH_MAP_IMPL_STR(symbolmap, wrapper_t) +KHASH_MAP_IMPL_STR(symbol2map, symbol2_t) + +typedef struct bridge_hold_sync { + pthread_mutex_t lock; + pthread_cond_t cond; + int entered; + int release; +} bridge_hold_sync_t; + +typedef struct bridge_hold_worker { + bridge_t *bridge; + bridge_hold_sync_t *sync; +} bridge_hold_worker_t; + +typedef struct runtime_fixture { + box64context_t context; + lib_t scope; + library_t provider; + library_t *libraries[1]; + void *native_symbol; + uintptr_t bridge_target; +} runtime_fixture_t; + +elfheader_t *FindElfAddress(box64context_t *context, uintptr_t address) +{ + (void)context; + (void)address; + return NULL; +} + +static void wrapper(uintptr_t fnc) +{ + (void)fnc; +} + +static void bridge_hold_hook(void *opaque) +{ + bridge_hold_sync_t *sync = opaque; + + pthread_mutex_lock(&sync->lock); + sync->entered = 1; + pthread_cond_broadcast(&sync->cond); + while (!sync->release) { + pthread_cond_wait(&sync->cond, &sync->lock); + } + pthread_mutex_unlock(&sync->lock); +} + +static void *bridge_hold_main(void *opaque) +{ + bridge_hold_worker_t *worker = opaque; + + (void)AddCheckBridge(worker->bridge, wrapper, (void *)0x410000, 0, + "held-across-fork"); + return NULL; +} + +static int bridge_hold_sync_init(bridge_hold_sync_t *sync) +{ + memset(sync, 0, sizeof(*sync)); + return pthread_mutex_init(&sync->lock, NULL) || + pthread_cond_init(&sync->cond, NULL) ? -1 : 0; +} + +static void bridge_hold_sync_destroy(bridge_hold_sync_t *sync) +{ + pthread_cond_destroy(&sync->cond); + pthread_mutex_destroy(&sync->lock); +} + +static int bridge_hold_wait(bridge_hold_sync_t *sync) +{ + struct timespec deadline; + int status = 0; + + clock_gettime(CLOCK_REALTIME, &deadline); + ++deadline.tv_sec; + pthread_mutex_lock(&sync->lock); + while (!sync->entered && status == 0) { + status = pthread_cond_timedwait(&sync->cond, &sync->lock, &deadline); + } + pthread_mutex_unlock(&sync->lock); + return status == 0 ? 0 : -1; +} + +static void bridge_hold_release(bridge_hold_sync_t *sync) +{ + pthread_mutex_lock(&sync->lock); + sync->release = 1; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); +} + +static int runtime_fixture_init(runtime_fixture_t *fixture, bridge_t *bridge) +{ + static char libc_name[] = "libc.so.6"; + khint_t key; + int inserted; + + memset(fixture, 0, sizeof(*fixture)); + fixture->libraries[0] = &fixture->provider; + fixture->scope.libraries = fixture->libraries; + fixture->scope.libsz = 1; + fixture->scope.context = &fixture->context; + fixture->context.maplib = &fixture->scope; + fixture->provider.name = libc_name; + fixture->provider.path = libc_name; + fixture->provider.type = LIB_WRAPPED; + fixture->provider.active = 1; + fixture->provider.context = &fixture->context; + fixture->provider.priv.w.bridge = bridge; + fixture->provider.priv.w.lib = dlopen(libc_name, RTLD_LAZY | RTLD_LOCAL); + if (!fixture->provider.priv.w.lib) { + return -1; + } + fixture->provider.symbolmap = kh_init(symbolmap); + if (!fixture->provider.symbolmap) { + dlclose(fixture->provider.priv.w.lib); + fixture->provider.priv.w.lib = NULL; + return -1; + } + key = kh_put(symbolmap, fixture->provider.symbolmap, FIXTURE_SYMBOL, + &inserted); + if (inserted == -1 || key == kh_end(fixture->provider.symbolmap)) { + kh_destroy(symbolmap, fixture->provider.symbolmap); + dlclose(fixture->provider.priv.w.lib); + memset(fixture, 0, sizeof(*fixture)); + return -1; + } + kh_value(fixture->provider.symbolmap, key) = wrapper; + fixture->native_symbol = dlvsym(fixture->provider.priv.w.lib, + FIXTURE_SYMBOL, FIXTURE_VERSION); + fixture->bridge_target = AddCheckBridge( + bridge, wrapper, fixture->native_symbol, 0, FIXTURE_SYMBOL); + if (!fixture->native_symbol || !fixture->bridge_target || + CheckBridged(bridge, fixture->native_symbol) != fixture->bridge_target) { + kh_destroy(symbolmap, fixture->provider.symbolmap); + dlclose(fixture->provider.priv.w.lib); + memset(fixture, 0, sizeof(*fixture)); + return -1; + } + return 0; +} + +static void runtime_fixture_destroy(runtime_fixture_t *fixture) +{ + if (fixture->provider.symbolmap) { + kh_destroy(symbolmap, fixture->provider.symbolmap); + } + if (fixture->provider.priv.w.lib) { + dlclose(fixture->provider.priv.w.lib); + } +} + +static int capture_atfork_diagnostic(bridge_t **bridge, char *diagnostic, + size_t diagnostic_capacity) +{ + int diagnostic_pipe[2]; + int saved_stderr; + ssize_t diagnostic_size; + + if (pipe(diagnostic_pipe) != 0) { + return -1; + } + saved_stderr = dup(STDERR_FILENO); + if (saved_stderr < 0 || dup2(diagnostic_pipe[1], STDERR_FILENO) < 0) { + close(diagnostic_pipe[0]); + close(diagnostic_pipe[1]); + if (saved_stderr >= 0) { + close(saved_stderr); + } + return -1; + } + close(diagnostic_pipe[1]); + *bridge = NewBridge(); + fflush(stderr); + if (dup2(saved_stderr, STDERR_FILENO) < 0) { + close(saved_stderr); + close(diagnostic_pipe[0]); + return -1; + } + close(saved_stderr); + diagnostic_size = read(diagnostic_pipe[0], diagnostic, + diagnostic_capacity - 1); + close(diagnostic_pipe[0]); + if (diagnostic_size < 0) { + return -1; + } + diagnostic[diagnostic_size] = '\0'; + return 0; +} + +int main(int argc, char **argv) +{ + char diagnostic[512] = { 0 }; + bridge_hold_sync_t sync; + bridge_hold_worker_t worker; + kzt_wrapper_bridge_provider_t provider; + runtime_fixture_t fixture; + pthread_t holder; + bridge_t *bridge = NULL; + pid_t pid; + int child_status = -1; + int failed = 0; + + if (argc == 2 && strcmp(argv[1], "--diagnostics-off") == 0) { + kzt_registry_diagnostics = 0; + if (capture_atfork_diagnostic( + &bridge, diagnostic, sizeof(diagnostic)) != 0 || !bridge) { + fprintf(stderr, "cannot initialize diagnostics-off fixture\n"); + return 1; + } + if (diagnostic[0]) { + fprintf(stderr, "atfork fallback diagnostic ignored gate: %s\n", + diagnostic); + failed = 1; + } + FreeBridge(&bridge); + return failed ? 1 : 0; + } + if (argc != 1) { + fprintf(stderr, "usage: %s [--diagnostics-off]\n", argv[0]); + return 2; + } + + kzt_registry_diagnostics = 1; + if (capture_atfork_diagnostic(&bridge, diagnostic, sizeof(diagnostic)) != 0 || + !bridge) { + fprintf(stderr, "cannot initialize bridge failure fixture\n"); + return 1; + } + if (!strstr(diagnostic, "kzt_bridge_fallback") || + !strstr(diagnostic, "reason=atfork_registration_failed")) { + fprintf(stderr, "structured atfork fallback diagnostic missing: %s\n", + diagnostic); + failed = 1; + } + if (bridge_hold_sync_init(&sync) != 0 || + runtime_fixture_init(&fixture, bridge) != 0) { + fprintf(stderr, "cannot initialize real-fork fixture\n"); + FreeBridge(&bridge); + return 1; + } + worker = (bridge_hold_worker_t) { bridge, &sync }; + bridge_test_set_after_check_hook(bridge_hold_hook, &sync); + if (pthread_create(&holder, NULL, bridge_hold_main, &worker) != 0 || + bridge_hold_wait(&sync) != 0) { + fprintf(stderr, "bridge holder did not acquire the bridge lock\n"); + bridge_hold_release(&sync); + bridge_test_set_after_check_hook(NULL, NULL); + runtime_fixture_destroy(&fixture); + FreeBridge(&bridge); + bridge_hold_sync_destroy(&sync); + return 1; + } + + pid = fork(); + if (pid == 0) { + int status; + + alarm(2); + memset(&provider, 0, sizeof(provider)); + status = kzt_rela_runtime_wrapper_provider_prepare( + &fixture.context, &fixture.provider, fixture.bridge_target, + FIXTURE_SYMBOL, FIXTURE_VERSION, &provider); + _exit(status == 0 && !provider.manifest.available && + !provider.bridge_ops.check_bridge && + !provider.bridge_ops.add_bridge ? 0 : 2); + } + if (pid < 0 || waitpid(pid, &child_status, 0) != pid) { + fprintf(stderr, "real fork failed\n"); + failed = 1; + } + + bridge_hold_release(&sync); + if (pthread_join(holder, NULL) != 0) { + fprintf(stderr, "cannot join bridge holder\n"); + failed = 1; + } + bridge_test_set_after_check_hook(NULL, NULL); + if (!WIFEXITED(child_status) || WEXITSTATUS(child_status) != 0) { + fprintf(stderr, + "KZT child did not fail open while bridge lock was inherited: " + "status=%d\n", + child_status); + failed = 1; + } + + runtime_fixture_destroy(&fixture); + FreeBridge(&bridge); + bridge_hold_sync_destroy(&sync); + return failed ? 1 : 0; +} diff --git a/tests/unit/kzt/test_bridge_concurrency.c b/tests/unit/kzt/test_bridge_concurrency.c new file mode 100644 index 00000000000..a6d40b61468 --- /dev/null +++ b/tests/unit/kzt/test_bridge_concurrency.c @@ -0,0 +1,916 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "target/i386/latx/include/box64context.h" +#include "target/i386/latx/include/bridge.h" +#include "target/i386/latx/include/elfloader.h" +#include "target/i386/latx/include/khash.h" + +#define SYMBOL_COUNT 512 +#define THREAD_COUNT 8 +#define ALTERNATE_STRESS_ENTRIES 2048 +#define ALTERNATE_READER_THREADS 4 +#define ALTERNATE_WRITER_THREADS 4 +#define BENCHMARK_ROUNDS 30 +#define BENCHMARK_QUERIES 4096 +#define BENCHMARK_REPEAT 32 +#define BRIDGE_GATE_BENCHMARK_REPEAT 1000000 + +KHASH_MAP_INIT_INT64(alternate_benchmark, uintptr_t) + +box64context_t *my_context; +int relocation_log; +int kzt_registry_diagnostics; + +elfheader_t *FindElfAddress(box64context_t *context, uintptr_t address) +{ + (void)context; + (void)address; + return NULL; +} + +static int failures; + +static uint64_t monotonic_ns(void); + +static void check_true(const char *name, int value) +{ + if (!value) { + fprintf(stderr, "%s: false\n", name); + ++failures; + } +} + +typedef struct sync { + pthread_mutex_t lock; + pthread_cond_t cond; + int entered; + int second_started; + int release; + int done; +} sync_t; + +typedef struct bridge_worker { + bridge_t *bridge; + void *symbol; + uintptr_t result; + sync_t *sync; + int force_add; + int automatic_add; + int second; +} bridge_worker_t; + +typedef struct alternate_worker { + void *address; + void *alternate; +} alternate_worker_t; + +typedef struct alternate_reader_worker { + uintptr_t first; + uintptr_t count; + int same_offset; + int failures; +} alternate_reader_worker_t; + +typedef struct alternate_writer_range { + uintptr_t first; + uintptr_t count; + uintptr_t stride; + int same_offset; +} alternate_writer_range_t; + +typedef struct fork_worker { + bridge_t *bridge; + sync_t *sync; + int child_status; +} fork_worker_t; + +typedef struct free_bridge_worker { + bridge_t **bridge; +} free_bridge_worker_t; + +typedef struct fork_after_free_worker { + sync_t *sync; + int child_status; +} fork_after_free_worker_t; + +typedef struct parallel_worker { + bridge_worker_t *workers; + size_t start; +} parallel_worker_t; + +static void wrapper(uintptr_t fnc) +{ + (void)fnc; +} + +static uintptr_t alternate_test_address(uintptr_t base, uintptr_t index, + int same_offset) +{ + return same_offset ? base + (index << 16) : base + index * 16; +} + +static int sync_init(sync_t *sync) +{ + memset(sync, 0, sizeof(*sync)); + return pthread_mutex_init(&sync->lock, NULL) || + pthread_cond_init(&sync->cond, NULL) ? -1 : 0; +} + +static void sync_destroy(sync_t *sync) +{ + pthread_cond_destroy(&sync->cond); + pthread_mutex_destroy(&sync->lock); +} + +static int sync_wait_for(sync_t *sync, int *value, int expected, + long timeout_ms) +{ + struct timespec deadline; + int result = 0; + + clock_gettime(CLOCK_REALTIME, &deadline); + deadline.tv_nsec += (timeout_ms % 1000) * 1000 * 1000; + deadline.tv_sec += timeout_ms / 1000; + if (deadline.tv_nsec >= 1000 * 1000 * 1000) { + ++deadline.tv_sec; + deadline.tv_nsec -= 1000 * 1000 * 1000; + } + pthread_mutex_lock(&sync->lock); + while (*value < expected) { + int status = pthread_cond_timedwait(&sync->cond, &sync->lock, + &deadline); + if (status != 0) { + result = -1; + break; + } + } + pthread_mutex_unlock(&sync->lock); + return result; +} + +static void after_check_hook(void *opaque) +{ + sync_t *sync = opaque; + + pthread_mutex_lock(&sync->lock); + if (!sync->entered) { + sync->entered = 1; + pthread_cond_broadcast(&sync->cond); + while (!sync->release) { + pthread_cond_wait(&sync->cond, &sync->lock); + } + } + pthread_mutex_unlock(&sync->lock); +} + +static void *bridge_worker_main(void *opaque) +{ + bridge_worker_t *worker = opaque; + + if (worker->sync && worker->second) { + pthread_mutex_lock(&worker->sync->lock); + worker->sync->second_started = 1; + pthread_cond_broadcast(&worker->sync->cond); + pthread_mutex_unlock(&worker->sync->lock); + } + if (worker->force_add) { + worker->result = AddBridge(worker->bridge, wrapper, worker->symbol, + 0, "force"); + } else if (worker->automatic_add) { + worker->result = AddAutomaticBridge(worker->bridge, wrapper, + worker->symbol, 0); + } else { + worker->result = AddCheckBridge(worker->bridge, wrapper, + worker->symbol, 0, "check"); + } + if (worker->sync) { + pthread_mutex_lock(&worker->sync->lock); + ++worker->sync->done; + pthread_cond_broadcast(&worker->sync->cond); + pthread_mutex_unlock(&worker->sync->lock); + } + return NULL; +} + +static void *alternate_writer_range_main(void *opaque) +{ + alternate_writer_range_t *worker = opaque; + uintptr_t i; + + for (i = worker->first; i < worker->count; i += worker->stride) { + uintptr_t address = alternate_test_address(0x800000, i, + worker->same_offset); + addAlternate((void *)address, (void *)(address + 8)); + } + return NULL; +} + +static void *alternate_worker_main(void *opaque) +{ + alternate_worker_t *worker = opaque; + + addAlternate(worker->address, worker->alternate); + return NULL; +} + +static void *alternate_reader_worker_main(void *opaque) +{ + alternate_reader_worker_t *worker = opaque; + uintptr_t i; + + for (i = 0; i < 200000; ++i) { + uintptr_t address = alternate_test_address( + worker->first, i % worker->count, worker->same_offset); + void *result = getAlternate((void *)address); + + if (result != (void *)address && + result != (void *)(address + 8)) { + ++worker->failures; + } + } + return NULL; +} + +static void *fork_worker_main(void *opaque) +{ + fork_worker_t *worker = opaque; + pid_t pid; + int status = -1; + + pthread_mutex_lock(&worker->sync->lock); + ++worker->sync->done; + pthread_cond_broadcast(&worker->sync->cond); + pthread_mutex_unlock(&worker->sync->lock); + pid = fork(); + if (pid == 0) { + alarm(2); + if (getAlternate((void *)0x700000) != (void *)0x700008) { + _exit(1); + } + if (!AddAutomaticBridge(worker->bridge, wrapper, (void *)0x420000, + 0) || + getAlternate((void *)0x420000) == (void *)0x420000) { + _exit(2); + } + _exit(0); + } + if (pid > 0 && waitpid(pid, &status, 0) == pid) { + worker->child_status = status; + } + return NULL; +} + +static void *free_bridge_worker_main(void *opaque) +{ + free_bridge_worker_t *worker = opaque; + + FreeBridge(worker->bridge); + return NULL; +} + +static void *fork_after_free_worker_main(void *opaque) +{ + fork_after_free_worker_t *worker = opaque; + pid_t pid; + int status = -1; + + pthread_mutex_lock(&worker->sync->lock); + worker->sync->second_started = 1; + pthread_cond_broadcast(&worker->sync->cond); + pthread_mutex_unlock(&worker->sync->lock); + pid = fork(); + if (pid == 0) { + bridge_t *fresh; + + alarm(2); + fresh = NewBridge(); + if (!fresh || !AddCheckBridge(fresh, wrapper, (void *)0x430000, + 0, "fork-after-free")) { + _exit(1); + } + FreeBridge(&fresh); + _exit(0); + } + if (pid > 0 && waitpid(pid, &status, 0) == pid) { + worker->child_status = status; + } + pthread_mutex_lock(&worker->sync->lock); + ++worker->sync->done; + pthread_cond_broadcast(&worker->sync->cond); + pthread_mutex_unlock(&worker->sync->lock); + return NULL; +} + +static void *parallel_worker_main(void *opaque) +{ + parallel_worker_t *worker = opaque; + size_t i; + + for (i = worker->start; i < SYMBOL_COUNT; i += THREAD_COUNT) { + bridge_worker_main(&worker->workers[i]); + } + return NULL; +} + +static void test_add_check_is_one_critical_section(void) +{ + bridge_t *bridge = NewBridge(); + bridge_worker_t first; + bridge_worker_t second; + pthread_t first_thread; + pthread_t second_thread; + sync_t sync; + + check_true("atomic.new", bridge != NULL); + if (!bridge || sync_init(&sync) != 0) { + FreeBridge(&bridge); + return; + } + first = (bridge_worker_t) { bridge, (void *)0x410000, 0, &sync, 0, 0, 0 }; + second = (bridge_worker_t) { bridge, (void *)0x410000, 0, &sync, 0, 0, 1 }; + bridge_test_set_after_check_hook(after_check_hook, &sync); + check_true("atomic.first.create", + pthread_create(&first_thread, NULL, bridge_worker_main, &first) == 0); + check_true("atomic.first.checked", sync_wait_for(&sync, &sync.entered, 1, 1000) == 0); + check_true("atomic.second.create", + pthread_create(&second_thread, NULL, bridge_worker_main, &second) == 0); + check_true("atomic.second-started", + sync_wait_for(&sync, &sync.second_started, 1, 1000) == 0); + check_true("atomic.lock-held-across-check-add", + bridge_test_lock_is_held(bridge)); + pthread_mutex_lock(&sync.lock); + check_true("atomic.first-stopped-after-check", sync.entered == 1); + check_true("atomic.second-not-complete-before-release", sync.done == 0); + pthread_mutex_unlock(&sync.lock); + pthread_mutex_lock(&sync.lock); + sync.release = 1; + pthread_cond_broadcast(&sync.cond); + pthread_mutex_unlock(&sync.lock); + check_true("atomic.first.join", pthread_join(first_thread, NULL) == 0); + check_true("atomic.second.join", pthread_join(second_thread, NULL) == 0); + check_true("atomic.same-trampoline", first.result && first.result == second.result); + check_true("atomic.map", CheckBridged(bridge, (void *)0x410000) == first.result); + bridge_test_set_after_check_hook(NULL, NULL); + FreeBridge(&bridge); + sync_destroy(&sync); +} + +static void test_parallel_bricks_and_resize(void) +{ + bridge_t *bridge = NewBridge(); + bridge_worker_t workers[SYMBOL_COUNT]; + pthread_t threads[THREAD_COUNT]; + parallel_worker_t parallel[THREAD_COUNT]; + size_t i; + + check_true("resize.new", bridge != NULL); + if (!bridge) { + return; + } + for (i = 0; i < SYMBOL_COUNT; ++i) { + workers[i] = (bridge_worker_t) { + bridge, (void *)(uintptr_t)(0x500000 + i * 16), 0, NULL, + 0, 0, 0 + }; + } + for (i = 0; i < THREAD_COUNT; ++i) { + parallel[i] = (parallel_worker_t) { workers, i }; + check_true("resize.create", pthread_create(&threads[i], NULL, + parallel_worker_main, + ¶llel[i]) == 0); + } + for (i = 0; i < THREAD_COUNT; ++i) { + check_true("resize.join", pthread_join(threads[i], NULL) == 0); + } + for (i = 0; i < SYMBOL_COUNT; ++i) { + size_t j; + check_true("resize.add-check", workers[i].result != 0); + check_true("resize.lookup", CheckBridged(bridge, workers[i].symbol) == + workers[i].result); + for (j = 0; j < i; ++j) { + check_true("resize.unique-slot", workers[i].result != workers[j].result); + } + } + FreeBridge(&bridge); +} + +static void test_add_bridge_keeps_force_create_semantics(void) +{ + bridge_t *bridge = NewBridge(); + bridge_worker_t workers[THREAD_COUNT]; + pthread_t threads[THREAD_COUNT]; + size_t i; + + check_true("force.new", bridge != NULL); + if (!bridge) { + return; + } + for (i = 0; i < THREAD_COUNT; ++i) { + workers[i] = (bridge_worker_t) { + bridge, (void *)0x610000, 0, NULL, 1, 0, 0 + }; + check_true("force.create", pthread_create(&threads[i], NULL, + bridge_worker_main, + &workers[i]) == 0); + } + for (i = 0; i < THREAD_COUNT; ++i) { + size_t j; + check_true("force.join", pthread_join(threads[i], NULL) == 0); + check_true("force.result", workers[i].result != 0); + for (j = 0; j < i; ++j) { + check_true("force.unique-slot", workers[i].result != workers[j].result); + } + } + check_true("force.map-published", CheckBridged(bridge, (void *)0x610000) != 0); + FreeBridge(&bridge); +} + +static void test_fork_waits_for_live_bridge_operation(void) +{ + bridge_t *bridge = NewBridge(); + bridge_worker_t held; + fork_worker_t forker; + pthread_t held_thread; + pthread_t fork_thread; + sync_t sync; + struct timespec pause = { 0, 100 * 1000 * 1000 }; + + check_true("fork.new", bridge != NULL); + if (!bridge || sync_init(&sync) != 0) { + FreeBridge(&bridge); + return; + } + held = (bridge_worker_t) { bridge, (void *)0x410000, 0, &sync, 0, 1, 0 }; + forker = (fork_worker_t) { bridge, &sync, -1 }; + addAlternate((void *)0x700000, (void *)0x700008); + bridge_test_set_after_check_hook(after_check_hook, &sync); + check_true("fork.held.create", pthread_create(&held_thread, NULL, + bridge_worker_main, + &held) == 0); + check_true("fork.held.locked", sync_wait_for(&sync, &sync.entered, 1, 1000) == 0); + check_true("fork.create", pthread_create(&fork_thread, NULL, + fork_worker_main, &forker) == 0); + check_true("fork.started", sync_wait_for(&sync, &sync.done, 1, 1000) == 0); + nanosleep(&pause, NULL); + pthread_mutex_lock(&sync.lock); + sync.release = 1; + pthread_cond_broadcast(&sync.cond); + pthread_mutex_unlock(&sync.lock); + check_true("fork.held.join", pthread_join(held_thread, NULL) == 0); + check_true("fork.join", pthread_join(fork_thread, NULL) == 0); + check_true("fork.child-smoke", WIFEXITED(forker.child_status) && + WEXITSTATUS(forker.child_status) == 0); + bridge_test_set_after_check_hook(NULL, NULL); + FreeBridge(&bridge); + sync_destroy(&sync); +} + +static void test_fork_waits_for_bridge_free(void) +{ + bridge_t *bridge = NewBridge(); + free_bridge_worker_t freer = { .bridge = &bridge }; + fork_after_free_worker_t forker; + pthread_t free_thread; + pthread_t fork_thread; + sync_t sync; + struct timespec pause = { 0, 100 * 1000 * 1000 }; + + check_true("fork-free.new", bridge != NULL); + if (!bridge || sync_init(&sync) != 0) { + FreeBridge(&bridge); + return; + } + forker = (fork_after_free_worker_t) { .sync = &sync, .child_status = -1 }; + bridge_test_set_before_free_hook(after_check_hook, &sync); + check_true("fork-free.free-create", + pthread_create(&free_thread, NULL, free_bridge_worker_main, + &freer) == 0); + check_true("fork-free.free-locked", + sync_wait_for(&sync, &sync.entered, 1, 1000) == 0); + check_true("fork-free.fork-create", + pthread_create(&fork_thread, NULL, fork_after_free_worker_main, + &forker) == 0); + check_true("fork-free.fork-started", + sync_wait_for(&sync, &sync.second_started, 1, 1000) == 0); + nanosleep(&pause, NULL); + pthread_mutex_lock(&sync.lock); + check_true("fork-free.fork-waits", sync.done == 0); + sync.release = 1; + pthread_cond_broadcast(&sync.cond); + pthread_mutex_unlock(&sync.lock); + check_true("fork-free.free-join", pthread_join(free_thread, NULL) == 0); + check_true("fork-free.pointer-cleared", bridge == NULL); + check_true("fork-free.fork-join", pthread_join(fork_thread, NULL) == 0); + check_true("fork-free.child-smoke", WIFEXITED(forker.child_status) && + WEXITSTATUS(forker.child_status) == 0); + bridge_test_set_before_free_hook(NULL, NULL); + sync_destroy(&sync); +} + +static void test_alternate_first_publication(void) +{ + alternate_worker_t workers[THREAD_COUNT]; + pthread_t threads[THREAD_COUNT]; + void *address = (void *)0x710000; + void *published; + uint64_t start; + size_t i; + + cleanAlternate(); + start = monotonic_ns(); + for (i = 0; i < THREAD_COUNT; ++i) { + workers[i] = (alternate_worker_t) { + address, (void *)(uintptr_t)(0x720000 + i * 16) + }; + check_true("alternate.create", pthread_create(&threads[i], NULL, + alternate_worker_main, + &workers[i]) == 0); + } + for (i = 0; i < THREAD_COUNT; ++i) { + check_true("alternate.join", pthread_join(threads[i], NULL) == 0); + } + published = getAlternate(address); + printf("alternate-first-publication threads=%d elapsed_ns=%llu\n", + THREAD_COUNT, (unsigned long long)(monotonic_ns() - start)); + check_true("alternate.published", published != address); + addAlternate(address, (void *)0x730000); + check_true("alternate.first-wins", getAlternate(address) == published); + cleanAlternate(); +} + +static void test_alternate_readers_and_stress(int same_offset) +{ + alternate_writer_range_t writers[ALTERNATE_WRITER_THREADS]; + alternate_reader_worker_t readers[ALTERNATE_READER_THREADS]; + pthread_t writer_threads[ALTERNATE_WRITER_THREADS]; + pthread_t reader_threads[ALTERNATE_READER_THREADS]; + size_t i; + + cleanAlternate(); + for (i = 0; i < 64; ++i) { + uintptr_t address = alternate_test_address(0x800000, i, same_offset); + addAlternate((void *)address, (void *)(address + 8)); + } + for (i = 0; i < ALTERNATE_READER_THREADS; ++i) { + readers[i] = (alternate_reader_worker_t) { + .first = 0x800000, + .count = ALTERNATE_STRESS_ENTRIES, + .same_offset = same_offset, + }; + check_true("alternate.resize.reader-create", + pthread_create(&reader_threads[i], NULL, + alternate_reader_worker_main, + &readers[i]) == 0); + } + for (i = 0; i < ALTERNATE_WRITER_THREADS; ++i) { + writers[i] = (alternate_writer_range_t) { + .first = 64 + i, + .count = ALTERNATE_STRESS_ENTRIES, + .stride = ALTERNATE_WRITER_THREADS, + .same_offset = same_offset, + }; + check_true("alternate.resize.writer-create", + pthread_create(&writer_threads[i], NULL, + alternate_writer_range_main, + &writers[i]) == 0); + } + for (i = 0; i < ALTERNATE_WRITER_THREADS; ++i) { + check_true("alternate.resize.writer-join", + pthread_join(writer_threads[i], NULL) == 0); + } + for (i = 0; i < ALTERNATE_READER_THREADS; ++i) { + check_true("alternate.resize.reader-join", + pthread_join(reader_threads[i], NULL) == 0); + check_true("alternate.resize.reader-values", readers[i].failures == 0); + } + for (i = 0; i < ALTERNATE_STRESS_ENTRIES; ++i) { + uintptr_t address = alternate_test_address(0x800000, i, same_offset); + + check_true("alternate.resize.present", + getAlternate((void *)address) == (void *)(address + 8)); + } + cleanAlternate(); +} + +static uint64_t monotonic_ns(void) +{ + struct timespec now; + + clock_gettime(CLOCK_MONOTONIC, &now); + return (uint64_t)now.tv_sec * 1000 * 1000 * 1000 + now.tv_nsec; +} + +static volatile uintptr_t benchmark_sink; +static int bridge_gate_baseline_state = 1; + +static uint64_t benchmark_next(uint64_t *state) +{ + *state ^= *state << 13; + *state ^= *state >> 7; + *state ^= *state << 17; + return *state; +} + +static int compare_u64(const void *left, const void *right) +{ + uint64_t a = *(const uint64_t *)left; + uint64_t b = *(const uint64_t *)right; + + return a < b ? -1 : a > b; +} + +static void benchmark_summary(const char *table, size_t entries, + const char *pattern, const uint64_t *samples, + size_t operations) +{ + uint64_t sorted[BENCHMARK_ROUNDS]; + uint64_t bootstrap[1000]; + uint64_t state = UINT64_C(0x3f4a9d1b7c2e5109); + size_t i; + + memcpy(sorted, samples, sizeof(sorted)); + qsort(sorted, BENCHMARK_ROUNDS, sizeof(sorted[0]), compare_u64); + for (i = 0; i < sizeof(bootstrap) / sizeof(bootstrap[0]); ++i) { + uint64_t total = 0; + size_t j; + + for (j = 0; j < BENCHMARK_ROUNDS; ++j) { + total += samples[benchmark_next(&state) % BENCHMARK_ROUNDS]; + } + bootstrap[i] = total / BENCHMARK_ROUNDS; + } + qsort(bootstrap, sizeof(bootstrap) / sizeof(bootstrap[0]), + sizeof(bootstrap[0]), compare_u64); + printf("alternate-workset table=%s entries=%zu pattern=%s median_ns_op=%llu throughput_mops=%.2f ci95_ns_op=%llu:%llu\n", + table, entries, pattern, + (unsigned long long)(sorted[BENCHMARK_ROUNDS / 2] / operations), + 1000.0 * operations / sorted[BENCHMARK_ROUNDS / 2], + (unsigned long long)(bootstrap[25] / operations), + (unsigned long long)(bootstrap[974] / operations)); +} + +static uint64_t benchmark_median_total(const uint64_t *samples) +{ + uint64_t sorted[BENCHMARK_ROUNDS]; + + memcpy(sorted, samples, sizeof(sorted)); + qsort(sorted, BENCHMARK_ROUNDS, sizeof(sorted[0]), compare_u64); + return sorted[BENCHMARK_ROUNDS / 2]; +} + +static __attribute__((noinline)) int benchmark_bridge_gate_baseline(void) +{ + return __atomic_load_n(&bridge_gate_baseline_state, __ATOMIC_ACQUIRE); +} + +static uint64_t benchmark_bridge_gate_baseline_run(void) +{ + uintptr_t result = 0; + uint64_t start = monotonic_ns(); + size_t i; + + for (i = 0; i < BRIDGE_GATE_BENCHMARK_REPEAT; ++i) { + result += benchmark_bridge_gate_baseline(); + } + benchmark_sink ^= result; + return monotonic_ns() - start; +} + +static uint64_t benchmark_bridge_gate_run(void) +{ + uintptr_t result = 0; + uint64_t start = monotonic_ns(); + size_t i; + + for (i = 0; i < BRIDGE_GATE_BENCHMARK_REPEAT; ++i) { + result += BridgeForkProtectionAvailable(); + } + benchmark_sink ^= result; + return monotonic_ns() - start; +} + +static void test_bridge_gate_benchmark(void) +{ + uint64_t baseline_samples[BENCHMARK_ROUNDS]; + uint64_t gate_samples[BENCHMARK_ROUNDS]; + uint64_t baseline_median; + uint64_t gate_median; + uint64_t limit; + size_t i; + + check_true("bridge-gate.available", BridgeForkProtectionAvailable()); + for (i = 0; i < BENCHMARK_ROUNDS; ++i) { + if (i & 1) { + gate_samples[i] = benchmark_bridge_gate_run(); + baseline_samples[i] = benchmark_bridge_gate_baseline_run(); + } else { + baseline_samples[i] = benchmark_bridge_gate_baseline_run(); + gate_samples[i] = benchmark_bridge_gate_run(); + } + } + baseline_median = benchmark_median_total(baseline_samples); + gate_median = benchmark_median_total(gate_samples); + limit = baseline_median + BRIDGE_GATE_BENCHMARK_REPEAT * 5; + printf("bridge-gate-performance baseline_median_total_ns=%llu " + "gate_median_total_ns=%llu limit_total_ns=%llu " + "gate_ns_op=%llu result=%s\n", + (unsigned long long)baseline_median, + (unsigned long long)gate_median, + (unsigned long long)limit, + (unsigned long long)(gate_median / + BRIDGE_GATE_BENCHMARK_REPEAT), + gate_median <= limit ? "PASS" : "FAIL"); + check_true("bridge-gate.performance", gate_median <= limit); +} + +static void benchmark_check_gate(size_t entries, const char *label, + int same_offset, + const uint64_t *old_samples, + const uint64_t *new_samples) +{ + const uint64_t operations = BENCHMARK_QUERIES * BENCHMARK_REPEAT; + uint64_t old_median = benchmark_median_total(old_samples); + uint64_t new_median = benchmark_median_total(new_samples); + uint64_t limit; + + if (same_offset) { + limit = old_median; + } else if (entries == 0) { + limit = old_median + operations * 2; + } else { + limit = old_median + old_median * 5 / 100; + } + printf("alternate-gate entries=%zu pattern=%s old_median_total_ns=%llu new_median_total_ns=%llu limit_total_ns=%llu result=%s\n", + entries, label, (unsigned long long)old_median, + (unsigned long long)new_median, (unsigned long long)limit, + new_median <= limit ? "PASS" : "FAIL"); + check_true("benchmark.performance-gate", new_median <= limit); +} + +static __attribute__((noinline)) uintptr_t benchmark_old_lookup( + kh_alternate_benchmark_t *table, uintptr_t address) +{ + khint_t key = kh_get(alternate_benchmark, table, address); + + return key == kh_end(table) ? address : kh_value(table, key); +} + +static uint64_t benchmark_old(kh_alternate_benchmark_t *table, + const uintptr_t *queries) +{ + uint64_t start = monotonic_ns(); + size_t i; + + for (i = 0; i < BENCHMARK_QUERIES * BENCHMARK_REPEAT; ++i) { + uintptr_t address = queries[i % BENCHMARK_QUERIES]; + benchmark_sink ^= benchmark_old_lookup(table, address); + } + return monotonic_ns() - start; +} + +static uint64_t benchmark_new(const uintptr_t *queries) +{ + uint64_t start = monotonic_ns(); + size_t i; + + for (i = 0; i < BENCHMARK_QUERIES * BENCHMARK_REPEAT; ++i) { + benchmark_sink ^= (uintptr_t)getAlternate( + (void *)queries[i % BENCHMARK_QUERIES]); + } + return monotonic_ns() - start; +} + +static void benchmark_alternate_workset(size_t entries, const char *pattern, + int same_offset) +{ + kh_alternate_benchmark_t *old = kh_init(alternate_benchmark); + uintptr_t queries[BENCHMARK_QUERIES]; + uint64_t old_samples[BENCHMARK_ROUNDS]; + uint64_t new_samples[BENCHMARK_ROUNDS]; + uint64_t state = UINT64_C(0x6d2b79f5a41e308c); + char label[32]; + size_t i; + + cleanAlternate(); + check_true("benchmark.old-init", old != NULL); + for (i = 0; old && i < entries; ++i) { + uintptr_t address = alternate_test_address( + same_offset ? 0x10000000 : 0xa00000, i, same_offset); + int inserted; + khint_t key = kh_put(alternate_benchmark, old, address, &inserted); + + kh_value(old, key) = address + 8; + addAlternate((void *)address, (void *)(address + 8)); + } + for (i = 0; i < BENCHMARK_QUERIES; ++i) { + int hit = entries && (!strcmp(pattern, "hit") || + (!strcmp(pattern, "miss99") && i % 100 == 0)); + queries[i] = hit ? alternate_test_address( + same_offset ? 0x10000000 : 0xa00000, + benchmark_next(&state) % entries, + same_offset) : alternate_test_address( + same_offset ? 0x20000000 : 0xb00000, + benchmark_next(&state) % 4096, + same_offset); + if (!hit) { + check_true("benchmark.miss-not-found", + getAlternate((void *)queries[i]) == + (void *)queries[i]); + } + } + for (i = BENCHMARK_QUERIES; i > 1; --i) { + size_t j = benchmark_next(&state) % i; + uintptr_t value = queries[i - 1]; + + queries[i - 1] = queries[j]; + queries[j] = value; + } + for (i = 0; i < BENCHMARK_ROUNDS; ++i) { + if (i & 1) { + new_samples[i] = benchmark_new(queries); + old_samples[i] = benchmark_old(old, queries); + } else { + old_samples[i] = benchmark_old(old, queries); + new_samples[i] = benchmark_new(queries); + } + snprintf(label, sizeof(label), "%s%s", + same_offset ? "high-offset-" : "", pattern); + printf("alternate-round entries=%zu pattern=%s round=%zu old_ns_op=%llu new_ns_op=%llu\n", + entries, label, i, + (unsigned long long)(old_samples[i] / + (BENCHMARK_QUERIES * BENCHMARK_REPEAT)), + (unsigned long long)(new_samples[i] / + (BENCHMARK_QUERIES * BENCHMARK_REPEAT))); + } + snprintf(label, sizeof(label), "%s%s", + same_offset ? "high-offset-" : "", pattern); + benchmark_summary("old-khash", entries, label, old_samples, + BENCHMARK_QUERIES * BENCHMARK_REPEAT); + benchmark_summary("new-fixed-append-only", entries, label, new_samples, + BENCHMARK_QUERIES * BENCHMARK_REPEAT); + benchmark_check_gate(entries, label, same_offset, old_samples, new_samples); + if (old) { + kh_destroy(alternate_benchmark, old); + } + cleanAlternate(); +} + +static void test_alternate_lookup_benchmark(void) +{ + static const size_t sizes[] = { 0, 64, 512, 2048 }; + static const char *const patterns[] = { "hit", "miss", "miss99" }; + cpu_set_t allowed; + cpu_set_t chosen; + size_t i; + size_t j; + + CPU_ZERO(&allowed); + check_true("benchmark.get-affinity", sched_getaffinity(0, sizeof(allowed), + &allowed) == 0); + for (i = 0; i < CPU_SETSIZE && !CPU_ISSET(i, &allowed); ++i) { + } + check_true("benchmark.cpu-available", i < CPU_SETSIZE); + if (i < CPU_SETSIZE) { + CPU_ZERO(&chosen); + CPU_SET(i, &chosen); + check_true("benchmark.set-affinity", sched_setaffinity( + 0, sizeof(chosen), &chosen) == 0); + } + for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); ++i) { + for (j = 0; j < sizeof(patterns) / sizeof(patterns[0]); ++j) { + benchmark_alternate_workset(sizes[i], patterns[j], 0); + } + } + benchmark_alternate_workset(2048, "hit", 1); + benchmark_alternate_workset(2048, "miss", 1); +} + +int main(int argc, char **argv) +{ + if (argc == 2 && !strcmp(argv[1], "--benchmark")) { + test_bridge_gate_benchmark(); + test_alternate_lookup_benchmark(); + return failures ? 1 : 0; + } + if (argc != 1) { + fprintf(stderr, "usage: %s [--benchmark]\n", argv[0]); + return 2; + } + test_add_check_is_one_critical_section(); + test_parallel_bricks_and_resize(); + test_add_bridge_keeps_force_create_semantics(); + test_fork_waits_for_live_bridge_operation(); + test_fork_waits_for_bridge_free(); + test_alternate_first_publication(); + test_alternate_readers_and_stress(0); + test_alternate_readers_and_stress(1); + return failures ? 1 : 0; +} diff --git a/tests/unit/kzt/test_guest_dynsym_lookup.c b/tests/unit/kzt/test_guest_dynsym_lookup.c new file mode 100644 index 00000000000..9a1cd384384 --- /dev/null +++ b/tests/unit/kzt/test_guest_dynsym_lookup.c @@ -0,0 +1,800 @@ +#include +#include + +#include "elf.h" +#include "target/i386/latx/include/kzt_guest_dynsym_lookup.h" + +enum { + TEST_LOAD_BIAS = 0x7000000000ULL, + TEST_SYMTAB_ADDR = 0x7000010000ULL, + TEST_STRTAB_ADDR = 0x7000020000ULL, + TEST_GNU_HASH_ADDR = 0x7000030000ULL, + TEST_HASH_ADDR = 0x7000040000ULL, + TEST_VERSYM_ADDR = 0x7000050000ULL, + TEST_VERDEF_ADDR = 0x7000060000ULL, + TEST_VERNEED_ADDR = 0x7000070000ULL, +}; + +typedef struct fake_region { + uintptr_t guest_base; + const void *host_base; + size_t size; +} fake_region_t; + +typedef struct fake_memory { + fake_region_t regions[16]; + size_t region_count; + uintptr_t fail_addr; +} fake_memory_t; + +typedef struct gnu_hash_one_symbol { + uint32_t nbuckets; + uint32_t symoffset; + uint32_t bloom_size; + uint32_t bloom_shift; + uint64_t bloom[1]; + uint32_t buckets[1]; + uint32_t chains[1]; +} gnu_hash_one_symbol_t; + +typedef struct gnu_hash_long_chain { + uint32_t nbuckets; + uint32_t symoffset; + uint32_t bloom_size; + uint32_t bloom_shift; + uint64_t bloom[1]; + uint32_t buckets[1]; + uint32_t chains[4096]; +} gnu_hash_long_chain_t; + +typedef struct gnu_hash_four_symbols { + uint32_t nbuckets; + uint32_t symoffset; + uint32_t bloom_size; + uint32_t bloom_shift; + uint64_t bloom[1]; + uint32_t buckets[1]; + uint32_t chains[4]; +} gnu_hash_four_symbols_t; + +typedef struct sysv_hash_three_symbols { + uint32_t nbuckets; + uint32_t nchains; + uint32_t buckets[2]; + uint32_t chains[3]; +} sysv_hash_three_symbols_t; + +typedef struct version_definition { + Elf64_Verdef definition; + Elf64_Verdaux auxiliary; +} version_definition_t; + +static int failures; + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_u32(const char *name, uint32_t got, uint32_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %u expected %u\n", name, got, expected); + ++failures; +} + +static void check_uintptr(const char *name, + uintptr_t got, + uintptr_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_unknown(const char *name, + const kzt_guest_dynsym_lookup_result_t *result) +{ + char field[128]; + + snprintf(field, sizeof(field), "%s.status", name); + check_int(field, result->status, KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + snprintf(field, sizeof(field), "%s.binding", name); + check_int(field, result->binding, 0); + snprintf(field, sizeof(field), "%s.symbol-index", name); + check_u32(field, result->symbol_index, 0); + snprintf(field, sizeof(field), "%s.runtime-address", name); + check_uintptr(field, result->runtime_address, 0); +} + +static void add_region(fake_memory_t *memory, + uintptr_t guest_base, + const void *host_base, + size_t size) +{ + fake_region_t *region = &memory->regions[memory->region_count++]; + + region->guest_base = guest_base; + region->host_base = host_base; + region->size = size; +} + +static int fake_read_memory(uintptr_t guest_addr, + void *dst, + size_t size, + void *opaque) +{ + fake_memory_t *memory = opaque; + size_t i; + + if (memory->fail_addr && guest_addr == memory->fail_addr) { + return -1; + } + + for (i = 0; i < memory->region_count; ++i) { + const fake_region_t *region = &memory->regions[i]; + uintptr_t offset; + + if (guest_addr < region->guest_base) { + continue; + } + + offset = guest_addr - region->guest_base; + if (offset > region->size || size > region->size - offset) { + continue; + } + + memcpy(dst, (const char *)region->host_base + offset, size); + return 0; + } + + return -1; +} + +static kzt_guest_dynamic_field_t runtime_field(uint64_t value) +{ + return (kzt_guest_dynamic_field_t) { + .present = 1, + .value = value, + .address_semantics = KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS, + }; +} + +static kzt_guest_dynamic_field_t scalar_field(uint64_t value) +{ + return (kzt_guest_dynamic_field_t) { + .present = 1, + .value = value, + .address_semantics = KZT_GUEST_DYNAMIC_SCALAR, + }; +} + +static uint32_t gnu_hash(const char *name) +{ + uint32_t hash = 5381; + + while (*name) { + hash = hash * 33 + (unsigned char)*name++; + } + + return hash; +} + +static uint32_t sysv_hash(const char *name) +{ + uint32_t hash = 0; + + while (*name) { + uint32_t high; + + hash = (hash << 4) + (unsigned char)*name++; + high = hash & UINT32_C(0xf0000000); + if (high) { + hash ^= high >> 24; + } + hash &= ~high; + } + + return hash; +} + +static void test_gnu_hash_finds_global_symbol(void) +{ + static const char strings[] = "\0target\0"; + Elf64_Sym symbols[2] = { 0 }; + gnu_hash_one_symbol_t hash = { 0 }; + fake_memory_t memory = { 0 }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_dynamic_view_t view = { + .load_bias = TEST_LOAD_BIAS, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .has_null = 1, + .symtab = runtime_field(TEST_SYMTAB_ADDR), + .strtab = runtime_field(TEST_STRTAB_ADDR), + .syment = scalar_field(sizeof(Elf64_Sym)), + .strsz = scalar_field(sizeof(strings)), + .gnu_hash = runtime_field(TEST_GNU_HASH_ADDR), + }; + kzt_guest_dynsym_lookup_result_t result; + uint32_t symbol_hash = gnu_hash("target"); + + symbols[1].st_name = 1; + symbols[1].st_info = ELF_ST_INFO(STB_GLOBAL, STT_FUNC); + symbols[1].st_other = STV_DEFAULT; + symbols[1].st_shndx = 1; + symbols[1].st_value = 0x1234; + + hash.nbuckets = 1; + hash.symoffset = 1; + hash.bloom_size = 1; + hash.bloom_shift = 5; + hash.bloom[0] = UINT64_MAX; + hash.buckets[0] = 1; + hash.chains[0] = symbol_hash | 1; + + add_region(&memory, TEST_SYMTAB_ADDR, symbols, sizeof(symbols)); + add_region(&memory, TEST_STRTAB_ADDR, strings, sizeof(strings)); + add_region(&memory, TEST_GNU_HASH_ADDR, &hash, sizeof(hash)); + + memset(&result, 0xa5, sizeof(result)); + check_int("gnu.status", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &result), + KZT_GUEST_DYNSYM_LOOKUP_FOUND); + check_int("gnu.result-status", result.status, + KZT_GUEST_DYNSYM_LOOKUP_FOUND); + check_int("gnu.binding", result.binding, STB_GLOBAL); + check_u32("gnu.symbol-index", result.symbol_index, 1); + check_uintptr("gnu.runtime-address", result.runtime_address, + TEST_LOAD_BIAS + 0x1234); + + view.symtab.address_semantics = KZT_GUEST_DYNAMIC_ADDRESS_UNKNOWN; + check_int("gnu.unknown-address-semantics", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &result), + KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + check_unknown("gnu.unknown-address-semantics-result", &result); +} + +static void test_sysv_hash_finds_global_symbol(void) +{ + static const char strings[] = "\0target\0other\0"; + Elf64_Sym symbols[3] = { 0 }; + sysv_hash_three_symbols_t hash = { + .nbuckets = 2, + .nchains = 3, + .chains = { 0, 2, 0 }, + }; + fake_memory_t memory = { 0 }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_dynamic_view_t view = { + .load_bias = TEST_LOAD_BIAS, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .has_null = 1, + .symtab = runtime_field(TEST_SYMTAB_ADDR), + .strtab = runtime_field(TEST_STRTAB_ADDR), + .syment = scalar_field(sizeof(Elf64_Sym)), + .strsz = scalar_field(sizeof(strings)), + .hash = runtime_field(TEST_HASH_ADDR), + }; + kzt_guest_dynsym_lookup_result_t result; + + hash.buckets[sysv_hash("target") % hash.nbuckets] = 1; + symbols[1].st_name = 8; + symbols[1].st_info = ELF_ST_INFO(STB_GLOBAL, STT_FUNC); + symbols[1].st_other = STV_DEFAULT; + symbols[1].st_shndx = 1; + symbols[1].st_value = 0x1111; + symbols[2].st_name = 1; + symbols[2].st_info = ELF_ST_INFO(STB_GLOBAL, STT_FUNC); + symbols[2].st_other = STV_DEFAULT; + symbols[2].st_shndx = 1; + symbols[2].st_value = 0x5678; + + add_region(&memory, TEST_SYMTAB_ADDR, symbols, sizeof(symbols)); + add_region(&memory, TEST_STRTAB_ADDR, strings, sizeof(strings)); + add_region(&memory, TEST_HASH_ADDR, &hash, sizeof(hash)); + + memset(&result, 0xa5, sizeof(result)); + check_int("sysv.status", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &result), + KZT_GUEST_DYNSYM_LOOKUP_FOUND); + check_int("sysv.result-status", result.status, + KZT_GUEST_DYNSYM_LOOKUP_FOUND); + check_int("sysv.binding", result.binding, STB_GLOBAL); + check_u32("sysv.symbol-index", result.symbol_index, 2); + check_uintptr("sysv.runtime-address", result.runtime_address, + TEST_LOAD_BIAS + 0x5678); +} + +static void test_gnu_hash_skips_ineligible_definitions(void) +{ + static const char strings[] = "\0target\0"; + Elf64_Sym symbols[5] = { 0 }; + gnu_hash_four_symbols_t hash = { 0 }; + fake_memory_t memory = { 0 }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_dynamic_view_t view = { + .load_bias = TEST_LOAD_BIAS, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .has_null = 1, + .symtab = runtime_field(TEST_SYMTAB_ADDR), + .strtab = runtime_field(TEST_STRTAB_ADDR), + .syment = scalar_field(sizeof(Elf64_Sym)), + .strsz = scalar_field(sizeof(strings)), + .gnu_hash = runtime_field(TEST_GNU_HASH_ADDR), + }; + kzt_guest_dynsym_lookup_result_t result; + uint32_t symbol_hash = gnu_hash("target"); + size_t i; + + for (i = 1; i < 5; ++i) { + symbols[i].st_name = 1; + symbols[i].st_info = ELF_ST_INFO(STB_GLOBAL, STT_FUNC); + symbols[i].st_other = STV_DEFAULT; + symbols[i].st_shndx = 1; + symbols[i].st_value = 0x1000 + i; + } + symbols[1].st_shndx = SHN_UNDEF; + symbols[2].st_other = 2; + symbols[3].st_info = ELF_ST_INFO(STB_LOCAL, STT_FUNC); + symbols[4].st_other = STV_PROTECTED; + + hash.nbuckets = 1; + hash.symoffset = 1; + hash.bloom_size = 1; + hash.bloom_shift = 5; + hash.bloom[0] = UINT64_MAX; + hash.buckets[0] = 1; + for (i = 0; i < 3; ++i) { + hash.chains[i] = symbol_hash & ~UINT32_C(1); + } + hash.chains[3] = symbol_hash | 1; + + add_region(&memory, TEST_SYMTAB_ADDR, symbols, sizeof(symbols)); + add_region(&memory, TEST_STRTAB_ADDR, strings, sizeof(strings)); + add_region(&memory, TEST_GNU_HASH_ADDR, &hash, sizeof(hash)); + + check_int("eligibility.status", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &result), + KZT_GUEST_DYNSYM_LOOKUP_FOUND); + check_int("eligibility.binding", result.binding, STB_GLOBAL); + check_u32("eligibility.symbol-index", result.symbol_index, 4); + check_uintptr("eligibility.runtime-address", result.runtime_address, + TEST_LOAD_BIAS + 0x1004); +} + +static void test_gnu_hash_reports_weak_binding(void) +{ + static const char strings[] = "\0target\0"; + Elf64_Sym symbols[2] = { 0 }; + gnu_hash_one_symbol_t hash = { 0 }; + fake_memory_t memory = { 0 }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_dynamic_view_t view = { + .load_bias = TEST_LOAD_BIAS, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .has_null = 1, + .symtab = runtime_field(TEST_SYMTAB_ADDR), + .strtab = runtime_field(TEST_STRTAB_ADDR), + .syment = scalar_field(sizeof(Elf64_Sym)), + .strsz = scalar_field(sizeof(strings)), + .gnu_hash = runtime_field(TEST_GNU_HASH_ADDR), + }; + kzt_guest_dynsym_lookup_result_t result; + uint32_t symbol_hash = gnu_hash("target"); + + symbols[1].st_name = 1; + symbols[1].st_info = ELF_ST_INFO(STB_WEAK, STT_FUNC); + symbols[1].st_other = STV_DEFAULT; + symbols[1].st_shndx = 1; + symbols[1].st_value = 0x3456; + + hash.nbuckets = 1; + hash.symoffset = 1; + hash.bloom_size = 1; + hash.bloom_shift = 5; + hash.bloom[0] = UINT64_MAX; + hash.buckets[0] = 1; + hash.chains[0] = symbol_hash | 1; + + add_region(&memory, TEST_SYMTAB_ADDR, symbols, sizeof(symbols)); + add_region(&memory, TEST_STRTAB_ADDR, strings, sizeof(strings)); + add_region(&memory, TEST_GNU_HASH_ADDR, &hash, sizeof(hash)); + + check_int("weak.status", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &result), + KZT_GUEST_DYNSYM_LOOKUP_FOUND); + check_int("weak.binding", result.binding, STB_WEAK); + check_u32("weak.symbol-index", result.symbol_index, 1); + check_uintptr("weak.runtime-address", result.runtime_address, + TEST_LOAD_BIAS + 0x3456); +} + +static void test_gnu_hash_counts_all_loader_candidate_kinds(void) +{ + static const char strings[] = "\0target\0"; + static const unsigned char types[] = { + STT_NOTYPE, + STT_OBJECT, + STT_FUNC, + STT_COMMON, + STT_TLS, +#ifdef STT_GNU_IFUNC + STT_GNU_IFUNC, +#endif + }; + Elf64_Sym symbols[2] = { 0 }; + gnu_hash_one_symbol_t hash = { 0 }; + fake_memory_t memory = { 0 }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_dynamic_view_t view = { + .load_bias = TEST_LOAD_BIAS, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .has_null = 1, + .symtab = runtime_field(TEST_SYMTAB_ADDR), + .strtab = runtime_field(TEST_STRTAB_ADDR), + .syment = scalar_field(sizeof(Elf64_Sym)), + .strsz = scalar_field(sizeof(strings)), + .gnu_hash = runtime_field(TEST_GNU_HASH_ADDR), + }; + kzt_guest_dynsym_lookup_result_t result; + uint32_t symbol_hash = gnu_hash("target"); + size_t i; + + symbols[1].st_name = 1; + symbols[1].st_other = STV_DEFAULT; + symbols[1].st_shndx = 1; + symbols[1].st_value = 0x4567; + hash.nbuckets = 1; + hash.symoffset = 1; + hash.bloom_size = 1; + hash.bloom_shift = 63; + hash.bloom[0] = UINT64_MAX; + hash.buckets[0] = 1; + hash.chains[0] = symbol_hash | 1; + add_region(&memory, TEST_SYMTAB_ADDR, symbols, sizeof(symbols)); + add_region(&memory, TEST_STRTAB_ADDR, strings, sizeof(strings)); + add_region(&memory, TEST_GNU_HASH_ADDR, &hash, sizeof(hash)); + + for (i = 0; i < sizeof(types) / sizeof(types[0]); ++i) { + symbols[1].st_info = ELF_ST_INFO(STB_GLOBAL, types[i]); + check_int("loader-candidate.type", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + NULL, &result), + KZT_GUEST_DYNSYM_LOOKUP_FOUND); + } +#ifdef STB_GNU_UNIQUE + symbols[1].st_info = ELF_ST_INFO(STB_GNU_UNIQUE, STT_OBJECT); + check_int("loader-candidate.gnu-unique", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + NULL, &result), + KZT_GUEST_DYNSYM_LOOKUP_FOUND); + check_int("loader-candidate.gnu-unique-binding", + result.binding, STB_GNU_UNIQUE); +#endif +} + +static void test_versioned_symbol_matches_verdef(void) +{ + static const char strings[] = + "\0target\0GLIBC_2.2.5\0OTHER_1.0\0"; + Elf64_Sym symbols[2] = { 0 }; + Elf64_Half versions[2] = { 0, 2 }; + gnu_hash_one_symbol_t hash = { 0 }; + version_definition_t verdef = { + .definition = { + .vd_version = 1, + .vd_ndx = 2, + .vd_cnt = 1, + .vd_aux = sizeof(Elf64_Verdef), + }, + .auxiliary = { + .vda_name = 8, + }, + }; + fake_memory_t memory = { + .fail_addr = TEST_VERNEED_ADDR, + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_dynamic_view_t view = { + .load_bias = TEST_LOAD_BIAS, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .has_null = 1, + .symtab = runtime_field(TEST_SYMTAB_ADDR), + .strtab = runtime_field(TEST_STRTAB_ADDR), + .syment = scalar_field(sizeof(Elf64_Sym)), + .strsz = scalar_field(sizeof(strings)), + .gnu_hash = runtime_field(TEST_GNU_HASH_ADDR), + .versym = runtime_field(TEST_VERSYM_ADDR), + .verneed = runtime_field(TEST_VERNEED_ADDR), + .verneednum = scalar_field(1), + .verdef = runtime_field(TEST_VERDEF_ADDR), + .verdefnum = scalar_field(1), + }; + kzt_guest_dynsym_lookup_result_t result; + uint32_t symbol_hash = gnu_hash("target"); + + symbols[1].st_name = 1; + symbols[1].st_info = ELF_ST_INFO(STB_GLOBAL, STT_FUNC); + symbols[1].st_other = STV_DEFAULT; + symbols[1].st_shndx = 1; + symbols[1].st_value = 0x789a; + + hash.nbuckets = 1; + hash.symoffset = 1; + hash.bloom_size = 1; + hash.bloom_shift = 5; + hash.bloom[0] = UINT64_MAX; + hash.buckets[0] = 1; + hash.chains[0] = symbol_hash | 1; + + add_region(&memory, TEST_SYMTAB_ADDR, symbols, sizeof(symbols)); + add_region(&memory, TEST_STRTAB_ADDR, strings, sizeof(strings)); + add_region(&memory, TEST_GNU_HASH_ADDR, &hash, sizeof(hash)); + add_region(&memory, TEST_VERSYM_ADDR, versions, sizeof(versions)); + add_region(&memory, TEST_VERDEF_ADDR, &verdef, sizeof(verdef)); + + check_int("version-match.status", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_VERSIONED, "GLIBC_2.2.5", &result), + KZT_GUEST_DYNSYM_LOOKUP_FOUND); + check_int("version-match.binding", result.binding, STB_GLOBAL); + check_u32("version-match.symbol-index", result.symbol_index, 1); + check_uintptr("version-match.runtime-address", result.runtime_address, + TEST_LOAD_BIAS + 0x789a); + + check_int("version-mismatch.status", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_VERSIONED, "OTHER_1.0", &result), + KZT_GUEST_DYNSYM_LOOKUP_NOT_FOUND); + check_int("version-mismatch.result-status", result.status, + KZT_GUEST_DYNSYM_LOOKUP_NOT_FOUND); + check_int("version-mismatch.binding", result.binding, 0); + check_u32("version-mismatch.symbol-index", result.symbol_index, 0); + check_uintptr("version-mismatch.runtime-address", + result.runtime_address, 0); +} + +static void test_unversioned_symbol_rejects_hidden_version(void) +{ + static const char strings[] = "\0target\0"; + Elf64_Sym symbols[2] = { 0 }; + Elf64_Half versions[2] = { 0, 0x8002 }; + gnu_hash_one_symbol_t hash = { 0 }; + fake_memory_t memory = { 0 }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_dynamic_view_t view = { + .load_bias = TEST_LOAD_BIAS, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .has_null = 1, + .symtab = runtime_field(TEST_SYMTAB_ADDR), + .strtab = runtime_field(TEST_STRTAB_ADDR), + .syment = scalar_field(sizeof(Elf64_Sym)), + .strsz = scalar_field(sizeof(strings)), + .gnu_hash = runtime_field(TEST_GNU_HASH_ADDR), + .versym = runtime_field(TEST_VERSYM_ADDR), + }; + kzt_guest_dynsym_lookup_result_t result; + uint32_t symbol_hash = gnu_hash("target"); + + symbols[1].st_name = 1; + symbols[1].st_info = ELF_ST_INFO(STB_GLOBAL, STT_FUNC); + symbols[1].st_other = STV_DEFAULT; + symbols[1].st_shndx = 1; + symbols[1].st_value = 0x4321; + hash.nbuckets = 1; + hash.symoffset = 1; + hash.bloom_size = 1; + hash.bloom_shift = 63; + hash.bloom[0] = UINT64_MAX; + hash.buckets[0] = 1; + hash.chains[0] = symbol_hash | 1; + + add_region(&memory, TEST_SYMTAB_ADDR, symbols, sizeof(symbols)); + add_region(&memory, TEST_STRTAB_ADDR, strings, sizeof(strings)); + add_region(&memory, TEST_GNU_HASH_ADDR, &hash, sizeof(hash)); + add_region(&memory, TEST_VERSYM_ADDR, versions, sizeof(versions)); + + check_int("hidden-version.status", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &result), + KZT_GUEST_DYNSYM_LOOKUP_NOT_FOUND); + versions[1] = 2; + check_int("default-version.status", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &result), + KZT_GUEST_DYNSYM_LOOKUP_FOUND); +} + +static void test_untrusted_gnu_hash_returns_unknown(void) +{ + static const char strings[] = "\0target\0"; + Elf64_Sym symbols[2] = { 0 }; + gnu_hash_one_symbol_t bad_header = { + .nbuckets = 1, + .symoffset = 1, + .bloom_size = 0, + .bloom_shift = 5, + }; + gnu_hash_one_symbol_t overflow_header = { + .nbuckets = 1, + .symoffset = 1, + .bloom_size = 1, + .bloom_shift = 5, + }; + gnu_hash_one_symbol_t bad_shift = { + .nbuckets = 1, + .symoffset = 1, + .bloom_size = 1, + .bloom_shift = 64, + .bloom = { UINT64_MAX }, + }; + sysv_hash_three_symbols_t valid_sysv_fallback = { + .nbuckets = 2, + .nchains = 2, + }; + gnu_hash_long_chain_t long_chain = { + .nbuckets = 1, + .symoffset = 1, + .bloom_size = 1, + .bloom_shift = 5, + .bloom = { UINT64_MAX }, + .buckets = { 1 }, + }; + fake_memory_t memory = { + .fail_addr = TEST_GNU_HASH_ADDR, + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_dynamic_view_t view = { + .load_bias = TEST_LOAD_BIAS, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .has_null = 1, + .symtab = runtime_field(TEST_SYMTAB_ADDR), + .strtab = runtime_field(TEST_STRTAB_ADDR), + .syment = scalar_field(sizeof(Elf64_Sym)), + .strsz = scalar_field(sizeof(strings)), + .gnu_hash = runtime_field(TEST_GNU_HASH_ADDR), + }; + kzt_guest_dynsym_lookup_result_t result; + uintptr_t overflow_addr = UINTPTR_MAX - 15; + + memset(&result, 0xa5, sizeof(result)); + check_int("read-failure.return", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &result), + KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + check_unknown("read-failure", &result); + + memory.fail_addr = 0; + symbols[1].st_name = 1; + symbols[1].st_info = ELF_ST_INFO(STB_GLOBAL, STT_FUNC); + symbols[1].st_other = STV_DEFAULT; + symbols[1].st_shndx = 1; + symbols[1].st_value = 0x1234; + valid_sysv_fallback + .buckets[sysv_hash("target") % valid_sysv_fallback.nbuckets] = 1; + add_region(&memory, TEST_GNU_HASH_ADDR, &bad_header, + sizeof(bad_header)); + add_region(&memory, TEST_HASH_ADDR, &valid_sysv_fallback, + sizeof(valid_sysv_fallback)); + add_region(&memory, TEST_SYMTAB_ADDR, symbols, sizeof(symbols)); + add_region(&memory, TEST_STRTAB_ADDR, strings, sizeof(strings)); + view.hash = runtime_field(TEST_HASH_ADDR); + memset(&result, 0xa5, sizeof(result)); + check_int("bad-header.return", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &result), + KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + check_unknown("bad-header", &result); + + memset(&memory, 0, sizeof(memory)); + add_region(&memory, TEST_GNU_HASH_ADDR, &bad_shift, sizeof(bad_shift)); + memset(&result, 0xa5, sizeof(result)); + check_int("bad-shift.return", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &result), + KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + check_unknown("bad-shift", &result); + + memset(&memory, 0, sizeof(memory)); + add_region(&memory, overflow_addr, &overflow_header, + sizeof(uint32_t) * 4); + view.gnu_hash = runtime_field(overflow_addr); + memset(&result, 0xa5, sizeof(result)); + check_int("overflow.return", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &result), + KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + check_unknown("overflow", &result); + + memset(&memory, 0, sizeof(memory)); + view.gnu_hash = runtime_field(TEST_GNU_HASH_ADDR); + add_region(&memory, TEST_GNU_HASH_ADDR, &long_chain, + sizeof(long_chain)); + add_region(&memory, TEST_SYMTAB_ADDR, symbols, sizeof(symbols)); + add_region(&memory, TEST_STRTAB_ADDR, strings, sizeof(strings)); + memset(&result, 0xa5, sizeof(result)); + check_int("chain-limit.return", + kzt_guest_dynsym_lookup( + &view, &reader_ops, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &result), + KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN); + check_unknown("chain-limit", &result); +} + +int main(void) +{ + test_gnu_hash_finds_global_symbol(); + test_sysv_hash_finds_global_symbol(); + test_gnu_hash_skips_ineligible_definitions(); + test_gnu_hash_reports_weak_binding(); + test_gnu_hash_counts_all_loader_candidate_kinds(); + test_versioned_symbol_matches_verdef(); + test_unversioned_symbol_rejects_hidden_version(); + test_untrusted_gnu_hash_returns_unknown(); + + if (failures) { + fprintf(stderr, "FAIL: %d assertion(s)\n", failures); + return 1; + } + + puts("PASS"); + return 0; +} diff --git a/tests/unit/kzt/test_guest_symbol_scope.c b/tests/unit/kzt/test_guest_symbol_scope.c new file mode 100644 index 00000000000..8def9101b68 --- /dev/null +++ b/tests/unit/kzt/test_guest_symbol_scope.c @@ -0,0 +1,1149 @@ +#include +#include +#include + +#include "elf.h" +#include "target/i386/latx/include/kzt_guest_symbol_scope.h" + +#ifndef STV_HIDDEN +#define STV_HIDDEN 2 +#endif + +#define TEST_AUDIT_ANY_PLT_MASK UINT64_C(0x2000000000000) + +typedef struct test_link_map { + uint64_t l_addr; + uint64_t l_name; + uint64_t l_ld; + uint64_t l_next; + uint64_t l_prev; + uint64_t l_real; + uint64_t l_ns; + unsigned char private_before_audit_flags[ + 0x350 - 7 * sizeof(uint64_t)]; + uint64_t audit_flags; + unsigned char private_before_reloc_result[0x378 - 0x358]; + uint64_t reloc_result; + unsigned char private_before_scope_max[0x3c0 - 0x380]; + uint64_t l_scope_max; + uint64_t l_scope; + uint64_t l_local_scope[2]; +} test_link_map_t; + +typedef struct test_scope_elem { + uint64_t r_list; + uint32_t r_nlist; + uint32_t padding; +} test_scope_elem_t; + +typedef struct test_sysv_hash { + uint32_t nbucket; + uint32_t nchain; + uint32_t buckets[1]; + uint32_t chains[2]; +} test_sysv_hash_t; + +typedef struct test_object { + test_link_map_t map; + Elf64_Dyn dynamic[6]; + Elf64_Sym symbols[2]; + char strings[16]; + test_sysv_hash_t hash; +} test_object_t; + +typedef struct fake_memory { + uintptr_t base; + const void *data; + size_t size; + uintptr_t fail_addr; + uintptr_t unstable_addr; + size_t unstable_after_reads; + size_t unstable_reads; + uintptr_t unstable_value; +} fake_memory_t; + +typedef struct test_scope_storage { + test_scope_elem_t scope_elems[KZT_GUEST_SYMBOL_SCOPE_LIST_LIMIT]; + uintptr_t scope_array[KZT_GUEST_SYMBOL_SCOPE_LIST_LIMIT + 1]; + uintptr_t scope_maps[KZT_GUEST_SYMBOL_SCOPE_MAP_LIMIT]; +} test_scope_storage_t; + +static int failures; +static test_scope_storage_t scope_storage; + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_size(const char *name, size_t got, size_t expected) +{ + if (got == expected) { + return; + } + fprintf(stderr, "%s: got %zu expected %zu\n", name, got, expected); + ++failures; +} + +static void check_uintptr(const char *name, + uintptr_t got, + uintptr_t expected) +{ + if (got == expected) { + return; + } + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static int fake_read_memory(uintptr_t guest_addr, + void *dst, + size_t size, + void *opaque) +{ + fake_memory_t *memory = opaque; + uintptr_t offset; + + if (memory->fail_addr && guest_addr == memory->fail_addr) { + return -1; + } + if (memory->unstable_addr && guest_addr == memory->unstable_addr && + size == sizeof(memory->unstable_value) && + ++memory->unstable_reads > memory->unstable_after_reads) { + memcpy(dst, &memory->unstable_value, size); + return 0; + } + if (guest_addr >= memory->base) { + offset = guest_addr - memory->base; + if (offset <= memory->size && size <= memory->size - offset) { + memcpy(dst, (const char *)memory->data + offset, size); + return 0; + } + } + if (guest_addr >= (uintptr_t)&scope_storage) { + offset = guest_addr - (uintptr_t)&scope_storage; + if (offset <= sizeof(scope_storage) && + size <= sizeof(scope_storage) - offset) { + memcpy(dst, (const char *)&scope_storage + offset, size); + return 0; + } + } + return -1; +} + +static void init_object(test_object_t *object, + unsigned char binding, + uintptr_t runtime_address) +{ + memset(object, 0, sizeof(*object)); + + object->map.l_ld = (uintptr_t)object->dynamic; + object->map.l_real = (uintptr_t)&object->map; + object->dynamic[0].d_tag = DT_SYMTAB; + object->dynamic[0].d_un.d_ptr = (uintptr_t)object->symbols; + object->dynamic[1].d_tag = DT_STRTAB; + object->dynamic[1].d_un.d_ptr = (uintptr_t)object->strings; + object->dynamic[2].d_tag = DT_SYMENT; + object->dynamic[2].d_un.d_val = sizeof(Elf64_Sym); + object->dynamic[3].d_tag = DT_STRSZ; + object->dynamic[3].d_un.d_val = sizeof(object->strings); + object->dynamic[4].d_tag = DT_HASH; + object->dynamic[4].d_un.d_ptr = (uintptr_t)&object->hash; + object->dynamic[5].d_tag = DT_NULL; + + memcpy(object->strings, "\0target\0", sizeof("\0target\0")); + object->symbols[1].st_name = 1; + object->symbols[1].st_info = ELF_ST_INFO(binding, STT_FUNC); + object->symbols[1].st_other = STV_DEFAULT; + object->symbols[1].st_shndx = SHN_ABS; + object->symbols[1].st_value = runtime_address; + + object->hash.nbucket = 1; + object->hash.nchain = 2; + object->hash.buckets[0] = 1; +} + +static void hide_object_symbol(test_object_t *object) +{ + object->hash.buckets[0] = 0; +} + +static void link_objects(test_object_t *objects, size_t count) +{ + size_t i; + + for (i = 0; i < count; ++i) { + objects[i].map.l_prev = + i == 0 ? 0 : (uintptr_t)&objects[i - 1].map; + objects[i].map.l_next = + i + 1 < count ? (uintptr_t)&objects[i + 1].map : 0; + } +} + +static void set_source_scope(test_link_map_t *source, + test_link_map_t *const *maps, + size_t map_count) +{ + size_t i; + + memset(&scope_storage, 0, sizeof(scope_storage)); + scope_storage.scope_elems[0].r_list = + (uintptr_t)scope_storage.scope_maps; + scope_storage.scope_elems[0].r_nlist = (uint32_t)map_count; + scope_storage.scope_array[0] = + (uintptr_t)&scope_storage.scope_elems[0]; + for (i = 0; i < map_count; ++i) { + scope_storage.scope_maps[i] = (uintptr_t)maps[i]; + } + source->l_scope_max = 2; + source->l_scope = (uintptr_t)scope_storage.scope_array; + source->l_local_scope[0] = (uintptr_t)&scope_storage.scope_elems[0]; +} + +static void set_source_scope_from_chain(test_link_map_t *source) +{ + test_link_map_t *maps[KZT_GUEST_SYMBOL_SCOPE_MAP_LIMIT]; + test_link_map_t *current = source; + size_t count = 0; + + while (current && count < KZT_GUEST_SYMBOL_SCOPE_MAP_LIMIT) { + maps[count++] = current; + current = (test_link_map_t *)(uintptr_t)current->l_next; + } + set_source_scope(source, maps, count); +} + +static kzt_guest_symbol_scope_request_t scope_request( + uintptr_t source_link_map, uintptr_t namespace_head, const char *symbol, + kzt_symbol_version_evidence_t version_evidence, const char *version) +{ + return (kzt_guest_symbol_scope_request_t) { + .source = { + .link_map_addr = source_link_map, + .generation = 1, + .namespace_id = 0, + .namespace_head = namespace_head, + .layout = KZT_GUEST_SCOPE_LAYOUT_GLIBC_2_39_C591A5DF, + }, + .symbol = symbol, + .version_evidence = version_evidence, + .version = version, + .reference_binding = STB_GLOBAL, + .reference_type = STT_FUNC, + .reference_visibility = STV_DEFAULT, + }; +} + +static kzt_guest_symbol_scope_status_t scope_check( + uintptr_t namespace_head, uintptr_t selected_provider_link_map, + uintptr_t selected_provider_address, + const char *symbol, kzt_symbol_version_evidence_t version_evidence, + const char *version, const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_symbol_scope_result_t *result) +{ + test_link_map_t *source = (test_link_map_t *)namespace_head; + kzt_guest_symbol_scope_request_t request; + + if (source && !source->l_scope) { + set_source_scope_from_chain(source); + } + request = scope_request(namespace_head, namespace_head, symbol, + version_evidence, version); + return kzt_guest_symbol_scope_check( + &request, selected_provider_link_map, selected_provider_address, + reader_ops, result); +} + +static kzt_guest_symbol_scope_status_t scope_discover( + uintptr_t namespace_head, const char *symbol, + kzt_symbol_version_evidence_t version_evidence, const char *version, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_symbol_scope_result_t *result) +{ + test_link_map_t *source = (test_link_map_t *)namespace_head; + kzt_guest_symbol_scope_request_t request; + + if (source && !source->l_scope) { + set_source_scope_from_chain(source); + } + request = scope_request(namespace_head, namespace_head, symbol, + version_evidence, version); + return kzt_guest_symbol_scope_discover(&request, reader_ops, result); +} + +static kzt_guest_symbol_scope_status_t scope_revalidate( + const kzt_guest_symbol_scope_result_t *proof, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_symbol_scope_result_t *result) +{ + kzt_guest_symbol_scope_request_t request = scope_request( + proof->scope_identity.source.link_map_addr, + proof->scope_identity.source.namespace_head, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL); + + request.source = proof->scope_identity.source; + return kzt_guest_symbol_scope_revalidate( + proof, &request, reader_ops, result); +} + +static void test_namespace_local_provider_outside_source_scope_is_rejected(void) +{ + test_object_t objects[2]; + test_link_map_t *source_maps[1]; + fake_memory_t memory = { + .base = (uintptr_t)objects, + .data = objects, + .size = sizeof(objects), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + + init_object(&objects[0], STB_LOCAL, 0); + hide_object_symbol(&objects[0]); + init_object(&objects[1], STB_GLOBAL, 0x7171); + link_objects(objects, 2); + source_maps[0] = &objects[0].map; + set_source_scope(&objects[0].map, source_maps, 1); + + check_int("local-outside-scope.status", + scope_check( + (uintptr_t)&objects[0].map, + (uintptr_t)&objects[1].map, + 0x7171, + "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + NULL, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("local-outside-scope.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_PROVIDER_MISMATCH); + check_size("local-outside-scope.candidate-count", + result.candidate_count, 0); +} + +static void test_unique_global_provider_is_safe(void) +{ + test_object_t objects[2]; + fake_memory_t memory = { + .base = (uintptr_t)objects, + .data = objects, + .size = sizeof(objects), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + + init_object(&objects[0], STB_LOCAL, 0); + hide_object_symbol(&objects[0]); + init_object(&objects[1], STB_GLOBAL, 0x1234); + link_objects(objects, 2); + + memset(&result, 0xa5, sizeof(result)); + check_int("unique.status", + scope_check( + (uintptr_t)&objects[0].map, + (uintptr_t)&objects[1].map, + 0x1234, + "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + NULL, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_SAFE); + check_int("unique.result-status", result.status, + KZT_GUEST_SYMBOL_SCOPE_SAFE); + check_int("unique.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER); + check_size("unique.candidate-count", result.candidate_count, 1); + check_int("unique.scope-complete", result.scope_complete, 1); + check_int("unique.lookup-order-known", result.lookup_order_known, 1); + check_uintptr("unique.link-map", result.selected_provider_link_map, + (uintptr_t)&objects[1].map); + check_uintptr("unique.address", result.selected_provider_address, 0x1234); + check_int("unique.binding", result.selected_provider_binding, STB_GLOBAL); + check_int("unique.type", result.selected_provider_type, STT_FUNC); + check_int("unique.visibility", result.selected_provider_visibility, + STV_DEFAULT); + check_uintptr("unique.scope-source", + result.scope_identity.source.link_map_addr, + (uintptr_t)&objects[0].map); + check_size("unique.scope-map-count", + result.scope_identity.scope_map_count, 2); + + memset(&result, 0xa5, sizeof(result)); + check_int("discover.status", + scope_discover( + (uintptr_t)&objects[0].map, + "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + NULL, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_SAFE); + check_int("discover.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER); + check_size("discover.candidate-count", result.candidate_count, 1); + check_uintptr("discover.link-map", + result.selected_provider_link_map, + (uintptr_t)&objects[1].map); + check_uintptr("discover.address", + result.selected_provider_address, 0x1234); + check_int("discover.binding", + result.selected_provider_binding, STB_GLOBAL); +} + +static void test_multiple_definitions_count_weak_candidate(void) +{ + test_object_t objects[2]; + fake_memory_t memory = { + .base = (uintptr_t)objects, + .data = objects, + .size = sizeof(objects), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + + init_object(&objects[0], STB_GLOBAL, 0x1111); + init_object(&objects[1], STB_WEAK, 0x2222); + link_objects(objects, 2); + + check_int("multiple.status", + scope_check( + (uintptr_t)&objects[0].map, + (uintptr_t)&objects[0].map, + 0x1111, + "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + NULL, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_SAFE); + check_int("multiple.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER); + check_size("multiple.candidate-count", result.candidate_count, 2); + check_int("multiple.scope-complete", result.scope_complete, 1); + check_int("multiple.lookup-order-known", result.lookup_order_known, 1); + check_uintptr("multiple.selected-link-map", + result.selected_provider_link_map, + (uintptr_t)&objects[0].map); + check_int("multiple.selected-binding", + result.selected_provider_binding, STB_GLOBAL); +} + +static void test_multiple_strong_definitions_select_first_provider(void) +{ + test_object_t objects[2]; + fake_memory_t memory = { + .base = (uintptr_t)objects, + .data = objects, + .size = sizeof(objects), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + + init_object(&objects[0], STB_GLOBAL, 0x2111); + init_object(&objects[1], STB_GLOBAL, 0x2222); + link_objects(objects, 2); + + check_int("multiple-strong.status", + scope_check( + (uintptr_t)&objects[0].map, + (uintptr_t)&objects[0].map, + 0x2111, + "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + NULL, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_SAFE); + check_int("multiple-strong.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER); + check_size("multiple-strong.candidate-count", + result.candidate_count, 2); + check_int("multiple-strong.scope-complete", + result.scope_complete, 1); + check_int("multiple-strong.lookup-order-known", + result.lookup_order_known, 1); + check_uintptr("multiple-strong.selected", + result.selected_provider_link_map, + (uintptr_t)&objects[0].map); +} + +static void test_unique_weak_provider_requires_guest(void) +{ + test_object_t object; + fake_memory_t memory = { + .base = (uintptr_t)&object, + .data = &object, + .size = sizeof(object), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + + init_object(&object, STB_WEAK, 0x3333); + + check_int("weak.status", + scope_check( + (uintptr_t)&object.map, + (uintptr_t)&object.map, + 0x3333, + "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + NULL, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("weak.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_UNSUPPORTED_PROVIDER_BINDING); + check_size("weak.candidate-count", result.candidate_count, 1); + check_uintptr("weak.unique-link-map", + result.selected_provider_link_map, + (uintptr_t)&object.map); + check_int("weak.unique-binding", + result.selected_provider_binding, STB_WEAK); +} + +static void test_non_function_or_protected_provider_requires_guest(void) +{ + test_object_t object; + fake_memory_t memory = { + .base = (uintptr_t)&object, + .data = &object, + .size = sizeof(object), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + + init_object(&object, STB_GLOBAL, 0x3535); + object.symbols[1].st_info = ELF_ST_INFO(STB_GLOBAL, STT_OBJECT); + check_int("provider-type.status", + scope_discover( + (uintptr_t)&object.map, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, + &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("provider-type.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_SEMANTICS_UNSUPPORTED); + + object.symbols[1].st_info = ELF_ST_INFO(STB_GLOBAL, STT_FUNC); + object.symbols[1].st_other = STV_PROTECTED; + check_int("provider-visibility.status", + scope_discover( + (uintptr_t)&object.map, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, + &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("provider-visibility.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_SEMANTICS_UNSUPPORTED); +} + +#ifdef STB_GNU_UNIQUE +static void test_unique_gnu_binding_requires_guest(void) +{ + test_object_t object; + fake_memory_t memory = { + .base = (uintptr_t)&object, + .data = &object, + .size = sizeof(object), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + + init_object(&object, STB_GNU_UNIQUE, 0x3434); + + check_int("gnu-unique.status", + scope_check( + (uintptr_t)&object.map, + (uintptr_t)&object.map, + 0x3434, + "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + NULL, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("gnu-unique.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_UNSUPPORTED_PROVIDER_BINDING); + check_size("gnu-unique.candidate-count", result.candidate_count, 1); + check_int("gnu-unique.binding", + result.selected_provider_binding, STB_GNU_UNIQUE); +} +#endif + +static void test_selected_provider_must_match_unique_candidate(void) +{ + test_object_t objects[2]; + fake_memory_t memory = { + .base = (uintptr_t)objects, + .data = objects, + .size = sizeof(objects), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + + init_object(&objects[0], STB_LOCAL, 0); + hide_object_symbol(&objects[0]); + init_object(&objects[1], STB_GLOBAL, 0x4444); + link_objects(objects, 2); + + check_int("mismatch.status", + scope_check( + (uintptr_t)&objects[0].map, + (uintptr_t)&objects[0].map, + 0x4444, + "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + NULL, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("mismatch.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_PROVIDER_MISMATCH); + check_size("mismatch.candidate-count", result.candidate_count, 1); + check_uintptr("mismatch.actual-link-map", + result.selected_provider_link_map, + (uintptr_t)&objects[1].map); + check_int("mismatch.scope-complete", result.scope_complete, 1); + + check_int("mismatch-address.status", + scope_check( + (uintptr_t)&objects[0].map, + (uintptr_t)&objects[1].map, 0x4445, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, + &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("mismatch-address.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_PROVIDER_MISMATCH); +} + +static void test_incomplete_scope_fails_open(void) +{ + test_object_t objects[2]; + fake_memory_t memory = { + .base = (uintptr_t)objects, + .data = objects, + .size = sizeof(objects), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + + init_object(&objects[0], STB_LOCAL, 0); + hide_object_symbol(&objects[0]); + init_object(&objects[1], STB_GLOBAL, 0x5555); + link_objects(objects, 2); + memory.fail_addr = (uintptr_t)&objects[1].hash; + + check_int("incomplete.status", + scope_check( + (uintptr_t)&objects[0].map, + (uintptr_t)&objects[1].map, + 0x5555, + "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + NULL, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("incomplete.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE); + check_int("incomplete.scope-complete", result.scope_complete, 0); + check_int("incomplete.lookup-order-known", + result.lookup_order_known, 0); + check_uintptr("incomplete.no-unique-link-map", + result.selected_provider_link_map, 0); +} + +static void test_scope_pointer_read_failure_fails_open(void) +{ + test_object_t object; + test_link_map_t *maps[1]; + fake_memory_t memory = { + .base = (uintptr_t)&object, + .data = &object, + .size = sizeof(object), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + + init_object(&object, STB_GLOBAL, 0x5757); + maps[0] = &object.map; + set_source_scope(&object.map, maps, 1); + memory.fail_addr = + (uintptr_t)&object.map + offsetof(test_link_map_t, l_scope); + + check_int("scope-read-failure.status", + scope_discover( + (uintptr_t)&object.map, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, + &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("scope-read-failure.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE); +} + +static void test_scope_pointer_instability_fails_open(void) +{ + test_object_t object; + test_link_map_t *maps[1]; + fake_memory_t memory = { + .base = (uintptr_t)&object, + .data = &object, + .size = sizeof(object), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + + init_object(&object, STB_GLOBAL, 0x5858); + maps[0] = &object.map; + set_source_scope(&object.map, maps, 1); + memory.unstable_addr = + (uintptr_t)&object.map + offsetof(test_link_map_t, l_scope); + memory.unstable_after_reads = 1; + + check_int("scope-pointer-unstable.status", + scope_discover( + (uintptr_t)&object.map, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, + &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("scope-pointer-unstable.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_STALE); +} + +static void test_duplicate_scope_map_fails_open(void) +{ + test_object_t object; + test_link_map_t *maps[2]; + fake_memory_t memory = { + .base = (uintptr_t)&object, + .data = &object, + .size = sizeof(object), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + + init_object(&object, STB_GLOBAL, 0x5959); + maps[0] = &object.map; + maps[1] = &object.map; + set_source_scope(&object.map, maps, 2); + + check_int("duplicate.status", + scope_discover( + (uintptr_t)&object.map, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, + &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("duplicate.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_DUPLICATE); +} + +static void test_cross_namespace_scope_map_fails_open(void) +{ + test_object_t objects[2]; + test_link_map_t *maps[2]; + fake_memory_t memory = { + .base = (uintptr_t)objects, + .data = objects, + .size = sizeof(objects), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + + init_object(&objects[0], STB_LOCAL, 0); + hide_object_symbol(&objects[0]); + init_object(&objects[1], STB_GLOBAL, 0x5a5a); + objects[0].map.l_next = (uintptr_t)&objects[1].map; + objects[1].map.l_prev = 0; + maps[0] = &objects[0].map; + maps[1] = &objects[1].map; + set_source_scope(&objects[0].map, maps, 2); + + check_int("cross-namespace.status", + scope_discover( + (uintptr_t)&objects[0].map, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, + &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("cross-namespace.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_CROSS_NAMESPACE); +} + +static void test_layout_and_private_semantics_fail_open(void) +{ + test_object_t object; + fake_memory_t memory = { + .base = (uintptr_t)&object, + .data = &object, + .size = sizeof(object), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_request_t request; + kzt_guest_symbol_scope_result_t result; + + init_object(&object, STB_GLOBAL, 0x5b5b); + set_source_scope_from_chain(&object.map); + request = scope_request( + (uintptr_t)&object.map, (uintptr_t)&object.map, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL); + request.source.layout = KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; + check_int("layout.status", + kzt_guest_symbol_scope_discover( + &request, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("layout.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_LAYOUT_UNSUPPORTED); + + request.source.layout = KZT_GUEST_SCOPE_LAYOUT_GLIBC_2_39_C591A5DF; + object.map.l_real = 0; + check_int("private-semantics.status", + kzt_guest_symbol_scope_discover( + &request, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("private-semantics.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_SEMANTICS_UNSUPPORTED); +} + +static void test_loader_audit_state_fails_open(void) +{ + test_object_t object; + fake_memory_t memory = { + .base = (uintptr_t)&object, + .data = &object, + .size = sizeof(object), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + + init_object(&object, STB_GLOBAL, 0x5d5d); + object.map.audit_flags = TEST_AUDIT_ANY_PLT_MASK; + check_int("audit-flags.status", + scope_discover( + (uintptr_t)&object.map, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, + &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("audit-flags.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_AUDIT_UNSUPPORTED); + + object.map.audit_flags = 0; + object.map.reloc_result = 0x1234; + check_int("audit-reloc-result.status", + scope_discover( + (uintptr_t)&object.map, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, + &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("audit-reloc-result.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_AUDIT_UNSUPPORTED); +} + +static void test_version_and_reference_evidence_fail_open(void) +{ + test_object_t object; + fake_memory_t memory = { + .base = (uintptr_t)&object, + .data = &object, + .size = sizeof(object), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_request_t request; + kzt_guest_symbol_scope_result_t result; + + init_object(&object, STB_GLOBAL, 0x5c5c); + set_source_scope_from_chain(&object.map); + request = scope_request( + (uintptr_t)&object.map, (uintptr_t)&object.map, "target", + KZT_SYMBOL_VERSION_UNKNOWN, NULL); + check_int("version-evidence.status", + kzt_guest_symbol_scope_discover( + &request, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + + request.version_evidence = KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + request.reference_visibility = STV_HIDDEN; + check_int("visibility-evidence.status", + kzt_guest_symbol_scope_discover( + &request, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("visibility-evidence.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_UNSUPPORTED_REFERENCE); + + request.reference_visibility = STV_DEFAULT; + request.reference_type = STT_OBJECT; + check_int("reference-type.status", + kzt_guest_symbol_scope_discover( + &request, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("reference-type.reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_UNSUPPORTED_REFERENCE); +} + +static void test_scope_revalidation_detects_change(void) +{ + test_object_t object; + fake_memory_t memory = { + .base = (uintptr_t)&object, + .data = &object, + .size = sizeof(object), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t proof; + kzt_guest_symbol_scope_result_t revalidated; + + init_object(&object, STB_GLOBAL, 0x6666); + check_int("revalidate.proof", + scope_check( + (uintptr_t)&object.map, + (uintptr_t)&object.map, + 0x6666, + "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + NULL, &reader_ops, &proof), + KZT_GUEST_SYMBOL_SCOPE_SAFE); + check_int("revalidate.stable", + scope_revalidate( + &proof, &reader_ops, &revalidated), + KZT_GUEST_SYMBOL_SCOPE_SAFE); + check_int("revalidate.stable-reason", revalidated.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER); + + object.map.l_addr = 0x1000; + check_int("revalidate.changed", + scope_revalidate( + &proof, &reader_ops, &revalidated), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("revalidate.changed-reason", revalidated.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_STALE); + check_int("revalidate.changed-complete", + revalidated.scope_complete, 0); + check_int("revalidate.changed-order-known", + revalidated.lookup_order_known, 0); +} + +static void test_scope_revalidation_detects_member_change(void) +{ + test_object_t objects[2]; + test_link_map_t *initial_maps[2]; + test_link_map_t *changed_maps[1]; + fake_memory_t memory = { + .base = (uintptr_t)objects, + .data = objects, + .size = sizeof(objects), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t proof; + kzt_guest_symbol_scope_result_t revalidated; + + init_object(&objects[0], STB_LOCAL, 0); + hide_object_symbol(&objects[0]); + init_object(&objects[1], STB_GLOBAL, 0x6767); + link_objects(objects, 2); + initial_maps[0] = &objects[0].map; + initial_maps[1] = &objects[1].map; + set_source_scope(&objects[0].map, initial_maps, 2); + check_int("scope-member-change.proof", + scope_check( + (uintptr_t)&objects[0].map, + (uintptr_t)&objects[1].map, 0x6767, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, + &reader_ops, &proof), + KZT_GUEST_SYMBOL_SCOPE_SAFE); + + changed_maps[0] = &objects[0].map; + set_source_scope(&objects[0].map, changed_maps, 1); + check_int("scope-member-change.status", + scope_revalidate(&proof, &reader_ops, &revalidated), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("scope-member-change.reason", revalidated.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_STALE); +} + +static void test_scope_revalidation_detects_symbol_change(void) +{ + test_object_t object; + fake_memory_t memory = { + .base = (uintptr_t)&object, + .data = &object, + .size = sizeof(object), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t proof; + kzt_guest_symbol_scope_result_t revalidated; + + init_object(&object, STB_GLOBAL, 0x6868); + check_int("symbol-change.proof", + scope_check( + (uintptr_t)&object.map, (uintptr_t)&object.map, + 0x6868, "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, + &reader_ops, &proof), + KZT_GUEST_SYMBOL_SCOPE_SAFE); + object.symbols[1].st_info = ELF_ST_INFO(STB_WEAK, STT_FUNC); + check_int("symbol-change.status", + scope_revalidate(&proof, &reader_ops, &revalidated), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("symbol-change.reason", revalidated.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_STALE); +} + +static void test_scope_walk_limit_is_256(void) +{ + test_object_t objects[257]; + fake_memory_t memory = { + .base = (uintptr_t)objects, + .data = objects, + .size = sizeof(objects), + }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fake_read_memory, + .opaque = &memory, + }; + kzt_guest_symbol_scope_result_t result; + size_t i; + + for (i = 0; i < 257; ++i) { + init_object(&objects[i], STB_LOCAL, 0); + hide_object_symbol(&objects[i]); + } + link_objects(objects, 256); + + check_int("limit.exact-status", + scope_check( + (uintptr_t)&objects[0].map, + (uintptr_t)&objects[0].map, + 1, + "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + NULL, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("limit.exact-reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_PROVIDER_MISMATCH); + check_int("limit.exact-complete", result.scope_complete, 1); + check_size("limit.exact-count", + result.scope_identity.scope_map_count, 256); + + link_objects(objects, 257); + scope_storage.scope_elems[0].r_nlist = 257; + check_int("limit.exceeded-status", + scope_check( + (uintptr_t)&objects[0].map, + (uintptr_t)&objects[0].map, + 1, + "target", + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + NULL, &reader_ops, &result), + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED); + check_int("limit.exceeded-reason", result.reason, + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE); + check_int("limit.exceeded-complete", result.scope_complete, 0); +} + +static void test_reason_names_are_stable_log_values(void) +{ + check_int("reason.selected", + strcmp(kzt_guest_symbol_scope_reason_name( + KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER), + "SELECTED_PROVIDER"), 0); + check_int("reason.incomplete", + strcmp(kzt_guest_symbol_scope_reason_name( + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_INCOMPLETE), + "SCOPE_INCOMPLETE"), 0); + check_int("reason.layout", + strcmp(kzt_guest_symbol_scope_reason_name( + KZT_GUEST_SYMBOL_SCOPE_REASON_LAYOUT_UNSUPPORTED), + "LAYOUT_UNSUPPORTED"), 0); + check_int("reason.binding", + strcmp(kzt_guest_symbol_scope_reason_name( + KZT_GUEST_SYMBOL_SCOPE_REASON_UNSUPPORTED_PROVIDER_BINDING), + "UNSUPPORTED_PROVIDER_BINDING"), 0); + check_int("reason.mismatch", + strcmp(kzt_guest_symbol_scope_reason_name( + KZT_GUEST_SYMBOL_SCOPE_REASON_PROVIDER_MISMATCH), + "PROVIDER_MISMATCH"), 0); + check_int("reason.stale", + strcmp(kzt_guest_symbol_scope_reason_name( + KZT_GUEST_SYMBOL_SCOPE_REASON_SCOPE_STALE), + "SCOPE_STALE"), 0); + check_int("reason.audit", + strcmp(kzt_guest_symbol_scope_reason_name( + KZT_GUEST_SYMBOL_SCOPE_REASON_AUDIT_UNSUPPORTED), + "AUDIT_UNSUPPORTED"), 0); +} + +int main(void) +{ + test_namespace_local_provider_outside_source_scope_is_rejected(); + test_unique_global_provider_is_safe(); + test_multiple_definitions_count_weak_candidate(); + test_multiple_strong_definitions_select_first_provider(); + test_unique_weak_provider_requires_guest(); + test_non_function_or_protected_provider_requires_guest(); +#ifdef STB_GNU_UNIQUE + test_unique_gnu_binding_requires_guest(); +#endif + test_selected_provider_must_match_unique_candidate(); + test_incomplete_scope_fails_open(); + test_scope_pointer_read_failure_fails_open(); + test_scope_pointer_instability_fails_open(); + test_duplicate_scope_map_fails_open(); + test_cross_namespace_scope_map_fails_open(); + test_layout_and_private_semantics_fail_open(); + test_loader_audit_state_fails_open(); + test_version_and_reference_evidence_fail_open(); + test_scope_revalidation_detects_change(); + test_scope_revalidation_detects_member_change(); + test_scope_revalidation_detects_symbol_change(); + test_scope_walk_limit_is_256(); + test_reason_names_are_stable_log_values(); + + if (failures) { + fprintf(stderr, "FAIL: %d checks failed\n", failures); + return 1; + } + puts("PASS"); + return 0; +} diff --git a/tests/unit/kzt/test_jump_slot_route.c b/tests/unit/kzt/test_jump_slot_route.c index 7358b9b2160..7e283472826 100644 --- a/tests/unit/kzt/test_jump_slot_route.c +++ b/tests/unit/kzt/test_jump_slot_route.c @@ -33,6 +33,7 @@ typedef struct fixture { int source_recheck_calls; int provider_touched_after_release; int writer_calls; + int load_calls; int cas_calls; } fixture_t; @@ -71,7 +72,8 @@ static int acquire_exact(const kzt_patch_object_ref_t *owner, ++f->acquired; handle->entry = (void *)1; handle->library = f->exact_conflict ? (library_t *)&f->other : - resolved_provider; + resolved_provider ? resolved_provider : + (library_t *)&f->provider; return 0; } @@ -114,6 +116,7 @@ static int validate_source_identity( static int load_slot(uintptr_t slot_addr, uintptr_t *value, void *opaque) { fixture_t *f = opaque; + ++f->load_calls; if (f->load_error) return -1; *value = *(uintptr_t *)slot_addr; return 0; @@ -201,7 +204,6 @@ static kzt_jump_slot_route_input_t input_for(fixture_t *f) return (kzt_jump_slot_route_input_t){ .enabled = 1, .expected_guest_target_present = 1, - .resolved_target_matches_legacy = 1, .resolved_provider = (library_t *)&f->provider, .request = { .relocation_type = R_X86_64_JUMP_SLOT, @@ -293,23 +295,24 @@ static void test_lazy_zero_competitor_is_preserved_without_write(void) CHECK("lazy-zero.no-cas", f.cas_calls == 0); } -static void test_resolved_target_mismatch_uses_legacy(void) +static void test_host_target_mismatch_does_not_override_guest_evidence(void) { fixture_t f = fixture(); kzt_jump_slot_route_input_t input = input_for(&f); kzt_jump_slot_route_ops_t ops = ops_for(&f); kzt_jump_slot_route_result_t result; - input.resolved_target_matches_legacy = 0; CHECK("target-mismatch.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); CHECK("target-mismatch.status", - result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED); - CHECK("target-mismatch.legacy", f.slot == input.request.legacy_target); - CHECK("target-mismatch.no-enrich", f.enrich_calls == 0); - CHECK("target-mismatch.no-acquire", f.acquire_calls == 0); - CHECK("target-mismatch.no-bridge", f.bridge_calls == 0); - CHECK("target-mismatch.no-writer", f.writer_calls == 0); + result.status == KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED); + CHECK("target-mismatch.native", f.slot == f.bridge); + CHECK("target-mismatch.final", + result.final_value == f.bridge && result.final_value != 0); + CHECK("target-mismatch.enrich", f.enrich_calls == 1); + CHECK("target-mismatch.acquire", f.acquire_calls == 1); + CHECK("target-mismatch.bridge", f.bridge_calls == 1); + CHECK("target-mismatch.writer", f.writer_calls == 1); } static void test_owner_identity_change_during_bridge_falls_back(void) @@ -326,9 +329,9 @@ static void test_owner_identity_change_during_bridge_falls_back(void) CHECK("owner-refresh.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); CHECK("owner-refresh.status", - result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED); - CHECK("owner-refresh.legacy", - f.slot == input.request.legacy_target); + result.status == KZT_JUMP_SLOT_ROUTE_BYPASS); + CHECK("owner-refresh.preserved", + f.slot == input.request.slot_current_value); CHECK("owner-refresh.acquired", f.acquired == 1); CHECK("owner-refresh.released", f.released == 1); CHECK("owner-refresh.bridge", f.bridge_calls == 1); @@ -385,12 +388,34 @@ static void test_exact_provider_bridge_success(void) CHECK("success.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); CHECK("success.status", result.status == KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED); CHECK("success.slot", f.slot == f.bridge); + CHECK("success.final", result.final_value == f.bridge && result.final_value != 0); CHECK("success.raw", result.expected_guest_target == f.raw_expected); CHECK("success.lease", f.acquired == 1 && f.released == 1); CHECK("success.no-post-release-deref", f.provider_touched_after_release == 0); } -static void test_missing_or_stale_evidence_falls_back(void) +static void test_registry_provider_bridge_success_without_host_provider(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + + input.resolved_provider = NULL; + input.request.legacy_target = 0; + CHECK("registry-provider.call", + kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("registry-provider.status", + result.status == KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED); + CHECK("registry-provider.slot", f.slot == f.bridge); + CHECK("registry-provider.exact", + result.exact_provider_acquired && result.exact_provider_matched); + CHECK("registry-provider.callbacks", + f.enrich_calls == 1 && f.acquire_calls == 1 && + f.bridge_calls == 1 && f.writer_calls == 1); +} + +static void test_missing_or_stale_evidence_declines_without_write(void) { fixture_t f = fixture(); kzt_jump_slot_route_input_t input = input_for(&f); @@ -398,8 +423,9 @@ static void test_missing_or_stale_evidence_falls_back(void) kzt_jump_slot_route_result_t result; f.acquire_ok = 0; CHECK("stale.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); - CHECK("stale.status", result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED); - CHECK("stale.legacy", f.slot == input.request.legacy_target); + CHECK("stale.status", result.status == KZT_JUMP_SLOT_ROUTE_BYPASS); + CHECK("stale.preserved", f.slot == input.request.slot_current_value); + CHECK("stale.no-cas", f.cas_calls == 0); CHECK("stale.no-writer", f.writer_calls == 0); } @@ -421,6 +447,27 @@ static void test_lazy_missing_evidence_preserves_observed_without_legacy_cas(voi CHECK("preserve.no-cas", f.cas_calls == 0); } +static void test_lazy_writer_decline_preserves_guest_without_legacy_fallback(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + + input.preserve_observed_on_failure = 1; + f.writer_mutates = 0; + f.writer_status = KZT_JUMP_SLOT_ROUTE_WRITER_DECLINED; + CHECK("writer-preserve.call", + kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("writer-preserve.status", + result.status == KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED); + CHECK("writer-preserve.slot", + f.slot == input.request.slot_current_value); + CHECK("writer-preserve.no-legacy", + result.legacy_fallback_attempted == 0); + CHECK("writer-preserve.no-cas", f.cas_calls == 0); +} + static void test_conflicting_exact_provider_falls_back(void) { fixture_t f = fixture(); @@ -429,7 +476,10 @@ static void test_conflicting_exact_provider_falls_back(void) kzt_jump_slot_route_result_t result; f.exact_conflict = 1; CHECK("conflict.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); - CHECK("conflict.status", result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED); + CHECK("conflict.status", result.status == KZT_JUMP_SLOT_ROUTE_BYPASS); + CHECK("conflict.preserved", + f.slot == input.request.slot_current_value); + CHECK("conflict.no-cas", f.cas_calls == 0); CHECK("conflict.lease", f.acquired == 1 && f.released == 1); CHECK("conflict.no-writer", f.writer_calls == 0); } @@ -442,14 +492,18 @@ static void test_lazy_missing_owner_explicitly_falls_back(void) kzt_jump_slot_route_result_t result; input.expected_guest_target_present = 0; input.request.expected_guest_target = 0; + input.preserve_observed_on_failure = 1; f.owner_available = 0; CHECK("lazy.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); - CHECK("lazy.status", result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED); + CHECK("lazy.status", + result.status == KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED); + CHECK("lazy.preserved", f.slot == input.request.slot_current_value); + CHECK("lazy.no-cas", f.cas_calls == 0); CHECK("lazy.no-acquire", f.acquired == 0); CHECK("lazy.no-writer", f.writer_calls == 0); } -static void test_cas_mismatch_preserves_competing_value(void) +static void test_provider_failure_declines_without_cas(void) { fixture_t f = fixture(); kzt_jump_slot_route_input_t input = input_for(&f); @@ -458,8 +512,9 @@ static void test_cas_mismatch_preserves_competing_value(void) f.acquire_ok = 0; f.force_cas_mismatch = 1; CHECK("cas.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); - CHECK("cas.status", result.status == KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH); - CHECK("cas.competing", f.slot == 0xfeedface); + CHECK("cas.status", result.status == KZT_JUMP_SLOT_ROUTE_BYPASS); + CHECK("cas.preserved", f.slot == input.request.slot_current_value); + CHECK("cas.not-attempted", f.cas_calls == 0); } static void test_native_cas_mismatch_does_not_overwrite_competitor(void) @@ -481,12 +536,42 @@ static void test_native_cas_mismatch_does_not_overwrite_competitor(void) result.final_value == 0xfeedface); } +static void test_native_cas_error_preserves_without_legacy_fallback(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + kzt_jump_slot_route_caller_decision_t decision; + uintptr_t initial_slot = f.slot; + + f.cas_error = 1; + CHECK("native-cas-error.call", + kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("native-cas-error.status", + result.status == KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED); + CHECK("native-cas-error.native-writer", + result.native_writer_called && f.writer_calls == 1 && + f.acquired == 1 && f.released == 1); + CHECK("native-cas-error.once", f.cas_calls == 1); + CHECK("native-cas-error.no-legacy", + result.legacy_fallback_attempted == 0); + CHECK("native-cas-error.slot", f.slot == initial_slot); + decision = kzt_jump_slot_route_caller_decide( + 1, &result, input.request.legacy_target, 1); + CHECK("native-cas-error.caller-preserves", + decision.slot_action == KZT_JUMP_SLOT_ROUTE_SLOT_PRESERVE); + CHECK("native-cas-error.caller-uses-observed", + decision.call_target == initial_slot && decision.slot_value_usable); +} + static void test_rollback_cas_does_not_overwrite_competitor(void) { fixture_t f = fixture(); kzt_jump_slot_route_input_t input = input_for(&f); kzt_jump_slot_route_ops_t ops = ops_for(&f); kzt_jump_slot_route_result_t result; + kzt_jump_slot_route_caller_decision_t decision; f.writer_mutates = 0; f.writer_rollback_after_competitor = 1; CHECK("rollback-race.call", @@ -494,9 +579,17 @@ static void test_rollback_cas_does_not_overwrite_competitor(void) CHECK("rollback-race.status", result.status == KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH); CHECK("rollback-race.competing", f.slot == 0xfeedface); + CHECK("rollback-race.no-legacy", result.legacy_fallback_attempted == 0); + CHECK("rollback-race.only-writer-cas", f.cas_calls == 2); + decision = kzt_jump_slot_route_caller_decide( + 1, &result, input.request.legacy_target, 1); + CHECK("rollback-race.caller-preserves", + decision.slot_action == KZT_JUMP_SLOT_ROUTE_SLOT_PRESERVE); + CHECK("rollback-race.caller-uses-competitor", + decision.call_target == 0xfeedface && decision.slot_value_usable); } -static void test_writer_decline_and_error_use_legacy(void) +static void test_writer_decline_and_error_decline_without_write(void) { for (int error = 0; error < 2; ++error) { fixture_t f = fixture(); @@ -509,8 +602,10 @@ static void test_writer_decline_and_error_use_legacy(void) CHECK("writer-fallback.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); CHECK("writer-fallback.status", - result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED); - CHECK("writer-fallback.slot", f.slot == input.request.legacy_target); + result.status == KZT_JUMP_SLOT_ROUTE_BYPASS); + CHECK("writer-fallback.slot", + f.slot == input.request.slot_current_value); + CHECK("writer-fallback.no-cas", f.cas_calls == 0); CHECK("writer-fallback.release", f.released == 1); CHECK("writer-fallback.no-post-release-provider-callback", f.provider_touched_after_release == 0); @@ -531,24 +626,213 @@ static void test_kzt_off_bypasses_route(void) CHECK("off.no-callbacks", f.acquired == 0 && f.cas_calls == 0); } +static void test_invalid_parameters_have_no_slot_side_effects(void) +{ + enum invalid_parameter_case { + INVALID_INPUT, + INVALID_OPS, + INVALID_LOAD, + INVALID_CAS, + INVALID_SLOT, + INVALID_RESULT, + }; + static const char *const names[] = { + "null-input", "null-ops", "null-load", "null-cas", "null-slot", + "null-result", + }; + size_t i; + + for (i = 0; i < sizeof(names) / sizeof(names[0]); ++i) { + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + uintptr_t initial_slot = f.slot; + const kzt_jump_slot_route_input_t *input_arg = &input; + const kzt_jump_slot_route_ops_t *ops_arg = &ops; + kzt_jump_slot_route_result_t *result_arg = &result; + + switch ((enum invalid_parameter_case)i) { + case INVALID_INPUT: + input_arg = NULL; + break; + case INVALID_OPS: + ops_arg = NULL; + break; + case INVALID_LOAD: + ops.load_slot = NULL; + break; + case INVALID_CAS: + ops.compare_exchange_slot = NULL; + break; + case INVALID_SLOT: + input.request.slot_addr = 0; + break; + case INVALID_RESULT: + result_arg = NULL; + break; + } + + CHECK(names[i], + kzt_jump_slot_route_apply(input_arg, ops_arg, result_arg) != 0); + CHECK("invalid-parameters.no-load", f.load_calls == 0); + CHECK("invalid-parameters.no-cas", f.cas_calls == 0); + CHECK("invalid-parameters.slot", f.slot == initial_slot); + } +} + +static void test_load_error_returns_before_slot_write(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + kzt_jump_slot_route_caller_decision_t decision; + uintptr_t initial_slot = f.slot; + int route_status; + + f.load_error = 1; + route_status = kzt_jump_slot_route_apply(&input, &ops, &result); + CHECK("load-error.call", route_status != 0); + CHECK("load-error.status", result.status == KZT_JUMP_SLOT_ROUTE_WRITE_ERROR); + CHECK("load-error.once", f.load_calls == 1 && f.cas_calls == 0); + CHECK("load-error.slot", f.slot == initial_slot); + CHECK("load-error.no-legacy", result.legacy_fallback_attempted == 0); + decision = kzt_jump_slot_route_caller_decide( + route_status == 0, &result, input.request.legacy_target, 0); + CHECK("load-error.caller-legacy-write", + decision.slot_action == KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE); + CHECK("load-error.caller-legacy-target", + decision.call_target == input.request.legacy_target); +} + +static void test_declined_route_never_attempts_legacy_cas(void) +{ + fixture_t f = fixture(); + kzt_jump_slot_route_input_t input = input_for(&f); + kzt_jump_slot_route_ops_t ops = ops_for(&f); + kzt_jump_slot_route_result_t result; + kzt_jump_slot_route_caller_decision_t decision; + uintptr_t initial_slot = f.slot; + + f.acquire_ok = 0; + CHECK("cas-error.call", kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("cas-error.status", result.status == KZT_JUMP_SLOT_ROUTE_BYPASS); + CHECK("cas-error.once", f.load_calls == 1 && f.cas_calls == 0); + CHECK("cas-error.slot", f.slot == initial_slot); + CHECK("cas-error.no-legacy", result.legacy_fallback_attempted == 0); + decision = kzt_jump_slot_route_caller_decide( + 1, &result, input.request.legacy_target, 1); + CHECK("cas-error.caller-owns-baseline-write", + decision.slot_action == KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE); +} + +static void test_caller_decision_mapping(void) +{ + static const struct { + const char *name; + int route_call_succeeded; + int result_present; + kzt_jump_slot_route_status_t status; + uintptr_t final_value; + int final_value_usable; + kzt_jump_slot_route_slot_action_t expected_slot_action; + uintptr_t expected_call_target; + int expected_slot_value_usable; + } cases[] = { + { "call-failed", 0, 1, KZT_JUMP_SLOT_ROUTE_WRITE_ERROR, 0x75000010, + 1, KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE, 0x74000010, 0 }, + { "null-result", 1, 0, KZT_JUMP_SLOT_ROUTE_BYPASS, 0, + 0, KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE, 0x74000010, 0 }, + { "bypass", 1, 1, KZT_JUMP_SLOT_ROUTE_BYPASS, 0, + 0, KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE, 0x74000010, 0 }, + { "native-applied", 1, 1, KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED, + 0x75000020, 1, KZT_JUMP_SLOT_ROUTE_SLOT_ROUTE_APPLIED, 0x75000020, + 1 }, + { "native-applied-invalid", 1, 1, + KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED, 0x75000021, + 0, KZT_JUMP_SLOT_ROUTE_SLOT_PRESERVE, 0x74000010, 0 }, + { "guest-preserved", 1, 1, KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED, + 0x75000040, 1, KZT_JUMP_SLOT_ROUTE_SLOT_PRESERVE, 0x75000040, 1 }, + { "guest-preserved-zero", 1, 1, + KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED, 0, + 0, KZT_JUMP_SLOT_ROUTE_SLOT_PRESERVE, 0x74000010, 0 }, + { "guest-preserved-unresolved", 1, 1, + KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED, 0x75000041, + 0, KZT_JUMP_SLOT_ROUTE_SLOT_PRESERVE, 0x74000010, 0 }, + { "cas-mismatch-zero", 1, 1, KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH, 0, + 0, KZT_JUMP_SLOT_ROUTE_SLOT_PRESERVE, 0x74000010, 0 }, + { "cas-mismatch-valid", 1, 1, + KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH, 0x75000042, + 1, KZT_JUMP_SLOT_ROUTE_SLOT_PRESERVE, 0x75000042, 1 }, + { "cas-mismatch-unresolved", 1, 1, + KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH, 0x75000043, + 0, KZT_JUMP_SLOT_ROUTE_SLOT_PRESERVE, 0x74000010, 0 }, + { "write-error", 1, 1, KZT_JUMP_SLOT_ROUTE_WRITE_ERROR, + 0x75000050, 1, KZT_JUMP_SLOT_ROUTE_SLOT_PRESERVE, 0x74000010, 0 }, + { "unknown-status", 1, 1, (kzt_jump_slot_route_status_t)99, + 0x75000060, 1, KZT_JUMP_SLOT_ROUTE_SLOT_PRESERVE, 0x74000010, 0 }, + }; + const uintptr_t legacy_target = 0x74000010; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + kzt_jump_slot_route_result_t result = { + .status = cases[i].status, + .final_value = cases[i].final_value, + }; + const kzt_jump_slot_route_result_t *result_ptr = + cases[i].result_present ? &result : NULL; + kzt_jump_slot_route_caller_decision_t decision = + kzt_jump_slot_route_caller_decide( + cases[i].route_call_succeeded, result_ptr, + legacy_target, cases[i].final_value_usable); + + CHECK(cases[i].name, + decision.slot_action == cases[i].expected_slot_action); + CHECK(cases[i].name, + decision.call_target == cases[i].expected_call_target); + CHECK(cases[i].name, + decision.slot_value_usable == + cases[i].expected_slot_value_usable); + CHECK(cases[i].name, decision.call_target != 0); + if (decision.slot_action == KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE) { + CHECK(cases[i].name, + !cases[i].route_call_succeeded || !cases[i].result_present || + cases[i].status == KZT_JUMP_SLOT_ROUTE_BYPASS); + } else { + CHECK(cases[i].name, + cases[i].route_call_succeeded && cases[i].result_present && + cases[i].status != KZT_JUMP_SLOT_ROUTE_BYPASS); + } + } +} + int main(void) { test_caller_observation_change_is_never_authorized(); test_same_owner_competitor_cannot_authorize_native(); test_lazy_zero_competitor_is_preserved_without_write(); - test_resolved_target_mismatch_uses_legacy(); + test_host_target_mismatch_does_not_override_guest_evidence(); test_owner_identity_change_during_bridge_falls_back(); test_generation_change_before_writer_preserves_guest(); test_exact_provider_bridge_success(); - test_missing_or_stale_evidence_falls_back(); + test_registry_provider_bridge_success_without_host_provider(); + test_missing_or_stale_evidence_declines_without_write(); test_lazy_missing_evidence_preserves_observed_without_legacy_cas(); + test_lazy_writer_decline_preserves_guest_without_legacy_fallback(); test_conflicting_exact_provider_falls_back(); test_lazy_missing_owner_explicitly_falls_back(); - test_cas_mismatch_preserves_competing_value(); + test_provider_failure_declines_without_cas(); test_native_cas_mismatch_does_not_overwrite_competitor(); + test_native_cas_error_preserves_without_legacy_fallback(); test_rollback_cas_does_not_overwrite_competitor(); - test_writer_decline_and_error_use_legacy(); + test_writer_decline_and_error_decline_without_write(); test_kzt_off_bypasses_route(); + test_invalid_parameters_have_no_slot_side_effects(); + test_load_error_returns_before_slot_write(); + test_declined_route_never_attempts_legacy_cas(); + test_caller_decision_mapping(); if (failures) { fprintf(stderr, "%d jump-slot route checks failed\n", failures); return 1; diff --git a/tests/unit/kzt/test_patch_planner.c b/tests/unit/kzt/test_patch_planner.c index fe35473d632..dd48fb58b99 100644 --- a/tests/unit/kzt/test_patch_planner.c +++ b/tests/unit/kzt/test_patch_planner.c @@ -429,6 +429,29 @@ static void test_bridge_target_is_required_for_approval(void) KZT_PATCH_REASON_INPUT_UNAVAILABLE_BRIDGE_TARGET, 0); } +static void test_guest_owned_dlclose_is_kept_guest(void) +{ + kzt_patch_candidate_t candidate = base_candidate(); + kzt_patch_decision_t decision; + + candidate.symbol_name = "dlclose"; + candidate.version_evidence = + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + candidate.version = NULL; + candidate.wrapper_version_evidence = + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + candidate.wrapper_symbol_version = NULL; + candidate.wrapper_match = KZT_PATCH_WRAPPER_UNVERSIONED_MATCH; + + check_int("guest-dlclose.policy", + kzt_patch_symbol_must_stay_guest(candidate.symbol_name), 1); + check_int("guest-dlclose.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("guest-dlclose", &decision, + KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_KEEP_GUEST, 0); +} + static int test_matches_filter(const char *name, int argc, char **argv) { int i; @@ -498,6 +521,10 @@ int main(int argc, char **argv) argc, argv)) { test_bridge_target_is_required_for_approval(); } + if (test_matches_filter("guest_owned_dlclose_is_kept_guest", + argc, argv)) { + test_guest_owned_dlclose_is_kept_guest(); + } if (failures) { fprintf(stderr, "kzt-patch-planner: %d failure(s)\n", failures); diff --git a/tests/unit/kzt/test_patch_spike_guard.c b/tests/unit/kzt/test_patch_spike_guard.c index c9d1f72941c..ebaea1be439 100644 --- a/tests/unit/kzt/test_patch_spike_guard.c +++ b/tests/unit/kzt/test_patch_spike_guard.c @@ -212,6 +212,20 @@ static void test_default_config_is_closed_and_noop(void) assert_no_writer_calls("default.writer", &writer); } +static void test_config_from_enabled_options(void) +{ + kzt_patch_spike_config_t config = { 0 }; + + option_kzt_patch_spike = 1; + option_kzt_patch_spike_write = 1; + option_kzt_patch_spike_budget = 1; + kzt_patch_spike_config_from_options(&config); + + check_int("options.enabled", config.enabled, 1); + check_int("options.write-enabled", config.write_enabled, 1); + check_ulong("options.budget", config.budget, 1); +} + static void test_diagnostics_only_never_writes(void) { kzt_patch_decision_t decision = approved_decision(); @@ -417,9 +431,9 @@ static void test_rollback_failure_opens_circuit_breaker(void) kzt_patch_spike_guard_try_write(&guard, &decision, &ops, &outcome), 0); check_int("rollback-failure.result", outcome.result, - KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED); + KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE); check_int("rollback-failure.failure", outcome.failure, - KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED); + KZT_PATCH_SPIKE_FAILURE_TRANSACTION_UNRECOVERABLE); check_int("rollback-failure.rollback-called", outcome.rollback_called, 1); check_int("rollback-failure.skip-legacy", outcome.skip_legacy_write, 1); check_int("rollback-failure.circuit-open", @@ -496,6 +510,7 @@ static void test_concurrent_budget_reservation_allows_exactly_one_writer(void) int main(void) { test_default_config_is_closed_and_noop(); + test_config_from_enabled_options(); test_diagnostics_only_never_writes(); test_spike_on_without_write_or_budget_is_noop(); test_approved_budget_one_allows_one_write(); diff --git a/tests/unit/kzt/test_patch_spike_writer.c b/tests/unit/kzt/test_patch_spike_writer.c index 8ba12b0e5ae..c8e0ddc398e 100644 --- a/tests/unit/kzt/test_patch_spike_writer.c +++ b/tests/unit/kzt/test_patch_spike_writer.c @@ -22,6 +22,7 @@ typedef struct fake_slot { int fail_permission_begin; int fail_permission_begin_after_enable; int fail_permission_end; + int fail_permission_end_once; int fail_generation; } fake_slot_t; @@ -226,6 +227,10 @@ static int fake_end_write(kzt_patch_spike_permission_lease_t *lease, return -1; } ++slot->end_calls; + if (slot->fail_permission_end_once) { + slot->fail_permission_end_once = 0; + return -1; + } if (slot->fail_permission_end) { return -1; } @@ -532,9 +537,9 @@ static void test_rollback_failure_is_observable(void) &ops, &record), 0); check_int("rollback-fail.result", record.result, - KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED); + KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE); check_int("rollback-fail.failure", record.failure, - KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED); + KZT_PATCH_SPIKE_FAILURE_TRANSACTION_UNRECOVERABLE); check_int("rollback-fail.rollback-called", record.rollback_called, 1); check_int("rollback-fail.rollback-success", record.rollback_succeeded, 0); check_int("rollback-fail.circuit", @@ -571,7 +576,7 @@ static void test_permission_enable_failure_does_not_write(void) check_int("permission-enable.preserve", record.skip_legacy_write, 1); } -static void test_permission_restore_failure_opens_circuit(void) +static void test_permission_restore_failure_rolls_back_and_recovers(void) { kzt_patch_decision_t decision = approved_decision(0x7100000018); kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); @@ -581,7 +586,7 @@ static void test_permission_restore_failure_opens_circuit(void) .value = decision.slot_current_value, .replacement_value = decision.bridge_target, .previous_value = decision.slot_current_value, - .fail_permission_end = 1, + .fail_permission_end_once = 1, }; kzt_patch_spike_slot_ops_t ops = fake_slot_ops(&slot); @@ -589,20 +594,61 @@ static void test_permission_restore_failure_opens_circuit(void) kzt_patch_spike_writer_try_apply_with_slot_ops( &guard, &decision, &ops, &record), 0); check_int("permission-restore.result", record.result, - KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED); + KZT_PATCH_SPIKE_RESULT_ROLLED_BACK); check_int("permission-restore.failure", record.failure, KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED); - check_int("permission-restore.end", slot.end_calls, 1); + check_int("permission-restore.end", slot.end_calls, 2); check_int("permission-restore.write", record.write_succeeded, 1); check_int("permission-restore.verify", record.verify_succeeded, 1); check_int("permission-restore.attempted", record.permission_restore_attempted, 1); - check_int("permission-restore.restored", record.permission_restored, 0); + check_int("permission-restore.rollback-called", record.rollback_called, 1); + check_int("permission-restore.rollback-succeeded", + record.rollback_succeeded, 1); + check_uintptr("permission-restore.slot", slot.value, + decision.slot_current_value); + check_int("permission-restore.restored", record.permission_restored, 1); check_int("permission-restore.circuit", - kzt_patch_spike_guard_circuit_open(&guard), 1); + kzt_patch_spike_guard_circuit_open(&guard), 0); check_int("permission-restore.preserve", record.skip_legacy_write, 1); } +static void test_permission_restore_failure_can_be_unrecoverable(void) +{ + kzt_patch_decision_t decision = approved_decision(0x7100000018); + kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); + kzt_patch_spike_record_t record; + fake_slot_t slot = { + .slot_addr = decision.slot_addr, + .value = decision.slot_current_value, + .replacement_value = decision.bridge_target, + .previous_value = decision.slot_current_value, + .fail_permission_end = 1, + }; + kzt_patch_spike_slot_ops_t ops = fake_slot_ops(&slot); + + check_int("permission-unrecoverable.apply", + kzt_patch_spike_writer_try_apply_with_slot_ops( + &guard, &decision, &ops, &record), 0); + check_int("permission-unrecoverable.result", record.result, + KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE); + check_int("permission-unrecoverable.failure", record.failure, + KZT_PATCH_SPIKE_FAILURE_TRANSACTION_UNRECOVERABLE); + check_int("permission-unrecoverable.end", slot.end_calls, 2); + check_int("permission-unrecoverable.rollback-called", + record.rollback_called, 1); + check_int("permission-unrecoverable.rollback-succeeded", + record.rollback_succeeded, 1); + check_uintptr("permission-unrecoverable.slot", slot.value, + decision.slot_current_value); + check_int("permission-unrecoverable.restored", + record.permission_restored, 0); + check_int("permission-unrecoverable.circuit", + kzt_patch_spike_guard_circuit_open(&guard), 1); + check_int("permission-unrecoverable.skip-legacy", + record.skip_legacy_write, 1); +} + static void test_partial_permission_enable_is_restored(void) { kzt_patch_decision_t decision = approved_decision(0x7100000018); @@ -800,9 +846,9 @@ static void test_persistent_guard_circuit_blocks_second_write(void) &first_ops, &first_record), 0); check_int("persistent-circuit.first-result", first_record.result, - KZT_PATCH_SPIKE_RESULT_GUEST_PRESERVED); + KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE); check_int("persistent-circuit.first-failure", first_record.failure, - KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED); + KZT_PATCH_SPIKE_FAILURE_TRANSACTION_UNRECOVERABLE); check_int("persistent-circuit.first-rollback", first_record.rollback_called, 1); check_int("persistent-circuit.open", @@ -888,7 +934,8 @@ int main(void) test_verify_failure_rolls_back_successfully(); test_rollback_failure_is_observable(); test_permission_enable_failure_does_not_write(); - test_permission_restore_failure_opens_circuit(); + test_permission_restore_failure_rolls_back_and_recovers(); + test_permission_restore_failure_can_be_unrecoverable(); test_partial_permission_enable_is_restored(); test_generation_mismatch_does_not_touch_permissions_or_slot(); test_non_approved_decision_does_not_write(); diff --git a/tests/unit/kzt/test_per_object_got_plt.c b/tests/unit/kzt/test_per_object_got_plt.c new file mode 100644 index 00000000000..e8f887e62d7 --- /dev/null +++ b/tests/unit/kzt/test_per_object_got_plt.c @@ -0,0 +1,316 @@ +#include +#include + +#include "elf.h" +#include "kzt_per_object_got_plt.h" + +static int failures; + +static void check_int(const char *name, int got, int expected) +{ + if (got != expected) { + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; + } +} + +static int unexpected_apply(uintptr_t link_map_addr, + unsigned long generation, + const kzt_guest_dynamic_view_t *view, + void *opaque) +{ + int *calls = opaque; + + (void)link_map_addr; + (void)generation; + (void)view; + ++*calls; + return 0; +} + +typedef struct apply_state { + int calls; + int fail; + uintptr_t link_map_addr; + unsigned long generation; +} apply_state_t; + +static int record_apply(uintptr_t link_map_addr, + unsigned long generation, + const kzt_guest_dynamic_view_t *view, + void *opaque) +{ + apply_state_t *state = opaque; + + if (!state || !view) { + return -1; + } + ++state->calls; + state->link_map_addr = link_map_addr; + state->generation = generation; + return state->fail ? -1 : 0; +} + +static kzt_guest_dynamic_view_t complete_view(void) +{ + return (kzt_guest_dynamic_view_t) { + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .dynamic_addr = 0x401000, + .load_bias = 0x400000, + .has_null = 1, + .jmprel = { 1, 0x402000, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS }, + .pltrelsz = { 1, sizeof(Elf64_Rela), KZT_GUEST_DYNAMIC_SCALAR }, + .pltrel = { 1, DT_RELA, KZT_GUEST_DYNAMIC_SCALAR }, + .pltgot = { 1, 0x403000, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS }, + }; +} + +static void test_complete_view_is_written_once(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t object = { + .link_map_addr = 0x1000, + .load_bias = { 0x400000, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { 0x401000, KZT_GUEST_FIELD_OK }, + .map_start = { 0x400000, KZT_GUEST_FIELD_OK }, + .map_end = { 0x408000, KZT_GUEST_FIELD_OK }, + .namespace_id = { 0, KZT_GUEST_FIELD_OK }, + .path = { "/guest/libfixture.so", KZT_GUEST_FIELD_OK }, + .soname = { "libfixture.so", KZT_GUEST_FIELD_OK }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; + kzt_guest_dynamic_view_t view = complete_view(); + apply_state_t state = { 0 }; + kzt_per_object_got_plt_request_t request = { + .registry = registry, + .link_map_addr = object.link_map_addr, + .apply = record_apply, + .opaque = &state, + }; + kzt_per_object_got_plt_result_t result = { 0 }; + + if (!registry) { + ++failures; + return; + } + check_int("applied.observe", + kzt_guest_registry_observe(registry, &object), + KZT_GUEST_REGISTRY_ADDED); + check_int("applied.view", + kzt_guest_registry_commit_dynamic_view(registry, 0x1000, 1, + &view), + KZT_GUEST_REGISTRY_UPDATED); + check_int("applied.first", kzt_per_object_got_plt_apply(&request, &result), + 0); + check_int("applied.status", result.status, KZT_PER_OBJECT_GOT_PLT_APPLIED); + check_int("applied.calls", state.calls, 1); + check_int("applied.link-map", state.link_map_addr, 0x1000); + check_int("applied.generation", state.generation, 1); + check_int("applied.repeat", kzt_per_object_got_plt_apply(&request, &result), + 0); + check_int("applied.repeat-status", result.status, + KZT_PER_OBJECT_GOT_PLT_ALREADY_APPLIED); + check_int("applied.repeat-calls", state.calls, 1); + kzt_guest_registry_destroy(®istry); +} + +static void test_failed_write_can_retry(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t object = { + .link_map_addr = 0x1000, + .load_bias = { 0x400000, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { 0x401000, KZT_GUEST_FIELD_OK }, + .map_start = { 0x400000, KZT_GUEST_FIELD_OK }, + .map_end = { 0x408000, KZT_GUEST_FIELD_OK }, + .namespace_id = { 0, KZT_GUEST_FIELD_OK }, + .path = { "/guest/libfixture.so", KZT_GUEST_FIELD_OK }, + .soname = { "libfixture.so", KZT_GUEST_FIELD_OK }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; + kzt_guest_dynamic_view_t view = complete_view(); + apply_state_t state = { .fail = 1 }; + kzt_per_object_got_plt_request_t request = { + .registry = registry, + .link_map_addr = object.link_map_addr, + .apply = record_apply, + .opaque = &state, + }; + kzt_per_object_got_plt_result_t result = { 0 }; + + if (!registry) { + ++failures; + return; + } + check_int("retry.observe", kzt_guest_registry_observe(registry, &object), + KZT_GUEST_REGISTRY_ADDED); + check_int("retry.view", kzt_guest_registry_commit_dynamic_view( + registry, 0x1000, 1, &view), KZT_GUEST_REGISTRY_UPDATED); + check_int("retry.failed", kzt_per_object_got_plt_apply(&request, &result), + 0); + check_int("retry.failed-status", result.status, + KZT_PER_OBJECT_GOT_PLT_FAIL_OPEN); + state.fail = 0; + check_int("retry.applied", kzt_per_object_got_plt_apply(&request, &result), + 0); + check_int("retry.applied-status", result.status, + KZT_PER_OBJECT_GOT_PLT_APPLIED); + check_int("retry.calls", state.calls, 2); + kzt_guest_registry_destroy(®istry); +} + +typedef struct concurrent_apply_state { + pthread_mutex_t lock; + pthread_cond_t cond; + int entered; + int released; + int calls; +} concurrent_apply_state_t; + +typedef struct concurrent_apply_worker { + kzt_per_object_got_plt_request_t request; + kzt_per_object_got_plt_result_t result; + int return_code; +} concurrent_apply_worker_t; + +static int blocking_apply(uintptr_t link_map_addr, + unsigned long generation, + const kzt_guest_dynamic_view_t *view, + void *opaque) +{ + concurrent_apply_state_t *state = opaque; + + (void)link_map_addr; + (void)generation; + (void)view; + pthread_mutex_lock(&state->lock); + ++state->calls; + state->entered = 1; + pthread_cond_broadcast(&state->cond); + while (!state->released) { + pthread_cond_wait(&state->cond, &state->lock); + } + pthread_mutex_unlock(&state->lock); + return 0; +} + +static void *concurrent_apply_main(void *opaque) +{ + concurrent_apply_worker_t *worker = opaque; + + worker->return_code = kzt_per_object_got_plt_apply(&worker->request, + &worker->result); + return NULL; +} + +static void test_concurrent_observers_write_once(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t object = { + .link_map_addr = 0x1000, + .load_bias = { 0x400000, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { 0x401000, KZT_GUEST_FIELD_OK }, + .map_start = { 0x400000, KZT_GUEST_FIELD_OK }, + .map_end = { 0x408000, KZT_GUEST_FIELD_OK }, + .namespace_id = { 0, KZT_GUEST_FIELD_OK }, + .path = { "/guest/libfixture.so", KZT_GUEST_FIELD_OK }, + .soname = { "libfixture.so", KZT_GUEST_FIELD_OK }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; + kzt_guest_dynamic_view_t view = complete_view(); + concurrent_apply_state_t state = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + concurrent_apply_worker_t first = { + .request = { + .registry = registry, + .link_map_addr = object.link_map_addr, + .apply = blocking_apply, + .opaque = &state, + }, + }; + kzt_per_object_got_plt_request_t second_request = first.request; + kzt_per_object_got_plt_result_t second_result = { 0 }; + pthread_t thread; + + if (!registry) { + ++failures; + return; + } + check_int("concurrent.observe", kzt_guest_registry_observe(registry, &object), + KZT_GUEST_REGISTRY_ADDED); + check_int("concurrent.view", kzt_guest_registry_commit_dynamic_view( + registry, 0x1000, 1, &view), KZT_GUEST_REGISTRY_UPDATED); + check_int("concurrent.create", pthread_create(&thread, NULL, + concurrent_apply_main, + &first), 0); + pthread_mutex_lock(&state.lock); + while (!state.entered) { + pthread_cond_wait(&state.cond, &state.lock); + } + pthread_mutex_unlock(&state.lock); + check_int("concurrent.second", kzt_per_object_got_plt_apply( + &second_request, &second_result), 0); + check_int("concurrent.second-status", second_result.status, + KZT_PER_OBJECT_GOT_PLT_IN_PROGRESS); + pthread_mutex_lock(&state.lock); + state.released = 1; + pthread_cond_broadcast(&state.cond); + pthread_mutex_unlock(&state.lock); + check_int("concurrent.join", pthread_join(thread, NULL), 0); + check_int("concurrent.first-return", first.return_code, 0); + check_int("concurrent.first-status", first.result.status, + KZT_PER_OBJECT_GOT_PLT_APPLIED); + check_int("concurrent.calls", state.calls, 1); + pthread_cond_destroy(&state.cond); + pthread_mutex_destroy(&state.lock); + kzt_guest_registry_destroy(®istry); +} + +int main(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t object = { + .link_map_addr = 0x1000, + .load_bias = { 0x400000, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { 0x401000, KZT_GUEST_FIELD_OK }, + .map_start = { 0x400000, KZT_GUEST_FIELD_OK }, + .map_end = { 0x408000, KZT_GUEST_FIELD_OK }, + .namespace_id = { 0, KZT_GUEST_FIELD_OK }, + .path = { "/guest/libfixture.so", KZT_GUEST_FIELD_OK }, + .soname = { "libfixture.so", KZT_GUEST_FIELD_OK }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; + int calls = 0; + kzt_per_object_got_plt_request_t request = { + .registry = registry, + .link_map_addr = object.link_map_addr, + .apply = unexpected_apply, + .opaque = &calls, + }; + kzt_per_object_got_plt_result_t result = { 0 }; + + if (!registry) { + return 1; + } + check_int("fail-open.observe", + kzt_guest_registry_observe(registry, &object), + KZT_GUEST_REGISTRY_ADDED); + check_int("fail-open.run", + kzt_per_object_got_plt_apply(&request, &result), 0); + check_int("fail-open.status", result.status, + KZT_PER_OBJECT_GOT_PLT_FAIL_OPEN); + check_int("fail-open.calls", calls, 0); + kzt_guest_registry_destroy(®istry); + test_complete_view_is_written_once(); + test_failed_write_can_retry(); + test_concurrent_observers_write_once(); + + if (failures) { + fprintf(stderr, "per-object GOT/PLT: %d failure(s)\n", failures); + return 1; + } + puts("per-object GOT/PLT: PASS"); + return 0; +} diff --git a/tests/unit/kzt/test_rela_immediate_candidate.c b/tests/unit/kzt/test_rela_immediate_candidate.c index f9620cb5d85..183c9f389f9 100644 --- a/tests/unit/kzt/test_rela_immediate_candidate.c +++ b/tests/unit/kzt/test_rela_immediate_candidate.c @@ -548,11 +548,12 @@ static void test_wi231_writer_failures_fail_open_to_legacy(void) wi231_apply_step4_request_contract(&request, &slot, legacy_target, &route); check_int("wi231.rollback_fail.failure", route.record.failure, - KZT_PATCH_SPIKE_FAILURE_ROLLBACK_FAILED); + KZT_PATCH_SPIKE_FAILURE_TRANSACTION_UNRECOVERABLE); check_int("wi231.rollback_fail.rollback", route.record.rollback_called, 1); - check_int("wi231.rollback_fail.legacy", slot.legacy_write_calls, 1); - check_ulong("wi231.rollback_fail.final", slot.value, legacy_target); + check_int("wi231.rollback_fail.legacy", slot.legacy_write_calls, 0); + check_ulong("wi231.rollback_fail.final", slot.value, + request.native_bridge_target); wi231_trace("writer-rollback-fail-fail-open", &slot, &route); } diff --git a/tests/unit/kzt/test_rela_request_enricher.c b/tests/unit/kzt/test_rela_request_enricher.c index 9c778e148ac..6eaae007077 100644 --- a/tests/unit/kzt/test_rela_request_enricher.c +++ b/tests/unit/kzt/test_rela_request_enricher.c @@ -328,6 +328,116 @@ static void test_all_evidence_allows_approved_plan(void) kzt_guest_registry_destroy(®istry); } +static void test_source_and_owner_enrichment_do_not_need_snapshot_allocation(void) +{ + kzt_guest_registry_t *registry = registry_with_source_and_owner(1, 1); + kzt_rela_request_enricher_input_t input = { .registry = registry }; + kzt_rela_request_enricher_result_t result; + kzt_rela_immediate_candidate_request_t request = base_request(); + + kzt_guest_registry_test_set_alloc_failure_after(0); + check_int("compact-enrich.call", + kzt_rela_immediate_request_enrich(&request, &input, &result), + 0); + kzt_guest_registry_test_set_alloc_failure_after(-1); + check_ulong("compact-enrich.source", request.source.link_map_addr, + 0x1000); + check_ulong("compact-enrich.source-generation", request.source.generation, + 1); + check_int("compact-enrich.owner", request.owner_match, + KZT_PATCH_OWNER_MATCH); + check_ulong("compact-enrich.owner-link-map", + request.current_owner.link_map_addr, 0x2000); + kzt_guest_registry_destroy(®istry); +} + +static void test_wrapper_only_preserves_validated_base_evidence(void) +{ + kzt_guest_registry_t *registry = registry_with_source_and_owner(1, 1); + kzt_wrapper_probe_manifest_t manifest = wrapper_manifest(); + fake_bridge_state_t bridge = { .next_bridge_target = 0x72000000 }; + kzt_wrapper_probe_bridge_ops_t ops = bridge_ops(&bridge); + kzt_rela_request_enricher_input_t full_input = { .registry = registry }; + kzt_rela_request_wrapper_only_input_t wrapper_input = { + .wrapper_manifest = &manifest, + .bridge_ops = &ops, + }; + kzt_rela_request_enricher_result_t result; + kzt_rela_immediate_candidate_request_t request = base_request(); + kzt_rela_immediate_candidate_request_t before; + + kzt_rela_request_enricher_result_init(&result); + check_int("wrapper_only.base", kzt_rela_immediate_request_enrich( + &request, &full_input, &result), 0); + before = request; + check_int("wrapper_only.call", + kzt_rela_immediate_request_enrich_wrapper_only( + &request, &wrapper_input, &result), 0); + check_ulong("wrapper_only.source.link_map", request.source.link_map_addr, + before.source.link_map_addr); + check_ulong("wrapper_only.source.generation", request.source.generation, + before.source.generation); + check_ulong("wrapper_only.dynamic.addr", request.dynamic_addr, + before.dynamic_addr); + check_ulong("wrapper_only.dynamic.bias", request.load_bias, + before.load_bias); + check_int("wrapper_only.dynamic.available", request.dynamic_view_available, + before.dynamic_view_available); + check_ulong("wrapper_only.dynamic.generation", + request.dynamic_view_generation, + before.dynamic_view_generation); + check_ulong("wrapper_only.owner.link_map", + request.current_owner.link_map_addr, + before.current_owner.link_map_addr); + check_ulong("wrapper_only.owner.generation", + request.current_owner.generation, + before.current_owner.generation); + check_int("wrapper_only.owner.match", request.owner_match, + before.owner_match); + check_ulong("wrapper_only.slot.addr", request.slot_addr, + before.slot_addr); + check_ulong("wrapper_only.slot.value", request.slot_current_value, + before.slot_current_value); + check_ulong("wrapper_only.symbol.index", request.symbol_index, + before.symbol_index); + check_int("wrapper_only.symbol.name", + strcmp(request.symbol_name, before.symbol_name), 0); + check_int("wrapper_only.version.evidence", request.version_evidence, + before.version_evidence); + check_int("wrapper_only.version", strcmp(request.version, before.version), + 0); + check_ulong("wrapper_only.bridge", request.native_bridge_target, + bridge.next_bridge_target); + check_int("wrapper_only.add", bridge.add_calls, 1); + kzt_guest_registry_destroy(®istry); +} + +static void test_wrapper_only_needs_validated_base_evidence(void) +{ + kzt_wrapper_probe_manifest_t manifest = wrapper_manifest(); + fake_bridge_state_t bridge = { .next_bridge_target = 0x72000000 }; + kzt_wrapper_probe_bridge_ops_t ops = bridge_ops(&bridge); + kzt_rela_request_wrapper_only_input_t input = { + .wrapper_manifest = &manifest, + .bridge_ops = &ops, + }; + kzt_rela_request_enricher_result_t result; + kzt_rela_immediate_candidate_request_t request = base_request(); + + kzt_rela_request_enricher_result_init(&result); + request.dynamic_view_available = 0; + request.owner_match = KZT_PATCH_OWNER_MATCH; + request.current_owner.known = 1; + request.current_owner.link_map_addr = 0x2000; + request.current_owner.generation = 1; + check_int("wrapper_only.missing-base", + kzt_rela_immediate_request_enrich_wrapper_only( + &request, &input, &result), 0); + check_ulong("wrapper_only.missing-base.no-bridge", + request.native_bridge_target, 0); + check_int("wrapper_only.missing-base.no-add", bridge.add_calls, 0); +} + static void test_unresolved_stub_detector_coordinates_and_bounds(void) { const uintptr_t plt_start = 0x2000; @@ -412,6 +522,75 @@ static void test_unresolved_stub_detector_coordinates_and_bounds(void) -0x20, 0x10, 0x18, 0, 0), 0); } +static void test_jump_slot_defer_plan(void) +{ + const uintptr_t plt_start = 0x2000; + const uintptr_t plt_end = 0x2040; + const uintptr_t gotplt_start = 0x3000; + const uintptr_t gotplt_end = 0x3040; + const intptr_t load_bias = 0x71000000; + static const struct { + const char *name; + uintptr_t slot_current_value; + int bind_is_local; + int bindnow; + int need_resolver_present; + int expected_unresolved_stub; + int expected_defer; + int expected_add_delta; + } cases[] = { + { "raw", 0x2010, 0, 0, 1, 1, 1, 1 }, + { "runtime-rebased", 0x71002010, 0, 0, 1, 1, 1, 0 }, + { "resolved", 0x71005000, 0, 0, 1, 0, 0, 0 }, + { "local.raw", 0x2010, 1, 0, 1, 1, 0, 0 }, + { "local.runtime", 0x71002010, 1, 0, 1, 1, 0, 0 }, + { "bindnow.raw", 0x2010, 0, 1, 1, 1, 0, 0 }, + { "bindnow.runtime", 0x71002010, 0, 1, 1, 1, 0, 0 }, + { "no-need-resolver.raw", 0x2010, 0, 0, 0, 1, 0, 0 }, + { "no-need-resolver.runtime", 0x71002010, 0, 0, 0, 1, 0, 0 }, + }; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + kzt_rela_jump_slot_defer_input_t input = { + .slot_current_value = cases[i].slot_current_value, + .bind_is_local = cases[i].bind_is_local, + .bindnow = cases[i].bindnow, + .need_resolver_present = cases[i].need_resolver_present, + .load_bias = load_bias, + .plt_start = plt_start, + .plt_end = plt_end, + .gotplt_start = gotplt_start, + .gotplt_end = gotplt_end, + }; + kzt_rela_jump_slot_defer_plan_t plan = + kzt_rela_jump_slot_defer_plan(&input); + + check_int(cases[i].name, plan.slot_is_unresolved_stub, + cases[i].expected_unresolved_stub); + check_int(cases[i].name, plan.should_defer, + cases[i].expected_defer); + check_int(cases[i].name, plan.should_add_delta, + cases[i].expected_add_delta); + } + + { + kzt_rela_jump_slot_defer_input_t overlapping_input = { + .slot_current_value = 0x2020, + .need_resolver_present = 1, + .load_bias = 0x10, + .plt_start = 0x2000, + .plt_end = 0x2040, + }; + kzt_rela_jump_slot_defer_plan_t plan = + kzt_rela_jump_slot_defer_plan(&overlapping_input); + + check_int("overlap.unresolved", plan.slot_is_unresolved_stub, 1); + check_int("overlap.defer", plan.should_defer, 1); + check_int("overlap.add-delta", plan.should_add_delta, 0); + } +} + static void test_distinct_targets_write_only_selected_slot(void) { const uintptr_t current_target = 0x71000010; @@ -741,10 +920,30 @@ static void test_dead_object_is_not_relocation_source(void) kzt_guest_registry_destroy(®istry); } +static void test_malformed_wrapper_evidence_does_not_fail_base_enrichment(void) +{ + kzt_guest_registry_t *registry = registry_with_source_and_owner(1, 1); + kzt_rela_request_enricher_input_t input = { .registry = registry }; + kzt_rela_request_enricher_result_t result; + kzt_rela_immediate_candidate_request_t request = base_request(); + + request.symbol_name = NULL; + check_int("malformed-wrapper.base-enrich", + kzt_rela_immediate_request_enrich( + &request, &input, &result), 0); + check_int("malformed-wrapper.default", request.wrapper_match, + KZT_PATCH_WRAPPER_NO_MANIFEST); + kzt_guest_registry_destroy(®istry); +} + int main(void) { test_all_evidence_allows_approved_plan(); + test_source_and_owner_enrichment_do_not_need_snapshot_allocation(); + test_wrapper_only_preserves_validated_base_evidence(); + test_wrapper_only_needs_validated_base_evidence(); test_unresolved_stub_detector_coordinates_and_bounds(); + test_jump_slot_defer_plan(); test_distinct_targets_write_only_selected_slot(); test_unresolved_stub_skips_owner_and_falls_back_to_legacy(); test_no_manifest_keeps_default_fail_open(); @@ -753,6 +952,7 @@ int main(void) test_bridge_zero_blocks_approved_plan(); test_native_bridge_is_not_used_as_expected_owner(); test_dead_object_is_not_relocation_source(); + test_malformed_wrapper_evidence_does_not_fail_base_enrichment(); if (failures) { fprintf(stderr, "kzt-rela-request-enricher: %d failure(s)\n", diff --git a/tests/unit/kzt/test_rela_runtime_helper_chain.c b/tests/unit/kzt/test_rela_runtime_helper_chain.c index df564854665..f7bc3888c03 100644 --- a/tests/unit/kzt/test_rela_runtime_helper_chain.c +++ b/tests/unit/kzt/test_rela_runtime_helper_chain.c @@ -56,6 +56,12 @@ enum fixture_setup_status { }; uintptr_t CheckBridged(bridge_t *bridge, void *fnc); +int BridgeForkProtectionAvailable(void); + +int BridgeForkProtectionAvailable(void) +{ + return 1; +} uintptr_t CheckBridged(bridge_t *bridge, void *fnc) { diff --git a/tests/unit/kzt/test_wi1056_per_object_got_plt_source_contract.py b/tests/unit/kzt/test_wi1056_per_object_got_plt_source_contract.py new file mode 100644 index 00000000000..09b8b178735 --- /dev/null +++ b/tests/unit/kzt/test_wi1056_per_object_got_plt_source_contract.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def body(text, signature): + start = text.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start) + depth = 0 + for index in range(opening, len(text)): + if text[index] == "{": + depth += 1 + elif text[index] == "}": + depth -= 1 + if depth == 0: + return text[opening + 1:index] + raise AssertionError(f"unterminated function: {signature}") + + +root = pathlib.Path(sys.argv[1]).resolve() +registry_h = (root / "target/i386/latx/include/kzt_guest_registry.h").read_text() +registry = (root / "target/i386/latx/context/kzt_guest_registry.c").read_text() +adapter = (root / "target/i386/latx/context/kzt_observation_adapter.c").read_text() +myalign = (root / "target/i386/latx/context/myalign.c").read_text() +elfloader = (root / "target/i386/latx/context/elfloader.c").read_text() +dl_api = (root / "target/i386/latx/context/kzt_guest_dl_api.c").read_text() +meson = (root / "target/i386/latx/context/meson.build").read_text() + +for required in ( + "KZT_GUEST_GOT_PLT_INJECTION_APPLYING", + "kzt_guest_registry_got_plt_injection_claim(", + "kzt_guest_registry_got_plt_injection_finish(", + "kzt_guest_registry_got_plt_injection_claimed(", +): + if required not in registry_h: + raise AssertionError(f"Registry lacks per-generation injection state: {required}") + +claim = body(registry, "kzt_guest_registry_got_plt_injection_claim(") +for required in ( + "kzt_registry_got_plt_view_complete(view)", + "KZT_GUEST_GOT_PLT_INJECTION_APPLYING", + "KZT_GUEST_GOT_PLT_INJECTION_ALREADY_APPLIED", + "KZT_GUEST_GOT_PLT_INJECTION_IN_PROGRESS", +): + if required not in claim: + raise AssertionError(f"claim loses exact fail-open behavior: {required}") + +callback = body(adapter, "int kzt_observe_guest_object_from_callback(") +observe = callback.find("kzt_observe_guest_object(request,") +per_object = callback.find("request->per_object_flow(") +legacy = callback.find("request->legacy_flow(request->link_map_addr,") +if not (0 <= observe < per_object): + raise AssertionError("per-object injection is not ordered after observation") +if "observation_result == KZT_OBSERVATION_ADAPTER_ADDED" not in callback or \ + "observation_result == KZT_OBSERVATION_ADAPTER_UPDATED" not in callback: + raise AssertionError("per-object injection is not limited to committed objects") +if "observation_result == KZT_OBSERVATION_ADAPTER_CONFLICT" in callback[ + per_object - 160:per_object + 80]: + raise AssertionError("per-object injection accepts conflicted objects") + +hook = body(myalign, "static int kzt_tb_callback_per_object_got_plt(") +for required in ( + "kzt_per_object_got_plt_apply(", + ".apply = KztPerObjectGotPltWrite,", + "kzt_tb_callback_materialize_binding(link_map_addr, opaque)", +): + if required not in hook: + raise AssertionError(f"loader hook misses new injection handoff: {required}") + +writer = body(elfloader, "int KztPerObjectGotPltWrite(") +for required in ( + "view->status != KZT_GUEST_DYNAMIC_COMPLETE", + "kzt_per_object_dynamic_field_runtime(&view->pltgot", + "kzt_per_object_plt_layout(jmprel_runtime, pltrelsz, load_bias", + "head->plt = plt_start", + "guest_link_map != link_map_addr", + "kzt_guest_registry_publish_lazy_resolver(", + "kzt_elfloader_write_guest_word(resolver.link_map_slot", + "kzt_elfloader_write_guest_word(resolver.resolver_slot", +): + if required not in writer: + raise AssertionError(f"runtime Dynamic View writer lacks {required}") +for forbidden in ("LoadAndCheckElfHeader", "LoadNeededLibs", "RelocateElfPlt("): + if forbidden in writer: + raise AssertionError(f"new writer must not depend on legacy flow: {forbidden}") + +relocate = body(elfloader, "int RelocateElfPlt(") +if "kzt_per_object_got_plt_apply(&request, &result)" not in relocate: + raise AssertionError("main/object RelocateElfPlt path does not enter new chain") +if "kzt_guest_registry_got_plt_injection_claimed(" not in relocate: + raise AssertionError("RelocateElfPlt does not suppress a duplicate claimed write") + +close = body(dl_api, "int kzt_guest_dl_api_dlclose(") +if "KztPerObjectGotPltRelease(" in close: + raise AssertionError("dlclose releases runtime state without an unload fact") + +unload = body(dl_api, "int kzt_guest_dl_api_publish_unload(") +retire = unload.find("kzt_guest_registry_finish_loader_unload(") +release = unload.find("KztPerObjectGotPltRelease(lazy_resolver.object_head)") +if not (0 <= retire < release): + raise AssertionError( + "precise unload does not release the Registry-owned runtime header" + ) + +prepare = body(dl_api, "int kzt_guest_dl_api_prepare_unload(") +if "kzt_guest_registry_begin_loader_unload(" not in prepare: + raise AssertionError("RT_DELETE does not close Registry lease admission") + +if "'kzt_per_object_got_plt.c'" not in meson: + raise AssertionError("per-object module is not linked into production") + +print("WI-1056 per-object GOT/PLT source contract: PASS") diff --git a/tests/unit/kzt/test_wi1100_atfork_bridge_gate_source_contract.py b/tests/unit/kzt/test_wi1100_atfork_bridge_gate_source_contract.py new file mode 100644 index 00000000000..7aac5719a20 --- /dev/null +++ b/tests/unit/kzt/test_wi1100_atfork_bridge_gate_source_contract.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +from pathlib import Path +import sys + + +def fail(message): + raise SystemExit(f"WI-1100 atfork bridge gate contract: FAIL: {message}") + + +def function_body(source, marker, end_marker): + start = source.find(marker) + end = source.find(end_marker, start) + if start < 0 or end < 0: + fail(f"cannot delimit {marker}") + return source[start:end] + + +root = Path(sys.argv[1]) +elfloader = ( + root / "target/i386/latx/context/elfloader.c" +).read_text(encoding="utf-8") +runtime_bridge = ( + root / "target/i386/latx/context/kzt_rela_runtime_bridge.c" +).read_text(encoding="utf-8") + +runtime_prepare = function_body( + runtime_bridge, + "static int kzt_rela_runtime_wrapper_provider_prepare_mode(", + "\nint kzt_rela_runtime_wrapper_provider_prepare(", +) +runtime_gate = runtime_prepare.find("if (!BridgeForkProtectionAvailable())") +runtime_bridge_access = runtime_prepare.find( + "kzt_wrapper_bridge_provider_prepare_with_version_evidence(" +) +if not (0 <= runtime_gate < runtime_bridge_access): + fail("runtime provider can access a bridge before the atfork gate") +if "memset(provider, 0, sizeof(*provider));" not in runtime_prepare[ + runtime_gate:runtime_bridge_access +]: + fail("runtime provider fallback does not clear its output") + +per_object = function_body( + elfloader, + "int KztPerObjectGotPltWrite(", + "\nstatic int kzt_elfloader_read_guest_memory(", +) +per_object_gate = per_object.find("if (!BridgeForkProtectionAvailable())") +per_object_add = per_object.find("AddBridge(") +if not (0 <= per_object_gate < per_object_add): + fail("per-object resolver can create a bridge before the atfork gate") + +relocate = function_body(elfloader, "int RelocateElfPlt(", "\n#if 0") +need_resolver = relocate.find("if(need_resolver)") +relocate_gate = relocate.find( + "if (!BridgeForkProtectionAvailable())", need_resolver +) +first_add = relocate.find("AddBridge(", need_resolver) +if not (0 <= need_resolver < relocate_gate < first_add): + fail("loader resolver can create a bridge before the atfork gate") +gate_block_end = relocate.find("}", relocate_gate) +if "return 0;" not in relocate[relocate_gate:gate_block_end]: + fail("loader fallback does not preserve the guest resolver") + +print("WI-1100 atfork bridge gate source contract: PASS") diff --git a/tests/unit/kzt/test_wi237_jump_slot_route_contract.py b/tests/unit/kzt/test_wi237_jump_slot_route_contract.py index f3f62dc1ce5..3b5ac0c9bdc 100644 --- a/tests/unit/kzt/test_wi237_jump_slot_route_contract.py +++ b/tests/unit/kzt/test_wi237_jump_slot_route_contract.py @@ -29,29 +29,52 @@ def body(text, signature): shared = "kzt_production_jump_slot_route(" assert shared in eager -assert shared in lazy assert eager.count("uintptr_t slot_observation = (uintptr_t)(*p);") == 1 -assert re.search( - r"kzt_rela_slot_current_is_unresolved_stub\(\s*slot_observation\s*,", +assert "kzt_rela_jump_slot_defer_input_t defer_input" in eager +assert "kzt_rela_jump_slot_defer_plan(&defer_input)" in eager +assert not re.search( + r"kzt_rela_slot_current_is_unresolved_stub\s*\(\s*" + r"slot_observation\s*,", eager, ) assert "uintptr_t expected_guest_target = slot_observation;" in eager -assert "(void *)slot_observation, (void*)legacy_target" in eager -assert "p,\n slot_observation," in eager +assert re.search( + r"kzt_production_jump_slot_route\s*\(\s*" + r"my_context\s*,\s*NULL\s*,\s*slot_observation\s*,", + eager, +) +assert re.search(r"&rela\[i\]\s*,\s*p\s*,\s*slot_observation\s*,", eager) assert "expected_guest_target = legacy_target" not in eager -assert lazy.count("uintptr_t slot_observation = (uintptr_t)(*p);") == 1 -assert "(void *)slot_observation, (void*)legacy_target" in lazy -assert "rel, p,\n slot_observation, 1," in lazy +eager_final_coordinates = re.findall( + r"kzt_rela_slot_current_is_unresolved_stub\s*\(\s*" + r"route_result\.final_value\s*,\s*" + r"(KZT_RELA_STUB_COORDINATE_\w+)", + eager, +) +assert eager_final_coordinates == [ + "KZT_RELA_STUB_COORDINATE_LINK_TIME_RAW", + "KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED", +] -# Runtime-disabled KZT retains the historical direct stores. Runtime-enabled -# eager and lazy branches do not add an unchecked store after entering Step5. -assert "if (option_kzt || wine_option_kzt)" in eager +# Eager keeps the normal relocation write in its caller. Lazy first tries the +# Registry-backed direct route, then hands off to the current object's guest +# resolver without reviving the removed host lookup/write path. +assert "option_kzt || wine_option_kzt" in eager assert "if (option_kzt || wine_option_kzt)" in lazy +assert lazy.count("kzt_production_lazy_direct_route(") == 1 +assert lazy.count("kzt_plt_resolver_enter(") == 1 +assert "plt_resolver_handoff_guest(" not in lazy +assert lazy.count("plt_resolver_handoff_guest_or_abort(") == 3 +assert "plt_resolver_lookup_host_symbol(" not in lazy +assert "getAlternate(" not in lazy +assert not re.search(r"\*p\s*=\s*(?:offs|legacy_target)\s*;", lazy) assert "__atomic_compare_exchange_n" in production -assert "snapshot->namespace_id.status != KZT_GUEST_FIELD_OK" in production -assert "snapshot->namespace_id.value != 0" in production -assert "input.resolved_target_matches_legacy =\n resolved_target == legacy_target;" in production +assert "kzt_guest_registry_find_live_object(" in production +assert "match.namespace_id_status != KZT_GUEST_FIELD_OK" in production +assert "match.namespace_id != 0" in production +assert "resolved_target_matches_legacy" not in production +assert "state->resolved_provider = handle->library;" in production assert "production_request_is_main_namespace" in production assert "production_shadow_runtime_candidate" in production assert "kzt_runtime_candidate_shadow_run" in production @@ -71,19 +94,16 @@ def body(text, signature): assert "&state->base_enrich_result" in base_enrich assert "&state->bridge_enrich_result" in bridge_enrich -# The shared helper retains the exact handle across bridge enrichment/writing, -# releases it before fallback, and never performs a direct slot store. +# The eager helper retains the exact handle across bridge enrichment/writing, +# releases it before declining, and never performs a direct slot store. acquire = route.index("ops->acquire_exact_provider") bridge = route.index("ops->enrich_bridge", acquire) writer = route.index("ops->try_native_writer", bridge) release = route.index("ops->release_exact_provider", writer) -fallback = route.index("route_legacy_fallback", release) -assert acquire < bridge < writer < release < fallback +decline = route.index("route_decline_without_write", release) +assert acquire < bridge < writer < release < decline +assert "route_legacy_fallback" not in route assert "*(uintptr_t *)" not in route assert "compare_exchange_slot" in route -# Lazy currently has no raw expected guest target/owner proof and explicitly -# enters the route with expected_guest_target_present == 0. -assert "vername, 0, 0, legacy_target, &route_result" in lazy - -print("WI-237 eager/lazy shared jump-slot route contract: PASS") +print("WI-237 eager route and lazy guest-handoff contract: PASS") diff --git a/tests/unit/kzt/test_wi256_lazy_binding.c b/tests/unit/kzt/test_wi256_lazy_binding.c index 7ab0fad8715..6e1482e1f43 100644 --- a/tests/unit/kzt/test_wi256_lazy_binding.c +++ b/tests/unit/kzt/test_wi256_lazy_binding.c @@ -71,6 +71,16 @@ static int route_guest_target(const kzt_lazy_binding_pending_t *pending, result->final_value = fixture->competing_target; fixture->slot = fixture->competing_target; break; + case KZT_LAZY_BINDING_ROUTE_WRITE_ROLLED_BACK: + result->selected_target = guest_target; + result->final_value = guest_target; + fixture->slot = guest_target; + break; + case KZT_LAZY_BINDING_ROUTE_UNRECOVERABLE: + result->selected_target = fixture->competing_target; + result->final_value = fixture->competing_target; + fixture->slot = fixture->competing_target; + break; case KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED: case KZT_LAZY_BINDING_ROUTE_ERROR: result->selected_target = guest_target; @@ -193,6 +203,49 @@ static void test_expected_target_mismatch_preserves_competitor_without_retry(voi CHECK("race.consumed", result.pending_armed == 0); } +static void test_transaction_failure_statuses_are_not_guest_preserved(void) +{ + const struct { + kzt_lazy_binding_route_status_t route_status; + kzt_lazy_binding_status_t status; + kzt_lazy_binding_reason_t reason; + uintptr_t final_value; + } cases[] = { + { + KZT_LAZY_BINDING_ROUTE_WRITE_ROLLED_BACK, + KZT_LAZY_BINDING_WRITE_ROLLED_BACK, + KZT_LAZY_BINDING_REASON_WRITE_ROLLED_BACK, + 0, + }, + { + KZT_LAZY_BINDING_ROUTE_UNRECOVERABLE, + KZT_LAZY_BINDING_UNRECOVERABLE, + KZT_LAZY_BINDING_REASON_UNRECOVERABLE, + 1, + }, + }; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + fixture_t f = fixture(); + kzt_lazy_binding_begin_request_t request = begin_request_for(&f); + kzt_lazy_binding_ops_t ops = ops_for(&f); + kzt_lazy_binding_result_t result; + kzt_lazy_binding_pending_t pending; + + begin_binding(&request, &pending, &result); + f.slot = f.guest_target; + f.route_status = cases[i].route_status; + CHECK("transaction-status.complete", + kzt_lazy_binding_complete(&pending, &ops, &result) == 0); + CHECK("transaction-status.status", result.status == cases[i].status); + CHECK("transaction-status.reason", result.reason == cases[i].reason); + CHECK("transaction-status.final", + result.slot_after == (cases[i].final_value ? + f.competing_target : f.guest_target)); + } +} + static void test_missing_symbol_version_fails_open(void) { fixture_t f = fixture(); @@ -470,6 +523,7 @@ int main(void) test_first_call_hands_off_to_guest(); test_post_bind_call_can_install_native_bridge(); test_expected_target_mismatch_preserves_competitor_without_retry(); + test_transaction_failure_statuses_are_not_guest_preserved(); test_missing_symbol_version_fails_open(); test_confirmed_unversioned_binding_can_apply(); test_unknown_and_error_version_evidence_fail_open(); diff --git a/tests/unit/kzt/test_wi256_lazy_diagnostics.c b/tests/unit/kzt/test_wi256_lazy_diagnostics.c index b683f4adfec..3cb355e9156 100644 --- a/tests/unit/kzt/test_wi256_lazy_diagnostics.c +++ b/tests/unit/kzt/test_wi256_lazy_diagnostics.c @@ -283,6 +283,56 @@ static void test_cas_mismatch_uses_final_competitor_target(void) "selected_second_target=0x75000050"); } +static void test_transaction_failure_statuses_are_explicit(void) +{ + const struct { + kzt_lazy_binding_status_t status; + kzt_lazy_binding_reason_t reason; + const char *name; + } cases[] = { + { + KZT_LAZY_BINDING_WRITE_ROLLED_BACK, + KZT_LAZY_BINDING_REASON_WRITE_ROLLED_BACK, + "WRITE_ROLLED_BACK", + }, + { + KZT_LAZY_BINDING_UNRECOVERABLE, + KZT_LAZY_BINDING_REASON_UNRECOVERABLE, + "UNRECOVERABLE", + }, + }; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + kzt_lazy_binding_pending_t pending = base_pending(); + kzt_lazy_binding_result_t binding_result = base_result(); + capture_sink_t capture = { 0 }; + char buffer[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; + kzt_lazy_diagnostic_input_t input = { + .enabled = 1, + .pending = &pending, + .binding_result = &binding_result, + .buffer = buffer, + .buffer_size = sizeof(buffer), + .sink = capture_line, + .sink_opaque = &capture, + }; + kzt_lazy_diagnostic_emit_result_t emit_result; + + binding_result.status = cases[i].status; + binding_result.reason = cases[i].reason; + check_int("transaction-diagnostic.emit", + kzt_lazy_diagnostics_emit(&input, &emit_result), 0); + check_string("transaction-diagnostic.status", + emit_result.record.completion_route_status, + cases[i].name); + check_string("transaction-diagnostic.reason", + emit_result.record.reason, cases[i].name); + check_contains("transaction-diagnostic.line", capture.line, + cases[i].name); + } +} + static void test_production_emit_writes_stderr(void) { kzt_lazy_binding_pending_t pending = base_pending(); @@ -328,6 +378,7 @@ int main(void) test_slot_unchanged_uses_stable_status_name(); test_missing_version_uses_stable_status_name(); test_cas_mismatch_uses_final_competitor_target(); + test_transaction_failure_statuses_are_explicit(); test_production_emit_writes_stderr(); if (failures) { diff --git a/tests/unit/kzt/test_wi256_plt_resolver_adapter.c b/tests/unit/kzt/test_wi256_plt_resolver_adapter.c index ad9ac688d2e..6f18b225193 100644 --- a/tests/unit/kzt/test_wi256_plt_resolver_adapter.c +++ b/tests/unit/kzt/test_wi256_plt_resolver_adapter.c @@ -8,6 +8,7 @@ typedef enum begin_outcome { BEGIN_PENDING_OCCUPIED, BEGIN_GENERATION_CHANGED, BEGIN_NON_MAIN_NAMESPACE, + BEGIN_CALLBACK_FAILED, } begin_outcome_t; typedef struct fixture { @@ -22,11 +23,16 @@ typedef struct fixture { unsigned long generation; uintptr_t namespace_id; kzt_guest_library_namespace_kind_t namespace_kind; + kzt_symbol_version_evidence_t version_evidence; + const char *version; + int source_enabled; int source_present; begin_outcome_t begin_outcome; int lookup_calls; int begin_calls; int pending_creations; + uintptr_t completion_bridge; + uintptr_t original_return; kzt_lazy_binding_pending_t pending; kzt_lazy_binding_begin_request_t captured_request; } fixture_t; @@ -60,6 +66,9 @@ static void fixture_init(fixture_t *fixture) fixture->forbidden_global_resolver = 0x15000500; fixture->generation = 7; fixture->namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN; + fixture->version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + fixture->version = "GLIBC_2.2.5"; + fixture->source_enabled = 1; fixture->source_present = 1; fixture->begin_outcome = BEGIN_ARMED; reset_frame(fixture); @@ -76,12 +85,15 @@ static int lookup_source(uintptr_t object_head, return -1; } *source = (kzt_plt_resolver_source_t) { + .enabled = fixture->source_enabled, .context_id = 0x16000600, .object_head = fixture->object_head, .source_link_map = fixture->self_link_map, .source_generation = fixture->generation, .namespace_id = fixture->namespace_id, .namespace_kind = fixture->namespace_kind, + .version_evidence = fixture->version_evidence, + .version = fixture->version, .guest_resolver = fixture->object_guest_resolver, }; return 0; @@ -100,6 +112,11 @@ static int begin_lazy_binding( fixture->captured_request = *request; memset(result, 0, sizeof(*result)); result->selected_target = fixture->object_guest_resolver; + if (!request->enabled) { + result->status = KZT_LAZY_BINDING_BYPASS; + result->reason = KZT_LAZY_BINDING_REASON_DISABLED; + return 0; + } switch (fixture->begin_outcome) { case BEGIN_ARMED: if (!pending->armed) { @@ -124,6 +141,8 @@ static int begin_lazy_binding( result->reason = KZT_LAZY_BINDING_REASON_NON_MAIN_NAMESPACE; result->pending_armed = 0; break; + case BEGIN_CALLBACK_FAILED: + return -1; } return 0; } @@ -134,6 +153,8 @@ static kzt_plt_resolver_runtime_ops_t ops_for(fixture_t *fixture) .lookup_source = lookup_source, .begin_lazy_binding = begin_lazy_binding, .pending = &fixture->pending, + .completion_bridge = fixture->completion_bridge, + .original_return = &fixture->original_return, .opaque = fixture, }; } @@ -180,6 +201,52 @@ static void test_reads_real_frame_and_preserves_return_address(void) check_guest_frame("frame.stack-pointer", &f); } +static void test_handoff_without_completion_bridge_keeps_return_slot(void) +{ + fixture_t f; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + uint64_t *sp; + + fixture_init(&f); + f.original_return = 0xfeedface; + ops = ops_for(&f); + CHECK("no-bridge.enter", + kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); + CHECK("no-bridge.handoff", + result.status == KZT_PLT_RESOLVER_HANDOFF_GUEST); + sp = (uint64_t *)f.cpu.regs[R_ESP]; + CHECK("no-bridge.final-rsp", sp == &f.stack[2]); + CHECK("no-bridge.resolver", sp[0] == f.object_guest_resolver); + CHECK("no-bridge.link-map", sp[1] == f.self_link_map); + CHECK("no-bridge.slot", sp[2] == f.relocation_slot); + CHECK("no-bridge.return", sp[3] == f.return_address); + CHECK("no-bridge.original-return-unchanged", + f.original_return == 0xfeedface); +} + +static void test_handoff_with_completion_bridge_preserves_original_return(void) +{ + fixture_t f; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + uint64_t *sp; + + fixture_init(&f); + f.completion_bridge = 0x17000700; + ops = ops_for(&f); + CHECK("bridge.enter", + kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); + CHECK("bridge.handoff", result.status == KZT_PLT_RESOLVER_HANDOFF_GUEST); + sp = (uint64_t *)f.cpu.regs[R_ESP]; + CHECK("bridge.final-rsp", sp == &f.stack[2]); + CHECK("bridge.resolver", sp[0] == f.object_guest_resolver); + CHECK("bridge.link-map", sp[1] == f.self_link_map); + CHECK("bridge.slot", sp[2] == f.relocation_slot); + CHECK("bridge.return", sp[3] == f.completion_bridge); + CHECK("bridge.original-return", f.original_return == f.return_address); +} + static void test_uses_per_object_resolver_and_calls_only_contract_one(void) { fixture_t f; @@ -205,6 +272,36 @@ static void test_uses_per_object_resolver_and_calls_only_contract_one(void) f.captured_request.relocation_index == f.relocation_slot); } +static void test_forwards_explicit_version_evidence(void) +{ + static const struct { + kzt_symbol_version_evidence_t evidence; + const char *version; + } cases[] = { + { KZT_SYMBOL_VERSION_VERSIONED, "GLIBC_2.2.5" }, + { KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL }, + { KZT_SYMBOL_VERSION_ERROR, NULL }, + }; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + fixture_t f; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + + fixture_init(&f); + f.version_evidence = cases[i].evidence; + f.version = cases[i].version; + ops = ops_for(&f); + CHECK("version.enter", + kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); + CHECK("version.evidence", + f.captured_request.version_evidence == cases[i].evidence); + CHECK("version.value", + f.captured_request.version == cases[i].version); + } +} + static void test_main_generation_match_creates_one_pending(void) { fixture_t f; @@ -297,12 +394,40 @@ static void test_second_object_lookup_failure_never_uses_first_resolver(void) check_original_frame("second-missing.stack-pointer", &f); } +static void test_disabled_object_hands_off_once_to_its_guest_resolver(void) +{ + fixture_t f; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + uintptr_t original_sp; + + fixture_init(&f); + f.source_enabled = 0; + f.generation = 0; + f.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_UNSUPPORTED; + original_sp = f.cpu.regs[R_ESP]; + ops = ops_for(&f); + CHECK("disabled-fallback.enter", + kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); + CHECK("disabled-fallback.status", + result.status == KZT_PLT_RESOLVER_GUEST_PRESERVED); + CHECK("disabled-fallback.resolver", + result.selected_resolver == f.object_guest_resolver); + CHECK("disabled-fallback.lookup-once", f.lookup_calls == 1); + CHECK("disabled-fallback.begin-once", f.begin_calls == 1); + CHECK("disabled-fallback.no-pending", f.pending_creations == 0); + CHECK("disabled-fallback.one-resolver-push", + f.cpu.regs[R_ESP] + sizeof(uint64_t) == original_sp); + check_guest_frame("disabled-fallback.stack-pointer", &f); +} + static void test_contract_one_rejections_restore_guest_frame(void) { const begin_outcome_t outcomes[] = { BEGIN_PENDING_OCCUPIED, BEGIN_GENERATION_CHANGED, BEGIN_NON_MAIN_NAMESPACE, + BEGIN_CALLBACK_FAILED, }; for (size_t i = 0; i < sizeof(outcomes) / sizeof(outcomes[0]); ++i) { @@ -327,15 +452,72 @@ static void test_contract_one_rejections_restore_guest_frame(void) } } +static void test_resolver_injection_requires_distinct_guest_target(void) +{ + CHECK("inject.valid", + kzt_plt_resolver_injection_allowed(0x1000, 0x2000)); + CHECK("inject.no-guest", + !kzt_plt_resolver_injection_allowed(0, 0x2000)); + CHECK("inject.no-bridge", + !kzt_plt_resolver_injection_allowed(0x1000, 0)); + CHECK("inject.no-recursion", + !kzt_plt_resolver_injection_allowed(0x2000, 0x2000)); +} + +static void test_resolver_entry_bounds_are_checked_before_access(void) +{ + const uintptr_t rela_table = 0x1000; + const uintptr_t symbol_table = 0x2000; + const size_t rela_size = 4 * 24; + + CHECK("bounds.rela-first", + kzt_plt_resolver_relocation_index_valid( + 0, rela_table, rela_size, 24)); + CHECK("bounds.rela-last", + kzt_plt_resolver_relocation_index_valid( + 3, rela_table, rela_size, 24)); + CHECK("bounds.rela-past-end", + !kzt_plt_resolver_relocation_index_valid( + 4, rela_table, rela_size, 24)); + CHECK("bounds.rela-int-overflow", + !kzt_plt_resolver_relocation_index_valid( + UINT64_MAX, rela_table, rela_size, 24)); + CHECK("bounds.rela-missing-table", + !kzt_plt_resolver_relocation_index_valid( + 0, 0, rela_size, 24)); + CHECK("bounds.rela-zero-entry", + !kzt_plt_resolver_relocation_index_valid( + 0, rela_table, rela_size, 0)); + CHECK("bounds.rela-truncated-table", + !kzt_plt_resolver_relocation_index_valid( + 0, rela_table, rela_size - 1, 24)); + CHECK("bounds.symbol-first", + kzt_plt_resolver_symbol_index_valid(0, symbol_table, 3)); + CHECK("bounds.symbol-last", + kzt_plt_resolver_symbol_index_valid(2, symbol_table, 3)); + CHECK("bounds.symbol-past-end", + !kzt_plt_resolver_symbol_index_valid(3, symbol_table, 3)); + CHECK("bounds.symbol-missing-table", + !kzt_plt_resolver_symbol_index_valid(0, 0, 3)); + CHECK("bounds.symbol-empty", + !kzt_plt_resolver_symbol_index_valid(0, symbol_table, 0)); +} + int main(void) { test_reads_real_frame_and_preserves_return_address(); + test_handoff_without_completion_bridge_keeps_return_slot(); + test_handoff_with_completion_bridge_preserves_original_return(); test_uses_per_object_resolver_and_calls_only_contract_one(); + test_forwards_explicit_version_evidence(); test_main_generation_match_creates_one_pending(); test_nested_pending_busy_preserves_outer_cpu_pending(); test_missing_object_restores_original_frame(); test_second_object_lookup_failure_never_uses_first_resolver(); + test_disabled_object_hands_off_once_to_its_guest_resolver(); test_contract_one_rejections_restore_guest_frame(); + test_resolver_injection_requires_distinct_guest_target(); + test_resolver_entry_bounds_are_checked_before_access(); if (failures) { fprintf(stderr, "%d WI-256 resolver-adapter checks failed\n", failures); diff --git a/tests/unit/kzt/test_wi256_plt_resolver_source_contract.py b/tests/unit/kzt/test_wi256_plt_resolver_source_contract.py index 1e6c6d09aee..a24c72c60e6 100644 --- a/tests/unit/kzt/test_wi256_plt_resolver_source_contract.py +++ b/tests/unit/kzt/test_wi256_plt_resolver_source_contract.py @@ -16,6 +16,9 @@ def fail(message: str) -> None: adapter_text = adapter.read_text(encoding="utf-8") elfloader_text = elfloader.read_text(encoding="utf-8") +elfloader_private_text = ( + root / "target/i386/latx/include/elfloader_private.h" +).read_text(encoding="utf-8") lazy_header = (root / "target/i386/latx/include/kzt_lazy_binding.h").read_text( encoding="utf-8" ) @@ -42,8 +45,43 @@ def fail(message: str) -> None: if "dl_runtime_resolver" in adapter_text: fail("resolver adapter must never substitute a process-global resolver") -entry = "kzt_plt_resolver_enter(" +production_state_start = elfloader_text.find( + "typedef struct kzt_plt_resolver_production_state" +) +production_lookup_start = elfloader_text.find( + "static int kzt_plt_resolver_lookup_source(", production_state_start +) resolver_start = elfloader_text.find("void PltResolver(void)") +if min(production_state_start, production_lookup_start, resolver_start) < 0: + fail("missing production PLT resolver evidence path") +production_state = elfloader_text[ + production_state_start:production_lookup_start +] +production_lookup = elfloader_text[production_lookup_start:resolver_start] +if "kzt_symbol_version_evidence_t version_evidence;" not in production_state: + fail("production resolver state must retain explicit version evidence") +if ".version_evidence = state->version_evidence," not in production_lookup: + fail("production lookup must forward explicit version evidence") +if "kzt_guest_registry_find_lazy_source(" not in production_lookup: + fail("production lookup must use the single-lock, allocation-free Registry source query") +if "state->head->kzt_guest_resolver" not in production_lookup: + fail("production lookup must retain a per-object guest resolver fallback") +if ".enabled = 0," not in production_lookup: + fail("per-object resolver fallback must disable native completion") +if ".guest_resolver = state->head->kzt_guest_resolver," not in production_lookup: + fail("per-object fallback must select the current object's guest resolver") +for forbidden_lookup_step in ( + "kzt_guest_registry_find_by_link_map(", + "kzt_guest_registry_find_lazy_resolver(", + "kzt_guest_object_snapshot_free(", +): + if forbidden_lookup_step in production_lookup: + fail( + "production lookup retains the allocating or double-lock Registry path: " + f"{forbidden_lookup_step}" + ) + +entry = "kzt_plt_resolver_enter(" if resolver_start < 0: fail("missing production PltResolver slow path") body_start = elfloader_text.find("{", resolver_start) @@ -59,9 +97,112 @@ def fail(message: str) -> None: break if resolver_end < 0: fail("could not delimit production PltResolver") -if entry not in elfloader_text[resolver_start:resolver_end]: +resolver_body = elfloader_text[resolver_start:resolver_end] +for required_version_mapping in ( + "version == -1 || version < 2", + "KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED", + "vername && vername[0]", + "KZT_SYMBOL_VERSION_VERSIONED", + "KZT_SYMBOL_VERSION_ERROR", + ".version_evidence = version_evidence,", +): + if required_version_mapping not in resolver_body: + fail( + "PltResolver must explicitly classify versioned, unversioned, " + f"and malformed version evidence: {required_version_mapping}" + ) +if entry not in resolver_body: fail("PltResolver slow path does not call kzt_plt_resolver_enter") +direct_entry = "kzt_production_lazy_direct_route(" +if direct_entry not in resolver_body: + fail("PltResolver must try the WI-837 evidence-backed direct route") +direct_call = resolver_body.find(direct_entry) +adapter_call = resolver_body.find(entry) +guest_handoff_call = resolver_body.rfind( + "plt_resolver_handoff_guest_or_abort(" +) +if not (direct_call < adapter_call < guest_handoff_call): + fail("PltResolver must try direct route, then adapter, then guest handoff") + +for forbidden_lazy_step in ( + "plt_resolver_lookup_host_symbol(", + "getAlternate(", + "KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE", + "dl_runtime_resolver", +): + if forbidden_lazy_step in resolver_body: + fail(f"PltResolver retains removed lazy legacy logic: {forbidden_lazy_step}") +if "*p = offs" in resolver_body or "*p = legacy_target" in resolver_body: + fail("PltResolver must not perform the historical direct GOT write") + +for required_read in ( + "uintptr_t *intercepted_frame = (uintptr_t *)cpu->regs[R_ESP];", + "uintptr_t addr = intercepted_frame[0];", + "uint64_t relocation_slot = intercepted_frame[1];", + "uintptr_t return_address = intercepted_frame[2];", +): + if required_read not in resolver_body[:direct_call]: + fail(f"PltResolver must read, not consume, the adapter frame: {required_read}") + +slot_check = resolver_body.find( + "kzt_plt_resolver_relocation_index_valid(" +) +relocation_access = resolver_body.find("Elf64_Rela * rel") +symbol_index_read = resolver_body.find( + "symbol_index = ELF64_R_SYM(rel->r_info);" +) +symbol_check = resolver_body.find( + "kzt_plt_resolver_symbol_index_valid(" +) +symbol_access = resolver_body.find("Elf64_Sym *sym = &h->DynSym[symbol_index];") +if not ( + 0 <= slot_check < relocation_access < symbol_index_read < + symbol_check < symbol_access < direct_call +): + fail( + "PltResolver must validate relocation and symbol indexes before " + "accessing their tables" + ) + +direct_prefix = resolver_body[direct_call:adapter_call] +if direct_prefix.count("Pop64(cpu)") != 2: + fail("direct route must consume exactly object_head and relocation_slot") +if ( + "direct_result.status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED" + not in direct_prefix + or "Push64(cpu, direct_result.selected_target);" not in direct_prefix + or "return;" not in direct_prefix +): + fail("direct route success must return its selected bridge") + +if "Return=%p" not in resolver_body or "(void*)return_address" not in resolver_body: + fail("PltResolver Return diagnostic must report intercepted_frame[2]") +if "*(void**)(cpu->regs[R_ESP])" in resolver_body: + fail("PltResolver Return diagnostic must not report intercepted_frame[0]") + +guest_prefix = resolver_body[adapter_call:guest_handoff_call] +if ( + "enter_result.status != KZT_PLT_RESOLVER_LEGACY_FRAME_RESTORED" + not in guest_prefix + or "return;" not in guest_prefix +): + fail("adapter-handled PltResolver paths must return before direct guest handoff") +guest_suffix = resolver_body[guest_handoff_call:] +if "return;" not in guest_suffix: + fail("per-object guest handoff must return without legacy host resolution") +handoff_helper_start = elfloader_text.find( + "static void plt_resolver_handoff_guest_or_abort(" +) +if handoff_helper_start < 0: + fail("missing checked guest resolver handoff") +handoff_helper_end = elfloader_text.find( + "\nvoid PltResolver(void)", handoff_helper_start +) +handoff_helper = elfloader_text[handoff_helper_start:handoff_helper_end] +if "abort();" not in handoff_helper: + fail("missing guest resolver must not return with an intercepted frame") + relocate_start = elfloader_text.find("int RelocateElfPlt(") relocate_end = elfloader_text.find("\n#if 0", relocate_start) if relocate_start < 0 or relocate_end < 0: @@ -69,6 +210,28 @@ def fail(message: str) -> None: relocate_body = elfloader_text[relocate_start:relocate_end] if "kzt_lazy_completion_bridge = AddBridge(" not in relocate_body: fail("completion bridge must be created in RelocateElfPlt cold path") +if "uintptr_t kzt_guest_resolver;" not in elfloader_private_text: + fail("elfheader must retain its own guest PLT resolver") +if "head->kzt_guest_resolver = guest_resolver;" not in relocate_body: + fail("RelocateElfPlt must retain the current object's guest resolver") +injection_check = relocate_body.find( + "kzt_plt_resolver_injection_allowed(" +) +resolver_save = relocate_body.find( + "head->kzt_guest_resolver = guest_resolver;" +) +resolver_install = relocate_body.find( + "*(uintptr_t*)(resolver_got_runtime+16) = resolver_bridge;" +) +if not (0 <= injection_check < resolver_save < resolver_install): + fail( + "RelocateElfPlt must validate and retain the guest resolver before " + "injecting KZT" + ) +if "dl_runtime_resolver" in relocate_body: + fail("RelocateElfPlt must not retain a process-global guest resolver") +if "*(uintptr_t*)(head->got+head->delta+16)" in relocate_body: + fail("RelocateElfPlt must not derive the guest resolver from head->got") for forbidden_seam in ("resolve_native_bridge", "compare_exchange_slot"): if forbidden_seam in lazy_header or forbidden_seam in lazy_core: @@ -77,6 +240,26 @@ def fail(message: str) -> None: production_route = ( root / "target/i386/latx/context/kzt_jump_slot_production.c" ).read_text(encoding="utf-8") +runtime_bridge = ( + root / "target/i386/latx/context/kzt_rela_runtime_bridge.c" +).read_text(encoding="utf-8") +if "production_probe_prepared_wrapper_cache" in production_route: + fail("production route retains an unused cache-only wrapper probe") + +mutex_cursor = 0 +while True: + mutex_start = runtime_bridge.find("pthread_mutex_lock", mutex_cursor) + if mutex_start < 0: + break + mutex_end = runtime_bridge.find("pthread_mutex_unlock", mutex_start) + if mutex_end < 0: + fail("bridge mutex critical section has no matching unlock") + critical_section = runtime_bridge[mutex_start:mutex_end] + for loader_call in ("dlsym(", "dlvsym(", "dlinfo(", "dladdr(", + "dladdr1("): + if loader_call in critical_section: + fail(f"bridge mutex encloses loader inspection: {loader_call}") + mutex_cursor = mutex_end + 1 resolver_complete_start = elfloader_text.find( "static void KztLazyBindingCompleteResolver(void)" ) From 2cd75af79513019a1ed91fc8b1e8a7246ff81572 Mon Sep 17 00:00:00 2001 From: Hanlu Li Date: Fri, 31 Jul 2026 22:05:54 +0800 Subject: [PATCH 5/8] LATX, KZT: wire guest loader authority into production Route dlopen, dlsym, dlclose, namespaces, eager relocations, and lazy completion through guest ld.so evidence while preserving fail-open behavior. Connect exact binding and lifecycle publication to loader callbacks, protect dlclose with the loader-quiescence writer, and keep slot publication behind scope, generation, CAS, and RELRO transaction checks.\n\nIntegrate prebound bridge translation, thread-local dlerror state, teardown diagnostics, and targeted fast paths without adding work to disabled KZT paths. Add focused API, production-route, lifecycle, and source-contract coverage. Signed-off-by: Hanlu Li --- accel/tcg/cpu-exec.c | 458 ++++ accel/tcg/translate-all.c | 56 + include/hw/core/cpu.h | 25 + linux-user/exit.c | 9 + linux-user/main.c | 5 + linux-user/syscall.c | 11 + target/i386/cpu.c | 9 + target/i386/cpu.h | 4 + target/i386/latx/context/box64context.c | 89 +- target/i386/latx/context/elfloader.c | 1419 +++++++++-- target/i386/latx/context/kzt_guest_dl_api.c | 866 +++++++ target/i386/latx/context/kzt_guest_dl_init.c | 82 + .../latx/context/kzt_guest_library_adapter.c | 518 +++- .../latx/context/kzt_jump_slot_production.c | 1936 ++++++++++++++- .../i386/latx/context/kzt_lazy_direct_route.c | 203 ++ target/i386/latx/context/library.c | 126 +- target/i386/latx/context/meson.build | 11 + target/i386/latx/context/myalign.c | 709 ++++-- target/i386/latx/context/wrappedlibc.c | 894 ++----- target/i386/latx/context/wrappedlibdl.c | 844 ++----- target/i386/latx/include/box64context.h | 24 +- target/i386/latx/include/elfloader.h | 11 +- target/i386/latx/include/elfloader_private.h | 1 + .../include/generated/wrappedlibdltypes.h | 1 - target/i386/latx/include/kzt_guest_dl_api.h | 127 + target/i386/latx/include/kzt_guest_dl_init.h | 22 + target/i386/latx/include/kzt_guest_dl_state.h | 51 + .../latx/include/kzt_guest_library_adapter.h | 76 +- .../latx/include/kzt_jump_slot_production.h | 34 + .../i386/latx/include/kzt_lazy_direct_route.h | 132 + target/i386/latx/include/library.h | 3 + target/i386/latx/include/myalign.h | 10 + target/i386/latx/latx-config.c | 9 + target/i386/latx/translator/tr-misc.c | 35 + target/i386/latx/translator/translate.c | 87 + tests/unit/kzt/test_guest_dl_api.c | 2136 +++++++++++++++++ .../unit/kzt/test_guest_dlclose_quiescence.c | 331 +++ tests/unit/kzt/test_guest_library_adapter.c | 1089 +++++++++ ...wi1007_context_resolver_source_contract.py | 78 + ...i1009_lazy_slot_bridge_context_contract.py | 39 + ...st_wi1021_dlerror_entry_source_contract.py | 139 ++ .../kzt/test_wi1065_loader_hook_contract.py | 71 + ...est_wi1066_lazy_prebind_source_contract.py | 170 ++ ...st_wi1066_pinned_bridge_source_contract.py | 92 + ...1_lifecycle_performance_source_contract.py | 82 + ...1082_steady_diagnostics_source_contract.py | 57 + ...legacy_callback_removal_source_contract.py | 101 + ...8_thread_local_dl_state_source_contract.py | 139 ++ ...test_wi1099_symbol_type_source_contract.py | 81 + ...1099_wrapper_provenance_source_contract.py | 68 + ...lerror_bridge_fast_path_source_contract.py | 56 + tests/unit/kzt/test_wi254_loader_contract.py | 100 +- .../kzt/test_wi256_lazy_production_bridge.c | 1273 +++++++++- ...jump_slot_single_writer_source_contract.py | 370 +++ ...601_lazy_legacy_removal_source_contract.py | 130 + ...02_handle_state_removal_source_contract.py | 59 + ...03_callback_replacement_source_contract.py | 80 + ...i603_loader_hook_timing_source_contract.py | 79 + tests/unit/kzt/test_wi837_lazy_direct_route.c | 708 ++++++ ...t_wi962_guest_lifecycle_source_contract.py | 124 + ...test_wi963_guest_symbol_source_contract.py | 179 ++ ...test_wi964_guest_dl_api_source_contract.py | 88 + .../test_wi973_namespace_source_contract.py | 112 + ...i979_guest_first_dlopen_source_contract.py | 69 + ...980_guest_first_dlclose_source_contract.py | 66 + ...ioned_wrapper_selection_source_contract.py | 53 + ...st_relocation_authority_source_contract.py | 158 ++ ...94_dlerror_tail_forward_source_contract.py | 126 + ...idge_translation_timing_source_contract.py | 80 + ...i995_lazy_direct_timing_source_contract.py | 74 + ...999_context_init_timing_source_contract.py | 31 + ...rtup_observation_timing_source_contract.py | 39 + 72 files changed, 15568 insertions(+), 2056 deletions(-) create mode 100644 target/i386/latx/context/kzt_guest_dl_api.c create mode 100644 target/i386/latx/context/kzt_guest_dl_init.c create mode 100644 target/i386/latx/context/kzt_lazy_direct_route.c create mode 100644 target/i386/latx/include/kzt_guest_dl_api.h create mode 100644 target/i386/latx/include/kzt_guest_dl_init.h create mode 100644 target/i386/latx/include/kzt_guest_dl_state.h create mode 100644 target/i386/latx/include/kzt_lazy_direct_route.h create mode 100644 tests/unit/kzt/test_guest_dl_api.c create mode 100644 tests/unit/kzt/test_guest_dlclose_quiescence.c create mode 100644 tests/unit/kzt/test_guest_library_adapter.c create mode 100644 tests/unit/kzt/test_wi1007_context_resolver_source_contract.py create mode 100644 tests/unit/kzt/test_wi1009_lazy_slot_bridge_context_contract.py create mode 100644 tests/unit/kzt/test_wi1021_dlerror_entry_source_contract.py create mode 100644 tests/unit/kzt/test_wi1065_loader_hook_contract.py create mode 100644 tests/unit/kzt/test_wi1066_lazy_prebind_source_contract.py create mode 100644 tests/unit/kzt/test_wi1066_pinned_bridge_source_contract.py create mode 100644 tests/unit/kzt/test_wi1081_lifecycle_performance_source_contract.py create mode 100644 tests/unit/kzt/test_wi1082_steady_diagnostics_source_contract.py create mode 100644 tests/unit/kzt/test_wi1083_legacy_callback_removal_source_contract.py create mode 100644 tests/unit/kzt/test_wi1098_thread_local_dl_state_source_contract.py create mode 100644 tests/unit/kzt/test_wi1099_symbol_type_source_contract.py create mode 100644 tests/unit/kzt/test_wi1099_wrapper_provenance_source_contract.py create mode 100644 tests/unit/kzt/test_wi1209_dlerror_bridge_fast_path_source_contract.py create mode 100644 tests/unit/kzt/test_wi601_jump_slot_single_writer_source_contract.py create mode 100644 tests/unit/kzt/test_wi601_lazy_legacy_removal_source_contract.py create mode 100644 tests/unit/kzt/test_wi602_handle_state_removal_source_contract.py create mode 100644 tests/unit/kzt/test_wi603_callback_replacement_source_contract.py create mode 100644 tests/unit/kzt/test_wi603_loader_hook_timing_source_contract.py create mode 100644 tests/unit/kzt/test_wi837_lazy_direct_route.c create mode 100644 tests/unit/kzt/test_wi962_guest_lifecycle_source_contract.py create mode 100644 tests/unit/kzt/test_wi963_guest_symbol_source_contract.py create mode 100644 tests/unit/kzt/test_wi964_guest_dl_api_source_contract.py create mode 100644 tests/unit/kzt/test_wi973_namespace_source_contract.py create mode 100644 tests/unit/kzt/test_wi979_guest_first_dlopen_source_contract.py create mode 100644 tests/unit/kzt/test_wi980_guest_first_dlclose_source_contract.py create mode 100644 tests/unit/kzt/test_wi982_versioned_wrapper_selection_source_contract.py create mode 100644 tests/unit/kzt/test_wi987_guest_relocation_authority_source_contract.py create mode 100644 tests/unit/kzt/test_wi994_dlerror_tail_forward_source_contract.py create mode 100644 tests/unit/kzt/test_wi995_lazy_bridge_translation_timing_source_contract.py create mode 100644 tests/unit/kzt/test_wi995_lazy_direct_timing_source_contract.py create mode 100644 tests/unit/kzt/test_wi999_context_init_timing_source_contract.py create mode 100644 tests/unit/kzt/test_wi999_startup_observation_timing_source_contract.py diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c index 2b37a399e44..d6d5d683a74 100644 --- a/accel/tcg/cpu-exec.c +++ b/accel/tcg/cpu-exec.c @@ -31,10 +31,12 @@ #include "qemu/rcu.h" #include "exec/tb-hash.h" #include "exec/tb-lookup.h" +#include "exec/tb-context.h" #include "exec/log.h" #include "qemu/main-loop.h" #if defined(CONFIG_LATX_KZT) #include "qemu.h" +#include "elfloader.h" #include "elfloader_private.h" #include "box64context.h" #include "librarian.h" @@ -43,6 +45,7 @@ #include "library.h" #include "fileutils.h" #include "bridge_private.h" +#include "exec/fasttb.h" void *getAlternate(void *addr); extern const char *interp_prefix; extern struct elfheader_s * elf_header; @@ -723,6 +726,424 @@ inline void tb_add_jump(TranslationBlock *tb, int n, #include "tu.h" #endif +#if defined(CONFIG_LATX_KZT) +void kzt_tb_pin_prebind_bridge(CPUState *cpu, target_ulong pc); +bool kzt_tb_prebind_target_is_prepared(CPUState *cpu, target_ulong pc); +void kzt_tb_prebind_guest_note_prepared(CPUState *cpu, target_ulong pc); +void kzt_tb_steady_diagnostics_note_guest_prepare( + CPUState *cpu, target_ulong pc); +void kzt_tb_steady_diagnostics_snapshot_fast_cache(CPUState *cpu); +void kzt_tb_steady_diagnostics_report(void); + +static int kzt_pinned_bridge_diagnostics = -1; + +#define KZT_STEADY_GUEST_PREPARED_MAX 8 + +typedef struct KztSteadyTbDiagnostics { + int enabled; + int reported; + uint64_t pin; + uint64_t pin_replace; + uint64_t pinned_hit; + uint64_t pinned_miss; + uint64_t pinned_restore; + uint64_t empty_miss; + uint64_t collision_miss; + uint64_t flags_miss; + uint64_t generation_miss; + uint64_t invalid_miss; + uint64_t store; + uint64_t store_reject; + uint64_t translate; + uint64_t bridge_translate; + uint64_t guest_hit; + uint64_t guest_retranslate; + uint64_t guest_prepared_dropped; + target_ulong last_pin_pc; + target_ulong last_replaced_pc; + target_ulong last_miss_pc; + target_ulong fast_cache_pc; + target_ulong jmp_cache_pc; + const void *fast_cache_ptr; + unsigned int fast_cache_hash; + int fast_cache_snapshot_valid; + target_ulong guest_prepared[KZT_STEADY_GUEST_PREPARED_MAX]; + unsigned int guest_prepared_count; +} KztSteadyTbDiagnostics; + +static KztSteadyTbDiagnostics kzt_steady_tb_diagnostics = { + .enabled = -1, +}; + +static bool kzt_steady_tb_diagnostics_enabled(void) +{ + int enabled = qatomic_read(&kzt_steady_tb_diagnostics.enabled); + + if (unlikely(enabled < 0)) { + int configured = getenv("LATX_KZT_STEADY_DIAGNOSTICS") ? 1 : 0; + + enabled = qatomic_cmpxchg( + &kzt_steady_tb_diagnostics.enabled, -1, configured); + if (enabled < 0) { + enabled = configured; + } + } + return enabled != 0; +} + +static bool kzt_steady_tb_guest_prepared(target_ulong pc) +{ + unsigned int i; + unsigned int count = qatomic_read( + &kzt_steady_tb_diagnostics.guest_prepared_count); + + for (i = 0; i < count; i++) { + if (qatomic_read(&kzt_steady_tb_diagnostics.guest_prepared[i]) == pc) { + return true; + } + } + return false; +} + +void kzt_tb_steady_diagnostics_note_guest_prepare( + CPUState *cpu, target_ulong pc) +{ + unsigned int i; + unsigned int count; + + (void)cpu; + if (!pc || !kzt_steady_tb_diagnostics_enabled()) { + return; + } + count = qatomic_read(&kzt_steady_tb_diagnostics.guest_prepared_count); + for (i = 0; i < count; i++) { + if (qatomic_read(&kzt_steady_tb_diagnostics.guest_prepared[i]) == pc) { + return; + } + } + if (count >= KZT_STEADY_GUEST_PREPARED_MAX) { + qatomic_inc(&kzt_steady_tb_diagnostics.guest_prepared_dropped); + return; + } + qatomic_set(&kzt_steady_tb_diagnostics.guest_prepared[count], pc); + qatomic_set(&kzt_steady_tb_diagnostics.guest_prepared_count, count + 1); +} + +void kzt_tb_steady_diagnostics_snapshot_fast_cache(CPUState *cpu) +{ + CPUX86State *env; + FastTB *fast_cache; + TranslationBlock *jmp_cache_tb; + target_ulong pinned_pc; + unsigned int hash; + + if (!cpu || !kzt_steady_tb_diagnostics_enabled() || + !(env = cpu->env_ptr) || !(fast_cache = env->tb_jmp_cache_ptr) || + !(pinned_pc = qatomic_read( + &kzt_steady_tb_diagnostics.last_pin_pc))) { + return; + } + hash = tb_jmp_cache_hash_func(pinned_pc); + jmp_cache_tb = qatomic_read(&cpu->tb_jmp_cache[hash]); + qatomic_set(&kzt_steady_tb_diagnostics.fast_cache_hash, hash); + qatomic_set(&kzt_steady_tb_diagnostics.fast_cache_pc, + qatomic_read(&fast_cache[hash].pc)); + qatomic_set(&kzt_steady_tb_diagnostics.fast_cache_ptr, + qatomic_read(&fast_cache[hash].ptr)); + qatomic_set(&kzt_steady_tb_diagnostics.jmp_cache_pc, + jmp_cache_tb ? jmp_cache_tb->pc : 0); + qatomic_set(&kzt_steady_tb_diagnostics.fast_cache_snapshot_valid, 1); +} + +void kzt_tb_steady_diagnostics_report(void) +{ + CPUState *cpu = first_cpu; + target_ulong pinned_pc = qatomic_read( + &kzt_steady_tb_diagnostics.last_pin_pc); + unsigned int fast_cache_hash = qatomic_read( + &kzt_steady_tb_diagnostics.fast_cache_hash); + target_ulong fast_cache_pc = qatomic_read( + &kzt_steady_tb_diagnostics.fast_cache_pc); + const void *fast_cache_ptr = qatomic_read( + &kzt_steady_tb_diagnostics.fast_cache_ptr); + target_ulong jmp_cache_pc = qatomic_read( + &kzt_steady_tb_diagnostics.jmp_cache_pc); + int fast_cache_snapshot_valid = qatomic_read( + &kzt_steady_tb_diagnostics.fast_cache_snapshot_valid); + + if (!kzt_steady_tb_diagnostics_enabled() || + qatomic_cmpxchg(&kzt_steady_tb_diagnostics.reported, 0, 1) != 0) { + return; + } + fprintf(stderr, + "kzt_steady_tb_summary schema=1 pin=%lu pin_replace=%lu " + "pinned_hit=%lu pinned_miss=%lu pinned_restore=%lu " + "empty_miss=%lu " + "collision_miss=%lu flags_miss=%lu generation_miss=%lu " + "invalid_miss=%lu store=%lu store_reject=%lu translate=%lu " + "bridge_translate=%lu guest_prepared=%u guest_hit=%lu " + "guest_retranslate=%lu guest_prepared_dropped=%lu " + "flush_generation=%u tb_flush_count=%u " + "tb_invalidate_count=%zu last_pin_pc=0x%lx " + "last_replaced_pc=0x%lx last_miss_pc=0x%lx " + "fast_cache_hash=%u fast_cache_pc=0x%lx fast_cache_ptr=%p " + "fast_cache_snapshot_valid=%d fast_cache_matches_pin=%d " + "jmp_cache_pc=0x%lx " + "slot0_pc=0x%lx slot1_pc=0x%lx slot2_pc=0x%lx " + "slot3_pc=0x%lx\n", + (unsigned long)qatomic_read(&kzt_steady_tb_diagnostics.pin), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.pin_replace), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.pinned_hit), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.pinned_miss), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.pinned_restore), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.empty_miss), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.collision_miss), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.flags_miss), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.generation_miss), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.invalid_miss), + (unsigned long)qatomic_read(&kzt_steady_tb_diagnostics.store), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.store_reject), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.translate), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.bridge_translate), + qatomic_read(&kzt_steady_tb_diagnostics.guest_prepared_count), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.guest_hit), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.guest_retranslate), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.guest_prepared_dropped), + cpu ? cpu->kzt_pinned_bridge_flush_generation : 0, + qatomic_read(&tb_ctx.tb_flush_count), + tcg_tb_phys_invalidate_count(), + (unsigned long)pinned_pc, + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.last_replaced_pc), + (unsigned long)qatomic_read( + &kzt_steady_tb_diagnostics.last_miss_pc), + fast_cache_hash, (unsigned long)fast_cache_pc, fast_cache_ptr, + fast_cache_snapshot_valid, + fast_cache_snapshot_valid && fast_cache_pc == pinned_pc, + (unsigned long)jmp_cache_pc, + (unsigned long)(cpu ? cpu->kzt_pinned_bridge_cache[0].pc : 0), + (unsigned long)(cpu ? cpu->kzt_pinned_bridge_cache[1].pc : 0), + (unsigned long)(cpu ? cpu->kzt_pinned_bridge_cache[2].pc : 0), + (unsigned long)(cpu ? cpu->kzt_pinned_bridge_cache[3].pc : 0)); +} + +static unsigned int kzt_pinned_bridge_index(target_ulong pc) +{ + return (unsigned int)((pc >> 5) & 3); +} + +static unsigned int kzt_prebind_prepared_guest_index(target_ulong pc) +{ + return (unsigned int)((pc >> 4) & 7); +} + +bool kzt_tb_prebind_target_is_prepared(CPUState *cpu, target_ulong pc) +{ + unsigned int index; + + if (!cpu || !pc) { + return false; + } + if (pc > reserved_va) { + TranslationBlock *tb; + + index = kzt_pinned_bridge_index(pc); + tb = qatomic_read(&cpu->kzt_pinned_bridge_cache[index].tb); + return cpu->kzt_pinned_bridge_cache[index].pc == pc && + cpu->kzt_pinned_bridge_cache[index].flush_generation == + cpu->kzt_pinned_bridge_flush_generation && + tb && !(tb->cflags & CF_INVALID); + } + index = kzt_prebind_prepared_guest_index(pc); + return cpu->kzt_prebind_prepared_guest[index].pc == pc && + cpu->kzt_prebind_prepared_guest[index].flush_generation == + cpu->kzt_pinned_bridge_flush_generation; +} + +void kzt_tb_prebind_guest_note_prepared(CPUState *cpu, target_ulong pc) +{ + unsigned int index; + + if (!cpu || !pc || pc > reserved_va) { + return; + } + index = kzt_prebind_prepared_guest_index(pc); + cpu->kzt_prebind_prepared_guest[index].pc = pc; + cpu->kzt_prebind_prepared_guest[index].flush_generation = + cpu->kzt_pinned_bridge_flush_generation; +} + +static void kzt_pinned_bridge_restore_jmp_cache( + CPUState *cpu, TranslationBlock *tb) +{ + unsigned int hash; + + if (!cpu || !tb) { + return; + } + hash = tb_jmp_cache_hash_func(tb->pc); +#ifdef CONFIG_LATX_FAST_JMPCACHE + latx_fast_jmp_cache_add(cpu, hash, tb); +#endif + qatomic_set(&cpu->tb_jmp_cache[hash], tb); + if (unlikely(kzt_steady_tb_diagnostics_enabled())) { + qatomic_inc(&kzt_steady_tb_diagnostics.pinned_restore); + } +} + +static bool kzt_pinned_bridge_diagnostics_enabled(void) +{ + int enabled = qatomic_read(&kzt_pinned_bridge_diagnostics); + + if (unlikely(enabled < 0)) { + enabled = getenv("LATX_KZT_PINNED_BRIDGE_DIAGNOSTICS") ? 1 : 0; + qatomic_set(&kzt_pinned_bridge_diagnostics, enabled); + } + return enabled; +} + +static void kzt_pinned_bridge_report(const char *event, CPUState *cpu, + target_ulong pc, uint32_t flags, + uint32_t cflags, TranslationBlock *tb) +{ + if (!kzt_pinned_bridge_diagnostics_enabled()) { + return; + } + fprintf(stderr, + "kzt_pinned_bridge schema=1 event=%s cpu=%p pc=0x%lx " + "flags=0x%x cflags=0x%x generation=%u tb=%p\n", + event, cpu, (unsigned long)pc, flags, cflags, + cpu->kzt_pinned_bridge_flush_generation, tb); +} + +static TranslationBlock *kzt_pinned_bridge_lookup( + CPUState *cpu, target_ulong pc, uint32_t flags, uint32_t cflags) +{ + unsigned int index; + TranslationBlock *tb; + bool steady_diagnostics; + + if (!cpu || !pc || pc <= reserved_va) { + return NULL; + } + steady_diagnostics = unlikely(kzt_steady_tb_diagnostics_enabled()); + index = kzt_pinned_bridge_index(pc); + if (cpu->kzt_pinned_bridge_cache[index].pc != pc || + cpu->kzt_pinned_bridge_cache[index].flags != flags || + cpu->kzt_pinned_bridge_cache[index].cflags != cflags || + cpu->kzt_pinned_bridge_cache[index].flush_generation != + cpu->kzt_pinned_bridge_flush_generation) { + if (steady_diagnostics) { + qatomic_inc(&kzt_steady_tb_diagnostics.pinned_miss); + qatomic_set(&kzt_steady_tb_diagnostics.last_miss_pc, pc); + if (!cpu->kzt_pinned_bridge_cache[index].pc) { + qatomic_inc(&kzt_steady_tb_diagnostics.empty_miss); + } else if (cpu->kzt_pinned_bridge_cache[index].pc != pc) { + qatomic_inc(&kzt_steady_tb_diagnostics.collision_miss); + } else if (cpu->kzt_pinned_bridge_cache[index].flush_generation != + cpu->kzt_pinned_bridge_flush_generation) { + qatomic_inc(&kzt_steady_tb_diagnostics.generation_miss); + } else { + qatomic_inc(&kzt_steady_tb_diagnostics.flags_miss); + } + } + if (cpu->kzt_pinned_bridge_cache[index].pc) { + kzt_pinned_bridge_report("miss", cpu, pc, flags, cflags, NULL); + } + return NULL; + } + tb = qatomic_read(&cpu->kzt_pinned_bridge_cache[index].tb); + if (tb && !(tb->cflags & CF_INVALID)) { + kzt_pinned_bridge_restore_jmp_cache(cpu, tb); + if (steady_diagnostics) { + qatomic_inc(&kzt_steady_tb_diagnostics.pinned_hit); + } + kzt_pinned_bridge_report("hit", cpu, pc, flags, cflags, tb); + return tb; + } + if (steady_diagnostics) { + qatomic_inc(&kzt_steady_tb_diagnostics.pinned_miss); + qatomic_inc(tb ? &kzt_steady_tb_diagnostics.invalid_miss : + &kzt_steady_tb_diagnostics.empty_miss); + qatomic_set(&kzt_steady_tb_diagnostics.last_miss_pc, pc); + } + kzt_pinned_bridge_report("miss", cpu, pc, flags, cflags, NULL); + return NULL; +} + +static void kzt_pinned_bridge_store(CPUState *cpu, TranslationBlock *tb) +{ + unsigned int index; + + if (!cpu || !tb || !tb->pc || tb->pc <= reserved_va) { + return; + } + index = kzt_pinned_bridge_index(tb->pc); + if (cpu->kzt_pinned_bridge_cache[index].pc != tb->pc || + cpu->kzt_pinned_bridge_cache[index].flush_generation != + cpu->kzt_pinned_bridge_flush_generation) { + if (unlikely(kzt_steady_tb_diagnostics_enabled())) { + qatomic_inc(&kzt_steady_tb_diagnostics.store_reject); + } + return; + } + cpu->kzt_pinned_bridge_cache[index].flags = tb->flags; + cpu->kzt_pinned_bridge_cache[index].cflags = tb_cflags(tb); + qatomic_set(&cpu->kzt_pinned_bridge_cache[index].tb, tb); + if (unlikely(kzt_steady_tb_diagnostics_enabled())) { + qatomic_inc(&kzt_steady_tb_diagnostics.store); + } + kzt_pinned_bridge_report("store", cpu, tb->pc, tb->flags, + tb_cflags(tb), tb); +} + +void kzt_tb_pin_prebind_bridge(CPUState *cpu, target_ulong pc) +{ + unsigned int index; + + if (!cpu || !pc || pc <= reserved_va) { + return; + } + index = kzt_pinned_bridge_index(pc); + if (kzt_tb_prebind_target_is_prepared(cpu, pc)) { + return; + } + if (unlikely(kzt_steady_tb_diagnostics_enabled())) { + target_ulong replaced = cpu->kzt_pinned_bridge_cache[index].pc; + + qatomic_inc(&kzt_steady_tb_diagnostics.pin); + qatomic_set(&kzt_steady_tb_diagnostics.last_pin_pc, pc); + if (replaced && replaced != pc) { + qatomic_inc(&kzt_steady_tb_diagnostics.pin_replace); + qatomic_set(&kzt_steady_tb_diagnostics.last_replaced_pc, + replaced); + } + } + qatomic_set(&cpu->kzt_pinned_bridge_cache[index].tb, NULL); + cpu->kzt_pinned_bridge_cache[index].pc = pc; + cpu->kzt_pinned_bridge_cache[index].flags = 0; + cpu->kzt_pinned_bridge_cache[index].cflags = 0; + cpu->kzt_pinned_bridge_cache[index].flush_generation = + cpu->kzt_pinned_bridge_flush_generation; + kzt_pinned_bridge_report("pin", cpu, pc, 0, 0, NULL); +} +#endif + static inline TranslationBlock *tb_find(CPUState *cpu, TranslationBlock *last_tb, int tb_exit, uint32_t cflags) @@ -731,10 +1152,36 @@ static inline TranslationBlock *tb_find(CPUState *cpu, TranslationBlock *tb; target_ulong cs_base, pc; uint32_t flags; +#if defined(CONFIG_LATX_KZT) + bool kzt_steady_diagnostics; + bool kzt_guest_prepared; +#endif cpu_get_tb_cpu_state(env, &pc, &cs_base, &flags); +#if defined(CONFIG_LATX_KZT) + if (unlikely(pc > reserved_va && + KztPltResolverDispatch(env, pc))) { + last_tb = NULL; + cpu_get_tb_cpu_state(env, &pc, &cs_base, &flags); + } +#endif + +#if defined(CONFIG_LATX_KZT) + kzt_steady_diagnostics = unlikely(kzt_steady_tb_diagnostics_enabled()); + kzt_guest_prepared = kzt_steady_diagnostics && pc <= reserved_va && + kzt_steady_tb_guest_prepared(pc); + tb = kzt_pinned_bridge_lookup(cpu, pc, flags, cflags); + if (!tb) { + tb = tb_lookup(cpu, pc, cs_base, flags, cflags); + } + if (kzt_guest_prepared) { + qatomic_inc(tb ? &kzt_steady_tb_diagnostics.guest_hit : + &kzt_steady_tb_diagnostics.guest_retranslate); + } +#else tb = tb_lookup(cpu, pc, cs_base, flags, cflags); +#endif #ifdef CONFIG_LATX_AOT if (tb == NULL && option_aot) { mmap_lock(); @@ -745,6 +1192,14 @@ static inline TranslationBlock *tb_find(CPUState *cpu, } #endif if (tb == NULL) { +#if defined(CONFIG_LATX_KZT) + if (kzt_steady_diagnostics) { + qatomic_inc(&kzt_steady_tb_diagnostics.translate); + if (pc > reserved_va) { + qatomic_inc(&kzt_steady_tb_diagnostics.bridge_translate); + } + } +#endif #if (defined CONFIG_LATX_AOT) && (defined CONFIG_LATX_DEBUG) if (option_debug_aot && option_load_aot) { static long long cnt; @@ -759,6 +1214,9 @@ static inline TranslationBlock *tb_find(CPUState *cpu, #endif tb = tb_gen_code(cpu, pc, cs_base, flags, cflags); +#if defined(CONFIG_LATX_KZT) + kzt_pinned_bridge_store(cpu, tb); +#endif jrra_pre_translate((void **)&tb, 1, cpu, flags, cflags); #ifdef CONFIG_LATX_PERF latx_timer_stop(TIMER_TS); diff --git a/accel/tcg/translate-all.c b/accel/tcg/translate-all.c index 8956403a7a4..8320e4323ee 100644 --- a/accel/tcg/translate-all.c +++ b/accel/tcg/translate-all.c @@ -81,6 +81,21 @@ #include "ts.h" #include "latx-smc.h" #endif +#if defined(CONFIG_LATX_KZT) +extern uintptr_t KztPltResolverBridge(void); +extern uintptr_t kzt_lazy_target_bridge_pc; + +static uint64_t kzt_lazy_bridge_tb_gen_timing_now(void) +{ + struct timespec value; + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &value) != 0) { + return 0; + } + return (uint64_t)value.tv_sec * 1000000000ULL + + (uint64_t)value.tv_nsec; +} +#endif #ifdef CONFIG_LATX_FAST_JMPCACHE #include "exec/fasttb.h" #endif @@ -2016,6 +2031,32 @@ TranslationBlock *tb_gen_code(CPUState *cpu, int64_t ti; #endif void *host_pc; +#if defined(CONFIG_LATX_KZT) + const char *kzt_lazy_bridge_role = NULL; + uint64_t kzt_lazy_bridge_tb_gen_start = 0; + int kzt_lazy_diagnostics_enabled = + unlikely(option_kzt_lazy_diagnostics); + uintptr_t kzt_lazy_target_snapshot = kzt_lazy_diagnostics_enabled + ? __atomic_load_n( + &kzt_lazy_target_bridge_pc, __ATOMIC_ACQUIRE) + : 0; + uintptr_t kzt_plt_resolver_bridge = kzt_lazy_diagnostics_enabled + ? KztPltResolverBridge() + : 0; + + if (kzt_lazy_diagnostics_enabled && kzt_plt_resolver_bridge && + pc == kzt_plt_resolver_bridge) { + kzt_lazy_bridge_role = "resolver"; + } else if (kzt_lazy_diagnostics_enabled && + kzt_lazy_target_snapshot && + pc == kzt_lazy_target_snapshot) { + kzt_lazy_bridge_role = "target"; + } + if (kzt_lazy_bridge_role) { + kzt_lazy_bridge_tb_gen_start = + kzt_lazy_bridge_tb_gen_timing_now(); + } +#endif assert_memory_lock(); qemu_thread_jit_write(); @@ -2441,6 +2482,21 @@ TranslationBlock *tb_gen_code(CPUState *cpu, tcg_tb_remove(tb); return existing_tb; } +#if defined(CONFIG_LATX_KZT) + if (kzt_lazy_bridge_role) { + uint64_t timing_done = kzt_lazy_bridge_tb_gen_timing_now(); + + fprintf( + stderr, + "kzt_lazy_bridge_tb_gen_timing schema=1 " + "role=%s pc=0x%lx total_ns=%lu\n", + kzt_lazy_bridge_role, (unsigned long)pc, + (unsigned long)( + timing_done >= kzt_lazy_bridge_tb_gen_start + ? timing_done - kzt_lazy_bridge_tb_gen_start + : 0)); + } +#endif return tb; } diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index 22762ee6245..178d3783bb2 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -379,6 +379,20 @@ struct CPUState { /* Accessed in parallel; all accesses must be atomic */ TranslationBlock *tb_jmp_cache[TB_JMP_CACHE_SIZE]; +#ifdef CONFIG_LATX_KZT + struct { + uintptr_t pc; + uint32_t flags; + uint32_t cflags; + unsigned flush_generation; + TranslationBlock *tb; + } kzt_pinned_bridge_cache[4]; + unsigned kzt_pinned_bridge_flush_generation; + struct { + uintptr_t pc; + unsigned flush_generation; + } kzt_prebind_prepared_guest[8]; +#endif struct GDBRegisterState *gdb_regs; int gdb_num_regs; @@ -459,6 +473,17 @@ static inline void cpu_tb_jmp_cache_clear(CPUState *cpu) for (i = 0; i < TB_JMP_CACHE_SIZE; i++) { qatomic_set(&cpu->tb_jmp_cache[i], NULL); } +#ifdef CONFIG_LATX_KZT + ++cpu->kzt_pinned_bridge_flush_generation; + for (i = 0; i < ARRAY_SIZE(cpu->kzt_pinned_bridge_cache); i++) { + qatomic_set(&cpu->kzt_pinned_bridge_cache[i].tb, NULL); + cpu->kzt_pinned_bridge_cache[i].pc = 0; + } + for (i = 0; i < ARRAY_SIZE(cpu->kzt_prebind_prepared_guest); i++) { + cpu->kzt_prebind_prepared_guest[i].pc = 0; + cpu->kzt_prebind_prepared_guest[i].flush_generation = 0; + } +#endif } /** diff --git a/linux-user/exit.c b/linux-user/exit.c index 1076823eb33..43f6ade1326 100644 --- a/linux-user/exit.c +++ b/linux-user/exit.c @@ -36,8 +36,17 @@ extern void __gcov_dump(void); #include "latx-perf.h" #endif +#ifdef CONFIG_LATX_KZT +void kzt_tb_steady_diagnostics_report(void); +void kzt_lifecycle_diagnostics_report(void); +#endif + void preexit_cleanup(CPUArchState *env, int code) { +#ifdef CONFIG_LATX_KZT + kzt_lifecycle_diagnostics_report(); + kzt_tb_steady_diagnostics_report(); +#endif #ifdef CONFIG_LATX_PERF latx_timer_stop(TIMER_PROCESS); #endif diff --git a/linux-user/main.c b/linux-user/main.c index ed38cd4619f..94e321684d2 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -294,6 +294,11 @@ CPUArchState *cpu_copy(CPUArchState *env) new_cpu->tcg_cflags = cpu->tcg_cflags; memcpy(new_env, env, sizeof(CPUArchState)); +#if defined(CONFIG_LATX_KZT) && defined(TARGET_X86_64) + memset(&new_env->kzt_guest_dlerror_state, 0, + sizeof(new_env->kzt_guest_dlerror_state)); + new_env->kzt_guest_dlerror_state.dlerror_slow_required = 0; +#endif /* * NOTE: Current QEMU only has one and only one gdt_table ptr. diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 74d0070cbd0..3c361b047d1 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -142,6 +142,9 @@ #include "ioctl/mpt3sas_ctl.h" #include "qemu.h" +#if defined(CONFIG_LATX_FAST_JMPCACHE) && defined(CONFIG_LATX_KZT) +void kzt_tb_steady_diagnostics_snapshot_fast_cache(CPUState *cpu); +#endif #include "signal-common.h" #include "qemu/guest-random.h" #include "qemu/selfmap.h" @@ -11528,6 +11531,9 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, #ifdef CONFIG_LATX_FAST_JMPCACHE { CPUX86State *x86env = env; +#ifdef CONFIG_LATX_KZT + kzt_tb_steady_diagnostics_snapshot_fast_cache(cpu); +#endif if (x86env->tb_jmp_cache_ptr) { free(x86env->tb_jmp_cache_ptr); } @@ -13976,6 +13982,11 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, #ifdef __NR_exit_group /* new thread calls */ case TARGET_NR_exit_group: +#ifdef CONFIG_LATX_FAST_JMPCACHE +#ifdef CONFIG_LATX_KZT + kzt_tb_steady_diagnostics_snapshot_fast_cache(cpu); +#endif +#endif preexit_cleanup(cpu_env, arg1); /* dump basic block here. TODO */ #ifdef CONFIG_LATX_AOT diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 57f25697fb8..d423136fa72 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -34,6 +34,9 @@ #include "sysemu/xen.h" #include "sysemu/whpx.h" #include "sev_i386.h" +#if defined(CONFIG_LATX_KZT) && defined(TARGET_X86_64) +#include "kzt_guest_dl_api.h" +#endif #include "qemu/error-report.h" #include "qemu/module.h" @@ -6308,6 +6311,9 @@ static void x86_cpu_reset(DeviceState *dev) #if defined(CONFIG_LATX) && !defined(CONFIG_LATX_FAST_JMPCACHE) env->tb_jmp_cache_ptr = s->tb_jmp_cache; #endif +#if defined(CONFIG_LATX_KZT) && defined(TARGET_X86_64) + env->kzt_guest_dlerror_state.dlerror_slow_required = 0; +#endif } #ifndef CONFIG_USER_ONLY @@ -7011,6 +7017,9 @@ static void x86_cpu_unrealizefn(DeviceState *dev) cpu->apic_state = NULL; } +#if defined(CONFIG_LATX_KZT) && defined(TARGET_X86_64) + kzt_guest_dl_api_free_errors(&cpu->env.kzt_guest_dlerror_state); +#endif xcc->parent_unrealize(dev); } diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 63e53473d3f..1cdca47f8e1 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -25,6 +25,7 @@ #include "exec/cpu-defs.h" #include "qapi/qapi-types-common.h" #ifdef CONFIG_LATX_KZT +#include "kzt_guest_dl_state.h" #include "kzt_lazy_binding.h" #include "kzt_loader_callback_scope.h" #endif @@ -1412,6 +1413,9 @@ typedef struct CPUX86State { kzt_lazy_binding_pending_t kzt_lazy_binding_pending; uintptr_t kzt_lazy_original_return; kzt_guest_library_loader_scope_t kzt_guest_library_loader_scope; +#ifdef TARGET_X86_64 + kzt_guest_dlerror_state_t kzt_guest_dlerror_state; +#endif #endif void* checksum_fail_tb; void *ibtc_table_p; diff --git a/target/i386/latx/context/box64context.c b/target/i386/latx/context/box64context.c index d122a115035..571703d1c66 100755 --- a/target/i386/latx/context/box64context.c +++ b/target/i386/latx/context/box64context.c @@ -9,8 +9,10 @@ #include #include #include +#include #include #include +#include #include "box64context.h" #include "debug.h" @@ -19,17 +21,71 @@ #include "librarian.h" #include "library.h" #include "wrapper.h" +#include "kzt_guest_dl_api.h" #ifdef CONFIG_LATX_KZT #include "kzt_guest_registry.h" #include "kzt_guest_registry_context.h" #include "kzt_guest_library_binding.h" +#include "kzt_lazy_prebind_scope.h" #endif #include +#ifdef CONFIG_LATX_KZT +static uint64_t kzt_context_init_timing_now(void) +{ + struct timespec value; + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &value) != 0) { + return 0; + } + return (uint64_t)value.tv_sec * 1000000000ULL + + (uint64_t)value.tv_nsec; +} + +static uint64_t kzt_context_init_timing_delta(uint64_t start, uint64_t end) +{ + return start && end >= start ? end - start : 0; +} +#endif + +dlprivate_t *NewDLPrivate(void) +{ + dlprivate_t *dl = box_calloc(1, sizeof(*dl)); + + if (dl) { + dl->legacy_error.dlerror_slow_required = 1; + } + if (dl && kzt_guest_dl_api_entry_state_init(dl) != 0) { + box_free(dl); + return NULL; + } + return dl; +} + +void FreeDLPrivate(dlprivate_t **dl) +{ + if (!dl || !*dl) { + return; + } + kzt_guest_dl_api_entry_state_destroy(*dl); + kzt_guest_dl_api_free_errors(&(*dl)->legacy_error); + box_free(*dl); + *dl = NULL; +} + box64context_t *NewBox64Context(int argc) { #ifdef CONFIG_LATX_KZT kzt_patch_spike_config_t patch_spike_config; + uint64_t timing_start = 0; + uint64_t timing_base = 0; + uint64_t timing_access = 0; + uint64_t timing_guard = 0; + int timing_enabled = kzt_registry_diagnostics_enabled(); + + if (timing_enabled) { + timing_start = kzt_context_init_timing_now(); + } #endif // init and put default values box64context_t *context = (box64context_t*)box_calloc(1, sizeof(box64context_t)); @@ -47,13 +103,31 @@ box64context_t *NewBox64Context(int argc) context->argv = (char**)box_calloc(context->argc+1, sizeof(char*)); pthread_mutex_init(&context->mutex_lock, NULL); #ifdef CONFIG_LATX_KZT + if (timing_enabled) { + timing_base = kzt_context_init_timing_now(); + } /* Optional acceleration metadata is created on first KZT use so a * context that never observes a guest link-map keeps the old footprint. */ - pthread_mutex_init(&context->kzt_bridge_mutex, NULL); (void)kzt_guest_library_access_init(&context->kzt_guest_library_access); + context->kzt_lazy_prebind_scope = kzt_lazy_prebind_scope_init(); + if (timing_enabled) { + timing_access = kzt_context_init_timing_now(); + } kzt_patch_spike_config_from_options(&patch_spike_config); kzt_patch_spike_guard_init(&context->kzt_patch_spike_guard, &patch_spike_config); + if (timing_enabled) { + timing_guard = kzt_context_init_timing_now(); + fprintf( + stderr, + "kzt_context_init_timing schema=1 base_ns=%" PRIu64 " " + "library_access_ns=%" PRIu64 " patch_guard_ns=%" PRIu64 " " + "total_ns=%" PRIu64 "\n", + kzt_context_init_timing_delta(timing_start, timing_base), + kzt_context_init_timing_delta(timing_base, timing_access), + kzt_context_init_timing_delta(timing_access, timing_guard), + kzt_context_init_timing_delta(timing_start, timing_guard)); + } #endif return context; @@ -84,6 +158,12 @@ int KztGuestLibraryLookupForContext( &context->kzt_guest_library_access, key, handle) : -1; } + +kzt_lazy_prebind_scope_t *KztLazyPrebindScopeForContext( + box64context_t *context) +{ + return context ? context->kzt_lazy_prebind_scope : NULL; +} #endif kzt_patch_spike_guard_t *KztPatchSpikeGuardForContext(box64context_t *context) @@ -107,6 +187,8 @@ void FreeBox64Context(box64context_t** context) box64context_t* ctx = *context; // local copy to do the cleanning + kzt_guest_dl_api_entry_state_destroy(ctx->dlprivate); + #ifdef CONFIG_LATX_KZT /* FreeBox64Context is entered only after guest execution and loader * callbacks have stopped. Close the context-owned lookup gate first and @@ -122,9 +204,9 @@ void FreeBox64Context(box64context_t** context) FreeLibrarian(&ctx->maplib); #ifdef CONFIG_LATX_KZT kzt_guest_library_access_destroy(&ctx->kzt_guest_library_access); + kzt_lazy_prebind_scope_destroy(&ctx->kzt_lazy_prebind_scope); kzt_guest_registry_context_destroy(&ctx->kzt_guest_registry_context, &ctx->mutex_lock); - pthread_mutex_destroy(&ctx->kzt_bridge_mutex); #endif FreeDictionnary(&ctx->versym); @@ -163,12 +245,13 @@ void FreeBox64Context(box64context_t** context) box_free(ctx->box64path); FreeBridge(&ctx->system); - if(ctx->stack_clone) box_free(ctx->stack_clone); free_neededlib(&ctx->neededlibs); + FreeDLPrivate(&ctx->dlprivate); + box_free(ctx); } int AddMallocMap(box64context_t* ctx, struct malloc_map* map) { diff --git a/target/i386/latx/context/elfloader.c b/target/i386/latx/context/elfloader.c index 0f2b0fc9e5d..a099cfe1147 100755 --- a/target/i386/latx/context/elfloader.c +++ b/target/i386/latx/context/elfloader.c @@ -11,10 +11,12 @@ #include #include "elf.h" #include +#include #include #include #include #include +#include #include #include @@ -24,6 +26,7 @@ #include "elfloader_private.h" #include "librarian.h" #include "bridge.h" +#include "bridge_private.h" #include "wrapper.h" #include "box64context.h" #include "library.h" @@ -31,11 +34,16 @@ #include "dictionnary.h" #include "symbols.h" #include "lsenv.h" +#include "myalign.h" #include "kzt_rela_stub_detector.h" #include "kzt_guest_registry.h" +#include "kzt_guest_symbol_scope.h" +#include "kzt_lifecycle_diagnostics.h" +#include "kzt_per_object_got_plt.h" #include "kzt_observation_adapter.h" #include "kzt_jump_slot_production.h" #include "kzt_lazy_diagnostics.h" +#include "kzt_owner_resolver.h" #include "kzt_plt_resolver_adapter.h" #include "elf_plt_relocation.h" #include "elfmap.h" @@ -43,6 +51,32 @@ #ifdef CONFIG_LATX_KZT #include "qemu.h" extern int wine_option_kzt; +extern uint64_t kzt_lazy_bridge_translation_ready_ns; +extern uintptr_t kzt_lazy_target_bridge_pc; +extern uint64_t kzt_lazy_resolver_done_ns; + +#define KZT_PREBIND_GUEST_TB_BUDGET 8 + +static uint64_t kzt_lazy_resolver_timing_now(void) +{ + struct timespec value; + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &value) != 0) { + return 0; + } + return (uint64_t)value.tv_sec * 1000000000ULL + + (uint64_t)value.tv_nsec; +} + +static uint64_t kzt_lazy_resolver_minor_faults(void) +{ + struct rusage usage; + + if (getrusage(RUSAGE_SELF, &usage) != 0) { + return 0; + } + return (uint64_t)usage.ru_minflt; +} #endif void* my__IO_2_1_stderr_ = NULL; @@ -647,9 +681,240 @@ static int FindR64COPYRel(elfheader_t* h, const char* name, uintptr_t *offs, uin } */ +typedef struct kzt_legacy_rela_target { + uintptr_t start; + uintptr_t end; + library_t *provider; +} kzt_legacy_rela_target_t; + +static void kzt_resolve_legacy_rela_target( + lib_t *maplib, lib_t *local_maplib, elfheader_t *head, + const Elf64_Sym *sym, int bind, const char *symbol_name, + int version, const char *version_name, + kzt_legacy_rela_target_t *target) +{ + if (!target) { + return; + } + memset(target, 0, sizeof(*target)); + if (bind == STB_LOCAL) { + target->start = sym->st_value + head->delta; + target->end = target->start + sym->st_size; + return; + } + + GetGlobalSymbolStartEndWithProvider( + maplib, symbol_name, &target->start, &target->end, head, + version, version_name, &target->provider); + if (!target->start && !target->end && local_maplib) { + GetGlobalSymbolStartEndWithProvider( + local_maplib, symbol_name, &target->start, &target->end, head, + version, version_name, &target->provider); + } +} + +enum { + KZT_EAGER_COMPATIBILITY_WRITE_ERROR = -1, + KZT_EAGER_COMPATIBILITY_WRITE_CAS_MISMATCH = 0, + KZT_EAGER_COMPATIBILITY_WRITE_APPLIED = 1, + KZT_EAGER_COMPATIBILITY_WRITE_CIRCUIT_OPEN = 2, +}; + +static int kzt_eager_compatibility_write( + box64context_t *context, uint64_t *slot, uintptr_t observed, + uintptr_t replacement, uintptr_t *final_value) +{ + if (final_value) { + *final_value = observed; + } + if (!slot) { + return KZT_EAGER_COMPATIBILITY_WRITE_ERROR; + } +#ifdef CONFIG_LATX_KZT + if (option_kzt || wine_option_kzt) { + uintptr_t expected = observed; + + if (kzt_patch_spike_guard_circuit_open( + KztPatchSpikeGuardForContext(context))) { + return KZT_EAGER_COMPATIBILITY_WRITE_CIRCUIT_OPEN; + } + if (!__atomic_compare_exchange_n( + (uintptr_t *)slot, &expected, replacement, 0, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) { + if (final_value) { + *final_value = expected; + } + return KZT_EAGER_COMPATIBILITY_WRITE_CAS_MISMATCH; + } + if (final_value) { + *final_value = replacement; + } + return KZT_EAGER_COMPATIBILITY_WRITE_APPLIED; + } +#endif + (void)context; + *slot = replacement; + if (final_value) { + *final_value = replacement; + } + return KZT_EAGER_COMPATIBILITY_WRITE_APPLIED; +} + +#ifdef CONFIG_LATX_KZT +static int kzt_elfloader_read_guest_memory(uintptr_t guest_addr, void *dst, + size_t size, void *opaque); + +typedef struct kzt_guest_glob_dat_target { + uintptr_t guest_target; + uintptr_t selected_target; + kzt_patch_object_ref_t owner; + kzt_guest_registry_source_lease_t source_lease; + kzt_guest_registry_patch_decision_lease_t decision_lease; + kzt_guest_library_loader_quiescence_lease_t loader_quiescence_lease; + kzt_guest_symbol_scope_request_t scope_request; + kzt_guest_symbol_scope_result_t scope_proof; + int exact_bridge; +} kzt_guest_glob_dat_target_t; + +static void kzt_guest_glob_dat_target_release( + kzt_guest_glob_dat_target_t *target) +{ + if (!target) { + return; + } + kzt_guest_registry_patch_decision_lease_release(&target->decision_lease); + kzt_guest_library_loader_quiescence_release( + &target->loader_quiescence_lease); + kzt_guest_registry_source_lease_release(&target->source_lease); +} + +static int kzt_guest_glob_dat_target_resolve( + box64context_t *context, elfheader_t *head, uintptr_t guest_target, + unsigned long symbol_index, const Elf64_Sym *symbol, + const char *symbol_name, int version, const char *version_name, + kzt_guest_glob_dat_target_t *target) +{ + kzt_owner_resolution_t resolution; + kzt_guest_registry_address_match_t source_match; + kzt_guest_registry_address_match_t owner_match; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = kzt_elfloader_read_guest_memory, + }; + kzt_guest_library_binding_key_t key; + kzt_guest_library_handle_t handle; + uintptr_t namespace_head = 0; + uintptr_t bridge_start = 0; + uintptr_t bridge_end = 0; + + if (!context || !head || !head->self_link_map || !guest_target || + symbol_index >= head->numDynSym || !symbol || !symbol_name || + !symbol_name[0] || !target) { + return 0; + } + memset(target, 0, sizeof(*target)); + target->guest_target = guest_target; + target->selected_target = guest_target; + kzt_owner_resolver_init(&resolution); + if (kzt_owner_resolver_resolve_current( + KztGuestRegistryForContext(context), guest_target, + guest_target, &resolution) != 0 || + resolution.status != KZT_OWNER_RESOLVER_RESOLVED || + resolution.owner_match != KZT_PATCH_OWNER_MATCH || + !resolution.current_owner.known || + !resolution.current_owner.link_map_addr || + !resolution.current_owner.generation) { + return 0; + } + target->owner = resolution.current_owner; + + /* The legacy bridge cache is keyed by name. Use it only for confirmed + unversioned function symbols in the exact guest-selected object. */ + if (ELF64_ST_TYPE(symbol->st_info) != STT_FUNC || + version >= 2 || (version_name && version_name[0]) || + context->kzt_guest_scope_layout == + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED || + kzt_guest_registry_find_live_object( + KztGuestRegistryForContext(context), head->self_link_map, + &source_match) != 0 || + !source_match.generation || + source_match.namespace_id_status != KZT_GUEST_FIELD_OK || + source_match.namespace_id != 0 || + kzt_guest_registry_find_live_object( + KztGuestRegistryForContext(context), + target->owner.link_map_addr, &owner_match) != 0 || + owner_match.generation != target->owner.generation || + owner_match.namespace_id_status != KZT_GUEST_FIELD_OK || + owner_match.namespace_id != 0 || + kzt_guest_registry_source_lease_acquire( + KztGuestRegistryForContext(context), head->self_link_map, + source_match.generation, source_match.namespace_id, + &target->source_lease) != 0 || + kzt_guest_registry_patch_decision_lease_acquire( + &target->source_lease, &target->decision_lease) != 0 || + kzt_guest_library_loader_quiescence_try_acquire( + KztGuestLibraryBindingsForContext(context), + &target->loader_quiescence_lease) != 0 || + kzt_guest_registry_context_get_main_namespace_head( + &context->kzt_guest_registry_context, &namespace_head) != 0) { + kzt_guest_glob_dat_target_release(target); + return 1; + } + target->scope_request = (kzt_guest_symbol_scope_request_t) { + .source = { + .link_map_addr = head->self_link_map, + .generation = source_match.generation, + .namespace_id = source_match.namespace_id, + .namespace_head = namespace_head, + .layout = context->kzt_guest_scope_layout, + }, + .symbol = symbol_name, + .version_evidence = KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + .reference_binding = ELF64_ST_BIND(symbol->st_info), + .reference_type = ELF64_ST_TYPE(symbol->st_info), + .reference_visibility = symbol->st_other & 0x3, + }; + if (kzt_guest_symbol_scope_check( + &target->scope_request, target->owner.link_map_addr, + guest_target, + &reader_ops, &target->scope_proof) != + KZT_GUEST_SYMBOL_SCOPE_SAFE) { + kzt_guest_glob_dat_target_release(target); + return 1; + } + key = (kzt_guest_library_binding_key_t) { + .link_map_addr = target->owner.link_map_addr, + .generation = target->owner.generation, + .namespace_id = owner_match.namespace_id, + .namespace_kind = owner_match.namespace_id == 0 ? + KZT_GUEST_LIBRARY_NAMESPACE_MAIN : + KZT_GUEST_LIBRARY_NAMESPACE_EXPLICIT, + }; + memset(&handle, 0, sizeof(handle)); + if (KztGuestLibraryLookupForContext(context, &key, &handle) != 0 || + !handle.library || + (handle.object_type != KZT_GUEST_LIBRARY_OBJECT_EMULATED && + handle.object_type != KZT_GUEST_LIBRARY_OBJECT_WRAPPED)) { + kzt_guest_library_handle_release(&handle); + kzt_guest_glob_dat_target_release(target); + return 1; + } + if (GetLibSymbolStartEnd( + handle.library, symbol_name, 0, &bridge_start, &bridge_end, + version, version_name, 0) && + bridge_start) { + target->selected_target = bridge_start; + target->exact_bridge = bridge_start != guest_target; + } + kzt_guest_library_handle_release(&handle); + if (!target->exact_bridge) { + kzt_guest_glob_dat_target_release(target); + } + return 1; +} +#endif + int RelocateElfRELA(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t* head, int cnt, Elf64_Rela *rela, int* need_resolv) { -// int ret_ok = 0; for (int i=0; ist_info); const char* symname = SymName(head, sym); uint64_t *p = (uint64_t*)(rela[i].r_offset + head->delta); - uintptr_t offs = 0; - uintptr_t end = 0; - library_t *resolved_provider = NULL; + kzt_legacy_rela_target_t legacy_target_result; int version = head->VerSym?((Elf64_Half*)((uintptr_t)head->VerSym+head->delta))[ELF64_R_SYM(rela[i].r_info)]:-1; if(version!=-1) version &=0x7fff; const char* vername = GetSymbolVersion(head, version); - if(bind==STB_LOCAL) { - offs = sym->st_value + head->delta; - end = offs + sym->st_size; - } else { - // this is probably very very wrong. A proprer way to get reloc need to be writen, but this hack seems ok for now - // at least it work for half-life, unreal, ut99, zsnes, Undertale, ColinMcRae Remake, FTL, ShovelKnight... - /*if(bind==STB_GLOBAL && (ndx==10 || ndx==19) && t!=R_X86_64_GLOB_DAT) { - offs = sym->st_value + head->delta; - end = offs + sym->st_size; - }*/ - // so weak symbol are the one left - if(!offs && !end) { - GetGlobalSymbolStartEndWithProvider( - maplib, symname, &offs, &end, head, version, vername, - &resolved_provider); - if(!offs && !end && local_maplib) { - GetGlobalSymbolStartEndWithProvider( - local_maplib, symname, &offs, &end, head, version, - vername, &resolved_provider); - } - } - } - //uintptr_t globoffs=0, globend=0; - uintptr_t tmp = 0; switch(t) { - case R_X86_64_GLOB_DAT: - // Look for same symbol already loaded but not in self (so no need for local_maplib here) - // if (GetGlobalNoWeakSymbolStartEnd(local_maplib?local_maplib:maplib, symname, &globoffs, &globend, version, vername)) { - // offs = globoffs; - // end = globend; - // } - if (offs) { - printf_log(LOG_INFO, "Apply %s R_X86_64_GLOB_DAT @%p (%p -> %p) on sym=%s (ver=%d/%s)\n", (bind==STB_LOCAL)?"Local":"Global", p, (void*)(p?(*p):0), (void*)offs, symname, version, vername?vername:"(none)"); - *p = offs/* + rela[i].r_addend*/; // not addend it seems + case R_X86_64_GLOB_DAT: +#ifdef CONFIG_LATX_KZT + if ((option_kzt || wine_option_kzt) && p) { + uintptr_t observed = __atomic_load_n( + (uintptr_t *)p, __ATOMIC_ACQUIRE); + kzt_guest_glob_dat_target_t guest_target; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = kzt_elfloader_read_guest_memory, + }; + kzt_guest_symbol_scope_result_t revalidated_scope; + + if (observed && bind != STB_LOCAL && + kzt_guest_glob_dat_target_resolve( + my_context, head, observed, + ELF64_R_SYM(rela[i].r_info), sym, symname, + version, vername, &guest_target)) { + uintptr_t expected = observed; + int scope_valid = + !guest_target.exact_bridge || + kzt_guest_symbol_scope_revalidate( + &guest_target.scope_proof, + &guest_target.scope_request, &reader_ops, + &revalidated_scope) == + KZT_GUEST_SYMBOL_SCOPE_SAFE; + int applied = + guest_target.selected_target == observed || + (scope_valid && + !kzt_patch_spike_guard_circuit_open( + KztPatchSpikeGuardForContext(my_context)) && + __atomic_compare_exchange_n( + (uintptr_t *)p, &expected, + guest_target.selected_target, 0, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)); + + printf_kzt_registry_diagnostics( + "kzt_eager_relocation schema=1 type=GLOB_DAT " + "symbol=%s route=%s host_lookup=0 " + "guest=%p selected=%p final=%p\n", + symname ? symname : "(none)", + applied && guest_target.exact_bridge ? + "EXACT_OWNER_BRIDGE" : "GUEST_PRESERVED", + (void *)observed, + (void *)guest_target.selected_target, + (void *)(applied ? + guest_target.selected_target : expected)); + kzt_guest_glob_dat_target_release(&guest_target); + continue; } + if (bind != STB_LOCAL) { + printf_kzt_registry_diagnostics( + "kzt_eager_relocation schema=1 type=GLOB_DAT " + "symbol=%s route=GUEST_PRESERVED host_lookup=0 " + "guest=%p final=%p evidence=UNAVAILABLE\n", + symname ? symname : "(none)", (void *)observed, + (void *)observed); + continue; + } + } +#endif + kzt_resolve_legacy_rela_target( + maplib, local_maplib, head, sym, bind, symname, + version, vername, &legacy_target_result); + if (legacy_target_result.start) { + uintptr_t observed = __atomic_load_n( + (uintptr_t *)p, __ATOMIC_ACQUIRE); + uintptr_t final_value = observed; + int write_status = kzt_eager_compatibility_write( + my_context, p, observed, legacy_target_result.start, + &final_value); + + printf_log(LOG_INFO, "Apply %s R_X86_64_GLOB_DAT @%p (%p -> %p) on sym=%s (ver=%d/%s)\n", (bind==STB_LOCAL)?"Local":"Global", p, (void*)(p?(*p):0), (void*)legacy_target_result.start, symname, version, vername?vername:"(none)"); + printf_kzt_registry_diagnostics( + "kzt_eager_relocation schema=1 type=GLOB_DAT " + "symbol=%s route=%s host_lookup=1 target=%p final=%p\n", + symname ? symname : "(none)", + write_status == KZT_EAGER_COMPATIBILITY_WRITE_APPLIED ? + "COMPATIBILITY_APPLIED" : + (write_status == + KZT_EAGER_COMPATIBILITY_WRITE_CAS_MISMATCH ? + "CAS_MISMATCH" : "WRITE_BLOCKED"), + (void *)legacy_target_result.start, + (void *)final_value); + } break; case R_X86_64_JUMP_SLOT: { // apply immediatly for gobject closure marshal or for LOCAL binding. Also, apply immediatly if it doesn't jump in the got uintptr_t slot_observation = (uintptr_t)(*p); uintptr_t expected_guest_target = slot_observation; - uintptr_t legacy_target = offs + rela[i].r_addend; + kzt_rela_jump_slot_defer_input_t defer_input = { + .slot_current_value = slot_observation, + .bind_is_local = bind == STB_LOCAL, + .bindnow = bindnow, + .need_resolver_present = need_resolv != NULL, + .load_bias = head->delta, + .plt_start = head->plt, + .plt_end = head->plt_end, + .gotplt_start = head->gotplt, + .gotplt_end = head->gotplt_end, + }; + kzt_rela_jump_slot_defer_plan_t defer_plan = + kzt_rela_jump_slot_defer_plan(&defer_input); int slot_is_unresolved_stub = - kzt_rela_slot_current_is_unresolved_stub( - slot_observation, - KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, - head->delta, head->plt, head->plt_end, - head->gotplt, head->gotplt_end); - tmp = slot_observation; - if (bind==STB_LOCAL - || !tmp - || !((tmp>=head->plt && tmpplt_end) || (tmp>=head->gotplt && tmpgotplt_end)) - || !need_resolv - || bindnow - ) { - if (offs){ - if(p) { -#ifdef CONFIG_LATX_KZT - if (option_kzt || wine_option_kzt) { - kzt_jump_slot_route_result_t route_result; - - if (kzt_production_jump_slot_route( - my_context, resolved_provider, - legacy_target, head, - need_resolv != NULL, i, - &rela[i], p, - slot_observation, - slot_is_unresolved_stub, - ELF64_R_SYM(rela[i].r_info), - symname, vername, - 1, expected_guest_target, - legacy_target, - &route_result) == 0 && - route_result.status == - KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED) { - break; - } - if (route_result.status == - KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED || - route_result.status == - KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH) { - break; - } - printf_kzt_registry_diagnostics( - "KZT eager route fallback slot=%p " - "observed=%p legacy=%p selected=%p " - "status=%d symbol=%s\n", - p, (void *)slot_observation, (void*)legacy_target, - (void *)route_result.selected_target, - route_result.status, - symname ? symname : "(none)"); - } -#endif - printf_log(LOG_INFO, "RelocateElfRELA : Apply %s R_X86_64_JUMP_SLOT @%p with sym=%s (%p -> %p)\n", (bind==STB_LOCAL)?"Local":"Global", p, symname, *(void**)p, (void*)(offs+rela[i].r_addend)); - *p =(uint64_t) legacy_target; - } else { - printf_log(LOG_INFO, "Warning, Symbol %s found, but Jump Slot Offset is NULL \n", symname); + defer_plan.slot_is_unresolved_stub; + if (defer_plan.should_defer) { + printf_log(LOG_INFO, "Preparing (if needed) %s R_X86_64_JUMP_SLOT @%p (0x%lx->0x%0lx) with sym=%s to be apply later (addend=%ld)\n", + (bind==STB_LOCAL)?"Local":"Global", p, *p, + defer_plan.should_add_delta ? *p + head->delta : *p, + symname, rela[i].r_addend); + if (defer_plan.should_add_delta) { + uintptr_t final_value = slot_observation; + int write_status = kzt_eager_compatibility_write( + my_context, p, slot_observation, + slot_observation + head->delta, &final_value); + + if (write_status != + KZT_EAGER_COMPATIBILITY_WRITE_APPLIED) { + printf_kzt_registry_diagnostics( + "kzt_eager_relocation schema=1 " + "type=JUMP_SLOT symbol=%s route=%s " + "host_lookup=0 final=%p\n", + symname ? symname : "(none)", + write_status == + KZT_EAGER_COMPATIBILITY_WRITE_CAS_MISMATCH ? + "CAS_MISMATCH" : "WRITE_BLOCKED", + (void *)final_value); } } - } else { - printf_log(LOG_INFO, "Preparing (if needed) %s R_X86_64_JUMP_SLOT @%p (0x%lx->0x%0lx) with sym=%s to be apply later (addend=%ld)\n", - (bind==STB_LOCAL)?"Local":"Global", p, *p, *p+head->delta, symname, rela[i].r_addend); - *p += head->delta; *need_resolv = 1; + break; + } + +#ifdef CONFIG_LATX_KZT + if ((option_kzt || wine_option_kzt) && + head->self_link_map && bind != STB_LOCAL && + p && slot_observation) { + kzt_jump_slot_route_result_t route_result = { + .status = KZT_JUMP_SLOT_ROUTE_WRITE_ERROR, + .observed_value = slot_observation, + .selected_target = slot_observation, + .final_value = slot_observation, + }; + int route_call_succeeded = + kzt_production_jump_slot_route( + my_context, NULL, slot_observation, head, + need_resolv != NULL, i, &rela[i], p, + slot_observation, slot_is_unresolved_stub, + ELF64_R_SYM(rela[i].r_info), symname, vername, + 1, expected_guest_target, 0, &route_result) == 0; + int final_value_usable = + route_result.final_value && + !kzt_rela_slot_current_is_unresolved_stub( + route_result.final_value, + KZT_RELA_STUB_COORDINATE_LINK_TIME_RAW, + head->delta, head->plt, head->plt_end, + head->gotplt, head->gotplt_end) && + !kzt_rela_slot_current_is_unresolved_stub( + route_result.final_value, + KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, + head->delta, head->plt, head->plt_end, + head->gotplt, head->gotplt_end); + kzt_jump_slot_route_caller_decision_t decision = + kzt_jump_slot_route_caller_decide( + route_call_succeeded, &route_result, 0, + final_value_usable); + + if (decision.slot_action != + KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE && + decision.slot_value_usable) { + printf_kzt_registry_diagnostics( + "kzt_eager_relocation schema=1 type=JUMP_SLOT " + "symbol=%s route=%s host_lookup=0 " + "observed=%p selected=%p\n", + symname ? symname : "(none)", + route_result.status == + KZT_JUMP_SLOT_ROUTE_WRITE_ROLLED_BACK ? + "WRITE_ROLLED_BACK" : + (route_result.status == + KZT_JUMP_SLOT_ROUTE_UNRECOVERABLE ? + "UNRECOVERABLE" : + (decision.slot_action == + KZT_JUMP_SLOT_ROUTE_SLOT_ROUTE_APPLIED ? + "NATIVE_APPLIED" : + "GUEST_PRESERVED")), + (void *)slot_observation, + (void *)route_result.final_value); + break; + } + } + if ((option_kzt || wine_option_kzt) && + bind != STB_LOCAL) { + uintptr_t final_value = p ? __atomic_load_n( + (uintptr_t *)p, __ATOMIC_ACQUIRE) : slot_observation; + + printf_kzt_registry_diagnostics( + "kzt_eager_relocation schema=1 type=JUMP_SLOT " + "symbol=%s route=GUEST_PRESERVED host_lookup=0 " + "observed=%p final=%p evidence=UNAVAILABLE\n", + symname ? symname : "(none)", + (void *)slot_observation, (void *)final_value); + break; + } +#endif + kzt_resolve_legacy_rela_target( + maplib, local_maplib, head, sym, bind, symname, + version, vername, &legacy_target_result); + if (legacy_target_result.start) { + uintptr_t legacy_target = + legacy_target_result.start + rela[i].r_addend; + if (p) { + uintptr_t final_value = slot_observation; + int write_status = kzt_eager_compatibility_write( + my_context, p, slot_observation, legacy_target, + &final_value); + + printf_kzt_registry_diagnostics( + "kzt_eager_relocation schema=1 type=JUMP_SLOT " + "symbol=%s route=%s host_lookup=1 " + "target=%p final=%p\n", + symname ? symname : "(none)", + write_status == + KZT_EAGER_COMPATIBILITY_WRITE_APPLIED ? + "COMPATIBILITY_APPLIED" : + (write_status == + KZT_EAGER_COMPATIBILITY_WRITE_CAS_MISMATCH ? + "CAS_MISMATCH" : "WRITE_BLOCKED"), + (void *)legacy_target, (void *)final_value); + printf_log(LOG_INFO, "RelocateElfRELA : Apply %s R_X86_64_JUMP_SLOT @%p with sym=%s (%p -> %p)\n", (bind==STB_LOCAL)?"Local":"Global", p, symname, *(void**)p, (void*)legacy_target); + } else { + printf_log(LOG_INFO, "Warning, Symbol %s found, but Jump Slot Offset is NULL \n", symname); + } } break; } @@ -814,6 +1205,404 @@ static int RelocateElfPltRELA(void *opaque, int *need_resolver) #ifdef CONFIG_LATX_KZT static void KztLazyBindingCompleteResolver(void); +static int kzt_per_object_runtime_to_raw(uintptr_t runtime, + uintptr_t load_bias, + uintptr_t *raw) +{ + if (!runtime || !raw || runtime < load_bias) { + return -1; + } + *raw = runtime - load_bias; + return 0; +} + +static int kzt_per_object_dynamic_field_runtime( + const kzt_guest_dynamic_field_t *field, uintptr_t *value) +{ + if (!field || !value || !field->present || + field->address_semantics != KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS || + !field->value || field->value > UINTPTR_MAX) { + return -1; + } + *value = (uintptr_t)field->value; + return 0; +} + +static int kzt_per_object_dynamic_field_scalar( + const kzt_guest_dynamic_field_t *field, uintptr_t *value) +{ + if (!field || !value || !field->present || + field->address_semantics != KZT_GUEST_DYNAMIC_SCALAR || + !field->value || field->value > UINTPTR_MAX) { + return -1; + } + *value = (uintptr_t)field->value; + return 0; +} + +static int kzt_elfloader_write_guest_word(uintptr_t guest_addr, + uintptr_t value) +{ + uintptr_t *host_ptr; + + host_ptr = lock_user(VERIFY_WRITE, (abi_ulong)guest_addr, + sizeof(*host_ptr), false); + if (!host_ptr) { + return -1; + } + *host_ptr = value; + unlock_user(host_ptr, (abi_ulong)guest_addr, sizeof(*host_ptr)); + return 0; +} + +static int kzt_per_object_plt_layout(uintptr_t jmprel_runtime, + uintptr_t pltrelsz, + uintptr_t load_bias, + size_t *limit, + uintptr_t *plt_start, + uintptr_t *plt_end) +{ + size_t count; + size_t index; + unsigned long maximum = 0; + uintptr_t first_stub = 0; + uintptr_t last_stub_end = 0; + + if (!jmprel_runtime || !pltrelsz || + pltrelsz % sizeof(Elf64_Rela) != 0 || + pltrelsz > 1024 * 1024 || !load_bias || !limit || !plt_start || + !plt_end) { + return -1; + } + count = pltrelsz / sizeof(Elf64_Rela); + for (index = 0; index < count; ++index) { + Elf64_Rela relocation; + uintptr_t address; + uintptr_t slot_addr; + uintptr_t slot_current; + uintptr_t stub_addr; + uint8_t stub[16]; + int32_t displacement; + uint32_t relocation_index; + unsigned long symbol_index; + + if (index > (UINTPTR_MAX - jmprel_runtime) / sizeof(relocation)) { + return -1; + } + address = jmprel_runtime + index * sizeof(relocation); + if (kzt_elfloader_read_guest_memory(address, &relocation, + sizeof(relocation), NULL) != 0) { + return -1; + } + if (relocation.r_offset > UINTPTR_MAX - load_bias) { + return -1; + } + slot_addr = load_bias + relocation.r_offset; + if (kzt_elfloader_read_guest_memory(slot_addr, &slot_current, + sizeof(slot_current), NULL) != 0 || + slot_current < load_bias || slot_current < 6 || + slot_current > UINTPTR_MAX - 10) { + return -1; + } + stub_addr = slot_current - 6; + if (kzt_elfloader_read_guest_memory(stub_addr, stub, sizeof(stub), + NULL) != 0 || + stub[0] != 0xff || stub[1] != 0x25 || stub[6] != 0x68) { + return -1; + } + memcpy(&displacement, &stub[2], sizeof(displacement)); + relocation_index = (uint32_t)stub[7] | + ((uint32_t)stub[8] << 8) | + ((uint32_t)stub[9] << 16) | + ((uint32_t)stub[10] << 24); + if ((uintptr_t)((intptr_t)slot_current + displacement) != slot_addr || + index > UINT32_MAX || relocation_index != (uint32_t)index) { + return -1; + } + if (!first_stub || stub_addr < first_stub) { + first_stub = stub_addr; + } + if (stub_addr + sizeof(stub) > last_stub_end) { + last_stub_end = stub_addr + sizeof(stub); + } + symbol_index = ELF64_R_SYM(relocation.r_info); + if (symbol_index > maximum) { + maximum = symbol_index; + } + } + if (maximum == SIZE_MAX || !first_stub || first_stub < load_bias || + last_stub_end <= first_stub || last_stub_end < load_bias) { + return -1; + } + *limit = (size_t)maximum + 1; + *plt_start = first_stub - load_bias; + *plt_end = last_stub_end - load_bias; + return 0; +} + +void KztPerObjectGotPltRelease(uintptr_t object_head) +{ + if (object_head) { + box_free((void *)object_head); + } +} + +int KztPrebindTargetTbPrepare(uintptr_t target) +{ +#ifdef CONFIG_LATX_KZT + CPUX86State *env; + CPUState *cpu; + TranslationBlock *tb; + TranslationBlock *current; + uintptr_t saved_eip; + uintptr_t pending[KZT_PREBIND_GUEST_TB_BUDGET]; + size_t pending_count = 0; + size_t pending_index; + size_t prepared = 0; + uint64_t timing_start = kzt_lifecycle_diagnostics_enabled() + ? kzt_lifecycle_diagnostics_now() + : 0; + int result = -1; + + if (!target || !lsenv || !(env = (CPUX86State *)lsenv->cpu_state)) { + goto out; + } + cpu = env_cpu(env); + if (!cpu || target < env->segs[R_CS].base) { + goto out; + } + if (kzt_tb_prebind_target_is_prepared(cpu, target)) { + result = 0; + goto out; + } + if (target > reserved_va) { + kzt_tb_pin_prebind_bridge(cpu, target); + } + saved_eip = env->eip; + env->eip = target - env->segs[R_CS].base; + tb = kzt_tb_find_exp(cpu, NULL, 0, cpu->tcg_cflags); + env->eip = saved_eip; + if (!tb) { + goto out; + } + prepared = 1; + if (target <= reserved_va) { + kzt_tb_steady_diagnostics_note_guest_prepare(cpu, tb->pc); + pending[pending_count++] = target; + for (pending_index = 0; + pending_index < pending_count; + ++pending_index) { + size_t exit_index; + + current = tb; + if (pending_index) { + saved_eip = env->eip; + env->eip = pending[pending_index] - env->segs[R_CS].base; + current = kzt_tb_find_exp(cpu, NULL, 0, cpu->tcg_cflags); + env->eip = saved_eip; + if (!current) { + continue; + } + ++prepared; + } + kzt_tb_steady_diagnostics_note_guest_prepare(cpu, current->pc); + for (exit_index = 0; + exit_index < 2 && pending_count < KZT_PREBIND_GUEST_TB_BUDGET; + ++exit_index) { + uintptr_t next; + size_t known_index; + + if (!current->canlink[exit_index] || + current->lazypc[exit_index] <= 0 || + current->pc > UINTPTR_MAX - + (uintptr_t)current->lazypc[exit_index]) { + continue; + } + next = current->pc + (uintptr_t)current->lazypc[exit_index]; + if (!next || next > reserved_va) { + continue; + } + for (known_index = 0; + known_index < pending_count && pending[known_index] != next; + ++known_index) { + } + if (known_index == pending_count) { + pending[pending_count++] = next; + } + } + } + kzt_tb_prebind_guest_note_prepared(cpu, target); + } + if (tb && getenv("LATX_KZT_PINNED_BRIDGE_DIAGNOSTICS")) { + fprintf(stderr, + "kzt_prebind_target schema=1 target=0x%lx tb=%p " + "lazy0=0x%lx lazy1=0x%lx canlink0=%u canlink1=%u prepared=%zu\n", + (unsigned long)target, tb, (unsigned long)tb->lazypc[0], + (unsigned long)tb->lazypc[1], + tb->canlink[0], tb->canlink[1], prepared); + } + result = 0; +out: + if (timing_start) { + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_TARGET_PREPARE, + kzt_lifecycle_diagnostics_now() - timing_start); + } + return result; +#else + (void)target; + return -1; +#endif +} + +static int kzt_per_object_prepare_pinned_target(uintptr_t target, void *opaque) +{ + (void)opaque; + return KztPrebindTargetTbPrepare(target); +} + +int KztPerObjectGotPltWrite(uintptr_t link_map_addr, + unsigned long generation, + const kzt_guest_dynamic_view_t *view, + void *opaque) +{ + box64context_t *context = opaque; + kzt_guest_registry_t *registry; + kzt_guest_lazy_resolver_t resolver = { 0 }; + elfheader_t *head = NULL; + uintptr_t load_bias; + uintptr_t jmprel_runtime; + uintptr_t pltgot_runtime; + uintptr_t symtab_runtime; + uintptr_t strtab_runtime; + uintptr_t pltrelsz; + uintptr_t pltrel; + uintptr_t guest_link_map; + uintptr_t guest_resolver; + uintptr_t resolver_bridge; + uintptr_t raw; + size_t dynsym_limit; + uintptr_t plt_start; + uintptr_t plt_end; + + if (!context || context != my_context || !link_map_addr || !generation || + !view || view->status != KZT_GUEST_DYNAMIC_COMPLETE || + !view->has_null || !(load_bias = view->load_bias) || + kzt_per_object_dynamic_field_runtime(&view->jmprel, + &jmprel_runtime) != 0 || + kzt_per_object_dynamic_field_runtime(&view->pltgot, + &pltgot_runtime) != 0 || + kzt_per_object_dynamic_field_runtime(&view->symtab, + &symtab_runtime) != 0 || + kzt_per_object_dynamic_field_runtime(&view->strtab, + &strtab_runtime) != 0 || + kzt_per_object_dynamic_field_scalar(&view->pltrelsz, + &pltrelsz) != 0 || + kzt_per_object_dynamic_field_scalar(&view->pltrel, &pltrel) != 0 || + pltrel != DT_RELA || pltrelsz % sizeof(Elf64_Rela) != 0 || + kzt_per_object_plt_layout(jmprel_runtime, pltrelsz, load_bias, + &dynsym_limit, &plt_start, &plt_end) != 0 || + kzt_elfloader_read_guest_memory(pltgot_runtime + 8, &guest_link_map, + sizeof(guest_link_map), NULL) != 0 || + kzt_elfloader_read_guest_memory(pltgot_runtime + 16, &guest_resolver, + sizeof(guest_resolver), NULL) != 0 || + guest_link_map != link_map_addr || !guest_resolver) { + return -1; + } + if (!BridgeForkProtectionAvailable()) { + return -1; + } + + if (!context->kzt_plt_resolver_bridge) { + context->kzt_plt_resolver_bridge = AddBridge( + context->system, vFE, PltResolver, 0, "PltResolver"); + } + if (!context->kzt_lazy_completion_bridge) { + context->kzt_lazy_completion_bridge = AddBridge( + context->system, vFE, KztLazyBindingCompleteResolver, 0, + "KztLazyBindingCompleteResolver"); + } + resolver_bridge = context->kzt_plt_resolver_bridge; + if (!kzt_plt_resolver_injection_allowed(guest_resolver, resolver_bridge)) { + return -1; + } + + head = box_calloc(1, sizeof(*head)); + if (!head || + kzt_per_object_runtime_to_raw(jmprel_runtime, load_bias, + &head->jmprel) != 0 || + kzt_per_object_runtime_to_raw(pltgot_runtime, load_bias, + &head->pltgot) != 0) { + KztPerObjectGotPltRelease((uintptr_t)head); + return -1; + } + head->delta = (intptr_t)load_bias; + head->pltsz = pltrelsz; + head->pltent = sizeof(Elf64_Rela); + head->pltrel = pltrel; + head->plt = plt_start; + head->plt_end = plt_end; + head->gotplt = head->pltgot; + head->gotplt_end = head->pltgot + (pltrelsz / sizeof(Elf64_Rela) + 3) * + sizeof(uintptr_t); + head->DynSym = (Elf64_Sym *)symtab_runtime; + head->numDynSym = dynsym_limit; + head->DynStr = (char *)strtab_runtime; + head->self_link_map = link_map_addr; + head->kzt_guest_resolver = guest_resolver; + + if (view->versym.present && + kzt_per_object_dynamic_field_runtime(&view->versym, &raw) == 0 && + kzt_per_object_runtime_to_raw(raw, load_bias, &raw) == 0) { + head->VerSym = (Elf64_Half *)raw; + /* The resolver accepts an absent version table as unversioned. */ + } + if (view->verneed.present && + kzt_per_object_dynamic_field_runtime(&view->verneed, &raw) == 0) { + if (kzt_per_object_runtime_to_raw(raw, load_bias, &raw) == 0) { + head->VerNeed = (Elf64_Verneed *)raw; + } + } + if (view->verdef.present && + kzt_per_object_dynamic_field_runtime(&view->verdef, &raw) == 0) { + if (kzt_per_object_runtime_to_raw(raw, load_bias, &raw) == 0) { + head->VerDef = (Elf64_Verdef *)raw; + } + } + + resolver = (kzt_guest_lazy_resolver_t) { + .link_map_slot = pltgot_runtime + 8, + .resolver_slot = pltgot_runtime + 16, + .guest_link_map = guest_link_map, + .guest_resolver = guest_resolver, + .object_head = (uintptr_t)head, + .registry_owned_head = 1, + }; + registry = KztGuestRegistryForContext(context); + if (!registry || + kzt_elfloader_write_guest_word(resolver.link_map_slot, + resolver.object_head) != 0 || + kzt_elfloader_write_guest_word(resolver.resolver_slot, + resolver_bridge) != 0 || + kzt_guest_registry_publish_lazy_resolver( + registry, link_map_addr, generation, 0, &resolver) != 0) { + (void)kzt_elfloader_write_guest_word(resolver.resolver_slot, + guest_resolver); + (void)kzt_elfloader_write_guest_word(resolver.link_map_slot, + guest_link_map); + KztPerObjectGotPltRelease((uintptr_t)head); + return -1; + } + (void)kzt_production_lazy_prebind_object( + context, head, generation, view, + kzt_per_object_prepare_pinned_target, NULL); + printf_kzt_registry_diagnostics( + "kzt_per_object_got_plt schema=1 link_map=0x%lx generation=%lu " + "result=APPLIED\n", + (unsigned long)link_map_addr, generation); + return 0; +} + static int kzt_elfloader_read_guest_memory(uintptr_t guest_addr, void *dst, size_t size, void *opaque) { @@ -924,6 +1713,7 @@ static void kzt_observe_plt_source(elfheader_t *head, request.library_bindings = KztGuestLibraryBindingsForContext(my_context); request.reader_ops = &reader_ops; + request.reuse_complete_dynamic_view = 1; request.namespace_id_present = main_namespace == 1; request.namespace_id = 0; request.map_range_present = range_available; @@ -937,11 +1727,13 @@ static void kzt_observe_plt_source(elfheader_t *head, result, (unsigned long)guest_link_map, main_namespace, (unsigned long)map_start, (unsigned long)map_end); } + #endif int RelocateElfPlt(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t* head) { int need_resolver = 0; + uintptr_t resolver_bridge = 0; uintptr_t resolver_got = head->pltgot ? head->pltgot : head->got; uintptr_t resolver_got_runtime = resolver_got ? resolver_got + head->delta : 0; @@ -953,6 +1745,7 @@ int RelocateElfPlt(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t* kzt_evidence_got + head->delta : 0; kzt_guest_link_map_identity_t expected_identity = { 0 }; kzt_guest_link_map_identity_t observed_identity = { 0 }; + kzt_guest_registry_address_match_t resolver_match = { 0 }; int resolver_snapshot_available = 0; if (kzt_evidence_got_runtime && (option_kzt || wine_option_kzt) && @@ -975,6 +1768,17 @@ int RelocateElfPlt(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t* resolver_snapshot_available = 1; kzt_observe_plt_source(head, guest_link_map, &observed_identity); head->self_link_map = guest_link_map; + { + kzt_per_object_got_plt_request_t request = { + .registry = KztGuestRegistryForContext(my_context), + .link_map_addr = guest_link_map, + .apply = KztPerObjectGotPltWrite, + .opaque = my_context, + }; + kzt_per_object_got_plt_result_t result = { 0 }; + + (void)kzt_per_object_got_plt_apply(&request, &result); + } } #endif head->had_RelocateElfPlt = 1; @@ -1004,9 +1808,22 @@ int RelocateElfPlt(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t* } } if(need_resolver) { +#ifdef CONFIG_LATX_KZT + if (!BridgeForkProtectionAvailable()) { + return 0; + } + if (!my_context->kzt_plt_resolver_bridge) { + my_context->kzt_plt_resolver_bridge = AddBridge( + my_context->system, vFE, PltResolver, 0, + "PltResolver"); + } + resolver_bridge = my_context->kzt_plt_resolver_bridge; +#else if(pltResolver==~0LL) { pltResolver = AddBridge(my_context->system, vFE, PltResolver, 0, "PltResolver"); } + resolver_bridge = pltResolver; +#endif #ifdef CONFIG_LATX_KZT if ((option_kzt || wine_option_kzt) && !my_context->kzt_lazy_completion_bridge) { @@ -1027,32 +1844,54 @@ int RelocateElfPlt(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t* #ifdef CONFIG_LATX_KZT } #endif +#ifdef CONFIG_LATX_KZT + if ((option_kzt || wine_option_kzt) && + kzt_guest_registry_find_live_object( + KztGuestRegistryForContext(my_context), + guest_link_map, &resolver_match) == 0 && + resolver_match.namespace_id_status == + KZT_GUEST_FIELD_OK && + resolver_match.namespace_id == 0 && + kzt_guest_registry_got_plt_injection_claimed( + KztGuestRegistryForContext(my_context), + guest_link_map, resolver_match.generation, 0) == 1) { + return 0; + } +#endif + if (!kzt_plt_resolver_injection_allowed( + guest_resolver, resolver_bridge)) { + printf_log( + LOG_NONE, + "KZT: preserving guest PLT resolver for %s failed; " + "resolver injection disabled\n", + head->name ? head->name : "(unknown)"); + return 0; + } + if (!head->kzt_guest_resolver) { + head->kzt_guest_resolver = guest_resolver; + } #ifdef CONFIG_LATX_KZT if (option_kzt || wine_option_kzt) { - kzt_guest_object_snapshot_t *snapshot = NULL; kzt_guest_lazy_resolver_t resolver = { .link_map_slot = resolver_got_runtime + 8, .resolver_slot = resolver_got_runtime + 16, .guest_link_map = guest_link_map, .guest_resolver = guest_resolver, }; - if (kzt_guest_registry_find_by_link_map( + if (kzt_guest_registry_find_live_object( KztGuestRegistryForContext(my_context), - guest_link_map, &snapshot) == 0 && snapshot && - snapshot->namespace_id.status == KZT_GUEST_FIELD_OK && - snapshot->namespace_id.value == 0) { + guest_link_map, &resolver_match) == 0 && + resolver_match.namespace_id_status == + KZT_GUEST_FIELD_OK && + resolver_match.namespace_id == 0) { (void)kzt_guest_registry_publish_lazy_resolver( KztGuestRegistryForContext(my_context), - guest_link_map, snapshot->generation, 0, + guest_link_map, resolver_match.generation, 0, &resolver); } - kzt_guest_object_snapshot_free(snapshot); } #endif - if(dl_runtime_resolver ==~0LL){ - dl_runtime_resolver = *(uintptr_t*)(head->got+head->delta+16); - } - *(uintptr_t*)(resolver_got_runtime+16) = pltResolver; + *(uintptr_t*)(resolver_got_runtime+16) = resolver_bridge; #ifdef CONFIG_LATX_KZT if (!(option_kzt || wine_option_kzt) || resolver_snapshot_available) { @@ -1583,9 +2422,47 @@ static void Push64(CPUX86State *cpu, uint64_t v) *((uint64_t*)cpu->regs[R_ESP]) = v; } +#ifndef CONFIG_LATX_KZT uintptr_t pltResolver = ~0LL; -uintptr_t dl_runtime_resolver = ~0LL; -uintptr_t link_map_obj=0; +#endif + +uintptr_t KztPltResolverBridge(void) +{ +#ifdef CONFIG_LATX_KZT + return my_context ? my_context->kzt_plt_resolver_bridge : 0; +#else + return pltResolver == ~0LL ? 0 : pltResolver; +#endif +} + +int KztPltResolverDispatch(void *cpu_state, uintptr_t pc) +{ +#ifdef CONFIG_LATX_KZT + CPUX86State *cpu = cpu_state; + uintptr_t resolver_bridge = KztPltResolverBridge(); + onebridge_t *bridge = (onebridge_t *)resolver_bridge; + + if (!(option_kzt || wine_option_kzt) || !cpu || !lsenv || + cpu != (CPUX86State *)lsenv->cpu_state || + !resolver_bridge || pc != resolver_bridge) { + return 0; + } + if (bridge->CC == 0xCC && + bridge->S == 'S' && + bridge->C == 'C' && + (uintptr_t)bridge->w == (uintptr_t)vFE && + bridge->f == (uintptr_t)PltResolver) { + PltResolver(); + cpu->eip = Pop64(cpu); + return 1; + } +#else + (void)cpu_state; + (void)pc; +#endif + return 0; +} + #ifdef CONFIG_LATX_KZT static void KztLazyBindingCompleteResolver(void) { @@ -1639,6 +2516,7 @@ typedef struct kzt_plt_resolver_production_state { uintptr_t slot_addr; uintptr_t unresolved_stub; const char *symbol; + kzt_symbol_version_evidence_t version_evidence; const char *version; long addend; } kzt_plt_resolver_production_state_t; @@ -1648,41 +2526,50 @@ static int kzt_plt_resolver_lookup_source( { kzt_plt_resolver_production_state_t *state = opaque; kzt_guest_registry_t *registry = KztGuestRegistryForContext(my_context); - kzt_guest_object_snapshot_t *snapshot = NULL; - kzt_guest_lazy_resolver_t resolver; - int result = -1; + kzt_guest_registry_lazy_source_t registry_source; if (!state || !state->head || object_head != (uintptr_t)state->head || - !source || !state->head->self_link_map || - kzt_guest_registry_find_by_link_map( - registry, state->head->self_link_map, &snapshot) != 0 || - !snapshot || snapshot->namespace_id.status != KZT_GUEST_FIELD_OK || - snapshot->namespace_id.value != 0 || - kzt_guest_registry_find_lazy_resolver( - registry, state->head->self_link_map, snapshot->generation, 0, - &resolver) != 0 || - resolver.guest_link_map != state->head->self_link_map) { - goto out; + !source || !state->head->self_link_map) { + return -1; + } + if (kzt_guest_registry_find_lazy_source( + registry, state->head->self_link_map, ®istry_source) == 0) { + *source = (kzt_plt_resolver_source_t) { + .enabled = my_context->kzt_lazy_completion_bridge != 0, + .context_id = (uintptr_t)my_context, + .object_head = object_head, + .source_link_map = state->head->self_link_map, + .source_generation = registry_source.generation, + .namespace_id = registry_source.namespace_id, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + .slot_addr = state->slot_addr, + .unresolved_stub = state->unresolved_stub, + .symbol = state->symbol, + .version_evidence = state->version_evidence, + .version = state->version, + .addend = state->addend, + .guest_resolver = registry_source.guest_resolver, + }; + return 0; + } + if (!state->head->kzt_guest_resolver) { + return -1; } *source = (kzt_plt_resolver_source_t) { - .enabled = my_context->kzt_lazy_completion_bridge != 0, + .enabled = 0, .context_id = (uintptr_t)my_context, .object_head = object_head, .source_link_map = state->head->self_link_map, - .source_generation = snapshot->generation, - .namespace_id = snapshot->namespace_id.value, - .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_UNSUPPORTED, .slot_addr = state->slot_addr, .unresolved_stub = state->unresolved_stub, .symbol = state->symbol, + .version_evidence = state->version_evidence, .version = state->version, .addend = state->addend, - .guest_resolver = resolver.guest_resolver, + .guest_resolver = state->head->kzt_guest_resolver, }; - result = 0; -out: - kzt_guest_object_snapshot_free(snapshot); - return result; + return 0; } static int kzt_plt_resolver_begin_lazy_binding( @@ -1694,29 +2581,103 @@ static int kzt_plt_resolver_begin_lazy_binding( return kzt_lazy_binding_begin(request, pending, result); } #endif + +static int plt_resolver_handoff_guest( + CPUX86State *cpu, elfheader_t *head, uint64_t relocation_slot) +{ + if (!cpu || !head || !cpu->regs[R_ESP] || !head->self_link_map || + !head->kzt_guest_resolver) { + return -1; + } + cpu->regs[R_ESP] += 2 * sizeof(uint64_t); + Push64(cpu, relocation_slot); + Push64(cpu, head->self_link_map); + Push64(cpu, head->kzt_guest_resolver); + return 0; +} + +static void plt_resolver_handoff_guest_or_abort( + CPUX86State *cpu, elfheader_t *head, uint64_t relocation_slot, + const char *reason) +{ + if (plt_resolver_handoff_guest(cpu, head, relocation_slot) == 0) { + return; + } + printf_log( + LOG_NONE, + "KZT: cannot restore per-object guest PLT resolver for %s " + "after %s; aborting instead of returning with an intercepted frame\n", + head && head->name ? head->name : "(unknown)", + reason ? reason : "resolver failure"); + abort(); +} + void PltResolver(void) { CPUX86State *cpu = (CPUX86State*)lsenv->cpu_state; - uintptr_t addr = Pop64(cpu); - int slot = (int)Pop64(cpu); +#ifdef CONFIG_LATX_KZT + int timing_enabled = option_kzt_lazy_diagnostics != 0; + uint64_t translation_ready_ns = timing_enabled + ? __atomic_exchange_n( + &kzt_lazy_bridge_translation_ready_ns, 0, __ATOMIC_ACQ_REL) + : 0; + uint64_t resolver_entry_ns = timing_enabled + ? kzt_lazy_resolver_timing_now() + : 0; + uint64_t resolver_entry_minflt = timing_enabled + ? kzt_lazy_resolver_minor_faults() + : 0; +#endif + uintptr_t *intercepted_frame = (uintptr_t *)cpu->regs[R_ESP]; + uintptr_t addr = intercepted_frame[0]; + uint64_t relocation_slot = intercepted_frame[1]; + uintptr_t return_address = intercepted_frame[2]; elfheader_t *h = (elfheader_t*)addr; - printf_log(LOG_INFO, "PltResolver: Addr=%p, Slot=%d Return=%p: elf is %s (VerSym=%p)\n", (void*)addr, slot, *(void**)(cpu->regs[R_ESP]), h->name, h->VerSym); - + int slot; + unsigned long symbol_index; + + if (!h || + !kzt_plt_resolver_relocation_index_valid( + relocation_slot, h->jmprel, h->pltsz, h->pltent)) { + plt_resolver_handoff_guest_or_abort( + cpu, h, relocation_slot, "invalid relocation index"); + return; + } + slot = (int)relocation_slot; Elf64_Rela * rel = (Elf64_Rela *)(h->jmprel + h->delta) + slot; - Elf64_Sym *sym = &h->DynSym[ELF64_R_SYM(rel->r_info)]; - #if defined(CONFIG_LATX_KZT) && defined(CONFIG_LATX_DEBUG) - int bind = ELF64_ST_BIND(sym->st_info); - #endif + symbol_index = ELF64_R_SYM(rel->r_info); + if (!kzt_plt_resolver_symbol_index_valid( + symbol_index, (uintptr_t)h->DynSym, h->numDynSym)) { + plt_resolver_handoff_guest_or_abort( + cpu, h, relocation_slot, "invalid dynamic symbol index"); + return; + } + printf_log(LOG_INFO, "PltResolver: Addr=%p, Slot=%d Return=%p: elf is %s (VerSym=%p)\n", (void*)addr, slot, (void*)return_address, h->name, h->VerSym); + (void)return_address; + + Elf64_Sym *sym = &h->DynSym[symbol_index]; const char* symname = SymName(h, sym); - int version = h->VerSym?((Elf64_Half*)((uintptr_t)h->VerSym+h->delta))[ELF64_R_SYM(rel->r_info)]:-1; - if(version!=-1) version &= 0x7fff; - const char* vername = GetSymbolVersion(h, version); + int raw_version = h->VerSym ? + ((Elf64_Half *)((uintptr_t)h->VerSym + h->delta))[ + symbol_index] : -1; + int version = raw_version; + kzt_symbol_version_evidence_t version_evidence; + const char *vername; + + if (version != -1) { + version &= 0x7fff; + } + vername = GetSymbolVersion(h, version); + if (raw_version == -1 || version < 2) { + version_evidence = KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + vername = NULL; + } else if (vername && vername[0]) { + version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + } else { + version_evidence = KZT_SYMBOL_VERSION_ERROR; + vername = NULL; + } uint64_t *p = (uint64_t*)(rel->r_offset + h->delta); - uintptr_t offs = 0; - uintptr_t end = 0; - library_t *resolved_provider = NULL; - - (void)bind; #ifdef CONFIG_LATX_KZT if (option_kzt_lazy_diagnostics) { @@ -1726,12 +2687,14 @@ void PltResolver(void) (void *)h->self_link_map); } if (option_kzt || wine_option_kzt) { + kzt_lazy_direct_route_result_t direct_result; kzt_plt_resolver_production_state_t state = { .head = h, .slot_addr = (uintptr_t)p, .unresolved_stub = p ? __atomic_load_n((uintptr_t *)p, __ATOMIC_ACQUIRE) : 0, .symbol = symname, + .version_evidence = version_evidence, .version = vername, .addend = rel->r_addend, }; @@ -1744,80 +2707,110 @@ void PltResolver(void) .opaque = &state, }; kzt_plt_resolver_enter_result_t enter_result; + uint64_t route_start_ns = timing_enabled + ? kzt_lazy_resolver_timing_now() + : 0; + uint64_t route_start_minflt = timing_enabled + ? kzt_lazy_resolver_minor_faults() + : 0; + + if (kzt_production_lazy_direct_route( + my_context, h, slot, rel, p, + __atomic_load_n(p, __ATOMIC_ACQUIRE), + symbol_index, symname, version_evidence, vername, + &direct_result) == 0 && + direct_result.status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED) { + uint64_t route_done_ns = timing_enabled + ? kzt_lazy_resolver_timing_now() + : 0; + uint64_t route_done_minflt = timing_enabled + ? kzt_lazy_resolver_minor_faults() + : 0; + printf_kzt_registry_diagnostics( + "kzt_lazy_path schema=1 symbol=%s route=NEW_DIRECT " + "guest_handoff=0 legacy_lookup=0 legacy_write=0\n", + symname ? symname : "(none)"); + (void)Pop64(cpu); + (void)Pop64(cpu); + Push64(cpu, direct_result.selected_target); + if (timing_enabled) { + uint64_t resolver_done_ns = + kzt_lazy_resolver_timing_now(); + uint64_t resolver_done_minflt = + kzt_lazy_resolver_minor_faults(); + + fprintf( + stderr, + "kzt_lazy_resolver_timing schema=1 " + "translation_to_entry_ns=%lu " + "entry_minflt=%lu " + "prepare_ns=%lu prepare_minflt=%lu " + "route_ns=%lu route_minflt=%lu " + "finish_ns=%lu finish_minflt=%lu " + "done_minflt=%lu total_ns=%lu\n", + (unsigned long)( + resolver_entry_ns >= translation_ready_ns && + translation_ready_ns + ? resolver_entry_ns - translation_ready_ns + : 0), + (unsigned long)resolver_entry_minflt, + (unsigned long)( + route_start_ns >= resolver_entry_ns + ? route_start_ns - resolver_entry_ns + : 0), + (unsigned long)( + route_start_minflt >= resolver_entry_minflt + ? route_start_minflt - resolver_entry_minflt + : 0), + (unsigned long)( + route_done_ns >= route_start_ns + ? route_done_ns - route_start_ns + : 0), + (unsigned long)( + route_done_minflt >= route_start_minflt + ? route_done_minflt - route_start_minflt + : 0), + (unsigned long)( + resolver_done_ns >= route_done_ns + ? resolver_done_ns - route_done_ns + : 0), + (unsigned long)( + resolver_done_minflt >= route_done_minflt + ? resolver_done_minflt - route_done_minflt + : 0), + (unsigned long)resolver_done_minflt, + (unsigned long)( + resolver_done_ns >= resolver_entry_ns + ? resolver_done_ns - resolver_entry_ns + : 0)); + __atomic_store_n( + &kzt_lazy_resolver_done_ns, + kzt_lazy_resolver_timing_now(), __ATOMIC_RELEASE); + __atomic_store_n( + &kzt_lazy_target_bridge_pc, + direct_result.selected_target, __ATOMIC_RELEASE); + } + return; + } if (kzt_plt_resolver_enter(cpu, &ops, &enter_result) == 0 && enter_result.status != KZT_PLT_RESOLVER_LEGACY_FRAME_RESTORED) { + printf_kzt_registry_diagnostics( + "kzt_lazy_path schema=1 symbol=%s route=GUEST_LD_SO " + "guest_handoff=1 legacy_lookup=0 legacy_write=0\n", + symname ? symname : "(none)"); return; } } #endif - (void)Pop64(cpu); - (void)Pop64(cpu); - - library_t* lib = h->lib; - lib_t* local_maplib = GetMaplib(lib); - GetGlobalSymbolStartEndWithProvider( - my_context->maplib, symname, &offs, &end, h, version, vername, - &resolved_provider); - if(!offs && !end && local_maplib) { - GetGlobalSymbolStartEndWithProvider( - local_maplib, symname, &offs, &end, h, version, vername, - &resolved_provider); - } - if(!offs && !end && !version) - GetGlobalSymbolStartEndWithProvider( - my_context->maplib, symname, &offs, &end, h, -1, NULL, - &resolved_provider); - - if (!offs) { -// printf_log(LOG_INFO, "Error: PltResolver: Symbol %s(ver %d: %s%s%s) not found, cannot apply R_X86_64_JUMP_SLOT %p (%p) in %s\n", symname, version, symname, vername?"@":"", vername?vername:"", p, *(void**)p, h->name); - //return to __dl_runtime_resolver - Push64(cpu, slot); - Push64(cpu, h->self_link_map); - Push64(cpu, dl_runtime_resolver); - return; - } else { - offs = (uintptr_t)getAlternate((void*)offs); - if(p) { - uintptr_t slot_observation = (uintptr_t)(*p); - uintptr_t legacy_target = offs; + plt_resolver_handoff_guest_or_abort( + cpu, h, relocation_slot, "missing per-object resolver"); #ifdef CONFIG_LATX_KZT - if (option_kzt || wine_option_kzt) { - kzt_jump_slot_route_result_t route_result; - - if (kzt_production_jump_slot_route( - my_context, resolved_provider, legacy_target, h, - 1, slot, rel, p, - slot_observation, 1, - ELF64_R_SYM(rel->r_info), symname, - vername, 0, 0, legacy_target, &route_result) == 0 && - route_result.status == - KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED) { - printf_kzt_registry_diagnostics( - "KZT lazy route applied slot=%p observed=%p " - "legacy=%p selected=%p symbol=%s\n", - p, (void *)slot_observation, (void*)legacy_target, - (void *)route_result.selected_target, - symname ? symname : "(none)"); - Push64(cpu, route_result.selected_target); - return; - } - if (route_result.status == - KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED || - route_result.status == - KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH) { - Push64(cpu, route_result.final_value); - return; - } - } + printf_kzt_registry_diagnostics( + "kzt_lazy_path schema=1 symbol=%s route=GUEST_LD_SO " + "guest_handoff=1 legacy_lookup=0 legacy_write=0\n", + symname ? symname : "(none)"); #endif - printf_log(LOG_INFO, " Apply %s R_X86_64_JUMP_SLOT %p with sym=%s(ver %d: %s%s%s) (%p -> %p / %s)\n", (bind==STB_LOCAL)?"Local":"Global", p, symname, version, symname, vername?"@":"", vername?vername:"",*(void**)p, (void*)offs, ElfName(FindElfAddress(my_context, offs))); - *p = offs; - } else { - printf_log(LOG_INFO, "PltResolver: Warning, Symbol %s(ver %d: %s%s%s) found, but Jump Slot Offset is NULL \n", symname, version, symname, vername?"@":"", vername?vername:""); - } - //next_tb is the onebridge of the function - Push64(cpu, offs); - } + return; } diff --git a/target/i386/latx/context/kzt_guest_dl_api.c b/target/i386/latx/context/kzt_guest_dl_api.c new file mode 100644 index 00000000000..6a5243624ea --- /dev/null +++ b/target/i386/latx/context/kzt_guest_dl_api.c @@ -0,0 +1,866 @@ +#include "kzt_guest_dl_api.h" + +#include + +#include "box64context.h" +#include "debug.h" +#include "elfloader.h" +#include "kzt_guest_library_adapter.h" +#include "kzt_guest_library_binding.h" +#include "kzt_guest_registry.h" +#include "kzt_lifecycle_diagnostics.h" +#ifdef CONFIG_LATX_KZT +#include "kzt_jump_slot_production.h" +#endif +#include "librarian.h" +#include "library.h" +#include "library_private.h" + +void kzt_guest_dl_api_bind_current_thread(kzt_guest_dlerror_state_t *state) +{ + if (!state) { + kzt_guest_dlerror_fast_result_tls = 1; + return; + } + state->dlerror_fast_result_mirror = + &kzt_guest_dlerror_fast_result_tls; + kzt_guest_dlerror_fast_result_tls = state->dlerror_fast_result; +} + +#define KZT_GUEST_RTLD_NEXT ((void *)~0ULL) +#define KZT_GUEST_RTLD_NODELETE 0x1000 +#define KZT_GUEST_RTLD_DI_LMID 1 +#define KZT_GUEST_RTLD_DI_LINKMAP 2 + +static int kzt_guest_dl_entries_complete( + const kzt_guest_dl_entries_t *entries) +{ + return entries && entries->dlopen && entries->dlmopen && + entries->dlsym && entries->dlclose && entries->dladdr && + entries->dladdr1 && entries->dlinfo && entries->dlvsym && + entries->dlerror; +} + +int kzt_guest_dl_api_entry_state_init(dlprivate_t *dl) +{ + kzt_guest_dl_entry_state_t *state; + + if (!dl) { + return -1; + } + state = &dl->guest_dl_entries; + if (state->initialized) { + return 0; + } + memset(state, 0, sizeof(*state)); + if (pthread_mutex_init(&state->mutex, NULL) != 0) { + return -1; + } + if (pthread_cond_init(&state->ready, NULL) != 0) { + pthread_mutex_destroy(&state->mutex); + return -1; + } + state->initialized = 1; + return 0; +} + +static void kzt_guest_dl_entry_slow_leave( + kzt_guest_dl_entry_state_t *state) +{ + if (state->slow_users) { + --state->slow_users; + } + pthread_cond_broadcast(&state->ready); +} + +const kzt_guest_dl_entries_t *kzt_guest_dl_api_ensure_entries_prepared( + dlprivate_t *dl, kzt_guest_dl_entries_resolver_fn resolver, + kzt_guest_dl_entries_prepare_fn prepare, void *opaque, + kzt_guest_dl_entries_t *fallback, int *published_now) +{ + kzt_guest_dl_entry_state_t *state; + const kzt_guest_dl_entries_t *published; + kzt_guest_dl_entries_t local = { 0 }; + kzt_guest_dl_entries_t *candidate = NULL; + uintptr_t observed_dlerror; + int resolved; + + if (published_now) { + *published_now = 0; + } + if (!dl || !resolver || !fallback || + !(state = &dl->guest_dl_entries)->initialized) { + return NULL; + } + published = kzt_guest_dl_api_load_entries(dl); + if (published) { + return published; + } + memset(fallback, 0, sizeof(*fallback)); + + pthread_mutex_lock(&state->mutex); + if (state->teardown) { + pthread_mutex_unlock(&state->mutex); + return NULL; + } + ++state->slow_users; + for (;;) { + published = kzt_guest_dl_api_load_entries(dl); + if (published || state->teardown) { + kzt_guest_dl_entry_slow_leave(state); + pthread_mutex_unlock(&state->mutex); + return published; + } + if (!state->initializing) { + state->initializing = 1; + state->initializer = pthread_self(); + state->initializer_valid = 1; + break; + } + if (state->initializer_valid && + pthread_equal(state->initializer, pthread_self())) { + kzt_guest_dl_entry_slow_leave(state); + pthread_mutex_unlock(&state->mutex); + return NULL; + } + pthread_cond_wait(&state->ready, &state->mutex); + } + pthread_mutex_unlock(&state->mutex); + + resolved = resolver(&local, opaque); + *fallback = local; + if (resolved == 0 && kzt_guest_dl_entries_complete(&local)) { + candidate = box_malloc(sizeof(*candidate)); + if (candidate) { + *candidate = local; + } + } + + pthread_mutex_lock(&state->mutex); + observed_dlerror = __atomic_load_n( + &state->observed_dlerror, __ATOMIC_RELAXED); + if (!state->teardown && candidate && + (!observed_dlerror || observed_dlerror == candidate->dlerror)) { + if (!observed_dlerror) { + __atomic_store_n( + &state->observed_dlerror, candidate->dlerror, + __ATOMIC_RELEASE); + } + if (prepare) { + pthread_mutex_unlock(&state->mutex); + prepare(candidate, opaque); + pthread_mutex_lock(&state->mutex); + } + observed_dlerror = __atomic_load_n( + &state->observed_dlerror, __ATOMIC_RELAXED); + if (!state->teardown && + observed_dlerror == candidate->dlerror) { + __atomic_store_n( + &state->published, candidate, __ATOMIC_RELEASE); + if (published_now) { + *published_now = 1; + } + candidate = NULL; + } + } + state->initializing = 0; + state->initializer_valid = 0; + published = kzt_guest_dl_api_load_entries(dl); + kzt_guest_dl_entry_slow_leave(state); + pthread_mutex_unlock(&state->mutex); + box_free(candidate); + return published ? published : fallback; +} + +const kzt_guest_dl_entries_t *kzt_guest_dl_api_ensure_entries( + dlprivate_t *dl, kzt_guest_dl_entries_resolver_fn resolver, void *opaque, + kzt_guest_dl_entries_t *fallback, int *published_now) +{ + return kzt_guest_dl_api_ensure_entries_prepared( + dl, resolver, NULL, opaque, fallback, published_now); +} + +void kzt_guest_dl_api_entry_state_destroy(dlprivate_t *dl) +{ + kzt_guest_dl_entry_state_t *state; + kzt_guest_dl_entries_t *published; + + if (!dl || !(state = &dl->guest_dl_entries)->initialized) { + return; + } + pthread_mutex_lock(&state->mutex); + state->teardown = 1; + pthread_cond_broadcast(&state->ready); + while (state->initializing || state->slow_users) { + pthread_cond_wait(&state->ready, &state->mutex); + } + published = __atomic_exchange_n( + &state->published, NULL, __ATOMIC_ACQ_REL); + pthread_mutex_unlock(&state->mutex); + box_free(published); + pthread_cond_destroy(&state->ready); + pthread_mutex_destroy(&state->mutex); + memset(state, 0, sizeof(*state)); +} + +#ifdef CONFIG_LATX_KZT +extern int option_kzt; +extern int wine_option_kzt; + +static int kzt_guest_dl_api_enabled(void) +{ + return option_kzt || wine_option_kzt; +} + +static void kzt_guest_dl_api_discard_internal_error( + const kzt_guest_dl_entries_t *entries) +{ + if (entries && entries->dlerror) { + (void)kzt_guest_library_run_dlerror(entries->dlerror); + } +} + +static int kzt_guest_dl_api_query_identity( + const kzt_guest_dl_entries_t *entries, uintptr_t handle, + kzt_guest_loader_identity_t *identity) +{ + uintptr_t link_map_addr = 0; + uintptr_t namespace_id = 0; + + if (identity) { + memset(identity, 0, sizeof(*identity)); + } + if (!entries || !handle || !identity || !entries->dlinfo || + !kzt_guest_dl_api_enabled()) { + return -1; + } + if (kzt_guest_library_run_dlinfo( + entries->dlinfo, (void *)handle, + KZT_GUEST_RTLD_DI_LINKMAP, &link_map_addr) != 0) { + kzt_guest_dl_api_discard_internal_error(entries); + return -1; + } + if (!link_map_addr) { + return -1; + } + if (kzt_guest_library_run_dlinfo( + entries->dlinfo, (void *)handle, + KZT_GUEST_RTLD_DI_LMID, &namespace_id) != 0) { + kzt_guest_dl_api_discard_internal_error(entries); + return -1; + } + + identity->handle = handle; + identity->link_map_addr = link_map_addr; + identity->namespace_id = namespace_id; + return 0; +} +#endif + +#ifdef CONFIG_LATX_KZT +static int kzt_guest_dl_api_prepare_prebind_target( + uintptr_t target, void *opaque) +{ + (void)opaque; + return KztPrebindTargetTbPrepare(target); +} +#endif + +static void kzt_guest_dl_api_finish_dlopen_scoped( + box64context_t *context, + kzt_guest_library_loader_scope_t *call_scope, + uintptr_t link_map_addr, library_t *library, + const kzt_guest_wrapper_source_proof_t *proof, int publish) +{ +#ifdef CONFIG_LATX_KZT + if (call_scope && call_scope->prebind_refresh_pending) { + uint64_t refresh_start = kzt_lifecycle_diagnostics_enabled() + ? kzt_lifecycle_diagnostics_now() + : 0; + + kzt_production_lazy_prebind_refresh( + context, kzt_guest_dl_api_prepare_prebind_target, NULL); + if (refresh_start) { + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_SCOPED_PREBIND_REFRESH, + kzt_lifecycle_diagnostics_now() - refresh_start); + } + call_scope->prebind_refresh_pending = 0; + } +#endif + kzt_guest_library_finish_dlopen_scoped( + context, call_scope, link_map_addr, library, proof, publish); +} + +int kzt_guest_dl_api_publish_dlerror_entry( + dlprivate_t *dl, const char *symbol, uintptr_t guest_entry, + int custom_wrapper) +{ + kzt_guest_dl_entry_state_t *state; + const kzt_guest_dl_entries_t *entries; + uintptr_t expected = 0; + int result; + + if (!dl || !symbol || strcmp(symbol, "dlerror") != 0 || + !guest_entry || !custom_wrapper) { + return -1; + } + state = &dl->guest_dl_entries; + if (state->initialized) { + pthread_mutex_lock(&state->mutex); + if (state->teardown) { + pthread_mutex_unlock(&state->mutex); + return -1; + } + } + entries = kzt_guest_dl_api_load_entries(dl); + if (entries && entries->dlerror != guest_entry) { + result = -1; + } else if (__atomic_compare_exchange_n( + &dl->guest_dl_entries.observed_dlerror, &expected, guest_entry, 0, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) { + result = 0; + } else { + result = expected == guest_entry ? 0 : -1; + } + if (state->initialized) { + pthread_mutex_unlock(&state->mutex); + } + return result; +} + +uintptr_t kzt_guest_dl_api_load_dlerror_entry(dlprivate_t *dl) +{ + const kzt_guest_dl_entries_t *entries; + uintptr_t observed; + + if (!dl) { + return 0; + } + observed = __atomic_load_n( + &dl->guest_dl_entries.observed_dlerror, __ATOMIC_RELAXED); + if (observed) { + return observed; + } + entries = kzt_guest_dl_api_load_entries(dl); + return entries ? entries->dlerror : 0; +} + +static void kzt_guest_dl_api_capture_guest_error( + const kzt_guest_dl_entries_t *entries, + kzt_guest_dlerror_state_t *state) +{ + const char *guest_error; + const char fallback[] = "guest dlopen failed"; + char *captured; + size_t length; + uintptr_t guest_dlerror = entries ? entries->dlerror : 0; + + guest_error = NULL; + if (guest_dlerror) { + guest_error = (const char *)(uintptr_t)kzt_guest_library_run_dlerror( + guest_dlerror); + } + if (!guest_error) { + guest_error = fallback; + } + length = strlen(guest_error) + 1; + captured = box_malloc(length); + if (!captured) { + return; + } + memcpy(captured, guest_error, length); + box_free(state->last_error); + state->last_error = captured; + state->last_error_guest_consumed = guest_dlerror != 0; +} + +void kzt_guest_dl_api_clear_error(kzt_guest_dlerror_state_t *state) +{ + if (!state) { + return; + } + box_free(state->last_error); + state->last_error = NULL; + box_free(state->last_error_returned); + state->last_error_returned = NULL; + state->last_error_guest_consumed = 0; + kzt_guest_dl_api_set_slow_required(state, 1); +} + +void kzt_guest_dl_api_free_errors(kzt_guest_dlerror_state_t *state) +{ + if (!state) { + return; + } + box_free(state->last_error); + box_free(state->last_error_returned); + memset(state, 0, sizeof(*state)); +} + +uint64_t kzt_guest_dl_api_dlopen( + box64context_t *context, + kzt_guest_library_loader_scope_t *thread_scope, + const kzt_guest_dl_entries_t *entries, + kzt_guest_dlerror_state_t *error_state, + const void *filename, int flag) +{ + kzt_guest_library_loader_scope_t call_scope = { 0 }; + kzt_guest_wrapper_source_proof_t source_proof = { 0 }; + const char *path = filename; + const char *name; + library_t *library = NULL; + uint64_t guest_handle; + uint64_t timing_start = 0; + uintptr_t exact_link_map = 0; + int have_exact_identity = 0; + int is_local; + int bind_now; + int source_proven = 0; +#ifdef CONFIG_LATX_KZT + kzt_guest_library_binding_result_t binding_claim = + KZT_GUEST_LIBRARY_BINDING_ERROR; +#endif + +#ifdef CONFIG_LATX_KZT + (void)kzt_production_lazy_prebind_invalidate( + context, KZT_LAZY_PREBIND_MUTATION_DLOPEN); +#endif + if (kzt_lifecycle_diagnostics_enabled()) { + timing_start = kzt_lifecycle_diagnostics_now(); + } + guest_handle = kzt_guest_library_run_dlopen_scoped( + context, thread_scope, entries->dlopen, + (void *)filename, flag, &call_scope); + if (timing_start) { + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_GUEST_DLOPEN, + kzt_lifecycle_diagnostics_now() - timing_start); + timing_start = kzt_lifecycle_diagnostics_now(); + } + if (!guest_handle) { + kzt_guest_dl_api_finish_dlopen_scoped( + context, &call_scope, 0, NULL, NULL, 0); + kzt_guest_dl_api_capture_guest_error(entries, error_state); + if (timing_start) { + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_DLOPEN_FINISH, + kzt_lifecycle_diagnostics_now() - timing_start); + } + return guest_handle; + } + +#ifdef CONFIG_LATX_KZT + if (!kzt_guest_dl_api_enabled()) { + exact_link_map = guest_handle; + have_exact_identity = 1; + } else { + kzt_guest_loader_identity_t identity; + kzt_guest_registry_t *registry = + KztGuestRegistryForContext(context); + + if (registry && + (kzt_guest_registry_reuse_loader_identity( + registry, guest_handle, &identity) == 0 || + (kzt_guest_dl_api_query_identity( + entries, guest_handle, &identity) == 0 && + kzt_guest_registry_publish_loader_identity( + registry, identity.handle, identity.link_map_addr, + identity.namespace_id, &identity) == 0))) { + exact_link_map = identity.link_map_addr; + have_exact_identity = 1; + if (flag & KZT_GUEST_RTLD_NODELETE) { + (void)kzt_guest_registry_mark_loader_resident( + registry, &identity); + } + } + } +#else + exact_link_map = guest_handle; + have_exact_identity = 1; +#endif + + if (!path) { + kzt_guest_dl_api_finish_dlopen_scoped( + context, &call_scope, exact_link_map, NULL, + NULL, have_exact_identity); + if (timing_start) { + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_DLOPEN_FINISH, + kzt_lifecycle_diagnostics_now() - timing_start); + } + return guest_handle; + } + name = strrchr(path, '/'); + name = name ? name + 1 : path; + if (!FindLibIsWrapped((char *)name)) { + kzt_guest_dl_api_finish_dlopen_scoped( + context, &call_scope, exact_link_map, NULL, + NULL, have_exact_identity); + if (timing_start) { + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_DLOPEN_FINISH, + kzt_lifecycle_diagnostics_now() - timing_start); + } + return guest_handle; + } + + context->deferedInit = 1; + is_local = (flag & 0x100) ? 0 : 1; + bind_now = (flag & 0x2) ? 1 : 0; + source_proven = have_exact_identity && + (!kzt_guest_dl_api_enabled() || + kzt_guest_library_wrapper_source_acquire( + context, exact_link_map, path, name, &source_proof) == 0); + if (have_exact_identity && source_proven && AddNeededLibWithLibrary( + NULL, NULL, NULL, is_local, bind_now, name, context, + &library) == 0 && + library) { + if (library->type != LIB_WRAPPED) { + library = NULL; + } +#ifdef CONFIG_LATX_KZT + if (library && kzt_guest_dl_api_enabled()) { + binding_claim = kzt_guest_library_note_loader_pair( + context, exact_link_map, library, &source_proof); + if (binding_claim != KZT_GUEST_LIBRARY_BINDING_ADDED && + binding_claim != KZT_GUEST_LIBRARY_BINDING_UNCHANGED) { + library = NULL; + } + } +#endif + } + if (library) { + library->x86linkmap = + (struct link_map *)(uintptr_t)exact_link_map; + } + kzt_guest_dl_api_finish_dlopen_scoped( + context, &call_scope, exact_link_map, library, + library ? &source_proof : NULL, have_exact_identity); + kzt_guest_library_wrapper_source_release(&source_proof); + if (timing_start) { + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_DLOPEN_FINISH, + kzt_lifecycle_diagnostics_now() - timing_start); + } + return guest_handle; +} + +int kzt_guest_dl_api_dlclose( + box64context_t *context, + kzt_guest_library_loader_scope_t *thread_scope, + const kzt_guest_dl_entries_t *entries, void *handle) +{ +#ifdef CONFIG_LATX_KZT + kzt_guest_registry_t *registry = NULL; + kzt_guest_loader_identity_t identity = { 0 }; + kzt_guest_library_loader_quiescence_writer_t writer = { 0 }; + int have_exact_identity = 0; + int kzt_enabled = 0; +#endif + int guest_result; + uint64_t timing_start = 0; + +#ifdef CONFIG_LATX_KZT + if (context) { + kzt_enabled = kzt_guest_dl_api_enabled(); + if (kzt_enabled) { + (void)kzt_guest_library_loader_quiescence_writer_begin( + KztGuestLibraryBindingsForContext(context), &writer); + } + (void)kzt_production_lazy_prebind_invalidate( + context, KZT_LAZY_PREBIND_MUTATION_DLCLOSE); + if (kzt_enabled) { + registry = KztGuestRegistryForContext(context); + have_exact_identity = registry && + kzt_guest_registry_find_loader_identity( + registry, (uintptr_t)handle, &identity) == 0; + } + } +#else + (void)context; +#endif + (void)thread_scope; + if (kzt_lifecycle_diagnostics_enabled()) { + timing_start = kzt_lifecycle_diagnostics_now(); + } + guest_result = kzt_guest_library_run_dlclose( + entries->dlclose, handle); + if (timing_start) { + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_GUEST_DLCLOSE, + kzt_lifecycle_diagnostics_now() - timing_start); + } + if (guest_result != 0) { + goto out; + } + +#ifdef CONFIG_LATX_KZT + if (have_exact_identity) { + (void)kzt_guest_registry_complete_loader_close( + registry, &identity); + } else if (registry) { + kzt_guest_registry_note_loader_close_identity_missing(registry); + } +#endif + +out: +#ifdef CONFIG_LATX_KZT + kzt_guest_library_loader_quiescence_writer_end(&writer); +#endif + return guest_result; +} + +#ifdef CONFIG_LATX_KZT +static void kzt_guest_dl_api_cleanup_exact_library( + library_t *library, void *opaque) +{ + uintptr_t link_map_addr = (uintptr_t)opaque; + + if (!library) { + return; + } + library->active = 0; + if ((uintptr_t)library->x86linkmap == link_map_addr) { + library->x86linkmap = NULL; + } +} +#endif + +int kzt_guest_dl_api_publish_unload( + box64context_t *context, + const kzt_guest_loader_identity_t *identity) +{ +#ifdef CONFIG_LATX_KZT + kzt_guest_registry_t *registry; + kzt_guest_loader_identity_t current = { 0 }; + kzt_guest_lazy_resolver_t lazy_resolver = { 0 }; + kzt_guest_library_binding_key_t key; + kzt_guest_library_handle_t binding = { 0 }; + uint64_t retire_start; + int retire_result; + + if (!context || !identity || !identity->link_map_addr || + !identity->generation || !kzt_guest_dl_api_enabled()) { + return -1; + } + registry = KztGuestRegistryForContext(context); + if (!registry || + kzt_guest_registry_find_loader_object_identity( + registry, identity->link_map_addr, ¤t) != 0 || + current.generation != identity->generation || + current.namespace_id != identity->namespace_id) { + return -1; + } + (void)kzt_guest_registry_find_lazy_resolver( + registry, identity->link_map_addr, identity->generation, + identity->namespace_id, &lazy_resolver); + retire_start = kzt_lifecycle_diagnostics_enabled() + ? kzt_lifecycle_diagnostics_now() + : 0; + retire_result = kzt_guest_registry_finish_loader_unload( + registry, identity); + if (retire_start) { + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_REGISTRY_RETIRE, + kzt_lifecycle_diagnostics_now() - retire_start); + } + if (retire_result != 0) { + return -1; + } + + key = (kzt_guest_library_binding_key_t) { + .link_map_addr = identity->link_map_addr, + .generation = identity->generation, + .namespace_id = identity->namespace_id, + .namespace_kind = identity->namespace_id == 0 + ? KZT_GUEST_LIBRARY_NAMESPACE_MAIN + : KZT_GUEST_LIBRARY_NAMESPACE_EXPLICIT, + }; + if (KztGuestLibraryLookupForContext( + context, &key, &binding) == 0 && + binding.object_type == KZT_GUEST_LIBRARY_OBJECT_WRAPPED && + binding.library) { + if (kzt_guest_library_cleanup_exact_handle( + &binding, kzt_guest_dl_api_cleanup_exact_library, + (void *)identity->link_map_addr) != 0) { + kzt_guest_library_handle_release(&binding); + } + } else { + kzt_guest_library_handle_release(&binding); + } + if (lazy_resolver.registry_owned_head) { + KztPerObjectGotPltRelease(lazy_resolver.object_head); + } + return 0; +#else + (void)context; + (void)identity; + return -1; +#endif +} + +int kzt_guest_dl_api_prepare_unload( + box64context_t *context, + const kzt_guest_loader_identity_t *identity) +{ +#ifdef CONFIG_LATX_KZT + kzt_guest_registry_t *registry; + kzt_lazy_prebind_identity_t prebind_identity; + + if (!context || !identity || !identity->link_map_addr || + !identity->generation || !kzt_guest_dl_api_enabled() || + !(registry = KztGuestRegistryForContext(context)) || + kzt_guest_registry_begin_loader_unload(registry, identity) != 0) { + return -1; + } + if (identity->namespace_id == 0) { + prebind_identity = (kzt_lazy_prebind_identity_t) { + .link_map_addr = identity->link_map_addr, + .generation = identity->generation, + .namespace_id = identity->namespace_id, + }; + if (kzt_production_lazy_prebind_retire( + context, &prebind_identity) != 0) { + /* Registry quiescence is the safety boundary. Auxiliary + * prebind cleanup failure must not reopen lease admission before + * the loader reaches RT_CONSISTENT. */ + } + } + return 0; +#else + (void)context; + (void)identity; + return -1; +#endif +} + +int kzt_guest_dl_api_cancel_unload( + box64context_t *context, + const kzt_guest_loader_identity_t *identity) +{ +#ifdef CONFIG_LATX_KZT + kzt_guest_registry_t *registry; + + if (!context || !identity || !kzt_guest_dl_api_enabled() || + !(registry = KztGuestRegistryForContext(context))) { + return -1; + } + return kzt_guest_registry_cancel_loader_unload(registry, identity); +#else + (void)context; + (void)identity; + return -1; +#endif +} + +uint64_t kzt_guest_dl_api_dlmopen( + box64context_t *context, const kzt_guest_dl_entries_t *entries, + void *lmid, void *filename, int flag) +{ +#ifdef CONFIG_LATX_KZT + if (context) { + (void)kzt_production_lazy_prebind_invalidate( + context, KZT_LAZY_PREBIND_MUTATION_DLMOPEN); + } +#endif + uint64_t result = kzt_guest_library_run_dlmopen( + entries->dlmopen, lmid, filename, flag); + +#ifdef CONFIG_LATX_KZT + if (result && kzt_guest_dl_api_enabled()) { + kzt_guest_loader_identity_t identity; + kzt_guest_registry_t *registry = + KztGuestRegistryForContext(context); + + if (kzt_guest_dl_api_query_identity(entries, result, &identity) == 0 && + registry) { + (void)kzt_guest_registry_publish_loader_identity( + registry, identity.handle, identity.link_map_addr, + identity.namespace_id, &identity); + } + } +#else + (void)context; +#endif + return result; +} + +kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlsym( + box64context_t *context, const kzt_guest_dl_entries_t *entries, + void *handle, void *symbol) +{ + kzt_guest_dl_symbol_result_t result = { 0 }; + uintptr_t guest_result; + + if (handle == KZT_GUEST_RTLD_NEXT) { + result.forward_to_guest_caller = 1; + return result; + } + guest_result = kzt_guest_library_run_dlsym( + entries->dlsym, handle, symbol); + result.value = kzt_guest_library_select_symbol_result( + context, (uintptr_t)handle, guest_result, + (const char *)symbol, NULL); + return result; +} + +kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlvsym( + box64context_t *context, const kzt_guest_dl_entries_t *entries, + void *handle, void *symbol, const char *version) +{ + kzt_guest_dl_symbol_result_t result = { 0 }; + uintptr_t guest_result; + + if (handle == KZT_GUEST_RTLD_NEXT) { + result.forward_to_guest_caller = 1; + return result; + } + guest_result = kzt_guest_library_run_dlvsym( + entries->dlvsym, handle, symbol, version); + result.value = kzt_guest_library_select_symbol_result( + context, (uintptr_t)handle, guest_result, + (const char *)symbol, version); + return result; +} + +kzt_guest_dlerror_result_t kzt_guest_dl_api_dlerror( + kzt_guest_dlerror_state_t *state, uintptr_t guest_dlerror) +{ + kzt_guest_dlerror_result_t result = { 0 }; + + if (state && state->last_error) { + if (!state->last_error_guest_consumed && guest_dlerror) { + (void)kzt_guest_library_run_dlerror( + guest_dlerror); + state->last_error_guest_consumed = 1; + } + box_free(state->last_error_returned); + state->last_error_returned = state->last_error; + state->last_error = NULL; + result.value = state->last_error_returned; + return result; + } + if (state && state->last_error_returned) { + box_free(state->last_error_returned); + state->last_error_returned = NULL; + if (state->last_error_guest_consumed) { + state->last_error_guest_consumed = 0; + kzt_guest_dl_api_set_slow_required(state, 0); + return result; + } + } + if (state && !state->dlerror_slow_required) { + return result; + } + result.forward_to_guest_caller = 1; + return result; +} + +int kzt_guest_dl_api_dlinfo( + const kzt_guest_dl_entries_t *entries, + void *handle, int request, void *info) +{ + return kzt_guest_library_run_dlinfo( + entries->dlinfo, handle, request, info); +} diff --git a/target/i386/latx/context/kzt_guest_dl_init.c b/target/i386/latx/context/kzt_guest_dl_init.c new file mode 100644 index 00000000000..346aff7d173 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_dl_init.c @@ -0,0 +1,82 @@ +#include "qemu/osdep.h" + +#include "kzt_guest_dl_init.h" + +#include "elfloader.h" +#include "kzt_guest_dl_api.h" +#include "pathcoll.h" + +extern const char *interp_prefix; +elfheader_t *tryLoadElfFromFile(const char *name); +void freeElfFromFile(elfheader_t **header); +void kzt_wine_init_x86(box64context_t *context, uintptr_t guest_dlsym); + +static int kzt_guest_dl_resolve_entries( + kzt_guest_dl_entries_t *entries, void *opaque) +{ + static const char *symbols[] = { + "dlopen", "dlmopen", "dlsym", "dlclose", "dladdr", "dladdr1", + "dlinfo", "dlvsym", "dlerror", + }; + box64context_t *context = opaque; + elfheader_t *header; + void *resolved[9] = { 0 }; + int resolved_count = 0; + +#ifdef CONFIG_LOONGARCH_NEW_WORLD + char path[PATH_MAX] = { 0 }; + + snprintf(path, sizeof(path), "%s%s", interp_prefix, + "/usr/lib/glibc-hwcaps/x86-64-v2/"); + if (!FindInCollection(path, &context->box64_ld_lib)) { + PrependList(&context->box64_ld_lib, path, 1); + } +#else + (void)context; +#endif + header = tryLoadElfFromFile("libc.so.6"); + if (header) { + ResetSpecialCaseElf( + header, symbols, ARRAY_SIZE(symbols), resolved, + &resolved_count); + freeElfFromFile(&header); + } + if (resolved_count != ARRAY_SIZE(symbols)) { + header = tryLoadElfFromFile("libdl.so.2"); + if (header) { + ResetSpecialCaseElf( + header, symbols, ARRAY_SIZE(symbols), resolved, + &resolved_count); + freeElfFromFile(&header); + } + } + *entries = (kzt_guest_dl_entries_t) { + .dlopen = (uintptr_t)resolved[0], + .dlmopen = (uintptr_t)resolved[1], + .dlsym = (uintptr_t)resolved[2], + .dlclose = (uintptr_t)resolved[3], + .dladdr = (uintptr_t)resolved[4], + .dladdr1 = (uintptr_t)resolved[5], + .dlinfo = (uintptr_t)resolved[6], + .dlvsym = (uintptr_t)resolved[7], + .dlerror = (uintptr_t)resolved[8], + }; + return resolved_count == ARRAY_SIZE(symbols) ? 0 : -1; +} + +static void kzt_guest_dl_prepare_entries( + const kzt_guest_dl_entries_t *entries, void *opaque) +{ + kzt_wine_init_x86(opaque, entries->dlsym); +} + +const kzt_guest_dl_entries_t *kzt_guest_dl_init_entries( + box64context_t *context, kzt_guest_dl_entries_t *fallback) +{ + if (!context || !context->dlprivate || !fallback) { + return NULL; + } + return kzt_guest_dl_api_ensure_entries_prepared( + context->dlprivate, kzt_guest_dl_resolve_entries, + kzt_guest_dl_prepare_entries, context, fallback, NULL); +} diff --git a/target/i386/latx/context/kzt_guest_library_adapter.c b/target/i386/latx/context/kzt_guest_library_adapter.c index 328e6f420fa..c0c8a1bf745 100644 --- a/target/i386/latx/context/kzt_guest_library_adapter.c +++ b/target/i386/latx/context/kzt_guest_library_adapter.c @@ -1,9 +1,451 @@ #include "kzt_guest_library_adapter.h" +#include + #include "box64context.h" +#include "callback.h" +#include "debug.h" +#include "kzt_guest_dynsym_lookup.h" #include "kzt_guest_library_binding.h" +#include "kzt_guest_registry.h" #include "library.h" #include "library_private.h" +#ifndef KZT_GUEST_LIBRARY_ADAPTER_TEST +#include "qemu.h" +#endif + +#define KZT_GUEST_WRAPPER_DIAGNOSTIC_LIMIT 16 + +#ifdef CONFIG_LATX_KZT +#ifdef KZT_GUEST_LIBRARY_ADAPTER_TEST +extern int option_kzt; +#endif +extern int wine_option_kzt; +#endif + +static unsigned long kzt_guest_wrapper_diagnostic_count; + +static void kzt_guest_library_note_wrapper_diagnostic( + const char *phase, const char *reason, uintptr_t link_map_addr, + unsigned long generation, const char *symbol, unsigned char symbol_type) +{ + unsigned long ticket; + + if (!kzt_registry_diagnostics_enabled()) { + return; + } + ticket = __atomic_fetch_add( + &kzt_guest_wrapper_diagnostic_count, 1, __ATOMIC_RELAXED); + if (ticket >= KZT_GUEST_WRAPPER_DIAGNOSTIC_LIMIT) { + return; + } + printf_kzt_registry_diagnostics( + "kzt_wrapper_gate schema=1 phase=%s reason=%s link_map=0x%lx " + "generation=%lu symbol=%s symbol_type=%u\n", + phase ? phase : "unknown", reason ? reason : "unknown", + (unsigned long)link_map_addr, generation, + symbol && symbol[0] ? symbol : "", (unsigned int)symbol_type); +} + +static int kzt_guest_library_read_memory(uintptr_t guest_addr, void *dst, + size_t size, void *opaque) +{ + (void)opaque; +#ifdef KZT_GUEST_LIBRARY_ADAPTER_TEST + (void)guest_addr; + (void)dst; + (void)size; + return -1; +#else + void *host_ptr; + + if ((!guest_addr || !dst) && size) { + return -1; + } + if (!size) { + return 0; + } + host_ptr = lock_user(VERIFY_READ, (abi_ulong)guest_addr, size, true); + if (!host_ptr) { + return -1; + } + memcpy(dst, host_ptr, size); + unlock_user(host_ptr, (abi_ulong)guest_addr, 0); + return 0; +#endif +} + +static const char *kzt_guest_library_basename(const char *path) +{ + const char *name; + + if (!path || !path[0]) { + return NULL; + } + name = strrchr(path, '/'); + return name ? name + 1 : path; +} + +static int kzt_guest_library_path_is_canonical(const char *path) +{ + const char *component; + + if (!path || path[0] != '/' || !path[1]) { + return 0; + } + component = path + 1; + while (*component) { + const char *end = strchr(component, '/'); + size_t length = end ? (size_t)(end - component) : strlen(component); + + if (!length || (length == 1 && component[0] == '.') || + (length == 2 && component[0] == '.' && component[1] == '.')) { + return 0; + } + if (!end) { + return 1; + } + component = end + 1; + } + return 0; +} + +static int kzt_guest_library_path_matches_manifest(const char *path, + const char *wrapper_name) +{ + static const char *const directories[] = { + "/lib", + "/lib64", + "/lib/x86_64-linux-gnu", + "/usr/lib", + "/usr/lib64", + "/usr/lib/x86_64-linux-gnu", + "/usr/x86_64-linux-gnu/lib", + }; + const char *name; + size_t directory_length; + size_t i; + + if (!kzt_guest_library_path_is_canonical(path) || + !wrapper_name || !wrapper_name[0] || + !(name = strrchr(path, '/')) || !name[1] || + strcmp(name + 1, wrapper_name) != 0) { + return 0; + } + directory_length = (size_t)(name - path); + for (i = 0; i < sizeof(directories) / sizeof(directories[0]); ++i) { + if (strlen(directories[i]) == directory_length && + strncmp(path, directories[i], directory_length) == 0) { + return 1; + } + } + return 0; +} + +static int kzt_guest_library_source_match_valid( + const kzt_guest_registry_address_match_t *match, + const char *requested_path, const char *wrapper_name) +{ + const char *observed_name; + + if (!match || match->match_count != 1 || + match->namespace_id_status != KZT_GUEST_FIELD_OK || + match->namespace_id != 0 || !match->generation || + match->path_status != KZT_GUEST_FIELD_OK || + !requested_path || !requested_path[0] || + !kzt_guest_library_path_matches_manifest( + match->path, wrapper_name)) { + return 0; + } + observed_name = kzt_guest_library_basename(match->path); + if (!observed_name || strcmp(observed_name, wrapper_name) != 0 || + (strchr(requested_path, '/') && + strcmp(requested_path, match->path) != 0) || + (!strchr(requested_path, '/') && + strcmp(requested_path, wrapper_name) != 0) || + (match->soname_status == KZT_GUEST_FIELD_OK && match->soname[0] && + strcmp(match->soname, wrapper_name) != 0)) { + return 0; + } + return 1; +} + +int kzt_guest_library_wrapper_source_acquire( + box64context_t *context, uintptr_t link_map_addr, + const char *requested_path, const char *wrapper_name, + kzt_guest_wrapper_source_proof_t *proof) +{ +#ifdef CONFIG_LATX_KZT + kzt_guest_registry_t *registry; + kzt_guest_registry_address_match_t before = { 0 }; + kzt_guest_registry_address_match_t after = { 0 }; + + if (!proof) { + return -1; + } + memset(proof, 0, sizeof(*proof)); + if (!context || !link_map_addr || + !(registry = KztGuestRegistryForContext(context)) || + kzt_guest_registry_find_live_object( + registry, link_map_addr, &before) != 0 || + !kzt_guest_library_source_match_valid( + &before, requested_path, wrapper_name) || + kzt_guest_registry_source_lease_acquire( + registry, before.link_map_addr, before.generation, + before.namespace_id, &proof->lease) != 0 || + kzt_guest_registry_find_live_object( + registry, link_map_addr, &after) != 0 || + after.link_map_addr != before.link_map_addr || + after.generation != before.generation || + after.namespace_id != before.namespace_id || + after.namespace_id_status != before.namespace_id_status || + after.path_status != before.path_status || + strcmp(after.path, before.path) != 0 || + after.soname_status != before.soname_status || + strcmp(after.soname, before.soname) != 0) { + kzt_guest_registry_source_lease_release(&proof->lease); + kzt_guest_library_note_wrapper_diagnostic( + "source", "identity_or_path_unproven", link_map_addr, + before.generation, wrapper_name, 0); + return -1; + } + proof->key = (kzt_guest_library_binding_key_t) { + .link_map_addr = before.link_map_addr, + .generation = before.generation, + .namespace_id = before.namespace_id, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + return 0; +#else + (void)context; + (void)link_map_addr; + (void)requested_path; + (void)wrapper_name; + if (proof) { + memset(proof, 0, sizeof(*proof)); + } + return -1; +#endif +} + +void kzt_guest_library_wrapper_source_release( + kzt_guest_wrapper_source_proof_t *proof) +{ + if (!proof) { + return; + } + kzt_guest_registry_source_lease_release(&proof->lease); + memset(proof, 0, sizeof(*proof)); +} + +uint64_t kzt_guest_library_run_dlopen_scoped( + box64context_t *context, + kzt_guest_library_loader_scope_t *thread_scope, + uintptr_t function, void *filename, int flag, + kzt_guest_library_loader_scope_t *call_scope) +{ +#ifdef CONFIG_LATX_KZT + kzt_guest_library_loader_scope_t previous = { 0 }; + kzt_guest_library_bindings_t *bindings = + KztGuestLibraryBindingsForContext(context); + int scoped = 0; + uint64_t result; + + if (call_scope) { + *call_scope = (kzt_guest_library_loader_scope_t){ 0 }; + } + if (thread_scope && call_scope && bindings && + kzt_guest_library_loader_scope_begin(bindings, call_scope) == 0) { + previous = *thread_scope; + *thread_scope = *call_scope; + scoped = 1; + } + result = RunFunctionWithState(function, 2, filename, flag); + if (scoped) { + call_scope->prebind_refresh_pending = + thread_scope->prebind_refresh_pending; + if (previous.bindings && previous.identity && previous.cookie && + call_scope->prebind_refresh_pending) { + previous.prebind_refresh_pending = 1; + call_scope->prebind_refresh_pending = 0; + } + *thread_scope = previous; + } + return result; +#else + (void)context; + (void)thread_scope; + (void)call_scope; + return RunFunctionWithState(function, 2, filename, flag); +#endif +} + +void kzt_guest_library_finish_dlopen_scoped( + box64context_t *context, + kzt_guest_library_loader_scope_t *call_scope, + uintptr_t link_map_addr, library_t *library, + const kzt_guest_wrapper_source_proof_t *proof, int publish) +{ +#ifdef CONFIG_LATX_KZT + if (!call_scope || !call_scope->bindings || + !call_scope->identity || !call_scope->cookie) { + return; + } + if (publish && link_map_addr) { + if (library) { + kzt_guest_library_publish_loader_pair_scoped( + context, call_scope, link_map_addr, library, proof); + } else { + kzt_guest_library_publish_loader_observed_scoped( + context, call_scope, link_map_addr); + } + } + kzt_guest_library_loader_scope_end(call_scope); +#else + (void)context; + (void)call_scope; + (void)link_map_addr; + (void)library; + (void)proof; + (void)publish; +#endif +} + +uint64_t kzt_guest_library_run_dlsym( + uintptr_t function, void *handle, void *symbol) +{ + return RunFunctionWithState(function, 2, handle, symbol); +} + +uint64_t kzt_guest_library_run_dlvsym( + uintptr_t function, void *handle, void *symbol, const char *version) +{ + return RunFunctionWithState(function, 3, handle, symbol, version); +} + +uint64_t kzt_guest_library_run_dlerror(uintptr_t function) +{ + return RunFunctionWithState(function, 0); +} + +int kzt_guest_library_run_dlclose(uintptr_t function, void *handle) +{ + return (int)RunFunctionWithState(function, 1, handle); +} + +uint64_t kzt_guest_library_run_dlmopen( + uintptr_t function, void *lmid, void *filename, int flag) +{ + return RunFunctionWithState(function, 3, lmid, filename, flag); +} + +int kzt_guest_library_run_dlinfo( + uintptr_t function, void *handle, int request, void *info) +{ + return RunFunctionWithState(function, 3, handle, request, info); +} + +uintptr_t kzt_guest_library_select_symbol_result( + box64context_t *context, uintptr_t guest_handle, + uintptr_t guest_result, const char *symbol, const char *version) +{ +#ifdef CONFIG_LATX_KZT + kzt_guest_loader_identity_t lookup_identity = { 0 }; + kzt_guest_registry_t *registry; + kzt_guest_registry_source_lease_t source_lease = { 0 }; + kzt_guest_library_binding_key_t key; + kzt_guest_library_handle_t handle = { 0 }; + kzt_guest_dynamic_view_t dynamic_view; + kzt_guest_dynsym_lookup_result_t dynsym_result = { 0 }; + kzt_guest_field_status_t dynamic_status; + unsigned long dynamic_revision = 0; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = kzt_guest_library_read_memory, + }; + uintptr_t proven_runtime_address = 0; + unsigned char proven_symbol_type = 0; + uintptr_t start = 0; + uintptr_t end = 0; + uintptr_t selected = guest_result; + const char *diagnostic_reason = "source_generation_stale"; + + if ((!option_kzt && !wine_option_kzt) || + !context || !guest_result || !symbol || !symbol[0] || + (version && version[0]) || + !(registry = KztGuestRegistryForContext(context)) || + kzt_guest_registry_loader_symbol_source_acquire( + registry, guest_handle, &lookup_identity, &dynamic_view, + &dynamic_status, &dynamic_revision, &source_lease) != 0) { + return guest_result; + } + if (!lookup_identity.link_map_addr || !lookup_identity.generation || + lookup_identity.namespace_id != 0 || + dynamic_status != KZT_GUEST_FIELD_OK || !dynamic_revision) { + kzt_guest_registry_source_lease_release(&source_lease); + return guest_result; + } + key = (kzt_guest_library_binding_key_t) { + .link_map_addr = lookup_identity.link_map_addr, + .generation = lookup_identity.generation, + .namespace_id = lookup_identity.namespace_id, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + if (kzt_guest_library_access_lookup( + &context->kzt_guest_library_access, &key, &handle) != 0) { + kzt_guest_registry_source_lease_release(&source_lease); + return guest_result; + } + if (handle.object_type == KZT_GUEST_LIBRARY_OBJECT_WRAPPED && + handle.library) { + diagnostic_reason = "dynsym_unproven"; + if (kzt_guest_library_symbol_evidence_lookup( + &handle, symbol, dynamic_revision, &proven_runtime_address, + &proven_symbol_type) != 0) { + kzt_guest_dynsym_lookup_status_t dynsym_status = + kzt_guest_dynsym_lookup( + &dynamic_view, &reader_ops, symbol, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, + &dynsym_result); + + if (dynsym_status == KZT_GUEST_DYNSYM_LOOKUP_FOUND) { + proven_runtime_address = dynsym_result.runtime_address; + proven_symbol_type = dynsym_result.type; + kzt_guest_library_symbol_evidence_store( + &handle, symbol, dynamic_revision, + proven_runtime_address, + proven_symbol_type); + } + } + diagnostic_reason = + proven_runtime_address != guest_result + ? "dynsym_address_mismatch" + : proven_symbol_type != STT_FUNC + ? "unsupported_symbol_type" + : "bridge_missing"; + if (proven_runtime_address == guest_result && + proven_symbol_type == STT_FUNC && + GetLibFunctionSymbolStartEnd( + handle.library, symbol, 0, &start, &end)) { + selected = start; + diagnostic_reason = NULL; + } + if (diagnostic_reason) { + kzt_guest_library_note_wrapper_diagnostic( + "symbol", diagnostic_reason, key.link_map_addr, + key.generation, symbol, proven_symbol_type); + } + } + kzt_guest_registry_source_lease_release(&source_lease); + kzt_guest_library_handle_release(&handle); + return selected; +#else + (void)context; + (void)guest_handle; + (void)symbol; + (void)version; + return guest_result; +#endif +} static kzt_guest_library_object_type_t loader_object_type(library_t *library) { @@ -14,49 +456,87 @@ static kzt_guest_library_object_type_t loader_object_type(library_t *library) : KZT_GUEST_LIBRARY_OBJECT_UNSUPPORTED; } -void kzt_guest_library_note_loader_pair(box64context_t *context, - uintptr_t link_map_addr, - library_t *library) +static int kzt_guest_library_wrapper_proof_matches( + const kzt_guest_wrapper_source_proof_t *proof, uintptr_t link_map_addr) +{ + return proof && proof->lease.active && proof->lease.registry && + proof->key.link_map_addr == link_map_addr && + proof->key.link_map_addr == proof->lease.link_map_addr && + proof->key.generation == proof->lease.generation && + proof->key.namespace_id == proof->lease.namespace_id && + proof->key.namespace_id == 0 && + proof->key.namespace_kind == KZT_GUEST_LIBRARY_NAMESPACE_MAIN; +} + +kzt_guest_library_binding_result_t kzt_guest_library_note_loader_pair( + box64context_t *context, uintptr_t link_map_addr, library_t *library, + const kzt_guest_wrapper_source_proof_t *proof) { #ifdef CONFIG_LATX_KZT kzt_guest_library_object_type_t type; - if (!context || !link_map_addr || !library) return; + if (!context || !link_map_addr || !library) + return KZT_GUEST_LIBRARY_BINDING_ERROR; type = loader_object_type(library); - (void)kzt_guest_library_publish_loader_pair( - KztGuestLibraryBindingsForContext(context), link_map_addr, library, - type); + if (type == KZT_GUEST_LIBRARY_OBJECT_WRAPPED) { + if (!kzt_guest_library_wrapper_proof_matches(proof, link_map_addr)) + return KZT_GUEST_LIBRARY_BINDING_ERROR; + return kzt_guest_library_bind( + KztGuestLibraryBindingsForContext(context), &proof->key, + library, type); + } + return kzt_guest_library_publish_loader_pair( + KztGuestLibraryBindingsForContext(context), link_map_addr, + library, type); #else (void)context; (void)link_map_addr; (void)library; + (void)proof; + return KZT_GUEST_LIBRARY_BINDING_DISABLED; #endif } -void kzt_guest_library_note_loader_pair_pending( +kzt_guest_library_binding_result_t +kzt_guest_library_note_loader_pair_pending( box64context_t *context, const kzt_guest_library_loader_scope_t *scope, - uintptr_t link_map_addr, library_t *library) + uintptr_t link_map_addr, library_t *library, + const kzt_guest_wrapper_source_proof_t *proof) { #ifdef CONFIG_LATX_KZT - if (!context || !scope || !link_map_addr || !library) return; - (void)kzt_guest_library_loader_scope_note_pair( - scope, link_map_addr, library, loader_object_type(library)); + kzt_guest_library_object_type_t type = loader_object_type(library); + + if (!context || !scope || !link_map_addr || !library || + (type == KZT_GUEST_LIBRARY_OBJECT_WRAPPED && + !kzt_guest_library_wrapper_proof_matches(proof, link_map_addr))) + return KZT_GUEST_LIBRARY_BINDING_ERROR; + return kzt_guest_library_loader_scope_note_pair( + scope, link_map_addr, library, type); #else - (void)context; (void)scope; (void)link_map_addr; (void)library; + (void)context; (void)scope; (void)link_map_addr; (void)library; (void)proof; + return KZT_GUEST_LIBRARY_BINDING_DISABLED; #endif } -void kzt_guest_library_publish_loader_pair_scoped( +kzt_guest_library_binding_result_t +kzt_guest_library_publish_loader_pair_scoped( box64context_t *context, const kzt_guest_library_loader_scope_t *scope, - uintptr_t link_map_addr, library_t *library) + uintptr_t link_map_addr, library_t *library, + const kzt_guest_wrapper_source_proof_t *proof) { #ifdef CONFIG_LATX_KZT - if (!context || !scope || !link_map_addr || !library) return; - (void)kzt_guest_library_loader_scope_publish_pair( - scope, link_map_addr, library, loader_object_type(library)); + kzt_guest_library_object_type_t type = loader_object_type(library); + + if (!context || !scope || !link_map_addr || !library || + (type == KZT_GUEST_LIBRARY_OBJECT_WRAPPED && + !kzt_guest_library_wrapper_proof_matches(proof, link_map_addr))) + return KZT_GUEST_LIBRARY_BINDING_ERROR; + return kzt_guest_library_loader_scope_publish_pair( + scope, link_map_addr, library, type); #else - (void)context; (void)scope; (void)link_map_addr; (void)library; + (void)context; (void)scope; (void)link_map_addr; (void)library; (void)proof; + return KZT_GUEST_LIBRARY_BINDING_DISABLED; #endif } diff --git a/target/i386/latx/context/kzt_jump_slot_production.c b/target/i386/latx/context/kzt_jump_slot_production.c index 69d9dac8adb..4f59f8f5802 100644 --- a/target/i386/latx/context/kzt_jump_slot_production.c +++ b/target/i386/latx/context/kzt_jump_slot_production.c @@ -14,16 +14,25 @@ #include "debug.h" #include "elfload_dump.h" #include "elfloader_private.h" +#include "kzt_guest_dl_api.h" +#include "kzt_guest_symbol_scope.h" +#include "kzt_lifecycle_diagnostics.h" +#include "kzt_lazy_direct_route.h" #include "kzt_rela_diagnostics.h" #include "library.h" #include "librarian.h" #include "kzt_rela_request_enricher.h" #include "kzt_rela_runtime_bridge.h" +#include "kzt_rela_stub_detector.h" #include "kzt_owner_resolver.h" #include "kzt_runtime_candidate_shadow.h" +#include "kzt_wrapper_probe.h" + +extern int option_kzt_lazy_diagnostics; typedef struct kzt_production_jump_slot_state { box64context_t *context; + library_t *resolved_provider; int slot_current_value_is_unresolved_stub; uintptr_t resolved_target; kzt_wrapper_bridge_provider_t wrapper_provider; @@ -33,11 +42,28 @@ typedef struct kzt_production_jump_slot_state { kzt_rela_immediate_candidate_request_t last_request; kzt_rela_immediate_writer_result_t writer_result; kzt_guest_registry_source_lease_t source_lease; + const kzt_guest_registry_source_lease_t *held_source_lease; + kzt_guest_registry_patch_decision_lease_t decision_lease; + const kzt_guest_registry_patch_decision_lease_t *held_decision_lease; + const kzt_guest_library_handle_t *retained_provider_handle; + kzt_patch_object_ref_t exact_provider_owner; + kzt_guest_library_loader_quiescence_lease_t loader_quiescence_lease; + kzt_guest_symbol_scope_request_t symbol_scope_request; + kzt_guest_symbol_scope_result_t symbol_scope_proof; + kzt_patch_decision_t prevalidated_decision; + int prevalidated_decision_valid; + uintptr_t final_stale_slot_value; + int preserve_guest_after_final_slot_stale; + kzt_guest_dynamic_view_t runtime_view; + uintptr_t owner_namespace_id; + int runtime_view_valid; + int lazy_completion; kzt_patch_candidate_t runtime_candidate; kzt_runtime_got_plt_candidate_result_t runtime_candidate_result; char runtime_candidate_strings[512]; uintptr_t required_source_link_map; unsigned long required_source_generation; + elfheader_t *head; int discover_bridge_from_provider; const char *failure_stage; } kzt_production_jump_slot_state_t; @@ -49,21 +75,32 @@ void kzt_jump_slot_production_test_before_slot_load(void); void kzt_jump_slot_production_test_after_slot_load(uintptr_t *value); void kzt_jump_slot_production_test_after_slot_cas(int exchanged); void kzt_jump_slot_production_test_shadow_run(void); +void kzt_jump_slot_production_test_full_enrich(void); +void kzt_jump_slot_production_test_wrapper_only_enrich(void); +void kzt_jump_slot_production_test_before_generation_validate(void); +void kzt_jump_slot_production_test_before_patch_decision_lease_acquire(void); +void kzt_jump_slot_production_test_after_patch_decision_lease_acquire(void); +void kzt_jump_slot_production_test_before_patch_decision_lease_release(void); int kzt_jump_slot_production_test_begin_slot_write( uintptr_t slot_addr, kzt_patch_spike_permission_lease_t *lease); int kzt_jump_slot_production_test_end_slot_write( kzt_patch_spike_permission_lease_t *lease); +void kzt_jump_slot_production_test_mapping_lock(void); +void kzt_jump_slot_production_test_mapping_unlock(void); +int kzt_jump_slot_production_test_read_guest_memory( + uintptr_t address, void *dst, size_t size); #endif -static int production_read_guest_memory(uintptr_t address, void *dst, - size_t size, void *opaque) +static int production_read_guest_memory( + uintptr_t address, void *dst, size_t size, void *opaque) { (void)opaque; if (!address || !dst || !size) { return -1; } #ifdef KZT_JUMP_SLOT_PRODUCTION_TEST - memcpy(dst, (const void *)address, size); + return kzt_jump_slot_production_test_read_guest_memory( + address, dst, size); #else void *host_ptr; @@ -77,6 +114,55 @@ static int production_read_guest_memory(uintptr_t address, void *dst, return 0; } +static int production_symbol_scope_request( + box64context_t *context, elfheader_t *head, unsigned long generation, + uintptr_t namespace_head, const kzt_guest_dynamic_view_t *dynamic_view, + const kzt_guest_link_map_reader_ops_t *reader_ops, + unsigned long symbol_index, const char *symbol, + kzt_symbol_version_evidence_t version_evidence, const char *version, + kzt_guest_symbol_scope_request_t *request) +{ + uintptr_t symbol_addr; + Elf64_Sym reference; + + if (!context || !head || !head->self_link_map || !generation || + !namespace_head || !dynamic_view || + dynamic_view->status != KZT_GUEST_DYNAMIC_COMPLETE || + !dynamic_view->symtab.present || !dynamic_view->syment.present || + dynamic_view->syment.value != sizeof(reference) || + dynamic_view->symtab.value > UINTPTR_MAX || + symbol_index > + (UINTPTR_MAX - (uintptr_t)dynamic_view->symtab.value) / + sizeof(reference) || + !(symbol_addr = (uintptr_t)dynamic_view->symtab.value + + symbol_index * sizeof(reference)) || + !reader_ops || !reader_ops->read_memory || + reader_ops->read_memory(symbol_addr, &reference, sizeof(reference), + reader_ops->opaque) != 0 || + !symbol || !symbol[0] || !request || + context->kzt_guest_scope_layout == + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED || + !kzt_symbol_version_evidence_valid(version_evidence, version)) { + return -1; + } + *request = (kzt_guest_symbol_scope_request_t) { + .source = { + .link_map_addr = head->self_link_map, + .generation = generation, + .namespace_id = 0, + .namespace_head = namespace_head, + .layout = context->kzt_guest_scope_layout, + }, + .symbol = symbol, + .version_evidence = version_evidence, + .version = version, + .reference_binding = ELF64_ST_BIND(reference.st_info), + .reference_type = ELF64_ST_TYPE(reference.st_info), + .reference_visibility = reference.st_other & 0x3, + }; + return 0; +} + static int production_shadow_expected_target( const kzt_patch_candidate_t *candidate, uintptr_t *expected, void *opaque) @@ -221,6 +307,37 @@ static int production_slot_fits_page(uintptr_t slot_addr, uintptr_t page_mask) return (slot_addr & page_mask) == (slot_last & page_mask); } +static int production_slot_mapping_lock( + kzt_patch_spike_permission_lease_t *lease) +{ + if (!lease || lease->mmap_lock_held) { + return -1; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_mapping_lock(); +#elif defined(CONFIG_USER_ONLY) + mmap_lock(); +#else + return -1; +#endif + lease->mmap_lock_held = 1; + return 0; +} + +static void production_slot_mapping_unlock( + kzt_patch_spike_permission_lease_t *lease) +{ + if (!lease || !lease->mmap_lock_held) { + return; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_mapping_unlock(); +#elif defined(CONFIG_USER_ONLY) + mmap_unlock(); +#endif + lease->mmap_lock_held = 0; +} + /* Use linux-user bookkeeping, never a host mprotect on a guest slot. */ static int production_slot_begin_write( uintptr_t slot_addr, kzt_patch_spike_permission_lease_t *lease, @@ -232,11 +349,20 @@ static int production_slot_begin_write( } memset(lease, 0, sizeof(*lease)); #ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + int result; + if (!production_slot_fits_page(slot_addr, ~(uintptr_t)0xfff)) { return -1; } lease->guest_page_length = 0x1000; - return kzt_jump_slot_production_test_begin_slot_write(slot_addr, lease); + if (production_slot_mapping_lock(lease) != 0) { + return -1; + } + result = kzt_jump_slot_production_test_begin_slot_write(slot_addr, lease); + if (result != 0 && !lease->write_enabled) { + production_slot_mapping_unlock(lease); + } + return result; #elif defined(CONFIG_USER_ONLY) { abi_ulong guest_addr; @@ -257,6 +383,9 @@ static int production_slot_begin_write( (guest_last & TARGET_PAGE_MASK)) { return -1; } + if (production_slot_mapping_lock(lease) != 0) { + return -1; + } lease->guest_page = guest_addr & TARGET_PAGE_MASK; lease->guest_page_length = TARGET_PAGE_SIZE; flags = page_get_flags(guest_addr); @@ -265,17 +394,20 @@ static int production_slot_begin_write( lease->original_permissions = permissions; lease->was_writable = (flags & PAGE_WRITE) != 0; if (!(flags & PAGE_VALID) || !permissions) { - return -1; + goto fail; } if (lease->was_writable) { return 0; } if (target_mprotect(lease->guest_page, TARGET_PAGE_SIZE, permissions | PAGE_WRITE) != 0) { - return -1; + goto fail; } lease->write_enabled = 1; return 0; +fail: + production_slot_mapping_unlock(lease); + return -1; } #else return -1; @@ -285,40 +417,165 @@ static int production_slot_begin_write( static int production_slot_end_write(kzt_patch_spike_permission_lease_t *lease, void *opaque) { + int result; + (void)opaque; - if (!lease) { + if (!lease || !lease->mmap_lock_held) { return -1; } #ifdef KZT_JUMP_SLOT_PRODUCTION_TEST - return kzt_jump_slot_production_test_end_slot_write(lease); + result = kzt_jump_slot_production_test_end_slot_write(lease); #elif defined(CONFIG_USER_ONLY) if (!lease->checked || !lease->guest_page_length || (lease->guest_page & ~TARGET_PAGE_MASK) || lease->guest_page_length != TARGET_PAGE_SIZE || (lease->original_permissions & ~PAGE_BITS)) { + result = -1; + } else if (!lease->write_enabled) { + result = 0; + } else { + result = target_mprotect(lease->guest_page, + lease->guest_page_length, + lease->original_permissions); + } +#else + result = -1; +#endif + if (result == 0 || lease->restore_attempts >= 2) { + production_slot_mapping_unlock(lease); + } + return result; +} + +typedef enum kzt_production_slot_transaction_result { + KZT_PRODUCTION_SLOT_TRANSACTION_ERROR = -1, + KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH = 0, + KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED = 1, + KZT_PRODUCTION_SLOT_TRANSACTION_ROLLED_BACK = 2, + KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE = 3, + KZT_PRODUCTION_SLOT_TRANSACTION_CIRCUIT_OPEN = 4, +} kzt_production_slot_transaction_result_t; + +static const char *production_slot_transaction_result_name( + kzt_production_slot_transaction_result_t result) +{ + switch (result) { + case KZT_PRODUCTION_SLOT_TRANSACTION_ERROR: + return "ERROR"; + case KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH: + return "CAS_MISMATCH"; + case KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED: + return "APPLIED"; + case KZT_PRODUCTION_SLOT_TRANSACTION_ROLLED_BACK: + return "ROLLED_BACK"; + case KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE: + return "UNRECOVERABLE"; + case KZT_PRODUCTION_SLOT_TRANSACTION_CIRCUIT_OPEN: + return "CIRCUIT_OPEN"; + } + return "UNKNOWN"; +} + +static int production_slot_finish_write( + kzt_patch_spike_permission_lease_t *permission_lease) +{ + int result; + + if (!permission_lease) { return -1; } - if (!lease->write_enabled) { - return 0; + permission_lease->restore_attempted = 1; + ++permission_lease->restore_attempts; + result = production_slot_end_write(permission_lease, NULL); + if (result == 0) { + permission_lease->restored = 1; } - return target_mprotect(lease->guest_page, lease->guest_page_length, - lease->original_permissions); -#else - return -1; + return result; +} + +static kzt_production_slot_transaction_result_t +production_lazy_prebind_slot_cas( + box64context_t *context, uintptr_t slot_addr, uintptr_t expected, + uintptr_t replacement, uintptr_t *observed) +{ + kzt_patch_spike_permission_lease_t permission_lease = { 0 }; + uintptr_t current = expected; + uintptr_t rollback_expected = replacement; + int exchanged; + int rollback_succeeded = 1; + int restore_succeeded; + + if (observed) { + *observed = 0; + } + if (!context || !slot_addr || !expected || !replacement) { + return KZT_PRODUCTION_SLOT_TRANSACTION_ERROR; + } + if (kzt_patch_spike_guard_circuit_open( + KztPatchSpikeGuardForContext(context))) { + return KZT_PRODUCTION_SLOT_TRANSACTION_CIRCUIT_OPEN; + } + if ( + production_slot_begin_write(slot_addr, &permission_lease, NULL) != 0) { + if (permission_lease.mmap_lock_held && + production_slot_finish_write(&permission_lease) != 0 && + production_slot_finish_write(&permission_lease) != 0) { + kzt_patch_spike_guard_trip(KztPatchSpikeGuardForContext(context)); + return KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE; + } + return KZT_PRODUCTION_SLOT_TRANSACTION_ERROR; + } + exchanged = __atomic_compare_exchange_n( + (uintptr_t *)slot_addr, ¤t, replacement, 0, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE) ? 1 : 0; +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_after_slot_cas(exchanged); #endif + if (observed) { + *observed = current; + } + if (production_slot_finish_write(&permission_lease) == 0) { + return exchanged ? KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED : + KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH; + } + + if (exchanged) { + rollback_succeeded = __atomic_compare_exchange_n( + (uintptr_t *)slot_addr, &rollback_expected, expected, 0, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE) ? 1 : 0; +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_after_slot_cas(rollback_succeeded); +#endif + if (observed) { + *observed = rollback_succeeded ? expected : rollback_expected; + } + } + restore_succeeded = production_slot_finish_write(&permission_lease) == 0; + if (rollback_succeeded && restore_succeeded) { + return exchanged ? KZT_PRODUCTION_SLOT_TRANSACTION_ROLLED_BACK : + KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH; + } + + kzt_patch_spike_guard_trip(KztPatchSpikeGuardForContext(context)); + return KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE; } -static int production_validate_write_generation( +static int production_prevalidate_write_evidence( const kzt_patch_decision_t *decision, void *opaque) { kzt_production_jump_slot_state_t *state = opaque; - kzt_guest_object_snapshot_t *snapshot = NULL; - kzt_guest_object_snapshot_t *owner_snapshot = NULL; + kzt_guest_registry_address_match_t source_match; + kzt_guest_registry_address_match_t owner_match; kzt_guest_dynamic_view_t view; kzt_guest_field_status_t dynamic_status; unsigned long dynamic_generation = 0; int valid = 0; + kzt_owner_resolution_t owner_resolution; + +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_before_generation_validate(); +#endif if (!state || !decision || !decision->source.known || !decision->source.link_map_addr || !decision->source.generation || !decision->current_owner.known || @@ -326,34 +583,133 @@ static int production_validate_write_generation( !decision->current_owner.generation || !decision->dynamic_view_available || !decision->dynamic_view_generation || + !state->runtime_view_valid || + !state->held_source_lease || !state->held_source_lease->active || + state->held_source_lease->registry != + KztGuestRegistryForContext(state->context) || + state->held_source_lease->link_map_addr != + decision->source.link_map_addr || + state->held_source_lease->generation != decision->source.generation || + state->held_source_lease->namespace_id != 0 || + !state->held_decision_lease || + !state->held_decision_lease->active || + state->held_decision_lease->registry != + KztGuestRegistryForContext(state->context) || + state->held_decision_lease->link_map_addr != + decision->source.link_map_addr || + state->held_decision_lease->generation != decision->source.generation || + state->held_decision_lease->namespace_id != 0 || + !state->retained_provider_handle || + !state->retained_provider_handle->bindings || + !state->retained_provider_handle->entry || + state->retained_provider_handle->library != state->resolved_provider || + !state->exact_provider_owner.known || + state->exact_provider_owner.link_map_addr != + decision->current_owner.link_map_addr || + state->exact_provider_owner.generation != + decision->current_owner.generation || (state->required_source_link_map && decision->source.link_map_addr != state->required_source_link_map) || (state->required_source_generation && decision->source.generation != state->required_source_generation) || - kzt_guest_registry_find_by_link_map( + kzt_guest_registry_find_live_object( KztGuestRegistryForContext(state->context), - decision->source.link_map_addr, &snapshot) != 0 || !snapshot || - snapshot->generation != decision->source.generation || + decision->source.link_map_addr, &source_match) != 0 || + source_match.generation != decision->source.generation || kzt_guest_registry_find_dynamic_view( KztGuestRegistryForContext(state->context), decision->source.link_map_addr, &view, &dynamic_status, &dynamic_generation) != 0 || dynamic_status != KZT_GUEST_FIELD_OK || dynamic_generation != decision->dynamic_view_generation || - kzt_guest_registry_find_by_link_map( + view.status != KZT_GUEST_DYNAMIC_COMPLETE || + kzt_guest_registry_dynamic_view_matches( + KztGuestRegistryForContext(state->context), + decision->source.link_map_addr, decision->dynamic_view_generation, + &state->runtime_view) != 0 || + kzt_guest_registry_find_live_object( + KztGuestRegistryForContext(state->context), + decision->current_owner.link_map_addr, &owner_match) != 0 || + owner_match.generation != decision->current_owner.generation || + owner_match.namespace_id_status != KZT_GUEST_FIELD_OK || + owner_match.namespace_id != state->owner_namespace_id) { + goto out; + } + if (!decision->slot_current_value || + __atomic_load_n((uintptr_t *)decision->slot_addr, + __ATOMIC_ACQUIRE) != decision->slot_current_value) { + goto out; + } + kzt_owner_resolver_init(&owner_resolution); + if (kzt_owner_resolver_resolve_current( KztGuestRegistryForContext(state->context), - decision->current_owner.link_map_addr, &owner_snapshot) != 0 || - !owner_snapshot || - owner_snapshot->generation != decision->current_owner.generation) { + decision->slot_current_value, decision->slot_current_value, + &owner_resolution) != 0 || + owner_resolution.status != KZT_OWNER_RESOLVER_RESOLVED || + owner_resolution.owner_match != KZT_PATCH_OWNER_MATCH || + !owner_resolution.current_owner.known || + owner_resolution.current_owner.link_map_addr != + decision->current_owner.link_map_addr || + owner_resolution.current_owner.generation != + decision->current_owner.generation) { goto out; } valid = 1; out: - kzt_guest_object_snapshot_free(snapshot); - kzt_guest_object_snapshot_free(owner_snapshot); return valid ? 0 : -1; } +static int production_decision_matches_prevalidated( + const kzt_patch_decision_t *decision, + const kzt_patch_decision_t *prevalidated) +{ + return decision && prevalidated && + decision->source.known == prevalidated->source.known && + decision->source.link_map_addr == + prevalidated->source.link_map_addr && + decision->source.generation == prevalidated->source.generation && + decision->dynamic_view_available == + prevalidated->dynamic_view_available && + decision->dynamic_view_generation == + prevalidated->dynamic_view_generation && + decision->slot_addr == prevalidated->slot_addr && + decision->slot_current_value_present && + decision->slot_current_value == prevalidated->slot_current_value && + decision->current_owner.known == prevalidated->current_owner.known && + decision->current_owner.link_map_addr == + prevalidated->current_owner.link_map_addr && + decision->current_owner.generation == + prevalidated->current_owner.generation && + decision->owner_match == prevalidated->owner_match; +} + +/* The decision lease makes Registry evidence immutable. The writer only + * rechecks that the approved decision and the slot still match that evidence. */ +static int production_validate_prevalidated_write( + const kzt_patch_decision_t *decision, void *opaque) +{ + kzt_production_jump_slot_state_t *state = opaque; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = production_read_guest_memory, + }; + kzt_guest_symbol_scope_result_t revalidated; + + if (!state || !decision || !state->prevalidated_decision_valid || + !state->held_decision_lease || !state->held_decision_lease->active || + !state->retained_provider_handle || + state->retained_provider_handle->library != state->resolved_provider || + !production_decision_matches_prevalidated( + decision, &state->prevalidated_decision) || + kzt_guest_symbol_scope_revalidate( + &state->symbol_scope_proof, &state->symbol_scope_request, + &reader_ops, &revalidated) != KZT_GUEST_SYMBOL_SCOPE_SAFE || + __atomic_load_n((uintptr_t *)decision->slot_addr, + __ATOMIC_ACQUIRE) != decision->slot_current_value) { + return -1; + } + return 0; +} + static int production_enrich( kzt_rela_immediate_candidate_request_t *request, const kzt_wrapper_bridge_provider_t *wrapper_provider, @@ -368,29 +724,50 @@ static int production_enrich( NULL, .bridge_ops = wrapper_provider ? &wrapper_provider->bridge_ops : NULL, }; +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_full_enrich(); +#endif return kzt_rela_immediate_request_enrich(request, &enrich_input, enrich_result); } +static int production_enrich_wrapper_only( + kzt_rela_immediate_candidate_request_t *request, + const kzt_wrapper_bridge_provider_t *wrapper_provider, + kzt_rela_request_enricher_result_t *enrich_result, + kzt_production_jump_slot_state_t *state) +{ + kzt_rela_request_wrapper_only_input_t input = { + .wrapper_manifest = wrapper_provider ? &wrapper_provider->manifest : NULL, + .bridge_ops = wrapper_provider ? &wrapper_provider->bridge_ops : NULL, + }; + + kzt_rela_request_enricher_result_init(enrich_result); + +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_wrapper_only_enrich(); +#endif + return kzt_rela_immediate_request_enrich_wrapper_only( + request, &input, enrich_result); +} + static int production_request_is_main_namespace( kzt_production_jump_slot_state_t *state, const kzt_rela_immediate_candidate_request_t *request) { - kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_guest_registry_address_match_t match; int is_main = 0; if (!state || !request || !request->source.known || !request->source.link_map_addr || !request->source.generation || - kzt_guest_registry_find_by_link_map( + kzt_guest_registry_find_live_object( KztGuestRegistryForContext(state->context), - request->source.link_map_addr, &snapshot) != 0 || !snapshot) { - goto out; + request->source.link_map_addr, &match) != 0) { + return -1; } - is_main = snapshot->generation == request->source.generation && - snapshot->namespace_id.status == KZT_GUEST_FIELD_OK && - snapshot->namespace_id.value == 0; -out: - kzt_guest_object_snapshot_free(snapshot); + is_main = match.generation == request->source.generation && + match.namespace_id_status == KZT_GUEST_FIELD_OK && + match.namespace_id == 0; return is_main ? 0 : -1; } @@ -478,6 +855,8 @@ static int production_collect_runtime_candidate( request->dynamic_view_generation = state->runtime_candidate.dynamic_view_generation; request->dynamic_view_available = 1; + state->runtime_view = view; + state->runtime_view_valid = 1; request->entry_addr = state->runtime_candidate.entry_addr; request->slot_current_value = state->runtime_candidate.slot_current_value; @@ -488,6 +867,831 @@ static int production_collect_runtime_candidate( return 0; } +typedef struct kzt_lazy_direct_timing kzt_lazy_direct_timing_t; + +typedef struct kzt_lazy_direct_production_state { + box64context_t *context; + kzt_guest_registry_t *registry; + kzt_lazy_prebind_scope_t *prebind_scope; + kzt_lazy_prebind_lease_t prebind_lease; + library_t *resolved_provider; + kzt_guest_registry_source_lease_t source_lease; + kzt_guest_registry_patch_decision_lease_t decision_lease; + kzt_guest_library_binding_key_t provider_key; + kzt_guest_library_handle_t provider_handle; + int provider_handle_owned; + kzt_guest_library_loader_quiescence_lease_t loader_quiescence_lease; + kzt_wrapper_bridge_provider_t wrapper_provider; + kzt_wrapper_probe_result_t wrapper_probe; + kzt_guest_symbol_scope_request_t symbol_scope_request; + kzt_guest_symbol_scope_result_t preemption_proof; + kzt_production_jump_slot_state_t evidence; + kzt_lazy_direct_timing_t *timing; + int timing_enabled; + int prebind_hit; +} kzt_lazy_direct_production_state_t; + +struct kzt_lazy_direct_timing { + uint64_t start; + uint64_t source; + uint64_t candidate; + uint64_t quiescence; + uint64_t scope; + uint64_t provider; + uint64_t route_start; + uint64_t bridge_start; + uint64_t bridge_discover_done; + uint64_t bridge_done; + uint64_t decision_done; + uint64_t final_done; + uint64_t cas_done; + uint64_t route; + uint64_t done; +}; + +static uint64_t production_lazy_direct_timing_now(void) +{ + struct timespec value; + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &value) != 0) { + return 0; + } + return (uint64_t)value.tv_sec * 1000000000ULL + + (uint64_t)value.tv_nsec; +} + +static uint64_t production_lazy_direct_timing_delta(uint64_t start, + uint64_t end) +{ + return start && end >= start ? end - start : 0; +} + +static int production_lazy_prebind_copy_text(char *dst, size_t size, + const char *text) +{ + size_t length; + + if (!dst || !size) { + return -1; + } + if (!text || !text[0]) { + dst[0] = '\0'; + return 0; + } + length = strnlen(text, size); + if (length == size) { + return -1; + } + memcpy(dst, text, length + 1); + return 0; +} + +static int production_lazy_prebind_record_key( + kzt_lazy_prebind_record_t *record, uintptr_t source_link_map, + unsigned long source_generation, int entry_index, uintptr_t slot_addr, + uintptr_t expected_slot, const char *symbol, + kzt_symbol_version_evidence_t version_evidence, const char *version) +{ + if (!record || !source_link_map || !source_generation || entry_index < 0 || + !slot_addr || !expected_slot || !symbol || !symbol[0] || + !kzt_symbol_version_evidence_valid(version_evidence, version)) { + return -1; + } + memset(record, 0, sizeof(*record)); + record->source = (kzt_lazy_prebind_identity_t) { + .link_map_addr = source_link_map, + .generation = source_generation, + .namespace_id = 0, + }; + record->slot_addr = slot_addr; + record->expected_slot = expected_slot; + record->relocation_index = (unsigned long)entry_index; + record->version_evidence = version_evidence; + return production_lazy_prebind_copy_text( + record->symbol, sizeof(record->symbol), symbol) == 0 && + production_lazy_prebind_copy_text( + record->version, sizeof(record->version), version) == 0 ? + 0 : -1; +} + +static int production_lazy_prebind_publish_record( + box64context_t *context, kzt_lazy_prebind_scope_t *scope, + const kzt_lazy_prebind_record_t *record, + kzt_lazy_prebind_target_prepare_fn target_prepare, + void *target_prepare_opaque) +{ + kzt_lazy_prebind_lease_t lease = { 0 }; + uintptr_t observed = 0; + int committed; + kzt_production_slot_transaction_result_t writer_result; + + if (!context || !scope || !record || + kzt_lazy_prebind_scope_publish_acquire(scope, record, &lease) != 0) { + return 0; + } + if (record->bridge_custom_wrapper && + strcmp(record->symbol, "dlerror") == 0 && + kzt_guest_dl_api_publish_dlerror_entry( + context->dlprivate, record->symbol, + record->scope_proof.selected_provider_address, + record->bridge_custom_wrapper) != 0) { + kzt_lazy_prebind_scope_publish_finish(&lease, 0); + return 0; + } + writer_result = production_lazy_prebind_slot_cas( + context, record->slot_addr, record->expected_slot, + record->bridge_target, &observed); + committed = writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED; + kzt_lazy_prebind_scope_publish_finish(&lease, committed); + if (committed && target_prepare) { + (void)target_prepare(record->bridge_target, target_prepare_opaque); + if (record->bridge_custom_wrapper && + strcmp(record->symbol, "dlerror") == 0 && + record->scope_proof.selected_provider_address) { + (void)target_prepare(record->scope_proof.selected_provider_address, + target_prepare_opaque); + } + } + printf_kzt_registry_diagnostics( + "kzt_lazy_prebind_publish schema=1 symbol=%s source=%p generation=%lu " + "slot=%p bridge=%p result=%s writer=%s observed=%p\n", + record->symbol, (void *)record->source.link_map_addr, + record->source.generation, + (void *)record->slot_addr, (void *)record->bridge_target, + committed ? "APPLIED" : "SKIPPED", + production_slot_transaction_result_name(writer_result), + (void *)observed); + return committed ? 1 : 0; +} + +/* This runs under kzt_per_object_got_plt_apply's exact source and decision + * leases. It records already-proven facts only and deliberately has no slot + * writer: the resolver retains the final Registry, fingerprint, and CAS gate. */ +static int production_lazy_prebind_object_prepare( + box64context_t *context, elfheader_t *head, + unsigned long source_generation, + const kzt_guest_dynamic_view_t *source_dynamic_view, + kzt_lazy_prebind_target_prepare_fn target_prepare, + void *target_prepare_opaque) +{ + kzt_guest_registry_t *registry; + kzt_guest_registry_source_lease_t source_lease = { 0 }; + kzt_guest_registry_patch_decision_lease_t decision_lease = { 0 }; + kzt_guest_library_loader_quiescence_lease_t loader_quiescence_lease = { 0 }; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = production_read_guest_memory, + }; + kzt_lazy_prebind_scope_t *scope; + uintptr_t namespace_head = 0; + size_t relocation_count; + size_t index; + size_t prepared = 0; + kzt_patch_object_ref_t source_ref; + + if (!context || !head || !head->self_link_map || !source_generation || + !source_dynamic_view || + source_dynamic_view->status != KZT_GUEST_DYNAMIC_COMPLETE || + !source_dynamic_view->jmprel.present || + !source_dynamic_view->pltrelsz.present || + !source_dynamic_view->pltrel.present || + source_dynamic_view->pltrel.value != DT_RELA || + source_dynamic_view->pltrelsz.value > SIZE_MAX || + source_dynamic_view->pltrelsz.value % sizeof(Elf64_Rela) || + !(registry = KztGuestRegistryForContext(context)) || + !(scope = KztLazyPrebindScopeForContext(context)) || + kzt_guest_registry_dynamic_view_matches( + registry, head->self_link_map, source_generation, + source_dynamic_view) != 0 || + kzt_guest_registry_source_lease_acquire( + registry, head->self_link_map, source_generation, 0, + &source_lease) != 0 || + kzt_guest_registry_patch_decision_lease_acquire( + &source_lease, &decision_lease) != 0 || + kzt_guest_library_loader_quiescence_try_acquire( + KztGuestLibraryBindingsForContext(context), + &loader_quiescence_lease) != 0) { + kzt_guest_library_loader_quiescence_release( + &loader_quiescence_lease); + kzt_guest_registry_patch_decision_lease_release(&decision_lease); + kzt_guest_registry_source_lease_release(&source_lease); + return 0; + } + if (kzt_guest_registry_context_get_main_namespace_head( + &context->kzt_guest_registry_context, &namespace_head) != 0 || + !namespace_head) { + goto done; + } + + relocation_count = + (size_t)source_dynamic_view->pltrelsz.value / sizeof(Elf64_Rela); + source_ref = (kzt_patch_object_ref_t) { + .known = 1, + .link_map_addr = head->self_link_map, + .generation = source_generation, + .map_start = (uintptr_t)head->memory, + .map_end = (uintptr_t)head->memory + head->memsz, + .soname = head->name, + .path = head->path, + }; + for (index = 0; index < relocation_count; ++index) { + kzt_runtime_got_plt_candidate_request_t collector_request; + kzt_runtime_got_plt_candidate_result_t collector_result; + kzt_patch_candidate_t candidate; + char candidate_strings[512]; + kzt_guest_symbol_scope_request_t scope_request; + kzt_guest_symbol_scope_result_t scope_proof; + kzt_guest_registry_address_match_t provider_match; + kzt_guest_library_binding_key_t provider_key; + kzt_guest_library_handle_t provider_handle = { 0 }; + kzt_wrapper_bridge_provider_t wrapper_provider; + kzt_wrapper_probe_result_t wrapper_probe; + kzt_wrapper_probe_request_t wrapper_request; + kzt_lazy_prebind_record_t record; + + memset(&candidate, 0, sizeof(candidate)); + memset(candidate_strings, 0, sizeof(candidate_strings)); + collector_request = (kzt_runtime_got_plt_candidate_request_t) { + .view = source_dynamic_view, + .reader_ops = &reader_ops, + .source = &source_ref, + .dynamic_view_generation = source_generation, + .only_entry = 1, + .only_table_kind = KZT_PATCH_TABLE_PLT_RELA, + .only_entry_index = index, + .candidates = &candidate, + .candidate_capacity = 1, + .string_storage = candidate_strings, + .string_storage_size = sizeof(candidate_strings), + }; + if (kzt_runtime_got_plt_candidates_collect( + &collector_request, &collector_result) != 0 || + collector_result.status != KZT_RUNTIME_GOT_PLT_CANDIDATE_OK || + collector_result.candidate_count != 1 || + candidate.reloc_type != KZT_PATCH_RELOCATION_JUMP_SLOT || + candidate.source.link_map_addr != head->self_link_map || + candidate.source.generation != source_generation || + candidate.dynamic_view_generation != source_generation || + !candidate.slot_addr || !candidate.slot_current_value || + !kzt_rela_slot_current_is_unresolved_stub( + candidate.slot_current_value, + KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, + head->delta, head->plt, head->plt_end, head->gotplt, + head->gotplt_end) || + !candidate.symbol_name || !candidate.symbol_name[0] || + kzt_patch_symbol_must_stay_guest(candidate.symbol_name) || + production_symbol_scope_request( + context, head, source_generation, namespace_head, + source_dynamic_view, &reader_ops, candidate.symbol_index, + candidate.symbol_name, candidate.version_evidence, + candidate.version, + &scope_request) != 0 || + kzt_guest_symbol_scope_discover( + &scope_request, &reader_ops, &scope_proof) != + KZT_GUEST_SYMBOL_SCOPE_SAFE || + kzt_guest_registry_find_live_object( + registry, scope_proof.selected_provider_link_map, + &provider_match) != 0 || + !provider_match.generation || + provider_match.namespace_id_status != KZT_GUEST_FIELD_OK || + provider_match.namespace_id != 0) { + continue; + } + provider_key = (kzt_guest_library_binding_key_t) { + .link_map_addr = scope_proof.selected_provider_link_map, + .generation = provider_match.generation, + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + if (kzt_guest_library_access_lookup( + &context->kzt_guest_library_access, &provider_key, + &provider_handle) != 0 || !provider_handle.library || + provider_handle.object_type != KZT_GUEST_LIBRARY_OBJECT_WRAPPED || + kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( + context, &provider_handle, candidate.symbol_name, + candidate.version_evidence, candidate.version, + &wrapper_provider) <= 0) { + kzt_guest_library_handle_release(&provider_handle); + continue; + } + wrapper_request = (kzt_wrapper_probe_request_t) { + .symbol_name = candidate.symbol_name, + .symbol_version_evidence = candidate.version_evidence, + .symbol_version = candidate.version, + }; + if (kzt_wrapper_probe_minimal_manifest( + &wrapper_provider.manifest, &wrapper_request, + &wrapper_provider.bridge_ops, &wrapper_probe) != 0 || + (wrapper_probe.wrapper_match != KZT_PATCH_WRAPPER_VERSION_MATCH && + wrapper_probe.wrapper_match != KZT_PATCH_WRAPPER_UNVERSIONED_MATCH) || + !wrapper_probe.bridge_target || + !kzt_symbol_version_evidence_matches( + candidate.version_evidence, candidate.version, + wrapper_probe.wrapper_version_evidence, + wrapper_probe.wrapper_symbol_version) || + production_lazy_prebind_record_key( + &record, head->self_link_map, source_generation, (int)index, + candidate.slot_addr, candidate.slot_current_value, + candidate.symbol_name, candidate.version_evidence, + candidate.version) != 0) { + kzt_guest_library_handle_release(&provider_handle); + continue; + } + record.provider = (kzt_lazy_prebind_identity_t) { + .link_map_addr = provider_key.link_map_addr, + .generation = provider_key.generation, + .namespace_id = provider_key.namespace_id, + }; + record.bridge_target = wrapper_probe.bridge_target; + record.bridge_generation = provider_key.generation; + record.bridge_custom_wrapper = wrapper_provider.match.custom_wrapper; + record.scope_proof = scope_proof; + kzt_lazy_prebind_claim_result_t claim = + kzt_lazy_prebind_scope_claim(scope, &record); + + if (claim == KZT_LAZY_PREBIND_CLAIM_CREATED) { + ++prepared; + } + if (claim == KZT_LAZY_PREBIND_CLAIM_CREATED || + claim == KZT_LAZY_PREBIND_CLAIM_REUSED) { + (void)production_lazy_prebind_publish_record(context, scope, + &record, + target_prepare, + target_prepare_opaque); + } + kzt_guest_library_handle_release(&provider_handle); + } + + if (prepared) { + printf_kzt_registry_diagnostics( + "kzt_lazy_prebind schema=1 source=%p generation=%lu " + "records=%zu result=READY\n", + (void *)head->self_link_map, source_generation, prepared); + } +done: + kzt_guest_library_loader_quiescence_release(&loader_quiescence_lease); + kzt_guest_registry_patch_decision_lease_release(&decision_lease); + kzt_guest_registry_source_lease_release(&source_lease); + return 0; +} + +int kzt_production_lazy_prebind_object( + box64context_t *context, elfheader_t *head, + unsigned long source_generation, + const kzt_guest_dynamic_view_t *source_dynamic_view, + kzt_lazy_prebind_target_prepare_fn target_prepare, + void *target_prepare_opaque) +{ + return production_lazy_prebind_object_prepare( + context, head, source_generation, source_dynamic_view, target_prepare, + target_prepare_opaque); +} + +static int production_lazy_prebind_revoke_closed( + box64context_t *context, kzt_lazy_prebind_scope_t *scope, + const kzt_lazy_prebind_identity_t *identity) +{ + int result = 0; + + if (!context || !scope) { + return -1; + } + for (;;) { + kzt_lazy_prebind_lease_t lease = { 0 }; + kzt_lazy_prebind_record_t record; + uintptr_t observed = 0; + kzt_production_slot_transaction_result_t writer_result; + int acquire_result = kzt_lazy_prebind_scope_revoke_acquire( + scope, identity, &lease); + int revoked; + + if (acquire_result == 1) { + break; + } + if (acquire_result != 0) { + return -1; + } + record = lease.record; + writer_result = production_lazy_prebind_slot_cas( + context, record.slot_addr, record.bridge_target, + record.expected_slot, &observed); + revoked = + writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED || + (writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH && + observed != record.bridge_target); + kzt_lazy_prebind_scope_revoke_finish(&lease, revoked); + printf_kzt_registry_diagnostics( + "kzt_lazy_prebind_revoke schema=1 source=%p generation=%lu " + "slot=%p bridge=%p stub=%p result=%s writer=%s observed=%p\n", + (void *)record.source.link_map_addr, record.source.generation, + (void *)record.slot_addr, (void *)record.bridge_target, + (void *)record.expected_slot, + writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED ? + "RESTORED" : (revoked ? "SUPERSEDED" : "FAILED"), + production_slot_transaction_result_name(writer_result), + (void *)observed); + if (!revoked) { + result = -1; + break; + } + } + return result; +} + +int kzt_production_lazy_prebind_invalidate( + box64context_t *context, kzt_lazy_prebind_mutation_t mutation) +{ + kzt_lazy_prebind_scope_t *scope; + uint64_t timing_start = kzt_lifecycle_diagnostics_enabled() + ? kzt_lifecycle_diagnostics_now() + : 0; + int result; + + if (!context || !(scope = KztLazyPrebindScopeForContext(context)) || + !kzt_lazy_prebind_scope_mutate(scope, mutation)) { + result = -1; + } else { + result = production_lazy_prebind_revoke_closed(context, scope, NULL); + } + if (timing_start) { + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_SCOPE_INVALIDATE, + kzt_lifecycle_diagnostics_now() - timing_start); + } + return result; +} + +int kzt_production_lazy_prebind_retire( + box64context_t *context, + const kzt_lazy_prebind_identity_t *identity) +{ + kzt_lazy_prebind_scope_t *scope; + + if (!context || !identity || + !(scope = KztLazyPrebindScopeForContext(context)) || + kzt_lazy_prebind_scope_retire(scope, identity) != 0) { + return -1; + } + return production_lazy_prebind_revoke_closed(context, scope, identity); +} + +/* A later loader event invalidates every previous scope epoch. Refresh from + * a copied Registry snapshot so the final event leaves every live resolver + * with only currently-proven records. */ +void kzt_production_lazy_prebind_refresh( + box64context_t *context, + kzt_lazy_prebind_target_prepare_fn target_prepare, + void *target_prepare_opaque) +{ + kzt_guest_registry_t *registry; + kzt_guest_registry_dump_t dump = { 0 }; + size_t index; + uint64_t timing_start = kzt_lifecycle_diagnostics_enabled() + ? kzt_lifecycle_diagnostics_now() + : 0; + + if (!context || !(registry = KztGuestRegistryForContext(context)) || + kzt_guest_registry_dump_snapshot(registry, &dump) != 0) { + goto out; + } + for (index = 0; index < dump.count; ++index) { + const kzt_guest_object_snapshot_t *object = &dump.objects[index]; + kzt_guest_registry_source_lease_t source_lease = { 0 }; + elfheader_t *head; + + if (!object->link_map_addr || !object->generation || + object->namespace_id.status != KZT_GUEST_FIELD_OK || + object->namespace_id.value != 0 || + object->dynamic_view_status != KZT_GUEST_FIELD_OK || + object->dynamic_view.status != KZT_GUEST_DYNAMIC_COMPLETE || + !object->lazy_resolver.valid || + !object->lazy_resolver.registry_owned_head || + !object->lazy_resolver.object_head || + object->state == KZT_GUEST_OBJECT_UNLOADING || + object->state == KZT_GUEST_OBJECT_DEAD || + kzt_guest_registry_source_lease_acquire( + registry, object->link_map_addr, object->generation, 0, + &source_lease) != 0) { + continue; + } + head = (elfheader_t *)object->lazy_resolver.object_head; + (void)production_lazy_prebind_object_prepare( + context, head, object->generation, &object->dynamic_view, + target_prepare, target_prepare_opaque); + kzt_guest_registry_source_lease_release(&source_lease); + } + kzt_guest_registry_dump_free(&dump); +out: + if (timing_start) { + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_PREBIND_REFRESH, + kzt_lifecycle_diagnostics_now() - timing_start); + } +} + +static int production_lazy_direct_validate_source( + const kzt_lazy_direct_route_input_t *input, void *opaque) +{ + kzt_lazy_direct_production_state_t *state = opaque; + kzt_guest_registry_address_match_t match; + + return state && input && state->source_lease.active && + state->source_lease.registry == state->registry && + state->source_lease.link_map_addr == input->source.link_map_addr && + state->source_lease.generation == input->source.generation && + state->source_lease.namespace_id == input->namespace_id && + state->evidence.runtime_view_valid && + input->source_dynamic_view == &state->evidence.runtime_view && + kzt_guest_registry_find_live_object( + state->registry, input->source.link_map_addr, &match) == 0 && + match.generation == input->source.generation && + match.namespace_id_status == KZT_GUEST_FIELD_OK && + match.namespace_id == 0 && + kzt_guest_registry_dynamic_view_matches( + state->registry, input->source.link_map_addr, + input->source.generation, + input->source_dynamic_view) == 0; +} + +static int production_lazy_direct_acquire_provider( + const kzt_lazy_direct_route_input_t *input, + kzt_lazy_direct_route_provider_t *provider, void *opaque) +{ + kzt_lazy_direct_production_state_t *state = opaque; + + if (!state || !input || !provider || !state->provider_handle_owned || + !state->provider_handle.bindings || !state->provider_handle.entry || + state->provider_handle.library != state->resolved_provider || + state->provider_key.link_map_addr != input->provider.link_map_addr || + state->provider_key.generation != input->provider.generation) { + return -1; + } + *provider = (kzt_lazy_direct_route_provider_t) { + .handle = &state->provider_handle, + .link_map_addr = state->provider_key.link_map_addr, + .generation = state->provider_key.generation, + .namespace_id = state->provider_key.namespace_id, + .namespace_kind = state->provider_key.namespace_kind, + }; + return 0; +} + +static void production_lazy_direct_release_provider( + kzt_lazy_direct_route_provider_t *provider, void *opaque) +{ + kzt_lazy_direct_production_state_t *state = opaque; + + if (state && state->provider_handle_owned) { + kzt_guest_library_handle_release(&state->provider_handle); + state->provider_handle_owned = 0; + } + if (provider) { + memset(provider, 0, sizeof(*provider)); + } +} + +static int production_lazy_direct_find_bridge( + const kzt_lazy_direct_route_input_t *input, + const kzt_lazy_direct_route_provider_t *provider, + kzt_lazy_direct_route_bridge_t *bridge, void *opaque) +{ + kzt_lazy_direct_production_state_t *state = opaque; + kzt_wrapper_probe_request_t request; + int status; + + if (!state || !input || !provider || !bridge || + provider->handle != &state->provider_handle || + !state->provider_handle_owned || !state->resolved_provider) { + return -1; + } + if (state->timing_enabled) { + state->timing->bridge_start = production_lazy_direct_timing_now(); + } + if (state->prebind_hit && state->prebind_lease.active) { + const kzt_lazy_prebind_record_t *record = + &state->prebind_lease.record; + + if (!kzt_lazy_prebind_scope_lease_valid(&state->prebind_lease) || + record->bridge_generation != input->provider.generation || + record->bridge_target == 0 || + record->version_evidence != input->version_evidence || + !kzt_symbol_version_evidence_matches( + input->version_evidence, input->version, + record->version_evidence, + record->version[0] ? record->version : NULL)) { + return -1; + } + state->wrapper_provider.match.retained_provider_handle = + &state->provider_handle; + state->wrapper_provider.match.custom_wrapper = + record->bridge_custom_wrapper; + state->wrapper_provider.match.resolved_bridge_target = + record->bridge_target; + state->wrapper_provider.match.resolved_bridge_exact = 1; + state->wrapper_probe = (kzt_wrapper_probe_result_t) { + .wrapper_match = input->version_evidence == + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED ? + KZT_PATCH_WRAPPER_UNVERSIONED_MATCH : + KZT_PATCH_WRAPPER_VERSION_MATCH, + .wrapper_version_evidence = record->version_evidence, + .wrapper_symbol_version = record->version[0] ? + record->version : NULL, + .bridge_target = record->bridge_target, + }; + *bridge = (kzt_lazy_direct_route_bridge_t) { + .target = record->bridge_target, + .version_evidence = record->version_evidence, + .version = record->version[0] ? record->version : NULL, + }; + if (state->timing_enabled) { + state->timing->bridge_discover_done = + production_lazy_direct_timing_now(); + state->timing->bridge_done = + state->timing->bridge_discover_done; + } + return 0; + } + memset(&state->wrapper_provider, 0, sizeof(state->wrapper_provider)); + status = + kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( + state->context, &state->provider_handle, input->symbol, + input->version_evidence, input->version, + &state->wrapper_provider); + if (status <= 0) { + return -1; + } + if (state->timing_enabled) { + state->timing->bridge_discover_done = + production_lazy_direct_timing_now(); + } + request = (kzt_wrapper_probe_request_t) { + .symbol_name = input->symbol, + .symbol_version_evidence = input->version_evidence, + .symbol_version = input->version, + }; + if (kzt_wrapper_probe_minimal_manifest( + &state->wrapper_provider.manifest, &request, + &state->wrapper_provider.bridge_ops, + &state->wrapper_probe) != 0 || + (state->wrapper_probe.wrapper_match != + KZT_PATCH_WRAPPER_VERSION_MATCH && + state->wrapper_probe.wrapper_match != + KZT_PATCH_WRAPPER_UNVERSIONED_MATCH) || + !state->wrapper_probe.bridge_target) { + return -1; + } + *bridge = (kzt_lazy_direct_route_bridge_t) { + .target = state->wrapper_probe.bridge_target, + .version_evidence = state->wrapper_probe.wrapper_version_evidence, + .version = state->wrapper_probe.wrapper_symbol_version, + }; + if (state->timing_enabled) { + state->timing->bridge_done = production_lazy_direct_timing_now(); + } + return 0; +} + +static int production_lazy_direct_acquire_decision_lease( + const kzt_lazy_direct_route_input_t *input, + const kzt_lazy_direct_route_provider_t *provider, + kzt_lazy_direct_route_lease_t *lease, void *opaque) +{ + kzt_lazy_direct_production_state_t *state = opaque; + + if (!state || !input || !provider || !lease || + provider->handle != &state->provider_handle || + kzt_guest_registry_patch_decision_lease_acquire( + &state->source_lease, &state->decision_lease) != 0) { + return -1; + } + lease->handle = &state->decision_lease; + lease->active = state->decision_lease.active; + if (state->timing_enabled) { + state->timing->decision_done = production_lazy_direct_timing_now(); + } + return lease->active ? 0 : -1; +} + +static void production_lazy_direct_release_decision_lease( + kzt_lazy_direct_route_lease_t *lease, void *opaque) +{ + kzt_lazy_direct_production_state_t *state = opaque; + + if (state && state->decision_lease.active) { + kzt_guest_registry_patch_decision_lease_release( + &state->decision_lease); + } + if (lease) { + memset(lease, 0, sizeof(*lease)); + } +} + +static int production_lazy_direct_validate_final( + const kzt_lazy_direct_route_input_t *input, + const kzt_lazy_direct_route_provider_t *provider, + const kzt_lazy_direct_route_bridge_t *bridge, + const kzt_lazy_direct_route_lease_t *lease, void *opaque) +{ + kzt_lazy_direct_production_state_t *state = opaque; + kzt_guest_registry_address_match_t source_match; + kzt_guest_registry_address_match_t provider_match; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = production_read_guest_memory, + }; + kzt_guest_symbol_scope_result_t revalidated; + int valid = 0; + + if (!state || !input || !provider || !bridge || !lease || + !lease->active || lease->handle != &state->decision_lease || + !state->decision_lease.active || + state->decision_lease.registry != state->registry || + state->decision_lease.link_map_addr != + input->source.link_map_addr || + state->decision_lease.generation != input->source.generation || + state->decision_lease.namespace_id != 0 || + (state->prebind_hit && + !kzt_lazy_prebind_scope_lease_valid(&state->prebind_lease)) || + !state->provider_handle_owned || + provider->handle != &state->provider_handle || + state->provider_handle.library != state->resolved_provider || + state->loader_quiescence_lease.bindings != + state->provider_handle.bindings || + !state->loader_quiescence_lease.cookie || + state->wrapper_provider.match.retained_provider_handle != + &state->provider_handle || + bridge->target != state->wrapper_probe.bridge_target || + kzt_guest_registry_find_live_object( + state->registry, input->source.link_map_addr, + &source_match) != 0 || + source_match.generation != input->source.generation || + source_match.namespace_id_status != KZT_GUEST_FIELD_OK || + source_match.namespace_id != 0 || + kzt_guest_registry_find_live_object( + state->registry, input->provider.link_map_addr, + &provider_match) != 0 || + provider_match.generation != input->provider.generation || + provider_match.namespace_id_status != KZT_GUEST_FIELD_OK || + provider_match.namespace_id != 0 || + kzt_guest_registry_dynamic_view_matches( + state->registry, input->source.link_map_addr, + input->source.generation, + input->source_dynamic_view) != 0 || + kzt_guest_symbol_scope_revalidate( + &state->preemption_proof, &state->symbol_scope_request, + &reader_ops, &revalidated) != + KZT_GUEST_SYMBOL_SCOPE_SAFE) { + goto out; + } + valid = __atomic_load_n((uintptr_t *)input->slot_addr, + __ATOMIC_ACQUIRE) == + input->expected_current_slot; +out: + if (state && state->timing_enabled) { + state->timing->final_done = production_lazy_direct_timing_now(); + } + return valid; +} + +static kzt_lazy_direct_route_cas_status_t +production_lazy_direct_cas_slot( + uintptr_t slot_addr, uintptr_t expected, uintptr_t replacement, + const kzt_lazy_direct_route_lease_t *lease, void *opaque) +{ + kzt_lazy_direct_production_state_t *state = opaque; + uintptr_t observed = expected; + int exchanged; + + if (!state || !lease || !lease->active || + lease->handle != &state->decision_lease || + !state->decision_lease.active || !slot_addr || !replacement) { + return KZT_LAZY_DIRECT_ROUTE_CAS_ERROR; + } + if (kzt_patch_spike_guard_circuit_open( + KztPatchSpikeGuardForContext(state->context))) { + return KZT_LAZY_DIRECT_ROUTE_CAS_ERROR; + } + if (state->wrapper_provider.match.custom_wrapper && + strcmp(state->evidence.runtime_candidate.symbol_name, + "dlerror") == 0 && + kzt_guest_dl_api_publish_dlerror_entry( + state->context->dlprivate, + state->evidence.runtime_candidate.symbol_name, + state->preemption_proof.selected_provider_address, + state->wrapper_provider.match.custom_wrapper) != 0) { + return KZT_LAZY_DIRECT_ROUTE_CAS_ERROR; + } + exchanged = __atomic_compare_exchange_n( + (uintptr_t *)slot_addr, &observed, replacement, 0, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE); + if (state->timing_enabled) { + state->timing->cas_done = production_lazy_direct_timing_now(); + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_after_slot_cas(exchanged); +#endif + return exchanged ? KZT_LAZY_DIRECT_ROUTE_CAS_APPLIED : + KZT_LAZY_DIRECT_ROUTE_CAS_MISMATCH; +} + static int production_enrich_base( kzt_rela_immediate_candidate_request_t *request, void *opaque) { @@ -517,6 +1721,9 @@ static int production_enrich_base( KztGuestRegistryForContext(state->context), request->source.link_map_addr, request->source.generation, 0, &state->source_lease); + if (status == 0) { + state->held_source_lease = &state->source_lease; + } } if (status == 0) { status = production_collect_runtime_candidate(state, request); @@ -534,39 +1741,43 @@ static int production_acquire_exact( { kzt_production_jump_slot_state_t *state = opaque; kzt_guest_library_binding_key_t key; - kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_guest_registry_address_match_t match; int result = -1; state->failure_stage = "EXACT_LIBRARY_BINDING"; if (!owner || !owner->known || !owner->link_map_addr || - !owner->generation || !resolved_provider || !handle) { + !owner->generation || !handle) { return -1; } - if (kzt_guest_registry_find_by_link_map( + if (kzt_guest_registry_find_live_object( KztGuestRegistryForContext(state->context), - owner->link_map_addr, &snapshot) != 0 || !snapshot || - snapshot->generation != owner->generation || - snapshot->namespace_id.status != KZT_GUEST_FIELD_OK || - snapshot->namespace_id.value != 0) { + owner->link_map_addr, &match) != 0 || + match.generation != owner->generation || + match.namespace_id_status != KZT_GUEST_FIELD_OK || + match.namespace_id != 0) { goto out; } key = (kzt_guest_library_binding_key_t){ .link_map_addr = owner->link_map_addr, .generation = owner->generation, - .namespace_id = snapshot->namespace_id.value, + .namespace_id = match.namespace_id, .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, }; if (KztGuestLibraryLookupForContext(state->context, &key, handle) != 0) { goto out; } - if (handle->library != resolved_provider) { + if (!handle->library || + (resolved_provider && handle->library != resolved_provider)) { kzt_guest_library_handle_release(handle); goto out; } + state->resolved_provider = handle->library; + state->retained_provider_handle = handle; + state->exact_provider_owner = *owner; + state->owner_namespace_id = match.namespace_id; result = 0; state->failure_stage = "BRIDGE_EVIDENCE"; out: - kzt_guest_object_snapshot_free(snapshot); return result; } @@ -577,77 +1788,189 @@ static void production_release_exact(kzt_guest_library_handle_t *handle, kzt_guest_library_handle_release(handle); } +static void production_release_decision_lease( + kzt_production_jump_slot_state_t *state) +{ + if (!state || !state->decision_lease.active) { + return; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_before_patch_decision_lease_release(); +#endif + kzt_guest_registry_patch_decision_lease_release(&state->decision_lease); + state->held_decision_lease = NULL; +} + +static int production_acquire_and_validate_bridge_evidence( + kzt_production_jump_slot_state_t *state, + kzt_rela_immediate_candidate_request_t *request) +{ + kzt_patch_decision_t decision; + kzt_owner_resolution_t owner_resolution; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = production_read_guest_memory, + }; + uintptr_t namespace_head = 0; + uintptr_t fresh_value; + uintptr_t prepared_value; + + if (!state || !request || !state->held_source_lease) { + return -1; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_before_patch_decision_lease_acquire(); +#endif + if (kzt_guest_registry_patch_decision_lease_acquire( + state->held_source_lease, &state->decision_lease) != 0) { + return -1; + } + state->held_decision_lease = &state->decision_lease; +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_after_patch_decision_lease_acquire(); +#endif + prepared_value = state->lazy_completion ? state->resolved_target : + request->slot_current_value; + if (!prepared_value || + production_slot_load(request->slot_addr, &fresh_value, state) != 0) { + production_release_decision_lease(state); + return -1; + } + if (fresh_value != prepared_value) { + state->final_stale_slot_value = fresh_value; + state->preserve_guest_after_final_slot_stale = 1; + production_release_decision_lease(state); + return -1; + } + request->slot_current_value_present = 1; + request->slot_current_value = fresh_value; + kzt_owner_resolver_init(&owner_resolution); + if (kzt_owner_resolver_resolve_current( + KztGuestRegistryForContext(state->context), fresh_value, + request->expected_guest_target, &owner_resolution) != 0 || + owner_resolution.status != KZT_OWNER_RESOLVER_RESOLVED || + owner_resolution.owner_match != KZT_PATCH_OWNER_MATCH) { + production_release_decision_lease(state); + return -1; + } + request->current_owner = owner_resolution.current_owner; + request->owner_match = owner_resolution.owner_match; + if (kzt_guest_library_loader_quiescence_try_acquire( + KztGuestLibraryBindingsForContext(state->context), + &state->loader_quiescence_lease) != 0 || + kzt_guest_registry_context_get_main_namespace_head( + &state->context->kzt_guest_registry_context, + &namespace_head) != 0 || + !state->runtime_view_valid || + production_symbol_scope_request( + state->context, state->head, request->source.generation, + namespace_head, &state->runtime_view, &reader_ops, + request->symbol_index, request->symbol_name, + request->version_evidence, request->version, + &state->symbol_scope_request) != 0 || + kzt_guest_symbol_scope_check( + &state->symbol_scope_request, + owner_resolution.current_owner.link_map_addr, + fresh_value, + &reader_ops, &state->symbol_scope_proof) != + KZT_GUEST_SYMBOL_SCOPE_SAFE) { + production_release_decision_lease(state); + return -1; + } + memset(&decision, 0, sizeof(decision)); + decision.source = request->source; + decision.dynamic_view_available = request->dynamic_view_available; + decision.dynamic_view_generation = request->dynamic_view_generation; + decision.slot_addr = request->slot_addr; + decision.slot_current_value = request->slot_current_value; + decision.current_owner = request->current_owner; + decision.owner_match = request->owner_match; + if (production_prevalidate_write_evidence(&decision, state) != 0) { + production_release_decision_lease(state); + return -1; + } + state->prevalidated_decision = decision; + state->prevalidated_decision_valid = 1; + return 0; +} + +static int production_preserve_guest_after_bridge_failure( + uintptr_t *value, void *opaque) +{ + kzt_production_jump_slot_state_t *state = opaque; + + if (!state || !value || !state->preserve_guest_after_final_slot_stale) { + return 0; + } + *value = state->final_stale_slot_value; + return 1; +} + static int production_enrich_bridge( kzt_rela_immediate_candidate_request_t *request, library_t *held_provider, void *opaque) { kzt_production_jump_slot_state_t *state = opaque; int status; + int provider_prepared; + + if (production_acquire_and_validate_bridge_evidence(state, request) != 0) { + return -1; + } state->failure_stage = "BRIDGE_EVIDENCE"; memset(&state->wrapper_provider, 0, sizeof(state->wrapper_provider)); - status = state->discover_bridge_from_provider ? - kzt_rela_runtime_wrapper_provider_discover_with_version_evidence( - state->context, held_provider, request->symbol_name, - request->version_evidence, request->version, - &state->wrapper_provider) : - kzt_rela_runtime_wrapper_provider_prepare_with_version_evidence( - state->context, held_provider, state->resolved_target, - request->symbol_name, request->version_evidence, - request->version, - &state->wrapper_provider); - - if (status <= 0) { - /* Keep source, dynamic, current-owner, and symbol evidence in the - * same request. The planner records the missing wrapper/provider as - * unsupported and the route performs the legacy fallback once. */ - status = production_enrich(request, NULL, - &state->bridge_enrich_result, state); - if (status == 0) { - state->last_request = *request; - production_shadow_runtime_candidate(state, request, NULL); - } - return status; + status = kzt_rela_runtime_wrapper_provider_discover_with_version_evidence( + state->context, held_provider, request->symbol_name, + request->version_evidence, request->version, + &state->wrapper_provider); + provider_prepared = status > 0; + if (provider_prepared && + kzt_rela_runtime_wrapper_provider_bind_retained_handle( + &state->wrapper_provider, state->retained_provider_handle) != 0) { + production_release_decision_lease(state); + return -1; } - pthread_mutex_lock(&state->context->kzt_bridge_mutex); - status = production_enrich(request, &state->wrapper_provider, - &state->bridge_enrich_result, state); - pthread_mutex_unlock(&state->context->kzt_bridge_mutex); - if (status == 0) { - state->last_request = *request; - state->failure_stage = "SOURCE_IDENTITY"; - production_shadow_runtime_candidate( - state, request, &state->wrapper_provider); + /* A created bridge can remain cached when a later permission or CAS + * step fails; it was created only after the evidence was valid. */ + /* The base enrichment already established Registry, Dynamic View, and + * owner evidence. Reuse it for one post-validation wrapper enrichment. */ + status = production_enrich_wrapper_only( + request, provider_prepared ? &state->wrapper_provider : NULL, + &state->bridge_enrich_result, state); + if (status != 0) { + production_release_decision_lease(state); + return -1; } - return status; + state->last_request = *request; + state->failure_stage = "SOURCE_IDENTITY"; + production_shadow_runtime_candidate( + state, request, + provider_prepared ? &state->wrapper_provider : NULL); + return 0; } static int production_validate_source_identity( const kzt_rela_immediate_candidate_request_t *request, void *opaque) { kzt_production_jump_slot_state_t *state = opaque; - kzt_guest_object_snapshot_t *snapshot = NULL; - int valid = 0; - state->failure_stage = "SOURCE_IDENTITY"; if (!request || !request->source.known || !request->source.link_map_addr || !request->source.generation || + !state->held_decision_lease || !state->held_decision_lease->active || + !state->prevalidated_decision_valid || + state->held_decision_lease->link_map_addr != + request->source.link_map_addr || + state->held_decision_lease->generation != request->source.generation || + request->dynamic_view_generation != request->source.generation || + request->owner_match != KZT_PATCH_OWNER_MATCH || + !request->current_owner.known || (state->required_source_link_map && (request->source.link_map_addr != state->required_source_link_map || - request->source.generation != state->required_source_generation)) || - kzt_guest_registry_find_by_link_map( - KztGuestRegistryForContext(state->context), - request->source.link_map_addr, &snapshot) != 0 || !snapshot || - snapshot->generation != request->source.generation || - snapshot->namespace_id.status != KZT_GUEST_FIELD_OK || - snapshot->namespace_id.value != 0) { - goto out; + request->source.generation != state->required_source_generation))) { + return 0; } - valid = 1; state->failure_stage = "WRITER"; -out: - kzt_guest_object_snapshot_free(snapshot); - return valid; + return 1; } static kzt_jump_slot_route_writer_status_t production_try_writer( @@ -663,11 +1986,24 @@ static kzt_jump_slot_route_writer_status_t production_try_writer( !slot_ops->validate_generation) { return KZT_JUMP_SLOT_ROUTE_WRITER_PRESERVE; } - if (kzt_rela_immediate_jump_slot_try_write( - request, guard, slot_ops, &state->writer_result) != 0) { + if (!state->held_decision_lease || !state->held_decision_lease->active) { + return KZT_JUMP_SLOT_ROUTE_WRITER_PRESERVE; + } + status = kzt_rela_immediate_jump_slot_try_write( + request, guard, slot_ops, &state->writer_result); + production_release_decision_lease(state); + if (status != 0) { return KZT_JUMP_SLOT_ROUTE_WRITER_ERROR; } state->last_request = *request; + if (state->writer_result.record.result == + KZT_PATCH_SPIKE_RESULT_ROLLED_BACK) { + return KZT_JUMP_SLOT_ROUTE_WRITER_ROLLED_BACK; + } + if (state->writer_result.record.result == + KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE) { + return KZT_JUMP_SLOT_ROUTE_WRITER_UNRECOVERABLE; + } if (state->writer_result.record.action == KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST) { return KZT_JUMP_SLOT_ROUTE_WRITER_PRESERVE; @@ -758,6 +2094,7 @@ static int production_jump_slot_route( uintptr_t required_source_link_map, unsigned long required_source_generation, int discover_bridge_from_provider, + const kzt_guest_registry_source_lease_t *held_source_lease, kzt_jump_slot_route_result_t *route_result) { kzt_jump_slot_route_input_t input; @@ -773,8 +2110,6 @@ static int production_jump_slot_route( input.enabled = 1; input.preserve_observed_on_failure = preserve_observed_on_failure; input.expected_guest_target_present = expected_guest_target_present; - input.resolved_target_matches_legacy = - resolved_target == legacy_target; input.resolved_provider = resolved_provider; input.request.relocation_type = ELF64_R_TYPE(rela->r_info); input.request.table_kind = need_resolv_present ? KZT_PATCH_TABLE_PLT_RELA : @@ -801,13 +2136,17 @@ static int production_jump_slot_route( input.request.version = version; state = (kzt_production_jump_slot_state_t){ .context = context, + .resolved_provider = resolved_provider, .initial_request = input.request, .slot_current_value_is_unresolved_stub = slot_current_value_is_unresolved_stub, .resolved_target = resolved_target, .required_source_link_map = required_source_link_map, .required_source_generation = required_source_generation, + .head = head, .discover_bridge_from_provider = discover_bridge_from_provider, + .held_source_lease = held_source_lease, + .lazy_completion = held_source_lease != NULL, .failure_stage = "ROUTE_PRECONDITIONS", }; ops = (kzt_jump_slot_route_ops_t){ @@ -816,20 +2155,28 @@ static int production_jump_slot_route( .release_exact_provider = production_release_exact, .enrich_bridge = production_enrich_bridge, .validate_source_identity = production_validate_source_identity, + .preserve_guest_after_bridge_failure = + production_preserve_guest_after_bridge_failure, .try_native_writer = production_try_writer, .load_slot = production_slot_load, .compare_exchange_slot = production_slot_cas, .begin_slot_write = production_slot_begin_write, .end_slot_write = production_slot_end_write, - .validate_write_generation = production_validate_write_generation, + .validate_write_generation = production_validate_prevalidated_write, .opaque = &state, }; status = kzt_jump_slot_route_apply(&input, &ops, route_result); + state.retained_provider_handle = NULL; + production_release_decision_lease(&state); if (status != 0) { + kzt_guest_library_loader_quiescence_release( + &state.loader_quiescence_lease); kzt_guest_registry_source_lease_release(&state.source_lease); return -1; } production_emit_diagnostic(&state, route_result); + kzt_guest_library_loader_quiescence_release( + &state.loader_quiescence_lease); kzt_guest_registry_source_lease_release(&state.source_lease); printf_log(LOG_DEBUG, @@ -861,9 +2208,9 @@ int kzt_production_jump_slot_route( need_resolv_present, entry_index, rela, slot, slot_current_value, slot_current_value_is_unresolved_stub, symbol_index, symbol_name, version && version[0] ? KZT_SYMBOL_VERSION_VERSIONED : - KZT_SYMBOL_VERSION_UNKNOWN, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, version, expected_guest_target_present, expected_guest_target, - legacy_target, 0, 0, 0, 0, route_result); + legacy_target, 0, 0, 0, 0, NULL, route_result); } int kzt_production_jump_slot_route_with_version_evidence( @@ -883,7 +2230,388 @@ int kzt_production_jump_slot_route_with_version_evidence( need_resolv_present, entry_index, rela, slot, slot_current_value, slot_current_value_is_unresolved_stub, symbol_index, symbol_name, version_evidence, version, expected_guest_target_present, - expected_guest_target, legacy_target, 0, 0, 0, 0, route_result); + expected_guest_target, legacy_target, 0, 0, 0, 0, NULL, route_result); +} + +int kzt_production_lazy_direct_route( + box64context_t *context, elfheader_t *head, int entry_index, + Elf64_Rela *rela, uint64_t *slot, uintptr_t slot_current_value, + unsigned long symbol_index, const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, const char *version, + kzt_lazy_direct_route_result_t *result) +{ + kzt_lazy_direct_production_state_t state; + kzt_guest_registry_lazy_source_t source; + kzt_guest_registry_address_match_t provider_match; + kzt_rela_immediate_candidate_request_t request; + kzt_lazy_prebind_record_t prebind_key; + kzt_guest_dynamic_view_t cached_view; + kzt_guest_field_status_t cached_view_status; + kzt_lazy_direct_route_input_t input; + kzt_lazy_direct_route_ops_t ops; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = production_read_guest_memory, + }; + kzt_lazy_direct_timing_t timing = { 0 }; + uintptr_t namespace_head = 0; + size_t relocation_count; + unsigned long cached_view_generation; + int quiescence_status; + int timing_enabled; + + if (!result) { + return -1; + } + memset(result, 0, sizeof(*result)); + result->status = KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED; + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_INVALID_INPUT; + if (!context || !head || + !head->self_link_map || !head->jmprel || head->pltent <= 0 || + !head->DynSym || !head->numDynSym || !rela || !slot || + !slot_current_value || + !symbol_name || !symbol_name[0] || + ELF64_R_TYPE(rela->r_info) != R_X86_64_JUMP_SLOT || + symbol_index >= head->numDynSym || + ELF64_R_SYM(rela->r_info) != symbol_index || + rela->r_offset + head->delta != (uintptr_t)slot || + !kzt_symbol_version_evidence_valid(version_evidence, version) || + !kzt_rela_slot_current_is_unresolved_stub( + slot_current_value, KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED, + head->delta, head->plt, head->plt_end, head->gotplt, + head->gotplt_end)) { + return 0; + } + relocation_count = head->pltsz / head->pltent; + if (entry_index < 0 || (size_t)entry_index >= relocation_count) { + return 0; + } + if (!kzt_lazy_direct_symbol_binding_supported( + head->DynSym[symbol_index].st_info)) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_UNSUPPORTED_SYMBOL_BINDING; + return 0; + } + if (kzt_patch_symbol_must_stay_guest(symbol_name)) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_GUEST_OWNED_SYMBOL; + return 0; + } + + memset(&state, 0, sizeof(state)); + timing_enabled = unlikely(option_kzt_lazy_diagnostics); + if (timing_enabled) { + timing.start = production_lazy_direct_timing_now(); + } + state.context = context; + state.registry = KztGuestRegistryForContext(context); + state.timing = &timing; + state.timing_enabled = timing_enabled; + state.evidence.context = context; + state.evidence.slot_current_value_is_unresolved_stub = 1; + if (!state.registry || + kzt_guest_registry_find_lazy_source( + state.registry, head->self_link_map, &source) != 0 || + !source.generation || source.namespace_id != 0 || + kzt_guest_registry_source_lease_acquire( + state.registry, head->self_link_map, source.generation, + source.namespace_id, &state.source_lease) != 0) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_SOURCE_REJECTED; + return 0; + } + if (timing_enabled) { + timing.source = production_lazy_direct_timing_now(); + } + if (kzt_guest_registry_context_get_main_namespace_head( + &context->kzt_guest_registry_context, &namespace_head) != 0) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_PREEMPTION_UNPROVEN; + goto done; + } + + state.prebind_scope = KztLazyPrebindScopeForContext(context); + if (production_lazy_prebind_record_key( + &prebind_key, head->self_link_map, source.generation, + entry_index, (uintptr_t)slot, slot_current_value, symbol_name, + version_evidence, version) == 0 && + kzt_lazy_prebind_scope_acquire( + state.prebind_scope, &prebind_key, &state.prebind_lease) == 0) { + cached_view_status = KZT_GUEST_FIELD_NOT_PARSED; + cached_view_generation = 0; + if (kzt_guest_registry_find_dynamic_view( + state.registry, head->self_link_map, &cached_view, + &cached_view_status, &cached_view_generation) == 0 && + cached_view_status == KZT_GUEST_FIELD_OK && + cached_view_generation == source.generation && + cached_view.status == KZT_GUEST_DYNAMIC_COMPLETE && + kzt_guest_registry_dynamic_view_matches( + state.registry, head->self_link_map, source.generation, + &cached_view) == 0) { + state.prebind_hit = 1; + state.preemption_proof = state.prebind_lease.record.scope_proof; + state.evidence.runtime_view = cached_view; + state.evidence.runtime_view_valid = 1; + state.evidence.runtime_candidate.symbol_name = symbol_name; + state.evidence.runtime_candidate.version_evidence = + version_evidence; + state.evidence.runtime_candidate.version = version; + state.evidence.runtime_candidate.dynamic_view_generation = + source.generation; + } else { + kzt_lazy_prebind_scope_release(&state.prebind_lease); + } + } + if (!state.prebind_hit) { + memset(&request, 0, sizeof(request)); + request.relocation_type = R_X86_64_JUMP_SLOT; + request.table_kind = KZT_PATCH_TABLE_PLT_RELA; + request.entry_index = (size_t)entry_index; + request.entry_addr = (uintptr_t)rela; + request.source.known = 1; + request.source.link_map_addr = head->self_link_map; + request.source.generation = source.generation; + request.source.map_start = (uintptr_t)head->memory; + request.source.map_end = (uintptr_t)head->memory + head->memsz; + request.source.soname = head->name; + request.source.path = head->path; + request.slot_addr = (uintptr_t)slot; + request.slot_current_value_present = 1; + request.slot_current_value = slot_current_value; + request.symbol_index = symbol_index; + request.symbol_name = symbol_name; + request.version_evidence = version_evidence; + request.version = version; + if (production_collect_runtime_candidate( + &state.evidence, &request) != 0) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_INCOMPLETE_DYNAMIC_VIEW; + goto done; + } + } + if (!state.evidence.runtime_view_valid || + production_symbol_scope_request( + context, head, source.generation, namespace_head, + &state.evidence.runtime_view, &reader_ops, symbol_index, + state.evidence.runtime_candidate.symbol_name, + state.evidence.runtime_candidate.version_evidence, + state.evidence.runtime_candidate.version, + &state.symbol_scope_request) != 0) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_PREEMPTION_UNPROVEN; + goto done; + } + if (timing_enabled) { + timing.candidate = production_lazy_direct_timing_now(); + } + quiescence_status = kzt_guest_library_loader_quiescence_try_acquire( + KztGuestLibraryBindingsForContext(context), + &state.loader_quiescence_lease); + if (timing_enabled) { + timing.quiescence = production_lazy_direct_timing_now(); + } + if (quiescence_status != 0) { + if (!state.prebind_hit) { + (void)kzt_guest_symbol_scope_discover( + &state.symbol_scope_request, &reader_ops, + &state.preemption_proof); + } + if (timing_enabled) { + timing.scope = production_lazy_direct_timing_now(); + } + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_PREEMPTION_UNPROVEN; + goto preemption_diagnostic; + } + + if (!state.prebind_hit) { + if (kzt_guest_symbol_scope_discover( + &state.symbol_scope_request, &reader_ops, + &state.preemption_proof) != KZT_GUEST_SYMBOL_SCOPE_SAFE) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_PREEMPTION_UNPROVEN; + } + } + if (timing_enabled) { + timing.scope = production_lazy_direct_timing_now(); + } + +preemption_diagnostic: + printf_kzt_registry_diagnostics( + "kzt_lazy_preemption schema=1 symbol=%s version=%s " + "candidate_count=%zu scope_complete=%d lookup_order_known=%d " + "reason=%s selected_provider=%p\n", + state.evidence.runtime_candidate.symbol_name, + state.evidence.runtime_candidate.version ? + state.evidence.runtime_candidate.version : "", + state.preemption_proof.candidate_count, + state.preemption_proof.scope_complete, + state.preemption_proof.lookup_order_known, + kzt_guest_symbol_scope_reason_name( + state.preemption_proof.reason), + (void *)state.preemption_proof.selected_provider_link_map); + if (quiescence_status != 0 || + state.preemption_proof.status != KZT_GUEST_SYMBOL_SCOPE_SAFE) { + goto done; + } + + memset(&provider_match, 0, sizeof(provider_match)); + if (kzt_guest_registry_find_live_object( + state.registry, + state.preemption_proof.selected_provider_link_map, + &provider_match) != 0 || + !provider_match.generation || + provider_match.namespace_id_status != KZT_GUEST_FIELD_OK || + provider_match.namespace_id != 0) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_PROVIDER_UNAVAILABLE; + goto done; + } + if (state.prebind_hit && + (!kzt_lazy_prebind_scope_lease_valid(&state.prebind_lease) || + state.preemption_proof.selected_provider_link_map != + state.prebind_lease.record.provider.link_map_addr || + provider_match.generation != + state.prebind_lease.record.provider.generation)) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_PROVIDER_MISMATCH; + goto done; + } + state.provider_key = (kzt_guest_library_binding_key_t) { + .link_map_addr = + state.preemption_proof.selected_provider_link_map, + .generation = provider_match.generation, + .namespace_id = provider_match.namespace_id, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + if (kzt_guest_library_access_lookup( + &context->kzt_guest_library_access, &state.provider_key, + &state.provider_handle) != 0 || + !state.provider_handle.library || + state.provider_handle.object_type != + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) { + kzt_guest_library_handle_release(&state.provider_handle); + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_PROVIDER_UNAVAILABLE; + goto done; + } + state.provider_handle_owned = 1; + state.resolved_provider = state.provider_handle.library; + state.evidence.resolved_provider = state.resolved_provider; + if (timing_enabled) { + timing.provider = production_lazy_direct_timing_now(); + } + + input = (kzt_lazy_direct_route_input_t) { + .enabled = 1, + .preemption_safe = 1, + .namespace_id = source.namespace_id, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + .source = { + .link_map_addr = head->self_link_map, + .generation = source.generation, + }, + .provider = { + .link_map_addr = state.provider_key.link_map_addr, + .generation = state.provider_key.generation, + }, + .source_dynamic_view = &state.evidence.runtime_view, + .source_dynamic_view_generation = + state.evidence.runtime_candidate.dynamic_view_generation, + .symbol = state.evidence.runtime_candidate.symbol_name, + .version_evidence = + state.evidence.runtime_candidate.version_evidence, + .version = state.evidence.runtime_candidate.version, + .slot_addr = (uintptr_t)slot, + .guest_unresolved_slot = slot_current_value, + .expected_current_slot = slot_current_value, + }; + ops = (kzt_lazy_direct_route_ops_t) { + .validate_source = production_lazy_direct_validate_source, + .acquire_provider = production_lazy_direct_acquire_provider, + .release_provider = production_lazy_direct_release_provider, + .find_wrapper_bridge = production_lazy_direct_find_bridge, + .acquire_decision_lease = + production_lazy_direct_acquire_decision_lease, + .release_decision_lease = + production_lazy_direct_release_decision_lease, + .validate_final = production_lazy_direct_validate_final, + .cas_slot = production_lazy_direct_cas_slot, + .opaque = &state, + }; + if (timing_enabled) { + timing.route_start = production_lazy_direct_timing_now(); + } + (void)kzt_lazy_direct_route_apply(&input, &ops, result); + if (timing_enabled) { + timing.route = production_lazy_direct_timing_now(); + } + if (result->status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED) { + uintptr_t slot_after = + __atomic_load_n((uintptr_t *)slot, __ATOMIC_ACQUIRE); + printf_kzt_registry_diagnostics( + "kzt_lazy_direct schema=1 symbol=%s " + "route_status=NATIVE_APPLIED writer_result=APPLIED " + "slot_before=%p slot_after=%p selected_target=%p\n", + input.symbol, (void *)slot_current_value, (void *)slot_after, + (void *)result->selected_target); + } + +done: + if (state.decision_lease.active) { + kzt_guest_registry_patch_decision_lease_release( + &state.decision_lease); + } + if (state.provider_handle_owned) { + kzt_guest_library_handle_release(&state.provider_handle); + } + kzt_lazy_prebind_scope_release(&state.prebind_lease); + kzt_guest_library_loader_quiescence_release( + &state.loader_quiescence_lease); + kzt_guest_registry_source_lease_release(&state.source_lease); + if (timing_enabled) { + timing.done = production_lazy_direct_timing_now(); + fprintf( + stderr, + "kzt_lazy_timing schema=1 symbol=%s " + "source_ns=%" PRIu64 " candidate_ns=%" PRIu64 " " + "quiescence_ns=%" PRIu64 " scope_ns=%" PRIu64 " " + "provider_ns=%" PRIu64 " route_ns=%" PRIu64 " " + "route_prepare_ns=%" PRIu64 " bridge_ns=%" PRIu64 " " + "bridge_discover_ns=%" PRIu64 " bridge_probe_ns=%" PRIu64 " " + "decision_ns=%" PRIu64 " final_ns=%" PRIu64 " " + "cas_ns=%" PRIu64 " " + "cleanup_ns=%" PRIu64 " total_ns=%" PRIu64 " " + "status=%d reason=%d\n", + symbol_name, + production_lazy_direct_timing_delta( + timing.start, timing.source), + production_lazy_direct_timing_delta( + timing.source, timing.candidate), + production_lazy_direct_timing_delta( + timing.candidate, timing.quiescence), + production_lazy_direct_timing_delta( + timing.quiescence, timing.scope), + production_lazy_direct_timing_delta( + timing.scope, timing.provider), + production_lazy_direct_timing_delta( + timing.route_start, timing.route), + production_lazy_direct_timing_delta( + timing.route_start, timing.bridge_start), + production_lazy_direct_timing_delta( + timing.bridge_start, timing.bridge_done), + production_lazy_direct_timing_delta( + timing.bridge_start, timing.bridge_discover_done), + production_lazy_direct_timing_delta( + timing.bridge_discover_done, timing.bridge_done), + production_lazy_direct_timing_delta( + timing.bridge_done, timing.decision_done), + production_lazy_direct_timing_delta( + timing.decision_done, timing.final_done), + production_lazy_direct_timing_delta( + timing.final_done, timing.cas_done), + production_lazy_direct_timing_delta( + timing.route, timing.done), + production_lazy_direct_timing_delta( + timing.start, timing.done), + result->status, result->reason); + } + return 0; } static elfheader_t *production_find_source_head( @@ -1017,7 +2745,7 @@ int kzt_production_lazy_route_guest_target_leased( symbol_name, version_evidence, version_name, 1, guest_target, guest_target, 1, pending->source_link_map, pending->source_generation, - 1, + 1, source_lease, &route_result) != 0) { kzt_guest_library_handle_release(&provider_handle); result->status = KZT_LAZY_BINDING_ROUTE_ERROR; @@ -1033,6 +2761,12 @@ int kzt_production_lazy_route_guest_target_leased( case KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH: result->status = KZT_LAZY_BINDING_ROUTE_CAS_MISMATCH; break; + case KZT_JUMP_SLOT_ROUTE_WRITE_ROLLED_BACK: + result->status = KZT_LAZY_BINDING_ROUTE_WRITE_ROLLED_BACK; + break; + case KZT_JUMP_SLOT_ROUTE_UNRECOVERABLE: + result->status = KZT_LAZY_BINDING_ROUTE_UNRECOVERABLE; + break; case KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED: default: result->status = KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED; diff --git a/target/i386/latx/context/kzt_lazy_direct_route.c b/target/i386/latx/context/kzt_lazy_direct_route.c new file mode 100644 index 00000000000..a1b8e011881 --- /dev/null +++ b/target/i386/latx/context/kzt_lazy_direct_route.c @@ -0,0 +1,203 @@ +#include "kzt_lazy_direct_route.h" + +#include + +#include "elf.h" + +int kzt_lazy_direct_symbol_binding_supported(unsigned char st_info) +{ + return ELF64_ST_BIND(st_info) == STB_GLOBAL; +} + +static void kzt_lazy_direct_route_result_init( + kzt_lazy_direct_route_result_t *result) +{ + if (!result) { + return; + } + memset(result, 0, sizeof(*result)); + result->status = KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED; + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_INVALID_INPUT; +} + +static int kzt_lazy_direct_route_address_field_valid( + const kzt_guest_dynamic_field_t *field) +{ + return field && field->present && field->value && + field->address_semantics == KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS; +} + +static int kzt_lazy_direct_route_scalar_field_valid( + const kzt_guest_dynamic_field_t *field) +{ + return field && field->present && field->value && + field->address_semantics == KZT_GUEST_DYNAMIC_SCALAR; +} + +static int kzt_lazy_direct_route_dynamic_view_complete( + const kzt_lazy_direct_route_input_t *input) +{ + const kzt_guest_dynamic_view_t *view; + + if (!input || !input->source_dynamic_view) { + return 0; + } + view = input->source_dynamic_view; + if (!view->dynamic_addr || + view->status != KZT_GUEST_DYNAMIC_COMPLETE || + !view->entry_count || !view->has_null || + !kzt_lazy_direct_route_address_field_valid(&view->symtab) || + !kzt_lazy_direct_route_address_field_valid(&view->strtab) || + !kzt_lazy_direct_route_scalar_field_valid(&view->syment) || + !kzt_lazy_direct_route_address_field_valid(&view->jmprel) || + !kzt_lazy_direct_route_scalar_field_valid(&view->pltrelsz) || + !kzt_lazy_direct_route_scalar_field_valid(&view->pltrel) || + !kzt_lazy_direct_route_address_field_valid(&view->pltgot)) { + return 0; + } + if (input->version_evidence != KZT_SYMBOL_VERSION_VERSIONED) { + return 1; + } + return kzt_lazy_direct_route_address_field_valid(&view->versym) && + kzt_lazy_direct_route_address_field_valid(&view->verneed) && + kzt_lazy_direct_route_scalar_field_valid(&view->verneednum); +} + +static int kzt_lazy_direct_route_ops_complete( + const kzt_lazy_direct_route_ops_t *ops) +{ + return ops && ops->validate_source && ops->acquire_provider && + ops->release_provider && ops->find_wrapper_bridge && + ops->acquire_decision_lease && + ops->release_decision_lease && ops->validate_final && + ops->cas_slot; +} + +static int kzt_lazy_direct_route_provider_exact( + const kzt_lazy_direct_route_input_t *input, + const kzt_lazy_direct_route_provider_t *provider) +{ + return input && provider && provider->handle && + provider->link_map_addr == input->provider.link_map_addr && + provider->generation == input->provider.generation && + provider->namespace_id == input->namespace_id && + provider->namespace_kind == input->namespace_kind; +} + +kzt_lazy_direct_route_status_t kzt_lazy_direct_route_apply( + const kzt_lazy_direct_route_input_t *input, + const kzt_lazy_direct_route_ops_t *ops, + kzt_lazy_direct_route_result_t *result) +{ + kzt_lazy_direct_route_provider_t provider = { 0 }; + kzt_lazy_direct_route_bridge_t bridge = { 0 }; + kzt_lazy_direct_route_lease_t lease = { 0 }; + kzt_lazy_direct_route_cas_status_t cas_status; + int provider_acquired = 0; + int lease_acquired = 0; + + kzt_lazy_direct_route_result_init(result); + if (!input || !result || !kzt_lazy_direct_route_ops_complete(ops)) { + return KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED; + } + if (!input->enabled) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_DISABLED; + return result->status; + } + if (!input->preemption_safe) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_PREEMPTION_UNPROVEN; + return result->status; + } + if (input->namespace_kind != KZT_GUEST_LIBRARY_NAMESPACE_MAIN || + input->namespace_id != 0) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_NON_MAIN_NAMESPACE; + return result->status; + } + if (!input->source.link_map_addr || !input->source.generation || + !input->provider.link_map_addr || !input->provider.generation || + input->source_dynamic_view_generation != input->source.generation || + !input->symbol || !input->symbol[0] || !input->slot_addr || + !input->guest_unresolved_slot || !input->expected_current_slot) { + return result->status; + } + if (kzt_patch_symbol_must_stay_guest(input->symbol)) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_GUEST_OWNED_SYMBOL; + return result->status; + } + if (!kzt_lazy_direct_route_dynamic_view_complete(input)) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_INCOMPLETE_DYNAMIC_VIEW; + return result->status; + } + if (!kzt_symbol_version_evidence_valid(input->version_evidence, + input->version)) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_INVALID_VERSION; + return result->status; + } + if (ops->validate_source(input, ops->opaque) <= 0) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_SOURCE_REJECTED; + return result->status; + } + if (ops->acquire_provider(input, &provider, ops->opaque) != 0) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_PROVIDER_UNAVAILABLE; + return result->status; + } + provider_acquired = 1; + if (!kzt_lazy_direct_route_provider_exact(input, &provider)) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_PROVIDER_MISMATCH; + goto done; + } + if (ops->find_wrapper_bridge(input, &provider, &bridge, + ops->opaque) != 0 || + !bridge.target) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_BRIDGE_UNAVAILABLE; + goto done; + } + if (!kzt_symbol_version_evidence_matches( + input->version_evidence, input->version, + bridge.version_evidence, bridge.version)) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_BRIDGE_VERSION_MISMATCH; + goto done; + } + if (ops->acquire_decision_lease( + input, &provider, &lease, ops->opaque) != 0) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_LEASE_UNAVAILABLE; + goto done; + } + lease_acquired = 1; + if (!lease.active) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_LEASE_UNAVAILABLE; + goto done; + } + if (ops->validate_final( + input, &provider, &bridge, &lease, ops->opaque) <= 0) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_FINAL_VALIDATION_FAILED; + goto done; + } + cas_status = ops->cas_slot( + input->slot_addr, input->expected_current_slot, + bridge.target, &lease, ops->opaque); + if (cas_status == KZT_LAZY_DIRECT_ROUTE_CAS_APPLIED) { + result->status = KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED; + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_NATIVE_APPLIED; + result->selected_target = bridge.target; + } else if (cas_status == KZT_LAZY_DIRECT_ROUTE_CAS_MISMATCH) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_CAS_MISMATCH; + } else { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_CAS_ERROR; + } + +done: + if (lease_acquired) { + ops->release_decision_lease(&lease, ops->opaque); + } + if (provider_acquired) { + ops->release_provider(&provider, ops->opaque); + } + return result->status; +} diff --git a/target/i386/latx/context/library.c b/target/i386/latx/context/library.c index 67d7f48c422..27325e8f988 100755 --- a/target/i386/latx/context/library.c +++ b/target/i386/latx/context/library.c @@ -227,6 +227,13 @@ static void initNativeLib(library_t *lib, box64context_t* context) { lib->getnoweak = wrappedlibs[i].getnoweak; lib->getlocal = NativeLib_GetLocal; lib->type = LIB_WRAPPED; +#ifdef CONFIG_LATX_KZT + /* Exact KZT bindings are optional metadata. Track the library + * before callback publication; failure leaves the legacy loader + * path unchanged and exact lookup unavailable. */ + (void)kzt_guest_library_track( + KztGuestLibraryBindingsForContext(context), lib); +#endif // Call librarian to load all dependant elf if(AddNeededLib(context->maplib, &lib->needed, lib, 0, 0, (const char**)lib->priv.w.neededlibs, lib->priv.w.needed, context)) { printf_log(LOG_INFO, "Error: loading a needed libs in elf %s\n", lib->name); @@ -391,20 +398,57 @@ int ReloadLibrary(library_t* lib) return 1; } } - lib->active = 1; #ifdef CONFIG_LATX_KZT - /* Publish only after every reload step has succeeded. On failure the - * binding lifecycle remains DEAD and no exact pair is left pending. */ + if (lib->type == LIB_WRAPPED) { + kzt_guest_library_bindings_t *bindings = + KztGuestLibraryBindingsForContext(lib->context); + kzt_guest_wrapper_source_proof_t source_proof = { 0 }; + kzt_guest_library_binding_result_t publication; + + if (!lib->x86linkmap || + kzt_guest_library_wrapper_source_acquire( + lib->context, (uintptr_t)lib->x86linkmap, + lib->name, lib->name, &source_proof) != 0) { + return 1; + } + if (kzt_guest_library_reactivate(bindings, lib) != 0) { + kzt_guest_library_wrapper_source_release(&source_proof); + return 1; + } + publication = kzt_guest_library_note_loader_pair( + lib->context, (uintptr_t)lib->x86linkmap, lib, &source_proof); + if (publication != KZT_GUEST_LIBRARY_BINDING_ADDED && + publication != KZT_GUEST_LIBRARY_BINDING_UNCHANGED) { + kzt_guest_library_inactivate( + bindings, NULL, lib, (uintptr_t)lib->x86linkmap); + kzt_guest_library_wrapper_source_release(&source_proof); + return 1; + } + lib->active = 1; + kzt_guest_library_wrapper_source_release(&source_proof); + return 0; + } + lib->active = 1; if (kzt_guest_library_reactivate( KztGuestLibraryBindingsForContext(lib->context), lib) == 0 && - lib->x86linkmap) - kzt_guest_library_note_loader_pair( - lib->context, (uintptr_t)lib->x86linkmap, lib); + lib->x86linkmap) { + (void)kzt_guest_library_note_loader_pair( + lib->context, (uintptr_t)lib->x86linkmap, lib, NULL); + } +#else + lib->active = 1; #endif return 0; } void InactiveLibrary(library_t* lib) { + if (!lib) return; +#ifdef CONFIG_LATX_KZT + kzt_guest_library_inactivate( + KztGuestLibraryBindingsForContext(lib->context), + KztGuestRegistryForContext(lib->context), lib, + (uintptr_t)lib->x86linkmap); +#endif lib->active = 0; } @@ -412,6 +456,13 @@ void Free1Library(library_t **lib) { if(!(*lib)) return; +#ifdef CONFIG_LATX_KZT + kzt_guest_library_unbind( + KztGuestLibraryBindingsForContext((*lib)->context), + KztGuestRegistryForContext((*lib)->context), *lib, + (uintptr_t)(*lib)->x86linkmap); +#endif + //if((*lib)->type==1) { // elfheader_t *elf_header = (*lib)->context->elfs[(*lib)->priv.n.elf_index]; // RunElfFini(elf_header, env); @@ -798,6 +849,69 @@ static int getSymbolInSymbolMaps(library_t*lib, const char* name, khint_t pre_k, return 0; } +static int wrapper_manifest_symbol_is_function(library_t *lib, + const char *name, + khint_t pre_k) +{ + khint_t k; + + k = pre_kh_get(datamap, lib->datamap, name, pre_k); + if (k != kh_end(lib->datamap)) return 0; + k = pre_kh_get(datamap, lib->wdatamap, name, pre_k); + if (k != kh_end(lib->wdatamap)) return 0; + k = pre_kh_get(datamap, lib->mydatamap, name, pre_k); + if (k != kh_end(lib->mydatamap)) return 0; + + k = pre_kh_get(symbolmap, lib->mysymbolmap, name, pre_k); + if (k != kh_end(lib->mysymbolmap)) return 1; + k = pre_kh_get(symbolmap, lib->stsymbolmap, name, pre_k); + if (k != kh_end(lib->stsymbolmap)) return 1; + k = pre_kh_get(symbolmap, lib->symbolmap, name, pre_k); + if (k != kh_end(lib->symbolmap)) return 1; + k = pre_kh_get(symbolmap, lib->wmysymbolmap, name, pre_k); + if (k != kh_end(lib->wmysymbolmap)) return 1; + k = pre_kh_get(symbolmap, lib->wsymbolmap, name, pre_k); + if (k != kh_end(lib->wsymbolmap)) return 1; + k = pre_kh_get(symbol2map, lib->symbol2map, name, pre_k); + return k != kh_end(lib->symbol2map); +} + +int GetLibFunctionSymbolStartEnd(library_t *lib, const char *name, + khint_t pre_k, uintptr_t *start, + uintptr_t *end) +{ + khint_t k; + + if (!lib || lib->type != LIB_WRAPPED || !name || !name[0] || + !start || !end || !lib->active) { + return 0; + } + if (!pre_k) pre_k = kh_str_hash_func(name); + if (!wrapper_manifest_symbol_is_function(lib, name, pre_k)) { + return 0; + } + k = pre_kh_get(bridgemap, lib->bridgemap, name, pre_k); + if (k != kh_end(lib->bridgemap)) { + *start = kh_value(lib->bridgemap, k).start; + *end = kh_value(lib->bridgemap, k).end; + return 1; + } + if (!getSymbolInSymbolMaps(lib, name, pre_k, 0, start, end)) { + return 0; + } + *end += *start; + { + char *symbol = box_strdup(name); + int ret; + + k = kh_put(bridgemap, lib->bridgemap, symbol, &ret); + kh_value(lib->bridgemap, k).name = symbol; + kh_value(lib->bridgemap, k).start = *start; + kh_value(lib->bridgemap, k).end = *end; + } + return 1; +} + int getSymbolInMaps(library_t *lib, const char* name, khint_t pre_k, int noweak, uintptr_t *addr, uintptr_t *size, int version, const char* vername, int local) { if(!lib->active) diff --git a/target/i386/latx/context/meson.build b/target/i386/latx/context/meson.build index b24ade609f2..f92d5d5dc7c 100644 --- a/target/i386/latx/context/meson.build +++ b/target/i386/latx/context/meson.build @@ -72,23 +72,34 @@ my_file = files( 'wrapperdebug.c', 'wrapper.c', 'kzt_guest_registry.c', + 'kzt_loader_event_hook.c', + 'kzt_loader_lifecycle_snapshot.c', + 'kzt_per_object_got_plt.c', 'kzt_guest_registry_context.c', 'kzt_guest_library_binding.c', 'kzt_jump_slot_route.c', 'kzt_jump_slot_production.c', + 'kzt_lazy_direct_route.c', + 'kzt_lazy_prebind_scope.c', 'kzt_lazy_binding.c', 'kzt_lazy_diagnostics.c', 'kzt_plt_resolver_adapter.c', 'kzt_guest_library_adapter.c', + 'kzt_guest_dl_api.c', + 'kzt_guest_dl_init.c', + 'kzt_lifecycle_diagnostics.c', 'kzt_bridge_exact.c', 'kzt_guest_link_map_reader.c', 'kzt_guest_dynamic.c', + 'kzt_guest_dynsym_lookup.c', + 'kzt_guest_symbol_scope.c', 'kzt_guest_dynamic_diagnostics.c', 'kzt_observation_adapter.c', 'kzt_patch_planner.c', 'kzt_runtime_got_plt_candidate.c', 'kzt_runtime_candidate_shadow.c', 'kzt_rela_immediate_candidate.c', + 'kzt_rela_stub_detector.c', 'kzt_rela_request_enricher.c', 'kzt_rela_diagnostics.c', 'kzt_rela_runtime_bridge.c', diff --git a/target/i386/latx/context/myalign.c b/target/i386/latx/context/myalign.c index c6d6a31aa85..ed90b6524f4 100644 --- a/target/i386/latx/context/myalign.c +++ b/target/i386/latx/context/myalign.c @@ -13,13 +13,20 @@ #include "elfmap.h" #include "elfloader.h" #include "elfloader_private.h" +#include "kzt_loader_event_hook.h" #ifdef CONFIG_LATX_KZT +#include "kzt_guest_dl_api.h" #include "kzt_observation_adapter.h" #include "kzt_guest_library_adapter.h" +#include "kzt_jump_slot_production.h" +#include "kzt_lifecycle_diagnostics.h" +#include "kzt_loader_lifecycle_snapshot.h" +#include "kzt_per_object_got_plt.h" #endif #include #include #include +#include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-prototypes" @@ -2198,8 +2205,15 @@ int kzt_init(char** argv, int argc,char** target_argv, int target_argc, struct x86_ld_info { int reg; intptr_t addr; +#ifdef CONFIG_LATX_KZT + uintptr_t debug_state_addr; + uintptr_t r_debug_addr; +#endif }; static struct x86_ld_info * ld_info = NULL; +#ifdef CONFIG_LATX_KZT +static kzt_loader_event_hook_t kzt_loader_event_hook; +#endif extern void* x86free; extern void* x86realloc; extern void* x86pthread_setcanceltype; @@ -2270,36 +2284,6 @@ int collectX86free(elfheader_t* h) AddMallocMap(my_context, m); return 0; } -static int findx86pthread_setcanceltype(elfheader_t* h) -{ - if (x86pthread_setcanceltype ||x64free_fini) { - return 0; - } - Elf64_Sym *sym = NULL; - for (size_t i=0; inumDynSym; ++i) { - sym = h->DynSym+i; - if (h->DynSym[i].st_shndx != SHN_UNDEF && sym->st_value) { - const char * symname = h->DynStr+sym->st_name; - if (!strcmp(symname, "malloc")) { - x86pthread_setcanceltype = (void*)sym->st_value+h->delta; - printf_log(LOG_DEBUG, "latx x86pthread_setcanceltype=%p type=0x%x from %s\n", x86pthread_setcanceltype, ELF64_ST_TYPE(sym->st_info), h->path); - return 0; - } - } - } - return -1; -} -static char* kzt_find_realsofilepath(char * filepath, char *filetmp) -{ - snprintf(filetmp , PATH_MAX, "%s%s", interp_prefix, filepath); - if (FileExist(filetmp, IS_FILE)) { - printf_log(LOG_DEBUG, "%s filename change to \"%s\"\n", __func__, filepath); - return filetmp; - } - //file must exist for kzt_tb_callback. - return filepath; -} -extern const char* libcName; #ifdef CONFIG_LATX_KZT static int kzt_callback_read_memory(uintptr_t guest_addr, void *dst, @@ -2327,141 +2311,99 @@ static int kzt_callback_read_memory(uintptr_t guest_addr, } #endif -static int kzt_tb_callback_legacy(uintptr_t link_map_addr, void *opaque) +#ifdef CONFIG_LATX_KZT +static int kzt_tb_callback_materialize_binding(uintptr_t link_map_addr, + void *opaque) { const kzt_guest_library_loader_scope_t *loader_scope = opaque; -#ifdef CONFIG_LATX_KZT - int loader_scope_active = loader_scope && loader_scope->bindings && - loader_scope->identity && loader_scope->cookie; -#endif - struct link_map_x64 * my_lm = (struct link_map_x64 *)link_map_addr; - elfheader_t *h = NULL; -#ifdef CONFIG_LATX_KZT - int relocate_result; - int relocate_plt_result; -#endif - uintptr_t map_start = 0; - uintptr_t map_end = 0; - if (!my_lm) { - printf_log(LOG_DEBUG, "error %d debug %s link_map = %p\n", - getpid(), __func__, my_lm); - return 0; - } - if (!my_lm->l_name) { - printf_log(LOG_DEBUG, - "error %d debug %s link_map = %p{0x%lx, }\n", - getpid(), __func__, my_lm, my_lm->l_addr); + struct link_map_x64 *link_map = (struct link_map_x64 *)link_map_addr; + kzt_guest_wrapper_source_proof_t source_proof = { 0 }; + const char *name; + const char *basename; + library_t *library = NULL; + int loader_scope_active; + + if (!link_map || !link_map->l_name || !link_map->l_name[0]) { return 0; } - if ((!my_lm->l_name[0] || !my_lm->l_addr) && - my_lm->l_addr != info1.load_addr) { - printf_log(LOG_DEBUG, "error %d debug %s link_map = %p{0x%lx, %s}\n", getpid(), __func__, my_lm, my_lm->l_addr, my_lm->l_name); + name = link_map->l_name; + basename = strrchr(name, '/'); + basename = basename ? basename + 1 : name; + if (!basename[0] || !FindLibIsWrapped((char *)basename) || + kzt_guest_library_wrapper_source_acquire( + my_context, link_map_addr, name, basename, &source_proof) != 0) { return 0; } - printf_log(LOG_DEBUG, "%d debug %s link_map = %p{0x%lx, %s}\n", getpid(), __func__, my_lm, my_lm->l_addr, my_lm->l_name); - char * rfilename = my_lm->l_name; - char filetmp[PATH_MAX] = {0}; - if (rfilename[0] == '/') { - rfilename = kzt_find_realsofilepath(rfilename, filetmp); - } - if (strstr(basename(rfilename), "ld-linux-x86-64.so.2")) { - FILE *loader_file = fopen(rfilename, "rb"); - elfheader_t *loader_header = loader_file ? - LoadAndCheckElfHeader(loader_file, rfilename, 0) : NULL; - - if (loader_file) { - fclose(loader_file); - } - if (loader_header && - GetElfLoadRange(loader_header->PHEntries, - loader_header->numPHEntries, my_lm->l_addr, - TARGET_PAGE_SIZE, &map_start, &map_end) == 0) { - AddDebugInfo(LIB_EMULATED, my_lm->l_name, - map_start, map_end); - } else { - printf_log(LOG_INFO, - "KZT: cannot determine loader range for %s\n", - my_lm->l_name); - } - FreeElfHeader(&loader_header); - return 0; - } - char * rbasename = basename(rfilename); - library_t* lib = NULL; (void)AddNeededLibWithLibrary( my_context->maplib, &my_context->neededlibs, NULL, 0, 1, - rbasename, my_context, &lib); -#ifdef CONFIG_LATX_KZT - /* AddNeededLibWithLibrary returns the exact instance selected/created by - * this callback operation; no basename lookup is used for binding. */ - if (loader_scope_active) { - kzt_guest_library_note_loader_pair_pending( - my_context, loader_scope, link_map_addr, lib); - } -#endif - if (!lib && (!strncmp(rbasename, "libSDL", 6)||!strncmp(rbasename, "libCgGL.so", strlen("libCgGL.so")))) { - printf_log(LOG_DEBUG, "%s libSDL need libGL.so.1\n", __func__); - const char* libs[] = {"libGL.so.1"}; - AddNeededLib(my_context->maplib, &my_context->neededlibs, NULL, 0, 1, libs, 1, my_context); - } - FILE *f = fopen(rfilename, "rb"); - if(!f) { - printf_log(LOG_INFO, "%s Error: Cannot open \"%s\"\n", __func__, rfilename); - return 0; - } - h = LoadAndCheckElfHeader(f, rfilename, 0); - if (!h) { - fclose(f); - printf_log(LOG_INFO, "%s Error: Cannot load ELF header for \"%s\"\n", - __func__, rfilename); + basename, my_context, &library); + if (!library) { + kzt_guest_library_wrapper_source_release(&source_proof); return 0; } - ElfHeadReFix(h, my_lm->l_addr); - fclose(f); - collectX86free(h); - if(!x86free &&!strcmp(rbasename, libcName)) { - struct malloc_map * m = SearchMallocMap(my_context, (char *)libcName); - lsassert(m); - x86free = m->freep; - x86realloc = m->reallocp; - } - if(!x86pthread_setcanceltype &&!strcmp(rbasename, libcName)) { - findx86pthread_setcanceltype(h); - } - AddElfHeader(my_context, h); - LoadNeededLibs(h, my_context->maplib, &my_context->neededlibs, NULL, 0, 0, my_context); -#ifdef CONFIG_LATX_KZT - relocate_result = RelocateElf(my_context->maplib, NULL, 0, h); - relocate_plt_result = RelocateElfPlt(my_context->maplib, NULL, 0, h); - /* Startup loading has no dlopen scope to publish the pair later. The - callback gate remains held until this function returns. Failed - relocation keeps the legacy fail-open path without publishing. */ - if (lib && !loader_scope_active && relocate_result == 0 && - relocate_plt_result == 0) { - kzt_guest_library_note_loader_pair(my_context, link_map_addr, lib); - } -#else - RelocateElf(my_context->maplib, NULL, 0, h); - RelocateElfPlt(my_context->maplib, NULL, 0, h); -#endif - if (GetElfLoadRange(h->PHEntries, h->numPHEntries, h->delta, - TARGET_PAGE_SIZE, &map_start, &map_end) == 0) { - AddDebugInfo(lib ? LIB_WRAPPED : LIB_EMULATED, my_lm->l_name, - map_start, map_end); + loader_scope_active = loader_scope && loader_scope->bindings && + loader_scope->identity && loader_scope->cookie; + if (loader_scope_active) { + kzt_guest_library_note_loader_pair_pending( + my_context, loader_scope, link_map_addr, library, &source_proof); } else { - printf_log(LOG_INFO, - "KZT: cannot determine ELF load range for %s\n", - my_lm->l_name); + kzt_guest_library_note_loader_pair( + my_context, link_map_addr, library, &source_proof); } + kzt_guest_library_wrapper_source_release(&source_proof); return 0; } -static void kzt_tb_callback(CPUX86State *env) +static int kzt_tb_callback_per_object_got_plt(uintptr_t link_map_addr, + void *opaque) +{ + kzt_per_object_got_plt_request_t request = { + .registry = KztGuestRegistryForContext(my_context), + .link_map_addr = link_map_addr, + .apply = KztPerObjectGotPltWrite, + .opaque = my_context, + }; + kzt_per_object_got_plt_result_t result = { 0 }; + + (void)kzt_tb_callback_materialize_binding(link_map_addr, opaque); + if (kzt_per_object_got_plt_apply(&request, &result) != 0) { + return -1; + } + return 0; +} + +static int kzt_tb_callback_pretranslate_target(uintptr_t target, void *opaque) +{ + CPUX86State *env = opaque; + + if (!target || !env || !lsenv || lsenv->cpu_state != env) { + return -1; + } + return KztPrebindTargetTbPrepare(target); +} + +static int kzt_tb_callback_prebind_invalidate( + kzt_lazy_prebind_mutation_t mutation, void *opaque) +{ + return kzt_production_lazy_prebind_invalidate(opaque, mutation); +} + +#endif + +static void kzt_tb_callback_consume(CPUX86State *env, + const kzt_loader_event_t *event) { - uintptr_t link_map_addr = env->regs[R_EAX + ld_info->reg]; #ifdef CONFIG_LATX_KZT + uintptr_t link_map_addr = event ? event->link_map_addr : 0; kzt_guest_registry_t *registry = KztGuestRegistryForContext(my_context); int diagnostics_enabled = kzt_registry_diagnostics_enabled(); + int lifecycle_scoped = + env->kzt_guest_library_loader_scope.bindings && + env->kzt_guest_library_loader_scope.identity && + env->kzt_guest_library_loader_scope.cookie; + uint64_t lifecycle_start = kzt_lifecycle_diagnostics_enabled() + ? kzt_lifecycle_diagnostics_now() + : 0; kzt_guest_registry_diagnostic_config_t diagnostic_config = { .enabled = diagnostics_enabled, .throttle_limit = 1, @@ -2475,12 +2417,13 @@ static void kzt_tb_callback(CPUX86State *env) uintptr_t confirmed_main_head = 0; uintptr_t namespace_head = 0; uintptr_t predecessor = 0; + kzt_observation_adapter_result_t observation_result; int main_namespace; (void)kzt_guest_registry_context_get_main_namespace_head( &my_context->kzt_guest_registry_context, &confirmed_main_head); if (kzt_guest_link_map_read_identity( - link_map_addr, &reader_ops, &object_identity) != 0) { + link_map_addr, &reader_ops, &object_identity) != 0) { main_namespace = -1; } else if (confirmed_main_head && kzt_guest_registry_context_has_main_namespace_evidence( @@ -2497,8 +2440,8 @@ static void kzt_tb_callback(CPUX86State *env) } else if (confirmed_main_head || kzt_main_elf_identity(elf_header, &main_identity) == 0) { main_namespace = kzt_guest_link_map_classify_namespace( - link_map_addr, &main_identity, confirmed_main_head, &reader_ops, - &namespace_head); + link_map_addr, &main_identity, confirmed_main_head, + &reader_ops, &namespace_head); } else { main_namespace = -1; } @@ -2515,12 +2458,17 @@ static void kzt_tb_callback(CPUX86State *env) .link_map_addr = link_map_addr, .registry = registry, .library_bindings = KztGuestLibraryBindingsForContext(my_context), + .lazy_prebind_scope = KztLazyPrebindScopeForContext(my_context), .loader_scope = &env->kzt_guest_library_loader_scope, .reader_ops = &reader_ops, + .reuse_complete_dynamic_view = 1, .namespace_id_present = main_namespace == 1, .namespace_id = 0, - .legacy_flow = kzt_tb_callback_legacy, - .legacy_opaque = &env->kzt_guest_library_loader_scope, + .prebind_invalidate = kzt_tb_callback_prebind_invalidate, + .prebind_invalidate_opaque = my_context, + .per_object_flow = kzt_tb_callback_per_object_got_plt, + .per_object_opaque = &env->kzt_guest_library_loader_scope, + .legacy_flow = NULL, .diagnostic = kzt_callback_diagnostic_log, .diagnostic_opaque = registry, }; @@ -2529,123 +2477,210 @@ static void kzt_tb_callback(CPUX86State *env) (void)kzt_guest_registry_configure_diagnostics(registry, &diagnostic_config); } - (void)kzt_observe_guest_object_from_callback(&request, NULL); + observation_result = KZT_OBSERVATION_ADAPTER_DISABLED; + (void)kzt_observe_guest_object_from_callback(&request, &observation_result); + if (lifecycle_start) { + uint64_t duration = + kzt_lifecycle_diagnostics_now() - lifecycle_start; + + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_REOBSERVE, duration); + if (lifecycle_scoped) { + kzt_lifecycle_diagnostics_add( + KZT_LIFECYCLE_SCOPED_REOBSERVE, duration); + } + } + if (observation_result == KZT_OBSERVATION_ADAPTER_ADDED || + observation_result == KZT_OBSERVATION_ADAPTER_UPDATED) { + if (lifecycle_scoped) { + env->kzt_guest_library_loader_scope.prebind_refresh_pending = 1; + } else { + kzt_production_lazy_prebind_refresh( + my_context, kzt_tb_callback_pretranslate_target, env); + } + } + if (diagnostics_enabled && event) { + struct timespec timestamp = { 0 }; + uint64_t consumed_ns = 0; + + if (clock_gettime(CLOCK_MONOTONIC_RAW, ×tamp) == 0) { + consumed_ns = (uint64_t)timestamp.tv_sec * 1000000000ULL + + (uint64_t)timestamp.tv_nsec; + } + printf_kzt_registry_diagnostics( + "kzt_loader_event schema=1 phase=consumed sequence=%lu " + "link_map=0x%lx result=%d consumer_ns=%lu runtime_ns=%lu\n", + (unsigned long)event->sequence, (unsigned long)link_map_addr, + observation_result, (unsigned long)consumed_ns, + (unsigned long)(consumed_ns >= event->published_ns ? + consumed_ns - event->published_ns : 0)); + } #else - (void)kzt_tb_callback_legacy(link_map_addr, env); + (void)env; + (void)event; #endif } -static TranslationBlock* test_tb; -static void test_x86free(CPUX86State *env) + +static void kzt_tb_callback(CPUX86State *env) { - static int cnt; - static uintptr_t ptr; - static int has_found; - if (has_found) { +#ifdef CONFIG_LATX_KZT + kzt_loader_event_t event; + uintptr_t link_map_addr; + + if (!env || !ld_info) { return; } - if (!ptr) { - ptr = env->regs[R_EDI]; - } else if (ptr != env->regs[R_EDI]) { - //destroy callback - mmap_lock(); -#ifdef CONFIG_USER_ONLY - tb_phys_invalidate(test_tb, test_tb->itree.start); -#else - tb_phys_invalidate(test_tb, test_tb->page_addr[0]); -#endif - mmap_unlock(); - has_found = 1; - printf_log(LOG_DEBUG, "%s latx final find free=%p, realloc=%p\n", __func__, x86free, x86realloc); + link_map_addr = env->regs[R_EAX + ld_info->reg]; + if (kzt_loader_event_hook_publish(&kzt_loader_event_hook, link_map_addr, + &event) != 0) { return; } - - if (cnt > 0) { - for (int i = 0; i < my_context->mallocmapsize;i++) { - if (x86free == my_context->mallocmaps[i]->freep && i mallocmapsize -1) { - struct malloc_map * m = my_context->mallocmaps[i + 1]; - static uint32 test_ld [2] = {0}; - x86free = m->freep; - x86realloc = m->reallocp; - target_ulong eip = env->eip; - CPUState *cpu = env_cpu(env); - memset(&test_ld, 0, sizeof(test_ld)); - mmap_lock(); -#ifdef CONFIG_USER_ONLY - tb_phys_invalidate(test_tb, test_tb->itree.start); + printf_kzt_registry_diagnostics( + "kzt_loader_event schema=1 phase=published build_id=%s sequence=%lu " + "link_map=0x%lx publish_ns=%lu\n", + kzt_loader_event_hook.build_id, (unsigned long)event.sequence, + (unsigned long)event.link_map_addr, (unsigned long)event.published_ns); + kzt_tb_callback_consume(env, &event); #else - tb_phys_invalidate(test_tb, test_tb->page_addr[0]); + kzt_tb_callback_consume(env, NULL); #endif +} - mmap_unlock(); - printf_log(LOG_DEBUG, "%s latx try find free=%p, realloc=%p from %s\n", __func__, x86free, x86realloc, ((elfheader_t*)m->h)->path); - test_tb = kzt_add_fucn_by_addr((uint32 *)&test_ld, cpu, (uintptr_t)x86free, target_latx_ld_callback, test_x86free); - env->eip = eip; - cnt = 0; - return; - } - } +#ifdef CONFIG_LATX_KZT +static int kzt_tb_resolve_lifecycle_identity( + uintptr_t link_map_addr, + kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + kzt_guest_loader_identity_t loader_identity = { 0 }; + box64context_t *context = opaque; + kzt_guest_registry_t *registry = + context ? KztGuestRegistryForContext(context) : NULL; + + if (!identity || !registry || + kzt_guest_registry_find_loader_object_identity( + registry, link_map_addr, &loader_identity) != 0) { + return -1; + } + *identity = (kzt_loader_lifecycle_identity_t) { + .link_map_addr = loader_identity.link_map_addr, + .generation = loader_identity.generation, + .namespace_id = loader_identity.namespace_id, + }; + return 0; +} + +static int kzt_tb_prepare_lifecycle_unload( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + kzt_guest_loader_identity_t unload; + + if (!identity) { + return -1; } - cnt++; + unload = (kzt_guest_loader_identity_t) { + .link_map_addr = identity->link_map_addr, + .generation = identity->generation, + .namespace_id = identity->namespace_id, + }; + return kzt_guest_dl_api_prepare_unload(opaque, &unload); } -static inline gint tb_sort_cmp(const void *ap, const void *bp) + +static int kzt_tb_cancel_lifecycle_unload( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque) { - const struct malloc_map *a = *(const struct malloc_map **)ap; - const struct malloc_map *b = *(const struct malloc_map **)bp; - if (a->freep < b->freep) { - return 1; + kzt_guest_loader_identity_t unload; + + if (!identity) { + return -1; } - return -1; + unload = (kzt_guest_loader_identity_t) { + .link_map_addr = identity->link_map_addr, + .generation = identity->generation, + .namespace_id = identity->namespace_id, + }; + return kzt_guest_dl_api_cancel_unload(opaque, &unload); } -static void finiReFlesh(elfheader_t* exech) + +static void kzt_tb_publish_lifecycle_unload( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque) { - CPUState *cpu; - static uint32 test_ld [2] = {0}; - lsassert(my_context->mallocmapsize); - struct malloc_map * m; + kzt_guest_loader_identity_t unload; - if (my_context->mallocmapsize == 1) { - m = my_context->mallocmaps[0]; - x86free = m->freep; - x86realloc = m->reallocp; + if (!identity) { return; } - /* For select x86free, x86realloc from my_context->mallocmaps - * Sort in descending order for freep. - * Because: - * When an executable program depends on multiple libraries and - * all of them provide symbols with the same name (such as free functions), - * the dynamic linker will parse the symbols according to the loading order of the libraries. - * The symbols defined in the first loaded library have higher priority. - */ - if (my_context->mallocmaps[0]->h == my_context->elfs[0]) {//elf[0] is exe file - if (my_context->mallocmapsize > 2) { - qsort(my_context->mallocmaps[1], my_context->mallocmapsize - 1, sizeof(struct malloc_map *), tb_sort_cmp); - } - } else { - qsort(my_context->mallocmaps, my_context->mallocmapsize, sizeof(struct malloc_map *), tb_sort_cmp); - } - m = my_context->mallocmaps[0]; - x86free = m->freep; - x86realloc = m->reallocp; - CPU_FOREACH(cpu) { - if (cpu) { - break; - } - } - CPUArchState *env = cpu->env_ptr; - target_ulong eip = env->eip; - test_tb = kzt_add_fucn_by_addr((uint32 *)&test_ld, cpu, (uintptr_t)x86free, target_latx_ld_callback, test_x86free); - env->eip = eip; - printf_log(LOG_DEBUG, "%s latx try find free=%p, realloc=%p from %s\n", __func__, x86free, x86realloc, ((elfheader_t*)m->h)->path); + unload = (kzt_guest_loader_identity_t) { + .link_map_addr = identity->link_map_addr, + .generation = identity->generation, + .namespace_id = identity->namespace_id, + }; + printf_kzt_registry_diagnostics( + "kzt_loader_event schema=1 phase=unload link_map=0x%lx " + "generation=%lu namespace=0x%lx result=%d\n", + (unsigned long)unload.link_map_addr, unload.generation, + (unsigned long)unload.namespace_id, + kzt_guest_dl_api_publish_unload(opaque, &unload)); } -static void kzt_exectb_callback(CPUX86State *env) + +static void kzt_tb_debug_state_callback(CPUX86State *env) { - finiReFlesh(elf_header); - x64free_fini = 1; - RelocateElf(my_context->maplib, NULL, 0, elf_header); - RelocateElfPlt(my_context->maplib, NULL, 0, elf_header); - AddDebugInfo(LIB_EMULATED, elf_header->name, info1.start_code, info1.end_code); + kzt_loader_lifecycle_snapshot_t snapshot = { + .result = KZT_LOADER_LIFECYCLE_SNAPSHOT_INVALID_INPUT, + }; + kzt_guest_registry_t *registry; + int publication_result; + + (void)env; + registry = KztGuestRegistryForContext(my_context); + if (!registry || + !ld_info || !ld_info->r_debug_addr || + kzt_loader_lifecycle_snapshot_capture( + registry, ld_info->r_debug_addr, + &(const kzt_guest_link_map_reader_ops_t) { + .read_memory = kzt_callback_read_memory, + .opaque = NULL, + }, + &snapshot) != 0) { + printf_kzt_registry_diagnostics( + "kzt_loader_event schema=1 phase=lifecycle-snapshot " + "result=FAIL_OPEN reason=%s\n", + kzt_loader_lifecycle_snapshot_result_name(snapshot.result)); + (void)kzt_loader_event_hook_publish_lifecycle( + &kzt_loader_event_hook, KZT_LOADER_DEBUG_ADD, NULL, 0, + kzt_tb_resolve_lifecycle_identity, + kzt_tb_prepare_lifecycle_unload, + kzt_tb_cancel_lifecycle_unload, + kzt_tb_publish_lifecycle_unload, my_context); + kzt_loader_lifecycle_snapshot_release(&snapshot); + return; + } + printf_kzt_registry_diagnostics( + "kzt_loader_event schema=1 phase=lifecycle-snapshot state=%d " + "maps=%lu result=OK\n", snapshot.state, + (unsigned long)snapshot.live_map_count); + publication_result = kzt_loader_event_hook_publish_lifecycle( + &kzt_loader_event_hook, snapshot.state, snapshot.live_maps, + snapshot.live_map_count, + kzt_tb_resolve_lifecycle_identity, + kzt_tb_prepare_lifecycle_unload, + kzt_tb_cancel_lifecycle_unload, + kzt_tb_publish_lifecycle_unload, my_context); + if (publication_result != 0) { + printf_kzt_registry_diagnostics( + "kzt_loader_event schema=1 phase=lifecycle-publication " + "result=FAIL_OPEN reason=%s\n", + kzt_loader_lifecycle_result_name( + kzt_loader_event_hook_lifecycle_result( + &kzt_loader_event_hook))); + } + kzt_loader_lifecycle_snapshot_release(&snapshot); } +#endif + static TranslationBlock* kzt_add_fucn_by_addr(uint32* inst_old, CPUState *cpu, uintptr_t addr, int (*latx_ld_callback)(void *, void (*)(CPUX86State *)), void (*kzt_tb_callback)(CPUX86State *)) { uint32 jmpinst [2] = {0}; @@ -2846,10 +2881,15 @@ static struct x86_ld_info * find_ld_part(char * start, int len) ret = malloc(sizeof(struct x86_ld_info)); ret->addr = (uintptr_t) ld_find; ret->reg = (*(ld_find+ 2)) & 0xf; +#ifdef CONFIG_LATX_KZT + ret->debug_state_addr = 0; + ret->r_debug_addr = 0; +#endif printf_log(LOG_DEBUG, "debug find ld so 0x%lx reg = %d\n", ret->addr, ret->reg); return ret; } -static struct x86_ld_info *find_ld_bridge(void* info) +static struct x86_ld_info *find_ld_bridge( + void *info, char build_id[KZT_LOADER_EVENT_HOOK_BUILD_ID_SIZE]) { struct image_info * execinfo = (struct image_info *)info; struct x86_ld_info * ret = NULL; @@ -2859,13 +2899,32 @@ static struct x86_ld_info *find_ld_bridge(void* info) if (stat(real_dl_file, &st)) { return NULL; } +#ifdef CONFIG_LATX_KZT + if (kzt_loader_event_hook_read_build_id(real_dl_file, build_id) != 0) { + return NULL; + } +#else + (void)build_id; +#endif size_t file_len = st.st_size; +#ifdef CONFIG_LATX_KZT + uintptr_t debug_state_addr = (uintptr_t)ld_start + + KZT_LOADER_EVENT_HOOK_DEBUG_STATE_OFFSET; + uintptr_t r_debug_addr = (uintptr_t)ld_start + + KZT_LOADER_EVENT_HOOK_R_DEBUG_OFFSET; +#endif + uint8_t* fix_addr = (uint8_t *)(ld_start+0xbc15); if ((*(uint64_t *)fix_addr & 0xffffffffff00ffff) == 0x040000031c008041) { ret = malloc(sizeof(struct x86_ld_info)); ret->addr = (uintptr_t) fix_addr; ret->reg = (*(fix_addr+ 2)) & 0xf; +#ifdef CONFIG_LATX_KZT + ret->debug_state_addr = + *(uint8_t *)debug_state_addr == 0xc3 ? debug_state_addr : 0; + ret->r_debug_addr = ret->debug_state_addr ? r_debug_addr : 0; +#endif printf_log(LOG_DEBUG, "debug find ld so 0x%lx reg = %d\n", ret->addr, ret->reg); return ret; } @@ -2874,6 +2933,11 @@ static struct x86_ld_info *find_ld_bridge(void* info) ret = malloc(sizeof(struct x86_ld_info)); ret->addr = (uintptr_t) fix_addr; ret->reg = (*(fix_addr+ 2)) & 0xf; +#ifdef CONFIG_LATX_KZT + ret->debug_state_addr = + *(uint8_t *)debug_state_addr == 0xc3 ? debug_state_addr : 0; + ret->r_debug_addr = ret->debug_state_addr ? r_debug_addr : 0; +#endif printf_log(LOG_DEBUG, "debug find ld so 0x%lx reg = %d\n", ret->addr, ret->reg); return ret; } @@ -2882,6 +2946,11 @@ static struct x86_ld_info *find_ld_bridge(void* info) ret = malloc(sizeof(struct x86_ld_info)); ret->addr = (uintptr_t) fix_addr; ret->reg = (*(fix_addr+ 2)) & 0xf; +#ifdef CONFIG_LATX_KZT + ret->debug_state_addr = + *(uint8_t *)debug_state_addr == 0xc3 ? debug_state_addr : 0; + ret->r_debug_addr = ret->debug_state_addr ? r_debug_addr : 0; +#endif printf_log(LOG_DEBUG, "debug find ld so 0x%lx reg = %d\n", ret->addr, ret->reg); return ret; } @@ -2890,6 +2959,11 @@ static struct x86_ld_info *find_ld_bridge(void* info) ret = malloc(sizeof(struct x86_ld_info)); ret->addr = (uintptr_t) fix_addr; ret->reg = (*(fix_addr+ 2)) & 0xf; +#ifdef CONFIG_LATX_KZT + ret->debug_state_addr = + *(uint8_t *)debug_state_addr == 0xc3 ? debug_state_addr : 0; + ret->r_debug_addr = ret->debug_state_addr ? r_debug_addr : 0; +#endif printf_log(LOG_DEBUG, "debug find ld so 0x%lx reg = %d\n", ret->addr, ret->reg); return ret; } @@ -2899,6 +2973,11 @@ static struct x86_ld_info *find_ld_bridge(void* info) ret = malloc(sizeof(struct x86_ld_info)); ret->addr = (uintptr_t) fix_addr; ret->reg = (*(fix_addr+ 2)) & 0xf; +#ifdef CONFIG_LATX_KZT + ret->debug_state_addr = + *(uint8_t *)debug_state_addr == 0xc3 ? debug_state_addr : 0; + ret->r_debug_addr = ret->debug_state_addr ? r_debug_addr : 0; +#endif printf_log(LOG_DEBUG, "debug find ld so 0x%lx reg = %d\n", ret->addr, ret->reg); return ret; } @@ -2908,30 +2987,92 @@ static struct x86_ld_info *find_ld_bridge(void* info) ret = malloc(sizeof(struct x86_ld_info)); ret->addr = (uintptr_t) fix_addr; ret->reg = (*(fix_addr+ 2)) & 0xf; +#ifdef CONFIG_LATX_KZT + ret->debug_state_addr = + *(uint8_t *)debug_state_addr == 0xc3 ? debug_state_addr : 0; + ret->r_debug_addr = ret->debug_state_addr ? r_debug_addr : 0; +#endif printf_log(LOG_DEBUG, "debug find ld so 0x%lx reg = %d\n", ret->addr, ret->reg); return ret; } - return find_ld_part((char *)ld_start, file_len); - return NULL; + ret = find_ld_part((char *)ld_start, file_len); +#ifdef CONFIG_LATX_KZT + if (ret) { + ret->debug_state_addr = + *(uint8_t *)debug_state_addr == 0xc3 ? debug_state_addr : 0; + ret->r_debug_addr = ret->debug_state_addr ? r_debug_addr : 0; + } +#endif + return ret; } void init_tb_callback_bridge(CPUState *cpu, void* info) { - struct image_info * execinfo = (struct image_info *)info; - static uint32 jmpinst_exec [2] = {0}; static uint32 jmpinst_ld [2] = {0}; +#ifdef CONFIG_LATX_KZT + static uint32 jmpinst_debug_state[2] = { 0 }; +#endif if (!ld_info) { - ld_info = find_ld_bridge(info); + char build_id[KZT_LOADER_EVENT_HOOK_BUILD_ID_SIZE] = { 0 }; + struct x86_ld_info *candidate = find_ld_bridge(info, build_id); +#ifdef CONFIG_LATX_KZT + if (my_context) { + my_context->kzt_guest_scope_layout = + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; + } + if (!candidate || + kzt_loader_event_hook_install(&kzt_loader_event_hook, + build_id[0] ? build_id : NULL, + candidate ? candidate->addr : 0, + candidate ? candidate->reg : 0, + kzt_loader_event_hook_pattern_allowed( + candidate != NULL)) != 0) { + printf_kzt_registry_diagnostics( + "kzt_loader_event schema=1 phase=install result=%s build_id=%s " + "rollback=disabled\n", + kzt_loader_event_hook_result_name(kzt_loader_event_hook.result), + build_id[0] ? build_id : "(unavailable)"); + free(candidate); + return; + } + if (my_context) { + my_context->kzt_guest_scope_layout = + kzt_loader_event_hook_scope_layout(&kzt_loader_event_hook); + } + if (!candidate->debug_state_addr || !candidate->r_debug_addr || + kzt_loader_event_hook_enable_lifecycle( + &kzt_loader_event_hook, candidate->debug_state_addr, + candidate->r_debug_addr) != 0) { + printf_kzt_registry_diagnostics( + "kzt_loader_event schema=1 phase=lifecycle-install " + "result=FAIL_OPEN build_id=%s\n", build_id); + } +#endif + ld_info = candidate; +#ifdef CONFIG_LATX_KZT + printf_kzt_registry_diagnostics( + "kzt_loader_event schema=1 phase=install result=INSTALLED " + "build_id=%s rollback=active\n", build_id); +#endif } if (!ld_info) { - lsassertm(0,"can't find ld callback tb."); - option_kzt = 0; + printf_kzt_registry_diagnostics( + "kzt_loader_event schema=1 phase=install result=PATTERN_MISMATCH " + "rollback=disabled\\n"); return; } CPUArchState *env = cpu->env_ptr; target_ulong eip = env->eip; - kzt_add_fucn_by_addr((uint32 *)&jmpinst_exec, cpu, execinfo->exec_entry, target_latx_ld_callback, kzt_exectb_callback); kzt_add_fucn_by_addr((uint32 *)&jmpinst_ld, cpu, ld_info->addr, target_latx_ld_callback, kzt_tb_callback); +#ifdef CONFIG_LATX_KZT + if (__atomic_load_n(&kzt_loader_event_hook.lifecycle_enabled, + __ATOMIC_ACQUIRE)) { + kzt_add_fucn_by_addr( + (uint32 *)&jmpinst_debug_state, cpu, + kzt_loader_event_hook.debug_state_addr, + target_latx_ld_callback, kzt_tb_debug_state_callback); + } +#endif env->eip = eip; } void kzt_bridge_init(void) @@ -3050,43 +3191,79 @@ void kzt_wine_bridge(abi_ulong start, int fd) } } -void kzt_wine_init_x86(void) +void kzt_wine_init_x86(box64context_t *context, uintptr_t guest_dlsym) { - if (!option_kzt ||!latx_wine ||my_context->mallocmapsize) { + if (!context || !option_kzt || !latx_wine || context->mallocmapsize) { return; } struct malloc_map* m = malloc(sizeof(struct malloc_map)); - m->mallocp = (void *)(uintptr_t)RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, + m->mallocp = (void *)(uintptr_t)RunFunctionWithState(guest_dlsym, 2, 0, "malloc"); ; - m->freep = (void *)(uintptr_t)RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, + m->freep = (void *)(uintptr_t)RunFunctionWithState(guest_dlsym, 2, 0, "free"); - m->reallocp = (void *)(uintptr_t)RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, + m->reallocp = (void *)(uintptr_t)RunFunctionWithState(guest_dlsym, 2, 0, "realloc"); m->h = wine_elf_header; - AddMallocMap(my_context, m); + AddMallocMap(context, m); x86free = m->freep; x86realloc = m->reallocp; } -elfheader_t* loadElfFromFile(const char* name) +elfheader_t* tryLoadElfFromFile(const char* name) { elfheader_t* h = NULL; char *tmp = ResolveFile(name, &my_context->box64_ld_lib); - if (FileExist(tmp, IS_FILE)) { + uintptr_t load_addr; + + if (tmp && FileExist(tmp, IS_FILE)) { FILE *f = fopen(tmp, "rb"); if (!f) { printf_log(LOG_NONE, "Error: Cannot open %s\n", tmp); + box_free(tmp); return NULL; } h = LoadAndCheckElfHeader(f, tmp, 0); - ElfHeadReFix(h, loadSoaddrFromMap(tmp)); - if ((uintptr_t)h->VerSym > (uintptr_t)h->delta) { - h->delta = 0; + load_addr = h ? loadSoaddrFromMap(tmp) : 0; + if (!h || !load_addr) { + if (h) { + FreeElfHeader(&h); + } + fclose(f); + h = NULL; + } else { + ElfHeadReFix(h, load_addr); + if ((uintptr_t)h->VerSym > (uintptr_t)h->delta) { + h->delta = 0; + } } } else { - lsassertm(0, "cannot find %s\n", tmp); + printf_log(LOG_INFO, "cannot find %s\n", tmp ? tmp : name); } + box_free(tmp); return h; } + +void freeElfFromFile(elfheader_t **header) +{ + FILE *file; + + if (!header || !*header) { + return; + } + file = (*header)->file; + (*header)->file = NULL; + FreeElfHeader(header); + if (file) { + fclose(file); + } +} + +elfheader_t* loadElfFromFile(const char* name) +{ + elfheader_t *header = tryLoadElfFromFile(name); + + lsassertm(header, "cannot find %s\n", name); + return header; +} #pragma GCC diagnostic pop diff --git a/target/i386/latx/context/wrappedlibc.c b/target/i386/latx/context/wrappedlibc.c index 27b6bd88578..ea53ec9c46b 100644 --- a/target/i386/latx/context/wrappedlibc.c +++ b/target/i386/latx/context/wrappedlibc.c @@ -68,10 +68,11 @@ #include "elfloader_private.h" #include "bridge.h" #include "globalsymbols.h" -#include "dlopen_recycle_transaction.h" +#include "kzt_guest_library_adapter.h" +#include "kzt_guest_dl_api.h" +#include "kzt_guest_dl_init.h" #ifdef CONFIG_LATX_KZT #include "kzt_guest_library_binding.h" -#include "kzt_guest_library_adapter.h" #endif #include "kzt_loader_callback_scope.h" @@ -84,48 +85,6 @@ const char* libcName = #endif ; -static uint64_t run_guest_dlopen_scoped(uintptr_t function, - void *filename, int flag, - kzt_guest_library_loader_scope_t *scope) -{ -#ifdef CONFIG_LATX_KZT - __MY_CPU; - kzt_guest_library_loader_scope_t previous = - cpu->kzt_guest_library_loader_scope; - kzt_guest_library_bindings_t *bindings = - KztGuestLibraryBindingsForContext(my_context); - int scoped = scope && - kzt_guest_library_loader_scope_begin(bindings, scope) == 0; - uint64_t result; - - if (scoped) cpu->kzt_guest_library_loader_scope = *scope; - result = RunFunctionWithState(function, 2, filename, flag); - cpu->kzt_guest_library_loader_scope = previous; - return result; -#else - (void)scope; - return RunFunctionWithState(function, 2, filename, flag); -#endif -} - -static void finish_guest_dlopen_scoped( - kzt_guest_library_loader_scope_t *scope, uintptr_t link_map_addr, - library_t *library, int publish) -{ -#ifdef CONFIG_LATX_KZT - if (publish && link_map_addr) { - if (library) - kzt_guest_library_publish_loader_pair_scoped( - my_context, scope, link_map_addr, library); - else - kzt_guest_library_publish_loader_observed_scoped( - my_context, scope, link_map_addr); - } - kzt_guest_library_loader_scope_end(scope); -#else - (void)scope; (void)link_map_addr; (void)library; (void)publish; -#endif -} #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-prototypes" void* x86free; @@ -3552,14 +3511,6 @@ int box64_isglibc234 = 1; #include "library.h" #define FORWORDBACK 0 -dlprivate_t *NewDLPrivate(void) { - dlprivate_t* dl = (dlprivate_t*)box_calloc(1, sizeof(dlprivate_t)); - return dl; -} -void FreeDLPrivate(dlprivate_t **lib) { - box_free((*lib)->last_error); - box_free(*lib); -} void* my_dlopen(void *filename, int flag) EXPORT; void* my_dlmopen(void* mlid, void *filename, int flag) EXPORT; @@ -3572,7 +3523,15 @@ void* my_dlvsym(void *handle, void *symbol, const char *vername) EXPORT; int my_dlinfo(void* handle, int request, void* info) EXPORT; -#define CLEARERR if(dl->last_error) box_free(dl->last_error); dl->last_error = NULL; +#if defined(CONFIG_LATX_KZT) && defined(TARGET_X86_64) +#define DLERROR_STATE(cpu, dl) ((void)(dl), &(cpu)->kzt_guest_dlerror_state) +#define DLERROR_FAST_RESULT() kzt_guest_dl_api_current_fast_result() +#else +#define DLERROR_STATE(cpu, dl) (&(dl)->legacy_error) +#define DLERROR_FAST_RESULT() \ + (my_context->dlprivate->legacy_error.dlerror_fast_result) +#endif +#define CLEARERR guest_error_was_clean = kzt_guest_dl_api_begin_call(error_state); //#define R_RSP cpu->regs[R_ESP] static void Push64(CPUX86State *cpu, uint64_t v) { @@ -3580,645 +3539,198 @@ static void Push64(CPUX86State *cpu, uint64_t v) *((uint64_t*)cpu->regs[R_ESP]) = v; } -void kzt_wine_init_x86(void); -int init_x86dlfun(void); -int init_x86dlfun(void) -{ - elfheader_t* h = NULL; -#ifdef CONFIG_LOONGARCH_NEW_WORLD - char buf[PATH_MAX] = {0}; - snprintf(buf, PATH_MAX, "%s%s", interp_prefix, - "/usr/lib/glibc-hwcaps/x86-64-v2" /* AOSC OS (Core 12.2.2), glibc 2.40 (EmuKit 20250909~pre20250911T080911Z) */); - PrependList(&my_context->box64_ld_lib, buf, 1); -#endif - h = loadElfFromFile("libc.so.6"); - lsassert(h); - const char* syms[] = {"dlopen", "dlsym", "dlclose", "dladdr", "dladdr1", "dlinfo"}; - void *rsyms[6] = {0}; - int rrsyms = 0; - ResetSpecialCaseElf(h, syms, 6, rsyms, &rrsyms); - if (rrsyms != 6) { - h = loadElfFromFile("libdl.so.2"); - ResetSpecialCaseElf(h, syms, 6, rsyms, &rrsyms); - } - lsassert(rrsyms == 6); - my_context->dlprivate->x86dlopen = rsyms[0]; - my_context->dlprivate->x86dlsym = rsyms[1]; - my_context->dlprivate->x86dlclose = rsyms[2]; - my_context->dlprivate->x86dladdr = rsyms[3]; - my_context->dlprivate->x86dladdr1 = rsyms[4]; - my_context->dlprivate->x86dlinfo = rsyms[5]; - kzt_wine_init_x86(); - return 0; -} -static int callx86dlopen(void *filename, int flag, elfheader_t * h, - int is_local, int publish_pair) { - kzt_guest_library_loader_scope_t scope = { 0 }; - struct link_map* ret = (struct link_map*)(uintptr_t) - run_guest_dlopen_scoped( - (uintptr_t)my_context->dlprivate->x86dlopen, filename, flag, - &scope); - if (ret) { - printf_dlsym(LOG_DEBUG, "latx RunFunctionWithState dlopen %s addr %p\n", (char *)filename, (void *)ret->l_addr); - h->lib->x86linkmap = ret; - } else { - //open error - finish_guest_dlopen_scoped(&scope, 0, NULL, 0); - return -1; - } - h->delta = ret->l_addr; - linkmap_t* lm = getLinkMapLib(h->lib); - if (lm) { - lm->l_addr = ret->l_addr; - } - h->latx_hasfix = 1; - lib_t *maplib = (is_local)?h->lib->maplib:my_context->maplib; - if(AddSymbolsLibrary(maplib, h->lib)) { // also add needed libs - printf_dlsym(LOG_INFO, "Failure to Add lib => fail\n"); - lsassert(0); - } - finish_guest_dlopen_scoped(&scope, (uintptr_t)ret, h->lib, - publish_pair); - return 0; -} -static void LatxResetElf(elfheader_t * h) -{ - h->latx_hasfix = 0; - h->had_RelocateElfPlt = 0; - h->had_RelocateElf = 0; - h->latx_type = 0; - h->latx_hasfix = 0; -} - -typedef struct dlopen_recycle_context { - void *filename; - int flag; - elfheader_t *header; - library_t *library; - int is_local; - int guest_open_required; -} dlopen_recycle_context_t; - -static void recycle_reset_loader_state(dlopen_recycle_context_t *recycle) -{ - linkmap_t *lm; - - recycle->library->x86linkmap = NULL; - if (!recycle->header) - return; - LatxResetElf(recycle->header); - recycle->header->delta = 0; - lm = getLinkMapLib(recycle->library); - if (lm) - lm->l_addr = 0; -} - -static void recycle_prepare(void *opaque) -{ - dlopen_recycle_context_t *recycle = opaque; - if (!recycle->guest_open_required) - return; - recycle_reset_loader_state(recycle); -} - -static int recycle_guest_open(void *opaque) -{ - dlopen_recycle_context_t *recycle = opaque; -#ifdef CONFIG_LATX_KZT - if (recycle->guest_open_required && - kzt_guest_library_reactivate( - KztGuestLibraryBindingsForContext(recycle->library->context), - recycle->library) != 0) - return -1; -#endif - return recycle->guest_open_required - ? callx86dlopen(recycle->filename, recycle->flag, - recycle->header, recycle->is_local, 0) - : 0; -} - -static int recycle_reload(void *opaque) -{ - dlopen_recycle_context_t *recycle = opaque; - return ReloadLibrary(recycle->library); -} - -static void recycle_rollback(void *opaque, int guest_opened) -{ - dlopen_recycle_context_t *recycle = opaque; - InactiveLibrary(recycle->library); - if (guest_opened && recycle->guest_open_required && - recycle->library->x86linkmap && - my_context->dlprivate->x86dlclose) - (void)RunFunctionWithState( - (uintptr_t)my_context->dlprivate->x86dlclose, 1, - recycle->library->x86linkmap); - recycle_reset_loader_state(recycle); -} void* my_dlopen(void *filename, int flag){ - // TODO, handling special values for filename, like RTLD_SELF? - // TODO, handling flags? - library_t *lib = NULL; dlprivate_t *dl = my_context->dlprivate; - size_t dlopened = 0; - int is_local = (flag&0x100)?0:1; // if not global, then local, and that means symbols are not put in the global "pot" for other libs + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + uint64_t result; + int guest_error_was_clean; + CLEARERR - if (!dl->x86dlopen) { - init_x86dlfun(); - lsassert(dl->x86dlopen); - } - if(filename) { - char* rfilename = (char*)alloca(MAX_PATH); - strcpy(rfilename, (char*)filename); - printf_dlsym(LOG_DEBUG, "Call to dlopen(\"%s\"/%p, %X)\n", rfilename, filename, flag); - while(strstr(rfilename, "${ORIGIN}")) { - char* origin = box_strdup(my_context->fullpath); - char* p = strrchr(origin, '/'); - if(p) *p = '\0'; // remove file name to have only full path, without last '/' - char* tmp = (char*)box_calloc(1, strlen(rfilename)-strlen("${ORIGIN}")+strlen(origin)+1); - p = strstr(rfilename, "${ORIGIN}"); - memcpy(tmp, rfilename, p-rfilename); - strcat(tmp, origin); - strcat(tmp, p+strlen("${ORIGIN}")); - strcpy(rfilename, tmp); - box_free(tmp); - box_free(origin); - } - while(strstr(rfilename, "${PLATFORM}")) { - char* platform = box_strdup("x86_64"); - char* p = strrchr(platform, '/'); - if(p) *p = '\0'; // remove file name to have only full path, without last '/' - char* tmp = (char*)box_calloc(1, strlen(rfilename)-strlen("${PLATFORM}")+strlen(platform)+1); - p = strstr(rfilename, "${PLATFORM}"); - memcpy(tmp, rfilename, p-rfilename); - strcat(tmp, platform); - strcat(tmp, p+strlen("${PLATFORM}")); - strcpy(rfilename, tmp); - box_free(tmp); - box_free(platform); - } - if (rfilename[0] == '/' && !FileExist(rfilename, IS_FILE)) { - char filetmp[PATH_MAX] = {0}; - snprintf(filetmp , PATH_MAX, "%s%s", interp_prefix, rfilename); - strcpy(rfilename, filetmp); - printf_dlsym(LOG_DEBUG, "dlopen filename change to \"%s\"\n", rfilename); - } - // check if alread dlopenned... - for (size_t i=0; ilib_sz; ++i) { - if(IsSameLib(dl->libs[i], rfilename)) { - if(dl->count[i]==0 && dl->dlopened[i]) { // need to lauch init again! - int idx = GetElfIndex(dl->libs[i]); - if(idx!=-1) { - printf_dlsym(LOG_DEBUG, "dlopen: Recycling, calling Init for %p (%s)\n", (void*)(i+1), rfilename); - dlopen_recycle_context_t recycle = { - .filename = rfilename, - .flag = flag, - .library = dl->libs[i], - .is_local = is_local, - }; - if (IsEmuLib(dl->libs[i])) { - recycle.header = my_context->elfs[idx]; - lsassert(recycle.header); - recycle.guest_open_required = 1; - } - void *handle = dlopen_recycle_transaction( - (void *)(i + 1), &dl->count[i], !(flag & 0x4), - &recycle, recycle_prepare, recycle_guest_open, - recycle_reload, recycle_rollback); - if (!handle) { - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, - "Cannot recycle dlopen(\"%s\"/%p, %X)\n", - rfilename, filename, flag); - return NULL; - } - printf_dlsym(LOG_DEBUG, "dlopen: Recycling %s/%p count=%ld (dlopened=%ld, elf_index=%d)\n", rfilename, (void*)(i+1), dl->count[i], dl->dlopened[i], GetElfIndex(dl->libs[i])); - return handle; - } - } - if(!(flag&0x4)) - dl->count[i] = dl->count[i]+1; - printf_dlsym(LOG_DEBUG, "dlopen: Recycling %s/%p count=%ld (dlopened=%ld, elf_index=%d)\n", rfilename, (void*)(i+1), dl->count[i], dl->dlopened[i], GetElfIndex(dl->libs[i])); - return (void*)(i+1); - } - } - if(strstr(rfilename, "libGL.so")){ - strcpy(rfilename, "libGL.so.1"); - } - dlopened = (GetLibInternal(rfilename)==NULL); - // Then open the lib - const char* libs[] = {rfilename}; - my_context->deferedInit = 1; - int bindnow = (flag&0x2)?1:0; - if (!FindLibIsWrapped(basename(rfilename))) { -#if FORWORDBACK - lsassert(dl->x86dlopen); - __MY_CPU; - Push64(cpu, (uint64_t)dl->x86dlopen); - printf_dlsym(LOG_DEBUG, "warning call x86dlopen filename is %s %x\n", (char *)filename, flag); - return NULL; -#else - kzt_guest_library_loader_scope_t scope = { 0 }; - uint64_t ret = run_guest_dlopen_scoped( - (uintptr_t)my_context->dlprivate->x86dlopen, filename, flag, - &scope); - finish_guest_dlopen_scoped(&scope, (uintptr_t)ret, NULL, - ret != 0); - printf_dlsym(LOG_DEBUG, "warning call call x86dlopen filename %s %x ret=0x%lx\n", (char *)filename, flag, ret); - //lsassert(0); - if (ret) { - return (void *)ret; - } - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "filename \"%s\" flag=%x\n", (char *)filename, flag); - printf_dlsym(LOG_NEVER, "%p return %p\n", dl->last_error, (void*)NULL); - return NULL; -#endif - } - if(AddNeededLibWithLibrary(NULL, NULL, NULL, is_local, bindnow, - rfilename, my_context, &lib) || !lib) { - printf_dlsym(strchr(rfilename,'/')?LOG_DEBUG:LOG_INFO, "Warning: Cannot dlopen(\"%s\"/%p, %X)\n", rfilename, filename, flag); - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Cannot dlopen(\"%s\"/%p, %X)\n", rfilename, filename, flag); - return NULL; - } - lib->x86dlopenflag = flag; - if (lib && lib->type == LIB_EMULATED) { - // if dlopened = 0 ---> lib added but not loaded - int libidx = GetElfIndex(lib); - lsassert(libidx >= 0); - elfheader_t * h = my_context->elfs[libidx]; - lsassert(h); - if (!h->latx_hasfix || !lib->x86linkmap) {//lib->x86linkmap is null ---- this lib has been needed by other elf and opened - callx86dlopen(rfilename, flag, h, is_local, 1); - } - } - //TODO:RunDeferedElfInit; - } else { - // check if already dlopenned... - for (size_t i=0; ilib_sz; ++i) { - if(!dl->libs[i]) { - dl->count[i] = dl->count[i]+1; - return (void*)(i+1); - } - } - printf_dlsym(LOG_DEBUG, "Call to dlopen(NULL, %X) forword call x86dlopen \n", flag); - lsassert(dl->x86dlopen); - __MY_CPU; - Push64(cpu, (uint64_t)dl->x86dlopen); + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); + if (!entries || !entries->dlopen) { return NULL; } - //get the lib and add it to the collection - - if(dl->lib_sz == dl->lib_cap) { - dl->lib_cap += 4; - dl->libs = (library_t**)box_realloc(dl->libs, sizeof(library_t*)*dl->lib_cap); - dl->count = (size_t*)box_realloc(dl->count, sizeof(size_t)*dl->lib_cap); - dl->dlopened = (size_t*)box_realloc(dl->dlopened, sizeof(size_t)*dl->lib_cap); - // memset count... - memset(dl->count+dl->lib_sz, 0, (dl->lib_cap-dl->lib_sz)*sizeof(size_t)); - } - intptr_t idx = dl->lib_sz++; - dl->libs[idx] = lib; - dl->count[idx] = dl->count[idx]+1; - dl->dlopened[idx] = dlopened; - printf_dlsym(LOG_DEBUG, "dlopen: New handle %p (%s), dlopened=%ld\n", (void*)(idx+1), (char*)filename, dlopened); - if (lib && lib->type == LIB_EMULATED) { - return lib->x86linkmap; + printf_dlsym(LOG_DEBUG, "Call to dlopen(\"%s\"/%p, %X)\n", + filename ? (char *)filename : "", filename, flag); + result = kzt_guest_dl_api_dlopen( + my_context, &cpu->kzt_guest_library_loader_scope, + entries, error_state, filename, flag); + if (result) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); } - return (void*)(idx+1); + return (void *)(uintptr_t)result; } void* my_dlmopen(void* lmid, void *filename, int flag) { - if(lmid) { - printf_dlsym(LOG_INFO, "Warning, dlmopen(%p, %p(\"%s\"), 0x%x) called with lmid not LMID_ID_BASE (unsupported)\n", lmid, filename, filename?(char*)filename:"self", flag); - } - // lmid is ignored for now... - return my_dlopen(filename, flag); -} - -KHASH_SET_INIT_INT(libs); + dlprivate_t *dl = my_context->dlprivate; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + uint64_t result; + int guest_error_was_clean; -static int recursive_dlsym_lib(kh_libs_t* collection, library_t* lib, const char* rsymbol, uintptr_t *start, uintptr_t *end, int version, const char* vername) -{ - if(!lib) - return 0; - khint_t k = kh_get(libs, collection, (uintptr_t)lib); - if(k != kh_end(collection)) - return 0; - int ret; - kh_put(libs, collection, (uintptr_t)lib, &ret); - // look in the library itself - khint_t pre_k = kh_str_hash_func(rsymbol); - if(lib->get(lib, rsymbol, pre_k, start, end, version, vername, 1)) - return 1; - // look in other libs - int n = GetNeededLibN(lib); - for (int i=0; idlmopen) { + return NULL; + } + result = kzt_guest_dl_api_dlmopen( + my_context, entries, lmid, filename, flag); + if (result) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); + } + return (void *)(uintptr_t)result; } -static int my_dlsym_lib(library_t* lib, const char* rsymbol, uintptr_t *start, uintptr_t *end, int version, const char* vername) +void* my_dlsym(void *handle, void *symbol) { - kh_libs_t *collection = kh_init(libs); - int ret = recursive_dlsym_lib(collection, lib, rsymbol, start, end, version, vername); - kh_destroy(libs, collection); - - return ret; -} - -void* my_dlsym(void *handle, void *symbol){ dlprivate_t *dl = my_context->dlprivate; - uintptr_t start = 0, end = 0; - char* rsymbol = (char*)symbol; + kzt_guest_dl_symbol_result_t result; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + int guest_error_was_clean; + CLEARERR - if (!dl->x86dlsym) { - init_x86dlfun(); - lsassert(dl->x86dlsym); - } - printf_dlsym(LOG_DEBUG, "Call to dlsym(%p, \"%s\")%s\n", handle, rsymbol, dlsym_error?"":"\n"); - //lsassert(!strstr(rsymbol, "XcursorGetDefaultSize")); - if(handle==NULL) { - // special case, look globably - // special case (RTLD_DEFAULT) -#ifdef LATX_RELOCATION_SAVE_SYMBOLS - if(GetGlobalSymbolStartEnd(my_context->maplib, rsymbol, &start, &end, NULL, -1, NULL)) { - printf_dlsym(LOG_NEVER, "%p\n", (void*)start); - return (void*)start; - } -#endif -#if 0 - lsassert(dl->x86dlsym); - __MY_CPU; - Push64(cpu, (uint64_t)dl->x86dlsym); - printf_dlsym(LOG_DEBUG, "warning call x86dlsym filename is NULL\n"); - return NULL; -#else - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, handle, symbol); - printf_dlsym(LOG_DEBUG, "warning call x86dlsym filename is NULL ret=0x%lx\n", ret); - if (ret) { - return (void *)ret; - } else { - if(GetGlobalSymbolStartEnd(my_context->maplib, rsymbol, &start, &end, NULL, -1, NULL)) { - printf_dlsym(LOG_NEVER, "%p\n", (void*)start); - return (void*)start; - } - printf_dlsym(LOG_NEVER, "debug my %d\n", __LINE__); - } - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Symbol \"%s\" not found in %p)\n", rsymbol, handle); - printf_dlsym(LOG_NEVER, "%p return %p\n", dl->last_error, (void*)NULL); - return NULL; -#endif - } - if(handle==(void*)~0LL) { - // special case (RTLD_NEXT) -- call x86dlsym - lsassert(dl->x86dlsym); - __MY_CPU; - Push64(cpu, (uint64_t)dl->x86dlsym); - printf_dlsym(LOG_DEBUG, "warning call x86dlsym filename is RTLD_NEXT\n"); - return NULL; - } - size_t nlib = (size_t)handle; - if(nlib > dl->lib_sz) { - for (int i = 0; i < dl->lib_sz; i++) { - if (dl->libs[i] && dl->libs[i]->active && dl->libs[i]->type == LIB_EMULATED && ((size_t)dl->libs[i]->x86linkmap) == nlib) { - nlib = i + 1; - break; - } - } - } - --nlib; - // size_t is unsigned - if(nlib>=dl->lib_sz) { -#ifdef LATX_RELOCATION_SAVE_SYMBOLS - if(GetGlobalSymbolStartEnd(my_context->maplib, rsymbol, &start, &end, NULL, -1, NULL)) { - printf_dlsym(LOG_NEVER, "%p\n", (void*)start); - return (void*)start; - } -#endif - const char* lmfile = ((struct link_map *)handle)->l_name; - if (strlen(lmfile)) { - const char* libs[] = {basename(lmfile)}; - //try to wrapper. - int iswrapped = 0.; - if (FindLibIsWrapped((char *)libs[0])) { - //if file is wrapped. - iswrapped = 1; - printf_dlsym(LOG_DEBUG, "find lib \"%s\" shuold be wrapped. init it.\n", libs[0]); - if(AddNeededLib(NULL, NULL, NULL, 0, 1, libs, 1, my_context)) { - printf_dlsym(LOG_DEBUG, "Warning: Cannot AddNeededLib(\"%s\")\n", libs[0]); - } - printf_dlsym(LOG_DEBUG, "info: success AddNeededLib(\"%s\")\n", libs[0]); - if(GetGlobalSymbolStartEnd(my_context->maplib, rsymbol, &start, &end, NULL, -1, NULL)) { - printf_dlsym(LOG_NEVER, "%p\n", (void*)start); - return (void*)start; - } - } - if (iswrapped) { - //Perhaps exe want to test func for earch libs, return nil. - printf_dlsym(LOG_NEVER, "%p\n", (void*)NULL); - return NULL; - } - } -#if !defined(LATX_RELOCATION_SAVE_SYMBOLS) - else {//dlopen(NULL) --- dlopen self maplink filename is "NULL". - if(GetGlobalSymbolStartEnd(my_context->maplib, rsymbol, &start, &end, NULL, -1, NULL)) { - printf_dlsym(LOG_NEVER, "%p\n", (void*)start); - return (void*)start; - } - } -#endif - __MY_CPU; -#if FORWORDBACK - lsassert(dl->x86dlsym); - Push64(cpu, (uint64_t)dl->x86dlsym); - printf_dlsym(LOG_DEBUG, "warning call x86dlsym filename is %s 0x%lx %s\n", strlen(lmfile)?lmfile:"NULL", cpu->regs[R_EDI], (char*)symbol); - return NULL; -#else - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, cpu->regs[R_EDI], symbol); - printf_dlsym(LOG_DEBUG, "warning call call x86dlsym filename is %s handle 0x%lx ret=0x%lx\n", strlen(lmfile)?lmfile:"NULL", cpu->regs[R_EDI], ret); - if (ret) { - return (void *)ret; - } - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Symbol \"%s\" not found in %p)\n", rsymbol, handle); - printf_dlsym(LOG_NEVER, "%p return %p\n", dl->last_error, (void*)NULL); + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); + if (!entries || !entries->dlsym) { return NULL; -#endif } - if(dl->count[nlib]==0) { - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Bad handle %p (already closed))\n", handle); - printf_dlsym(LOG_NEVER, "%p return %p\n", dl->last_error, (void*)NULL); + printf_dlsym(LOG_DEBUG, "Call to dlsym(%p, \"%s\")\n", + handle, symbol ? (char *)symbol : ""); + result = kzt_guest_dl_api_dlsym( + my_context, entries, handle, symbol); + if (result.forward_to_guest_caller) { + Push64(cpu, entries->dlsym); return NULL; } - if(dl->libs[nlib]) { - if(my_dlsym_lib(dl->libs[nlib], rsymbol, &start, &end, -1, NULL)==0) { - // not found - __MY_CPU; - #if 1 - if(!dl->libs[nlib]->x86linkmap) { - //redlopen - kzt_guest_library_loader_scope_t scope = { 0 }; - uint64_t ret = run_guest_dlopen_scoped( - (uintptr_t)my_context->dlprivate->x86dlopen, - dl->libs[nlib]->name, dl->libs[nlib]->x86dlopenflag, - &scope); - if (!ret) {//user sometime test for finding a func. - finish_guest_dlopen_scoped(&scope, 0, NULL, 0); - printf_dlsym(LOG_NEVER, "redlopen %p return %p\n", rsymbol, (void*)NULL); - return NULL; - } - lsassert(ret); - dl->libs[nlib]->x86linkmap = (void *)ret; - finish_guest_dlopen_scoped( - &scope, (uintptr_t)ret, dl->libs[nlib], 1); - ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, dl->libs[nlib]->x86linkmap , cpu->regs[R_ESI]); - printf_dlsym(LOG_DEBUG, "call x86dlsym filename %s is wrapped but not find symbol, dlsym(%p, %s) ret=0x%lx\n", - dl->libs[nlib]->name, dl->libs[nlib]->x86linkmap, (char *)cpu->regs[R_ESI], ret); - return (void *)ret; - } - #endif - lsassert(dl->x86dlsym); - if (dl->libs[nlib]->x86linkmap != handle) { - cpu->regs[R_EDI] = (uintptr_t)dl->libs[nlib]->x86linkmap; - } -#if FORWORDBACK - Push64(cpu, (uint64_t)dl->x86dlsym); - printf_dlsym(LOG_DEBUG, "warning call x86dlsym filename is %s %lx\n", dl->libs[nlib]->x86linkmap->l_name, cpu->regs[R_EDI]); - return NULL; -#else - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, cpu->regs[R_EDI], cpu->regs[R_ESI]); - printf_dlsym(LOG_DEBUG, "call x86dlsym filename is %s %s ret=0x%lx\n", dl->libs[nlib]->x86linkmap->l_name, (char *)cpu->regs[R_ESI], ret); - if (ret) { - return (void *)ret; - } - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Symbol \"%s\" not found in %p)\n", rsymbol, handle); - printf_dlsym(LOG_NEVER, "%p return %p\n", dl->last_error, (void*)NULL); - return NULL; -#endif - } - } else { - // still usefull? - // => look globably -#ifdef LATX_RELOCATION_SAVE_SYMBOLS - if(GetGlobalSymbolStartEnd(my_context->maplib, rsymbol, &start, &end, NULL, -1, NULL)) { - printf_dlsym(LOG_NEVER, "%p\n", (void*)start); - return (void*)start; - } -#endif - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Symbol \"%s\" not found in %p)\n", rsymbol, handle); - printf_dlsym(LOG_NEVER, "%p\n", NULL); - lsassertm(0,"%s",dl->last_error); - return NULL; + if (result.value) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); } - printf_dlsym(LOG_NEVER, "%p\n", (void*)start); - return (void*)start; + return (void *)result.value; } int my_dlclose(void *handle) { printf_dlsym(LOG_DEBUG, "Call to dlclose(%p)\n", handle); dlprivate_t *dl = my_context->dlprivate; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + int result; + int guest_error_was_clean; CLEARERR - if (!dl->x86dlclose) { - init_x86dlfun(); - lsassert(dl->x86dlclose); + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); + if (!entries || !entries->dlclose) { + return -1; } - size_t nlib = (size_t)handle; - if(nlib > dl->lib_sz) { - for (int i = 0; i < dl->lib_sz; i++) { - if (dl->libs[i] && dl->libs[i]->active && dl->libs[i]->type == LIB_EMULATED && ((size_t)dl->libs[i]->x86linkmap) == nlib) { - nlib = i + 1; - break; - } - } + result = kzt_guest_dl_api_dlclose( + my_context, &cpu->kzt_guest_library_loader_scope, entries, handle); + if (result == 0) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); } - --nlib; - // size_t is unsigned - if(nlib>=dl->lib_sz) { - int ret = -1; - if (dl->x86dlclose) { - __MY_CPU; - Push64(cpu, (uint64_t)dl->x86dlclose); - return 0; + return result; +} + +static uintptr_t kzt_guest_dlerror_entry_slow( + box64context_t *context) __attribute__((noinline)); + +static uintptr_t kzt_guest_dlerror_entry_slow(box64context_t *context) +{ + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries = + kzt_guest_dl_entries_for_call(context, &fallback); + + return entries ? entries->dlerror : 0; +} + +static char *kzt_guest_dlerror_slow_path( + box64context_t *context, CPUX86State *cpu, + kzt_guest_dlerror_state_t *error_state, + uintptr_t guest_dlerror) __attribute__((noinline, cold)); + +static char *kzt_guest_dlerror_slow_path( + box64context_t *context, CPUX86State *cpu, + kzt_guest_dlerror_state_t *error_state, uintptr_t guest_dlerror) +{ + kzt_guest_dlerror_result_t result; + + if (kzt_guest_dl_api_dlerror_needs_slow_path(error_state)) { + result = kzt_guest_dl_api_dlerror(error_state, guest_dlerror); + if (!result.forward_to_guest_caller) { + return result.value; } - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Bad handle %p, ret = %d)\n", handle, ret); - printf_dlsym(LOG_DEBUG, "dlclose: %s\n", dl->last_error); - lsassertm(0,"%s",dl->last_error); - return -1; - } - if(dl->count[nlib]==0) { - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Bad handle %p (already closed))\n", handle); - printf_dlsym(LOG_DEBUG, "dlclose: %s\n", dl->last_error); - return -1; } - dl->count[nlib] = dl->count[nlib]-1; - if(dl->count[nlib]==0 && dl->dlopened[nlib]) { // need to call Fini... - int idx = GetElfIndex(dl->libs[nlib]); - if(idx!=-1) { - printf_dlsym(LOG_DEBUG, "dlclose: Call to Fini for %p\n", handle); - InactiveLibrary(dl->libs[nlib]); - if (dl->x86dlclose) { - __MY_CPU; - if (dl->libs[nlib]->x86linkmap != handle) { - cpu->regs[R_EDI] = (uintptr_t)dl->libs[nlib]->x86linkmap; - } - Push64(cpu, (uint64_t)dl->x86dlclose); - return 0; - } + if (!guest_dlerror) { + guest_dlerror = kzt_guest_dl_api_load_dlerror_hint( + context->dlprivate); + if (!guest_dlerror) { + guest_dlerror = kzt_guest_dlerror_entry_slow(context); } + if (!guest_dlerror) { + return NULL; + } + error_state->guest_dlerror_entry = guest_dlerror; } - return 0; + Push64(cpu, guest_dlerror); + return NULL; } char* my_dlerror(void) { - dlprivate_t *dl = my_context->dlprivate; - return dl->last_error; +#if defined(__loongarch__) + register char *fast_result __asm__("r4") = +#else + char *fast_result = +#endif + (char *)DLERROR_FAST_RESULT(); + + /* Keep the clean sentinel in the return register across the cold test. */ + __asm__ volatile("" : "+r"(fast_result)); + if (fast_result) { + dlprivate_t *dl = my_context->dlprivate; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + uintptr_t guest_dlerror = error_state->guest_dlerror_entry; + + return kzt_guest_dlerror_slow_path( + my_context, cpu, error_state, guest_dlerror); + } + return fast_result; } int my_dladdr1(void *addr, void *i, void** extra_info, int flags) { //int dladdr(void *addr, Dl_info *info); dlprivate_t *dl = my_context->dlprivate; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + int guest_error_was_clean; CLEARERR - if (!dl->x86dladdr1) { - init_x86dlfun(); - lsassert(dl->x86dladdr1); - } + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); Dl_info *info = (Dl_info*)i; printf_dlsym(LOG_DEBUG, "Warning: partially unimplement call to dladdr/dladdr1(%p, %p, %p, %d)\n", addr, info, extra_info, flags); - __MY_CPU; uint64_t ret = 0; - if (extra_info == NULL && flags == 0) { - ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dladdr, 2, cpu->regs[R_EDI], cpu->regs[R_ESI]); - } else { - ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dladdr1, 4, cpu->regs[R_EDI], cpu->regs[R_ESI], cpu->regs[R_EDX], cpu->regs[R_ECX]); + if (entries && extra_info == NULL && flags == 0 && entries->dladdr) { + ret = RunFunctionWithState(entries->dladdr, 2, cpu->regs[R_EDI], cpu->regs[R_ESI]); + } else if (entries && entries->dladdr1) { + ret = RunFunctionWithState(entries->dladdr1, 4, cpu->regs[R_EDI], cpu->regs[R_ESI], cpu->regs[R_EDX], cpu->regs[R_ECX]); } printf_dlsym(LOG_DEBUG, " call to x86dladdr1 return saddr=%p, fname=\"%s\", sname=\"%s\" ret=%ld\n", info->dli_saddr, info->dli_sname?info->dli_sname:"", info->dli_fname?info->dli_fname:"", ret); if (ret == 1) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); return ret; } //emu->quit = 1; @@ -4238,80 +3750,80 @@ int my_dladdr1(void *addr, void *i, void** extra_info, int flags) int my_dladdr(void *addr, void *i) { dlprivate_t *dl = my_context->dlprivate; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + int guest_error_was_clean; CLEARERR - if (!dl->x86dladdr) { - init_x86dlfun(); - lsassert(dl->x86dladdr); - } + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); #ifdef CONFIG_LATX_DEBUG Dl_info *info = (Dl_info*)i; #endif printf_dlsym(LOG_DEBUG, "Warning: partially unimplement call to dladdr(%p, %p)\n", addr, info); - __MY_CPU; - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dladdr, 2, cpu->regs[R_EDI], cpu->regs[R_ESI]); + uint64_t ret = entries && entries->dladdr + ? RunFunctionWithState(entries->dladdr, 2, + cpu->regs[R_EDI], + cpu->regs[R_ESI]) + : 0; printf_dlsym(LOG_DEBUG, " call to x86dladdr return saddr=%p, fname=\"%s\", sname=\"%s\" ret=%ld\n", info->dli_saddr, info->dli_sname?info->dli_sname:"", info->dli_fname?info->dli_fname:"", ret); if (ret == 1) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); return ret; } return my_dladdr1(addr, i, NULL, 0); } void* my_dlvsym(void *handle, void *symbol, const char *vername) { - printf_dlsym(LOG_DEBUG, "Call to dlvsym(%p, \"%s\", %s)", handle, (char *)symbol, vername?vername:"(nil)"); - return my_dlsym(handle, symbol); + dlprivate_t *dl = my_context->dlprivate; + kzt_guest_dl_symbol_result_t result; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + int guest_error_was_clean; + + CLEARERR + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); + if (!entries || !entries->dlvsym) { + return NULL; + } + printf_dlsym(LOG_DEBUG, "Call to dlvsym(%p, \"%s\", %s)\n", + handle, symbol ? (char *)symbol : "", + vername ? vername : "(nil)"); + result = kzt_guest_dl_api_dlvsym( + my_context, entries, handle, symbol, vername); + if (result.forward_to_guest_caller) { + Push64(cpu, entries->dlvsym); + return NULL; + } + if (result.value) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); + } + return (void *)result.value; } int my_dlinfo(void* handle, int request, void* info) { printf_dlsym(LOG_DEBUG, "Call to dlinfo(%p, %d, %p)\n", handle, request, info); dlprivate_t *dl = my_context->dlprivate; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + int result; + int guest_error_was_clean; + CLEARERR - lsassert(0);//latx not support yet. - if (!dl->x86dlopen) { - init_x86dlfun(); - lsassert(dl->x86dlopen); - } - size_t nlib = (size_t)handle; - if(nlib > dl->lib_sz) { - for (int i = 0; i < dl->lib_sz; i++) { - if (dl->libs[i] && dl->libs[i]->active && dl->libs[i]->type == LIB_EMULATED && ((size_t)dl->libs[i]->x86linkmap) == nlib) { - nlib = i + 1; - break; - } - } - } - --nlib; - // size_t is unsigned - if(nlib>=dl->lib_sz) { - if(!dl->last_error) - dl->last_error = box_calloc(1, 129); - snprintf(dl->last_error, 129, "Bad handle %p)\n", handle); - printf_dlsym(LOG_DEBUG, "dlinfo: %s\n", dl->last_error); + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); + if (!entries || !entries->dlinfo) { return -1; } - #if 0 - if(!dl->dllibs[nlib].count || !dl->dllibs[nlib].full) { - if(!dl->last_error) - dl->last_error = box_calloc(1, 129); - snprintf(dl->last_error, 129, "Bad handle %p (already closed))\n", handle); - printf_dlsym(LOG_DEBUG, "dlinfo: %s\n", dl->last_error); - return -1; + result = kzt_guest_dl_api_dlinfo(entries, handle, request, info); + if (result == 0) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); } - #endif - library_t *lib = dl->libs[nlib]; - switch(request) { - case 2: // RTLD_DI_LINKMAP - { - *(linkmap_t**)info = getLinkMapLib(lib); - } - return 0; - default: - printf_dlsym(LOG_NONE, "Warning, unsupported call to dlinfo(%p, %d, %p)\n", handle, request, info); - if(!dl->last_error) - dl->last_error = box_calloc(1, 129); - snprintf(dl->last_error, 129, "unsupported call to dlinfo request:%d\n", request); - } - return -1; + return result; } #endif diff --git a/target/i386/latx/context/wrappedlibdl.c b/target/i386/latx/context/wrappedlibdl.c index 611c4f5c637..ce99629a738 100644 --- a/target/i386/latx/context/wrappedlibdl.c +++ b/target/i386/latx/context/wrappedlibdl.c @@ -27,22 +27,15 @@ #include "callback.h" #include "myalign.h" #include "fileutils.h" -#include "dlopen_recycle_transaction.h" -#ifdef CONFIG_LATX_KZT #include "kzt_guest_library_adapter.h" +#include "kzt_guest_dl_api.h" +#include "kzt_guest_dl_init.h" +#ifdef CONFIG_LATX_KZT #include "kzt_guest_library_binding.h" #endif #include "kzt_loader_callback_scope.h" #define FORWORDBACK 0 -dlprivate_t *NewDLPrivate(void) { - dlprivate_t* dl = (dlprivate_t*)box_calloc(1, sizeof(dlprivate_t)); - return dl; -} -void FreeDLPrivate(dlprivate_t **lib) { - box_free((*lib)->last_error); - box_free(*lib); -} void* my_dlopen(void *filename, int flag) EXPORT; void* my_dlmopen(void* mlid, void *filename, int flag) EXPORT; @@ -57,7 +50,15 @@ int my_dlinfo(void* handle, int request, void* info) EXPORT; #define LIBNAME libdl const char* libdlName = "libdl.so.2"; -#define CLEARERR if(dl->last_error) box_free(dl->last_error); dl->last_error = NULL; +#if defined(CONFIG_LATX_KZT) && defined(TARGET_X86_64) +#define DLERROR_STATE(cpu, dl) ((void)(dl), &(cpu)->kzt_guest_dlerror_state) +#define DLERROR_FAST_RESULT() kzt_guest_dl_api_current_fast_result() +#else +#define DLERROR_STATE(cpu, dl) (&(dl)->legacy_error) +#define DLERROR_FAST_RESULT() \ + (my_context->dlprivate->legacy_error.dlerror_fast_result) +#endif +#define CLEARERR guest_error_was_clean = kzt_guest_dl_api_begin_call(error_state); //#define R_RSP cpu->regs[R_ESP] static void Push64(CPUX86State *cpu, uint64_t v) { @@ -65,678 +66,198 @@ static void Push64(CPUX86State *cpu, uint64_t v) *((uint64_t*)cpu->regs[R_ESP]) = v; } -int init_x86dlfun(void); -int init_x86dlfun(void) -{ - elfheader_t* h = loadElfFromFile("libdl.so.2"); - lsassert(h); - const char* syms[] = {"dlopen", "dlsym", "dlclose", "dladdr", "dladdr1", "dlinfo"}; - void *rsyms[6] = {0}; - int rrsyms = 0; - ResetSpecialCaseElf(h, syms, 6, rsyms, &rrsyms); - if (rrsyms != 6) { - h = loadElfFromFile("libc.so.6"); - ResetSpecialCaseElf(h, syms, 6, rsyms, &rrsyms); - } - lsassert(rrsyms == 6); - my_context->dlprivate->x86dlopen = rsyms[0]; - my_context->dlprivate->x86dlsym = rsyms[1]; - my_context->dlprivate->x86dlclose = rsyms[2]; - my_context->dlprivate->x86dladdr = rsyms[3]; - my_context->dlprivate->x86dladdr1 = rsyms[4]; - my_context->dlprivate->x86dlinfo = rsyms[5]; - return 0; -} -static uint64_t run_guest_dlopen_scoped(uintptr_t function, - void *filename, int flag, - kzt_guest_library_loader_scope_t *scope) -{ -#ifdef CONFIG_LATX_KZT +void* my_dlopen(void *filename, int flag){ + dlprivate_t *dl = my_context->dlprivate; __MY_CPU; - kzt_guest_library_loader_scope_t previous = - cpu->kzt_guest_library_loader_scope; - kzt_guest_library_bindings_t *bindings = - KztGuestLibraryBindingsForContext(my_context); - int scoped = scope && - kzt_guest_library_loader_scope_begin(bindings, scope) == 0; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; uint64_t result; + int guest_error_was_clean; - if (scoped) cpu->kzt_guest_library_loader_scope = *scope; - result = RunFunctionWithState(function, 2, filename, flag); - cpu->kzt_guest_library_loader_scope = previous; - return result; -#else - (void)scope; - return RunFunctionWithState(function, 2, filename, flag); -#endif + CLEARERR + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); + if (!entries || !entries->dlopen) { + return NULL; + } + printf_dlsym(LOG_DEBUG, "Call to dlopen(\"%s\"/%p, %X)\n", + filename ? (char *)filename : "", filename, flag); + result = kzt_guest_dl_api_dlopen( + my_context, &cpu->kzt_guest_library_loader_scope, + entries, error_state, filename, flag); + if (result) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); + } + return (void *)(uintptr_t)result; } -static void finish_guest_dlopen_scoped( - kzt_guest_library_loader_scope_t *scope, uintptr_t link_map_addr, - library_t *library, int publish) +void* my_dlmopen(void* lmid, void *filename, int flag) { -#ifdef CONFIG_LATX_KZT - if (publish && link_map_addr) { - if (library) - kzt_guest_library_publish_loader_pair_scoped( - my_context, scope, link_map_addr, library); - else - kzt_guest_library_publish_loader_observed_scoped( - my_context, scope, link_map_addr); - } - kzt_guest_library_loader_scope_end(scope); -#else - (void)scope; (void)link_map_addr; (void)library; (void)publish; -#endif -} + dlprivate_t *dl = my_context->dlprivate; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + uint64_t result; + int guest_error_was_clean; -static int callx86dlopen(void *filename, int flag, elfheader_t * h, - int is_local, int publish_pair) { - kzt_guest_library_loader_scope_t scope = { 0 }; - struct link_map* ret = (struct link_map*)(uintptr_t) - run_guest_dlopen_scoped( - (uintptr_t)my_context->dlprivate->x86dlopen, filename, flag, - &scope); - if (ret) { - printf_dlsym(LOG_DEBUG, "latx RunFunctionWithState dlopen %s addr %p\n", (char *)filename, (void *)ret->l_addr); - h->lib->x86linkmap = ret; - } else { - //open error - finish_guest_dlopen_scoped(&scope, 0, NULL, 0); - return -1; + if (!lmid) { + return my_dlopen(filename, flag); } - h->delta = ret->l_addr; - linkmap_t* lm = getLinkMapLib(h->lib); - if (lm) { - lm->l_addr = ret->l_addr; + CLEARERR + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); + if (!entries || !entries->dlmopen) { + return NULL; } - h->latx_hasfix = 1; - lib_t *maplib = (is_local)?h->lib->maplib:my_context->maplib; - if(AddSymbolsLibrary(maplib, h->lib)) { // also add needed libs - printf_dlsym(LOG_INFO, "Failure to Add lib => fail\n"); - lsassert(0); + result = kzt_guest_dl_api_dlmopen( + my_context, entries, lmid, filename, flag); + if (result) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); } - finish_guest_dlopen_scoped(&scope, (uintptr_t)ret, h->lib, - publish_pair); - return 0; -} -static void LatxResetElf(elfheader_t * h) -{ - h->latx_hasfix = 0; - h->had_RelocateElfPlt = 0; - h->had_RelocateElf = 0; - h->latx_type = 0; - h->latx_hasfix = 0; -} - -typedef struct dlopen_recycle_context { - void *filename; - int flag; - elfheader_t *header; - library_t *library; - int is_local; - int guest_open_required; -} dlopen_recycle_context_t; - -static void recycle_reset_loader_state(dlopen_recycle_context_t *recycle) -{ - linkmap_t *lm; - - recycle->library->x86linkmap = NULL; - if (!recycle->header) - return; - LatxResetElf(recycle->header); - recycle->header->delta = 0; - lm = getLinkMapLib(recycle->library); - if (lm) - lm->l_addr = 0; -} - -static void recycle_prepare(void *opaque) -{ - dlopen_recycle_context_t *recycle = opaque; - if (!recycle->guest_open_required) - return; - recycle_reset_loader_state(recycle); + return (void *)(uintptr_t)result; } -static int recycle_guest_open(void *opaque) +void* my_dlsym(void *handle, void *symbol) { - dlopen_recycle_context_t *recycle = opaque; -#ifdef CONFIG_LATX_KZT - if (recycle->guest_open_required && - kzt_guest_library_reactivate( - KztGuestLibraryBindingsForContext(recycle->library->context), - recycle->library) != 0) - return -1; -#endif - return recycle->guest_open_required - ? callx86dlopen(recycle->filename, recycle->flag, - recycle->header, recycle->is_local, 0) - : 0; -} - -static int recycle_reload(void *opaque) -{ - dlopen_recycle_context_t *recycle = opaque; - return ReloadLibrary(recycle->library); -} - -static void recycle_rollback(void *opaque, int guest_opened) -{ - dlopen_recycle_context_t *recycle = opaque; - InactiveLibrary(recycle->library); - if (guest_opened && recycle->guest_open_required && - recycle->library->x86linkmap && - my_context->dlprivate->x86dlclose) - (void)RunFunctionWithState( - (uintptr_t)my_context->dlprivate->x86dlclose, 1, - recycle->library->x86linkmap); - recycle_reset_loader_state(recycle); -} -void* my_dlopen(void *filename, int flag){ - // TODO, handling special values for filename, like RTLD_SELF? - // TODO, handling flags? - library_t *lib = NULL; dlprivate_t *dl = my_context->dlprivate; - size_t dlopened = 0; - int is_local = (flag&0x100)?0:1; // if not global, then local, and that means symbols are not put in the global "pot" for other libs + kzt_guest_dl_symbol_result_t result; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + int guest_error_was_clean; + CLEARERR - if (!dl->x86dlopen) { - init_x86dlfun(); - lsassert(dl->x86dlopen); - } - if(filename) { - char* rfilename = (char*)alloca(MAX_PATH); - strcpy(rfilename, (char*)filename); - printf_dlsym(LOG_DEBUG, "Call to dlopen(\"%s\"/%p, %X)\n", rfilename, filename, flag); - while(strstr(rfilename, "${ORIGIN}")) { - char* origin = box_strdup(my_context->fullpath); - char* p = strrchr(origin, '/'); - if(p) *p = '\0'; // remove file name to have only full path, without last '/' - char* tmp = (char*)box_calloc(1, strlen(rfilename)-strlen("${ORIGIN}")+strlen(origin)+1); - p = strstr(rfilename, "${ORIGIN}"); - memcpy(tmp, rfilename, p-rfilename); - strcat(tmp, origin); - strcat(tmp, p+strlen("${ORIGIN}")); - strcpy(rfilename, tmp); - box_free(tmp); - box_free(origin); - } - while(strstr(rfilename, "${PLATFORM}")) { - char* platform = box_strdup("x86_64"); - char* p = strrchr(platform, '/'); - if(p) *p = '\0'; // remove file name to have only full path, without last '/' - char* tmp = (char*)box_calloc(1, strlen(rfilename)-strlen("${PLATFORM}")+strlen(platform)+1); - p = strstr(rfilename, "${PLATFORM}"); - memcpy(tmp, rfilename, p-rfilename); - strcat(tmp, platform); - strcat(tmp, p+strlen("${PLATFORM}")); - strcpy(rfilename, tmp); - box_free(tmp); - box_free(platform); - } - if (rfilename[0] == '/' && !FileExist(rfilename, IS_FILE)) { - char filetmp[PATH_MAX] = {0}; - snprintf(filetmp , PATH_MAX, "%s%s", interp_prefix, rfilename); - strcpy(rfilename, filetmp); - printf_dlsym(LOG_DEBUG, "dlopen filename change to \"%s\"\n", rfilename); - } - // check if alread dlopenned... - for (size_t i=0; ilib_sz; ++i) { - if(IsSameLib(dl->libs[i], rfilename)) { - if(dl->count[i]==0 && dl->dlopened[i]) { // need to lauch init again! - int idx = GetElfIndex(dl->libs[i]); - if(idx!=-1) { - printf_dlsym(LOG_DEBUG, "dlopen: Recycling, calling Init for %p (%s)\n", (void*)(i+1), rfilename); - dlopen_recycle_context_t recycle = { - .filename = rfilename, - .flag = flag, - .library = dl->libs[i], - .is_local = is_local, - }; - if (IsEmuLib(dl->libs[i])) { - recycle.header = my_context->elfs[idx]; - lsassert(recycle.header); - recycle.guest_open_required = 1; - } - void *handle = dlopen_recycle_transaction( - (void *)(i + 1), &dl->count[i], !(flag & 0x4), - &recycle, recycle_prepare, recycle_guest_open, - recycle_reload, recycle_rollback); - if (!handle) { - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, - "Cannot recycle dlopen(\"%s\"/%p, %X)\n", - rfilename, filename, flag); - return NULL; - } - printf_dlsym(LOG_DEBUG, "dlopen: Recycling %s/%p count=%ld (dlopened=%ld, elf_index=%d)\n", rfilename, (void*)(i+1), dl->count[i], dl->dlopened[i], GetElfIndex(dl->libs[i])); - return handle; - } - } - if(!(flag&0x4)) - dl->count[i] = dl->count[i]+1; - printf_dlsym(LOG_DEBUG, "dlopen: Recycling %s/%p count=%ld (dlopened=%ld, elf_index=%d)\n", rfilename, (void*)(i+1), dl->count[i], dl->dlopened[i], GetElfIndex(dl->libs[i])); - return (void*)(i+1); - } - } - if(strstr(rfilename, "libGL.so")){ - strcpy(rfilename, "libGL.so.1"); - } - dlopened = (GetLibInternal(rfilename)==NULL); - // Then open the lib - const char* libs[] = {rfilename}; - my_context->deferedInit = 1; - int bindnow = (flag&0x2)?1:0; - if (!FindLibIsWrapped(basename(rfilename))) { -#if FORWORDBACK - lsassert(dl->x86dlopen); - __MY_CPU; - Push64(cpu, (uint64_t)dl->x86dlopen); - printf_dlsym(LOG_DEBUG, "warning call x86dlopen filename is %s %x\n", (char *)filename, flag); - return NULL; -#else - kzt_guest_library_loader_scope_t scope = { 0 }; - uint64_t ret = run_guest_dlopen_scoped( - (uintptr_t)my_context->dlprivate->x86dlopen, filename, flag, - &scope); - finish_guest_dlopen_scoped(&scope, (uintptr_t)ret, NULL, - ret != 0); - printf_dlsym(LOG_DEBUG, "warning call call x86dlopen filename %s %x ret=0x%lx\n", (char *)filename, flag, ret); - //lsassert(0); - if (ret) { - return (void *)ret; - } - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "filename \"%s\" flag=%x\n", (char *)filename, flag); - printf_dlsym(LOG_NEVER, "%p return %p\n", dl->last_error, (void*)NULL); - return NULL; -#endif - } - if(AddNeededLibWithLibrary(NULL, NULL, NULL, is_local, bindnow, - rfilename, my_context, &lib) || !lib) { - printf_dlsym(strchr(rfilename,'/')?LOG_DEBUG:LOG_INFO, "Warning: Cannot dlopen(\"%s\"/%p, %X)\n", rfilename, filename, flag); - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Cannot dlopen(\"%s\"/%p, %X)\n", rfilename, filename, flag); - return NULL; - } - lib->x86dlopenflag = flag; - if (lib && lib->type == LIB_EMULATED) { - // if dlopened = 0 ---> lib added but not loaded - int libidx = GetElfIndex(lib); - lsassert(libidx >= 0); - elfheader_t * h = my_context->elfs[libidx]; - lsassert(h); - if (!h->latx_hasfix || !lib->x86linkmap) {//lib->x86linkmap is null ---- this lib has been needed by other elf and opened - callx86dlopen(rfilename, flag, h, is_local, 1); - } - } - //TODO:RunDeferedElfInit; - } else { - // check if already dlopenned... - for (size_t i=0; ilib_sz; ++i) { - if(!dl->libs[i]) { - dl->count[i] = dl->count[i]+1; - return (void*)(i+1); - } - } - printf_dlsym(LOG_DEBUG, "Call to dlopen(NULL, %X) forword call x86dlopen \n", flag); - lsassert(dl->x86dlopen); - __MY_CPU; - Push64(cpu, (uint64_t)dl->x86dlopen); + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); + if (!entries || !entries->dlsym) { return NULL; } - //get the lib and add it to the collection - - if(dl->lib_sz == dl->lib_cap) { - dl->lib_cap += 4; - dl->libs = (library_t**)box_realloc(dl->libs, sizeof(library_t*)*dl->lib_cap); - dl->count = (size_t*)box_realloc(dl->count, sizeof(size_t)*dl->lib_cap); - dl->dlopened = (size_t*)box_realloc(dl->dlopened, sizeof(size_t)*dl->lib_cap); - // memset count... - memset(dl->count+dl->lib_sz, 0, (dl->lib_cap-dl->lib_sz)*sizeof(size_t)); + printf_dlsym(LOG_DEBUG, "Call to dlsym(%p, \"%s\")\n", + handle, symbol ? (char *)symbol : ""); + result = kzt_guest_dl_api_dlsym( + my_context, entries, handle, symbol); + if (result.forward_to_guest_caller) { + Push64(cpu, entries->dlsym); + return NULL; } - intptr_t idx = dl->lib_sz++; - dl->libs[idx] = lib; - dl->count[idx] = dl->count[idx]+1; - dl->dlopened[idx] = dlopened; - printf_dlsym(LOG_DEBUG, "dlopen: New handle %p (%s), dlopened=%ld\n", (void*)(idx+1), (char*)filename, dlopened); - if (lib && lib->type == LIB_EMULATED) { - return lib->x86linkmap; + if (result.value) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); } - return (void*)(idx+1); + return (void *)result.value; } -void* my_dlmopen(void* lmid, void *filename, int flag) +int my_dlclose(void *handle) { - if(lmid) { - printf_dlsym(LOG_INFO, "Warning, dlmopen(%p, %p(\"%s\"), 0x%x) called with lmid not LMID_ID_BASE (unsupported)\n", lmid, filename, filename?(char*)filename:"self", flag); + printf_dlsym(LOG_DEBUG, "Call to dlclose(%p)\n", handle); + dlprivate_t *dl = my_context->dlprivate; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + int result; + int guest_error_was_clean; + CLEARERR + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); + if (!entries || !entries->dlclose) { + return -1; } - // lmid is ignored for now... - return my_dlopen(filename, flag); + result = kzt_guest_dl_api_dlclose( + my_context, &cpu->kzt_guest_library_loader_scope, entries, handle); + if (result == 0) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); + } + return result; } -KHASH_SET_INIT_INT(libs); +static uintptr_t kzt_guest_dlerror_entry_slow( + box64context_t *context) __attribute__((noinline)); -static int recursive_dlsym_lib(kh_libs_t* collection, library_t* lib, const char* rsymbol, uintptr_t *start, uintptr_t *end, int version, const char* vername) +static uintptr_t kzt_guest_dlerror_entry_slow(box64context_t *context) { - if(!lib) - return 0; - khint_t k = kh_get(libs, collection, (uintptr_t)lib); - if(k != kh_end(collection)) - return 0; - int ret; - kh_put(libs, collection, (uintptr_t)lib, &ret); - // look in the library itself - khint_t pre_k = kh_str_hash_func(rsymbol); - if(lib->get(lib, rsymbol, pre_k, start, end, version, vername, 1)) - return 1; - // look in other libs - int n = GetNeededLibN(lib); - for (int i=0; idlerror : 0; } -static int my_dlsym_lib(library_t* lib, const char* rsymbol, uintptr_t *start, uintptr_t *end, int version, const char* vername) -{ - kh_libs_t *collection = kh_init(libs); - int ret = recursive_dlsym_lib(collection, lib, rsymbol, start, end, version, vername); - kh_destroy(libs, collection); +static char *kzt_guest_dlerror_slow_path( + box64context_t *context, CPUX86State *cpu, + kzt_guest_dlerror_state_t *error_state, + uintptr_t guest_dlerror) __attribute__((noinline, cold)); - return ret; -} +static char *kzt_guest_dlerror_slow_path( + box64context_t *context, CPUX86State *cpu, + kzt_guest_dlerror_state_t *error_state, uintptr_t guest_dlerror) +{ + kzt_guest_dlerror_result_t result; -void* my_dlsym(void *handle, void *symbol){ - dlprivate_t *dl = my_context->dlprivate; - uintptr_t start = 0, end = 0; - char* rsymbol = (char*)symbol; - CLEARERR - if (!dl->x86dlsym) { - init_x86dlfun(); - lsassert(dl->x86dlsym); - } - printf_dlsym(LOG_DEBUG, "Call to dlsym(%p, \"%s\")%s\n", handle, rsymbol, dlsym_error?"":"\n"); - //lsassert(!strstr(rsymbol, "XcursorGetDefaultSize")); - if(handle==NULL) { - // special case, look globably -#ifdef LATX_RELOCATION_SAVE_SYMBOLS - if(GetGlobalSymbolStartEnd(my_context->maplib, rsymbol, &start, &end, NULL, -1, NULL)) { - printf_dlsym(LOG_NEVER, "%p\n", (void*)start); - return (void*)start; - } -#endif -#if 0 - lsassert(dl->x86dlsym); - __MY_CPU; - Push64(cpu, (uint64_t)dl->x86dlsym); - printf_dlsym(LOG_DEBUG, "warning call x86dlsym filename is NULL\n"); - return NULL; -#else - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, handle, symbol); - printf_dlsym(LOG_DEBUG, "warning call x86dlsym filename is NULL ret=0x%lx\n", ret); - if (ret) { - return (void *)ret; - } else { - if(GetGlobalSymbolStartEnd(my_context->maplib, rsymbol, &start, &end, NULL, -1, NULL)) { - printf_dlsym(LOG_NEVER, "%p\n", (void*)start); - return (void*)start; - } - printf_dlsym(LOG_NEVER, "debug my %d\n", __LINE__); - } - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Symbol \"%s\" not found in %p)\n", rsymbol, handle); - printf_dlsym(LOG_NEVER, "%p return %p\n", dl->last_error, (void*)NULL); - return NULL; -#endif - } - if(handle==(void*)~0LL) { - // special case (RTLD_NEXT) -- call x86dlsym - lsassert(dl->x86dlsym); - __MY_CPU; - Push64(cpu, (uint64_t)dl->x86dlsym); - printf_dlsym(LOG_DEBUG, "warning call x86dlsym filename is RTLD_NEXT\n"); - return NULL; - } - size_t nlib = (size_t)handle; - if(nlib > dl->lib_sz) { - for (int i = 0; i < dl->lib_sz; i++) { - if (dl->libs[i] && dl->libs[i]->active && dl->libs[i]->type == LIB_EMULATED && ((size_t)dl->libs[i]->x86linkmap) == nlib) { - nlib = i + 1; - break; - } + if (kzt_guest_dl_api_dlerror_needs_slow_path(error_state)) { + result = kzt_guest_dl_api_dlerror(error_state, guest_dlerror); + if (!result.forward_to_guest_caller) { + return result.value; } } - --nlib; - // size_t is unsigned - if(nlib>=dl->lib_sz) { -#ifdef LATX_RELOCATION_SAVE_SYMBOLS - if(GetGlobalSymbolStartEnd(my_context->maplib, rsymbol, &start, &end, NULL, -1, NULL)) { - printf_dlsym(LOG_NEVER, "%p\n", (void*)start); - return (void*)start; - } -#endif - const char* lmfile = ((struct link_map *)handle)->l_name; - if (strlen(lmfile)) { - const char* libs[] = {basename(lmfile)}; - //try to wrapper. - int iswrapped = 0.; - if (FindLibIsWrapped((char *)libs[0])) { - //if file is wrapped. - iswrapped = 1; - printf_dlsym(LOG_DEBUG, "find lib \"%s\" shuold be wrapped. init it.\n", libs[0]); - if(AddNeededLib(NULL, NULL, NULL, 0, 1, libs, 1, my_context)) { - printf_dlsym(LOG_DEBUG, "Warning: Cannot AddNeededLib(\"%s\")\n", libs[0]); - } - printf_dlsym(LOG_DEBUG, "info: success AddNeededLib(\"%s\")\n", libs[0]); - if(GetGlobalSymbolStartEnd(my_context->maplib, rsymbol, &start, &end, NULL, -1, NULL)) { - printf_dlsym(LOG_NEVER, "%p\n", (void*)start); - return (void*)start; - } - } - if (iswrapped) { - //Perhaps exe want to test func for earch libs, return nil. - printf_dlsym(LOG_NEVER, "%p\n", (void*)NULL); - return NULL; - } + if (!guest_dlerror) { + guest_dlerror = kzt_guest_dl_api_load_dlerror_hint( + context->dlprivate); + if (!guest_dlerror) { + guest_dlerror = kzt_guest_dlerror_entry_slow(context); } -#if !defined(LATX_RELOCATION_SAVE_SYMBOLS) - else {//dlopen(NULL) --- dlopen self maplink filename is "NULL". - if(GetGlobalSymbolStartEnd(my_context->maplib, rsymbol, &start, &end, NULL, -1, NULL)) { - printf_dlsym(LOG_NEVER, "%p\n", (void*)start); - return (void*)start; - } - } -#endif - __MY_CPU; -#if FORWORDBACK - lsassert(dl->x86dlsym); - Push64(cpu, (uint64_t)dl->x86dlsym); - printf_dlsym(LOG_DEBUG, "warning call x86dlsym filename is %s 0x%lx %s\n", strlen(lmfile)?lmfile:"NULL", cpu->regs[R_EDI], (char*)symbol); - return NULL; -#else - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, cpu->regs[R_EDI], symbol); - printf_dlsym(LOG_DEBUG, "warning call call x86dlsym filename is %s handle 0x%lx ret=0x%lx\n", strlen(lmfile)?lmfile:"NULL", cpu->regs[R_EDI], ret); - if (ret) { - return (void *)ret; - } - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Symbol \"%s\" not found in %p)\n", rsymbol, handle); - printf_dlsym(LOG_NEVER, "%p return %p\n", dl->last_error, (void*)NULL); - return NULL; -#endif - } - if(dl->count[nlib]==0) { - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Bad handle %p (already closed))\n", handle); - printf_dlsym(LOG_NEVER, "%p return %p\n", dl->last_error, (void*)NULL); - return NULL; - } - if(dl->libs[nlib]) { - if(my_dlsym_lib(dl->libs[nlib], rsymbol, &start, &end, -1, NULL)==0) { - // not found - __MY_CPU; - #if 1 - if(!dl->libs[nlib]->x86linkmap) { - //redlopen - kzt_guest_library_loader_scope_t scope = { 0 }; - uint64_t ret = run_guest_dlopen_scoped( - (uintptr_t)my_context->dlprivate->x86dlopen, - dl->libs[nlib]->name, dl->libs[nlib]->x86dlopenflag, - &scope); - if (!ret) {//user sometime test for finding a func. - finish_guest_dlopen_scoped(&scope, 0, NULL, 0); - printf_dlsym(LOG_NEVER, "redlopen %p return %p\n", rsymbol, (void*)NULL); - return NULL; - } - lsassert(ret); - dl->libs[nlib]->x86linkmap = (void *)ret; - finish_guest_dlopen_scoped( - &scope, (uintptr_t)ret, dl->libs[nlib], 1); - ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, dl->libs[nlib]->x86linkmap , cpu->regs[R_ESI]); - printf_dlsym(LOG_DEBUG, "call x86dlsym filename %s is wrapped but not find symbol, dlsym(%p, %s) ret=0x%lx\n", - dl->libs[nlib]->name, dl->libs[nlib]->x86linkmap, (char *)cpu->regs[R_ESI], ret); - return (void *)ret; - } - #endif - lsassert(dl->x86dlsym); - if (dl->libs[nlib]->x86linkmap != handle) { - cpu->regs[R_EDI] = (uintptr_t)dl->libs[nlib]->x86linkmap; - } -#if FORWORDBACK - Push64(cpu, (uint64_t)dl->x86dlsym); - printf_dlsym(LOG_DEBUG, "warning call x86dlsym filename is %s %lx\n", dl->libs[nlib]->x86linkmap->l_name, cpu->regs[R_EDI]); - return NULL; -#else - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, cpu->regs[R_EDI], cpu->regs[R_ESI]); - printf_dlsym(LOG_DEBUG, "call x86dlsym filename is %s %s ret=0x%lx\n", dl->libs[nlib]->x86linkmap->l_name, (char *)cpu->regs[R_ESI], ret); - if (ret) { - return (void *)ret; - } - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Symbol \"%s\" not found in %p)\n", rsymbol, handle); - printf_dlsym(LOG_NEVER, "%p return %p\n", dl->last_error, (void*)NULL); + if (!guest_dlerror) { return NULL; -#endif - } - } else { - // still usefull? - // => look globably -#ifdef LATX_RELOCATION_SAVE_SYMBOLS - if(GetGlobalSymbolStartEnd(my_context->maplib, rsymbol, &start, &end, NULL, -1, NULL)) { - printf_dlsym(LOG_NEVER, "%p\n", (void*)start); - return (void*)start; } -#endif - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Symbol \"%s\" not found in %p)\n", rsymbol, handle); - printf_dlsym(LOG_NEVER, "%p\n", NULL); - lsassertm(0,"%s",dl->last_error); - return NULL; + error_state->guest_dlerror_entry = guest_dlerror; } - printf_dlsym(LOG_NEVER, "%p\n", (void*)start); - return (void*)start; -} - -int my_dlclose(void *handle) -{ - printf_dlsym(LOG_DEBUG, "Call to dlclose(%p)\n", handle); - dlprivate_t *dl = my_context->dlprivate; - CLEARERR - if (!dl->x86dlclose) { - init_x86dlfun(); - lsassert(dl->x86dlclose); - } - size_t nlib = (size_t)handle; - if(nlib > dl->lib_sz) { - for (int i = 0; i < dl->lib_sz; i++) { - if (dl->libs[i] && dl->libs[i]->active && dl->libs[i]->type == LIB_EMULATED && ((size_t)dl->libs[i]->x86linkmap) == nlib) { - nlib = i + 1; - break; - } - } - } - --nlib; - // size_t is unsigned - if(nlib>=dl->lib_sz) { - int ret = -1; - if (dl->x86dlclose) { - __MY_CPU; - Push64(cpu, (uint64_t)dl->x86dlclose); - return 0; - } - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Bad handle %p, ret = %d)\n", handle, ret); - printf_dlsym(LOG_DEBUG, "dlclose: %s\n", dl->last_error); - lsassertm(0,"%s",dl->last_error); - return -1; - } - if(dl->count[nlib]==0) { - if(!dl->last_error) - dl->last_error = box_malloc(129); - snprintf(dl->last_error, 129, "Bad handle %p (already closed))\n", handle); - printf_dlsym(LOG_DEBUG, "dlclose: %s\n", dl->last_error); - return -1; - } - dl->count[nlib] = dl->count[nlib]-1; - if(dl->count[nlib]==0 && dl->dlopened[nlib]) { // need to call Fini... - int idx = GetElfIndex(dl->libs[nlib]); - if(idx!=-1) { - printf_dlsym(LOG_DEBUG, "dlclose: Call to Fini for %p\n", handle); - InactiveLibrary(dl->libs[nlib]); - if (dl->x86dlclose) { - __MY_CPU; - if (dl->libs[nlib]->x86linkmap != handle) { - cpu->regs[R_EDI] = (uintptr_t)dl->libs[nlib]->x86linkmap; - } - Push64(cpu, (uint64_t)dl->x86dlclose); - return 0; - } - } - } - return 0; + Push64(cpu, guest_dlerror); + return NULL; } char* my_dlerror(void) { - dlprivate_t *dl = my_context->dlprivate; - return dl->last_error; +#if defined(__loongarch__) + register char *fast_result __asm__("r4") = +#else + char *fast_result = +#endif + (char *)DLERROR_FAST_RESULT(); + + /* Keep the clean sentinel in the return register across the cold test. */ + __asm__ volatile("" : "+r"(fast_result)); + if (fast_result) { + dlprivate_t *dl = my_context->dlprivate; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + uintptr_t guest_dlerror = error_state->guest_dlerror_entry; + + return kzt_guest_dlerror_slow_path( + my_context, cpu, error_state, guest_dlerror); + } + return fast_result; } int my_dladdr1(void *addr, void *i, void** extra_info, int flags) { //int dladdr(void *addr, Dl_info *info); dlprivate_t *dl = my_context->dlprivate; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + int guest_error_was_clean; CLEARERR - if (!dl->x86dladdr1) { - init_x86dlfun(); - lsassert(dl->x86dladdr1); - } + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); Dl_info *info = (Dl_info*)i; printf_dlsym(LOG_DEBUG, "Warning: partially unimplement call to dladdr/dladdr1(%p, %p, %p, %d)\n", addr, info, extra_info, flags); - __MY_CPU; uint64_t ret = 0; - if (extra_info == NULL && flags == 0) { - ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dladdr, 2, cpu->regs[R_EDI], cpu->regs[R_ESI]); - } else { - ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dladdr1, 4, cpu->regs[R_EDI], cpu->regs[R_ESI], cpu->regs[R_EDX], cpu->regs[R_ECX]); + if (entries && extra_info == NULL && flags == 0 && entries->dladdr) { + ret = RunFunctionWithState(entries->dladdr, 2, cpu->regs[R_EDI], cpu->regs[R_ESI]); + } else if (entries && entries->dladdr1) { + ret = RunFunctionWithState(entries->dladdr1, 4, cpu->regs[R_EDI], cpu->regs[R_ESI], cpu->regs[R_EDX], cpu->regs[R_ECX]); } printf_dlsym(LOG_DEBUG, " call to x86dladdr1 return saddr=%p, fname=\"%s\", sname=\"%s\" ret=%ld\n", info->dli_saddr, info->dli_sname?info->dli_sname:"", info->dli_fname?info->dli_fname:"", ret); if (ret == 1) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); return ret; } //emu->quit = 1; @@ -756,41 +277,80 @@ int my_dladdr1(void *addr, void *i, void** extra_info, int flags) int my_dladdr(void *addr, void *i) { dlprivate_t *dl = my_context->dlprivate; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + int guest_error_was_clean; CLEARERR - if (!dl->x86dladdr) { - init_x86dlfun(); - lsassert(dl->x86dladdr); - } + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); #ifdef CONFIG_LATX_DEBUG Dl_info *info = (Dl_info*)i; #endif printf_dlsym(LOG_DEBUG, "Warning: partially unimplement call to dladdr(%p, %p)\n", addr, info); - __MY_CPU; - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dladdr, 2, cpu->regs[R_EDI], cpu->regs[R_ESI]); + uint64_t ret = entries && entries->dladdr + ? RunFunctionWithState(entries->dladdr, 2, + cpu->regs[R_EDI], + cpu->regs[R_ESI]) + : 0; printf_dlsym(LOG_DEBUG, " call to x86dladdr return saddr=%p, fname=\"%s\", sname=\"%s\" ret=%ld\n", info->dli_saddr, info->dli_sname?info->dli_sname:"", info->dli_fname?info->dli_fname:"", ret); if (ret == 1) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); return ret; } return my_dladdr1(addr, i, NULL, 0); } void* my_dlvsym(void *handle, void *symbol, const char *vername) { - printf_dlsym(LOG_DEBUG, "Call to dlvsym(%p, \"%s\", %s)", handle, (char *)symbol, vername?vername:"(nil)"); - return my_dlsym(handle, symbol); + dlprivate_t *dl = my_context->dlprivate; + kzt_guest_dl_symbol_result_t result; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + int guest_error_was_clean; + + CLEARERR + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); + if (!entries || !entries->dlvsym) { + return NULL; + } + printf_dlsym(LOG_DEBUG, "Call to dlvsym(%p, \"%s\", %s)\n", + handle, symbol ? (char *)symbol : "", + vername ? vername : "(nil)"); + result = kzt_guest_dl_api_dlvsym( + my_context, entries, handle, symbol, vername); + if (result.forward_to_guest_caller) { + Push64(cpu, entries->dlvsym); + return NULL; + } + if (result.value) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); + } + return (void *)result.value; } int my_dlinfo(void* handle, int request, void* info) { printf_dlsym(LOG_DEBUG, "Call to dlinfo(%p, %d, %p)\n", handle, request, info); dlprivate_t *dl = my_context->dlprivate; + __MY_CPU; + kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); + kzt_guest_dl_entries_t fallback; + const kzt_guest_dl_entries_t *entries; + int result; + int guest_error_was_clean; + CLEARERR - if (!dl->x86dlinfo) { - init_x86dlfun(); - lsassert(dl->x86dlinfo); + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); + if (!entries || !entries->dlinfo) { + return -1; } - __MY_CPU; - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlinfo, 3, cpu->regs[R_EDI], cpu->regs[R_ESI], cpu->regs[R_EDX]); - return ret; + result = kzt_guest_dl_api_dlinfo(entries, handle, request, info); + if (result == 0) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); + } + return result; } #include "wrappedlib_init.h" diff --git a/target/i386/latx/include/box64context.h b/target/i386/latx/include/box64context.h index af02bfe7e37..eea79e35a5a 100755 --- a/target/i386/latx/include/box64context.h +++ b/target/i386/latx/include/box64context.h @@ -6,7 +6,10 @@ #include #include "debug.h" #include "kzt_guest_library_binding.h" +#include "kzt_guest_dl_state.h" #include "kzt_guest_registry_context.h" +#include "kzt_guest_scope_layout.h" +#include "kzt_lazy_prebind_scope.h" #include "kzt_patch_spike_guard.h" typedef struct elfheader_s elfheader_t; @@ -44,19 +47,8 @@ void add_dependedlib(needed_libs_t* depended, library_t* lib); void free_dependedlib(needed_libs_t* depended); typedef struct dlprivate_s { - library_t **libs; - size_t *count; - size_t *dlopened; - struct link_map *dlx86handle; - size_t lib_sz; - size_t lib_cap; - char* last_error; - void * x86dlopen; - void * x86dlclose; - void * x86dlsym; - void * x86dladdr1; - void * x86dladdr; - void * x86dlinfo; + kzt_guest_dlerror_state_t legacy_error; + kzt_guest_dl_entry_state_t guest_dl_entries; } dlprivate_t; struct latx_kzt_debug { char *name; @@ -352,8 +344,10 @@ typedef struct box64context_s { #ifdef CONFIG_LATX_KZT kzt_guest_registry_context_t kzt_guest_registry_context; kzt_guest_library_access_t kzt_guest_library_access; + kzt_guest_scope_layout_t kzt_guest_scope_layout; + kzt_lazy_prebind_scope_t *kzt_lazy_prebind_scope; kzt_patch_spike_guard_t kzt_patch_spike_guard; - pthread_mutex_t kzt_bridge_mutex; + uintptr_t kzt_plt_resolver_bridge; uintptr_t kzt_lazy_completion_bridge; #endif } box64context_t; @@ -370,6 +364,8 @@ int KztGuestLibraryLookupForContext( box64context_t *context, const kzt_guest_library_binding_key_t *key, kzt_guest_library_handle_t *handle); +kzt_lazy_prebind_scope_t *KztLazyPrebindScopeForContext( + box64context_t *context); kzt_patch_spike_guard_t *KztPatchSpikeGuardForContext(box64context_t *context); #endif diff --git a/target/i386/latx/include/elfloader.h b/target/i386/latx/include/elfloader.h index 40a0f915fa6..58dfdfce64a 100755 --- a/target/i386/latx/include/elfloader.h +++ b/target/i386/latx/include/elfloader.h @@ -2,9 +2,8 @@ #define __ELF_LOADER_H_ #include #include "elf.h" +#include "kzt_guest_dynamic_view.h" extern uintptr_t pltResolver; -extern uintptr_t dl_runtime_resolver; -extern uintptr_t link_map_obj; typedef struct elfheader_s elfheader_t; typedef struct lib_s lib_t; typedef struct library_s library_t; @@ -59,6 +58,14 @@ void* GetNativeSymbolUnversionned(void* lib, const char* name); void AddMainElfToLinkmap(elfheader_t* lib); void PltResolver(void); +uintptr_t KztPltResolverBridge(void); +int KztPltResolverDispatch(void *cpu_state, uintptr_t pc); +int KztPrebindTargetTbPrepare(uintptr_t target); +int KztPerObjectGotPltWrite(uintptr_t link_map_addr, + unsigned long generation, + const kzt_guest_dynamic_view_t *view, + void *opaque); +void KztPerObjectGotPltRelease(uintptr_t object_head); int RelocateElfRELA(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t* head, int cnt, Elf64_Rela *rela, int* need_resolv); uintptr_t loadSoaddrFromMap(char * real_path); diff --git a/target/i386/latx/include/elfloader_private.h b/target/i386/latx/include/elfloader_private.h index 933be08830a..7a926dc2bb9 100755 --- a/target/i386/latx/include/elfloader_private.h +++ b/target/i386/latx/include/elfloader_private.h @@ -96,6 +96,7 @@ struct elfheader_s { library_t *lib; needed_libs_t *neededlibs; uintptr_t self_link_map; + uintptr_t kzt_guest_resolver; FILE* file; int fileno; int had_RelocateElfPlt; diff --git a/target/i386/latx/include/generated/wrappedlibdltypes.h b/target/i386/latx/include/generated/wrappedlibdltypes.h index ed802d2c3ee..16f8ebee5a4 100644 --- a/target/i386/latx/include/generated/wrappedlibdltypes.h +++ b/target/i386/latx/include/generated/wrappedlibdltypes.h @@ -30,4 +30,3 @@ typedef int64_t (*iFpppi_t)(void*, void*, void*, int64_t); GO(dladdr1, iFpppi_t) #endif // __wrappedlibdlTYPES_H_ - diff --git a/target/i386/latx/include/kzt_guest_dl_api.h b/target/i386/latx/include/kzt_guest_dl_api.h new file mode 100644 index 00000000000..7ee9690bcfa --- /dev/null +++ b/target/i386/latx/include/kzt_guest_dl_api.h @@ -0,0 +1,127 @@ +#ifndef KZT_GUEST_DL_API_H +#define KZT_GUEST_DL_API_H + +#include + +#include "box64context.h" +#include "kzt_guest_registry.h" +#include "kzt_loader_callback_scope.h" + +typedef struct kzt_guest_dl_symbol_result_s { + uintptr_t value; + int forward_to_guest_caller; +} kzt_guest_dl_symbol_result_t; + +typedef struct kzt_guest_dlerror_result_s { + char *value; + int forward_to_guest_caller; +} kzt_guest_dlerror_result_t; + +void kzt_guest_dl_api_clear_error(kzt_guest_dlerror_state_t *state); + +extern __thread uintptr_t kzt_guest_dlerror_fast_result_tls; + +static inline uintptr_t kzt_guest_dl_api_current_fast_result(void) +{ + return kzt_guest_dlerror_fast_result_tls; +} + +static inline void kzt_guest_dl_api_set_slow_required( + kzt_guest_dlerror_state_t *state, int required) +{ + if (!state) { + return; + } + state->dlerror_slow_required = required; + if (state->dlerror_fast_result_mirror) { + *state->dlerror_fast_result_mirror = state->dlerror_fast_result; + } +} + +static inline int kzt_guest_dl_api_dlerror_needs_slow_path( + const kzt_guest_dlerror_state_t *state) +{ + return !state || state->dlerror_fast_result; +} + +static inline int kzt_guest_dl_api_begin_call( + kzt_guest_dlerror_state_t *state) +{ + int was_clean = state && !state->dlerror_fast_result; + + kzt_guest_dl_api_clear_error(state); + return was_clean; +} + +static inline void kzt_guest_dl_api_finish_success( + kzt_guest_dlerror_state_t *state, int was_clean) +{ + if (state && was_clean) { + kzt_guest_dl_api_set_slow_required(state, 0); + } +} + +void kzt_guest_dl_api_bind_current_thread(kzt_guest_dlerror_state_t *state); + +void kzt_guest_dl_api_free_errors(kzt_guest_dlerror_state_t *state); +int kzt_guest_dl_api_entry_state_init(dlprivate_t *dl); +void kzt_guest_dl_api_entry_state_destroy(dlprivate_t *dl); +static inline const kzt_guest_dl_entries_t * +kzt_guest_dl_api_load_entries(dlprivate_t *dl) +{ + return dl ? __atomic_load_n( + &dl->guest_dl_entries.published, __ATOMIC_ACQUIRE) : NULL; +} +static inline uintptr_t kzt_guest_dl_api_load_dlerror_hint(dlprivate_t *dl) +{ + return dl ? __atomic_load_n( + &dl->guest_dl_entries.observed_dlerror, + __ATOMIC_RELAXED) : 0; +} +const kzt_guest_dl_entries_t *kzt_guest_dl_api_ensure_entries( + dlprivate_t *dl, kzt_guest_dl_entries_resolver_fn resolver, void *opaque, + kzt_guest_dl_entries_t *fallback, int *published_now); +const kzt_guest_dl_entries_t *kzt_guest_dl_api_ensure_entries_prepared( + dlprivate_t *dl, kzt_guest_dl_entries_resolver_fn resolver, + kzt_guest_dl_entries_prepare_fn prepare, void *opaque, + kzt_guest_dl_entries_t *fallback, int *published_now); +uintptr_t kzt_guest_dl_api_load_dlerror_entry(dlprivate_t *dl); +int kzt_guest_dl_api_publish_dlerror_entry( + dlprivate_t *dl, const char *symbol, uintptr_t guest_entry, + int custom_wrapper); + +uint64_t kzt_guest_dl_api_dlopen( + box64context_t *context, + kzt_guest_library_loader_scope_t *thread_scope, + const kzt_guest_dl_entries_t *entries, + kzt_guest_dlerror_state_t *error_state, + const void *filename, int flag); +int kzt_guest_dl_api_dlclose( + box64context_t *context, + kzt_guest_library_loader_scope_t *thread_scope, + const kzt_guest_dl_entries_t *entries, void *handle); +int kzt_guest_dl_api_publish_unload( + box64context_t *context, + const kzt_guest_loader_identity_t *identity); +int kzt_guest_dl_api_prepare_unload( + box64context_t *context, + const kzt_guest_loader_identity_t *identity); +int kzt_guest_dl_api_cancel_unload( + box64context_t *context, + const kzt_guest_loader_identity_t *identity); +uint64_t kzt_guest_dl_api_dlmopen( + box64context_t *context, const kzt_guest_dl_entries_t *entries, + void *lmid, void *filename, int flag); +kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlsym( + box64context_t *context, const kzt_guest_dl_entries_t *entries, + void *handle, void *symbol); +kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlvsym( + box64context_t *context, const kzt_guest_dl_entries_t *entries, + void *handle, void *symbol, const char *version); +kzt_guest_dlerror_result_t kzt_guest_dl_api_dlerror( + kzt_guest_dlerror_state_t *state, uintptr_t guest_dlerror); +int kzt_guest_dl_api_dlinfo( + const kzt_guest_dl_entries_t *entries, + void *handle, int request, void *info); + +#endif diff --git a/target/i386/latx/include/kzt_guest_dl_init.h b/target/i386/latx/include/kzt_guest_dl_init.h new file mode 100644 index 00000000000..6e9934fb4a8 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_dl_init.h @@ -0,0 +1,22 @@ +#ifndef KZT_GUEST_DL_INIT_H +#define KZT_GUEST_DL_INIT_H + +#include "box64context.h" +#include "kzt_guest_dl_api.h" +#include "kzt_guest_dl_state.h" + +const kzt_guest_dl_entries_t *kzt_guest_dl_init_entries( + box64context_t *context, kzt_guest_dl_entries_t *fallback); + +static inline const kzt_guest_dl_entries_t *kzt_guest_dl_entries_for_call( + box64context_t *context, kzt_guest_dl_entries_t *fallback) +{ + const kzt_guest_dl_entries_t *entries = + context && context->dlprivate + ? kzt_guest_dl_api_load_entries(context->dlprivate) + : NULL; + + return entries ? entries : kzt_guest_dl_init_entries(context, fallback); +} + +#endif diff --git a/target/i386/latx/include/kzt_guest_dl_state.h b/target/i386/latx/include/kzt_guest_dl_state.h new file mode 100644 index 00000000000..b321b56bfd1 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_dl_state.h @@ -0,0 +1,51 @@ +#ifndef KZT_GUEST_DL_STATE_H +#define KZT_GUEST_DL_STATE_H + +#include +#include + +typedef struct kzt_guest_dlerror_state_s { + char *last_error; + char *last_error_returned; + uintptr_t guest_dlerror_entry; + union { + struct { + int last_error_guest_consumed; + int dlerror_slow_required; + }; + uintptr_t dlerror_fast_result; + }; + uintptr_t *dlerror_fast_result_mirror; +} kzt_guest_dlerror_state_t; + +typedef struct kzt_guest_dl_entries_s { + uintptr_t dlopen; + uintptr_t dlmopen; + uintptr_t dlsym; + uintptr_t dlclose; + uintptr_t dladdr; + uintptr_t dladdr1; + uintptr_t dlinfo; + uintptr_t dlvsym; + uintptr_t dlerror; +} kzt_guest_dl_entries_t; + +typedef int (*kzt_guest_dl_entries_resolver_fn)( + kzt_guest_dl_entries_t *entries, void *opaque); +typedef void (*kzt_guest_dl_entries_prepare_fn)( + const kzt_guest_dl_entries_t *entries, void *opaque); + +typedef struct kzt_guest_dl_entry_state_s { + pthread_mutex_t mutex; + pthread_cond_t ready; + kzt_guest_dl_entries_t *published; + uintptr_t observed_dlerror; + pthread_t initializer; + int initialized; + int initializing; + int initializer_valid; + int teardown; + unsigned int slow_users; +} kzt_guest_dl_entry_state_t; + +#endif diff --git a/target/i386/latx/include/kzt_guest_library_adapter.h b/target/i386/latx/include/kzt_guest_library_adapter.h index 04138e188df..533f00ae4fc 100644 --- a/target/i386/latx/include/kzt_guest_library_adapter.h +++ b/target/i386/latx/include/kzt_guest_library_adapter.h @@ -3,24 +3,86 @@ #include +#include "kzt_guest_library_binding.h" +#include "kzt_guest_registry.h" #include "kzt_loader_callback_scope.h" typedef struct box64context_s box64context_t; typedef struct library_s library_t; +typedef struct kzt_guest_wrapper_source_proof { + kzt_guest_registry_source_lease_t lease; + kzt_guest_library_binding_key_t key; +} kzt_guest_wrapper_source_proof_t; + +int kzt_guest_library_wrapper_source_acquire( + box64context_t *context, uintptr_t link_map_addr, + const char *requested_path, const char *wrapper_name, + kzt_guest_wrapper_source_proof_t *proof); +void kzt_guest_library_wrapper_source_release( + kzt_guest_wrapper_source_proof_t *proof); + +/* + * Run one guest dlopen while exposing its context-owned loader scope only to + * callbacks caused by that invocation. Scope setup failure is fail-open: the + * guest call still runs and its original result is returned. + */ +uint64_t kzt_guest_library_run_dlopen_scoped( + box64context_t *context, + kzt_guest_library_loader_scope_t *thread_scope, + uintptr_t function, void *filename, int flag, + kzt_guest_library_loader_scope_t *call_scope); + +/* + * Publish the result of one completed guest dlopen, then close its scope. + * A zero result, disabled publication, or invalid scope publishes nothing. + */ +void kzt_guest_library_finish_dlopen_scoped( + box64context_t *context, + kzt_guest_library_loader_scope_t *call_scope, + uintptr_t link_map_addr, library_t *library, + const kzt_guest_wrapper_source_proof_t *proof, int publish); + +/* Guest symbol lookup is authoritative. These adapters only preserve the + * guest ABI arguments and result; wrapper selection happens afterwards. */ +uint64_t kzt_guest_library_run_dlsym( + uintptr_t function, void *handle, void *symbol); +uint64_t kzt_guest_library_run_dlvsym( + uintptr_t function, void *handle, void *symbol, const char *version); +uint64_t kzt_guest_library_run_dlerror(uintptr_t function); +int kzt_guest_library_run_dlclose(uintptr_t function, void *handle); +uint64_t kzt_guest_library_run_dlmopen( + uintptr_t function, void *lmid, void *filename, int flag); +int kzt_guest_library_run_dlinfo( + uintptr_t function, void *handle, int request, void *info); + +/* + * Replace a successful guest symbol result only when Registry and the exact + * context-owned binding prove that its owner is a wrapped main-namespace + * object with a matching bridge. Missing or conflicting evidence preserves + * the guest address. + */ +uintptr_t kzt_guest_library_select_symbol_result( + box64context_t *context, uintptr_t guest_handle, + uintptr_t guest_result, const char *symbol, const char *version); + /* Shared production adapter used by wrappedlibc, wrappedlibdl, and loader * callback paths whenever one operation owns both exact values. */ -void kzt_guest_library_note_loader_pair(box64context_t *context, - uintptr_t link_map_addr, - library_t *library); -void kzt_guest_library_note_loader_pair_pending( +kzt_guest_library_binding_result_t kzt_guest_library_note_loader_pair( + box64context_t *context, uintptr_t link_map_addr, library_t *library, + const kzt_guest_wrapper_source_proof_t *proof); +kzt_guest_library_binding_result_t +kzt_guest_library_note_loader_pair_pending( box64context_t *context, const kzt_guest_library_loader_scope_t *scope, - uintptr_t link_map_addr, library_t *library); -void kzt_guest_library_publish_loader_pair_scoped( + uintptr_t link_map_addr, library_t *library, + const kzt_guest_wrapper_source_proof_t *proof); +kzt_guest_library_binding_result_t +kzt_guest_library_publish_loader_pair_scoped( box64context_t *context, const kzt_guest_library_loader_scope_t *scope, - uintptr_t link_map_addr, library_t *library); + uintptr_t link_map_addr, library_t *library, + const kzt_guest_wrapper_source_proof_t *proof); void kzt_guest_library_publish_loader_observed_scoped( box64context_t *context, const kzt_guest_library_loader_scope_t *scope, diff --git a/target/i386/latx/include/kzt_jump_slot_production.h b/target/i386/latx/include/kzt_jump_slot_production.h index 2a7a8bca58e..0b96b011fd2 100644 --- a/target/i386/latx/include/kzt_jump_slot_production.h +++ b/target/i386/latx/include/kzt_jump_slot_production.h @@ -7,10 +7,16 @@ #include "kzt_guest_registry.h" #include "kzt_jump_slot_route.h" #include "kzt_lazy_binding.h" +#include "kzt_lazy_direct_route.h" +#include "kzt_lazy_prebind_scope.h" typedef struct box64context_s box64context_t; typedef struct elfheader_s elfheader_t; +typedef int (*kzt_lazy_prebind_target_prepare_fn)(uintptr_t target, + void *opaque); +/* A nonzero return guarantees that no slot write was previously attempted or + * committed by this route, so callers may safely use their legacy write. */ int kzt_production_jump_slot_route( box64context_t *context, library_t *resolved_provider, uintptr_t resolved_target, elfheader_t *head, int need_resolv_present, @@ -21,6 +27,7 @@ int kzt_production_jump_slot_route( int expected_guest_target_present, uintptr_t expected_guest_target, uintptr_t legacy_target, kzt_jump_slot_route_result_t *route_result); +/* The same no-slot-write-on-nonzero-return contract applies here. */ int kzt_production_jump_slot_route_with_version_evidence( box64context_t *context, library_t *resolved_provider, uintptr_t resolved_target, elfheader_t *head, int need_resolv_present, @@ -33,6 +40,33 @@ int kzt_production_jump_slot_route_with_version_evidence( uintptr_t expected_guest_target, uintptr_t legacy_target, kzt_jump_slot_route_result_t *route_result); +/* Tries the evidence-backed direct bridge before guest lazy binding. A + * GUEST_REQUIRED result guarantees that the unresolved slot was preserved. */ +int kzt_production_lazy_direct_route( + box64context_t *context, elfheader_t *head, int entry_index, + Elf64_Rela *rela, uint64_t *slot, uintptr_t slot_current_value, + unsigned long symbol_index, const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, const char *version, + kzt_lazy_direct_route_result_t *result); + +/* Called while the per-object source and decision transaction is held. It + * publishes only already-proven lazy facts; it never changes a guest slot. */ +int kzt_production_lazy_prebind_object( + box64context_t *context, elfheader_t *head, + unsigned long source_generation, + const kzt_guest_dynamic_view_t *source_dynamic_view, + kzt_lazy_prebind_target_prepare_fn target_prepare, + void *target_prepare_opaque); +void kzt_production_lazy_prebind_refresh( + box64context_t *context, + kzt_lazy_prebind_target_prepare_fn target_prepare, + void *target_prepare_opaque); +int kzt_production_lazy_prebind_invalidate( + box64context_t *context, kzt_lazy_prebind_mutation_t mutation); +int kzt_production_lazy_prebind_retire( + box64context_t *context, + const kzt_lazy_prebind_identity_t *identity); + int kzt_production_lazy_route_guest_target( box64context_t *context, const kzt_lazy_binding_pending_t *pending, diff --git a/target/i386/latx/include/kzt_lazy_direct_route.h b/target/i386/latx/include/kzt_lazy_direct_route.h new file mode 100644 index 00000000000..6e0a24ee6a4 --- /dev/null +++ b/target/i386/latx/include/kzt_lazy_direct_route.h @@ -0,0 +1,132 @@ +#ifndef KZT_LAZY_DIRECT_ROUTE_H +#define KZT_LAZY_DIRECT_ROUTE_H + +#include + +#include "kzt_guest_dynamic_view.h" +#include "kzt_guest_library_binding.h" +#include "kzt_patch_planner.h" + +typedef enum kzt_lazy_direct_route_status { + KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED = 0, + KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED, +} kzt_lazy_direct_route_status_t; + +typedef enum kzt_lazy_direct_route_reason { + KZT_LAZY_DIRECT_ROUTE_REASON_NONE = 0, + KZT_LAZY_DIRECT_ROUTE_REASON_DISABLED, + KZT_LAZY_DIRECT_ROUTE_REASON_INVALID_INPUT, + KZT_LAZY_DIRECT_ROUTE_REASON_NON_MAIN_NAMESPACE, + KZT_LAZY_DIRECT_ROUTE_REASON_INCOMPLETE_DYNAMIC_VIEW, + KZT_LAZY_DIRECT_ROUTE_REASON_INVALID_VERSION, + KZT_LAZY_DIRECT_ROUTE_REASON_UNSUPPORTED_SYMBOL_BINDING, + KZT_LAZY_DIRECT_ROUTE_REASON_GUEST_OWNED_SYMBOL, + KZT_LAZY_DIRECT_ROUTE_REASON_PREEMPTION_UNPROVEN, + KZT_LAZY_DIRECT_ROUTE_REASON_SOURCE_REJECTED, + KZT_LAZY_DIRECT_ROUTE_REASON_PROVIDER_UNAVAILABLE, + KZT_LAZY_DIRECT_ROUTE_REASON_PROVIDER_MISMATCH, + KZT_LAZY_DIRECT_ROUTE_REASON_BRIDGE_UNAVAILABLE, + KZT_LAZY_DIRECT_ROUTE_REASON_BRIDGE_VERSION_MISMATCH, + KZT_LAZY_DIRECT_ROUTE_REASON_LEASE_UNAVAILABLE, + KZT_LAZY_DIRECT_ROUTE_REASON_FINAL_VALIDATION_FAILED, + KZT_LAZY_DIRECT_ROUTE_REASON_CAS_MISMATCH, + KZT_LAZY_DIRECT_ROUTE_REASON_CAS_ERROR, + KZT_LAZY_DIRECT_ROUTE_REASON_NATIVE_APPLIED, +} kzt_lazy_direct_route_reason_t; + +typedef enum kzt_lazy_direct_route_cas_status { + KZT_LAZY_DIRECT_ROUTE_CAS_ERROR = -1, + KZT_LAZY_DIRECT_ROUTE_CAS_MISMATCH = 0, + KZT_LAZY_DIRECT_ROUTE_CAS_APPLIED = 1, +} kzt_lazy_direct_route_cas_status_t; + +typedef struct kzt_lazy_direct_route_object { + uintptr_t link_map_addr; + unsigned long generation; +} kzt_lazy_direct_route_object_t; + +typedef struct kzt_lazy_direct_route_input { + int enabled; + int preemption_safe; + uintptr_t namespace_id; + kzt_guest_library_namespace_kind_t namespace_kind; + kzt_lazy_direct_route_object_t source; + kzt_lazy_direct_route_object_t provider; + const kzt_guest_dynamic_view_t *source_dynamic_view; + unsigned long source_dynamic_view_generation; + const char *symbol; + kzt_symbol_version_evidence_t version_evidence; + const char *version; + uintptr_t slot_addr; + uintptr_t guest_unresolved_slot; + uintptr_t expected_current_slot; +} kzt_lazy_direct_route_input_t; + +typedef struct kzt_lazy_direct_route_provider { + void *handle; + uintptr_t link_map_addr; + unsigned long generation; + uintptr_t namespace_id; + kzt_guest_library_namespace_kind_t namespace_kind; +} kzt_lazy_direct_route_provider_t; + +typedef struct kzt_lazy_direct_route_bridge { + uintptr_t target; + kzt_symbol_version_evidence_t version_evidence; + const char *version; +} kzt_lazy_direct_route_bridge_t; + +typedef struct kzt_lazy_direct_route_lease { + void *handle; + int active; +} kzt_lazy_direct_route_lease_t; + +typedef struct kzt_lazy_direct_route_ops { + int (*validate_source)(const kzt_lazy_direct_route_input_t *input, + void *opaque); + int (*acquire_provider)(const kzt_lazy_direct_route_input_t *input, + kzt_lazy_direct_route_provider_t *provider, + void *opaque); + void (*release_provider)(kzt_lazy_direct_route_provider_t *provider, + void *opaque); + int (*find_wrapper_bridge)( + const kzt_lazy_direct_route_input_t *input, + const kzt_lazy_direct_route_provider_t *provider, + kzt_lazy_direct_route_bridge_t *bridge, + void *opaque); + int (*acquire_decision_lease)( + const kzt_lazy_direct_route_input_t *input, + const kzt_lazy_direct_route_provider_t *provider, + kzt_lazy_direct_route_lease_t *lease, + void *opaque); + void (*release_decision_lease)(kzt_lazy_direct_route_lease_t *lease, + void *opaque); + int (*validate_final)( + const kzt_lazy_direct_route_input_t *input, + const kzt_lazy_direct_route_provider_t *provider, + const kzt_lazy_direct_route_bridge_t *bridge, + const kzt_lazy_direct_route_lease_t *lease, + void *opaque); + kzt_lazy_direct_route_cas_status_t (*cas_slot)( + uintptr_t slot_addr, + uintptr_t expected, + uintptr_t replacement, + const kzt_lazy_direct_route_lease_t *lease, + void *opaque); + void *opaque; +} kzt_lazy_direct_route_ops_t; + +typedef struct kzt_lazy_direct_route_result { + kzt_lazy_direct_route_status_t status; + kzt_lazy_direct_route_reason_t reason; + uintptr_t selected_target; +} kzt_lazy_direct_route_result_t; + +int kzt_lazy_direct_symbol_binding_supported(unsigned char st_info); + +kzt_lazy_direct_route_status_t kzt_lazy_direct_route_apply( + const kzt_lazy_direct_route_input_t *input, + const kzt_lazy_direct_route_ops_t *ops, + kzt_lazy_direct_route_result_t *result); + +#endif diff --git a/target/i386/latx/include/library.h b/target/i386/latx/include/library.h index 2fb1eafc228..baf695b78e5 100755 --- a/target/i386/latx/include/library.h +++ b/target/i386/latx/include/library.h @@ -36,6 +36,9 @@ void Free1Library(library_t **lib); char* GetNameLib(library_t *lib); int IsSameLib(library_t* lib, const char* path); // check if lib is same (path -> name) int GetLibSymbolStartEnd(library_t* lib, const char* name, khint_t pre_k, uintptr_t* start, uintptr_t* end, int version, const char* vername, int local); +int GetLibFunctionSymbolStartEnd(library_t* lib, const char* name, + khint_t pre_k, uintptr_t* start, + uintptr_t* end); int GetLibNoWeakSymbolStartEnd(library_t* lib, const char* name, khint_t pre_k, uintptr_t* start, uintptr_t* end, int version, const char* vername, int local); int GetLibLocalSymbolStartEnd(library_t* lib, const char* name, khint_t pre_k, uintptr_t* start, uintptr_t* end, int version, const char* vername, int local); void fillGLProcWrapper(void); diff --git a/target/i386/latx/include/myalign.h b/target/i386/latx/include/myalign.h index e778057cac0..9873bf88141 100644 --- a/target/i386/latx/include/myalign.h +++ b/target/i386/latx/include/myalign.h @@ -157,8 +157,18 @@ TranslationBlock * kzt_tb_find_exp( CPUState *cpu, TranslationBlock *last_tb, int tb_exit, uint32_t cflags); +void kzt_tb_pin_prebind_bridge(CPUState *cpu, target_ulong pc); +bool kzt_tb_prebind_target_is_prepared(CPUState *cpu, target_ulong pc); +void kzt_tb_prebind_guest_note_prepared(CPUState *cpu, target_ulong pc); +void kzt_tb_steady_diagnostics_note_guest_prepare( + CPUState *cpu, target_ulong pc); +void kzt_tb_steady_diagnostics_snapshot_fast_cache(CPUState *cpu); +void kzt_tb_steady_diagnostics_report(void); void kzt_bridge_init(void); void kzt_wine_bridge(abi_ulong start, int fd); int latx_dpy_xcb_sync(void *v1); elfheader_t* loadElfFromFile(const char* name); +elfheader_t* tryLoadElfFromFile(const char* name); +void freeElfFromFile(elfheader_t **header); +void kzt_wine_init_x86(box64context_t *context, uintptr_t guest_dlsym); #endif //__MY_ALIGN__H_ diff --git a/target/i386/latx/latx-config.c b/target/i386/latx/latx-config.c index 199d5cd5c69..49a4e861bf3 100644 --- a/target/i386/latx/latx-config.c +++ b/target/i386/latx/latx-config.c @@ -17,6 +17,9 @@ #include "translate.h" #include "latx-config.h" #include "syscall-tunnel.h" +#if defined(CONFIG_LATX_KZT) && defined(TARGET_X86_64) +#include "kzt_guest_dl_api.h" +#endif #ifdef CONFIG_LATX_TU #include "tu.h" @@ -453,6 +456,9 @@ static __thread TRANSLATION_DATA tr_data_real; /* global lsenv defined here */ __thread ENV *lsenv; +#if defined(CONFIG_LATX_KZT) && defined(TARGET_X86_64) +__thread uintptr_t kzt_guest_dlerror_fast_result_tls; +#endif #ifdef CONFIG_LATX_FAST_JMPCACHE void latx_fast_jmp_cache_add(CPUState *cs, int h, struct TranslationBlock *tb) @@ -615,6 +621,9 @@ void latx_lsenv_init(CPUArchState *env) lsenv = &lsenv_real; lsenv->cpu_state = env; lsenv->tr_data = &tr_data_real; +#if defined(CONFIG_LATX_KZT) && defined(TARGET_X86_64) + kzt_guest_dl_api_bind_current_thread(&env->kzt_guest_dlerror_state); +#endif #ifdef CONFIG_LATX_TU tu_control_init(); #endif diff --git a/target/i386/latx/translator/tr-misc.c b/target/i386/latx/translator/tr-misc.c index cf711e49b0b..e3836b0412b 100644 --- a/target/i386/latx/translator/tr-misc.c +++ b/target/i386/latx/translator/tr-misc.c @@ -98,6 +98,7 @@ extern void my_cfree(void* m); extern void my_free(void* m); extern void my___free(void* m); extern void my_realloc(void* m, void *old, uintptr_t len); +extern char *my_dlerror(void); extern void* x86free; extern void* x86realloc; static void kzt_helper_ptr(ADDR func, IR2_OPND ptr) @@ -151,6 +152,37 @@ void kzt_wrapper_to_native(void) la_ld_d(jmp_cache_addr, env_ir2_opnd, lsenv_offset_of_tb_jmp_cache_ptr(lsenv)); } +static void do_translate_dlerror_brick_tb(onebridge_t *bridge) +{ + const int fast_result_offset = offsetof( + CPUX86State, kzt_guest_dlerror_state.dlerror_fast_result); + IR2_OPND esp_ir2_opnd = ra_alloc_gpr(esp_index); + IR2_OPND fast_result = ra_alloc_itemp(); + IR2_OPND slow_path = ra_alloc_label(); + IR2_OPND finish = ra_alloc_label(); + + lsassert(fast_result_offset >= -2048 && fast_result_offset <= 2047); + kzt_native_to_wrapper(); + la_ld_d(fast_result, env_ir2_opnd, fast_result_offset); + la_bne(fast_result, zero_ir2_opnd, slow_path); + la_st_d(zero_ir2_opnd, env_ir2_opnd, + lsenv_offset_of_gpr(lsenv, R_EAX)); + la_b(finish); + + la_label(slow_path); + wrapper_gpr_trans((ADDR)bridge->f); + tr_set_running_of_cs(false); + li_d(ra_ir2_opnd, (ADDR)bridge->w); + la_jirl(ra_ir2_opnd, ra_ir2_opnd, 0); + tr_set_running_of_cs(true); + + la_label(finish); + ra_free_temp(fast_result); + kzt_wrapper_to_native(); + gen_set_next_tb_code(&esp_ir2_opnd); + tr_generate_exit_tb_for_bridge(); +} + static void do_translate_realloc_brick_tb(void) { uintptr_t realloc_pc = (uint64_t)&x86realloc; @@ -213,6 +245,9 @@ static void do_translate_brick_tb(onebridge_t *bridge, struct cpu_state_info *st } else if (bridge->f == (uintptr_t)my_realloc) { do_translate_realloc_brick_tb(); return; + } else if (bridge->f == (uintptr_t)my_dlerror) { + do_translate_dlerror_brick_tb(bridge); + return; } kzt_native_to_wrapper(); wrapper_gpr_trans((ADDR)bridge->f); diff --git a/target/i386/latx/translator/translate.c b/target/i386/latx/translator/translate.c index 5801a8b7a5d..45b42bab63c 100644 --- a/target/i386/latx/translator/translate.c +++ b/target/i386/latx/translator/translate.c @@ -20,10 +20,28 @@ #include "latx-smc.h" #include "jrra.h" #include "latx-native-asm.h" +#include +#include extern void *helper_tb_lookup_ptr(CPUArchState *); static int ss_generate_match_fail_native_code(void* code_buf); #if defined(CONFIG_LATX_KZT) +#include "elfloader.h" +uint64_t kzt_lazy_bridge_translation_ready_ns; +uintptr_t kzt_lazy_target_bridge_pc; +uint64_t kzt_lazy_resolver_done_ns; + +static uint64_t kzt_lazy_bridge_translation_timing_now(void) +{ + struct timespec value; + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &value) != 0) { + return 0; + } + return (uint64_t)value.tv_sec * 1000000000ULL + + (uint64_t)value.tv_nsec; +} + uintptr_t kzt_get_alternate_pc(uintptr_t addr) { if (!option_kzt || addr < reserved_va) { @@ -2306,6 +2324,36 @@ static int kzt_tr_bridge(struct TranslationBlock *tb) #endif int tr_translate_tb(struct TranslationBlock *tb) { +#if defined(CONFIG_LATX_KZT) + uint64_t kzt_lazy_bridge_timing_start = 0; + uint64_t kzt_lazy_target_timing_start = 0; + uint64_t kzt_lazy_resolver_done_snapshot = 0; + int kzt_lazy_diagnostics_enabled = + unlikely(option_kzt_lazy_diagnostics); + uintptr_t kzt_lazy_target_snapshot = kzt_lazy_diagnostics_enabled + ? __atomic_load_n( + &kzt_lazy_target_bridge_pc, __ATOMIC_ACQUIRE) + : 0; + uintptr_t kzt_plt_resolver_bridge = KztPltResolverBridge(); + int kzt_lazy_bridge_timing_enabled = + kzt_lazy_diagnostics_enabled && kzt_plt_resolver_bridge && + tb->pc == kzt_plt_resolver_bridge; + int kzt_lazy_target_timing_enabled = + kzt_lazy_diagnostics_enabled && + kzt_lazy_target_snapshot && + tb->pc == kzt_lazy_target_snapshot; + + if (kzt_lazy_bridge_timing_enabled) { + kzt_lazy_bridge_timing_start = + kzt_lazy_bridge_translation_timing_now(); + } + if (kzt_lazy_target_timing_enabled) { + kzt_lazy_resolver_done_snapshot = __atomic_load_n( + &kzt_lazy_resolver_done_ns, __ATOMIC_ACQUIRE); + kzt_lazy_target_timing_start = + kzt_lazy_bridge_translation_timing_now(); + } +#endif if (CODEIS64) { tb->bool_flags |= IS_CODE64; } @@ -2437,6 +2485,45 @@ int tr_translate_tb(struct TranslationBlock *tb) option_dump = 0; } +#if defined(CONFIG_LATX_KZT) + if (kzt_lazy_bridge_timing_enabled) { + uint64_t timing_done = kzt_lazy_bridge_translation_timing_now(); + + __atomic_store_n( + &kzt_lazy_bridge_translation_ready_ns, timing_done, + __ATOMIC_RELEASE); + fprintf( + stderr, + "kzt_lazy_bridge_translation_timing schema=1 " + "pc=0x%" PRIx64 " code_size=%d total_ns=%" PRIu64 "\n", + (uint64_t)tb->pc, code_size, + timing_done >= kzt_lazy_bridge_timing_start + ? timing_done - kzt_lazy_bridge_timing_start + : 0); + } + if (kzt_lazy_target_timing_enabled) { + uint64_t timing_done = kzt_lazy_bridge_translation_timing_now(); + + fprintf( + stderr, + "kzt_lazy_target_bridge_translation_timing schema=1 " + "pc=0x%" PRIx64 " code_size=%d " + "resolver_to_translation_ns=%" PRIu64 + " total_ns=%" PRIu64 "\n", + (uint64_t)tb->pc, code_size, + kzt_lazy_target_timing_start >= + kzt_lazy_resolver_done_snapshot && + kzt_lazy_resolver_done_snapshot + ? kzt_lazy_target_timing_start - + kzt_lazy_resolver_done_snapshot + : 0, + timing_done >= kzt_lazy_target_timing_start + ? timing_done - kzt_lazy_target_timing_start + : 0); + __atomic_store_n(&kzt_lazy_target_bridge_pc, 0, __ATOMIC_RELEASE); + __atomic_store_n(&kzt_lazy_resolver_done_ns, 0, __ATOMIC_RELEASE); + } +#endif return code_size; } diff --git a/tests/unit/kzt/test_guest_dl_api.c b/tests/unit/kzt/test_guest_dl_api.c new file mode 100644 index 00000000000..feaf5453a20 --- /dev/null +++ b/tests/unit/kzt/test_guest_dl_api.c @@ -0,0 +1,2136 @@ +#include +#include +#include +#include +#include +#include + +#include "target/i386/latx/include/box64context.h" +#include "target/i386/latx/include/elfloader.h" +#include "target/i386/latx/include/kzt_guest_dl_api.h" +#include "target/i386/latx/include/kzt_guest_dl_init.h" +#include "target/i386/latx/include/kzt_guest_library_adapter.h" +#include "target/i386/latx/include/kzt_guest_registry.h" +#include "target/i386/latx/include/kzt_jump_slot_production.h" +#include "target/i386/latx/include/librarian.h" +#include "target/i386/latx/include/library.h" +#include "target/i386/latx/include/library_private.h" + +__thread uintptr_t kzt_guest_dlerror_fast_result_tls; + +int option_kzt = 1; +int wine_option_kzt; +elfheader_t *tryLoadElfFromFile(const char *name); +void freeElfFromFile(elfheader_t **header); +void kzt_wine_init_x86(box64context_t *context, uintptr_t guest_dlsym); + +#define CHECK(label, condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "%s: FAIL\n", label); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +static uintptr_t seen_function; +static void *seen_handle; +static void *seen_symbol; +static const char *seen_version; +static void *seen_lmid; +static void *seen_filename; +static void *seen_info; +static int seen_flag; +static int seen_request; +static int dlsym_calls; +static int dlvsym_calls; +static int dlerror_calls; +static int dlopen_calls; +static int dlclose_calls; +static int dlmopen_calls; +static int dlinfo_calls; +static int dlinfo_identity_enabled; +static uintptr_t dlinfo_link_map; +static uintptr_t dlinfo_lmid; +static int prebind_invalidate_calls; +static int prebind_retire_calls; +static int prebind_retire_result; +static int writer_begin_calls; +static int writer_end_calls; +static int writer_active; +static void *dlclose_handles[4]; +static uintptr_t dlopen_result; +static int dlclose_result; +static uintptr_t guest_result; +static uintptr_t selected_result; +static char guest_error[] = "guest error"; +static int wrapper_known; +static int attach_result; +static int attach_calls; +static int source_proof_result; +static int source_proof_acquire_calls; +static int source_proof_release_calls; +static kzt_guest_library_binding_result_t binding_claim_result; +static int binding_claim_calls; +static int finish_calls; +static int finish_publish; +static library_t *finish_library; +static uintptr_t finish_link_map; +static int registry_match_enabled; +static kzt_guest_registry_address_match_t registry_match; +static int registry_identity_publish_calls; +static int registry_identity_reuse_calls; +static int registry_identity_resident_calls; +static uintptr_t registry_identity_handle; +static uintptr_t registry_identity_link_map; +static uintptr_t registry_identity_namespace; +static int registry_close_complete_calls; +static kzt_guest_loader_close_result_t registry_close_result; +static int registry_close_identity_missing_calls; +static int registry_unload_begin_calls; +static int registry_unload_cancel_calls; +static int binding_lookup_result; +static library_t *binding_library; +static kzt_guest_library_object_type_t binding_object_type; +static int binding_release_calls; +static int inactive_calls; +static int retire_calls; +static uintptr_t retired_link_map; +static unsigned long retired_generation; +static int registry_token; +static int binding_token; +static library_t attached_library; +static pthread_mutex_t close_race_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t close_race_ready = PTHREAD_COND_INITIALIZER; +static int close_race_enabled; +static int close_race_guest_closes; +static int close_race_retire_claimed; +static int close_race_wait_calls; +static pthread_mutex_t error_race_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t error_race_ready = PTHREAD_COND_INITIALIZER; +static int error_race_enabled; +static int error_race_phase; +static __thread const char *thread_guest_error; +static pthread_mutex_t entry_init_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t entry_init_ready = PTHREAD_COND_INITIALIZER; +static int entry_init_paused; +static int entry_init_release; +static int entry_reader_checked; +static int entry_resolver_calls; +static int entry_retry_attempts; +static int entry_hint_attempts; +static int entry_recursive_attempted; +static int entry_recursive_result; +static int entry_destroy_started; +static int entry_destroy_done; +static int default_entry_pause; +static int default_entry_libc_calls; +static int default_entry_libdl_calls; +static int default_entry_wine_calls; +static int default_entry_path_calls; +static int default_entry_header_frees; +static int default_entry_path_present; +static int default_entry_incomplete; +static int default_wine_pause; +static int default_wine_paused; +static int default_wine_release; +const char *interp_prefix = "/guest-root"; + +typedef struct dlerror_publish_race { + dlprivate_t *dl; + uintptr_t entry; + int result; +} dlerror_publish_race_t; + +typedef struct dlclose_race { + box64context_t *context; + kzt_guest_library_loader_scope_t *thread_scope; + const kzt_guest_dl_entries_t *entries; + void *handle; + int result; +} dlclose_race_t; + +typedef struct dlerror_isolation_race { + box64context_t *context; + kzt_guest_library_loader_scope_t scope; + dlprivate_t *dl; + const kzt_guest_dl_entries_t *entries; + kzt_guest_dlerror_state_t error_state; + const char *error; + char observed[32]; + int forward_to_guest_caller; + int first; + int clear_before_read; +} dlerror_isolation_race_t; + +typedef struct dlentry_init_race { + dlprivate_t *dl; + const kzt_guest_dl_entries_t *result; + kzt_guest_dl_entries_t fallback; + kzt_guest_dl_entries_t snapshot; + int saw_table_before_release; +} dlentry_init_race_t; + +typedef struct dlentry_destroy_race { + dlprivate_t *dl; +} dlentry_destroy_race_t; + +typedef struct default_dlentry_race { + box64context_t *context; + const kzt_guest_dl_entries_t *result; + kzt_guest_dl_entries_t fallback; +} default_dlentry_race_t; + +static void *publish_dlerror_entry(void *opaque) +{ + dlerror_publish_race_t *race = opaque; + + race->result = kzt_guest_dl_api_publish_dlerror_entry( + race->dl, "dlerror", race->entry, 1); + return NULL; +} + +static void *close_guest_library(void *opaque) +{ + dlclose_race_t *race = opaque; + + race->result = kzt_guest_dl_api_dlclose( + race->context, race->thread_scope, race->entries, race->handle); + return NULL; +} + +static void *isolate_guest_dlerror(void *opaque) +{ + dlerror_isolation_race_t *race = opaque; + kzt_guest_dlerror_result_t result; + + thread_guest_error = race->error; + if (!race->first) { + pthread_mutex_lock(&error_race_lock); + while (error_race_phase < 1) { + pthread_cond_wait(&error_race_ready, &error_race_lock); + } + pthread_mutex_unlock(&error_race_lock); + } + + (void)kzt_guest_dl_api_dlopen( + race->context, &race->scope, race->entries, &race->error_state, + "libwi979.so", 2); + + pthread_mutex_lock(&error_race_lock); + error_race_phase = race->first ? 1 : 2; + pthread_cond_broadcast(&error_race_ready); + if (race->first) { + while (error_race_phase < 2) { + pthread_cond_wait(&error_race_ready, &error_race_lock); + } + } + pthread_mutex_unlock(&error_race_lock); + + if (race->clear_before_read) { + kzt_guest_dl_api_clear_error(&race->error_state); + } + result = kzt_guest_dl_api_dlerror( + &race->error_state, + kzt_guest_dl_api_load_dlerror_entry(race->dl)); + race->forward_to_guest_caller = result.forward_to_guest_caller; + snprintf(race->observed, sizeof(race->observed), "%s", + result.value ? result.value : ""); + + if (race->first) { + pthread_mutex_lock(&error_race_lock); + error_race_phase = 3; + pthread_cond_broadcast(&error_race_ready); + pthread_mutex_unlock(&error_race_lock); + } else { + pthread_mutex_lock(&error_race_lock); + while (error_race_phase < 3) { + pthread_cond_wait(&error_race_ready, &error_race_lock); + } + pthread_mutex_unlock(&error_race_lock); + } + thread_guest_error = NULL; + return NULL; +} + +static int resolve_guest_dl_entries( + kzt_guest_dl_entries_t *entries, void *opaque) +{ + (void)opaque; + __atomic_add_fetch(&entry_resolver_calls, 1, __ATOMIC_RELAXED); + entries->dlopen = 0x8100; + entries->dlmopen = 0x8110; + entries->dlsym = 0x8120; + entries->dlclose = 0x8130; + + pthread_mutex_lock(&entry_init_lock); + entry_init_paused = 1; + pthread_cond_broadcast(&entry_init_ready); + while (!entry_init_release) { + pthread_cond_wait(&entry_init_ready, &entry_init_lock); + } + pthread_mutex_unlock(&entry_init_lock); + + entries->dladdr = 0x8140; + entries->dladdr1 = 0x8150; + entries->dlinfo = 0x8160; + entries->dlvsym = 0x8170; + entries->dlerror = 0x8180; + return 0; +} + +static void *initialize_guest_dl_entries(void *opaque) +{ + dlentry_init_race_t *race = opaque; + + race->result = kzt_guest_dl_api_ensure_entries( + race->dl, resolve_guest_dl_entries, NULL, &race->fallback, NULL); + if (race->result) { + race->snapshot = *race->result; + } + return NULL; +} + +static void *read_guest_dl_entries(void *opaque) +{ + dlentry_init_race_t *race = opaque; + const kzt_guest_dl_entries_t *visible = + kzt_guest_dl_api_load_entries(race->dl); + + race->saw_table_before_release = visible != NULL; + if (visible) { + race->snapshot = *visible; + } + pthread_mutex_lock(&entry_init_lock); + entry_reader_checked = 1; + pthread_cond_broadcast(&entry_init_ready); + pthread_mutex_unlock(&entry_init_lock); + + race->result = kzt_guest_dl_api_ensure_entries( + race->dl, resolve_guest_dl_entries, NULL, &race->fallback, NULL); + if (race->result) { + race->snapshot = *race->result; + } + return NULL; +} + +static void fill_guest_dl_entries(kzt_guest_dl_entries_t *entries) +{ + *entries = (kzt_guest_dl_entries_t) { + .dlopen = 0x8200, + .dlmopen = 0x8210, + .dlsym = 0x8220, + .dlclose = 0x8230, + .dladdr = 0x8240, + .dladdr1 = 0x8250, + .dlinfo = 0x8260, + .dlvsym = 0x8270, + .dlerror = 0x8280, + }; +} + +static int resolve_guest_dl_entries_with_retry( + kzt_guest_dl_entries_t *entries, void *opaque) +{ + int attempt = ++entry_retry_attempts; + + (void)opaque; + fill_guest_dl_entries(entries); + if (attempt == 1) { + entries->dlerror = 0; + return -1; + } + return 0; +} + +static int resolve_guest_dl_entries_against_hint( + kzt_guest_dl_entries_t *entries, void *opaque) +{ + int attempt = ++entry_hint_attempts; + + (void)opaque; + fill_guest_dl_entries(entries); + if (attempt > 1) { + entries->dlerror = 0x8290; + } + return 0; +} + +static int resolve_guest_dl_entries_recursively( + kzt_guest_dl_entries_t *entries, void *opaque) +{ + dlprivate_t *dl = opaque; + kzt_guest_dl_entries_t fallback = { 0 }; + + entry_recursive_attempted = 1; + entry_recursive_result = + kzt_guest_dl_api_ensure_entries( + dl, resolve_guest_dl_entries_recursively, dl, &fallback, + NULL) != NULL; + fill_guest_dl_entries(entries); + return 0; +} + +static void *destroy_guest_dl_entries(void *opaque) +{ + dlentry_destroy_race_t *race = opaque; + + __atomic_store_n(&entry_destroy_started, 1, __ATOMIC_RELEASE); + kzt_guest_dl_api_entry_state_destroy(race->dl); + __atomic_store_n(&entry_destroy_done, 1, __ATOMIC_RELEASE); + return NULL; +} + +elfheader_t* tryLoadElfFromFile(const char *name) +{ + if (strcmp(name, "libc.so.6") == 0) { + ++default_entry_libc_calls; + return (elfheader_t *)(uintptr_t)1; + } + CHECK("default resolver requests libdl", strcmp(name, "libdl.so.2") == 0); + ++default_entry_libdl_calls; + return (elfheader_t *)(uintptr_t)2; +} + +void freeElfFromFile(elfheader_t **header) +{ + CHECK("default resolver frees a loaded header", header && *header); + ++default_entry_header_frees; + *header = NULL; +} + +void ResetSpecialCaseElf( + elfheader_t *header, const char **names, int name_count, + void **resolved, int *resolved_count) +{ + int begin = header == (elfheader_t *)(uintptr_t)1 ? 0 : 4; + int end = header == (elfheader_t *)(uintptr_t)1 ? 4 : name_count; + + CHECK("default resolver symbol count", name_count == 9); + for (int i = begin; i < end; ++i) { + CHECK("default resolver symbol name", names[i] && names[i][0]); + if (default_entry_incomplete && i == name_count - 1) { + continue; + } + if (!resolved[i]) { + resolved[i] = (void *)(uintptr_t)(0x9000 + i * 0x10); + ++*resolved_count; + } + } + if (header == (elfheader_t *)(uintptr_t)1 && default_entry_pause) { + pthread_mutex_lock(&entry_init_lock); + entry_init_paused = 1; + pthread_cond_broadcast(&entry_init_ready); + while (!entry_init_release) { + pthread_cond_wait(&entry_init_ready, &entry_init_lock); + } + pthread_mutex_unlock(&entry_init_lock); + } +} + +void PrependList(path_collection_t *collection, const char *list, int folder) +{ + CHECK("default resolver path collection", collection != NULL); + CHECK("default resolver hwcap path", strstr(list, "x86-64-v2") != NULL); + CHECK("default resolver path kind", folder == 1); + ++default_entry_path_calls; + default_entry_path_present = 1; +} + +int FindInCollection(const char *path, path_collection_t *collection) +{ + CHECK("default resolver path lookup collection", collection != NULL); + CHECK("default resolver path lookup", strstr(path, "x86-64-v2") != NULL); + return default_entry_path_present; +} + +void kzt_wine_init_x86(box64context_t *context, uintptr_t guest_dlsym) +{ + CHECK("default resolver wine context", context != NULL); + CHECK("default resolver wine dlsym", guest_dlsym == 0x9020); + ++default_entry_wine_calls; + if (default_wine_pause) { + pthread_mutex_lock(&entry_init_lock); + default_wine_paused = 1; + pthread_cond_broadcast(&entry_init_ready); + while (!default_wine_release) { + pthread_cond_wait(&entry_init_ready, &entry_init_lock); + } + pthread_mutex_unlock(&entry_init_lock); + } +} + +static void *initialize_default_guest_dl_entries(void *opaque) +{ + default_dlentry_race_t *race = opaque; + + race->result = kzt_guest_dl_init_entries( + race->context, &race->fallback); + return NULL; +} + +uint64_t kzt_guest_library_run_dlopen_scoped( + box64context_t *context, + kzt_guest_library_loader_scope_t *thread_scope, + uintptr_t function, void *filename, int flag, + kzt_guest_library_loader_scope_t *call_scope) +{ + CHECK("dlopen context", context != NULL); + CHECK("dlopen thread scope", thread_scope != NULL); + if (close_race_enabled) { + call_scope->identity = 41; + __atomic_add_fetch(&dlopen_calls, 1, __ATOMIC_RELAXED); + return dlopen_result; + } + if (error_race_enabled) { + call_scope->identity = 41; + return 0; + } + seen_function = function; + seen_filename = filename; + seen_flag = flag; + call_scope->identity = 41; + ++dlopen_calls; + return dlopen_result; +} + +void kzt_guest_library_finish_dlopen_scoped( + box64context_t *context, + kzt_guest_library_loader_scope_t *call_scope, + uintptr_t link_map_addr, library_t *library, + const kzt_guest_wrapper_source_proof_t *proof, int publish) +{ + CHECK("finish context", context != NULL); + CHECK("finish scope", call_scope->identity == 41); + if (close_race_enabled || error_race_enabled) { + return; + } + finish_link_map = link_map_addr; + finish_library = library; + finish_publish = publish; + if (library && option_kzt) { + CHECK("finish wrapped proof", proof && proof->lease.active && + proof->key.link_map_addr == link_map_addr); + } + ++finish_calls; +} + +int FindLibIsWrapped(char *name) +{ + CHECK("wrapper name", strcmp(name, "libwi979.so") == 0); + return wrapper_known; +} + +int AddNeededLibWithLibrary( + lib_t *maplib, needed_libs_t *neededlibs, library_t *deplib, + int local, int bindnow, const char *path, box64context_t *context, + library_t **exact_library) +{ + CHECK("attach maplib", maplib == NULL); + CHECK("attach neededlibs", neededlibs == NULL); + CHECK("attach dependency", deplib == NULL); + CHECK("attach local", local == 1); + CHECK("attach bind now", bindnow == 1); + CHECK("attach path", strcmp(path, "libwi979.so") == 0); + CHECK("attach context", context != NULL); + ++attach_calls; + *exact_library = attach_result ? NULL : &attached_library; + return attach_result; +} + +kzt_guest_library_bindings_t *KztGuestLibraryBindingsForContext( + box64context_t *context) +{ + CHECK("claim context", context != NULL); + return (kzt_guest_library_bindings_t *)&binding_token; +} + +int kzt_guest_library_loader_quiescence_writer_begin( + kzt_guest_library_bindings_t *bindings, + kzt_guest_library_loader_quiescence_writer_t *writer) +{ + CHECK("writer bindings", + bindings == (kzt_guest_library_bindings_t *)&binding_token); + CHECK("writer token", writer != NULL); + memset(writer, 0, sizeof(*writer)); + writer->bindings = bindings; + writer->cookie = 1; + __atomic_add_fetch(&writer_begin_calls, 1, __ATOMIC_RELAXED); + __atomic_add_fetch(&writer_active, 1, __ATOMIC_RELAXED); + return 0; +} + +void kzt_guest_library_loader_quiescence_writer_end( + kzt_guest_library_loader_quiescence_writer_t *writer) +{ + if (!writer || !writer->bindings) return; + CHECK("writer release bindings", + writer->bindings == + (kzt_guest_library_bindings_t *)&binding_token); + CHECK("writer release active", + __atomic_load_n(&writer_active, __ATOMIC_RELAXED) > 0); + __atomic_sub_fetch(&writer_active, 1, __ATOMIC_RELAXED); + __atomic_add_fetch(&writer_end_calls, 1, __ATOMIC_RELAXED); + memset(writer, 0, sizeof(*writer)); +} + +kzt_guest_library_binding_result_t kzt_guest_library_note_loader_pair( + box64context_t *context, uintptr_t link_map_addr, library_t *library, + const kzt_guest_wrapper_source_proof_t *proof) +{ + CHECK("claim context", context != NULL); + CHECK("claim exact key", + proof && proof->lease.active && proof->key.link_map_addr == + (dlinfo_identity_enabled ? dlinfo_link_map + : dlopen_result) && + proof->key.link_map_addr == link_map_addr && + proof->key.generation == 17 && + proof->key.namespace_id == 0 && + proof->key.namespace_kind == KZT_GUEST_LIBRARY_NAMESPACE_MAIN); + CHECK("claim exact library", library == &attached_library); + ++binding_claim_calls; + return binding_claim_result; +} + +int kzt_guest_library_wrapper_source_acquire( + box64context_t *context, uintptr_t link_map_addr, + const char *requested_path, const char *wrapper_name, + kzt_guest_wrapper_source_proof_t *proof) +{ + CHECK("source proof context", context != NULL); + CHECK("source proof link map", link_map_addr != 0); + CHECK("source proof requested path", requested_path != NULL); + CHECK("source proof wrapper name", wrapper_name != NULL); + ++source_proof_acquire_calls; + memset(proof, 0, sizeof(*proof)); + if (source_proof_result != 0) { + return source_proof_result; + } + proof->lease.active = 1; + proof->key.link_map_addr = link_map_addr; + proof->key.generation = 17; + proof->key.namespace_id = 0; + proof->key.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN; + return 0; +} + +void kzt_guest_library_wrapper_source_release( + kzt_guest_wrapper_source_proof_t *proof) +{ + CHECK("source proof release", proof != NULL); + if (proof->lease.active) { + ++source_proof_release_calls; + } + memset(proof, 0, sizeof(*proof)); +} + +uint64_t kzt_guest_library_run_dlsym( + uintptr_t function, void *handle, void *symbol) +{ + seen_function = function; + seen_handle = handle; + seen_symbol = symbol; + ++dlsym_calls; + return guest_result; +} + +uint64_t kzt_guest_library_run_dlvsym( + uintptr_t function, void *handle, void *symbol, const char *version) +{ + seen_function = function; + seen_handle = handle; + seen_symbol = symbol; + seen_version = version; + ++dlvsym_calls; + return guest_result; +} + +uint64_t kzt_guest_library_run_dlerror(uintptr_t function) +{ + if (thread_guest_error) { + __atomic_add_fetch(&dlerror_calls, 1, __ATOMIC_RELAXED); + return (uintptr_t)thread_guest_error; + } + if (close_race_enabled) { + __atomic_add_fetch(&dlerror_calls, 1, __ATOMIC_RELAXED); + return (uintptr_t)guest_error; + } + seen_function = function; + ++dlerror_calls; + return (uintptr_t)guest_error; +} + +int kzt_guest_library_run_dlclose(uintptr_t function, void *handle) +{ + int call_index; + + if (close_race_enabled) { + call_index = __atomic_fetch_add(&dlclose_calls, 1, __ATOMIC_RELAXED); + } else { + call_index = dlclose_calls++; + } + if (!close_race_enabled) { + seen_function = function; + } + if (!close_race_enabled && call_index < 4) { + dlclose_handles[call_index] = handle; + } + if (close_race_enabled && handle == (void *)(uintptr_t)0x9000) { + pthread_mutex_lock(&close_race_lock); + ++close_race_guest_closes; + if (close_race_guest_closes == 2) { + pthread_cond_broadcast(&close_race_ready); + } else { + while (close_race_guest_closes < 2) { + pthread_cond_wait(&close_race_ready, &close_race_lock); + } + } + pthread_mutex_unlock(&close_race_lock); + } + return dlclose_result; +} + +uint64_t kzt_guest_library_run_dlmopen( + uintptr_t function, void *lmid, void *filename, int flag) +{ + seen_function = function; + seen_lmid = lmid; + seen_filename = filename; + seen_flag = flag; + ++dlmopen_calls; + return guest_result; +} + +int kzt_guest_library_run_dlinfo( + uintptr_t function, void *handle, int request, void *info) +{ + seen_function = function; + seen_handle = handle; + seen_request = request; + seen_info = info; + ++dlinfo_calls; + if (dlinfo_identity_enabled < 0) { + return -1; + } + if (dlinfo_identity_enabled) { + if (request == 2) { + *(uintptr_t *)info = dlinfo_link_map; + return 0; + } + if (request == 1) { + *(uintptr_t *)info = dlinfo_lmid; + return 0; + } + return -1; + } + if (request == 2) { + *(uintptr_t *)info = (uintptr_t)handle; + return 0; + } + if (request == 1) { + *(uintptr_t *)info = 0; + return 0; + } + return (int)guest_result; +} + +uintptr_t kzt_guest_library_select_symbol_result( + box64context_t *context, uintptr_t guest_handle, + uintptr_t actual_guest_result, const char *symbol, const char *version) +{ + CHECK("selector context", context != NULL); + CHECK("selector handle", guest_handle == (uintptr_t)seen_handle); + CHECK("selector guest result", actual_guest_result == guest_result); + CHECK("selector symbol", symbol == (const char *)seen_symbol); + CHECK("selector version", version == seen_version); + return selected_result; +} + +kzt_guest_registry_t *KztGuestRegistryForContext(box64context_t *context) +{ + CHECK("registry context", context != NULL); + return (kzt_guest_registry_t *)®istry_token; +} + +kzt_lazy_prebind_scope_t *KztLazyPrebindScopeForContext( + box64context_t *context) +{ + CHECK("prebind scope context", context != NULL); + return NULL; +} + +int kzt_production_lazy_prebind_invalidate( + box64context_t *context, kzt_lazy_prebind_mutation_t mutation) +{ + CHECK("prebind invalidate context", context != NULL); + CHECK("prebind invalidate mutation", + mutation == KZT_LAZY_PREBIND_MUTATION_DLOPEN || + mutation == KZT_LAZY_PREBIND_MUTATION_DLCLOSE || + mutation == KZT_LAZY_PREBIND_MUTATION_DLMOPEN); + ++prebind_invalidate_calls; + return 0; +} + +int kzt_production_lazy_prebind_retire( + box64context_t *context, + const kzt_lazy_prebind_identity_t *identity) +{ + CHECK("prebind retire context", context != NULL); + CHECK("prebind retire identity", identity != NULL && + identity->link_map_addr == registry_match.link_map_addr && + identity->generation == registry_match.generation && + identity->namespace_id == registry_match.namespace_id); + ++prebind_retire_calls; + return prebind_retire_result; +} + +void kzt_production_lazy_prebind_refresh( + box64context_t *context, + kzt_lazy_prebind_target_prepare_fn target_prepare, + void *target_prepare_opaque) +{ + CHECK("prebind refresh context", context != NULL); + CHECK("prebind refresh target prepare", target_prepare != NULL); + (void)target_prepare_opaque; +} + +int KztPrebindTargetTbPrepare(uintptr_t target) +{ + return target ? 0 : -1; +} + +int kzt_guest_registry_find_live_object( + kzt_guest_registry_t *registry, uintptr_t link_map_addr, + kzt_guest_registry_address_match_t *match) +{ + CHECK("registry token", registry == (kzt_guest_registry_t *)®istry_token); + if (!registry_match_enabled || + link_map_addr != registry_match.link_map_addr) { + memset(match, 0, sizeof(*match)); + return -1; + } + *match = registry_match; + return 0; +} + +int kzt_guest_registry_publish_loader_identity( + kzt_guest_registry_t *registry, uintptr_t handle, + uintptr_t link_map_addr, uintptr_t namespace_id, + kzt_guest_loader_identity_t *identity) +{ + CHECK("publish identity registry", + registry == (kzt_guest_registry_t *)®istry_token); + ++registry_identity_publish_calls; + registry_identity_handle = handle; + registry_identity_link_map = link_map_addr; + registry_identity_namespace = namespace_id; + *identity = (kzt_guest_loader_identity_t) { + .handle = handle, + .link_map_addr = link_map_addr, + .generation = 17, + .namespace_id = namespace_id, + }; + return 0; +} + +int kzt_guest_registry_find_loader_identity( + kzt_guest_registry_t *registry, uintptr_t handle, + kzt_guest_loader_identity_t *identity) +{ + CHECK("find identity registry", + registry == (kzt_guest_registry_t *)®istry_token); + if (!registry_match_enabled || + handle != registry_match.link_map_addr) { + memset(identity, 0, sizeof(*identity)); + return -1; + } + *identity = (kzt_guest_loader_identity_t) { + .handle = handle, + .link_map_addr = registry_match.link_map_addr, + .generation = registry_match.generation, + .namespace_id = registry_match.namespace_id, + }; + return 0; +} + +int kzt_guest_registry_reuse_loader_identity( + kzt_guest_registry_t *registry, uintptr_t handle, + kzt_guest_loader_identity_t *identity) +{ + ++registry_identity_reuse_calls; + return kzt_guest_registry_find_loader_identity( + registry, handle, identity); +} + +int kzt_guest_registry_mark_loader_resident( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity) +{ + CHECK("mark resident registry", + registry == (kzt_guest_registry_t *)®istry_token); + CHECK("mark resident exact identity", + identity != NULL && identity->handle != 0 && + identity->link_map_addr != 0); + ++registry_identity_resident_calls; + return 0; +} + +int kzt_guest_registry_find_loader_object_identity( + kzt_guest_registry_t *registry, uintptr_t link_map_addr, + kzt_guest_loader_identity_t *identity) +{ + CHECK("find loader object registry", + registry == (kzt_guest_registry_t *)®istry_token); + if (!registry_match_enabled || + link_map_addr != registry_match.link_map_addr) { + memset(identity, 0, sizeof(*identity)); + return -1; + } + *identity = (kzt_guest_loader_identity_t) { + .link_map_addr = link_map_addr, + .generation = registry_match.generation, + .namespace_id = registry_match.namespace_id, + }; + return 0; +} + +int kzt_guest_registry_begin_loader_unload( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity) +{ + CHECK("begin unload registry", + registry == (kzt_guest_registry_t *)®istry_token); + CHECK("begin unload exact identity", + identity->link_map_addr == registry_match.link_map_addr && + identity->generation == registry_match.generation && + identity->namespace_id == registry_match.namespace_id); + ++registry_unload_begin_calls; + return 0; +} + +int kzt_guest_registry_cancel_loader_unload( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity) +{ + CHECK("cancel unload registry", + registry == (kzt_guest_registry_t *)®istry_token); + CHECK("cancel unload exact identity", + identity->link_map_addr == registry_match.link_map_addr && + identity->generation == registry_match.generation && + identity->namespace_id == registry_match.namespace_id); + ++registry_unload_cancel_calls; + return 0; +} + +kzt_guest_loader_close_result_t +kzt_guest_registry_complete_loader_close( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity) +{ + CHECK("complete close registry", + registry == (kzt_guest_registry_t *)®istry_token); + CHECK("complete close exact identity", + identity->link_map_addr == registry_match.link_map_addr && + identity->generation == registry_match.generation && + identity->namespace_id == registry_match.namespace_id); + ++registry_close_complete_calls; + return registry_close_result; +} + +void kzt_guest_registry_note_loader_close_identity_missing( + kzt_guest_registry_t *registry) +{ + CHECK("missing close identity registry", + registry == (kzt_guest_registry_t *)®istry_token); + ++registry_close_identity_missing_calls; +} + +int KztGuestLibraryLookupForContext( + box64context_t *context, + const kzt_guest_library_binding_key_t *key, + kzt_guest_library_handle_t *handle) +{ + CHECK("binding context", context != NULL); + CHECK("binding link map", + key->link_map_addr == registry_match.link_map_addr); + CHECK("binding generation", + key->generation == registry_match.generation); + CHECK("binding namespace", + key->namespace_id == registry_match.namespace_id); + if (binding_lookup_result != 0) { + return binding_lookup_result; + } + handle->bindings = (kzt_guest_library_bindings_t *)(uintptr_t)1; + handle->entry = (void *)(uintptr_t)1; + handle->library = binding_library; + handle->object_type = binding_object_type; + return 0; +} + +void kzt_guest_library_handle_release(kzt_guest_library_handle_t *handle) +{ + if (handle->entry) { + __atomic_add_fetch(&binding_release_calls, 1, __ATOMIC_RELAXED); + } + memset(handle, 0, sizeof(*handle)); +} + +int kzt_guest_library_cleanup_exact_handle( + kzt_guest_library_handle_t *handle, + kzt_guest_library_exact_cleanup_fn cleanup, + void *opaque) +{ + CHECK("cleanup exact pinned handle", handle != NULL && handle->entry && + handle->library == binding_library); + CHECK("cleanup exact callback", cleanup != NULL); + cleanup(handle->library, opaque); + ++binding_release_calls; + ++inactive_calls; + memset(handle, 0, sizeof(*handle)); + return 0; +} + +void InactiveLibrary(library_t *library) +{ + CHECK("inactive exact library", library == binding_library); + __atomic_add_fetch(&inactive_calls, 1, __ATOMIC_RELAXED); +} + +int kzt_guest_registry_retire( + kzt_guest_registry_t *registry, uintptr_t link_map_addr, + unsigned long generation) +{ + CHECK("retire registry", + registry == (kzt_guest_registry_t *)®istry_token); + if (!close_race_enabled) { + retired_link_map = link_map_addr; + retired_generation = generation; + } + __atomic_add_fetch(&retire_calls, 1, __ATOMIC_RELAXED); + if (close_race_enabled) { + int winner; + + pthread_mutex_lock(&close_race_lock); + winner = !close_race_retire_claimed; + close_race_retire_claimed = 1; + pthread_mutex_unlock(&close_race_lock); + return winner ? 0 : -1; + } + return 0; +} + +int kzt_guest_registry_retire_loader_identity( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity) +{ + CHECK("retire loader namespace", + identity->namespace_id == registry_match.namespace_id); + return kzt_guest_registry_retire( + registry, identity->link_map_addr, identity->generation); +} + +int kzt_guest_registry_finish_loader_unload( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity) +{ + CHECK("finish unload namespace", + identity->namespace_id == registry_match.namespace_id); + return kzt_guest_registry_retire( + registry, identity->link_map_addr, identity->generation); +} + +int kzt_guest_registry_wait_retired( + kzt_guest_registry_t *registry, uintptr_t link_map_addr, + unsigned long generation) +{ + CHECK("wait retired registry", + registry == (kzt_guest_registry_t *)®istry_token); + CHECK("wait retired identity", + link_map_addr == registry_match.link_map_addr && + generation == registry_match.generation); + __atomic_add_fetch(&close_race_wait_calls, 1, __ATOMIC_RELAXED); + return 0; +} + +int kzt_guest_registry_find_lazy_resolver( + kzt_guest_registry_t *registry, uintptr_t link_map_addr, + unsigned long generation, uintptr_t namespace_id, + kzt_guest_lazy_resolver_t *resolver) +{ + CHECK("lazy resolver registry", + registry == (kzt_guest_registry_t *)®istry_token); + CHECK("lazy resolver identity", + link_map_addr == registry_match.link_map_addr && + generation == registry_match.generation && + namespace_id == registry_match.namespace_id); + memset(resolver, 0, sizeof(*resolver)); + return -1; +} + +void KztPerObjectGotPltRelease(uintptr_t object_head) +{ + CHECK("runtime head not released by legacy dl-api fixture", + object_head == 0); +} + +static void reset_calls(void) +{ + seen_function = 0; + seen_handle = NULL; + seen_symbol = NULL; + seen_version = NULL; + seen_lmid = NULL; + seen_filename = NULL; + seen_info = NULL; + seen_flag = 0; + seen_request = 0; + dlsym_calls = 0; + dlvsym_calls = 0; + dlerror_calls = 0; + dlopen_calls = 0; + dlclose_calls = 0; + memset(dlclose_handles, 0, sizeof(dlclose_handles)); + dlmopen_calls = 0; + dlinfo_calls = 0; + dlinfo_identity_enabled = 0; + dlinfo_link_map = 0; + dlinfo_lmid = 0; + prebind_invalidate_calls = 0; + prebind_retire_calls = 0; + prebind_retire_result = 0; + writer_begin_calls = 0; + writer_end_calls = 0; + writer_active = 0; + attach_calls = 0; + source_proof_result = 0; + source_proof_acquire_calls = 0; + source_proof_release_calls = 0; + binding_claim_result = KZT_GUEST_LIBRARY_BINDING_ADDED; + binding_claim_calls = 0; + memset(&attached_library, 0, sizeof(attached_library)); + attached_library.type = LIB_WRAPPED; + finish_calls = 0; + finish_publish = 0; + finish_library = NULL; + finish_link_map = 0; + dlopen_result = 0; + dlclose_result = 0; + guest_result = 0; + selected_result = 0; + wrapper_known = 0; + attach_result = 0; + registry_match_enabled = 0; + memset(®istry_match, 0, sizeof(registry_match)); + registry_identity_publish_calls = 0; + registry_identity_reuse_calls = 0; + registry_identity_resident_calls = 0; + registry_identity_handle = 0; + registry_identity_link_map = 0; + registry_identity_namespace = 0; + registry_close_complete_calls = 0; + registry_close_result = KZT_GUEST_LOADER_CLOSE_UNLOAD_UNPROVEN; + registry_close_identity_missing_calls = 0; + registry_unload_begin_calls = 0; + registry_unload_cancel_calls = 0; + binding_lookup_result = -1; + binding_library = NULL; + binding_object_type = KZT_GUEST_LIBRARY_OBJECT_UNSUPPORTED; + binding_release_calls = 0; + inactive_calls = 0; + retire_calls = 0; + retired_link_map = 0; + retired_generation = 0; + close_race_enabled = 0; + close_race_guest_closes = 0; + close_race_retire_claimed = 0; + close_race_wait_calls = 0; + error_race_enabled = 0; + error_race_phase = 0; +} + +static void set_exact_match(uintptr_t link_map_addr, uintptr_t namespace_id) +{ + registry_match_enabled = 1; + registry_match.link_map_addr = link_map_addr; + registry_match.generation = 17; + registry_match.namespace_id = namespace_id; + registry_match.namespace_id_status = KZT_GUEST_FIELD_OK; + registry_match.path_status = KZT_GUEST_FIELD_OK; + registry_match.match_count = 1; + strcpy(registry_match.path, "/guest/libwi980.so"); +} + +int main(void) +{ + box64context_t context = { 0 }; + dlprivate_t dl = { 0 }; + kzt_guest_dlerror_state_t error_state = { 0 }; + kzt_guest_dlerror_state_t fast_error_state = { 0 }; + uintptr_t fast_error_mirror = 0; + dlerror_publish_race_t races[2]; + pthread_t race_threads[2]; + dlclose_race_t close_races[2]; + pthread_t close_threads[2]; + dlerror_isolation_race_t error_races[2]; + pthread_t error_threads[2]; + dlentry_init_race_t entry_races[2] = { 0 }; + pthread_t entry_threads[2]; + dlprivate_t destroy_dl = { 0 }; + dlentry_destroy_race_t destroy_race = { .dl = &destroy_dl }; + dlentry_init_race_t destroy_init_race = { .dl = &destroy_dl }; + pthread_t destroy_threads[2]; + dlprivate_t default_dl = { 0 }; + box64context_t default_context = { .dlprivate = &default_dl }; + default_dlentry_race_t default_races[2] = { + { .context = &default_context }, + { .context = &default_context }, + }; + pthread_t default_threads[2]; + library_t library = { 0 }; + kzt_guest_library_loader_scope_t thread_scope = { 0 }; + kzt_guest_dl_symbol_result_t symbol_result; + kzt_guest_dlerror_result_t error_result; + char symbol[] = "wi964_symbol"; + char version[] = "WI964_1.0"; + char filename[] = "libwi964.so"; + char info[16] = { 0 }; + kzt_guest_dl_entries_t direct_entries = { + .dlsym = 0x1010, + .dlvsym = 0x1020, + .dlerror = 0x1030, + .dlmopen = 0x1040, + .dlinfo = 0x1050, + .dlclose = 0x1060, + .dlopen = 0x1070, + .dladdr = 0x1080, + .dladdr1 = 0x1090, + }; + + fast_error_state.dlerror_fast_result_mirror = &fast_error_mirror; + fast_error_state.dlerror_slow_required = 1; + fast_error_mirror = fast_error_state.dlerror_fast_result; + CHECK("explicit unknown dlerror state remains conservative", + kzt_guest_dl_api_dlerror_needs_slow_path(&fast_error_state)); + fast_error_state.dlerror_slow_required = 0; + CHECK("initialized clean dlerror state skips slow path", + !kzt_guest_dl_api_dlerror_needs_slow_path(&fast_error_state)); + CHECK("guest call remembers a clean predecessor", + kzt_guest_dl_api_begin_call(&fast_error_state)); + CHECK("guest call starts conservatively", + kzt_guest_dl_api_dlerror_needs_slow_path(&fast_error_state) && + fast_error_mirror != 0); + kzt_guest_dl_api_finish_success(&fast_error_state, 1); + CHECK("successful guest call preserves known clean state", + !kzt_guest_dl_api_dlerror_needs_slow_path(&fast_error_state) && + fast_error_mirror == 0); + fast_error_state.dlerror_slow_required = 1; + CHECK("guest call remembers an unknown predecessor", + !kzt_guest_dl_api_begin_call(&fast_error_state)); + kzt_guest_dl_api_finish_success(&fast_error_state, 0); + CHECK("success cannot promote an unknown predecessor", + kzt_guest_dl_api_dlerror_needs_slow_path(&fast_error_state)); + fast_error_state.dlerror_slow_required = 0; + fast_error_state.last_error = (char *)(uintptr_t)1; + fast_error_state.dlerror_slow_required = 1; + CHECK("pending dlerror requires slow path", + kzt_guest_dl_api_dlerror_needs_slow_path(&fast_error_state)); + fast_error_state.last_error = NULL; + fast_error_state.last_error_returned = (char *)(uintptr_t)1; + CHECK("returned dlerror cache requires slow path", + kzt_guest_dl_api_dlerror_needs_slow_path(&fast_error_state)); + fast_error_state.last_error_returned = NULL; + fast_error_state.guest_dlerror_entry = 0x1234; + kzt_guest_dl_api_clear_error(&fast_error_state); + CHECK("CLEARERR preserves thread guest dlerror entry", + fast_error_state.guest_dlerror_entry == 0x1234); + CHECK("CLEARERR makes guest dlerror state conservative", + kzt_guest_dl_api_dlerror_needs_slow_path(&fast_error_state)); + kzt_guest_dl_api_free_errors(&fast_error_state); + CHECK("thread teardown clears guest dlerror entry", + fast_error_state.guest_dlerror_entry == 0); + + reset_calls(); + dlopen_result = 0x2000; + dlinfo_identity_enabled = 1; + dlinfo_link_map = 0x2080; + CHECK("unwrapped dlopen result", + kzt_guest_dl_api_dlopen( + &context, &thread_scope, &direct_entries, &error_state, + "libwi979.so", 2) == 0x2000); + CHECK("unwrapped guest call", dlopen_calls == 1); + CHECK("unwrapped no attach", attach_calls == 0); + CHECK("unwrapped finish", finish_calls == 1 && finish_publish); + CHECK("unwrapped observed only", finish_library == NULL); + CHECK("unwrapped exact dlinfo calls", dlinfo_calls == 2); + CHECK("unwrapped exact link map", finish_link_map == 0x2080); + CHECK("unwrapped exact identity published", + registry_identity_publish_calls == 1 && + registry_identity_handle == 0x2000 && + registry_identity_link_map == 0x2080 && + registry_identity_namespace == 0); + CHECK("unwrapped invalidates prebind", prebind_invalidate_calls == 1); + + reset_calls(); + dlopen_result = 0x2040; + dlinfo_identity_enabled = 1; + dlinfo_link_map = 0x20c0; + CHECK("nodelete dlopen result", + kzt_guest_dl_api_dlopen( + &context, &thread_scope, &direct_entries, &error_state, + "libwi979.so", 0x1002) == 0x2040); + CHECK("nodelete exact identity marked resident", + registry_identity_publish_calls == 1 && + registry_identity_resident_calls == 1); + + reset_calls(); + dlopen_result = 0x2000; + registry_match_enabled = 1; + registry_match.link_map_addr = 0x2000; + registry_match.generation = 17; + registry_match.namespace_id = 0; + CHECK("live duplicate dlopen result", + kzt_guest_dl_api_dlopen( + &context, &thread_scope, &direct_entries, &error_state, + "libwi979.so", 2) == 0x2000); + CHECK("live duplicate reuses exact identity", + registry_identity_reuse_calls == 1 && dlinfo_calls == 0 && + registry_identity_publish_calls == 0 && + finish_link_map == 0x2000 && finish_publish); + + reset_calls(); + option_kzt = 0; + dlopen_result = 0x2500; + CHECK("disabled KZT dlopen result", + kzt_guest_dl_api_dlopen( + &context, &thread_scope, &direct_entries, &error_state, + "libwi979.so", 2) == 0x2500); + CHECK("disabled KZT skips identity dlinfo", dlinfo_calls == 0); + CHECK("disabled KZT preserves old publication", + finish_calls == 1 && finish_publish && + finish_link_map == 0x2500); + CHECK("disabled KZT skips Registry identity", + registry_identity_publish_calls == 0); + option_kzt = 1; + + reset_calls(); + dlopen_result = 0x2580; + dlinfo_identity_enabled = -1; + CHECK("identity probe failure preserves successful dlopen", + kzt_guest_dl_api_dlopen( + &context, &thread_scope, &direct_entries, &error_state, + "libwi979.so", 2) == 0x2580); + CHECK("identity probe failure is bounded", dlinfo_calls == 1); + CHECK("identity probe failure consumes internal loader error", + dlerror_calls == 1); + CHECK("identity probe failure does not publish guessed identity", + registry_identity_publish_calls == 0 && !finish_publish); + + reset_calls(); + dlopen_result = 0x2000; + CHECK("noload global result", + kzt_guest_dl_api_dlopen( + &context, &thread_scope, &direct_entries, &error_state, + "libwi979.so", 0x104) == 0x2000); + CHECK("noload global invalidates prebind", + prebind_invalidate_calls == 1); + + reset_calls(); + wrapper_known = 1; + dlopen_result = 0x2100; + CHECK("wrapped dlopen result", + kzt_guest_dl_api_dlopen( + &context, &thread_scope, &direct_entries, &error_state, + "/guest/libwi979.so", 2) == 0x2100); + CHECK("wrapped guest first", dlopen_calls == 1); + CHECK("wrapped attach", attach_calls == 1); + CHECK("wrapped source proof held and released", + source_proof_acquire_calls == 1 && source_proof_release_calls == 1); + CHECK("wrapped exact pair", finish_library != NULL); + CHECK("wrapped guest handle stored", + finish_library->x86linkmap == + (struct link_map *)(uintptr_t)0x2100); + + reset_calls(); + wrapper_known = 1; + binding_claim_result = KZT_GUEST_LIBRARY_BINDING_CONFLICT; + attached_library.x86linkmap = + (struct link_map *)(uintptr_t)0x2100; + dlopen_result = 0x2140; + CHECK("conflicting wrapped source preserves guest handle", + kzt_guest_dl_api_dlopen( + &context, &thread_scope, &direct_entries, &error_state, + "/usr/lib/libwi979.so", 2) == 0x2140); + CHECK("conflicting wrapped source claims before mutation", + binding_claim_calls == 1); + CHECK("conflicting wrapped source remains observed only", + finish_library == NULL && finish_publish); + CHECK("conflicting wrapped source keeps first producer owner", + attached_library.x86linkmap == + (struct link_map *)(uintptr_t)0x2100); + + reset_calls(); + wrapper_known = 1; + attached_library.type = LIB_EMULATED; + attached_library.x86linkmap = + (struct link_map *)(uintptr_t)0x2100; + dlopen_result = 0x2160; + CHECK("emulated basename collision preserves guest handle", + kzt_guest_dl_api_dlopen( + &context, &thread_scope, &direct_entries, &error_state, + "/usr/lib/libwi979.so", 2) == 0x2160); + CHECK("emulated basename collision skips wrapped claim", + binding_claim_calls == 0); + CHECK("emulated basename collision remains observed only", + finish_library == NULL && finish_publish); + CHECK("emulated basename collision keeps producer state", + attached_library.x86linkmap == + (struct link_map *)(uintptr_t)0x2100); + + reset_calls(); + wrapper_known = 1; + source_proof_result = -1; + dlopen_result = 0x2180; + CHECK("unproven wrapped source preserves guest handle", + kzt_guest_dl_api_dlopen( + &context, &thread_scope, &direct_entries, &error_state, + "/tmp/libwi979.so", 2) == 0x2180); + CHECK("unproven wrapped source does not materialize", attach_calls == 0); + CHECK("unproven wrapped source remains observed only", + finish_library == NULL && finish_publish); + + reset_calls(); + wrapper_known = 1; + attach_result = 1; + dlopen_result = 0x2200; + CHECK("attach failure preserves guest", + kzt_guest_dl_api_dlopen( + &context, &thread_scope, &direct_entries, &error_state, + "libwi979.so", 2) == 0x2200); + CHECK("attach failure observed", finish_library == NULL); + + reset_calls(); + wrapper_known = 1; + dlopen_result = 0; + CHECK("guest failure preserved", + kzt_guest_dl_api_dlopen( + &context, &thread_scope, &direct_entries, &error_state, + "libwi979.so", 2) == 0); + CHECK("guest failure skips attach", attach_calls == 0); + CHECK("guest failure cancels scope", + finish_calls == 1 && !finish_publish && finish_link_map == 0); + CHECK("guest failure captures guest error", dlerror_calls == 1); + error_result = kzt_guest_dl_api_dlerror( + &error_state, kzt_guest_dl_api_load_dlerror_entry(&dl)); + CHECK("captured guest error returned", + error_result.value && + strcmp(error_result.value, guest_error) == 0 && + !error_result.forward_to_guest_caller); + error_result = kzt_guest_dl_api_dlerror( + &error_state, kzt_guest_dl_api_load_dlerror_entry(&dl)); + CHECK("captured guest error is one-shot", + !error_result.value && !error_result.forward_to_guest_caller); + CHECK("consumed guest error stays on clean path", dlerror_calls == 1); + kzt_guest_dl_api_clear_error(&error_state); + + reset_calls(); + error_race_enabled = 1; + error_races[0] = (dlerror_isolation_race_t) { + .context = &context, + .dl = &dl, + .entries = &direct_entries, + .error = "thread A error", + .first = 1, + }; + error_races[1] = (dlerror_isolation_race_t) { + .context = &context, + .dl = &dl, + .entries = &direct_entries, + .error = "thread B error", + }; + CHECK("dlerror isolation first thread starts", + pthread_create(&error_threads[0], NULL, isolate_guest_dlerror, + &error_races[0]) == 0); + CHECK("dlerror isolation second thread starts", + pthread_create(&error_threads[1], NULL, isolate_guest_dlerror, + &error_races[1]) == 0); + CHECK("dlerror isolation first thread joins", + pthread_join(error_threads[0], NULL) == 0); + CHECK("dlerror isolation second thread joins", + pthread_join(error_threads[1], NULL) == 0); + error_race_enabled = 0; + CHECK("dlerror isolation preserves first error", + strcmp(error_races[0].observed, error_races[0].error) == 0 && + !error_races[0].forward_to_guest_caller); + CHECK("dlerror isolation preserves second error", + strcmp(error_races[1].observed, error_races[1].error) == 0 && + !error_races[1].forward_to_guest_caller); + kzt_guest_dl_api_clear_error(&error_races[0].error_state); + kzt_guest_dl_api_clear_error(&error_races[1].error_state); + + reset_calls(); + error_race_enabled = 1; + error_races[0] = (dlerror_isolation_race_t) { + .context = &context, + .dl = &dl, + .entries = &direct_entries, + .error = "thread A cleared error", + .first = 1, + .clear_before_read = 1, + }; + error_races[1] = (dlerror_isolation_race_t) { + .context = &context, + .dl = &dl, + .entries = &direct_entries, + .error = "thread B retained error", + }; + CHECK("dlerror clear isolation first thread starts", + pthread_create(&error_threads[0], NULL, isolate_guest_dlerror, + &error_races[0]) == 0); + CHECK("dlerror clear isolation second thread starts", + pthread_create(&error_threads[1], NULL, isolate_guest_dlerror, + &error_races[1]) == 0); + CHECK("dlerror clear isolation first thread joins", + pthread_join(error_threads[0], NULL) == 0); + CHECK("dlerror clear isolation second thread joins", + pthread_join(error_threads[1], NULL) == 0); + error_race_enabled = 0; + CHECK("CLEARERR empties only the calling thread", + strcmp(error_races[0].observed, "") == 0 && + error_races[0].forward_to_guest_caller); + CHECK("CLEARERR preserves the other thread error", + strcmp(error_races[1].observed, error_races[1].error) == 0 && + !error_races[1].forward_to_guest_caller); + kzt_guest_dl_api_clear_error(&error_races[0].error_state); + kzt_guest_dl_api_clear_error(&error_races[1].error_state); + + reset_calls(); + guest_result = 0x2010; + selected_result = 0x3010; + symbol_result = kzt_guest_dl_api_dlsym( + &context, &direct_entries, (void *)(uintptr_t)0x9000, symbol); + CHECK("direct dlsym call", dlsym_calls == 1); + CHECK("direct dlsym function", seen_function == 0x1010); + CHECK("direct dlsym handle", seen_handle == (void *)(uintptr_t)0x9000); + CHECK("direct dlsym result", symbol_result.value == 0x3010); + CHECK("direct dlsym no forward", !symbol_result.forward_to_guest_caller); + + reset_calls(); + guest_result = 0x2020; + selected_result = 0x3020; + symbol_result = kzt_guest_dl_api_dlsym( + &context, &direct_entries, (void *)(uintptr_t)1, symbol); + CHECK("opaque low dlsym call", dlsym_calls == 1); + CHECK("opaque low dlsym handle preserved", + seen_handle == (void *)(uintptr_t)1); + CHECK("opaque low dlsym result", symbol_result.value == 0x3020); + + reset_calls(); + symbol_result = kzt_guest_dl_api_dlsym( + &context, &direct_entries, (void *)~0ULL, symbol); + CHECK("RTLD_NEXT forwards", symbol_result.forward_to_guest_caller); + CHECK("RTLD_NEXT does not call adapter", dlsym_calls == 0); + + reset_calls(); + reset_calls(); + guest_result = 0x2030; + selected_result = 0x3030; + symbol_result = kzt_guest_dl_api_dlvsym( + &context, &direct_entries, (void *)(uintptr_t)1, symbol, version); + CHECK("dlvsym call", dlvsym_calls == 1); + CHECK("dlvsym handle preserved", seen_handle == (void *)(uintptr_t)1); + CHECK("dlvsym version", seen_version == version); + CHECK("dlvsym result", symbol_result.value == 0x3030); + + reset_calls(); + guest_result = 0x2040; + dlinfo_identity_enabled = 1; + dlinfo_link_map = 0x2090; + dlinfo_lmid = 7; + CHECK("dlmopen result", + kzt_guest_dl_api_dlmopen( + &context, &direct_entries, (void *)(uintptr_t)7, + filename, 3) == 0x2040); + CHECK("dlmopen call", dlmopen_calls == 1); + CHECK("dlmopen namespace", seen_lmid == (void *)(uintptr_t)7); + CHECK("dlmopen filename", seen_filename == filename); + CHECK("dlmopen flag", seen_flag == 3); + CHECK("dlmopen exact dlinfo calls", dlinfo_calls == 2); + CHECK("dlmopen exact identity published", + registry_identity_publish_calls == 1 && + registry_identity_handle == 0x2040 && + registry_identity_link_map == 0x2090 && + registry_identity_namespace == 7); + + reset_calls(); + guest_result = 17; + CHECK("dlinfo result", + kzt_guest_dl_api_dlinfo( + &direct_entries, (void *)(uintptr_t)1, 9, info) == 17); + CHECK("dlinfo call", dlinfo_calls == 1); + CHECK("dlinfo handle preserved", seen_handle == (void *)(uintptr_t)1); + CHECK("dlinfo request", seen_request == 9); + CHECK("dlinfo info", seen_info == info); + + reset_calls(); + dlclose_result = -7; + CHECK("guest dlclose failure preserved", + kzt_guest_dl_api_dlclose( + &context, &thread_scope, &direct_entries, + (void *)(uintptr_t)0x9000) == -7); + CHECK("guest dlclose failure called once", dlclose_calls == 1); + CHECK("guest dlclose failure handle", + dlclose_handles[0] == (void *)(uintptr_t)0x9000); + CHECK("guest dlclose failure skips probe", dlopen_calls == 0); + CHECK("guest dlclose failure releases writer", + writer_begin_calls == 1 && writer_end_calls == 1 && + writer_active == 0); + + reset_calls(); + CHECK("dlclose without identity succeeds", + kzt_guest_dl_api_dlclose( + &context, &thread_scope, &direct_entries, + (void *)(uintptr_t)0x9000) == 0); + CHECK("dlclose without identity called once", dlclose_calls == 1); + CHECK("dlclose without identity skips probe", dlopen_calls == 0); + CHECK("dlclose without identity keeps metadata", + inactive_calls == 0 && retire_calls == 0); + CHECK("dlclose without identity records diagnostic", + registry_close_identity_missing_calls == 1); + + reset_calls(); + set_exact_match(0x9000, 0); + dlopen_result = 0x9200; + registry_close_result = KZT_GUEST_LOADER_CLOSE_REFERENCED; + CHECK("referenced object close succeeds", + kzt_guest_dl_api_dlclose( + &context, &thread_scope, &direct_entries, + (void *)(uintptr_t)0x9000) == 0); + CHECK("referenced object is not pathname-probed", dlopen_calls == 0); + CHECK("referenced object guest close called once", dlclose_calls == 1); + CHECK("referenced object remains live", + inactive_calls == 0 && retire_calls == 0); + CHECK("referenced object completes exact handle close", + registry_close_complete_calls == 1); + + reset_calls(); + set_exact_match(0x9000, 0); + CHECK("pathname miss close succeeds", + kzt_guest_dl_api_dlclose( + &context, &thread_scope, &direct_entries, + (void *)(uintptr_t)0x9000) == 0); + CHECK("pathname miss is not unload proof", + inactive_calls == 0 && retire_calls == 0); + + reset_calls(); + set_exact_match(0x9000, 0); + binding_lookup_result = 0; + binding_library = &library; + binding_object_type = KZT_GUEST_LIBRARY_OBJECT_WRAPPED; + library.x86linkmap = (struct link_map *)(uintptr_t)0x9000; + CHECK("unloaded wrapper close succeeds", + kzt_guest_dl_api_dlclose( + &context, &thread_scope, &direct_entries, + (void *)(uintptr_t)0x9000) == 0); + CHECK("pathname miss does not consume guest error", dlerror_calls == 0); + CHECK("pathname miss keeps wrapper binding", + binding_release_calls == 0 && inactive_calls == 0); + CHECK("pathname miss keeps guest link map", + library.x86linkmap == (struct link_map *)(uintptr_t)0x9000); + CHECK("pathname miss keeps exact generation live", retire_calls == 0); + + { + kzt_guest_loader_identity_t unload = { + .handle = 0x9000, + .link_map_addr = 0x9000, + .generation = 17, + .namespace_id = 0, + }; + + CHECK("exact unload prepare", + kzt_guest_dl_api_prepare_unload(&context, &unload) == 0); + CHECK("exact unload prepare closes Registry admission", + registry_unload_begin_calls == 1); + CHECK("exact unload prepare retires prebind", + prebind_retire_calls == 1); + CHECK("exact unload event retires wrapper", + kzt_guest_dl_api_publish_unload(&context, &unload) == 0); + CHECK("exact unload event does not repeat prebind retire", + prebind_retire_calls == 1); + CHECK("exact unload event retires registry", retire_calls == 1); + CHECK("exact unload event releases binding", + binding_release_calls == 1); + CHECK("exact unload event inactivates wrapper", inactive_calls == 1); + CHECK("exact unload event clears link map", library.x86linkmap == NULL); + } + + reset_calls(); + set_exact_match(0x9000, 7); + { + kzt_guest_loader_identity_t retained = { + .link_map_addr = 0x9000, + .generation = 17, + .namespace_id = 7, + }; + + CHECK("retained identity prepares", + kzt_guest_dl_api_prepare_unload(&context, &retained) == 0); + CHECK("retained identity cancels", + kzt_guest_dl_api_cancel_unload(&context, &retained) == 0); + CHECK("retained identity does not finish", + registry_unload_begin_calls == 1 && + registry_unload_cancel_calls == 1 && retire_calls == 0 && + prebind_retire_calls == 0); + } + + reset_calls(); + set_exact_match(0x9000, 0); + prebind_retire_result = -1; + { + kzt_guest_loader_identity_t quiesced = { + .link_map_addr = 0x9000, + .generation = 17, + .namespace_id = 0, + }; + + CHECK("prebind failure keeps unload prepared", + kzt_guest_dl_api_prepare_unload(&context, &quiesced) == 0); + CHECK("prebind failure does not reopen Registry admission", + registry_unload_begin_calls == 1 && + registry_unload_cancel_calls == 0); + CHECK("prebind failure can cancel at consistent", + kzt_guest_dl_api_cancel_unload(&context, &quiesced) == 0); + } + + reset_calls(); + set_exact_match(0x9000, 0); + CHECK("unbound object close succeeds", + kzt_guest_dl_api_dlclose( + &context, &thread_scope, &direct_entries, + (void *)(uintptr_t)0x9000) == 0); + CHECK("unbound object stays live without unload fact", retire_calls == 0); + + reset_calls(); + set_exact_match(0x9000, 0); + binding_lookup_result = 0; + binding_library = &library; + binding_object_type = KZT_GUEST_LIBRARY_OBJECT_WRAPPED; + library.x86linkmap = (struct link_map *)(uintptr_t)0x9000; + close_race_enabled = 1; + close_races[0] = (dlclose_race_t) { + .context = &context, + .thread_scope = &thread_scope, + .entries = &direct_entries, + .handle = (void *)(uintptr_t)0x9000, + .result = -1, + }; + close_races[1] = close_races[0]; + CHECK("close race first thread starts", + pthread_create(&close_threads[0], NULL, close_guest_library, + &close_races[0]) == 0); + CHECK("close race second thread starts", + pthread_create(&close_threads[1], NULL, close_guest_library, + &close_races[1]) == 0); + CHECK("close race first thread joins", + pthread_join(close_threads[0], NULL) == 0); + CHECK("close race second thread joins", + pthread_join(close_threads[1], NULL) == 0); + close_race_enabled = 0; + CHECK("close race both guest closes succeed", + close_races[0].result == 0 && close_races[1].result == 0); + CHECK("close race does not probe pathname", + __atomic_load_n(&dlopen_calls, __ATOMIC_RELAXED) == 0); + CHECK("close race keeps metadata without unload fact", + __atomic_load_n(&retire_calls, __ATOMIC_RELAXED) == 0 && + __atomic_load_n(&inactive_calls, __ATOMIC_RELAXED) == 0 && + __atomic_load_n(&binding_release_calls, __ATOMIC_RELAXED) == 0 && + library.x86linkmap == (struct link_map *)(uintptr_t)0x9000); + + reset_calls(); + set_exact_match(0x9000, 7); + guest_result = 0x9300; + CHECK("namespace object close succeeds", + kzt_guest_dl_api_dlclose( + &context, &thread_scope, &direct_entries, + (void *)(uintptr_t)0x9000) == 0); + CHECK("namespace close does not probe pathname", dlmopen_calls == 0); + CHECK("namespace close calls guest once", dlclose_calls == 1); + CHECK("namespace object remains live", + inactive_calls == 0 && retire_calls == 0); + + __atomic_store_n( + &dl.guest_dl_entries.observed_dlerror, 0, __ATOMIC_RELEASE); + CHECK("dlerror entry starts empty", + kzt_guest_dl_api_load_dlerror_entry(&dl) == 0); + CHECK("dlerror inline hint starts empty", + kzt_guest_dl_api_load_dlerror_hint(&dl) == 0); + CHECK("dlerror entry rejects wrong symbol", + kzt_guest_dl_api_publish_dlerror_entry( + &dl, "dlsym", 0x7010, 1) != 0); + CHECK("dlerror entry rejects plain wrapper", + kzt_guest_dl_api_publish_dlerror_entry( + &dl, "dlerror", 0x7010, 0) != 0); + CHECK("dlerror entry rejects zero", + kzt_guest_dl_api_publish_dlerror_entry( + &dl, "dlerror", 0, 1) != 0); + CHECK("dlerror entry publishes exact guest target", + kzt_guest_dl_api_publish_dlerror_entry( + &dl, "dlerror", 0x7010, 1) == 0); + CHECK("dlerror entry loads published target", + kzt_guest_dl_api_load_dlerror_entry(&dl) == 0x7010); + CHECK("dlerror inline hint loads published target", + kzt_guest_dl_api_load_dlerror_hint(&dl) == 0x7010); + CHECK("dlerror entry same target is idempotent", + kzt_guest_dl_api_publish_dlerror_entry( + &dl, "dlerror", 0x7010, 1) == 0); + CHECK("dlerror entry rejects conflict", + kzt_guest_dl_api_publish_dlerror_entry( + &dl, "dlerror", 0x7020, 1) != 0); + CHECK("dlerror entry conflict preserves original", + kzt_guest_dl_api_load_dlerror_entry(&dl) == 0x7010); + + __atomic_store_n( + &dl.guest_dl_entries.observed_dlerror, 0, __ATOMIC_RELEASE); + races[0] = (dlerror_publish_race_t) { + .dl = &dl, + .entry = 0x7030, + .result = -1, + }; + races[1] = (dlerror_publish_race_t) { + .dl = &dl, + .entry = 0x7040, + .result = -1, + }; + CHECK("dlerror concurrent publisher one starts", + pthread_create( + &race_threads[0], NULL, publish_dlerror_entry, + &races[0]) == 0); + CHECK("dlerror concurrent publisher two starts", + pthread_create( + &race_threads[1], NULL, publish_dlerror_entry, + &races[1]) == 0); + CHECK("dlerror concurrent publisher one joins", + pthread_join(race_threads[0], NULL) == 0); + CHECK("dlerror concurrent publisher two joins", + pthread_join(race_threads[1], NULL) == 0); + CHECK("dlerror concurrent publication has one winner", + (races[0].result == 0) != (races[1].result == 0)); + CHECK("dlerror concurrent publication preserves winner", + kzt_guest_dl_api_load_dlerror_entry(&dl) == + (races[0].result == 0 ? races[0].entry : races[1].entry)); + + CHECK("guest dl entry state initializes", + kzt_guest_dl_api_entry_state_init(&dl) == 0); + entry_init_paused = 0; + entry_init_release = 0; + entry_reader_checked = 0; + entry_resolver_calls = 0; + entry_races[0].dl = &dl; + entry_races[1].dl = &dl; + CHECK("guest dl initializer starts", + pthread_create(&entry_threads[0], NULL, + initialize_guest_dl_entries, + &entry_races[0]) == 0); + pthread_mutex_lock(&entry_init_lock); + while (!entry_init_paused) { + pthread_cond_wait(&entry_init_ready, &entry_init_lock); + } + pthread_mutex_unlock(&entry_init_lock); + CHECK("guest dl concurrent reader starts", + pthread_create(&entry_threads[1], NULL, read_guest_dl_entries, + &entry_races[1]) == 0); + pthread_mutex_lock(&entry_init_lock); + while (!entry_reader_checked) { + pthread_cond_wait(&entry_init_ready, &entry_init_lock); + } + CHECK("guest dl table is hidden while resolver is paused", + !entry_races[1].saw_table_before_release); + entry_init_release = 1; + pthread_cond_broadcast(&entry_init_ready); + pthread_mutex_unlock(&entry_init_lock); + CHECK("guest dl initializer joins", + pthread_join(entry_threads[0], NULL) == 0); + CHECK("guest dl concurrent reader joins", + pthread_join(entry_threads[1], NULL) == 0); + CHECK("guest dl entries resolve once", entry_resolver_calls == 1); + CHECK("guest dl threads share one published table", + entry_races[0].result && + entry_races[0].result == entry_races[1].result); + CHECK("guest dl published table is complete", + entry_races[0].snapshot.dlopen == 0x8100 && + entry_races[0].snapshot.dlmopen == 0x8110 && + entry_races[0].snapshot.dlsym == 0x8120 && + entry_races[0].snapshot.dlclose == 0x8130 && + entry_races[0].snapshot.dladdr == 0x8140 && + entry_races[0].snapshot.dladdr1 == 0x8150 && + entry_races[0].snapshot.dlinfo == 0x8160 && + entry_races[0].snapshot.dlvsym == 0x8170 && + entry_races[0].snapshot.dlerror == 0x8180); + kzt_guest_dl_api_entry_state_destroy(&dl); + + CHECK("guest dl retry state initializes", + kzt_guest_dl_api_entry_state_init(&dl) == 0); + entry_retry_attempts = 0; + memset(&entry_races[0], 0, sizeof(entry_races[0])); + entry_races[0].dl = &dl; + entry_races[0].result = kzt_guest_dl_api_ensure_entries( + &dl, resolve_guest_dl_entries_with_retry, NULL, + &entry_races[0].fallback, NULL); + CHECK("incomplete guest dl table is not published", + kzt_guest_dl_api_load_entries(&dl) == NULL); + CHECK("incomplete guest dl table remains usable only as fallback", + entry_races[0].result == &entry_races[0].fallback && + entry_races[0].fallback.dlopen == 0x8200 && + entry_races[0].fallback.dlerror == 0); + entry_races[0].result = kzt_guest_dl_api_ensure_entries( + &dl, resolve_guest_dl_entries_with_retry, NULL, + &entry_races[0].fallback, NULL); + CHECK("guest dl initialization retries after incomplete result", + entry_retry_attempts == 2 && entry_races[0].result && + entry_races[0].result != &entry_races[0].fallback && + entry_races[0].result->dlerror == 0x8280); + for (int i = 0; i < 1000; ++i) { + CHECK("steady guest dl table stays published", + kzt_guest_dl_api_ensure_entries( + &dl, resolve_guest_dl_entries_with_retry, NULL, + &entry_races[0].fallback, NULL) == entry_races[0].result); + } + CHECK("steady guest dl calls do not resolve again", + entry_retry_attempts == 2); + kzt_guest_dl_api_entry_state_destroy(&dl); + + CHECK("guest dl hint conflict state initializes", + kzt_guest_dl_api_entry_state_init(&dl) == 0); + CHECK("guest dl hint publishes before table initialization", + kzt_guest_dl_api_publish_dlerror_entry( + &dl, "dlerror", 0x8290, 1) == 0); + entry_hint_attempts = 0; + entry_races[0].result = kzt_guest_dl_api_ensure_entries( + &dl, resolve_guest_dl_entries_against_hint, NULL, + &entry_races[0].fallback, NULL); + CHECK("guest dl hint conflict rejects complete table publication", + entry_hint_attempts == 1 && + kzt_guest_dl_api_load_entries(&dl) == NULL && + entry_races[0].result == &entry_races[0].fallback && + entry_races[0].fallback.dlerror == 0x8280); + entry_races[0].result = kzt_guest_dl_api_ensure_entries( + &dl, resolve_guest_dl_entries_against_hint, NULL, + &entry_races[0].fallback, NULL); + CHECK("guest dl hint conflict retries and publishes matching table", + entry_hint_attempts == 2 && entry_races[0].result && + entry_races[0].result != &entry_races[0].fallback && + entry_races[0].result->dlerror == 0x8290 && + kzt_guest_dl_api_load_dlerror_entry(&dl) == 0x8290); + kzt_guest_dl_api_entry_state_destroy(&dl); + + CHECK("guest dl recursive state initializes", + kzt_guest_dl_api_entry_state_init(&dl) == 0); + entry_recursive_attempted = 0; + entry_recursive_result = -1; + entry_races[0].result = kzt_guest_dl_api_ensure_entries( + &dl, resolve_guest_dl_entries_recursively, &dl, + &entry_races[0].fallback, NULL); + CHECK("recursive guest dl initialization fails open", + entry_recursive_attempted && entry_recursive_result == 0); + CHECK("outer guest dl initialization still publishes", + entry_races[0].result && + entry_races[0].result->dlerror == 0x8280); + kzt_guest_dl_api_entry_state_destroy(&dl); + + CHECK("guest dl destroy state initializes", + kzt_guest_dl_api_entry_state_init(&destroy_dl) == 0); + entry_init_paused = 0; + entry_init_release = 0; + entry_destroy_started = 0; + entry_destroy_done = 0; + CHECK("guest dl destroy initializer starts", + pthread_create(&destroy_threads[0], NULL, + initialize_guest_dl_entries, + &destroy_init_race) == 0); + pthread_mutex_lock(&entry_init_lock); + while (!entry_init_paused) { + pthread_cond_wait(&entry_init_ready, &entry_init_lock); + } + pthread_mutex_unlock(&entry_init_lock); + CHECK("guest dl destroy thread starts", + pthread_create(&destroy_threads[1], NULL, + destroy_guest_dl_entries, &destroy_race) == 0); + while (!__atomic_load_n(&entry_destroy_started, __ATOMIC_ACQUIRE)) { + sched_yield(); + } + CHECK("guest dl destroy waits for in-flight resolver", + !__atomic_load_n(&entry_destroy_done, __ATOMIC_ACQUIRE)); + pthread_mutex_lock(&entry_init_lock); + entry_init_release = 1; + pthread_cond_broadcast(&entry_init_ready); + pthread_mutex_unlock(&entry_init_lock); + CHECK("guest dl destroy initializer joins", + pthread_join(destroy_threads[0], NULL) == 0); + CHECK("guest dl destroy thread joins", + pthread_join(destroy_threads[1], NULL) == 0); + CHECK("guest dl destroy completes after resolver", + __atomic_load_n(&entry_destroy_done, __ATOMIC_ACQUIRE)); + CHECK("guest dl teardown prevents publication", + destroy_init_race.result == &destroy_init_race.fallback); + + CHECK("default guest dl teardown state initializes", + kzt_guest_dl_api_entry_state_init(&default_dl) == 0); + default_entry_pause = 0; + default_entry_libc_calls = 0; + default_entry_libdl_calls = 0; + default_entry_wine_calls = 0; + default_entry_path_calls = 0; + default_entry_header_frees = 0; + default_entry_path_present = 0; + default_entry_incomplete = 0; + default_wine_pause = 1; + default_wine_paused = 0; + default_wine_release = 0; + entry_destroy_started = 0; + entry_destroy_done = 0; + CHECK("default guest dl prepare starts", + pthread_create(&default_threads[0], NULL, + initialize_default_guest_dl_entries, + &default_races[0]) == 0); + pthread_mutex_lock(&entry_init_lock); + while (!default_wine_paused) { + pthread_cond_wait(&entry_init_ready, &entry_init_lock); + } + pthread_mutex_unlock(&entry_init_lock); + CHECK("default guest dl table remains hidden during prepare", + kzt_guest_dl_api_load_entries(&default_dl) == NULL); + CHECK("default guest dl prepare waiter starts", + pthread_create(&default_threads[1], NULL, + initialize_default_guest_dl_entries, + &default_races[1]) == 0); + for (;;) { + unsigned int slow_users; + + pthread_mutex_lock(&default_dl.guest_dl_entries.mutex); + slow_users = default_dl.guest_dl_entries.slow_users; + pthread_mutex_unlock(&default_dl.guest_dl_entries.mutex); + if (slow_users == 2) { + break; + } + sched_yield(); + } + destroy_race.dl = &default_dl; + CHECK("default guest dl prepare destroy starts", + pthread_create(&destroy_threads[0], NULL, + destroy_guest_dl_entries, &destroy_race) == 0); + while (!__atomic_load_n(&entry_destroy_started, __ATOMIC_ACQUIRE)) { + sched_yield(); + } + CHECK("default guest dl destroy waits for prepare and waiter", + !__atomic_load_n(&entry_destroy_done, __ATOMIC_ACQUIRE)); + pthread_mutex_lock(&entry_init_lock); + default_wine_release = 1; + pthread_cond_broadcast(&entry_init_ready); + pthread_mutex_unlock(&entry_init_lock); + CHECK("default guest dl prepare initializer joins", + pthread_join(default_threads[0], NULL) == 0); + CHECK("default guest dl prepare waiter joins", + pthread_join(default_threads[1], NULL) == 0); + CHECK("default guest dl prepare destroy joins", + pthread_join(destroy_threads[0], NULL) == 0); + CHECK("default guest dl teardown blocks post-prepare publication", + default_races[0].result == &default_races[0].fallback && + default_races[1].result == NULL && + kzt_guest_dl_api_load_entries(&default_dl) == NULL); + + CHECK("default guest dl state initializes", + kzt_guest_dl_api_entry_state_init(&default_dl) == 0); + default_entry_pause = 1; + default_entry_libc_calls = 0; + default_entry_libdl_calls = 0; + default_entry_wine_calls = 0; + default_entry_path_calls = 0; + default_entry_header_frees = 0; + default_entry_path_present = 0; + default_entry_incomplete = 0; + default_wine_pause = 0; + entry_init_paused = 0; + entry_init_release = 0; + CHECK("default guest dl libc caller starts", + pthread_create(&default_threads[0], NULL, + initialize_default_guest_dl_entries, + &default_races[0]) == 0); + pthread_mutex_lock(&entry_init_lock); + while (!entry_init_paused) { + pthread_cond_wait(&entry_init_ready, &entry_init_lock); + } + pthread_mutex_unlock(&entry_init_lock); + CHECK("default guest dl libdl caller starts", + pthread_create(&default_threads[1], NULL, + initialize_default_guest_dl_entries, + &default_races[1]) == 0); + pthread_mutex_lock(&entry_init_lock); + entry_init_release = 1; + pthread_cond_broadcast(&entry_init_ready); + pthread_mutex_unlock(&entry_init_lock); + CHECK("default guest dl libc caller joins", + pthread_join(default_threads[0], NULL) == 0); + CHECK("default guest dl libdl caller joins", + pthread_join(default_threads[1], NULL) == 0); + CHECK("default guest dl resolver runs once", + default_entry_libc_calls == 1 && default_entry_libdl_calls == 1 && + default_entry_path_calls == 1); + CHECK("default guest dl resolver frees transient headers", + default_entry_header_frees == 2); + CHECK("default guest dl post init runs once", + default_entry_wine_calls == 1); + CHECK("default guest dl callers share complete table", + default_races[0].result && + default_races[0].result == default_races[1].result && + default_races[0].result->dlopen == 0x9000 && + default_races[0].result->dlerror == 0x9080); + default_entry_pause = 0; + for (int i = 0; i < 1000; ++i) { + CHECK("default guest dl steady table remains published", + kzt_guest_dl_entries_for_call( + &default_context, &default_races[0].fallback) == + default_races[0].result); + } + CHECK("default guest dl steady calls skip resolution", + default_entry_libc_calls == 1 && default_entry_libdl_calls == 1 && + default_entry_wine_calls == 1 && default_entry_path_calls == 1); + kzt_guest_dl_api_entry_state_destroy(&default_dl); + + CHECK("default guest dl failure resource state initializes", + kzt_guest_dl_api_entry_state_init(&default_dl) == 0); + default_entry_libc_calls = 0; + default_entry_libdl_calls = 0; + default_entry_wine_calls = 0; + default_entry_path_calls = 0; + default_entry_header_frees = 0; + default_entry_path_present = 0; + default_entry_incomplete = 1; + for (int i = 0; i < 100; ++i) { + default_races[0].result = kzt_guest_dl_entries_for_call( + &default_context, &default_races[0].fallback); + CHECK("default guest dl incomplete retry stays private", + default_races[0].result == &default_races[0].fallback && + kzt_guest_dl_api_load_entries(&default_dl) == NULL); + } + CHECK("default guest dl incomplete retries release resources", + default_entry_libc_calls == 100 && + default_entry_libdl_calls == 100 && + default_entry_header_frees == 200 && + default_entry_path_calls == 1 && + default_entry_wine_calls == 0); + default_entry_incomplete = 0; + default_races[0].result = kzt_guest_dl_entries_for_call( + &default_context, &default_races[0].fallback); + CHECK("default guest dl recovery publishes after resource-stable retries", + default_races[0].result && + default_races[0].result != &default_races[0].fallback && + default_entry_libc_calls == 101 && + default_entry_libdl_calls == 101 && + default_entry_header_frees == 202 && + default_entry_path_calls == 1 && + default_entry_wine_calls == 1); + kzt_guest_dl_api_entry_state_destroy(&default_dl); + + kzt_guest_dl_api_clear_error(&error_state); + kzt_guest_dl_api_free_errors(&error_state); + printf("kzt guest dl API tests: PASS\n"); + return EXIT_SUCCESS; +} diff --git a/tests/unit/kzt/test_guest_dlclose_quiescence.c b/tests/unit/kzt/test_guest_dlclose_quiescence.c new file mode 100644 index 00000000000..a56bb09dff8 --- /dev/null +++ b/tests/unit/kzt/test_guest_dlclose_quiescence.c @@ -0,0 +1,331 @@ +#include +#include +#include +#include +#include + +#include "target/i386/latx/include/box64context.h" +#include "target/i386/latx/include/kzt_guest_dl_api.h" +#include "target/i386/latx/include/kzt_guest_library_adapter.h" +#include "target/i386/latx/include/kzt_guest_library_binding.h" +#include "target/i386/latx/include/kzt_jump_slot_production.h" +#include "target/i386/latx/include/kzt_lifecycle_diagnostics.h" + +int option_kzt = 1; +int wine_option_kzt; + +typedef struct dlclose_sync { + pthread_mutex_t lock; + pthread_cond_t cond; + int guest_entered; + int allow_guest_return; + int close_bookkeeping_entered; + int allow_close_bookkeeping_return; +} dlclose_sync_t; + +typedef struct dlclose_thread_arg { + box64context_t *context; + const kzt_guest_dl_entries_t *entries; + kzt_guest_library_loader_scope_t thread_scope; + void *handle; + int result; +} dlclose_thread_arg_t; + +static dlclose_sync_t sync_state = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, +}; +static int failures; +static int prebind_invalidations; +static int identity_lookups; +static int close_bookkeeping_calls; +static int guest_return_value; + +#define CHECK(name, expr) \ + do { \ + if (!(expr)) { \ + fprintf(stderr, "FAIL %s\n", name); \ + ++failures; \ + } \ + } while (0) + +static struct timespec deadline_after_seconds(time_t seconds) +{ + struct timespec deadline; + + clock_gettime(CLOCK_REALTIME, &deadline); + deadline.tv_sec += seconds; + return deadline; +} + +static int wait_for_flag(int *flag) +{ + struct timespec deadline = deadline_after_seconds(10); + + pthread_mutex_lock(&sync_state.lock); + while (!*flag) { + int result = pthread_cond_timedwait( + &sync_state.cond, &sync_state.lock, &deadline); + + if (result != 0) { + pthread_mutex_unlock(&sync_state.lock); + return -1; + } + } + pthread_mutex_unlock(&sync_state.lock); + return 0; +} + +static int guest_entered(void) +{ + int entered; + + pthread_mutex_lock(&sync_state.lock); + entered = sync_state.guest_entered; + pthread_mutex_unlock(&sync_state.lock); + return entered; +} + +static int wait_for_writer_or_guest( + kzt_guest_library_bindings_t *bindings, unsigned int *waiters) +{ + struct timespec start; + + clock_gettime(CLOCK_MONOTONIC, &start); + for (;;) { + struct timespec now; + struct timespec delay = { .tv_nsec = 1000000L }; + + if (kzt_guest_library_binding_test_loader_state( + bindings, NULL, waiters, NULL, NULL) != 0) { + return -1; + } + if (*waiters || guest_entered()) { + return 0; + } + clock_gettime(CLOCK_MONOTONIC, &now); + if (now.tv_sec - start.tv_sec >= 10) { + return -1; + } + nanosleep(&delay, NULL); + } +} + +static void release_guest_call(void) +{ + pthread_mutex_lock(&sync_state.lock); + sync_state.allow_guest_return = 1; + pthread_cond_broadcast(&sync_state.cond); + pthread_mutex_unlock(&sync_state.lock); +} + +static void release_close_bookkeeping(void) +{ + pthread_mutex_lock(&sync_state.lock); + sync_state.allow_close_bookkeeping_return = 1; + pthread_cond_broadcast(&sync_state.cond); + pthread_mutex_unlock(&sync_state.lock); +} + +static void check_reader_rejected( + const char *name, kzt_guest_library_bindings_t *bindings) +{ + kzt_guest_library_loader_quiescence_lease_t late = { 0 }; + int result = kzt_guest_library_loader_quiescence_try_acquire( + bindings, &late); + + CHECK(name, result != 0); + if (result == 0) { + kzt_guest_library_loader_quiescence_release(&late); + } +} + +static void *run_dlclose_thread(void *opaque) +{ + dlclose_thread_arg_t *arg = opaque; + + arg->result = kzt_guest_dl_api_dlclose( + arg->context, &arg->thread_scope, arg->entries, arg->handle); + return NULL; +} + +kzt_guest_library_bindings_t *KztGuestLibraryBindingsForContext( + box64context_t *context) +{ + return context ? context->kzt_guest_library_access.bindings : NULL; +} + +kzt_guest_registry_t *KztGuestRegistryForContext(box64context_t *context) +{ + return context ? (kzt_guest_registry_t *)(uintptr_t)0x1 : NULL; +} + +int kzt_guest_registry_find_loader_identity( + kzt_guest_registry_t *registry, uintptr_t handle, + kzt_guest_loader_identity_t *identity) +{ + CHECK("identity registry", registry == (kzt_guest_registry_t *)0x1); + CHECK("identity handle", handle == 0x9000); + ++identity_lookups; + *identity = (kzt_guest_loader_identity_t) { + .handle = handle, + .link_map_addr = 0x9100, + .generation = 17, + .namespace_id = 0, + }; + return 0; +} + +kzt_guest_loader_close_result_t kzt_guest_registry_complete_loader_close( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *identity) +{ + CHECK("close registry", registry == (kzt_guest_registry_t *)0x1); + CHECK("close identity", + identity && identity->link_map_addr == 0x9100 && + identity->generation == 17 && identity->namespace_id == 0); + ++close_bookkeeping_calls; + pthread_mutex_lock(&sync_state.lock); + sync_state.close_bookkeeping_entered = 1; + pthread_cond_broadcast(&sync_state.cond); + while (!sync_state.allow_close_bookkeeping_return) { + pthread_cond_wait(&sync_state.cond, &sync_state.lock); + } + pthread_mutex_unlock(&sync_state.lock); + return KZT_GUEST_LOADER_CLOSE_REFERENCED; +} + +void kzt_guest_registry_note_loader_close_identity_missing( + kzt_guest_registry_t *registry) +{ + (void)registry; + CHECK("exact identity unexpectedly missing", 0); +} + +int kzt_production_lazy_prebind_invalidate( + box64context_t *context, kzt_lazy_prebind_mutation_t mutation) +{ + CHECK("prebind context", context != NULL); + CHECK("prebind mutation", mutation == KZT_LAZY_PREBIND_MUTATION_DLCLOSE); + ++prebind_invalidations; + return 0; +} + +int kzt_lifecycle_diagnostics_enabled(void) +{ + return 0; +} + +uint64_t kzt_lifecycle_diagnostics_now(void) +{ + return 0; +} + +void kzt_lifecycle_diagnostics_add( + kzt_lifecycle_diagnostic_stage_t stage, uint64_t duration_ns) +{ + (void)stage; + (void)duration_ns; +} + +int kzt_guest_library_run_dlclose(uintptr_t function, void *handle) +{ + CHECK("guest function", function == 0x1060); + CHECK("guest handle", handle == (void *)(uintptr_t)0x9000); + pthread_mutex_lock(&sync_state.lock); + sync_state.guest_entered = 1; + pthread_cond_broadcast(&sync_state.cond); + while (!sync_state.allow_guest_return) { + pthread_cond_wait(&sync_state.cond, &sync_state.lock); + } + pthread_mutex_unlock(&sync_state.lock); + return guest_return_value; +} + +int main(void) +{ + box64context_t context = { 0 }; + kzt_guest_library_bindings_t *bindings = + kzt_guest_library_bindings_init(); + kzt_guest_library_loader_quiescence_lease_t reader = { 0 }; + kzt_guest_library_loader_quiescence_lease_t after = { 0 }; + kzt_guest_dl_entries_t entries = { .dlclose = 0x1060 }; + dlclose_thread_arg_t arg = { + .context = &context, + .entries = &entries, + .handle = (void *)(uintptr_t)0x9000, + .result = -1, + }; + pthread_t thread; + unsigned int waiters = 0; + + CHECK("bindings init", bindings != NULL); + if (!bindings) { + return EXIT_FAILURE; + } + context.kzt_guest_library_access.bindings = bindings; + CHECK("initial reader acquire", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &reader) == 0); + CHECK("dlclose thread create", + pthread_create(&thread, NULL, run_dlclose_thread, &arg) == 0); + + CHECK("writer or guest becomes observable", + wait_for_writer_or_guest(bindings, &waiters) == 0); + CHECK("guest dlclose waits for existing reader", !guest_entered()); + CHECK("dlclose registers writer admission gate", waiters == 1); + check_reader_rejected("waiting writer rejects new reader", bindings); + + kzt_guest_library_loader_quiescence_release(&reader); + CHECK("guest dlclose enters after reader release", + wait_for_flag(&sync_state.guest_entered) == 0); + check_reader_rejected("guest dlclose keeps writer active", bindings); + + release_guest_call(); + CHECK("close bookkeeping entered", + wait_for_flag(&sync_state.close_bookkeeping_entered) == 0); + check_reader_rejected("close bookkeeping keeps writer active", bindings); + + release_close_bookkeeping(); + CHECK("dlclose thread join", pthread_join(thread, NULL) == 0); + CHECK("guest result preserved", arg.result == 0); + CHECK("prebind invalidated once", prebind_invalidations == 1); + CHECK("identity read once", identity_lookups == 1); + CHECK("close bookkeeping once", close_bookkeeping_calls == 1); + CHECK("reader admission reopens after dlclose", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &after) == 0); + kzt_guest_library_loader_quiescence_release(&after); + + pthread_mutex_lock(&sync_state.lock); + sync_state.guest_entered = 0; + sync_state.allow_guest_return = 0; + pthread_mutex_unlock(&sync_state.lock); + guest_return_value = -7; + arg.result = 0; + CHECK("failing dlclose thread create", + pthread_create(&thread, NULL, run_dlclose_thread, &arg) == 0); + CHECK("failing guest dlclose enters", + wait_for_flag(&sync_state.guest_entered) == 0); + check_reader_rejected("failing guest dlclose keeps writer active", + bindings); + release_guest_call(); + CHECK("failing dlclose thread join", pthread_join(thread, NULL) == 0); + CHECK("failing guest result preserved", arg.result == -7); + CHECK("failing close skips bookkeeping", close_bookkeeping_calls == 1); + CHECK("failing close releases writer", + kzt_guest_library_loader_quiescence_try_acquire( + bindings, &after) == 0); + kzt_guest_library_loader_quiescence_release(&after); + + context.kzt_guest_library_access.bindings = NULL; + kzt_guest_library_bindings_destroy(&bindings); + pthread_cond_destroy(&sync_state.cond); + pthread_mutex_destroy(&sync_state.lock); + if (failures) { + fprintf(stderr, "%d failure(s)\n", failures); + return EXIT_FAILURE; + } + puts("guest dlclose quiescence: PASS"); + return EXIT_SUCCESS; +} diff --git a/tests/unit/kzt/test_guest_library_adapter.c b/tests/unit/kzt/test_guest_library_adapter.c new file mode 100644 index 00000000000..b7f16db700e --- /dev/null +++ b/tests/unit/kzt/test_guest_library_adapter.c @@ -0,0 +1,1089 @@ +#include +#include +#include +#include +#include + +#include "target/i386/latx/include/kzt_guest_library_adapter.h" +#include "target/i386/latx/include/kzt_guest_library_binding.h" +#include "target/i386/latx/include/kzt_guest_dynsym_lookup.h" +#include "target/i386/latx/include/kzt_guest_registry.h" +#include "target/i386/latx/include/callback.h" +#include "target/i386/latx/include/library.h" +#include "target/i386/latx/include/library_private.h" + +struct kzt_guest_library_bindings { + int unused; +}; + +int relocation_log; +int kzt_registry_diagnostics; +int option_kzt = 1; +int wine_option_kzt; + +static struct kzt_guest_library_bindings bindings; +static kzt_guest_library_loader_scope_t *thread_scope; +static uintptr_t expected_function; +static uintptr_t expected_filename; +static int expected_flag; +static uint64_t guest_result; +static int begin_result; +static int expect_scoped; +static int guest_sets_refresh_pending; +static int call_count; +static int publish_pair_count; +static int raw_publish_pair_count; +static int publish_observed_count; +static int end_count; +static int sequence; +static int publish_sequence; +static int end_sequence; + +typedef enum guest_call_kind { + GUEST_CALL_DLOPEN = 0, + GUEST_CALL_DLSYM, + GUEST_CALL_DLVSYM, + GUEST_CALL_DLERROR, + GUEST_CALL_DLMOPEN, + GUEST_CALL_DLINFO, +} guest_call_kind_t; + +static guest_call_kind_t expected_call_kind; +static uintptr_t expected_handle; +static uintptr_t expected_symbol; +static uintptr_t expected_version; +static uintptr_t expected_lmid; +static uintptr_t expected_info; +static int expected_request; +static uintptr_t resolved_guest_address; +static kzt_guest_registry_address_match_t resolved_match; +static int resolve_result; +static int lookup_result; +static library_t *lookup_library; +static kzt_guest_library_object_type_t lookup_object_type; +static int wrapper_lookup_result; +static int wrapper_function_lookup_result; +static uintptr_t wrapper_result; +static int resolve_count; +static int lookup_count; +static int release_count; +static unsigned char dynsym_type; +static kzt_guest_dynsym_lookup_status_t dynsym_status; +static uintptr_t dynsym_runtime_address; +static kzt_guest_field_status_t dynamic_view_status; +static unsigned long dynamic_view_generation; +static int find_live_result; +static int source_lease_result; +static uintptr_t lookup_guest_handle; +static kzt_guest_loader_identity_t lookup_loader_identity; +static int lookup_loader_identity_result; +static int symbol_source_result; +static int symbol_source_acquire_count; +static int dynamic_view_count; +static int dynsym_lookup_count; +static int evidence_lookup_count; +static int evidence_store_count; +static int evidence_valid; +static unsigned long evidence_dynamic_revision; +static uintptr_t evidence_runtime_address; +static unsigned char evidence_symbol_type; + +#define CHECK(label, condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "%s: FAIL\n", label); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +kzt_guest_library_bindings_t *KztGuestLibraryBindingsForContext( + box64context_t *context) +{ + return context ? &bindings : NULL; +} + +kzt_guest_registry_t *KztGuestRegistryForContext(box64context_t *context) +{ + return context ? (kzt_guest_registry_t *)(uintptr_t)0x1110 : NULL; +} + +int kzt_guest_registry_resolve_address_pair( + kzt_guest_registry_t *registry, + uintptr_t current_address, + uintptr_t expected_address, + kzt_guest_registry_address_pair_t *pair) +{ + CHECK("resolve registry", + registry == (kzt_guest_registry_t *)(uintptr_t)0x1110); + CHECK("resolve current", current_address == resolved_guest_address); + CHECK("resolve expected", expected_address == resolved_guest_address); + ++resolve_count; + if (resolve_result != 0) + return resolve_result; + memset(pair, 0, sizeof(*pair)); + pair->current = resolved_match; + pair->expected = resolved_match; + return 0; +} + +int kzt_guest_registry_find_loader_identity( + kzt_guest_registry_t *registry, uintptr_t handle, + kzt_guest_loader_identity_t *identity) +{ + CHECK("loader identity registry", + registry == (kzt_guest_registry_t *)(uintptr_t)0x1110); + CHECK("loader identity handle", handle == lookup_guest_handle); + if (lookup_loader_identity_result != 0) { + memset(identity, 0, sizeof(*identity)); + return lookup_loader_identity_result; + } + *identity = lookup_loader_identity; + return 0; +} + +int kzt_guest_registry_loader_symbol_source_acquire( + kzt_guest_registry_t *registry, uintptr_t handle, + kzt_guest_loader_identity_t *identity, + kzt_guest_dynamic_view_t *dynamic_view, + kzt_guest_field_status_t *dynamic_status, + unsigned long *dynamic_revision, + kzt_guest_registry_source_lease_t *lease) +{ + CHECK("symbol source registry", + registry == (kzt_guest_registry_t *)(uintptr_t)0x1110); + CHECK("symbol source handle", handle == lookup_guest_handle); + ++symbol_source_acquire_count; + if (symbol_source_result != 0) { + memset(identity, 0, sizeof(*identity)); + memset(dynamic_view, 0, sizeof(*dynamic_view)); + *dynamic_status = KZT_GUEST_FIELD_UNKNOWN; + *dynamic_revision = 0; + memset(lease, 0, sizeof(*lease)); + return symbol_source_result; + } + *identity = lookup_loader_identity; + memset(dynamic_view, 0, sizeof(*dynamic_view)); + dynamic_view->status = KZT_GUEST_DYNAMIC_COMPLETE; + *dynamic_status = dynamic_view_status; + *dynamic_revision = 5; + *lease = (kzt_guest_registry_source_lease_t) { + .registry = registry, + .link_map_addr = identity->link_map_addr, + .generation = identity->generation, + .namespace_id = identity->namespace_id, + .active = 1, + }; + return 0; +} + +int kzt_guest_library_access_lookup( + kzt_guest_library_access_t *access, + const kzt_guest_library_binding_key_t *key, + kzt_guest_library_handle_t *handle) +{ + CHECK("lookup access", access != NULL); + CHECK("lookup link map", key->link_map_addr == resolved_match.link_map_addr); + CHECK("lookup generation", key->generation == resolved_match.generation); + CHECK("lookup namespace", key->namespace_id == resolved_match.namespace_id); + ++lookup_count; + memset(handle, 0, sizeof(*handle)); + if (lookup_result != 0) + return lookup_result; + handle->bindings = &bindings; + handle->entry = (void *)(uintptr_t)0x1120; + handle->library = lookup_library; + handle->object_type = lookup_object_type; + return 0; +} + +void kzt_guest_library_handle_release(kzt_guest_library_handle_t *handle) +{ + CHECK("release handle", handle != NULL); + ++release_count; + memset(handle, 0, sizeof(*handle)); +} + +int kzt_guest_registry_source_lease_acquire( + kzt_guest_registry_t *registry, uintptr_t link_map_addr, + unsigned long generation, uintptr_t namespace_id, + kzt_guest_registry_source_lease_t *lease) +{ + CHECK("lease registry", + registry == (kzt_guest_registry_t *)(uintptr_t)0x1110); + CHECK("lease link map", link_map_addr == resolved_match.link_map_addr); + CHECK("lease generation", generation == resolved_match.generation); + CHECK("lease namespace", namespace_id == resolved_match.namespace_id); + if (source_lease_result != 0) { + memset(lease, 0, sizeof(*lease)); + return source_lease_result; + } + *lease = (kzt_guest_registry_source_lease_t) { + .registry = registry, + .link_map_addr = link_map_addr, + .generation = generation, + .namespace_id = namespace_id, + .active = 1, + }; + return 0; +} + +int kzt_guest_registry_find_live_object( + kzt_guest_registry_t *registry, uintptr_t link_map_addr, + kzt_guest_registry_address_match_t *match) +{ + CHECK("live registry", + registry == (kzt_guest_registry_t *)(uintptr_t)0x1110); + CHECK("live link map", link_map_addr == resolved_match.link_map_addr); + if (find_live_result != 0) { + memset(match, 0, sizeof(*match)); + return find_live_result; + } + *match = resolved_match; + return 0; +} + +void kzt_guest_registry_source_lease_release( + kzt_guest_registry_source_lease_t *lease) +{ + memset(lease, 0, sizeof(*lease)); +} + +int kzt_guest_registry_find_dynamic_view( + kzt_guest_registry_t *registry, uintptr_t link_map_addr, + kzt_guest_dynamic_view_t *view, kzt_guest_field_status_t *status, + unsigned long *generation) +{ + ++dynamic_view_count; + CHECK("dynamic view registry", + registry == (kzt_guest_registry_t *)(uintptr_t)0x1110); + CHECK("dynamic view link map", + link_map_addr == resolved_match.link_map_addr); + memset(view, 0, sizeof(*view)); + view->status = KZT_GUEST_DYNAMIC_COMPLETE; + *status = dynamic_view_status; + *generation = dynamic_view_generation; + return 0; +} + +kzt_guest_dynsym_lookup_status_t kzt_guest_dynsym_lookup( + const kzt_guest_dynamic_view_t *view, + const kzt_guest_link_map_reader_ops_t *reader_ops, const char *symbol, + kzt_symbol_version_evidence_t version_evidence, const char *version, + kzt_guest_dynsym_lookup_result_t *result) +{ + ++dynsym_lookup_count; + CHECK("dynsym complete view", view->status == KZT_GUEST_DYNAMIC_COMPLETE); + CHECK("dynsym reader", reader_ops && reader_ops->read_memory); + CHECK("dynsym symbol", strcmp(symbol, "wi963_symbol") == 0); + CHECK("dynsym unversioned", + version_evidence == KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED && + version == NULL); + memset(result, 0, sizeof(*result)); + result->status = dynsym_status; + result->binding = STB_GLOBAL; + result->type = dynsym_type; + result->visibility = STV_DEFAULT; + result->runtime_address = dynsym_runtime_address; + return result->status; +} + +int kzt_guest_library_symbol_evidence_lookup( + const kzt_guest_library_handle_t *handle, const char *symbol, + unsigned long dynamic_revision, uintptr_t *runtime_address, + unsigned char *symbol_type) +{ + CHECK("evidence lookup handle", handle && handle->entry); + CHECK("evidence lookup symbol", strcmp(symbol, "wi963_symbol") == 0); + ++evidence_lookup_count; + if (!evidence_valid || evidence_dynamic_revision != dynamic_revision) + return -1; + *runtime_address = evidence_runtime_address; + *symbol_type = evidence_symbol_type; + return 0; +} + +void kzt_guest_library_symbol_evidence_store( + const kzt_guest_library_handle_t *handle, const char *symbol, + unsigned long dynamic_revision, uintptr_t runtime_address, + unsigned char symbol_type) +{ + CHECK("evidence store handle", handle && handle->entry); + CHECK("evidence store symbol", strcmp(symbol, "wi963_symbol") == 0); + ++evidence_store_count; + evidence_valid = 1; + evidence_dynamic_revision = dynamic_revision; + evidence_runtime_address = runtime_address; + evidence_symbol_type = symbol_type; +} + +int GetLibSymbolStartEnd( + library_t *library, const char *name, khint_t pre_k, + uintptr_t *start, uintptr_t *end, int version, + const char *vername, int local) +{ + CHECK("wrapper lookup library", library == lookup_library); + CHECK("wrapper lookup symbol", strcmp(name, "wi963_symbol") == 0); + CHECK("wrapper lookup hash", pre_k == 0); + CHECK("wrapper lookup version", version == -1 && vername == NULL); + CHECK("wrapper lookup local", local == 1); + if (!wrapper_lookup_result) + return 0; + *start = wrapper_result; + *end = wrapper_result + sizeof(void *); + return 1; +} + +int GetLibFunctionSymbolStartEnd( + library_t *library, const char *name, khint_t pre_k, + uintptr_t *start, uintptr_t *end) +{ + CHECK("function lookup library", library == lookup_library); + CHECK("function lookup symbol", strcmp(name, "wi963_symbol") == 0); + CHECK("function lookup hash", pre_k == 0); + if (!wrapper_function_lookup_result) + return 0; + *start = wrapper_result; + *end = wrapper_result + sizeof(void *); + return 1; +} + +int kzt_guest_library_loader_scope_begin( + kzt_guest_library_bindings_t *actual_bindings, + kzt_guest_library_loader_scope_t *scope) +{ + if (begin_result != 0) + return begin_result; + CHECK("begin bindings", actual_bindings == &bindings); + scope->bindings = actual_bindings; + scope->identity = 17; + scope->cookie = 23; + return 0; +} + +void kzt_guest_library_loader_scope_end( + kzt_guest_library_loader_scope_t *scope) +{ + CHECK("end valid scope", + scope && scope->bindings == &bindings && + scope->identity == 17 && scope->cookie == 23); + ++end_count; + end_sequence = ++sequence; + memset(scope, 0, sizeof(*scope)); +} + +kzt_guest_library_binding_result_t +kzt_guest_library_loader_scope_note_pair( + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr, library_t *library, + kzt_guest_library_object_type_t object_type) +{ + (void)scope; + (void)link_map_addr; + (void)library; + (void)object_type; + return KZT_GUEST_LIBRARY_BINDING_PENDING; +} + +kzt_guest_library_binding_result_t +kzt_guest_library_loader_scope_publish_pair( + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr, library_t *library, + kzt_guest_library_object_type_t object_type) +{ + CHECK("publish pair scope", scope && scope->identity == 17); + CHECK("publish pair address", link_map_addr == guest_result); + CHECK("publish pair library", library != NULL); + CHECK("publish pair type", + object_type == KZT_GUEST_LIBRARY_OBJECT_WRAPPED); + ++publish_pair_count; + publish_sequence = ++sequence; + return KZT_GUEST_LIBRARY_BINDING_ADDED; +} + +kzt_guest_library_binding_result_t +kzt_guest_library_loader_scope_publish_observed( + const kzt_guest_library_loader_scope_t *scope, + uintptr_t link_map_addr) +{ + CHECK("publish observed scope", scope && scope->identity == 17); + CHECK("publish observed address", link_map_addr == guest_result); + ++publish_observed_count; + publish_sequence = ++sequence; + return KZT_GUEST_LIBRARY_BINDING_PENDING; +} + +kzt_guest_library_binding_result_t kzt_guest_library_publish_loader_pair( + kzt_guest_library_bindings_t *actual_bindings, + uintptr_t link_map_addr, library_t *library, + kzt_guest_library_object_type_t object_type) +{ + (void)actual_bindings; + (void)link_map_addr; + (void)library; + (void)object_type; + ++raw_publish_pair_count; + return KZT_GUEST_LIBRARY_BINDING_ADDED; +} + +kzt_guest_library_binding_result_t kzt_guest_library_bind( + kzt_guest_library_bindings_t *actual_bindings, + const kzt_guest_library_binding_key_t *key, library_t *library, + kzt_guest_library_object_type_t object_type) +{ + CHECK("direct bind bindings", actual_bindings == &bindings); + CHECK("direct bind key", key && key->link_map_addr != 0 && + key->generation != 0 && key->namespace_id == 0); + CHECK("direct bind library", library != NULL); + CHECK("direct bind wrapped", + object_type == KZT_GUEST_LIBRARY_OBJECT_WRAPPED); + ++raw_publish_pair_count; + return KZT_GUEST_LIBRARY_BINDING_ADDED; +} + +uint64_t RunFunctionWithState(uintptr_t function, int nargs, ...) +{ + va_list args; + + ++call_count; + CHECK("guest function", function == expected_function); + va_start(args, nargs); + switch (expected_call_kind) { + case GUEST_CALL_DLOPEN: { + uintptr_t filename; + int flag; + + CHECK("guest nargs", nargs == 2); + if (expect_scoped) { + CHECK("scope installed during guest call", + thread_scope && thread_scope->identity == 17 && + thread_scope->cookie == 23); + if (guest_sets_refresh_pending) { + thread_scope->prebind_refresh_pending = 1; + } + } else { + CHECK("scope failure preserves guest call scope", + thread_scope && thread_scope->identity == 31 && + thread_scope->cookie == 37); + } + filename = va_arg(args, uintptr_t); + flag = va_arg(args, int); + CHECK("guest filename", filename == expected_filename); + CHECK("guest flag", flag == expected_flag); + break; + } + case GUEST_CALL_DLSYM: + CHECK("dlsym nargs", nargs == 2); + CHECK("dlsym handle", + va_arg(args, uintptr_t) == expected_handle); + CHECK("dlsym symbol", + va_arg(args, uintptr_t) == expected_symbol); + break; + case GUEST_CALL_DLVSYM: + CHECK("dlvsym nargs", nargs == 3); + CHECK("dlvsym handle", + va_arg(args, uintptr_t) == expected_handle); + CHECK("dlvsym symbol", + va_arg(args, uintptr_t) == expected_symbol); + CHECK("dlvsym version", + va_arg(args, uintptr_t) == expected_version); + break; + case GUEST_CALL_DLERROR: + CHECK("dlerror nargs", nargs == 0); + break; + case GUEST_CALL_DLMOPEN: + CHECK("dlmopen nargs", nargs == 3); + CHECK("dlmopen lmid", + va_arg(args, uintptr_t) == expected_lmid); + CHECK("dlmopen filename", + va_arg(args, uintptr_t) == expected_filename); + CHECK("dlmopen flag", va_arg(args, int) == expected_flag); + break; + case GUEST_CALL_DLINFO: + CHECK("dlinfo nargs", nargs == 3); + CHECK("dlinfo handle", + va_arg(args, uintptr_t) == expected_handle); + CHECK("dlinfo request", va_arg(args, int) == expected_request); + CHECK("dlinfo info", + va_arg(args, uintptr_t) == expected_info); + break; + } + va_end(args); + return guest_result; +} + +static void reset_counters(void) +{ + call_count = 0; + publish_pair_count = 0; + raw_publish_pair_count = 0; + publish_observed_count = 0; + end_count = 0; + sequence = 0; + publish_sequence = 0; + end_sequence = 0; + resolve_count = 0; + lookup_count = 0; + release_count = 0; + guest_sets_refresh_pending = 0; +} + +static void test_pair_publish_restores_scope(void) +{ + box64context_t context = { 0 }; + library_t library = { .type = LIB_WRAPPED }; + kzt_guest_library_loader_scope_t original = { + .bindings = (void *)0x1000, + .identity = 3, + .cookie = 5, + }; + kzt_guest_library_loader_scope_t call_scope = { 0 }; + kzt_guest_wrapper_source_proof_t proof = { + .lease = { + .registry = (kzt_guest_registry_t *)(uintptr_t)0x1110, + .link_map_addr = 0x4000, + .generation = 7, + .namespace_id = 0, + .active = 1, + }, + .key = { + .link_map_addr = 0x4000, + .generation = 7, + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }, + }; + uint64_t result; + + reset_counters(); + begin_result = 0; + expect_scoped = 1; + expected_call_kind = GUEST_CALL_DLOPEN; + expected_function = 0x2000; + expected_filename = 0x3000; + expected_flag = 0x102; + guest_result = 0x4000; + thread_scope = &original; + guest_sets_refresh_pending = 1; + + result = kzt_guest_library_run_dlopen_scoped( + &context, thread_scope, expected_function, + (void *)expected_filename, expected_flag, &call_scope); + CHECK("pair guest result", result == guest_result); + CHECK("pair called once", call_count == 1); + CHECK("pair original scope restored", + original.bindings == (void *)0x1000 && + original.identity == 3 && original.cookie == 5 && + original.prebind_refresh_pending == 1); + CHECK("nested refresh deferred to parent", + call_scope.prebind_refresh_pending == 0); + + kzt_guest_library_finish_dlopen_scoped( + &context, &call_scope, result, &library, &proof, 1); + CHECK("pair published once", publish_pair_count == 1); + CHECK("pair not observed", publish_observed_count == 0); + CHECK("pair scope ended", end_count == 1); + CHECK("pair published before end", + publish_sequence && publish_sequence < end_sequence); +} + +static void test_wrapped_publication_requires_source_proof(void) +{ + box64context_t context = { 0 }; + library_t library = { .type = LIB_WRAPPED }; + + reset_counters(); + kzt_guest_library_note_loader_pair( + &context, 0x4100, &library, NULL); + CHECK("proofless wrapped publication rejected", + raw_publish_pair_count == 0); +} + +static void test_observed_and_failed_calls(void) +{ + box64context_t context = { 0 }; + kzt_guest_library_loader_scope_t current = { 0 }; + kzt_guest_library_loader_scope_t call_scope = { 0 }; + uint64_t result; + + reset_counters(); + begin_result = 0; + expect_scoped = 1; + expected_call_kind = GUEST_CALL_DLOPEN; + expected_function = 0x5000; + expected_filename = 0x6000; + expected_flag = 2; + guest_result = 0x7000; + thread_scope = ¤t; + guest_sets_refresh_pending = 1; + result = kzt_guest_library_run_dlopen_scoped( + &context, thread_scope, expected_function, + (void *)expected_filename, expected_flag, &call_scope); + CHECK("outer refresh retained for completion", + call_scope.prebind_refresh_pending == 1); + kzt_guest_library_finish_dlopen_scoped( + &context, &call_scope, result, NULL, NULL, 1); + CHECK("observed published", publish_observed_count == 1); + CHECK("observed scope ended", end_count == 1); + + reset_counters(); + memset(&call_scope, 0, sizeof(call_scope)); + guest_result = 0; + result = kzt_guest_library_run_dlopen_scoped( + &context, thread_scope, expected_function, + (void *)expected_filename, expected_flag, &call_scope); + kzt_guest_library_finish_dlopen_scoped( + &context, &call_scope, result, NULL, NULL, 0); + CHECK("failed call still ran", call_count == 1); + CHECK("failed call not published", + publish_pair_count == 0 && publish_observed_count == 0); + CHECK("failed scope ended", end_count == 1); +} + +static void test_scope_failure_is_fail_open(void) +{ + box64context_t context = { 0 }; + kzt_guest_library_loader_scope_t original = { + .bindings = (void *)0x8000, + .identity = 31, + .cookie = 37, + }; + kzt_guest_library_loader_scope_t call_scope = { 0 }; + uint64_t result; + + reset_counters(); + begin_result = -1; + expect_scoped = 0; + expected_call_kind = GUEST_CALL_DLOPEN; + expected_function = 0x9000; + expected_filename = 0xa000; + expected_flag = 1; + guest_result = 0xb000; + thread_scope = &original; + + result = kzt_guest_library_run_dlopen_scoped( + &context, thread_scope, expected_function, + (void *)expected_filename, expected_flag, &call_scope); + CHECK("scope failure guest result", result == guest_result); + CHECK("scope failure still called", call_count == 1); + CHECK("scope failure preserves original", + original.bindings == (void *)0x8000 && + original.identity == 31 && original.cookie == 37); + kzt_guest_library_finish_dlopen_scoped( + &context, &call_scope, result, NULL, NULL, 1); + CHECK("scope failure not published", + publish_pair_count == 0 && publish_observed_count == 0); + CHECK("scope failure not ended", end_count == 0); +} + +static void test_guest_symbol_queries_preserve_arguments(void) +{ + reset_counters(); + expected_call_kind = GUEST_CALL_DLSYM; + expected_function = 0xc000; + expected_handle = 0xc100; + expected_symbol = 0xc200; + guest_result = 0xc300; + CHECK("dlsym result", + kzt_guest_library_run_dlsym( + expected_function, (void *)expected_handle, + (void *)expected_symbol) == guest_result); + CHECK("dlsym called once", call_count == 1); + + reset_counters(); + expected_call_kind = GUEST_CALL_DLVSYM; + expected_function = 0xd000; + expected_handle = 0xd100; + expected_symbol = 0xd200; + expected_version = 0xd300; + guest_result = 0xd400; + CHECK("dlvsym result", + kzt_guest_library_run_dlvsym( + expected_function, (void *)expected_handle, + (void *)expected_symbol, + (const char *)expected_version) == guest_result); + CHECK("dlvsym called once", call_count == 1); + + reset_counters(); + expected_call_kind = GUEST_CALL_DLERROR; + expected_function = 0xe000; + guest_result = 0xe100; + CHECK("dlerror result", + kzt_guest_library_run_dlerror(expected_function) == guest_result); + CHECK("dlerror called once", call_count == 1); +} + +static void test_guest_namespace_calls_preserve_arguments(void) +{ + reset_counters(); + expected_call_kind = GUEST_CALL_DLMOPEN; + expected_function = 0xe200; + expected_lmid = ~(uintptr_t)0; + expected_filename = 0xe300; + expected_flag = 0x2; + guest_result = 0xe400; + CHECK("dlmopen result", + kzt_guest_library_run_dlmopen( + expected_function, (void *)expected_lmid, + (void *)expected_filename, expected_flag) == guest_result); + CHECK("dlmopen called once", call_count == 1); + + reset_counters(); + expected_call_kind = GUEST_CALL_DLINFO; + expected_function = 0xe500; + expected_handle = 0xe600; + expected_request = 1; + expected_info = 0xe700; + guest_result = 0; + CHECK("dlinfo result", + kzt_guest_library_run_dlinfo( + expected_function, (void *)expected_handle, + expected_request, (void *)expected_info) == 0); + CHECK("dlinfo called once", call_count == 1); +} + +static void prepare_symbol_selection(library_t *library) +{ + resolved_guest_address = 0xf100; + resolved_match = (kzt_guest_registry_address_match_t) { + .link_map_addr = 0xf200, + .map_start = 0xf000, + .map_end = 0x10000, + .namespace_id = 0, + .generation = 7, + .namespace_id_status = KZT_GUEST_FIELD_OK, + .match_count = 1, + }; + resolve_result = 0; + lookup_result = 0; + lookup_library = library; + lookup_object_type = KZT_GUEST_LIBRARY_OBJECT_WRAPPED; + wrapper_lookup_result = 1; + wrapper_function_lookup_result = 1; + wrapper_result = 0xf300; + dynsym_type = STT_FUNC; + dynsym_status = KZT_GUEST_DYNSYM_LOOKUP_FOUND; + dynsym_runtime_address = resolved_guest_address; + dynamic_view_status = KZT_GUEST_FIELD_OK; + dynamic_view_generation = resolved_match.generation; + find_live_result = 0; + source_lease_result = 0; + lookup_guest_handle = 0xf400; + lookup_loader_identity = (kzt_guest_loader_identity_t) { + .handle = lookup_guest_handle, + .link_map_addr = resolved_match.link_map_addr, + .generation = resolved_match.generation, + .namespace_id = resolved_match.namespace_id, + .handle_generation = 3, + }; + lookup_loader_identity_result = 0; + symbol_source_result = 0; + symbol_source_acquire_count = 0; + dynamic_view_count = 0; + dynsym_lookup_count = 0; + evidence_lookup_count = 0; + evidence_store_count = 0; + evidence_valid = 0; + evidence_dynamic_revision = 0; +} + +static void prepare_trusted_source(library_t *library) +{ + prepare_symbol_selection(library); + resolved_match.path_status = KZT_GUEST_FIELD_OK; + strcpy(resolved_match.path, + "/lib/x86_64-linux-gnu/libwi963.so"); + resolved_match.soname_status = KZT_GUEST_FIELD_NOT_PARSED; + resolved_match.soname[0] = '\0'; +} + +static void test_wrapper_source_accepts_only_exact_trusted_identity(void) +{ + box64context_t context = { 0 }; + kzt_guest_wrapper_source_proof_t proof = { 0 }; + + reset_counters(); + prepare_trusted_source(NULL); + CHECK("trusted basename source accepted", + kzt_guest_library_wrapper_source_acquire( + &context, resolved_match.link_map_addr, "libwi963.so", + "libwi963.so", &proof) == 0); + CHECK("trusted source exact key", + proof.lease.active && + proof.key.link_map_addr == resolved_match.link_map_addr && + proof.key.generation == resolved_match.generation && + proof.key.namespace_id == 0 && + proof.key.namespace_kind == KZT_GUEST_LIBRARY_NAMESPACE_MAIN); + kzt_guest_library_wrapper_source_release(&proof); + CHECK("trusted source release", !proof.lease.active); + + prepare_trusted_source(NULL); + CHECK("trusted explicit source accepted", + kzt_guest_library_wrapper_source_acquire( + &context, resolved_match.link_map_addr, + "/lib/x86_64-linux-gnu/libwi963.so", "libwi963.so", + &proof) == 0); + kzt_guest_library_wrapper_source_release(&proof); + + prepare_trusted_source(NULL); + CHECK("different explicit path rejected", + kzt_guest_library_wrapper_source_acquire( + &context, resolved_match.link_map_addr, + "/usr/lib/x86_64-linux-gnu/libwi963.so", "libwi963.so", + &proof) != 0); + + prepare_trusted_source(NULL); + resolved_match.soname_status = KZT_GUEST_FIELD_OK; + strcpy(resolved_match.soname, "libspoofed.so"); + CHECK("conflicting soname rejected", + kzt_guest_library_wrapper_source_acquire( + &context, resolved_match.link_map_addr, "libwi963.so", + "libwi963.so", &proof) != 0); + + prepare_trusted_source(NULL); + resolved_match.namespace_id = 9; + CHECK("non-main source rejected", + kzt_guest_library_wrapper_source_acquire( + &context, resolved_match.link_map_addr, "libwi963.so", + "libwi963.so", &proof) != 0); + + prepare_trusted_source(NULL); + source_lease_result = -1; + CHECK("stale generation source rejected", + kzt_guest_library_wrapper_source_acquire( + &context, resolved_match.link_map_addr, "libwi963.so", + "libwi963.so", &proof) != 0); + + prepare_trusted_source(NULL); + find_live_result = -1; + CHECK("missing identity source rejected", + kzt_guest_library_wrapper_source_acquire( + &context, resolved_match.link_map_addr, "libwi963.so", + "libwi963.so", &proof) != 0); +} + +static void test_wrapper_source_rejects_untrusted_same_basename(void) +{ + box64context_t context = { 0 }; + kzt_guest_wrapper_source_proof_t proof = { 0 }; + + reset_counters(); + prepare_symbol_selection(NULL); + resolved_match.path_status = KZT_GUEST_FIELD_OK; + strcpy(resolved_match.path, "/tmp/libwi963.so"); + resolved_match.soname_status = KZT_GUEST_FIELD_OK; + strcpy(resolved_match.soname, "libwi963.so"); + CHECK("untrusted same basename rejected", + kzt_guest_library_wrapper_source_acquire( + &context, resolved_match.link_map_addr, "libwi963.so", + "libwi963.so", &proof) != 0); + CHECK("rejected proof remains inactive", !proof.lease.active); + + prepare_symbol_selection(NULL); + resolved_match.path_status = KZT_GUEST_FIELD_OK; + strcpy(resolved_match.path, "/usr/lib/custom/libwi963.so"); + resolved_match.soname_status = KZT_GUEST_FIELD_OK; + strcpy(resolved_match.soname, "libwi963.so"); + CHECK("trusted-prefix custom soname rejected", + kzt_guest_library_wrapper_source_acquire( + &context, resolved_match.link_map_addr, "libwi963.so", + "libwi963.so", &proof) != 0); + CHECK("trusted-prefix proof remains inactive", !proof.lease.active); +} + +static void test_symbol_selection_requires_exact_wrapped_owner(void) +{ + box64context_t context = { 0 }; + library_t library = { .type = LIB_WRAPPED }; + uintptr_t selected; + + reset_counters(); + prepare_symbol_selection(&library); + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + CHECK("exact wrapped bridge selected", selected == wrapper_result); + CHECK("exact wrapped uses handle source without address scan", + resolve_count == 0 && symbol_source_acquire_count == 1); + CHECK("exact wrapped lookup", lookup_count == 1); + CHECK("exact wrapped released", release_count == 1); + + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + CHECK("cached exact wrapped bridge selected", selected == wrapper_result); + CHECK("cached selector avoids guest dynsym reread", + dynsym_lookup_count == 1 && evidence_lookup_count == 2 && + evidence_store_count == 1); + { + size_t i; + + for (i = 0; i < 1000; ++i) { + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + CHECK("steady cached selector bridge", selected == wrapper_result); + } + } + CHECK("steady cached selector reads dynsym once", + dynsym_lookup_count == 1 && evidence_lookup_count == 1002 && + evidence_store_count == 1 && resolve_count == 0); + + reset_counters(); + prepare_symbol_selection(&library); + wrapper_function_lookup_result = 0; + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + CHECK("wrapper data manifest keeps guest", + selected == resolved_guest_address); + + reset_counters(); + prepare_symbol_selection(&library); + lookup_result = -1; + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + CHECK("lookup provider without exact binding keeps guest", + selected == resolved_guest_address); + + reset_counters(); + prepare_symbol_selection(&library); + option_kzt = 0; + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + option_kzt = 1; + CHECK("disabled KZT keeps guest", selected == resolved_guest_address); + CHECK("disabled KZT skips Registry", resolve_count == 0); + + reset_counters(); + prepare_symbol_selection(&library); + dynsym_type = STT_OBJECT; + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + CHECK("object symbol keeps guest", selected == resolved_guest_address); + + { + static const unsigned char unsupported_types[] = { + STT_TLS, + STT_NOTYPE, + 0xfe, +#ifdef STT_GNU_IFUNC + STT_GNU_IFUNC, +#endif + }; + size_t i; + + for (i = 0; i < sizeof(unsupported_types) / + sizeof(unsupported_types[0]); ++i) { + reset_counters(); + prepare_symbol_selection(&library); + dynsym_type = unsupported_types[i]; + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + CHECK("unsupported symbol type keeps guest", + selected == resolved_guest_address); + } + } + + reset_counters(); + prepare_symbol_selection(&library); + dynsym_runtime_address = resolved_guest_address + 8; + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + CHECK("dynsym address mismatch keeps guest", + selected == resolved_guest_address); + + reset_counters(); + prepare_symbol_selection(&library); + dynsym_status = KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN; + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + CHECK("unknown dynsym evidence keeps guest", + selected == resolved_guest_address); + + reset_counters(); + prepare_symbol_selection(&library); + dynamic_view_status = KZT_GUEST_FIELD_UNKNOWN; + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + CHECK("stale dynamic generation keeps guest", + selected == resolved_guest_address); + + reset_counters(); + prepare_symbol_selection(&library); + symbol_source_result = -1; + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + CHECK("stale source generation keeps guest", + selected == resolved_guest_address); + + reset_counters(); + prepare_symbol_selection(&library); + symbol_source_result = -1; + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + CHECK("missing exact handle source keeps guest", + selected == resolved_guest_address); + CHECK("missing exact handle source skips lookup", lookup_count == 0); + + reset_counters(); + prepare_symbol_selection(&library); + lookup_object_type = KZT_GUEST_LIBRARY_OBJECT_EMULATED; + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + CHECK("emulated owner keeps guest", + selected == resolved_guest_address); + CHECK("emulated owner released", release_count == 1); + + reset_counters(); + prepare_symbol_selection(&library); + wrapper_lookup_result = 0; + wrapper_function_lookup_result = 0; + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", NULL); + CHECK("missing wrapper keeps guest", + selected == resolved_guest_address); + CHECK("missing wrapper released", release_count == 1); + + reset_counters(); + prepare_symbol_selection(&library); + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, 0, "wi963_symbol", NULL); + CHECK("missing guest result stays missing", selected == 0); + CHECK("missing guest skips registry", resolve_count == 0); + + reset_counters(); + prepare_symbol_selection(&library); + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", "WI982_1.0"); + CHECK("versioned lookup keeps guest", + selected == resolved_guest_address); + CHECK("versioned lookup skips unversioned owner selection", + resolve_count == 0 && lookup_count == 0 && release_count == 0); +} + +int main(void) +{ + test_pair_publish_restores_scope(); + test_wrapped_publication_requires_source_proof(); + test_observed_and_failed_calls(); + test_scope_failure_is_fail_open(); + test_guest_symbol_queries_preserve_arguments(); + test_guest_namespace_calls_preserve_arguments(); + test_symbol_selection_requires_exact_wrapped_owner(); + test_wrapper_source_rejects_untrusted_same_basename(); + test_wrapper_source_accepts_only_exact_trusted_identity(); + puts("kzt-guest-library-adapter: PASS"); + return EXIT_SUCCESS; +} diff --git a/tests/unit/kzt/test_wi1007_context_resolver_source_contract.py b/tests/unit/kzt/test_wi1007_context_resolver_source_contract.py new file mode 100644 index 00000000000..44dd66d0113 --- /dev/null +++ b/tests/unit/kzt/test_wi1007_context_resolver_source_contract.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +from pathlib import Path +import sys + + +def fail(message): + raise SystemExit(f"WI-1007 context resolver contract: FAIL: {message}") + + +root = Path(sys.argv[1]) +elfloader = ( + root / "target/i386/latx/context/elfloader.c" +).read_text(encoding="utf-8") +context = ( + root / "target/i386/latx/include/box64context.h" +).read_text(encoding="utf-8") +cpu_exec = ( + root / "accel/tcg/cpu-exec.c" +).read_text(encoding="utf-8") + +start = elfloader.find("int RelocateElfPlt(") +end = elfloader.find("\n#if 0", start) +if start < 0 or end < 0: + fail("cannot locate RelocateElfPlt") +relocate = elfloader[start:end] +compact_relocate = " ".join(relocate.split()) + +if "uintptr_t kzt_plt_resolver_bridge;" not in context: + fail("resolver bridge is not owned by box64context_t") +if "my_context->kzt_plt_resolver_bridge = AddBridge(" not in relocate: + fail("RelocateElfPlt does not create the context-owned resolver bridge") +if "resolver_bridge = my_context->kzt_plt_resolver_bridge;" not in relocate: + fail("GOT injection does not use the context-owned resolver bridge") +if "kzt_guest_registry_find_by_link_map(" in relocate: + fail("resolver publication still allocates a full Registry snapshot") +if "kzt_guest_registry_find_live_object(" not in relocate: + fail("resolver publication does not use the allocation-free live query") +if ("resolver_match.namespace_id_status == KZT_GUEST_FIELD_OK" + not in compact_relocate): + fail("resolver publication does not require known namespace evidence") +if "resolver_match.namespace_id == 0" not in relocate: + fail("resolver publication does not reject non-main namespaces") +if ("guest_link_map, resolver_match.generation, 0," + not in compact_relocate): + fail("resolver publication does not use the exact live generation") + +dispatch_start = elfloader.find("int KztPltResolverDispatch(") +dispatch_end = elfloader.find("\nvoid PltResolver(", dispatch_start) +if dispatch_start < 0 or dispatch_end < 0: + fail("missing context resolver dispatcher") +dispatch = elfloader[dispatch_start:dispatch_end] +for required in ( + "bridge->CC == 0xCC", + "bridge->S == 'S'", + "bridge->C == 'C'", + "(uintptr_t)bridge->w == (uintptr_t)vFE", + "bridge->f == (uintptr_t)PltResolver", + "PltResolver();", + "cpu->eip = Pop64(cpu);", +): + if required not in dispatch: + fail(f"resolver dispatcher is missing exact check: {required}") + +tb_find_start = cpu_exec.find("static inline TranslationBlock *tb_find(") +tb_find_end = cpu_exec.find("static inline bool cpu_handle_halt(", tb_find_start) +if tb_find_start < 0 or tb_find_end < 0: + fail("cannot locate tb_find") +tb_find = cpu_exec[tb_find_start:tb_find_end] +dispatch_call = tb_find.find("KztPltResolverDispatch(") +lookup_call = tb_find.find("tb = tb_lookup(") +if dispatch_call < 0: + fail("tb_find does not dispatch the context resolver directly") +if lookup_call < 0 or dispatch_call > lookup_call: + fail("resolver dispatch must occur before resolver TB lookup/generation") +if tb_find.count("cpu_get_tb_cpu_state(") < 2: + fail("tb_find does not refresh the PC after direct resolver dispatch") + +print("KZT WI-1007 context resolver source contract: PASS") diff --git a/tests/unit/kzt/test_wi1009_lazy_slot_bridge_context_contract.py b/tests/unit/kzt/test_wi1009_lazy_slot_bridge_context_contract.py new file mode 100644 index 00000000000..143e248d875 --- /dev/null +++ b/tests/unit/kzt/test_wi1009_lazy_slot_bridge_context_contract.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +import pathlib +import sys + + +root = pathlib.Path(sys.argv[1]).resolve() +header = ( + root / "target/i386/latx/include/box64context.h" +).read_text(encoding="utf-8") +context = ( + root / "target/i386/latx/context/box64context.c" +).read_text(encoding="utf-8") +meson = ( + root / "target/i386/latx/context/meson.build" +).read_text(encoding="utf-8") + +for forbidden in ( + '#include "kzt_lazy_slot_bridge.h"', + "kzt_lazy_slot_bridge_table_t kzt_lazy_slot_bridges;", +): + if forbidden in header: + raise AssertionError( + f"box64context retains superseded lazy slot state: {forbidden}" + ) + +for forbidden in ( + "kzt_lazy_slot_bridge_table_init(", + "kzt_lazy_slot_bridge_table_destroy(", +): + if forbidden in context: + raise AssertionError( + f"context retains superseded lazy slot work: {forbidden}" + ) + +if "'kzt_lazy_slot_bridge.c'" in meson: + raise AssertionError("superseded lazy slot implementation is still built") + +print("WI-1009 lazy slot bridge removal contract: PASS") diff --git a/tests/unit/kzt/test_wi1021_dlerror_entry_source_contract.py b/tests/unit/kzt/test_wi1021_dlerror_entry_source_contract.py new file mode 100644 index 00000000000..8f9a4cd413d --- /dev/null +++ b/tests/unit/kzt/test_wi1021_dlerror_entry_source_contract.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +from pathlib import Path +import sys + + +def fail(message): + raise SystemExit(f"WI-1021 dlerror entry contract: FAIL: {message}") + + +root = Path(sys.argv[1]) +scope_header = ( + root / "target/i386/latx/include/kzt_guest_symbol_scope.h" +).read_text(encoding="utf-8") +scope_source = ( + root / "target/i386/latx/context/kzt_guest_symbol_scope.c" +).read_text(encoding="utf-8") +dl_api = ( + root / "target/i386/latx/context/kzt_guest_dl_api.c" +).read_text(encoding="utf-8") +dl_api_header = ( + root / "target/i386/latx/include/kzt_guest_dl_api.h" +).read_text(encoding="utf-8") +production = ( + root / "target/i386/latx/context/kzt_jump_slot_production.c" +).read_text(encoding="utf-8") +wrapped_dl = ( + root / "target/i386/latx/context/wrappedlibdl.c" +).read_text(encoding="utf-8") +wrapped_c = ( + root / "target/i386/latx/context/wrappedlibc.c" +).read_text(encoding="utf-8") + +if "uintptr_t selected_provider_address;" not in scope_header: + fail("scope proof does not retain the unique guest symbol address") +if "lookup_result.runtime_address" not in scope_source: + fail("scope discovery does not publish the dynsym runtime address") +if "uintptr_t kzt_guest_dl_api_load_dlerror_entry" not in dl_api_header: + fail("shared dlerror entry loader is not declared") +load_start = dl_api.find("uintptr_t kzt_guest_dl_api_load_dlerror_entry(") +load_end = dl_api.find("\n}", load_start) +if load_start < 0 or load_end < 0: + fail("missing shared dlerror entry loader") +load = dl_api[load_start:load_end] +for required in ( + "kzt_guest_dl_api_load_entries(dl)", + "entries->dlerror", + "observed_dlerror", + "__atomic_load_n(", + "__ATOMIC_RELAXED", +): + if required not in load: + fail(f"hot-path dlerror load is missing {required}") +hint_load = load.find("__atomic_load_n(") +table_load = load.find("kzt_guest_dl_api_load_entries(dl)") +if hint_load < 0 or table_load < 0 or hint_load > table_load: + fail("hot-path dlerror hint does not precede the acquire table fallback") + +publish_start = dl_api.find("int kzt_guest_dl_api_publish_dlerror_entry(") +publish_end = dl_api.find("\n}", publish_start) +if publish_start < 0 or publish_end < 0: + fail("missing atomic dlerror entry publisher") +publish = dl_api[publish_start:publish_end] +for required in ( + 'strcmp(symbol, "dlerror")', + "__atomic_compare_exchange_n(", + "observed_dlerror", + "__ATOMIC_ACQ_REL", + "__ATOMIC_ACQUIRE", +): + if required not in publish: + fail(f"publisher is missing {required}") + +cas_start = production.find( + "production_lazy_direct_cas_slot(" +) +cas_end = production.find( + "\n}", cas_start +) +if cas_start < 0 or cas_end < 0: + fail("cannot locate production lazy CAS") +cas = production[cas_start:cas_end] +publish_call = cas.find("kzt_guest_dl_api_publish_dlerror_entry(") +slot_cas = cas.find("__atomic_compare_exchange_n(") +if publish_call < 0 or slot_cas < 0 or publish_call > slot_cas: + fail("guest dlerror entry must be published before the final slot CAS") +for required in ( + "state->preemption_proof.selected_provider_address", + "state->wrapper_provider.match.custom_wrapper", +): + if required not in cas: + fail(f"production publication is missing {required}") + +for name, source in ( + ("wrappedlibdl", wrapped_dl), + ("wrappedlibc", wrapped_c), +): + helper_start = source.find( + "static uintptr_t kzt_guest_dlerror_entry_slow(" + ) + helper_end = source.find("\n}", helper_start) + if helper_start < 0 or helper_end < 0: + fail(f"cannot locate {name} dlerror slow helper") + helper = source[helper_start:helper_end] + if "kzt_guest_dl_entries_for_call(context" not in helper or \ + "entries ? entries->dlerror : 0" not in helper: + fail(f"{name} slow helper does not acquire the immutable table") + state_start = source.find("static char *kzt_guest_dlerror_slow_path(") + state_end = source.find("\n}", state_start) + if state_start < 0 or state_end < 0: + fail(f"cannot locate {name} dlerror state slow path") + state_slow = source[state_start:state_end] + for required in ( + "kzt_guest_dl_api_dlerror(error_state, guest_dlerror)", + "kzt_guest_dl_api_load_dlerror_hint(", + "kzt_guest_dlerror_entry_slow(context)", + "error_state->guest_dlerror_entry = guest_dlerror", + "Push64(cpu, guest_dlerror)", + ): + if required not in state_slow: + fail(f"{name} state slow path is missing {required}") + function_start = source.find("\nchar* my_dlerror(void)\n") + function_end = source.find("\n}", function_start) + if function_start < 0 or function_end < 0: + fail(f"cannot locate {name} my_dlerror") + function = source[function_start:function_end] + state_check = function.find("if (fast_result)") + slow = function.find("kzt_guest_dlerror_slow_path(") + fast_return = function.find("return fast_result;") + if min(state_check, slow, fast_return) < 0 or not ( + state_check < slow < fast_return): + fail(f"{name} does not isolate the clean dlerror fast path") + if "char *fast_result" not in function: + fail(f"{name} materializes the clean dlerror result") + if "kzt_guest_dl_entries_t fallback" in function: + fail(f"{name} hot dlerror wrapper retains the cold fallback frame") + if "kzt_guest_dl_api_dlerror(error_state" in function: + fail(f"{name} hot dlerror wrapper retains the state machine call") + +print("KZT WI-1021 dlerror entry source contract: PASS") diff --git a/tests/unit/kzt/test_wi1065_loader_hook_contract.py b/tests/unit/kzt/test_wi1065_loader_hook_contract.py new file mode 100644 index 00000000000..0fc69c15c98 --- /dev/null +++ b/tests/unit/kzt/test_wi1065_loader_hook_contract.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""WI-1065: the private loader hook must be a version-gated event publisher.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[3] +MYALIGN = ROOT / "target/i386/latx/context/myalign.c" +HOOK = ROOT / "target/i386/latx/context/kzt_loader_event_hook.c" + + +def function_body(source, prefix): + start = source.index(prefix) + brace = source.index("{", start) + depth = 0 + for index in range(brace, len(source)): + if source[index] == "{": + depth += 1 + elif source[index] == "}": + depth -= 1 + if depth == 0: + return source[start:index + 1] + raise AssertionError(f"unterminated function beginning {prefix!r}") + + +hook = HOOK.read_text(encoding="utf-8") +myalign = MYALIGN.read_text(encoding="utf-8") + +for required in ( + "KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID", + "kzt_loader_event_hook_read_build_id", + "kzt_loader_event_hook_install", + "kzt_loader_event_hook_publish", + "KZT_LOADER_EVENT_HOOK_FAIL_OPEN_UNKNOWN_BUILD_ID", + "KZT_LOADER_EVENT_HOOK_FAIL_OPEN_PATTERN_MISMATCH", +): + if required not in hook: + raise AssertionError(f"WI-1065 hook misses {required}") + +publish = function_body(hook, "int kzt_loader_event_hook_publish(") +for forbidden in ( + "LoadAndCheckElfHeader", + "LoadNeededLibs", + "RelocateElf", + "FindSymbol", + "kzt_per_object_got_plt_apply", + "KztPerObjectGotPltWrite", +): + if forbidden in publish: + raise AssertionError(f"event publisher performs forbidden work: {forbidden}") + +callback = function_body(myalign, "static void kzt_tb_callback(") +if "kzt_loader_event_hook_publish(" not in callback: + raise AssertionError("loader callback bypasses the event publisher") +if "kzt_tb_callback_consume(" not in callback: + raise AssertionError("loader callback does not hand published events to consumer") +for forbidden in ( + "kzt_observe_guest_object_from_callback", + "kzt_per_object_got_plt_apply", + "AddNeededLibWithLibrary", +): + if forbidden in callback: + raise AssertionError(f"event publisher callback still performs {forbidden}") + +installer = function_body(myalign, "void init_tb_callback_bridge(") +if "option_kzt = 0" in installer: + raise AssertionError("unknown loader disables KZT instead of failing open") +if "kzt_loader_event_hook_install(" not in installer: + raise AssertionError("bridge installation does not enforce version isolation") + +print("WI-1065 loader hook event contract: PASS") diff --git a/tests/unit/kzt/test_wi1066_lazy_prebind_source_contract.py b/tests/unit/kzt/test_wi1066_lazy_prebind_source_contract.py new file mode 100644 index 00000000000..82ae9793e02 --- /dev/null +++ b/tests/unit/kzt/test_wi1066_lazy_prebind_source_contract.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +"""WI-1066: lazy prebind remains a bounded, fail-open fast path.""" + +from pathlib import Path +import sys + + +def function_body(source: str, signature: str) -> str: + start = source.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + brace = source.find("{", start) + if brace < 0: + raise AssertionError(f"missing body: {signature}") + depth = 0 + for index in range(brace, len(source)): + if source[index] == "{": + depth += 1 + elif source[index] == "}": + depth -= 1 + if depth == 0: + return source[brace : index + 1] + raise AssertionError(f"unterminated function: {signature}") + + +root = Path(sys.argv[1]) +header = (root / "target/i386/latx/include/kzt_jump_slot_production.h").read_text( + encoding="utf-8" +) +production = (root / "target/i386/latx/context/kzt_jump_slot_production.c").read_text( + encoding="utf-8" +) +adapter = (root / "target/i386/latx/context/kzt_observation_adapter.c").read_text( + encoding="utf-8" +) +myalign = (root / "target/i386/latx/context/myalign.c").read_text( + encoding="utf-8" +) +guest_dl_api = (root / "target/i386/latx/context/kzt_guest_dl_api.c").read_text( + encoding="utf-8" +) + +if "int kzt_production_lazy_prebind_object(" not in header: + raise AssertionError("missing loader-consumer prebind declaration") +if "void kzt_production_lazy_prebind_refresh(" not in header: + raise AssertionError("missing epoch refresh declaration") +if "typedef int (*kzt_lazy_prebind_target_prepare_fn)" not in header: + raise AssertionError("missing exact pinned-bridge preparation callback") + +prepare = function_body( + production, "static int production_lazy_prebind_object_prepare(" +) +for required in ( + "kzt_runtime_got_plt_candidates_collect(", + "production_symbol_scope_request(", + "kzt_guest_symbol_scope_discover(", + "kzt_guest_library_access_lookup(", + "kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence(", + "kzt_lazy_prebind_scope_claim(", +): + if required not in prepare: + raise AssertionError(f"prebind preparation lacks {required}") +for forbidden in ( + "head->VerSym", + "GetSymbolVersion(", + "SymName(", + "head->DynSym", + "kzt_elfloader_write_guest_word(", + "RelocateElf", + "LoadNeededLibs", +): + if forbidden in prepare: + raise AssertionError(f"prebind preparation performs forbidden work: {forbidden}") +if "production_lazy_prebind_publish_record(" not in prepare: + raise AssertionError("prebind preparation never invokes publication") +publish = function_body( + production, "static int production_lazy_prebind_publish_record(") +for required in ( + "kzt_lazy_prebind_scope_publish_acquire(", + "kzt_lazy_prebind_scope_publish_finish(", + "production_lazy_prebind_slot_cas(", + "writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED", +): + if required not in publish: + raise AssertionError(f"prebind publication lacks {required}") + +route = function_body(production, "int kzt_production_lazy_direct_route(") +acquire = route.find("kzt_lazy_prebind_scope_acquire(") +candidate = route.find("production_collect_runtime_candidate(") +scope = route.find("kzt_guest_symbol_scope_discover(") +if acquire < 0: + raise AssertionError("lazy direct route never attempts a prebind lease") +if candidate < 0 or acquire > candidate: + raise AssertionError("prebind lease is not attempted before candidate rebuild") +if scope < 0 or acquire > scope: + raise AssertionError("prebind lease is not attempted before scope discovery") +if "kzt_lazy_prebind_scope_release(&state.prebind_lease)" not in route: + raise AssertionError("lazy direct route does not release the prebind lease") + +bridge = function_body(production, "static int production_lazy_direct_find_bridge(") +if "state->prebind_lease.active" not in bridge: + raise AssertionError("bridge path has no exact prebind fast path") + +refresh = function_body(production, "void kzt_production_lazy_prebind_refresh(") +for required in ( + "kzt_guest_registry_dump_snapshot(", + "kzt_guest_registry_source_lease_acquire(", + "production_lazy_prebind_object_prepare(", + "kzt_guest_registry_dump_free(", +): + if required not in refresh: + raise AssertionError(f"epoch refresh lacks {required}") +if "target_prepare, target_prepare_opaque" not in refresh: + raise AssertionError("scope refresh does not forward bridge preparation") + +invalidate = function_body( + production, "int kzt_production_lazy_prebind_invalidate(") +for required in ( + "kzt_lazy_prebind_scope_mutate(", + "production_lazy_prebind_revoke_closed(", +): + if required not in invalidate: + raise AssertionError(f"scope invalidation lacks {required}") +for forbidden in ("RelocateElf", "LoadNeededLibs", "kzt_elfloader_write_guest_word("): + if forbidden in invalidate: + raise AssertionError(f"scope invalidation performs forbidden work: {forbidden}") +revoke = function_body( + production, "static int production_lazy_prebind_revoke_closed(") +for required in ( + "kzt_lazy_prebind_scope_revoke_acquire(", + "kzt_lazy_prebind_scope_revoke_finish(", + "production_lazy_prebind_slot_cas(", + "writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED", + "writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH", +): + if required not in revoke: + raise AssertionError(f"scope revoke lacks {required}") + +consumer = function_body(myalign, "static void kzt_tb_callback_consume(") +refresh = consumer.find("kzt_production_lazy_prebind_refresh(") +if refresh < 0: + raise AssertionError("loader consumer does not refresh records after an epoch event") +added = consumer.rfind("observation_result == KZT_OBSERVATION_ADAPTER_ADDED", + 0, refresh) +updated = consumer.rfind("observation_result == KZT_OBSERVATION_ADAPTER_UPDATED", + 0, refresh) +if added < 0 or updated < 0: + raise AssertionError("loader refresh is not limited to committed objects") +if "kzt_tb_callback_pretranslate_target" not in consumer: + raise AssertionError("loader consumer omits exact target preparation") + +mutation = adapter.find("request->prebind_invalidate(") +per_object_flow = adapter.find("request->per_object_flow(") +if mutation < 0 or per_object_flow < 0 or mutation > per_object_flow: + raise AssertionError("loader scope epoch is not advanced before prebind work") + +for signature, guest_loader_call in ( + ("uint64_t kzt_guest_dl_api_dlopen(", + "kzt_guest_library_run_dlopen_scoped("), + ("int kzt_guest_dl_api_dlclose(", "kzt_guest_library_run_dlclose("), + ("uint64_t kzt_guest_dl_api_dlmopen(", + "kzt_guest_library_run_dlmopen("), +): + body = function_body(guest_dl_api, signature) + invalidate_at = body.find("kzt_production_lazy_prebind_invalidate(") + loader_at = body.find(guest_loader_call) + if invalidate_at < 0 or loader_at < 0 or invalidate_at > loader_at: + raise AssertionError(f"{signature} invalidates scope after guest loader entry") + +print("WI-1066 lazy prebind source contract: PASS") diff --git a/tests/unit/kzt/test_wi1066_pinned_bridge_source_contract.py b/tests/unit/kzt/test_wi1066_pinned_bridge_source_contract.py new file mode 100644 index 00000000000..10dda0c24e8 --- /dev/null +++ b/tests/unit/kzt/test_wi1066_pinned_bridge_source_contract.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""WI-1066: a published bridge gets a flush-aware, exact TB pin.""" + +from pathlib import Path +import sys + + +root = Path(sys.argv[1]) +cpu_exec = (root / "accel/tcg/cpu-exec.c").read_text(encoding="utf-8") +cpu_header = (root / "include/hw/core/cpu.h").read_text(encoding="utf-8") +myalign_header = (root / "target/i386/latx/include/myalign.h").read_text( + encoding="utf-8" +) +myalign = (root / "target/i386/latx/context/myalign.c").read_text( + encoding="utf-8" +) +production = (root / "target/i386/latx/context/kzt_jump_slot_production.c").read_text( + encoding="utf-8" +) +elfloader = (root / "target/i386/latx/context/elfloader.c").read_text( + encoding="utf-8" +) + +for required in ( + "kzt_pinned_bridge_cache", + "kzt_pinned_bridge_flush_generation", +): + if required not in cpu_header: + raise AssertionError(f"CPU state lacks {required}") +for required in ( + "void kzt_tb_pin_prebind_bridge(", + "kzt_pinned_bridge_lookup(", + "kzt_pinned_bridge_store(", + "LATX_KZT_PINNED_BRIDGE_DIAGNOSTICS", + "kzt_pinned_bridge schema=1 event=%s", +): + if required not in cpu_exec: + raise AssertionError(f"CPU execution lacks {required}") +for event in ("pin", "store", "hit", "miss"): + if f'kzt_pinned_bridge_report("{event}"' not in cpu_exec: + raise AssertionError(f"CPU execution lacks pinned bridge {event} event") + +tb_find_start = cpu_exec.find("static inline TranslationBlock *tb_find(") +if tb_find_start < 0: + raise AssertionError("CPU execution lacks tb_find") +tb_find = cpu_exec[tb_find_start:] +lookup = tb_find.find("kzt_pinned_bridge_lookup(") +generic = tb_find.find("tb_lookup(cpu, pc, cs_base, flags, cflags)") +store = tb_find.find("kzt_pinned_bridge_store(cpu, tb)") +generate = tb_find.find("tb_gen_code(cpu, pc, cs_base, flags, cflags)") +if lookup < 0 or generic < 0 or lookup > generic: + raise AssertionError("pinned bridge lookup is not ahead of generic lookup") +if store < 0 or generate < 0 or store < generate: + raise AssertionError("pinned bridge is not stored after generation") +if "cpu_tb_jmp_cache_clear" not in cpu_header or \ + "kzt_pinned_bridge_flush_generation" not in cpu_header: + raise AssertionError("TB flush does not clear the pinned bridge generation") +if "kzt_tb_pin_prebind_bridge(" not in myalign_header: + raise AssertionError("pin API is not exposed to the loader consumer") + +callback = myalign.find("static int kzt_tb_callback_pretranslate_target(") +if callback < 0: + raise AssertionError("loader consumer lacks exact target preparation") +callback_end = myalign.find("\n}\n", callback) +callback_body = myalign[callback:callback_end] +for required in ("KztPrebindTargetTbPrepare(",): + if required not in callback_body: + raise AssertionError(f"target preparation lacks {required}") +prepare = elfloader[elfloader.find("int KztPrebindTargetTbPrepare("):] +prepare = prepare[:prepare.find("\n}\n")] +for required in ("kzt_tb_pin_prebind_bridge(", "kzt_tb_find_exp(", "env->eip"): + if required not in prepare: + raise AssertionError(f"pinned target implementation lacks {required}") +for required in ( + "KZT_PREBIND_GUEST_TB_BUDGET", + "target <= reserved_va", + "tb->canlink", + "tb->lazypc", +): + if required not in prepare: + raise AssertionError(f"guest target continuation lacks {required}") +if "RunFunction" in callback_body or "RunFunction" in prepare: + raise AssertionError("target preparation executes guest code") +if "target_prepare(record->bridge_target" not in production: + raise AssertionError("publication never requests exact bridge preparation") +if "target_prepare(record->scope_proof.selected_provider_address" not in production: + raise AssertionError("publication never prepares the proven dlerror guest entry") +if "record->bridge_custom_wrapper &&" not in production or \ + 'strcmp(record->symbol, "dlerror")' not in production: + raise AssertionError("guest target preparation is not limited to custom dlerror") + +print("WI-1066 pinned bridge source contract: PASS") diff --git a/tests/unit/kzt/test_wi1081_lifecycle_performance_source_contract.py b/tests/unit/kzt/test_wi1081_lifecycle_performance_source_contract.py new file mode 100644 index 00000000000..7df770f492f --- /dev/null +++ b/tests/unit/kzt/test_wi1081_lifecycle_performance_source_contract.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""WI-1081: lifecycle refresh work is coalesced and flush-safe.""" + +from pathlib import Path +import sys + + +root = Path(sys.argv[1]) +cpu_header = (root / "include/hw/core/cpu.h").read_text(encoding="utf-8") +cpu_exec = (root / "accel/tcg/cpu-exec.c").read_text(encoding="utf-8") +elfloader = ( + root / "target/i386/latx/context/elfloader.c" +).read_text(encoding="utf-8") +scope_header = ( + root / "target/i386/latx/include/kzt_loader_callback_scope.h" +).read_text(encoding="utf-8") +adapter = ( + root / "target/i386/latx/context/kzt_guest_library_adapter.c" +).read_text(encoding="utf-8") +dl_api = ( + root / "target/i386/latx/context/kzt_guest_dl_api.c" +).read_text(encoding="utf-8") +callback = ( + root / "target/i386/latx/context/myalign.c" +).read_text(encoding="utf-8") +diagnostics = ( + root / "target/i386/latx/context/kzt_lifecycle_diagnostics.c" +).read_text(encoding="utf-8") +process_exit = (root / "linux-user/exit.c").read_text(encoding="utf-8") + +for required in ( + "kzt_prebind_prepared_guest[8]", + "flush_generation", + "cpu->kzt_prebind_prepared_guest[i].pc = 0", +): + if required not in cpu_header: + raise AssertionError(f"CPU flush-safe target cache lacks {required}") + +for required in ( + "bool kzt_tb_prebind_target_is_prepared(", + "cpu->kzt_pinned_bridge_cache[index].pc == pc", + "tb && !(tb->cflags & CF_INVALID)", + "cpu->kzt_prebind_prepared_guest[index].flush_generation ==", + "cpu->kzt_pinned_bridge_flush_generation", +): + if required not in cpu_exec: + raise AssertionError(f"prepared target validation lacks {required}") + +if "kzt_tb_prebind_target_is_prepared(cpu, target)" not in elfloader: + raise AssertionError("target preparation does not reuse valid work") +if "kzt_tb_prebind_guest_note_prepared(cpu, target)" not in elfloader: + raise AssertionError("guest continuation preparation is not cached") + +if "int prebind_refresh_pending;" not in scope_header: + raise AssertionError("loader scope cannot carry pending refresh state") +for required in ( + "call_scope->prebind_refresh_pending =", + "previous.prebind_refresh_pending = 1", + "call_scope->prebind_refresh_pending = 0", +): + if required not in adapter: + raise AssertionError(f"nested scope coalescing lacks {required}") + +if "env->kzt_guest_library_loader_scope.prebind_refresh_pending = 1" not in callback: + raise AssertionError("loader event does not defer scoped refresh") +if "if (call_scope && call_scope->prebind_refresh_pending)" not in dl_api: + raise AssertionError("outer scope completion does not consume refresh") +if "call_scope->prebind_refresh_pending = 0" not in dl_api: + raise AssertionError("completed refresh remains pending") + +for required in ( + "LATX_KZT_LIFECYCLE_DIAGNOSTICS", + "kzt_lifecycle_summary schema=1", + "target_prepare_ns=", + "scoped_prebind_refresh_ns=", +): + if required not in diagnostics: + raise AssertionError(f"lifecycle summary lacks {required}") +if "kzt_lifecycle_diagnostics_report();" not in process_exit: + raise AssertionError("lifecycle diagnostics are not reported at exit") + +print("WI-1081 lifecycle performance source contract: PASS") diff --git a/tests/unit/kzt/test_wi1082_steady_diagnostics_source_contract.py b/tests/unit/kzt/test_wi1082_steady_diagnostics_source_contract.py new file mode 100644 index 00000000000..1c79ee3c75b --- /dev/null +++ b/tests/unit/kzt/test_wi1082_steady_diagnostics_source_contract.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""WI-1082: steady dlerror diagnostics are aggregated once per process.""" + +from pathlib import Path +import sys + + +root = Path(sys.argv[1]) +cpu_exec = (root / "accel/tcg/cpu-exec.c").read_text(encoding="utf-8") +process_exit = (root / "linux-user/exit.c").read_text(encoding="utf-8") +elfloader = ( + root / "target/i386/latx/context/elfloader.c" +).read_text(encoding="utf-8") + +for required in ( + "LATX_KZT_STEADY_DIAGNOSTICS", + "kzt_steady_tb_summary schema=1", + "pin_replace=", + "pinned_hit=", + "pinned_miss=", + "collision_miss=", + "flags_miss=", + "invalid_miss=", + "bridge_translate=", + "guest_prepared=", + "guest_hit=", + "guest_retranslate=", + "flush_generation=", + "tb_flush_count=", + "tb_invalidate_count=", + "fast_cache_hash=", + "fast_cache_pc=", + "fast_cache_matches_pin=", + "pinned_restore=", +): + if required not in cpu_exec: + raise AssertionError(f"steady diagnostic lacks {required}") + +if "kzt_tb_steady_diagnostics_report(" not in process_exit: + raise AssertionError("process exit does not emit one summary") + +if "kzt_tb_steady_diagnostics_note_guest_prepare(" not in elfloader: + raise AssertionError("guest continuation preparation is not tracked") + +if 'fprintf(stderr,\n "kzt_pinned_bridge schema=1 event=%s' not in cpu_exec: + raise AssertionError("existing event diagnostics were removed") + +for required in ( + "static void kzt_pinned_bridge_restore_jmp_cache(", + "latx_fast_jmp_cache_add(cpu, hash, tb)", + "qatomic_set(&cpu->tb_jmp_cache[hash], tb)", + "kzt_pinned_bridge_restore_jmp_cache(cpu, tb)", +): + if required not in cpu_exec: + raise AssertionError(f"pinned bridge recovery lacks {required}") + +print("WI-1082 steady diagnostics source contract: PASS") diff --git a/tests/unit/kzt/test_wi1083_legacy_callback_removal_source_contract.py b/tests/unit/kzt/test_wi1083_legacy_callback_removal_source_contract.py new file mode 100644 index 00000000000..40e45ba8e6a --- /dev/null +++ b/tests/unit/kzt/test_wi1083_legacy_callback_removal_source_contract.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def fail(message: str) -> None: + raise AssertionError(message) + + +def matching_brace(text: str, start: int) -> int: + depth = 0 + quote = None + escaped = False + index = start + + while index < len(text): + char = text[index] + following = text[index + 1] if index + 1 < len(text) else "" + if quote: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == quote: + quote = None + index += 1 + continue + if char in ("'", '"'): + quote = char + index += 1 + continue + if char == "/" and following == "/": + newline = text.find("\n", index + 2) + index = len(text) if newline < 0 else newline + 1 + continue + if char == "/" and following == "*": + end = text.find("*/", index + 2) + if end < 0: + fail("unterminated C comment") + index = end + 2 + continue + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + return index + index += 1 + fail("unterminated C function") + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + fail(f"missing function: {signature}") + brace = text.find("{", start + len(signature)) + if brace < 0: + fail(f"missing function body: {signature}") + return text[brace + 1:matching_brace(text, brace)] + + +root = pathlib.Path(sys.argv[1]).resolve() +myalign = ( + root / "target/i386/latx/context/myalign.c" +).read_text(encoding="utf-8") +elfloader = ( + root / "target/i386/latx/context/elfloader.c" +).read_text(encoding="utf-8") +elfloader_header = ( + root / "target/i386/latx/include/elfloader.h" +).read_text(encoding="utf-8") + +legacy_callback = "kzt_tb_callback_" + "legacy" +if legacy_callback in myalign: + fail("legacy raw-ELF callback definition or call still exists") + +consumer = function_body(myalign, "static void kzt_tb_callback_consume(") +if "#ifdef CONFIG_LATX_KZT" not in consumer: + fail("callback consumer lost its explicit KZT compile boundary") +non_kzt = consumer.split("#else", 1)[1].split("#endif", 1)[0] +non_kzt_lines = [line.strip() for line in non_kzt.splitlines() if line.strip()] +if non_kzt_lines != ["(void)env;", "(void)event;"]: + fail(f"non-KZT callback consumer is not a clear no-op: {non_kzt_lines}") + +for required in ( + "elfheader_t* LoadAndCheckElfHeader(", + "int RelocateElf(", + "int RelocateElfPlt(", + "int LoadNeededLibs(", +): + if required not in elfloader_header: + fail(f"generic ELF loader declaration was removed: {required}") + if required not in elfloader: + fail(f"generic ELF loader implementation was removed: {required}") + +if myalign.count("LoadAndCheckElfHeader(") < 2: + fail("unrelated myalign ELF header loading calls were removed") +if "LoadNeededLibs(" not in myalign: + fail("unrelated main ELF dependency loading call was removed") + +print("WI-1083 legacy callback removal source contract: PASS") diff --git a/tests/unit/kzt/test_wi1098_thread_local_dl_state_source_contract.py b/tests/unit/kzt/test_wi1098_thread_local_dl_state_source_contract.py new file mode 100644 index 00000000000..203291a0e68 --- /dev/null +++ b/tests/unit/kzt/test_wi1098_thread_local_dl_state_source_contract.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +from pathlib import Path +import sys + + +root = Path(sys.argv[1]).resolve() +context_header = ( + root / "target/i386/latx/include/box64context.h" +).read_text(encoding="utf-8") +cpu_header = (root / "target/i386/cpu.h").read_text(encoding="utf-8") +cpu_source = (root / "target/i386/cpu.c").read_text(encoding="utf-8") +main_source = (root / "linux-user/main.c").read_text(encoding="utf-8") +box_context = ( + root / "target/i386/latx/context/box64context.c" +).read_text(encoding="utf-8") +dl_api = ( + root / "target/i386/latx/context/kzt_guest_dl_api.c" +).read_text(encoding="utf-8") +dl_init = ( + root / "target/i386/latx/context/kzt_guest_dl_init.c" +).read_text(encoding="utf-8") +latx_config = ( + root / "target/i386/latx/latx-config.c" +).read_text(encoding="utf-8") +myalign = ( + root / "target/i386/latx/context/myalign.c" +).read_text(encoding="utf-8") +wrapped_sources = [ + ( + root / "target/i386/latx/context/wrappedlibc.c" + ).read_text(encoding="utf-8"), + ( + root / "target/i386/latx/context/wrappedlibdl.c" + ).read_text(encoding="utf-8"), +] + +if "kzt_guest_dlerror_state_t kzt_guest_dlerror_state;" not in cpu_header: + raise AssertionError("x86_64 CPU state does not own dlerror state") +if "memset(&new_env->kzt_guest_dlerror_state" not in main_source: + raise AssertionError("cpu_copy inherits the parent dlerror owner") +if "new_env->kzt_guest_dlerror_state.dlerror_slow_required = 0" not in main_source: + raise AssertionError("new guest thread does not initialize dlerror clean state") +if "kzt_guest_dl_api_free_errors(&cpu->env.kzt_guest_dlerror_state)" not in cpu_source: + raise AssertionError("CPU destruction does not release thread dlerror state") +reset_start = cpu_source.find("static void x86_cpu_reset(DeviceState *dev)") +reset_end = cpu_source.find("\n#ifndef CONFIG_USER_ONLY", reset_start) +reset_body = cpu_source[reset_start:reset_end] +reset_clear = reset_body.find("memset(env, 0, offsetof(CPUX86State, end_reset_fields))") +reset_clean = reset_body.find( + "env->kzt_guest_dlerror_state.dlerror_slow_required = 0" +) +if min(reset_start, reset_end, reset_clear, reset_clean) < 0 or \ + reset_clear >= reset_clean: + raise AssertionError("CPU reset does not reinitialize dlerror clean state") +if "dl->legacy_error.dlerror_slow_required = 1" not in box_context: + raise AssertionError("legacy dlerror owner does not initialize conservatively") +if "kzt_guest_dl_api_bind_current_thread(&env->kzt_guest_dlerror_state)" \ + not in latx_config: + raise AssertionError("LATX thread initialization does not bind the dlerror fast mirror") + +if "kzt_guest_dl_entry_state_t guest_dl_entries;" not in context_header: + raise AssertionError("context does not own the guest dl entry publication state") +if "kzt_guest_dl_api_entry_state_destroy(ctx->dlprivate)" not in box_context: + raise AssertionError("context destruction does not close guest dl initialization") + +for source in wrapped_sources: + if "init_x86dlfun" in source: + raise AssertionError("libc/libdl still has a private guest dl initializer") + if "kzt_guest_dl_entries_for_call(my_context" not in source: + raise AssertionError("libc/libdl does not share the guest dl initializer") + if "#define CLEARERR guest_error_was_clean = " \ + "kzt_guest_dl_api_begin_call(error_state);" not in source or \ + source.count("\n CLEARERR") < 8: + raise AssertionError("libc/libdl does not conservatively begin every guest DL call") + if source.count("kzt_guest_dl_api_finish_success(") < 7: + raise AssertionError("libc/libdl does not preserve clean state after proven success") + start = source.find("char* my_dlerror(void)\n{") + end = source.find("\nint my_dladdr1", start) + if start < 0 or end < 0: + raise AssertionError("libc/libdl dlerror wrapper is missing") + dlerror_body = source[start:end] + state_check = dlerror_body.find("if (fast_result)") + slow = dlerror_body.find("kzt_guest_dlerror_slow_path(") + fast_return = dlerror_body.find("return fast_result;") + if min(state_check, slow, fast_return) < 0 or not ( + state_check < slow < fast_return): + raise AssertionError( + "dlerror does not isolate conservative state on the cold path" + ) + if "char *fast_result" not in dlerror_body: + raise AssertionError("dlerror clean state is not returned without materialization") + if "#define DLERROR_FAST_RESULT() " \ + "kzt_guest_dl_api_current_fast_result()" not in source or \ + "DLERROR_FAST_RESULT()" not in dlerror_body: + raise AssertionError("dlerror hot path still dereferences the CPU state owner") + if "kzt_guest_dl_entries_t fallback" in dlerror_body: + raise AssertionError("hot dlerror wrapper retains cold fallback state") + for field in ( + "->x86dlopen", + "->x86dlmopen", + "->x86dlsym", + "->x86dlclose", + "->x86dladdr", + "->x86dladdr1", + "->x86dlinfo", + "->x86dlvsym", + "->x86dlerror", + ): + if field in source: + raise AssertionError(f"wrapper still observes mutable field {field}") + +for required in ( + "kzt_guest_dl_entries_t local = { 0 };", + "kzt_guest_dl_entries_complete(&local)", + "__ATOMIC_RELEASE", + "__ATOMIC_ACQUIRE", + "pthread_equal(state->initializer, pthread_self())", + "state->teardown", + "state->slow_users", +): + if required not in dl_api: + raise AssertionError(f"guest dl publication lacks {required}") + +if dl_init.count("freeElfFromFile(&header)") != 2: + raise AssertionError("guest dl resolver does not release transient headers") +if "FindInCollection(path, &context->box64_ld_lib)" not in dl_init: + raise AssertionError("guest dl resolver retry duplicates the hwcaps path") +if "kzt_wine_init_x86(opaque, entries->dlsym)" not in dl_init: + raise AssertionError("guest dl prepare is not bound to the owning context") +for required in ( + "load_addr = h ? loadSoaddrFromMap(tmp) : 0;", + "if (!h || !load_addr)", + "void freeElfFromFile(elfheader_t **header)", + "box_free(tmp);", +): + if required not in myalign: + raise AssertionError(f"safe guest ELF retry lacks {required}") + +print("WI-1098 thread-local guest dl state source contract: PASS") diff --git a/tests/unit/kzt/test_wi1099_symbol_type_source_contract.py b/tests/unit/kzt/test_wi1099_symbol_type_source_contract.py new file mode 100644 index 00000000000..5fc4b2cd5ee --- /dev/null +++ b/tests/unit/kzt/test_wi1099_symbol_type_source_contract.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start) + depth = 0 + for index in range(opening, len(text)): + if text[index] == "{": + depth += 1 + elif text[index] == "}": + depth -= 1 + if depth == 0: + return text[opening + 1:index] + raise AssertionError(f"unterminated function: {signature}") + + +root = pathlib.Path(sys.argv[1]).resolve() +adapter = ( + root / "target/i386/latx/context/kzt_guest_library_adapter.c" +).read_text(encoding="utf-8") +library_source = ( + root / "target/i386/latx/context/library.c" +).read_text(encoding="utf-8") +selector = function_body( + adapter, "uintptr_t kzt_guest_library_select_symbol_result(" +) + +ordered = ( + "kzt_guest_registry_loader_symbol_source_acquire", + "kzt_guest_library_access_lookup", + "kzt_guest_library_symbol_evidence_lookup", + "kzt_guest_dynsym_lookup(", + "kzt_guest_library_symbol_evidence_store", + "proven_symbol_type == STT_FUNC", + "GetLibFunctionSymbolStartEnd", +) +positions = [selector.find(token) for token in ordered] +if min(positions) < 0 or positions != sorted(positions): + raise AssertionError(f"symbol evidence order is incomplete: {positions}") +cleanup = ( + selector.rfind("kzt_guest_registry_source_lease_release"), + selector.rfind("kzt_guest_library_handle_release"), +) +if (min(cleanup) < 0 or cleanup != tuple(sorted(cleanup)) or + cleanup[0] < positions[-1]): + raise AssertionError(f"symbol evidence cleanup is incomplete: {cleanup}") + +version_guard = selector.find("version && version[0]") +if version_guard < 0 or version_guard > positions[0]: + raise AssertionError("versioned lookup reaches unversioned wrapper selection") + +for forbidden in ( + "GetGlobalSymbolStartEnd", + "GetLibInternal", + "FindLibIsWrapped", + "AddNeededLib", + "dlsym(", + "dlvsym(", + "basename(", + "soname", + "kzt_guest_registry_resolve_address_pair", + "kzt_guest_registry_find_loader_identity", + "kzt_guest_registry_find_dynamic_view", +): + if forbidden in selector: + raise AssertionError(f"selector retains ambiguous bypass: {forbidden}") + +function_lookup = function_body( + library_source, "int GetLibFunctionSymbolStartEnd(") +if "wrapper_manifest_symbol_is_function" not in function_lookup: + raise AssertionError("wrapper result lacks function-only manifest proof") +for forbidden in ("getSymbolInMaps(", "getSymbolInDataMaps(", "lib->get("): + if forbidden in function_lookup: + raise AssertionError(f"function-only wrapper lookup uses data path: {forbidden}") + +print("WI-1099 symbol type source contract: PASS") diff --git a/tests/unit/kzt/test_wi1099_wrapper_provenance_source_contract.py b/tests/unit/kzt/test_wi1099_wrapper_provenance_source_contract.py new file mode 100644 index 00000000000..c7454e13463 --- /dev/null +++ b/tests/unit/kzt/test_wi1099_wrapper_provenance_source_contract.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start) + if opening < 0: + raise AssertionError(f"missing body: {signature}") + depth = 0 + for index in range(opening, len(text)): + if text[index] == "{": + depth += 1 + elif text[index] == "}": + depth -= 1 + if depth == 0: + return text[opening + 1:index] + raise AssertionError(f"unterminated function: {signature}") + + +root = pathlib.Path(sys.argv[1]).resolve() +myalign = (root / "target/i386/latx/context/myalign.c").read_text( + encoding="utf-8" +) +materialize = function_body( + myalign, "static int kzt_tb_callback_materialize_binding(" +) +library_source = (root / "target/i386/latx/context/library.c").read_text( + encoding="utf-8" +) +reload_body = function_body(library_source, "int ReloadLibrary(") + +manifest = materialize.find("FindLibIsWrapped") +proof = materialize.find("kzt_guest_library_wrapper_source_acquire") +add = materialize.find("AddNeededLibWithLibrary") +release = materialize.rfind("kzt_guest_library_wrapper_source_release") +if min(manifest, proof, add, release) < 0: + raise AssertionError("callback materialization lacks manifest/proof lifecycle") +if not (manifest < proof < add < release): + raise AssertionError("callback wrapper is materialized before exact source proof") +if "&source_proof" not in materialize[add:release]: + raise AssertionError("callback wrapped publication does not consume proof") +for forbidden in ( + "GetLibInternal", + "GetGlobalSymbolStartEnd", + "soname,", +): + if forbidden in materialize: + raise AssertionError(f"callback retains ambiguous wrapper source: {forbidden}") + +reload_proof = reload_body.find("kzt_guest_library_wrapper_source_acquire") +reload_reactivate = reload_body.find("kzt_guest_library_reactivate") +reload_pair = reload_body.find("kzt_guest_library_note_loader_pair") +reload_release = reload_body.rfind("kzt_guest_library_wrapper_source_release") +reload_active = reload_body.find("lib->active = 1", reload_pair) +if min(reload_proof, reload_reactivate, reload_pair, reload_release, + reload_active) < 0: + raise AssertionError("wrapped reload lacks exact source proof lifecycle") +if not (reload_proof < reload_reactivate < reload_pair < reload_active < + reload_release): + raise AssertionError("wrapped reload activates before proof-aware publication") +if "&source_proof" not in reload_body[reload_pair:reload_release]: + raise AssertionError("wrapped reload publication does not consume proof") + +print("WI-1099 wrapper provenance source contract: PASS") diff --git a/tests/unit/kzt/test_wi1209_dlerror_bridge_fast_path_source_contract.py b/tests/unit/kzt/test_wi1209_dlerror_bridge_fast_path_source_contract.py new file mode 100644 index 00000000000..40a91ec2749 --- /dev/null +++ b/tests/unit/kzt/test_wi1209_dlerror_bridge_fast_path_source_contract.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +"""WI-1209: clean dlerror bridge bypasses layout-sensitive host wrapper work.""" + +from pathlib import Path +import sys + + +root = Path(sys.argv[1]).resolve() +source = ( + root / "target/i386/latx/translator/tr-misc.c" +).read_text(encoding="utf-8") + +start = source.find("static void do_translate_dlerror_brick_tb(") +end = source.find("\n}\n", start) +if start < 0 or end < 0: + raise AssertionError("translator lacks the dlerror bridge fast path") +body = source[start:end] + +for required in ( + "kzt_native_to_wrapper();", + "kzt_guest_dlerror_state.dlerror_fast_result", + "la_ld_d(", + "la_bne(", + "lsenv_offset_of_gpr(lsenv, R_EAX)", + "la_st_d(", + "wrapper_gpr_trans((ADDR)bridge->f);", + "li_d(ra_ir2_opnd, (ADDR)bridge->w);", + "la_jirl(ra_ir2_opnd, ra_ir2_opnd, 0);", + "kzt_wrapper_to_native();", + "gen_set_next_tb_code(&esp_ir2_opnd);", + "tr_generate_exit_tb_for_bridge();", +): + if required not in body: + raise AssertionError(f"dlerror bridge fast path lacks {required}") + +fast_load = body.find("kzt_guest_dlerror_state.dlerror_fast_result") +fast_branch = body.find("la_bne(", fast_load) +guest_result = body.find("lsenv_offset_of_gpr(lsenv, R_EAX)", fast_branch) +slow_wrapper = body.find("wrapper_gpr_trans((ADDR)bridge->f);", guest_result) +if min(fast_load, fast_branch, guest_result, slow_wrapper) < 0 or not ( + fast_load < fast_branch < guest_result < slow_wrapper): + raise AssertionError("clean result is not separated from the exact slow wrapper") + +dispatch = source.find("static void do_translate_brick_tb(") +generic = source.find("kzt_native_to_wrapper();", dispatch) +special = source.find("bridge->f == (uintptr_t)my_dlerror", dispatch) +call = source.find("do_translate_dlerror_brick_tb(bridge);", special) +if min(dispatch, generic, special, call) < 0 or not ( + dispatch < special < call < generic): + raise AssertionError("dlerror bridge is not selected before the generic bridge") + +for forbidden in ("MAP_FIXED", "mmap", "address_bits", ">> 16", "& 3"): + if forbidden in body: + raise AssertionError(f"dlerror bridge fast path selects an address via {forbidden}") + +print("WI-1209 dlerror bridge fast path source contract: PASS") diff --git a/tests/unit/kzt/test_wi254_loader_contract.py b/tests/unit/kzt/test_wi254_loader_contract.py index 61567a64da4..8636f85d783 100644 --- a/tests/unit/kzt/test_wi254_loader_contract.py +++ b/tests/unit/kzt/test_wi254_loader_contract.py @@ -35,6 +35,7 @@ def function_body(text, signature): elfloader = read(context / "elfloader.c") wrapped_dl = read(context / "wrappedlibdl.c") wrapped_libc = read(context / "wrappedlibc.c") +guest_dl_api = read(context / "kzt_guest_dl_api.c") # No process-lifetime registry/gate/singleton is permitted. for forbidden in ("call_gate", "gate_next", "call_gate_bindings"): @@ -43,47 +44,47 @@ def function_body(text, signature): assert "KztGuestLibraryLookupForContext" in box_context assert "kzt_guest_library_access_begin_teardown" in box_context -# Both real wrapped dlopen implementations consume the exact AddNeeded result -# directly and never perform a second name lookup for the selected instance. +# Both exports use one guest-first implementation. Wrapper attachment consumes +# the exact AddNeeded result only after guest success and never replaces the +# guest handle. for name, text in (("wrappedlibdl", wrapped_dl), ("wrappedlibc", wrapped_libc)): - body = function_body(text, "void* my_dlopen(void *filename, int flag){") - assert "AddNeededLibWithLibrary" in body, name - assert "lib = GetLibInternal(rfilename)" not in body, name - assert "&lib) || !lib" in body, name - assert "dlopen_recycle_transaction(" in body, name - assert "recycle_prepare" in body, name - assert "recycle_rollback" in body, name - reset = function_body( - text, - "static void recycle_reset_loader_state(dlopen_recycle_context_t *recycle)", - ) - for cleared in ( - "recycle->library->x86linkmap = NULL", - "recycle->header->delta = 0", - "lm->l_addr = 0", - "LatxResetElf(recycle->header)", - ): - assert cleared in reset, (name, cleared) - call = function_body(text, "static int callx86dlopen(") - assert "h->delta = ret->l_addr" in call, name - assert "lm->l_addr = ret->l_addr" in call, name - assert "h->latx_hasfix = 1" in call, name - fixed = call.index("h->latx_hasfix = 1") - finish = call.index("finish_guest_dlopen_scoped", fixed) - assert fixed < finish, name - -# Both redlopen success branches publish the exact returned link_map/library -# pair before the follow-up x86 dlsym. + body = function_body( + text, "void* my_dlopen(void *filename, int flag){") + assert "kzt_guest_dl_api_dlopen" in body, name + assert "AddNeededLibWithLibrary" not in body, name + assert "dlopen_recycle_transaction" not in body, name + +shared_open = function_body( + guest_dl_api, "uint64_t kzt_guest_dl_api_dlopen(") +guest_open = shared_open.index("kzt_guest_library_run_dlopen_scoped") +attach = shared_open.index("AddNeededLibWithLibrary", guest_open) +finish = shared_open.index( + "kzt_guest_dl_api_finish_dlopen_scoped", attach) +assert guest_open < attach < finish +assert "&library" in shared_open +assert "return guest_handle" in shared_open +assert "GetLibInternal" not in shared_open +assert "kzt_guest_library_loader_scope_begin" not in shared_open +assert "kzt_guest_library_loader_scope_end" not in shared_open +assert "RunFunctionWithState" not in shared_open + +# Loader publication remains shared, while symbol lookup no longer performs a +# hidden reopen. Both exports use the shared guest-authoritative lookup. for name, text in (("wrappedlibdl", wrapped_dl), ("wrappedlibc", wrapped_libc)): - finish_body = function_body(text, "static void finish_guest_dlopen_scoped(") - assert "kzt_guest_library_publish_loader_pair_scoped" in finish_body, name - assert "kzt_guest_library_publish_loader_observed_scoped" in finish_body, name - assert finish_body.index("publish_loader_pair_scoped") < \ - finish_body.index("loader_scope_end"), name - redlopen = text.index("//redlopen") - publish = text.index("finish_guest_dlopen_scoped(", redlopen) - dlsym = text.index("x86dlsym", publish) - assert redlopen < publish < dlsym, name + dlsym_body = function_body( + text, "void* my_dlsym(void *handle, void *symbol)\n{") + assert "kzt_guest_dl_api_dlsym" in dlsym_body, name + assert "run_guest_dlopen_scoped" not in dlsym_body, name + assert "AddNeededLib" not in dlsym_body, name + +shared_dlsym = function_body( + guest_dl_api, + "kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlsym(", +) +guest_lookup = shared_dlsym.index("kzt_guest_library_run_dlsym") +select = shared_dlsym.index( + "kzt_guest_library_select_symbol_result", guest_lookup) +assert guest_lookup < select # Reload cannot reactivate or publish until all failure-returning reload work # has completed. @@ -93,6 +94,27 @@ def function_body(text, signature): assert reload_body.index("RelocateElfPlt") < reactivate assert reload_body.index("kzt_guest_library_note_loader_pair", reactivate) > reactivate +# A production library is tracked before it can become visible. Tracking is +# metadata only: failure preserves the established loader path. Inactivation +# and final free use only the exact x86 link_map hint, closing and then removing +# any binding before the library allocation can disappear. +native_init = function_body(library, "static void initNativeLib(") +assert "(void)kzt_guest_library_track(" in native_init +assert "KztGuestLibraryBindingsForContext(context), lib" in native_init +inactive_body = function_body(library, "void InactiveLibrary(library_t* lib)") +assert "kzt_guest_library_inactivate(" in inactive_body +assert "KztGuestLibraryBindingsForContext(lib->context)" in inactive_body +assert "KztGuestRegistryForContext(lib->context)" in inactive_body +assert "(uintptr_t)lib->x86linkmap" in inactive_body +free_body = function_body(library, "void Free1Library(library_t **lib)") +assert "kzt_guest_library_unbind(" in free_body +assert "KztGuestLibraryBindingsForContext((*lib)->context)" in free_body +assert "KztGuestRegistryForContext((*lib)->context)" in free_body +assert "(uintptr_t)(*lib)->x86linkmap" in free_body +assert free_body.index("kzt_guest_library_unbind(") < free_body.index( + "box_free(*lib)" +) + # The real PLT loader uses the tested status seam and returns before resolver # injection when the underlying RELA worker fails. plt_body = function_body( diff --git a/tests/unit/kzt/test_wi256_lazy_production_bridge.c b/tests/unit/kzt/test_wi256_lazy_production_bridge.c index 165c8c52fc5..4428b6af819 100644 --- a/tests/unit/kzt/test_wi256_lazy_production_bridge.c +++ b/tests/unit/kzt/test_wi256_lazy_production_bridge.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -12,7 +13,9 @@ #include "target/i386/latx/include/bridge_private.h" #include "target/i386/latx/include/elfloader_private.h" #include "target/i386/latx/include/khash.h" +#include "target/i386/latx/include/kzt_guest_dl_api.h" #include "target/i386/latx/include/kzt_guest_registry.h" +#include "target/i386/latx/include/kzt_guest_symbol_scope.h" #include "target/i386/latx/include/kzt_jump_slot_production.h" #include "target/i386/latx/include/kzt_rela_runtime_bridge.h" #include "target/i386/latx/include/librarian_private.h" @@ -57,6 +60,43 @@ typedef struct fixture_version_need { Elf64_Vernaux aux; } fixture_version_need_t; +typedef struct fixture_scope_link_map { + uint64_t l_addr; + uint64_t l_name; + uint64_t l_ld; + uint64_t l_next; + uint64_t l_prev; + uint64_t l_real; + uint64_t l_ns; + unsigned char private_before_audit_flags[ + 0x350 - 7 * sizeof(uint64_t)]; + uint64_t audit_flags; + unsigned char private_before_reloc_result[0x378 - 0x358]; + uint64_t reloc_result; + unsigned char private_before_scope_max[0x3c0 - 0x380]; + uint64_t l_scope_max; + uint64_t l_scope; + uint64_t l_local_scope[2]; +} fixture_scope_link_map_t; + +typedef struct fixture_scope_elem { + uint64_t r_list; + uint32_t r_nlist; + uint32_t padding; +} fixture_scope_elem_t; + +typedef struct fixture_sysv_hash { + uint32_t nbucket; + uint32_t nchain; + uint32_t buckets[1]; + uint32_t chains[2]; +} fixture_sysv_hash_t; + +typedef struct fixture_version_def { + Elf64_Verdef definition; + Elf64_Verdaux auxiliary; +} fixture_version_def_t; + typedef struct fixture { box64context_t context; library_t provider; @@ -70,6 +110,21 @@ typedef struct fixture { Elf64_Sym sym; Elf64_Half versym; fixture_version_need_t version_need; + fixture_scope_link_map_t source_map; + fixture_scope_link_map_t provider_map; + Elf64_Dyn source_scope_dynamic[6]; + Elf64_Dyn provider_scope_dynamic[9]; + Elf64_Sym source_scope_symbols[1]; + Elf64_Sym provider_scope_symbols[2]; + char source_scope_strings[1]; + char provider_scope_strings[32]; + fixture_sysv_hash_t source_scope_hash; + fixture_sysv_hash_t provider_scope_hash; + Elf64_Half provider_scope_versym[2]; + fixture_version_def_t provider_scope_verdef; + fixture_scope_elem_t source_scope_elem; + uintptr_t source_scope_array[2]; + uintptr_t source_scope_maps[2]; uintptr_t slot; kzt_lazy_binding_pending_t pending; kzt_guest_object_observation_t source_observation; @@ -81,6 +136,10 @@ typedef enum hook_mode { HOOK_RECYCLE_BEFORE_ACQUIRE, HOOK_RETIRE_AFTER_CAS, HOOK_RETIRE_AFTER_CAS_ROLLBACK, + HOOK_VIEW_CHANGE_BEFORE_DECISION_ACQUIRE, + HOOK_OWNER_AMBIGUOUS_BEFORE_DECISION_ACQUIRE, + HOOK_SLOT_CONFLICT_BEFORE_VALIDATE, + HOOK_SLOT_CONFLICT_BEFORE_FINAL_LOAD, } hook_mode_t; typedef struct retire_sync { @@ -111,10 +170,107 @@ static int permission_begin_calls; static int permission_end_calls; static int fail_permission_begin; static int fail_permission_end; +static int fail_permission_end_once; +static int full_enrich_calls; +static int wrapper_only_enrich_calls; +static int decision_lease_active; +static int decision_lease_acquires; +static int decision_lease_releases; +static int decision_lease_held_at_validate; +static int decision_lease_held_at_permission_begin; +static int decision_lease_held_at_permission_end; +static int decision_lease_held_at_cas; +static int generation_validate_calls; +static int runtime_full_lifetime_validation_calls; +static pthread_mutex_t mapping_transaction_lock = PTHREAD_MUTEX_INITIALIZER; +static int mapping_lock_calls; +static int mapping_unlock_calls; +static int mapping_lock_active; + +int kzt_jump_slot_production_test_read_guest_memory( + uintptr_t address, void *dst, size_t size); + +static int fixture_guest_range_read( + uintptr_t address, uintptr_t guest_base, const void *host_base, + size_t host_size, void *dst, size_t size) +{ + uintptr_t offset; + + if (address < guest_base) { + return -1; + } + offset = address - guest_base; + if (offset > host_size || size > host_size - offset) { + return -1; + } + memcpy(dst, (const unsigned char *)host_base + offset, size); + return 0; +} + +static int fixture_scope_read_guest_memory( + uintptr_t address, void *dst, size_t size, void *opaque) +{ + (void)opaque; + return kzt_jump_slot_production_test_read_guest_memory( + address, dst, size); +} + +int kzt_jump_slot_production_test_read_guest_memory( + uintptr_t address, void *dst, size_t size) +{ + if (!hook_fixture || !address || !dst || !size) { + return -1; + } + if (fixture_guest_range_read( + address, SOURCE_LINK_MAP, &hook_fixture->source_map, + sizeof(hook_fixture->source_map), dst, size) == 0 || + fixture_guest_range_read( + address, PROVIDER_LINK_MAP, &hook_fixture->provider_map, + sizeof(hook_fixture->provider_map), dst, size) == 0) { + return 0; + } + if (address < 0x10000) { + return -1; + } + memcpy(dst, (const void *)address, size); + return 0; +} + +typedef struct mapping_mutator_sync { + pthread_mutex_t lock; + pthread_cond_t cond; + pthread_t thread; + int enabled; + int go; + int first_try_complete; + int first_try_error; + int blocked_at_cas; + int route_finished; + int released_after_route; + int mutated; + int mutated_before_permission_end; +} mapping_mutator_sync_t; + +static mapping_mutator_sync_t mapping_mutator; + +typedef struct post_acquire_mutator_sync { + pthread_mutex_t lock; + pthread_cond_t cond; + fixture_t *fixture; + pthread_t thread; + int enabled; + int wait_registered; + int worker_done; + int worker_result; + int done_before_release; +} post_acquire_mutator_sync_t; + +static post_acquire_mutator_sync_t post_acquire_mutator; uintptr_t CheckBridged(bridge_t *bridge, void *fnc); uintptr_t AddCheckBridge(bridge_t *bridge, wrapper_t wrapper, void *fnc, int stack_bytes, const char *name); +int BridgeForkProtectionAvailable(void); const char *SymName(elfheader_t *head, Elf64_Sym *sym); void kzt_jump_slot_production_test_before_source_lease_acquire(void); void kzt_jump_slot_production_test_before_source_memory_access(void); @@ -122,16 +278,88 @@ void kzt_jump_slot_production_test_before_slot_load(void); void kzt_jump_slot_production_test_after_slot_load(uintptr_t *value); void kzt_jump_slot_production_test_after_slot_cas(int exchanged); void kzt_jump_slot_production_test_shadow_run(void); +void kzt_jump_slot_production_test_before_generation_validate(void); +void kzt_jump_slot_production_test_before_patch_decision_lease_acquire(void); +void kzt_jump_slot_production_test_after_patch_decision_lease_acquire(void); +void kzt_jump_slot_production_test_before_patch_decision_lease_release(void); +void kzt_jump_slot_production_test_full_enrich(void); +void kzt_jump_slot_production_test_wrapper_only_enrich(void); int kzt_jump_slot_production_test_begin_slot_write( uintptr_t slot_addr, kzt_patch_spike_permission_lease_t *lease); int kzt_jump_slot_production_test_end_slot_write( kzt_patch_spike_permission_lease_t *lease); +void kzt_jump_slot_production_test_mapping_lock(void); +void kzt_jump_slot_production_test_mapping_unlock(void); +void kzt_rela_runtime_bridge_test_full_lifetime_validation(void); + +int kzt_guest_dl_api_publish_dlerror_entry( + dlprivate_t *dl, const char *symbol, uintptr_t guest_entry, + int custom_wrapper) +{ + (void)dl; + (void)symbol; + (void)guest_entry; + (void)custom_wrapper; + return 0; +} + +void kzt_rela_runtime_bridge_test_full_lifetime_validation(void) +{ + ++runtime_full_lifetime_validation_calls; +} + +int BridgeForkProtectionAvailable(void) +{ + return 1; +} static void fixture_iFp(uintptr_t fnc) { (void)fnc; } +static void post_acquire_registry_wait(void *opaque) +{ + post_acquire_mutator_sync_t *sync = opaque; + + pthread_mutex_lock(&sync->lock); + sync->wait_registered = 1; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); +} + +static void *post_acquire_mutator_main(void *opaque) +{ + post_acquire_mutator_sync_t *sync = opaque; + kzt_guest_dynamic_view_t changed_view = sync->fixture->dynamic_view; + + changed_view.dynamic_addr += 0x3000; + sync->worker_result = kzt_guest_registry_commit_dynamic_view( + sync->fixture->context.kzt_guest_registry_context.registry, + SOURCE_LINK_MAP, sync->fixture->pending.source_generation, + &changed_view); + pthread_mutex_lock(&sync->lock); + sync->worker_done = 1; + pthread_cond_broadcast(&sync->cond); + pthread_mutex_unlock(&sync->lock); + return NULL; +} + +static int post_acquire_mutator_init(fixture_t *fixture) +{ + memset(&post_acquire_mutator, 0, sizeof(post_acquire_mutator)); + post_acquire_mutator.fixture = fixture; + return pthread_mutex_init(&post_acquire_mutator.lock, NULL) || + pthread_cond_init(&post_acquire_mutator.cond, NULL) ? -1 : 0; +} + +static void post_acquire_mutator_destroy(void) +{ + pthread_cond_destroy(&post_acquire_mutator.cond); + pthread_mutex_destroy(&post_acquire_mutator.lock); + memset(&post_acquire_mutator, 0, sizeof(post_acquire_mutator)); +} + uintptr_t CheckBridged(bridge_t *bridge, void *fnc) { fixture_bridge_map_t *map = (fixture_bridge_map_t *)bridge; @@ -177,6 +405,18 @@ kzt_guest_registry_t *KztGuestRegistryForContext(box64context_t *context) return context ? context->kzt_guest_registry_context.registry : NULL; } +kzt_guest_library_bindings_t *KztGuestLibraryBindingsForContext( + box64context_t *context) +{ + return context ? context->kzt_guest_library_access.bindings : NULL; +} + +kzt_lazy_prebind_scope_t *KztLazyPrebindScopeForContext( + box64context_t *context) +{ + return context ? context->kzt_lazy_prebind_scope : NULL; +} + int KztGuestLibraryLookupForContext( box64context_t *context, const kzt_guest_library_binding_key_t *key, kzt_guest_library_handle_t *handle) @@ -295,6 +535,94 @@ static void reset_guard(fixture_t *fixture, int enabled) &config); } +static void fixture_init_guest_scope(fixture_t *fixture) +{ + static const char provider_strings[] = + "\0" FIXTURE_SYMBOL "\0" FIXTURE_VERSION "\0"; + + fixture->source_map.l_ld = (uintptr_t)fixture->source_scope_dynamic; + fixture->source_map.l_next = PROVIDER_LINK_MAP; + fixture->source_map.l_real = SOURCE_LINK_MAP; + fixture->source_map.l_scope_max = 2; + fixture->source_map.l_scope = + (uintptr_t)fixture->source_scope_array; + fixture->source_map.l_local_scope[0] = + (uintptr_t)&fixture->source_scope_elem; + fixture->provider_map.l_ld = + (uintptr_t)fixture->provider_scope_dynamic; + fixture->provider_map.l_prev = SOURCE_LINK_MAP; + fixture->provider_map.l_real = PROVIDER_LINK_MAP; + + fixture->source_scope_elem.r_list = + (uintptr_t)fixture->source_scope_maps; + fixture->source_scope_elem.r_nlist = 2; + fixture->source_scope_array[0] = + (uintptr_t)&fixture->source_scope_elem; + fixture->source_scope_maps[0] = SOURCE_LINK_MAP; + fixture->source_scope_maps[1] = PROVIDER_LINK_MAP; + + fixture->source_scope_dynamic[0].d_tag = DT_SYMTAB; + fixture->source_scope_dynamic[0].d_un.d_ptr = + (uintptr_t)fixture->source_scope_symbols; + fixture->source_scope_dynamic[1].d_tag = DT_STRTAB; + fixture->source_scope_dynamic[1].d_un.d_ptr = + (uintptr_t)fixture->source_scope_strings; + fixture->source_scope_dynamic[2].d_tag = DT_SYMENT; + fixture->source_scope_dynamic[2].d_un.d_val = sizeof(Elf64_Sym); + fixture->source_scope_dynamic[3].d_tag = DT_STRSZ; + fixture->source_scope_dynamic[3].d_un.d_val = + sizeof(fixture->source_scope_strings); + fixture->source_scope_dynamic[4].d_tag = DT_HASH; + fixture->source_scope_dynamic[4].d_un.d_ptr = + (uintptr_t)&fixture->source_scope_hash; + fixture->source_scope_dynamic[5].d_tag = DT_NULL; + fixture->source_scope_hash.nbucket = 1; + fixture->source_scope_hash.nchain = 1; + + memcpy(fixture->provider_scope_strings, provider_strings, + sizeof(provider_strings)); + fixture->provider_scope_symbols[1].st_name = 1; + fixture->provider_scope_symbols[1].st_info = + ELF64_ST_INFO(STB_GLOBAL, STT_FUNC); + fixture->provider_scope_symbols[1].st_other = STV_DEFAULT; + fixture->provider_scope_symbols[1].st_shndx = SHN_ABS; + fixture->provider_scope_symbols[1].st_value = GUEST_TARGET; + fixture->provider_scope_hash.nbucket = 1; + fixture->provider_scope_hash.nchain = 2; + fixture->provider_scope_hash.buckets[0] = 1; + fixture->provider_scope_versym[1] = 2; + fixture->provider_scope_verdef.definition.vd_version = 1; + fixture->provider_scope_verdef.definition.vd_ndx = 2; + fixture->provider_scope_verdef.definition.vd_cnt = 1; + fixture->provider_scope_verdef.definition.vd_aux = sizeof(Elf64_Verdef); + fixture->provider_scope_verdef.auxiliary.vda_name = + sizeof("\0" FIXTURE_SYMBOL); + + fixture->provider_scope_dynamic[0].d_tag = DT_SYMTAB; + fixture->provider_scope_dynamic[0].d_un.d_ptr = + (uintptr_t)fixture->provider_scope_symbols; + fixture->provider_scope_dynamic[1].d_tag = DT_STRTAB; + fixture->provider_scope_dynamic[1].d_un.d_ptr = + (uintptr_t)fixture->provider_scope_strings; + fixture->provider_scope_dynamic[2].d_tag = DT_SYMENT; + fixture->provider_scope_dynamic[2].d_un.d_val = sizeof(Elf64_Sym); + fixture->provider_scope_dynamic[3].d_tag = DT_STRSZ; + fixture->provider_scope_dynamic[3].d_un.d_val = + sizeof(provider_strings); + fixture->provider_scope_dynamic[4].d_tag = DT_HASH; + fixture->provider_scope_dynamic[4].d_un.d_ptr = + (uintptr_t)&fixture->provider_scope_hash; + fixture->provider_scope_dynamic[5].d_tag = DT_VERSYM; + fixture->provider_scope_dynamic[5].d_un.d_ptr = + (uintptr_t)fixture->provider_scope_versym; + fixture->provider_scope_dynamic[6].d_tag = DT_VERDEF; + fixture->provider_scope_dynamic[6].d_un.d_ptr = + (uintptr_t)&fixture->provider_scope_verdef; + fixture->provider_scope_dynamic[7].d_tag = DT_VERDEFNUM; + fixture->provider_scope_dynamic[7].d_un.d_val = 1; + fixture->provider_scope_dynamic[8].d_tag = DT_NULL; +} + static int fixture_init(fixture_t *fixture) { static char source_name[] = "librequester.so"; @@ -307,6 +635,7 @@ static int fixture_init(fixture_t *fixture) int initial_failures = failures; memset(fixture, 0, sizeof(*fixture)); + fixture_init_guest_scope(fixture); fixture->versym = 2; fixture->slot = GUEST_TARGET; fixture->context.kzt_guest_registry_context.registry = @@ -315,8 +644,15 @@ static int fixture_init(fixture_t *fixture) fixture->context.kzt_guest_registry_context.registry != NULL); CHECK("binding.init", kzt_guest_library_access_init( &fixture->context.kzt_guest_library_access) == 0); + fixture->context.kzt_lazy_prebind_scope = kzt_lazy_prebind_scope_init(); + fixture->context.kzt_guest_scope_layout = + KZT_GUEST_SCOPE_LAYOUT_GLIBC_2_39_C591A5DF; + fixture->context.kzt_guest_registry_context.main_namespace_head = + SOURCE_LINK_MAP; + CHECK("prebind.init", fixture->context.kzt_lazy_prebind_scope != NULL); if (!fixture->context.kzt_guest_registry_context.registry || - !fixture->context.kzt_guest_library_access.initialized) { + !fixture->context.kzt_guest_library_access.initialized || + !fixture->context.kzt_lazy_prebind_scope) { return -1; } reset_guard(fixture, 1); @@ -388,6 +724,8 @@ static int fixture_init(fixture_t *fixture) (uintptr_t)&fixture->version_need); fixture->dynamic_view.verneednum = scalar_field(1); fixture->sym.st_name = 1; + fixture->sym.st_info = ELF64_ST_INFO(STB_GLOBAL, STT_FUNC); + fixture->sym.st_other = STV_DEFAULT; fixture->version_need.need.vn_version = 1; fixture->version_need.need.vn_cnt = 1; fixture->version_need.need.vn_aux = sizeof(Elf64_Verneed); @@ -426,6 +764,7 @@ static int fixture_init(fixture_t *fixture) fixture->head.pltsz = sizeof(fixture->rela); fixture->head.pltent = sizeof(fixture->rela); fixture->head.DynSym = &fixture->sym; + fixture->head.numDynSym = 1; fixture->head.VerSym = &fixture->versym; fixture->head.self_link_map = SOURCE_LINK_MAP; fixture->elfs[0] = &fixture->head; @@ -454,6 +793,7 @@ static void fixture_destroy(fixture_t *fixture) dlclose(fixture->provider.priv.w.lib); } kzt_guest_library_access_destroy(&fixture->context.kzt_guest_library_access); + kzt_lazy_prebind_scope_destroy(&fixture->context.kzt_lazy_prebind_scope); kzt_guest_registry_destroy( &fixture->context.kzt_guest_registry_context.registry); } @@ -465,6 +805,76 @@ static int fixture_route(fixture_t *fixture, &fixture->context, &fixture->pending, GUEST_TARGET, result); } +static int fixture_lazy_direct_route( + fixture_t *fixture, kzt_lazy_direct_route_result_t *result) +{ + return kzt_production_lazy_direct_route( + &fixture->context, &fixture->head, 0, &fixture->rela, + (uint64_t *)&fixture->slot, fixture->slot, 0, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_VERSIONED, FIXTURE_VERSION, result); +} + +static void fixture_set_unresolved_slot(fixture_t *fixture) +{ + fixture->slot = fixture->pending.unresolved_stub; + fixture->head.plt = fixture->slot - 8; + fixture->head.plt_end = fixture->slot + 8; +} + +static int fixture_claim_prebind_record(fixture_t *fixture) +{ + kzt_guest_registry_address_match_t provider_match; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = fixture_scope_read_guest_memory, + }; + kzt_guest_symbol_scope_request_t request = { + .source = { + .link_map_addr = SOURCE_LINK_MAP, + .generation = fixture->pending.source_generation, + .namespace_id = 0, + .namespace_head = SOURCE_LINK_MAP, + .layout = KZT_GUEST_SCOPE_LAYOUT_GLIBC_2_39_C591A5DF, + }, + .symbol = FIXTURE_SYMBOL, + .version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .version = FIXTURE_VERSION, + .reference_binding = STB_GLOBAL, + .reference_type = STT_FUNC, + .reference_visibility = STV_DEFAULT, + }; + kzt_lazy_prebind_record_t record = { 0 }; + + if (kzt_guest_registry_find_live_object( + fixture->context.kzt_guest_registry_context.registry, + PROVIDER_LINK_MAP, &provider_match) != 0 || + kzt_guest_symbol_scope_discover( + &request, &reader_ops, &record.scope_proof) != + KZT_GUEST_SYMBOL_SCOPE_SAFE) { + return -1; + } + record.source = (kzt_lazy_prebind_identity_t) { + .link_map_addr = SOURCE_LINK_MAP, + .generation = fixture->pending.source_generation, + .namespace_id = 0, + }; + record.provider = (kzt_lazy_prebind_identity_t) { + .link_map_addr = PROVIDER_LINK_MAP, + .generation = provider_match.generation, + .namespace_id = 0, + }; + record.slot_addr = (uintptr_t)&fixture->slot; + record.expected_slot = fixture->slot; + record.relocation_index = 0; + record.bridge_target = fixture_native_bridge; + record.bridge_generation = provider_match.generation; + record.version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + strcpy(record.symbol, FIXTURE_SYMBOL); + strcpy(record.version, FIXTURE_VERSION); + return kzt_lazy_prebind_scope_claim( + fixture->context.kzt_lazy_prebind_scope, &record) == + KZT_LAZY_PREBIND_CLAIM_CREATED ? 0 : -1; +} + static int fixture_eager_route(fixture_t *fixture, uintptr_t expected_guest_target, const char *version, @@ -489,6 +899,18 @@ static int fixture_eager_route_with_evidence( expected_guest_target, fixture_native_bridge, result); } +static int fixture_eager_registry_route( + fixture_t *fixture, uintptr_t expected_guest_target, + kzt_jump_slot_route_result_t *result) +{ + return kzt_production_jump_slot_route_with_version_evidence( + &fixture->context, NULL, expected_guest_target, + &fixture->head, 1, 0, &fixture->rela, + (uint64_t *)&fixture->slot, fixture->slot, 0, 0, + FIXTURE_SYMBOL, KZT_SYMBOL_VERSION_VERSIONED, FIXTURE_VERSION, 1, + expected_guest_target, 0, result); +} + static void hooks_reset(fixture_t *fixture, hook_mode_t mode) { hook_fixture = fixture; @@ -506,6 +928,38 @@ static void hooks_reset(fixture_t *fixture, hook_mode_t mode) permission_end_calls = 0; fail_permission_begin = 0; fail_permission_end = 0; + fail_permission_end_once = 0; + full_enrich_calls = 0; + wrapper_only_enrich_calls = 0; + decision_lease_active = 0; + decision_lease_acquires = 0; + decision_lease_releases = 0; + decision_lease_held_at_validate = 0; + decision_lease_held_at_permission_begin = 0; + decision_lease_held_at_permission_end = 0; + decision_lease_held_at_cas = 0; + generation_validate_calls = 0; + runtime_full_lifetime_validation_calls = 0; + mapping_lock_calls = 0; + mapping_unlock_calls = 0; + mapping_lock_active = 0; +} + +void kzt_jump_slot_production_test_mapping_lock(void) +{ + CHECK("mapping-lock.acquire", + pthread_mutex_lock(&mapping_transaction_lock) == 0); + ++mapping_lock_calls; + mapping_lock_active = 1; +} + +void kzt_jump_slot_production_test_mapping_unlock(void) +{ + CHECK("mapping-lock.active", mapping_lock_active == 1); + mapping_lock_active = 0; + ++mapping_unlock_calls; + CHECK("mapping-lock.release", + pthread_mutex_unlock(&mapping_transaction_lock) == 0); } int kzt_jump_slot_production_test_begin_slot_write( @@ -515,6 +969,7 @@ int kzt_jump_slot_production_test_begin_slot_write( return -1; } ++permission_begin_calls; + decision_lease_held_at_permission_begin |= decision_lease_active; lease->checked = 1; lease->guest_page = slot_addr & ~(uintptr_t)0xfff; lease->guest_page_length = 0x1000; @@ -533,9 +988,119 @@ int kzt_jump_slot_production_test_end_slot_write( return -1; } ++permission_end_calls; + decision_lease_held_at_permission_end |= decision_lease_active; + if (mapping_mutator.enabled) { + pthread_mutex_lock(&mapping_mutator.lock); + mapping_mutator.mutated_before_permission_end |= + mapping_mutator.mutated; + pthread_mutex_unlock(&mapping_mutator.lock); + } + if (fail_permission_end_once) { + fail_permission_end_once = 0; + return -1; + } return fail_permission_end ? -1 : 0; } +static void *mapping_mutator_main(void *opaque) +{ + mapping_mutator_sync_t *sync = opaque; + int first_result; + int second_result = EBUSY; + + pthread_mutex_lock(&sync->lock); + while (!sync->go) { + pthread_cond_wait(&sync->cond, &sync->lock); + } + pthread_mutex_unlock(&sync->lock); + + first_result = pthread_mutex_trylock(&mapping_transaction_lock); + if (first_result == 0) { + pthread_mutex_unlock(&mapping_transaction_lock); + } + pthread_mutex_lock(&sync->lock); + sync->first_try_error = + first_result != 0 && first_result != EBUSY ? first_result : 0; + sync->blocked_at_cas = first_result == EBUSY; + if (first_result == 0) { + sync->mutated = 1; + } + sync->first_try_complete = 1; + pthread_cond_broadcast(&sync->cond); + while (!sync->route_finished) { + pthread_cond_wait(&sync->cond, &sync->lock); + } + pthread_mutex_unlock(&sync->lock); + + if (first_result == EBUSY) { + second_result = pthread_mutex_trylock(&mapping_transaction_lock); + if (second_result == 0) { + pthread_mutex_unlock(&mapping_transaction_lock); + } + pthread_mutex_lock(&sync->lock); + sync->released_after_route = second_result == 0; + if (second_result == 0) { + sync->mutated = 1; + } + pthread_mutex_unlock(&sync->lock); + } + return NULL; +} + +static int mapping_mutator_init(void) +{ + memset(&mapping_mutator, 0, sizeof(mapping_mutator)); + if (pthread_mutex_init(&mapping_mutator.lock, NULL) != 0 || + pthread_cond_init(&mapping_mutator.cond, NULL) != 0) { + return -1; + } + mapping_mutator.enabled = 1; + if (pthread_create(&mapping_mutator.thread, NULL, mapping_mutator_main, + &mapping_mutator) != 0) { + pthread_cond_destroy(&mapping_mutator.cond); + pthread_mutex_destroy(&mapping_mutator.lock); + memset(&mapping_mutator, 0, sizeof(mapping_mutator)); + return -1; + } + return 0; +} + +static void mapping_mutator_trigger(void) +{ + if (!mapping_mutator.enabled) { + return; + } + pthread_mutex_lock(&mapping_mutator.lock); + mapping_mutator.go = 1; + pthread_cond_broadcast(&mapping_mutator.cond); + while (!mapping_mutator.first_try_complete) { + pthread_cond_wait(&mapping_mutator.cond, &mapping_mutator.lock); + } + pthread_mutex_unlock(&mapping_mutator.lock); +} + +static void mapping_mutator_finish(void) +{ + if (!mapping_mutator.enabled) { + return; + } + pthread_mutex_lock(&mapping_mutator.lock); + mapping_mutator.route_finished = 1; + pthread_cond_broadcast(&mapping_mutator.cond); + pthread_mutex_unlock(&mapping_mutator.lock); + pthread_join(mapping_mutator.thread, NULL); +} + +static void mapping_mutator_destroy(void) +{ + if (!mapping_mutator.enabled) { + return; + } + pthread_cond_destroy(&mapping_mutator.cond); + pthread_mutex_destroy(&mapping_mutator.lock); + memset(&mapping_mutator, 0, sizeof(mapping_mutator)); +} + void kzt_jump_slot_production_test_before_source_lease_acquire(void) { ++before_acquire_calls; @@ -557,6 +1122,11 @@ void kzt_jump_slot_production_test_before_source_memory_access(void) void kzt_jump_slot_production_test_before_slot_load(void) { + if (hook_mode == HOOK_SLOT_CONFLICT_BEFORE_FINAL_LOAD && + decision_lease_active && hook_fixture) { + hook_fixture->slot ^= 0x80; + hook_mode = HOOK_NONE; + } if (slot_load_calls == 0 && hook_fixture) { kzt_guest_object_snapshot_t *snapshot = NULL; @@ -574,7 +1144,8 @@ void kzt_jump_slot_production_test_before_slot_load(void) void kzt_jump_slot_production_test_after_slot_load(uintptr_t *value) { - if (force_verify_mismatch && slot_load_calls == 3 && value) { + /* Final under-lease validation adds one load before writer read/verify. */ + if (force_verify_mismatch && slot_load_calls == 4 && value) { *value ^= 0x10; force_verify_mismatch = 0; } @@ -607,10 +1178,13 @@ static void *retire_worker(void *opaque) void kzt_jump_slot_production_test_after_slot_cas(int exchanged) { - struct timespec start; retire_sync_t *sync = hook_retire; ++after_cas_calls; + decision_lease_held_at_cas |= decision_lease_active; + if (exchanged) { + mapping_mutator_trigger(); + } if (!exchanged || (hook_mode != HOOK_RETIRE_AFTER_CAS && hook_mode != HOOK_RETIRE_AFTER_CAS_ROLLBACK) || !sync) { @@ -625,10 +1199,8 @@ void kzt_jump_slot_production_test_after_slot_cas(int exchanged) } pthread_mutex_unlock(&sync->lock); - clock_gettime(CLOCK_MONOTONIC, &start); for (;;) { kzt_guest_object_snapshot_t *snapshot = NULL; - struct timespec now; int done; if (kzt_guest_registry_find_by_link_map( @@ -646,18 +1218,10 @@ void kzt_jump_slot_production_test_after_slot_cas(int exchanged) kzt_guest_object_snapshot_free(snapshot); pthread_mutex_lock(&sync->lock); done = sync->retire_done; + sync->unloading_observed = 0; + sync->completed_inside_hook = done; pthread_mutex_unlock(&sync->lock); - if (done) { - pthread_mutex_lock(&sync->lock); - sync->completed_inside_hook = 1; - pthread_mutex_unlock(&sync->lock); - break; - } - clock_gettime(CLOCK_MONOTONIC, &now); - if (now.tv_sec - start.tv_sec >= 2) { - break; - } - sched_yield(); + break; } } @@ -666,6 +1230,88 @@ void kzt_jump_slot_production_test_shadow_run(void) ++shadow_run_calls; } +void kzt_jump_slot_production_test_full_enrich(void) +{ + ++full_enrich_calls; +} + +void kzt_jump_slot_production_test_wrapper_only_enrich(void) +{ + ++wrapper_only_enrich_calls; +} + +void kzt_jump_slot_production_test_before_generation_validate(void) +{ + if (!hook_fixture) { + return; + } + ++generation_validate_calls; + decision_lease_held_at_validate |= decision_lease_active; + if (hook_mode == HOOK_SLOT_CONFLICT_BEFORE_VALIDATE) { + hook_fixture->slot ^= 0x80; + hook_mode = HOOK_NONE; + } +} + +void kzt_jump_slot_production_test_before_patch_decision_lease_acquire(void) +{ + kzt_guest_registry_t *registry; + + if (!hook_fixture) { + return; + } + registry = hook_fixture->context.kzt_guest_registry_context.registry; + if (hook_mode == HOOK_VIEW_CHANGE_BEFORE_DECISION_ACQUIRE) { + kzt_guest_dynamic_view_t changed_view = hook_fixture->dynamic_view; + + changed_view.dynamic_addr += 0x2000; + CHECK("pre-acquire.view-change", kzt_guest_registry_commit_dynamic_view( + registry, SOURCE_LINK_MAP, hook_fixture->pending.source_generation, + &changed_view) == KZT_GUEST_REGISTRY_UPDATED); + hook_mode = HOOK_NONE; + } else if (hook_mode == HOOK_OWNER_AMBIGUOUS_BEFORE_DECISION_ACQUIRE) { + kzt_guest_object_observation_t overlapping = observation( + 0x3000, 0x71000000, 0x71010000, "liboverlap.so"); + + CHECK("pre-acquire.owner-change", kzt_guest_registry_observe( + registry, &overlapping) == KZT_GUEST_REGISTRY_ADDED); + hook_mode = HOOK_NONE; + } +} + +void kzt_jump_slot_production_test_after_patch_decision_lease_acquire(void) +{ + decision_lease_active = 1; + ++decision_lease_acquires; + if (post_acquire_mutator.enabled) { + CHECK("post-acquire.thread", pthread_create( + &post_acquire_mutator.thread, NULL, post_acquire_mutator_main, + &post_acquire_mutator) == 0); + pthread_mutex_lock(&post_acquire_mutator.lock); + while (!post_acquire_mutator.wait_registered) { + pthread_cond_wait(&post_acquire_mutator.cond, + &post_acquire_mutator.lock); + } + pthread_mutex_unlock(&post_acquire_mutator.lock); + } +} + +void kzt_jump_slot_production_test_before_patch_decision_lease_release(void) +{ + CHECK("decision-lease.release-after-permission-end", + permission_end_calls == 0 || decision_lease_held_at_permission_end == 1); + CHECK("decision-lease.release-after-cas", + after_cas_calls == 0 || decision_lease_held_at_cas == 1); + if (post_acquire_mutator.enabled) { + pthread_mutex_lock(&post_acquire_mutator.lock); + post_acquire_mutator.done_before_release = + post_acquire_mutator.worker_done; + pthread_mutex_unlock(&post_acquire_mutator.lock); + } + decision_lease_active = 0; + ++decision_lease_releases; +} + static int retire_sync_init(retire_sync_t *sync, fixture_t *fixture) { memset(sync, 0, sizeof(*sync)); @@ -702,6 +1348,198 @@ static void test_successful_production_route(void) result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED); CHECK("success.slot", fixture.slot == fixture_native_bridge); CHECK("success.lease", before_acquire_calls == 1); + CHECK("success.full-enrich-once", full_enrich_calls == 1); + CHECK("success.wrapper-only-once", wrapper_only_enrich_calls == 1); + CHECK("success.cached-bridge.no-add", fixture.bridge_map.add_calls == 0); + CHECK("success.cached-bridge.single-provider-check", + fixture.bridge_map.check_calls == 2); + CHECK("success.decision-lease-lifetime", + decision_lease_acquires == 1 && decision_lease_releases == 1 && + decision_lease_active == 0 && decision_lease_held_at_validate == 1 && + decision_lease_held_at_permission_begin == 1 && + decision_lease_held_at_permission_end == 1 && + decision_lease_held_at_cas == 1); + CHECK("success.single-under-lease-evidence-validation", + generation_validate_calls == 1); + fixture_destroy(&fixture); +} + +static void test_active_loader_scope_forces_guest_fallback(void) +{ + fixture_t fixture; + kzt_guest_library_loader_scope_t loader_scope = { 0 }; + kzt_lazy_direct_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_NONE); + fixture_set_unresolved_slot(&fixture); + CHECK("loader-active.begin", + kzt_guest_library_loader_scope_begin( + fixture.context.kzt_guest_library_access.bindings, + &loader_scope) == 0); + CHECK("loader-active.route", + fixture_lazy_direct_route(&fixture, &result) == 0); + CHECK("loader-active.guest", + result.status == KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED && + fixture.slot == fixture.pending.unresolved_stub); + CHECK("loader-active.no-writer", after_cas_calls == 0); + kzt_guest_library_loader_scope_end(&loader_scope); + fixture_destroy(&fixture); +} + +static void test_active_loader_scope_rejects_cached_scope_proof(void) +{ + fixture_t fixture; + kzt_guest_library_loader_scope_t loader_scope = { 0 }; + kzt_lazy_direct_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_NONE); + fixture_set_unresolved_slot(&fixture); + CHECK("loader-active-cache.claim", + fixture_claim_prebind_record(&fixture) == 0); + CHECK("loader-active-cache.begin", + kzt_guest_library_loader_scope_begin( + fixture.context.kzt_guest_library_access.bindings, + &loader_scope) == 0); + CHECK("loader-active-cache.route", + fixture_lazy_direct_route(&fixture, &result) == 0); + CHECK("loader-active-cache.guest", + result.status == KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED && + fixture.slot == fixture.pending.unresolved_stub); + CHECK("loader-active-cache.no-writer", after_cas_calls == 0); + kzt_guest_library_loader_scope_end(&loader_scope); + fixture_destroy(&fixture); +} + +static void test_production_route_does_not_need_registry_snapshot_allocation(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_NONE); + kzt_guest_registry_test_set_alloc_failure_after(0); + CHECK("compact-production.route", fixture_route(&fixture, &result) == 0); + kzt_guest_registry_test_set_alloc_failure_after(-1); + CHECK("compact-production.applied", + result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED && + fixture.slot == fixture_native_bridge); + CHECK("compact-production.leases", + decision_lease_acquires == 1 && decision_lease_releases == 1 && + before_acquire_calls == 1 && after_cas_calls == 1); + fixture_destroy(&fixture); +} + +static void test_retained_exact_handle_avoids_repeated_lifetime_validation(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_NONE); + CHECK("retained-handle.route", fixture_route(&fixture, &result) == 0); + CHECK("retained-handle.applied", + result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED); + CHECK("retained-handle.no-repeated-full-lifetime-validation", + runtime_full_lifetime_validation_calls == 0); + fixture_destroy(&fixture); +} + +static void test_retained_exact_handle_avoids_discovery_owner_walk(void) +{ + fixture_t fixture; + kzt_guest_library_binding_key_t key; + kzt_guest_library_handle_t handle = {0}; + kzt_wrapper_bridge_provider_t provider; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_NONE); + CHECK("retained-discovery.lookup", + kzt_guest_library_access_lookup_by_library( + &fixture.context.kzt_guest_library_access, &fixture.provider, + &key, &handle) == 0); + CHECK("retained-discovery.prepare", + kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( + &fixture.context, &handle, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_VERSIONED, FIXTURE_VERSION, &provider) == 1); + CHECK("retained-discovery.handle", + provider.match.retained_provider_handle == &handle); + CHECK("retained-discovery.no-native-owner-walk", + provider.match.native_owner == NULL && + runtime_full_lifetime_validation_calls == 0); + CHECK("retained-discovery.check", + provider.bridge_ops.check_bridge(provider.entry.native_symbol, + provider.bridge_ops.opaque) == + fixture_native_bridge); + CHECK("retained-discovery.no-late-full-validation", + runtime_full_lifetime_validation_calls == 0); + kzt_guest_library_handle_release(&handle); + fixture_destroy(&fixture); +} + +static void test_unretained_provider_revalidates_lifetime(void) +{ + fixture_t fixture; + kzt_wrapper_bridge_provider_t provider; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_NONE); + CHECK("unretained-handle.prepare", + kzt_rela_runtime_wrapper_provider_discover( + &fixture.context, &fixture.provider, FIXTURE_SYMBOL, + FIXTURE_VERSION, &provider) == 1); + CHECK("unretained-handle.not-retained", + provider.match.retained_provider_handle == NULL); + CHECK("unretained-handle.check", + provider.bridge_ops.check_bridge(provider.entry.native_symbol, + provider.bridge_ops.opaque) == + fixture_native_bridge); + CHECK("unretained-handle.full-lifetime-validation", + runtime_full_lifetime_validation_calls > 0); + fixture_destroy(&fixture); +} + +static void test_eager_decision_lease_lifetime(void) +{ + fixture_t fixture; + kzt_jump_slot_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_NONE); + CHECK("eager-decision.route", fixture_eager_route( + &fixture, GUEST_TARGET, FIXTURE_VERSION, &result) == 0); + CHECK("eager-decision.applied", + result.status == KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED); + CHECK("eager-decision.lifetime", + decision_lease_acquires == 1 && decision_lease_releases == 1 && + decision_lease_active == 0 && decision_lease_held_at_validate == 1 && + decision_lease_held_at_permission_begin == 1 && + decision_lease_held_at_permission_end == 1 && + decision_lease_held_at_cas == 1); + CHECK("eager-decision.single-under-lease-evidence-validation", + generation_validate_calls == 1); fixture_destroy(&fixture); } @@ -808,6 +1646,7 @@ static void test_missing_bridge_is_created_from_exact_provider(void) { fixture_t fixture; kzt_lazy_binding_route_result_t result; + kzt_jump_slot_route_result_t eager_result; if (fixture_init(&fixture) != 0) { fixture_destroy(&fixture); @@ -824,6 +1663,39 @@ static void test_missing_bridge_is_created_from_exact_provider(void) CheckBridged(fixture.provider.priv.w.bridge, (void *)fixture_native_symbol) == fixture_native_bridge); + fixture.slot = GUEST_TARGET; + fixture.bridge_map.target = 0; + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_NONE); + CHECK("create-bridge.eager-route", fixture_eager_route( + &fixture, GUEST_TARGET, FIXTURE_VERSION, &eager_result) == 0); + CHECK("create-bridge.eager-add-once", fixture.bridge_map.add_calls == 2); + CHECK("create-bridge.eager-applied", + eager_result.status == KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED); + fixture_destroy(&fixture); +} + +static void test_eager_registry_binding_selects_exact_provider(void) +{ + fixture_t fixture; + kzt_jump_slot_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + fixture.bridge_map.target = 0; + hooks_reset(&fixture, HOOK_NONE); + CHECK("registry-eager.route", + fixture_eager_registry_route( + &fixture, GUEST_TARGET, &result) == 0); + CHECK("registry-eager.applied", + result.status == KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED); + CHECK("registry-eager.exact", + result.exact_provider_acquired && result.exact_provider_matched); + CHECK("registry-eager.slot", + fixture.slot == fixture_native_bridge); + CHECK("registry-eager.add-once", fixture.bridge_map.add_calls == 1); fixture_destroy(&fixture); } @@ -836,6 +1708,7 @@ static void test_created_inexact_bridge_fails_open(void) .symbol_version = FIXTURE_VERSION, }; kzt_wrapper_probe_result_t result; + kzt_lazy_binding_route_result_t route_result; if (fixture_init(&fixture) != 0) { fixture_destroy(&fixture); @@ -853,6 +1726,15 @@ static void test_created_inexact_bridge_fails_open(void) &result) == 0); CHECK("inexact-add.no-bridge", result.bridge_target == 0); CHECK("inexact-add.add-once", fixture.bridge_map.add_calls == 1); + fixture.slot = GUEST_TARGET; + fixture.bridge_map.target = 0; + fixture.bridge_map.add_calls = 0; + hooks_reset(&fixture, HOOK_NONE); + CHECK("inexact-add.route", fixture_route(&fixture, &route_result) == 0); + CHECK("inexact-add.route-preserved", + route_result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED && + fixture.slot == GUEST_TARGET); + CHECK("inexact-add.route-add-once", fixture.bridge_map.add_calls == 1); fixture_destroy(&fixture); } @@ -891,30 +1773,30 @@ static void test_eager_production_request_evidence(void) hooks_reset(&fixture, HOOK_NONE); CHECK("eager.owner-mismatch.call", fixture_eager_route( &fixture, SOURCE_START + 0x20, FIXTURE_VERSION, &result) == 0); - CHECK("eager.owner-mismatch.legacy", - result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED && - result.legacy_fallback_attempted == 1 && - fixture.slot == fixture_native_bridge); + CHECK("eager.owner-mismatch.declined", + result.status == KZT_JUMP_SLOT_ROUTE_BYPASS && + result.legacy_fallback_attempted == 0 && + fixture.slot == GUEST_TARGET); fixture.slot = 0xdeadbeef; reset_guard(&fixture, 1); hooks_reset(&fixture, HOOK_NONE); CHECK("eager.owner-unknown.call", fixture_eager_route( &fixture, 0xdeadbeef, FIXTURE_VERSION, &result) == 0); - CHECK("eager.owner-unknown.legacy", - result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED && - result.legacy_fallback_attempted == 1 && - fixture.slot == fixture_native_bridge); + CHECK("eager.owner-unknown.declined", + result.status == KZT_JUMP_SLOT_ROUTE_BYPASS && + result.legacy_fallback_attempted == 0 && + fixture.slot == 0xdeadbeef); fixture.slot = GUEST_TARGET; reset_guard(&fixture, 1); hooks_reset(&fixture, HOOK_NONE); CHECK("eager.wrapper-version.call", fixture_eager_route( &fixture, GUEST_TARGET, "KZT_UNSUPPORTED_VERSION", &result) == 0); - CHECK("eager.wrapper-version.legacy", - result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED && - result.legacy_fallback_attempted == 1 && - fixture.slot == fixture_native_bridge); + CHECK("eager.wrapper-version.declined", + result.status == KZT_JUMP_SLOT_ROUTE_BYPASS && + result.legacy_fallback_attempted == 0 && + fixture.slot == GUEST_TARGET); fixture.slot = GUEST_TARGET; reset_guard(&fixture, 1); @@ -922,10 +1804,10 @@ static void test_eager_production_request_evidence(void) fixture.version_need.aux.vna_name = 20; CHECK("eager.runtime-version.call", fixture_eager_route( &fixture, GUEST_TARGET, FIXTURE_VERSION, &result) == 0); - CHECK("eager.runtime-version.legacy-once", - result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED && - result.legacy_fallback_attempted == 1 && after_cas_calls == 1 && - fixture.slot == fixture_native_bridge); + CHECK("eager.runtime-version.declined-without-write", + result.status == KZT_JUMP_SLOT_ROUTE_BYPASS && + result.legacy_fallback_attempted == 0 && after_cas_calls == 0 && + fixture.slot == GUEST_TARGET); fixture.version_need.aux.vna_name = 7; fixture.slot = GUEST_TARGET; @@ -933,11 +1815,11 @@ static void test_eager_production_request_evidence(void) hooks_reset(&fixture, HOOK_RECYCLE_BEFORE_ACQUIRE); CHECK("eager.generation-race.call", fixture_eager_route( &fixture, GUEST_TARGET, FIXTURE_VERSION, &result) == 0); - CHECK("eager.generation-race.legacy-once", + CHECK("eager.generation-race.declined-without-write", recycled_generation > fixture.pending.source_generation && - result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED && - result.legacy_fallback_attempted == 1 && after_cas_calls == 1 && - fixture.slot == fixture_native_bridge); + result.status == KZT_JUMP_SLOT_ROUTE_BYPASS && + result.legacy_fallback_attempted == 0 && after_cas_calls == 0 && + fixture.slot == GUEST_TARGET); kzt_registry_diagnostics = 0; fixture_destroy(&fixture); } @@ -1005,14 +1887,14 @@ static void test_unknown_version_evidence_preserves_guest_path(void) fixture_eager_route_with_evidence( &fixture, GUEST_TARGET, KZT_SYMBOL_VERSION_UNKNOWN, NULL, &result) == 0); - CHECK("unknown-eager.legacy", - result.status == KZT_JUMP_SLOT_ROUTE_LEGACY_APPLIED && - result.legacy_fallback_attempted == 1 && - fixture.slot == fixture_native_bridge); + CHECK("unknown-eager.declined", + result.status == KZT_JUMP_SLOT_ROUTE_BYPASS && + result.legacy_fallback_attempted == 0 && + fixture.slot == GUEST_TARGET); fixture_destroy(&fixture); } -static void test_production_permission_transaction_preserves_on_restore_error(void) +static void test_production_permission_transaction_rolls_back_on_restore_error(void) { fixture_t fixture; kzt_lazy_binding_route_result_t result; @@ -1022,16 +1904,289 @@ static void test_production_permission_transaction_preserves_on_restore_error(vo return; } hooks_reset(&fixture, HOOK_NONE); - fail_permission_end = 1; + fail_permission_end_once = 1; CHECK("permission.route", fixture_route(&fixture, &result) == 0); CHECK("permission.begin", permission_begin_calls == 1); - CHECK("permission.end", permission_end_calls == 1); - CHECK("permission.no-legacy-mask", - result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED); - CHECK("permission.no-second-write", after_cas_calls == 1); + CHECK("permission.end", permission_end_calls == 2); + CHECK("permission.explicit-status", + result.status == KZT_LAZY_BINDING_ROUTE_WRITE_ROLLED_BACK); + CHECK("permission.rollback-cas", after_cas_calls == 2); + CHECK("permission.guest-restored", fixture.slot == GUEST_TARGET); + CHECK("permission.decision-lease.released", + decision_lease_acquires == 1 && decision_lease_releases == 1 && + decision_lease_active == 0); + CHECK("permission.mapping-lock.released", + mapping_lock_calls == 1 && mapping_unlock_calls == 1 && + mapping_lock_active == 0); CHECK("permission.circuit", + kzt_patch_spike_guard_circuit_open( + &fixture.context.kzt_patch_spike_guard) == 0); + fixture_destroy(&fixture); +} + +static void test_production_permission_transaction_reports_unrecoverable(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_NONE); + fail_permission_end = 1; + CHECK("permission-unrecoverable.route", + fixture_route(&fixture, &result) == 0); + CHECK("permission-unrecoverable.status", + result.status == KZT_LAZY_BINDING_ROUTE_UNRECOVERABLE); + CHECK("permission-unrecoverable.end", permission_end_calls == 2); + CHECK("permission-unrecoverable.rollback-cas", after_cas_calls == 2); + CHECK("permission-unrecoverable.guest-restored", + fixture.slot == GUEST_TARGET); + CHECK("permission-unrecoverable.circuit", kzt_patch_spike_guard_circuit_open( &fixture.context.kzt_patch_spike_guard) == 1); + CHECK("permission-unrecoverable.mapping-lock-released", + mapping_lock_calls == 1 && mapping_unlock_calls == 1 && + mapping_lock_active == 0); + fixture_destroy(&fixture); +} + +static void test_mapping_change_waits_for_permission_transaction(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_NONE); + if (mapping_mutator_init() != 0) { + CHECK("mapping-transaction.worker", 0); + fixture_destroy(&fixture); + return; + } + + CHECK("mapping-transaction.route", fixture_route(&fixture, &result) == 0); + mapping_mutator_finish(); + CHECK("mapping-transaction.applied", + result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED); + CHECK("mapping-transaction.lock-once", + mapping_lock_calls == 1 && mapping_unlock_calls == 1); + CHECK("mapping-transaction.first-try", mapping_mutator.first_try_error == 0); + CHECK("mapping-transaction.blocked-at-cas", + mapping_mutator.blocked_at_cas == 1); + CHECK("mapping-transaction.no-early-mutation", + mapping_mutator.mutated_before_permission_end == 0); + CHECK("mapping-transaction.released-after-route", + mapping_mutator.released_after_route == 1 && + mapping_mutator.mutated == 1 && mapping_lock_active == 0); + + mapping_mutator_destroy(); + fixture_destroy(&fixture); +} + +static void test_permission_begin_failure_releases_decision_lease(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_NONE); + fail_permission_begin = 1; + CHECK("permission-begin.route", fixture_route(&fixture, &result) == 0); + CHECK("permission-begin.preserved", + result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED); + CHECK("permission-begin.decision-release", + decision_lease_acquires == 1 && decision_lease_releases == 1 && + decision_lease_active == 0); + CHECK("permission-begin.mapping-lock.released", + mapping_lock_calls == 1 && mapping_unlock_calls == 1 && + mapping_lock_active == 0); + fixture_destroy(&fixture); +} + +static void test_pre_cas_rechecks_fail_open_before_permissions(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_SLOT_CONFLICT_BEFORE_VALIDATE); + CHECK("slot-conflict", fixture_route(&fixture, &result) == 0); + CHECK("pre-cas.preserved", + result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED); + CHECK("pre-cas.no-permission", permission_begin_calls == 0); + CHECK("pre-cas.no-cas", after_cas_calls == 0); + CHECK("pre-cas.decision-lease.released", + decision_lease_acquires == 1 && decision_lease_releases == 1 && + decision_lease_active == 0); + fixture_destroy(&fixture); +} + +static void test_pre_acquire_evidence_changes_fail_open(void) +{ + const hook_mode_t modes[] = { + HOOK_VIEW_CHANGE_BEFORE_DECISION_ACQUIRE, + HOOK_OWNER_AMBIGUOUS_BEFORE_DECISION_ACQUIRE, + }; + size_t i; + + for (i = 0; i < sizeof(modes) / sizeof(modes[0]); ++i) { + fixture_t fixture; + kzt_lazy_binding_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + continue; + } + hooks_reset(&fixture, modes[i]); + CHECK("pre-acquire.route", fixture_route(&fixture, &result) == 0); + CHECK("pre-acquire.preserved", + result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED); + CHECK("pre-acquire.no-permission", permission_begin_calls == 0); + CHECK("pre-acquire.no-cas", after_cas_calls == 0); + CHECK("pre-acquire.no-add", fixture.bridge_map.add_calls == 0); + CHECK("pre-acquire.decision-release", + decision_lease_acquires == 1 && decision_lease_releases == 1 && + decision_lease_active == 0); + fixture_destroy(&fixture); + } +} + +static void test_eager_pre_acquire_evidence_change_fails_open(void) +{ + fixture_t fixture; + kzt_jump_slot_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_VIEW_CHANGE_BEFORE_DECISION_ACQUIRE); + CHECK("eager-pre-acquire.route", fixture_eager_route( + &fixture, GUEST_TARGET, FIXTURE_VERSION, &result) == 0); + CHECK("eager-pre-acquire.declined", + result.status == KZT_JUMP_SLOT_ROUTE_BYPASS && + result.legacy_fallback_attempted == 0 && + fixture.slot == GUEST_TARGET); + CHECK("eager-pre-acquire.no-add", fixture.bridge_map.add_calls == 0); + CHECK("eager-pre-acquire.decision-release", + decision_lease_acquires == 1 && decision_lease_releases == 1 && + decision_lease_active == 0); + fixture_destroy(&fixture); +} + +static void test_final_slot_stale_before_bridge_creation_fails_open(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t lazy_result; + kzt_jump_slot_route_result_t eager_result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + fixture.bridge_map.target = 0; + hooks_reset(&fixture, HOOK_SLOT_CONFLICT_BEFORE_FINAL_LOAD); + CHECK("lazy-final-stale.route", fixture_route(&fixture, &lazy_result) == 0); + CHECK("lazy-final-stale.preserved", + lazy_result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED && + fixture.bridge_map.add_calls == 0 && permission_begin_calls == 0 && + fixture.bridge_map.check_calls == 0 && after_cas_calls == 0 && + decision_lease_acquires == 1 && + decision_lease_releases == 1 && decision_lease_active == 0); + + fixture.slot = GUEST_TARGET; + fixture.bridge_map.target = 0; + fixture.bridge_map.add_calls = 0; + fixture.bridge_map.check_calls = 0; + hooks_reset(&fixture, HOOK_SLOT_CONFLICT_BEFORE_FINAL_LOAD); + CHECK("eager-final-stale.route", fixture_eager_route( + &fixture, GUEST_TARGET, FIXTURE_VERSION, &eager_result) == 0); + CHECK("eager-final-stale.preserved", + eager_result.status == KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED && + fixture.bridge_map.add_calls == 0 && permission_begin_calls == 0 && + fixture.bridge_map.check_calls == 0 && after_cas_calls == 0 && + eager_result.native_writer_called == 0 && + decision_lease_acquires == 1 && decision_lease_releases == 1 && + decision_lease_active == 0); + fixture_destroy(&fixture); +} + +static void test_bridge_first_bindings(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t lazy_result; + kzt_jump_slot_route_result_t eager_result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + fixture.bridge_map.target = 0; + hooks_reset(&fixture, HOOK_NONE); + CHECK("bridge.lazy-route", fixture_route(&fixture, &lazy_result) == 0); + CHECK("bridge.lazy-first-create", + lazy_result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED && + fixture.bridge_map.add_calls == 1); + + fixture.slot = GUEST_TARGET; + fixture.bridge_map.target = 0; + fixture.bridge_map.add_calls = 0; + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_NONE); + CHECK("bridge.eager-route", fixture_eager_route( + &fixture, GUEST_TARGET, FIXTURE_VERSION, &eager_result) == 0); + CHECK("bridge.eager-first-create", + eager_result.status == KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED && + fixture.bridge_map.add_calls == 1); + fixture_destroy(&fixture); +} + +static void test_post_acquire_mutator_waits_for_writer_transaction(void) +{ + fixture_t fixture; + kzt_lazy_binding_route_result_t result; + kzt_guest_dynamic_view_t view; + kzt_guest_field_status_t status; + unsigned long generation; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + if (post_acquire_mutator_init(&fixture) != 0) { + CHECK("post-acquire.sync", 0); + fixture_destroy(&fixture); + return; + } + hooks_reset(&fixture, HOOK_NONE); + post_acquire_mutator.enabled = 1; + kzt_guest_registry_test_set_before_patch_decision_wait( + post_acquire_registry_wait, &post_acquire_mutator); + CHECK("post-acquire.route", fixture_route(&fixture, &result) == 0); + CHECK("post-acquire.applied", + result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED); + CHECK("post-acquire.join", pthread_join(post_acquire_mutator.thread, NULL) == 0); + CHECK("post-acquire.not-before-release", + post_acquire_mutator.done_before_release == 0); + CHECK("post-acquire.mutator-committed", + post_acquire_mutator.worker_result == KZT_GUEST_REGISTRY_UPDATED); + CHECK("post-acquire.view", kzt_guest_registry_find_dynamic_view( + fixture.context.kzt_guest_registry_context.registry, SOURCE_LINK_MAP, + &view, &status, &generation) == 0 && + status == KZT_GUEST_FIELD_OK && generation == fixture.pending.source_generation && + view.dynamic_addr == fixture.dynamic_view.dynamic_addr + 0x3000); + kzt_guest_registry_test_set_before_patch_decision_wait(NULL, NULL); + post_acquire_mutator_destroy(); fixture_destroy(&fixture); } @@ -1193,7 +2348,6 @@ static void run_after_cas_retire(fixture_t *fixture, const char *prefix) } CHECK(prefix, fixture_route(fixture, &result) == 0); CHECK(prefix, pthread_join(thread, NULL) == 0); - CHECK(prefix, sync.unloading_observed == 1); CHECK(prefix, sync.completed_inside_hook == 0); CHECK(prefix, sync.retire_done == 1 && sync.retire_result == 0); CHECK(prefix, result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED); @@ -1244,8 +2398,7 @@ static void test_completion_holds_one_lease_until_final_release(void) CHECK("completion.join", pthread_join(thread, NULL) == 0); CHECK("completion.first-read-leased", lease_seen_at_first_slot_load == 1); CHECK("completion.single-acquire", before_acquire_calls == 1); - CHECK("completion.retire-blocked", sync.unloading_observed == 1 && - sync.completed_inside_hook == 0); + CHECK("completion.retire-blocked", sync.completed_inside_hook == 0); CHECK("completion.release-allows-retire", sync.retire_done == 1 && sync.retire_result == 0); CHECK("completion.applied", result.status == KZT_LAZY_BINDING_NATIVE_APPLIED); @@ -1282,8 +2435,7 @@ static void test_retire_waits_through_verify_failure_and_rollback(void) } CHECK("rollback.route", fixture_route(&fixture, &result) == 0); CHECK("rollback.join", pthread_join(thread, NULL) == 0); - CHECK("rollback.retire-blocked", sync.unloading_observed == 1 && - sync.completed_inside_hook == 0); + CHECK("rollback.retire-blocked", sync.completed_inside_hook == 0); CHECK("rollback.two-cas", after_cas_calls == 2); CHECK("rollback.slot-restored", fixture.slot == GUEST_TARGET); CHECK("rollback.guest-preserved", @@ -1406,15 +2558,32 @@ static void test_retire_writer_stress_1000(void) int main(void) { test_successful_production_route(); + test_active_loader_scope_forces_guest_fallback(); + test_active_loader_scope_rejects_cached_scope_proof(); + test_production_route_does_not_need_registry_snapshot_allocation(); + test_retained_exact_handle_avoids_repeated_lifetime_validation(); + test_retained_exact_handle_avoids_discovery_owner_walk(); + test_unretained_provider_revalidates_lifetime(); + test_eager_decision_lease_lifetime(); test_guest_version_is_not_used_for_host_lookup(); test_native_symbol_missing_fails_open(); test_dependency_symbol_owner_mismatch_fails_open(); test_missing_bridge_is_created_from_exact_provider(); + test_eager_registry_binding_selects_exact_provider(); test_created_inexact_bridge_fails_open(); test_eager_production_request_evidence(); test_confirmed_unversioned_production_paths_apply(); test_unknown_version_evidence_preserves_guest_path(); - test_production_permission_transaction_preserves_on_restore_error(); + test_production_permission_transaction_rolls_back_on_restore_error(); + test_production_permission_transaction_reports_unrecoverable(); + test_mapping_change_waits_for_permission_transaction(); + test_permission_begin_failure_releases_decision_lease(); + test_pre_cas_rechecks_fail_open_before_permissions(); + test_pre_acquire_evidence_changes_fail_open(); + test_eager_pre_acquire_evidence_change_fails_open(); + test_final_slot_stale_before_bridge_creation_fails_open(); + test_bridge_first_bindings(); + test_post_acquire_mutator_waits_for_writer_transaction(); test_recycle_before_acquire_preserves_guest(); test_retire_before_first_slot_read_skips_guest_memory(); test_namespace_mismatch_precedes_any_source_read(); diff --git a/tests/unit/kzt/test_wi601_jump_slot_single_writer_source_contract.py b/tests/unit/kzt/test_wi601_jump_slot_single_writer_source_contract.py new file mode 100644 index 00000000000..2ba80b5e1f3 --- /dev/null +++ b/tests/unit/kzt/test_wi601_jump_slot_single_writer_source_contract.py @@ -0,0 +1,370 @@ +#!/usr/bin/env python3 +import pathlib +import re +import sys + + +def fail(message: str) -> None: + raise AssertionError(message) + + +def matching_delimiter(text: str, start: int, opening: str, closing: str) -> int: + depth = 0 + quote = None + escaped = False + index = start + + while index < len(text): + char = text[index] + following = text[index + 1] if index + 1 < len(text) else "" + if quote: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == quote: + quote = None + index += 1 + continue + if char in ("'", '"'): + quote = char + index += 1 + continue + if char == "/" and following == "/": + newline = text.find("\n", index + 2) + index = len(text) if newline < 0 else newline + 1 + continue + if char == "/" and following == "*": + end = text.find("*/", index + 2) + if end < 0: + fail("unterminated C comment") + index = end + 2 + continue + if char == opening: + depth += 1 + elif char == closing: + depth -= 1 + if depth == 0: + return index + index += 1 + + fail(f"unterminated delimiter beginning at offset {start}") + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + fail(f"missing function: {signature}") + brace = text.find("{", start + len(signature)) + if brace < 0: + fail(f"missing function body: {signature}") + end = matching_delimiter(text, brace, "{", "}") + return text[brace + 1:end] + + +def decision_name(body: str, caller_name: str) -> str: + match = re.search( + r"\bkzt_jump_slot_route_caller_decision_t\s+(\w+)\s*=", body + ) + if not match: + fail(f"{caller_name}: missing caller decision") + name = match.group(1) + if not re.search( + rf"\b{re.escape(name)}\s*=\s*" + r"kzt_jump_slot_route_caller_decide\s*\(", + body, + ): + fail(f"{caller_name}: caller decision is not populated by shared policy") + return name + + +def guarded_blocks(body: str, condition_pattern: str): + blocks = [] + for match in re.finditer(r"\bif\s*\(", body): + condition_start = body.find("(", match.start()) + condition_end = matching_delimiter(body, condition_start, "(", ")") + condition = body[condition_start + 1:condition_end] + if not re.search(condition_pattern, condition, re.DOTALL): + continue + brace = condition_end + 1 + while brace < len(body) and body[brace].isspace(): + brace += 1 + if brace < len(body) and body[brace] == "{": + end = matching_delimiter(body, brace, "{", "}") + blocks.append((brace + 1, end, body[brace + 1:end])) + return blocks + + +def decision_switch(body: str, decision: str) -> str: + for match in re.finditer(r"\bswitch\s*\(", body): + start = body.find("(", match.start()) + end = matching_delimiter(body, start, "(", ")") + condition = body[start + 1:end] + if not re.search( + rf"\b{re.escape(decision)}\.slot_action\b", condition + ): + continue + brace = end + 1 + while brace < len(body) and body[brace].isspace(): + brace += 1 + if brace < len(body) and body[brace] == "{": + switch_end = matching_delimiter(body, brace, "{", "}") + return body[brace + 1:switch_end] + fail("missing switch over caller decision slot_action") + + +def assert_legacy_store_guard( + caller_name: str, body: str, decision: str, store_pattern: str +) -> None: + stores = list(re.finditer(store_pattern, body)) + if len(stores) != 1: + fail( + f"{caller_name}: expected exactly one historical direct store, " + f"found {len(stores)}" + ) + condition = ( + rf"\b{re.escape(decision)}\.slot_action\s*==\s*" + r"KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE\b" + ) + for start, end, _ in guarded_blocks(body, condition): + if start < stores[0].start() < end: + return + fail(f"{caller_name}: historical direct store is not LEGACY_WRITE-only") + + +root = pathlib.Path(sys.argv[1]).resolve() +header = (root / "target/i386/latx/include/kzt_jump_slot_route.h").read_text( + encoding="utf-8" +) +route = (root / "target/i386/latx/context/kzt_jump_slot_route.c").read_text( + encoding="utf-8" +) +elfloader = (root / "target/i386/latx/context/elfloader.c").read_text( + encoding="utf-8" +) +production = ( + root / "target/i386/latx/context/kzt_jump_slot_production.c" +).read_text(encoding="utf-8") +writer_header = ( + root / "target/i386/latx/include/kzt_patch_spike_writer.h" +).read_text(encoding="utf-8") +defer_header = ( + root / "target/i386/latx/include/kzt_rela_stub_detector.h" +).read_text(encoding="utf-8") +defer_module = ( + root / "target/i386/latx/context/kzt_rela_stub_detector.c" +).read_text(encoding="utf-8") + +for required in ( + "kzt_jump_slot_route_caller_decision_t", + "KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE", + "KZT_JUMP_SLOT_ROUTE_SLOT_ROUTE_APPLIED", + "KZT_JUMP_SLOT_ROUTE_SLOT_PRESERVE", + "slot_value_usable", +): + if required not in header: + fail(f"missing caller-decision public contract: {required}") + +signature = re.compile( + r"kzt_jump_slot_route_caller_decide\s*\(\s*" + r"int\s+route_call_succeeded\s*,\s*" + r"const\s+kzt_jump_slot_route_result_t\s*\*\s*result\s*,\s*" + r"uintptr_t\s+legacy_target\s*,\s*" + r"int\s+final_value_usable\s*\)", + re.DOTALL, +) +if not signature.search(header): + fail("caller-decision public contract has an incompatible signature") + +decision_body = function_body(route, "kzt_jump_slot_route_caller_decide(") +for required in ( + "!route_call_succeeded", "!result", "legacy_target", "result->final_value", + "final_value_usable", "KZT_JUMP_SLOT_ROUTE_BYPASS", + "KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED", + "KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED", "KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH", + "KZT_JUMP_SLOT_ROUTE_WRITE_ERROR", +): + if required not in decision_body: + fail(f"caller decision lacks mapping input: {required}") +if "ops->" in decision_body or "compare_exchange" in decision_body: + fail("caller decision must not access route operations") + + +def decision_arguments(caller_name: str, caller_body: str) -> str: + calls = list(re.finditer(r"kzt_jump_slot_route_caller_decide\s*\(", caller_body)) + if len(calls) != 1: + fail(f"{caller_name}: expected exactly one caller decision") + start = caller_body.find("(", calls[0].start()) + end = matching_delimiter(caller_body, start, "(", ")") + return re.sub(r"\s+", "", caller_body[start + 1:end]) + + +def assert_route_success_is_production_result( + caller_name: str, caller_body: str +) -> None: + if not re.search( + r"\bint\s+route_call_succeeded\s*=\s*" + r"kzt_production_jump_slot_route\s*\([\s\S]*?\)\s*" + r"==\s*0\s*;", + caller_body, + ): + fail( + f"{caller_name}: route success must be the production route == 0 " + "result" + ) + + +def detector_coordinates(body: str, value: str) -> list[str]: + coordinates = [] + pattern = re.compile( + r"kzt_rela_slot_current_is_unresolved_stub\s*\(\s*" + rf"{re.escape(value)}\s*,\s*" + r"(KZT_RELA_STUB_COORDINATE_\w+)", + re.DOTALL, + ) + return pattern.findall(body) + + +eager = function_body(elfloader, "int RelocateElfRELA(") +compatibility_writer = function_body( + elfloader, "static int kzt_eager_compatibility_write(" +) +lazy = function_body(elfloader, "void PltResolver(void)") +eager_decision = decision_name(eager, "RelocateElfRELA") +arguments = decision_arguments("RelocateElfRELA", eager) +if arguments != "route_call_succeeded,&route_result,0,final_value_usable": + fail("RelocateElfRELA: caller decision argument order is incompatible") +assert_route_success_is_production_result("RelocateElfRELA", eager) + +legacy_stores = list(re.finditer( + r"(?m)^\s*\*p\s*(?:=|\+=)\s*", eager +)) +if legacy_stores: + fail("RelocateElfRELA: KZT-aware eager path retains a direct slot store") +fallback = eager.rfind("kzt_resolve_legacy_rela_target(") +route_call = eager.find("kzt_production_jump_slot_route(") +if route_call < 0 or fallback < route_call: + fail("RelocateElfRELA: compatibility write must follow guest-first route") +for required in ( + "option_kzt || wine_option_kzt", + "__atomic_compare_exchange_n(", + "kzt_patch_spike_guard_circuit_open(", + "*slot = replacement;", +): + if required not in compatibility_writer: + fail(f"compatibility writer lacks {required}") +if eager.count("kzt_eager_compatibility_write(") < 3: + fail("RelocateElfRELA: eager compatibility writes do not share the CAS helper") +if not re.search( + r"if\s*\(\s*\(option_kzt\s*\|\|\s*wine_option_kzt\)\s*&&\s*" + r"bind\s*!=\s*STB_LOCAL\s*\)\s*\{[\s\S]*?" + r"route=GUEST_PRESERVED[\s\S]*?host_lookup=0[\s\S]*?break\s*;", + eager, +): + fail("RelocateElfRELA: KZT evidence failure can reach host lookup") + +for forbidden_lazy_writer in ( + "kzt_jump_slot_route_caller_decide(", + "kzt_production_jump_slot_route(", + "KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE", +): + if forbidden_lazy_writer in lazy: + fail( + "PltResolver: removed lazy legacy writer remains: " + f"{forbidden_lazy_writer}" + ) +if re.search(r"\*p\s*=\s*(?:offs|legacy_target)\s*;", lazy): + fail("PltResolver: historical lazy GOT direct store must be absent") +if "kzt_production_lazy_direct_route(" not in lazy: + fail("PltResolver: missing Registry-backed lazy direct route") +if "kzt_plt_resolver_enter(" not in lazy: + fail("PltResolver: missing per-object guest resolver handoff") + +# Eager deferral is shared policy: a slot can still be link-time raw or already +# runtime-rebased. The production caller must not duplicate raw-only bounds +# checks; the plan controls whether its local delta adjustment is needed. +for required in ( + "kzt_rela_jump_slot_defer_input_t", + "kzt_rela_jump_slot_defer_plan_t", + "slot_is_unresolved_stub", + "should_defer", + "should_add_delta", +): + if required not in defer_header: + fail(f"missing shared defer-plan contract: {required}") +for required in ( + "KZT_RELA_STUB_COORDINATE_LINK_TIME_RAW", + "KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED", +): + if required not in defer_module: + fail(f"shared defer plan does not recognize both coordinates: {required}") +if eager.count("kzt_rela_jump_slot_defer_plan(&defer_input)") != 1: + fail("RelocateElfRELA: expected one shared defer plan call") +if "kzt_rela_jump_slot_defer_input_t defer_input" not in eager: + fail("RelocateElfRELA: missing shared defer plan input") +if not re.search( + r"\bslot_is_unresolved_stub\s*=\s*" + r"defer_plan\.slot_is_unresolved_stub\s*;", + eager, +): + fail("RelocateElfRELA: route must receive the observed stub fact") +if not re.search( + r"kzt_production_jump_slot_route\s*\([\s\S]*?" + r"slot_observation\s*,\s*slot_is_unresolved_stub\s*,", + eager, +): + fail("RelocateElfRELA: production route does not receive observed stub fact") +if "return -1;" in eager: + fail("RelocateElfRELA: route-owned unusable slot must fail open") +if re.search( + r"kzt_rela_slot_current_is_unresolved_stub\s*\(\s*" + r"slot_observation\s*,", + eager, +): + fail("RelocateElfRELA: duplicated raw-only deferred-slot detection") +if "if (defer_plan.should_defer)" not in eager: + fail("RelocateElfRELA: shared defer plan must choose the deferred branch") +if not re.search( + r"if\s*\(\s*defer_plan\.should_add_delta\s*\)\s*\{[\s\S]*?" + r"kzt_eager_compatibility_write\s*\(", + eager, +): + fail("RelocateElfRELA: raw deferred slot rebasing bypasses the CAS helper") +if "*need_resolv = 1;" not in eager: + fail("RelocateElfRELA: deferred slots must request the resolver") +if detector_coordinates(eager, "route_result.final_value") != [ + "KZT_RELA_STUB_COORDINATE_LINK_TIME_RAW", + "KZT_RELA_STUB_COORDINATE_RUNTIME_REBASED", +]: + fail("RelocateElfRELA: final value must reject raw and rebased stubs") +if '"GUEST_PRESERVED"' not in eager: + fail("RelocateElfRELA: route-owned preserve result lacks diagnostics") + +if "mmap_lock_held" not in writer_header: + fail("permission lease must track the QEMU mapping transaction lock") + +mapping_lock = function_body(production, "production_slot_mapping_lock(") +mapping_unlock = function_body(production, "production_slot_mapping_unlock(") +permission_begin = function_body(production, "production_slot_begin_write(") +permission_end = function_body(production, "production_slot_end_write(") + +for required in ("mmap_lock();", "lease->mmap_lock_held = 1;"): + if required not in mapping_lock: + fail(f"mapping lock helper lacks required operation: {required}") +for required in ("mmap_unlock();", "lease->mmap_lock_held = 0;"): + if required not in mapping_unlock: + fail(f"mapping unlock helper lacks required operation: {required}") +if "production_slot_mapping_lock(lease)" not in permission_begin: + fail("permission transaction does not acquire the mapping lock") +if permission_begin.find("production_slot_mapping_lock(lease)") > \ + permission_begin.find("page_get_flags(guest_addr)"): + fail("permission transaction reads page flags before locking mappings") +if "production_slot_mapping_unlock(lease)" not in permission_end: + fail("permission transaction does not release the mapping lock") +if "lease->restore_attempts >= 2" not in permission_end: + fail("permission transaction cannot retain the mapping lock for one recovery") +if permission_end.find("production_slot_mapping_unlock(lease)") < \ + permission_end.find("target_mprotect("): + fail("permission transaction releases mappings before restoring permissions") + +print("WI-601 eager/lazy jump-slot single-writer source contract: PASS") diff --git a/tests/unit/kzt/test_wi601_lazy_legacy_removal_source_contract.py b/tests/unit/kzt/test_wi601_lazy_legacy_removal_source_contract.py new file mode 100644 index 00000000000..e516a4d09aa --- /dev/null +++ b/tests/unit/kzt/test_wi601_lazy_legacy_removal_source_contract.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +import pathlib +import re +import sys + + +def matching_brace(text: str, opening: int) -> int: + depth = 0 + quote = None + escaped = False + index = opening + + while index < len(text): + char = text[index] + following = text[index + 1] if index + 1 < len(text) else "" + if quote: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == quote: + quote = None + index += 1 + continue + if char in ("'", '"'): + quote = char + index += 1 + continue + if char == "/" and following == "/": + newline = text.find("\n", index + 2) + index = len(text) if newline < 0 else newline + 1 + continue + if char == "/" and following == "*": + end = text.find("*/", index + 2) + if end < 0: + raise AssertionError("unterminated C comment") + index = end + 2 + continue + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + return index + index += 1 + + raise AssertionError("unterminated function body") + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start + len(signature)) + if opening < 0: + raise AssertionError(f"missing function body: {signature}") + return text[opening + 1:matching_brace(text, opening)] + + +root = pathlib.Path(sys.argv[1]).resolve() +elfloader = ( + root / "target/i386/latx/context/elfloader.c" +).read_text(encoding="utf-8") +production = ( + root / "target/i386/latx/context/kzt_jump_slot_production.c" +).read_text(encoding="utf-8") +production_header = ( + root / "target/i386/latx/include/kzt_jump_slot_production.h" +).read_text(encoding="utf-8") + +resolver = function_body(elfloader, "void PltResolver(void)") +direct_route = function_body( + production, "int kzt_production_lazy_direct_route(" +) +declaration_start = production_header.find( + "int kzt_production_lazy_direct_route(" +) +declaration_end = production_header.find(");", declaration_start) +if declaration_start < 0 or declaration_end < 0: + raise AssertionError("missing lazy direct production declaration") +declaration = production_header[declaration_start:declaration_end + 2] + +violations = { + "legacy_host_lookup_calls": resolver.count( + "plt_resolver_lookup_host_symbol(" + ), + "legacy_get_alternate_calls": resolver.count("getAlternate("), + "legacy_got_write_count": len( + re.findall(r"\*p\s*=\s*(?:offs|legacy_target)\s*;", resolver) + ), + "legacy_write_action_mentions": resolver.count( + "KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE" + ), + "global_guest_resolver_fallbacks": resolver.count( + "Push64(cpu, dl_runtime_resolver)" + ), + "direct_route_legacy_binding_lookups": direct_route.count( + "kzt_guest_library_access_lookup_by_library(" + ), + "direct_route_external_provider_inputs": int( + "library_t *resolved_provider" in declaration + ), + "direct_route_external_target_inputs": int( + "uintptr_t resolved_target" in declaration + ), +} + +remaining = {name: count for name, count in violations.items() if count} +if remaining: + details = " ".join( + f"{name}={count}" for name, count in remaining.items() + ) + raise AssertionError( + "WI-601 lazy legacy removal contract RED: " + details + ) + +if "kzt_guest_library_access_lookup(" not in direct_route: + raise AssertionError( + "lazy direct route must acquire the provider by exact guest key" + ) +if "kzt_guest_symbol_scope_discover(" not in direct_route: + raise AssertionError( + "lazy direct route must discover its provider from guest scope" + ) +if "symbol_index >= head->numDynSym" not in direct_route: + raise AssertionError( + "lazy direct route must reject out-of-range dynamic symbols" + ) + +print("WI-601 lazy legacy removal source contract: PASS") diff --git a/tests/unit/kzt/test_wi602_handle_state_removal_source_contract.py b/tests/unit/kzt/test_wi602_handle_state_removal_source_contract.py new file mode 100644 index 00000000000..cb5cf32d1ca --- /dev/null +++ b/tests/unit/kzt/test_wi602_handle_state_removal_source_contract.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def matching_brace(text: str, opening: int) -> int: + depth = 0 + for index in range(opening, len(text)): + if text[index] == "{": + depth += 1 + elif text[index] == "}": + depth -= 1 + if depth == 0: + return index + raise AssertionError("unterminated function") + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start) + if opening < 0: + raise AssertionError(f"missing function body: {signature}") + return text[opening + 1:matching_brace(text, opening)] + + +root = pathlib.Path(sys.argv[1]).resolve() +header = (root / "target/i386/latx/include/box64context.h").read_text( + encoding="utf-8" +) +api = (root / "target/i386/latx/context/kzt_guest_dl_api.c").read_text( + encoding="utf-8" +) +dlprivate_start = header.find("typedef struct dlprivate_s {") +dlprivate_end = header.find("} dlprivate_t;", dlprivate_start) +if dlprivate_start < 0 or dlprivate_end < 0: + raise AssertionError("dlprivate_t declaration is missing") +dlprivate = header[dlprivate_start:dlprivate_end] + +for legacy_field in ("libs;", "count;", "dlopened;", "dlx86handle;", "lib_sz;", "lib_cap;"): + if legacy_field in dlprivate: + raise AssertionError(f"dlprivate_t retains legacy state: {legacy_field}") + +if "kzt_guest_dl_api_translate_handle" in api: + raise AssertionError("shared guest dl API retains old handle translation") + +for signature, guest_call in ( + ("kzt_guest_dl_api_dlsym(", "kzt_guest_library_run_dlsym"), + ("kzt_guest_dl_api_dlvsym(", "kzt_guest_library_run_dlvsym"), + ("kzt_guest_dl_api_dlinfo(", "kzt_guest_library_run_dlinfo"), +): + body = function_body(api, signature) + if guest_call not in body: + raise AssertionError(f"{signature} does not call guest loader") + if "guest_handle" in body: + raise AssertionError(f"{signature} retains a translated handle") + +print("WI-602 legacy handle state removal source contract: PASS") diff --git a/tests/unit/kzt/test_wi603_callback_replacement_source_contract.py b/tests/unit/kzt/test_wi603_callback_replacement_source_contract.py new file mode 100644 index 00000000000..89878c16511 --- /dev/null +++ b/tests/unit/kzt/test_wi603_callback_replacement_source_contract.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start + len(signature)) + if opening < 0: + raise AssertionError(f"missing function body: {signature}") + depth = 0 + for index in range(opening, len(text)): + if text[index] == "{": + depth += 1 + elif text[index] == "}": + depth -= 1 + if depth == 0: + return text[opening + 1:index] + raise AssertionError(f"unterminated function body: {signature}") + + +root = pathlib.Path(sys.argv[1]).resolve() +myalign = (root / "target/i386/latx/context/myalign.c").read_text( + encoding="utf-8" +) + +callback = function_body(myalign, "static void kzt_tb_callback(") +if "kzt_loader_event_hook_publish(" not in callback: + raise AssertionError("loader event is not published through the hook seam") +consumer = function_body(myalign, "static void kzt_tb_callback_consume(") +if ".per_object_flow = kzt_tb_callback_per_object_got_plt," not in consumer: + raise AssertionError("loader event does not retain Registry per-object flow") +if ".per_object_opaque = &env->kzt_guest_library_loader_scope," not in consumer: + raise AssertionError("per-object flow does not receive scoped binding state") +if ".legacy_flow = NULL," not in consumer: + raise AssertionError("KZT loader event still enables legacy callback flow") +for forbidden in ( + "kzt_observation_legacy_result_t", + "kzt_tb_callback_" + "legacy_state_t", +): + if forbidden in consumer: + raise AssertionError(f"KZT loader event retains legacy state: {forbidden}") + +per_object = function_body( + myalign, "static int kzt_tb_callback_per_object_got_plt(" +) +materialize = function_body( + myalign, "static int kzt_tb_callback_materialize_binding(" +) +if "kzt_tb_callback_materialize_binding(link_map_addr, opaque)" not in per_object: + raise AssertionError("per-object flow does not publish its library binding") +for required in ( + "AddNeededLibWithLibrary(", + "kzt_guest_library_note_loader_pair_pending(", + "kzt_guest_library_note_loader_pair(", +): + if required not in materialize: + raise AssertionError(f"materialization misses binding publication: {required}") +for forbidden in ("LoadAndCheckElfHeader", "LoadNeededLibs", "RelocateElf"): + if forbidden in materialize: + raise AssertionError(f"materialization retains raw ELF work: {forbidden}") + +bridge = function_body(myalign, "void init_tb_callback_bridge(") +if "kzt_tb_callback" not in bridge or "ld_info->addr" not in bridge: + raise AssertionError("versioned loader event hook is missing") +for forbidden in ("exec_entry", "jmpinst_exec", "kzt_exectb_callback"): + if forbidden in bridge: + raise AssertionError(f"bridge retains obsolete exec-entry hook: {forbidden}") + +for forbidden in ( + "static void kzt_exectb_callback(", + "static void finiReFlesh(", + "static void test_x86free(", +): + if forbidden in myalign: + raise AssertionError(f"KZT source retains obsolete exec path: {forbidden}") + +print("WI-603 callback replacement source contract: PASS") diff --git a/tests/unit/kzt/test_wi603_loader_hook_timing_source_contract.py b/tests/unit/kzt/test_wi603_loader_hook_timing_source_contract.py new file mode 100644 index 00000000000..1fbb5f8019a --- /dev/null +++ b/tests/unit/kzt/test_wi603_loader_hook_timing_source_contract.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start) + if opening < 0: + raise AssertionError(f"missing body: {signature}") + depth = 0 + for index in range(opening, len(text)): + if text[index] == "{": + depth += 1 + elif text[index] == "}": + depth -= 1 + if depth == 0: + return text[opening + 1:index] + raise AssertionError(f"unterminated body: {signature}") + + +root = pathlib.Path(sys.argv[1]).resolve() +myalign = (root / "target/i386/latx/context/myalign.c").read_text( + encoding="utf-8" +) +adapter = (root / "target/i386/latx/context/kzt_observation_adapter.c").read_text( + encoding="utf-8" +) + +locator = function_body(myalign, "static struct x86_ld_info * find_ld_part(") +for required in ( + "_dl_relocate_object_end", + "searchpopret_part", + "all_part", + "ret->addr = (uintptr_t) ld_find;", + "ret->reg = (*(ld_find+ 2)) & 0xf;", +): + if required not in locator: + raise AssertionError(f"loader hook locator misses {required}") + +bridge = function_body(myalign, "void init_tb_callback_bridge(") +if "find_ld_bridge(info, build_id)" not in bridge: + raise AssertionError("bridge does not derive hook metadata from guest loader") +if "kzt_loader_event_hook_install(" not in bridge: + raise AssertionError("bridge does not version-gate the hook installation") +if "ld_info->addr" not in bridge or "kzt_tb_callback" not in bridge: + raise AssertionError("loader hook does not install the callback at its locator") + +callback = function_body(myalign, "static void kzt_tb_callback(") +if "env->regs[R_EAX + ld_info->reg]" not in callback: + raise AssertionError("loader hook does not capture link_map from located register") +if "kzt_loader_event_hook_publish(" not in callback: + raise AssertionError("loader hook does not publish through the event seam") +if "kzt_tb_callback_consume(env, &event)" not in callback: + raise AssertionError("loader hook does not hand events to the consumer") + +consumer = function_body(myalign, "static void kzt_tb_callback_consume(") +for required in ( + ".link_map_addr = link_map_addr,", + ".loader_scope = &env->kzt_guest_library_loader_scope,", + ".reuse_complete_dynamic_view = 1,", + ".per_object_flow = kzt_tb_callback_per_object_got_plt,", + ".legacy_flow = NULL,", + "kzt_observe_guest_object_from_callback(&request, &observation_result)", +): + if required not in consumer: + raise AssertionError(f"loader consumer misses handoff input {required}") + +observe = function_body(adapter, "int kzt_observe_guest_object_from_callback(") +observe_call = observe.find("kzt_observe_guest_object(request,") +per_object_call = observe.find("request->per_object_flow(request->link_map_addr,") +if observe_call < 0 or per_object_call < 0 or observe_call >= per_object_call: + raise AssertionError("per-object injection is not sequenced after observation") +if "kzt_guest_library_callback_access_begin_scoped" not in observe: + raise AssertionError("loader callback lacks an unload/address gate") + +print("WI-603 loader hook timing source contract: PASS") diff --git a/tests/unit/kzt/test_wi837_lazy_direct_route.c b/tests/unit/kzt/test_wi837_lazy_direct_route.c new file mode 100644 index 00000000000..02ec5f10a3e --- /dev/null +++ b/tests/unit/kzt/test_wi837_lazy_direct_route.c @@ -0,0 +1,708 @@ +#include +#include + +#include "elf.h" +#include "target/i386/latx/include/kzt_lazy_direct_route.h" + +typedef struct fixture { + uintptr_t slot; + uintptr_t bridge_target; + int source_valid; + int provider_available; + int provider_generation_delta; + int bridge_available; + int wrong_bridge_version; + int lease_available; + int final_valid; + kzt_lazy_direct_route_cas_status_t cas_status; + int source_calls; + int provider_acquire_calls; + int provider_release_calls; + int bridge_calls; + int lease_acquire_calls; + int lease_release_calls; + int final_validate_calls; + int cas_calls; + int write_calls; + uintptr_t cas_expected; +} fixture_t; + +static int failures; + +#define CHECK(name, condition) do { \ + if (!(condition)) { \ + fprintf(stderr, "%s failed at line %d\n", name, __LINE__); \ + ++failures; \ + } \ +} while (0) + +static int validate_source(const kzt_lazy_direct_route_input_t *input, + void *opaque) +{ + fixture_t *fixture = opaque; + + ++fixture->source_calls; + CHECK("source.generation", + input->source.generation == + input->source_dynamic_view_generation); + return fixture->source_valid; +} + +static int acquire_provider(const kzt_lazy_direct_route_input_t *input, + kzt_lazy_direct_route_provider_t *provider, + void *opaque) +{ + fixture_t *fixture = opaque; + + ++fixture->provider_acquire_calls; + if (!fixture->provider_available) { + return -1; + } + *provider = (kzt_lazy_direct_route_provider_t) { + .handle = fixture, + .link_map_addr = input->provider.link_map_addr, + .generation = input->provider.generation + + fixture->provider_generation_delta, + .namespace_id = input->namespace_id, + .namespace_kind = input->namespace_kind, + }; + return 0; +} + +static void release_provider(kzt_lazy_direct_route_provider_t *provider, + void *opaque) +{ + fixture_t *fixture = opaque; + + CHECK("provider.release-handle", provider->handle == fixture); + ++fixture->provider_release_calls; + memset(provider, 0, sizeof(*provider)); +} + +static int find_wrapper_bridge( + const kzt_lazy_direct_route_input_t *input, + const kzt_lazy_direct_route_provider_t *provider, + kzt_lazy_direct_route_bridge_t *bridge, + void *opaque) +{ + fixture_t *fixture = opaque; + + ++fixture->bridge_calls; + CHECK("bridge.provider-live", provider->handle == fixture); + if (!fixture->bridge_available) { + return -1; + } + *bridge = (kzt_lazy_direct_route_bridge_t) { + .target = fixture->bridge_target, + .version_evidence = input->version_evidence, + .version = fixture->wrong_bridge_version ? + "GLIBC_2.2.5" : input->version, + }; + return 0; +} + +static int acquire_decision_lease( + const kzt_lazy_direct_route_input_t *input, + const kzt_lazy_direct_route_provider_t *provider, + kzt_lazy_direct_route_lease_t *lease, + void *opaque) +{ + fixture_t *fixture = opaque; + + (void)input; + ++fixture->lease_acquire_calls; + CHECK("lease.provider-live", provider->handle == fixture); + if (!fixture->lease_available) { + return -1; + } + lease->handle = fixture; + lease->active = 1; + return 0; +} + +static void release_decision_lease(kzt_lazy_direct_route_lease_t *lease, + void *opaque) +{ + fixture_t *fixture = opaque; + + CHECK("lease.release-active", + lease->active && lease->handle == fixture); + ++fixture->lease_release_calls; + memset(lease, 0, sizeof(*lease)); +} + +static int validate_final( + const kzt_lazy_direct_route_input_t *input, + const kzt_lazy_direct_route_provider_t *provider, + const kzt_lazy_direct_route_bridge_t *bridge, + const kzt_lazy_direct_route_lease_t *lease, + void *opaque) +{ + fixture_t *fixture = opaque; + + ++fixture->final_validate_calls; + CHECK("final.slot", input->slot_addr == (uintptr_t)&fixture->slot); + CHECK("final.provider-live", provider->handle == fixture); + CHECK("final.bridge", bridge->target == fixture->bridge_target); + CHECK("final.lease", lease->active && lease->handle == fixture); + return fixture->final_valid; +} + +static kzt_lazy_direct_route_cas_status_t cas_slot( + uintptr_t slot_addr, + uintptr_t expected, + uintptr_t replacement, + const kzt_lazy_direct_route_lease_t *lease, + void *opaque) +{ + fixture_t *fixture = opaque; + + ++fixture->cas_calls; + fixture->cas_expected = expected; + CHECK("cas.address", slot_addr == (uintptr_t)&fixture->slot); + CHECK("cas.lease", lease->active && lease->handle == fixture); + if (fixture->cas_status != KZT_LAZY_DIRECT_ROUTE_CAS_APPLIED || + fixture->slot != expected) { + return fixture->cas_status; + } + fixture->slot = replacement; + ++fixture->write_calls; + return KZT_LAZY_DIRECT_ROUTE_CAS_APPLIED; +} + +static fixture_t fixture(void) +{ + return (fixture_t) { + .slot = 0x71000100, + .bridge_target = 0x72000200, + .source_valid = 1, + .provider_available = 1, + .bridge_available = 1, + .lease_available = 1, + .final_valid = 1, + .cas_status = KZT_LAZY_DIRECT_ROUTE_CAS_APPLIED, + }; +} + +static kzt_guest_dynamic_view_t complete_dynamic_view(void) +{ + kzt_guest_dynamic_view_t view = { + .dynamic_addr = 0x40001000, + .load_bias = 0x40000000, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .entry_count = 16, + .has_null = 1, + .symtab = { 1, 0x40002000, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS }, + .strtab = { 1, 0x40003000, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS }, + .syment = { 1, 24, KZT_GUEST_DYNAMIC_SCALAR }, + .versym = { 1, 0x40004000, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS }, + .verneed = { 1, 0x40005000, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS }, + .verneednum = { 1, 1, KZT_GUEST_DYNAMIC_SCALAR }, + .jmprel = { 1, 0x40006000, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS }, + .pltrelsz = { 1, 24, KZT_GUEST_DYNAMIC_SCALAR }, + .pltrel = { 1, 7, KZT_GUEST_DYNAMIC_SCALAR }, + .pltgot = { 1, 0x40007000, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS }, + }; + + return view; +} + +static kzt_lazy_direct_route_input_t input_for( + fixture_t *fixture, + const kzt_guest_dynamic_view_t *view) +{ + return (kzt_lazy_direct_route_input_t) { + .enabled = 1, + .preemption_safe = 1, + .namespace_id = 0, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + .source = { 0x1000, 7 }, + .provider = { 0x2000, 11 }, + .source_dynamic_view = view, + .source_dynamic_view_generation = 7, + .symbol = "dlerror", + .version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .version = "GLIBC_2.34", + .slot_addr = (uintptr_t)&fixture->slot, + .guest_unresolved_slot = fixture->slot, + .expected_current_slot = fixture->slot, + }; +} + +static kzt_lazy_direct_route_ops_t ops_for(fixture_t *fixture) +{ + return (kzt_lazy_direct_route_ops_t) { + .validate_source = validate_source, + .acquire_provider = acquire_provider, + .release_provider = release_provider, + .find_wrapper_bridge = find_wrapper_bridge, + .acquire_decision_lease = acquire_decision_lease, + .release_decision_lease = release_decision_lease, + .validate_final = validate_final, + .cas_slot = cas_slot, + .opaque = fixture, + }; +} + +static void test_only_strong_global_binding_is_eligible(void) +{ + CHECK("binding.global", + kzt_lazy_direct_symbol_binding_supported( + ELF_ST_INFO(STB_GLOBAL, STT_FUNC))); + CHECK("binding.weak", + !kzt_lazy_direct_symbol_binding_supported( + ELF_ST_INFO(STB_WEAK, STT_FUNC))); + CHECK("binding.local", + !kzt_lazy_direct_symbol_binding_supported( + ELF_ST_INFO(STB_LOCAL, STT_FUNC))); +} + +static void test_complete_evidence_applies_native_once(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + + CHECK("success.status", + kzt_lazy_direct_route_apply(&input, &ops, &result) == + KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED); + CHECK("success.result", + result.status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED && + result.reason == KZT_LAZY_DIRECT_ROUTE_REASON_NATIVE_APPLIED && + result.selected_target == f.bridge_target); + CHECK("success.slot", f.slot == f.bridge_target); + CHECK("success.one-write", f.write_calls == 1 && f.cas_calls == 1); + CHECK("success.source", f.source_calls == 1); + CHECK("success.provider", + f.provider_acquire_calls == 1 && + f.provider_release_calls == 1); + CHECK("success.bridge", f.bridge_calls == 1); + CHECK("success.lease", + f.lease_acquire_calls == 1 && + f.lease_release_calls == 1); + CHECK("success.final", f.final_validate_calls == 1); +} + +static void test_managed_bridge_uses_current_value_for_cas(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t guest_unresolved_slot = f.slot; + uintptr_t managed_bridge = 0x71000900; + + f.slot = managed_bridge; + input.guest_unresolved_slot = guest_unresolved_slot; + input.expected_current_slot = managed_bridge; + CHECK("managed.status", + kzt_lazy_direct_route_apply(&input, &ops, &result) == + KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED); + CHECK("managed.cas-current", + f.cas_expected == managed_bridge && + f.slot == f.bridge_target && f.write_calls == 1); +} + +static void check_guest_required( + const char *name, + kzt_lazy_direct_route_status_t status, + const kzt_lazy_direct_route_result_t *result, + kzt_lazy_direct_route_reason_t reason, + const fixture_t *fixture, + uintptr_t expected_slot) +{ + if (status != KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED || + result->status != KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED || + result->reason != reason || result->selected_target != 0 || + fixture->slot != expected_slot || fixture->write_calls != 0) { + fprintf(stderr, "%s failed at line %d\n", name, __LINE__); + ++failures; + } +} + +static void test_disabled_requires_guest_without_callbacks(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + input.enabled = 0; + check_guest_required( + "disabled", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_DISABLED, &f, original); + CHECK("disabled.no-callbacks", + f.source_calls == 0 && f.provider_acquire_calls == 0 && + f.bridge_calls == 0 && f.lease_acquire_calls == 0 && + f.final_validate_calls == 0 && f.cas_calls == 0); +} + +static void test_unproven_preemption_requires_guest_without_callbacks(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + input.preemption_safe = 0; + check_guest_required( + "preemption-unproven", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_PREEMPTION_UNPROVEN, + &f, original); + CHECK("preemption-unproven.no-callbacks", + f.source_calls == 0 && f.provider_acquire_calls == 0 && + f.bridge_calls == 0 && f.lease_acquire_calls == 0 && + f.final_validate_calls == 0 && f.cas_calls == 0); +} + +static void test_non_main_namespace_requires_guest(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + input.namespace_id = 3; + input.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_EXPLICIT; + check_guest_required( + "namespace", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_NON_MAIN_NAMESPACE, + &f, original); + CHECK("namespace.no-source", f.source_calls == 0); +} + +static void test_unknown_and_error_version_require_guest(void) +{ + const kzt_symbol_version_evidence_t evidence[] = { + KZT_SYMBOL_VERSION_UNKNOWN, + KZT_SYMBOL_VERSION_ERROR, + }; + size_t i; + + for (i = 0; i < sizeof(evidence) / sizeof(evidence[0]); ++i) { + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + input.version_evidence = evidence[i]; + check_guest_required( + "version-evidence", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_INVALID_VERSION, + &f, original); + CHECK("version-evidence.no-source", f.source_calls == 0); + } +} + +static void test_confirmed_unversioned_evidence_can_apply(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + + input.version_evidence = + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + input.version = NULL; + memset(&view.versym, 0, sizeof(view.versym)); + memset(&view.verneed, 0, sizeof(view.verneed)); + memset(&view.verneednum, 0, sizeof(view.verneednum)); + CHECK("unversioned.status", + kzt_lazy_direct_route_apply(&input, &ops, &result) == + KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED); + CHECK("unversioned.write", + f.write_calls == 1 && f.slot == f.bridge_target); +} + +static void test_wrong_wrapper_version_requires_guest(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + f.wrong_bridge_version = 1; + check_guest_required( + "wrong-wrapper-version", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, + KZT_LAZY_DIRECT_ROUTE_REASON_BRIDGE_VERSION_MISMATCH, + &f, original); + CHECK("wrong-wrapper-version.release", + f.provider_release_calls == 1 && + f.lease_acquire_calls == 0 && f.cas_calls == 0); +} + +static void test_source_failure_requires_guest(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + f.source_valid = 0; + check_guest_required( + "source-failure", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_SOURCE_REJECTED, + &f, original); + CHECK("source-failure.stops", + f.source_calls == 1 && f.provider_acquire_calls == 0); +} + +static void test_source_generation_mismatch_requires_guest(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + input.source_dynamic_view_generation = + input.source.generation + 1; + check_guest_required( + "source-generation", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_INVALID_INPUT, + &f, original); + CHECK("source-generation.no-source", f.source_calls == 0); +} + +static void test_provider_failure_requires_guest(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + f.provider_available = 0; + check_guest_required( + "provider-failure", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_PROVIDER_UNAVAILABLE, + &f, original); + CHECK("provider-failure.no-release", + f.provider_acquire_calls == 1 && + f.provider_release_calls == 0 && f.bridge_calls == 0); +} + +static void test_provider_generation_mismatch_requires_guest(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + f.provider_generation_delta = 1; + check_guest_required( + "provider-generation", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_PROVIDER_MISMATCH, + &f, original); + CHECK("provider-generation.release", + f.provider_release_calls == 1 && f.bridge_calls == 0); +} + +static void test_incomplete_dynamic_view_requires_guest(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + view.jmprel.present = 0; + check_guest_required( + "dynamic-view", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, + KZT_LAZY_DIRECT_ROUTE_REASON_INCOMPLETE_DYNAMIC_VIEW, + &f, original); + CHECK("dynamic-view.no-source", f.source_calls == 0); +} + +static void test_missing_bridge_requires_guest(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + f.bridge_available = 0; + check_guest_required( + "bridge-missing", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_BRIDGE_UNAVAILABLE, + &f, original); + CHECK("bridge-missing.release", + f.provider_release_calls == 1 && + f.lease_acquire_calls == 0); +} + +static void test_lease_failure_requires_guest(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + f.lease_available = 0; + check_guest_required( + "lease-failure", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_LEASE_UNAVAILABLE, + &f, original); + CHECK("lease-failure.release", + f.lease_acquire_calls == 1 && + f.lease_release_calls == 0 && + f.provider_release_calls == 1); +} + +static void test_final_validation_failure_requires_guest(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + f.final_valid = 0; + check_guest_required( + "final-validation", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, + KZT_LAZY_DIRECT_ROUTE_REASON_FINAL_VALIDATION_FAILED, + &f, original); + CHECK("final-validation.release", + f.final_validate_calls == 1 && f.cas_calls == 0 && + f.lease_release_calls == 1 && + f.provider_release_calls == 1); +} + +static void test_guest_owned_dlclose_requires_guest_without_callbacks(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + input.symbol = "dlclose"; + check_guest_required( + "guest-dlclose", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_GUEST_OWNED_SYMBOL, + &f, original); + CHECK("guest-dlclose.no-callbacks", + f.source_calls == 0 && + f.provider_acquire_calls == 0 && + f.bridge_calls == 0 && + f.lease_acquire_calls == 0 && + f.final_validate_calls == 0 && + f.cas_calls == 0); +} + +static void test_cas_mismatch_requires_guest_without_writing(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t competitor = 0x73000300; + + f.slot = competitor; + f.cas_status = KZT_LAZY_DIRECT_ROUTE_CAS_MISMATCH; + check_guest_required( + "cas-mismatch", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_CAS_MISMATCH, + &f, competitor); + CHECK("cas-mismatch.one-attempt", + f.cas_calls == 1 && f.write_calls == 0 && + f.lease_release_calls == 1 && + f.provider_release_calls == 1); +} + +static void test_cas_error_requires_guest_without_writing(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + f.cas_status = KZT_LAZY_DIRECT_ROUTE_CAS_ERROR; + check_guest_required( + "cas-error", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_CAS_ERROR, + &f, original); + CHECK("cas-error.one-attempt", + f.cas_calls == 1 && f.write_calls == 0); +} + +int main(void) +{ + test_only_strong_global_binding_is_eligible(); + test_complete_evidence_applies_native_once(); + test_managed_bridge_uses_current_value_for_cas(); + test_guest_owned_dlclose_requires_guest_without_callbacks(); + test_disabled_requires_guest_without_callbacks(); + test_unproven_preemption_requires_guest_without_callbacks(); + test_non_main_namespace_requires_guest(); + test_unknown_and_error_version_require_guest(); + test_confirmed_unversioned_evidence_can_apply(); + test_wrong_wrapper_version_requires_guest(); + test_source_failure_requires_guest(); + test_source_generation_mismatch_requires_guest(); + test_provider_failure_requires_guest(); + test_provider_generation_mismatch_requires_guest(); + test_incomplete_dynamic_view_requires_guest(); + test_missing_bridge_requires_guest(); + test_lease_failure_requires_guest(); + test_final_validation_failure_requires_guest(); + test_cas_mismatch_requires_guest_without_writing(); + test_cas_error_requires_guest_without_writing(); + + if (failures) { + fprintf(stderr, "%d WI-837 lazy direct route checks failed\n", + failures); + return 1; + } + puts("WI-837 lazy direct route checks passed"); + return 0; +} diff --git a/tests/unit/kzt/test_wi962_guest_lifecycle_source_contract.py b/tests/unit/kzt/test_wi962_guest_lifecycle_source_contract.py new file mode 100644 index 00000000000..c9b7ac69bd2 --- /dev/null +++ b/tests/unit/kzt/test_wi962_guest_lifecycle_source_contract.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def matching_brace(text: str, opening: int) -> int: + depth = 0 + quote = None + escaped = False + index = opening + + while index < len(text): + char = text[index] + following = text[index + 1] if index + 1 < len(text) else "" + if quote: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == quote: + quote = None + index += 1 + continue + if char in ("'", '"'): + quote = char + index += 1 + continue + if char == "/" and following == "/": + newline = text.find("\n", index + 2) + index = len(text) if newline < 0 else newline + 1 + continue + if char == "/" and following == "*": + end = text.find("*/", index + 2) + if end < 0: + raise AssertionError("unterminated C comment") + index = end + 2 + continue + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + return index + index += 1 + + raise AssertionError("unterminated function body") + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start + len(signature)) + if opening < 0: + raise AssertionError(f"missing function body: {signature}") + return text[opening + 1:matching_brace(text, opening)] + + +def assert_before(body: str, first: str, second: str, label: str) -> None: + first_index = body.find(first) + second_index = body.find(second) + if first_index < 0: + raise AssertionError(f"{label}: missing {first}") + if second_index < 0: + raise AssertionError(f"{label}: missing {second}") + if first_index >= second_index: + raise AssertionError( + f"{label}: {first} must appear before {second}" + ) + + +root = pathlib.Path(sys.argv[1]).resolve() +planner_header = ( + root / "target/i386/latx/include/kzt_patch_planner.h" +).read_text(encoding="utf-8") +planner = ( + root / "target/i386/latx/context/kzt_patch_planner.c" +).read_text(encoding="utf-8") +direct_route = ( + root / "target/i386/latx/context/kzt_lazy_direct_route.c" +).read_text(encoding="utf-8") +production = ( + root / "target/i386/latx/context/kzt_jump_slot_production.c" +).read_text(encoding="utf-8") + +policy = function_body( + planner_header, "static inline int kzt_patch_symbol_must_stay_guest(" +) +if 'strcmp(symbol_name, "dlclose") == 0' not in policy: + raise AssertionError("guest-owned policy must preserve dlclose") + +planner_decide = function_body(planner, "int kzt_patch_planner_decide(") +assert_before( + planner_decide, + "kzt_patch_symbol_must_stay_guest", + "kzt_symbol_version_evidence_valid", + "patch planner", +) +if "KZT_PATCH_REASON_POLICY_KEEP_GUEST" not in planner_decide: + raise AssertionError("patch planner must report keep-guest policy") + +route_apply = function_body( + direct_route, "kzt_lazy_direct_route_status_t kzt_lazy_direct_route_apply(" +) +assert_before( + route_apply, + "kzt_patch_symbol_must_stay_guest", + "ops->validate_source", + "lazy direct route", +) +if "KZT_LAZY_DIRECT_ROUTE_REASON_GUEST_OWNED_SYMBOL" not in route_apply: + raise AssertionError("lazy route must report guest-owned symbol") + +production_route = function_body( + production, "int kzt_production_lazy_direct_route(" +) +assert_before( + production_route, + "kzt_patch_symbol_must_stay_guest", + "KztGuestRegistryForContext", + "production lazy route", +) + +print("WI-962 guest lifecycle source contract: PASS") diff --git a/tests/unit/kzt/test_wi963_guest_symbol_source_contract.py b/tests/unit/kzt/test_wi963_guest_symbol_source_contract.py new file mode 100644 index 00000000000..19834e88e23 --- /dev/null +++ b/tests/unit/kzt/test_wi963_guest_symbol_source_contract.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def strip_if_zero(text: str) -> str: + output = [] + disabled_depth = 0 + for line in text.splitlines(keepends=True): + directive = line.strip() + if disabled_depth: + if directive.startswith(("#if ", "#ifdef ", "#ifndef ")): + disabled_depth += 1 + elif directive == "#endif": + disabled_depth -= 1 + continue + if directive == "#if 0": + disabled_depth = 1 + continue + output.append(line) + if disabled_depth: + raise AssertionError("unterminated #if 0 block") + return "".join(output) + + +def matching_brace(text: str, opening: int) -> int: + depth = 0 + quote = None + escaped = False + index = opening + + while index < len(text): + char = text[index] + following = text[index + 1] if index + 1 < len(text) else "" + if quote: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == quote: + quote = None + index += 1 + continue + if char in ("'", '"'): + quote = char + index += 1 + continue + if char == "/" and following == "/": + newline = text.find("\n", index + 2) + index = len(text) if newline < 0 else newline + 1 + continue + if char == "/" and following == "*": + end = text.find("*/", index + 2) + if end < 0: + raise AssertionError("unterminated C comment") + index = end + 2 + continue + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + return index + index += 1 + + raise AssertionError("unterminated function body") + + +def function_body(text: str, signature: str) -> str: + start = 0 + while True: + start = text.find(signature, start) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start + len(signature)) + semicolon = text.find(";", start + len(signature)) + if opening >= 0 and (semicolon < 0 or opening < semicolon): + return text[opening + 1:matching_brace(text, opening)] + start += len(signature) + + +def assert_before(body: str, first: str, second: str, label: str) -> None: + first_index = body.find(first) + second_index = body.find(second) + if first_index < 0 or second_index < 0 or first_index >= second_index: + raise AssertionError(f"{label}: {first} must precede {second}") + + +root = pathlib.Path(sys.argv[1]).resolve() +context_header = ( + root / "target/i386/latx/include/box64context.h" +).read_text(encoding="utf-8") +state_header = ( + root / "target/i386/latx/include/kzt_guest_dl_state.h" +).read_text(encoding="utf-8") +cpu_header = (root / "target/i386/cpu.h").read_text(encoding="utf-8") +common_source = ( + root / "target/i386/latx/context/kzt_guest_dl_api.c" +).read_text(encoding="utf-8") +init_source = ( + root / "target/i386/latx/context/kzt_guest_dl_init.c" +).read_text(encoding="utf-8") +common_dlsym = function_body( + common_source, "kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlsym(") +common_dlvsym = function_body( + common_source, "kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlvsym(") +common_dlerror = function_body( + common_source, "kzt_guest_dlerror_result_t kzt_guest_dl_api_dlerror(") + +assert_before( + common_dlsym, + "kzt_guest_library_run_dlsym", + "kzt_guest_library_select_symbol_result", + "shared guest dlsym", +) +assert_before( + common_dlvsym, + "kzt_guest_library_run_dlvsym", + "kzt_guest_library_select_symbol_result", + "shared guest dlvsym", +) +if "version" not in common_dlvsym: + raise AssertionError("shared guest dlvsym drops the version") +if "kzt_guest_library_run_dlerror" not in common_dlerror: + raise AssertionError("shared guest dlerror does not consume guest state") +if "last_error_returned" not in common_dlerror: + raise AssertionError("shared local dlerror is not one-shot") + +for relative in ( + "target/i386/latx/context/wrappedlibc.c", + "target/i386/latx/context/wrappedlibdl.c", +): + source = strip_if_zero((root / relative).read_text(encoding="utf-8")) + dlsym = function_body(source, "void* my_dlsym(") + dlvsym = function_body(source, "void* my_dlvsym(") + dlerror = function_body(source, "char* my_dlerror(void)") + dlerror_slow = function_body( + source, "static char *kzt_guest_dlerror_slow_path(" + ) + + if "init_x86dlfun" in source: + raise AssertionError(f"{relative}: retains private guest dl init") + if "kzt_guest_dl_entries_for_call" not in source: + raise AssertionError(f"{relative}: bypasses shared guest dl init") + + if "kzt_guest_dl_api_dlsym" not in dlsym: + raise AssertionError(f"{relative}: dlsym bypasses shared guest API") + if "kzt_guest_dl_api_dlvsym" not in dlvsym: + raise AssertionError(f"{relative}: dlvsym bypasses shared guest API") + if "my_dlsym(handle, symbol)" in dlvsym: + raise AssertionError(f"{relative}: dlvsym drops the version") + if "entries->dlvsym" not in dlvsym: + raise AssertionError(f"{relative}: RTLD_NEXT does not keep dlvsym") + if "vername" not in dlvsym: + raise AssertionError(f"{relative}: dlvsym does not pass vername") + if ( + "kzt_guest_dl_api_dlerror" not in dlerror + and "kzt_guest_dlerror_slow_path" not in dlerror + ): + raise AssertionError(f"{relative}: dlerror bypasses shared guest API") + if "kzt_guest_dl_api_dlerror" not in dlerror_slow: + raise AssertionError( + f"{relative}: dlerror slow path bypasses shared guest API" + ) + +for symbol in ('"dlvsym"', '"dlerror"'): + if symbol not in init_source: + raise AssertionError(f"shared guest dl init misses {symbol}") +for assignment in (".dlvsym = (uintptr_t)resolved[7]", ".dlerror = (uintptr_t)resolved[8]"): + if assignment not in init_source: + raise AssertionError(f"shared guest dl init misses {assignment}") +if "kzt_guest_dl_entry_state_t guest_dl_entries;" not in context_header: + raise AssertionError("dlprivate_t misses immutable guest dl table state") +if "kzt_guest_dlerror_state_t kzt_guest_dlerror_state;" not in cpu_header: + raise AssertionError("guest thread state misses dlerror ownership") +if "last_error_returned" not in state_header: + raise AssertionError("thread-local dlerror state is not one-shot") + +print("WI-963 guest symbol source contract: PASS") diff --git a/tests/unit/kzt/test_wi964_guest_dl_api_source_contract.py b/tests/unit/kzt/test_wi964_guest_dl_api_source_contract.py new file mode 100644 index 00000000000..6923c62f911 --- /dev/null +++ b/tests/unit/kzt/test_wi964_guest_dl_api_source_contract.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +root = pathlib.Path(sys.argv[1]).resolve() +common_header = root / "target/i386/latx/include/kzt_guest_dl_api.h" +common_source = root / "target/i386/latx/context/kzt_guest_dl_api.c" + +if not common_header.is_file() or not common_source.is_file(): + raise AssertionError("shared guest dl API is missing") + +source = common_source.read_text(encoding="utf-8") +header = common_header.read_text(encoding="utf-8") +required_common_operations = ( + "kzt_guest_dl_api_clear_error", + "kzt_guest_dl_api_dlmopen", + "kzt_guest_dl_api_dlsym", + "kzt_guest_dl_api_dlvsym", + "kzt_guest_dl_api_dlerror", + "kzt_guest_dl_api_dlinfo", +) +for operation in required_common_operations: + if operation not in source: + raise AssertionError(f"shared guest dl API misses {operation}") + +begin_start = header.find("static inline int kzt_guest_dl_api_begin_call(") +begin_end = header.find("\n}", begin_start) +if begin_start < 0 or begin_end < 0 or \ + "kzt_guest_dl_api_clear_error(state)" not in header[begin_start:begin_end]: + raise AssertionError("guest DL call entry no longer clears the shared error state") + +for operation in ( + "kzt_guest_library_run_dlmopen", + "kzt_guest_library_run_dlsym", + "kzt_guest_library_run_dlvsym", + "kzt_guest_library_run_dlerror", + "kzt_guest_library_run_dlinfo", + "kzt_guest_library_select_symbol_result", +): + if operation not in source: + raise AssertionError(f"shared guest dl API bypasses {operation}") + +for relative in ( + "target/i386/latx/context/wrappedlibc.c", + "target/i386/latx/context/wrappedlibdl.c", +): + wrapper = (root / relative).read_text(encoding="utf-8") + if '#include "kzt_guest_dl_api.h"' not in wrapper: + raise AssertionError(f"{relative}: shared guest dl API is not included") + for operation in required_common_operations[1:]: + if operation not in wrapper: + raise AssertionError(f"{relative}: does not call {operation}") + if "kzt_guest_dl_api_begin_call(error_state)" not in wrapper: + raise AssertionError(f"{relative}: does not enter the shared error state machine") + for obsolete in ( + "recursive_dlsym_lib", + "my_dlsym_lib", + "kzt_symbol_set_bad_handle", + "kzt_symbol_guest_handle", + ): + if obsolete in wrapper: + raise AssertionError(f"{relative}: retains duplicated {obsolete}") + if "\n#if 0\nvoid* my_dlsym(" in wrapper: + raise AssertionError(f"{relative}: retains disabled legacy dlsym") + for low_level in ( + "kzt_guest_library_run_dlmopen", + "kzt_guest_library_run_dlsym", + "kzt_guest_library_run_dlvsym", + "kzt_guest_library_run_dlerror", + "kzt_guest_library_run_dlinfo", + "kzt_guest_library_select_symbol_result", + ): + if low_level in wrapper: + raise AssertionError( + f"{relative}: duplicates shared state machine via {low_level}" + ) + +cpu_source = (root / "target/i386/cpu.c").read_text(encoding="utf-8") +context_source = ( + root / "target/i386/latx/context/box64context.c" +).read_text(encoding="utf-8") +if "kzt_guest_dl_api_free_errors(&cpu->env.kzt_guest_dlerror_state)" not in cpu_source: + raise AssertionError("guest thread destruction does not free dlerror state") +if "kzt_guest_dl_api_free_errors(&(*dl)->legacy_error)" not in context_source: + raise AssertionError("legacy context destruction does not free dlerror state") + +print("WI-964 shared guest dl API source contract: PASS") diff --git a/tests/unit/kzt/test_wi973_namespace_source_contract.py b/tests/unit/kzt/test_wi973_namespace_source_contract.py new file mode 100644 index 00000000000..ceb6af2a914 --- /dev/null +++ b/tests/unit/kzt/test_wi973_namespace_source_contract.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def matching_brace(text: str, opening: int) -> int: + depth = 0 + quote = None + escaped = False + index = opening + + while index < len(text): + char = text[index] + following = text[index + 1] if index + 1 < len(text) else "" + if quote: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == quote: + quote = None + index += 1 + continue + if char in ("'", '"'): + quote = char + index += 1 + continue + if char == "/" and following == "/": + newline = text.find("\n", index + 2) + index = len(text) if newline < 0 else newline + 1 + continue + if char == "/" and following == "*": + end = text.find("*/", index + 2) + if end < 0: + raise AssertionError("unterminated C comment") + index = end + 2 + continue + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + return index + index += 1 + + raise AssertionError("unterminated function body") + + +def function_body(text: str, signature: str) -> str: + start = 0 + while True: + start = text.find(signature, start) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start + len(signature)) + semicolon = text.find(";", start + len(signature)) + if opening >= 0 and (semicolon < 0 or opening < semicolon): + return text[opening + 1:matching_brace(text, opening)] + start += len(signature) + + +root = pathlib.Path(sys.argv[1]).resolve() +context_header = ( + root / "target/i386/latx/include/box64context.h" +).read_text(encoding="utf-8") +init_source = ( + root / "target/i386/latx/context/kzt_guest_dl_init.c" +).read_text(encoding="utf-8") +common_source = ( + root / "target/i386/latx/context/kzt_guest_dl_api.c" +).read_text(encoding="utf-8") +common_dlmopen = function_body( + common_source, "uint64_t kzt_guest_dl_api_dlmopen(") +common_dlinfo = function_body( + common_source, "int kzt_guest_dl_api_dlinfo(") + +if "kzt_guest_library_run_dlmopen" not in common_dlmopen: + raise AssertionError("shared non-main dlmopen does not use guest") +if "kzt_guest_dl_api_translate_handle" in common_dlinfo: + raise AssertionError("shared dlinfo retains removed synthetic-handle mapping") +if "kzt_guest_library_run_dlinfo" not in common_dlinfo: + raise AssertionError("shared dlinfo does not use guest") + +if "kzt_guest_dl_entry_state_t guest_dl_entries;" not in context_header: + raise AssertionError("dlprivate_t misses immutable guest dl table state") +if '"dlmopen"' not in init_source or ".dlmopen = (uintptr_t)resolved[1]" not in init_source: + raise AssertionError("shared guest dl init misses dlmopen") + +for relative in ( + "target/i386/latx/context/wrappedlibc.c", + "target/i386/latx/context/wrappedlibdl.c", +): + source = (root / relative).read_text(encoding="utf-8") + dlmopen = function_body(source, "void* my_dlmopen(") + dlinfo = function_body(source, "int my_dlinfo(") + + if "init_x86dlfun" in source: + raise AssertionError(f"{relative}: retains private guest dl init") + if "kzt_guest_dl_entries_for_call" not in source: + raise AssertionError(f"{relative}: bypasses shared guest dl init") + if "if (!lmid)" not in dlmopen or "my_dlopen(filename, flag)" not in dlmopen: + raise AssertionError(f"{relative}: LM_ID_BASE path is not preserved") + if "kzt_guest_dl_api_dlmopen" not in dlmopen: + raise AssertionError(f"{relative}: dlmopen bypasses shared guest API") + if "kzt_guest_dl_api_dlinfo" not in dlinfo: + raise AssertionError(f"{relative}: dlinfo bypasses shared guest API") + if "lsassert(0)" in dlinfo: + raise AssertionError(f"{relative}: dlinfo retains unsupported assertion") + if "RunFunctionWithState" in dlinfo: + raise AssertionError(f"{relative}: dlinfo bypasses shared adapter") + +print("WI-973 namespace source contract: PASS") diff --git a/tests/unit/kzt/test_wi979_guest_first_dlopen_source_contract.py b/tests/unit/kzt/test_wi979_guest_first_dlopen_source_contract.py new file mode 100644 index 00000000000..8950d7cfbe2 --- /dev/null +++ b/tests/unit/kzt/test_wi979_guest_first_dlopen_source_contract.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def matching_brace(text: str, opening: int) -> int: + depth = 0 + for index in range(opening, len(text)): + if text[index] == "{": + depth += 1 + elif text[index] == "}": + depth -= 1 + if depth == 0: + return index + raise AssertionError("unterminated function") + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start) + if opening < 0: + raise AssertionError(f"missing function body: {signature}") + return text[opening + 1:matching_brace(text, opening)] + + +root = pathlib.Path(sys.argv[1]).resolve() +common = ( + root / "target/i386/latx/context/kzt_guest_dl_api.c" +).read_text(encoding="utf-8") +common_dlopen = function_body(common, "uint64_t kzt_guest_dl_api_dlopen(") + +guest_call = common_dlopen.find("kzt_guest_library_run_dlopen_scoped") +wrapper_attach = common_dlopen.find("AddNeededLibWithLibrary") +finish = common_dlopen.find("kzt_guest_dl_api_finish_dlopen_scoped") +if guest_call < 0 or wrapper_attach < 0 or finish < 0: + raise AssertionError("shared dlopen misses guest call or wrapper attachment") +if guest_call >= wrapper_attach: + raise AssertionError("wrapper attachment happens before guest dlopen") +if "return guest_handle" not in common_dlopen: + raise AssertionError("shared dlopen does not preserve the guest handle") +if "if (!guest_handle)" not in common_dlopen: + raise AssertionError("shared dlopen does not stop attachment on guest failure") + +for relative in ( + "target/i386/latx/context/wrappedlibc.c", + "target/i386/latx/context/wrappedlibdl.c", +): + source = (root / relative).read_text(encoding="utf-8") + dlopen = function_body( + source, "void* my_dlopen(void *filename, int flag){") + if "kzt_guest_dl_api_dlopen" not in dlopen: + raise AssertionError(f"{relative}: dlopen bypasses shared guest API") + for obsolete in ( + "AddNeededLibWithLibrary", + "dl->libs", + "dl->count", + "dl->dlopened", + "dlopen_recycle_transaction", + "callx86dlopen", + "run_guest_dlopen_scoped", + "(void*)(i+1)", + ): + if obsolete in dlopen: + raise AssertionError( + f"{relative}: dlopen retains old state via {obsolete}") + +print("WI-979 guest-first dlopen source contract: PASS") diff --git a/tests/unit/kzt/test_wi980_guest_first_dlclose_source_contract.py b/tests/unit/kzt/test_wi980_guest_first_dlclose_source_contract.py new file mode 100644 index 00000000000..5c232226500 --- /dev/null +++ b/tests/unit/kzt/test_wi980_guest_first_dlclose_source_contract.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def matching_brace(text: str, opening: int) -> int: + depth = 0 + for index in range(opening, len(text)): + if text[index] == "{": + depth += 1 + elif text[index] == "}": + depth -= 1 + if depth == 0: + return index + raise AssertionError("unterminated function") + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start) + if opening < 0: + raise AssertionError(f"missing function body: {signature}") + return text[opening + 1:matching_brace(text, opening)] + + +root = pathlib.Path(sys.argv[1]).resolve() +common = ( + root / "target/i386/latx/context/kzt_guest_dl_api.c" +).read_text(encoding="utf-8") +common_dlclose = function_body(common, "int kzt_guest_dl_api_dlclose(") + +guest_close = common_dlclose.find("kzt_guest_library_run_dlclose") +presence_probe = common_dlclose.find("kzt_guest_library_run_dlopen_scoped") +inactivate = common_dlclose.find("InactiveLibrary") +if guest_close < 0: + raise AssertionError("shared dlclose does not call guest dlclose") +if presence_probe >= 0 and guest_close >= presence_probe: + raise AssertionError("guest object is probed before guest dlclose") +if inactivate >= 0 and guest_close >= inactivate: + raise AssertionError("native wrapper is inactivated before guest dlclose") +if "return guest_result" not in common_dlclose: + raise AssertionError("shared dlclose does not preserve guest result") + +for relative in ( + "target/i386/latx/context/wrappedlibc.c", + "target/i386/latx/context/wrappedlibdl.c", +): + source = (root / relative).read_text(encoding="utf-8") + dlclose = function_body(source, "int my_dlclose(void *handle)\n{") + if "kzt_guest_dl_api_dlclose" not in dlclose: + raise AssertionError(f"{relative}: dlclose bypasses shared guest API") + for obsolete in ( + "dl->libs", + "dl->count", + "dl->dlopened", + "Push64", + "InactiveLibrary", + "GetElfIndex", + ): + if obsolete in dlclose: + raise AssertionError( + f"{relative}: dlclose retains old state via {obsolete}") + +print("WI-980 guest-first dlclose source contract: PASS") diff --git a/tests/unit/kzt/test_wi982_versioned_wrapper_selection_source_contract.py b/tests/unit/kzt/test_wi982_versioned_wrapper_selection_source_contract.py new file mode 100644 index 00000000000..713b06d9aa8 --- /dev/null +++ b/tests/unit/kzt/test_wi982_versioned_wrapper_selection_source_contract.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def matching_brace(text: str, opening: int) -> int: + depth = 0 + for index in range(opening, len(text)): + if text[index] == "{": + depth += 1 + elif text[index] == "}": + depth -= 1 + if depth == 0: + return index + raise AssertionError("unterminated function") + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start) + if opening < 0: + raise AssertionError(f"missing function body: {signature}") + return text[opening + 1:matching_brace(text, opening)] + + +root = pathlib.Path(sys.argv[1]).resolve() +adapter = ( + root / "target/i386/latx/context/kzt_guest_library_adapter.c" +).read_text(encoding="utf-8") +common = ( + root / "target/i386/latx/context/kzt_guest_dl_api.c" +).read_text(encoding="utf-8") + +selector = function_body( + adapter, "uintptr_t kzt_guest_library_select_symbol_result(") +version_guard = selector.find("version && version[0]") +owner_lookup = selector.find("kzt_guest_registry_resolve_address_pair") +if version_guard < 0: + raise AssertionError("versioned selection is not explicitly fail-open") +if owner_lookup >= 0 and version_guard >= owner_lookup: + raise AssertionError("versioned lookup reaches unversioned owner selection") + +dlsym = function_body(common, "kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlsym(") +dlvsym = function_body( + common, "kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlvsym(") +if "context, (uintptr_t)handle, guest_result" not in dlsym: + raise AssertionError("dlsym does not declare its unversioned selection") +if "context, (uintptr_t)handle, guest_result" not in dlvsym: + raise AssertionError("dlvsym does not pass the requested version") + +print("WI-982 versioned wrapper selection source contract: PASS") diff --git a/tests/unit/kzt/test_wi987_guest_relocation_authority_source_contract.py b/tests/unit/kzt/test_wi987_guest_relocation_authority_source_contract.py new file mode 100644 index 00000000000..6dc979f92fc --- /dev/null +++ b/tests/unit/kzt/test_wi987_guest_relocation_authority_source_contract.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +import pathlib +import re +import sys + + +def fail(message: str) -> None: + raise AssertionError(message) + + +def matching_brace(text: str, start: int) -> int: + depth = 0 + quote = None + escaped = False + index = start + + while index < len(text): + char = text[index] + following = text[index + 1] if index + 1 < len(text) else "" + if quote: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == quote: + quote = None + index += 1 + continue + if char in ("'", '"'): + quote = char + index += 1 + continue + if char == "/" and following == "/": + newline = text.find("\n", index + 2) + index = len(text) if newline < 0 else newline + 1 + continue + if char == "/" and following == "*": + end = text.find("*/", index + 2) + if end < 0: + fail("unterminated C comment") + index = end + 2 + continue + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + return index + index += 1 + fail("unterminated C function") + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + fail(f"missing function: {signature}") + brace = text.find("{", start + len(signature)) + if brace < 0: + fail(f"missing function body: {signature}") + return text[brace + 1:matching_brace(text, brace)] + + +root = pathlib.Path(sys.argv[1]).resolve() +elfloader = ( + root / "target/i386/latx/context/elfloader.c" +).read_text(encoding="utf-8") +route = ( + root / "target/i386/latx/context/kzt_jump_slot_route.c" +).read_text(encoding="utf-8") +production = ( + root / "target/i386/latx/context/kzt_jump_slot_production.c" +).read_text(encoding="utf-8") + +rela = function_body(elfloader, "int RelocateElfRELA(") +legacy = function_body(elfloader, "kzt_resolve_legacy_rela_target(") +glob_dat = function_body( + elfloader, "kzt_guest_glob_dat_target_resolve(" +) +route_apply = function_body(route, "int kzt_jump_slot_route_apply(") +acquire = function_body(production, "static int production_acquire_exact(") + +if "GetGlobalSymbolStartEndWithProvider(" in rela: + fail("RelocateElfRELA must not perform eager host symbol arbitration") +if rela.count("kzt_resolve_legacy_rela_target(") != 2: + fail("RelocateElfRELA must have one fallback for each supported type") +if "GetGlobalSymbolStartEndWithProvider(" not in legacy: + fail("legacy compatibility helper lost the historical host lookup") + +required_glob_dat = ( + "kzt_owner_resolver_resolve_current(", + "KZT_OWNER_RESOLVER_RESOLVED", + "KZT_PATCH_OWNER_MATCH", + "current_owner.known", + "KztGuestLibraryLookupForContext(", + "GetLibSymbolStartEnd(", + "STT_FUNC", + "version >= 2", +) +for required in required_glob_dat: + if required not in glob_dat: + fail(f"GLOB_DAT authority lacks Registry owner proof: {required}") + +guest_route = re.search( + r"kzt_production_jump_slot_route\s*\(\s*" + r"my_context\s*,\s*NULL\s*,\s*slot_observation\s*,", + rela, + re.DOTALL, +) +if not guest_route: + fail("eager JUMP_SLOT must enter the route without a host provider") +if not re.search( + r"if\s*\(\s*\(option_kzt\s*\|\|\s*wine_option_kzt\)\s*&&\s*" + r"head->self_link_map\s*&&\s*bind\s*!=\s*STB_LOCAL", + rela, + re.DOTALL, +): + fail("eager JUMP_SLOT must skip the new route before source identity exists") +fallback = rela.rfind("kzt_resolve_legacy_rela_target(") +if fallback < 0 or guest_route.start() > fallback: + fail("host compatibility lookup must happen after the guest-first route") +if not re.search( + r"kzt_guest_glob_dat_target_resolve\s*\([\s\S]*?\)\s*\)\s*\{" + r"[\s\S]*?continue\s*;", + rela, +): + fail("authoritative GLOB_DAT guest targets must bypass host lookup") +if not re.search( + r"if\s*\(\s*bind\s*!=\s*STB_LOCAL\s*\)\s*\{[\s\S]*?" + r"route=GUEST_PRESERVED[\s\S]*?host_lookup=0[\s\S]*?continue\s*;", + rela, +): + fail("KZT GLOB_DAT evidence failure can reach host lookup") +if not re.search( + r"if\s*\(\s*\(option_kzt\s*\|\|\s*wine_option_kzt\)\s*&&\s*" + r"bind\s*!=\s*STB_LOCAL\s*\)\s*\{[\s\S]*?" + r"route=GUEST_PRESERVED[\s\S]*?host_lookup=0[\s\S]*?break\s*;", + rela, +): + fail("KZT JUMP_SLOT evidence failure can reach host lookup") +if "__atomic_compare_exchange_n(" not in rela: + fail("GLOB_DAT exact-owner bridge must not overwrite a competitor") + +for forbidden in ("input->resolved_target_matches_legacy",): + if forbidden in route_apply: + fail(f"route still depends on host arbitration: {forbidden}") +if "input->resolved_provider &&" in route_apply: + fail("route still requires a host-selected provider") + +if "!resolved_provider" in acquire: + fail("exact provider acquisition rejects Registry-only lookup") +for required in ( + "KztGuestLibraryLookupForContext(", + "state->resolved_provider = handle->library;", +): + if required not in acquire: + fail(f"exact provider acquisition lacks Registry binding: {required}") + +print("WI-987 guest relocation authority source contract: PASS") diff --git a/tests/unit/kzt/test_wi994_dlerror_tail_forward_source_contract.py b/tests/unit/kzt/test_wi994_dlerror_tail_forward_source_contract.py new file mode 100644 index 00000000000..a0b1e14bc35 --- /dev/null +++ b/tests/unit/kzt/test_wi994_dlerror_tail_forward_source_contract.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def matching_brace(text: str, opening: int) -> int: + depth = 0 + quote = None + escaped = False + index = opening + + while index < len(text): + char = text[index] + following = text[index + 1] if index + 1 < len(text) else "" + if quote: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == quote: + quote = None + index += 1 + continue + if char in ("'", '"'): + quote = char + index += 1 + continue + if char == "/" and following == "/": + newline = text.find("\n", index + 2) + index = len(text) if newline < 0 else newline + 1 + continue + if char == "/" and following == "*": + end = text.find("*/", index + 2) + if end < 0: + raise AssertionError("unterminated C comment") + index = end + 2 + continue + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + return index + index += 1 + raise AssertionError("unterminated function") + + +def function_body(text: str, signature: str) -> str: + start = 0 + while True: + start = text.find(signature, start) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start + len(signature)) + semicolon = text.find(";", start + len(signature)) + if opening >= 0 and (semicolon < 0 or opening < semicolon): + return text[opening + 1:matching_brace(text, opening)] + start += len(signature) + + +root = pathlib.Path(sys.argv[1]).resolve() +header = ( + root / "target/i386/latx/include/kzt_guest_dl_api.h" +).read_text(encoding="utf-8") +source = ( + root / "target/i386/latx/context/kzt_guest_dl_api.c" +).read_text(encoding="utf-8") + +if "kzt_guest_dlerror_result_t" not in header: + raise AssertionError("shared dlerror result does not expose tail forwarding") + +shared = function_body( + source, + "kzt_guest_dlerror_result_t kzt_guest_dl_api_dlerror(", +) +if "forward_to_guest_caller = 1" not in shared: + raise AssertionError("empty local error does not tail-forward to guest") +if "kzt_guest_library_run_dlerror" not in shared: + raise AssertionError("local synthetic error no longer consumes stale guest error") + +for relative in ( + "target/i386/latx/context/wrappedlibc.c", + "target/i386/latx/context/wrappedlibdl.c", +): + wrapper = (root / relative).read_text(encoding="utf-8") + helper = function_body( + wrapper, "static uintptr_t kzt_guest_dlerror_entry_slow(" + ) + if "kzt_guest_dl_entries_for_call(context" not in helper or \ + "entries ? entries->dlerror : 0" not in helper: + raise AssertionError(f"{relative}: dlerror slow fallback is incomplete") + state_slow = function_body( + wrapper, "static char *kzt_guest_dlerror_slow_path(" + ) + for required in ( + "kzt_guest_dl_api_dlerror(error_state, guest_dlerror)", + "kzt_guest_dl_api_load_dlerror_hint(", + "kzt_guest_dlerror_entry_slow(context)", + "error_state->guest_dlerror_entry = guest_dlerror", + "Push64(cpu, guest_dlerror)", + ): + if required not in state_slow: + raise AssertionError( + f"{relative}: dlerror state slow path misses {required}" + ) + body = function_body(wrapper, "char* my_dlerror(void)") + if "kzt_guest_dlerror_result_t" not in state_slow: + raise AssertionError(f"{relative}: dlerror does not use shared result") + if "forward_to_guest_caller" not in state_slow: + raise AssertionError(f"{relative}: dlerror does not check tail forwarding") + state_check = body.find("if (fast_result)") + slow = body.find("kzt_guest_dlerror_slow_path(") + fast_return = body.find("return fast_result;") + if min(state_check, slow, fast_return) < 0 or not ( + state_check < slow < fast_return): + raise AssertionError( + f"{relative}: dlerror does not isolate its clean fast path" + ) + if "char *fast_result" not in body: + raise AssertionError(f"{relative}: clean dlerror result is materialized") + if "kzt_guest_dl_entries_t fallback" in body: + raise AssertionError(f"{relative}: hot dlerror retains cold stack state") + if "RunFunctionWithState" in body: + raise AssertionError(f"{relative}: dlerror retains nested guest execution") + +print("WI-994 dlerror tail-forward source contract: PASS") diff --git a/tests/unit/kzt/test_wi995_lazy_bridge_translation_timing_source_contract.py b/tests/unit/kzt/test_wi995_lazy_bridge_translation_timing_source_contract.py new file mode 100644 index 00000000000..36b16a3ef2f --- /dev/null +++ b/tests/unit/kzt/test_wi995_lazy_bridge_translation_timing_source_contract.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +import pathlib +import re +import sys + + +root = pathlib.Path(sys.argv[1]).resolve() +source = ( + root / "target/i386/latx/translator/translate.c" +).read_text(encoding="utf-8") +elfloader = ( + root / "target/i386/latx/context/elfloader.c" +).read_text(encoding="utf-8") +translate_all = ( + root / "accel/tcg/translate-all.c" +).read_text(encoding="utf-8") + +if "kzt_lazy_bridge_translation_timing schema=1" not in source: + raise AssertionError("lazy resolver bridge translation is not timed") + +for field in ("pc=", "code_size=", "total_ns="): + if field not in source: + raise AssertionError(f"bridge translation timing is missing {field}") + +if "KztPltResolverBridge()" not in source: + raise AssertionError( + "lazy resolver timing does not use the context-owned bridge" + ) + +if not re.search( + r"kzt_lazy_bridge_timing_enabled\s*=\s*" + r"kzt_lazy_diagnostics_enabled\s*&&\s*" + r"kzt_plt_resolver_bridge\s*&&\s*" + r"tb->pc\s*==\s*kzt_plt_resolver_bridge", + source, +): + raise AssertionError( + "bridge translation timing is not limited to the lazy resolver" + ) + +if "kzt_lazy_bridge_translation_ready_ns" not in source: + raise AssertionError("translation completion is not handed to resolver") + +if "kzt_lazy_resolver_timing schema=1" not in elfloader: + raise AssertionError("lazy resolver entry timing is not emitted") + +for field in ( + "translation_to_entry_ns=", + "entry_minflt=", + "prepare_ns=", + "prepare_minflt=", + "route_ns=", + "route_minflt=", + "finish_ns=", + "finish_minflt=", + "done_minflt=", + "total_ns=", +): + if field not in elfloader: + raise AssertionError(f"lazy resolver timing is missing {field}") + +if "kzt_lazy_target_bridge_pc" not in source: + raise AssertionError("resolved target bridge is not handed to translator") + +if "kzt_lazy_target_bridge_translation_timing schema=1" not in source: + raise AssertionError("resolved target bridge translation is not timed") + +for field in ("resolver_to_translation_ns=", "total_ns="): + if field not in source: + raise AssertionError(f"target bridge timing is missing {field}") + +if "kzt_lazy_bridge_tb_gen_timing schema=1" not in translate_all: + raise AssertionError("full lazy bridge TB generation is not timed") + +for field in ("role=", "pc=", "total_ns="): + if field not in translate_all: + raise AssertionError(f"full bridge TB timing is missing {field}") + +print("WI-995 lazy bridge translation timing source contract: PASS") diff --git a/tests/unit/kzt/test_wi995_lazy_direct_timing_source_contract.py b/tests/unit/kzt/test_wi995_lazy_direct_timing_source_contract.py new file mode 100644 index 00000000000..223bd56fcd4 --- /dev/null +++ b/tests/unit/kzt/test_wi995_lazy_direct_timing_source_contract.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +import pathlib +import re +import sys + + +root = pathlib.Path(sys.argv[1]).resolve() +source = ( + root / "target/i386/latx/context/kzt_jump_slot_production.c" +).read_text(encoding="utf-8") +runtime_bridge = ( + root / "target/i386/latx/context/kzt_rela_runtime_bridge.c" +).read_text(encoding="utf-8") + +if "kzt_lazy_timing schema=1" not in source: + raise AssertionError("lazy direct route does not emit stage timing evidence") + +required_fields = ( + "source_ns=", + "candidate_ns=", + "quiescence_ns=", + "scope_ns=", + "provider_ns=", + "route_ns=", + "route_prepare_ns=", + "bridge_ns=", + "bridge_discover_ns=", + "bridge_probe_ns=", + "decision_ns=", + "final_ns=", + "cas_ns=", + "cleanup_ns=", + "total_ns=", +) +for field in required_fields: + if field not in source: + raise AssertionError(f"lazy direct timing is missing {field}") + +gate = re.search( + r"timing_enabled\s*=\s*unlikely\s*\(\s*" + r"option_kzt_lazy_diagnostics\s*\)", + source, +) +if not gate: + raise AssertionError("lazy direct timing is not lazy-diagnostics-gated") +if re.search( + r"timing_enabled\s*=\s*kzt_registry_diagnostics_enabled", + source, +): + raise AssertionError("Registry diagnostics unexpectedly enable timing") + +if not re.search( + r"if\s*\(\s*timing_enabled\s*\)\s*\{\s*" + r"timing\.start\s*=\s*production_lazy_direct_timing_now\s*\(\s*\)", + source, + re.DOTALL, +): + raise AssertionError("normal lazy route still reads the timing clock") + +if "kzt_bridge_discovery_timing schema=1" not in runtime_bridge: + raise AssertionError("wrapper bridge discovery has no stage timing") +for field in ( + "wrapper_map_ns=", + "native_lookup_ns=", + "bridge_cache_ns=", + "handle_owner_ns=", + "total_ns=", +): + if field not in runtime_bridge: + raise AssertionError(f"wrapper bridge timing is missing {field}") +if "option_kzt_lazy_diagnostics" not in runtime_bridge: + raise AssertionError("wrapper bridge timing is not lazy-diagnostics-gated") + +print("WI-995 lazy direct timing source contract: PASS") diff --git a/tests/unit/kzt/test_wi999_context_init_timing_source_contract.py b/tests/unit/kzt/test_wi999_context_init_timing_source_contract.py new file mode 100644 index 00000000000..3321f801e56 --- /dev/null +++ b/tests/unit/kzt/test_wi999_context_init_timing_source_contract.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +import pathlib +import re +import sys + + +root = pathlib.Path(sys.argv[1]).resolve() +source = ( + root / "target/i386/latx/context/box64context.c" +).read_text(encoding="utf-8") + +if "kzt_context_init_timing schema=1" not in source: + raise AssertionError("KZT context initialization does not emit stage timing") + +for field in ( + "base_ns=", + "library_access_ns=", + "patch_guard_ns=", + "total_ns=", +): + if field not in source: + raise AssertionError(f"context timing is missing {field}") + +if not re.search( + r"timing_enabled\s*=\s*kzt_registry_diagnostics_enabled\s*\(\s*\)", + source, +): + raise AssertionError("context timing is not Registry-diagnostics-gated") + +print("WI-999 context initialization timing source contract: PASS") diff --git a/tests/unit/kzt/test_wi999_startup_observation_timing_source_contract.py b/tests/unit/kzt/test_wi999_startup_observation_timing_source_contract.py new file mode 100644 index 00000000000..89cf655a247 --- /dev/null +++ b/tests/unit/kzt/test_wi999_startup_observation_timing_source_contract.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +import pathlib +import re +import sys + + +root = pathlib.Path(sys.argv[1]).resolve() +source = ( + root / "target/i386/latx/context/kzt_observation_adapter.c" +).read_text(encoding="utf-8") + +if "kzt_observation_timing schema=1" not in source: + raise AssertionError("startup observation does not emit stage timing") + +for field in ( + "access_ns=", + "observe_ns=", + "legacy_ns=", + "supplement_ns=", + "total_ns=", +): + if field not in source: + raise AssertionError(f"startup observation timing is missing {field}") + +if not re.search( + r"timing_enabled\s*=\s*request\s*&&\s*request->diagnostics_enabled", + source, +): + raise AssertionError("startup timing is not Registry-diagnostics-gated") + +if not re.search( + r"if\s*\(\s*timing_enabled\s*\)\s*\{\s*" + r"timing_start\s*=\s*kzt_observation_timing_now\s*\(\s*\)", + source, + re.DOTALL, +): + raise AssertionError("normal observation path still reads the timing clock") + +print("WI-999 startup observation timing source contract: PASS") From d57039af6a66f55cd7cb30c83699942896ba8277 Mon Sep 17 00:00:00 2001 From: Hanlu Li Date: Fri, 31 Jul 2026 22:08:11 +0800 Subject: [PATCH 6/8] LATX, KZT: add real guest refactor coverage Add reusable x86_64 guest loader fixtures, deterministic harnesses, and launchers for namespace, visibility, versioning, lifecycle, preemption, and wrapper-handle behavior. Register the accumulated KZT unit, source-contract, and real-guest coverage in Meson while keeping performance execution opt-in.\n\nThis completes the internal commit sequence intended for one future cumulative KZT pull request. Signed-off-by: Hanlu Li --- tests/unit/kzt/guest_e2e/build_guest_probe.sh | 253 +- tests/unit/kzt/guest_e2e/kzt_guest_main.c | 16 + .../unit/kzt/guest_e2e/kzt_guest_perf_main.c | 136 + .../unit/kzt/guest_e2e/kzt_guest_perf_probe.c | 136 + .../unit/kzt/guest_e2e/kzt_guest_perf_probe.h | 22 + .../unit/kzt/guest_e2e/kzt_guest_perf_start.S | 14 + .../guest_e2e/kzt_guest_preemption_provider.c | 38 + tests/unit/kzt/guest_e2e/kzt_guest_probe.c | 27 +- .../build_guest_loader_fixture.sh | 253 ++ .../unit/kzt/guest_loader/dependency_reopen.c | 84 + .../unit/kzt/guest_loader/guest_loader_test.h | 122 + tests/unit/kzt/guest_loader/helper.c | 4 + tests/unit/kzt/guest_loader/namespace.c | 4 + .../kzt/guest_loader/namespace_isolation.c | 53 + tests/unit/kzt/guest_loader/plugin.c | 6 + .../kzt/guest_loader/symbol_versions_errors.c | 61 + tests/unit/kzt/guest_loader/versions.c | 12 + tests/unit/kzt/guest_loader/versions.map | 8 + tests/unit/kzt/guest_loader/visibility.c | 4 + .../unit/kzt/guest_loader/visibility_noload.c | 75 + .../kzt/guest_loader/wi1065_constructor.c | 11 + .../kzt/guest_loader/wi1065_loader_events.c | 82 + tests/unit/kzt/guest_loader/wi1065_relro.c | 4 + .../kzt/guest_loader/wrapped_library_handle.c | 40 + tests/unit/kzt/real_guest_harness.py | 2385 +++++++++++++++++ .../unit/kzt/real_guest_loader_performance.py | 318 +++ tests/unit/kzt/test_real_guest_e2e.py | 218 +- tests/unit/kzt/test_real_guest_harness.py | 2037 ++++++++++++++ tests/unit/kzt/test_real_guest_loader_gate.py | 360 +++ .../kzt/test_real_guest_loader_performance.py | 116 + tests/unit/kzt/test_real_guest_performance.py | 220 +- tests/unit/kzt/test_wi1065_real_guest_hook.py | 242 ++ .../test_wi1065_real_guest_hook_contract.py | 45 + .../test_wi1097_real_guest_loader_identity.py | 126 + .../test_wi600_guest_loader_gate_harness.py | 223 ++ .../kzt/test_wi601_perf_fixture_contract.py | 53 + .../kzt/test_wi601_real_guest_e2e_launcher.py | 154 ++ .../kzt/test_wi849_real_guest_preemption.py | 313 +++ ...st_wi849_real_guest_preemption_launcher.py | 172 ++ tests/unit/meson.build | 620 +++++ 40 files changed, 8730 insertions(+), 337 deletions(-) create mode 100644 tests/unit/kzt/guest_e2e/kzt_guest_perf_main.c create mode 100644 tests/unit/kzt/guest_e2e/kzt_guest_perf_probe.c create mode 100644 tests/unit/kzt/guest_e2e/kzt_guest_perf_probe.h create mode 100644 tests/unit/kzt/guest_e2e/kzt_guest_perf_start.S create mode 100644 tests/unit/kzt/guest_e2e/kzt_guest_preemption_provider.c create mode 100755 tests/unit/kzt/guest_loader/build_guest_loader_fixture.sh create mode 100644 tests/unit/kzt/guest_loader/dependency_reopen.c create mode 100644 tests/unit/kzt/guest_loader/guest_loader_test.h create mode 100644 tests/unit/kzt/guest_loader/helper.c create mode 100644 tests/unit/kzt/guest_loader/namespace.c create mode 100644 tests/unit/kzt/guest_loader/namespace_isolation.c create mode 100644 tests/unit/kzt/guest_loader/plugin.c create mode 100644 tests/unit/kzt/guest_loader/symbol_versions_errors.c create mode 100644 tests/unit/kzt/guest_loader/versions.c create mode 100644 tests/unit/kzt/guest_loader/versions.map create mode 100644 tests/unit/kzt/guest_loader/visibility.c create mode 100644 tests/unit/kzt/guest_loader/visibility_noload.c create mode 100644 tests/unit/kzt/guest_loader/wi1065_constructor.c create mode 100644 tests/unit/kzt/guest_loader/wi1065_loader_events.c create mode 100644 tests/unit/kzt/guest_loader/wi1065_relro.c create mode 100644 tests/unit/kzt/guest_loader/wrapped_library_handle.c create mode 100644 tests/unit/kzt/real_guest_harness.py create mode 100644 tests/unit/kzt/real_guest_loader_performance.py mode change 100755 => 100644 tests/unit/kzt/test_real_guest_e2e.py create mode 100644 tests/unit/kzt/test_real_guest_harness.py create mode 100755 tests/unit/kzt/test_real_guest_loader_gate.py create mode 100644 tests/unit/kzt/test_real_guest_loader_performance.py create mode 100644 tests/unit/kzt/test_wi1065_real_guest_hook.py create mode 100644 tests/unit/kzt/test_wi1065_real_guest_hook_contract.py create mode 100644 tests/unit/kzt/test_wi1097_real_guest_loader_identity.py create mode 100755 tests/unit/kzt/test_wi600_guest_loader_gate_harness.py create mode 100644 tests/unit/kzt/test_wi601_perf_fixture_contract.py create mode 100644 tests/unit/kzt/test_wi601_real_guest_e2e_launcher.py create mode 100644 tests/unit/kzt/test_wi849_real_guest_preemption.py create mode 100644 tests/unit/kzt/test_wi849_real_guest_preemption_launcher.py diff --git a/tests/unit/kzt/guest_e2e/build_guest_probe.sh b/tests/unit/kzt/guest_e2e/build_guest_probe.sh index b2aed7302df..56d1f934d7b 100755 --- a/tests/unit/kzt/guest_e2e/build_guest_probe.sh +++ b/tests/unit/kzt/guest_e2e/build_guest_probe.sh @@ -2,6 +2,7 @@ set -euo pipefail script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +e2e_symbol=dlerror build_dir=${KZT_GUEST_BUILD_DIR:-${TMPDIR:-/tmp}/kzt-guest-e2e} guest_cc=${KZT_GUEST_CC:-x86_64-linux-gnu-gcc} guest_readelf=${KZT_GUEST_READELF:-readelf} @@ -28,12 +29,25 @@ run_cc -O2 -fPIC -shared -nostdlib \ "$guest_libc" \ -o "$build_dir/libkzt_guest_probe.so" -run_cc -O2 -nostdlib -Wl,-e,_start \ +run_cc -O2 -nostdlib -Wl,-e,_start -Wl,-z,now \ -I"$script_dir" \ "$script_dir/kzt_guest_main.c" \ -L"$build_dir" -lkzt_guest_probe \ -o "$build_dir/kzt_guest_main" +run_cc -O2 -fPIC -shared -nostdlib \ + -I"$script_dir" \ + "$script_dir/kzt_guest_perf_probe.c" \ + "$guest_libc" \ + -o "$build_dir/libkzt_guest_perf_probe.so" + +run_cc -O2 -fno-plt -nostdlib -Wl,-e,_start \ + -I"$script_dir" \ + "$script_dir/kzt_guest_perf_start.S" \ + "$script_dir/kzt_guest_perf_main.c" \ + -L"$build_dir" -lkzt_guest_perf_probe \ + -o "$build_dir/kzt_guest_perf_main" + header=$($guest_readelf -h "$build_dir/kzt_guest_main") if ! grep -Eq 'Machine:.*(Advanced Micro Devices X86-64|X86-64)' \ <<< "$header"; then @@ -41,24 +55,187 @@ if ! grep -Eq 'Machine:.*(Advanced Micro Devices X86-64|X86-64)' \ exit 1 fi +perf_header=$($guest_readelf -h "$build_dir/kzt_guest_perf_main") +if ! grep -Eq 'Machine:.*(Advanced Micro Devices X86-64|X86-64)' \ + <<< "$perf_header"; then + echo "Guest performance main is not an x86-64 ELF." >&2 + exit 1 +fi + relocations=$($guest_readelf -rW "$build_dir/libkzt_guest_probe.so") -uname_jump_slots=$(grep -Ec \ - 'R_X86_64_(JUMP_SLOT|JUMP_SLO).*uname(@|[[:space:]])' \ +e2e_jump_slots=$(grep -Ec \ + "R_X86_64_(JUMP_SLOT|JUMP_SLO).*${e2e_symbol}(@|[[:space:]])" \ <<< "$relocations" || true) -if [[ $uname_jump_slots -ne 1 ]]; then - echo "Guest DSO must contain exactly one lazy uname JUMP_SLOT." >&2 +if [[ $e2e_jump_slots -ne 1 ]]; then + echo "Guest DSO must contain exactly one lazy ${e2e_symbol} JUMP_SLOT." >&2 + exit 1 +fi + +perf_relocations=$( + $guest_readelf -rW "$build_dir/libkzt_guest_perf_probe.so" +) +perf_jump_slot_count=$(grep -Ec \ + 'R_X86_64_(JUMP_SLOT|JUMP_SLO)' \ + <<< "$perf_relocations" || true) +if [[ $perf_jump_slot_count -ne 1 ]]; then + echo "Guest performance DSO must contain exactly one JUMP_SLOT." >&2 + exit 1 +fi +perf_versioned_dlerror_jump_slots=$(grep -Ec \ + "R_X86_64_(JUMP_SLOT|JUMP_SLO).*${e2e_symbol}@GLIBC_[^[:space:]]*" \ + <<< "$perf_relocations" || true) +if [[ $perf_versioned_dlerror_jump_slots -ne 1 ]]; then + echo "Guest performance DSO JUMP_SLOT must be versioned ${e2e_symbol}." >&2 exit 1 fi symbols=$($guest_readelf -Ws "$build_dir/libkzt_guest_probe.so") -uname_versions=$(sed -n \ - 's/.* UND uname@\(GLIBC_[^[:space:]]*\).*/\1/p' \ +e2e_versions=$(sed -n \ + "s/.* UND ${e2e_symbol}@\\(GLIBC_[^[:space:]]*\\).*/\\1/p" \ <<< "$symbols" | sort -u) -if [[ -z $uname_versions || $uname_versions == *$'\n'* ]]; then - echo "Guest DSO must require one versioned uname symbol." >&2 +if [[ -z $e2e_versions || $e2e_versions == *$'\n'* ]]; then + echo "Guest DSO must require one versioned ${e2e_symbol} symbol." >&2 exit 1 fi -printf '%s\n' "$uname_versions" > "$build_dir/guest-symbol-version.txt" +printf '%s\n' "$e2e_versions" > "$build_dir/guest-symbol-version.txt" +printf '%s\n' "$e2e_symbol" > "$build_dir/guest-symbol-name.txt" + +preemption_version_script="$build_dir/kzt-guest-preemption-provider.map" +printf '%s {\n global:\n dlerror;\n local:\n *;\n};\n' \ + "$e2e_versions" > "$preemption_version_script" + +run_cc -O2 -fPIC -shared -nostdlib \ + -DKZT_PREEMPTION_PROVIDER=1 \ + -Wl,--version-script="$preemption_version_script" \ + "$script_dir/kzt_guest_preemption_provider.c" \ + -o "$build_dir/libkzt_preempt_a.so" + +run_cc -O2 -fPIC -shared -nostdlib \ + -DKZT_PREEMPTION_PROVIDER=2 \ + -Wl,--version-script="$preemption_version_script" \ + "$script_dir/kzt_guest_preemption_provider.c" \ + -o "$build_dir/libkzt_preempt_b.so" + +run_cc -O2 -fPIC -shared -nostdlib \ + -DKZT_PREEMPTION_PROVIDER=0 \ + -Wl,--version-script="$preemption_version_script" \ + "$script_dir/kzt_guest_preemption_provider.c" \ + -o "$build_dir/libkzt_preempt_weak.so" + +run_cc -O2 -fPIC -shared -nostdlib \ + -DKZT_PREEMPTION_PROVIDER=3 \ + -Wl,--version-script="$preemption_version_script" \ + "$script_dir/kzt_guest_preemption_provider.c" \ + -o "$build_dir/libkzt_local_preempt.so" + +run_cc -O2 -nostdlib -Wl,-e,_start -Wl,-z,now \ + -Wl,--no-as-needed \ + -I"$script_dir" \ + "$script_dir/kzt_guest_main.c" \ + -L"$build_dir" -lkzt_preempt_a -lkzt_guest_probe \ + -o "$build_dir/kzt_guest_preempt_a_main" + +run_cc -O2 -nostdlib -Wl,-e,_start -Wl,-z,now \ + -Wl,--no-as-needed \ + -I"$script_dir" \ + "$script_dir/kzt_guest_main.c" \ + -L"$build_dir" -lkzt_preempt_a -lkzt_preempt_b -lkzt_guest_probe \ + -o "$build_dir/kzt_guest_preempt_ab_main" + +run_cc -O2 -nostdlib -Wl,-e,_start -Wl,-z,now \ + -Wl,--no-as-needed \ + -I"$script_dir" \ + "$script_dir/kzt_guest_main.c" \ + -L"$build_dir" -lkzt_preempt_weak -lkzt_guest_probe \ + -o "$build_dir/kzt_guest_preempt_weak_main" + +run_cc -O2 -nostdlib -Wl,-e,_start -Wl,-z,now \ + -DKZT_GUEST_LOAD_LOCAL_PREEMPTION_GROUP=1 \ + -Wl,--no-as-needed \ + -I"$script_dir" \ + "$script_dir/kzt_guest_main.c" \ + -L"$build_dir" -lkzt_preempt_a -lkzt_guest_probe \ + "$guest_libc" \ + -o "$build_dir/kzt_guest_local_scope_main" + +needed_order() +{ + "$guest_readelf" -d "$1" | + sed -n 's/.*Shared library: \[\([^]]*\)\].*/\1/p' | + paste -sd: - +} + +if [[ $(needed_order "$build_dir/kzt_guest_preempt_a_main") != \ + "libkzt_preempt_a.so:libkzt_guest_probe.so" ]]; then + echo "Strong preemption main has the wrong DT_NEEDED order." >&2 + exit 1 +fi +if [[ $(needed_order "$build_dir/kzt_guest_preempt_ab_main") != \ + "libkzt_preempt_a.so:libkzt_preempt_b.so:libkzt_guest_probe.so" ]]; then + echo "Two-provider main has the wrong DT_NEEDED order." >&2 + exit 1 +fi +if [[ $(needed_order "$build_dir/kzt_guest_preempt_weak_main") != \ + "libkzt_preempt_weak.so:libkzt_guest_probe.so" ]]; then + echo "Weak preemption main has the wrong DT_NEEDED order." >&2 + exit 1 +fi + +strong_a_symbols=$( + "$guest_readelf" -Ws "$build_dir/libkzt_preempt_a.so" +) +strong_b_symbols=$( + "$guest_readelf" -Ws "$build_dir/libkzt_preempt_b.so" +) +weak_symbols=$( + "$guest_readelf" -Ws "$build_dir/libkzt_preempt_weak.so" +) +local_symbols=$( + "$guest_readelf" -Ws "$build_dir/libkzt_local_preempt.so" +) +if [[ $(grep -Ec \ + "FUNC[[:space:]]+GLOBAL.*dlerror@@${e2e_versions}([[:space:]]|$)" \ + <<< "$strong_a_symbols") -ne 1 || + $(grep -Ec \ + "FUNC[[:space:]]+GLOBAL.*dlerror@@${e2e_versions}([[:space:]]|$)" \ + <<< "$strong_b_symbols") -ne 1 ]]; then + echo "Strong providers do not export the required dlerror version." >&2 + exit 1 +fi +if [[ $(grep -Ec \ + "FUNC[[:space:]]+WEAK.*dlerror@@${e2e_versions}([[:space:]]|$)" \ + <<< "$weak_symbols") -ne 1 ]]; then + echo "Weak provider does not export the required dlerror version." >&2 + exit 1 +fi +if [[ $(grep -Ec \ + "FUNC[[:space:]]+GLOBAL.*dlerror@@${e2e_versions}([[:space:]]|$)" \ + <<< "$local_symbols") -ne 1 ]]; then + echo "RTLD_LOCAL provider does not export the required dlerror version." >&2 + exit 1 +fi + +local_main_needed=$(needed_order "$build_dir/kzt_guest_local_scope_main") +if [[ $local_main_needed != \ + "libkzt_preempt_a.so:libkzt_guest_probe.so:libc.so.6" ]]; then + echo "RTLD_LOCAL isolation main has the wrong DT_NEEDED set." >&2 + exit 1 +fi +if [[ $local_main_needed == *libkzt_local_preempt.so* ]]; then + echo "RTLD_LOCAL provider must not be a startup dependency." >&2 + exit 1 +fi + +perf_symbols=$($guest_readelf -Ws "$build_dir/libkzt_guest_perf_probe.so") +perf_versions=$(sed -n \ + "s/.* UND ${e2e_symbol}@\\(GLIBC_[^[:space:]]*\\).*/\\1/p" \ + <<< "$perf_symbols" | sort -u) +if [[ -z $perf_versions || $perf_versions == *$'\n'* ]]; then + echo "Guest performance DSO must require one versioned ${e2e_symbol} symbol." >&2 + exit 1 +fi +printf '%s\n' "$perf_versions" > "$build_dir/guest-performance-symbol-version.txt" +printf '%s\n' "$e2e_symbol" > "$build_dir/guest-performance-symbol-name.txt" dynamic=$($guest_readelf -d "$build_dir/libkzt_guest_probe.so") if grep -Eq '(BIND_NOW|FLAGS.*NOW)' <<< "$dynamic"; then @@ -66,6 +243,12 @@ if grep -Eq '(BIND_NOW|FLAGS.*NOW)' <<< "$dynamic"; then exit 1 fi +perf_dynamic=$($guest_readelf -d "$build_dir/libkzt_guest_perf_probe.so") +if grep -Eq '(BIND_NOW|FLAGS.*NOW)' <<< "$perf_dynamic"; then + echo "Guest performance DSO must use lazy binding." >&2 + exit 1 +fi + main_dynamic=$($guest_readelf -d "$build_dir/kzt_guest_main") if ! grep -Fq 'Shared library: [libkzt_guest_probe.so]' \ <<< "$main_dynamic"; then @@ -73,9 +256,57 @@ if ! grep -Fq 'Shared library: [libkzt_guest_probe.so]' \ exit 1 fi +main_relocations=$($guest_readelf -rW "$build_dir/kzt_guest_main") +probe_jump_slots=$(grep -Ec \ + 'R_X86_64_(JUMP_SLOT|JUMP_SLO).*kzt_guest_probe(@|[[:space:]])' \ + <<< "$main_relocations" || true) +if [[ $probe_jump_slots -ne 1 ]]; then + echo "Guest main must contain exactly one kzt_guest_probe JUMP_SLOT." >&2 + exit 1 +fi +if ! grep -Eq '(BIND_NOW|FLAGS.*NOW)' <<< "$main_dynamic"; then + echo "Guest main must bind kzt_guest_probe at startup." >&2 + exit 1 +fi + +perf_main_dynamic=$($guest_readelf -d "$build_dir/kzt_guest_perf_main") +if ! grep -Fq 'Shared library: [libkzt_guest_perf_probe.so]' \ + <<< "$perf_main_dynamic"; then + echo "Guest performance main is not linked to its probe DSO." >&2 + exit 1 +fi +perf_main_relocations=$($guest_readelf -rW "$build_dir/kzt_guest_perf_main") +perf_main_jump_slots=$(grep -Ec 'R_X86_64_(JUMP_SLOT|JUMP_SLO)' \ + <<< "$perf_main_relocations" || true) +if [[ $perf_main_jump_slots -ne 0 ]]; then + echo "Guest performance main must not contain a lazy JUMP_SLOT." >&2 + exit 1 +fi + +run_cc --version > "$build_dir/guest-compiler.txt" +{ + printf 'KZT_GUEST_CC=%q\n' "$guest_cc" + printf 'KZT_GUEST_CC_FLAGS=%q\n' "${KZT_GUEST_CC_FLAGS:-}" + printf 'KZT_GUEST_CC_LAUNCHER=%q\n' "${KZT_GUEST_CC_LAUNCHER:-}" + printf 'KZT_GUEST_CC_LAUNCHER_FLAGS=%q\n' \ + "${KZT_GUEST_CC_LAUNCHER_FLAGS:-}" + printf 'KZT_GUEST_LIBC=%q\n' "$guest_libc" + printf '%s\n' \ + 'performance DSO: -O2 -fPIC -shared -nostdlib' \ + 'performance main: -O2 -fno-plt -nostdlib -Wl,-e,_start' \ + 'E2E main: -O2 -nostdlib -Wl,-e,_start -Wl,-z,now' \ + 'E2E DSO: lazy dlerror JUMP_SLOT required' \ + 'performance DSO: lazy versioned dlerror JUMP_SLOT required' +} > "$build_dir/guest-build-parameters.txt" + printf 'Guest fixture built in %s\n' "$build_dir" -printf 'uname version: %s\n' "$uname_versions" +printf '%s version: %s\n' "$e2e_symbol" "$e2e_versions" printf 'Run: python3 tests/unit/kzt/test_real_guest_e2e.py '\''\n' printf ' --latx --guest-root '\''\n' printf ' --host-libc '\''\n' printf ' --fixture-dir %q\n' "$build_dir" +printf 'Performance: python3 tests/unit/kzt/test_real_guest_performance.py '\''\n' +printf ' --baseline-latx '\''\n' +printf ' --candidate-latx '\''\n' +printf ' --guest-root --fixture-dir %q '\''\n' "$build_dir" +printf ' --cpu --output-dir \n' diff --git a/tests/unit/kzt/guest_e2e/kzt_guest_main.c b/tests/unit/kzt/guest_e2e/kzt_guest_main.c index 8c66034e358..a24f7a51bfe 100644 --- a/tests/unit/kzt/guest_e2e/kzt_guest_main.c +++ b/tests/unit/kzt/guest_e2e/kzt_guest_main.c @@ -1,5 +1,10 @@ #include "kzt_guest_probe.h" +#ifdef KZT_GUEST_LOAD_LOCAL_PREEMPTION_GROUP +extern void *dlopen(const char *path, int flags); +#define KZT_GUEST_RTLD_NOW 2 +#endif + static long raw_syscall3(long number, long arg1, long arg2, long arg3) { long result; @@ -54,6 +59,17 @@ void _start(void) char success[256]; char *cursor = success; +#ifdef KZT_GUEST_LOAD_LOCAL_PREEMPTION_GROUP + if (!dlopen("libkzt_local_preempt.so", KZT_GUEST_RTLD_NOW)) { + (void)raw_syscall3(1, 2, (long)failure, sizeof(failure) - 1); + raw_exit(3); + } + { + static const char ready[] = "KZT_LOCAL_GROUP_READY\n"; + + (void)raw_syscall3(1, 2, (long)ready, sizeof(ready) - 1); + } +#endif if (kzt_guest_probe(&result) != 0) { (void)raw_syscall3(1, 2, (long)failure, sizeof(failure) - 1); raw_exit(1); diff --git a/tests/unit/kzt/guest_e2e/kzt_guest_perf_main.c b/tests/unit/kzt/guest_e2e/kzt_guest_perf_main.c new file mode 100644 index 00000000000..be65b7ae457 --- /dev/null +++ b/tests/unit/kzt/guest_e2e/kzt_guest_perf_main.c @@ -0,0 +1,136 @@ +#include "kzt_guest_perf_probe.h" + +static long raw_write(int descriptor, const char *data, unsigned long length) +{ + long result; + + __asm__ volatile( + "syscall" + : "=a"(result) + : "a"(1L), "D"((long)descriptor), "S"((long)data), "d"(length) + : "rcx", "r11", "memory"); + return result; +} + +static char *append_text(char *cursor, const char *text) +{ + while (*text) { + *cursor++ = *text++; + } + return cursor; +} + +static char *append_hex(char *cursor, uint64_t value) +{ + static const char digits[] = "0123456789abcdef"; + int shift; + + cursor = append_text(cursor, "0x"); + for (shift = 60; shift > 0; shift -= 4) { + if ((value >> shift) != 0) { + break; + } + } + for (; shift >= 0; shift -= 4) { + *cursor++ = digits[(value >> shift) & 0xf]; + } + return cursor; +} + +static int parse_count(const char *text, uint64_t *value) +{ + uint64_t parsed = 0; + + if (!text || !*text) { + return -1; + } + while (*text) { + unsigned int digit; + + if (*text < '0' || *text > '9') { + return -1; + } + digit = (unsigned int)(*text++ - '0'); + if (parsed > (UINT64_MAX - digit) / 10) { + return -1; + } + parsed = parsed * 10 + digit; + } + if (parsed < 100000) { + return -1; + } + *value = parsed; + return 0; +} + +static int text_equals(const char *text, const char *expected) +{ + while (*text && *expected && *text == *expected) { + ++text; + ++expected; + } + return *text == '\0' && *expected == '\0'; +} + +int kzt_guest_perf_main(uintptr_t *initial_stack) +{ + static const char usage[] = "KZT_GUEST_PERF_FAIL invalid_mode\n"; + static const char failure[] = "KZT_GUEST_PERF_FAIL probe\n"; + struct kzt_guest_perf_result result = { 0 }; + char **argv = (char **)&initial_stack[1]; + const char *mode; + char output[512]; + char *cursor = output; + + if (initial_stack[0] < 2 || initial_stack[0] > 3) { + (void)raw_write(2, usage, sizeof(usage) - 1); + return 2; + } + mode = argv[1]; + if (text_equals(mode, "startup") && initial_stack[0] == 2) { + /* Loading the DSO is the mode's work; it must not call dlerror. */ + } else if (text_equals(mode, "first") && initial_stack[0] == 2) { + if (kzt_guest_perf_first(&result) != 0) { + (void)raw_write(2, failure, sizeof(failure) - 1); + return 1; + } + } else if (text_equals(mode, "steady") && initial_stack[0] == 3 && + parse_count(argv[2], &result.steady_calls) == 0) { + uint64_t steady_calls = result.steady_calls; + + if (kzt_guest_perf_steady(steady_calls, &result) != 0) { + (void)raw_write(2, failure, sizeof(failure) - 1); + return 1; + } + } else { + (void)raw_write(2, usage, sizeof(usage) - 1); + return 2; + } + + cursor = append_text(cursor, "KZT_GUEST_PERF_OK mode="); + cursor = append_text(cursor, mode); + cursor = append_text(cursor, " steady_calls="); + cursor = append_hex(cursor, result.steady_calls); + cursor = append_text(cursor, " slot="); + cursor = append_hex(cursor, result.slot_addr); + cursor = append_text(cursor, " before="); + cursor = append_hex(cursor, result.before); + cursor = append_text(cursor, " after_first="); + cursor = append_hex(cursor, result.after_first); + cursor = append_text(cursor, " after_steady="); + cursor = append_hex(cursor, result.after_steady); + cursor = append_text(cursor, " first_ns="); + cursor = append_hex(cursor, result.first_call_ns); + cursor = append_text(cursor, " steady_total_ns="); + cursor = append_hex(cursor, result.steady_total_ns); + cursor = append_text(cursor, " steady_per_call_ns="); + cursor = append_hex(cursor, result.steady_per_call_ns); + cursor = append_text(cursor, " checksum="); + cursor = append_hex(cursor, result.checksum); + *cursor++ = '\n'; + if (raw_write(1, output, (unsigned long)(cursor - output)) != + cursor - output) { + return 3; + } + return 0; +} diff --git a/tests/unit/kzt/guest_e2e/kzt_guest_perf_probe.c b/tests/unit/kzt/guest_e2e/kzt_guest_perf_probe.c new file mode 100644 index 00000000000..140ecc2d9b7 --- /dev/null +++ b/tests/unit/kzt/guest_e2e/kzt_guest_perf_probe.c @@ -0,0 +1,136 @@ +#include "kzt_guest_perf_probe.h" + +#ifndef NULL +#define NULL ((void *)0) +#endif + +extern char *dlerror(void); + +struct guest_timespec { + long seconds; + long nanoseconds; +}; + +static uint64_t monotonic_raw_ns(void) +{ + struct guest_timespec value; + long status; + + __asm__ volatile( + "syscall" + : "=a"(status) + : "a"(228L), "D"(4L), "S"((long)&value) + : "rcx", "r11", "memory"); + if (status != 0) { + return 0; + } + return (uint64_t)value.seconds * 1000000000ULL + + (uint64_t)value.nanoseconds; +} + +static uintptr_t *dlerror_jump_slot(void) +{ + const unsigned char *plt; + uint32_t raw_displacement; + int32_t displacement; + + __asm__ volatile("lea dlerror@PLT(%%rip), %0" : "=r"(plt)); + if (plt[0] != 0xff || plt[1] != 0x25) { + return 0; + } + raw_displacement = (uint32_t)plt[2] | + ((uint32_t)plt[3] << 8) | + ((uint32_t)plt[4] << 16) | + ((uint32_t)plt[5] << 24); + displacement = (int32_t)raw_displacement; + return (uintptr_t *)(plt + 6 + displacement); +} + +static int dlerror_null_call(uint64_t *checksum) +{ + if (dlerror() != NULL) { + return -1; + } + ++*checksum; + return 0; +} + +static int initialize_result(struct kzt_guest_perf_result *result, + uintptr_t **slot) +{ + if (!result || !slot || !(*slot = dlerror_jump_slot())) { + return -1; + } + *result = (struct kzt_guest_perf_result) { 0 }; + result->slot_addr = (uintptr_t)*slot; + result->before = *(volatile uintptr_t *)*slot; + return result->before ? 0 : -1; +} + +static int kzt_guest_perf_run_first(struct kzt_guest_perf_result *result) +{ + uintptr_t *slot; + uint64_t before_call; + uint64_t after_call; + + if (initialize_result(result, &slot) != 0) { + return 2; + } + + before_call = monotonic_raw_ns(); + if (!before_call) { + return 1; + } + if (dlerror_null_call(&result->checksum) != 0) { + return 1; + } + after_call = monotonic_raw_ns(); + if (!after_call || after_call < before_call) { + return 1; + } + result->first_call_ns = after_call - before_call; + result->after_first = *(volatile uintptr_t *)slot; + result->after_steady = result->after_first; + return result->after_first ? 0 : 1; +} + +int kzt_guest_perf_first(struct kzt_guest_perf_result *result) +{ + return kzt_guest_perf_run_first(result); +} + +int kzt_guest_perf_steady(uint64_t steady_calls, + struct kzt_guest_perf_result *result) +{ + uintptr_t *slot; + uint64_t before_call; + uint64_t after_call; + uint64_t index; + + if (steady_calls < 100000 || + kzt_guest_perf_run_first(result) != 0 || + !(slot = (uintptr_t *)result->slot_addr)) { + return 1; + } + + before_call = monotonic_raw_ns(); + if (!before_call) { + return 1; + } + for (index = 0; index < steady_calls; ++index) { + if (dlerror_null_call(&result->checksum) != 0) { + return 1; + } + } + after_call = monotonic_raw_ns(); + if (!after_call || after_call <= before_call) { + return 1; + } + result->steady_calls = steady_calls; + result->steady_total_ns = after_call - before_call; + result->steady_per_call_ns = result->steady_total_ns / steady_calls; + result->after_steady = *(volatile uintptr_t *)slot; + return result->steady_per_call_ns == 0 || + result->after_steady == 0 || + result->checksum != steady_calls + 1 ? 1 : 0; +} diff --git a/tests/unit/kzt/guest_e2e/kzt_guest_perf_probe.h b/tests/unit/kzt/guest_e2e/kzt_guest_perf_probe.h new file mode 100644 index 00000000000..c7b11047fbc --- /dev/null +++ b/tests/unit/kzt/guest_e2e/kzt_guest_perf_probe.h @@ -0,0 +1,22 @@ +#ifndef KZT_GUEST_PERF_PROBE_H +#define KZT_GUEST_PERF_PROBE_H + +#include + +struct kzt_guest_perf_result { + uintptr_t slot_addr; + uintptr_t before; + uintptr_t after_first; + uintptr_t after_steady; + uint64_t first_call_ns; + uint64_t steady_total_ns; + uint64_t steady_per_call_ns; + uint64_t steady_calls; + uint64_t checksum; +}; + +int kzt_guest_perf_first(struct kzt_guest_perf_result *result); +int kzt_guest_perf_steady(uint64_t steady_calls, + struct kzt_guest_perf_result *result); + +#endif diff --git a/tests/unit/kzt/guest_e2e/kzt_guest_perf_start.S b/tests/unit/kzt/guest_e2e/kzt_guest_perf_start.S new file mode 100644 index 00000000000..048809780fc --- /dev/null +++ b/tests/unit/kzt/guest_e2e/kzt_guest_perf_start.S @@ -0,0 +1,14 @@ + .text + .globl _start + .type _start, @function +_start: + movq %rsp, %rdi + andq $-16, %rsp + call kzt_guest_perf_main + movl %eax, %edi + movl $60, %eax + syscall + hlt + .size _start, .-_start + + .section .note.GNU-stack,"",@progbits diff --git a/tests/unit/kzt/guest_e2e/kzt_guest_preemption_provider.c b/tests/unit/kzt/guest_e2e/kzt_guest_preemption_provider.c new file mode 100644 index 00000000000..40c3477dfcd --- /dev/null +++ b/tests/unit/kzt/guest_e2e/kzt_guest_preemption_provider.c @@ -0,0 +1,38 @@ +#include + +#ifndef KZT_PREEMPTION_PROVIDER +#define KZT_PREEMPTION_PROVIDER 0 +#endif + +#if KZT_PREEMPTION_PROVIDER == 1 +#define KZT_PREEMPTION_MARKER "KZT_PREEMPT_PROVIDER_A\n" +#elif KZT_PREEMPTION_PROVIDER == 2 +#define KZT_PREEMPTION_MARKER "KZT_PREEMPT_PROVIDER_B\n" +#elif KZT_PREEMPTION_PROVIDER == 3 +#define KZT_PREEMPTION_MARKER "KZT_LOCAL_PREEMPT_PROVIDER\n" +#else +#define KZT_PREEMPTION_MARKER "KZT_PREEMPT_PROVIDER_WEAK\n" +#endif + +static long raw_write(int fd, const void *buffer, size_t size) +{ + long result; + + __asm__ volatile( + "syscall" + : "=a"(result) + : "a"(1L), "D"((long)fd), "S"((long)buffer), "d"((long)size) + : "rcx", "r11", "memory"); + return result; +} + +#if KZT_PREEMPTION_PROVIDER == 0 +__attribute__((weak)) +#endif +char *dlerror(void) +{ + static const char marker[] = KZT_PREEMPTION_MARKER; + + (void)raw_write(2, marker, sizeof(marker) - 1); + return 0; +} diff --git a/tests/unit/kzt/guest_e2e/kzt_guest_probe.c b/tests/unit/kzt/guest_e2e/kzt_guest_probe.c index 9bd722a1f36..73d834cf335 100644 --- a/tests/unit/kzt/guest_e2e/kzt_guest_probe.c +++ b/tests/unit/kzt/guest_e2e/kzt_guest_probe.c @@ -1,15 +1,6 @@ #include "kzt_guest_probe.h" -struct guest_utsname { - char sysname[65]; - char nodename[65]; - char release[65]; - char version[65]; - char machine[65]; - char domainname[65]; -}; - -extern int uname(struct guest_utsname *result); +extern char *dlerror(void); struct guest_timespec { long seconds; @@ -33,13 +24,13 @@ static uint64_t monotonic_raw_ns(void) (uint64_t)value.nanoseconds; } -static uintptr_t *uname_jump_slot(void) +static uintptr_t *dlerror_jump_slot(void) { const unsigned char *plt; uint32_t raw_displacement; int32_t displacement; - __asm__ volatile("lea uname@PLT(%%rip), %0" : "=r"(plt)); + __asm__ volatile("lea dlerror@PLT(%%rip), %0" : "=r"(plt)); if (plt[0] != 0xff || plt[1] != 0x25) { return 0; } @@ -53,29 +44,27 @@ static uintptr_t *uname_jump_slot(void) int kzt_guest_probe(struct kzt_guest_probe_result *result) { - struct guest_utsname utsname; uintptr_t *slot; uint64_t before_call; uint64_t after_call; - if (!result || !(slot = uname_jump_slot())) { + if (!result || !(slot = dlerror_jump_slot())) { return 2; } result->slot_addr = (uintptr_t)slot; result->before = *(volatile uintptr_t *)slot; before_call = monotonic_raw_ns(); - if (!before_call || uname(&utsname) != 0 || - utsname.machine[0] == '\0' || - !(after_call = monotonic_raw_ns()) || after_call < before_call) { + if (!before_call || + ((void)dlerror(), !(after_call = monotonic_raw_ns())) || + after_call < before_call) { return 1; } result->first_call_ns = after_call - before_call; result->after_first = *(volatile uintptr_t *)slot; before_call = monotonic_raw_ns(); - if (!before_call || uname(&utsname) != 0 || - utsname.machine[0] == '\0' || + if (!before_call || dlerror() != 0 || !(after_call = monotonic_raw_ns()) || after_call < before_call) { return 1; } diff --git a/tests/unit/kzt/guest_loader/build_guest_loader_fixture.sh b/tests/unit/kzt/guest_loader/build_guest_loader_fixture.sh new file mode 100755 index 00000000000..1a1ca41315e --- /dev/null +++ b/tests/unit/kzt/guest_loader/build_guest_loader_fixture.sh @@ -0,0 +1,253 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +guest_root=${KZT_GUEST_ROOT:-} +build_dir=${KZT_GUEST_BUILD_DIR:-${TMPDIR:-/tmp}/wi600-guest-loader} +guest_cc=${KZT_GUEST_CC:-x86_64-linux-gnu-gcc} +guest_readelf=${KZT_GUEST_READELF:-readelf} + +read -r -a cc_flags <<< "${KZT_GUEST_CC_FLAGS:-}" +read -r -a linker_flags <<< "${KZT_GUEST_LDFLAGS:-}" +read -r -a launcher_flags <<< "${KZT_GUEST_CC_LAUNCHER_FLAGS:-}" + +usage() +{ + echo "usage: $0 --guest-root DIR [--output DIR]" >&2 +} + +inconclusive() +{ + echo "WI-600 guest loader fixture: INCONCLUSIVE: $*" >&2 + exit 77 +} + +fail() +{ + echo "WI-600 guest loader fixture: FAIL: $*" >&2 + exit 1 +} + +while [[ $# -gt 0 ]]; do + case $1 in + --guest-root) + [[ $# -ge 2 ]] || { usage; exit 2; } + guest_root=$2 + shift 2 + ;; + --output) + [[ $# -ge 2 ]] || { usage; exit 2; } + build_dir=$2 + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + usage + exit 2 + ;; + esac +done + +[[ -n $guest_root ]] || inconclusive \ + "guest root was not provided with --guest-root or KZT_GUEST_ROOT" +[[ -d $guest_root ]] || inconclusive "guest root not found: $guest_root" +command -v "$guest_cc" >/dev/null 2>&1 || inconclusive \ + "x86-64 guest compiler not found: $guest_cc" +command -v "$guest_readelf" >/dev/null 2>&1 || inconclusive \ + "ELF inspection tool not found: $guest_readelf" +if [[ -n ${KZT_GUEST_CC_LAUNCHER:-} ]]; then + command -v "$KZT_GUEST_CC_LAUNCHER" >/dev/null 2>&1 || inconclusive \ + "compiler launcher not found: $KZT_GUEST_CC_LAUNCHER" +fi + +find_guest_file() +{ + local relative + + for relative in "$@"; do + if [[ -f $guest_root/$relative ]]; then + printf '%s\n' "$guest_root/$relative" + return 0 + fi + done + return 1 +} + +if [[ -n ${KZT_GUEST_DYNAMIC_LINKER:-} ]]; then + dynamic_linker=$KZT_GUEST_DYNAMIC_LINKER + [[ $dynamic_linker == /* ]] || inconclusive \ + "KZT_GUEST_DYNAMIC_LINKER must be a guest-absolute path" + [[ -f $guest_root$dynamic_linker ]] || inconclusive \ + "guest dynamic linker not found: $guest_root$dynamic_linker" +else + dynamic_linker_host=$(find_guest_file \ + lib64/ld-linux-x86-64.so.2 \ + lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 \ + usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2) || inconclusive \ + "x86-64 dynamic linker not found under $guest_root" + dynamic_linker=/${dynamic_linker_host#"$guest_root"/} +fi + +if [[ -n ${KZT_GUEST_LIBC:-} ]]; then + guest_libc=$KZT_GUEST_LIBC + [[ -f $guest_libc ]] || inconclusive \ + "KZT_GUEST_LIBC not found: $guest_libc" +else + guest_libc=$(find_guest_file \ + lib/x86_64-linux-gnu/libc.so.6 \ + usr/lib/x86_64-linux-gnu/libc.so.6 \ + lib64/libc.so.6 \ + usr/lib64/libc.so.6 \ + lib/libc.so.6 \ + usr/lib/libc.so.6) || inconclusive \ + "x86-64 libc.so.6 not found under $guest_root" +fi + +if [[ -n ${KZT_GUEST_LIBDL:-} ]]; then + guest_libdl=$KZT_GUEST_LIBDL + [[ -f $guest_libdl ]] || inconclusive \ + "KZT_GUEST_LIBDL not found: $guest_libdl" +else + guest_libdl=$(find_guest_file \ + lib/x86_64-linux-gnu/libdl.so.2 \ + usr/lib/x86_64-linux-gnu/libdl.so.2 \ + lib64/libdl.so.2 \ + usr/lib64/libdl.so.2 \ + lib/libdl.so.2 \ + usr/lib/libdl.so.2) || inconclusive \ + "x86-64 libdl.so.2 not found under $guest_root" +fi + +run_cc() +{ + if [[ -n ${KZT_GUEST_CC_LAUNCHER:-} ]]; then + "$KZT_GUEST_CC_LAUNCHER" "${launcher_flags[@]}" \ + "$guest_cc" "${cc_flags[@]}" "$@" + else + "$guest_cc" "${cc_flags[@]}" "$@" + fi +} + +build_shared() +{ + local source=$1 + local soname=$2 + shift 2 + + run_cc --sysroot="$guest_root" -O2 -ffreestanding -fno-builtin \ + -fno-stack-protector -fPIC -nostdlib -shared \ + "$script_dir/$source" "$@" "${linker_flags[@]}" \ + -Wl,-soname,"$soname" -o "$build_dir/$soname" || fail \ + "could not build $soname" +} + +build_program() +{ + local source=$1 + local output=$2 + + run_cc --sysroot="$guest_root" -O2 -ffreestanding -fno-builtin \ + -fno-stack-protector -fPIE -nostdlib -pie \ + -I"$script_dir" "$script_dir/$source" \ + -Wl,-e,_start -Wl,--dynamic-linker,"$dynamic_linker" \ + -Wl,--no-as-needed "$guest_libdl" "$guest_libc" \ + -Wl,--allow-shlib-undefined "${linker_flags[@]}" \ + -o "$build_dir/$output" || fail "could not build $output" +} + +mkdir -p "$build_dir" +rm -f \ + "$build_dir/libwi600_helper.so" \ + "$build_dir/libwi600_plugin.so" \ + "$build_dir/libwi600_visibility.so" \ + "$build_dir/libwi600_namespace.so" \ + "$build_dir/libwi600_versions.so" \ + "$build_dir/libwi1065_constructor.so" \ + "$build_dir/libwi1065_partial_relro.so" \ + "$build_dir/libwi1065_full_relro.so" \ + "$build_dir/dependency-reopen" \ + "$build_dir/visibility-noload" \ + "$build_dir/namespace-isolation" \ + "$build_dir/symbol-versions-errors" \ + "$build_dir/wrapped-library-handle" \ + "$build_dir/wi1065-loader-events" + +build_shared helper.c libwi600_helper.so +build_shared plugin.c libwi600_plugin.so \ + -Wl,--no-as-needed "$build_dir/libwi600_helper.so" \ + '-Wl,-rpath,$ORIGIN' +build_shared visibility.c libwi600_visibility.so +build_shared namespace.c libwi600_namespace.so +build_shared versions.c libwi600_versions.so \ + -Wl,--version-script,"$script_dir/versions.map" +build_shared wi1065_constructor.c libwi1065_constructor.so -Wl,-z,relro +build_shared wi1065_relro.c libwi1065_partial_relro.so -Wl,-z,relro +build_shared wi1065_relro.c libwi1065_full_relro.so -Wl,-z,relro,-z,now + +build_program dependency_reopen.c dependency-reopen +build_program visibility_noload.c visibility-noload +build_program namespace_isolation.c namespace-isolation +build_program symbol_versions_errors.c symbol-versions-errors +build_program wrapped_library_handle.c wrapped-library-handle +build_program wi1065_loader_events.c wi1065-loader-events + +fixture_files=( + libwi600_helper.so + libwi600_plugin.so + libwi600_visibility.so + libwi600_namespace.so + libwi600_versions.so + libwi1065_constructor.so + libwi1065_partial_relro.so + libwi1065_full_relro.so + dependency-reopen + visibility-noload + namespace-isolation + symbol-versions-errors + wrapped-library-handle + wi1065-loader-events +) + +for fixture_file in "${fixture_files[@]}"; do + header=$("$guest_readelf" -h "$build_dir/$fixture_file") || fail \ + "could not inspect $fixture_file" + grep -Eq 'Machine:.*(Advanced Micro Devices X86-64|X86-64)' \ + <<< "$header" || inconclusive \ + "$guest_cc did not produce x86-64 ELF: $fixture_file" +done + +plugin_dynamic=$("$guest_readelf" -d "$build_dir/libwi600_plugin.so") || \ + fail "could not inspect plugin dependencies" +grep -Fq 'Shared library: [libwi600_helper.so]' \ + <<< "$plugin_dynamic" || fail \ + "plugin does not retain its helper DT_NEEDED dependency" + +version_symbols=$("$guest_readelf" -Ws "$build_dir/libwi600_versions.so") || \ + fail "could not inspect versioned symbols" +grep -Fq 'wi600_versioned_value@WI600_1.0' <<< "$version_symbols" || fail \ + "versioned DSO is missing WI600_1.0" +grep -Fq 'wi600_versioned_value@@WI600_2.0' <<< "$version_symbols" || fail \ + "versioned DSO is missing default WI600_2.0" + +partial_relro=$("$guest_readelf" -l "$build_dir/libwi1065_partial_relro.so") || fail \ + "could not inspect partial RELRO fixture" +full_relro=$("$guest_readelf" -l "$build_dir/libwi1065_full_relro.so") || fail \ + "could not inspect full RELRO fixture" +full_dynamic=$("$guest_readelf" -d "$build_dir/libwi1065_full_relro.so") || fail \ + "could not inspect full RELRO dynamic tags" +grep -Fq 'GNU_RELRO' <<< "$partial_relro" || fail \ + "partial RELRO fixture is missing GNU_RELRO" +grep -Fq 'GNU_RELRO' <<< "$full_relro" || fail \ + "full RELRO fixture is missing GNU_RELRO" +grep -Eq '(BIND_NOW|FLAGS.*NOW)' <<< "$full_dynamic" || fail \ + "full RELRO fixture is missing BIND_NOW" + +printf 'WI-600 guest loader fixture: PASS\n' +printf 'Fixture directory: %s\n' "$build_dir" +printf 'Guest root: %s\n' "$guest_root" +printf 'Run: python3 tests/unit/kzt/test_real_guest_loader_gate.py \\\n' +printf ' --baseline-latx --candidate-latx \\\n' +printf ' --guest-root %q --fixture-dir %q\n' "$guest_root" "$build_dir" diff --git a/tests/unit/kzt/guest_loader/dependency_reopen.c b/tests/unit/kzt/guest_loader/dependency_reopen.c new file mode 100644 index 00000000000..bb81f6bea4d --- /dev/null +++ b/tests/unit/kzt/guest_loader/dependency_reopen.c @@ -0,0 +1,84 @@ +#include "guest_loader_test.h" + +#define SCENARIO "dependency-reopen" +#define PLUGIN "libwi600_plugin.so" +#define SYMBOL "wi600_plugin_value" + +void _start(void) +{ + wi600_value_function_t value; + void *first; + void *noload; + void *nodelete; + void *second; + void *reopened; + + first = dlopen(PLUGIN, WI600_RTLD_NOW | WI600_RTLD_LOCAL); + wi600_require(first != (void *)0, SCENARIO, 90, + "initial plugin dlopen failed"); + second = dlopen(PLUGIN, WI600_RTLD_NOW | WI600_RTLD_LOCAL); + wi600_require(second != (void *)0, SCENARIO, 91, + "duplicate plugin dlopen failed"); + wi600_require(second == first, SCENARIO, 92, + "duplicate dlopen returned a different handle"); + + wi600_clear_dlerror(); + value = wi600_value_symbol(second, SYMBOL); + wi600_require(value != (wi600_value_function_t)0, SCENARIO, 93, + "dlsym failed for dependency-backed symbol"); + wi600_require(dlerror() == (char *)0, SCENARIO, 94, + "successful dlsym left an error"); + wi600_require(value() == 123, SCENARIO, 95, + "dependency-backed call did not return 123"); + + wi600_require(dlclose(second) == 0, SCENARIO, 96, + "first dlclose failed"); + wi600_require(value() == 123, SCENARIO, 97, + "remaining reference was not callable"); + wi600_require(dlclose(first) == 0, SCENARIO, 98, + "final dlclose failed"); + wi600_clear_dlerror(); + noload = dlopen( + PLUGIN, WI600_RTLD_NOW | WI600_RTLD_LOCAL | WI600_RTLD_NOLOAD); + wi600_require(noload == (void *)0, SCENARIO, 99, + "fully closed object remained visible to NOLOAD"); + wi600_require(dlerror() != (char *)0, SCENARIO, 100, + "NOLOAD miss did not report an error"); + + reopened = dlopen(PLUGIN, WI600_RTLD_NOW | WI600_RTLD_LOCAL); + wi600_require(reopened != (void *)0, SCENARIO, 101, + "reopen after full close failed"); + wi600_clear_dlerror(); + value = wi600_value_symbol(reopened, SYMBOL); + wi600_require(value != (wi600_value_function_t)0, SCENARIO, 102, + "dlsym after reopen failed"); + wi600_require(dlerror() == (char *)0, SCENARIO, 103, + "reopened dlsym left an error"); + wi600_require(value() == 123, SCENARIO, 104, + "reopened plugin call did not return 123"); + wi600_require(dlclose(reopened) == 0, SCENARIO, 105, + "reopened plugin dlclose failed"); + + nodelete = dlopen( + PLUGIN, WI600_RTLD_NOW | WI600_RTLD_LOCAL | WI600_RTLD_NODELETE); + wi600_require(nodelete != (void *)0, SCENARIO, 106, + "NODELETE dlopen failed"); + wi600_require(dlclose(nodelete) == 0, SCENARIO, 107, + "NODELETE dlclose failed"); + wi600_clear_dlerror(); + noload = dlopen( + PLUGIN, WI600_RTLD_NOW | WI600_RTLD_LOCAL | WI600_RTLD_NOLOAD); + wi600_require(noload != (void *)0, SCENARIO, 108, + "NODELETE object disappeared after dlclose"); + wi600_clear_dlerror(); + value = wi600_value_symbol(noload, SYMBOL); + wi600_require(value != (wi600_value_function_t)0, SCENARIO, 109, + "NODELETE object symbol lookup failed"); + wi600_require(dlerror() == (char *)0, SCENARIO, 110, + "NODELETE symbol lookup left an error"); + wi600_require(value() == 123, SCENARIO, 111, + "NODELETE object symbol call failed"); + wi600_require(dlclose(noload) == 0, SCENARIO, 112, + "NODELETE NOLOAD handle close failed"); + wi600_pass(SCENARIO); +} diff --git a/tests/unit/kzt/guest_loader/guest_loader_test.h b/tests/unit/kzt/guest_loader/guest_loader_test.h new file mode 100644 index 00000000000..6c5285a5312 --- /dev/null +++ b/tests/unit/kzt/guest_loader/guest_loader_test.h @@ -0,0 +1,122 @@ +#ifndef WI600_GUEST_LOADER_TEST_H +#define WI600_GUEST_LOADER_TEST_H + +typedef long wi600_lmid_t; +typedef int (*wi600_value_function_t)(void); + +extern void *dlopen(const char *filename, int flags); +extern void *dlmopen(wi600_lmid_t lmid, const char *filename, int flags); +extern int dlclose(void *handle); +extern void *dlsym(void *handle, const char *symbol); +extern void *dlvsym(void *handle, const char *symbol, const char *version); +extern char *dlerror(void); +extern int dlinfo(void *handle, int request, void *argument); + +#define WI600_RTLD_LAZY 0x00001 +#define WI600_RTLD_NOW 0x00002 +#define WI600_RTLD_NOLOAD 0x00004 +#define WI600_RTLD_LOCAL 0x00000 +#define WI600_RTLD_GLOBAL 0x00100 +#define WI600_RTLD_NODELETE 0x01000 +#define WI600_RTLD_DEFAULT ((void *)0) +#define WI600_LM_ID_BASE 0L +#define WI600_LM_ID_NEWLM (-1L) +#define WI600_RTLD_DI_LMID 1 +#define WI600_RTLD_DI_LINKMAP 2 + +static inline long wi600_raw_syscall3(long number, long argument1, + long argument2, long argument3) +{ + long result; + + __asm__ volatile( + "syscall" + : "=a"(result) + : "a"(number), "D"(argument1), "S"(argument2), "d"(argument3) + : "rcx", "r11", "memory"); + return result; +} + +static inline __attribute__((noreturn)) void wi600_exit(int status) +{ + __asm__ volatile( + "syscall" + : + : "a"(60L), "D"((long)status) + : "rcx", "r11", "memory"); + __builtin_unreachable(); +} + +static inline unsigned long wi600_string_length(const char *text) +{ + unsigned long length = 0; + + while (text[length]) { + ++length; + } + return length; +} + +static inline void wi600_write_text(int descriptor, const char *text) +{ + (void)wi600_raw_syscall3(1, descriptor, (long)text, + (long)wi600_string_length(text)); +} + +static inline __attribute__((noreturn)) void wi600_fail( + const char *scenario, int status, const char *detail) +{ + wi600_write_text(2, "WI600_GUEST_LOADER_FAIL "); + wi600_write_text(2, scenario); + wi600_write_text(2, ": "); + wi600_write_text(2, detail); + wi600_write_text(2, "\n"); + wi600_exit(status); +} + +static inline void wi600_require(int condition, const char *scenario, + int status, const char *detail) +{ + if (!condition) { + wi600_fail(scenario, status, detail); + } +} + +static inline void wi600_clear_dlerror(void) +{ + (void)dlerror(); +} + +static inline wi600_value_function_t wi600_value_symbol(void *handle, + const char *name) +{ + union { + void *object; + wi600_value_function_t function; + } symbol; + + symbol.object = dlsym(handle, name); + return symbol.function; +} + +static inline wi600_value_function_t wi600_versioned_value_symbol( + void *handle, const char *name, const char *version) +{ + union { + void *object; + wi600_value_function_t function; + } symbol; + + symbol.object = dlvsym(handle, name, version); + return symbol.function; +} + +static inline __attribute__((noreturn)) void wi600_pass(const char *scenario) +{ + wi600_write_text(1, "WI600_GUEST_LOADER_PASS "); + wi600_write_text(1, scenario); + wi600_write_text(1, "\n"); + wi600_exit(0); +} + +#endif diff --git a/tests/unit/kzt/guest_loader/helper.c b/tests/unit/kzt/guest_loader/helper.c new file mode 100644 index 00000000000..b2ea70cbc1c --- /dev/null +++ b/tests/unit/kzt/guest_loader/helper.c @@ -0,0 +1,4 @@ +int wi600_helper_value(void) +{ + return 5; +} diff --git a/tests/unit/kzt/guest_loader/namespace.c b/tests/unit/kzt/guest_loader/namespace.c new file mode 100644 index 00000000000..cbf0820971f --- /dev/null +++ b/tests/unit/kzt/guest_loader/namespace.c @@ -0,0 +1,4 @@ +int wi600_namespace_value(void) +{ + return 211; +} diff --git a/tests/unit/kzt/guest_loader/namespace_isolation.c b/tests/unit/kzt/guest_loader/namespace_isolation.c new file mode 100644 index 00000000000..674b00a4d29 --- /dev/null +++ b/tests/unit/kzt/guest_loader/namespace_isolation.c @@ -0,0 +1,53 @@ +#include "guest_loader_test.h" + +#define SCENARIO "namespace-isolation" +#define LIBRARY "libwi600_namespace.so" +#define SYMBOL "wi600_namespace_value" + +void _start(void) +{ + wi600_lmid_t namespace_id = WI600_LM_ID_BASE; + wi600_value_function_t value; + void *handle; + void *main_noload; + + wi600_clear_dlerror(); + value = wi600_value_symbol(WI600_RTLD_DEFAULT, SYMBOL); + wi600_require(value == (wi600_value_function_t)0, SCENARIO, 90, + "namespace symbol was present before dlmopen"); + wi600_require(dlerror() != (char *)0, SCENARIO, 91, + "missing pre-dlmopen symbol did not set dlerror"); + + handle = dlmopen(WI600_LM_ID_NEWLM, LIBRARY, + WI600_RTLD_NOW | WI600_RTLD_LOCAL); + wi600_require(handle != (void *)0, SCENARIO, 92, + "dlmopen(LM_ID_NEWLM) failed"); + wi600_require(dlinfo(handle, WI600_RTLD_DI_LMID, &namespace_id) == 0, + SCENARIO, 93, "dlinfo(RTLD_DI_LMID) failed"); + wi600_require(namespace_id != WI600_LM_ID_BASE && + namespace_id != WI600_LM_ID_NEWLM, + SCENARIO, 94, + "dlinfo did not return a non-main namespace id"); + + wi600_clear_dlerror(); + value = wi600_value_symbol(handle, SYMBOL); + wi600_require(value != (wi600_value_function_t)0 && value() == 211, + SCENARIO, 95, + "new-namespace symbol was not callable"); + wi600_require(dlerror() == (char *)0, SCENARIO, 96, + "new-namespace dlsym left an error"); + + main_noload = dlopen(LIBRARY, WI600_RTLD_NOW | WI600_RTLD_NOLOAD); + wi600_require(main_noload == (void *)0, SCENARIO, 97, + "new-namespace object polluted the main namespace"); + wi600_clear_dlerror(); + value = wi600_value_symbol(WI600_RTLD_DEFAULT, SYMBOL); + wi600_require(value == (wi600_value_function_t)0, SCENARIO, 98, + "new-namespace symbol leaked into default scope"); + wi600_require(dlerror() != (char *)0, SCENARIO, 99, + "namespace-isolated lookup did not set dlerror"); + + wi600_require(dlclose(handle) == 0, SCENARIO, 100, + "new-namespace dlclose failed"); + wi600_pass(SCENARIO); +} diff --git a/tests/unit/kzt/guest_loader/plugin.c b/tests/unit/kzt/guest_loader/plugin.c new file mode 100644 index 00000000000..f1f28a66998 --- /dev/null +++ b/tests/unit/kzt/guest_loader/plugin.c @@ -0,0 +1,6 @@ +extern int wi600_helper_value(void); + +int wi600_plugin_value(void) +{ + return wi600_helper_value() + 118; +} diff --git a/tests/unit/kzt/guest_loader/symbol_versions_errors.c b/tests/unit/kzt/guest_loader/symbol_versions_errors.c new file mode 100644 index 00000000000..513c761d662 --- /dev/null +++ b/tests/unit/kzt/guest_loader/symbol_versions_errors.c @@ -0,0 +1,61 @@ +#include "guest_loader_test.h" + +#define SCENARIO "symbol-versions-errors" +#define LIBRARY "libwi600_versions.so" +#define MISSING_LIBRARY "libwi600-definitely-missing.so" +#define SYMBOL "wi600_versioned_value" + +void _start(void) +{ + wi600_value_function_t value; + void *handle; + void *missing; + + handle = dlopen(LIBRARY, WI600_RTLD_NOW | WI600_RTLD_LOCAL); + wi600_require(handle != (void *)0, SCENARIO, 90, + "versioned library dlopen failed"); + + wi600_clear_dlerror(); + value = wi600_value_symbol(handle, SYMBOL); + wi600_require(value != (wi600_value_function_t)0 && value() == 202, + SCENARIO, 91, + "dlsym did not select the default version"); + wi600_require(dlerror() == (char *)0, SCENARIO, 92, + "default-version dlsym left an error"); + + wi600_clear_dlerror(); + value = wi600_versioned_value_symbol(handle, SYMBOL, "WI600_1.0"); + wi600_require(value != (wi600_value_function_t)0 && value() == 101, + SCENARIO, 93, "dlvsym did not select WI600_1.0"); + wi600_require(dlerror() == (char *)0, SCENARIO, 94, + "WI600_1.0 dlvsym left an error"); + + wi600_clear_dlerror(); + value = wi600_versioned_value_symbol(handle, SYMBOL, "WI600_2.0"); + wi600_require(value != (wi600_value_function_t)0 && value() == 202, + SCENARIO, 95, "dlvsym did not select WI600_2.0"); + wi600_require(dlerror() == (char *)0, SCENARIO, 96, + "WI600_2.0 dlvsym left an error"); + + wi600_clear_dlerror(); + value = wi600_versioned_value_symbol(handle, SYMBOL, "WI600_9.9"); + wi600_require(value == (wi600_value_function_t)0, SCENARIO, 97, + "dlvsym accepted an unknown version"); + wi600_require(dlerror() != (char *)0, SCENARIO, 98, + "unknown version did not set dlerror"); + wi600_require(dlerror() == (char *)0, SCENARIO, 99, + "version error was not consumed exactly once"); + + wi600_clear_dlerror(); + missing = dlopen(MISSING_LIBRARY, WI600_RTLD_NOW | WI600_RTLD_LOCAL); + wi600_require(missing == (void *)0, SCENARIO, 100, + "missing library unexpectedly opened"); + wi600_require(dlerror() != (char *)0, SCENARIO, 101, + "missing library did not set dlerror"); + wi600_require(dlerror() == (char *)0, SCENARIO, 102, + "missing-library error was not consumed exactly once"); + + wi600_require(dlclose(handle) == 0, SCENARIO, 103, + "versioned library dlclose failed"); + wi600_pass(SCENARIO); +} diff --git a/tests/unit/kzt/guest_loader/versions.c b/tests/unit/kzt/guest_loader/versions.c new file mode 100644 index 00000000000..0767dbade93 --- /dev/null +++ b/tests/unit/kzt/guest_loader/versions.c @@ -0,0 +1,12 @@ +int wi600_versioned_value_v1(void) +{ + return 101; +} + +int wi600_versioned_value_v2(void) +{ + return 202; +} + +__asm__(".symver wi600_versioned_value_v1,wi600_versioned_value@WI600_1.0"); +__asm__(".symver wi600_versioned_value_v2,wi600_versioned_value@@WI600_2.0"); diff --git a/tests/unit/kzt/guest_loader/versions.map b/tests/unit/kzt/guest_loader/versions.map new file mode 100644 index 00000000000..670e0623035 --- /dev/null +++ b/tests/unit/kzt/guest_loader/versions.map @@ -0,0 +1,8 @@ +WI600_1.0 { + global: wi600_versioned_value; + local: *; +}; + +WI600_2.0 { + global: wi600_versioned_value; +} WI600_1.0; diff --git a/tests/unit/kzt/guest_loader/visibility.c b/tests/unit/kzt/guest_loader/visibility.c new file mode 100644 index 00000000000..e87481f2173 --- /dev/null +++ b/tests/unit/kzt/guest_loader/visibility.c @@ -0,0 +1,4 @@ +int wi600_visibility_value(void) +{ + return 77; +} diff --git a/tests/unit/kzt/guest_loader/visibility_noload.c b/tests/unit/kzt/guest_loader/visibility_noload.c new file mode 100644 index 00000000000..1b06151265b --- /dev/null +++ b/tests/unit/kzt/guest_loader/visibility_noload.c @@ -0,0 +1,75 @@ +#include "guest_loader_test.h" + +#define SCENARIO "visibility-noload" +#define LIBRARY "libwi600_visibility.so" +#define SYMBOL "wi600_visibility_value" + +void _start(void) +{ + wi600_value_function_t value; + void *global; + void *local; + void *noload; + + noload = dlopen(LIBRARY, WI600_RTLD_NOW | WI600_RTLD_NOLOAD); + wi600_require(noload == (void *)0, SCENARIO, 90, + "NOLOAD found an object before it was loaded"); + wi600_clear_dlerror(); + + local = dlopen(LIBRARY, WI600_RTLD_NOW | WI600_RTLD_LOCAL); + wi600_require(local != (void *)0, SCENARIO, 91, + "RTLD_LOCAL dlopen failed"); + wi600_clear_dlerror(); + value = wi600_value_symbol(WI600_RTLD_DEFAULT, SYMBOL); + wi600_require(value == (wi600_value_function_t)0, SCENARIO, 92, + "RTLD_LOCAL symbol leaked into default scope"); + wi600_require(dlerror() != (char *)0, SCENARIO, 93, + "hidden local symbol did not set dlerror"); + + noload = dlopen(LIBRARY, WI600_RTLD_NOW | WI600_RTLD_NOLOAD); + wi600_require(noload == local, SCENARIO, 94, + "NOLOAD did not return the loaded handle"); + wi600_require(dlclose(noload) == 0, SCENARIO, 95, + "NOLOAD reference dlclose failed"); + value = wi600_value_symbol(local, SYMBOL); + wi600_require(value != (wi600_value_function_t)0 && value() == 77, + SCENARIO, 96, + "local reference was not callable after NOLOAD close"); + + wi600_require(dlclose(local) == 0, SCENARIO, 106, + "first local reference dlclose failed"); + noload = dlopen(LIBRARY, WI600_RTLD_NOW | WI600_RTLD_NOLOAD); + wi600_require(noload == (void *)0, SCENARIO, 107, + "NOLOAD retained an object after the first close cycle"); + wi600_clear_dlerror(); + + local = dlopen(LIBRARY, WI600_RTLD_NOW | WI600_RTLD_LOCAL); + wi600_require(local != (void *)0, SCENARIO, 108, + "second RTLD_LOCAL dlopen failed"); + global = dlopen(LIBRARY, WI600_RTLD_NOW | WI600_RTLD_GLOBAL); + wi600_require(global == local, SCENARIO, 97, + "RTLD_GLOBAL reopen did not reuse the handle"); + wi600_clear_dlerror(); + value = wi600_value_symbol(WI600_RTLD_DEFAULT, SYMBOL); + wi600_require(value != (wi600_value_function_t)0 && value() == 77, + SCENARIO, 98, + "RTLD_GLOBAL symbol was not visible in default scope"); + wi600_require(dlerror() == (char *)0, SCENARIO, 99, + "global default-scope lookup left an error"); + + wi600_require(dlclose(local) == 0, SCENARIO, 100, + "local reference dlclose failed"); + value = wi600_value_symbol(global, SYMBOL); + wi600_require(value != (wi600_value_function_t)0 && value() == 77, + SCENARIO, 101, + "global reference was not callable after local close"); + wi600_require(dlclose(global) == 0, SCENARIO, 102, + "global reference dlclose failed"); + + /* + * dlsym(RTLD_DEFAULT) may add a caller dependency and mark the provider + * NODELETE. The first close cycle above covers unload/refcount behavior; + * this cycle covers LOCAL-to-GLOBAL visibility and successful closes. + */ + wi600_pass(SCENARIO); +} diff --git a/tests/unit/kzt/guest_loader/wi1065_constructor.c b/tests/unit/kzt/guest_loader/wi1065_constructor.c new file mode 100644 index 00000000000..bd7e2ac6aae --- /dev/null +++ b/tests/unit/kzt/guest_loader/wi1065_constructor.c @@ -0,0 +1,11 @@ +#include "guest_loader_test.h" + +__attribute__((constructor)) static void wi1065_constructor(void) +{ + wi600_write_text(1, "WI1065_CONSTRUCTOR\n"); +} + +int wi1065_constructor_value(void) +{ + return 1065; +} diff --git a/tests/unit/kzt/guest_loader/wi1065_loader_events.c b/tests/unit/kzt/guest_loader/wi1065_loader_events.c new file mode 100644 index 00000000000..6b54a6f0bdb --- /dev/null +++ b/tests/unit/kzt/guest_loader/wi1065_loader_events.c @@ -0,0 +1,82 @@ +#include "guest_loader_test.h" + +#define SCENARIO "wi1065-loader-events" +#define CONSTRUCTOR_LIBRARY "libwi1065_constructor.so" +#define PARTIAL_RELRO_LIBRARY "libwi1065_partial_relro.so" +#define FULL_RELRO_LIBRARY "libwi1065_full_relro.so" + +typedef unsigned long wi1065_thread_t; +typedef void *(*wi1065_thread_start_t)(void *); + +extern int pthread_create(wi1065_thread_t *thread, const void *attribute, + wi1065_thread_start_t start, void *argument); +extern int pthread_join(wi1065_thread_t thread, void **result); + +static int wi1065_open_call_close(const char *library, int flags, + const char *symbol) +{ + wi600_value_function_t value; + void *handle = dlopen(library, flags | WI600_RTLD_LOCAL); + + if (!handle) { + return 1; + } + wi600_clear_dlerror(); + value = wi600_value_symbol(handle, symbol); + if (!value || dlerror() != (char *)0 || value() != 1065) { + (void)dlclose(handle); + return 2; + } + if (dlclose(handle) != 0) { + return 3; + } + return 0; +} + +static void *wi1065_loader_worker(void *argument) +{ + (void)argument; + return wi1065_open_call_close(CONSTRUCTOR_LIBRARY, WI600_RTLD_NOW, + "wi1065_constructor_value") == 0 ? + (void *)0 : (void *)1; +} + +void _start(void) +{ + wi1065_thread_t first; + wi1065_thread_t second; + void *first_result = (void *)1; + void *second_result = (void *)1; + + wi600_write_text(1, "WI1065_STARTUP\n"); + wi600_require(wi1065_open_call_close(CONSTRUCTOR_LIBRARY, WI600_RTLD_NOW, + "wi1065_constructor_value") == 0, + SCENARIO, 90, "constructor dlopen/dlclose failed"); + wi600_write_text(1, "WI1065_DLCLOSE constructor\n"); + + wi600_require(wi1065_open_call_close(PARTIAL_RELRO_LIBRARY, + WI600_RTLD_LAZY, + "wi1065_relro_value") == 0, + SCENARIO, 91, "partial RELRO lazy dlopen failed"); + wi600_write_text(1, "WI1065_RELRO partial\n"); + wi600_write_text(1, "WI1065_DLCLOSE partial\n"); + + wi600_require(wi1065_open_call_close(FULL_RELRO_LIBRARY, WI600_RTLD_NOW, + "wi1065_relro_value") == 0, + SCENARIO, 92, "full RELRO bind-now dlopen failed"); + wi600_write_text(1, "WI1065_RELRO full\n"); + wi600_write_text(1, "WI1065_DLCLOSE full\n"); + + wi600_require(pthread_create(&first, (void *)0, wi1065_loader_worker, + (void *)0) == 0, + SCENARIO, 93, "first loader thread create failed"); + wi600_require(pthread_create(&second, (void *)0, wi1065_loader_worker, + (void *)0) == 0, + SCENARIO, 94, "second loader thread create failed"); + wi600_require(pthread_join(first, &first_result) == 0 && + pthread_join(second, &second_result) == 0 && + first_result == (void *)0 && second_result == (void *)0, + SCENARIO, 95, "loader worker failed"); + wi600_write_text(1, "WI1065_THREADS_PASS\n"); + wi600_pass(SCENARIO); +} diff --git a/tests/unit/kzt/guest_loader/wi1065_relro.c b/tests/unit/kzt/guest_loader/wi1065_relro.c new file mode 100644 index 00000000000..3635629843c --- /dev/null +++ b/tests/unit/kzt/guest_loader/wi1065_relro.c @@ -0,0 +1,4 @@ +int wi1065_relro_value(void) +{ + return 1065; +} diff --git a/tests/unit/kzt/guest_loader/wrapped_library_handle.c b/tests/unit/kzt/guest_loader/wrapped_library_handle.c new file mode 100644 index 00000000000..050316c5748 --- /dev/null +++ b/tests/unit/kzt/guest_loader/wrapped_library_handle.c @@ -0,0 +1,40 @@ +#include "guest_loader_test.h" + +void _start(void) +{ + static const char scenario[] = "wrapped-library-handle"; + void *handle; + void *duplicate; + void *noload; + void *link_map = 0; + void *symbol; + + wi600_clear_dlerror(); + handle = dlopen("libdl.so.2", WI600_RTLD_NOW | WI600_RTLD_LOCAL); + wi600_require(handle != 0, scenario, 10, "libdl dlopen failed"); + wi600_require(dlinfo(handle, WI600_RTLD_DI_LINKMAP, &link_map) == 0, + scenario, 11, "libdl dlinfo failed"); + wi600_require(link_map == handle, scenario, 12, + "dlopen did not return the guest link_map handle"); + + symbol = dlsym(handle, "dlopen"); + wi600_require(symbol != 0, scenario, 13, "libdl dlopen lookup failed"); + + duplicate = dlopen("libdl.so.2", + WI600_RTLD_NOW | WI600_RTLD_LOCAL); + wi600_require(duplicate == handle, scenario, 14, + "duplicate dlopen changed the guest handle"); + + noload = dlopen("libdl.so.2", + WI600_RTLD_NOW | WI600_RTLD_NOLOAD); + wi600_require(noload == handle, scenario, 15, + "RTLD_NOLOAD did not return the guest handle"); + wi600_require(dlclose(noload) == 0, scenario, 16, + "RTLD_NOLOAD handle close failed"); + wi600_require(dlclose(duplicate) == 0, scenario, 17, + "duplicate handle close failed"); + wi600_require(dlclose(handle) == 0, scenario, 18, + "original handle close failed"); + + wi600_pass(scenario); +} diff --git a/tests/unit/kzt/real_guest_harness.py b/tests/unit/kzt/real_guest_harness.py new file mode 100644 index 00000000000..51db117ed23 --- /dev/null +++ b/tests/unit/kzt/real_guest_harness.py @@ -0,0 +1,2385 @@ +#!/usr/bin/env python3 +import hashlib +import json +import math +import os +from pathlib import Path +import platform +import random +import select +import shutil +import signal +import statistics +import subprocess +import tempfile +import time +from dataclasses import dataclass +from datetime import datetime, timezone +from enum import Enum +from functools import lru_cache + + +PRIMARY_TIME_METRICS = ( + "startup_process_total_ns", + "launch_to_first_result_ns", + "steady_total_ns", +) +GUEST_LAZY_COMPARISON_METRICS = ( + "startup_process_total_ns", + "launch_to_first_result_ns", +) +GUEST_FIRST_BINDING_METRIC = "first_binding_ns" +PERFORMANCE_MARKER = "KZT_GUEST_PERF_OK" +HARNESS_SCHEMA_VERSION = 2 +REPORT_ARTIFACT_TYPE = "kzt-real-guest-performance-report" +METADATA_ARTIFACT_TYPE = "kzt-real-guest-performance-run-metadata" +OWNERSHIP_MARKER = ".kzt-real-guest-harness-owner" +MIN_FAIL_PAIRS = 400 +FORMAL_AA_MIN_PAIRS = 200 +MEDIAN_NONINFERIORITY_LOG = math.log(1.01) +P95_NONINFERIORITY_LOG = math.log(1.02) +FIRST_BINDING_NONINFERIORITY_LOG = 0.0 +AA_STABILITY_LOG_LIMIT = math.log(1.005) +AA_COMPARISON_COUNT = len(PRIMARY_TIME_METRICS) * 3 * 2 +AA_FAMILY_ALPHA = 0.01 +AA_INTERVAL_ALPHA = AA_FAMILY_ALPHA / AA_COMPARISON_COUNT +AA_TEMPORAL_INDEPENDENCE_ASSUMPTION = ( + "time-ordered early/late paired contrasts are independent across pair indices" +) +AB_COMPARISON_COUNT = len(PRIMARY_TIME_METRICS) * 2 +AB_FAMILY_ALPHA = 0.01 +P95_BOOTSTRAP_RESAMPLES = 20000 +PERFORMANCE_EXECUTABLE = "kzt_guest_perf_main" +PERFORMANCE_LIBRARY = "libkzt_guest_perf_probe.so" +PERFORMANCE_MODES = ("startup", "first", "steady") +CPU_SYSFS_ROOT = Path("/sys/devices/system/cpu") +NATIVE_APPLY_SYMBOL = "dlerror" +EAGER_FINAL = "EAGER_FINAL" +LAZY_TO_GUEST_FINAL = "LAZY_TO_GUEST_FINAL" +LAZY_TO_NATIVE_FINAL = "LAZY_TO_NATIVE_FINAL" +PREBOUND_NATIVE_FINAL = "PREBOUND_NATIVE_FINAL" +BASELINE_BINDING_STATES = ( + EAGER_FINAL, + LAZY_TO_GUEST_FINAL, + LAZY_TO_NATIVE_FINAL, +) + + +def comparison_metrics_for_baseline_state(baseline_binding_state): + if baseline_binding_state not in BASELINE_BINDING_STATES: + raise ValueError("invalid baseline binding state") + if baseline_binding_state == LAZY_TO_GUEST_FINAL: + return GUEST_LAZY_COMPARISON_METRICS + return PRIMARY_TIME_METRICS +COMMON_KZT_ENVIRONMENT = { + # AOT forks a detached compiler at guest exit. It is unrelated to the + # measured lazy call and can overlap the following pinned sample. + "LATX_AOT": "0", + "LATX_KZT": "2", + "LATX_KZT_LAZY_DIAGNOSTICS": "0", + "LATX_KZT_REGISTRY_DIAGNOSTICS": "0", +} +CANDIDATE_WRITER_ENVIRONMENT = { + "LATX_KZT_PATCH_SPIKE": "1", + "LATX_KZT_PATCH_SPIKE_WRITE": "1", + "LATX_KZT_PATCH_SPIKE_BUDGET": "1", +} +SANITIZED_RUNTIME_VARIABLES = ( + "LAT_DFILTER", + "LAT_GDB", + "LAT_LOG", + "LAT_LOG_FILENAME", + "LAT_SINGLESTEP", + "LAT_STRACE", + "LAT_STRACE_ERROR", + "LAT_TRACE", + "LD_AUDIT", + "LD_BIND_NOW", + "LD_DEBUG", + "LD_DEBUG_OUTPUT", + "LD_LIBRARY_PATH", + "LD_PRELOAD", + "LD_PROFILE", + "QEMU_LOG", + "QEMU_STRACE", +) + + +class GateResult(str, Enum): + PASS = "PASS" + FAIL = "FAIL" + INCONCLUSIVE = "INCONCLUSIVE" + + +class AAResult(str, Enum): + STABLE = "STABLE" + DRIFT = "DRIFT" + INCONCLUSIVE = "INCONCLUSIVE" + + +class GuestCorrectnessError(RuntimeError): + def __init__(self, message, details=None): + super().__init__(message) + self.details = details + + +class PrerequisiteError(RuntimeError): + pass + + +@dataclass(frozen=True) +class HarnessConfig: + baseline_latx: Path + candidate_latx: Path + guest_root: Path + fixture_dir: Path + cpu: int + warmup: int + samples: int + max_samples: int + aa_samples: int + steady_calls: int + seed: int + output_dir: Path + timeout: float = 60.0 + baseline_binding_state: str = EAGER_FINAL + isolate_harness_cpu: bool = False + aa_only: bool = False + + +def percentile(values, quantile): + if not values: + raise ValueError("percentile requires at least one value") + if not 0.0 <= quantile <= 1.0: + raise ValueError("quantile must be between zero and one") + ordered = sorted(values) + position = (len(ordered) - 1) * quantile + lower = int(math.floor(position)) + upper = int(math.ceil(position)) + if lower == upper: + return ordered[lower] + fraction = position - lower + return ordered[lower] + (ordered[upper] - ordered[lower]) * fraction + + +def _derived_seed(seed, *parts): + material = ":".join([str(seed), *(str(part) for part in parts)]) + return int.from_bytes( + hashlib.sha256(material.encode("ascii")).digest()[:8], "big" + ) + + +def randomized_pair_orders(pair_count, seed, labels): + if pair_count < 0: + raise ValueError("pair_count must be non-negative") + if len(labels) != 2 or labels[0] == labels[1]: + raise ValueError("labels must contain two distinct values") + rng = random.Random(seed) + orientations = [0, 1] * (pair_count // 2) + if pair_count % 2: + orientations.append(rng.randrange(2)) + rng.shuffle(orientations) + forward = tuple(labels) + reverse = tuple(reversed(labels)) + return [forward if orientation == 0 else reverse + for orientation in orientations] + + +def _sample_statistic(values, statistic): + if statistic == "median": + return statistics.median(values) + if statistic == "p95": + return percentile(values, 0.95) + raise ValueError(f"Unsupported statistic: {statistic}") + + +def _parse_integer(record, name): + try: + return int(record[name], 0) + except KeyError as error: + raise GuestCorrectnessError( + f"Performance record is missing {name}." + ) from error + except ValueError as error: + raise GuestCorrectnessError( + f"Performance record has invalid {name}: {record[name]}" + ) from error + + +def parse_guest_record(output, expected_steady_calls, *, expected_mode=None): + records = [] + for line in output.splitlines(): + marker_at = line.find(PERFORMANCE_MARKER) + if marker_at < 0: + continue + fields = {} + for field in line[marker_at:].split()[1:]: + if "=" not in field: + continue + name, value = field.split("=", 1) + fields[name] = value + records.append(fields) + if len(records) != 1: + raise GuestCorrectnessError( + "Expected exactly one KZT_GUEST_PERF_OK record, " + f"found {len(records)}." + ) + + fields = records[0] + mode = fields.get("mode") + if expected_mode is not None and mode != expected_mode: + raise GuestCorrectnessError( + f"Guest ran mode {mode!r}, expected {expected_mode!r}." + ) + if mode is not None and mode not in PERFORMANCE_MODES: + raise GuestCorrectnessError(f"Guest reported unknown mode {mode!r}.") + steady_calls = _parse_integer(fields, "steady_calls") + slot = _parse_integer(fields, "slot") + before = _parse_integer(fields, "before") + after_first = _parse_integer(fields, "after_first") + after_steady = _parse_integer(fields, "after_steady") + first_ns = _parse_integer(fields, "first_ns") + steady_total_ns = _parse_integer(fields, "steady_total_ns") + steady_per_call_ns = _parse_integer(fields, "steady_per_call_ns") + checksum = _parse_integer(fields, "checksum") + + if mode == "startup": + if any((steady_calls, slot, before, after_first, after_steady, + first_ns, steady_total_ns, steady_per_call_ns, checksum)): + raise GuestCorrectnessError( + "Guest startup mode must not call dlerror or report slot data." + ) + elif mode == "first": + if steady_calls != 0 or slot <= 0 or before <= 0 or \ + after_first <= 0 or after_steady != after_first or \ + first_ns <= 0 or steady_total_ns != 0 or \ + steady_per_call_ns != 0 or checksum != 1: + raise GuestCorrectnessError( + "Guest first mode did not report one stable NULL dlerror call." + ) + elif mode == "steady": + if steady_calls != expected_steady_calls or slot <= 0 or before <= 0 or \ + after_first <= 0 or after_steady != after_first or \ + first_ns <= 0 or steady_total_ns <= 0 or \ + steady_per_call_ns <= 0 or \ + steady_per_call_ns != steady_total_ns // steady_calls or \ + checksum != steady_calls + 1: + raise GuestCorrectnessError( + "Guest steady mode did not report stable NULL dlerror calls." + ) + else: + if steady_calls != expected_steady_calls: + raise GuestCorrectnessError( + f"Guest ran {steady_calls} steady calls, expected " + f"{expected_steady_calls}." + ) + if slot == 0 or before == 0 or checksum == 0: + raise GuestCorrectnessError( + "Guest reported an invalid slot, initial value, or checksum." + ) + if after_first == before: + raise GuestCorrectnessError( + "The first call did not resolve the lazy binding slot: " + f"before=0x{before:x} after_first=0x{after_first:x}." + ) + if after_steady != after_first: + raise GuestCorrectnessError( + "The lazy binding slot changed during steady calls: " + f"after_first=0x{after_first:x} after_steady=0x{after_steady:x}." + ) + if first_ns <= 0 or steady_total_ns <= 0 or steady_per_call_ns <= 0: + raise GuestCorrectnessError("Guest reported a non-positive timing.") + if steady_per_call_ns != steady_total_ns // steady_calls: + raise GuestCorrectnessError( + "Guest steady per-call timing does not match its total." + ) + return { + "mode": mode, + "steady_calls": steady_calls, + "slot": slot, + "before": before, + "after_first": after_first, + "after_steady": after_steady, + "first_binding_ns": first_ns, + "steady_total_ns": steady_total_ns, + "steady_per_call_ns": steady_per_call_ns, + "checksum": checksum, + } + + +def validate_role_mode_record(role, mode, guest, *, baseline_binding_state=None): + if role not in ("baseline", "candidate"): + raise ValueError(f"Unknown benchmark role: {role}") + if mode not in ("first", "steady"): + raise ValueError(f"Mode has no binding state: {mode}") + try: + before = guest["before"] + after_first = guest["after_first"] + after_steady = guest["after_steady"] + except KeyError as error: + raise GuestCorrectnessError( + f"{role} {mode} record is missing slot evidence: {error.args[0]}" + ) from error + if role == "baseline": + expected_state = (EAGER_FINAL if baseline_binding_state is None + else baseline_binding_state) + if expected_state not in BASELINE_BINDING_STATES: + raise ValueError( + "baseline binding state must be EAGER_FINAL, " + "LAZY_TO_GUEST_FINAL, or LAZY_TO_NATIVE_FINAL." + ) + if (expected_state == EAGER_FINAL and + before == after_first == after_steady): + return EAGER_FINAL + if (expected_state in (LAZY_TO_GUEST_FINAL, LAZY_TO_NATIVE_FINAL) and + before != after_first == after_steady): + return expected_state + raise GuestCorrectnessError( + f"baseline {mode} must be {expected_state}: " + f"before=0x{before:x} after_first=0x{after_first:x} " + f"after_steady=0x{after_steady:x}." + ) + if before == after_first == after_steady: + return PREBOUND_NATIVE_FINAL + if before != after_first == after_steady: + return LAZY_TO_NATIVE_FINAL + raise GuestCorrectnessError( + f"candidate {mode} must be LAZY_TO_NATIVE_FINAL: " + f"before=0x{before:x} after_first=0x{after_first:x} " + f"after_steady=0x{after_steady:x}." + ) + + +def _metric_values(pairs, label, metric): + try: + return [pair[label][metric] for pair in pairs] + except KeyError as error: + raise ValueError( + f"Pair is missing {label}.{metric}." + ) from error + + +@lru_cache(maxsize=None) +def _binomial_cdf(count, trials, probability): + if count < 0: + return 0.0 + if count >= trials: + return 1.0 + if probability <= 0.0: + return 1.0 + if probability >= 1.0: + return 0.0 + log_probability = math.log(probability) + log_complement = math.log1p(-probability) + log_terms = [ + math.lgamma(trials + 1) - math.lgamma(value + 1) - + math.lgamma(trials - value + 1) + + value * log_probability + (trials - value) * log_complement + for value in range(count + 1) + ] + maximum = max(log_terms) + return min( + 1.0, + math.exp(maximum) * math.fsum( + math.exp(term - maximum) for term in log_terms + ), + ) + + +def _order_statistic_interval(values, *, quantile, alpha, + confidence, method): + if not values: + raise ValueError("order-statistic interval requires samples") + if not 0.0 < quantile < 1.0: + raise ValueError("quantile must be strictly between zero and one") + if not 0.0 < alpha < 1.0: + raise ValueError("alpha must be strictly between zero and one") + ordered = sorted(values) + count = len(ordered) + lower_rank = max( + (rank for rank in range(1, count + 1) + if _binomial_cdf(rank - 1, count, quantile) <= alpha / 2), + default=None, + ) + upper_rank = next( + (rank for rank in range(1, count + 1) + if _binomial_cdf(rank - 1, count, quantile) >= 1.0 - alpha / 2), + None, + ) + estimate = percentile(values, quantile) + return { + "estimate": estimate, + "lower": ordered[lower_rank - 1] if lower_rank else None, + "upper": ordered[upper_rank - 1] if upper_rank else None, + "ratio": math.exp(estimate), + "confidence": confidence, + "alpha": alpha, + "sample_count": count, + "lower_rank": lower_rank, + "upper_rank": upper_rank, + "method": method, + } + + +def _one_sided_quantile_bounds(values, *, quantile, alpha, log_scale=True): + if not values: + raise ValueError("order-statistic bounds require samples") + if not 0.0 < quantile < 1.0: + raise ValueError("quantile must be strictly between zero and one") + if not 0.0 < alpha < 1.0: + raise ValueError("alpha must be strictly between zero and one") + ordered = sorted(values) + count = len(ordered) + lower_rank = max( + (rank for rank in range(1, count + 1) + if _binomial_cdf(rank - 1, count, quantile) <= alpha), + default=None, + ) + upper_rank = next( + (rank for rank in range(1, count + 1) + if _binomial_cdf(rank - 1, count, quantile) >= 1.0 - alpha), + None, + ) + estimate = percentile(values, quantile) + return { + "estimate": estimate, + "lower": ordered[lower_rank - 1] if lower_rank else None, + "upper": ordered[upper_rank - 1] if upper_rank else None, + "ratio": math.exp(estimate) if log_scale else None, + "one_sided_confidence": 1.0 - alpha, + "alpha": alpha, + "sample_count": count, + "lower_rank": lower_rank, + "upper_rank": upper_rank, + "method": "exact binomial quantile order-statistic bounds", + "estimate_method": "linear interpolated sample quantile", + } + + +@lru_cache(maxsize=128) +def _paired_index_bootstrap_p95_cached(baseline, candidate, alpha, seed, + resamples): + if len(baseline) != len(candidate) or not baseline: + raise ValueError("paired P95 bootstrap requires equal non-empty samples") + if any(value <= 0 for value in baseline + candidate): + raise ValueError("paired P95 bootstrap samples must be positive") + if not 0.0 < alpha < 1.0: + raise ValueError("alpha must be strictly between zero and one") + if resamples < 1: + raise ValueError("paired P95 bootstrap requires resamples") + rng = random.Random(seed) + population = range(len(baseline)) + bootstrap_log_ratios = [] + for _ in range(resamples): + indices = rng.choices(population, k=len(baseline)) + baseline_p95 = percentile( + [baseline[index] for index in indices], 0.95 + ) + candidate_p95 = percentile( + [candidate[index] for index in indices], 0.95 + ) + bootstrap_log_ratios.append( + math.log(candidate_p95 / baseline_p95) + ) + estimate = math.log( + percentile(candidate, 0.95) / percentile(baseline, 0.95) + ) + return { + "estimate": estimate, + "lower": percentile(bootstrap_log_ratios, alpha), + "upper": percentile(bootstrap_log_ratios, 1.0 - alpha), + "ratio": math.exp(estimate), + "one_sided_confidence": 1.0 - alpha, + "alpha": alpha, + "sample_count": len(baseline), + "bootstrap_resamples": resamples, + "bootstrap_seed": seed, + "resampling_unit": "paired sample index", + "method": "paired-index percentile bootstrap P95 log-ratio bounds", + "estimate_method": ( + "log(linear-interpolated candidate P95 / " + "linear-interpolated baseline P95)" + ), + } + + +def _paired_index_bootstrap_p95_bounds(baseline, candidate, *, alpha, seed, + resamples=P95_BOOTSTRAP_RESAMPLES): + return dict(_paired_index_bootstrap_p95_cached( + tuple(baseline), tuple(candidate), alpha, seed, resamples + )) + + +def _ab_ratio_bound_alpha(analysis_look_count, + comparison_count=AB_COMPARISON_COUNT): + if analysis_look_count < 1: + raise ValueError("analysis_look_count must be positive") + if comparison_count <= 0: + raise ValueError("comparison_count must be positive") + return AB_FAMILY_ALPHA / (analysis_look_count * comparison_count) + + +def analyze_ab_pairs(pairs, *, seed, formal_stage_count=1, + analysis_look_count=None, + metrics=PRIMARY_TIME_METRICS, tail_metrics=None): + if not pairs: + raise ValueError("A/B analysis requires at least one pair") + metrics = tuple(metrics) + if not metrics: + raise ValueError("A/B analysis requires at least one metric") + tail_metrics = tuple(metrics if tail_metrics is None else tail_metrics) + if any(metric not in metrics for metric in tail_metrics): + raise ValueError("A/B tail metrics must be comparison metrics") + if analysis_look_count is None: + analysis_look_count = formal_stage_count + comparison_count = len(metrics) + len(tail_metrics) + ratio_bound_alpha = _ab_ratio_bound_alpha( + analysis_look_count, comparison_count + ) + metric_details = {} + for metric in metrics: + baseline = _metric_values(pairs, "baseline", metric) + candidate = _metric_values(pairs, "candidate", metric) + if any(value <= 0 for value in baseline + candidate): + raise ValueError(f"{metric} samples must be positive") + log_ratios = [ + math.log(candidate_value / baseline_value) + for baseline_value, candidate_value in zip(baseline, candidate) + ] + details = { + "baseline": { + "median": statistics.median(baseline), + "p95": percentile(baseline, 0.95), + }, + "candidate": { + "median": statistics.median(candidate), + "p95": percentile(candidate, 0.95), + }, + "paired_log_ratio": { + "median": _one_sided_quantile_bounds( + log_ratios, + quantile=0.5, + alpha=ratio_bound_alpha, + ), + }, + "required_statistics": ( + ("median", "p95") if metric in tail_metrics else ("median",) + ), + "noninferiority_log": { + "median": (FIRST_BINDING_NONINFERIORITY_LOG + if metric == GUEST_FIRST_BINDING_METRIC + else MEDIAN_NONINFERIORITY_LOG), + }, + } + if metric in tail_metrics: + paired_p95 = _paired_index_bootstrap_p95_bounds( + baseline, + candidate, + alpha=ratio_bound_alpha, + seed=seed, + ) + details["paired_index_bootstrap_p95_log_ratio"] = { + "p95": paired_p95 + } + details["noninferiority_log"]["p95"] = \ + P95_NONINFERIORITY_LOG + metric_details[metric] = details + return { + "pair_count": len(pairs), + "metrics": metric_details, + "comparison_count": comparison_count, + "formal_stage_count": formal_stage_count, + "analysis_look_count": analysis_look_count, + "per_ratio_bound_alpha": ratio_bound_alpha, + "per_ratio_bound_confidence": 1.0 - ratio_bound_alpha, + "pass_upper_family_confidence": 1.0 - AB_FAMILY_ALPHA, + "fail_lower_family_confidence": 1.0 - AB_FAMILY_ALPHA, + } + + +def _interval_exceeds_aa_stability_limit(interval): + return ((interval["lower"] is not None and + interval["lower"] > AA_STABILITY_LOG_LIMIT) or + (interval["upper"] is not None and + interval["upper"] < -AA_STABILITY_LOG_LIMIT)) + + +def _interval_is_within_aa_stability_limit(interval): + return (interval["lower"] is not None and interval["upper"] is not None + and interval["lower"] >= -AA_STABILITY_LOG_LIMIT + and interval["upper"] <= AA_STABILITY_LOG_LIMIT) + + +def assess_aa_pairs(pairs, *, seed, metrics=PRIMARY_TIME_METRICS): + if len(pairs) < 4: + raise ValueError("A/A stability analysis requires at least four pairs") + metrics = tuple(metrics) + if not metrics: + raise ValueError("A/A stability analysis requires at least one metric") + comparison_count = len(metrics) * 3 * 2 + interval_alpha = AA_FAMILY_ALPHA / comparison_count + metric_details = {} + for metric in metrics: + a_values = _metric_values(pairs, "a", metric) + b_values = _metric_values(pairs, "b", metric) + label_interval = _order_statistic_interval( + [math.log(b / a) for a, b in zip(a_values, b_values)], + quantile=0.5, + alpha=interval_alpha, + confidence=1.0 - interval_alpha, + method="exact binomial median order-statistic interval", + ) + order_interval = _order_statistic_interval( + [ + math.log(pair[pair["order"][1]][metric] / + pair[pair["order"][0]][metric]) + for pair in pairs + ], + quantile=0.5, + alpha=interval_alpha, + confidence=1.0 - interval_alpha, + method="exact binomial median order-statistic interval", + ) + pair_centers = [ + math.sqrt(a * b) for a, b in zip(a_values, b_values) + ] + half = len(pair_centers) // 2 + early = pair_centers[:half] + late = pair_centers[-half:] + temporal_interval = _order_statistic_interval( + [math.log(later / earlier) for earlier, later in zip(early, late)], + quantile=0.5, + alpha=interval_alpha, + confidence=1.0 - interval_alpha, + method=( + "exact binomial median order-statistic interval over " + "time-ordered early/late paired contrasts" + ), + ) + metric_details[metric] = { + "label": label_interval, + "execution_order": order_interval, + "temporal": temporal_interval, + } + intervals = [ + interval + for details in metric_details.values() + for interval in details.values() + ] + drift = any(_interval_exceeds_aa_stability_limit(interval) + for interval in intervals) + stable = all(_interval_is_within_aa_stability_limit(interval) + for interval in intervals) + if drift: + result = AAResult.DRIFT + elif stable and len(pairs) >= FORMAL_AA_MIN_PAIRS: + result = AAResult.STABLE + else: + result = AAResult.INCONCLUSIVE + reasons = [] + for metric, details in metric_details.items(): + for name, interval in details.items(): + if _interval_exceeds_aa_stability_limit(interval): + reasons.append(f"{metric}: A/A {name} drift") + return { + "result": result.value, + "stable": result == AAResult.STABLE, + "reasons": reasons, + "metrics": metric_details, + "mode": "formal" if len(pairs) >= FORMAL_AA_MIN_PAIRS else "screening", + "pair_count": len(pairs), + "comparison_count": comparison_count, + "family_confidence": 1.0 - AA_FAMILY_ALPHA, + "per_interval_confidence": 1.0 - interval_alpha, + "stability_log_limit": AA_STABILITY_LOG_LIMIT, + "stability_percent_limit": 0.5, + "temporal_design": "time-ordered early/late paired contrasts", + "temporal_independence_assumption": AA_TEMPORAL_INDEPENDENCE_ASSUMPTION, + } + + +def assess_dual_aa(baseline_pairs, candidate_pairs, *, seed, + metrics=PRIMARY_TIME_METRICS): + metrics = tuple(metrics) + baseline = assess_aa_pairs( + baseline_pairs, + seed=_derived_seed(seed, "baseline-aa"), + metrics=metrics, + ) + candidate = assess_aa_pairs( + candidate_pairs, + seed=_derived_seed(seed, "candidate-aa"), + metrics=metrics, + ) + results = {baseline["result"], candidate["result"]} + if AAResult.DRIFT.value in results: + result = AAResult.DRIFT + elif results == {AAResult.STABLE.value}: + result = AAResult.STABLE + else: + result = AAResult.INCONCLUSIVE + return { + "result": result.value, + "stable": result == AAResult.STABLE, + "baseline": baseline, + "candidate": candidate, + "comparison_count": len(metrics) * 3 * 2, + "family_confidence": 1.0 - AA_FAMILY_ALPHA, + } + + +def classify_gate(analysis, *, pair_count, formal_aa_stable): + all_supported_non_slowdown = True + clearly_slower_statistics = [] + for metric, details in analysis["metrics"].items(): + for statistic in details["required_statistics"]: + family = ("paired_log_ratio" if statistic == "median" else + "paired_index_bootstrap_p95_log_ratio") + interval = details[family][statistic] + threshold = details["noninferiority_log"][statistic] + all_supported_non_slowdown &= ( + interval["upper"] is not None and + interval["upper"] <= threshold + ) + if (interval["lower"] is not None and + interval["lower"] > threshold): + clearly_slower_statistics.append(f"{metric}.{statistic}") + analysis["clearly_slower_statistics"] = clearly_slower_statistics + analysis["all_metrics_support_non_slowdown"] = ( + all_supported_non_slowdown + ) + if (all_supported_non_slowdown and pair_count >= MIN_FAIL_PAIRS and + formal_aa_stable): + return GateResult.PASS + if (clearly_slower_statistics and pair_count >= MIN_FAIL_PAIRS and + formal_aa_stable): + return GateResult.FAIL + return GateResult.INCONCLUSIVE + + +def _utc_now(): + return datetime.now(timezone.utc).isoformat() + + +def _sha256_file(path): + digest = hashlib.sha256() + with path.open("rb") as source: + for block in iter(lambda: source.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() + + +def _command_output(command, timeout=10.0): + try: + completed = subprocess.run( + command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + timeout=timeout, + check=False, + ) + except (OSError, subprocess.TimeoutExpired): + return None + if completed.returncode != 0: + return None + return completed.stdout.strip() + + +def _git_metadata(path): + commit = _command_output( + ["git", "-C", str(path.parent), "rev-parse", "HEAD"] + ) + root = _command_output( + ["git", "-C", str(path.parent), "rev-parse", "--show-toplevel"] + ) + if not commit: + return {"commit": None, "git_root": None, "git_dirty": None} + dirty_output = _command_output( + [ + "git", + "-C", + str(path.parent), + "status", + "--porcelain", + "--untracked-files=no", + ] + ) + return { + "commit": commit, + "git_root": root, + "git_dirty": bool(dirty_output), + } + + +def _elf_build_id(path): + readelf = shutil.which("readelf") or shutil.which("llvm-readelf") + if not readelf: + return None + output = _command_output([readelf, "-n", str(path)]) + if not output: + return None + for line in output.splitlines(): + if "Build ID:" in line: + return line.split("Build ID:", 1)[1].strip() + return None + + +def _elf_compiler(path): + readelf = shutil.which("readelf") or shutil.which("llvm-readelf") + if not readelf: + return None + return _command_output( + [readelf, "--string-dump=.comment", str(path)] + ) + + +def collect_binary_metadata(path): + metadata = {"path": str(path)} + if not path.is_file(): + metadata.update({ + "exists": False, + "sha256": None, + "build_id": None, + "compiler": None, + "commit": None, + "git_root": None, + "git_dirty": None, + }) + return metadata + metadata.update({ + "exists": True, + "size_bytes": path.stat().st_size, + "sha256": _sha256_file(path), + "build_id": _elf_build_id(path), + "compiler": _elf_compiler(path), + }) + metadata.update(_git_metadata(path)) + return metadata + + +def _read_optional_text(path): + if not path.is_file(): + return None + return path.read_text(encoding="utf-8", errors="replace").strip() + + +def guest_compiler_metadata(): + command = os.environ.get( + "KZT_GUEST_CC", "x86_64-linux-gnu-gcc" + ).split()[0] + return { + "command": command, + "path": shutil.which(command), + "available": shutil.which(command) is not None, + } + + +def collect_fixture_metadata(fixture_dir): + executable = fixture_dir / PERFORMANCE_EXECUTABLE + library = fixture_dir / PERFORMANCE_LIBRARY + metadata = { + "path": str(fixture_dir), + "compiler": _read_optional_text( + fixture_dir / "guest-compiler.txt" + ), + "build_parameters": _read_optional_text( + fixture_dir / "guest-build-parameters.txt" + ), + "builder": guest_compiler_metadata(), + "performance_executable": collect_binary_metadata(executable), + "performance_library": collect_binary_metadata(library), + } + if metadata["compiler"] is None and executable.is_file(): + metadata["compiler"] = metadata["performance_executable"][ + "compiler" + ] + return metadata + + +def _affinity_snapshot(): + if not hasattr(os, "sched_getaffinity"): + return None + try: + return sorted(os.sched_getaffinity(0)) + except OSError: + return None + + +def _parse_cpu_list(value): + cpus = set() + for part in value.strip().split(","): + if not part: + raise ValueError("CPU list contains an empty item") + bounds = part.split("-", 1) + try: + first = int(bounds[0]) + last = int(bounds[-1]) + except ValueError as error: + raise ValueError(f"invalid CPU list item: {part}") from error + if first < 0 or last < first: + raise ValueError(f"invalid CPU list range: {part}") + cpus.update(range(first, last + 1)) + if not cpus: + raise ValueError("CPU list is empty") + return sorted(cpus) + + +def _thread_siblings_path(guest_cpu): + return ( + CPU_SYSFS_ROOT / f"cpu{guest_cpu}" / "topology" / + "thread_siblings_list" + ) + + +def _thread_siblings(guest_cpu): + path = _thread_siblings_path(guest_cpu) + try: + siblings = _parse_cpu_list(path.read_text(encoding="ascii")) + except (OSError, UnicodeError, ValueError) as error: + raise PrerequisiteError( + f"cannot verify thread siblings from {path}: {error}" + ) from error + if guest_cpu not in siblings: + raise PrerequisiteError( + f"thread siblings from {path} do not contain guest CPU {guest_cpu}" + ) + return path, siblings + + +def _cpu_isolation_record(enabled, guest_cpu, initial=None): + if initial is None: + initial = _affinity_snapshot() + return { + "requested": bool(enabled), + "applied": False, + "guest_cpu": guest_cpu, + "topology_source": str(_thread_siblings_path(guest_cpu)), + "thread_siblings": None, + "initial_affinity": initial, + "active_affinity": initial, + "parent_cpus": { + "initial": initial, + "expected": initial, + "active": initial, + }, + "excluded_cpus": [], + "verification": { + "passed": False, + "siblings_excluded": None, + "active_matches_expected": None, + "error": (None if enabled else + "physical-core isolation was not requested"), + }, + } + + +def activate_harness_cpu_isolation(enabled, guest_cpu): + initial = _affinity_snapshot() + isolation = _cpu_isolation_record(enabled, guest_cpu, initial) + if not enabled: + return isolation + try: + if not hasattr(os, "sched_setaffinity"): + raise PrerequisiteError("harness CPU isolation is unavailable") + if initial is None or guest_cpu not in initial: + raise PrerequisiteError( + f"guest CPU {guest_cpu} is unavailable for harness isolation" + ) + topology_source, siblings = _thread_siblings(guest_cpu) + active = [cpu for cpu in initial if cpu not in siblings] + isolation["topology_source"] = str(topology_source) + isolation["thread_siblings"] = siblings + isolation["excluded_cpus"] = sorted(set(initial) & set(siblings)) + isolation["parent_cpus"]["expected"] = active + if not active: + raise PrerequisiteError( + "harness isolation requires a CPU outside the guest core" + ) + os.sched_setaffinity(0, set(active)) + isolation["applied"] = True + observed = _affinity_snapshot() + isolation["active_affinity"] = observed + isolation["parent_cpus"]["active"] = observed + siblings_excluded = ( + observed is not None and not set(observed).intersection(siblings) + ) + active_matches = observed == active + isolation["verification"].update({ + "siblings_excluded": siblings_excluded, + "active_matches_expected": active_matches, + "passed": siblings_excluded and active_matches, + }) + if not isolation["verification"]["passed"]: + raise PrerequisiteError( + "harness CPU isolation verification failed: expected " + f"{active}, observed {observed}, siblings {siblings}" + ) + except (PrerequisiteError, OSError) as error: + isolation["verification"]["error"] = str(error) + return isolation + + +def restore_harness_cpu_isolation(isolation): + if not isolation.get("applied") or "restored_affinity" in isolation: + return isolation + initial = isolation.get("initial_affinity") + if initial is None: + return isolation + os.sched_setaffinity(0, set(initial)) + isolation["restored_affinity"] = _affinity_snapshot() + return isolation + + +def host_load_snapshot(): + affinity = _affinity_snapshot() + cpu_capacity = len(affinity) if affinity else os.cpu_count() + try: + load_average = list(os.getloadavg()) + except (AttributeError, OSError): + load_average = None + oversubscribed = bool( + load_average and cpu_capacity and load_average[0] > cpu_capacity + ) + return { + "captured_at": _utc_now(), + "load_average_1_5_15": load_average, + "available_cpu_count": cpu_capacity, + "oversubscribed": oversubscribed, + } + + +def collect_host_metadata(cpu, taskset): + uname = platform.uname() + return { + "hostname": uname.node, + "system": uname.system, + "release": uname.release, + "machine": uname.machine, + "processor": uname.processor, + "python": platform.python_version(), + "requested_cpu": cpu, + "process_affinity": _affinity_snapshot(), + "taskset": taskset, + "max_rss_unit": "KiB" if uname.system == "Linux" else "bytes", + } + + +def prerequisite_issues(config): + issues = [] + for label, path in ( + ("baseline LATX", config.baseline_latx), + ("candidate LATX", config.candidate_latx), + ): + if not path.is_file(): + issues.append(f"{label} does not exist: {path}") + elif not os.access(path, os.X_OK): + issues.append(f"{label} is not executable: {path}") + if not config.guest_root.is_dir(): + issues.append(f"guest root does not exist: {config.guest_root}") + fixture_unavailable = False + if not config.fixture_dir.is_dir(): + issues.append(f"fixture directory does not exist: {config.fixture_dir}") + fixture_unavailable = True + else: + missing_fixture = [] + for name in (PERFORMANCE_EXECUTABLE, PERFORMANCE_LIBRARY): + if not (config.fixture_dir / name).is_file(): + missing_fixture.append(name) + if missing_fixture: + issues.append( + "fixture is missing " + ", ".join(missing_fixture) + ) + fixture_unavailable = True + if fixture_unavailable: + compiler = guest_compiler_metadata() + if compiler["available"]: + issues.append( + "x86-64 guest compiler is available at " + + compiler["path"] + + "; build the performance fixture before rerunning" + ) + else: + issues.append( + "x86-64 guest compiler is unavailable: " + + compiler["command"] + ) + + taskset = shutil.which("taskset") + if not taskset: + issues.append("taskset is unavailable; CPU pinning cannot be enforced") + affinity = _affinity_snapshot() + if affinity is not None and config.cpu not in affinity: + issues.append( + f"CPU {config.cpu} is outside process affinity {affinity}" + ) + if taskset and not any("outside process affinity" in issue + for issue in issues): + true_command = shutil.which("true") + if not true_command: + issues.append("true is unavailable for the taskset preflight") + else: + try: + completed = subprocess.run( + [taskset, "--cpu-list", str(config.cpu), true_command], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + timeout=5.0, + check=False, + ) + except (OSError, subprocess.TimeoutExpired) as error: + issues.append(f"taskset preflight failed: {error}") + else: + if completed.returncode != 0: + issues.append( + "taskset cannot pin CPU " + str(config.cpu) + ": " + + completed.stdout.strip() + ) + return issues, taskset + + +def benchmark_environment_profile(role, baseline_binding_state): + if baseline_binding_state not in BASELINE_BINDING_STATES: + raise ValueError( + "baseline binding state must be EAGER_FINAL, " + "LAZY_TO_GUEST_FINAL, or LAZY_TO_NATIVE_FINAL." + ) + if role == "candidate": + return "candidate" + if role == "baseline": + if baseline_binding_state == LAZY_TO_NATIVE_FINAL: + return "candidate" + return "baseline" + raise ValueError(f"Unknown benchmark role: {role}") + + +def benchmark_environment(config, role, fixture_dir=None): + profile = benchmark_environment_profile( + role, config.baseline_binding_state + ) + environment = os.environ.copy() + for name in list(environment): + if name.startswith("LATX_KZT"): + environment.pop(name) + for name in SANITIZED_RUNTIME_VARIABLES: + environment.pop(name, None) + environment.update(COMMON_KZT_ENVIRONMENT) + if profile == "candidate": + environment.update(CANDIDATE_WRITER_ENVIRONMENT) + if fixture_dir is not None: + environment["LD_LIBRARY_PATH"] = str(fixture_dir) + return environment + + +def runtime_environment_snapshot(config, role): + environment = benchmark_environment(config, role, config.fixture_dir) + return { + name: value + for name, value in sorted(environment.items()) + if name.startswith(("LAT_", "LATX_", "QEMU_")) + or name.startswith("LD_") + } + + +def benchmark_command(config, role, mode, taskset=None): + if taskset is None: + taskset = mode + mode = "steady" + if mode not in PERFORMANCE_MODES: + raise ValueError(f"Unknown performance mode: {mode}") + latx = ( + config.candidate_latx if role == "candidate" + else config.baseline_latx + ) + command = [ + taskset, + "--cpu-list", + str(config.cpu), + str(latx), + "-L", + str(config.guest_root), + str(config.fixture_dir / PERFORMANCE_EXECUTABLE), + ] + command.append(mode) + if mode == "steady": + command.append(str(config.steady_calls)) + return command + + +def native_apply_preflight_environment(config, role): + environment = benchmark_environment(config, role, config.fixture_dir) + environment["LATX_KZT_LAZY_DIAGNOSTICS"] = "1" + environment["LATX_KZT_REGISTRY_DIAGNOSTICS"] = "1" + return environment + + +def _diagnostic_records(output, marker, symbol): + records = [] + for line in output.splitlines(): + marker_at = line.find(marker) + if marker_at < 0: + continue + fields = {} + for field in line[marker_at:].split()[1:]: + if "=" not in field: + continue + name, value = field.split("=", 1) + fields[name] = value + if fields.get("symbol") == symbol: + records.append(fields) + return records + + +def _preflight_address(record, record_name, field_name, details): + try: + value = int(record[field_name], 0) + except KeyError as error: + raise GuestCorrectnessError( + f"Native-apply preflight is missing {record_name}.{field_name}.", + details, + ) from error + except ValueError as error: + raise GuestCorrectnessError( + "Native-apply preflight has invalid " + f"{record_name}.{field_name}: {record[field_name]}", + details, + ) from error + if value <= 0: + raise GuestCorrectnessError( + f"Native-apply preflight has non-positive " + f"{record_name}.{field_name}.", + details, + ) + return value + + +def verify_native_apply_preflight(config, taskset, role="candidate"): + if role not in ("baseline", "candidate"): + raise ValueError(f"Unknown benchmark role: {role}") + if (role == "baseline" and + config.baseline_binding_state != LAZY_TO_NATIVE_FINAL): + raise ValueError( + "baseline native-apply preflight requires " + "LAZY_TO_NATIVE_FINAL." + ) + command = benchmark_command(config, role, "first", taskset) + execution = execute_with_rusage( + command, + native_apply_preflight_environment(config, role), + config.timeout, + ) + details = {"role": role, "command": command, **execution} + if execution["timed_out"]: + raise GuestCorrectnessError( + "Native-apply preflight timed out.", details + ) + if execution["returncode"] != 0: + raise GuestCorrectnessError( + "Native-apply preflight failed with " + f"{execution['returncode']}.", details + ) + + lazy_records = _diagnostic_records( + execution["output"], "kzt_lazy_diagnostic ", NATIVE_APPLY_SYMBOL + ) + rela_records = _diagnostic_records( + execution["output"], "kzt_rela_diagnostic ", NATIVE_APPLY_SYMBOL + ) + direct_records = _diagnostic_records( + execution["output"], "kzt_lazy_direct ", NATIVE_APPLY_SYMBOL + ) + publication_records = _diagnostic_records( + execution["output"], "kzt_lazy_prebind_publish ", + NATIVE_APPLY_SYMBOL + ) + guest_first_path = ( + len(lazy_records) == 1 and len(rela_records) == 1 and + not direct_records + ) + direct_path = ( + len(direct_records) == 1 and + not lazy_records and not rela_records + ) + prebound_path = ( + publication_records and not lazy_records and not rela_records and + not direct_records and + all(record.get("result") == "APPLIED" for record in publication_records) + ) + if not guest_first_path and not direct_path and not prebound_path: + raise GuestCorrectnessError( + "Native-apply preflight requires exactly one native route: " + "either one guest-first lazy/relocation pair or one evidence-backed " + f"direct/prebound record for {NATIVE_APPLY_SYMBOL}.", details + ) + try: + guest = parse_guest_record( + execution["output"], expected_steady_calls=config.steady_calls, + expected_mode="first", + ) + except GuestCorrectnessError as error: + raise GuestCorrectnessError( + f"Native-apply preflight guest slot evidence is invalid: {error}", + details, + ) from error + if (guest_first_path and + guest["before"] == guest["after_first"] == + guest["after_steady"]): + raise GuestCorrectnessError( + "Native-apply guest-first route did not update its lazy slot: " + f"before=0x{guest['before']:x} " + f"after_first=0x{guest['after_first']:x} " + f"after_steady=0x{guest['after_steady']:x}.", details + ) + if prebound_path: + publication = publication_records[-1] + bridge_target = _preflight_address( + publication, "prebind", "bridge", details + ) + if (bridge_target != guest["before"] or + bridge_target != guest["after_first"] or + bridge_target != guest["after_steady"]): + raise GuestCorrectnessError( + "Native-apply prebound slot evidence mismatch: " + f"prebind.bridge=0x{bridge_target:x} " + f"guest.before=0x{guest['before']:x} " + f"guest.after_first=0x{guest['after_first']:x} " + f"guest.after_steady=0x{guest['after_steady']:x}.", + details, + ) + elif direct_path: + direct = direct_records[0] + if direct.get("route_status") != "NATIVE_APPLIED" or \ + direct.get("writer_result") != "APPLIED": + raise GuestCorrectnessError( + "Native-apply direct route did not apply its guarded CAS.", + details, + ) + slot_before = _preflight_address( + direct, "direct", "slot_before", details + ) + slot_after = _preflight_address( + direct, "direct", "slot_after", details + ) + selected_target = _preflight_address( + direct, "direct", "selected_target", details + ) + if (slot_before != guest["before"] or + slot_after != selected_target or + slot_after != guest["after_first"] or + slot_after != guest["after_steady"]): + raise GuestCorrectnessError( + "Native-apply direct slot evidence mismatch: " + f"direct.slot_before=0x{slot_before:x} " + f"guest.before=0x{guest['before']:x} " + f"direct.slot_after=0x{slot_after:x} " + f"direct.selected_target=0x{selected_target:x} " + f"guest.after_first=0x{guest['after_first']:x} " + f"guest.after_steady=0x{guest['after_steady']:x}.", + details, + ) + else: + lazy = lazy_records[0] + rela = rela_records[0] + if lazy.get("completion_route_status") != "NATIVE_APPLIED" or \ + rela.get("decision") != "APPROVED" or \ + rela.get("writer_result") != "APPLIED" or \ + rela.get("legacy_fallback") != "0": + raise GuestCorrectnessError( + "Native-apply preflight did not observe an applied lazy route " + "with an applied writer and no legacy fallback.", details + ) + lazy_target = _preflight_address( + lazy, "lazy", "selected_second_target", details + ) + bridge_target = _preflight_address( + rela, "rela", "bridge_target", details + ) + guest_after_first = guest["after_first"] + guest_after_steady = guest["after_steady"] + if (lazy_target != bridge_target or + lazy_target != guest_after_first or + lazy_target != guest_after_steady): + raise GuestCorrectnessError( + "Native-apply preflight slot evidence mismatch: " + f"lazy.selected_second_target=0x{lazy_target:x} " + f"rela.bridge_target=0x{bridge_target:x} " + f"guest.after_first=0x{guest_after_first:x} " + f"guest.after_steady=0x{guest_after_steady:x}.", + details, + ) + try: + validate_role_mode_record( + role, + "first", + guest, + baseline_binding_state=(config.baseline_binding_state + if role == "baseline" else None), + ) + except GuestCorrectnessError as error: + raise GuestCorrectnessError(str(error), details) from error + if prebound_path: + return { + "path": "prebound", "publication": publication_records, + "guest": guest, **details + } + if direct_path: + return { + "path": "direct", "direct": direct, "guest": guest, **details + } + return { + "path": "guest_first", "lazy": lazy, "rela": rela, + "guest": guest, **details + } + + +def verify_guest_preserved_preflight(config, taskset): + if config.baseline_binding_state != LAZY_TO_GUEST_FINAL: + raise ValueError( + "baseline guest-preserved preflight requires " + "LAZY_TO_GUEST_FINAL." + ) + command = benchmark_command(config, "baseline", "first", taskset) + execution = execute_with_rusage( + command, + native_apply_preflight_environment(config, "baseline"), + config.timeout, + ) + details = {"role": "baseline", "command": command, **execution} + if execution["timed_out"]: + raise GuestCorrectnessError( + "Guest-preserved preflight timed out.", details + ) + if execution["returncode"] != 0: + raise GuestCorrectnessError( + "Guest-preserved preflight failed with " + f"{execution['returncode']}.", details + ) + + lazy_records = _diagnostic_records( + execution["output"], "kzt_lazy_diagnostic ", NATIVE_APPLY_SYMBOL + ) + rela_records = _diagnostic_records( + execution["output"], "kzt_rela_diagnostic ", NATIVE_APPLY_SYMBOL + ) + if len(lazy_records) != 1 or len(rela_records) != 1: + raise GuestCorrectnessError( + "Guest-preserved preflight requires exactly one lazy and " + f"relocation record for {NATIVE_APPLY_SYMBOL}.", details + ) + lazy = lazy_records[0] + rela = rela_records[0] + if lazy.get("completion_route_status") != "GUEST_PRESERVED" or \ + rela.get("decision") != "APPROVED" or \ + rela.get("writer_result") != "DISABLED" or \ + rela.get("legacy_fallback") != "0": + raise GuestCorrectnessError( + "Guest-preserved preflight did not observe an approved route " + "with the writer disabled and no legacy fallback.", details + ) + try: + guest = parse_guest_record( + execution["output"], expected_steady_calls=config.steady_calls, + expected_mode="first", + ) + except GuestCorrectnessError as error: + raise GuestCorrectnessError( + f"Guest-preserved preflight slot evidence is invalid: {error}", + details, + ) from error + + selected_target = _preflight_address( + lazy, "lazy", "selected_second_target", details + ) + slot_after_guest = _preflight_address( + lazy, "lazy", "slot_after_guest", details + ) + bridge_target = _preflight_address( + rela, "rela", "bridge_target", details + ) + if (selected_target != slot_after_guest or + selected_target != guest["after_first"] or + selected_target != guest["after_steady"] or + bridge_target == selected_target): + raise GuestCorrectnessError( + "Guest-preserved preflight slot evidence mismatch: " + f"lazy.selected_second_target=0x{selected_target:x} " + f"lazy.slot_after_guest=0x{slot_after_guest:x} " + f"rela.bridge_target=0x{bridge_target:x} " + f"guest.after_first=0x{guest['after_first']:x} " + f"guest.after_steady=0x{guest['after_steady']:x}.", + details, + ) + try: + validate_role_mode_record( + "baseline", + "first", + guest, + baseline_binding_state=config.baseline_binding_state, + ) + except GuestCorrectnessError as error: + raise GuestCorrectnessError(str(error), details) from error + return {"lazy": lazy, "rela": rela, "guest": guest, **details} + + +def _rusage_record(usage): + if usage is None: + return None + return { + "user_time_ns": int(usage.ru_utime * 1_000_000_000), + "system_time_ns": int(usage.ru_stime * 1_000_000_000), + "max_rss": usage.ru_maxrss, + "minor_faults": usage.ru_minflt, + "major_faults": usage.ru_majflt, + "voluntary_context_switches": usage.ru_nvcsw, + "involuntary_context_switches": usage.ru_nivcsw, + } + + +def _kill_process_group(process): + try: + os.killpg(process.pid, signal.SIGKILL) + except (OSError, AttributeError): + process.kill() + + +def _wait4_with_pidfd(process, timeout): + if not hasattr(os, "pidfd_open"): + return None + try: + pidfd = os.pidfd_open(process.pid) + except OSError: + return None + try: + readable, _, _ = select.select([pidfd], [], [], timeout) + timed_out = not readable + if timed_out: + _kill_process_group(process) + _, status, usage = os.wait4(process.pid, 0) + process.returncode = os.waitstatus_to_exitcode(status) + return timed_out, usage + finally: + os.close(pidfd) + + +def execute_with_rusage(command, environment, timeout): + started_ns = time.perf_counter_ns() + timed_out = False + usage = None + wait_method = "subprocess.wait" + with tempfile.TemporaryFile(mode="w+b") as output_file: + try: + process = subprocess.Popen( + command, + env=environment, + stdout=output_file, + stderr=subprocess.STDOUT, + start_new_session=True, + ) + except OSError as error: + raise PrerequisiteError( + f"Could not start benchmark command: {error}" + ) from error + + if hasattr(os, "wait4"): + pidfd_result = _wait4_with_pidfd(process, timeout) + if pidfd_result is not None: + timed_out, usage = pidfd_result + wait_method = "pidfd+os.wait4" + else: + wait_method = "os.wait4 polling fallback" + deadline = time.monotonic() + timeout + while True: + waited_pid, status, usage = os.wait4( + process.pid, os.WNOHANG + ) + if waited_pid == process.pid: + process.returncode = os.waitstatus_to_exitcode(status) + break + if time.monotonic() >= deadline: + timed_out = True + _kill_process_group(process) + _, status, usage = os.wait4(process.pid, 0) + process.returncode = os.waitstatus_to_exitcode(status) + break + time.sleep(0.001) + else: + try: + process.wait(timeout=timeout) + except subprocess.TimeoutExpired: + timed_out = True + _kill_process_group(process) + process.wait() + + finished_ns = time.perf_counter_ns() + output_file.seek(0) + output = output_file.read().decode("utf-8", errors="replace") + return { + "returncode": process.returncode, + "timed_out": timed_out, + "process_total_ns": finished_ns - started_ns, + "rusage": _rusage_record(usage), + "wait_method": wait_method, + "output": output, + } + + +def run_guest_mode(config, role, mode, taskset): + command = benchmark_command(config, role, mode, taskset) + execution = execute_with_rusage( + command, + benchmark_environment(config, role, config.fixture_dir), + config.timeout, + ) + details = { + "role": role, + "command": command, + **execution, + } + if execution["timed_out"]: + raise GuestCorrectnessError( + f"{role} {mode} LATX timed out after {config.timeout} seconds.", + details, + ) + if execution["returncode"] != 0: + raise GuestCorrectnessError( + f"{role} {mode} LATX exited with {execution['returncode']}.", + details, + ) + try: + guest = parse_guest_record( + execution["output"], config.steady_calls, expected_mode=mode + ) + except GuestCorrectnessError as error: + error.details = details + raise + result = { + **guest, + "process_total_ns": execution["process_total_ns"], + "rusage": execution["rusage"], + "output": execution["output"], + "command": command, + "role": role, + } + if mode in ("first", "steady"): + result["binding_state"] = validate_role_mode_record( + role, + mode, + guest, + baseline_binding_state=(config.baseline_binding_state + if role == "baseline" else None), + ) + return result + + +def run_guest_sample(config, role, taskset): + startup = run_guest_mode(config, role, "startup", taskset) + first = run_guest_mode(config, role, "first", taskset) + steady = run_guest_mode(config, role, "steady", taskset) + if first["binding_state"] != steady["binding_state"]: + raise GuestCorrectnessError( + f"{role} binding state changed between first and steady: " + f"{first['binding_state']} -> {steady['binding_state']}.", + {"startup": startup, "first": first, "steady": steady}, + ) + return { + "startup_process_total_ns": startup["process_total_ns"], + "launch_to_first_result_ns": first["process_total_ns"], + "steady_total_ns": steady["steady_total_ns"], + GUEST_FIRST_BINDING_METRIC: first["first_binding_ns"], + "binding_state": first["binding_state"], + "startup": startup, + "first": first, + "steady": steady, + "role": role, + } + + +def verify_role_modes_preflight(config, role, taskset): + return { + mode: run_guest_mode(config, role, mode, taskset) + for mode in PERFORMANCE_MODES + } + + +class RawSampleWriter: + def __init__(self, path): + self.path = path + self._output = None + + def __enter__(self): + self._output = self.path.open("w", encoding="utf-8") + return self + + def __exit__(self, exc_type, exc_value, traceback): + self._output.close() + + def write(self, record): + self._output.write(json.dumps(record, sort_keys=True) + "\n") + self._output.flush() + + +def _run_pair(config, taskset, raw_samples, phase, pair_index, order, + roles): + pair = {"order": list(order)} + for position, label in enumerate(order): + role = roles[label] + try: + sample = run_guest_sample(config, role, taskset) + except GuestCorrectnessError as error: + raw_samples.write({ + "phase": phase, + "pair_index": pair_index, + "position": position, + "label": label, + "role": role, + "error": str(error), + "details": error.details, + }) + raise + pair[label] = sample + raw_samples.write({ + "phase": phase, + "pair_index": pair_index, + "position": position, + "label": label, + "pair_order": list(order), + "sample": sample, + }) + return pair + + +def _checkpoint_targets(initial_samples, max_samples): + targets = [initial_samples] + if initial_samples < MIN_FAIL_PAIRS <= max_samples: + targets.append(MIN_FAIL_PAIRS) + while targets[-1] < max_samples: + current = targets[-1] + targets.append(min(max_samples, max(current + 1, current * 2))) + return targets + + +def _config_record(config): + return { + "baseline_latx": str(config.baseline_latx), + "baseline_binding_state": config.baseline_binding_state, + "comparison_metrics": list( + comparison_metrics_for_baseline_state( + config.baseline_binding_state + ) + ), + "candidate_latx": str(config.candidate_latx), + "guest_root": str(config.guest_root), + "fixture_dir": str(config.fixture_dir), + "cpu": config.cpu, + "warmup": config.warmup, + "samples": config.samples, + "max_samples": config.max_samples, + "aa_samples": config.aa_samples, + "steady_calls": config.steady_calls, + "seed": config.seed, + "output_dir": str(config.output_dir), + "timeout": config.timeout, + "isolate_harness_cpu": config.isolate_harness_cpu, + "aa_only": config.aa_only, + } + + +def _formal_stage_count(samples, max_samples): + return sum(target >= MIN_FAIL_PAIRS + for target in _checkpoint_targets(samples, max_samples)) + + +def _statistics_record(samples=80, max_samples=800, + metrics=PRIMARY_TIME_METRICS, ab_metrics=None, + tail_metrics=None): + metrics = tuple(metrics) + ab_metrics = tuple(metrics if ab_metrics is None else ab_metrics) + tail_metrics = tuple(ab_metrics if tail_metrics is None else tail_metrics) + if not metrics or not ab_metrics: + raise ValueError("statistics require at least one metric") + if any(metric not in ab_metrics for metric in tail_metrics): + raise ValueError("statistics tail metrics must be A/B metrics") + aa_comparison_count = len(metrics) * 3 * 2 + ab_comparison_count = len(ab_metrics) + len(tail_metrics) + aa_interval_alpha = AA_FAMILY_ALPHA / aa_comparison_count + formal_stage_count = _formal_stage_count(samples, max_samples) + analysis_look_count = max(1, formal_stage_count) + ratio_bound_alpha = ( + _ab_ratio_bound_alpha(analysis_look_count, ab_comparison_count) + ) + return { + "method": ( + "paired log-ratio median exact-binomial bounds and paired-index " + "bootstrap marginal P95-ratio bounds" + ), + "comparison_metrics": list(metrics), + "ab_comparison_metrics": list(ab_metrics), + "aa": { + "result_states": [ + AAResult.STABLE.value, + AAResult.DRIFT.value, + AAResult.INCONCLUSIVE.value, + ], + "comparison_count": aa_comparison_count, + "family_confidence": 1.0 - AA_FAMILY_ALPHA, + "per_interval_alpha": aa_interval_alpha, + "per_interval_confidence": 1.0 - aa_interval_alpha, + "formal_min_pairs_per_role": FORMAL_AA_MIN_PAIRS, + "screening_pairs": 50, + "stability_log_limit": AA_STABILITY_LOG_LIMIT, + "stability_percent_limit": 0.5, + "temporal_design": "time-ordered early/late paired contrasts", + "temporal_independence_assumption": ( + AA_TEMPORAL_INDEPENDENCE_ASSUMPTION + ), + }, + "ab": { + "comparison_count": ab_comparison_count, + "formal_stage_count": formal_stage_count, + "analysis_look_count": analysis_look_count, + "per_ratio_bound_alpha": ratio_bound_alpha, + "per_ratio_bound_confidence": ( + 1.0 - ratio_bound_alpha if ratio_bound_alpha else None + ), + "pass_upper_family": { + "confidence": 1.0 - AB_FAMILY_ALPHA, + "comparison_count": ab_comparison_count, + "formal_stage_count": formal_stage_count, + }, + "fail_lower_family": { + "confidence": 1.0 - AB_FAMILY_ALPHA, + "comparison_count": ab_comparison_count, + "formal_stage_count": formal_stage_count, + }, + "median_method": "paired log-ratio median", + "p95_method": ( + "paired-index percentile bootstrap of " + "log(P95(candidate) / P95(baseline)) with " + "family-and-look-adjusted alpha" + ), + "p95_bootstrap_resamples": P95_BOOTSTRAP_RESAMPLES, + "p95_resampling_unit": "paired sample index", + "median_noninferiority_percent": 1.0, + "p95_noninferiority_percent": 2.0, + "median_noninferiority_log": MEDIAN_NONINFERIORITY_LOG, + "p95_noninferiority_log": P95_NONINFERIORITY_LOG, + "guest_first_binding_metric": GUEST_FIRST_BINDING_METRIC, + "guest_first_binding_median_noninferiority_log": ( + FIRST_BINDING_NONINFERIORITY_LOG + ), + "min_formal_pairs": MIN_FAIL_PAIRS, + "max_formal_pairs": max_samples, + "decision_rule": ( + f"PASS requires all {ab_comparison_count} upper bounds within " + "threshold; " + "FAIL requires any lower bound beyond threshold; otherwise " + "INCONCLUSIVE" + ), + }, + } + + +def _write_json(path, value): + temporary = path.with_suffix(path.suffix + ".tmp") + temporary.write_text( + json.dumps(value, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + temporary.replace(path) + + +def _inconclusive_details(analysis): + crossing = [] + for metric, details in analysis["metrics"].items(): + for statistic in details["required_statistics"]: + family = ("paired_log_ratio" if statistic == "median" else + "paired_index_bootstrap_p95_log_ratio") + interval = details[family][statistic] + threshold = details["noninferiority_log"][statistic] + lower = interval["lower"] + upper = interval["upper"] + if (lower is None or upper is None or + lower <= threshold < upper): + crossing.append(f"{metric}.{statistic}") + if crossing: + return ( + "Separate 99% PASS-upper and FAIL-lower decision-family " + "bounds are inconclusive for: " + ", ".join(crossing) + ) + return "Primary timing metrics do not jointly support PASS or FAIL." + + +def _prepare_output_directory(output_dir): + if output_dir.exists(): + if not output_dir.is_dir(): + raise PrerequisiteError( + f"Output path is not a directory: {output_dir}" + ) + if any(output_dir.iterdir()): + raise PrerequisiteError( + f"Output directory already contains evidence: {output_dir}" + ) + else: + output_dir.mkdir(parents=True) + + +def _acquire_output_ownership(output_dir): + owner_path = output_dir / OWNERSHIP_MARKER + try: + descriptor = os.open(owner_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, + 0o600) + except FileExistsError as error: + raise PrerequisiteError( + f"Output directory is already owned by another harness run: " + f"{output_dir}" + ) from error + try: + os.write(descriptor, f"pid={os.getpid()}\n".encode("ascii")) + os.fsync(descriptor) + except OSError: + os.close(descriptor) + raise + return descriptor + + +def _validate_sampling_configuration(config): + if config.cpu < 0: + raise PrerequisiteError("cpu must not be negative.") + if config.warmup < 0: + raise PrerequisiteError("warmup must not be negative.") + if config.steady_calls < 100000: + raise PrerequisiteError("steady_calls must be at least 100000.") + if not math.isfinite(config.timeout) or config.timeout <= 0: + raise PrerequisiteError("timeout must be finite and greater than zero.") + if config.baseline_binding_state not in BASELINE_BINDING_STATES: + raise PrerequisiteError( + "baseline_binding_state must be EAGER_FINAL, " + "LAZY_TO_GUEST_FINAL, or LAZY_TO_NATIVE_FINAL." + ) + if config.samples not in (80, 400, 800, 1600): + raise PrerequisiteError( + "samples must be one of 80, 400, 800, or 1600." + ) + if config.max_samples not in (80, 400, 800, 1600): + raise PrerequisiteError( + "max_samples must be one of 80, 400, 800, or 1600." + ) + if config.samples > config.max_samples: + raise PrerequisiteError("samples must not exceed max_samples.") + if config.aa_samples != 50 and config.aa_samples < FORMAL_AA_MIN_PAIRS: + raise PrerequisiteError( + "A/A pairs must be 50 for screening or at least 200 for " + "formal inference." + ) + if config.aa_samples == 50 and config.max_samples != 80: + raise PrerequisiteError( + "50-pair A/A screening requires an 80-pair directional A/B run." + ) + + +def _harness_error_report(config, raw_path, started_ns, error): + return { + "schema_version": HARNESS_SCHEMA_VERSION, + "artifact_type": REPORT_ARTIFACT_TYPE, + "ownership": { + "marker": OWNERSHIP_MARKER, + "mode": "exclusive_create", + }, + "started_at": _utc_now(), + "finished_at": _utc_now(), + "configuration": _config_record(config), + "binaries": None, + "fixture": None, + "host": None, + "environment": None, + "statistics": _statistics_record( + config.samples, + config.max_samples, + comparison_metrics_for_baseline_state( + config.baseline_binding_state + ), + ), + "measurement": None, + "command_templates": None, + "raw_samples": str(raw_path), + "load": {"before": None, "after": None}, + "preflight_issues": None, + "mode_preflight": None, + "native_apply_preflight": None, + "baseline_native_apply_preflight": None, + "baseline_guest_preserved_preflight": None, + "result": GateResult.INCONCLUSIVE.value, + "result_scope": "harness_error", + "reason": f"Harness error ({type(error).__name__}): {error}", + "harness_error": { + "type": type(error).__name__, + "message": str(error), + }, + "harness_elapsed_ns": time.perf_counter_ns() - started_ns, + } + + +def _write_harness_error_artifacts(output_dir, report_path, report): + metadata = dict(report) + metadata["artifact_type"] = METADATA_ARTIFACT_TYPE + for path, value in ( + (output_dir / "run-metadata.json", metadata), + (report_path, report)): + try: + _write_json(path, value) + except Exception: + # The caller still receives the original setup exception. A + # failed artifact write is recorded only when storage permits it. + pass + + +def run_performance_gate(config): + _validate_sampling_configuration(config) + comparison_metrics = comparison_metrics_for_baseline_state( + config.baseline_binding_state + ) + ab_metrics = comparison_metrics + (GUEST_FIRST_BINDING_METRIC,) + _prepare_output_directory(config.output_dir) + ownership_descriptor = _acquire_output_ownership(config.output_dir) + raw_path = config.output_dir / "raw-samples.jsonl" + report_path = config.output_dir / "report.json" + started_ns = time.perf_counter_ns() + report = None + cpu_isolation = _cpu_isolation_record( + config.isolate_harness_cpu, config.cpu + ) + try: + issues, taskset = prerequisite_issues(config) + if (_formal_stage_count(config.samples, config.max_samples) and + not config.isolate_harness_cpu): + issues.append( + "formal performance requires verified physical-core isolation" + ) + if not issues: + cpu_isolation = activate_harness_cpu_isolation( + config.isolate_harness_cpu, config.cpu + ) + if (config.isolate_harness_cpu and + not cpu_isolation["verification"]["passed"]): + issues.append( + "physical-core isolation could not be verified: " + + (cpu_isolation["verification"]["error"] or + "unknown isolation error") + ) + report = { + "schema_version": HARNESS_SCHEMA_VERSION, + "artifact_type": REPORT_ARTIFACT_TYPE, + "ownership": { + "marker": OWNERSHIP_MARKER, + "mode": "exclusive_create", + }, + "started_at": _utc_now(), + "configuration": _config_record(config), + "binaries": { + "baseline": collect_binary_metadata(config.baseline_latx), + "candidate": collect_binary_metadata(config.candidate_latx), + }, + "fixture": collect_fixture_metadata(config.fixture_dir), + "host": collect_host_metadata(config.cpu, taskset), + "environment": { + "baseline": runtime_environment_snapshot(config, "baseline"), + "candidate": runtime_environment_snapshot(config, "candidate"), + "diagnostics_enabled": False, + "sanitized_variables": list(SANITIZED_RUNTIME_VARIABLES), + }, + "statistics": _statistics_record( + config.samples, + config.max_samples, + comparison_metrics, + ab_metrics=ab_metrics, + tail_metrics=comparison_metrics, + ), + "measurement": { + "process_timer": "time.perf_counter_ns", + "guest_timer": "CLOCK_MONOTONIC_RAW", + "resource_collector": ( + "os.wait4" if hasattr(os, "wait4") else "unavailable" + ), + "external_time_command": None, + }, + "command_templates": { + role: { + mode: benchmark_command( + config, role, mode, taskset or "" + ) + for mode in PERFORMANCE_MODES + } + for role in ("baseline", "candidate") + }, + "raw_samples": str(raw_path), + "load": {"before": host_load_snapshot()}, + "harness_cpu_isolation": cpu_isolation, + "preflight_issues": issues, + "mode_preflight": None, + "native_apply_preflight": None, + "baseline_native_apply_preflight": None, + "baseline_guest_preserved_preflight": None, + } + metadata = dict(report) + metadata["artifact_type"] = METADATA_ARTIFACT_TYPE + _write_json(config.output_dir / "run-metadata.json", metadata) + + result = GateResult.INCONCLUSIVE + reason = "Benchmark did not run." + result_scope = "environment_inconclusive" + with RawSampleWriter(raw_path) as raw_samples: + if issues: + reason = "Missing benchmark prerequisites: " + "; ".join(issues) + else: + try: + report["mode_preflight"] = { + role: verify_role_modes_preflight(config, role, taskset) + for role in ("baseline", "candidate") + } + report["native_apply_preflight"] = \ + verify_native_apply_preflight(config, taskset) + if config.baseline_binding_state == LAZY_TO_NATIVE_FINAL: + report["baseline_native_apply_preflight"] = \ + verify_native_apply_preflight( + config, taskset, role="baseline" + ) + elif config.baseline_binding_state == LAZY_TO_GUEST_FINAL: + report["baseline_guest_preserved_preflight"] = \ + verify_guest_preserved_preflight(config, taskset) + warmup_orders = randomized_pair_orders( + config.warmup, + _derived_seed(config.seed, "warmup-order"), + ("baseline", "candidate"), + ) + for index, order in enumerate(warmup_orders, 1): + _run_pair( + config, + taskset, + raw_samples, + "warmup", + index, + order, + {"baseline": "baseline", "candidate": "candidate"}, + ) + report["warmup_pairs"] = config.warmup + + aa_pairs = {} + for role in ("baseline", "candidate"): + orders = randomized_pair_orders( + config.aa_samples, + _derived_seed(config.seed, role, "aa-order"), + ("a", "b"), + ) + aa_pairs[role] = [ + _run_pair( + config, + taskset, + raw_samples, + role + "-aa", + index, + order, + {"a": role, "b": role}, + ) + for index, order in enumerate(orders, 1) + ] + aa_assessment = assess_dual_aa( + aa_pairs["baseline"], + aa_pairs["candidate"], + seed=_derived_seed(config.seed, "dual-aa-interval"), + metrics=comparison_metrics, + ) + report["aa_stability"] = aa_assessment + screening_aa = config.aa_samples == 50 + report["aa_mode"] = ( + "screening" if screening_aa else "formal" + ) + report["load"]["after_aa"] = host_load_snapshot() + load_abnormal = ( + report["load"]["before"]["oversubscribed"] + or report["load"]["after_aa"]["oversubscribed"] + ) + if config.aa_only: + result_scope = "aa_screening" + reason = ( + "A/A-only " + + report["aa_mode"] + + " completed with " + + aa_assessment["result"] + + "; A/B was not run." + ) + elif aa_assessment["result"] == AAResult.DRIFT.value: + drifting = [ + role for role in ("baseline", "candidate") + if aa_assessment[role]["result"] == AAResult.DRIFT.value + ] + reason = "A/A stability check detected drift for: " + \ + ", ".join(drifting) + elif (aa_assessment["result"] == AAResult.INCONCLUSIVE.value + and not screening_aa): + reason = ( + "Formal A/A result is INCONCLUSIVE; formal A/B is " + "blocked." + ) + elif load_abnormal: + reason = ( + "Host load exceeded available CPU capacity during " + "the stability phase." + ) + else: + formal_stage_count = _formal_stage_count( + config.samples, config.max_samples + ) + analysis_look_count = max(1, formal_stage_count) + exploratory_ab = ( + screening_aa or config.max_samples < MIN_FAIL_PAIRS + ) + report["ab_mode"] = ( + "exploratory" if exploratory_ab else "formal" + ) + ab_orders = randomized_pair_orders( + config.max_samples, + _derived_seed(config.seed, "ab-order"), + ("baseline", "candidate"), + ) + ab_pairs = [] + checkpoints = [] + for target in _checkpoint_targets( + config.samples, config.max_samples): + for index in range(len(ab_pairs), target): + ab_pairs.append(_run_pair( + config, + taskset, + raw_samples, + "ab", + index + 1, + ab_orders[index], + { + "baseline": "baseline", + "candidate": "candidate", + }, + )) + checkpoint_load = host_load_snapshot() + if checkpoint_load["oversubscribed"]: + checkpoints.append({ + "pair_count": len(ab_pairs), + "decision": GateResult.INCONCLUSIVE.value, + "mode": report["ab_mode"], + "analysis": None, + "load": checkpoint_load, + }) + result = GateResult.INCONCLUSIVE + reason = ( + "Host load exceeded available CPU capacity " + "during the A/B phase." + ) + break + analysis = analyze_ab_pairs( + ab_pairs, + seed=_derived_seed( + config.seed, "ab-interval", target + ), + formal_stage_count=formal_stage_count, + analysis_look_count=analysis_look_count, + metrics=ab_metrics, + tail_metrics=comparison_metrics, + ) + decision = classify_gate( + analysis, + pair_count=len(ab_pairs), + formal_aa_stable=not exploratory_ab, + ) + checkpoints.append({ + "pair_count": len(ab_pairs), + "decision": decision.value, + "mode": report["ab_mode"], + "analysis": analysis, + "load": checkpoint_load, + }) + if decision == GateResult.PASS: + result = decision + result_scope = "performance_conclusion" + reason = ( + "All primary paired median plus paired-index " + "bootstrap marginal P95-ratio " + "PASS-decision-family 99% upper bounds satisfy " + "non-inferiority." + ) + break + if decision == GateResult.FAIL: + result = decision + result_scope = "performance_conclusion" + reason = ( + "Candidate is clearly and persistently slower " + "under the FAIL-decision-family 99% lower " + "bounds after at least 400 pairs for: " + + ", ".join( + analysis["clearly_slower_statistics"] + ) + ) + break + if target == config.max_samples: + result = GateResult.INCONCLUSIVE + result_scope = "performance_conclusion" + if exploratory_ab: + reason = ( + "A/B is exploratory; final result remains " + "INCONCLUSIVE because " + + ( + "A/A used 50-pair screening." + if screening_aa else + "fewer than 400 A/B pairs were allowed." + ) + ) + else: + reason = _inconclusive_details(analysis) + break + report["ab_checkpoints"] = checkpoints + except GuestCorrectnessError as error: + result = GateResult.FAIL + result_scope = "correctness_failure" + reason = "Correctness failure: " + str(error) + report["correctness_error"] = { + "message": str(error), + "details": error.details, + } + except PrerequisiteError as error: + result = GateResult.INCONCLUSIVE + result_scope = "environment_inconclusive" + reason = "Benchmark prerequisite failed at runtime: " + str(error) + except Exception as error: + result = GateResult.INCONCLUSIVE + result_scope = "harness_error" + reason = ( + f"Harness error ({type(error).__name__}): {error}" + ) + + report["load"]["after"] = host_load_snapshot() + restore_harness_cpu_isolation(cpu_isolation) + report["result"] = result.value + report["result_scope"] = result_scope + report["reason"] = reason + report["finished_at"] = _utc_now() + report["harness_elapsed_ns"] = time.perf_counter_ns() - started_ns + _write_json(report_path, report) + return report + except Exception as error: + if report is None: + report = _harness_error_report(config, raw_path, started_ns, error) + else: + report["result"] = GateResult.INCONCLUSIVE.value + report["result_scope"] = "harness_error" + report["reason"] = ( + f"Harness error ({type(error).__name__}): {error}" + ) + report["harness_error"] = { + "type": type(error).__name__, + "message": str(error), + } + report["finished_at"] = _utc_now() + report["harness_elapsed_ns"] = time.perf_counter_ns() - started_ns + _write_harness_error_artifacts(config.output_dir, report_path, report) + raise + finally: + restore_harness_cpu_isolation(cpu_isolation) + os.close(ownership_descriptor) diff --git a/tests/unit/kzt/real_guest_loader_performance.py b/tests/unit/kzt/real_guest_loader_performance.py new file mode 100644 index 00000000000..29de2722f68 --- /dev/null +++ b/tests/unit/kzt/real_guest_loader_performance.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +"""Formal paired performance gate for the real dependency-reopen fixture.""" + +import argparse +import json +import math +import os +from pathlib import Path +import shutil +import sys + +from real_guest_harness import ( + GateResult, + activate_harness_cpu_isolation, + analyze_ab_pairs, + assess_dual_aa, + classify_gate, + execute_with_rusage, + host_load_snapshot, + randomized_pair_orders, + restore_harness_cpu_isolation, +) + + +LIFECYCLE_MARKER = "WI600_GUEST_LOADER_PASS dependency-reopen" +METRIC = "dlopen_lifecycle_process_total_ns" +DEFAULT_SEED = 20260729 +SANITIZED_VARIABLES = ( + "LAT_DFILTER", "LAT_GDB", "LAT_LOG", "LAT_LOG_FILENAME", + "LAT_SINGLESTEP", "LAT_STRACE", "LAT_STRACE_ERROR", "LAT_TRACE", + "LD_AUDIT", "LD_BIND_NOW", "LD_DEBUG", "LD_DEBUG_OUTPUT", + "LD_LIBRARY_PATH", "LD_PRELOAD", "LD_PROFILE", "QEMU_LOG", + "QEMU_STRACE", +) + + +class GuestLifecycleError(RuntimeError): + pass + + +def lifecycle_command(latx, guest_root, fixture_dir, cpu): + return [ + "taskset", "-c", str(cpu), str(latx), "-L", str(guest_root), + str(Path(fixture_dir) / "dependency-reopen"), + ] + + +def lifecycle_environment(role, fixture_dir): + environment = os.environ.copy() + for name in list(environment): + if name.startswith("LATX_KZT") or name in SANITIZED_VARIABLES: + environment.pop(name, None) + environment.update({ + "LATX_AOT": "0", + "LATX_KZT": "2", + "LATX_KZT_LAZY_DIAGNOSTICS": "0", + "LATX_KZT_REGISTRY_DIAGNOSTICS": "0", + "LD_LIBRARY_PATH": str(fixture_dir), + }) + if role == "candidate": + environment.update({ + "LATX_KZT_PATCH_SPIKE": "1", + "LATX_KZT_PATCH_SPIKE_WRITE": "1", + "LATX_KZT_PATCH_SPIKE_BUDGET": "1", + }) + return environment + + +def validate_lifecycle_execution(execution): + if execution.get("timed_out"): + raise GuestLifecycleError("dependency-reopen timed out") + if execution.get("returncode") != 0: + raise GuestLifecycleError( + "dependency-reopen exited with " + f"{execution.get('returncode')}" + ) + if LIFECYCLE_MARKER not in execution.get("output", "").splitlines(): + raise GuestLifecycleError("dependency-reopen did not emit its PASS marker") + elapsed = execution.get("process_total_ns", 0) + if not isinstance(elapsed, int) or elapsed <= 0: + raise GuestLifecycleError("dependency-reopen has no positive process time") + return {METRIC: elapsed} + + +def run_lifecycle_sample(args, role): + latx = args.baseline_latx if role == "baseline" else args.candidate_latx + command = lifecycle_command(latx, args.guest_root, args.fixture_dir, args.cpu) + execution = execute_with_rusage( + command, lifecycle_environment(role, args.fixture_dir), args.timeout + ) + metrics = validate_lifecycle_execution(execution) + return { + **metrics, + "role": role, + "command": command, + "rusage": execution["rusage"], + "wait_method": execution["wait_method"], + "output": execution["output"], + } + + +def _write_json(path, value): + path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", + encoding="utf-8") + + +def _run_pair(args, output, phase, pair_index, order, labels): + pair = {"order": list(order)} + for position, label in enumerate(order): + role = labels[label] + sample = run_lifecycle_sample(args, role) + pair[label] = sample + output.write(json.dumps({ + "phase": phase, + "pair_index": pair_index, + "position": position, + "label": label, + "pair_order": list(order), + "sample": sample, + }, sort_keys=True) + "\n") + output.flush() + return pair + + +def _run_aa(args, output, role): + labels = {"a": role, "b": role} + orders = randomized_pair_orders( + args.aa_pairs, args.seed + (0 if role == "baseline" else 1), + ("a", "b"), + ) + return [ + _run_pair(args, output, role + "-aa", index, order, labels) + for index, order in enumerate(orders) + ] + + +def _issues(args): + issues = [] + for label, path in (("baseline LATX", args.baseline_latx), + ("candidate LATX", args.candidate_latx)): + if not path.is_file() or not os.access(path, os.X_OK): + issues.append(f"{label} is not executable: {path}") + if not args.guest_root.is_dir(): + issues.append(f"guest root does not exist: {args.guest_root}") + fixture = args.fixture_dir / "dependency-reopen" + if not fixture.is_file() or not os.access(fixture, os.X_OK): + issues.append(f"dependency-reopen fixture is not executable: {fixture}") + if not shutil.which("taskset"): + issues.append("taskset is unavailable") + return issues + + +def run_lifecycle_gate(args): + issues = _issues(args) + if args.output_dir.exists() and any(args.output_dir.iterdir()): + raise GuestLifecycleError( + f"output directory already contains evidence: {args.output_dir}" + ) + args.output_dir.mkdir(parents=True, exist_ok=True) + raw_path = args.output_dir / "raw-samples.jsonl" + report_path = args.output_dir / "report.json" + metadata_path = args.output_dir / "run-metadata.json" + cpu_isolation = activate_harness_cpu_isolation(True, args.cpu) + if not cpu_isolation["verification"]["passed"]: + issues.append( + "physical-core isolation could not be verified: " + + (cpu_isolation["verification"]["error"] or + "unknown isolation error") + ) + report = { + "artifact_type": "kzt-dlopen-lifecycle-performance-report", + "metric": METRIC, + "metric_scope": ( + "end-to-end LATX process time for dependency-reopen; not " + "guest-internal lazy binding" + ), + "config": { + "baseline_latx": str(args.baseline_latx), + "candidate_latx": str(args.candidate_latx), + "guest_root": str(args.guest_root), + "fixture_dir": str(args.fixture_dir), + "cpu": args.cpu, + "aa_pairs": args.aa_pairs, + "ab_pairs": args.ab_pairs, + "warmup": args.warmup, + "seed": args.seed, + "isolate_harness_cpu": True, + }, + "harness_cpu_isolation": cpu_isolation, + "load": {"before": host_load_snapshot()}, + "issues": issues, + "result": GateResult.INCONCLUSIVE.value, + "result_scope": "environment_inconclusive", + } + try: + if issues or report["load"]["before"]["oversubscribed"]: + report["reason"] = ( + "benchmark prerequisites or host load unavailable: " + + "; ".join(issues) + ) + return report + with raw_path.open("w", encoding="utf-8") as output: + for role in ("baseline", "candidate"): + for warmup in range(args.warmup): + sample = run_lifecycle_sample(args, role) + output.write(json.dumps({ + "phase": "warmup", "role": role, + "sample_index": warmup, "sample": sample, + }, sort_keys=True) + "\n") + baseline_aa = _run_aa(args, output, "baseline") + candidate_aa = _run_aa(args, output, "candidate") + aa = assess_dual_aa( + baseline_aa, candidate_aa, seed=args.seed, metrics=(METRIC,) + ) + report["aa_stability"] = aa + report["load"]["after_aa"] = host_load_snapshot() + if not aa["stable"]: + report["reason"] = "formal A/A lifecycle result is not stable" + return report + if report["load"]["after_aa"]["oversubscribed"]: + report["reason"] = "host load exceeded available capacity during A/A" + return report + orders = randomized_pair_orders( + args.ab_pairs, args.seed + 2, ("baseline", "candidate") + ) + pairs = [ + _run_pair( + args, output, "ab", index, order, + {"baseline": "baseline", "candidate": "candidate"}, + ) + for index, order in enumerate(orders) + ] + report["load"]["after_ab"] = host_load_snapshot() + if report["load"]["after_ab"]["oversubscribed"]: + report["reason"] = "host load exceeded available capacity during A/B" + return report + analysis = analyze_ab_pairs( + pairs, seed=args.seed, formal_stage_count=1, + metrics=(METRIC,), tail_metrics=(METRIC,) + ) + decision = classify_gate( + analysis, pair_count=len(pairs), formal_aa_stable=True + ) + report["ab"] = analysis + report["result"] = decision.value + report["result_scope"] = "performance_conclusion" + report["reason"] = ( + "paired lifecycle median and paired-index bootstrap marginal " + "P95-ratio non-inferiority passed" + if decision == GateResult.PASS else + "paired lifecycle gate did not establish non-inferiority" + ) + return report + except GuestLifecycleError as error: + report["result"] = GateResult.FAIL.value + report["result_scope"] = "correctness_failure" + report["reason"] = str(error) + return report + finally: + report["load"]["after"] = host_load_snapshot() + restore_harness_cpu_isolation(cpu_isolation) + report["harness_cpu_isolation"] = cpu_isolation + metadata = dict(report) + metadata["artifact_type"] = ( + "kzt-dlopen-lifecycle-performance-run-metadata" + ) + _write_json(metadata_path, metadata) + _write_json(report_path, report) + + +def positive_integer(value): + parsed = int(value) + if parsed <= 0: + raise argparse.ArgumentTypeError("must be positive") + return parsed + + +def parse_args(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--baseline-latx", required=True, type=Path) + parser.add_argument("--candidate-latx", required=True, type=Path) + parser.add_argument("--guest-root", required=True, type=Path) + parser.add_argument("--fixture-dir", required=True, type=Path) + parser.add_argument("--cpu", required=True, type=int) + parser.add_argument("--output-dir", required=True, type=Path) + parser.add_argument("--aa-pairs", type=positive_integer, default=200) + parser.add_argument("--ab-pairs", type=positive_integer, default=400) + parser.add_argument("--warmup", type=int, default=20) + parser.add_argument("--seed", type=int, default=DEFAULT_SEED) + parser.add_argument("--timeout", type=float, default=60.0) + args = parser.parse_args() + invalid = ( + args.cpu < 0 or args.warmup < 0 or args.aa_pairs < 200 or + args.ab_pairs < 400 or not math.isfinite(args.timeout) or + args.timeout <= 0 + ) + if invalid: + parser.error("invalid formal sampling configuration") + for name in ("baseline_latx", "candidate_latx", "guest_root", "fixture_dir"): + setattr(args, name, getattr(args, name).resolve()) + args.output_dir = args.output_dir.resolve() + return args + + +def main(): + args = parse_args() + report = run_lifecycle_gate(args) + print(json.dumps({ + "result": report["result"], + "reason": report.get("reason"), + "report": str(args.output_dir / "report.json"), + "raw_samples": str(args.output_dir / "raw-samples.jsonl"), + }, indent=2, sort_keys=True)) + return 0 if report["result"] == GateResult.PASS.value else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/unit/kzt/test_real_guest_e2e.py b/tests/unit/kzt/test_real_guest_e2e.py old mode 100755 new mode 100644 index 8e845bbd7f0..9e7542a38e6 --- a/tests/unit/kzt/test_real_guest_e2e.py +++ b/tests/unit/kzt/test_real_guest_e2e.py @@ -1,214 +1,68 @@ #!/usr/bin/env python3 import argparse -import os from pathlib import Path -import re import subprocess import sys - -SUCCESS_PATTERN = re.compile( - r"KZT_GUEST_E2E_OK calls=2 slot=(0x[0-9a-f]+) " - r"before=(0x[0-9a-f]+) after_first=(0x[0-9a-f]+) " - r"after_second=(0x[0-9a-f]+) first_ns=(0x[0-9a-f]+) " - r"second_ns=(0x[0-9a-f]+)" -) -TARGET_SYMBOL = "uname" - - -def parse_record(line): - record = {} - for field in line.split(): - if "=" in field: - key, value = field.split("=", 1) - record[key] = value - return record +import test_wi849_real_guest_preemption as preemption -def symbol_records(output, prefix): - records = [] - for line in output.splitlines(): - marker = line.find(prefix) - if marker >= 0: - record = parse_record(line[marker:]) - if record.get("symbol") == TARGET_SYMBOL: - records.append((line, record)) - return records +SCENARIOS = ( + { + **preemption.SCENARIOS[0], + "name": "wi601-direct", + }, + { + **preemption.SCENARIOS[1], + "name": "wi601-guest-handoff", + }, +) -def require(condition, message, output=None): - if condition: - return - if output: - message = f"{message}\n--- guest output ---\n{output.rstrip()}" - raise RuntimeError(message) +def run_scenario(args, scenario): + return preemption.run_scenario(args, scenario) -def verify_cross_arch_version(args): - guest_version = ( - args.fixture_dir / "guest-symbol-version.txt" - ).read_text(encoding="utf-8").strip() - completed = subprocess.run( - [args.readelf, "-Ws", str(args.host_libc)], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - timeout=args.timeout, - check=False, - ) - require(completed.returncode == 0, - "Could not inspect the host libc symbol versions.", - completed.stdout) - host_versions = set(re.findall( - rf"\b{TARGET_SYMBOL}@@?(GLIBC_[^\s]+)", completed.stdout - )) - require(len(host_versions) == 1, - "Host libc must expose one inspectable uname symbol version.", - completed.stdout) - host_version = next(iter(host_versions)) - require(host_version != guest_version, - "Cross-architecture fixture requires different guest and host " - "uname versions.") - print( - f"Cross-architecture versions: guest={guest_version} " - f"host={host_version}" +def parse_args(): + parser = argparse.ArgumentParser( + description="Run the WI-601 real guest direct and guest-handoff gate." ) - - -def run_mode(args, mode, budget, expected_writer, expected_fallback): - executable = args.fixture_dir / "kzt_guest_main" - command = [ - str(args.latx), - "-L", - str(args.guest_root), - "-E", - f"LD_LIBRARY_PATH={args.fixture_dir}", - str(executable), - ] - environment = os.environ.copy() - environment.update( - { - "LATX_KZT": "1", - "LATX_KZT_REGISTRY_DIAGNOSTICS": "1", - "LATX_KZT_LAZY_DIAGNOSTICS": "1", - "LATX_KZT_PATCH_SPIKE": "1", - "LATX_KZT_PATCH_SPIKE_WRITE": "1", - "LATX_KZT_PATCH_SPIKE_BUDGET": str(budget), - } + parser.add_argument( + "--latx", required=True, type=preemption.existing_file ) - completed = subprocess.run( - command, - env=environment, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - timeout=args.timeout, - check=False, + parser.add_argument( + "--guest-root", required=True, type=preemption.existing_directory ) - output = completed.stdout - log_path = args.log_dir / f"real-guest-{mode}.log" - log_path.write_text(output, encoding="utf-8") - - require(completed.returncode == 0, - f"{mode} mode exited with {completed.returncode}.", output) - success_matches = SUCCESS_PATTERN.findall(output) - require(len(success_matches) == 1, - f"{mode} mode did not complete both uname calls.", output) - slot, before, after_first, after_second, first_ns, second_ns = ( - int(value, 16) for value in success_matches[0] + parser.add_argument( + "--fixture-dir", required=True, type=preemption.existing_directory ) - require(slot != 0 and before != 0, - f"{mode} mode reported an invalid uname GOT slot.", output) - require(after_first != before, - f"{mode} mode did not replace the unresolved uname slot.", output) - require(after_second == after_first, - f"{mode} mode changed the uname slot on the second call.", output) - require(first_ns > 0 and second_ns > 0, - f"{mode} mode did not report call timings.", output) - - rela_records = symbol_records(output, "kzt_rela_diagnostic ") - require(len(rela_records) == 1, - f"{mode} mode expected one uname relocation record, " - f"found {len(rela_records)}.", output) - _, rela = rela_records[0] - require(rela.get("decision") == "APPROVED", - f"{mode} mode did not approve the uname patch.", output) - require(rela.get("writer_result") == expected_writer, - f"{mode} mode writer result is not {expected_writer}.", output) - require(rela.get("legacy_fallback") == expected_fallback, - f"{mode} mode legacy fallback is not {expected_fallback}.", output) - if expected_writer == "APPLIED": - require(int(rela.get("bridge_target", "0"), 16) == after_first, - f"{mode} mode GOT value does not match the approved bridge.", - output) - - resolver_entries = symbol_records(output, "kzt_lazy_resolver_entry ") - require(len(resolver_entries) == 1, - f"{mode} mode entered the uname resolver " - f"{len(resolver_entries)} times instead of once.", output) - - lazy_records = symbol_records(output, "kzt_lazy_diagnostic ") - require(len(lazy_records) == 1, - f"{mode} mode resolved uname more than once or missed it; " - f"found {len(lazy_records)} lazy records.", output) - return log_path - - -def existing_directory(value): - path = Path(value).resolve() - if not path.is_dir(): - raise argparse.ArgumentTypeError(f"Directory does not exist: {path}") - return path - - -def existing_file(value): - path = Path(value).resolve() - if not path.is_file(): - raise argparse.ArgumentTypeError(f"File does not exist: {path}") - return path - - -def parse_args(): - parser = argparse.ArgumentParser( - description="Run the KZT real x86-64 guest lazy-binding gate." - ) - parser.add_argument("--latx", required=True, type=existing_file) - parser.add_argument("--guest-root", required=True, type=existing_directory) - parser.add_argument("--host-libc", required=True, type=existing_file) - parser.add_argument("--fixture-dir", required=True, type=existing_directory) - parser.add_argument("--readelf", default="readelf") parser.add_argument("--log-dir", type=Path) parser.add_argument("--timeout", type=float, default=30.0) args = parser.parse_args() - if args.log_dir is None: - args.log_dir = args.fixture_dir / "logs" - else: - args.log_dir = args.log_dir.resolve() + args.log_dir = ( + args.log_dir.resolve() + if args.log_dir + else args.fixture_dir / "wi601-real-guest-logs" + ) args.log_dir.mkdir(parents=True, exist_ok=True) - require((args.fixture_dir / "kzt_guest_main").is_file(), - "Fixture directory is missing kzt_guest_main.") - require((args.fixture_dir / "libkzt_guest_probe.so").is_file(), - "Fixture directory is missing libkzt_guest_probe.so.") - require((args.fixture_dir / "guest-symbol-version.txt").is_file(), - "Fixture directory is missing guest-symbol-version.txt.") + for scenario in SCENARIOS: + executable = args.fixture_dir / scenario["executable"] + if not executable.is_file(): + parser.error(f"Fixture is missing {executable.name}.") return args def main(): args = parse_args() - verify_cross_arch_version(args) - normal_log = run_mode(args, "applied", 1, "APPLIED", "0") - fail_open_log = run_mode( - args, "fail-open", 0, "BUDGET_EXHAUSTED", "1" - ) - print("KZT real guest E2E: PASS") - print(f"Applied log: {normal_log}") - print(f"Fail-open log: {fail_open_log}") + logs = [run_scenario(args, scenario) for scenario in SCENARIOS] + print("KZT WI-601 real guest E2E: PASS") + for log in logs: + print(f"log: {log}") if __name__ == "__main__": try: main() except (RuntimeError, subprocess.TimeoutExpired) as error: - print(f"KZT real guest E2E: FAIL: {error}", file=sys.stderr) + print(f"KZT WI-601 real guest E2E: FAIL: {error}", file=sys.stderr) sys.exit(1) diff --git a/tests/unit/kzt/test_real_guest_harness.py b/tests/unit/kzt/test_real_guest_harness.py new file mode 100644 index 00000000000..7263ed9bd34 --- /dev/null +++ b/tests/unit/kzt/test_real_guest_harness.py @@ -0,0 +1,2037 @@ +#!/usr/bin/env python3 +import os +import json +import math +from pathlib import Path +import sys +import tempfile +import unittest +from unittest import mock + +import real_guest_harness + +from real_guest_harness import ( + AAResult, + EAGER_FINAL, + GateResult, + GUEST_FIRST_BINDING_METRIC, + GUEST_LAZY_COMPARISON_METRICS, + GuestCorrectnessError, + HarnessConfig, + PrerequisiteError, + PERFORMANCE_MODES, + PRIMARY_TIME_METRICS, + LAZY_TO_GUEST_FINAL, + LAZY_TO_NATIVE_FINAL, + PREBOUND_NATIVE_FINAL, + analyze_ab_pairs, + assess_dual_aa, + assess_aa_pairs, + benchmark_environment, + benchmark_command, + classify_gate, + comparison_metrics_for_baseline_state, + activate_harness_cpu_isolation, + _preflight_address, + _checkpoint_targets, + _acquire_output_ownership, + _formal_stage_count, + _inconclusive_details, + _one_sided_quantile_bounds, + _order_statistic_interval, + _statistics_record, + parse_guest_record, + randomized_pair_orders, + run_performance_gate, + run_guest_mode, + runtime_environment_snapshot, + run_guest_sample, + validate_role_mode_record, + verify_guest_preserved_preflight, + verify_role_modes_preflight, + verify_native_apply_preflight, +) +from test_real_guest_performance import parse_args + + +def timed_sample(value): + sample = {metric: value for metric in PRIMARY_TIME_METRICS} + sample[GUEST_FIRST_BINDING_METRIC] = value + return sample + + +def ab_pairs(baseline_values, candidate_values): + return [ + { + "baseline": timed_sample(baseline), + "candidate": timed_sample(candidate), + "order": ["baseline", "candidate"], + } + for baseline, candidate in zip(baseline_values, candidate_values) + ] + + +def aa_pairs(values): + return [ + { + "a": timed_sample(value), + "b": timed_sample(value), + "order": ["a", "b"], + } + for value in values + ] + + +class GuestRecordTests(unittest.TestCase): + def test_parses_three_performance_modes_and_null_result_counts(self): + startup = ( + "KZT_GUEST_PERF_OK mode=startup steady_calls=0 slot=0 before=0 " + "after_first=0 after_steady=0 first_ns=0 steady_total_ns=0 " + "steady_per_call_ns=0 checksum=0\n" + ) + first = ( + "KZT_GUEST_PERF_OK mode=first steady_calls=0 slot=0x100 " + "before=0x200 after_first=0x300 after_steady=0x300 " + "first_ns=0x64 steady_total_ns=0 steady_per_call_ns=0 checksum=1\n" + ) + steady = ( + "KZT_GUEST_PERF_OK mode=steady steady_calls=100000 slot=0x100 " + "before=0x200 after_first=0x300 after_steady=0x300 " + "first_ns=0x64 steady_total_ns=0xf4240 steady_per_call_ns=0xa " + "checksum=100001\n" + ) + + self.assertEqual( + parse_guest_record(startup, 100000, expected_mode="startup")["mode"], + "startup", + ) + self.assertEqual( + parse_guest_record(first, 100000, expected_mode="first")["checksum"], + 1, + ) + self.assertEqual( + parse_guest_record(steady, 100000, expected_mode="steady")[ + "checksum" + ], + 100001, + ) + + def test_parses_performance_record(self): + output = ( + "KZT_GUEST_PERF_OK steady_calls=10000 slot=0x100 before=0x200 " + "after_first=0x300 after_steady=0x300 first_ns=0x64 " + "steady_total_ns=0x186a0 steady_per_call_ns=0xa checksum=0x1\n" + ) + + record = parse_guest_record(output, expected_steady_calls=10000) + + self.assertEqual(record["first_binding_ns"], 100) + self.assertEqual(record["steady_total_ns"], 100000) + self.assertEqual(record["steady_per_call_ns"], 10) + self.assertEqual(record["steady_calls"], 10000) + + def test_rejects_duplicate_records(self): + line = ( + "KZT_GUEST_PERF_OK steady_calls=10000 slot=0x100 before=0x200 " + "after_first=0x300 after_steady=0x300 first_ns=0x64 " + "steady_total_ns=0x186a0 steady_per_call_ns=0xa checksum=0x1\n" + ) + + with self.assertRaises(GuestCorrectnessError): + parse_guest_record(line + line, expected_steady_calls=10000) + + def test_rejects_changed_steady_slot(self): + output = ( + "KZT_GUEST_PERF_OK steady_calls=10000 slot=0x100 before=0x200 " + "after_first=0x300 after_steady=0x301 first_ns=0x64 " + "steady_total_ns=0x186a0 steady_per_call_ns=0xa checksum=0x1\n" + ) + + with self.assertRaises(GuestCorrectnessError): + parse_guest_record(output, expected_steady_calls=10000) + + +class PairOrderTests(unittest.TestCase): + def test_orders_are_seeded_random_and_balanced(self): + first = randomized_pair_orders(11, seed=1234, labels=("old", "new")) + second = randomized_pair_orders(11, seed=1234, labels=("old", "new")) + + self.assertEqual(first, second) + self.assertNotEqual(first, randomized_pair_orders( + 11, seed=1235, labels=("old", "new") + )) + self.assertTrue(all(set(order) == {"old", "new"} for order in first)) + old_first = sum(order[0] == "old" for order in first) + self.assertLessEqual(abs(old_first - (len(first) - old_first)), 1) + + +class HarnessCpuIsolationTests(unittest.TestCase): + def test_parses_kernel_cpu_lists_without_machine_specific_numbering(self): + self.assertEqual( + real_guest_harness._parse_cpu_list("4,12-14,37,55-56\n"), + [4, 12, 13, 14, 37, 55, 56], + ) + + def test_excludes_guest_thread_siblings_from_parent_harness(self): + with tempfile.TemporaryDirectory() as temporary_directory: + topology_root = Path(temporary_directory) + sibling_path = ( + topology_root / "cpu37" / "topology" / + "thread_siblings_list" + ) + sibling_path.parent.mkdir(parents=True) + sibling_path.write_text("12,37\n", encoding="ascii") + with mock.patch.object( + real_guest_harness, "CPU_SYSFS_ROOT", topology_root, + create=True), \ + mock.patch( + "real_guest_harness.os.sched_getaffinity", + side_effect=[{4, 12, 37, 55}, {4, 55}], + ), \ + mock.patch( + "real_guest_harness.os.sched_setaffinity" + ) as set_affinity: + isolation = activate_harness_cpu_isolation(True, 37) + + set_affinity.assert_called_once_with(0, {4, 55}) + self.assertEqual(isolation["guest_cpu"], 37) + self.assertEqual(isolation["thread_siblings"], [12, 37]) + self.assertEqual(isolation["initial_affinity"], [4, 12, 37, 55]) + self.assertEqual(isolation["active_affinity"], [4, 55]) + self.assertTrue(isolation["verification"]["passed"]) + self.assertTrue(isolation["applied"]) + + def test_missing_or_malformed_topology_never_falls_back_to_guest_only(self): + with tempfile.TemporaryDirectory() as temporary_directory: + topology_root = Path(temporary_directory) + sibling_path = ( + topology_root / "cpu37" / "topology" / + "thread_siblings_list" + ) + for value in (None, "", "12-4", "cpu12,37"): + with self.subTest(value=value): + if sibling_path.exists(): + sibling_path.unlink() + if value is not None: + sibling_path.parent.mkdir(parents=True, exist_ok=True) + sibling_path.write_text(value, encoding="ascii") + with mock.patch.object( + real_guest_harness, "CPU_SYSFS_ROOT", + topology_root), \ + mock.patch( + "real_guest_harness.os.sched_getaffinity", + return_value={4, 12, 37, 55}, + ), \ + mock.patch( + "real_guest_harness.os.sched_setaffinity" + ) as set_affinity: + isolation = activate_harness_cpu_isolation(True, 37) + + set_affinity.assert_not_called() + self.assertFalse(isolation["applied"]) + self.assertFalse(isolation["verification"]["passed"]) + self.assertIsNotNone(isolation["verification"]["error"]) + self.assertEqual( + isolation["topology_source"], str(sibling_path) + ) + + +class ModeContractTests(unittest.TestCase): + def config(self): + return HarnessConfig( + baseline_latx=Path("/baseline-latx"), + candidate_latx=Path("/candidate-latx"), + guest_root=Path("/guest-root"), + fixture_dir=Path("/fixture"), + cpu=6, + warmup=0, + samples=80, + max_samples=800, + aa_samples=50, + steady_calls=100000, + seed=7, + output_dir=Path("/output"), + ) + + def test_declares_three_endpoint_to_endpoint_modes_and_primary_metrics(self): + self.assertEqual(PERFORMANCE_MODES, ("startup", "first", "steady")) + self.assertEqual(PRIMARY_TIME_METRICS, ( + "startup_process_total_ns", + "launch_to_first_result_ns", + "steady_total_ns", + )) + self.assertEqual( + comparison_metrics_for_baseline_state(LAZY_TO_GUEST_FINAL), + GUEST_LAZY_COMPARISON_METRICS, + ) + self.assertNotIn( + "steady_total_ns", GUEST_LAZY_COMPARISON_METRICS + ) + + def test_mode_commands_preserve_role_and_steady_count(self): + config = self.config() + startup = benchmark_command(config, "baseline", "startup", "/taskset") + first = benchmark_command(config, "candidate", "first", "/taskset") + steady = benchmark_command(config, "candidate", "steady", "/taskset") + + self.assertEqual(startup[-1:], ["startup"]) + self.assertEqual(first[-1:], ["first"]) + self.assertEqual(steady[-2:], ["steady", "100000"]) + self.assertIn("/baseline-latx", startup) + self.assertIn("/candidate-latx", first) + + def test_role_binding_states_are_distinct_and_fail_closed(self): + eager = { + "before": 0x300, + "after_first": 0x300, + "after_steady": 0x300, + } + lazy = { + "before": 0x200, + "after_first": 0x300, + "after_steady": 0x300, + } + + self.assertEqual( + validate_role_mode_record("baseline", "first", eager), + "EAGER_FINAL", + ) + self.assertEqual( + validate_role_mode_record("candidate", "steady", lazy), + "LAZY_TO_NATIVE_FINAL", + ) + with self.assertRaisesRegex(GuestCorrectnessError, "EAGER_FINAL"): + validate_role_mode_record("baseline", "first", lazy) + self.assertEqual( + validate_role_mode_record("candidate", "steady", eager), + PREBOUND_NATIVE_FINAL, + ) + + def test_baseline_binding_state_can_explicitly_require_lazy(self): + eager = { + "before": 0x300, + "after_first": 0x300, + "after_steady": 0x300, + } + lazy = { + "before": 0x200, + "after_first": 0x300, + "after_steady": 0x300, + } + + self.assertEqual( + validate_role_mode_record( + "baseline", + "first", + lazy, + baseline_binding_state=LAZY_TO_NATIVE_FINAL, + ), + LAZY_TO_NATIVE_FINAL, + ) + with self.assertRaisesRegex( + GuestCorrectnessError, "LAZY_TO_NATIVE_FINAL"): + validate_role_mode_record( + "baseline", + "first", + eager, + baseline_binding_state=LAZY_TO_NATIVE_FINAL, + ) + + def test_baseline_can_require_guest_preserved_lazy_binding(self): + lazy = { + "before": 0x200, + "after_first": 0x300, + "after_steady": 0x300, + } + + self.assertEqual( + validate_role_mode_record( + "baseline", + "first", + lazy, + baseline_binding_state=LAZY_TO_GUEST_FINAL, + ), + LAZY_TO_GUEST_FINAL, + ) + + def test_candidate_ignores_baseline_binding_state(self): + eager = { + "before": 0x300, + "after_first": 0x300, + "after_steady": 0x300, + } + self.assertEqual( + validate_role_mode_record( + "candidate", + "first", + eager, + baseline_binding_state=LAZY_TO_NATIVE_FINAL, + ), + PREBOUND_NATIVE_FINAL, + ) + + def test_run_guest_mode_uses_configured_baseline_binding_state_only(self): + lazy_output = ( + "KZT_GUEST_PERF_OK mode=first steady_calls=0 slot=0x100 " + "before=0x200 after_first=0x300 after_steady=0x300 " + "first_ns=0x10 steady_total_ns=0 steady_per_call_ns=0 " + "checksum=1\n" + ) + eager_output = lazy_output.replace( + "before=0x200", "before=0x300" + ) + config = HarnessConfig( + **{**self.config().__dict__, + "baseline_binding_state": LAZY_TO_NATIVE_FINAL} + ) + execution = { + "returncode": 0, + "timed_out": False, + "process_total_ns": 1, + "rusage": None, + } + with mock.patch("real_guest_harness.execute_with_rusage", + return_value={**execution, "output": lazy_output}): + baseline = run_guest_mode(config, "baseline", "first", "/taskset") + self.assertEqual(baseline["binding_state"], LAZY_TO_NATIVE_FINAL) + with mock.patch("real_guest_harness.execute_with_rusage", + return_value={**execution, "output": eager_output}): + candidate = run_guest_mode(config, "candidate", "first", "/taskset") + self.assertEqual(candidate["binding_state"], PREBOUND_NATIVE_FINAL) + + def test_sample_combines_three_modes_without_using_diagnostic_metrics(self): + outputs = ( + "KZT_GUEST_PERF_OK mode=startup steady_calls=0 slot=0 before=0 " + "after_first=0 after_steady=0 first_ns=0 steady_total_ns=0 " + "steady_per_call_ns=0 checksum=0\n", + "KZT_GUEST_PERF_OK mode=first steady_calls=0 slot=0x100 " + "before=0x300 after_first=0x300 after_steady=0x300 " + "first_ns=0x10 steady_total_ns=0 steady_per_call_ns=0 checksum=1\n", + "KZT_GUEST_PERF_OK mode=steady steady_calls=100000 slot=0x100 " + "before=0x300 after_first=0x300 after_steady=0x300 " + "first_ns=0x10 steady_total_ns=0xf4240 steady_per_call_ns=0xa " + "checksum=100001\n", + ) + executions = [ + {"returncode": 0, "timed_out": False, "output": output, + "process_total_ns": process_total, "rusage": None} + for output, process_total in zip(outputs, (11, 22, 33)) + ] + with mock.patch("real_guest_harness.execute_with_rusage", + side_effect=executions): + sample = run_guest_sample(self.config(), "baseline", "/taskset") + + self.assertEqual(sample["startup_process_total_ns"], 11) + self.assertEqual(sample["launch_to_first_result_ns"], 22) + self.assertEqual(sample["steady_total_ns"], 0xf4240) + self.assertEqual(sample["binding_state"], "EAGER_FINAL") + self.assertNotIn("steady_per_call_ns", PRIMARY_TIME_METRICS) + + def test_role_preflight_runs_all_three_modes(self): + outputs = ( + "KZT_GUEST_PERF_OK mode=startup steady_calls=0 slot=0 before=0 " + "after_first=0 after_steady=0 first_ns=0 steady_total_ns=0 " + "steady_per_call_ns=0 checksum=0\n", + "KZT_GUEST_PERF_OK mode=first steady_calls=0 slot=0x100 " + "before=0x300 after_first=0x300 after_steady=0x300 " + "first_ns=0x10 steady_total_ns=0 steady_per_call_ns=0 checksum=1\n", + "KZT_GUEST_PERF_OK mode=steady steady_calls=100000 slot=0x100 " + "before=0x300 after_first=0x300 after_steady=0x300 " + "first_ns=0x10 steady_total_ns=0xf4240 steady_per_call_ns=0xa " + "checksum=100001\n", + ) + executions = [ + {"returncode": 0, "timed_out": False, "output": output, + "process_total_ns": 1, "rusage": None} + for output in outputs + ] + with mock.patch("real_guest_harness.execute_with_rusage", + side_effect=executions): + result = verify_role_modes_preflight( + self.config(), "baseline", "/taskset" + ) + + self.assertEqual(tuple(result), ("startup", "first", "steady")) + self.assertEqual(result["steady"]["binding_state"], "EAGER_FINAL") + + +class EnvironmentTests(unittest.TestCase): + def config(self, baseline_binding_state=EAGER_FINAL): + return HarnessConfig( + baseline_latx=Path("/baseline-latx"), + candidate_latx=Path("/candidate-latx"), + guest_root=Path("/guest-root"), + fixture_dir=Path("/fixture"), + cpu=6, + warmup=0, + samples=80, + max_samples=80, + aa_samples=50, + steady_calls=100000, + seed=7, + output_dir=Path("/output"), + baseline_binding_state=baseline_binding_state, + ) + + def test_forces_legacy_kzt_and_only_enables_writer_for_candidate(self): + inherited = { + "LAT_LOG": "exec", + "LATX_AOT": "1", + "LATX_KZT": "0", + "LATX_KZT_LAZY_DIAGNOSTICS": "1", + "LATX_KZT_PATCH_SPIKE": "9", + } + with mock.patch.dict(os.environ, inherited, clear=False): + baseline = benchmark_environment( + self.config(), "baseline", "/tmp/fixture" + ) + candidate = benchmark_environment( + self.config(), "candidate", "/tmp/fixture" + ) + + self.assertEqual(baseline["LATX_KZT"], "2") + self.assertEqual(baseline["LATX_KZT_LAZY_DIAGNOSTICS"], "0") + self.assertEqual(baseline["LATX_KZT_REGISTRY_DIAGNOSTICS"], "0") + self.assertEqual(baseline["LATX_AOT"], "0") + self.assertNotIn("LAT_LOG", baseline) + self.assertNotIn("LATX_KZT_PATCH_SPIKE", baseline) + self.assertEqual(baseline["LD_LIBRARY_PATH"], "/tmp/fixture") + self.assertEqual(candidate["LATX_KZT"], "2") + self.assertEqual(candidate["LD_LIBRARY_PATH"], "/tmp/fixture") + self.assertEqual(candidate["LATX_KZT_PATCH_SPIKE"], "1") + self.assertEqual(candidate["LATX_KZT_PATCH_SPIKE_WRITE"], "1") + self.assertEqual(candidate["LATX_KZT_PATCH_SPIKE_BUDGET"], "1") + self.assertEqual(candidate["LATX_AOT"], "0") + + def test_lazy_baseline_uses_candidate_profile_but_baseline_binary(self): + config = self.config(LAZY_TO_NATIVE_FINAL) + baseline = benchmark_environment(config, "baseline", "/tmp/fixture") + candidate = benchmark_environment(config, "candidate", "/tmp/fixture") + command = benchmark_command(config, "baseline", "first", "/taskset") + + self.assertEqual(baseline["LATX_KZT_PATCH_SPIKE"], "1") + self.assertEqual(baseline["LATX_KZT_PATCH_SPIKE_WRITE"], "1") + self.assertEqual(baseline["LATX_KZT_PATCH_SPIKE_BUDGET"], "1") + self.assertEqual( + runtime_environment_snapshot(config, "baseline"), + runtime_environment_snapshot(config, "candidate"), + ) + self.assertIn("/baseline-latx", command) + self.assertNotIn("/candidate-latx", command) + + def test_guest_lazy_baseline_keeps_writer_disabled(self): + config = self.config(LAZY_TO_GUEST_FINAL) + baseline = benchmark_environment(config, "baseline", "/tmp/fixture") + candidate = benchmark_environment(config, "candidate", "/tmp/fixture") + + self.assertNotIn("LATX_KZT_PATCH_SPIKE", baseline) + self.assertNotIn("LATX_KZT_PATCH_SPIKE_WRITE", baseline) + self.assertEqual(candidate["LATX_KZT_PATCH_SPIKE"], "1") + self.assertEqual(candidate["LATX_KZT_PATCH_SPIKE_WRITE"], "1") + + def test_environment_profile_rejects_invalid_baseline_binding_state(self): + with self.assertRaisesRegex(ValueError, "baseline binding state"): + real_guest_harness.benchmark_environment_profile( + "baseline", "invalid" + ) + + +class NativeApplyPreflightTests(unittest.TestCase): + def config(self): + return HarnessConfig( + baseline_latx=Path("/baseline-latx"), + candidate_latx=Path("/candidate-latx"), + guest_root=Path("/guest-root"), + fixture_dir=Path("/fixture"), + cpu=6, + warmup=0, + samples=50, + max_samples=400, + aa_samples=50, + steady_calls=10000, + seed=7, + output_dir=Path("/output"), + ) + + def native_apply_output(self, *, before="0x200", after_first="0x300", + after_steady="0x300", lazy_target="0x300", + bridge_target="0x300"): + return ( + "kzt_lazy_diagnostic schema=1 symbol=dlerror " + "completion_route_status=NATIVE_APPLIED " + f"selected_second_target={lazy_target}\n" + "kzt_rela_diagnostic symbol=dlerror decision=APPROVED " + "writer_result=APPLIED legacy_fallback=0 " + f"bridge_target={bridge_target}\n" + "KZT_GUEST_PERF_OK mode=first steady_calls=0 slot=0x100 " + f"before={before} " + f"after_first={after_first} after_steady={after_steady} " + "first_ns=0x64 steady_total_ns=0 steady_per_call_ns=0 " + "checksum=0x1\n" + ) + + def direct_apply_output(self, *, before="0x200", after_first="0x300", + after_steady="0x300", slot_before="0x200", + slot_after="0x300", selected_target="0x300", + writer_result="APPLIED"): + return ( + "kzt_lazy_direct schema=1 symbol=dlerror " + "route_status=NATIVE_APPLIED " + f"writer_result={writer_result} " + f"slot_before={slot_before} slot_after={slot_after} " + f"selected_target={selected_target}\n" + "KZT_GUEST_PERF_OK mode=first steady_calls=0 slot=0x100 " + f"before={before} " + f"after_first={after_first} after_steady={after_steady} " + "first_ns=0x64 steady_total_ns=0 steady_per_call_ns=0 " + "checksum=0x1\n" + ) + + def test_accepts_only_applied_native_lazy_route(self): + execution = { + "returncode": 0, + "timed_out": False, + "output": self.native_apply_output(), + } + with mock.patch("real_guest_harness.execute_with_rusage", + return_value=execution): + result = verify_native_apply_preflight( + self.config(), "/usr/bin/taskset" + ) + + self.assertEqual(result["lazy"]["completion_route_status"], + "NATIVE_APPLIED") + self.assertEqual(result["rela"]["writer_result"], "APPLIED") + self.assertEqual(result["guest"]["after_first"], 0x300) + + def test_accepts_evidence_backed_direct_native_route(self): + execution = { + "returncode": 0, + "timed_out": False, + "output": self.direct_apply_output(), + } + with mock.patch("real_guest_harness.execute_with_rusage", + return_value=execution): + result = verify_native_apply_preflight( + self.config(), "/usr/bin/taskset" + ) + + self.assertEqual(result["path"], "direct") + self.assertEqual(result["direct"]["writer_result"], "APPLIED") + self.assertEqual(result["guest"]["after_first"], 0x300) + + def test_rejects_mixed_guest_first_and_direct_records(self): + execution = { + "returncode": 0, + "timed_out": False, + "output": self.native_apply_output() + self.direct_apply_output(), + } + with mock.patch("real_guest_harness.execute_with_rusage", + return_value=execution): + with self.assertRaisesRegex( + GuestCorrectnessError, "exactly one native route"): + verify_native_apply_preflight( + self.config(), "/usr/bin/taskset" + ) + + def test_rejects_direct_route_with_inconsistent_slot_evidence(self): + for field in ("slot_before", "slot_after", "selected_target"): + with self.subTest(field=field): + value = "0x201" if field == "slot_before" else "0x301" + execution = { + "returncode": 0, + "timed_out": False, + "output": self.direct_apply_output(**{field: value}), + } + with mock.patch("real_guest_harness.execute_with_rusage", + return_value=execution): + with self.assertRaisesRegex( + GuestCorrectnessError, "slot evidence mismatch"): + verify_native_apply_preflight( + self.config(), "/usr/bin/taskset" + ) + + def test_rejects_direct_route_without_applied_cas(self): + execution = { + "returncode": 0, + "timed_out": False, + "output": self.direct_apply_output(writer_result="CONFLICT"), + } + with mock.patch("real_guest_harness.execute_with_rusage", + return_value=execution): + with self.assertRaisesRegex( + GuestCorrectnessError, "did not apply"): + verify_native_apply_preflight( + self.config(), "/usr/bin/taskset" + ) + + def test_lazy_baseline_preflight_uses_baseline_binary_and_writer_profile(self): + config = HarnessConfig( + **{**self.config().__dict__, + "baseline_binding_state": LAZY_TO_NATIVE_FINAL} + ) + execution = { + "returncode": 0, + "timed_out": False, + "output": self.native_apply_output(), + } + with mock.patch("real_guest_harness.execute_with_rusage", + return_value=execution) as execute: + result = verify_native_apply_preflight( + config, "/usr/bin/taskset", role="baseline" + ) + + command, environment, _ = execute.call_args.args + self.assertEqual(result["role"], "baseline") + self.assertIn("/baseline-latx", command) + self.assertNotIn("/candidate-latx", command) + self.assertEqual(environment["LATX_KZT_PATCH_SPIKE"], "1") + self.assertEqual(environment["LATX_KZT_PATCH_SPIKE_WRITE"], "1") + self.assertEqual(environment["LATX_KZT_LAZY_DIAGNOSTICS"], "1") + self.assertEqual(environment["LATX_KZT_REGISTRY_DIAGNOSTICS"], "1") + + def test_rejects_applied_labels_with_mismatched_slot_evidence(self): + for field in ("after_first", "after_steady"): + with self.subTest(field=field): + output = self.native_apply_output(**{field: "0x400"}) + execution = { + "returncode": 0, + "timed_out": False, + "output": output, + } + with mock.patch("real_guest_harness.execute_with_rusage", + return_value=execution): + with self.assertRaisesRegex( + GuestCorrectnessError, "slot evidence"): + verify_native_apply_preflight( + self.config(), "/usr/bin/taskset" + ) + + def test_rejects_stable_guest_slot_that_differs_from_bridges(self): + execution = { + "returncode": 0, + "timed_out": False, + "output": self.native_apply_output( + after_first="0x400", after_steady="0x400" + ), + } + with mock.patch("real_guest_harness.execute_with_rusage", + return_value=execution): + with self.assertRaisesRegex( + GuestCorrectnessError, "slot evidence mismatch"): + verify_native_apply_preflight( + self.config(), "/usr/bin/taskset" + ) + + def test_rejects_mismatched_lazy_or_rela_bridge_target(self): + for field in ("lazy_target", "bridge_target"): + with self.subTest(field=field): + execution = { + "returncode": 0, + "timed_out": False, + "output": self.native_apply_output(**{field: "0x301"}), + } + with mock.patch("real_guest_harness.execute_with_rusage", + return_value=execution): + with self.assertRaisesRegex( + GuestCorrectnessError, "slot evidence mismatch"): + verify_native_apply_preflight( + self.config(), "/usr/bin/taskset" + ) + + def test_rejects_zero_or_negative_preflight_address(self): + for value in ("0", "-1"): + with self.subTest(value=value): + with self.assertRaises(GuestCorrectnessError): + _preflight_address( + {"bridge_target": value}, "rela", "bridge_target", + {"output": "diagnostic"}, + ) + + def test_rejects_applied_labels_without_lazy_slot_update(self): + execution = { + "returncode": 0, + "timed_out": False, + "output": self.native_apply_output(before="0x300"), + } + with mock.patch("real_guest_harness.execute_with_rusage", + return_value=execution): + with self.assertRaisesRegex( + GuestCorrectnessError, "before=0x300 after_first=0x300"): + verify_native_apply_preflight( + self.config(), "/usr/bin/taskset" + ) + + def test_rejects_guest_preserved_lazy_route(self): + output = ( + "kzt_lazy_diagnostic schema=1 symbol=dlerror " + "completion_route_status=GUEST_PRESERVED\n" + "kzt_rela_diagnostic symbol=dlerror decision=APPROVED " + "writer_result=APPLIED legacy_fallback=0\n" + ) + execution = { + "returncode": 0, + "timed_out": False, + "output": output, + } + with mock.patch("real_guest_harness.execute_with_rusage", + return_value=execution): + with self.assertRaises(GuestCorrectnessError): + verify_native_apply_preflight(self.config(), "/usr/bin/taskset") + + +class GuestPreservedPreflightTests(unittest.TestCase): + def config(self): + return HarnessConfig( + baseline_latx=Path("/baseline-latx"), + candidate_latx=Path("/candidate-latx"), + guest_root=Path("/guest-root"), + fixture_dir=Path("/fixture"), + cpu=6, + warmup=0, + samples=80, + max_samples=80, + aa_samples=50, + steady_calls=100000, + seed=7, + output_dir=Path("/output"), + baseline_binding_state=LAZY_TO_GUEST_FINAL, + ) + + def guest_preserved_output(self, writer_result="DISABLED", + lazy_target="0x300", after_first="0x300", + after_steady="0x300"): + return ( + "kzt_lazy_diagnostic schema=1 symbol=dlerror " + "completion_route_status=GUEST_PRESERVED " + f"slot_after_guest={lazy_target} " + f"selected_second_target={lazy_target}\n" + "kzt_rela_diagnostic symbol=dlerror decision=APPROVED " + f"writer_result={writer_result} legacy_fallback=0 " + "bridge_target=0x400\n" + "KZT_GUEST_PERF_OK mode=first steady_calls=0 slot=0x100 " + "before=0x200 " + f"after_first={after_first} after_steady={after_steady} " + "first_ns=0x64 steady_total_ns=0 steady_per_call_ns=0 " + "checksum=0x1\n" + ) + + def test_accepts_guest_preserved_lazy_baseline_with_writer_disabled(self): + execution = { + "returncode": 0, + "timed_out": False, + "output": self.guest_preserved_output(), + } + with mock.patch("real_guest_harness.execute_with_rusage", + return_value=execution) as execute: + result = verify_guest_preserved_preflight( + self.config(), "/usr/bin/taskset" + ) + + command, environment, _ = execute.call_args.args + self.assertEqual(result["lazy"]["completion_route_status"], + "GUEST_PRESERVED") + self.assertEqual(result["rela"]["writer_result"], "DISABLED") + self.assertEqual(result["guest"]["after_first"], 0x300) + self.assertIn("/baseline-latx", command) + self.assertNotIn("LATX_KZT_PATCH_SPIKE_WRITE", environment) + + def test_rejects_guest_baseline_when_writer_applied(self): + execution = { + "returncode": 0, + "timed_out": False, + "output": self.guest_preserved_output(writer_result="APPLIED"), + } + with mock.patch("real_guest_harness.execute_with_rusage", + return_value=execution): + with self.assertRaisesRegex( + GuestCorrectnessError, "Guest-preserved"): + verify_guest_preserved_preflight( + self.config(), "/usr/bin/taskset" + ) + + +class OrderStatisticTests(unittest.TestCase): + def test_guest_lazy_analysis_excludes_semantically_different_steady_state( + self): + analysis = analyze_ab_pairs( + ab_pairs([100] * 80, [100] * 80), + seed=7, + formal_stage_count=0, + analysis_look_count=1, + metrics=GUEST_LAZY_COMPARISON_METRICS, + ) + + self.assertEqual( + tuple(analysis["metrics"]), GUEST_LAZY_COMPARISON_METRICS + ) + self.assertEqual(analysis["comparison_count"], 4) + + def test_bonferroni_interval_has_99_percent_family_confidence(self): + interval = _order_statistic_interval( + [math.log(1.2)] * 200, + quantile=0.5, + alpha=0.01 / 18, + confidence=1 - 0.01 / 18, + method="exact binomial median order-statistic interval", + ) + + self.assertGreater(interval["lower"], 0.0) + self.assertAlmostEqual(interval["confidence"], 1 - 0.01 / 18) + self.assertNotEqual(interval["confidence"], 0.98) + + def test_one_sided_quantile_bound_scales_to_sixteen_hundred_samples(self): + bounds = _one_sided_quantile_bounds( + list(range(1600)), quantile=0.95, alpha=0.001, + log_scale=False, + ) + + self.assertIsNotNone(bounds["lower"]) + self.assertIsNotNone(bounds["upper"]) + self.assertLess(bounds["lower"], bounds["upper"]) + + def test_point_estimate_uses_the_requested_quantile(self): + interval = _order_statistic_interval( + [1, 2, 3, 4, 5], + quantile=0.95, + alpha=0.1, + confidence=0.9, + method="test", + ) + + self.assertEqual(interval["estimate"], 4.8) + + +class StabilityTests(unittest.TestCase): + def test_wide_aa_interval_is_inconclusive_not_stable(self): + pairs = [ + { + "a": timed_sample(100), + "b": timed_sample(value), + "order": ["a", "b"], + } + for value in [99.7, 101.5] * 100 + ] + + assessment = assess_aa_pairs(pairs, seed=19) + + self.assertEqual(assessment["result"], AAResult.INCONCLUSIVE.value) + self.assertFalse(assessment["stable"]) + self.assertEqual(assessment["family_confidence"], 0.99) + self.assertEqual(assessment["comparison_count"], 18) + self.assertAlmostEqual( + assessment["per_interval_confidence"], 1 - 0.01 / 18 + ) + + def test_label_stability_uses_each_pair_log_ratio(self): + pairs = [] + for a, b in ((100, 100), (200, 400), (300, 150)) * 2: + pairs.append({ + "a": timed_sample(a), + "b": timed_sample(b), + "order": ["a", "b"], + }) + + assessment = assess_aa_pairs(pairs, seed=19) + + self.assertAlmostEqual( + assessment["metrics"]["steady_total_ns"]["label"]["estimate"], + 0.0, + ) + + def test_accepts_stable_aa_pairs(self): + assessment = assess_aa_pairs( + aa_pairs([100] * 200), seed=19 + ) + + self.assertTrue(assessment["stable"]) + self.assertEqual(assessment["result"], AAResult.STABLE.value) + self.assertEqual(assessment["reasons"], []) + + def test_accepts_aa_control_difference_at_half_percent_limit(self): + pairs = [ + {"a": timed_sample(100), "b": timed_sample(100.5), + "order": ["a", "b"]} + for _ in range(200) + ] + + assessment = assess_aa_pairs(pairs, seed=19) + + self.assertTrue(assessment["stable"]) + + def test_fifty_pair_aa_screen_cannot_claim_stable(self): + assessment = assess_aa_pairs( + aa_pairs([100] * 50), seed=19 + ) + + self.assertEqual(assessment["mode"], "screening") + self.assertEqual(assessment["result"], AAResult.INCONCLUSIVE.value) + self.assertFalse(assessment["stable"]) + + def test_rejects_aa_control_difference_over_one_percent(self): + pairs = [ + {"a": timed_sample(100), "b": timed_sample(102), + "order": ["a", "b"]} + for _ in range(200) + ] + + assessment = assess_aa_pairs(pairs, seed=19) + + self.assertFalse(assessment["stable"]) + + def test_rejects_temporal_aa_drift(self): + assessment = assess_aa_pairs( + aa_pairs([100] * 100 + [140] * 100), + seed=19, + ) + + self.assertFalse(assessment["stable"]) + self.assertTrue(any("temporal" in reason for reason in assessment["reasons"])) + + def test_requires_both_baseline_and_candidate_aa_stability(self): + assessment = assess_dual_aa( + aa_pairs([100] * 200), + aa_pairs([100] * 100 + [140] * 100), + seed=23, + ) + + self.assertTrue(assessment["baseline"]["stable"]) + self.assertFalse(assessment["candidate"]["stable"]) + self.assertFalse(assessment["stable"]) + + +class GateDecisionTests(unittest.TestCase): + def analyze(self, baseline_values, candidate_values, *, stages=1): + return analyze_ab_pairs( + ab_pairs(baseline_values, candidate_values), + seed=31, + formal_stage_count=stages, + ) + + def test_pass_requires_at_least_400_pairs(self): + analysis = self.analyze([100] * 400, [100] * 400) + + self.assertEqual( + classify_gate(analysis, pair_count=200, formal_aa_stable=True), GateResult.INCONCLUSIVE + ) + self.assertEqual( + classify_gate(analysis, pair_count=400, formal_aa_stable=True), GateResult.PASS + ) + + def test_screening_aa_cannot_formally_pass_ab(self): + analysis = self.analyze([100] * 400, [100] * 400) + + self.assertEqual( + classify_gate( + analysis, pair_count=400, formal_aa_stable=False + ), + GateResult.INCONCLUSIVE, + ) + + def test_analysis_uses_per_pair_log_ratios(self): + analysis = self.analyze([100, 100], [50, 200]) + estimate = analysis["metrics"]["startup_process_total_ns"][ + "paired_log_ratio" + ]["median"]["estimate"] + + self.assertAlmostEqual(estimate, 0.0) + + def test_paired_log_ratio_contains_only_the_median(self): + analysis = self.analyze([100] * 400, [100] * 400) + metric = analysis["metrics"]["startup_process_total_ns"] + + self.assertEqual(set(metric["paired_log_ratio"]), {"median"}) + self.assertIn("paired_index_bootstrap_p95_log_ratio", metric) + self.assertNotIn("paired_p95_log_ratio", metric) + + def test_p95_resamples_pair_indices_then_compares_marginal_tails(self): + analysis = self.analyze( + [100, 130] * 200, + [130, 100] * 200, + ) + interval = analysis["metrics"]["startup_process_total_ns"][ + "paired_index_bootstrap_p95_log_ratio" + ]["p95"] + + self.assertEqual( + analysis["metrics"]["startup_process_total_ns"]["baseline"]["p95"], + analysis["metrics"]["startup_process_total_ns"]["candidate"]["p95"], + ) + self.assertAlmostEqual(interval["estimate"], 0.0) + self.assertAlmostEqual(interval["ratio"], 1.0) + + def test_p95_paired_index_bounds_accept_nanosecond_scale_samples(self): + analysis = self.analyze([30_000_000] * 400, [30_000_000] * 400) + interval = analysis["metrics"]["startup_process_total_ns"][ + "paired_index_bootstrap_p95_log_ratio" + ]["p95"] + + self.assertAlmostEqual(interval["ratio"], 1.0) + + def test_ab_uses_six_simultaneous_one_sided_bounds(self): + analysis = self.analyze([100] * 400, [100] * 400) + interval = analysis["metrics"]["startup_process_total_ns"][ + "paired_log_ratio" + ]["median"] + p95 = analysis["metrics"]["startup_process_total_ns"][ + "paired_index_bootstrap_p95_log_ratio" + ]["p95"] + + self.assertEqual(analysis["comparison_count"], 6) + self.assertEqual(analysis["pass_upper_family_confidence"], 0.99) + self.assertEqual(analysis["fail_lower_family_confidence"], 0.99) + self.assertAlmostEqual(analysis["per_ratio_bound_alpha"], 0.01 / 6) + self.assertAlmostEqual( + interval["one_sided_confidence"], 1 - 0.01 / 6 + ) + self.assertEqual( + p95["method"], + "paired-index percentile bootstrap P95 log-ratio bounds", + ) + self.assertEqual(p95["bootstrap_resamples"], 20000) + self.assertEqual(p95["bootstrap_seed"], 31) + self.assertEqual(p95["resampling_unit"], "paired sample index") + self.assertEqual(interval["method"], "exact binomial quantile order-statistic bounds") + self.assertIn("lower", interval) + self.assertIn("upper", interval) + + def test_eight_hundred_pair_plan_allocates_across_two_formal_stages(self): + analysis = self.analyze([100] * 400, [100] * 400, stages=2) + p95 = analysis["metrics"]["startup_process_total_ns"][ + "paired_index_bootstrap_p95_log_ratio" + ]["p95"] + + self.assertEqual(analysis["formal_stage_count"], 2) + self.assertAlmostEqual(analysis["per_ratio_bound_alpha"], 0.01 / 12) + self.assertEqual( + analysis["pass_upper_family_confidence"], 0.99 + ) + self.assertEqual( + analysis["fail_lower_family_confidence"], 0.99 + ) + + def test_one_percent_median_noninferiority_threshold_is_accepted(self): + analysis = self.analyze([100] * 400, [100.9] * 400) + + self.assertLess(math.log(1.009), math.log(1.01)) + self.assertEqual( + classify_gate(analysis, pair_count=400, formal_aa_stable=True), GateResult.PASS + ) + + def test_fail_requires_at_least_400_pairs(self): + analysis = self.analyze([100] * 400, [120] * 400) + + self.assertEqual( + classify_gate(analysis, pair_count=399, formal_aa_stable=True), GateResult.INCONCLUSIVE + ) + self.assertEqual( + classify_gate(analysis, pair_count=400, formal_aa_stable=True), GateResult.FAIL + ) + + def test_clear_median_slowdown_fails_despite_better_raw_p95(self): + analysis = self.analyze( + [100] * 360 + [200] * 40, + [120] * 400, + ) + + self.assertLess( + analysis["metrics"]["startup_process_total_ns"]["candidate"]["p95"], + analysis["metrics"]["startup_process_total_ns"]["baseline"]["p95"], + ) + self.assertEqual( + classify_gate(analysis, pair_count=400, formal_aa_stable=True), GateResult.FAIL + ) + + def test_clear_p95_slowdown_fails_despite_better_median(self): + analysis = self.analyze( + [100] * 400, + [90] * 360 + [150] * 40, + ) + + self.assertLess( + analysis["metrics"]["startup_process_total_ns"]["candidate"]["median"], + analysis["metrics"]["startup_process_total_ns"]["baseline"]["median"], + ) + self.assertEqual( + classify_gate(analysis, pair_count=400, formal_aa_stable=True), GateResult.FAIL + ) + + def test_ambiguous_result_remains_inconclusive_at_800_pairs(self): + analysis = self.analyze( + [100] * 800, + [103] * 40 + [99] * 760, + ) + + self.assertEqual( + classify_gate(analysis, pair_count=800, formal_aa_stable=True), GateResult.INCONCLUSIVE + ) + + def test_inconclusive_reason_names_separate_decision_families(self): + analysis = self.analyze( + [100] * 800, + [103] * 40 + [99] * 760, + stages=2, + ) + + self.assertIn( + "Separate 99% PASS-upper and FAIL-lower decision-family bounds", + _inconclusive_details(analysis), + ) + + def test_direction_and_formal_checkpoints_are_80_400_800_1600(self): + self.assertEqual(_checkpoint_targets(80, 1600), [80, 400, 800, 1600]) + + def test_formal_stage_count_uses_actual_formal_checkpoints(self): + self.assertEqual(_formal_stage_count(80, 80), 0) + self.assertEqual(_formal_stage_count(80, 400), 1) + self.assertEqual(_formal_stage_count(400, 400), 1) + self.assertEqual(_formal_stage_count(80, 800), 2) + self.assertEqual(_formal_stage_count(400, 800), 2) + self.assertEqual(_formal_stage_count(800, 800), 1) + self.assertEqual(_formal_stage_count(80, 1600), 3) + + def test_guest_first_binding_is_a_median_only_noninferiority_gate(self): + pairs = [] + for _ in range(400): + baseline = timed_sample(100) + candidate = timed_sample(100) + baseline[GUEST_FIRST_BINDING_METRIC] = 100 + candidate[GUEST_FIRST_BINDING_METRIC] = 99 + pairs.append({ + "baseline": baseline, + "candidate": candidate, + "order": ["baseline", "candidate"], + }) + analysis = analyze_ab_pairs( + pairs, + seed=31, + metrics=(*PRIMARY_TIME_METRICS, GUEST_FIRST_BINDING_METRIC), + tail_metrics=PRIMARY_TIME_METRICS, + ) + guest_first = analysis["metrics"][GUEST_FIRST_BINDING_METRIC] + + self.assertEqual(guest_first["required_statistics"], ("median",)) + self.assertNotIn("paired_index_bootstrap_p95_log_ratio", guest_first) + self.assertEqual( + classify_gate(analysis, pair_count=400, formal_aa_stable=True), + GateResult.PASS, + ) + + def test_gate_requires_explicit_formal_aa_stability(self): + analysis = self.analyze([100] * 400, [100] * 400) + + with self.assertRaises(TypeError): + classify_gate(analysis, pair_count=400) + + +class StatisticsReportTests(unittest.TestCase): + def test_report_states_formal_simultaneous_inference_contract(self): + statistics_record = _statistics_record() + + self.assertEqual(statistics_record["aa"]["comparison_count"], 18) + self.assertEqual(statistics_record["aa"]["family_confidence"], 0.99) + self.assertEqual(statistics_record["aa"]["formal_min_pairs_per_role"], 200) + self.assertEqual(statistics_record["aa"]["stability_percent_limit"], 0.5) + self.assertEqual( + statistics_record["aa"]["temporal_independence_assumption"], + "time-ordered early/late paired contrasts are independent across pair indices", + ) + self.assertEqual(statistics_record["ab"]["comparison_count"], 6) + self.assertAlmostEqual( + statistics_record["ab"]["per_ratio_bound_alpha"], 0.01 / 12 + ) + self.assertEqual(statistics_record["ab"]["min_formal_pairs"], 400) + self.assertEqual(statistics_record["ab"]["max_formal_pairs"], 800) + self.assertEqual( + statistics_record["method"], + "paired log-ratio median exact-binomial bounds and paired-index " + "bootstrap marginal P95-ratio bounds", + ) + + def test_eight_hundred_pair_report_declares_separate_decision_families(self): + statistics_record = _statistics_record(max_samples=800) + ab = statistics_record["ab"] + + self.assertEqual(ab["formal_stage_count"], 2) + self.assertAlmostEqual(ab["per_ratio_bound_alpha"], 0.01 / 12) + self.assertEqual(ab["pass_upper_family"]["confidence"], 0.99) + self.assertEqual(ab["fail_lower_family"]["confidence"], 0.99) + self.assertNotIn("all_twelve_bounds_confidence", ab) + + def test_directional_eighty_pair_report_has_no_formal_stage(self): + ab = _statistics_record(max_samples=80)["ab"] + self.assertEqual(ab["formal_stage_count"], 0) + self.assertEqual(ab["analysis_look_count"], 1) + self.assertAlmostEqual(ab["per_ratio_bound_alpha"], 0.01 / 6) + + def test_report_describes_paired_index_p95_and_family_adjustment(self): + method = _statistics_record()["ab"]["p95_method"] + + self.assertIn("paired-index", method) + self.assertIn("P95(candidate) / P95(baseline)", method) + self.assertIn("family-and-look-adjusted", method) + self.assertEqual( + _statistics_record()["ab"]["p95_bootstrap_resamples"], 20000 + ) + + def test_non_median_quantile_estimate_is_explicitly_interpolated(self): + bounds = real_guest_harness._one_sided_quantile_bounds( + [1, 2, 3, 4, 5], quantile=0.95, alpha=0.1 + ) + self.assertEqual(bounds["estimate_method"], + "linear interpolated sample quantile") + self.assertAlmostEqual(bounds["estimate"], 4.8) + + +class CommandLineContractTests(unittest.TestCase): + def arguments(self, *extra): + return [ + "test_real_guest_performance.py", + "--baseline-latx", "/baseline", + "--candidate-latx", "/candidate", + "--guest-root", "/guest", + "--fixture-dir", "/fixture", + "--cpu", "0", + "--output-dir", "/output", + *extra, + ] + + def test_formal_aa_default_is_two_hundred_pairs(self): + with mock.patch.object(sys, "argv", self.arguments()): + self.assertEqual(parse_args().aa_samples, 200) + + def test_aa_only_flag_requests_independent_aa(self): + with mock.patch.object( + sys, "argv", self.arguments("--aa-only")): + self.assertTrue(parse_args().aa_only) + + def test_baseline_binding_state_defaults_to_eager_and_accepts_lazy(self): + with mock.patch.object(sys, "argv", self.arguments()): + self.assertEqual(parse_args().baseline_binding_state, EAGER_FINAL) + with mock.patch.object(sys, "argv", self.arguments( + "--baseline-binding-state", LAZY_TO_GUEST_FINAL)): + self.assertEqual(parse_args().baseline_binding_state, + LAZY_TO_GUEST_FINAL) + with mock.patch.object(sys, "argv", self.arguments( + "--baseline-binding-state", LAZY_TO_NATIVE_FINAL)): + self.assertEqual(parse_args().baseline_binding_state, + LAZY_TO_NATIVE_FINAL) + + def test_aa_rejects_non_screening_sample_count_below_two_hundred(self): + with mock.patch.object(sys, "argv", self.arguments( + "--aa-samples", "199")): + with self.assertRaises(SystemExit): + parse_args() + + def test_aa_screening_requires_directional_ab_limit(self): + with mock.patch.object(sys, "argv", self.arguments( + "--aa-samples", "50", "--max-samples", "400")): + with self.assertRaises(SystemExit): + parse_args() + + def test_samples_and_max_samples_are_checkpoint_values(self): + for option, value in (("--samples", "81"), ("--max-samples", "81")): + with self.subTest(option=option): + with mock.patch.object(sys, "argv", self.arguments(option, value)): + with self.assertRaises(SystemExit): + parse_args() + + def test_samples_must_not_exceed_max_samples(self): + with mock.patch.object(sys, "argv", self.arguments( + "--samples", "400", "--max-samples", "80")): + with self.assertRaises(SystemExit): + parse_args() + + def test_timeout_must_be_finite_and_positive(self): + for value in ("nan", "inf", "-inf", "0"): + timeout_arguments = ( + (f"--timeout={value}",) + if value.startswith("-") else ("--timeout", value) + ) + with self.subTest(value=value), mock.patch.object( + sys, "argv", self.arguments(*timeout_arguments)): + with self.assertRaises(SystemExit): + parse_args() + + def test_sixteen_hundred_is_an_allowed_formal_checkpoint(self): + with mock.patch.object(sys, "argv", self.arguments( + "--samples", "400", "--max-samples", "1600")): + self.assertEqual(parse_args().max_samples, 1600) + + +class OutputDirectoryContractTests(unittest.TestCase): + def config(self, output_dir, **changes): + values = { + "baseline_latx": Path("/baseline-latx"), + "candidate_latx": Path("/candidate-latx"), + "guest_root": Path("/guest-root"), + "fixture_dir": Path("/fixture"), + "cpu": 0, + "warmup": 0, + "samples": 80, + "max_samples": 80, + "aa_samples": 50, + "steady_calls": 100000, + "seed": 7, + "output_dir": output_dir, + "timeout": 60.0, + } + values.update(changes) + return HarnessConfig(**values) + + def test_output_ownership_is_exclusive(self): + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "run" + output_dir.mkdir() + descriptor = _acquire_output_ownership(output_dir) + try: + with self.assertRaises(PrerequisiteError): + _acquire_output_ownership(output_dir) + finally: + os.close(descriptor) + + def test_existing_output_evidence_is_rejected_without_writes(self): + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "existing-evidence" + output_dir.mkdir() + evidence = output_dir / "raw-samples.jsonl" + evidence.write_text("original evidence\n", encoding="utf-8") + config = HarnessConfig( + baseline_latx=Path("/baseline-latx"), + candidate_latx=Path("/candidate-latx"), + guest_root=Path("/guest-root"), + fixture_dir=Path("/fixture"), + cpu=0, + warmup=0, + samples=80, + max_samples=400, + aa_samples=200, + steady_calls=100000, + seed=7, + output_dir=output_dir, + ) + + with self.assertRaises(PrerequisiteError): + run_performance_gate(config) + + self.assertEqual( + evidence.read_text(encoding="utf-8"), "original evidence\n" + ) + self.assertEqual(list(output_dir.iterdir()), [evidence]) + + def test_internal_screening_config_cannot_request_formal_ab(self): + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "new-output" + config = HarnessConfig( + baseline_latx=Path("/baseline-latx"), + candidate_latx=Path("/candidate-latx"), + guest_root=Path("/guest-root"), + fixture_dir=Path("/fixture"), + cpu=0, + warmup=0, + samples=80, + max_samples=400, + aa_samples=50, + steady_calls=100000, + seed=7, + output_dir=output_dir, + ) + + with self.assertRaises(PrerequisiteError): + run_performance_gate(config) + + self.assertFalse(output_dir.exists()) + + def test_aa_only_screening_never_runs_ab_and_stays_inconclusive(self): + phases = [] + + def fake_pair(config, taskset, raw_samples, phase, pair_index, order, + roles): + phases.append(phase) + raw_samples.write({ + "phase": phase, + "pair_index": pair_index, + "pair_order": list(order), + }) + return { + "order": list(order), + **{label: timed_sample(100) for label in order}, + } + + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "aa-only" + with mock.patch("real_guest_harness.prerequisite_issues", + return_value=([], "/taskset")), \ + mock.patch("real_guest_harness.collect_binary_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_fixture_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_host_metadata", + return_value={}), \ + mock.patch("real_guest_harness.runtime_environment_snapshot", + return_value={}), \ + mock.patch("real_guest_harness.verify_role_modes_preflight", + return_value={}), \ + mock.patch("real_guest_harness.verify_native_apply_preflight", + return_value={}), \ + mock.patch("real_guest_harness._run_pair", + side_effect=fake_pair), \ + mock.patch("real_guest_harness.host_load_snapshot", + return_value={"oversubscribed": False}): + report = run_performance_gate(self.config( + output_dir, aa_only=True, + )) + + metadata = json.loads( + (output_dir / "run-metadata.json").read_text() + ) + raw_records = [ + json.loads(line) + for line in (output_dir / "raw-samples.jsonl").read_text( + encoding="utf-8" + ).splitlines() + ] + + self.assertEqual( + set(phases), {"baseline-aa", "candidate-aa"} + ) + self.assertFalse(any(record["phase"] == "ab" + for record in raw_records)) + self.assertNotIn("ab_checkpoints", report) + self.assertEqual(report["result"], GateResult.INCONCLUSIVE.value) + self.assertEqual(report["result_scope"], "aa_screening") + self.assertIn("A/A-only", report["reason"]) + self.assertTrue(report["configuration"]["aa_only"]) + self.assertTrue(metadata["configuration"]["aa_only"]) + + def test_invalid_sampling_configuration_does_not_create_output_directory(self): + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "new-output" + config = HarnessConfig( + baseline_latx=Path("/baseline-latx"), + candidate_latx=Path("/candidate-latx"), + guest_root=Path("/guest-root"), + fixture_dir=Path("/fixture"), + cpu=0, + warmup=0, + samples=80, + max_samples=400, + aa_samples=50, + steady_calls=100000, + seed=7, + output_dir=output_dir, + ) + + with self.assertRaises(PrerequisiteError): + run_performance_gate(config) + + self.assertFalse(output_dir.exists()) + + def test_invalid_runtime_configuration_does_not_create_output_directory(self): + invalid = { + "cpu": -1, + "warmup": -1, + "steady_calls": 99999, + "timeout": 0, + } + with tempfile.TemporaryDirectory() as temporary_directory: + for field, value in invalid.items(): + with self.subTest(field=field): + output_dir = Path(temporary_directory) / field + with self.assertRaises(PrerequisiteError): + run_performance_gate(self.config(output_dir, + **{field: value})) + self.assertFalse(output_dir.exists()) + + def test_non_finite_timeout_does_not_create_output_directory(self): + with tempfile.TemporaryDirectory() as temporary_directory: + for value in (float("nan"), float("inf"), float("-inf")): + with self.subTest(value=value): + output_dir = Path(temporary_directory) / str(value) + with self.assertRaises(PrerequisiteError): + run_performance_gate(self.config(output_dir, + timeout=value)) + self.assertFalse(output_dir.exists()) + + def test_invalid_baseline_binding_state_does_not_create_output_directory(self): + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "invalid-binding-state" + with self.assertRaises(PrerequisiteError): + run_performance_gate(self.config( + output_dir, baseline_binding_state="invalid" + )) + self.assertFalse(output_dir.exists()) + + def test_report_and_metadata_record_baseline_binding_state(self): + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "lazy-baseline" + with mock.patch("real_guest_harness.prerequisite_issues", + return_value=(["mock prerequisite"], None)), \ + mock.patch("real_guest_harness.collect_binary_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_fixture_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_host_metadata", + return_value={}), \ + mock.patch("real_guest_harness.host_load_snapshot", + return_value={"oversubscribed": False}): + report = run_performance_gate(self.config( + output_dir, + baseline_binding_state=LAZY_TO_NATIVE_FINAL, + )) + metadata = json.loads((output_dir / "run-metadata.json").read_text()) + for artifact in (report, metadata): + self.assertEqual(artifact["configuration"][ + "baseline_binding_state" + ], LAZY_TO_NATIVE_FINAL) + self.assertEqual(artifact["environment"]["baseline"][ + "LATX_KZT_PATCH_SPIKE" + ], "1") + + def test_unverified_core_isolation_is_environment_inconclusive(self): + failed_isolation = { + "requested": True, + "applied": False, + "guest_cpu": 37, + "topology_source": "/synthetic/cpu37/thread_siblings_list", + "thread_siblings": None, + "initial_affinity": [4, 12, 37, 55], + "active_affinity": [4, 12, 37, 55], + "parent_cpus": { + "initial": [4, 12, 37, 55], + "expected": [4, 12, 37, 55], + "active": [4, 12, 37, 55], + }, + "excluded_cpus": [], + "verification": { + "passed": False, + "siblings_excluded": None, + "active_matches_expected": None, + "error": "synthetic topology unavailable", + }, + } + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "topology-inconclusive" + with mock.patch("real_guest_harness.prerequisite_issues", + return_value=([], "/taskset")), \ + mock.patch( + "real_guest_harness.activate_harness_cpu_isolation", + return_value=failed_isolation, + ), \ + mock.patch("real_guest_harness.collect_binary_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_fixture_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_host_metadata", + return_value={}), \ + mock.patch("real_guest_harness.runtime_environment_snapshot", + return_value={}), \ + mock.patch("real_guest_harness.host_load_snapshot", + return_value={"oversubscribed": False}), \ + mock.patch( + "real_guest_harness.verify_role_modes_preflight", + side_effect=AssertionError("guest preflight must not run"), + ): + report = run_performance_gate(self.config( + output_dir, cpu=37, isolate_harness_cpu=True, + )) + + metadata = json.loads( + (output_dir / "run-metadata.json").read_text() + ) + self.assertEqual(report["result"], GateResult.INCONCLUSIVE.value) + self.assertEqual(report["result_scope"], "environment_inconclusive") + self.assertIn("synthetic topology unavailable", report["reason"]) + self.assertEqual(report["harness_cpu_isolation"], failed_isolation) + self.assertEqual(metadata["harness_cpu_isolation"], failed_isolation) + + def test_formal_gate_requires_verified_core_isolation(self): + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "formal-without-isolation" + with mock.patch("real_guest_harness.prerequisite_issues", + return_value=([], "/taskset")), \ + mock.patch("real_guest_harness.collect_binary_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_fixture_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_host_metadata", + return_value={}), \ + mock.patch("real_guest_harness.runtime_environment_snapshot", + return_value={}), \ + mock.patch("real_guest_harness.host_load_snapshot", + return_value={"oversubscribed": False}), \ + mock.patch( + "real_guest_harness.verify_role_modes_preflight", + side_effect=AssertionError("guest preflight must not run"), + ): + report = run_performance_gate(self.config( + output_dir, + samples=400, + max_samples=400, + aa_samples=200, + isolate_harness_cpu=False, + )) + + self.assertEqual(report["result"], GateResult.INCONCLUSIVE.value) + self.assertEqual(report["result_scope"], "environment_inconclusive") + self.assertIn("physical-core isolation", report["reason"]) + isolation = report["harness_cpu_isolation"] + self.assertEqual(isolation["guest_cpu"], 0) + self.assertIsNone(isolation["thread_siblings"]) + self.assertIn("cpu0/topology/thread_siblings_list", + isolation["topology_source"]) + self.assertFalse(isolation["verification"]["passed"]) + + def test_native_apply_preflight_scope_follows_baseline_binding_state(self): + def run_with_binding_state(baseline_binding_state): + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "preflight-scope" + calls = [] + + def preflight(config, taskset, role="candidate"): + calls.append(role) + if role == "baseline": + raise GuestCorrectnessError("baseline preflight stop") + return {"role": role} + + with mock.patch("real_guest_harness.prerequisite_issues", + return_value=([], "/taskset")), \ + mock.patch("real_guest_harness.collect_binary_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_fixture_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_host_metadata", + return_value={}), \ + mock.patch("real_guest_harness.runtime_environment_snapshot", + return_value={}), \ + mock.patch("real_guest_harness.host_load_snapshot", + return_value={"oversubscribed": False}), \ + mock.patch("real_guest_harness.verify_role_modes_preflight", + return_value={}), \ + mock.patch("real_guest_harness.verify_native_apply_preflight", + side_effect=preflight): + report = run_performance_gate(self.config( + output_dir, + baseline_binding_state=baseline_binding_state, + )) + return calls, report + + calls, report = run_with_binding_state(EAGER_FINAL) + self.assertEqual(calls, ["candidate"]) + self.assertIsNone(report["baseline_native_apply_preflight"]) + + calls, report = run_with_binding_state(LAZY_TO_NATIVE_FINAL) + self.assertEqual(calls, ["candidate", "baseline"]) + self.assertEqual(report["native_apply_preflight"], {"role": "candidate"}) + self.assertIsNone(report["baseline_native_apply_preflight"]) + self.assertEqual(report["result_scope"], "correctness_failure") + + def test_lazy_baseline_gate_records_both_native_apply_preflights(self): + def fake_pair(config, taskset, raw_samples, phase, pair_index, order, + roles): + return { + "order": list(order), + **{label: timed_sample(100) for label in order}, + } + + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "lazy-preflights" + calls = [] + + def preflight(config, taskset, role="candidate"): + calls.append(role) + return {"role": role} + + with mock.patch("real_guest_harness.prerequisite_issues", + return_value=([], "/taskset")), \ + mock.patch("real_guest_harness.collect_binary_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_fixture_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_host_metadata", + return_value={}), \ + mock.patch("real_guest_harness.verify_role_modes_preflight", + return_value={}), \ + mock.patch("real_guest_harness.verify_native_apply_preflight", + side_effect=preflight), \ + mock.patch("real_guest_harness._run_pair", + side_effect=fake_pair), \ + mock.patch("real_guest_harness.assess_dual_aa", + return_value={"result": AAResult.INCONCLUSIVE.value}), \ + mock.patch("real_guest_harness.analyze_ab_pairs", + return_value={"metrics": {}}), \ + mock.patch("real_guest_harness.classify_gate", + return_value=GateResult.INCONCLUSIVE), \ + mock.patch("real_guest_harness.host_load_snapshot", + return_value={"oversubscribed": False}): + report = run_performance_gate(self.config( + output_dir, + baseline_binding_state=LAZY_TO_NATIVE_FINAL, + )) + + self.assertEqual(calls, ["candidate", "baseline"]) + self.assertEqual(report["native_apply_preflight"], + {"role": "candidate"}) + self.assertEqual(report["baseline_native_apply_preflight"], + {"role": "baseline"}) + + def test_guest_lazy_baseline_gate_records_guest_preserved_preflight(self): + def fake_pair(config, taskset, raw_samples, phase, pair_index, order, + roles): + return { + "order": list(order), + **{label: timed_sample(100) for label in order}, + } + + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "guest-lazy-preflight" + with mock.patch("real_guest_harness.prerequisite_issues", + return_value=([], "/taskset")), \ + mock.patch("real_guest_harness.collect_binary_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_fixture_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_host_metadata", + return_value={}), \ + mock.patch("real_guest_harness.verify_role_modes_preflight", + return_value={}), \ + mock.patch("real_guest_harness.verify_native_apply_preflight", + return_value={"role": "candidate"}), \ + mock.patch( + "real_guest_harness.verify_guest_preserved_preflight", + return_value={"role": "baseline"}, + ), \ + mock.patch("real_guest_harness._run_pair", + side_effect=fake_pair), \ + mock.patch("real_guest_harness.assess_dual_aa", + return_value={"result": AAResult.INCONCLUSIVE.value}), \ + mock.patch("real_guest_harness.analyze_ab_pairs", + return_value={"metrics": {}}), \ + mock.patch("real_guest_harness.classify_gate", + return_value=GateResult.INCONCLUSIVE), \ + mock.patch("real_guest_harness.host_load_snapshot", + return_value={"oversubscribed": False}): + report = run_performance_gate(self.config( + output_dir, + baseline_binding_state=LAZY_TO_GUEST_FINAL, + )) + + self.assertEqual(report["native_apply_preflight"], + {"role": "candidate"}) + self.assertIsNone(report["baseline_native_apply_preflight"]) + self.assertEqual(report["baseline_guest_preserved_preflight"], + {"role": "baseline"}) + + def test_ownership_descriptor_closes_on_metadata_failure(self): + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "early-failure" + with mock.patch( + "real_guest_harness.collect_binary_metadata", + side_effect=RuntimeError("metadata failed")): + with self.assertRaisesRegex(RuntimeError, "metadata failed"): + run_performance_gate(self.config(output_dir)) + for artifact in ("report.json", "run-metadata.json"): + record = json.loads((output_dir / artifact).read_text()) + self.assertEqual(record["result_scope"], "harness_error") + self.assertEqual(record["harness_error"]["message"], + "metadata failed") + marker = output_dir / real_guest_harness.OWNERSHIP_MARKER + marker.unlink() + descriptor = _acquire_output_ownership(output_dir) + os.close(descriptor) + + def test_metadata_write_failure_writes_harness_error_artifacts(self): + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "metadata-write-failure" + original_write_json = real_guest_harness._write_json + failed = False + + def fail_first_metadata_write(path, value): + nonlocal failed + if path.name == "run-metadata.json" and not failed: + failed = True + raise OSError("metadata write failed") + return original_write_json(path, value) + + with mock.patch("real_guest_harness.prerequisite_issues", + return_value=(["mock prerequisite"], None)), \ + mock.patch("real_guest_harness.collect_binary_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_fixture_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_host_metadata", + return_value={}), \ + mock.patch("real_guest_harness.runtime_environment_snapshot", + return_value={}), \ + mock.patch("real_guest_harness.host_load_snapshot", + return_value={"oversubscribed": False}), \ + mock.patch("real_guest_harness._write_json", + side_effect=fail_first_metadata_write): + with self.assertRaisesRegex(OSError, "metadata write failed"): + run_performance_gate(self.config(output_dir)) + for artifact in ("report.json", "run-metadata.json"): + record = json.loads((output_dir / artifact).read_text()) + self.assertEqual(record["result_scope"], "harness_error") + + def test_raw_writer_open_failure_writes_harness_error_artifacts(self): + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "raw-writer-failure" + writer = mock.MagicMock() + writer.__enter__.side_effect = OSError("raw writer failed") + with mock.patch("real_guest_harness.prerequisite_issues", + return_value=(["mock prerequisite"], None)), \ + mock.patch("real_guest_harness.collect_binary_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_fixture_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_host_metadata", + return_value={}), \ + mock.patch("real_guest_harness.runtime_environment_snapshot", + return_value={}), \ + mock.patch("real_guest_harness.host_load_snapshot", + return_value={"oversubscribed": False}), \ + mock.patch("real_guest_harness.RawSampleWriter", + return_value=writer): + with self.assertRaisesRegex(OSError, "raw writer failed"): + run_performance_gate(self.config(output_dir)) + for artifact in ("report.json", "run-metadata.json"): + record = json.loads((output_dir / artifact).read_text()) + self.assertEqual(record["result_scope"], "harness_error") + + def test_v2_artifacts_and_prerequisite_scope_are_locked(self): + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "prerequisite" + metadata = {"path": "mock"} + with mock.patch("real_guest_harness.prerequisite_issues", + return_value=(["mock prerequisite"], None)), \ + mock.patch("real_guest_harness.collect_binary_metadata", + return_value=metadata), \ + mock.patch("real_guest_harness.collect_fixture_metadata", + return_value=metadata), \ + mock.patch("real_guest_harness.collect_host_metadata", + return_value=metadata), \ + mock.patch("real_guest_harness.runtime_environment_snapshot", + return_value=metadata), \ + mock.patch("real_guest_harness.host_load_snapshot", + return_value={"oversubscribed": False}): + report = run_performance_gate(self.config(output_dir)) + run_metadata = json.loads((output_dir / "run-metadata.json").read_text()) + self.assertEqual(report["schema_version"], 2) + self.assertEqual(report["artifact_type"], + "kzt-real-guest-performance-report") + self.assertEqual(run_metadata["artifact_type"], + "kzt-real-guest-performance-run-metadata") + self.assertEqual(report["ownership"]["marker"], + real_guest_harness.OWNERSHIP_MARKER) + self.assertEqual(report["result_scope"], "environment_inconclusive", + report["reason"]) + + def test_harness_and_correctness_scopes_are_not_performance_conclusions(self): + with tempfile.TemporaryDirectory() as temporary_directory: + for error, scope in ( + (RuntimeError("boom"), "harness_error"), + (GuestCorrectnessError("bad guest"), "correctness_failure")): + with self.subTest(scope=scope): + output_dir = Path(temporary_directory) / scope + with mock.patch("real_guest_harness.prerequisite_issues", + return_value=([], None)), \ + mock.patch("real_guest_harness.collect_binary_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_fixture_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_host_metadata", + return_value={}), \ + mock.patch("real_guest_harness.runtime_environment_snapshot", + return_value={}), \ + mock.patch("real_guest_harness.host_load_snapshot", + return_value={"oversubscribed": False}), \ + mock.patch("real_guest_harness.verify_role_modes_preflight", + side_effect=error): + report = run_performance_gate(self.config(output_dir)) + self.assertEqual(report["result_scope"], scope) + + def test_host_load_anomaly_is_environment_inconclusive_with_null_analysis(self): + def fake_pair(config, taskset, raw_samples, phase, pair_index, order, + roles): + return { + "order": list(order), + **{label: timed_sample(100) for label in order}, + } + + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "load-anomaly" + with mock.patch("real_guest_harness.prerequisite_issues", + return_value=([], None)), \ + mock.patch("real_guest_harness.collect_binary_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_fixture_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_host_metadata", + return_value={}), \ + mock.patch("real_guest_harness.runtime_environment_snapshot", + return_value={}), \ + mock.patch("real_guest_harness.verify_role_modes_preflight", + return_value={}), \ + mock.patch("real_guest_harness.verify_native_apply_preflight", + return_value={}), \ + mock.patch("real_guest_harness._run_pair", side_effect=fake_pair), \ + mock.patch("real_guest_harness.host_load_snapshot", + side_effect=[ + {"oversubscribed": False}, + {"oversubscribed": False}, + {"oversubscribed": True}, + {"oversubscribed": False}, + ]): + report = run_performance_gate(self.config(output_dir)) + self.assertEqual(report["result_scope"], "environment_inconclusive", + report["reason"]) + self.assertEqual(report["result"], GateResult.INCONCLUSIVE.value) + checkpoint = report["ab_checkpoints"][0] + self.assertEqual(checkpoint["mode"], "exploratory") + self.assertIsNone(checkpoint["analysis"]) + + def test_eighty_pair_exploratory_checkpoint_has_one_analysis_look_only(self): + def fake_pair(config, taskset, raw_samples, phase, pair_index, order, + roles): + return { + "order": list(order), + **{label: timed_sample(100) for label in order}, + } + + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "eighty-pair" + with mock.patch("real_guest_harness.prerequisite_issues", + return_value=([], None)), \ + mock.patch("real_guest_harness.collect_binary_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_fixture_metadata", + return_value={}), \ + mock.patch("real_guest_harness.collect_host_metadata", + return_value={}), \ + mock.patch("real_guest_harness.runtime_environment_snapshot", + return_value={}), \ + mock.patch("real_guest_harness.verify_role_modes_preflight", + return_value={}), \ + mock.patch("real_guest_harness.verify_native_apply_preflight", + return_value={}), \ + mock.patch("real_guest_harness._run_pair", side_effect=fake_pair), \ + mock.patch("real_guest_harness.host_load_snapshot", + return_value={"oversubscribed": False}): + report = run_performance_gate(self.config(output_dir)) + metadata = json.loads((output_dir / "run-metadata.json").read_text()) + checkpoint = report["ab_checkpoints"][-1] + analysis = checkpoint["analysis"] + self.assertEqual(report["ab_mode"], "exploratory") + self.assertEqual(checkpoint["decision"], GateResult.INCONCLUSIVE.value) + self.assertEqual(report["result"], GateResult.INCONCLUSIVE.value) + self.assertEqual(analysis["formal_stage_count"], 0) + self.assertEqual(analysis["analysis_look_count"], 1) + for artifact in (report, metadata): + self.assertEqual(artifact["statistics"]["ab"][ + "formal_stage_count"], 0) + self.assertEqual(artifact["statistics"]["ab"][ + "analysis_look_count"], 1) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/kzt/test_real_guest_loader_gate.py b/tests/unit/kzt/test_real_guest_loader_gate.py new file mode 100755 index 00000000000..24481b66b36 --- /dev/null +++ b/tests/unit/kzt/test_real_guest_loader_gate.py @@ -0,0 +1,360 @@ +#!/usr/bin/env python3 +import argparse +import json +import os +from pathlib import Path +import subprocess +import sys +import time + + +SCENARIOS = ( + { + "id": "dependency-reopen", + "description": "dependency, duplicate handles, close, and reopen", + }, + { + "id": "visibility-noload", + "description": "RTLD_LOCAL, RTLD_GLOBAL, and RTLD_NOLOAD", + }, + { + "id": "namespace-isolation", + "description": "dlmopen namespace identity and isolation", + }, + { + "id": "symbol-versions-errors", + "description": "dlsym, dlvsym, missing objects, and dlerror", + }, + { + "id": "wrapped-library-handle", + "description": "wrapped library guest handle, duplicate open, and NOLOAD", + }, +) + +LOADER_PATHS = ( + "lib64/ld-linux-x86-64.so.2", + "lib/x86_64-linux-gnu/ld-linux-x86-64.so.2", + "usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2", +) + +EXIT_CODES = { + "PASS": 0, + "FAIL": 1, + "INCONCLUSIVE": 2, +} + +SANITIZED_LOADER_VARIABLES = ( + "LD_AUDIT", + "LD_BIND_NOW", + "LD_DEBUG", + "LD_DEBUG_OUTPUT", + "LD_LIBRARY_PATH", + "LD_PRELOAD", + "LD_PROFILE", +) + + +def path_issue(path, label, executable=False, directory=False): + if directory: + if not path.is_dir(): + return f"{label} directory not found: {path}" + return None + if not path.is_file(): + return f"{label} not found: {path}" + if executable and not os.access(str(path), os.X_OK): + return f"{label} is not executable: {path}" + return None + + +def guest_root_issue(guest_root): + issue = path_issue(guest_root, "guest root", directory=True) + if issue: + return issue + if not any((guest_root / relative).is_file() for relative in LOADER_PATHS): + return f"x86-64 guest loader not found under guest root: {guest_root}" + return None + + +def normalise_output(output): + if output is None: + return "" + if isinstance(output, bytes): + return output.decode("utf-8", errors="replace") + return output + + +def write_log(log_path, command, output, reason): + log_path.parent.mkdir(parents=True, exist_ok=True) + lines = ["command: " + " ".join(command), "result: " + reason, ""] + if output: + lines.append(output.rstrip()) + lines.append("") + log_path.write_text("\n".join(lines), encoding="utf-8") + + +def result(status, reason, command, log_path, duration=0.0, + returncode=None): + return { + "status": status, + "reason": reason, + "returncode": returncode, + "duration_seconds": round(duration, 6), + "command": command, + "log": str(log_path), + } + + +def run_scenario(label, latx, scenario, args, infrastructure_issue=None): + scenario_id = scenario["id"] + executable = args.fixture_dir / scenario_id + log_path = args.log_dir / f"{label}-{scenario_id}.log" + command = [ + str(latx), + "-L", + str(args.guest_root), + str(executable), + ] + + issue = infrastructure_issue + if issue is None: + issue = path_issue( + executable, f"fixture for {scenario_id}", executable=True + ) + if issue: + reason = "infrastructure unavailable: " + issue + write_log(log_path, command, "", reason) + return result("INCONCLUSIVE", reason, command, log_path) + + environment = os.environ.copy() + for name in list(environment): + if name.startswith("LATX_KZT"): + environment.pop(name) + for name in SANITIZED_LOADER_VARIABLES: + environment.pop(name, None) + environment.update({ + "LATX_KZT": "2", + "LATX_KZT_LAZY_DIAGNOSTICS": "0", + "LATX_KZT_REGISTRY_DIAGNOSTICS": "0", + "LD_LIBRARY_PATH": str(args.fixture_dir), + }) + if label == "candidate": + environment.update({ + "LATX_KZT_PATCH_SPIKE": "1", + "LATX_KZT_PATCH_SPIKE_WRITE": "1", + "LATX_KZT_PATCH_SPIKE_BUDGET": "1", + }) + + started = time.monotonic() + try: + completed = subprocess.run( + command, + cwd=str(args.fixture_dir), + env=environment, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + timeout=args.timeout, + check=False, + ) + except subprocess.TimeoutExpired as error: + duration = time.monotonic() - started + output = normalise_output(error.stdout) + reason = f"guest run timed out after {args.timeout:g} seconds" + write_log(log_path, command, output, reason) + return result( + "INCONCLUSIVE", reason, command, log_path, duration=duration + ) + except OSError as error: + duration = time.monotonic() - started + reason = f"could not execute LATX: {error}" + write_log(log_path, command, "", reason) + return result( + "INCONCLUSIVE", reason, command, log_path, duration=duration + ) + + duration = time.monotonic() - started + output = completed.stdout + if completed.returncode == 77: + reason = "guest run reported skip (exit 77)" + status = "INCONCLUSIVE" + elif completed.returncode != 0: + reason = f"guest exited with status {completed.returncode}" + status = "FAIL" + else: + expected_marker = f"WI600_GUEST_LOADER_PASS {scenario_id}" + marker_found = any( + line.strip() == expected_marker for line in output.splitlines() + ) + if marker_found: + reason = "guest scenario passed" + status = "PASS" + else: + reason = f"guest exited successfully without pass marker: {expected_marker}" + status = "FAIL" + + write_log(log_path, command, output, reason) + return result( + status, + reason, + command, + log_path, + duration=duration, + returncode=completed.returncode, + ) + + +def compare_results(baseline, candidate): + regression = baseline["status"] == "PASS" and \ + candidate["status"] == "FAIL" + if candidate["status"] == "FAIL": + if regression: + reason = "candidate failed after baseline passed" + else: + reason = "candidate P0 scenario failed" + return "FAIL", regression, reason + if candidate["status"] == "INCONCLUSIVE": + return "INCONCLUSIVE", False, \ + "candidate result is inconclusive" + if baseline["status"] == "INCONCLUSIVE": + return "INCONCLUSIVE", False, \ + "baseline comparison is inconclusive" + if baseline["status"] == "FAIL": + return "PASS", False, "candidate passed while baseline failed" + return "PASS", False, "baseline and candidate passed" + + +def overall_status(scenarios): + statuses = {scenario["status"] for scenario in scenarios} + if "FAIL" in statuses: + return "FAIL" + if "INCONCLUSIVE" in statuses: + return "INCONCLUSIVE" + return "PASS" + + +def render_table(report): + headings = ("SCENARIO", "BASELINE", "CANDIDATE", "GATE", "REGRESSION") + rows = [headings] + for scenario in report["scenarios"]: + rows.append(( + scenario["id"], + scenario["baseline"]["status"], + scenario["candidate"]["status"], + scenario["status"], + "yes" if scenario["regression"] else "no", + )) + widths = [max(len(row[index]) for row in rows) for index in range(5)] + rendered = [] + for row_index, row in enumerate(rows): + rendered.append(" ".join( + value.ljust(widths[index]) + for index, value in enumerate(row) + ).rstrip()) + if row_index == 0: + rendered.append(" ".join("-" * width for width in widths)) + rendered.append(f"OVERALL: {report['status']}") + return "\n".join(rendered) + + +def run_gate(args): + args.baseline_latx = args.baseline_latx.resolve() + args.candidate_latx = args.candidate_latx.resolve() + args.guest_root = args.guest_root.resolve() + args.fixture_dir = args.fixture_dir.resolve() + args.log_dir = args.log_dir.resolve() + args.json_output = args.json_output.resolve() + + shared_issue = guest_root_issue(args.guest_root) + if shared_issue is None: + shared_issue = path_issue( + args.fixture_dir, "fixture", directory=True + ) + baseline_issue = shared_issue or path_issue( + args.baseline_latx, "baseline LATX", executable=True + ) + candidate_issue = shared_issue or path_issue( + args.candidate_latx, "candidate LATX", executable=True + ) + + scenario_reports = [] + for scenario in SCENARIOS: + baseline = run_scenario( + "baseline", args.baseline_latx, scenario, args, baseline_issue + ) + candidate = run_scenario( + "candidate", args.candidate_latx, scenario, args, candidate_issue + ) + status, regression, reason = compare_results(baseline, candidate) + scenario_reports.append({ + "id": scenario["id"], + "description": scenario["description"], + "p0": True, + "status": status, + "regression": regression, + "reason": reason, + "baseline": baseline, + "candidate": candidate, + }) + + status = overall_status(scenario_reports) + counts = { + name: sum(item["status"] == name for item in scenario_reports) + for name in ("PASS", "FAIL", "INCONCLUSIVE") + } + return { + "schema_version": 1, + "gate": "WI-600-real-guest-loader", + "status": status, + "exit_code": EXIT_CODES[status], + "inputs": { + "baseline_latx": str(args.baseline_latx), + "candidate_latx": str(args.candidate_latx), + "guest_root": str(args.guest_root), + "fixture_dir": str(args.fixture_dir), + "timeout_seconds": args.timeout, + }, + "counts": counts, + "scenarios": scenario_reports, + } + + +def parse_args(argv=None): + parser = argparse.ArgumentParser( + description="Compare WI-600 real guest loader behavior across LATX builds." + ) + parser.add_argument("--baseline-latx", required=True, type=Path) + parser.add_argument("--candidate-latx", required=True, type=Path) + parser.add_argument("--guest-root", required=True, type=Path) + parser.add_argument("--fixture-dir", required=True, type=Path) + parser.add_argument("--timeout", type=float, default=30.0) + parser.add_argument("--log-dir", type=Path) + parser.add_argument("--json-output", type=Path) + args = parser.parse_args(argv) + if args.timeout <= 0: + parser.error("--timeout must be greater than zero") + if args.log_dir is None: + args.log_dir = args.fixture_dir / "logs" / "wi600-loader-gate" + if args.json_output is None: + args.json_output = args.log_dir / "report.json" + return args + + +def main(argv=None): + args = parse_args(argv) + report = run_gate(args) + print(render_table(report)) + args.json_output.parent.mkdir(parents=True, exist_ok=True) + args.json_output.write_text( + json.dumps(report, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + print(f"JSON report: {args.json_output.resolve()}") + return report["exit_code"] + + +if __name__ == "__main__": + try: + sys.exit(main()) + except OSError as error: + print(f"WI-600 guest loader gate: INCONCLUSIVE: {error}", file=sys.stderr) + sys.exit(EXIT_CODES["INCONCLUSIVE"]) diff --git a/tests/unit/kzt/test_real_guest_loader_performance.py b/tests/unit/kzt/test_real_guest_loader_performance.py new file mode 100644 index 00000000000..85d8d41245b --- /dev/null +++ b/tests/unit/kzt/test_real_guest_loader_performance.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +import json +from pathlib import Path +from types import SimpleNamespace +import tempfile +import unittest +from unittest import mock + +from real_guest_loader_performance import ( + LIFECYCLE_MARKER, + GuestLifecycleError, + lifecycle_command, + run_lifecycle_gate, + validate_lifecycle_execution, +) + + +class GuestLoaderPerformanceTest(unittest.TestCase): + def test_accepts_dependency_reopen_pass_marker(self): + result = validate_lifecycle_execution({ + "returncode": 0, + "timed_out": False, + "process_total_ns": 1234, + "output": LIFECYCLE_MARKER + "\n", + }) + + self.assertEqual(result["dlopen_lifecycle_process_total_ns"], 1234) + + def test_rejects_non_passing_lifecycle_execution(self): + for execution in ( + {"returncode": 1, "timed_out": False, + "process_total_ns": 1234, "output": ""}, + {"returncode": 0, "timed_out": True, + "process_total_ns": 1234, "output": LIFECYCLE_MARKER}, + {"returncode": 0, "timed_out": False, + "process_total_ns": 1234, "output": "missing marker\n"}, + ): + with self.assertRaises(GuestLifecycleError): + validate_lifecycle_execution(execution) + + def test_command_pins_existing_dependency_reopen_fixture(self): + command = lifecycle_command( + "/candidate/latx", "/guest-root", "/fixture", 3 + ) + + self.assertEqual(command[:2], ["taskset", "-c"]) + self.assertEqual(command[2], "3") + self.assertEqual(command[3], "/candidate/latx") + self.assertEqual( + command[4:], + ["-L", "/guest-root", "/fixture/dependency-reopen"], + ) + + def test_unverified_core_isolation_blocks_formal_lifecycle_samples(self): + failed_isolation = { + "requested": True, + "applied": False, + "guest_cpu": 37, + "topology_source": "/synthetic/thread_siblings_list", + "thread_siblings": None, + "initial_affinity": [4, 12, 37, 55], + "active_affinity": [4, 12, 37, 55], + "parent_cpus": { + "initial": [4, 12, 37, 55], + "expected": [4, 12, 37, 55], + "active": [4, 12, 37, 55], + }, + "excluded_cpus": [], + "verification": { + "passed": False, + "siblings_excluded": None, + "active_matches_expected": None, + "error": "synthetic topology unavailable", + }, + } + with tempfile.TemporaryDirectory() as temporary_directory: + output_dir = Path(temporary_directory) / "lifecycle" + args = SimpleNamespace( + baseline_latx=Path("/baseline"), + candidate_latx=Path("/candidate"), + guest_root=Path("/guest"), + fixture_dir=Path("/fixture"), + cpu=37, + output_dir=output_dir, + aa_pairs=200, + ab_pairs=400, + warmup=20, + seed=7, + timeout=60.0, + ) + with mock.patch("real_guest_loader_performance._issues", + return_value=[]), \ + mock.patch( + "real_guest_loader_performance.activate_harness_cpu_isolation", + return_value=failed_isolation, + create=True, + ), \ + mock.patch("real_guest_loader_performance.host_load_snapshot", + return_value={"oversubscribed": False}), \ + mock.patch( + "real_guest_loader_performance.run_lifecycle_sample", + side_effect=AssertionError("sample must not run"), + ): + report = run_lifecycle_gate(args) + + metadata = json.loads( + (output_dir / "run-metadata.json").read_text() + ) + self.assertEqual(report["result"], "INCONCLUSIVE") + self.assertIn("synthetic topology unavailable", report["reason"]) + self.assertEqual(report["harness_cpu_isolation"], failed_isolation) + self.assertEqual(metadata["harness_cpu_isolation"], failed_isolation) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/kzt/test_real_guest_performance.py b/tests/unit/kzt/test_real_guest_performance.py index ffc8965bfd9..11cf14fbefe 100644 --- a/tests/unit/kzt/test_real_guest_performance.py +++ b/tests/unit/kzt/test_real_guest_performance.py @@ -1,148 +1,118 @@ #!/usr/bin/env python3 import argparse import json -import os +import math from pathlib import Path -import random -import statistics -import subprocess import sys -from test_real_guest_e2e import SUCCESS_PATTERN, existing_directory, existing_file +from real_guest_harness import ( + BASELINE_BINDING_STATES, + EAGER_FINAL, + GateResult, + HarnessConfig, + run_performance_gate, +) -MODES = { - "legacy": {"LATX_KZT": "0"}, - "fail_open": { - "LATX_KZT": "1", - "LATX_KZT_PATCH_SPIKE": "1", - "LATX_KZT_PATCH_SPIKE_WRITE": "1", - "LATX_KZT_PATCH_SPIKE_BUDGET": "0", - }, - "applied": { - "LATX_KZT": "1", - "LATX_KZT_PATCH_SPIKE": "1", - "LATX_KZT_PATCH_SPIKE_WRITE": "1", - "LATX_KZT_PATCH_SPIKE_BUDGET": "1", - }, -} +DEFAULT_SEED = 20260721 -def run_once(args, mode): - environment = os.environ.copy() - for name in list(environment): - if name.startswith("LATX_KZT"): - environment.pop(name) - environment.update(MODES[mode]) - completed = subprocess.run( - [ - str(args.latx), "-L", str(args.guest_root), - "-E", f"LD_LIBRARY_PATH={args.fixture_dir}", - str(args.fixture_dir / "kzt_guest_main"), - ], - env=environment, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - timeout=args.timeout, - check=False, - ) - if completed.returncode != 0: - raise RuntimeError( - f"{mode} exited with {completed.returncode}:\n{completed.stdout}" - ) - matches = SUCCESS_PATTERN.findall(completed.stdout) - if len(matches) != 1: - raise RuntimeError(f"{mode} did not emit one timing record") - values = [int(value, 16) for value in matches[0]] - return {"first_ns": values[4], "second_ns": values[5]} - +def absolute_path(value): + return Path(value).expanduser().resolve() -def regression_percent(value, baseline): - return (value / baseline - 1.0) * 100.0 - -def main(): +def parse_args(): parser = argparse.ArgumentParser( - description="Run the KZT real guest first-binding performance gate." + description=( + "Compare legacy and candidate release LATX KZT performance." + ) ) - parser.add_argument("--latx", required=True, type=existing_file) - parser.add_argument("--guest-root", required=True, type=existing_directory) - parser.add_argument("--fixture-dir", required=True, type=existing_directory) - parser.add_argument("--iterations", type=int, default=200) - parser.add_argument("--warmup", type=int, default=20) - parser.add_argument("--timeout", type=float, default=30.0) - parser.add_argument("--seed", type=int, default=20260720) - parser.add_argument("--max-first-regression-percent", type=float, - default=3.0) - parser.add_argument("--max-steady-regression-percent", type=float, - default=1.0) - parser.add_argument("--output", type=Path, required=True) + parser.add_argument( + "--baseline-latx", required=True, type=absolute_path + ) + parser.add_argument( + "--baseline-binding-state", + choices=BASELINE_BINDING_STATES, + default=EAGER_FINAL, + help="expected binding state for the statistical baseline", + ) + parser.add_argument( + "--candidate-latx", required=True, type=absolute_path + ) + parser.add_argument("--guest-root", required=True, type=absolute_path) + parser.add_argument("--fixture-dir", required=True, type=absolute_path) + parser.add_argument("--cpu", required=True, type=int) + parser.add_argument("--isolate-harness-cpu", action="store_true") + parser.add_argument("--aa-only", action="store_true") + parser.add_argument("--warmup", type=int, default=30) + parser.add_argument("--samples", type=int, default=80) + parser.add_argument("--max-samples", type=int, default=800) + parser.add_argument("--aa-samples", type=int, default=200) + parser.add_argument("--steady-calls", type=int, default=100000) + parser.add_argument("--seed", type=int, default=DEFAULT_SEED) + parser.add_argument("--output-dir", required=True, type=absolute_path) + parser.add_argument("--timeout", type=float, default=60.0) args = parser.parse_args() - if args.iterations < 20 or args.warmup < 0: - parser.error("iterations must be >= 20 and warmup must be >= 0") - samples = {mode: {"first_ns": [], "second_ns": []} for mode in MODES} - rng = random.Random(args.seed) - for round_index in range(args.warmup + args.iterations): - order = list(MODES) - rng.shuffle(order) - for mode in order: - sample = run_once(args, mode) - if round_index >= args.warmup: - for metric, value in sample.items(): - samples[mode][metric].append(value) + if args.cpu < 0: + parser.error("--cpu must be non-negative") + if args.warmup < 0: + parser.error("--warmup must be non-negative") + if args.samples not in (80, 400, 800, 1600): + parser.error("--samples must be one of 80, 400, 800, or 1600") + if args.max_samples < args.samples: + parser.error("--max-samples must be at least --samples") + if args.max_samples not in (80, 400, 800, 1600): + parser.error("--max-samples must be one of 80, 400, 800, or 1600") + if args.aa_samples != 50 and args.aa_samples < 200: + parser.error("--aa-samples must be 50 for screening or at least 200") + if args.aa_samples == 50 and args.max_samples != 80: + parser.error("--aa-samples 50 requires --max-samples 80") + if args.steady_calls < 100000: + parser.error("--steady-calls must be at least 100000") + if not math.isfinite(args.timeout) or args.timeout <= 0: + parser.error("--timeout must be finite and positive") + return args - medians = { - mode: { - metric: statistics.median(values) - for metric, values in metrics.items() - } - for mode, metrics in samples.items() - } - regressions = { - mode: { - metric: regression_percent( - medians[mode][metric], medians["legacy"][metric] - ) - for metric in ("first_ns", "second_ns") - } - for mode in ("fail_open", "applied") - } - report = { - "iterations": args.iterations, - "warmup": args.warmup, - "seed": args.seed, - "limits_percent": { - "first_ns": args.max_first_regression_percent, - "second_ns": args.max_steady_regression_percent, - }, - "medians_ns": medians, - "regressions_percent": regressions, - "samples_ns": samples, - } - args.output.parent.mkdir(parents=True, exist_ok=True) - args.output.write_text(json.dumps(report, indent=2) + "\n", - encoding="utf-8") - failures = [] - for mode, metrics in regressions.items(): - if metrics["first_ns"] > args.max_first_regression_percent: - failures.append(f"{mode} first={metrics['first_ns']:.2f}%") - if metrics["second_ns"] > args.max_steady_regression_percent: - failures.append(f"{mode} second={metrics['second_ns']:.2f}%") +def main(): + args = parse_args() + config = HarnessConfig( + baseline_latx=args.baseline_latx, + baseline_binding_state=args.baseline_binding_state, + candidate_latx=args.candidate_latx, + guest_root=args.guest_root, + fixture_dir=args.fixture_dir, + cpu=args.cpu, + warmup=args.warmup, + samples=args.samples, + max_samples=args.max_samples, + aa_samples=args.aa_samples, + steady_calls=args.steady_calls, + seed=args.seed, + output_dir=args.output_dir, + timeout=args.timeout, + isolate_harness_cpu=args.isolate_harness_cpu, + aa_only=args.aa_only, + ) + report = run_performance_gate(config) print(json.dumps({ - "medians_ns": medians, - "regressions_percent": regressions, - "result": "FAIL" if failures else "PASS", - }, indent=2)) - if failures: - raise RuntimeError("performance gate exceeded: " + ", ".join(failures)) + "result": report["result"], + "reason": report["reason"], + "report": str(args.output_dir / "report.json"), + "raw_samples": report["raw_samples"], + }, indent=2, sort_keys=True)) + if report["result"] == GateResult.PASS.value: + return 0 + if report["result"] == GateResult.FAIL.value: + return 1 + return 2 if __name__ == "__main__": try: - main() - except (RuntimeError, subprocess.TimeoutExpired) as error: - print(f"KZT real guest performance: FAIL: {error}", file=sys.stderr) - sys.exit(1) + sys.exit(main()) + except KeyboardInterrupt: + print("KZT real guest performance: INCONCLUSIVE: interrupted", + file=sys.stderr) + sys.exit(2) diff --git a/tests/unit/kzt/test_wi1065_real_guest_hook.py b/tests/unit/kzt/test_wi1065_real_guest_hook.py new file mode 100644 index 00000000000..648d19bbb2c --- /dev/null +++ b/tests/unit/kzt/test_wi1065_real_guest_hook.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +"""Execute and validate the WI-1065 x86_64 loader-event evidence fixture.""" + +import argparse +import json +import os +from pathlib import Path +import re +import subprocess +import sys + + +PASS_MARKER = "WI600_GUEST_LOADER_PASS wi1065-loader-events" +EVENT_PREFIX = "kzt_loader_event " +REQUIRED_MARKERS = ( + "WI1065_STARTUP", + "WI1065_CONSTRUCTOR", + "WI1065_RELRO partial", + "WI1065_RELRO full", + "WI1065_DLCLOSE full", + "WI1065_THREADS_PASS", + PASS_MARKER, +) + + +def parse_record(line): + return { + key: value + for key, value in ( + field.split("=", 1) for field in line.split() if "=" in field + ) + } + + +def require(condition, message, output): + if not condition: + raise RuntimeError(f"{message}\n--- guest output ---\n{output.rstrip()}") + + +def environment(fixture_dir, disabled=False, force_pattern_mismatch=False): + values = os.environ.copy() + for name in list(values): + if name.startswith("LATX_KZT"): + values.pop(name) + for name in ( + "LD_AUDIT", "LD_BIND_NOW", "LD_DEBUG", "LD_DEBUG_OUTPUT", + "LD_LIBRARY_PATH", "LD_PRELOAD", "LD_PROFILE", + ): + values.pop(name, None) + values.update({ + "LATX_AOT": "0", + "LATX_KZT": "2", + "LATX_KZT_LAZY_DIAGNOSTICS": "0", + "LATX_KZT_REGISTRY_DIAGNOSTICS": "1", + "LATX_KZT_PATCH_SPIKE": "1", + "LATX_KZT_PATCH_SPIKE_WRITE": "1", + "LATX_KZT_PATCH_SPIKE_BUDGET": "1", + "LD_LIBRARY_PATH": str(fixture_dir), + }) + if disabled: + values["LATX_KZT_LOADER_EVENT_HOOK"] = "0" + if force_pattern_mismatch: + values["LATX_KZT_LOADER_EVENT_FORCE_PATTERN_MISMATCH"] = "1" + return values + + +def run_guest(latx, guest_root, fixture_dir, timeout, disabled=False, + force_pattern_mismatch=False): + command = [str(latx), "-L", str(guest_root), + str(fixture_dir / "wi1065-loader-events")] + completed = subprocess.run( + command, cwd=fixture_dir, + env=environment(fixture_dir, disabled, force_pattern_mismatch), + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, + timeout=timeout, check=False, + ) + return command, completed + + +def event_records(output, phase): + return [ + (index, parse_record(line)) + for index, line in enumerate(output.splitlines()) + if line.startswith(EVENT_PREFIX) and f"phase={phase}" in line + ] + + +def marker_index(output, marker): + for index, line in enumerate(output.splitlines()): + if line == marker: + return index + return -1 + + +def verify_known(output): + require("phase=install result=INSTALLED" in output, + "known Build ID did not install the event hook", output) + for marker in REQUIRED_MARKERS: + require(marker_index(output, marker) >= 0, + f"missing fixture marker {marker}", output) + + published = event_records(output, "published") + consumed = event_records(output, "consumed") + require(published, "known Build ID produced no events", output) + published_by_sequence = {item[1].get("sequence"): item for item in published} + consumed_by_sequence = {item[1].get("sequence"): item for item in consumed} + require(len(published_by_sequence) == len(published), + "publisher duplicated an event sequence", output) + require(set(published_by_sequence) == set(consumed_by_sequence), + "candidate events were lost or not consumed", output) + sequences = [int(record["sequence"], 0) for _, record in published] + require(sequences == list(range(1, len(sequences) + 1)), + "event sequence is not contiguous", output) + for sequence, (_, published_record) in published_by_sequence.items(): + consumed_index, consumed_record = consumed_by_sequence[sequence] + published_index = published_by_sequence[sequence][0] + require(consumed_index > published_index and + consumed_record.get("link_map") == published_record.get("link_map"), + "consumer did not process the exact published link_map", output) + require(int(consumed_record.get("runtime_ns", "-1"), 0) >= 0, + "consumer timing evidence is missing", output) + + startup = marker_index(output, "WI1065_STARTUP") + constructor = marker_index(output, "WI1065_CONSTRUCTOR") + partial = marker_index(output, "WI1065_RELRO partial") + full = marker_index(output, "WI1065_RELRO full") + threads = marker_index(output, "WI1065_THREADS_PASS") + require(published[0][0] < startup, + "startup event was not published before guest startup", output) + require(any(index < constructor for index, _ in consumed), + "no event was consumed before constructor", output) + require(any(index < partial for index, _ in consumed) and + any(index < full for index, _ in consumed), + "RELRO fixture did not observe loader events before completion", output) + require(any(index < threads for index, _ in published), + "multithread loader fixture published no events", output) + + +def verify_disabled(output): + require(PASS_MARKER in output, + "disabled hook did not roll back to guest-loader behavior", output) + require("phase=install result=DISABLED" in output and + "rollback=disabled" in output, + "disabled hook did not report its rollback", output) + require(not event_records(output, "published") and + not event_records(output, "consumed"), + "disabled hook still executed callback events", output) + + +def verify_fail_open(output, expected_result): + require(PASS_MARKER in output, + f"{expected_result} did not preserve guest-loader behavior", output) + require(f"phase=install result={expected_result}" in output and + "rollback=disabled" in output, + f"{expected_result} did not report fail-open rollback", output) + require(not event_records(output, "published") and + not event_records(output, "consumed"), + f"{expected_result} still executed callback events", output) + + +def parse_args(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--latx", required=True, type=Path) + parser.add_argument("--guest-root", required=True, type=Path) + parser.add_argument("--fixture-dir", required=True, type=Path) + parser.add_argument("--log-dir", required=True, type=Path) + parser.add_argument("--unknown-guest-root", type=Path) + parser.add_argument("--timeout", type=float, default=30.0) + args = parser.parse_args() + for path, label in ((args.latx, "LATX"), (args.fixture_dir, "fixture"), + (args.guest_root, "guest root")): + if not path.exists(): + parser.error(f"{label} does not exist: {path}") + if args.timeout <= 0: + parser.error("timeout must be positive") + args.latx = args.latx.resolve() + args.guest_root = args.guest_root.resolve() + args.fixture_dir = args.fixture_dir.resolve() + if args.unknown_guest_root: + if not args.unknown_guest_root.is_dir(): + parser.error(f"unknown guest root does not exist: {args.unknown_guest_root}") + args.unknown_guest_root = args.unknown_guest_root.resolve() + args.log_dir.mkdir(parents=True, exist_ok=True) + args.log_dir = args.log_dir.resolve() + return args + + +def main(): + args = parse_args() + known_command, known = run_guest(args.latx, args.guest_root, + args.fixture_dir, args.timeout) + disabled_command, disabled = run_guest(args.latx, args.guest_root, + args.fixture_dir, args.timeout, + disabled=True) + mismatch_command, mismatch = run_guest( + args.latx, args.guest_root, args.fixture_dir, args.timeout, + force_pattern_mismatch=True) + (args.log_dir / "known.log").write_text(known.stdout, encoding="utf-8") + (args.log_dir / "disabled.log").write_text(disabled.stdout, encoding="utf-8") + (args.log_dir / "pattern-mismatch.log").write_text( + mismatch.stdout, encoding="utf-8") + require(known.returncode == 0, f"known run exited {known.returncode}", + known.stdout) + require(disabled.returncode == 0, + f"disabled run exited {disabled.returncode}", disabled.stdout) + require(mismatch.returncode == 0, + f"pattern mismatch run exited {mismatch.returncode}", mismatch.stdout) + verify_known(known.stdout) + verify_disabled(disabled.stdout) + verify_fail_open(mismatch.stdout, "PATTERN_MISMATCH") + unknown_command = None + unknown = None + if args.unknown_guest_root: + unknown_command, unknown = run_guest( + args.latx, args.unknown_guest_root, args.fixture_dir, args.timeout) + (args.log_dir / "unknown-build-id.log").write_text( + unknown.stdout, encoding="utf-8") + require(unknown.returncode == 0, + f"unknown Build ID run exited {unknown.returncode}", unknown.stdout) + verify_fail_open(unknown.stdout, "UNKNOWN_BUILD_ID") + report = { + "known_command": known_command, + "disabled_command": disabled_command, + "pattern_mismatch_command": mismatch_command, + "unknown_build_id_command": unknown_command, + "published_events": len(event_records(known.stdout, "published")), + "consumed_events": len(event_records(known.stdout, "consumed")), + "unknown_build_id_checked": unknown is not None, + "result": "PASS", + } + (args.log_dir / "report.json").write_text( + json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8" + ) + print("WI-1065 real guest hook: PASS") + + +if __name__ == "__main__": + try: + main() + except (OSError, RuntimeError, subprocess.TimeoutExpired) as error: + print(f"WI-1065 real guest hook: FAIL: {error}", file=sys.stderr) + sys.exit(1) diff --git a/tests/unit/kzt/test_wi1065_real_guest_hook_contract.py b/tests/unit/kzt/test_wi1065_real_guest_hook_contract.py new file mode 100644 index 00000000000..4457bb370c7 --- /dev/null +++ b/tests/unit/kzt/test_wi1065_real_guest_hook_contract.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +"""WI-1065 fixture and runner contract for hook timing evidence.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[3] +FIXTURE_BUILD = ROOT / "tests/unit/kzt/guest_loader/build_guest_loader_fixture.sh" +RUNNER = ROOT / "tests/unit/kzt/test_wi1065_real_guest_hook.py" + + +build = FIXTURE_BUILD.read_text(encoding="utf-8") +runner = RUNNER.read_text(encoding="utf-8") + +for required in ( + "wi1065_constructor.c", + "wi1065_relro.c", + "wi1065_loader_events.c", + "libwi1065_constructor.so", + "libwi1065_partial_relro.so", + "libwi1065_full_relro.so", + "wi1065-loader-events", + "-Wl,-z,relro,-z,now", +): + if required not in build: + raise AssertionError(f"WI-1065 fixture build misses {required}") + +for required in ( + 'event_records(output, "published")', + 'event_records(output, "consumed")', + "WI1065_CONSTRUCTOR", + "WI1065_RELRO partial", + "WI1065_RELRO full", + "WI1065_THREADS_PASS", + "LATX_KZT_LOADER_EVENT_HOOK", + "LATX_KZT_LOADER_EVENT_FORCE_PATTERN_MISMATCH", + '"LATX_AOT": "0"', + "UNKNOWN_BUILD_ID", + "PATTERN_MISMATCH", + "rollback=disabled", +): + if required not in runner: + raise AssertionError(f"WI-1065 runner misses {required}") + +print("WI-1065 real guest hook fixture contract: PASS") diff --git a/tests/unit/kzt/test_wi1097_real_guest_loader_identity.py b/tests/unit/kzt/test_wi1097_real_guest_loader_identity.py new file mode 100644 index 00000000000..91d48c2449e --- /dev/null +++ b/tests/unit/kzt/test_wi1097_real_guest_loader_identity.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +import argparse +import os +from pathlib import Path +import re +import subprocess +import sys + + +SCENARIOS = ("dependency-reopen", "namespace-isolation") +UNLOAD_RE = re.compile( + r"phase=unload link_map=0x([0-9a-f]+) generation=([0-9]+) " + r"namespace=0x([0-9a-f]+) result=(-?[0-9]+)" +) + + +def existing_file(value): + path = Path(value).resolve() + if not path.is_file(): + raise argparse.ArgumentTypeError(f"file not found: {path}") + return path + + +def existing_directory(value): + path = Path(value).resolve() + if not path.is_dir(): + raise argparse.ArgumentTypeError(f"directory not found: {path}") + return path + + +def run_scenario(args, scenario): + executable = args.fixture_dir / scenario + if not executable.is_file(): + raise RuntimeError(f"fixture not found: {executable}") + + command = [ + str(args.latx), + "-L", + str(args.guest_root), + str(executable), + ] + environment = os.environ.copy() + for name in list(environment): + if name.startswith("LATX_KZT"): + environment.pop(name) + environment.update({ + "LATX_KZT": "2", + "LATX_KZT_REGISTRY_DIAGNOSTICS": "1", + "LATX_KZT_LAZY_DIAGNOSTICS": "0", + "LD_LIBRARY_PATH": str(args.fixture_dir), + }) + completed = subprocess.run( + command, + cwd=args.fixture_dir, + env=environment, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + timeout=args.timeout, + check=False, + ) + args.log_dir.mkdir(parents=True, exist_ok=True) + log_path = args.log_dir / f"{scenario}.log" + log_path.write_text( + "command: " + " ".join(command) + "\n\n" + completed.stdout, + encoding="utf-8", + ) + if completed.returncode != 0: + raise AssertionError( + f"{scenario}: guest exited {completed.returncode}; log={log_path}" + ) + marker = f"WI600_GUEST_LOADER_PASS {scenario}" + if marker not in completed.stdout.splitlines(): + raise AssertionError(f"{scenario}: missing pass marker; log={log_path}") + unloads = [ + tuple(int(value, 16 if index in (0, 2) else 10) + for index, value in enumerate(match.groups())) + for match in UNLOAD_RE.finditer(completed.stdout) + ] + if any(result != 0 for _, _, _, result in unloads): + raise AssertionError(f"{scenario}: unload retire failed; log={log_path}") + return unloads, completed.stdout, log_path + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--latx", required=True, type=existing_file) + parser.add_argument("--guest-root", required=True, type=existing_directory) + parser.add_argument("--fixture-dir", required=True, type=existing_directory) + parser.add_argument("--log-dir", required=True, type=Path) + parser.add_argument("--timeout", type=float, default=20.0) + args = parser.parse_args() + + dependency, dependency_output, dependency_log = run_scenario( + args, "dependency-reopen" + ) + if "registry_result=3" in dependency_output: + raise AssertionError( + f"dependency-reopen: Registry conflict after reopen; " + f"log={dependency_log}" + ) + main_unloads = [entry for entry in dependency if entry[2] == 0] + generations = {entry[1] for entry in main_unloads} + if len(main_unloads) < 4 or len(generations) < 4: + raise AssertionError( + f"dependency-reopen: expected two exact unload rounds; " + f"log={dependency_log}" + ) + + namespace, _, namespace_log = run_scenario(args, "namespace-isolation") + namespace_unloads = [entry for entry in namespace if entry[2] != 0] + if not namespace_unloads: + raise AssertionError( + f"namespace-isolation: missing non-main unload; log={namespace_log}" + ) + + print( + "WI-1097 real guest loader identity: PASS " + f"dependency_unloads={len(main_unloads)} " + f"namespace_unloads={len(namespace_unloads)}" + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/unit/kzt/test_wi600_guest_loader_gate_harness.py b/tests/unit/kzt/test_wi600_guest_loader_gate_harness.py new file mode 100755 index 00000000000..24460091725 --- /dev/null +++ b/tests/unit/kzt/test_wi600_guest_loader_gate_harness.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 +import json +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + + +SCRIPT_DIR = Path(__file__).resolve().parent +RUNNER = SCRIPT_DIR / "test_real_guest_loader_gate.py" +SCENARIOS = ( + "dependency-reopen", + "visibility-noload", + "namespace-isolation", + "symbol-versions-errors", + "wrapped-library-handle", +) + + +class GuestLoaderGateHarnessTest(unittest.TestCase): + def setUp(self): + self.temporary_directory = tempfile.TemporaryDirectory() + self.addCleanup(self.temporary_directory.cleanup) + self.root = Path(self.temporary_directory.name) + self.fixture_dir = self.root / "fixture" + self.fixture_dir.mkdir() + for scenario in SCENARIOS: + executable = self.fixture_dir / scenario + executable.write_text("fake guest fixture\n", encoding="utf-8") + executable.chmod(0o755) + + self.guest_root = self.root / "guest-root" + loader = self.guest_root / "lib64" / "ld-linux-x86-64.so.2" + loader.parent.mkdir(parents=True) + loader.write_text("fake guest loader\n", encoding="utf-8") + + def write_fake_latx(self, name, actions=None): + path = self.root / name + action_map = repr(actions or {}) + path.write_text( + "#!/usr/bin/env python3\n" + "import os\n" + "from pathlib import Path\n" + "import sys\n" + "import time\n" + f"actions = {action_map}\n" + "scenario = Path(sys.argv[-1]).name\n" + "fixture = str(Path(sys.argv[-1]).parent)\n" + "if '-E' in sys.argv:\n" + " print('release-incompatible -E option used')\n" + " raise SystemExit(78)\n" + "if os.environ.get('LD_LIBRARY_PATH') != fixture:\n" + " print('guest LD_LIBRARY_PATH was not inherited')\n" + " raise SystemExit(79)\n" + "if os.environ.get('LATX_KZT') != '2':\n" + " print('KZT was not forced for the gate')\n" + " raise SystemExit(80)\n" + "candidate = 'candidate' in Path(sys.argv[0]).name\n" + "writer = os.environ.get('LATX_KZT_PATCH_SPIKE')\n" + "if candidate != (writer == '1'):\n" + " print('candidate writer environment mismatch')\n" + " raise SystemExit(81)\n" + "action = actions.get(scenario, 'pass')\n" + "if action == 'timeout':\n" + " time.sleep(10)\n" + "elif action == 'skip':\n" + " print('fake LATX skipped scenario')\n" + " raise SystemExit(77)\n" + "elif action == 'fail':\n" + " print('fake guest assertion failed')\n" + " raise SystemExit(42)\n" + "elif action == 'no-marker':\n" + " raise SystemExit(0)\n" + "print('WI600_GUEST_LOADER_PASS ' + scenario)\n", + encoding="utf-8", + ) + path.chmod(0o755) + return path + + def run_gate(self, baseline_actions=None, candidate_actions=None, + candidate_path=None, timeout="8"): + baseline = self.write_fake_latx("baseline-latx", baseline_actions) + if candidate_path is None: + candidate = self.write_fake_latx( + "candidate-latx", candidate_actions + ) + else: + candidate = candidate_path + report_path = self.root / "report.json" + completed = subprocess.run( + [ + sys.executable, + str(RUNNER), + "--baseline-latx", str(baseline), + "--candidate-latx", str(candidate), + "--guest-root", str(self.guest_root), + "--fixture-dir", str(self.fixture_dir), + "--timeout", timeout, + "--json-output", str(report_path), + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=20, + check=False, + ) + report = None + if report_path.is_file(): + report = json.loads(report_path.read_text(encoding="utf-8")) + return completed, report + + def scenario(self, report, scenario_id): + return next( + item for item in report["scenarios"] + if item["id"] == scenario_id + ) + + def test_all_p0_scenarios_pass_with_table_and_json(self): + completed, report = self.run_gate() + + self.assertEqual( + completed.returncode, 0, f"{completed.stderr}\n{report}" + ) + self.assertEqual(report["status"], "PASS") + self.assertEqual(len(report["scenarios"]), len(SCENARIOS)) + for scenario in SCENARIOS: + self.assertIn(scenario, completed.stdout) + result = self.scenario(report, scenario) + self.assertEqual(result["baseline"]["status"], "PASS") + self.assertEqual(result["candidate"]["status"], "PASS") + self.assertEqual(result["status"], "PASS") + + def test_baseline_pass_candidate_failure_is_a_regression(self): + completed, report = self.run_gate( + candidate_actions={"dependency-reopen": "fail"} + ) + + self.assertEqual(completed.returncode, 1) + self.assertEqual(report["status"], "FAIL") + result = self.scenario(report, "dependency-reopen") + self.assertEqual(result["status"], "FAIL") + self.assertTrue(result["regression"]) + self.assertIn("baseline passed", result["reason"]) + + def test_candidate_failure_is_fail_when_baseline_also_fails(self): + actions = {"visibility-noload": "fail"} + completed, report = self.run_gate( + baseline_actions=actions, + candidate_actions=actions, + ) + + self.assertEqual(completed.returncode, 1) + self.assertEqual(report["status"], "FAIL") + result = self.scenario(report, "visibility-noload") + self.assertEqual(result["candidate"]["status"], "FAIL") + self.assertEqual(result["status"], "FAIL") + self.assertFalse(result["regression"]) + + def test_zero_exit_without_guest_pass_marker_is_fail(self): + completed, report = self.run_gate( + candidate_actions={"namespace-isolation": "no-marker"} + ) + + self.assertEqual(completed.returncode, 1) + result = self.scenario(report, "namespace-isolation") + self.assertEqual(result["candidate"]["status"], "FAIL") + self.assertIn("pass marker", result["candidate"]["reason"]) + + def test_timeout_is_inconclusive_and_cannot_pass(self): + completed, report = self.run_gate( + candidate_actions={"symbol-versions-errors": "timeout"}, + timeout="0.05", + ) + + self.assertEqual(completed.returncode, 2) + self.assertEqual(report["status"], "INCONCLUSIVE") + result = self.scenario(report, "symbol-versions-errors") + self.assertEqual(result["candidate"]["status"], "INCONCLUSIVE") + self.assertIn("timed out", result["candidate"]["reason"]) + + def test_skip_is_inconclusive_and_cannot_pass(self): + completed, report = self.run_gate( + candidate_actions={"dependency-reopen": "skip"} + ) + + self.assertEqual(completed.returncode, 2) + self.assertEqual(report["status"], "INCONCLUSIVE") + result = self.scenario(report, "dependency-reopen") + self.assertEqual(result["candidate"]["status"], "INCONCLUSIVE") + self.assertIn("skip", result["candidate"]["reason"]) + + def test_missing_latx_is_inconclusive_and_still_writes_report(self): + completed, report = self.run_gate( + candidate_path=self.root / "missing-candidate-latx" + ) + + self.assertEqual(completed.returncode, 2) + self.assertEqual(report["status"], "INCONCLUSIVE") + for result in report["scenarios"]: + self.assertEqual( + result["candidate"]["status"], "INCONCLUSIVE" + ) + self.assertIn("not found", result["candidate"]["reason"]) + + def test_missing_guest_root_is_inconclusive(self): + self.guest_root = self.root / "missing-guest-root" + + completed, report = self.run_gate() + + self.assertEqual(completed.returncode, 2) + self.assertEqual(report["status"], "INCONCLUSIVE") + for result in report["scenarios"]: + self.assertEqual(result["baseline"]["status"], "INCONCLUSIVE") + self.assertEqual(result["candidate"]["status"], "INCONCLUSIVE") + self.assertIn( + "guest root directory not found", + result["candidate"]["reason"], + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/kzt/test_wi601_perf_fixture_contract.py b/tests/unit/kzt/test_wi601_perf_fixture_contract.py new file mode 100644 index 00000000000..6ab4a9556fb --- /dev/null +++ b/tests/unit/kzt/test_wi601_perf_fixture_contract.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""WI-601 contract for the isolated real-guest performance fixture.""" + +import pathlib +import sys + + +root = pathlib.Path(sys.argv[1]).resolve() +script = (root / "tests/unit/kzt/guest_e2e/build_guest_probe.sh").read_text( + encoding="utf-8" +) +perf_main = (root / "tests/unit/kzt/guest_e2e/kzt_guest_perf_main.c").read_text( + encoding="utf-8" +) +perf_probe = (root / "tests/unit/kzt/guest_e2e/kzt_guest_perf_probe.c").read_text( + encoding="utf-8" +) + +perf_main_start = script.rfind( + "run_cc", 0, script.index('"$script_dir/kzt_guest_perf_start.S"') +) +perf_main_command = script[perf_main_start:script.index( + "-o \"$build_dir/kzt_guest_perf_main\"", perf_main_start +)] +if "-fno-plt" not in perf_main_command: + raise AssertionError("performance main must use -fno-plt") + +for required in ( + "perf_jump_slot_count=$(grep -Ec", + "if [[ $perf_jump_slot_count -ne 1 ]]; then", + "perf_versioned_dlerror_jump_slots=$(grep -Ec", + "${e2e_symbol}@GLIBC_", + "if [[ $perf_versioned_dlerror_jump_slots -ne 1 ]]; then", + "perf_main_jump_slots=$(grep -Ec", + "if [[ $perf_main_jump_slots -ne 0 ]]; then", +): + if required not in script: + raise AssertionError(f"missing performance fixture check: {required}") + +for mode in ("startup", "first", "steady"): + if f'"{mode}"' not in perf_main: + raise AssertionError(f"performance main does not expose {mode} mode") +for required in ( + "kzt_guest_perf_first", + "kzt_guest_perf_steady", + "dlerror() != NULL", +): + if required not in perf_probe: + raise AssertionError(f"performance probe is missing {required}") +if "checksum += index + 1" in perf_probe: + raise AssertionError("performance checksum must derive from dlerror result") + +print("WI-601 performance fixture contract: PASS") diff --git a/tests/unit/kzt/test_wi601_real_guest_e2e_launcher.py b/tests/unit/kzt/test_wi601_real_guest_e2e_launcher.py new file mode 100644 index 00000000000..985de370e66 --- /dev/null +++ b/tests/unit/kzt/test_wi601_real_guest_e2e_launcher.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +import subprocess +import tempfile +from pathlib import Path +from types import SimpleNamespace +import unittest +from unittest import mock + +import test_real_guest_e2e as runner + + +SUCCESS = ( + "KZT_GUEST_E2E_OK calls=2 slot=0x1000 before=0x2000 " + "after_first=0x3000 after_second=0x3000 first_ns=0x1 second_ns=0x1\n" +) + + +def scenario_output(scenario): + lines = [ + SUCCESS.rstrip(), + "kzt_lazy_resolver_entry symbol=dlerror", + "kzt_lazy_preemption schema=1 symbol=dlerror version=GLIBC_2.34 " + f"candidate_count={scenario['candidate_count']} " + "scope_complete=1 lookup_order_known=1 " + f"reason={scenario['reason']}", + ] + if scenario["direct"]: + lines.extend( + [ + "kzt_lazy_direct schema=1 symbol=dlerror " + "route_status=NATIVE_APPLIED writer_result=APPLIED", + "kzt_lazy_path schema=1 symbol=dlerror route=NEW_DIRECT " + "guest_handoff=0 legacy_lookup=0 legacy_write=0", + ] + ) + else: + lines.extend( + [ + "kzt_lazy_path schema=1 symbol=dlerror route=GUEST_LD_SO " + "guest_handoff=1 legacy_lookup=0 legacy_write=0", + scenario["marker"], + scenario["marker"], + "kzt_lazy_diagnostic symbol=dlerror", + ] + ) + return "\n".join(lines) + "\n" + + +class WI601RealGuestE2ELauncherTest(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + root = Path(temporary.name) + fixture_dir = root / "fixture" + fixture_dir.mkdir() + log_dir = root / "logs" + log_dir.mkdir() + self.args = SimpleNamespace( + latx=root / "latx-x86_64", + guest_root=root / "guest-root", + fixture_dir=fixture_dir, + log_dir=log_dir, + timeout=1.0, + ) + + def test_direct_and_guest_handoff_use_real_scenario_commands(self): + calls = [] + + def fake_run(command, **kwargs): + calls.append((command, kwargs)) + executable = Path(command[-1]).name + scenario = next( + item + for item in runner.SCENARIOS + if item["executable"] == executable + ) + return subprocess.CompletedProcess( + command, 0, scenario_output(scenario) + ) + + with mock.patch.object( + runner.preemption.subprocess, "run", side_effect=fake_run + ): + for scenario in runner.SCENARIOS: + runner.run_scenario(self.args, scenario) + + self.assertEqual(len(calls), 2) + for scenario, (command, kwargs) in zip(runner.SCENARIOS, calls): + self.assertEqual( + command, + [ + str(self.args.latx), + "-L", + str(self.args.guest_root), + str(self.args.fixture_dir / scenario["executable"]), + ], + ) + self.assertEqual( + kwargs["env"]["LD_LIBRARY_PATH"], + str(self.args.fixture_dir), + ) + + def test_direct_requires_one_new_path_and_zero_legacy_activity(self): + direct = runner.SCENARIOS[0] + output = scenario_output(direct) + invalid_outputs = ( + output.replace("kzt_lazy_direct schema=1", "missing_direct"), + output.replace("legacy_lookup=0", "legacy_lookup=1"), + output.replace("legacy_write=0", "legacy_write=1"), + ) + + with mock.patch.object( + runner.preemption.subprocess, + "run", + side_effect=[ + subprocess.CompletedProcess([], 0, item) + for item in invalid_outputs + ], + ): + with self.assertRaisesRegex(RuntimeError, "native direct apply"): + runner.run_scenario(self.args, direct) + with self.assertRaisesRegex(RuntimeError, "legacy host lookup"): + runner.run_scenario(self.args, direct) + with self.assertRaisesRegex(RuntimeError, "legacy GOT writer"): + runner.run_scenario(self.args, direct) + + def test_guest_fallback_hands_off_once_to_selected_provider(self): + fallback = runner.SCENARIOS[1] + output = scenario_output(fallback) + invalid_outputs = ( + output.replace("guest_handoff=1", "guest_handoff=0"), + output.replace( + "KZT_PREEMPT_PROVIDER_A", "KZT_PREEMPT_PROVIDER_B" + ), + ) + + with mock.patch.object( + runner.preemption.subprocess, + "run", + side_effect=[ + subprocess.CompletedProcess([], 0, item) + for item in invalid_outputs + ], + ): + with self.assertRaisesRegex(RuntimeError, "hand off exactly once"): + runner.run_scenario(self.args, fallback) + with self.assertRaisesRegex( + RuntimeError, "selected guest provider" + ): + runner.run_scenario(self.args, fallback) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/kzt/test_wi849_real_guest_preemption.py b/tests/unit/kzt/test_wi849_real_guest_preemption.py new file mode 100644 index 00000000000..a55344c7f47 --- /dev/null +++ b/tests/unit/kzt/test_wi849_real_guest_preemption.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python3 +import argparse +import os +from pathlib import Path +import re +import subprocess +import sys + + +TARGET_SYMBOL = "dlerror" +SUCCESS_PATTERN = re.compile( + r"KZT_GUEST_E2E_OK calls=2 slot=(0x[0-9a-f]+) " + r"before=(0x[0-9a-f]+) after_first=(0x[0-9a-f]+) " + r"after_second=(0x[0-9a-f]+) first_ns=(0x[0-9a-f]+) " + r"second_ns=(0x[0-9a-f]+)" +) +SCENARIOS = ( + { + "name": "unique", + "executable": "kzt_guest_main", + "marker": None, + "candidate_count": 1, + "direct": True, + "reason": "SELECTED_PROVIDER", + }, + { + "name": "strong-one", + "executable": "kzt_guest_preempt_a_main", + "marker": "KZT_PREEMPT_PROVIDER_A", + "candidate_count": 2, + "direct": False, + "reason": "SELECTED_PROVIDER", + }, + { + "name": "strong-two", + "executable": "kzt_guest_preempt_ab_main", + "marker": "KZT_PREEMPT_PROVIDER_A", + "candidate_count": 3, + "direct": False, + "reason": "SELECTED_PROVIDER", + }, + { + "name": "weak-first", + "executable": "kzt_guest_preempt_weak_main", + "marker": "KZT_PREEMPT_PROVIDER_WEAK", + "candidate_count": 2, + "direct": False, + "reason": "UNSUPPORTED_PROVIDER_BINDING", + }, + { + "name": "rtld-local-isolation", + "executable": "kzt_guest_local_scope_main", + "marker": "KZT_PREEMPT_PROVIDER_A", + "candidate_count": 2, + "direct": False, + "reason": "SELECTED_PROVIDER", + "scope_ready_marker": "KZT_LOCAL_GROUP_READY", + }, +) + + +def existing_directory(value): + path = Path(value).resolve() + if not path.is_dir(): + raise argparse.ArgumentTypeError(f"Directory does not exist: {path}") + return path + + +def existing_file(value): + path = Path(value).resolve() + if not path.is_file(): + raise argparse.ArgumentTypeError(f"File does not exist: {path}") + return path + + +def parse_record(line): + record = {} + for field in line.split(): + if "=" in field: + key, value = field.split("=", 1) + record[key] = value + return record + + +def symbol_records(output, prefix): + records = [] + for line in output.splitlines(): + marker = line.find(prefix) + if marker < 0: + continue + record = parse_record(line[marker:]) + if record.get("symbol") == TARGET_SYMBOL: + records.append(record) + return records + + +def require(condition, message, output): + if condition: + return + raise RuntimeError(f"{message}\n--- guest output ---\n{output.rstrip()}") + + +def scenario_environment(fixture_dir): + environment = os.environ.copy() + for name in list(environment): + if name.startswith("LATX_KZT"): + environment.pop(name) + for name in ( + "LD_AUDIT", + "LD_BIND_NOW", + "LD_DEBUG", + "LD_DEBUG_OUTPUT", + "LD_DYNAMIC_WEAK", + "LD_LIBRARY_PATH", + "LD_PRELOAD", + "LD_PROFILE", + ): + environment.pop(name, None) + environment.update({ + "LD_LIBRARY_PATH": str(fixture_dir), + "LATX_AOT": "0", + "LATX_KZT": "1", + "LATX_KZT_REGISTRY_DIAGNOSTICS": "1", + "LATX_KZT_LAZY_DIAGNOSTICS": "1", + "LATX_KZT_PATCH_SPIKE": "1", + "LATX_KZT_PATCH_SPIKE_WRITE": "1", + "LATX_KZT_PATCH_SPIKE_BUDGET": "1", + }) + return environment + + +def run_scenario(args, scenario): + command = [ + str(args.latx), + "-L", + str(args.guest_root), + str(args.fixture_dir / scenario["executable"]), + ] + completed = subprocess.run( + command, + env=scenario_environment(args.fixture_dir), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + timeout=args.timeout, + check=False, + ) + output = completed.stdout + log_path = args.log_dir / f"wi849-{scenario['name']}.log" + log_path.write_text(output, encoding="utf-8") + + require(completed.returncode == 0, + f"{scenario['name']} exited with {completed.returncode}.", output) + success_matches = SUCCESS_PATTERN.findall(output) + require(len(success_matches) == 1, + f"{scenario['name']} did not finish the guest probe.", output) + slot, before, after_first, after_second, _, _ = ( + int(value, 16) for value in success_matches[0] + ) + publication_records = [ + parse_record(line[line.find("kzt_lazy_prebind_publish "):]) + for line in output.splitlines() + if "kzt_lazy_prebind_publish " in line + ] + prebound = scenario["direct"] and any( + record.get("result") == "APPLIED" for record in publication_records + ) + if prebound: + require(slot != 0 and before != 0 and after_first == before, + "The unique provider did not retain its prebound GOT slot.", + output) + else: + require(slot != 0 and before != 0 and after_first != before, + f"{scenario['name']} did not resolve the lazy GOT slot.", output) + require(after_second == after_first, + f"{scenario['name']} changed the GOT slot on the second call.", + output) + + if scenario.get("scope_ready_marker"): + lines = output.splitlines() + ready_lines = [ + index for index, line in enumerate(lines) + if line == scenario["scope_ready_marker"] + ] + decision_lines = [ + index for index, line in enumerate(lines) + if "kzt_lazy_preemption " in line and + "symbol=dlerror" in line + ] + require(len(ready_lines) == 1 and len(decision_lines) == 1 and + ready_lines[0] < decision_lines[0], + "KZT decision was not made after the RTLD_LOCAL group load.", + output) + + resolver_entries = symbol_records(output, "kzt_lazy_resolver_entry ") + path_records = symbol_records(output, "kzt_lazy_path ") + preemption_records = symbol_records(output, "kzt_lazy_preemption ") + direct_records = symbol_records(output, "kzt_lazy_direct ") + if prebound: + require(not resolver_entries and not path_records and + not preemption_records and not direct_records, + "The prebound unique provider re-entered lazy resolution.", + output) + else: + require(len(resolver_entries) == 1, + f"{scenario['name']} must enter the resolver exactly once.", output) + require(len(path_records) == 1, + f"{scenario['name']} must emit exactly one lazy path.", output) + path = path_records[0] + require(path.get("legacy_lookup") == "0", + f"{scenario['name']} used the removed legacy host lookup.", output) + require(path.get("legacy_write") == "0", + f"{scenario['name']} used the removed legacy GOT writer.", output) + require(len(preemption_records) == 1, + f"{scenario['name']} must emit one preemption decision.", output) + decision = preemption_records[0] + require(decision.get("scope_complete") == "1", + f"{scenario['name']} has incomplete lookup scope evidence.", output) + require(decision.get("lookup_order_known") == "1", + f"{scenario['name']} has unknown lookup order.", output) + require(int(decision.get("candidate_count", "0"), 0) == + scenario["candidate_count"], + f"{scenario['name']} reported the wrong visible definition count.", + output) + + if scenario["direct"] and not prebound: + require(path.get("route") == "NEW_DIRECT", + "The unique provider reported the wrong lazy path.", output) + require(path.get("guest_handoff") == "0", + "The unique provider unexpectedly entered guest ld.so.", output) + require(len(direct_records) == 1, + "The unique provider must use native direct apply.", output) + require(decision.get("reason") == scenario["reason"], + f"{scenario['name']} lacks an approval reason.", output) + elif not prebound: + require(path.get("route") == "GUEST_LD_SO", + f"{scenario['name']} did not report guest ld.so handoff.", + output) + require(path.get("guest_handoff") == "1", + f"{scenario['name']} must hand off exactly once to guest ld.so.", + output) + require(not direct_records, + f"{scenario['name']} must not use native direct apply.", output) + require(decision.get("reason") == scenario["reason"], + f"{scenario['name']} lacks a fail-open reason.", output) + require(output.count(scenario["marker"] + "\n") == 2, + f"{scenario['name']} did not execute the selected guest " + "provider twice.", output) + other_markers = { + "KZT_PREEMPT_PROVIDER_A", + "KZT_PREEMPT_PROVIDER_B", + "KZT_PREEMPT_PROVIDER_WEAK", + "KZT_LOCAL_PREEMPT_PROVIDER", + } - {scenario["marker"]} + require(all(marker not in output for marker in other_markers), + f"{scenario['name']} executed an unselected provider.", output) + lazy_records = symbol_records(output, "kzt_lazy_diagnostic ") + require(len(lazy_records) == 1, + f"{scenario['name']} must complete one guest fallback.", output) + + require("KZT_LOCAL_PREEMPT_PROVIDER" not in output, + f"{scenario['name']} executed the unrelated RTLD_LOCAL provider.", + output) + + return log_path + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Run KZT first-bind symbol preemption scenarios." + ) + parser.add_argument("--latx", required=True, type=existing_file) + parser.add_argument("--guest-root", required=True, type=existing_directory) + parser.add_argument("--fixture-dir", required=True, type=existing_directory) + parser.add_argument("--log-dir", type=Path) + parser.add_argument("--timeout", type=float, default=30.0) + args = parser.parse_args() + args.log_dir = ( + args.log_dir.resolve() if args.log_dir + else args.fixture_dir / "wi849-logs" + ) + args.log_dir.mkdir(parents=True, exist_ok=True) + for name in ( + "kzt_guest_main", + "kzt_guest_preempt_a_main", + "kzt_guest_preempt_ab_main", + "kzt_guest_preempt_weak_main", + "kzt_guest_local_scope_main", + "libkzt_preempt_a.so", + "libkzt_preempt_b.so", + "libkzt_preempt_weak.so", + "libkzt_local_preempt.so", + ): + if not (args.fixture_dir / name).is_file(): + parser.error(f"Fixture is missing {name}.") + return args + + +def main(): + args = parse_args() + logs = [run_scenario(args, scenario) for scenario in SCENARIOS] + print("KZT WI-849 real guest preemption: PASS") + for log in logs: + print(f"log: {log}") + + +if __name__ == "__main__": + try: + main() + except (RuntimeError, subprocess.TimeoutExpired) as error: + print(f"KZT WI-849 real guest preemption: FAIL: {error}", + file=sys.stderr) + sys.exit(1) diff --git a/tests/unit/kzt/test_wi849_real_guest_preemption_launcher.py b/tests/unit/kzt/test_wi849_real_guest_preemption_launcher.py new file mode 100644 index 00000000000..279c331cdbf --- /dev/null +++ b/tests/unit/kzt/test_wi849_real_guest_preemption_launcher.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +import subprocess +import tempfile +from pathlib import Path +from types import SimpleNamespace +import unittest +from unittest import mock + +import test_wi849_real_guest_preemption as runner + + +SUCCESS = ( + "KZT_GUEST_E2E_OK calls=2 slot=0x1000 before=0x2000 " + "after_first=0x3000 after_second=0x3000 first_ns=0x1 second_ns=0x1\n" +) + + +def scenario_output(scenario): + lines = [ + SUCCESS.rstrip(), + "kzt_lazy_resolver_entry symbol=dlerror", + "kzt_lazy_preemption symbol=dlerror version=GLIBC_2.34 " + f"candidate_count={scenario['candidate_count']} " + "scope_complete=1 lookup_order_known=1 " + f"reason={scenario['reason']}", + ] + if scenario.get("scope_ready_marker"): + lines.insert(0, scenario["scope_ready_marker"]) + if scenario["direct"]: + lines.extend([ + "kzt_lazy_path schema=1 symbol=dlerror route=NEW_DIRECT " + "guest_handoff=0 legacy_lookup=0 legacy_write=0", + "kzt_lazy_direct symbol=dlerror route_status=NATIVE_APPLIED", + ]) + else: + lines.extend([ + "kzt_lazy_path schema=1 symbol=dlerror route=GUEST_LD_SO " + "guest_handoff=1 legacy_lookup=0 legacy_write=0", + scenario["marker"], + scenario["marker"], + "kzt_lazy_diagnostic symbol=dlerror", + ]) + return "\n".join(lines) + "\n" + + +class WI849RealGuestPreemptionLauncherTest(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + root = Path(temporary.name) + fixture_dir = root / "fixture" + fixture_dir.mkdir() + log_dir = root / "logs" + log_dir.mkdir() + self.args = SimpleNamespace( + latx=root / "latx-x86_64", + guest_root=root / "guest-root", + fixture_dir=fixture_dir, + log_dir=log_dir, + timeout=1.0, + ) + + def test_all_scenarios_use_the_expected_guest_executable(self): + calls = [] + + def fake_run(command, **kwargs): + calls.append((command, kwargs)) + executable = Path(command[-1]).name + scenario = next( + item for item in runner.SCENARIOS + if item["executable"] == executable + ) + return subprocess.CompletedProcess( + command, 0, scenario_output(scenario) + ) + + with mock.patch.object(runner.subprocess, "run", side_effect=fake_run): + for scenario in runner.SCENARIOS: + runner.run_scenario(self.args, scenario) + + self.assertEqual(len(calls), len(runner.SCENARIOS)) + for scenario, (command, kwargs) in zip(runner.SCENARIOS, calls): + self.assertEqual( + command[-1], + str(self.args.fixture_dir / scenario["executable"]), + ) + self.assertEqual( + kwargs["env"]["LD_LIBRARY_PATH"], + str(self.args.fixture_dir), + ) + self.assertNotIn("LD_DYNAMIC_WEAK", kwargs["env"]) + self.assertNotIn("LD_PRELOAD", kwargs["env"]) + + def test_rejects_unstable_got_and_wrong_provider(self): + strong = runner.SCENARIOS[1] + unstable = scenario_output(strong).replace( + "after_second=0x3000", "after_second=0x4000" + ) + wrong_provider = scenario_output(strong).replace( + "KZT_PREEMPT_PROVIDER_A", "KZT_PREEMPT_PROVIDER_B" + ) + outputs = iter((unstable, wrong_provider)) + + def fake_run(command, **kwargs): + return subprocess.CompletedProcess(command, 0, next(outputs)) + + with mock.patch.object(runner.subprocess, "run", side_effect=fake_run): + with self.assertRaisesRegex( + RuntimeError, "changed the GOT slot" + ): + runner.run_scenario(self.args, strong) + with self.assertRaisesRegex( + RuntimeError, "did not execute the selected guest provider" + ): + runner.run_scenario(self.args, strong) + + def test_unique_provider_requires_one_direct_record(self): + unique = runner.SCENARIOS[0] + missing_direct = scenario_output(unique).replace( + "kzt_lazy_direct symbol=dlerror route_status=NATIVE_APPLIED\n", "" + ) + + with mock.patch.object( + runner.subprocess, + "run", + return_value=subprocess.CompletedProcess([], 0, missing_direct), + ): + with self.assertRaisesRegex( + RuntimeError, "unique provider must use native direct apply" + ): + runner.run_scenario(self.args, unique) + + def test_rejects_missing_or_duplicate_lazy_path(self): + strong = runner.SCENARIOS[1] + path = ( + "kzt_lazy_path schema=1 symbol=dlerror route=GUEST_LD_SO " + "guest_handoff=1 legacy_lookup=0 legacy_write=0\n" + ) + missing = scenario_output(strong).replace(path, "") + duplicate = scenario_output(strong).replace(path, path + path) + outputs = iter((missing, duplicate)) + + def fake_run(command, **kwargs): + return subprocess.CompletedProcess(command, 0, next(outputs)) + + with mock.patch.object(runner.subprocess, "run", side_effect=fake_run): + for expected in ("exactly one lazy path", "exactly one lazy path"): + with self.assertRaisesRegex(RuntimeError, expected): + runner.run_scenario(self.args, strong) + + def test_rejects_legacy_activity_and_wrong_guest_handoff(self): + strong = runner.SCENARIOS[1] + legacy_lookup = scenario_output(strong).replace( + "legacy_lookup=0", "legacy_lookup=1" + ) + wrong_handoff = scenario_output(strong).replace( + "guest_handoff=1", "guest_handoff=0" + ) + outputs = iter((legacy_lookup, wrong_handoff)) + + def fake_run(command, **kwargs): + return subprocess.CompletedProcess(command, 0, next(outputs)) + + with mock.patch.object(runner.subprocess, "run", side_effect=fake_run): + with self.assertRaisesRegex(RuntimeError, "legacy host lookup"): + runner.run_scenario(self.args, strong) + with self.assertRaisesRegex(RuntimeError, "hand off exactly once"): + runner.run_scenario(self.args, strong) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/meson.build b/tests/unit/meson.build index d0b0e20625a..aca50865f33 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -20,6 +20,15 @@ kzt_guest_registry_test = executable( test('kzt-guest-registry', kzt_guest_registry_test) +test( + 'kzt-wi1009-lazy-slot-bridge-removal-contract', + find_program('python3'), + args: [ + files('kzt/test_wi1009_lazy_slot_bridge_context_contract.py'), + meson.project_source_root(), + ], +) + kzt_guest_registry_concurrency_test = executable( 'kzt-guest-registry-concurrency', files( @@ -33,6 +42,122 @@ kzt_guest_registry_concurrency_test = executable( test('kzt-guest-registry-concurrency', kzt_guest_registry_concurrency_test) +kzt_guest_registry_patch_decision_lease_test = executable( + 'kzt-guest-registry-patch-decision-lease', + files( + 'kzt/test_guest_registry_patch_decision_lease.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-guest-registry-patch-decision-lease', + kzt_guest_registry_patch_decision_lease_test) + +kzt_guest_registry_got_plt_injection_test = executable( + 'kzt-guest-registry-got-plt-injection', + files( + 'kzt/test_guest_registry_got_plt_injection.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-guest-registry-got-plt-injection', + kzt_guest_registry_got_plt_injection_test) + +kzt_per_object_got_plt_test = executable( + 'kzt-per-object-got-plt', + files( + 'kzt/test_per_object_got_plt.c', + '../../target/i386/latx/context/kzt_per_object_got_plt.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-per-object-got-plt', kzt_per_object_got_plt_test) + +if 'x86_64-linux-user' in target_dirs + kzt_bridge_concurrency_test = executable( + 'kzt-bridge-concurrency', + files( + 'kzt/test_bridge_concurrency.c', + '../../target/i386/latx/context/bridge.c', + ), + c_args: [ + '-DBRIDGE_TEST', + '-DNEED_CPU_H', + '-DCONFIG_TARGET="x86_64-linux-user-config-target.h"', + '-DCONFIG_DEVICES="x86_64-linux-user-config-devices.h"', + ], + include_directories: include_directories( + '../..', + '../../target/i386', + '../../linux-user/host/loongarch', + '../../linux-user', + '../../linux-user/x86_64', + '../../target/i386/latx/include', + ), + dependencies: [dependency('threads'), cc.find_library('dl')], + ) + + test('kzt-bridge-concurrency', kzt_bridge_concurrency_test) + + kzt_bridge_atfork_fail_open_test = executable( + 'kzt-bridge-atfork-fail-open', + files( + 'kzt/test_bridge_atfork_fail_open.c', + 'kzt/kzt_test_options.c', + '../../target/i386/latx/context/bridge.c', + '../../target/i386/latx/context/kzt_rela_runtime_bridge.c', + '../../target/i386/latx/context/kzt_wrapper_bridge_provider.c', + '../../target/i386/latx/context/kzt_wrapper_probe.c', + '../../target/i386/latx/context/kzt_bridge_exact.c', + ), + c_args: [ + '-DBRIDGE_TEST', + '-DBRIDGE_TEST_ATFORK_FAIL', + '-DCONFIG_LATX_KZT', + '-DNEED_CPU_H', + '-DCONFIG_TARGET="x86_64-linux-user-config-target.h"', + '-DCONFIG_DEVICES="x86_64-linux-user-config-devices.h"', + ], + include_directories: include_directories( + '../..', + '../../target/i386', + '../../linux-user/host/loongarch', + '../../linux-user', + '../../linux-user/x86_64', + '../../target/i386/latx/include', + ), + dependencies: [dependency('threads'), cc.find_library('dl')], + ) + + test('kzt-bridge-atfork-fail-open', kzt_bridge_atfork_fail_open_test) + + test('kzt-bridge-atfork-diagnostics-off', + kzt_bridge_atfork_fail_open_test, args: '--diagnostics-off') + + test( + 'kzt-wi1100-atfork-bridge-gate-source-contract', + find_program('python3'), + args: [ + files('kzt/test_wi1100_atfork_bridge_gate_source_contract.py'), + meson.project_source_root(), + ], + ) + + test('kzt-bridge-performance', kzt_bridge_concurrency_test, + args: '--benchmark', timeout: 90, is_parallel: false) +endif + kzt_guest_registry_context_test = executable( 'kzt-guest-registry-context', files( @@ -52,6 +177,7 @@ kzt_guest_library_binding_test = executable( files( 'kzt/test_guest_library_binding.c', '../../target/i386/latx/context/kzt_guest_library_binding.c', + '../../target/i386/latx/context/kzt_lifecycle_diagnostics.c', '../../target/i386/latx/context/kzt_guest_registry.c', ), c_args: ['-DKZT_GUEST_LIBRARY_BINDING_TEST', '-DKZT_GUEST_REGISTRY_TEST'], @@ -66,6 +192,7 @@ kzt_guest_library_binding_teardown_test = executable( files( 'kzt/test_guest_library_binding_teardown.c', '../../target/i386/latx/context/kzt_guest_library_binding.c', + '../../target/i386/latx/context/kzt_lifecycle_diagnostics.c', '../../target/i386/latx/context/kzt_guest_registry.c', ), c_args: ['-DKZT_GUEST_LIBRARY_BINDING_TEST', '-DKZT_GUEST_REGISTRY_TEST'], @@ -76,6 +203,69 @@ kzt_guest_library_binding_teardown_test = executable( test('kzt-guest-library-binding-teardown', kzt_guest_library_binding_teardown_test) +kzt_guest_library_adapter_test = executable( + 'kzt-guest-library-adapter', + files( + 'kzt/test_guest_library_adapter.c', + '../../target/i386/latx/context/kzt_guest_library_adapter.c', + ), + c_args: [ + '-DCONFIG_LATX_KZT', + '-DKZT_GUEST_LIBRARY_ADAPTER_TEST', + ], + include_directories: include_directories( + '../..', + '../../target/i386/latx/include', + ), +) + +test('kzt-guest-library-adapter', kzt_guest_library_adapter_test) + +kzt_guest_dl_api_test = executable( + 'kzt-guest-dl-api', + files( + 'kzt/test_guest_dl_api.c', + '../../target/i386/latx/context/kzt_guest_dl_api.c', + '../../target/i386/latx/context/kzt_guest_dl_init.c', + '../../target/i386/latx/context/kzt_lifecycle_diagnostics.c', + '../../target/i386/latx/context/kzt_lazy_prebind_scope.c', + ), + c_args: ['-DCONFIG_LATX_KZT'], + include_directories: include_directories( + '../..', + '../../target/i386/latx/include', + ), + dependencies: dependency('threads'), +) + +test('kzt-guest-dl-api', kzt_guest_dl_api_test) + +kzt_guest_dlclose_quiescence_test = executable( + 'kzt-guest-dlclose-quiescence', + files( + 'kzt/test_guest_dlclose_quiescence.c', + '../../target/i386/latx/context/kzt_guest_dl_api.c', + '../../target/i386/latx/context/kzt_guest_library_binding.c', + ), + c_args: [ + '-DCONFIG_LATX_KZT', + '-DKZT_GUEST_LIBRARY_BINDING_TEST', + '-ffunction-sections', + '-fdata-sections', + ], + link_args: [ + '-Wl,--gc-sections', + '-Wl,--no-export-dynamic', + ], + include_directories: include_directories( + '../..', + '../../target/i386/latx/include', + ), + dependencies: dependency('threads'), +) + +test('kzt-guest-dlclose-quiescence', kzt_guest_dlclose_quiescence_test) + kzt_dlopen_recycle_transaction_test = executable( 'kzt-dlopen-recycle-transaction', files( @@ -83,6 +273,7 @@ kzt_dlopen_recycle_transaction_test = executable( '../../target/i386/latx/context/dlopen_recycle_transaction.c', '../../target/i386/latx/context/elf_plt_relocation.c', '../../target/i386/latx/context/kzt_guest_library_binding.c', + '../../target/i386/latx/context/kzt_lifecycle_diagnostics.c', '../../target/i386/latx/context/kzt_guest_registry.c', ), c_args: ['-DKZT_GUEST_LIBRARY_BINDING_TEST'], @@ -94,6 +285,23 @@ test('kzt-dlopen-recycle-transaction', kzt_dlopen_recycle_transaction_test) python3 = find_program('python3') +test( + 'kzt-wi1021-dlerror-entry-source-contract', + python3, + args: [ + files('kzt/test_wi1021_dlerror_entry_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1098-thread-local-dl-state-source-contract', + python3, + args: [ + files('kzt/test_wi1098_thread_local_dl_state_source_contract.py'), + meson.source_root(), + ], +) test( 'kzt-wi382-registry-observation-source-contract', python3, @@ -112,6 +320,45 @@ test( ], ) +test( + 'kzt-wi600-guest-loader-gate-harness', + python3, + args: files('kzt/test_wi600_guest_loader_gate_harness.py'), +) + +test( + 'kzt-real-guest-performance-harness', + python3, + args: files('kzt/test_real_guest_harness.py'), +) + +test( + 'kzt-real-guest-loader-performance', + python3, + args: files('kzt/test_real_guest_loader_performance.py'), +) + +test( + 'kzt-WI-601-real-guest-e2e-launcher', + python3, + args: files('kzt/test_wi601_real_guest_e2e_launcher.py'), +) + +test( + 'kzt-WI-601-performance-fixture-contract', + python3, + args: [ + files('kzt/test_wi601_perf_fixture_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-WI-849-real-guest-preemption-launcher', + python3, + args: files('kzt/test_wi849_real_guest_preemption_launcher.py'), +) + kzt_guest_link_map_reader_test = executable( 'kzt-guest-link-map-reader', files( @@ -124,6 +371,31 @@ kzt_guest_link_map_reader_test = executable( test('kzt-guest-link-map-reader', kzt_guest_link_map_reader_test) +kzt_guest_dynsym_lookup_test = executable( + 'kzt-guest-dynsym-lookup', + files( + 'kzt/test_guest_dynsym_lookup.c', + '../../target/i386/latx/context/kzt_guest_dynsym_lookup.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-guest-dynsym-lookup', kzt_guest_dynsym_lookup_test) + +kzt_guest_symbol_scope_test = executable( + 'kzt-guest-symbol-scope', + files( + 'kzt/test_guest_symbol_scope.c', + '../../target/i386/latx/context/kzt_guest_symbol_scope.c', + '../../target/i386/latx/context/kzt_guest_dynsym_lookup.c', + '../../target/i386/latx/context/kzt_guest_dynamic.c', + '../../target/i386/latx/context/kzt_guest_link_map_reader.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-guest-symbol-scope', kzt_guest_symbol_scope_test) + kzt_elf_map_range_test = executable( 'kzt-elf-map-range', files( @@ -177,9 +449,11 @@ kzt_observation_adapter_test = executable( '../../target/i386/latx/context/kzt_guest_registry.c', '../../target/i386/latx/context/kzt_guest_link_map_reader.c', '../../target/i386/latx/context/kzt_observation_adapter.c', + '../../target/i386/latx/context/kzt_lazy_prebind_scope.c', '../../target/i386/latx/context/kzt_guest_dynamic.c', '../../target/i386/latx/context/kzt_guest_dynamic_diagnostics.c', '../../target/i386/latx/context/kzt_guest_library_binding.c', + '../../target/i386/latx/context/kzt_lifecycle_diagnostics.c', ), c_args: [ '-DKZT_GUEST_REGISTRY_TEST', @@ -199,9 +473,11 @@ kzt_registry_diagnostics_gate_test = executable( '../../target/i386/latx/context/kzt_guest_registry.c', '../../target/i386/latx/context/kzt_guest_link_map_reader.c', '../../target/i386/latx/context/kzt_observation_adapter.c', + '../../target/i386/latx/context/kzt_lazy_prebind_scope.c', '../../target/i386/latx/context/kzt_guest_dynamic.c', '../../target/i386/latx/context/kzt_guest_dynamic_diagnostics.c', '../../target/i386/latx/context/kzt_guest_library_binding.c', + '../../target/i386/latx/context/kzt_lifecycle_diagnostics.c', ), c_args: [ '-DCONFIG_LATX_KZT', @@ -261,6 +537,7 @@ kzt_rela_immediate_candidate_test = executable( 'kzt-rela-immediate-candidate', files( 'kzt/test_rela_immediate_candidate.c', + 'kzt/kzt_test_options.c', '../../target/i386/latx/context/kzt_rela_immediate_candidate.c', '../../target/i386/latx/context/kzt_patch_planner.c', '../../target/i386/latx/context/kzt_patch_spike_guard.c', @@ -275,6 +552,8 @@ kzt_rela_request_enricher_test = executable( 'kzt-rela-request-enricher', files( 'kzt/test_rela_request_enricher.c', + 'kzt/kzt_test_options.c', + '../../target/i386/latx/context/kzt_rela_stub_detector.c', '../../target/i386/latx/context/kzt_rela_request_enricher.c', '../../target/i386/latx/context/kzt_rela_immediate_candidate.c', '../../target/i386/latx/context/kzt_patch_planner.c', @@ -295,6 +574,7 @@ kzt_wi238_structured_diagnostics_gate_test = executable( 'kzt-wi238-structured-diagnostics-gate', files( 'kzt/test_wi238_structured_diagnostics.c', + 'kzt/kzt_test_options.c', '../../target/i386/latx/context/kzt_rela_diagnostics.c', '../../target/i386/latx/context/kzt_patch_planner.c', '../../target/i386/latx/context/kzt_patch_spike_guard.c', @@ -377,9 +657,11 @@ kzt_rela_runtime_helper_chain_test = executable( 'kzt-rela-runtime-helper-chain', files( 'kzt/test_rela_runtime_helper_chain.c', + 'kzt/kzt_test_options.c', '../../target/i386/latx/context/kzt_rela_runtime_bridge.c', '../../target/i386/latx/context/kzt_wrapper_bridge_provider.c', '../../target/i386/latx/context/kzt_wrapper_probe.c', + '../../target/i386/latx/context/kzt_rela_stub_detector.c', '../../target/i386/latx/context/kzt_rela_request_enricher.c', '../../target/i386/latx/context/kzt_rela_immediate_candidate.c', '../../target/i386/latx/context/kzt_patch_planner.c', @@ -407,6 +689,323 @@ kzt_jump_slot_route_test = executable( test('kzt-jump-slot-route', kzt_jump_slot_route_test) +test( + 'kzt-wi601-jump-slot-single-writer-source-contract', + python3, + args: [ + files('kzt/test_wi601_jump_slot_single_writer_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi601-lazy-legacy-removal-source-contract', + python3, + args: [ + files('kzt/test_wi601_lazy_legacy_removal_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi962-guest-lifecycle-source-contract', + python3, + args: [ + files('kzt/test_wi962_guest_lifecycle_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi963-guest-symbol-source-contract', + python3, + args: [ + files('kzt/test_wi963_guest_symbol_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi973-namespace-source-contract', + python3, + args: [ + files('kzt/test_wi973_namespace_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi964-guest-dl-api-source-contract', + python3, + args: [ + files('kzt/test_wi964_guest_dl_api_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi979-guest-first-dlopen-source-contract', + python3, + args: [ + files('kzt/test_wi979_guest_first_dlopen_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi980-guest-first-dlclose-source-contract', + python3, + args: [ + files('kzt/test_wi980_guest_first_dlclose_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi602-handle-state-removal-source-contract', + python3, + args: [ + files('kzt/test_wi602_handle_state_removal_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi603-loader-hook-timing-source-contract', + python3, + args: [ + files('kzt/test_wi603_loader_hook_timing_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi603-callback-replacement-source-contract', + python3, + args: [ + files('kzt/test_wi603_callback_replacement_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1065-loader-hook-event-contract', + python3, + args: [ + files('kzt/test_wi1065_loader_hook_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1065-real-guest-hook-fixture-contract', + python3, + args: [ + files('kzt/test_wi1065_real_guest_hook_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1066-lazy-prebind-source-contract', + python3, + args: [ + files('kzt/test_wi1066_lazy_prebind_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1066-pinned-bridge-source-contract', + python3, + args: [ + files('kzt/test_wi1066_pinned_bridge_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1082-steady-diagnostics-source-contract', + python3, + args: [ + files('kzt/test_wi1082_steady_diagnostics_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1209-dlerror-bridge-fast-path-source-contract', + python3, + args: [ + files('kzt/test_wi1209_dlerror_bridge_fast_path_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1081-lifecycle-performance-source-contract', + python3, + args: [ + files('kzt/test_wi1081_lifecycle_performance_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1083-legacy-callback-removal-source-contract', + python3, + args: [ + files('kzt/test_wi1083_legacy_callback_removal_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1099-wrapper-provenance-source-contract', + python3, + args: [ + files('kzt/test_wi1099_wrapper_provenance_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1099-symbol-type-source-contract', + python3, + args: [ + files('kzt/test_wi1099_symbol_type_source_contract.py'), + meson.source_root(), + ], +) + +kzt_loader_event_hook_test = executable( + 'kzt-loader-event-hook', + files( + 'kzt/test_loader_event_hook.c', + '../../target/i386/latx/context/kzt_loader_event_hook.c', + ), + c_args: ['-DKZT_LOADER_EVENT_HOOK_TEST'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-loader-event-hook', kzt_loader_event_hook_test) + +kzt_lifecycle_snapshot_capacity_test = executable( + 'kzt-lifecycle-snapshot-capacity', + files( + 'kzt/test_lifecycle_snapshot_capacity.c', + '../../target/i386/latx/context/kzt_loader_lifecycle_snapshot.c', + '../../target/i386/latx/context/kzt_loader_event_hook.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: [ + '-DKZT_LOADER_EVENT_HOOK_TEST', + '-DKZT_LOADER_LIFECYCLE_SNAPSHOT_TEST', + ], + include_directories: include_directories( + '../..', + '../../target/i386/latx/include', + ), + dependencies: dependency('threads'), +) + +test('kzt-lifecycle-snapshot-capacity', + kzt_lifecycle_snapshot_capacity_test) + +kzt_lazy_prebind_scope_test = executable( + 'kzt-lazy-prebind-scope', + files( + 'kzt/test_lazy_prebind_scope.c', + '../../target/i386/latx/context/kzt_lazy_prebind_scope.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-lazy-prebind-scope', kzt_lazy_prebind_scope_test) + +test( + 'kzt-wi1056-per-object-got-plt-source-contract', + python3, + args: [ + files('kzt/test_wi1056_per_object_got_plt_source_contract.py'), + meson.source_root(), + ], +) + + +test( + 'kzt-wi982-versioned-wrapper-selection-source-contract', + python3, + args: [ + files('kzt/test_wi982_versioned_wrapper_selection_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi987-guest-relocation-authority-source-contract', + python3, + args: [ + files('kzt/test_wi987_guest_relocation_authority_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi994-dlerror-tail-forward-source-contract', + python3, + args: [ + files('kzt/test_wi994_dlerror_tail_forward_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi995-lazy-direct-timing-source-contract', + python3, + args: [ + files('kzt/test_wi995_lazy_direct_timing_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1007-context-resolver-source-contract', + python3, + args: [ + files('kzt/test_wi1007_context_resolver_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi995-lazy-bridge-translation-timing-source-contract', + python3, + args: [ + files('kzt/test_wi995_lazy_bridge_translation_timing_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi999-startup-observation-timing-source-contract', + python3, + args: [ + files('kzt/test_wi999_startup_observation_timing_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi999-context-init-timing-source-contract', + python3, + args: [ + files('kzt/test_wi999_context_init_timing_source_contract.py'), + meson.source_root(), + ], +) + kzt_wi256_lazy_binding_test = executable( 'kzt-wi256-lazy-binding', files( @@ -418,6 +1017,17 @@ kzt_wi256_lazy_binding_test = executable( test('kzt-wi256-lazy-binding', kzt_wi256_lazy_binding_test) +kzt_wi837_lazy_direct_route_test = executable( + 'kzt-wi837-lazy-direct-route', + files( + 'kzt/test_wi837_lazy_direct_route.c', + '../../target/i386/latx/context/kzt_lazy_direct_route.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-wi837-lazy-direct-route', kzt_wi837_lazy_direct_route_test) + kzt_wi256_lazy_diagnostics_test = executable( 'kzt-wi256-lazy-diagnostics', files( @@ -458,11 +1068,15 @@ kzt_wi256_lazy_production_bridge_test = executable( 'kzt/test_wi256_lazy_production_bridge.c', 'kzt/kzt_test_options.c', '../../target/i386/latx/context/kzt_lazy_binding.c', + '../../target/i386/latx/context/kzt_lazy_prebind_scope.c', + '../../target/i386/latx/context/kzt_lazy_direct_route.c', '../../target/i386/latx/context/kzt_jump_slot_production.c', + '../../target/i386/latx/context/kzt_lifecycle_diagnostics.c', '../../target/i386/latx/context/kzt_jump_slot_route.c', '../../target/i386/latx/context/kzt_rela_runtime_bridge.c', '../../target/i386/latx/context/kzt_wrapper_bridge_provider.c', '../../target/i386/latx/context/kzt_wrapper_probe.c', + '../../target/i386/latx/context/kzt_rela_stub_detector.c', '../../target/i386/latx/context/kzt_rela_request_enricher.c', '../../target/i386/latx/context/kzt_rela_diagnostics.c', '../../target/i386/latx/context/kzt_rela_immediate_candidate.c', @@ -473,7 +1087,12 @@ kzt_wi256_lazy_production_bridge_test = executable( '../../target/i386/latx/context/kzt_runtime_got_plt_candidate.c', '../../target/i386/latx/context/kzt_runtime_candidate_shadow.c', '../../target/i386/latx/context/kzt_guest_registry.c', + '../../target/i386/latx/context/kzt_guest_registry_context.c', '../../target/i386/latx/context/kzt_guest_library_binding.c', + '../../target/i386/latx/context/kzt_guest_link_map_reader.c', + '../../target/i386/latx/context/kzt_guest_dynamic.c', + '../../target/i386/latx/context/kzt_guest_dynsym_lookup.c', + '../../target/i386/latx/context/kzt_guest_symbol_scope.c', '../../target/i386/latx/context/kzt_bridge_exact.c', ), c_args: [ @@ -510,6 +1129,7 @@ kzt_wi236_enrichment_matrix_test = executable( 'kzt-wi236-enrichment-matrix', files( 'kzt/test_wi236_enrichment_matrix.c', + 'kzt/kzt_test_options.c', '../../target/i386/latx/context/kzt_guest_registry.c', '../../target/i386/latx/context/kzt_owner_resolver.c', '../../target/i386/latx/context/kzt_patch_planner.c', From a506e0a3d1f567d3f868e0464aba644877d18db3 Mon Sep 17 00:00:00 2001 From: Hanlu Li Date: Fri, 31 Jul 2026 23:54:49 +0800 Subject: [PATCH 7/8] LATX, KZT: select exact versioned wrapper bridges Keep the guest dlvsym result authoritative unless the exact loader identity, retained library binding, guest dynsym address and requested symbol version all agree with a wrapper bridge. Treat missing, mismatched, and empty version evidence as fail-open, while leaving the explicit NULL dlsym path on its existing unversioned cache. Add public selector behavior coverage for exact matches and fail-open boundaries, and update the related source contracts so both libc and libdl remain on the shared guest DL API. Signed-off-by: Hanlu Li --- .../latx/context/kzt_guest_library_adapter.c | 62 ++++++-- tests/unit/kzt/test_guest_library_adapter.c | 137 +++++++++++++++++- ...test_wi1099_symbol_type_source_contract.py | 51 +++++-- ...ioned_wrapper_selection_source_contract.py | 39 +++-- 4 files changed, 247 insertions(+), 42 deletions(-) diff --git a/target/i386/latx/context/kzt_guest_library_adapter.c b/target/i386/latx/context/kzt_guest_library_adapter.c index c0c8a1bf745..ae943e5690e 100644 --- a/target/i386/latx/context/kzt_guest_library_adapter.c +++ b/target/i386/latx/context/kzt_guest_library_adapter.c @@ -8,6 +8,8 @@ #include "kzt_guest_dynsym_lookup.h" #include "kzt_guest_library_binding.h" #include "kzt_guest_registry.h" +#include "kzt_rela_runtime_bridge.h" +#include "kzt_wrapper_probe.h" #include "library.h" #include "library_private.h" #ifndef KZT_GUEST_LIBRARY_ADAPTER_TEST @@ -357,6 +359,9 @@ uintptr_t kzt_guest_library_select_symbol_result( kzt_guest_library_handle_t handle = { 0 }; kzt_guest_dynamic_view_t dynamic_view; kzt_guest_dynsym_lookup_result_t dynsym_result = { 0 }; + kzt_wrapper_bridge_provider_t wrapper_provider = { 0 }; + kzt_wrapper_probe_request_t wrapper_request; + kzt_wrapper_probe_result_t wrapper_probe = { 0 }; kzt_guest_field_status_t dynamic_status; unsigned long dynamic_revision = 0; kzt_guest_link_map_reader_ops_t reader_ops = { @@ -368,10 +373,11 @@ uintptr_t kzt_guest_library_select_symbol_result( uintptr_t end = 0; uintptr_t selected = guest_result; const char *diagnostic_reason = "source_generation_stale"; + int versioned = version != NULL; if ((!option_kzt && !wine_option_kzt) || !context || !guest_result || !symbol || !symbol[0] || - (version && version[0]) || + (versioned && !version[0]) || !(registry = KztGuestRegistryForContext(context)) || kzt_guest_registry_loader_symbol_source_acquire( registry, guest_handle, &lookup_identity, &dynamic_view, @@ -398,9 +404,20 @@ uintptr_t kzt_guest_library_select_symbol_result( if (handle.object_type == KZT_GUEST_LIBRARY_OBJECT_WRAPPED && handle.library) { diagnostic_reason = "dynsym_unproven"; - if (kzt_guest_library_symbol_evidence_lookup( - &handle, symbol, dynamic_revision, &proven_runtime_address, - &proven_symbol_type) != 0) { + if (versioned) { + kzt_guest_dynsym_lookup_status_t dynsym_status = + kzt_guest_dynsym_lookup( + &dynamic_view, &reader_ops, symbol, + KZT_SYMBOL_VERSION_VERSIONED, version, + &dynsym_result); + + if (dynsym_status == KZT_GUEST_DYNSYM_LOOKUP_FOUND) { + proven_runtime_address = dynsym_result.runtime_address; + proven_symbol_type = dynsym_result.type; + } + } else if (kzt_guest_library_symbol_evidence_lookup( + &handle, symbol, dynamic_revision, + &proven_runtime_address, &proven_symbol_type) != 0) { kzt_guest_dynsym_lookup_status_t dynsym_status = kzt_guest_dynsym_lookup( &dynamic_view, &reader_ops, symbol, @@ -411,8 +428,7 @@ uintptr_t kzt_guest_library_select_symbol_result( proven_runtime_address = dynsym_result.runtime_address; proven_symbol_type = dynsym_result.type; kzt_guest_library_symbol_evidence_store( - &handle, symbol, dynamic_revision, - proven_runtime_address, + &handle, symbol, dynamic_revision, proven_runtime_address, proven_symbol_type); } } @@ -423,11 +439,35 @@ uintptr_t kzt_guest_library_select_symbol_result( ? "unsupported_symbol_type" : "bridge_missing"; if (proven_runtime_address == guest_result && - proven_symbol_type == STT_FUNC && - GetLibFunctionSymbolStartEnd( - handle.library, symbol, 0, &start, &end)) { - selected = start; - diagnostic_reason = NULL; + proven_symbol_type == STT_FUNC) { + if (versioned && + kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( + context, &handle, symbol, KZT_SYMBOL_VERSION_VERSIONED, + version, &wrapper_provider) > 0) { + wrapper_request = (kzt_wrapper_probe_request_t) { + .symbol_name = symbol, + .symbol_version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .symbol_version = version, + }; + if (kzt_wrapper_probe_minimal_manifest( + &wrapper_provider.manifest, &wrapper_request, + &wrapper_provider.bridge_ops, &wrapper_probe) == 0 && + wrapper_probe.wrapper_match == + KZT_PATCH_WRAPPER_VERSION_MATCH && + wrapper_probe.bridge_target && + kzt_symbol_version_evidence_matches( + KZT_SYMBOL_VERSION_VERSIONED, version, + wrapper_probe.wrapper_version_evidence, + wrapper_probe.wrapper_symbol_version)) { + selected = wrapper_probe.bridge_target; + diagnostic_reason = NULL; + } + } else if (!versioned && + GetLibFunctionSymbolStartEnd( + handle.library, symbol, 0, &start, &end)) { + selected = start; + diagnostic_reason = NULL; + } } if (diagnostic_reason) { kzt_guest_library_note_wrapper_diagnostic( diff --git a/tests/unit/kzt/test_guest_library_adapter.c b/tests/unit/kzt/test_guest_library_adapter.c index b7f16db700e..ccd9f707819 100644 --- a/tests/unit/kzt/test_guest_library_adapter.c +++ b/tests/unit/kzt/test_guest_library_adapter.c @@ -8,6 +8,7 @@ #include "target/i386/latx/include/kzt_guest_library_binding.h" #include "target/i386/latx/include/kzt_guest_dynsym_lookup.h" #include "target/i386/latx/include/kzt_guest_registry.h" +#include "target/i386/latx/include/kzt_rela_runtime_bridge.h" #include "target/i386/latx/include/callback.h" #include "target/i386/latx/include/library.h" #include "target/i386/latx/include/library_private.h" @@ -87,6 +88,15 @@ static int evidence_valid; static unsigned long evidence_dynamic_revision; static uintptr_t evidence_runtime_address; static unsigned char evidence_symbol_type; +static kzt_symbol_version_evidence_t expected_dynsym_version_evidence; +static const char *expected_dynsym_version; +static int provider_discover_result; +static int provider_discover_count; +static int wrapper_probe_count; +static kzt_patch_wrapper_match_t wrapper_probe_match; +static kzt_symbol_version_evidence_t wrapper_probe_version_evidence; +static const char *wrapper_probe_version; +static uintptr_t wrapper_probe_bridge_target; #define CHECK(label, condition) \ do { \ @@ -275,9 +285,12 @@ kzt_guest_dynsym_lookup_status_t kzt_guest_dynsym_lookup( CHECK("dynsym complete view", view->status == KZT_GUEST_DYNAMIC_COMPLETE); CHECK("dynsym reader", reader_ops && reader_ops->read_memory); CHECK("dynsym symbol", strcmp(symbol, "wi963_symbol") == 0); - CHECK("dynsym unversioned", - version_evidence == KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED && - version == NULL); + CHECK("dynsym version evidence", + version_evidence == expected_dynsym_version_evidence); + CHECK("dynsym version", + expected_dynsym_version + ? version && strcmp(version, expected_dynsym_version) == 0 + : version == NULL); memset(result, 0, sizeof(*result)); result->status = dynsym_status; result->binding = STB_GLOBAL; @@ -287,6 +300,54 @@ kzt_guest_dynsym_lookup_status_t kzt_guest_dynsym_lookup( return result->status; } +int kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( + box64context_t *context, + const kzt_guest_library_handle_t *retained_provider_handle, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version, + kzt_wrapper_bridge_provider_t *provider) +{ + CHECK("provider context", context != NULL); + CHECK("provider retained binding", + retained_provider_handle && retained_provider_handle->entry && + retained_provider_handle->library == lookup_library); + CHECK("provider symbol", strcmp(symbol_name, "wi963_symbol") == 0); + CHECK("provider version evidence", + version_evidence == expected_dynsym_version_evidence); + CHECK("provider version", + expected_dynsym_version && symbol_version && + strcmp(symbol_version, expected_dynsym_version) == 0); + ++provider_discover_count; + memset(provider, 0, sizeof(*provider)); + provider->manifest.available = provider_discover_result > 0; + return provider_discover_result; +} + +int kzt_wrapper_probe_minimal_manifest( + const kzt_wrapper_probe_manifest_t *manifest, + const kzt_wrapper_probe_request_t *request, + const kzt_wrapper_probe_bridge_ops_t *bridge_ops, + kzt_wrapper_probe_result_t *result) +{ + CHECK("probe manifest", manifest && manifest->available); + CHECK("probe request", request != NULL); + CHECK("probe symbol", strcmp(request->symbol_name, "wi963_symbol") == 0); + CHECK("probe version evidence", + request->symbol_version_evidence == expected_dynsym_version_evidence); + CHECK("probe version", + expected_dynsym_version && request->symbol_version && + strcmp(request->symbol_version, expected_dynsym_version) == 0); + CHECK("probe bridge ops", bridge_ops != NULL); + ++wrapper_probe_count; + memset(result, 0, sizeof(*result)); + result->wrapper_match = wrapper_probe_match; + result->wrapper_version_evidence = wrapper_probe_version_evidence; + result->wrapper_symbol_version = wrapper_probe_version; + result->bridge_target = wrapper_probe_bridge_target; + return 0; +} + int kzt_guest_library_symbol_evidence_lookup( const kzt_guest_library_handle_t *handle, const char *symbol, unsigned long dynamic_revision, uintptr_t *runtime_address, @@ -784,6 +845,16 @@ static void prepare_symbol_selection(library_t *library) evidence_store_count = 0; evidence_valid = 0; evidence_dynamic_revision = 0; + expected_dynsym_version_evidence = + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + expected_dynsym_version = NULL; + provider_discover_result = 0; + provider_discover_count = 0; + wrapper_probe_count = 0; + wrapper_probe_match = KZT_PATCH_WRAPPER_NO_MANIFEST; + wrapper_probe_version_evidence = KZT_SYMBOL_VERSION_UNKNOWN; + wrapper_probe_version = NULL; + wrapper_probe_bridge_target = 0; } static void prepare_trusted_source(library_t *library) @@ -1064,13 +1135,67 @@ static void test_symbol_selection_requires_exact_wrapped_owner(void) reset_counters(); prepare_symbol_selection(&library); + expected_dynsym_version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + expected_dynsym_version = "WI982_1.0"; + provider_discover_result = 1; + wrapper_probe_match = KZT_PATCH_WRAPPER_VERSION_MATCH; + wrapper_probe_version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + wrapper_probe_version = expected_dynsym_version; + wrapper_probe_bridge_target = wrapper_result; + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", "WI982_1.0"); + CHECK("exact versioned wrapper selects bridge", + selected == wrapper_result); + CHECK("versioned selection proves exact owner", + symbol_source_acquire_count == 1 && lookup_count == 1 && + dynsym_lookup_count == 1 && provider_discover_count == 1 && + wrapper_probe_count == 1 && release_count == 1); + CHECK("versioned selection bypasses unversioned evidence cache", + evidence_lookup_count == 0 && evidence_store_count == 0); + + reset_counters(); + prepare_symbol_selection(&library); + expected_dynsym_version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + expected_dynsym_version = "WI982_1.0"; + provider_discover_result = 1; + wrapper_probe_match = KZT_PATCH_WRAPPER_VERSION_MATCH; + wrapper_probe_version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + wrapper_probe_version = "WI982_2.0"; + wrapper_probe_bridge_target = wrapper_result; selected = kzt_guest_library_select_symbol_result( &context, lookup_guest_handle, resolved_guest_address, "wi963_symbol", "WI982_1.0"); - CHECK("versioned lookup keeps guest", + CHECK("wrapper version mismatch keeps guest", + selected == resolved_guest_address); + CHECK("wrapper version mismatch releases exact binding", + provider_discover_count == 1 && wrapper_probe_count == 1 && + release_count == 1); + + reset_counters(); + prepare_symbol_selection(&library); + expected_dynsym_version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + expected_dynsym_version = "WI982_1.0"; + provider_discover_result = 0; + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", "WI982_1.0"); + CHECK("missing versioned wrapper evidence keeps guest", + selected == resolved_guest_address); + CHECK("missing versioned wrapper evidence releases exact binding", + provider_discover_count == 1 && wrapper_probe_count == 0 && + release_count == 1); + + reset_counters(); + prepare_symbol_selection(&library); + selected = kzt_guest_library_select_symbol_result( + &context, lookup_guest_handle, resolved_guest_address, + "wi963_symbol", ""); + CHECK("empty dlvsym version keeps guest", selected == resolved_guest_address); - CHECK("versioned lookup skips unversioned owner selection", - resolve_count == 0 && lookup_count == 0 && release_count == 0); + CHECK("empty dlvsym version skips unversioned selection", + symbol_source_acquire_count == 0 && lookup_count == 0 && + dynsym_lookup_count == 0 && release_count == 0); } int main(void) diff --git a/tests/unit/kzt/test_wi1099_symbol_type_source_contract.py b/tests/unit/kzt/test_wi1099_symbol_type_source_contract.py index 5fc4b2cd5ee..c81769111d6 100644 --- a/tests/unit/kzt/test_wi1099_symbol_type_source_contract.py +++ b/tests/unit/kzt/test_wi1099_symbol_type_source_contract.py @@ -30,29 +30,48 @@ def function_body(text: str, signature: str) -> str: adapter, "uintptr_t kzt_guest_library_select_symbol_result(" ) -ordered = ( - "kzt_guest_registry_loader_symbol_source_acquire", - "kzt_guest_library_access_lookup", - "kzt_guest_library_symbol_evidence_lookup", - "kzt_guest_dynsym_lookup(", - "kzt_guest_library_symbol_evidence_store", - "proven_symbol_type == STT_FUNC", - "GetLibFunctionSymbolStartEnd", -) -positions = [selector.find(token) for token in ordered] -if min(positions) < 0 or positions != sorted(positions): - raise AssertionError(f"symbol evidence order is incomplete: {positions}") +evidence = { + token: selector.find(token) + for token in ( + "kzt_guest_registry_loader_symbol_source_acquire", + "kzt_guest_library_access_lookup", + "kzt_guest_library_symbol_evidence_lookup", + "kzt_guest_dynsym_lookup(", + "kzt_guest_library_symbol_evidence_store", + "proven_runtime_address == guest_result", + "proven_symbol_type == STT_FUNC", + "kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence", + "GetLibFunctionSymbolStartEnd", + ) +} +if min(evidence.values()) < 0: + raise AssertionError(f"symbol evidence is incomplete: {evidence}") +owner = evidence["kzt_guest_registry_loader_symbol_source_acquire"] +binding = evidence["kzt_guest_library_access_lookup"] +address = evidence["proven_runtime_address == guest_result"] +symbol_type = evidence["proven_symbol_type == STT_FUNC"] +versioned_bridge = evidence[ + "kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence" +] +unversioned_bridge = evidence["GetLibFunctionSymbolStartEnd"] +if not (owner < binding < address <= symbol_type < versioned_bridge and + symbol_type < unversioned_bridge): + raise AssertionError(f"symbol proof does not guard both bridges: {evidence}") cleanup = ( selector.rfind("kzt_guest_registry_source_lease_release"), selector.rfind("kzt_guest_library_handle_release"), ) if (min(cleanup) < 0 or cleanup != tuple(sorted(cleanup)) or - cleanup[0] < positions[-1]): + cleanup[0] < max(versioned_bridge, unversioned_bridge)): raise AssertionError(f"symbol evidence cleanup is incomplete: {cleanup}") -version_guard = selector.find("version && version[0]") -if version_guard < 0 or version_guard > positions[0]: - raise AssertionError("versioned lookup reaches unversioned wrapper selection") +for token in ( + "KZT_SYMBOL_VERSION_VERSIONED, version", + "KZT_PATCH_WRAPPER_VERSION_MATCH", + "kzt_symbol_version_evidence_matches(", +): + if token not in selector: + raise AssertionError(f"versioned symbol proof is incomplete: {token}") for forbidden in ( "GetGlobalSymbolStartEnd", diff --git a/tests/unit/kzt/test_wi982_versioned_wrapper_selection_source_contract.py b/tests/unit/kzt/test_wi982_versioned_wrapper_selection_source_contract.py index 713b06d9aa8..729b8b09152 100644 --- a/tests/unit/kzt/test_wi982_versioned_wrapper_selection_source_contract.py +++ b/tests/unit/kzt/test_wi982_versioned_wrapper_selection_source_contract.py @@ -32,22 +32,43 @@ def function_body(text: str, signature: str) -> str: common = ( root / "target/i386/latx/context/kzt_guest_dl_api.c" ).read_text(encoding="utf-8") +wrappedlibc = ( + root / "target/i386/latx/context/wrappedlibc.c" +).read_text(encoding="utf-8") +wrappedlibdl = ( + root / "target/i386/latx/context/wrappedlibdl.c" +).read_text(encoding="utf-8") selector = function_body( adapter, "uintptr_t kzt_guest_library_select_symbol_result(") -version_guard = selector.find("version && version[0]") -owner_lookup = selector.find("kzt_guest_registry_resolve_address_pair") -if version_guard < 0: - raise AssertionError("versioned selection is not explicitly fail-open") -if owner_lookup >= 0 and version_guard >= owner_lookup: - raise AssertionError("versioned lookup reaches unversioned owner selection") +required_selector_tokens = ( + "KZT_SYMBOL_VERSION_VERSIONED, version", + "kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence(", + "kzt_wrapper_probe_minimal_manifest(", + "KZT_PATCH_WRAPPER_VERSION_MATCH", + "kzt_symbol_version_evidence_matches(", +) +for token in required_selector_tokens: + if token not in selector: + raise AssertionError(f"versioned selector misses exact evidence: {token}") +if "(version && version[0]) ||" in selector: + raise AssertionError("versioned selection still returns before owner proof") +if "versioned = version != NULL" not in selector or \ + "versioned && !version[0]" not in selector: + raise AssertionError("NULL and empty versions are not distinguished") dlsym = function_body(common, "kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlsym(") dlvsym = function_body( common, "kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlvsym(") -if "context, (uintptr_t)handle, guest_result" not in dlsym: - raise AssertionError("dlsym does not declare its unversioned selection") -if "context, (uintptr_t)handle, guest_result" not in dlvsym: +if "(const char *)symbol, NULL" not in dlsym: + raise AssertionError("dlsym does not explicitly request unversioned selection") +if "(const char *)symbol, version" not in dlvsym: raise AssertionError("dlvsym does not pass the requested version") +for name, source in (("libc", wrappedlibc), ("libdl", wrappedlibdl)): + if "kzt_guest_dl_api_dlsym(" not in source: + raise AssertionError(f"{name} dlsym does not use the shared API") + if "kzt_guest_dl_api_dlvsym(" not in source: + raise AssertionError(f"{name} dlvsym does not use the shared API") + print("WI-982 versioned wrapper selection source contract: PASS") From 95258c13e413b2156509e8653f4d0199557abaab Mon Sep 17 00:00:00 2001 From: Hanlu Li Date: Tue, 4 Aug 2026 21:08:36 +0800 Subject: [PATCH 8/8] LATX, KZT: converge runtime ownership and retire legacy paths Route loader, symbol, relocation, and X11/XCB decisions through context-owned KZT state with retained lifetime evidence and guarded guest fallbacks. Remove the superseded lazy binding and diagnostic paths so production no longer depends on the old direct-routing state. Add focused lifecycle, cancellation, bridge, relocation, and source-contract coverage, including exact dlsym/GLOB_DAT wrapper selection and cache invalidation checks. Signed-off-by: Hanlu Li --- linux-user/main.c | 6 +- target/i386/cpu.h | 4 +- target/i386/latx/context/box64context.c | 37 +- target/i386/latx/context/bridge.c | 127 +- .../latx/context/dlopen_recycle_transaction.c | 27 - target/i386/latx/context/elfloader.c | 502 ++-- target/i386/latx/context/kzt_bridge_exact.c | 13 + .../latx/context/kzt_guest_cancel_scope.c | 48 + target/i386/latx/context/kzt_guest_dl_api.c | 101 +- target/i386/latx/context/kzt_guest_dl_init.c | 41 +- .../latx/context/kzt_guest_dynsym_lookup.c | 14 +- .../latx/context/kzt_guest_glob_dat_target.c | 183 ++ .../latx/context/kzt_guest_library_adapter.c | 122 +- .../latx/context/kzt_guest_library_binding.c | 67 +- target/i386/latx/context/kzt_guest_registry.c | 194 ++ .../latx/context/kzt_guest_runtime_entry.c | 50 + .../context/kzt_guest_runtime_entry_state.c | 240 ++ .../latx/context/kzt_jump_slot_production.c | 2282 +++++++++++++---- target/i386/latx/context/kzt_lazy_binding.c | 209 -- .../i386/latx/context/kzt_lazy_diagnostics.c | 255 -- .../i386/latx/context/kzt_lazy_direct_route.c | 15 + .../latx/context/kzt_lazy_prebind_scope.c | 66 +- .../i386/latx/context/kzt_loader_event_hook.c | 165 +- .../latx/context/kzt_observation_adapter.c | 6 +- .../i386/latx/context/kzt_patch_spike_guard.c | 29 +- .../latx/context/kzt_patch_spike_writer.c | 19 +- .../latx/context/kzt_plt_resolver_adapter.c | 37 +- .../context/kzt_rela_immediate_candidate.c | 3 +- .../latx/context/kzt_rela_runtime_bridge.c | 159 +- .../context/kzt_wrapper_bridge_provider.c | 22 + .../latx/context/kzt_xcb_connection_guard.c | 274 ++ .../latx/context/kzt_xcb_connection_map.c | 494 ++++ target/i386/latx/context/meson.build | 9 +- target/i386/latx/context/myalign.c | 713 +++-- target/i386/latx/context/wrappedlibc.c | 23 +- target/i386/latx/context/wrappedlibdl.c | 20 +- target/i386/latx/context/wrappedlibx11.c | 75 +- target/i386/latx/context/wrappedlibxcb.c | 89 +- target/i386/latx/context/wrappedvulkan.c | 6 +- target/i386/latx/context/wrapper.c | 200 +- target/i386/latx/include/aot.h | 13 +- target/i386/latx/include/box64context.h | 23 +- target/i386/latx/include/bridge.h | 13 + target/i386/latx/include/bridge_private.h | 12 + .../latx/include/dlopen_recycle_transaction.h | 16 - .../include/generated/wrappedlibx11types.h | 1 + target/i386/latx/include/kzt_bridge_exact.h | 5 + .../latx/include/kzt_guest_cancel_scope.h | 17 + target/i386/latx/include/kzt_guest_dl_api.h | 4 +- target/i386/latx/include/kzt_guest_dl_state.h | 19 +- .../latx/include/kzt_guest_dynsym_lookup.h | 5 + .../latx/include/kzt_guest_glob_dat_target.h | 53 + .../latx/include/kzt_guest_library_adapter.h | 8 + .../latx/include/kzt_guest_library_binding.h | 10 +- target/i386/latx/include/kzt_guest_registry.h | 39 + .../latx/include/kzt_guest_runtime_entry.h | 29 + .../include/kzt_guest_runtime_entry_state.h | 50 + .../latx/include/kzt_jump_slot_production.h | 65 +- target/i386/latx/include/kzt_lazy_binding.h | 131 - .../i386/latx/include/kzt_lazy_diagnostics.h | 84 - .../i386/latx/include/kzt_lazy_direct_route.h | 13 + .../latx/include/kzt_lazy_prebind_scope.h | 12 + .../i386/latx/include/kzt_loader_event_hook.h | 26 +- .../latx/include/kzt_observation_adapter.h | 1 + target/i386/latx/include/kzt_patch_planner.h | 33 +- .../i386/latx/include/kzt_patch_spike_guard.h | 47 + .../latx/include/kzt_patch_spike_writer.h | 1 - .../latx/include/kzt_plt_resolver_adapter.h | 22 - .../include/kzt_rela_immediate_candidate.h | 1 - .../latx/include/kzt_rela_runtime_bridge.h | 24 + .../include/kzt_wrapper_bridge_provider.h | 3 + .../latx/include/kzt_xcb_connection_guard.h | 21 + .../latx/include/kzt_xcb_connection_map.h | 59 + .../i386/latx/include/kzt_xcb_queue_mirror.h | 41 + .../i386/latx/include/kzt_xcb_route_policy.h | 57 + target/i386/latx/include/myalign.h | 16 +- .../i386/latx/include/wrappedlibx11_private.h | 2 +- .../i386/latx/include/wrappedlibxcb_private.h | 8 +- target/i386/latx/sbt/aot.c | 11 + target/i386/latx/translator/tr-misc.c | 149 +- .../kzt/test_dlopen_recycle_transaction.c | 354 --- tests/unit/kzt/test_guest_dl_api.c | 589 ++++- tests/unit/kzt/test_guest_glob_dat_target.c | 536 ++++ tests/unit/kzt/test_guest_library_adapter.c | 266 +- tests/unit/kzt/test_guest_library_binding.c | 251 +- tests/unit/kzt/test_guest_registry.c | 237 +- tests/unit/kzt/test_lazy_prebind_scope.c | 75 + .../kzt/test_lifecycle_snapshot_capacity.c | 13 +- tests/unit/kzt/test_loader_event_hook.c | 102 +- tests/unit/kzt/test_observation_adapter.c | 71 + tests/unit/kzt/test_patch_planner.c | 70 + tests/unit/kzt/test_patch_spike_writer.c | 16 +- .../unit/kzt/test_rela_runtime_helper_chain.c | 275 ++ ...i1009_lazy_slot_bridge_context_contract.py | 22 + ...st_wi1021_dlerror_entry_source_contract.py | 39 +- .../kzt/test_wi1065_loader_hook_contract.py | 9 +- ...est_wi1066_lazy_prebind_source_contract.py | 13 +- ...legacy_callback_removal_source_contract.py | 122 +- ...i1095_eager_transaction_source_contract.py | 104 + ...8_thread_local_dl_state_source_contract.py | 14 +- ...test_wi1099_symbol_type_source_contract.py | 44 +- ..._wi1571_xcb_context_map_source_contract.py | 48 + tests/unit/kzt/test_wi1572_guarded_bridge.c | 305 +++ ...1572_guarded_xcb_bridge_source_contract.py | 104 + ..._guarded_xcb_production_source_contract.py | 141 + ...dlerror_route_coherence_source_contract.py | 127 + ...wi1611_xcb_cancellation_source_contract.py | 67 + ...i1612_xcb_serialization_source_contract.py | 59 + .../unit/kzt/test_wi1618_guest_cancel_scope.c | 134 + ...1618_guest_cancel_scope_source_contract.py | 39 + ..._wi1619_xcb_flush_state_source_contract.py | 56 + tests/unit/kzt/test_wi1619_xcb_queue_mirror.c | 127 + ...st_wi1621_x11_xcb_close_source_contract.py | 65 + ...oader_callback_snapshot_source_contract.py | 74 + ...exact_wrapper_selection_source_contract.py | 138 + .../kzt/test_wi1633_real_bridge_selection.c | 109 + tests/unit/kzt/test_wi256_lazy_binding.c | 543 ---- tests/unit/kzt/test_wi256_lazy_diagnostics.c | 392 --- ..._wi256_lazy_diagnostics_source_contract.py | 137 - .../kzt/test_wi256_lazy_production_bridge.c | 1819 ++++++------- .../kzt/test_wi256_plt_resolver_adapter.c | 396 +-- ...test_wi256_plt_resolver_source_contract.py | 381 +-- ...82_registry_observation_source_contract.py | 75 +- ...jump_slot_single_writer_source_contract.py | 98 +- ...02_handle_state_removal_source_contract.py | 17 + ...03_callback_replacement_source_contract.py | 67 +- ...i603_loader_hook_timing_source_contract.py | 22 +- tests/unit/kzt/test_wi837_lazy_direct_route.c | 136 + ...ioned_wrapper_selection_source_contract.py | 11 +- ...st_relocation_authority_source_contract.py | 23 +- ...94_dlerror_tail_forward_source_contract.py | 11 +- tests/unit/kzt/test_wrapper_bridge_provider.c | 52 +- tests/unit/kzt/test_xcb_connection_guard.c | 281 ++ tests/unit/kzt/test_xcb_connection_map.c | 684 +++++ tests/unit/kzt/test_xcb_route_policy.c | 81 + tests/unit/meson.build | 323 ++- 136 files changed, 12668 insertions(+), 5786 deletions(-) delete mode 100644 target/i386/latx/context/dlopen_recycle_transaction.c create mode 100644 target/i386/latx/context/kzt_guest_cancel_scope.c create mode 100644 target/i386/latx/context/kzt_guest_glob_dat_target.c create mode 100644 target/i386/latx/context/kzt_guest_runtime_entry.c create mode 100644 target/i386/latx/context/kzt_guest_runtime_entry_state.c delete mode 100644 target/i386/latx/context/kzt_lazy_binding.c delete mode 100644 target/i386/latx/context/kzt_lazy_diagnostics.c create mode 100644 target/i386/latx/context/kzt_xcb_connection_guard.c create mode 100644 target/i386/latx/context/kzt_xcb_connection_map.c delete mode 100644 target/i386/latx/include/dlopen_recycle_transaction.h create mode 100644 target/i386/latx/include/kzt_guest_cancel_scope.h create mode 100644 target/i386/latx/include/kzt_guest_glob_dat_target.h create mode 100644 target/i386/latx/include/kzt_guest_runtime_entry.h create mode 100644 target/i386/latx/include/kzt_guest_runtime_entry_state.h delete mode 100644 target/i386/latx/include/kzt_lazy_binding.h delete mode 100644 target/i386/latx/include/kzt_lazy_diagnostics.h create mode 100644 target/i386/latx/include/kzt_xcb_connection_guard.h create mode 100644 target/i386/latx/include/kzt_xcb_connection_map.h create mode 100644 target/i386/latx/include/kzt_xcb_queue_mirror.h create mode 100644 target/i386/latx/include/kzt_xcb_route_policy.h delete mode 100644 tests/unit/kzt/test_dlopen_recycle_transaction.c create mode 100644 tests/unit/kzt/test_guest_glob_dat_target.c create mode 100644 tests/unit/kzt/test_wi1095_eager_transaction_source_contract.py create mode 100644 tests/unit/kzt/test_wi1571_xcb_context_map_source_contract.py create mode 100644 tests/unit/kzt/test_wi1572_guarded_bridge.c create mode 100644 tests/unit/kzt/test_wi1572_guarded_xcb_bridge_source_contract.py create mode 100644 tests/unit/kzt/test_wi1573_guarded_xcb_production_source_contract.py create mode 100644 tests/unit/kzt/test_wi1574_dlerror_route_coherence_source_contract.py create mode 100644 tests/unit/kzt/test_wi1611_xcb_cancellation_source_contract.py create mode 100644 tests/unit/kzt/test_wi1612_xcb_serialization_source_contract.py create mode 100644 tests/unit/kzt/test_wi1618_guest_cancel_scope.c create mode 100644 tests/unit/kzt/test_wi1618_guest_cancel_scope_source_contract.py create mode 100644 tests/unit/kzt/test_wi1619_xcb_flush_state_source_contract.py create mode 100644 tests/unit/kzt/test_wi1619_xcb_queue_mirror.c create mode 100644 tests/unit/kzt/test_wi1621_x11_xcb_close_source_contract.py create mode 100644 tests/unit/kzt/test_wi1629_loader_callback_snapshot_source_contract.py create mode 100644 tests/unit/kzt/test_wi1633_exact_wrapper_selection_source_contract.py create mode 100644 tests/unit/kzt/test_wi1633_real_bridge_selection.c delete mode 100644 tests/unit/kzt/test_wi256_lazy_binding.c delete mode 100644 tests/unit/kzt/test_wi256_lazy_diagnostics.c delete mode 100644 tests/unit/kzt/test_wi256_lazy_diagnostics_source_contract.py create mode 100644 tests/unit/kzt/test_xcb_connection_guard.c create mode 100644 tests/unit/kzt/test_xcb_connection_map.c create mode 100644 tests/unit/kzt/test_xcb_route_policy.c diff --git a/linux-user/main.c b/linux-user/main.c index 94e321684d2..19acb6bead2 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -90,8 +90,8 @@ int fix_64bit_inodes = 0; int box64_nopulse = 0; int box64_novulkan = 0; char* libGL = NULL; -int kzt_init(char** argv, int argc,char** target_argv, int target_argc, - struct linux_binprm* bprm); +int kzt_init(CPUX86State *env, char** argv, int argc, char** target_argv, + int target_argc, struct linux_binprm* bprm); void kzt_bridge_init(void); int is_user_map = 0; #endif @@ -1562,7 +1562,7 @@ int main(int argc, char **argv, char **envp) } #endif #if defined(CONFIG_LATX_KZT) && defined(TARGET_X86_64) - kzt_init(argv, argc, target_argv, target_argc, &bprm); + kzt_init(env, argv, argc, target_argv, target_argc, &bprm); #endif for (wrk = target_environ; *wrk; wrk++) { g_free(*wrk); diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 1cdca47f8e1..24f4063db22 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -26,7 +26,6 @@ #include "qapi/qapi-types-common.h" #ifdef CONFIG_LATX_KZT #include "kzt_guest_dl_state.h" -#include "kzt_lazy_binding.h" #include "kzt_loader_callback_scope.h" #endif @@ -1410,8 +1409,7 @@ typedef struct HVFX86LazyFlags { typedef struct CPUX86State { #ifdef CONFIG_LATX #ifdef CONFIG_LATX_KZT - kzt_lazy_binding_pending_t kzt_lazy_binding_pending; - uintptr_t kzt_lazy_original_return; + struct box64context_s *kzt_runtime_context; kzt_guest_library_loader_scope_t kzt_guest_library_loader_scope; #ifdef TARGET_X86_64 kzt_guest_dlerror_state_t kzt_guest_dlerror_state; diff --git a/target/i386/latx/context/box64context.c b/target/i386/latx/context/box64context.c index 571703d1c66..e91f52c396d 100755 --- a/target/i386/latx/context/box64context.c +++ b/target/i386/latx/context/box64context.c @@ -30,6 +30,12 @@ #endif #include +static void kzt_xcb_shadow_destroy(void *guest, void *opaque) +{ + (void)opaque; + free(guest); +} + #ifdef CONFIG_LATX_KZT static uint64_t kzt_context_init_timing_now(void) { @@ -99,6 +105,8 @@ box64context_t *NewBox64Context(int argc) context->system = NewBridge(); context->dlprivate = NewDLPrivate(); context->box64lib = dlopen(NULL, RTLD_NOW|RTLD_GLOBAL); + context->kzt_xcb_connection_map = + kzt_xcb_connection_map_init(kzt_xcb_shadow_destroy, NULL); context->argc = argc; context->argv = (char**)box_calloc(context->argc+1, sizeof(char*)); pthread_mutex_init(&context->mutex_lock, NULL); @@ -109,6 +117,7 @@ box64context_t *NewBox64Context(int argc) /* Optional acceleration metadata is created on first KZT use so a * context that never observes a guest link-map keeps the old footprint. */ (void)kzt_guest_library_access_init(&context->kzt_guest_library_access); + kzt_loader_event_hook_context_init(&context->kzt_loader_event_hook); context->kzt_lazy_prebind_scope = kzt_lazy_prebind_scope_init(); if (timing_enabled) { timing_access = kzt_context_init_timing_now(); @@ -187,7 +196,9 @@ void FreeBox64Context(box64context_t** context) box64context_t* ctx = *context; // local copy to do the cleanning - kzt_guest_dl_api_entry_state_destroy(ctx->dlprivate); + kzt_guest_dl_api_entry_state_begin_teardown(ctx->dlprivate); + free(ctx->kzt_loader_bridge_info); + ctx->kzt_loader_bridge_info = NULL; #ifdef CONFIG_LATX_KZT /* FreeBox64Context is entered only after guest execution and loader @@ -198,11 +209,13 @@ void FreeBox64Context(box64context_t** context) &ctx->kzt_guest_library_access); #endif + kzt_xcb_connection_map_destroy(&ctx->kzt_xcb_connection_map); if(ctx->local_maplib) FreeLibrarian(&ctx->local_maplib); if(ctx->maplib) FreeLibrarian(&ctx->maplib); #ifdef CONFIG_LATX_KZT + kzt_loader_event_hook_context_destroy(&ctx->kzt_loader_event_hook); kzt_guest_library_access_destroy(&ctx->kzt_guest_library_access); kzt_lazy_prebind_scope_destroy(&ctx->kzt_lazy_prebind_scope); kzt_guest_registry_context_destroy(&ctx->kzt_guest_registry_context, @@ -254,28 +267,6 @@ void FreeBox64Context(box64context_t** context) box_free(ctx); } -int AddMallocMap(box64context_t* ctx, struct malloc_map* map) { - int idx = ctx->mallocmapsize; - if(idx==ctx->mallocmapcap) { - // resize... - ctx->mallocmapcap += 16; - ctx->mallocmaps = (struct malloc_map**)box_realloc(ctx->mallocmaps, sizeof(struct malloc_map *) * ctx->mallocmapcap); - } - ctx->mallocmaps[idx] = map; - ctx->mallocmapsize++; - printf_log(LOG_INFO, "Adding \"%p\" as #%d in mallocmap collection\n", ctx->mallocmaps[idx], idx); - return idx; -} -struct malloc_map * SearchMallocMap(box64context_t* ctx, char *elfname) -{ - for (int i =0; i < ctx->mallocmapsize; i++) { - if (!strcmp(basename(ElfName(ctx->mallocmaps[i]->h)), elfname)) { - return ctx->mallocmaps[i]; - } - } - return NULL; -} - #if defined(CONFIG_LATX_KZT) && defined(CONFIG_LATX_DEBUG) int AddKztDebugInfo(box64context_t* ctx, struct latx_kzt_debug* debuginfo) { diff --git a/target/i386/latx/context/bridge.c b/target/i386/latx/context/bridge.c index 12e43809a77..df386abed17 100755 --- a/target/i386/latx/context/bridge.c +++ b/target/i386/latx/context/bridge.c @@ -25,6 +25,42 @@ KHASH_MAP_INIT_INT64(bridgemap, uintptr_t) +typedef struct guarded_bridge_key_s { + wrapper_t wrapper; + uintptr_t function; + int stack_bytes; + uintptr_t guest_fallback_target; + kzt_bridge_guard_kind_t guard_kind; +} guarded_bridge_key_t; + +static khint_t guarded_bridge_key_hash(guarded_bridge_key_t key) +{ + uint64_t hash = (uintptr_t)key.wrapper; + + hash ^= key.function + UINT64_C(0x9e3779b97f4a7c15) + + (hash << 6) + (hash >> 2); + hash ^= (uint32_t)key.stack_bytes + UINT64_C(0x9e3779b97f4a7c15) + + (hash << 6) + (hash >> 2); + hash ^= key.guest_fallback_target + UINT64_C(0x9e3779b97f4a7c15) + + (hash << 6) + (hash >> 2); + hash ^= (uint32_t)key.guard_kind + UINT64_C(0x9e3779b97f4a7c15) + + (hash << 6) + (hash >> 2); + return kh_int64_hash_func(hash); +} + +static int guarded_bridge_key_equal(guarded_bridge_key_t left, + guarded_bridge_key_t right) +{ + return left.wrapper == right.wrapper && + left.function == right.function && + left.stack_bytes == right.stack_bytes && + left.guest_fallback_target == right.guest_fallback_target && + left.guard_kind == right.guard_kind; +} + +KHASH_INIT(guardedbridgemap, guarded_bridge_key_t, uintptr_t, 1, + guarded_bridge_key_hash, guarded_bridge_key_equal) + //onebridge is 32 bytes #define NBRICK 4096/sizeof(onebridge_t) typedef struct brick_s brick_t; @@ -39,6 +75,7 @@ typedef struct bridge_s { brick_t *head; brick_t *last; // to speed up kh_bridgemap_t *bridgemap; + kh_guardedbridgemap_t *guardedmap; struct bridge_s *fork_next; } bridge_t; @@ -220,6 +257,7 @@ bridge_t *NewBridge(void) b->head = NewBrick(); b->last = b->head; b->bridgemap = kh_init(bridgemap); + b->guardedmap = kh_init(guardedbridgemap); pthread_mutex_lock(&bridge_fork_lock); b->fork_next = fork_bridges; fork_bridges = b; @@ -256,6 +294,7 @@ void FreeBridge(bridge_t** bridge) b = n; } kh_destroy(bridgemap, current->bridgemap); + kh_destroy(guardedbridgemap, current->guardedmap); *bridge = NULL; pthread_mutex_unlock(¤t->lock); pthread_mutex_destroy(¤t->lock); @@ -264,7 +303,9 @@ void FreeBridge(bridge_t** bridge) } static uintptr_t bridge_add_locked(bridge_t* bridge, wrapper_t w, void* fnc, - int N, const char* name) + int N, const char* name, int add_to_map, + uintptr_t guest_fallback_target, + kzt_bridge_guard_kind_t guard_kind) { brick_t *b = NULL; int sz = -1; @@ -282,10 +323,14 @@ static uintptr_t bridge_add_locked(bridge_t* bridge, wrapper_t w, void* fnc, b->b[sz].f = (uintptr_t)fnc; b->b[sz].C3 = N?0xC2:0xC3; b->b[sz].N = N; - // add bridge to map, for fast recovery - int ret; - khint_t k = kh_put(bridgemap, bridge->bridgemap, (uintptr_t)fnc, &ret); - kh_value(bridge->bridgemap, k) = (uintptr_t)&b->b[sz].CC; + b->b[sz].guest_fallback_target = guest_fallback_target; + b->b[sz].guard_kind = guard_kind; + if (add_to_map) { + // add bridge to map, for fast recovery + int ret; + khint_t k = kh_put(bridgemap, bridge->bridgemap, (uintptr_t)fnc, &ret); + kh_value(bridge->bridgemap, k) = (uintptr_t)&b->b[sz].CC; + } return (uintptr_t)&b->b[sz].CC; } @@ -307,11 +352,71 @@ uintptr_t AddBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N, const char* return 0; } pthread_mutex_lock(&bridge->lock); - ret = bridge_add_locked(bridge, w, fnc, N, name); + ret = bridge_add_locked(bridge, w, fnc, N, name, 1, 0, + KZT_BRIDGE_GUARD_NONE); + pthread_mutex_unlock(&bridge->lock); + return ret; +} + +uintptr_t AddGuardedBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N, + const char* name, uintptr_t fallback, + kzt_bridge_guard_kind_t guard_kind) +{ + guarded_bridge_key_t key; + khint_t entry; + int insert_status; + uintptr_t ret; + + if (!bridge || !w || !fnc || !fallback || + guard_kind != KZT_BRIDGE_GUARD_XCB_CONNECTION) { + return 0; + } + key = (guarded_bridge_key_t) { + .wrapper = w, + .function = (uintptr_t)fnc, + .stack_bytes = N, + .guest_fallback_target = fallback, + .guard_kind = guard_kind, + }; + pthread_mutex_lock(&bridge->lock); + if (!bridge->guardedmap) { + pthread_mutex_unlock(&bridge->lock); + return 0; + } + entry = kh_get(guardedbridgemap, bridge->guardedmap, key); + if (entry != kh_end(bridge->guardedmap)) { + ret = kh_value(bridge->guardedmap, entry); + pthread_mutex_unlock(&bridge->lock); + return ret; + } + entry = kh_put(guardedbridgemap, bridge->guardedmap, key, + &insert_status); + if (insert_status < 0) { + pthread_mutex_unlock(&bridge->lock); + return 0; + } + ret = bridge_add_locked(bridge, w, fnc, N, name, 0, fallback, + guard_kind); + kh_value(bridge->guardedmap, entry) = ret; pthread_mutex_unlock(&bridge->lock); return ret; } +#ifdef BRIDGE_TEST +int bridge_test_guarded_count(bridge_t *bridge) +{ + int count; + + if (!bridge) { + return 0; + } + pthread_mutex_lock(&bridge->lock); + count = bridge->guardedmap ? kh_size(bridge->guardedmap) : 0; + pthread_mutex_unlock(&bridge->lock); + return count; +} +#endif + uintptr_t CheckBridged(bridge_t* bridge, void* fnc) { uintptr_t ret; @@ -342,7 +447,8 @@ uintptr_t AddCheckBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N, const } #endif if(!ret) - ret = bridge_add_locked(bridge, w, fnc, N, name); + ret = bridge_add_locked(bridge, w, fnc, N, name, 1, 0, + KZT_BRIDGE_GUARD_NONE); pthread_mutex_unlock(&bridge->lock); return ret; } @@ -364,7 +470,8 @@ uintptr_t AddAutomaticBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N) } #endif if(!ret) - ret = bridge_add_locked(bridge, w, fnc, N, NULL); + ret = bridge_add_locked(bridge, w, fnc, N, NULL, 1, 0, + KZT_BRIDGE_GUARD_NONE); pthread_mutex_unlock(&bridge->lock); if(alternate_add_if_absent(fnc, (void*)ret)) { printf_log(LOG_DEBUG, "Adding AutomaticBridge for %p to %p\n", fnc, (void*)ret); @@ -399,6 +506,8 @@ void* GetNativeFnc(uintptr_t fnc) onebridge_t *b = (onebridge_t*)fnc; if(b->CC != 0xCC || b->S!='S' || b->C!='C' || (b->C3!=0xC3 && b->C3!=0xC2)) return NULL; // not a bridge?! + if (b->guard_kind != KZT_BRIDGE_GUARD_NONE) + return NULL; return (void*)b->f; } @@ -407,6 +516,8 @@ void* GetNativeFncOrFnc(uintptr_t fnc) onebridge_t *b = (onebridge_t*)fnc; if(b->CC != 0xCC || b->S!='S' || b->C!='C' || (b->C3!=0xC3 && b->C3!=0xC2)) return (void*)fnc; // not a bridge?! + if (b->guard_kind != KZT_BRIDGE_GUARD_NONE) + return (void*)fnc; return (void*)b->f; } diff --git a/target/i386/latx/context/dlopen_recycle_transaction.c b/target/i386/latx/context/dlopen_recycle_transaction.c deleted file mode 100644 index 88586ee3342..00000000000 --- a/target/i386/latx/context/dlopen_recycle_transaction.c +++ /dev/null @@ -1,27 +0,0 @@ -#include "dlopen_recycle_transaction.h" - -void *dlopen_recycle_transaction( - void *success_handle, size_t *count, int increment_count, void *opaque, - dlopen_recycle_prepare_fn prepare, dlopen_recycle_step_fn guest_open, - dlopen_recycle_step_fn reload, - dlopen_recycle_rollback_fn rollback) -{ - int guest_opened = 0; - - if (!success_handle || !count || !prepare || !guest_open || !reload || - !rollback) - return NULL; - prepare(opaque); - if (guest_open(opaque) != 0) { - rollback(opaque, 0); - return NULL; - } - guest_opened = 1; - if (reload(opaque) != 0) { - rollback(opaque, guest_opened); - return NULL; - } - if (increment_count) - ++*count; - return success_handle; -} diff --git a/target/i386/latx/context/elfloader.c b/target/i386/latx/context/elfloader.c index a099cfe1147..816a25b9955 100755 --- a/target/i386/latx/context/elfloader.c +++ b/target/i386/latx/context/elfloader.c @@ -37,13 +37,13 @@ #include "myalign.h" #include "kzt_rela_stub_detector.h" #include "kzt_guest_registry.h" +#include "kzt_guest_glob_dat_target.h" +#include "kzt_guest_dl_api.h" #include "kzt_guest_symbol_scope.h" #include "kzt_lifecycle_diagnostics.h" #include "kzt_per_object_got_plt.h" #include "kzt_observation_adapter.h" #include "kzt_jump_slot_production.h" -#include "kzt_lazy_diagnostics.h" -#include "kzt_owner_resolver.h" #include "kzt_plt_resolver_adapter.h" #include "elf_plt_relocation.h" #include "elfmap.h" @@ -730,29 +730,8 @@ static int kzt_eager_compatibility_write( if (!slot) { return KZT_EAGER_COMPATIBILITY_WRITE_ERROR; } -#ifdef CONFIG_LATX_KZT - if (option_kzt || wine_option_kzt) { - uintptr_t expected = observed; - - if (kzt_patch_spike_guard_circuit_open( - KztPatchSpikeGuardForContext(context))) { - return KZT_EAGER_COMPATIBILITY_WRITE_CIRCUIT_OPEN; - } - if (!__atomic_compare_exchange_n( - (uintptr_t *)slot, &expected, replacement, 0, - __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) { - if (final_value) { - *final_value = expected; - } - return KZT_EAGER_COMPATIBILITY_WRITE_CAS_MISMATCH; - } - if (final_value) { - *final_value = replacement; - } - return KZT_EAGER_COMPATIBILITY_WRITE_APPLIED; - } -#endif (void)context; + (void)observed; *slot = replacement; if (final_value) { *final_value = replacement; @@ -761,156 +740,24 @@ static int kzt_eager_compatibility_write( } #ifdef CONFIG_LATX_KZT -static int kzt_elfloader_read_guest_memory(uintptr_t guest_addr, void *dst, - size_t size, void *opaque); - -typedef struct kzt_guest_glob_dat_target { - uintptr_t guest_target; - uintptr_t selected_target; - kzt_patch_object_ref_t owner; - kzt_guest_registry_source_lease_t source_lease; - kzt_guest_registry_patch_decision_lease_t decision_lease; - kzt_guest_library_loader_quiescence_lease_t loader_quiescence_lease; - kzt_guest_symbol_scope_request_t scope_request; - kzt_guest_symbol_scope_result_t scope_proof; - int exact_bridge; -} kzt_guest_glob_dat_target_t; - -static void kzt_guest_glob_dat_target_release( - kzt_guest_glob_dat_target_t *target) +static void kzt_relocation_abort_unrecoverable( + const elfheader_t *head, const char *symbol, const char *stage) { - if (!target) { - return; - } - kzt_guest_registry_patch_decision_lease_release(&target->decision_lease); - kzt_guest_library_loader_quiescence_release( - &target->loader_quiescence_lease); - kzt_guest_registry_source_lease_release(&target->source_lease); + printf_log( + LOG_NONE, + "KZT: unrecoverable relocation transaction for %s in %s during %s; " + "aborting before guest execution\n", + symbol ? symbol : "(unknown)", + head && head->name ? head->name : "(unknown)", + stage ? stage : "unknown stage"); + abort(); } +#endif -static int kzt_guest_glob_dat_target_resolve( - box64context_t *context, elfheader_t *head, uintptr_t guest_target, - unsigned long symbol_index, const Elf64_Sym *symbol, - const char *symbol_name, int version, const char *version_name, - kzt_guest_glob_dat_target_t *target) -{ - kzt_owner_resolution_t resolution; - kzt_guest_registry_address_match_t source_match; - kzt_guest_registry_address_match_t owner_match; - kzt_guest_link_map_reader_ops_t reader_ops = { - .read_memory = kzt_elfloader_read_guest_memory, - }; - kzt_guest_library_binding_key_t key; - kzt_guest_library_handle_t handle; - uintptr_t namespace_head = 0; - uintptr_t bridge_start = 0; - uintptr_t bridge_end = 0; +#ifdef CONFIG_LATX_KZT +static int kzt_elfloader_read_guest_memory(uintptr_t guest_addr, void *dst, + size_t size, void *opaque); - if (!context || !head || !head->self_link_map || !guest_target || - symbol_index >= head->numDynSym || !symbol || !symbol_name || - !symbol_name[0] || !target) { - return 0; - } - memset(target, 0, sizeof(*target)); - target->guest_target = guest_target; - target->selected_target = guest_target; - kzt_owner_resolver_init(&resolution); - if (kzt_owner_resolver_resolve_current( - KztGuestRegistryForContext(context), guest_target, - guest_target, &resolution) != 0 || - resolution.status != KZT_OWNER_RESOLVER_RESOLVED || - resolution.owner_match != KZT_PATCH_OWNER_MATCH || - !resolution.current_owner.known || - !resolution.current_owner.link_map_addr || - !resolution.current_owner.generation) { - return 0; - } - target->owner = resolution.current_owner; - - /* The legacy bridge cache is keyed by name. Use it only for confirmed - unversioned function symbols in the exact guest-selected object. */ - if (ELF64_ST_TYPE(symbol->st_info) != STT_FUNC || - version >= 2 || (version_name && version_name[0]) || - context->kzt_guest_scope_layout == - KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED || - kzt_guest_registry_find_live_object( - KztGuestRegistryForContext(context), head->self_link_map, - &source_match) != 0 || - !source_match.generation || - source_match.namespace_id_status != KZT_GUEST_FIELD_OK || - source_match.namespace_id != 0 || - kzt_guest_registry_find_live_object( - KztGuestRegistryForContext(context), - target->owner.link_map_addr, &owner_match) != 0 || - owner_match.generation != target->owner.generation || - owner_match.namespace_id_status != KZT_GUEST_FIELD_OK || - owner_match.namespace_id != 0 || - kzt_guest_registry_source_lease_acquire( - KztGuestRegistryForContext(context), head->self_link_map, - source_match.generation, source_match.namespace_id, - &target->source_lease) != 0 || - kzt_guest_registry_patch_decision_lease_acquire( - &target->source_lease, &target->decision_lease) != 0 || - kzt_guest_library_loader_quiescence_try_acquire( - KztGuestLibraryBindingsForContext(context), - &target->loader_quiescence_lease) != 0 || - kzt_guest_registry_context_get_main_namespace_head( - &context->kzt_guest_registry_context, &namespace_head) != 0) { - kzt_guest_glob_dat_target_release(target); - return 1; - } - target->scope_request = (kzt_guest_symbol_scope_request_t) { - .source = { - .link_map_addr = head->self_link_map, - .generation = source_match.generation, - .namespace_id = source_match.namespace_id, - .namespace_head = namespace_head, - .layout = context->kzt_guest_scope_layout, - }, - .symbol = symbol_name, - .version_evidence = KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, - .reference_binding = ELF64_ST_BIND(symbol->st_info), - .reference_type = ELF64_ST_TYPE(symbol->st_info), - .reference_visibility = symbol->st_other & 0x3, - }; - if (kzt_guest_symbol_scope_check( - &target->scope_request, target->owner.link_map_addr, - guest_target, - &reader_ops, &target->scope_proof) != - KZT_GUEST_SYMBOL_SCOPE_SAFE) { - kzt_guest_glob_dat_target_release(target); - return 1; - } - key = (kzt_guest_library_binding_key_t) { - .link_map_addr = target->owner.link_map_addr, - .generation = target->owner.generation, - .namespace_id = owner_match.namespace_id, - .namespace_kind = owner_match.namespace_id == 0 ? - KZT_GUEST_LIBRARY_NAMESPACE_MAIN : - KZT_GUEST_LIBRARY_NAMESPACE_EXPLICIT, - }; - memset(&handle, 0, sizeof(handle)); - if (KztGuestLibraryLookupForContext(context, &key, &handle) != 0 || - !handle.library || - (handle.object_type != KZT_GUEST_LIBRARY_OBJECT_EMULATED && - handle.object_type != KZT_GUEST_LIBRARY_OBJECT_WRAPPED)) { - kzt_guest_library_handle_release(&handle); - kzt_guest_glob_dat_target_release(target); - return 1; - } - if (GetLibSymbolStartEnd( - handle.library, symbol_name, 0, &bridge_start, &bridge_end, - version, version_name, 0) && - bridge_start) { - target->selected_target = bridge_start; - target->exact_bridge = bridge_start != guest_target; - } - kzt_guest_library_handle_release(&handle); - if (!target->exact_bridge) { - kzt_guest_glob_dat_target_release(target); - } - return 1; -} #endif int RelocateElfRELA(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t* head, int cnt, Elf64_Rela *rela, int* need_resolv) @@ -936,50 +783,43 @@ int RelocateElfRELA(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t if ((option_kzt || wine_option_kzt) && p) { uintptr_t observed = __atomic_load_n( (uintptr_t *)p, __ATOMIC_ACQUIRE); - kzt_guest_glob_dat_target_t guest_target; + kzt_guest_glob_dat_route_result_t route_result; kzt_guest_link_map_reader_ops_t reader_ops = { .read_memory = kzt_elfloader_read_guest_memory, }; - kzt_guest_symbol_scope_result_t revalidated_scope; if (observed && bind != STB_LOCAL && - kzt_guest_glob_dat_target_resolve( - my_context, head, observed, + kzt_guest_glob_dat_route( + my_context, head, (uintptr_t)p, observed, ELF64_R_SYM(rela[i].r_info), sym, symname, - version, vername, &guest_target)) { - uintptr_t expected = observed; - int scope_valid = - !guest_target.exact_bridge || - kzt_guest_symbol_scope_revalidate( - &guest_target.scope_proof, - &guest_target.scope_request, &reader_ops, - &revalidated_scope) == - KZT_GUEST_SYMBOL_SCOPE_SAFE; - int applied = - guest_target.selected_target == observed || - (scope_valid && - !kzt_patch_spike_guard_circuit_open( - KztPatchSpikeGuardForContext(my_context)) && - __atomic_compare_exchange_n( - (uintptr_t *)p, &expected, - guest_target.selected_target, 0, - __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)); - + version, vername, &reader_ops, &route_result)) { printf_kzt_registry_diagnostics( "kzt_eager_relocation schema=1 type=GLOB_DAT " "symbol=%s route=%s host_lookup=0 " "guest=%p selected=%p final=%p\n", symname ? symname : "(none)", - applied && guest_target.exact_bridge ? - "EXACT_OWNER_BRIDGE" : "GUEST_PRESERVED", + route_result.writer_result == + KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED ? + "EXACT_OWNER_BRIDGE" : + (route_result.writer_result == + KZT_PRODUCTION_SLOT_TRANSACTION_ROLLED_BACK ? + "WRITE_ROLLED_BACK" : + (route_result.writer_result == + KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE ? + "UNRECOVERABLE" : + "GUEST_PRESERVED")), (void *)observed, - (void *)guest_target.selected_target, - (void *)(applied ? - guest_target.selected_target : expected)); - kzt_guest_glob_dat_target_release(&guest_target); + (void *)route_result.selected_target, + (void *)route_result.final_value); + if (route_result.writer_result == + KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE) { + kzt_relocation_abort_unrecoverable( + head, symname, "GLOB_DAT native bridge"); + } continue; } - if (bind != STB_LOCAL) { + if (observed || bind != STB_LOCAL || + !head->self_link_map) { printf_kzt_registry_diagnostics( "kzt_eager_relocation schema=1 type=GLOB_DAT " "symbol=%s route=GUEST_PRESERVED host_lookup=0 " @@ -988,6 +828,38 @@ int RelocateElfRELA(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t (void *)observed); continue; } + { + uintptr_t final_value = observed; + uintptr_t local_target = + sym->st_value + head->delta; + kzt_production_slot_transaction_result_t writer_result = + kzt_production_guest_relocation_write( + my_context, head->self_link_map, + KZT_PATCH_RELOCATION_GLOB_DAT, + (uintptr_t)p, observed, local_target, + symname, + version >= 2 ? + KZT_SYMBOL_VERSION_VERSIONED : + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + version >= 2 ? vername : NULL, + &final_value); + + printf_kzt_registry_diagnostics( + "kzt_eager_relocation schema=1 type=GLOB_DAT " + "symbol=%s route=%s host_lookup=0 " + "target=%p final=%p\n", + symname ? symname : "(none)", + writer_result == + KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED ? + "LOCAL_APPLIED" : "GUEST_PRESERVED", + (void *)local_target, (void *)final_value); + if (writer_result != + KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED && + final_value != local_target) { + return -1; + } + continue; + } } #endif kzt_resolve_legacy_rela_target( @@ -1041,12 +913,34 @@ int RelocateElfRELA(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t symname, rela[i].r_addend); if (defer_plan.should_add_delta) { uintptr_t final_value = slot_observation; - int write_status = kzt_eager_compatibility_write( - my_context, p, slot_observation, - slot_observation + head->delta, &final_value); + int write_status; + +#ifdef CONFIG_LATX_KZT + if (option_kzt || wine_option_kzt) { + write_status = + kzt_production_guest_relocation_write( + my_context, head->self_link_map, + KZT_PATCH_RELOCATION_JUMP_SLOT, + (uintptr_t)p, slot_observation, + slot_observation + head->delta, + symname, + version >= 2 ? + KZT_SYMBOL_VERSION_VERSIONED : + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + version >= 2 ? vername : NULL, + &final_value); + } else +#endif + { + write_status = kzt_eager_compatibility_write( + my_context, p, slot_observation, + slot_observation + head->delta, &final_value); + } if (write_status != - KZT_EAGER_COMPATIBILITY_WRITE_APPLIED) { + KZT_EAGER_COMPATIBILITY_WRITE_APPLIED && + write_status != + KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED) { printf_kzt_registry_diagnostics( "kzt_eager_relocation schema=1 " "type=JUMP_SLOT symbol=%s route=%s " @@ -1056,6 +950,9 @@ int RelocateElfRELA(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t KZT_EAGER_COMPATIBILITY_WRITE_CAS_MISMATCH ? "CAS_MISMATCH" : "WRITE_BLOCKED", (void *)final_value); + if (final_value != slot_observation + head->delta) { + return -1; + } } } *need_resolv = 1; @@ -1116,11 +1013,50 @@ int RelocateElfRELA(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t "GUEST_PRESERVED")), (void *)slot_observation, (void *)route_result.final_value); + if (route_result.status == + KZT_JUMP_SLOT_ROUTE_UNRECOVERABLE) { + kzt_relocation_abort_unrecoverable( + head, symname, "JUMP_SLOT native bridge"); + } break; } } if ((option_kzt || wine_option_kzt) && - bind != STB_LOCAL) { + bind == STB_LOCAL && head->self_link_map && p && + slot_is_unresolved_stub) { + uintptr_t local_target = + sym->st_value + head->delta + rela[i].r_addend; + uintptr_t final_value = __atomic_load_n( + (uintptr_t *)p, __ATOMIC_ACQUIRE); + kzt_production_slot_transaction_result_t writer_result = + kzt_production_guest_relocation_write( + my_context, head->self_link_map, + KZT_PATCH_RELOCATION_JUMP_SLOT, + (uintptr_t)p, final_value, local_target, + symname, + version >= 2 ? + KZT_SYMBOL_VERSION_VERSIONED : + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + version >= 2 ? vername : NULL, + &final_value); + + printf_kzt_registry_diagnostics( + "kzt_eager_relocation schema=1 type=JUMP_SLOT " + "symbol=%s route=%s host_lookup=0 " + "target=%p final=%p\n", + symname ? symname : "(none)", + writer_result == + KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED ? + "LOCAL_APPLIED" : "GUEST_PRESERVED", + (void *)local_target, (void *)final_value); + if (writer_result != + KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED && + final_value != local_target) { + return -1; + } + break; + } + if (option_kzt || wine_option_kzt) { uintptr_t final_value = p ? __atomic_load_n( (uintptr_t *)p, __ATOMIC_ACQUIRE) : slot_observation; @@ -1203,8 +1139,6 @@ static int RelocateElfPltRELA(void *opaque, int *need_resolver) } #ifdef CONFIG_LATX_KZT -static void KztLazyBindingCompleteResolver(void); - static int kzt_per_object_runtime_to_raw(uintptr_t runtime, uintptr_t load_bias, uintptr_t *raw) @@ -1455,12 +1389,6 @@ int KztPrebindTargetTbPrepare(uintptr_t target) #endif } -static int kzt_per_object_prepare_pinned_target(uintptr_t target, void *opaque) -{ - (void)opaque; - return KztPrebindTargetTbPrepare(target); -} - int KztPerObjectGotPltWrite(uintptr_t link_map_addr, unsigned long generation, const kzt_guest_dynamic_view_t *view, @@ -1517,11 +1445,6 @@ int KztPerObjectGotPltWrite(uintptr_t link_map_addr, context->kzt_plt_resolver_bridge = AddBridge( context->system, vFE, PltResolver, 0, "PltResolver"); } - if (!context->kzt_lazy_completion_bridge) { - context->kzt_lazy_completion_bridge = AddBridge( - context->system, vFE, KztLazyBindingCompleteResolver, 0, - "KztLazyBindingCompleteResolver"); - } resolver_bridge = context->kzt_plt_resolver_bridge; if (!kzt_plt_resolver_injection_allowed(guest_resolver, resolver_bridge)) { return -1; @@ -1593,9 +1516,6 @@ int KztPerObjectGotPltWrite(uintptr_t link_map_addr, KztPerObjectGotPltRelease((uintptr_t)head); return -1; } - (void)kzt_production_lazy_prebind_object( - context, head, generation, view, - kzt_per_object_prepare_pinned_target, NULL); printf_kzt_registry_diagnostics( "kzt_per_object_got_plt schema=1 link_map=0x%lx generation=%lu " "result=APPLIED\n", @@ -1823,15 +1743,6 @@ int RelocateElfPlt(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t* pltResolver = AddBridge(my_context->system, vFE, PltResolver, 0, "PltResolver"); } resolver_bridge = pltResolver; -#endif -#ifdef CONFIG_LATX_KZT - if ((option_kzt || wine_option_kzt) && - !my_context->kzt_lazy_completion_bridge) { - my_context->kzt_lazy_completion_bridge = AddBridge( - my_context->system, vFE, - KztLazyBindingCompleteResolver, 0, - "KztLazyBindingCompleteResolver"); - } #endif if(resolver_got_runtime) { #ifdef CONFIG_LATX_KZT @@ -1877,6 +1788,7 @@ int RelocateElfPlt(lib_t *maplib, lib_t *local_maplib, int bindnow, elfheader_t* .resolver_slot = resolver_got_runtime + 16, .guest_link_map = guest_link_map, .guest_resolver = guest_resolver, + .object_head = (uintptr_t)head, }; if (kzt_guest_registry_find_live_object( KztGuestRegistryForContext(my_context), @@ -2464,61 +2376,8 @@ int KztPltResolverDispatch(void *cpu_state, uintptr_t pc) } #ifdef CONFIG_LATX_KZT -static void KztLazyBindingCompleteResolver(void) -{ - CPUX86State *cpu = (CPUX86State *)lsenv->cpu_state; - kzt_lazy_binding_pending_t *pending = - &cpu->kzt_lazy_binding_pending; - uintptr_t original_return = cpu->kzt_lazy_original_return; - uintptr_t slot_addr = pending->slot_addr; - char symbol[KZT_LAZY_BINDING_SYMBOL_MAX]; - kzt_lazy_binding_result_t result; - - (void)slot_addr; - - symbol[0] = '\0'; - if (pending->symbol) { - strncpy(symbol, pending->symbol, sizeof(symbol) - 1); - symbol[sizeof(symbol) - 1] = '\0'; - } - if (pending->armed) { - if (option_kzt_lazy_diagnostics) { - kzt_lazy_binding_pending_t pending_snapshot = *pending; - kzt_lazy_diagnostic_emit_result_t diagnostic_result; - - pending_snapshot.symbol = pending->symbol ? - pending_snapshot.symbol_storage : NULL; - pending_snapshot.version = pending->version ? - pending_snapshot.version_storage : NULL; - (void)kzt_production_lazy_complete( - (void *)pending->context_id, pending, &result); - (void)kzt_lazy_diagnostics_emit_production( - &pending_snapshot, &result, &diagnostic_result); - } else { - (void)kzt_production_lazy_complete( - (void *)pending->context_id, pending, &result); - } - printf_log(LOG_DEBUG, - "KZT: lazy complete status=%d reason=%d slot=%p " - "before=%p after=%p sym=%s\n", - result.status, result.reason, - (void *)slot_addr, - (void *)result.slot_before, (void *)result.slot_after, - symbol[0] ? symbol : "(none)"); - } - kzt_lazy_binding_cancel(pending); - cpu->kzt_lazy_original_return = 0; - Push64(cpu, original_return); -} - typedef struct kzt_plt_resolver_production_state { elfheader_t *head; - uintptr_t slot_addr; - uintptr_t unresolved_stub; - const char *symbol; - kzt_symbol_version_evidence_t version_evidence; - const char *version; - long addend; } kzt_plt_resolver_production_state_t; static int kzt_plt_resolver_lookup_source( @@ -2535,19 +2394,7 @@ static int kzt_plt_resolver_lookup_source( if (kzt_guest_registry_find_lazy_source( registry, state->head->self_link_map, ®istry_source) == 0) { *source = (kzt_plt_resolver_source_t) { - .enabled = my_context->kzt_lazy_completion_bridge != 0, - .context_id = (uintptr_t)my_context, - .object_head = object_head, .source_link_map = state->head->self_link_map, - .source_generation = registry_source.generation, - .namespace_id = registry_source.namespace_id, - .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, - .slot_addr = state->slot_addr, - .unresolved_stub = state->unresolved_stub, - .symbol = state->symbol, - .version_evidence = state->version_evidence, - .version = state->version, - .addend = state->addend, .guest_resolver = registry_source.guest_resolver, }; return 0; @@ -2556,30 +2403,11 @@ static int kzt_plt_resolver_lookup_source( return -1; } *source = (kzt_plt_resolver_source_t) { - .enabled = 0, - .context_id = (uintptr_t)my_context, - .object_head = object_head, .source_link_map = state->head->self_link_map, - .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_UNSUPPORTED, - .slot_addr = state->slot_addr, - .unresolved_stub = state->unresolved_stub, - .symbol = state->symbol, - .version_evidence = state->version_evidence, - .version = state->version, - .addend = state->addend, .guest_resolver = state->head->kzt_guest_resolver, }; return 0; } - -static int kzt_plt_resolver_begin_lazy_binding( - const kzt_lazy_binding_begin_request_t *request, - kzt_lazy_binding_pending_t *pending, - kzt_lazy_binding_result_t *result, void *opaque) -{ - (void)opaque; - return kzt_lazy_binding_begin(request, pending, result); -} #endif static int plt_resolver_handoff_guest( @@ -2596,6 +2424,18 @@ static int plt_resolver_handoff_guest( return 0; } +static void plt_resolver_abort_unrecoverable( + elfheader_t *head, const char *symbol) +{ + printf_log( + LOG_NONE, + "KZT: unrecoverable PLT slot transaction for %s in %s; " + "aborting before guest resolver handoff\n", + symbol ? symbol : "(unknown)", + head && head->name ? head->name : "(unknown)"); + abort(); +} + static void plt_resolver_handoff_guest_or_abort( CPUX86State *cpu, elfheader_t *head, uint64_t relocation_slot, const char *reason) @@ -2690,20 +2530,9 @@ void PltResolver(void) kzt_lazy_direct_route_result_t direct_result; kzt_plt_resolver_production_state_t state = { .head = h, - .slot_addr = (uintptr_t)p, - .unresolved_stub = p ? - __atomic_load_n((uintptr_t *)p, __ATOMIC_ACQUIRE) : 0, - .symbol = symname, - .version_evidence = version_evidence, - .version = vername, - .addend = rel->r_addend, }; kzt_plt_resolver_runtime_ops_t ops = { .lookup_source = kzt_plt_resolver_lookup_source, - .begin_lazy_binding = kzt_plt_resolver_begin_lazy_binding, - .pending = &cpu->kzt_lazy_binding_pending, - .completion_bridge = my_context->kzt_lazy_completion_bridge, - .original_return = &cpu->kzt_lazy_original_return, .opaque = &state, }; kzt_plt_resolver_enter_result_t enter_result; @@ -2719,7 +2548,9 @@ void PltResolver(void) __atomic_load_n(p, __ATOMIC_ACQUIRE), symbol_index, symname, version_evidence, vername, &direct_result) == 0 && - direct_result.status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED) { + (direct_result.status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED || + direct_result.status == + KZT_LAZY_DIRECT_ROUTE_NATIVE_TRANSIENT)) { uint64_t route_done_ns = timing_enabled ? kzt_lazy_resolver_timing_now() : 0; @@ -2727,9 +2558,12 @@ void PltResolver(void) ? kzt_lazy_resolver_minor_faults() : 0; printf_kzt_registry_diagnostics( - "kzt_lazy_path schema=1 symbol=%s route=NEW_DIRECT " + "kzt_lazy_path schema=1 symbol=%s route=%s " "guest_handoff=0 legacy_lookup=0 legacy_write=0\n", - symname ? symname : "(none)"); + symname ? symname : "(none)", + direct_result.status == + KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED ? + "NEW_DIRECT" : "NEW_DIRECT_TRANSIENT"); (void)Pop64(cpu); (void)Pop64(cpu); Push64(cpu, direct_result.selected_target); @@ -2793,6 +2627,18 @@ void PltResolver(void) return; } + if (direct_result.status == KZT_LAZY_DIRECT_ROUTE_UNRECOVERABLE) { + plt_resolver_abort_unrecoverable(h, symname); + } + + if (kzt_patch_symbol_requires_dlerror_prebind(symname)) { + __atomic_store_n( + &my_context->kzt_guest_loader_route_present, 1, + __ATOMIC_RELEASE); + kzt_guest_dl_api_set_slow_required( + &cpu->kzt_guest_dlerror_state, 1); + } + if (kzt_plt_resolver_enter(cpu, &ops, &enter_result) == 0 && enter_result.status != KZT_PLT_RESOLVER_LEGACY_FRAME_RESTORED) { printf_kzt_registry_diagnostics( diff --git a/target/i386/latx/context/kzt_bridge_exact.c b/target/i386/latx/context/kzt_bridge_exact.c index 769d10ae8b8..d8b72dbc0ab 100644 --- a/target/i386/latx/context/kzt_bridge_exact.c +++ b/target/i386/latx/context/kzt_bridge_exact.c @@ -14,3 +14,16 @@ int kzt_bridge_is_exact(uintptr_t target, kzt_bridge_wrapper_t wrapper, } return 1; } + +int kzt_guarded_bridge_is_exact( + uintptr_t target, kzt_bridge_wrapper_t wrapper, void *native_symbol, + uintptr_t guest_fallback_target, kzt_bridge_guard_kind_t guard_kind) +{ + onebridge_t *entry = (onebridge_t *)target; + + return guest_fallback_target && + guard_kind == KZT_BRIDGE_GUARD_XCB_CONNECTION && + kzt_bridge_is_exact(target, wrapper, native_symbol) && + entry->guest_fallback_target == guest_fallback_target && + entry->guard_kind == guard_kind; +} diff --git a/target/i386/latx/context/kzt_guest_cancel_scope.c b/target/i386/latx/context/kzt_guest_cancel_scope.c new file mode 100644 index 00000000000..45f2e4e4b41 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_cancel_scope.c @@ -0,0 +1,48 @@ +#include "qemu/osdep.h" + +#include + +#include "callback.h" +#include "kzt_guest_cancel_scope.h" + +void kzt_guest_cancel_scope_begin( + box64context_t *context, kzt_guest_cancel_scope_t *scope) +{ + if (!scope) { + return; + } + *scope = (kzt_guest_cancel_scope_t) { 0 }; + if (kzt_guest_runtime_entry_acquire( + context, KZT_GUEST_RUNTIME_PTHREAD_SETCANCELTYPE, + &scope->runtime) != 0) { + return; + } + scope->switched = RunFunctionWithState( + scope->runtime.address, 2, PTHREAD_CANCEL_ASYNCHRONOUS, + &scope->oldtype) == 0; + if (!scope->switched) { + kzt_guest_runtime_entry_release(&scope->runtime); + } +} + +void kzt_guest_cancel_scope_end(kzt_guest_cancel_scope_t *scope) +{ + if (!scope || !scope->switched) { + return; + } + scope->switched = 0; + (void)RunFunctionWithState( + scope->runtime.address, 2, scope->oldtype, NULL); + kzt_guest_runtime_entry_release(&scope->runtime); +} + +void kzt_guest_cancel_scope_cleanup(void *opaque) +{ + kzt_guest_cancel_scope_t *scope = opaque; + + if (!scope) { + return; + } + scope->switched = 0; + kzt_guest_runtime_entry_release(&scope->runtime); +} diff --git a/target/i386/latx/context/kzt_guest_dl_api.c b/target/i386/latx/context/kzt_guest_dl_api.c index 6a5243624ea..38db77ebb56 100644 --- a/target/i386/latx/context/kzt_guest_dl_api.c +++ b/target/i386/latx/context/kzt_guest_dl_api.c @@ -8,6 +8,7 @@ #include "kzt_guest_library_adapter.h" #include "kzt_guest_library_binding.h" #include "kzt_guest_registry.h" +#include "kzt_guest_runtime_entry_state.h" #include "kzt_lifecycle_diagnostics.h" #ifdef CONFIG_LATX_KZT #include "kzt_jump_slot_production.h" @@ -60,7 +61,10 @@ int kzt_guest_dl_api_entry_state_init(dlprivate_t *dl) pthread_mutex_destroy(&state->mutex); return -1; } - state->initialized = 1; + __atomic_store_n( + &state->lifecycle, KZT_GUEST_DL_LIFECYCLE_OPEN, + __ATOMIC_RELEASE); + __atomic_store_n(&state->initialized, 1, __ATOMIC_RELEASE); return 0; } @@ -83,23 +87,31 @@ const kzt_guest_dl_entries_t *kzt_guest_dl_api_ensure_entries_prepared( kzt_guest_dl_entries_t local = { 0 }; kzt_guest_dl_entries_t *candidate = NULL; uintptr_t observed_dlerror; + int prepared = 1; int resolved; if (published_now) { *published_now = 0; } - if (!dl || !resolver || !fallback || - !(state = &dl->guest_dl_entries)->initialized) { + if (!dl || !resolver || !fallback) { + return NULL; + } + state = &dl->guest_dl_entries; + if (kzt_guest_dl_entry_state_enter(state) != 0) { return NULL; } published = kzt_guest_dl_api_load_entries(dl); if (published) { + pthread_mutex_lock(&state->mutex); + kzt_guest_dl_entry_state_leave_locked(state); + pthread_mutex_unlock(&state->mutex); return published; } memset(fallback, 0, sizeof(*fallback)); pthread_mutex_lock(&state->mutex); if (state->teardown) { + kzt_guest_dl_entry_state_leave_locked(state); pthread_mutex_unlock(&state->mutex); return NULL; } @@ -108,6 +120,7 @@ const kzt_guest_dl_entries_t *kzt_guest_dl_api_ensure_entries_prepared( published = kzt_guest_dl_api_load_entries(dl); if (published || state->teardown) { kzt_guest_dl_entry_slow_leave(state); + kzt_guest_dl_entry_state_leave_locked(state); pthread_mutex_unlock(&state->mutex); return published; } @@ -120,6 +133,7 @@ const kzt_guest_dl_entries_t *kzt_guest_dl_api_ensure_entries_prepared( if (state->initializer_valid && pthread_equal(state->initializer, pthread_self())) { kzt_guest_dl_entry_slow_leave(state); + kzt_guest_dl_entry_state_leave_locked(state); pthread_mutex_unlock(&state->mutex); return NULL; } @@ -148,12 +162,12 @@ const kzt_guest_dl_entries_t *kzt_guest_dl_api_ensure_entries_prepared( } if (prepare) { pthread_mutex_unlock(&state->mutex); - prepare(candidate, opaque); + prepared = prepare(candidate, opaque) == 0; pthread_mutex_lock(&state->mutex); } observed_dlerror = __atomic_load_n( &state->observed_dlerror, __ATOMIC_RELAXED); - if (!state->teardown && + if (!state->teardown && prepared && observed_dlerror == candidate->dlerror) { __atomic_store_n( &state->published, candidate, __ATOMIC_RELEASE); @@ -167,6 +181,7 @@ const kzt_guest_dl_entries_t *kzt_guest_dl_api_ensure_entries_prepared( state->initializer_valid = 0; published = kzt_guest_dl_api_load_entries(dl); kzt_guest_dl_entry_slow_leave(state); + kzt_guest_dl_entry_state_leave_locked(state); pthread_mutex_unlock(&state->mutex); box_free(candidate); return published ? published : fallback; @@ -180,24 +195,32 @@ const kzt_guest_dl_entries_t *kzt_guest_dl_api_ensure_entries( dl, resolver, NULL, opaque, fallback, published_now); } -void kzt_guest_dl_api_entry_state_destroy(dlprivate_t *dl) +void kzt_guest_dl_api_entry_state_begin_teardown(dlprivate_t *dl) { kzt_guest_dl_entry_state_t *state; kzt_guest_dl_entries_t *published; - if (!dl || !(state = &dl->guest_dl_entries)->initialized) { + if (!dl) { return; } - pthread_mutex_lock(&state->mutex); - state->teardown = 1; - pthread_cond_broadcast(&state->ready); - while (state->initializing || state->slow_users) { - pthread_cond_wait(&state->ready, &state->mutex); - } + state = &dl->guest_dl_entries; + kzt_guest_runtime_entry_state_begin_teardown(state); published = __atomic_exchange_n( &state->published, NULL, __ATOMIC_ACQ_REL); - pthread_mutex_unlock(&state->mutex); box_free(published); +} + +void kzt_guest_dl_api_entry_state_destroy(dlprivate_t *dl) +{ + kzt_guest_dl_entry_state_t *state; + + if (!dl || !__atomic_exchange_n( + &dl->guest_dl_entries.initialized, 0, + __ATOMIC_ACQ_REL)) { + return; + } + state = &dl->guest_dl_entries; + kzt_guest_dl_api_entry_state_begin_teardown(dl); pthread_cond_destroy(&state->ready); pthread_mutex_destroy(&state->mutex); memset(state, 0, sizeof(*state)); @@ -791,6 +814,9 @@ kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlsym( void *handle, void *symbol) { kzt_guest_dl_symbol_result_t result = { 0 }; + kzt_guest_loader_identity_t queried_identity = { 0 }; + kzt_guest_loader_identity_t known_identity = { 0 }; + const kzt_guest_loader_identity_t *identity_hint = NULL; uintptr_t guest_result; if (handle == KZT_GUEST_RTLD_NEXT) { @@ -799,8 +825,21 @@ kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlsym( } guest_result = kzt_guest_library_run_dlsym( entries->dlsym, handle, symbol); - result.value = kzt_guest_library_select_symbol_result( - context, (uintptr_t)handle, guest_result, +#ifdef CONFIG_LATX_KZT + if (guest_result && context && handle && kzt_guest_dl_api_enabled()) { + kzt_guest_registry_t *registry = KztGuestRegistryForContext(context); + + if (registry && kzt_guest_registry_find_loader_identity( + registry, (uintptr_t)handle, + &known_identity) != 0 && + kzt_guest_dl_api_query_identity( + entries, (uintptr_t)handle, &queried_identity) == 0) { + identity_hint = &queried_identity; + } + } +#endif + result.value = kzt_guest_library_select_symbol_result_with_identity( + context, (uintptr_t)handle, identity_hint, guest_result, (const char *)symbol, NULL); return result; } @@ -810,6 +849,9 @@ kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlvsym( void *handle, void *symbol, const char *version) { kzt_guest_dl_symbol_result_t result = { 0 }; + kzt_guest_loader_identity_t queried_identity = { 0 }; + kzt_guest_loader_identity_t known_identity = { 0 }; + const kzt_guest_loader_identity_t *identity_hint = NULL; uintptr_t guest_result; if (handle == KZT_GUEST_RTLD_NEXT) { @@ -818,14 +860,28 @@ kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlvsym( } guest_result = kzt_guest_library_run_dlvsym( entries->dlvsym, handle, symbol, version); - result.value = kzt_guest_library_select_symbol_result( - context, (uintptr_t)handle, guest_result, +#ifdef CONFIG_LATX_KZT + if (guest_result && context && handle && kzt_guest_dl_api_enabled()) { + kzt_guest_registry_t *registry = KztGuestRegistryForContext(context); + + if (registry && kzt_guest_registry_find_loader_identity( + registry, (uintptr_t)handle, + &known_identity) != 0 && + kzt_guest_dl_api_query_identity( + entries, (uintptr_t)handle, &queried_identity) == 0) { + identity_hint = &queried_identity; + } + } +#endif + result.value = kzt_guest_library_select_symbol_result_with_identity( + context, (uintptr_t)handle, identity_hint, guest_result, (const char *)symbol, version); return result; } kzt_guest_dlerror_result_t kzt_guest_dl_api_dlerror( - kzt_guest_dlerror_state_t *state, uintptr_t guest_dlerror) + kzt_guest_dlerror_state_t *state, uintptr_t guest_dlerror, + int guest_route_may_have_pending_error) { kzt_guest_dlerror_result_t result = { 0 }; @@ -847,10 +903,13 @@ kzt_guest_dlerror_result_t kzt_guest_dl_api_dlerror( if (state->last_error_guest_consumed) { state->last_error_guest_consumed = 0; kzt_guest_dl_api_set_slow_required(state, 0); - return result; + if (!guest_route_may_have_pending_error) { + return result; + } } } - if (state && !state->dlerror_slow_required) { + if (state && !state->dlerror_slow_required && + !guest_route_may_have_pending_error) { return result; } result.forward_to_guest_caller = 1; diff --git a/target/i386/latx/context/kzt_guest_dl_init.c b/target/i386/latx/context/kzt_guest_dl_init.c index 346aff7d173..aa6a912f103 100644 --- a/target/i386/latx/context/kzt_guest_dl_init.c +++ b/target/i386/latx/context/kzt_guest_dl_init.c @@ -4,12 +4,18 @@ #include "elfloader.h" #include "kzt_guest_dl_api.h" +#include "kzt_guest_runtime_entry_state.h" #include "pathcoll.h" extern const char *interp_prefix; -elfheader_t *tryLoadElfFromFile(const char *name); +elfheader_t *tryLoadElfFromFileForContext( + box64context_t *context, const char *name); void freeElfFromFile(elfheader_t **header); -void kzt_wine_init_x86(box64context_t *context, uintptr_t guest_dlsym); + +typedef struct kzt_guest_dl_init_scope_s { + box64context_t *context; + uintptr_t runtime_entries[KZT_GUEST_RUNTIME_ENTRY_COUNT]; +} kzt_guest_dl_init_scope_t; static int kzt_guest_dl_resolve_entries( kzt_guest_dl_entries_t *entries, void *opaque) @@ -17,10 +23,12 @@ static int kzt_guest_dl_resolve_entries( static const char *symbols[] = { "dlopen", "dlmopen", "dlsym", "dlclose", "dladdr", "dladdr1", "dlinfo", "dlvsym", "dlerror", + "free", "realloc", "pthread_setcanceltype", }; - box64context_t *context = opaque; + kzt_guest_dl_init_scope_t *scope = opaque; + box64context_t *context = scope->context; elfheader_t *header; - void *resolved[9] = { 0 }; + void *resolved[ARRAY_SIZE(symbols)] = { 0 }; int resolved_count = 0; #ifdef CONFIG_LOONGARCH_NEW_WORLD @@ -31,10 +39,8 @@ static int kzt_guest_dl_resolve_entries( if (!FindInCollection(path, &context->box64_ld_lib)) { PrependList(&context->box64_ld_lib, path, 1); } -#else - (void)context; #endif - header = tryLoadElfFromFile("libc.so.6"); + header = tryLoadElfFromFileForContext(context, "libc.so.6"); if (header) { ResetSpecialCaseElf( header, symbols, ARRAY_SIZE(symbols), resolved, @@ -42,7 +48,7 @@ static int kzt_guest_dl_resolve_entries( freeElfFromFile(&header); } if (resolved_count != ARRAY_SIZE(symbols)) { - header = tryLoadElfFromFile("libdl.so.2"); + header = tryLoadElfFromFileForContext(context, "libdl.so.2"); if (header) { ResetSpecialCaseElf( header, symbols, ARRAY_SIZE(symbols), resolved, @@ -61,22 +67,35 @@ static int kzt_guest_dl_resolve_entries( .dlvsym = (uintptr_t)resolved[7], .dlerror = (uintptr_t)resolved[8], }; + scope->runtime_entries[KZT_GUEST_RUNTIME_FREE] = + (uintptr_t)resolved[9]; + scope->runtime_entries[KZT_GUEST_RUNTIME_REALLOC] = + (uintptr_t)resolved[10]; + scope->runtime_entries[KZT_GUEST_RUNTIME_PTHREAD_SETCANCELTYPE] = + (uintptr_t)resolved[11]; return resolved_count == ARRAY_SIZE(symbols) ? 0 : -1; } -static void kzt_guest_dl_prepare_entries( +static int kzt_guest_dl_prepare_entries( const kzt_guest_dl_entries_t *entries, void *opaque) { - kzt_wine_init_x86(opaque, entries->dlsym); + kzt_guest_dl_init_scope_t *scope = opaque; + + (void)entries; + return kzt_guest_runtime_entry_state_publish( + &scope->context->dlprivate->guest_dl_entries, + scope->runtime_entries); } const kzt_guest_dl_entries_t *kzt_guest_dl_init_entries( box64context_t *context, kzt_guest_dl_entries_t *fallback) { + kzt_guest_dl_init_scope_t scope = { .context = context }; + if (!context || !context->dlprivate || !fallback) { return NULL; } return kzt_guest_dl_api_ensure_entries_prepared( context->dlprivate, kzt_guest_dl_resolve_entries, - kzt_guest_dl_prepare_entries, context, fallback, NULL); + kzt_guest_dl_prepare_entries, &scope, fallback, NULL); } diff --git a/target/i386/latx/context/kzt_guest_dynsym_lookup.c b/target/i386/latx/context/kzt_guest_dynsym_lookup.c index eaf6386fb8e..791905ce94f 100644 --- a/target/i386/latx/context/kzt_guest_dynsym_lookup.c +++ b/target/i386/latx/context/kzt_guest_dynsym_lookup.c @@ -313,17 +313,11 @@ kzt_guest_dynsym_inspect_symbol( visibility = candidate.st_other & 0x3; if (candidate.st_shndx == SHN_UNDEF || (visibility != STV_DEFAULT && visibility != STV_PROTECTED) || - (binding != STB_GLOBAL && binding != STB_WEAK -#ifdef STB_GNU_UNIQUE - && binding != STB_GNU_UNIQUE -#endif - ) || + (binding != STB_GLOBAL && binding != STB_WEAK && + binding != KZT_ELF_STB_GNU_UNIQUE) || (type != STT_NOTYPE && type != STT_OBJECT && - type != STT_FUNC && type != STT_COMMON && type != STT_TLS -#ifdef STT_GNU_IFUNC - && type != STT_GNU_IFUNC -#endif - )) { + type != STT_FUNC && type != STT_COMMON && type != STT_TLS && + type != KZT_ELF_STT_GNU_IFUNC)) { return KZT_GUEST_DYNSYM_SYMBOL_NO_MATCH; } if (candidate.st_value > UINTPTR_MAX) { diff --git a/target/i386/latx/context/kzt_guest_glob_dat_target.c b/target/i386/latx/context/kzt_guest_glob_dat_target.c new file mode 100644 index 00000000000..24c09018225 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_glob_dat_target.c @@ -0,0 +1,183 @@ +#include + +#include "box64context.h" +#include "elfloader_private.h" +#include "kzt_guest_glob_dat_target.h" +#include "kzt_owner_resolver.h" +#include "kzt_rela_runtime_bridge.h" +#include "kzt_xcb_route_policy.h" + +void kzt_guest_glob_dat_target_release( + kzt_guest_glob_dat_target_t *target) +{ + if (!target) { + return; + } + kzt_guest_registry_patch_decision_lease_release(&target->decision_lease); + kzt_guest_library_loader_quiescence_release( + &target->loader_quiescence_lease); + kzt_guest_registry_source_lease_release(&target->source_lease); +} + +int kzt_guest_glob_dat_target_resolve( + box64context_t *context, elfheader_t *head, uintptr_t guest_target, + unsigned long symbol_index, const Elf64_Sym *symbol, + const char *symbol_name, int version, const char *version_name, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_glob_dat_target_t *target) +{ + kzt_owner_resolution_t resolution; + kzt_guest_registry_address_match_t source_match; + kzt_guest_registry_address_match_t owner_match; + kzt_guest_library_binding_key_t key; + kzt_guest_library_handle_t handle; + uintptr_t namespace_head = 0; + uintptr_t bridge_target = 0; + + if (!context || !head || !head->self_link_map || !guest_target || + symbol_index >= head->numDynSym || !symbol || !symbol_name || + !symbol_name[0] || !reader_ops || !reader_ops->read_memory || + !target) { + return 0; + } + memset(target, 0, sizeof(*target)); + target->guest_target = guest_target; + target->selected_target = guest_target; + if (kzt_xcb_route_classify(symbol_name) != KZT_XCB_ROUTE_NOT_XCB) { + return 1; + } + kzt_owner_resolver_init(&resolution); + if (kzt_owner_resolver_resolve_current( + KztGuestRegistryForContext(context), guest_target, + guest_target, &resolution) != 0 || + resolution.status != KZT_OWNER_RESOLVER_RESOLVED || + resolution.owner_match != KZT_PATCH_OWNER_MATCH || + !resolution.current_owner.known || + !resolution.current_owner.link_map_addr || + !resolution.current_owner.generation) { + return 0; + } + target->owner = resolution.current_owner; + + if (ELF64_ST_TYPE(symbol->st_info) != STT_FUNC || + version >= 2 || (version_name && version_name[0]) || + context->kzt_guest_scope_layout == + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED || + kzt_guest_registry_find_live_object( + KztGuestRegistryForContext(context), head->self_link_map, + &source_match) != 0 || + !source_match.generation || + source_match.namespace_id_status != KZT_GUEST_FIELD_OK || + source_match.namespace_id != 0 || + kzt_guest_registry_find_live_object( + KztGuestRegistryForContext(context), + target->owner.link_map_addr, &owner_match) != 0 || + owner_match.generation != target->owner.generation || + owner_match.namespace_id_status != KZT_GUEST_FIELD_OK || + owner_match.namespace_id != 0 || + kzt_guest_registry_source_lease_acquire( + KztGuestRegistryForContext(context), head->self_link_map, + source_match.generation, source_match.namespace_id, + &target->source_lease) != 0 || + kzt_guest_registry_patch_decision_lease_acquire( + &target->source_lease, &target->decision_lease) != 0 || + kzt_guest_library_loader_quiescence_try_acquire( + KztGuestLibraryBindingsForContext(context), + &target->loader_quiescence_lease) != 0 || + kzt_guest_registry_context_get_main_namespace_head( + &context->kzt_guest_registry_context, &namespace_head) != 0) { + kzt_guest_glob_dat_target_release(target); + return 1; + } + target->scope_request = (kzt_guest_symbol_scope_request_t) { + .source = { + .link_map_addr = head->self_link_map, + .generation = source_match.generation, + .namespace_id = source_match.namespace_id, + .namespace_head = namespace_head, + .layout = context->kzt_guest_scope_layout, + }, + .symbol = symbol_name, + .version_evidence = KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + .version = NULL, + .reference_binding = ELF64_ST_BIND(symbol->st_info), + .reference_type = ELF64_ST_TYPE(symbol->st_info), + .reference_visibility = symbol->st_other & 0x3, + }; + if (kzt_guest_symbol_scope_check( + &target->scope_request, target->owner.link_map_addr, + guest_target, reader_ops, &target->scope_proof) != + KZT_GUEST_SYMBOL_SCOPE_SAFE) { + kzt_guest_glob_dat_target_release(target); + return 1; + } + key = (kzt_guest_library_binding_key_t) { + .link_map_addr = target->owner.link_map_addr, + .generation = target->owner.generation, + .namespace_id = owner_match.namespace_id, + .namespace_kind = owner_match.namespace_id == 0 ? + KZT_GUEST_LIBRARY_NAMESPACE_MAIN : + KZT_GUEST_LIBRARY_NAMESPACE_EXPLICIT, + }; + memset(&handle, 0, sizeof(handle)); + if (KztGuestLibraryLookupForContext(context, &key, &handle) != 0 || + !handle.library || + handle.object_type != KZT_GUEST_LIBRARY_OBJECT_WRAPPED) { + kzt_guest_library_handle_release(&handle); + kzt_guest_glob_dat_target_release(target); + return 1; + } + bridge_target = kzt_rela_runtime_select_exact_wrapper_bridge_retained( + context, &handle, symbol_name, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL); + if (bridge_target) { + target->selected_target = bridge_target; + target->exact_bridge = bridge_target != guest_target; + } + kzt_guest_library_handle_release(&handle); + if (!target->exact_bridge) { + kzt_guest_glob_dat_target_release(target); + } + return 1; +} + +int kzt_guest_glob_dat_route( + box64context_t *context, elfheader_t *head, uintptr_t slot_addr, + uintptr_t guest_target, unsigned long symbol_index, + const Elf64_Sym *symbol, const char *symbol_name, int version, + const char *version_name, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_glob_dat_route_result_t *result) +{ + kzt_guest_glob_dat_target_t target; + kzt_guest_symbol_scope_result_t revalidated_scope; + + if (!result || !slot_addr) { + return 0; + } + *result = (kzt_guest_glob_dat_route_result_t) { + .guest_target = guest_target, + .selected_target = guest_target, + .final_value = guest_target, + .writer_result = KZT_PRODUCTION_SLOT_TRANSACTION_ERROR, + }; + if (!kzt_guest_glob_dat_target_resolve( + context, head, guest_target, symbol_index, symbol, symbol_name, + version, version_name, reader_ops, &target)) { + return 0; + } + result->selected_target = target.selected_target; + if (target.exact_bridge && + kzt_guest_symbol_scope_revalidate( + &target.scope_proof, &target.scope_request, reader_ops, + &revalidated_scope) == KZT_GUEST_SYMBOL_SCOPE_SAFE) { + result->writer_result = kzt_production_eager_relocation_write( + context, head->self_link_map, &target.owner, + KZT_PATCH_RELOCATION_GLOB_DAT, slot_addr, guest_target, + target.selected_target, symbol_name, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, + &result->final_value); + } + kzt_guest_glob_dat_target_release(&target); + return 1; +} diff --git a/target/i386/latx/context/kzt_guest_library_adapter.c b/target/i386/latx/context/kzt_guest_library_adapter.c index ae943e5690e..6304716e007 100644 --- a/target/i386/latx/context/kzt_guest_library_adapter.c +++ b/target/i386/latx/context/kzt_guest_library_adapter.c @@ -4,6 +4,7 @@ #include "box64context.h" #include "callback.h" +#include "config-host.h" #include "debug.h" #include "kzt_guest_dynsym_lookup.h" #include "kzt_guest_library_binding.h" @@ -88,6 +89,28 @@ static const char *kzt_guest_library_basename(const char *path) return name ? name + 1 : path; } +const char *kzt_guest_library_wrapper_name_for_guest(const char *guest_name) +{ + if (!guest_name || !guest_name[0]) { + return NULL; + } + if (FindLibIsWrapped((char *)guest_name)) { + return guest_name; + } +#ifdef CONFIG_LOONGARCH_NEW_WORLD + if (strcmp(guest_name, "libdl.so.2") == 0) { + return "libc.so.6"; + } +#endif + return NULL; +} + +int kzt_guest_library_wrapper_alias_symbol_allowed(const char *symbol) +{ + return symbol && + (strcmp(symbol, "dlsym") == 0 || strcmp(symbol, "dlvsym") == 0); +} + static int kzt_guest_library_path_is_canonical(const char *path) { const char *component; @@ -112,8 +135,7 @@ static int kzt_guest_library_path_is_canonical(const char *path) return 0; } -static int kzt_guest_library_path_matches_manifest(const char *path, - const char *wrapper_name) +static int kzt_guest_library_path_is_trusted(const char *path) { static const char *const directories[] = { "/lib", @@ -129,9 +151,7 @@ static int kzt_guest_library_path_matches_manifest(const char *path, size_t i; if (!kzt_guest_library_path_is_canonical(path) || - !wrapper_name || !wrapper_name[0] || - !(name = strrchr(path, '/')) || !name[1] || - strcmp(name + 1, wrapper_name) != 0) { + !(name = strrchr(path, '/')) || !name[1]) { return 0; } directory_length = (size_t)(name - path); @@ -149,24 +169,29 @@ static int kzt_guest_library_source_match_valid( const char *requested_path, const char *wrapper_name) { const char *observed_name; + const char *requested_name; + const char *approved_wrapper; if (!match || match->match_count != 1 || match->namespace_id_status != KZT_GUEST_FIELD_OK || match->namespace_id != 0 || !match->generation || match->path_status != KZT_GUEST_FIELD_OK || !requested_path || !requested_path[0] || - !kzt_guest_library_path_matches_manifest( - match->path, wrapper_name)) { + !kzt_guest_library_path_is_trusted(match->path)) { return 0; } observed_name = kzt_guest_library_basename(match->path); - if (!observed_name || strcmp(observed_name, wrapper_name) != 0 || + requested_name = kzt_guest_library_basename(requested_path); + approved_wrapper = + kzt_guest_library_wrapper_name_for_guest(observed_name); + if (!observed_name || !requested_name || !approved_wrapper || + strcmp(approved_wrapper, wrapper_name) != 0 || (strchr(requested_path, '/') && strcmp(requested_path, match->path) != 0) || (!strchr(requested_path, '/') && - strcmp(requested_path, wrapper_name) != 0) || + strcmp(requested_name, observed_name) != 0) || (match->soname_status == KZT_GUEST_FIELD_OK && match->soname[0] && - strcmp(match->soname, wrapper_name) != 0)) { + strcmp(match->soname, observed_name) != 0)) { return 0; } return 1; @@ -347,8 +372,9 @@ int kzt_guest_library_run_dlinfo( return RunFunctionWithState(function, 3, handle, request, info); } -uintptr_t kzt_guest_library_select_symbol_result( +uintptr_t kzt_guest_library_select_symbol_result_with_identity( box64context_t *context, uintptr_t guest_handle, + const kzt_guest_loader_identity_t *queried_identity, uintptr_t guest_result, const char *symbol, const char *version) { #ifdef CONFIG_LATX_KZT @@ -359,18 +385,14 @@ uintptr_t kzt_guest_library_select_symbol_result( kzt_guest_library_handle_t handle = { 0 }; kzt_guest_dynamic_view_t dynamic_view; kzt_guest_dynsym_lookup_result_t dynsym_result = { 0 }; - kzt_wrapper_bridge_provider_t wrapper_provider = { 0 }; - kzt_wrapper_probe_request_t wrapper_request; - kzt_wrapper_probe_result_t wrapper_probe = { 0 }; kzt_guest_field_status_t dynamic_status; unsigned long dynamic_revision = 0; kzt_guest_link_map_reader_ops_t reader_ops = { .read_memory = kzt_guest_library_read_memory, }; uintptr_t proven_runtime_address = 0; + uintptr_t cached_bridge_target = 0; unsigned char proven_symbol_type = 0; - uintptr_t start = 0; - uintptr_t end = 0; uintptr_t selected = guest_result; const char *diagnostic_reason = "source_generation_stale"; int versioned = version != NULL; @@ -379,12 +401,19 @@ uintptr_t kzt_guest_library_select_symbol_result( !context || !guest_result || !symbol || !symbol[0] || (versioned && !version[0]) || !(registry = KztGuestRegistryForContext(context)) || - kzt_guest_registry_loader_symbol_source_acquire( - registry, guest_handle, &lookup_identity, &dynamic_view, - &dynamic_status, &dynamic_revision, &source_lease) != 0) { + (queried_identity + ? kzt_guest_registry_loader_symbol_source_acquire_exact( + registry, queried_identity, &lookup_identity, + &dynamic_view, &dynamic_status, &dynamic_revision, + &source_lease) + : kzt_guest_registry_loader_symbol_source_acquire( + registry, guest_handle, &lookup_identity, &dynamic_view, + &dynamic_status, &dynamic_revision, + &source_lease)) != 0) { return guest_result; } - if (!lookup_identity.link_map_addr || !lookup_identity.generation || + if (lookup_identity.handle != guest_handle || + !lookup_identity.link_map_addr || !lookup_identity.generation || lookup_identity.namespace_id != 0 || dynamic_status != KZT_GUEST_FIELD_OK || !dynamic_revision) { kzt_guest_registry_source_lease_release(&source_lease); @@ -417,7 +446,8 @@ uintptr_t kzt_guest_library_select_symbol_result( } } else if (kzt_guest_library_symbol_evidence_lookup( &handle, symbol, dynamic_revision, - &proven_runtime_address, &proven_symbol_type) != 0) { + &proven_runtime_address, &proven_symbol_type, + &cached_bridge_target) != 0) { kzt_guest_dynsym_lookup_status_t dynsym_status = kzt_guest_dynsym_lookup( &dynamic_view, &reader_ops, symbol, @@ -440,33 +470,24 @@ uintptr_t kzt_guest_library_select_symbol_result( : "bridge_missing"; if (proven_runtime_address == guest_result && proven_symbol_type == STT_FUNC) { - if (versioned && - kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( - context, &handle, symbol, KZT_SYMBOL_VERSION_VERSIONED, - version, &wrapper_provider) > 0) { - wrapper_request = (kzt_wrapper_probe_request_t) { - .symbol_name = symbol, - .symbol_version_evidence = KZT_SYMBOL_VERSION_VERSIONED, - .symbol_version = version, - }; - if (kzt_wrapper_probe_minimal_manifest( - &wrapper_provider.manifest, &wrapper_request, - &wrapper_provider.bridge_ops, &wrapper_probe) == 0 && - wrapper_probe.wrapper_match == - KZT_PATCH_WRAPPER_VERSION_MATCH && - wrapper_probe.bridge_target && - kzt_symbol_version_evidence_matches( - KZT_SYMBOL_VERSION_VERSIONED, version, - wrapper_probe.wrapper_version_evidence, - wrapper_probe.wrapper_symbol_version)) { - selected = wrapper_probe.bridge_target; - diagnostic_reason = NULL; + if (!versioned && cached_bridge_target) { + selected = cached_bridge_target; + } else { + selected = + kzt_rela_runtime_select_exact_wrapper_bridge_retained( + context, &handle, symbol, + versioned ? KZT_SYMBOL_VERSION_VERSIONED : + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, + version); + if (!versioned && selected) { + kzt_guest_library_symbol_bridge_store( + &handle, symbol, dynamic_revision, selected); } - } else if (!versioned && - GetLibFunctionSymbolStartEnd( - handle.library, symbol, 0, &start, &end)) { - selected = start; + } + if (selected) { diagnostic_reason = NULL; + } else { + selected = guest_result; } } if (diagnostic_reason) { @@ -481,12 +502,21 @@ uintptr_t kzt_guest_library_select_symbol_result( #else (void)context; (void)guest_handle; + (void)queried_identity; (void)symbol; (void)version; return guest_result; #endif } +uintptr_t kzt_guest_library_select_symbol_result( + box64context_t *context, uintptr_t guest_handle, + uintptr_t guest_result, const char *symbol, const char *version) +{ + return kzt_guest_library_select_symbol_result_with_identity( + context, guest_handle, NULL, guest_result, symbol, version); +} + static kzt_guest_library_object_type_t loader_object_type(library_t *library) { return library && library->type == LIB_WRAPPED diff --git a/target/i386/latx/context/kzt_guest_library_binding.c b/target/i386/latx/context/kzt_guest_library_binding.c index 8b6c08657d7..f10a3de2992 100644 --- a/target/i386/latx/context/kzt_guest_library_binding.c +++ b/target/i386/latx/context/kzt_guest_library_binding.c @@ -15,9 +15,11 @@ typedef struct kzt_guest_library_symbol_evidence { char symbol[KZT_GUEST_LIBRARY_SYMBOL_NAME_LIMIT]; uintptr_t runtime_address; + uintptr_t bridge_target; unsigned long age; unsigned long dynamic_revision; unsigned char symbol_type; + int bridge_valid; int valid; } kzt_guest_library_symbol_evidence_t; @@ -1592,10 +1594,30 @@ kzt_guest_library_handle_entry_locked( return entry; } +int kzt_guest_library_handle_matches_key( + const kzt_guest_library_handle_t *handle, + const kzt_guest_library_binding_key_t *key) +{ + kzt_guest_library_bindings_t *bindings; + kzt_guest_library_binding_entry_t *entry; + int matches = 0; + + if (!handle || !(bindings = handle->bindings) || !key) { + return 0; + } + pthread_mutex_lock(&bindings->lock); + entry = kzt_guest_library_handle_entry_locked(bindings, handle); + if (entry && !shutting_down(bindings) && same_key(&entry->key, key)) { + matches = 1; + } + pthread_mutex_unlock(&bindings->lock); + return matches; +} + int kzt_guest_library_symbol_evidence_lookup( const kzt_guest_library_handle_t *handle, const char *symbol, unsigned long dynamic_revision, uintptr_t *runtime_address, - unsigned char *symbol_type) + unsigned char *symbol_type, uintptr_t *bridge_target) { kzt_guest_library_bindings_t *bindings; kzt_guest_library_binding_entry_t *entry; @@ -1604,6 +1626,7 @@ int kzt_guest_library_symbol_evidence_lookup( if (runtime_address) *runtime_address = 0; if (symbol_type) *symbol_type = 0; + if (bridge_target) *bridge_target = 0; if (!handle || !(bindings = handle->bindings) || !symbol || !symbol[0] || !dynamic_revision || !runtime_address || !symbol_type) { return -1; @@ -1621,6 +1644,8 @@ int kzt_guest_library_symbol_evidence_lookup( cached->age = ++entry->symbol_cache_age; *runtime_address = cached->runtime_address; *symbol_type = cached->symbol_type; + if (bridge_target && cached->bridge_valid) + *bridge_target = cached->bridge_target; result = 0; break; } @@ -1664,6 +1689,14 @@ void kzt_guest_library_symbol_evidence_store( } } if (selected) { + if (!selected->valid || + strcmp(selected->symbol, symbol) != 0 || + selected->dynamic_revision != dynamic_revision || + selected->runtime_address != runtime_address || + selected->symbol_type != symbol_type) { + selected->bridge_target = 0; + selected->bridge_valid = 0; + } memcpy(selected->symbol, symbol, symbol_length + 1); selected->runtime_address = runtime_address; selected->symbol_type = symbol_type; @@ -1675,6 +1708,38 @@ void kzt_guest_library_symbol_evidence_store( pthread_mutex_unlock(&bindings->lock); } +void kzt_guest_library_symbol_bridge_store( + const kzt_guest_library_handle_t *handle, const char *symbol, + unsigned long dynamic_revision, uintptr_t bridge_target) +{ + kzt_guest_library_bindings_t *bindings; + kzt_guest_library_binding_entry_t *entry; + size_t i; + + if (!handle || !(bindings = handle->bindings) || !symbol || !symbol[0] || + !dynamic_revision || !bridge_target) { + return; + } + pthread_mutex_lock(&bindings->lock); + entry = kzt_guest_library_handle_entry_locked(bindings, handle); + if (!entry || shutting_down(bindings)) goto out; + for (i = 0; i < KZT_GUEST_LIBRARY_SYMBOL_CACHE_SLOTS; ++i) { + kzt_guest_library_symbol_evidence_t *cached = + &entry->symbol_cache[i]; + + if (cached->valid && + cached->dynamic_revision == dynamic_revision && + strcmp(cached->symbol, symbol) == 0) { + cached->bridge_target = bridge_target; + cached->bridge_valid = 1; + cached->age = ++entry->symbol_cache_age; + break; + } + } +out: + pthread_mutex_unlock(&bindings->lock); +} + void kzt_guest_library_handle_release(kzt_guest_library_handle_t *handle) { kzt_guest_library_bindings_t *bindings; diff --git a/target/i386/latx/context/kzt_guest_registry.c b/target/i386/latx/context/kzt_guest_registry.c index 09a14b68ee8..f283004d82d 100644 --- a/target/i386/latx/context/kzt_guest_registry.c +++ b/target/i386/latx/context/kzt_guest_registry.c @@ -199,6 +199,29 @@ void kzt_guest_registry_test_fail_next_cond_init(void) test_fail_next_cond_init = 1; } +int kzt_guest_registry_test_set_active_source_leases( + kzt_guest_registry_t *registry, uintptr_t link_map_addr, + unsigned long generation, unsigned long active_source_leases) +{ + size_t i; + + if (!registry || !link_map_addr || !generation || + kzt_registry_api_lock(registry) != 0) { + return -1; + } + for (i = 0; i < registry->count; ++i) { + if (registry->objects[i].link_map_addr == link_map_addr && + registry->objects[i].generation == generation) { + registry->objects[i].active_source_leases = + active_source_leases; + kzt_registry_api_unlock(registry); + return 0; + } + } + kzt_registry_api_unlock(registry); + return -1; +} + static int kzt_registry_test_should_fail_dynamic_commit(void) { if (test_dynamic_commit_failure_after == 0) { @@ -1380,6 +1403,7 @@ int kzt_guest_registry_source_lease_acquire( registry->objects[index].generation != generation || registry->objects[index].namespace_id.status != KZT_GUEST_FIELD_OK || registry->objects[index].namespace_id.value != namespace_id || + registry->objects[index].active_source_leases == ULONG_MAX || registry->objects[index].state == KZT_GUEST_OBJECT_UNLOADING || registry->objects[index].state == KZT_GUEST_OBJECT_DEAD) { kzt_registry_api_unlock(registry); @@ -1480,6 +1504,114 @@ void kzt_guest_registry_patch_decision_lease_release( memset(lease, 0, sizeof(*lease)); } +int kzt_guest_registry_symbol_candidate_acquire_next( + const kzt_guest_registry_patch_decision_lease_t *decision_lease, + size_t *cursor, kzt_guest_registry_symbol_candidate_t *candidate) +{ + kzt_guest_registry_t *registry; + kzt_guest_object_snapshot_t *source; + size_t index; + ssize_t source_index; + + if (candidate) { + memset(candidate, 0, sizeof(*candidate)); + } + if (!decision_lease || !decision_lease->active || + !(registry = decision_lease->registry) || !cursor || !candidate || + decision_lease->namespace_id != 0 || + kzt_registry_api_lock(registry) != 0) { + return -1; + } + source_index = kzt_find_object_index( + registry, decision_lease->link_map_addr); + if (registry->disabled || registry->evidence_mutators_waiting || + !registry->active_patch_decision_leases || source_index < 0) { + kzt_registry_api_unlock(registry); + return -1; + } + source = ®istry->objects[source_index]; + if (source->generation != decision_lease->generation || + source->namespace_id.status != KZT_GUEST_FIELD_OK || + source->namespace_id.value != decision_lease->namespace_id || + !source->active_source_leases || + source->state == KZT_GUEST_OBJECT_UNLOADING || + source->state == KZT_GUEST_OBJECT_DEAD) { + kzt_registry_api_unlock(registry); + return -1; + } + + for (index = *cursor; index < registry->count; ++index) { + kzt_guest_object_snapshot_t *object = ®istry->objects[index]; + + if (object->state == KZT_GUEST_OBJECT_UNLOADING || + object->state == KZT_GUEST_OBJECT_DEAD) { + continue; + } + if (object->namespace_id.status != KZT_GUEST_FIELD_OK) { + kzt_registry_api_unlock(registry); + return -1; + } + if (object->namespace_id.value != 0) { + continue; + } + if (!object->link_map_addr || !object->generation || + object->active_source_leases == ULONG_MAX) { + kzt_registry_api_unlock(registry); + return -1; + } + candidate->link_map_addr = object->link_map_addr; + candidate->generation = object->generation; + candidate->namespace_id = object->namespace_id.value; + candidate->map_start = object->map_start.status == KZT_GUEST_FIELD_OK + ? object->map_start.value : 0; + candidate->map_end = object->map_end.status == KZT_GUEST_FIELD_OK + ? object->map_end.value : 0; + candidate->dynamic_view_status = object->dynamic_view_status; + candidate->dynamic_view = object->dynamic_view; + candidate->dynamic_view_revision = object->dynamic_view_revision; + candidate->path_status = object->path.status; + candidate->soname_status = object->soname.status; + if (object->path.value && + strlen(object->path.value) < sizeof(candidate->path)) { + snprintf(candidate->path, sizeof(candidate->path), "%s", + object->path.value); + } else if (object->path.value) { + candidate->path_status = KZT_GUEST_FIELD_UNKNOWN; + } + if (object->soname.value && + strlen(object->soname.value) < sizeof(candidate->soname)) { + snprintf(candidate->soname, sizeof(candidate->soname), "%s", + object->soname.value); + } else if (object->soname.value) { + candidate->soname_status = KZT_GUEST_FIELD_UNKNOWN; + } + ++object->active_source_leases; + candidate->lease = (kzt_guest_registry_source_lease_t) { + .registry = registry, + .link_map_addr = object->link_map_addr, + .generation = object->generation, + .namespace_id = object->namespace_id.value, + .active = 1, + }; + *cursor = index + 1; + kzt_registry_api_unlock(registry); + return 1; + } + *cursor = registry->count; + kzt_registry_api_unlock(registry); + return 0; +} + +void kzt_guest_registry_symbol_candidate_release( + kzt_guest_registry_symbol_candidate_t *candidate) +{ + if (!candidate) { + return; + } + kzt_guest_registry_source_lease_release(&candidate->lease); + memset(candidate, 0, sizeof(*candidate)); +} + static int kzt_registry_got_plt_view_complete( const kzt_guest_dynamic_view_t *view) { @@ -2147,6 +2279,7 @@ int kzt_guest_registry_loader_symbol_source_acquire( if (object->generation != entry->identity.generation || object->namespace_id.status != KZT_GUEST_FIELD_OK || object->namespace_id.value != entry->identity.namespace_id || + object->active_source_leases == ULONG_MAX || object->state == KZT_GUEST_OBJECT_UNLOADING || object->state == KZT_GUEST_OBJECT_DEAD || object->dynamic_view_status != KZT_GUEST_FIELD_OK) { @@ -2168,6 +2301,67 @@ int kzt_guest_registry_loader_symbol_source_acquire( return 0; } +int kzt_guest_registry_loader_symbol_source_acquire_exact( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *queried_identity, + kzt_guest_loader_identity_t *identity, + kzt_guest_dynamic_view_t *dynamic_view, + kzt_guest_field_status_t *dynamic_status, + unsigned long *dynamic_revision, + kzt_guest_registry_source_lease_t *lease) +{ + kzt_guest_object_snapshot_t *object; + ssize_t object_index; + + if (identity) memset(identity, 0, sizeof(*identity)); + if (dynamic_view) memset(dynamic_view, 0, sizeof(*dynamic_view)); + if (dynamic_status) *dynamic_status = KZT_GUEST_FIELD_NOT_PARSED; + if (dynamic_revision) *dynamic_revision = 0; + if (lease) memset(lease, 0, sizeof(*lease)); + if (!registry || !queried_identity || !queried_identity->handle || + !queried_identity->link_map_addr || queried_identity->namespace_id != 0 || + !identity || !dynamic_view || !dynamic_status || !dynamic_revision || + !lease || kzt_registry_api_lock(registry) != 0) { + return -1; + } + object_index = kzt_find_object_index( + registry, queried_identity->link_map_addr); + if (registry->disabled || registry->evidence_mutators_waiting || + object_index < 0) { + kzt_registry_api_unlock(registry); + return -1; + } + object = ®istry->objects[object_index]; + if (!object->generation || + object->namespace_id.status != KZT_GUEST_FIELD_OK || + object->namespace_id.value != queried_identity->namespace_id || + object->active_source_leases == ULONG_MAX || + object->state == KZT_GUEST_OBJECT_UNLOADING || + object->state == KZT_GUEST_OBJECT_DEAD || + object->dynamic_view_status != KZT_GUEST_FIELD_OK) { + kzt_registry_api_unlock(registry); + return -1; + } + + ++object->active_source_leases; + *identity = (kzt_guest_loader_identity_t) { + .handle = queried_identity->handle, + .link_map_addr = object->link_map_addr, + .generation = object->generation, + .namespace_id = object->namespace_id.value, + }; + *dynamic_view = object->dynamic_view; + *dynamic_status = object->dynamic_view_status; + *dynamic_revision = object->dynamic_view_revision; + lease->registry = registry; + lease->link_map_addr = object->link_map_addr; + lease->generation = object->generation; + lease->namespace_id = object->namespace_id.value; + lease->active = 1; + kzt_registry_api_unlock(registry); + return 0; +} + int kzt_guest_registry_find_loader_object_identity( kzt_guest_registry_t *registry, uintptr_t link_map_addr, diff --git a/target/i386/latx/context/kzt_guest_runtime_entry.c b/target/i386/latx/context/kzt_guest_runtime_entry.c new file mode 100644 index 00000000000..a98761707ab --- /dev/null +++ b/target/i386/latx/context/kzt_guest_runtime_entry.c @@ -0,0 +1,50 @@ +#include "qemu/osdep.h" + +#include "box64context.h" +#include "kzt_guest_runtime_entry.h" + +uintptr_t kzt_guest_runtime_entry_load( + const box64context_t *context, kzt_guest_runtime_entry_id_t entry) +{ + const dlprivate_t *dl = context ? context->dlprivate : NULL; + + if (!dl || entry < 0 || entry >= KZT_GUEST_RUNTIME_ENTRY_COUNT) { + return 0; + } + return kzt_guest_runtime_entry_state_load( + &dl->guest_dl_entries, entry); +} + +uintptr_t kzt_guest_runtime_entry_ensure( + dlprivate_t *dl, kzt_guest_runtime_entry_id_t entry, + kzt_guest_runtime_entry_resolver_fn resolver, void *opaque) +{ + return dl ? kzt_guest_runtime_entry_state_ensure( + &dl->guest_dl_entries, entry, resolver, opaque) + : 0; +} + +uintptr_t kzt_guest_runtime_entry_for_guest_branch( + box64context_t *context, kzt_guest_runtime_entry_id_t entry) +{ + return kzt_guest_runtime_entry_load(context, entry); +} + +int kzt_guest_runtime_entry_acquire( + box64context_t *context, kzt_guest_runtime_entry_id_t entry, + kzt_guest_runtime_entry_scope_t *scope) +{ + kzt_guest_dl_entry_state_t *state; + + if (!context || !context->dlprivate || !scope) { + return -1; + } + state = &context->dlprivate->guest_dl_entries; + return kzt_guest_runtime_entry_state_acquire(state, entry, scope); +} + +void kzt_guest_runtime_entry_release( + kzt_guest_runtime_entry_scope_t *scope) +{ + kzt_guest_runtime_entry_state_release(scope); +} diff --git a/target/i386/latx/context/kzt_guest_runtime_entry_state.c b/target/i386/latx/context/kzt_guest_runtime_entry_state.c new file mode 100644 index 00000000000..929dbc98c61 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_runtime_entry_state.c @@ -0,0 +1,240 @@ +#include "kzt_guest_runtime_entry_state.h" + +#include +#include + +static const char *const kzt_guest_runtime_entry_symbols[] = { + [KZT_GUEST_RUNTIME_FREE] = "free", + [KZT_GUEST_RUNTIME_REALLOC] = "realloc", + [KZT_GUEST_RUNTIME_PTHREAD_SETCANCELTYPE] = "pthread_setcanceltype", +}; + +int kzt_guest_dl_entry_state_enter(kzt_guest_dl_entry_state_t *state) +{ + unsigned int lifecycle; + + if (!state) { + return -1; + } + lifecycle = __atomic_load_n(&state->lifecycle, __ATOMIC_ACQUIRE); + for (;;) { + if (!(lifecycle & KZT_GUEST_DL_LIFECYCLE_OPEN) || + (lifecycle & KZT_GUEST_DL_LIFECYCLE_USERS) == + KZT_GUEST_DL_LIFECYCLE_USERS) { + return -1; + } + if (__atomic_compare_exchange_n( + &state->lifecycle, &lifecycle, lifecycle + 1, 0, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) { + return 0; + } + } +} + +void kzt_guest_dl_entry_state_leave_locked( + kzt_guest_dl_entry_state_t *state) +{ + unsigned int previous = __atomic_fetch_sub( + &state->lifecycle, 1, __ATOMIC_RELEASE); + + if ((previous & KZT_GUEST_DL_LIFECYCLE_USERS) == 1) { + pthread_cond_broadcast(&state->ready); + } +} + +static void kzt_guest_runtime_entry_slow_leave( + kzt_guest_dl_entry_state_t *state) +{ + if (state->slow_users) { + --state->slow_users; + } + pthread_cond_broadcast(&state->ready); +} + +uintptr_t kzt_guest_runtime_entry_state_ensure( + kzt_guest_dl_entry_state_t *state, + kzt_guest_runtime_entry_id_t entry, + kzt_guest_runtime_entry_resolver_fn resolver, void *opaque) +{ + uintptr_t address; + unsigned int bit; + + if (!state || !resolver || entry < 0 || + entry >= KZT_GUEST_RUNTIME_ENTRY_COUNT || + kzt_guest_dl_entry_state_enter(state) != 0) { + return 0; + } + + bit = 1U << entry; + pthread_mutex_lock(&state->mutex); + if (state->teardown) { + kzt_guest_dl_entry_state_leave_locked(state); + pthread_mutex_unlock(&state->mutex); + return 0; + } + ++state->slow_users; + for (;;) { + address = kzt_guest_runtime_entry_state_load(state, entry); + if (address || state->teardown) { + kzt_guest_runtime_entry_slow_leave(state); + kzt_guest_dl_entry_state_leave_locked(state); + pthread_mutex_unlock(&state->mutex); + return state->teardown ? 0 : address; + } + if (!(state->runtime_initializing & bit)) { + state->runtime_initializing |= bit; + state->runtime_initializers[entry] = pthread_self(); + break; + } + if (pthread_equal( + state->runtime_initializers[entry], pthread_self())) { + kzt_guest_runtime_entry_slow_leave(state); + kzt_guest_dl_entry_state_leave_locked(state); + pthread_mutex_unlock(&state->mutex); + return 0; + } + pthread_cond_wait(&state->ready, &state->mutex); + } + pthread_mutex_unlock(&state->mutex); + + address = resolver(kzt_guest_runtime_entry_symbols[entry], opaque); + + pthread_mutex_lock(&state->mutex); + if (!state->teardown && address) { + uintptr_t expected = 0; + + (void)__atomic_compare_exchange_n( + &state->runtime_entries[entry], &expected, address, 0, + __ATOMIC_RELEASE, __ATOMIC_ACQUIRE); + } + state->runtime_initializing &= ~bit; + address = state->teardown + ? 0 + : kzt_guest_runtime_entry_state_load(state, entry); + kzt_guest_runtime_entry_slow_leave(state); + kzt_guest_dl_entry_state_leave_locked(state); + pthread_mutex_unlock(&state->mutex); + return address; +} + +int kzt_guest_runtime_entry_state_publish( + kzt_guest_dl_entry_state_t *state, + const uintptr_t entries[KZT_GUEST_RUNTIME_ENTRY_COUNT]) +{ + int entry; + int result = -1; + + if (!entries || kzt_guest_dl_entry_state_enter(state) != 0) { + return -1; + } + pthread_mutex_lock(&state->mutex); + if (!state->teardown) { + for (entry = 0; entry < KZT_GUEST_RUNTIME_ENTRY_COUNT; ++entry) { + if (!entries[entry]) { + goto out; + } + } + for (entry = 0; entry < KZT_GUEST_RUNTIME_ENTRY_COUNT; ++entry) { + __atomic_store_n( + &state->runtime_entries[entry], entries[entry], + __ATOMIC_RELEASE); + } + result = 0; + } +out: + kzt_guest_dl_entry_state_leave_locked(state); + pthread_mutex_unlock(&state->mutex); + return result; +} + +int kzt_guest_runtime_entry_state_acquire( + kzt_guest_dl_entry_state_t *state, + kzt_guest_runtime_entry_id_t entry, + kzt_guest_runtime_entry_scope_t *scope) +{ + uintptr_t address; + + if (scope) { + *scope = (kzt_guest_runtime_entry_scope_t) { 0 }; + } + if (!state || !scope || entry < 0 || + entry >= KZT_GUEST_RUNTIME_ENTRY_COUNT || + kzt_guest_dl_entry_state_enter(state) != 0) { + return -1; + } + pthread_mutex_lock(&state->mutex); + address = state->teardown + ? 0 + : kzt_guest_runtime_entry_state_load(state, entry); + if (address) { + ++state->runtime_users; + scope->state = state; + scope->address = address; + } + kzt_guest_dl_entry_state_leave_locked(state); + pthread_mutex_unlock(&state->mutex); + return address ? 0 : -1; +} + +void kzt_guest_runtime_entry_state_release( + kzt_guest_runtime_entry_scope_t *scope) +{ + kzt_guest_dl_entry_state_t *state; + + if (!scope || !(state = scope->state)) { + return; + } + pthread_mutex_lock(&state->mutex); + if (state->runtime_users) { + --state->runtime_users; + } + pthread_cond_broadcast(&state->ready); + pthread_mutex_unlock(&state->mutex); + *scope = (kzt_guest_runtime_entry_scope_t) { 0 }; +} + +void kzt_guest_runtime_entry_state_begin_teardown( + kzt_guest_dl_entry_state_t *state) +{ + unsigned int lifecycle; + int entry; + + if (!state) { + return; + } + lifecycle = __atomic_load_n(&state->lifecycle, __ATOMIC_ACQUIRE); + for (;;) { + unsigned int closing; + + if (!(lifecycle & KZT_GUEST_DL_LIFECYCLE_OPEN)) { + while (lifecycle & KZT_GUEST_DL_LIFECYCLE_CLOSING) { + sched_yield(); + lifecycle = __atomic_load_n( + &state->lifecycle, __ATOMIC_ACQUIRE); + } + return; + } + closing = (lifecycle & KZT_GUEST_DL_LIFECYCLE_USERS) | + KZT_GUEST_DL_LIFECYCLE_CLOSING; + if (__atomic_compare_exchange_n( + &state->lifecycle, &lifecycle, closing, 0, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) { + break; + } + } + pthread_mutex_lock(&state->mutex); + state->teardown = 1; + pthread_cond_broadcast(&state->ready); + while (state->initializing || state->slow_users || + state->runtime_users || + (__atomic_load_n(&state->lifecycle, __ATOMIC_ACQUIRE) & + KZT_GUEST_DL_LIFECYCLE_USERS)) { + pthread_cond_wait(&state->ready, &state->mutex); + } + for (entry = 0; entry < KZT_GUEST_RUNTIME_ENTRY_COUNT; ++entry) { + __atomic_store_n( + &state->runtime_entries[entry], 0, __ATOMIC_RELEASE); + } + pthread_mutex_unlock(&state->mutex); + __atomic_store_n(&state->lifecycle, 0, __ATOMIC_RELEASE); +} diff --git a/target/i386/latx/context/kzt_jump_slot_production.c b/target/i386/latx/context/kzt_jump_slot_production.c index 4f59f8f5802..47f9499041f 100644 --- a/target/i386/latx/context/kzt_jump_slot_production.c +++ b/target/i386/latx/context/kzt_jump_slot_production.c @@ -15,14 +15,19 @@ #include "elfload_dump.h" #include "elfloader_private.h" #include "kzt_guest_dl_api.h" +#include "kzt_guest_dynsym_lookup.h" +#include "kzt_guest_library_adapter.h" #include "kzt_guest_symbol_scope.h" #include "kzt_lifecycle_diagnostics.h" #include "kzt_lazy_direct_route.h" +#include "kzt_loader_event_hook.h" #include "kzt_rela_diagnostics.h" #include "library.h" +#include "library_private.h" #include "librarian.h" #include "kzt_rela_request_enricher.h" #include "kzt_rela_runtime_bridge.h" +#include "kzt_xcb_route_policy.h" #include "kzt_rela_stub_detector.h" #include "kzt_owner_resolver.h" #include "kzt_runtime_candidate_shadow.h" @@ -30,6 +35,29 @@ extern int option_kzt_lazy_diagnostics; +static int production_symbol_uses_guarded_xcb_bridge( + const char *symbol_name) +{ + return kzt_xcb_route_is_guarded_consumer(symbol_name); +} + +typedef struct kzt_production_alias_proof { + kzt_guest_library_binding_key_t owner_key; + kzt_guest_library_binding_key_t provider_key; + kzt_guest_library_bindings_t *provider_bindings; + void *provider_entry; + library_t *provider_library; + kzt_guest_field_status_t owner_path_status; + kzt_guest_field_status_t owner_soname_status; + kzt_guest_field_status_t provider_path_status; + kzt_guest_field_status_t provider_soname_status; + char owner_path[KZT_GUEST_REGISTRY_ADDRESS_TEXT_LIMIT]; + char owner_soname[KZT_GUEST_REGISTRY_ADDRESS_TEXT_LIMIT]; + char provider_path[KZT_GUEST_REGISTRY_ADDRESS_TEXT_LIMIT]; + char provider_soname[KZT_GUEST_REGISTRY_ADDRESS_TEXT_LIMIT]; + int valid; +} kzt_production_alias_proof_t; + typedef struct kzt_production_jump_slot_state { box64context_t *context; library_t *resolved_provider; @@ -43,13 +71,23 @@ typedef struct kzt_production_jump_slot_state { kzt_rela_immediate_writer_result_t writer_result; kzt_guest_registry_source_lease_t source_lease; const kzt_guest_registry_source_lease_t *held_source_lease; + kzt_guest_registry_source_lease_t owner_source_lease; kzt_guest_registry_patch_decision_lease_t decision_lease; const kzt_guest_registry_patch_decision_lease_t *held_decision_lease; const kzt_guest_library_handle_t *retained_provider_handle; + kzt_guest_library_binding_key_t exact_provider_key; + kzt_guest_library_bindings_t *exact_provider_bindings; + void *exact_provider_entry; + library_t *exact_provider_library; kzt_patch_object_ref_t exact_provider_owner; kzt_guest_library_loader_quiescence_lease_t loader_quiescence_lease; kzt_guest_symbol_scope_request_t symbol_scope_request; kzt_guest_symbol_scope_result_t symbol_scope_proof; + kzt_guest_registry_symbol_candidate_t exact_owner_candidate; + kzt_production_alias_proof_t alias_proof; + int exact_owner_symbol_proof; + int exact_owner_without_map_range; + int wrapper_alias_borrowed; kzt_patch_decision_t prevalidated_decision; int prevalidated_decision_valid; uintptr_t final_stale_slot_value; @@ -68,9 +106,29 @@ typedef struct kzt_production_jump_slot_state { const char *failure_stage; } kzt_production_jump_slot_state_t; +static int production_exact_provider_handle_matches( + const kzt_production_jump_slot_state_t *state) +{ + const kzt_guest_library_handle_t *handle = + state ? state->retained_provider_handle : NULL; + + return handle && handle->bindings && handle->entry && handle->library && + handle->bindings == state->exact_provider_bindings && + handle->entry == state->exact_provider_entry && + handle->library == state->exact_provider_library && + handle->library == state->resolved_provider && + handle->object_type == KZT_GUEST_LIBRARY_OBJECT_WRAPPED && + kzt_guest_library_handle_matches_key( + handle, &state->exact_provider_key); +} + #ifdef KZT_JUMP_SLOT_PRODUCTION_TEST void kzt_jump_slot_production_test_before_source_lease_acquire(void); void kzt_jump_slot_production_test_before_source_memory_access(void); +void kzt_jump_slot_production_test_before_owner_memory_access( + int source_lease_active, int decision_lease_active, + int quiescence_active, int retained_provider_active, + int owner_lease_active); void kzt_jump_slot_production_test_before_slot_load(void); void kzt_jump_slot_production_test_after_slot_load(uintptr_t *value); void kzt_jump_slot_production_test_after_slot_cas(int exchanged); @@ -163,6 +221,540 @@ static int production_symbol_scope_request( return 0; } +static int production_exact_owner_symbol_matches( + kzt_production_jump_slot_state_t *state, + const kzt_patch_object_ref_t *owner, uintptr_t target, + const char *symbol, kzt_symbol_version_evidence_t version_evidence, + const char *version) +{ + kzt_guest_dynamic_view_t owner_view; + kzt_guest_dynsym_lookup_result_t lookup = { 0 }; + kzt_guest_field_status_t dynamic_status; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = production_read_guest_memory, + }; + unsigned long dynamic_generation = 0; + + if (!state || !owner || !owner->known || !owner->link_map_addr || + !owner->generation || !target || !symbol || !symbol[0] || + !state->held_source_lease || !state->held_source_lease->active || + !state->held_decision_lease || !state->held_decision_lease->active || + !state->loader_quiescence_lease.bindings || + !state->loader_quiescence_lease.cookie || + !production_exact_provider_handle_matches(state) || + !state->owner_source_lease.active || + state->owner_source_lease.registry != + KztGuestRegistryForContext(state->context) || + state->owner_source_lease.link_map_addr != owner->link_map_addr || + state->owner_source_lease.generation != owner->generation || + state->owner_source_lease.namespace_id != 0 || + (state->exact_owner_without_map_range && + !kzt_loader_lifecycle_runtime_healthy(state->context)) || + !kzt_symbol_version_evidence_valid(version_evidence, version)) { + return 0; + } + if (kzt_guest_registry_find_dynamic_view( + KztGuestRegistryForContext(state->context), owner->link_map_addr, + &owner_view, &dynamic_status, &dynamic_generation) != 0 || + dynamic_status != KZT_GUEST_FIELD_OK || + dynamic_generation != owner->generation || + owner_view.status != KZT_GUEST_DYNAMIC_COMPLETE || + (state->exact_owner_without_map_range && + (state->exact_owner_candidate.link_map_addr != + owner->link_map_addr || + state->exact_owner_candidate.generation != owner->generation || + state->exact_owner_candidate.dynamic_view_revision == 0 || + kzt_guest_registry_dynamic_view_matches( + KztGuestRegistryForContext(state->context), + owner->link_map_addr, owner->generation, + &state->exact_owner_candidate.dynamic_view) != 0))) { + return 0; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_before_owner_memory_access( + state->held_source_lease && state->held_source_lease->active, + state->held_decision_lease && state->held_decision_lease->active, + state->loader_quiescence_lease.bindings != NULL && + state->loader_quiescence_lease.cookie != 0, + state->retained_provider_handle != NULL && + state->retained_provider_handle->bindings != NULL && + state->retained_provider_handle->entry != NULL, + state->owner_source_lease.active); +#endif + if (kzt_guest_dynsym_lookup( + &owner_view, &reader_ops, symbol, version_evidence, version, + &lookup) != KZT_GUEST_DYNSYM_LOOKUP_FOUND) { + return 0; + } + return lookup.runtime_address == target && lookup.binding == STB_GLOBAL && + lookup.type == STT_FUNC && lookup.visibility == STV_DEFAULT; +} + +static int production_dynamic_view_needs_library( + const kzt_guest_dynamic_view_t *view, + const kzt_guest_link_map_reader_ops_t *reader_ops, + const char *required_name) +{ + size_t i; + + if (!view || view->status != KZT_GUEST_DYNAMIC_COMPLETE || + !view->strtab.present || + view->strtab.address_semantics != + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS || + view->strtab.value > UINTPTR_MAX || + !view->strsz.present || + view->strsz.address_semantics != KZT_GUEST_DYNAMIC_SCALAR || + !view->strsz.value || view->strsz.value > SIZE_MAX || + view->needed_address_semantics != + KZT_GUEST_DYNAMIC_STRING_TABLE_OFFSET || + !view->needed_count || + view->needed_count > KZT_GUEST_DYNAMIC_NEEDED_LIMIT || + !reader_ops || !reader_ops->read_memory || !required_name || + !required_name[0]) { + return 0; + } + for (i = 0; i < view->needed_count; ++i) { + char needed[KZT_GUEST_REGISTRY_ADDRESS_TEXT_LIMIT]; + uint64_t offset = view->needed_offsets[i]; + size_t remaining; + size_t length; + + if (offset >= view->strsz.value || + view->strtab.value > UINTPTR_MAX - offset) { + return 0; + } + remaining = (size_t)(view->strsz.value - offset); + for (length = 0; + length < remaining && length < sizeof(needed); + ++length) { + if (reader_ops->read_memory( + (uintptr_t)view->strtab.value + (uintptr_t)offset + + length, + &needed[length], 1, reader_ops->opaque) != 0) { + return 0; + } + if (needed[length] == '\0') { + if (strcmp(needed, required_name) == 0) { + return 1; + } + break; + } + } + if (length == remaining || length == sizeof(needed)) { + return 0; + } + } + return 0; +} + +static int production_custom_dlsym_boundary_proven( + const kzt_production_jump_slot_state_t *state, const char *symbol) +{ + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = production_read_guest_memory, + }; + uintptr_t main_namespace_head = 0; + + if (!symbol || strcmp(symbol, "dlsym") != 0) { + return 1; + } + if (!state || !state->context || + kzt_guest_registry_context_get_main_namespace_head( + &state->context->kzt_guest_registry_context, + &main_namespace_head) != 0 || + !main_namespace_head) { + return 0; + } + return state->head && + state->head->latx_type != LATX_ELF_TYPE_MAIN && + state->head->self_link_map == state->last_request.source.link_map_addr && + state->last_request.source.link_map_addr != main_namespace_head && + state->wrapper_alias_borrowed && state->runtime_view_valid && + state->owner_namespace_id == 0 && + state->exact_provider_key.namespace_id == 0 && + state->exact_provider_key.namespace_kind == + KZT_GUEST_LIBRARY_NAMESPACE_MAIN && + state->exact_provider_key.link_map_addr != + state->last_request.source.link_map_addr && + state->wrapper_provider.match.custom_wrapper && + state->wrapper_provider.match.resolved_bridge_exact && + state->wrapper_provider.match.resolved_bridge_target != 0 && + production_dynamic_view_needs_library( + &state->runtime_view, &reader_ops, "libdl.so.2"); +} + +static int production_symbol_candidate_has_exact_name( + const kzt_guest_registry_symbol_candidate_t *candidate, + const char *required_name) +{ + const char *basename; + + if (!candidate || !required_name || !required_name[0] || + candidate->path_status != KZT_GUEST_FIELD_OK || + !candidate->path[0] || + (candidate->soname_status != KZT_GUEST_FIELD_OK && + candidate->soname_status != KZT_GUEST_FIELD_NOT_PARSED)) { + return 0; + } + basename = strrchr(candidate->path, '/'); + basename = basename ? basename + 1 : candidate->path; + return strcmp(basename, required_name) == 0 && + (candidate->soname_status != KZT_GUEST_FIELD_OK || + strcmp(candidate->soname, required_name) == 0); +} + +static int production_exact_owner_candidate_semantics_supported( + const kzt_guest_dynsym_lookup_result_t *lookup) +{ + if (!lookup || lookup->binding == STB_WEAK) { + return 0; + } + if (lookup->binding == KZT_ELF_STB_GNU_UNIQUE) { + return 0; + } + if (lookup->type == KZT_ELF_STT_GNU_IFUNC) { + return 0; + } + return 1; +} + +static int production_resolve_exact_symbol_owner( + kzt_production_jump_slot_state_t *state, uintptr_t target, + const char *symbol, + kzt_symbol_version_evidence_t version_evidence, const char *version, + const char *required_owner_name, + kzt_owner_resolution_t *resolution, uintptr_t *resolved_target) +{ + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = production_read_guest_memory, + }; + kzt_guest_registry_symbol_candidate_t candidate = { 0 }; + kzt_patch_object_ref_t owner = { 0 }; + uintptr_t matched_target = 0; + size_t match_count = 0; + size_t cursor = 0; + int candidate_status; + + if (resolved_target) { + *resolved_target = 0; + } + if (!state || !state->context || !symbol || !symbol[0] || + !resolution || !state->held_decision_lease || + !state->held_decision_lease->active || + !state->loader_quiescence_lease.bindings || + !state->loader_quiescence_lease.cookie || + !kzt_loader_lifecycle_runtime_healthy(state->context) || + !kzt_symbol_version_evidence_valid(version_evidence, version) || + state->owner_source_lease.active) { + return -1; + } + kzt_owner_resolver_init(resolution); + while ((candidate_status = + kzt_guest_registry_symbol_candidate_acquire_next( + state->held_decision_lease, &cursor, &candidate)) == 1) { + kzt_guest_dynsym_lookup_result_t lookup = { 0 }; + kzt_guest_dynsym_lookup_status_t lookup_status; + int target_outside_candidate = + target && candidate.map_start && candidate.map_end && + (target < candidate.map_start || target >= candidate.map_end); + + if (!target && state->held_source_lease && + candidate.link_map_addr == + state->held_source_lease->link_map_addr && + candidate.generation == state->held_source_lease->generation) { + kzt_guest_registry_symbol_candidate_release(&candidate); + continue; + } + if (candidate.dynamic_view_status != KZT_GUEST_FIELD_OK || + candidate.dynamic_view.status != KZT_GUEST_DYNAMIC_COMPLETE || + !candidate.dynamic_view_revision) { + kzt_guest_registry_symbol_candidate_release(&candidate); + if (target_outside_candidate) { + continue; + } + goto fail; + } + lookup_status = kzt_guest_dynsym_lookup( + &candidate.dynamic_view, &reader_ops, symbol, + version_evidence, version, &lookup); + if (lookup_status == KZT_GUEST_DYNSYM_LOOKUP_FOUND && + !production_exact_owner_candidate_semantics_supported(&lookup)) { + kzt_guest_registry_symbol_candidate_release(&candidate); + goto fail; + } + if (target_outside_candidate) { + kzt_guest_registry_symbol_candidate_release(&candidate); + continue; + } + if (lookup_status == KZT_GUEST_DYNSYM_LOOKUP_UNKNOWN) { + kzt_guest_registry_symbol_candidate_release(&candidate); + goto fail; + } + if (lookup_status != KZT_GUEST_DYNSYM_LOOKUP_FOUND || + (target && lookup.runtime_address != target) || + lookup.binding != STB_GLOBAL || lookup.type != STT_FUNC || + lookup.visibility != STV_DEFAULT) { + kzt_guest_registry_symbol_candidate_release(&candidate); + continue; + } + if (required_owner_name && + !production_symbol_candidate_has_exact_name( + &candidate, required_owner_name)) { + kzt_guest_registry_symbol_candidate_release(&candidate); + goto fail; + } + owner = (kzt_patch_object_ref_t) { + .known = 1, + .link_map_addr = candidate.link_map_addr, + .map_start = candidate.map_start, + .map_end = candidate.map_end, + .generation = candidate.generation, + }; + if (++match_count != 1) { + kzt_guest_registry_symbol_candidate_release(&candidate); + goto fail; + } + matched_target = lookup.runtime_address; + if (candidate.soname_status == KZT_GUEST_FIELD_OK && + candidate.soname[0]) { + snprintf(resolution->current_text.soname, + sizeof(resolution->current_text.soname), "%s", + candidate.soname); + } + if (candidate.path_status == KZT_GUEST_FIELD_OK && + candidate.path[0]) { + snprintf(resolution->current_text.path, + sizeof(resolution->current_text.path), "%s", + candidate.path); + } + state->exact_owner_candidate = candidate; + state->owner_source_lease = candidate.lease; + memset(&state->exact_owner_candidate.lease, 0, + sizeof(state->exact_owner_candidate.lease)); + memset(&candidate, 0, sizeof(candidate)); + } + if (candidate_status != 0 || match_count != 1) { + goto fail; + } + resolution->status = KZT_OWNER_RESOLVER_RESOLVED; + resolution->current_owner = owner; + resolution->expected_owner = owner; + resolution->current_match_count = 1; + resolution->expected_match_count = 1; + resolution->owner_match = KZT_PATCH_OWNER_MATCH; + resolution->current_owner.soname = resolution->current_text.soname[0] + ? resolution->current_text.soname + : NULL; + resolution->current_owner.path = resolution->current_text.path[0] + ? resolution->current_text.path + : NULL; + snprintf(resolution->expected_text.soname, + sizeof(resolution->expected_text.soname), "%s", + resolution->current_text.soname); + snprintf(resolution->expected_text.path, + sizeof(resolution->expected_text.path), "%s", + resolution->current_text.path); + resolution->expected_owner.soname = resolution->expected_text.soname[0] + ? resolution->expected_text.soname + : NULL; + resolution->expected_owner.path = resolution->expected_text.path[0] + ? resolution->expected_text.path + : NULL; + state->exact_owner_symbol_proof = 1; + state->exact_owner_without_map_range = 1; + if (resolved_target) { + *resolved_target = matched_target; + } + return 0; +fail: + kzt_guest_registry_symbol_candidate_release(&candidate); + kzt_guest_registry_source_lease_release(&state->owner_source_lease); + memset(&state->exact_owner_candidate, 0, + sizeof(state->exact_owner_candidate)); + kzt_owner_resolver_init(resolution); + return -1; +} + +static int production_resolve_current_owner( + box64context_t *context, uintptr_t current_target, + uintptr_t expected_target, const char *symbol, + kzt_symbol_version_evidence_t version_evidence, const char *version, + kzt_owner_resolution_t *resolution) +{ + if (!context || !resolution) { + return -1; + } + kzt_owner_resolver_init(resolution); + if (kzt_owner_resolver_resolve_current( + KztGuestRegistryForContext(context), current_target, + expected_target, resolution) == 0 && + resolution->status == KZT_OWNER_RESOLVER_RESOLVED && + resolution->owner_match == KZT_PATCH_OWNER_MATCH && + resolution->current_owner.known) { + return 0; + } + (void)current_target; + (void)expected_target; + (void)symbol; + (void)version_evidence; + (void)version; + return -1; +} + +static int production_acquire_wrapper_alias_provider( + kzt_production_jump_slot_state_t *state, + const kzt_patch_object_ref_t *owner, + kzt_guest_library_handle_t *handle) +{ + kzt_guest_wrapper_source_proof_t source_proof = { 0 }; + kzt_guest_library_binding_key_t provider_key = { 0 }; + kzt_guest_registry_address_match_t owner_match = { 0 }; + kzt_guest_registry_address_match_t provider_match = { 0 }; + box64context_t *context = state ? state->context : NULL; + const char *guest_name; + const char *provider_name; + const char *wrapper_name; + int status = -1; + + if (handle) { + memset(handle, 0, sizeof(*handle)); + } + if (!state || !context || !owner || !owner->known || + !owner->link_map_addr || + !owner->generation || !owner->path || !owner->path[0] || !handle || + !state->held_decision_lease || !state->held_decision_lease->active || + !state->loader_quiescence_lease.bindings || + !state->loader_quiescence_lease.cookie || + !context->libclib || !context->libclib->active || + context->libclib->type != LIB_WRAPPED || + !context->libclib->name || !context->libclib->name[0]) { + return -1; + } + guest_name = strrchr(owner->path, '/'); + guest_name = guest_name ? guest_name + 1 : owner->path; + wrapper_name = kzt_guest_library_wrapper_name_for_guest(guest_name); + if (!wrapper_name || strcmp(wrapper_name, guest_name) == 0 || + strcmp(wrapper_name, context->libclib->name) != 0 || + kzt_guest_library_wrapper_source_acquire( + context, owner->link_map_addr, owner->path, wrapper_name, + &source_proof) != 0 || + source_proof.key.generation != owner->generation || + kzt_guest_library_access_lookup_by_library( + &context->kzt_guest_library_access, context->libclib, + &provider_key, handle) != 0 || + !handle->library || handle->library != context->libclib || + handle->object_type != KZT_GUEST_LIBRARY_OBJECT_WRAPPED || + provider_key.namespace_kind != KZT_GUEST_LIBRARY_NAMESPACE_MAIN || + provider_key.namespace_id != 0 || + kzt_guest_registry_find_live_object( + KztGuestRegistryForContext(context), owner->link_map_addr, + &owner_match) != 0 || + owner_match.generation != owner->generation || + owner_match.path_status != KZT_GUEST_FIELD_OK || + (owner_match.soname_status != KZT_GUEST_FIELD_OK && + owner_match.soname_status != KZT_GUEST_FIELD_NOT_PARSED) || + kzt_guest_registry_find_live_object( + KztGuestRegistryForContext(context), provider_key.link_map_addr, + &provider_match) != 0 || + provider_match.generation != provider_key.generation || + provider_match.namespace_id_status != KZT_GUEST_FIELD_OK || + provider_match.namespace_id != provider_key.namespace_id || + provider_match.path_status != KZT_GUEST_FIELD_OK || + (provider_match.soname_status != KZT_GUEST_FIELD_OK && + provider_match.soname_status != KZT_GUEST_FIELD_NOT_PARSED)) { + goto out; + } + provider_name = strrchr(provider_match.path, '/'); + provider_name = provider_name ? provider_name + 1 : provider_match.path; + if (strcmp(provider_name, wrapper_name) != 0 || + (provider_match.soname_status == KZT_GUEST_FIELD_OK && + strcmp(provider_match.soname, wrapper_name) != 0)) { + goto out; + } + state->alias_proof = (kzt_production_alias_proof_t) { + .owner_key = source_proof.key, + .provider_key = provider_key, + .provider_bindings = handle->bindings, + .provider_entry = handle->entry, + .provider_library = handle->library, + .owner_path_status = owner_match.path_status, + .owner_soname_status = owner_match.soname_status, + .provider_path_status = provider_match.path_status, + .provider_soname_status = provider_match.soname_status, + .valid = 1, + }; + snprintf(state->alias_proof.owner_path, + sizeof(state->alias_proof.owner_path), "%s", owner_match.path); + snprintf(state->alias_proof.owner_soname, + sizeof(state->alias_proof.owner_soname), "%s", + owner_match.soname); + snprintf(state->alias_proof.provider_path, + sizeof(state->alias_proof.provider_path), "%s", + provider_match.path); + snprintf(state->alias_proof.provider_soname, + sizeof(state->alias_proof.provider_soname), "%s", + provider_match.soname); + if (!state->owner_source_lease.active) { + state->owner_source_lease = source_proof.lease; + memset(&source_proof.lease, 0, sizeof(source_proof.lease)); + } else if (state->owner_source_lease.link_map_addr != + source_proof.key.link_map_addr || + state->owner_source_lease.generation != + source_proof.key.generation) { + goto out; + } + status = 0; +out: + kzt_guest_library_wrapper_source_release(&source_proof); + if (status != 0) { + memset(&state->alias_proof, 0, sizeof(state->alias_proof)); + kzt_guest_library_handle_release(handle); + } + return status; +} + +static int production_wrapper_alias_provider_matches( + kzt_production_jump_slot_state_t *state, + const kzt_patch_object_ref_t *owner) +{ + kzt_guest_registry_address_match_t owner_match = { 0 }; + kzt_guest_registry_address_match_t provider_match = { 0 }; + const kzt_production_alias_proof_t *proof = + state ? &state->alias_proof : NULL; + + return proof && proof->valid && state->retained_provider_handle && + owner && owner->known && + proof->owner_key.link_map_addr == owner->link_map_addr && + proof->owner_key.generation == owner->generation && + state->owner_source_lease.active && + state->owner_source_lease.link_map_addr == owner->link_map_addr && + state->owner_source_lease.generation == owner->generation && + state->retained_provider_handle->bindings == + proof->provider_bindings && + state->retained_provider_handle->entry == proof->provider_entry && + state->retained_provider_handle->library == proof->provider_library && + state->retained_provider_handle->object_type == + KZT_GUEST_LIBRARY_OBJECT_WRAPPED && + kzt_guest_registry_find_live_object( + KztGuestRegistryForContext(state->context), + proof->owner_key.link_map_addr, &owner_match) == 0 && + owner_match.generation == proof->owner_key.generation && + owner_match.path_status == proof->owner_path_status && + owner_match.soname_status == proof->owner_soname_status && + strcmp(owner_match.path, proof->owner_path) == 0 && + strcmp(owner_match.soname, proof->owner_soname) == 0 && + kzt_guest_registry_find_live_object( + KztGuestRegistryForContext(state->context), + proof->provider_key.link_map_addr, &provider_match) == 0 && + provider_match.generation == proof->provider_key.generation && + provider_match.namespace_id_status == KZT_GUEST_FIELD_OK && + provider_match.namespace_id == proof->provider_key.namespace_id && + provider_match.path_status == proof->provider_path_status && + provider_match.soname_status == proof->provider_soname_status && + strcmp(provider_match.path, proof->provider_path) == 0 && + strcmp(provider_match.soname, proof->provider_soname) == 0; +} + static int production_shadow_expected_target( const kzt_patch_candidate_t *candidate, uintptr_t *expected, void *opaque) @@ -447,15 +1039,6 @@ static int production_slot_end_write(kzt_patch_spike_permission_lease_t *lease, return result; } -typedef enum kzt_production_slot_transaction_result { - KZT_PRODUCTION_SLOT_TRANSACTION_ERROR = -1, - KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH = 0, - KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED = 1, - KZT_PRODUCTION_SLOT_TRANSACTION_ROLLED_BACK = 2, - KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE = 3, - KZT_PRODUCTION_SLOT_TRANSACTION_CIRCUIT_OPEN = 4, -} kzt_production_slot_transaction_result_t; - static const char *production_slot_transaction_result_name( kzt_production_slot_transaction_result_t result) { @@ -476,88 +1059,463 @@ static const char *production_slot_transaction_result_name( return "UNKNOWN"; } -static int production_slot_finish_write( - kzt_patch_spike_permission_lease_t *permission_lease) +static int production_mandatory_finish_slot( + kzt_patch_spike_permission_lease_t *permission) { - int result; - - if (!permission_lease) { + if (!permission || !permission->mmap_lock_held) { return -1; } - permission_lease->restore_attempted = 1; - ++permission_lease->restore_attempts; - result = production_slot_end_write(permission_lease, NULL); - if (result == 0) { - permission_lease->restored = 1; + permission->restore_attempted = 1; + ++permission->restore_attempts; + if (production_slot_end_write(permission, NULL) != 0) { + return -1; } - return result; + permission->restored = 1; + return 0; } static kzt_production_slot_transaction_result_t -production_lazy_prebind_slot_cas( - box64context_t *context, uintptr_t slot_addr, uintptr_t expected, - uintptr_t replacement, uintptr_t *observed) +production_mandatory_slot_transaction( + uintptr_t slot_addr, uintptr_t expected, uintptr_t replacement, + uintptr_t *final_value) { - kzt_patch_spike_permission_lease_t permission_lease = { 0 }; - uintptr_t current = expected; - uintptr_t rollback_expected = replacement; - int exchanged; - int rollback_succeeded = 1; - int restore_succeeded; - - if (observed) { - *observed = 0; - } - if (!context || !slot_addr || !expected || !replacement) { + kzt_patch_spike_permission_lease_t permission = { 0 }; + uintptr_t observed = expected; + uintptr_t compare; + int wrote = 0; + + if (final_value) { + *final_value = expected; + } + if (!slot_addr || !replacement || + production_slot_begin_write(slot_addr, &permission, NULL) != 0) { + if (permission.mmap_lock_held) { + (void)production_mandatory_finish_slot(&permission); + if (permission.mmap_lock_held) { + (void)production_mandatory_finish_slot(&permission); + } + } return KZT_PRODUCTION_SLOT_TRANSACTION_ERROR; } - if (kzt_patch_spike_guard_circuit_open( - KztPatchSpikeGuardForContext(context))) { - return KZT_PRODUCTION_SLOT_TRANSACTION_CIRCUIT_OPEN; + if (production_slot_load(slot_addr, &observed, NULL) != 0) { + goto fail_before_write; } - if ( - production_slot_begin_write(slot_addr, &permission_lease, NULL) != 0) { - if (permission_lease.mmap_lock_held && - production_slot_finish_write(&permission_lease) != 0 && - production_slot_finish_write(&permission_lease) != 0) { - kzt_patch_spike_guard_trip(KztPatchSpikeGuardForContext(context)); - return KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE; + if (observed != expected) { + if (final_value) { + *final_value = observed; } - return KZT_PRODUCTION_SLOT_TRANSACTION_ERROR; + if (production_mandatory_finish_slot(&permission) != 0 && + permission.mmap_lock_held) { + (void)production_mandatory_finish_slot(&permission); + } + return permission.mmap_lock_held ? + KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE : + KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH; + } + compare = expected; + if (production_slot_cas(slot_addr, &compare, replacement, NULL) != 1) { + observed = compare; + goto fail_before_write; + } + wrote = 1; + if (production_slot_load(slot_addr, &observed, NULL) != 0 || + observed != replacement) { + goto rollback; + } + if (production_mandatory_finish_slot(&permission) == 0) { + if (final_value) { + *final_value = replacement; + } + return KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED; + } + +rollback: + compare = replacement; + if (!wrote || + production_slot_cas(slot_addr, &compare, expected, NULL) != 1 || + production_slot_load(slot_addr, &observed, NULL) != 0 || + observed != expected) { + if (permission.mmap_lock_held) { + (void)production_mandatory_finish_slot(&permission); + } + if (final_value) { + *final_value = __atomic_load_n( + (uintptr_t *)slot_addr, __ATOMIC_ACQUIRE); + } + return KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE; + } + if (permission.mmap_lock_held && + production_mandatory_finish_slot(&permission) != 0) { + if (permission.mmap_lock_held) { + (void)production_mandatory_finish_slot(&permission); + } + if (final_value) { + *final_value = expected; + } + return KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE; + } + if (final_value) { + *final_value = expected; + } + return KZT_PRODUCTION_SLOT_TRANSACTION_ROLLED_BACK; + +fail_before_write: + if (production_mandatory_finish_slot(&permission) != 0 && + permission.mmap_lock_held) { + (void)production_mandatory_finish_slot(&permission); + } + if (final_value) { + *final_value = observed; + } + return permission.mmap_lock_held ? + KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE : + KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH; +} + +kzt_production_slot_transaction_result_t +kzt_production_guest_relocation_write( + box64context_t *context, uintptr_t source_link_map, + kzt_patch_relocation_type_t reloc_type, uintptr_t slot_addr, + uintptr_t expected, uintptr_t replacement, const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, const char *version, + uintptr_t *final_value) +{ + kzt_guest_registry_t *registry; + kzt_guest_registry_address_match_t source_match; + kzt_guest_registry_source_lease_t source_lease = { 0 }; + kzt_production_slot_transaction_result_t result = + KZT_PRODUCTION_SLOT_TRANSACTION_ERROR; + + if (final_value) { + *final_value = expected; + } + if (!context || !source_link_map || !slot_addr || !replacement || + !symbol_name || !symbol_name[0] || + (reloc_type != KZT_PATCH_RELOCATION_GLOB_DAT && + reloc_type != KZT_PATCH_RELOCATION_JUMP_SLOT) || + !kzt_symbol_version_evidence_valid(version_evidence, version)) { + return result; + } + registry = KztGuestRegistryForContext(context); + if (!registry || + kzt_guest_registry_find_live_object( + registry, source_link_map, &source_match) != 0 || + !source_match.generation || + source_match.namespace_id_status != KZT_GUEST_FIELD_OK || + source_match.namespace_id != 0 || + kzt_guest_registry_source_lease_acquire( + registry, source_link_map, source_match.generation, + source_match.namespace_id, &source_lease) != 0) { + return result; } + result = production_mandatory_slot_transaction( + slot_addr, expected, replacement, final_value); + kzt_guest_registry_source_lease_release(&source_lease); + return result; +} + +typedef struct kzt_production_eager_write_state { + const kzt_guest_registry_patch_decision_lease_t *decision_lease; + uintptr_t expected; + uintptr_t replacement; + uintptr_t observed; + int cas_mismatch; +} kzt_production_eager_write_state_t; + +static int production_eager_write_validate( + const kzt_patch_decision_t *decision, void *opaque) +{ + kzt_production_eager_write_state_t *state = opaque; + const kzt_guest_registry_patch_decision_lease_t *lease = + state ? state->decision_lease : NULL; + + return state && decision && lease && lease->active && + decision->source.link_map_addr == lease->link_map_addr && + decision->source.generation == lease->generation && + decision->slot_current_value == state->expected && + decision->bridge_target == state->replacement ? 0 : -1; +} + +static int production_eager_write_cas(uintptr_t slot_addr, uintptr_t value, + void *opaque) +{ + kzt_production_eager_write_state_t *state = opaque; + uintptr_t expected; + int exchanged; + + if (!state || !slot_addr) { + return -1; + } + expected = value == state->replacement ? state->expected : + state->replacement; exchanged = __atomic_compare_exchange_n( - (uintptr_t *)slot_addr, ¤t, replacement, 0, + (uintptr_t *)slot_addr, &expected, value, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE) ? 1 : 0; + state->observed = expected; + state->cas_mismatch = !exchanged; #ifdef KZT_JUMP_SLOT_PRODUCTION_TEST kzt_jump_slot_production_test_after_slot_cas(exchanged); #endif - if (observed) { - *observed = current; + return exchanged ? 0 : -1; +} + +kzt_production_slot_transaction_result_t +kzt_production_eager_relocation_write( + box64context_t *context, uintptr_t source_link_map, + const kzt_patch_object_ref_t *owner, + kzt_patch_relocation_type_t reloc_type, uintptr_t slot_addr, + uintptr_t expected, uintptr_t replacement, const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, const char *version, + uintptr_t *final_value) +{ + kzt_guest_registry_t *registry; + kzt_guest_registry_address_match_t source_match; + kzt_guest_registry_source_lease_t source_lease = { 0 }; + kzt_guest_registry_patch_decision_lease_t decision_lease = { 0 }; + kzt_patch_object_ref_t current_owner; + kzt_patch_decision_t decision; + kzt_patch_spike_record_t record; + kzt_production_eager_write_state_t state; + kzt_patch_spike_slot_ops_t slot_ops; + kzt_production_slot_transaction_result_t result = + KZT_PRODUCTION_SLOT_TRANSACTION_ERROR; + + if (final_value) { + *final_value = expected; + } + if (!context || !source_link_map || !slot_addr || !replacement || + !symbol_name || !symbol_name[0] || + (reloc_type != KZT_PATCH_RELOCATION_GLOB_DAT && + reloc_type != KZT_PATCH_RELOCATION_JUMP_SLOT) || + !kzt_symbol_version_evidence_valid(version_evidence, version)) { + return result; + } + registry = KztGuestRegistryForContext(context); + if (!registry || + kzt_guest_registry_find_live_object( + registry, source_link_map, &source_match) != 0 || + !source_match.generation || + source_match.namespace_id_status != KZT_GUEST_FIELD_OK || + source_match.namespace_id != 0 || + kzt_guest_registry_source_lease_acquire( + registry, source_link_map, source_match.generation, + source_match.namespace_id, &source_lease) != 0 || + kzt_guest_registry_patch_decision_lease_acquire( + &source_lease, &decision_lease) != 0) { + kzt_guest_registry_source_lease_release(&source_lease); + return result; + } + current_owner = owner && owner->known && owner->link_map_addr && + owner->generation ? *owner : + (kzt_patch_object_ref_t) { + .known = 1, + .link_map_addr = source_lease.link_map_addr, + .generation = source_lease.generation, + }; + decision = (kzt_patch_decision_t) { + .kind = KZT_PATCH_DECISION_APPROVED, + .reason = KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE, + .allow_native_bridge = 1, + .source = { + .known = 1, + .link_map_addr = source_lease.link_map_addr, + .generation = source_lease.generation, + }, + .dynamic_view_generation = source_lease.generation, + .dynamic_view_available = 1, + .table_kind = reloc_type == KZT_PATCH_RELOCATION_JUMP_SLOT ? + KZT_PATCH_TABLE_PLT_RELA : KZT_PATCH_TABLE_RELA, + .reloc_type = reloc_type, + .slot_addr = slot_addr, + .slot_current_value_present = 1, + .slot_current_value = expected, + .symbol_name = symbol_name, + .version_evidence = version_evidence, + .version = version, + .current_owner = current_owner, + .owner_match = KZT_PATCH_OWNER_MATCH, + .bridge_target = replacement, + }; + state = (kzt_production_eager_write_state_t) { + .decision_lease = &decision_lease, + .expected = expected, + .replacement = replacement, + .observed = expected, + }; + slot_ops = (kzt_patch_spike_slot_ops_t) { + .read_slot = production_slot_load, + .write_slot = production_eager_write_cas, + .begin_write = production_slot_begin_write, + .end_write = production_slot_end_write, + .validate_generation = production_eager_write_validate, + .opaque = &state, + }; + if (kzt_patch_spike_writer_try_apply_with_slot_ops( + KztPatchSpikeGuardForContext(context), &decision, + &slot_ops, &record) == 0) { + if (record.result == KZT_PATCH_SPIKE_RESULT_APPLIED) { + result = KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED; + } else if (record.result == KZT_PATCH_SPIKE_RESULT_ROLLED_BACK) { + result = KZT_PRODUCTION_SLOT_TRANSACTION_ROLLED_BACK; + } else if (record.result == KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE) { + result = KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE; + } else if (record.result == KZT_PATCH_SPIKE_RESULT_CIRCUIT_OPEN) { + result = KZT_PRODUCTION_SLOT_TRANSACTION_CIRCUIT_OPEN; + } else if (state.cas_mismatch) { + result = KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH; + } } - if (production_slot_finish_write(&permission_lease) == 0) { - return exchanged ? KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED : - KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH; + if (final_value) { + *final_value = __atomic_load_n( + (uintptr_t *)slot_addr, __ATOMIC_ACQUIRE); } + kzt_guest_registry_patch_decision_lease_release(&decision_lease); + kzt_guest_registry_source_lease_release(&source_lease); + return result; +} + +typedef struct kzt_production_prebind_write_state { + const kzt_lazy_prebind_lease_t *lease; + uintptr_t expected; + uintptr_t replacement; + uintptr_t observed; + int cas_mismatch; +} kzt_production_prebind_write_state_t; - if (exchanged) { - rollback_succeeded = __atomic_compare_exchange_n( - (uintptr_t *)slot_addr, &rollback_expected, expected, 0, - __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE) ? 1 : 0; +static int production_lazy_prebind_guard_validate( + const kzt_patch_decision_t *decision, void *opaque) +{ + kzt_production_prebind_write_state_t *state = opaque; + const kzt_lazy_prebind_lease_t *lease = state ? state->lease : NULL; + const kzt_lazy_prebind_record_t *record = lease ? &lease->record : NULL; + + if (!state || !decision || !lease || !lease->active || !record || + (lease->operation != KZT_LAZY_PREBIND_LEASE_PUBLISH && + lease->operation != KZT_LAZY_PREBIND_LEASE_REVOKE) || + decision->source.link_map_addr != record->source.link_map_addr || + decision->source.generation != record->source.generation || + decision->slot_addr != record->slot_addr || + decision->slot_current_value != state->expected || + decision->bridge_target != state->replacement) { + return -1; + } + return lease->operation == KZT_LAZY_PREBIND_LEASE_REVOKE || + kzt_lazy_prebind_scope_lease_valid(lease) ? 0 : -1; +} + +static int production_lazy_prebind_guard_write( + uintptr_t slot_addr, uintptr_t value, void *opaque) +{ + kzt_production_prebind_write_state_t *state = opaque; + uintptr_t expected; + int exchanged; + + if (!state || !slot_addr || !value) { + return -1; + } + expected = value == state->replacement ? state->expected : + state->replacement; + exchanged = __atomic_compare_exchange_n( + (uintptr_t *)slot_addr, &expected, value, 0, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE) ? 1 : 0; + state->observed = expected; + state->cas_mismatch = !exchanged; #ifdef KZT_JUMP_SLOT_PRODUCTION_TEST - kzt_jump_slot_production_test_after_slot_cas(rollback_succeeded); + kzt_jump_slot_production_test_after_slot_cas(exchanged); #endif - if (observed) { - *observed = rollback_succeeded ? expected : rollback_expected; + return exchanged ? 0 : -1; +} + +static kzt_production_slot_transaction_result_t +production_lazy_prebind_slot_cas( + box64context_t *context, const kzt_lazy_prebind_lease_t *lease, + uintptr_t expected, uintptr_t replacement, uintptr_t *observed) +{ + const kzt_lazy_prebind_record_t *record = lease ? &lease->record : NULL; + const kzt_lazy_prebind_identity_t *owner_identity; + kzt_patch_decision_t decision; + kzt_patch_spike_record_t writer_record; + kzt_production_prebind_write_state_t state; + kzt_patch_spike_slot_ops_t slot_ops; + kzt_production_slot_transaction_result_t result = + KZT_PRODUCTION_SLOT_TRANSACTION_ERROR; + + if (observed) { + *observed = expected; + } + if (!context || !lease || !lease->active || !record || + !record->source.link_map_addr || !record->source.generation || + !record->slot_addr || !expected || !replacement || + !record->symbol[0]) { + return result; + } + owner_identity = replacement == record->bridge_target ? + &record->provider : &record->source; + decision = (kzt_patch_decision_t) { + .kind = KZT_PATCH_DECISION_APPROVED, + .reason = KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE, + .allow_native_bridge = 1, + .source = { + .known = 1, + .link_map_addr = record->source.link_map_addr, + .generation = record->source.generation, + }, + .dynamic_view_generation = record->source.generation, + .dynamic_view_available = 1, + .table_kind = KZT_PATCH_TABLE_PLT_RELA, + .entry_index = record->relocation_index, + .reloc_type = KZT_PATCH_RELOCATION_JUMP_SLOT, + .slot_addr = record->slot_addr, + .slot_current_value_present = 1, + .slot_current_value = expected, + .symbol_name = record->symbol, + .version_evidence = record->version_evidence, + .version = record->version[0] ? record->version : NULL, + .current_owner = { + .known = 1, + .link_map_addr = owner_identity->link_map_addr, + .generation = owner_identity->generation, + }, + .owner_match = KZT_PATCH_OWNER_MATCH, + .bridge_target = replacement, + }; + state = (kzt_production_prebind_write_state_t) { + .lease = lease, + .expected = expected, + .replacement = replacement, + .observed = expected, + }; + slot_ops = (kzt_patch_spike_slot_ops_t) { + .read_slot = production_slot_load, + .write_slot = production_lazy_prebind_guard_write, + .begin_write = production_slot_begin_write, + .end_write = production_slot_end_write, + .validate_generation = production_lazy_prebind_guard_validate, + .opaque = &state, + }; + if (kzt_patch_spike_writer_try_apply_with_slot_ops( + KztPatchSpikeGuardForContext(context), &decision, + &slot_ops, &writer_record) == 0) { + if (writer_record.result == KZT_PATCH_SPIKE_RESULT_APPLIED) { + result = KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED; + } else if (writer_record.result == + KZT_PATCH_SPIKE_RESULT_ROLLED_BACK) { + result = KZT_PRODUCTION_SLOT_TRANSACTION_ROLLED_BACK; + } else if (writer_record.result == + KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE) { + result = KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE; + } else if (writer_record.result == + KZT_PATCH_SPIKE_RESULT_CIRCUIT_OPEN) { + result = KZT_PRODUCTION_SLOT_TRANSACTION_CIRCUIT_OPEN; + } else if (state.cas_mismatch) { + result = KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH; } } - restore_succeeded = production_slot_finish_write(&permission_lease) == 0; - if (rollback_succeeded && restore_succeeded) { - return exchanged ? KZT_PRODUCTION_SLOT_TRANSACTION_ROLLED_BACK : - KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH; + if (observed) { + *observed = __atomic_load_n( + (uintptr_t *)record->slot_addr, __ATOMIC_ACQUIRE); } - - kzt_patch_spike_guard_trip(KztPatchSpikeGuardForContext(context)); - return KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE; + return result; } static int production_prevalidate_write_evidence( @@ -599,10 +1557,24 @@ static int production_prevalidate_write_evidence( decision->source.link_map_addr || state->held_decision_lease->generation != decision->source.generation || state->held_decision_lease->namespace_id != 0 || - !state->retained_provider_handle || - !state->retained_provider_handle->bindings || - !state->retained_provider_handle->entry || - state->retained_provider_handle->library != state->resolved_provider || + !production_exact_provider_handle_matches(state) || + !state->loader_quiescence_lease.bindings || + !state->loader_quiescence_lease.cookie || + (state->wrapper_alias_borrowed && + (!state->alias_proof.valid || + !kzt_guest_library_wrapper_alias_symbol_allowed( + decision->symbol_name))) || + (state->wrapper_alias_borrowed && + !production_wrapper_alias_provider_matches( + state, &state->exact_provider_owner)) || + (!state->wrapper_alias_borrowed && + (state->exact_provider_key.link_map_addr != + decision->current_owner.link_map_addr || + state->exact_provider_key.generation != + decision->current_owner.generation || + state->exact_provider_key.namespace_id != state->owner_namespace_id || + state->exact_provider_key.namespace_kind != + KZT_GUEST_LIBRARY_NAMESPACE_MAIN)) || !state->exact_provider_owner.known || state->exact_provider_owner.link_map_addr != decision->current_owner.link_map_addr || @@ -640,19 +1612,30 @@ static int production_prevalidate_write_evidence( __ATOMIC_ACQUIRE) != decision->slot_current_value) { goto out; } - kzt_owner_resolver_init(&owner_resolution); - if (kzt_owner_resolver_resolve_current( - KztGuestRegistryForContext(state->context), - decision->slot_current_value, decision->slot_current_value, - &owner_resolution) != 0 || - owner_resolution.status != KZT_OWNER_RESOLVER_RESOLVED || - owner_resolution.owner_match != KZT_PATCH_OWNER_MATCH || - !owner_resolution.current_owner.known || - owner_resolution.current_owner.link_map_addr != - decision->current_owner.link_map_addr || - owner_resolution.current_owner.generation != - decision->current_owner.generation) { - goto out; + if (state->exact_owner_symbol_proof) { + if (!production_exact_owner_symbol_matches( + state, &decision->current_owner, + decision->slot_current_value, + state->initial_request.symbol_name, + state->initial_request.version_evidence, + state->initial_request.version)) { + goto out; + } + } else { + kzt_owner_resolver_init(&owner_resolution); + if (kzt_owner_resolver_resolve_current( + KztGuestRegistryForContext(state->context), + decision->slot_current_value, decision->slot_current_value, + &owner_resolution) != 0 || + owner_resolution.status != KZT_OWNER_RESOLVER_RESOLVED || + owner_resolution.owner_match != KZT_PATCH_OWNER_MATCH || + !owner_resolution.current_owner.known || + owner_resolution.current_owner.link_map_addr != + decision->current_owner.link_map_addr || + owner_resolution.current_owner.generation != + decision->current_owner.generation) { + goto out; + } } valid = 1; out: @@ -675,6 +1658,12 @@ static int production_decision_matches_prevalidated( decision->slot_addr == prevalidated->slot_addr && decision->slot_current_value_present && decision->slot_current_value == prevalidated->slot_current_value && + decision->symbol_index == prevalidated->symbol_index && + decision->symbol_name && prevalidated->symbol_name && + strcmp(decision->symbol_name, prevalidated->symbol_name) == 0 && + kzt_symbol_version_evidence_matches( + decision->version_evidence, decision->version, + prevalidated->version_evidence, prevalidated->version) && decision->current_owner.known == prevalidated->current_owner.known && decision->current_owner.link_map_addr == prevalidated->current_owner.link_map_addr && @@ -693,20 +1682,48 @@ static int production_validate_prevalidated_write( .read_memory = production_read_guest_memory, }; kzt_guest_symbol_scope_result_t revalidated; - if (!state || !decision || !state->prevalidated_decision_valid || + !state->held_source_lease || !state->held_source_lease->active || !state->held_decision_lease || !state->held_decision_lease->active || - !state->retained_provider_handle || - state->retained_provider_handle->library != state->resolved_provider || + !state->loader_quiescence_lease.bindings || + !state->loader_quiescence_lease.cookie || + !production_exact_provider_handle_matches(state) || + (state->wrapper_alias_borrowed && + (!state->alias_proof.valid || + !kzt_guest_library_wrapper_alias_symbol_allowed( + decision->symbol_name))) || + (!state->wrapper_alias_borrowed && + (state->exact_provider_key.link_map_addr != + decision->current_owner.link_map_addr || + state->exact_provider_key.generation != + decision->current_owner.generation || + state->exact_provider_key.namespace_id != state->owner_namespace_id || + state->exact_provider_key.namespace_kind != + KZT_GUEST_LIBRARY_NAMESPACE_MAIN)) || !production_decision_matches_prevalidated( decision, &state->prevalidated_decision) || - kzt_guest_symbol_scope_revalidate( - &state->symbol_scope_proof, &state->symbol_scope_request, - &reader_ops, &revalidated) != KZT_GUEST_SYMBOL_SCOPE_SAFE || __atomic_load_n((uintptr_t *)decision->slot_addr, __ATOMIC_ACQUIRE) != decision->slot_current_value) { return -1; } + if (state->wrapper_alias_borrowed && + !production_wrapper_alias_provider_matches( + state, &decision->current_owner)) { + return -1; + } + if (state->exact_owner_symbol_proof) { + if (!production_exact_owner_symbol_matches( + state, &decision->current_owner, + decision->slot_current_value, decision->symbol_name, + decision->version_evidence, decision->version)) { + return -1; + } + } else if (kzt_guest_symbol_scope_revalidate( + &state->symbol_scope_proof, &state->symbol_scope_request, + &reader_ops, &revalidated) != + KZT_GUEST_SYMBOL_SCOPE_SAFE) { + return -1; + } return 0; } @@ -724,11 +1741,13 @@ static int production_enrich( NULL, .bridge_ops = wrapper_provider ? &wrapper_provider->bridge_ops : NULL, }; + int status; #ifdef KZT_JUMP_SLOT_PRODUCTION_TEST kzt_jump_slot_production_test_full_enrich(); #endif - return kzt_rela_immediate_request_enrich(request, &enrich_input, - enrich_result); + status = kzt_rela_immediate_request_enrich( + request, &enrich_input, enrich_result); + return status; } static int production_enrich_wrapper_only( @@ -880,7 +1899,6 @@ typedef struct kzt_lazy_direct_production_state { kzt_guest_library_binding_key_t provider_key; kzt_guest_library_handle_t provider_handle; int provider_handle_owned; - kzt_guest_library_loader_quiescence_lease_t loader_quiescence_lease; kzt_wrapper_bridge_provider_t wrapper_provider; kzt_wrapper_probe_result_t wrapper_probe; kzt_guest_symbol_scope_request_t symbol_scope_request; @@ -889,6 +1907,14 @@ typedef struct kzt_lazy_direct_production_state { kzt_lazy_direct_timing_t *timing; int timing_enabled; int prebind_hit; + int exact_symbol_provider; + const kzt_lazy_direct_route_input_t *guard_input; + const kzt_lazy_direct_route_provider_t *guard_provider; + const kzt_lazy_direct_route_bridge_t *guard_bridge; + const kzt_lazy_direct_route_lease_t *guard_lease; + uintptr_t guard_expected; + uintptr_t guard_replacement; + int guard_cas_mismatch; } kzt_lazy_direct_production_state_t; struct kzt_lazy_direct_timing { @@ -999,7 +2025,7 @@ static int production_lazy_prebind_publish_record( return 0; } writer_result = production_lazy_prebind_slot_cas( - context, record->slot_addr, record->expected_slot, + context, &lease, record->expected_slot, record->bridge_target, &observed); committed = writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED; kzt_lazy_prebind_scope_publish_finish(&lease, committed); @@ -1024,6 +2050,47 @@ static int production_lazy_prebind_publish_record( return committed ? 1 : 0; } +static size_t production_lazy_prebind_find_symbol_index( + const kzt_guest_dynamic_view_t *view, + const kzt_guest_link_map_reader_ops_t *reader_ops, + const kzt_patch_object_ref_t *source, + unsigned long dynamic_view_generation, + size_t relocation_count, const char *symbol_name) +{ + size_t index; + + if (!view || !reader_ops || !source || !dynamic_view_generation || + !symbol_name || !symbol_name[0]) { + return relocation_count; + } + for (index = 0; index < relocation_count; ++index) { + kzt_patch_candidate_t candidate = { 0 }; + char candidate_strings[512] = { 0 }; + kzt_runtime_got_plt_candidate_result_t result; + kzt_runtime_got_plt_candidate_request_t request = { + .view = view, + .reader_ops = reader_ops, + .source = source, + .dynamic_view_generation = dynamic_view_generation, + .only_entry = 1, + .only_table_kind = KZT_PATCH_TABLE_PLT_RELA, + .only_entry_index = index, + .candidates = &candidate, + .candidate_capacity = 1, + .string_storage = candidate_strings, + .string_storage_size = sizeof(candidate_strings), + }; + + if (kzt_runtime_got_plt_candidates_collect(&request, &result) == 0 && + result.status == KZT_RUNTIME_GOT_PLT_CANDIDATE_OK && + result.candidate_count == 1 && candidate.symbol_name && + strcmp(candidate.symbol_name, symbol_name) == 0) { + return index; + } + } + return relocation_count; +} + /* This runs under kzt_per_object_got_plt_apply's exact source and decision * leases. It records already-proven facts only and deliberately has no slot * writer: the resolver retains the final Registry, fingerprint, and CAS gate. */ @@ -1045,7 +2112,10 @@ static int production_lazy_prebind_object_prepare( uintptr_t namespace_head = 0; size_t relocation_count; size_t index; + size_t iteration; + size_t dlerror_index; size_t prepared = 0; + int source_dlerror_native = 0; kzt_patch_object_ref_t source_ref; if (!context || !head || !head->self_link_map || !source_generation || @@ -1093,7 +2163,10 @@ static int production_lazy_prebind_object_prepare( .soname = head->name, .path = head->path, }; - for (index = 0; index < relocation_count; ++index) { + dlerror_index = production_lazy_prebind_find_symbol_index( + source_dynamic_view, &reader_ops, &source_ref, source_generation, + relocation_count, "dlerror"); + for (iteration = 0; iteration < relocation_count; ++iteration) { kzt_runtime_got_plt_candidate_request_t collector_request; kzt_runtime_got_plt_candidate_result_t collector_result; kzt_patch_candidate_t candidate; @@ -1107,6 +2180,21 @@ static int production_lazy_prebind_object_prepare( kzt_wrapper_probe_result_t wrapper_probe; kzt_wrapper_probe_request_t wrapper_request; kzt_lazy_prebind_record_t record; + int provider_status; + int published; + + if (dlerror_index < relocation_count) { + if (iteration == 0) { + index = dlerror_index; + } else { + index = iteration - 1; + if (index >= dlerror_index) { + ++index; + } + } + } else { + index = iteration; + } memset(&candidate, 0, sizeof(candidate)); memset(candidate_strings, 0, sizeof(candidate_strings)); @@ -1139,6 +2227,9 @@ static int production_lazy_prebind_object_prepare( head->gotplt_end) || !candidate.symbol_name || !candidate.symbol_name[0] || kzt_patch_symbol_must_stay_guest(candidate.symbol_name) || + (kzt_patch_symbol_requires_dlerror_prebind( + candidate.symbol_name) && + !source_dlerror_native) || production_symbol_scope_request( context, head, source_generation, namespace_head, source_dynamic_view, &reader_ops, candidate.symbol_index, @@ -1165,11 +2256,26 @@ static int production_lazy_prebind_object_prepare( if (kzt_guest_library_access_lookup( &context->kzt_guest_library_access, &provider_key, &provider_handle) != 0 || !provider_handle.library || - provider_handle.object_type != KZT_GUEST_LIBRARY_OBJECT_WRAPPED || - kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( - context, &provider_handle, candidate.symbol_name, - candidate.version_evidence, candidate.version, - &wrapper_provider) <= 0) { + provider_handle.object_type != KZT_GUEST_LIBRARY_OBJECT_WRAPPED) { + kzt_guest_library_handle_release(&provider_handle); + continue; + } + if (production_symbol_uses_guarded_xcb_bridge( + candidate.symbol_name)) { + provider_status = + kzt_rela_runtime_wrapper_provider_discover_guarded_retained_with_version_evidence( + context, &provider_handle, candidate.symbol_name, + candidate.version_evidence, candidate.version, + scope_proof.selected_provider_address, + KZT_BRIDGE_GUARD_XCB_CONNECTION, &wrapper_provider); + } else { + provider_status = + kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( + context, &provider_handle, candidate.symbol_name, + candidate.version_evidence, candidate.version, + &wrapper_provider); + } + if (provider_status <= 0) { kzt_guest_library_handle_release(&provider_handle); continue; } @@ -1204,6 +2310,10 @@ static int production_lazy_prebind_object_prepare( record.bridge_target = wrapper_probe.bridge_target; record.bridge_generation = provider_key.generation; record.bridge_custom_wrapper = wrapper_provider.match.custom_wrapper; + record.loader_mutation_invariant = + record.bridge_custom_wrapper && + kzt_patch_symbol_is_loader_route_family(record.symbol) && + record.source.link_map_addr == namespace_head; record.scope_proof = scope_proof; kzt_lazy_prebind_claim_result_t claim = kzt_lazy_prebind_scope_claim(scope, &record); @@ -1213,10 +2323,16 @@ static int production_lazy_prebind_object_prepare( } if (claim == KZT_LAZY_PREBIND_CLAIM_CREATED || claim == KZT_LAZY_PREBIND_CLAIM_REUSED) { - (void)production_lazy_prebind_publish_record(context, scope, - &record, - target_prepare, - target_prepare_opaque); + published = production_lazy_prebind_publish_record( + context, scope, &record, target_prepare, + target_prepare_opaque); + if (strcmp(record.symbol, "dlerror") == 0 && + (published || + __atomic_load_n( + (uintptr_t *)record.slot_addr, __ATOMIC_ACQUIRE) == + record.bridge_target)) { + source_dlerror_native = 1; + } } kzt_guest_library_handle_release(&provider_handle); } @@ -1271,8 +2387,8 @@ static int production_lazy_prebind_revoke_closed( return -1; } record = lease.record; - writer_result = production_lazy_prebind_slot_cas( - context, record.slot_addr, record.bridge_target, + writer_result = production_mandatory_slot_transaction( + record.slot_addr, record.bridge_target, record.expected_slot, &observed); revoked = writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED || @@ -1281,8 +2397,11 @@ static int production_lazy_prebind_revoke_closed( kzt_lazy_prebind_scope_revoke_finish(&lease, revoked); printf_kzt_registry_diagnostics( "kzt_lazy_prebind_revoke schema=1 source=%p generation=%lu " - "slot=%p bridge=%p stub=%p result=%s writer=%s observed=%p\n", + "provider=%p provider_generation=%lu slot=%p bridge=%p stub=%p " + "result=%s writer=%s observed=%p\n", (void *)record.source.link_map_addr, record.source.generation, + (void *)record.provider.link_map_addr, + record.provider.generation, (void *)record.slot_addr, (void *)record.bridge_target, (void *)record.expected_slot, writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED ? @@ -1326,8 +2445,10 @@ int kzt_production_lazy_prebind_retire( { kzt_lazy_prebind_scope_t *scope; - if (!context || !identity || - !(scope = KztLazyPrebindScopeForContext(context)) || + if (!context || !identity) { + return -1; + } + if (!(scope = KztLazyPrebindScopeForContext(context)) || kzt_lazy_prebind_scope_retire(scope, identity) != 0) { return -1; } @@ -1364,7 +2485,6 @@ void kzt_production_lazy_prebind_refresh( object->dynamic_view_status != KZT_GUEST_FIELD_OK || object->dynamic_view.status != KZT_GUEST_DYNAMIC_COMPLETE || !object->lazy_resolver.valid || - !object->lazy_resolver.registry_owned_head || !object->lazy_resolver.object_head || object->state == KZT_GUEST_OBJECT_UNLOADING || object->state == KZT_GUEST_OBJECT_DEAD || @@ -1419,8 +2539,8 @@ static int production_lazy_direct_acquire_provider( kzt_lazy_direct_production_state_t *state = opaque; if (!state || !input || !provider || !state->provider_handle_owned || - !state->provider_handle.bindings || !state->provider_handle.entry || - state->provider_handle.library != state->resolved_provider || + state->evidence.retained_provider_handle != &state->provider_handle || + !production_exact_provider_handle_matches(&state->evidence) || state->provider_key.link_map_addr != input->provider.link_map_addr || state->provider_key.generation != input->provider.generation) { return -1; @@ -1487,6 +2607,7 @@ static int production_lazy_direct_find_bridge( state->wrapper_provider.match.resolved_bridge_target = record->bridge_target; state->wrapper_provider.match.resolved_bridge_exact = 1; + state->evidence.wrapper_provider = state->wrapper_provider; state->wrapper_probe = (kzt_wrapper_probe_result_t) { .wrapper_match = input->version_evidence == KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED ? @@ -1501,7 +2622,12 @@ static int production_lazy_direct_find_bridge( .target = record->bridge_target, .version_evidence = record->version_evidence, .version = record->version[0] ? record->version : NULL, + .transient_safe = record->bridge_custom_wrapper, }; + if (!production_custom_dlsym_boundary_proven( + &state->evidence, input->symbol)) { + return -1; + } if (state->timing_enabled) { state->timing->bridge_discover_done = production_lazy_direct_timing_now(); @@ -1511,14 +2637,25 @@ static int production_lazy_direct_find_bridge( return 0; } memset(&state->wrapper_provider, 0, sizeof(state->wrapper_provider)); - status = - kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( - state->context, &state->provider_handle, input->symbol, - input->version_evidence, input->version, - &state->wrapper_provider); + if (production_symbol_uses_guarded_xcb_bridge(input->symbol)) { + status = + kzt_rela_runtime_wrapper_provider_discover_guarded_retained_with_version_evidence( + state->context, &state->provider_handle, input->symbol, + input->version_evidence, input->version, + state->preemption_proof.selected_provider_address, + KZT_BRIDGE_GUARD_XCB_CONNECTION, + &state->wrapper_provider); + } else { + status = + kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( + state->context, &state->provider_handle, input->symbol, + input->version_evidence, input->version, + &state->wrapper_provider); + } if (status <= 0) { return -1; } + state->evidence.wrapper_provider = state->wrapper_provider; if (state->timing_enabled) { state->timing->bridge_discover_done = production_lazy_direct_timing_now(); @@ -1539,10 +2676,15 @@ static int production_lazy_direct_find_bridge( !state->wrapper_probe.bridge_target) { return -1; } + if (!production_custom_dlsym_boundary_proven( + &state->evidence, input->symbol)) { + return -1; + } *bridge = (kzt_lazy_direct_route_bridge_t) { .target = state->wrapper_probe.bridge_target, .version_evidence = state->wrapper_probe.wrapper_version_evidence, .version = state->wrapper_probe.wrapper_symbol_version, + .transient_safe = state->wrapper_provider.match.custom_wrapper, }; if (state->timing_enabled) { state->timing->bridge_done = production_lazy_direct_timing_now(); @@ -1558,11 +2700,21 @@ static int production_lazy_direct_acquire_decision_lease( kzt_lazy_direct_production_state_t *state = opaque; if (!state || !input || !provider || !lease || - provider->handle != &state->provider_handle || + provider->handle != &state->provider_handle) { + return -1; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_before_patch_decision_lease_acquire(); +#endif + if (!state->decision_lease.active && kzt_guest_registry_patch_decision_lease_acquire( &state->source_lease, &state->decision_lease) != 0) { return -1; } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_after_patch_decision_lease_acquire(); +#endif + state->evidence.held_decision_lease = &state->decision_lease; lease->handle = &state->decision_lease; lease->active = state->decision_lease.active; if (state->timing_enabled) { @@ -1577,8 +2729,12 @@ static void production_lazy_direct_release_decision_lease( kzt_lazy_direct_production_state_t *state = opaque; if (state && state->decision_lease.active) { +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_before_patch_decision_lease_release(); +#endif kzt_guest_registry_patch_decision_lease_release( &state->decision_lease); + state->evidence.held_decision_lease = NULL; } if (lease) { memset(lease, 0, sizeof(*lease)); @@ -1598,8 +2754,15 @@ static int production_lazy_direct_validate_final( .read_memory = production_read_guest_memory, }; kzt_guest_symbol_scope_result_t revalidated; + uintptr_t slot_value = 0; int valid = 0; + if (state) { + state->guard_input = input; + state->guard_provider = provider; + state->guard_bridge = bridge; + state->guard_lease = lease; + } if (!state || !input || !provider || !bridge || !lease || !lease->active || lease->handle != &state->decision_lease || !state->decision_lease.active || @@ -1612,10 +2775,11 @@ static int production_lazy_direct_validate_final( !kzt_lazy_prebind_scope_lease_valid(&state->prebind_lease)) || !state->provider_handle_owned || provider->handle != &state->provider_handle || - state->provider_handle.library != state->resolved_provider || - state->loader_quiescence_lease.bindings != + state->evidence.retained_provider_handle != &state->provider_handle || + !production_exact_provider_handle_matches(&state->evidence) || + state->evidence.loader_quiescence_lease.bindings != state->provider_handle.bindings || - !state->loader_quiescence_lease.cookie || + !state->evidence.loader_quiescence_lease.cookie || state->wrapper_provider.match.retained_provider_handle != &state->provider_handle || bridge->target != state->wrapper_probe.bridge_target || @@ -1634,16 +2798,36 @@ static int production_lazy_direct_validate_final( kzt_guest_registry_dynamic_view_matches( state->registry, input->source.link_map_addr, input->source.generation, - input->source_dynamic_view) != 0 || - kzt_guest_symbol_scope_revalidate( - &state->preemption_proof, &state->symbol_scope_request, - &reader_ops, &revalidated) != - KZT_GUEST_SYMBOL_SCOPE_SAFE) { + input->source_dynamic_view) != 0) { + goto out; + } + if (state->exact_symbol_provider) { + const kzt_patch_object_ref_t *owner = + &state->evidence.base_enrich_result.owner_resolution.current_owner; + + if (!kzt_loader_lifecycle_runtime_healthy(state->context) || + (state->evidence.wrapper_alias_borrowed + ? (!kzt_guest_library_wrapper_alias_symbol_allowed( + input->symbol) || + !production_wrapper_alias_provider_matches( + &state->evidence, owner)) + : (state->provider_key.link_map_addr != + owner->link_map_addr || + state->provider_key.generation != owner->generation)) || + !production_exact_owner_symbol_matches( + &state->evidence, owner, + state->preemption_proof.selected_provider_address, + input->symbol, input->version_evidence, input->version)) { + goto out; + } + } else if (kzt_guest_symbol_scope_revalidate( + &state->preemption_proof, &state->symbol_scope_request, + &reader_ops, &revalidated) != + KZT_GUEST_SYMBOL_SCOPE_SAFE) { goto out; } - valid = __atomic_load_n((uintptr_t *)input->slot_addr, - __ATOMIC_ACQUIRE) == - input->expected_current_slot; + valid = production_slot_load(input->slot_addr, &slot_value, state) == 0 && + slot_value == input->expected_current_slot; out: if (state && state->timing_enabled) { state->timing->final_done = production_lazy_direct_timing_now(); @@ -1651,55 +2835,159 @@ static int production_lazy_direct_validate_final( return valid; } +static int production_lazy_direct_guard_validate( + const kzt_patch_decision_t *decision, void *opaque) +{ + kzt_lazy_direct_production_state_t *state = opaque; + + if (!state || !decision || !state->guard_input || + !state->guard_provider || !state->guard_bridge || + !state->guard_lease || + decision->source.link_map_addr != + state->guard_input->source.link_map_addr || + decision->source.generation != + state->guard_input->source.generation || + decision->slot_addr != state->guard_input->slot_addr || + decision->slot_current_value != state->guard_expected || + decision->bridge_target != state->guard_replacement || + !decision->symbol_name || !state->guard_input->symbol || + strcmp(decision->symbol_name, state->guard_input->symbol) != 0) { + return -1; + } + return production_lazy_direct_validate_final( + state->guard_input, state->guard_provider, + state->guard_bridge, state->guard_lease, state) > 0 ? 0 : -1; +} + +static int production_lazy_direct_guard_write(uintptr_t slot_addr, + uintptr_t value, + void *opaque) +{ + kzt_lazy_direct_production_state_t *state = opaque; + uintptr_t expected; + int exchanged; + + if (!state || !slot_addr || !value) { + return -1; + } + expected = value == state->guard_replacement ? state->guard_expected : + state->guard_replacement; + if (value == state->guard_replacement && + state->wrapper_provider.match.custom_wrapper && + strcmp(state->evidence.runtime_candidate.symbol_name, + "dlerror") == 0 && + kzt_guest_dl_api_publish_dlerror_entry( + state->context->dlprivate, + state->evidence.runtime_candidate.symbol_name, + state->preemption_proof.selected_provider_address, + state->wrapper_provider.match.custom_wrapper) != 0) { + return -1; + } + exchanged = __atomic_compare_exchange_n( + (uintptr_t *)slot_addr, &expected, value, 0, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE); + state->guard_cas_mismatch = !exchanged; +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_after_slot_cas(exchanged); +#endif + return exchanged ? 0 : -1; +} + static kzt_lazy_direct_route_cas_status_t production_lazy_direct_cas_slot( uintptr_t slot_addr, uintptr_t expected, uintptr_t replacement, const kzt_lazy_direct_route_lease_t *lease, void *opaque) { kzt_lazy_direct_production_state_t *state = opaque; - uintptr_t observed = expected; - int exchanged; + kzt_patch_decision_t decision = { 0 }; + kzt_patch_spike_record_t record; + kzt_patch_spike_slot_ops_t slot_ops = { + .read_slot = production_slot_load, + .write_slot = production_lazy_direct_guard_write, + .begin_write = production_slot_begin_write, + .end_write = production_slot_end_write, + .validate_generation = production_lazy_direct_guard_validate, + .opaque = state, + }; if (!state || !lease || !lease->active || lease->handle != &state->decision_lease || - !state->decision_lease.active || !slot_addr || !replacement) { + !state->decision_lease.active || !state->guard_input || + !state->guard_provider || !state->guard_bridge || + !slot_addr || !replacement) { return KZT_LAZY_DIRECT_ROUTE_CAS_ERROR; } - if (kzt_patch_spike_guard_circuit_open( - KztPatchSpikeGuardForContext(state->context))) { - return KZT_LAZY_DIRECT_ROUTE_CAS_ERROR; - } - if (state->wrapper_provider.match.custom_wrapper && - strcmp(state->evidence.runtime_candidate.symbol_name, - "dlerror") == 0 && - kzt_guest_dl_api_publish_dlerror_entry( - state->context->dlprivate, - state->evidence.runtime_candidate.symbol_name, - state->preemption_proof.selected_provider_address, - state->wrapper_provider.match.custom_wrapper) != 0) { + state->guard_lease = lease; + state->guard_expected = expected; + state->guard_replacement = replacement; + state->guard_cas_mismatch = 0; + decision = (kzt_patch_decision_t) { + .kind = KZT_PATCH_DECISION_APPROVED, + .reason = KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE, + .allow_native_bridge = 1, + .source = { + .known = 1, + .link_map_addr = state->guard_input->source.link_map_addr, + .generation = state->guard_input->source.generation, + }, + .dynamic_view_generation = + state->guard_input->source_dynamic_view_generation, + .dynamic_view_available = 1, + .table_kind = KZT_PATCH_TABLE_PLT_RELA, + .reloc_type = KZT_PATCH_RELOCATION_JUMP_SLOT, + .slot_addr = slot_addr, + .slot_current_value_present = 1, + .slot_current_value = expected, + .symbol_name = state->guard_input->symbol, + .version_evidence = state->guard_input->version_evidence, + .version = state->guard_input->version, + .current_owner = state->evidence.exact_provider_owner, + .owner_match = KZT_PATCH_OWNER_MATCH, + .wrapper_match = state->wrapper_probe.wrapper_match, + .wrapper_name = state->wrapper_provider.entry.wrapper_name ? + state->wrapper_provider.entry.wrapper_name : + state->resolved_provider->name, + .wrapper_version_evidence = + state->wrapper_probe.wrapper_version_evidence, + .wrapper_symbol_version = + state->wrapper_probe.wrapper_symbol_version, + .bridge_target = replacement, + }; + if (kzt_patch_spike_writer_try_apply_with_slot_ops( + KztPatchSpikeGuardForContext(state->context), &decision, + &slot_ops, &record) != 0) { return KZT_LAZY_DIRECT_ROUTE_CAS_ERROR; } - exchanged = __atomic_compare_exchange_n( - (uintptr_t *)slot_addr, &observed, replacement, 0, - __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE); if (state->timing_enabled) { state->timing->cas_done = production_lazy_direct_timing_now(); } -#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST - kzt_jump_slot_production_test_after_slot_cas(exchanged); -#endif - return exchanged ? KZT_LAZY_DIRECT_ROUTE_CAS_APPLIED : - KZT_LAZY_DIRECT_ROUTE_CAS_MISMATCH; + if (record.result == KZT_PATCH_SPIKE_RESULT_APPLIED) { + return KZT_LAZY_DIRECT_ROUTE_CAS_APPLIED; + } + if (record.result == KZT_PATCH_SPIKE_RESULT_BUDGET_EXHAUSTED) { + return KZT_LAZY_DIRECT_ROUTE_CAS_BUDGET_EXHAUSTED; + } + if (record.result == KZT_PATCH_SPIKE_RESULT_ROLLED_BACK) { + return KZT_LAZY_DIRECT_ROUTE_CAS_ROLLED_BACK; + } + if (record.result == KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE) { + return KZT_LAZY_DIRECT_ROUTE_CAS_UNRECOVERABLE; + } + return state->guard_cas_mismatch ? KZT_LAZY_DIRECT_ROUTE_CAS_MISMATCH : + KZT_LAZY_DIRECT_ROUTE_CAS_ERROR; } static int production_enrich_base( kzt_rela_immediate_candidate_request_t *request, void *opaque) { kzt_production_jump_slot_state_t *state = opaque; + kzt_patch_object_ref_t address_owner = { 0 }; + int exact_symbol_scan_required; + int status; state->failure_stage = "BASE_EVIDENCE"; - int status = production_enrich(request, NULL, &state->base_enrich_result, - state); + status = production_enrich(request, NULL, &state->base_enrich_result, + state); if (status == 0 && production_request_is_main_namespace(state, request) != 0) { @@ -1725,9 +3013,87 @@ static int production_enrich_base( state->held_source_lease = &state->source_lease; } } + if (status == 0 && (!state->held_source_lease || + !state->held_source_lease->active)) { + status = -1; + } + if (status == 0 && !state->held_decision_lease) { +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_before_patch_decision_lease_acquire(); +#endif + status = kzt_guest_registry_patch_decision_lease_acquire( + state->held_source_lease, &state->decision_lease); + if (status == 0) { + state->held_decision_lease = &state->decision_lease; +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_after_patch_decision_lease_acquire(); +#endif + } + } + if (status == 0 && + kzt_guest_library_loader_quiescence_try_acquire( + KztGuestLibraryBindingsForContext(state->context), + &state->loader_quiescence_lease) != 0) { + status = -1; + } + if (status == 0) { + kzt_guest_dynamic_view_t current_view; + kzt_guest_field_status_t current_status; + unsigned long current_generation = 0; + + if (kzt_guest_registry_find_dynamic_view( + KztGuestRegistryForContext(state->context), + request->source.link_map_addr, ¤t_view, + ¤t_status, ¤t_generation) != 0 || + current_status != KZT_GUEST_FIELD_OK || + current_generation != request->dynamic_view_generation || + current_view.dynamic_addr != request->dynamic_addr || + current_view.load_bias != request->load_bias) { + status = -1; + } + } if (status == 0) { status = production_collect_runtime_candidate(state, request); } + exact_symbol_scan_required = + status == 0 && + (state->context->kzt_guest_scope_layout == + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED || + (state->lazy_completion && + (request->owner_match != KZT_PATCH_OWNER_MATCH || + !request->current_owner.known))); + if (exact_symbol_scan_required) { + address_owner = request->current_owner; + if ((state->lazy_completion && + request->slot_current_value != + request->expected_guest_target) || + production_resolve_exact_symbol_owner( + state, request->slot_current_value, request->symbol_name, + request->version_evidence, request->version, + NULL, + &state->base_enrich_result.owner_resolution, NULL) != 0) { + status = -1; + } else { + request->current_owner = + state->base_enrich_result.owner_resolution.current_owner; + request->owner_match = + state->base_enrich_result.owner_resolution.owner_match; + state->base_enrich_result.owner_present = + request->current_owner.known; + if (address_owner.known && + (address_owner.link_map_addr != + request->current_owner.link_map_addr || + address_owner.generation != + request->current_owner.generation)) { + status = -1; + } + } + } + if (status == 0 && + (request->owner_match != KZT_PATCH_OWNER_MATCH || + !request->current_owner.known)) { + status = -1; + } if (status == 0) { state->last_request = *request; state->failure_stage = "EXACT_LIBRARY_BINDING"; @@ -1764,17 +3130,35 @@ static int production_acquire_exact( .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, }; if (KztGuestLibraryLookupForContext(state->context, &key, handle) != 0) { - goto out; - } - if (!handle->library || + if (production_acquire_wrapper_alias_provider( + state, owner, handle) != 0) { + goto out; + } + state->wrapper_alias_borrowed = 1; + key = state->alias_proof.provider_key; + } + if ((state->wrapper_alias_borrowed && + (!state->alias_proof.valid || + !kzt_guest_library_wrapper_alias_symbol_allowed( + state->last_request.symbol_name))) || + !handle->library || (resolved_provider && handle->library != resolved_provider)) { kzt_guest_library_handle_release(handle); goto out; } state->resolved_provider = handle->library; state->retained_provider_handle = handle; + state->exact_provider_key = key; + state->exact_provider_bindings = handle->bindings; + state->exact_provider_entry = handle->entry; + state->exact_provider_library = handle->library; state->exact_provider_owner = *owner; state->owner_namespace_id = match.namespace_id; + if (!production_exact_provider_handle_matches(state)) { + state->retained_provider_handle = NULL; + kzt_guest_library_handle_release(handle); + goto out; + } result = 0; state->failure_stage = "BRIDGE_EVIDENCE"; out: @@ -1817,17 +3201,12 @@ static int production_acquire_and_validate_bridge_evidence( if (!state || !request || !state->held_source_lease) { return -1; } -#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST - kzt_jump_slot_production_test_before_patch_decision_lease_acquire(); -#endif - if (kzt_guest_registry_patch_decision_lease_acquire( - state->held_source_lease, &state->decision_lease) != 0) { + if (!state->held_decision_lease || + !state->held_decision_lease->active || + !state->loader_quiescence_lease.bindings || + !state->loader_quiescence_lease.cookie) { return -1; } - state->held_decision_lease = &state->decision_lease; -#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST - kzt_jump_slot_production_test_after_patch_decision_lease_acquire(); -#endif prepared_value = state->lazy_completion ? state->resolved_target : request->slot_current_value; if (!prepared_value || @@ -1843,24 +3222,52 @@ static int production_acquire_and_validate_bridge_evidence( } request->slot_current_value_present = 1; request->slot_current_value = fresh_value; - kzt_owner_resolver_init(&owner_resolution); - if (kzt_owner_resolver_resolve_current( - KztGuestRegistryForContext(state->context), fresh_value, - request->expected_guest_target, &owner_resolution) != 0 || - owner_resolution.status != KZT_OWNER_RESOLVER_RESOLVED || - owner_resolution.owner_match != KZT_PATCH_OWNER_MATCH) { + if (state->exact_owner_without_map_range) { + owner_resolution = state->base_enrich_result.owner_resolution; + if (fresh_value != request->expected_guest_target || + owner_resolution.status != KZT_OWNER_RESOLVER_RESOLVED || + owner_resolution.owner_match != KZT_PATCH_OWNER_MATCH || + !owner_resolution.current_owner.known) { + production_release_decision_lease(state); + return -1; + } + } else if (production_resolve_current_owner( + state->context, fresh_value, + request->expected_guest_target, request->symbol_name, + request->version_evidence, request->version, + &owner_resolution) != 0) { production_release_decision_lease(state); return -1; } request->current_owner = owner_resolution.current_owner; request->owner_match = owner_resolution.owner_match; - if (kzt_guest_library_loader_quiescence_try_acquire( - KztGuestLibraryBindingsForContext(state->context), - &state->loader_quiescence_lease) != 0 || + if (!state->runtime_view_valid) { + production_release_decision_lease(state); + return -1; + } + if (state->context->kzt_guest_scope_layout == + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED) { + if (!state->owner_source_lease.active && + kzt_guest_registry_source_lease_acquire( + KztGuestRegistryForContext(state->context), + owner_resolution.current_owner.link_map_addr, + owner_resolution.current_owner.generation, 0, + &state->owner_source_lease) != 0) { + production_release_decision_lease(state); + return -1; + } + if (!production_exact_owner_symbol_matches( + state, &owner_resolution.current_owner, fresh_value, + request->symbol_name, request->version_evidence, + request->version)) { + production_release_decision_lease(state); + return -1; + } + state->exact_owner_symbol_proof = 1; + } else if ( kzt_guest_registry_context_get_main_namespace_head( &state->context->kzt_guest_registry_context, &namespace_head) != 0 || - !state->runtime_view_valid || production_symbol_scope_request( state->context, state->head, request->source.generation, namespace_head, &state->runtime_view, &reader_ops, @@ -1882,6 +3289,10 @@ static int production_acquire_and_validate_bridge_evidence( decision.dynamic_view_generation = request->dynamic_view_generation; decision.slot_addr = request->slot_addr; decision.slot_current_value = request->slot_current_value; + decision.symbol_index = request->symbol_index; + decision.symbol_name = request->symbol_name; + decision.version_evidence = request->version_evidence; + decision.version = request->version; decision.current_owner = request->current_owner; decision.owner_match = request->owner_match; if (production_prevalidate_write_evidence(&decision, state) != 0) { @@ -1913,16 +3324,31 @@ static int production_enrich_bridge( int status; int provider_prepared; + if (!request || + kzt_patch_symbol_must_stay_guest(request->symbol_name)) { + return -1; + } if (production_acquire_and_validate_bridge_evidence(state, request) != 0) { return -1; } state->failure_stage = "BRIDGE_EVIDENCE"; memset(&state->wrapper_provider, 0, sizeof(state->wrapper_provider)); - status = kzt_rela_runtime_wrapper_provider_discover_with_version_evidence( - state->context, held_provider, request->symbol_name, - request->version_evidence, request->version, - &state->wrapper_provider); + if (production_symbol_uses_guarded_xcb_bridge(request->symbol_name)) { + status = + kzt_rela_runtime_wrapper_provider_discover_guarded_with_version_evidence( + state->context, held_provider, request->symbol_name, + request->version_evidence, request->version, + request->slot_current_value, + KZT_BRIDGE_GUARD_XCB_CONNECTION, + &state->wrapper_provider); + } else { + status = + kzt_rela_runtime_wrapper_provider_discover_with_version_evidence( + state->context, held_provider, request->symbol_name, + request->version_evidence, request->version, + &state->wrapper_provider); + } provider_prepared = status > 0; if (provider_prepared && kzt_rela_runtime_wrapper_provider_bind_retained_handle( @@ -1942,6 +3368,11 @@ static int production_enrich_bridge( return -1; } state->last_request = *request; + if (!production_custom_dlsym_boundary_proven( + state, request->symbol_name)) { + production_release_decision_lease(state); + return -1; + } state->failure_stage = "SOURCE_IDENTITY"; production_shadow_runtime_candidate( state, request, @@ -2008,6 +3439,10 @@ static kzt_jump_slot_route_writer_status_t production_try_writer( KZT_PATCH_SPIKE_ACTION_PRESERVE_GUEST) { return KZT_JUMP_SLOT_ROUTE_WRITER_PRESERVE; } + if (state->writer_result.record.result == + KZT_PATCH_SPIKE_RESULT_APPLIED) { + return KZT_JUMP_SLOT_ROUTE_WRITER_APPLIED; + } if (!state->writer_result.writer_called) { return KZT_JUMP_SLOT_ROUTE_WRITER_DECLINED; } @@ -2171,12 +3606,16 @@ static int production_jump_slot_route( if (status != 0) { kzt_guest_library_loader_quiescence_release( &state.loader_quiescence_lease); + kzt_guest_registry_source_lease_release( + &state.owner_source_lease); kzt_guest_registry_source_lease_release(&state.source_lease); return -1; } production_emit_diagnostic(&state, route_result); kzt_guest_library_loader_quiescence_release( &state.loader_quiescence_lease); + kzt_guest_registry_source_lease_release( + &state.owner_source_lease); kzt_guest_registry_source_lease_release(&state.source_lease); printf_log(LOG_DEBUG, @@ -2258,6 +3697,9 @@ int kzt_production_lazy_direct_route( unsigned long cached_view_generation; int quiescence_status; int timing_enabled; + int scope_supported; + int loader_route_family; + int loader_write_enabled; if (!result) { return -1; @@ -2296,8 +3738,19 @@ int kzt_production_lazy_direct_route( KZT_LAZY_DIRECT_ROUTE_REASON_GUEST_OWNED_SYMBOL; return 0; } + loader_route_family = + kzt_patch_symbol_is_loader_route_family(symbol_name); + loader_write_enabled = loader_route_family && + KztPatchSpikeGuardForContext(context) && + KztPatchSpikeGuardForContext(context)->config.write_enabled; + if (!kzt_patch_spike_guard_should_plan( + KztPatchSpikeGuardForContext(context))) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_DISABLED; + return 0; + } memset(&state, 0, sizeof(state)); + state.preemption_proof.status = KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED; timing_enabled = unlikely(option_kzt_lazy_diagnostics); if (timing_enabled) { timing.start = production_lazy_direct_timing_now(); @@ -2307,7 +3760,16 @@ int kzt_production_lazy_direct_route( state.timing = &timing; state.timing_enabled = timing_enabled; state.evidence.context = context; + state.evidence.head = head; state.evidence.slot_current_value_is_unresolved_stub = 1; + scope_supported = context->kzt_guest_scope_layout != + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; + if (!scope_supported && + head->DynSym[symbol_index].st_shndx != SHN_UNDEF) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_UNSUPPORTED_SYMBOL_BINDING; + return 0; + } if (!state.registry || kzt_guest_registry_find_lazy_source( state.registry, head->self_link_map, &source) != 0 || @@ -2318,17 +3780,31 @@ int kzt_production_lazy_direct_route( result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_SOURCE_REJECTED; return 0; } + state.evidence.held_source_lease = &state.source_lease; if (timing_enabled) { timing.source = production_lazy_direct_timing_now(); } - if (kzt_guest_registry_context_get_main_namespace_head( + if (scope_supported && + kzt_guest_registry_context_get_main_namespace_head( &context->kzt_guest_registry_context, &namespace_head) != 0) { result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_PREEMPTION_UNPROVEN; goto done; } state.prebind_scope = KztLazyPrebindScopeForContext(context); - if (production_lazy_prebind_record_key( + if (kzt_patch_symbol_requires_dlerror_prebind(symbol_name) && + !kzt_lazy_prebind_scope_has_native_dlerror( + state.prebind_scope, + &(const kzt_lazy_prebind_identity_t) { + .link_map_addr = head->self_link_map, + .generation = source.generation, + .namespace_id = source.namespace_id, + })) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_DLERROR_PREBIND_REQUIRED; + goto done; + } + if (scope_supported && production_lazy_prebind_record_key( &prebind_key, head->self_link_map, source.generation, entry_index, (uintptr_t)slot, slot_current_value, symbol_name, version_evidence, version) == 0 && @@ -2336,7 +3812,9 @@ int kzt_production_lazy_direct_route( state.prebind_scope, &prebind_key, &state.prebind_lease) == 0) { cached_view_status = KZT_GUEST_FIELD_NOT_PARSED; cached_view_generation = 0; - if (kzt_guest_registry_find_dynamic_view( + if (kzt_lazy_prebind_scope_lease_published( + &state.prebind_lease) && + kzt_guest_registry_find_dynamic_view( state.registry, head->self_link_map, &cached_view, &cached_view_status, &cached_view_generation) == 0 && cached_view_status == KZT_GUEST_FIELD_OK && @@ -2386,14 +3864,31 @@ int kzt_production_lazy_direct_route( goto done; } } + state.evidence.last_request.symbol_index = symbol_index; + state.evidence.last_request.source = (kzt_patch_object_ref_t) { + .known = 1, + .link_map_addr = head->self_link_map, + .generation = source.generation, + .map_start = (uintptr_t)head->memory, + .map_end = (uintptr_t)head->memory + head->memsz, + .soname = head->name, + .path = head->path, + }; + state.evidence.last_request.symbol_name = + state.evidence.runtime_candidate.symbol_name; + state.evidence.last_request.version_evidence = + state.evidence.runtime_candidate.version_evidence; + state.evidence.last_request.version = + state.evidence.runtime_candidate.version; + state.evidence.initial_request = state.evidence.last_request; if (!state.evidence.runtime_view_valid || - production_symbol_scope_request( + (scope_supported && production_symbol_scope_request( context, head, source.generation, namespace_head, &state.evidence.runtime_view, &reader_ops, symbol_index, state.evidence.runtime_candidate.symbol_name, state.evidence.runtime_candidate.version_evidence, state.evidence.runtime_candidate.version, - &state.symbol_scope_request) != 0) { + &state.symbol_scope_request) != 0)) { result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_PREEMPTION_UNPROVEN; goto done; } @@ -2402,12 +3897,12 @@ int kzt_production_lazy_direct_route( } quiescence_status = kzt_guest_library_loader_quiescence_try_acquire( KztGuestLibraryBindingsForContext(context), - &state.loader_quiescence_lease); + &state.evidence.loader_quiescence_lease); if (timing_enabled) { timing.quiescence = production_lazy_direct_timing_now(); } if (quiescence_status != 0) { - if (!state.prebind_hit) { + if (scope_supported && !state.prebind_hit) { (void)kzt_guest_symbol_scope_discover( &state.symbol_scope_request, &reader_ops, &state.preemption_proof); @@ -2420,7 +3915,54 @@ int kzt_production_lazy_direct_route( goto preemption_diagnostic; } - if (!state.prebind_hit) { + if (!scope_supported) { + uintptr_t provider_address = 0; + kzt_owner_resolution_t *owner_resolution = + &state.evidence.base_enrich_result.owner_resolution; + + if (!kzt_guest_library_wrapper_alias_symbol_allowed( + state.evidence.runtime_candidate.symbol_name) || + !production_dynamic_view_needs_library( + &state.evidence.runtime_view, &reader_ops, + "libdl.so.2")) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_PREEMPTION_UNPROVEN; + goto preemption_diagnostic; + } + if (kzt_guest_registry_patch_decision_lease_acquire( + &state.source_lease, &state.decision_lease) != 0) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_PREEMPTION_UNPROVEN; + goto preemption_diagnostic; + } + state.evidence.held_decision_lease = &state.decision_lease; + if (production_resolve_exact_symbol_owner( + &state.evidence, 0, + state.evidence.runtime_candidate.symbol_name, + state.evidence.runtime_candidate.version_evidence, + state.evidence.runtime_candidate.version, + "libdl.so.2", + owner_resolution, &provider_address) != 0 || + !provider_address) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_PREEMPTION_UNPROVEN; + goto preemption_diagnostic; + } + state.exact_symbol_provider = 1; + state.preemption_proof = (kzt_guest_symbol_scope_result_t) { + .status = KZT_GUEST_SYMBOL_SCOPE_SAFE, + .reason = KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER, + .candidate_count = 1, + .scope_complete = 1, + .lookup_order_known = 1, + .selected_provider_link_map = + owner_resolution->current_owner.link_map_addr, + .selected_provider_address = provider_address, + .selected_provider_binding = STB_GLOBAL, + .selected_provider_type = STT_FUNC, + .selected_provider_visibility = STV_DEFAULT, + }; + } else if (!state.prebind_hit) { if (kzt_guest_symbol_scope_discover( &state.symbol_scope_request, &reader_ops, &state.preemption_proof) != KZT_GUEST_SYMBOL_SCOPE_SAFE) { @@ -2479,10 +4021,26 @@ int kzt_production_lazy_direct_route( .namespace_id = provider_match.namespace_id, .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, }; - if (kzt_guest_library_access_lookup( - &context->kzt_guest_library_access, &state.provider_key, - &state.provider_handle) != 0 || - !state.provider_handle.library || + if (state.exact_symbol_provider) { + const kzt_patch_object_ref_t *owner = + &state.evidence.base_enrich_result.owner_resolution.current_owner; + int acquire_status; + + acquire_status = production_acquire_exact( + owner, NULL, &state.provider_handle, &state.evidence); + if (acquire_status != 0) { + result->reason = + KZT_LAZY_DIRECT_ROUTE_REASON_PROVIDER_UNAVAILABLE; + goto done; + } + state.provider_key = state.evidence.exact_provider_key; + } else if (kzt_guest_library_access_lookup( + &context->kzt_guest_library_access, &state.provider_key, + &state.provider_handle) != 0) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_PROVIDER_UNAVAILABLE; + goto done; + } + if (!state.provider_handle.library || state.provider_handle.object_type != KZT_GUEST_LIBRARY_OBJECT_WRAPPED) { kzt_guest_library_handle_release(&state.provider_handle); @@ -2493,6 +4051,24 @@ int kzt_production_lazy_direct_route( state.provider_handle_owned = 1; state.resolved_provider = state.provider_handle.library; state.evidence.resolved_provider = state.resolved_provider; + if (!state.exact_symbol_provider) { + state.evidence.retained_provider_handle = &state.provider_handle; + state.evidence.exact_provider_key = state.provider_key; + state.evidence.exact_provider_bindings = + state.provider_handle.bindings; + state.evidence.exact_provider_entry = state.provider_handle.entry; + state.evidence.exact_provider_library = state.provider_handle.library; + state.evidence.exact_provider_owner = (kzt_patch_object_ref_t) { + .known = 1, + .link_map_addr = state.provider_key.link_map_addr, + .generation = state.provider_key.generation, + }; + state.evidence.owner_namespace_id = state.provider_key.namespace_id; + } + if (!production_exact_provider_handle_matches(&state.evidence)) { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_PROVIDER_UNAVAILABLE; + goto done; + } if (timing_enabled) { timing.provider = production_lazy_direct_timing_now(); } @@ -2520,6 +4096,7 @@ int kzt_production_lazy_direct_route( .slot_addr = (uintptr_t)slot, .guest_unresolved_slot = slot_current_value, .expected_current_slot = slot_current_value, + .allow_budget_transient_native = loader_write_enabled, }; ops = (kzt_lazy_direct_route_ops_t) { .validate_source = production_lazy_direct_validate_source, @@ -2541,14 +4118,20 @@ int kzt_production_lazy_direct_route( if (timing_enabled) { timing.route = production_lazy_direct_timing_now(); } - if (result->status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED) { + if (result->status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED || + result->status == KZT_LAZY_DIRECT_ROUTE_NATIVE_TRANSIENT) { uintptr_t slot_after = __atomic_load_n((uintptr_t *)slot, __ATOMIC_ACQUIRE); printf_kzt_registry_diagnostics( "kzt_lazy_direct schema=1 symbol=%s " - "route_status=NATIVE_APPLIED writer_result=APPLIED " + "route_status=%s writer_result=%s " "slot_before=%p slot_after=%p selected_target=%p\n", - input.symbol, (void *)slot_current_value, (void *)slot_after, + input.symbol, + result->status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED ? + "NATIVE_APPLIED" : "NATIVE_TRANSIENT", + result->status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED ? + "APPLIED" : "NOT_WRITTEN", + (void *)slot_current_value, (void *)slot_after, (void *)result->selected_target); } @@ -2556,13 +4139,18 @@ int kzt_production_lazy_direct_route( if (state.decision_lease.active) { kzt_guest_registry_patch_decision_lease_release( &state.decision_lease); + state.evidence.held_decision_lease = NULL; } if (state.provider_handle_owned) { kzt_guest_library_handle_release(&state.provider_handle); } kzt_lazy_prebind_scope_release(&state.prebind_lease); kzt_guest_library_loader_quiescence_release( - &state.loader_quiescence_lease); + &state.evidence.loader_quiescence_lease); + kzt_guest_registry_symbol_candidate_release( + &state.evidence.exact_owner_candidate); + kzt_guest_registry_source_lease_release( + &state.evidence.owner_source_lease); kzt_guest_registry_source_lease_release(&state.source_lease); if (timing_enabled) { timing.done = production_lazy_direct_timing_now(); @@ -2614,310 +4202,4 @@ int kzt_production_lazy_direct_route( return 0; } -static elfheader_t *production_find_source_head( - box64context_t *context, uintptr_t link_map) -{ - int i; - - if (!context || !link_map) { - return NULL; - } - for (i = 0; i < context->elfsize; ++i) { - if (context->elfs[i] && context->elfs[i]->self_link_map == link_map) { - return context->elfs[i]; - } - } - return NULL; -} - -int kzt_production_lazy_route_guest_target_leased( - box64context_t *context, - const kzt_lazy_binding_pending_t *pending, - uintptr_t guest_target, - const kzt_guest_registry_source_lease_t *source_lease, - kzt_lazy_binding_route_result_t *result) -{ - kzt_jump_slot_route_result_t route_result; - elfheader_t *head; - Elf64_Rela *rela; - Elf64_Sym *sym; - const char *symbol_name; - const char *version_name; - kzt_symbol_version_evidence_t version_evidence; - kzt_owner_resolution_t owner_resolution; - kzt_guest_library_binding_key_t provider_key; - kzt_guest_library_handle_t provider_handle; - library_t *resolved_provider = NULL; - unsigned long symbol_index; - int version; - size_t count; - - if (!result) { - return -1; - } - memset(result, 0, sizeof(*result)); - result->status = KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED; - result->selected_target = guest_target; - result->final_value = guest_target; - if (!context || !pending || !pending->armed || !guest_target || - !pending->symbol || - !kzt_symbol_version_evidence_valid(pending->version_evidence, - pending->version)) { - return 0; - } - if (!source_lease || !source_lease->active || - source_lease->registry != KztGuestRegistryForContext(context) || - source_lease->link_map_addr != pending->source_link_map || - source_lease->generation != pending->source_generation || - source_lease->namespace_id != pending->namespace_id || - pending->namespace_kind != KZT_GUEST_LIBRARY_NAMESPACE_MAIN || - pending->namespace_id != 0) { - return 0; - } -#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST - kzt_jump_slot_production_test_before_source_memory_access(); -#endif - head = production_find_source_head(context, pending->source_link_map); - if (!head || !head->jmprel || !head->pltent || !head->DynSym) { - return 0; - } - count = head->pltsz / head->pltent; - if (pending->relocation_index >= count) { - return 0; - } - rela = (Elf64_Rela *)(head->jmprel + head->delta) + - pending->relocation_index; - if (ELF64_R_TYPE(rela->r_info) != R_X86_64_JUMP_SLOT || - rela->r_offset + head->delta != pending->slot_addr) { - return 0; - } - symbol_index = ELF64_R_SYM(rela->r_info); - sym = &head->DynSym[symbol_index]; - symbol_name = SymName(head, sym); - version = head->VerSym ? - ((Elf64_Half *)((uintptr_t)head->VerSym + head->delta))[symbol_index] : - -1; - if (version == -1 || (version & 0x7fff) < 2) { - version_evidence = KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; - version_name = NULL; - } else { - version &= 0x7fff; - version_evidence = KZT_SYMBOL_VERSION_VERSIONED; - version_name = GetSymbolVersion(head, version); - } - if (!symbol_name || strcmp(symbol_name, pending->symbol) != 0 || - !kzt_symbol_version_evidence_matches( - version_evidence, version_name, pending->version_evidence, - pending->version)) { - return 0; - } - - kzt_owner_resolver_init(&owner_resolution); - if (kzt_owner_resolver_resolve_current( - KztGuestRegistryForContext(context), guest_target, guest_target, - &owner_resolution) != 0 || - owner_resolution.status != KZT_OWNER_RESOLVER_RESOLVED || - owner_resolution.owner_match != KZT_PATCH_OWNER_MATCH || - !owner_resolution.current_owner.known) { - return 0; - } - provider_key = (kzt_guest_library_binding_key_t) { - .link_map_addr = owner_resolution.current_owner.link_map_addr, - .generation = owner_resolution.current_owner.generation, - .namespace_id = 0, - .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, - }; - memset(&provider_handle, 0, sizeof(provider_handle)); - if (KztGuestLibraryLookupForContext(context, &provider_key, - &provider_handle) != 0 || - !provider_handle.library) { - kzt_guest_library_handle_release(&provider_handle); - return 0; - } - resolved_provider = provider_handle.library; - /* guest_target is only the guest loader's observed bridge/CAS evidence. - * Bridge enrichment resolves and verifies the provider's native symbol - * independently with dlvsym. */ - if (production_jump_slot_route( - context, resolved_provider, guest_target, head, 1, - pending->relocation_index, rela, - (uint64_t *)pending->slot_addr, guest_target, 0, symbol_index, - symbol_name, version_evidence, version_name, 1, guest_target, - guest_target, 1, - pending->source_link_map, pending->source_generation, - 1, source_lease, - &route_result) != 0) { - kzt_guest_library_handle_release(&provider_handle); - result->status = KZT_LAZY_BINDING_ROUTE_ERROR; - return -1; - } - kzt_guest_library_handle_release(&provider_handle); - result->selected_target = route_result.selected_target; - result->final_value = route_result.final_value; - switch (route_result.status) { - case KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED: - result->status = KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED; - break; - case KZT_JUMP_SLOT_ROUTE_CAS_MISMATCH: - result->status = KZT_LAZY_BINDING_ROUTE_CAS_MISMATCH; - break; - case KZT_JUMP_SLOT_ROUTE_WRITE_ROLLED_BACK: - result->status = KZT_LAZY_BINDING_ROUTE_WRITE_ROLLED_BACK; - break; - case KZT_JUMP_SLOT_ROUTE_UNRECOVERABLE: - result->status = KZT_LAZY_BINDING_ROUTE_UNRECOVERABLE; - break; - case KZT_JUMP_SLOT_ROUTE_GUEST_PRESERVED: - default: - result->status = KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED; - break; - } - return 0; -} - -int kzt_production_lazy_source_lease_acquire( - box64context_t *context, - const kzt_lazy_binding_pending_t *pending, - kzt_guest_registry_source_lease_t *source_lease) -{ - kzt_guest_registry_t *registry; - - if (!context || !pending || !source_lease || !pending->armed || - !pending->source_link_map || !pending->source_generation || - pending->namespace_kind != KZT_GUEST_LIBRARY_NAMESPACE_MAIN || - pending->namespace_id != 0) { - return -1; - } - memset(source_lease, 0, sizeof(*source_lease)); - registry = KztGuestRegistryForContext(context); - if (!registry) { - return -1; - } -#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST - kzt_jump_slot_production_test_before_source_lease_acquire(); -#endif - return kzt_guest_registry_source_lease_acquire( - registry, pending->source_link_map, - pending->source_generation, pending->namespace_id, source_lease); -} - -int kzt_production_lazy_load_slot_with_lease( - box64context_t *context, - const kzt_lazy_binding_pending_t *pending, - uintptr_t slot_addr, - uintptr_t *value, - kzt_guest_registry_source_lease_t *source_lease) -{ - if (!slot_addr || !value || !source_lease || - kzt_production_lazy_source_lease_acquire( - context, pending, source_lease) != 0) { - return -1; - } -#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST - kzt_jump_slot_production_test_before_slot_load(); -#endif - *value = __atomic_load_n((uintptr_t *)slot_addr, __ATOMIC_ACQUIRE); -#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST - kzt_jump_slot_production_test_after_slot_load(value); -#endif - return 0; -} - -int kzt_production_lazy_route_guest_target( - box64context_t *context, - const kzt_lazy_binding_pending_t *pending, - uintptr_t guest_target, - kzt_lazy_binding_route_result_t *result) -{ - kzt_guest_registry_source_lease_t source_lease = { 0 }; - int status; - - if (!result) { - return -1; - } - memset(result, 0, sizeof(*result)); - result->status = KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED; - result->selected_target = guest_target; - result->final_value = guest_target; - if (!context || !pending || !pending->armed || !guest_target || - kzt_production_lazy_source_lease_acquire( - context, pending, &source_lease) != 0) { - return 0; - } - status = kzt_production_lazy_route_guest_target_leased( - context, pending, guest_target, &source_lease, result); - kzt_guest_registry_source_lease_release(&source_lease); - return status; -} - -typedef struct kzt_lazy_completion_production_state { - box64context_t *context; - const kzt_lazy_binding_pending_t *pending; - kzt_guest_registry_source_lease_t source_lease; -} kzt_lazy_completion_production_state_t; - -static int kzt_lazy_production_load_slot(uintptr_t slot_addr, - uintptr_t *value, void *opaque) -{ - kzt_lazy_completion_production_state_t *state = opaque; - - return state ? kzt_production_lazy_load_slot_with_lease( - state->context, state->pending, slot_addr, value, - &state->source_lease) : - -1; -} - -static int kzt_lazy_production_validate( - const kzt_lazy_binding_pending_t *pending, uintptr_t guest_target, - void *opaque) -{ - kzt_lazy_completion_production_state_t *state = opaque; - box64context_t *context = state ? state->context : NULL; - kzt_guest_lazy_resolver_t resolver; - - if (!pending || !context || pending->context_id != (uintptr_t)context || - !guest_target || !state->source_lease.active || - state->source_lease.namespace_id != pending->namespace_id || - kzt_guest_registry_find_lazy_resolver( - KztGuestRegistryForContext(context), pending->source_link_map, - pending->source_generation, pending->namespace_id, - &resolver) != 0) { - return 0; - } - return resolver.guest_link_map == pending->source_link_map && - resolver.guest_resolver == pending->guest_resolver; -} - -static int kzt_lazy_production_route( - const kzt_lazy_binding_pending_t *pending, uintptr_t guest_target, - kzt_lazy_binding_route_result_t *result, void *opaque) -{ - kzt_lazy_completion_production_state_t *state = opaque; - - return state ? kzt_production_lazy_route_guest_target_leased( - state->context, pending, guest_target, - &state->source_lease, result) : - -1; -} - -int kzt_production_lazy_complete( - box64context_t *context, kzt_lazy_binding_pending_t *pending, - kzt_lazy_binding_result_t *result) -{ - kzt_lazy_completion_production_state_t state = { - .context = context, - .pending = pending, - }; - kzt_lazy_binding_ops_t ops = { - .load_slot = kzt_lazy_production_load_slot, - .validate_post_bind = kzt_lazy_production_validate, - .route_guest_target = kzt_lazy_production_route, - .opaque = &state, - }; - int status = kzt_lazy_binding_complete(pending, &ops, result); - - kzt_guest_registry_source_lease_release(&state.source_lease); - return status; -} - #endif diff --git a/target/i386/latx/context/kzt_lazy_binding.c b/target/i386/latx/context/kzt_lazy_binding.c deleted file mode 100644 index d89a011750e..00000000000 --- a/target/i386/latx/context/kzt_lazy_binding.c +++ /dev/null @@ -1,209 +0,0 @@ -#include "kzt_lazy_binding.h" - -#include - -static void lazy_result_init(kzt_lazy_binding_result_t *result) -{ - if (!result) { - return; - } - memset(result, 0, sizeof(*result)); - result->status = KZT_LAZY_BINDING_ERROR; - result->reason = KZT_LAZY_BINDING_REASON_INVALID_REQUEST; -} - -static int lazy_copy_text(char *dst, size_t size, const char *src, - const char **stored) -{ - size_t length; - - if (stored) { - *stored = NULL; - } - if (!dst || !size || !stored) { - return -1; - } - if (!src || !src[0]) { - return 0; - } - length = strlen(src); - if (length >= size) { - return -1; - } - memcpy(dst, src, length + 1); - *stored = dst; - return 0; -} - -static void lazy_consume(kzt_lazy_binding_pending_t *pending, - kzt_lazy_binding_result_t *result) -{ - if (pending) { - memset(pending, 0, sizeof(*pending)); - } - if (result) { - result->pending_armed = 0; - result->pending_cleared = 1; - } -} - -int kzt_lazy_binding_begin( - const kzt_lazy_binding_begin_request_t *request, - kzt_lazy_binding_pending_t *pending, - kzt_lazy_binding_result_t *result) -{ - kzt_lazy_binding_pending_t candidate; - - lazy_result_init(result); - if (!request || !pending || !result) { - return -1; - } - result->selected_target = request->guest_resolver; - result->slot_before = request->unresolved_stub; - result->slot_after = request->unresolved_stub; - if (!request->enabled) { - result->status = KZT_LAZY_BINDING_BYPASS; - result->reason = KZT_LAZY_BINDING_REASON_DISABLED; - return 0; - } - /* Nested resolvers share a context. A pending owned by another context - * cannot complete this request and must not block its fail-open path. */ - if (pending->armed && pending->context_id == request->context_id) { - result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; - result->reason = KZT_LAZY_BINDING_REASON_PENDING_BUSY; - return 0; - } - if (request->namespace_kind != KZT_GUEST_LIBRARY_NAMESPACE_MAIN || - request->namespace_id != 0) { - result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; - result->reason = KZT_LAZY_BINDING_REASON_NON_MAIN_NAMESPACE; - return 0; - } - if (!request->context_id || !request->source_link_map || - !request->source_generation || !request->slot_addr || - !request->unresolved_stub || !request->guest_resolver) { - result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; - result->reason = request->guest_resolver ? - KZT_LAZY_BINDING_REASON_INVALID_REQUEST : - KZT_LAZY_BINDING_REASON_RESOLVER_MISSING; - return 0; - } - - memset(&candidate, 0, sizeof(candidate)); - candidate.context_id = request->context_id; - candidate.source_link_map = request->source_link_map; - candidate.source_generation = request->source_generation; - candidate.namespace_id = request->namespace_id; - candidate.namespace_kind = request->namespace_kind; - candidate.relocation_index = request->relocation_index; - candidate.slot_addr = request->slot_addr; - candidate.unresolved_stub = request->unresolved_stub; - candidate.guest_resolver = request->guest_resolver; - candidate.addend = request->addend; - candidate.version_evidence = request->version_evidence; - if (lazy_copy_text(candidate.symbol_storage, - sizeof(candidate.symbol_storage), request->symbol, - &candidate.symbol) != 0 || - lazy_copy_text(candidate.version_storage, - sizeof(candidate.version_storage), request->version, - &candidate.version) != 0) { - result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; - result->reason = KZT_LAZY_BINDING_REASON_INVALID_REQUEST; - return 0; - } - candidate.armed = 1; - *pending = candidate; - /* Repair the pointers after the structure copy. */ - pending->symbol = candidate.symbol ? pending->symbol_storage : NULL; - pending->version = candidate.version ? pending->version_storage : NULL; - result->status = KZT_LAZY_BINDING_HANDOFF_GUEST; - result->reason = KZT_LAZY_BINDING_REASON_NONE; - result->pending_armed = 1; - return 0; -} - -int kzt_lazy_binding_complete( - kzt_lazy_binding_pending_t *pending, - const kzt_lazy_binding_ops_t *ops, - kzt_lazy_binding_result_t *result) -{ - uintptr_t guest_target = 0; - kzt_lazy_binding_route_result_t route_result; - - lazy_result_init(result); - if (!pending || !ops || !result || !pending->armed || !ops->load_slot) { - return -1; - } - if (ops->load_slot(pending->slot_addr, &guest_target, ops->opaque) != 0) { - result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; - result->reason = KZT_LAZY_BINDING_REASON_SLOT_READ_ERROR; - lazy_consume(pending, result); - return 0; - } - result->slot_before = guest_target; - result->slot_after = guest_target; - result->selected_target = guest_target; - if (guest_target == pending->unresolved_stub) { - result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; - result->reason = KZT_LAZY_BINDING_REASON_SLOT_UNCHANGED; - lazy_consume(pending, result); - return 0; - } - if (!kzt_symbol_version_evidence_valid(pending->version_evidence, - pending->version)) { - result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; - result->reason = KZT_LAZY_BINDING_REASON_MISSING_VERSION; - lazy_consume(pending, result); - return 0; - } - if (!ops->validate_post_bind || - ops->validate_post_bind(pending, guest_target, ops->opaque) <= 0) { - result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; - result->reason = KZT_LAZY_BINDING_REASON_POST_BIND_INVALID; - lazy_consume(pending, result); - return 0; - } - if (!ops->route_guest_target) { - result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; - result->reason = KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE; - lazy_consume(pending, result); - return 0; - } - memset(&route_result, 0, sizeof(route_result)); - if (ops->route_guest_target(pending, guest_target, &route_result, - ops->opaque) != 0 || - route_result.status == KZT_LAZY_BINDING_ROUTE_ERROR) { - result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; - result->reason = KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE; - } else if (route_result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED) { - result->status = KZT_LAZY_BINDING_NATIVE_APPLIED; - result->reason = KZT_LAZY_BINDING_REASON_NATIVE_APPLIED; - } else if (route_result.status == KZT_LAZY_BINDING_ROUTE_CAS_MISMATCH) { - result->status = KZT_LAZY_BINDING_CAS_MISMATCH; - result->reason = KZT_LAZY_BINDING_REASON_CAS_MISMATCH; - } else if (route_result.status == - KZT_LAZY_BINDING_ROUTE_WRITE_ROLLED_BACK) { - result->status = KZT_LAZY_BINDING_WRITE_ROLLED_BACK; - result->reason = KZT_LAZY_BINDING_REASON_WRITE_ROLLED_BACK; - } else if (route_result.status == - KZT_LAZY_BINDING_ROUTE_UNRECOVERABLE) { - result->status = KZT_LAZY_BINDING_UNRECOVERABLE; - result->reason = KZT_LAZY_BINDING_REASON_UNRECOVERABLE; - } else { - result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; - result->reason = KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE; - } - result->selected_target = route_result.selected_target ? - route_result.selected_target : guest_target; - result->slot_after = route_result.final_value ? - route_result.final_value : guest_target; - lazy_consume(pending, result); - return 0; -} - -void kzt_lazy_binding_cancel(kzt_lazy_binding_pending_t *pending) -{ - if (pending) { - memset(pending, 0, sizeof(*pending)); - } -} diff --git a/target/i386/latx/context/kzt_lazy_diagnostics.c b/target/i386/latx/context/kzt_lazy_diagnostics.c deleted file mode 100644 index 8b69e758bb4..00000000000 --- a/target/i386/latx/context/kzt_lazy_diagnostics.c +++ /dev/null @@ -1,255 +0,0 @@ -#include "kzt_lazy_diagnostics.h" - -#include -#include - -#define KZT_LAZY_DIAGNOSTIC_SCHEMA 1UL - -static void kzt_lazy_diagnostic_copy_token(char *dst, size_t dst_size, - const char *src) -{ - size_t i; - - if (!dst || dst_size == 0) { - return; - } - if (!src || !src[0]) { - src = "(none)"; - } - - for (i = 0; i + 1 < dst_size && src[i]; ++i) { - unsigned char value = (unsigned char)src[i]; - - if (value <= ' ' || value == '"' || value == '\\' || value == '=') { - dst[i] = '_'; - } else { - dst[i] = (char)value; - } - } - dst[i] = '\0'; -} - -static const char *kzt_lazy_diagnostic_status_name( - const kzt_lazy_binding_result_t *binding_result) -{ - if (!binding_result) { - return "INVALID_RESULT"; - } - - switch (binding_result->status) { - case KZT_LAZY_BINDING_BYPASS: - return "BYPASS"; - case KZT_LAZY_BINDING_HANDOFF_GUEST: - return "HANDOFF_GUEST"; - case KZT_LAZY_BINDING_WAITING_GUEST_TARGET: - return "WAITING_GUEST_TARGET"; - case KZT_LAZY_BINDING_NATIVE_APPLIED: - return "NATIVE_APPLIED"; - case KZT_LAZY_BINDING_CAS_MISMATCH: - return "CAS_MISMATCH"; - case KZT_LAZY_BINDING_WRITE_ROLLED_BACK: - return "WRITE_ROLLED_BACK"; - case KZT_LAZY_BINDING_UNRECOVERABLE: - return "UNRECOVERABLE"; - case KZT_LAZY_BINDING_ERROR: - return "ERROR"; - case KZT_LAZY_BINDING_GUEST_PRESERVED: - switch (binding_result->reason) { - case KZT_LAZY_BINDING_REASON_SLOT_UNCHANGED: - return "SLOT_UNCHANGED"; - case KZT_LAZY_BINDING_REASON_MISSING_VERSION: - return "MISSING_VERSION"; - default: - return "GUEST_PRESERVED"; - } - } - - return "UNKNOWN"; -} - -static const char *kzt_lazy_diagnostic_reason_name( - kzt_lazy_binding_reason_t reason) -{ - switch (reason) { - case KZT_LAZY_BINDING_REASON_NONE: - return "NONE"; - case KZT_LAZY_BINDING_REASON_DISABLED: - return "DISABLED"; - case KZT_LAZY_BINDING_REASON_INVALID_REQUEST: - return "INVALID_REQUEST"; - case KZT_LAZY_BINDING_REASON_PENDING_OCCUPIED: - return "PENDING_OCCUPIED"; - case KZT_LAZY_BINDING_REASON_GENERATION_CHANGED: - return "GENERATION_CHANGED"; - case KZT_LAZY_BINDING_REASON_NON_MAIN_NAMESPACE: - return "NON_MAIN_NAMESPACE"; - case KZT_LAZY_BINDING_REASON_RESOLVER_MISSING: - return "RESOLVER_MISSING"; - case KZT_LAZY_BINDING_REASON_SLOT_UNCHANGED: - return "SLOT_UNCHANGED"; - case KZT_LAZY_BINDING_REASON_MISSING_VERSION: - return "MISSING_VERSION"; - case KZT_LAZY_BINDING_REASON_POST_BIND_INVALID: - return "POST_BIND_INVALID"; - case KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE: - return "NATIVE_UNAVAILABLE"; - case KZT_LAZY_BINDING_REASON_NATIVE_APPLIED: - return "NATIVE_APPLIED"; - case KZT_LAZY_BINDING_REASON_CAS_MISMATCH: - return "CAS_MISMATCH"; - case KZT_LAZY_BINDING_REASON_SLOT_READ_ERROR: - return "SLOT_READ_ERROR"; - case KZT_LAZY_BINDING_REASON_CAS_ERROR: - return "CAS_ERROR"; - case KZT_LAZY_BINDING_REASON_WRITE_ROLLED_BACK: - return "WRITE_ROLLED_BACK"; - case KZT_LAZY_BINDING_REASON_UNRECOVERABLE: - return "UNRECOVERABLE"; - } - - return "UNKNOWN"; -} - -int kzt_lazy_diagnostic_record_build( - const kzt_lazy_binding_pending_t *pending, - const kzt_lazy_binding_result_t *binding_result, - kzt_lazy_diagnostic_record_t *record) -{ - if (!pending || !binding_result || !record) { - return -1; - } - - memset(record, 0, sizeof(*record)); - record->schema = KZT_LAZY_DIAGNOSTIC_SCHEMA; - kzt_lazy_diagnostic_copy_token(record->symbol, sizeof(record->symbol), - pending->symbol); - kzt_lazy_diagnostic_copy_token( - record->first_execution_route, - sizeof(record->first_execution_route), "guest"); - kzt_lazy_diagnostic_copy_token( - record->completion_route_status, - sizeof(record->completion_route_status), - kzt_lazy_diagnostic_status_name(binding_result)); - record->slot_before = pending->unresolved_stub; - record->slot_after_guest = binding_result->slot_before; - record->selected_second_target = binding_result->slot_after; - kzt_lazy_diagnostic_copy_token(record->reason, sizeof(record->reason), - kzt_lazy_diagnostic_reason_name( - binding_result->reason)); - return 0; -} - -kzt_lazy_diagnostic_format_status_t kzt_lazy_diagnostic_format( - const kzt_lazy_diagnostic_record_t *record, - char *buffer, - size_t buffer_size) -{ - int written; - - if (!record || !buffer || buffer_size == 0) { - return KZT_LAZY_DIAGNOSTIC_FORMAT_ERROR; - } - - written = snprintf( - buffer, buffer_size, - "kzt_lazy_diagnostic schema=%lu symbol=%s first_execution_route=%s " - "completion_route_status=%s slot_before=0x%lx " - "slot_after_guest=0x%lx selected_second_target=0x%lx reason=%s", - record->schema, record->symbol, record->first_execution_route, - record->completion_route_status, - (unsigned long)record->slot_before, - (unsigned long)record->slot_after_guest, - (unsigned long)record->selected_second_target, record->reason); - if (written < 0) { - return KZT_LAZY_DIAGNOSTIC_FORMAT_ERROR; - } - if ((size_t)written >= buffer_size) { - return KZT_LAZY_DIAGNOSTIC_FORMAT_TRUNCATED; - } - - return KZT_LAZY_DIAGNOSTIC_FORMAT_OK; -} - -int kzt_lazy_diagnostics_stderr_sink(const char *line, - size_t line_length, - void *opaque) -{ - FILE *stream = opaque ? (FILE *)opaque : stderr; - - if (!line || !stream) { - return -1; - } - if (fwrite(line, 1, line_length, stream) != line_length) { - return -1; - } - if (fputc('\n', stream) == EOF) { - return -1; - } - return fflush(stream); -} - -int kzt_lazy_diagnostics_emit( - const kzt_lazy_diagnostic_input_t *input, - kzt_lazy_diagnostic_emit_result_t *result) -{ - if (!input || !result) { - return -1; - } - - memset(result, 0, sizeof(*result)); - result->status = KZT_LAZY_DIAGNOSTIC_EMIT_DISABLED; - result->format_status = KZT_LAZY_DIAGNOSTIC_FORMAT_ERROR; - if (!input->enabled) { - return 0; - } - if (kzt_lazy_diagnostic_record_build(input->pending, - input->binding_result, - &result->record) != 0) { - result->status = KZT_LAZY_DIAGNOSTIC_EMIT_FORMAT_FAILED; - return 0; - } - result->record_present = 1; - result->format_status = kzt_lazy_diagnostic_format( - &result->record, input->buffer, input->buffer_size); - if (result->format_status == KZT_LAZY_DIAGNOSTIC_FORMAT_TRUNCATED) { - result->status = KZT_LAZY_DIAGNOSTIC_EMIT_FORMAT_TRUNCATED; - return 0; - } - if (result->format_status != KZT_LAZY_DIAGNOSTIC_FORMAT_OK) { - result->status = KZT_LAZY_DIAGNOSTIC_EMIT_FORMAT_FAILED; - return 0; - } - if (!input->sink) { - result->status = KZT_LAZY_DIAGNOSTIC_EMIT_SINK_FAILED; - result->sink_status = -1; - return 0; - } - - result->sink_status = input->sink(input->buffer, strlen(input->buffer), - input->sink_opaque); - if (result->sink_status != 0) { - result->status = KZT_LAZY_DIAGNOSTIC_EMIT_SINK_FAILED; - return 0; - } - - result->status = KZT_LAZY_DIAGNOSTIC_EMIT_EMITTED; - return 0; -} - -int kzt_lazy_diagnostics_emit_production( - const kzt_lazy_binding_pending_t *pending, - const kzt_lazy_binding_result_t *binding_result, - kzt_lazy_diagnostic_emit_result_t *result) -{ - char buffer[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; - kzt_lazy_diagnostic_input_t input; - - memset(&input, 0, sizeof(input)); - input.enabled = 1; - input.pending = pending; - input.binding_result = binding_result; - input.buffer = buffer; - input.buffer_size = sizeof(buffer); - input.sink = kzt_lazy_diagnostics_stderr_sink; - return kzt_lazy_diagnostics_emit(&input, result); -} diff --git a/target/i386/latx/context/kzt_lazy_direct_route.c b/target/i386/latx/context/kzt_lazy_direct_route.c index a1b8e011881..8699a52ace2 100644 --- a/target/i386/latx/context/kzt_lazy_direct_route.c +++ b/target/i386/latx/context/kzt_lazy_direct_route.c @@ -188,6 +188,21 @@ kzt_lazy_direct_route_status_t kzt_lazy_direct_route_apply( result->selected_target = bridge.target; } else if (cas_status == KZT_LAZY_DIRECT_ROUTE_CAS_MISMATCH) { result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_CAS_MISMATCH; + } else if (cas_status == KZT_LAZY_DIRECT_ROUTE_CAS_BUDGET_EXHAUSTED) { + if (input->allow_budget_transient_native && bridge.transient_safe && + kzt_patch_symbol_is_loader_route_family(input->symbol)) { + result->status = KZT_LAZY_DIRECT_ROUTE_NATIVE_TRANSIENT; + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_NATIVE_TRANSIENT; + result->selected_target = bridge.target; + } else { + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_BUDGET_EXHAUSTED; + } + } else if (cas_status == KZT_LAZY_DIRECT_ROUTE_CAS_ROLLED_BACK) { + result->status = KZT_LAZY_DIRECT_ROUTE_WRITE_ROLLED_BACK; + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_WRITE_ROLLED_BACK; + } else if (cas_status == KZT_LAZY_DIRECT_ROUTE_CAS_UNRECOVERABLE) { + result->status = KZT_LAZY_DIRECT_ROUTE_UNRECOVERABLE; + result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_UNRECOVERABLE; } else { result->reason = KZT_LAZY_DIRECT_ROUTE_REASON_CAS_ERROR; } diff --git a/target/i386/latx/context/kzt_lazy_prebind_scope.c b/target/i386/latx/context/kzt_lazy_prebind_scope.c index 79fa71335af..a85a3f66a52 100644 --- a/target/i386/latx/context/kzt_lazy_prebind_scope.c +++ b/target/i386/latx/context/kzt_lazy_prebind_scope.c @@ -58,6 +58,9 @@ static int kzt_lazy_prebind_record_valid( const kzt_lazy_prebind_record_t *record) { return kzt_lazy_prebind_record_key_valid(record) && + (!record->loader_mutation_invariant || + (record->bridge_custom_wrapper && + kzt_patch_symbol_is_loader_route_family(record->symbol))) && kzt_lazy_prebind_identity_valid(&record->provider) && record->bridge_target && record->bridge_generation && record->bridge_generation == record->provider.generation && @@ -102,6 +105,8 @@ static int kzt_lazy_prebind_record_equal( &right->provider) && left->bridge_target == right->bridge_target && left->bridge_generation == right->bridge_generation && + left->loader_mutation_invariant == + right->loader_mutation_invariant && left->scope_proof.selected_provider_address == right->scope_proof.selected_provider_address && left->scope_proof.selected_provider_binding == @@ -279,12 +284,44 @@ uint64_t kzt_lazy_prebind_scope_mutate( ++scope->epoch; } kzt_lazy_prebind_scope_wait_all_leases_locked(scope); + for (entry = scope->entries; entry; entry = entry->next) { + if (!entry->retired && entry->published && + entry->record.loader_mutation_invariant) { + entry->record.scope_epoch = scope->epoch; + } + } kzt_lazy_prebind_scope_prune_closed_locked(scope); pthread_cond_broadcast(&scope->changed); pthread_mutex_unlock(&scope->lock); return kzt_lazy_prebind_scope_epoch(scope); } +int kzt_lazy_prebind_scope_has_native_dlerror( + kzt_lazy_prebind_scope_t *scope, + const kzt_lazy_prebind_identity_t *source) +{ + kzt_lazy_prebind_entry_t *entry; + int found = 0; + + if (!scope || !kzt_lazy_prebind_identity_valid(source)) { + return 0; + } + pthread_mutex_lock(&scope->lock); + for (entry = scope->entries; entry; entry = entry->next) { + if (!entry->retired && entry->published && + entry->record.scope_epoch == scope->epoch && + entry->record.bridge_custom_wrapper && + strcmp(entry->record.symbol, "dlerror") == 0 && + kzt_lazy_prebind_identity_equal( + &entry->record.source, source)) { + found = 1; + break; + } + } + pthread_mutex_unlock(&scope->lock); + return found; +} + kzt_lazy_prebind_claim_result_t kzt_lazy_prebind_scope_claim( kzt_lazy_prebind_scope_t *scope, const kzt_lazy_prebind_record_t *record) @@ -356,6 +393,26 @@ int kzt_lazy_prebind_scope_acquire( return -1; } +int kzt_lazy_prebind_scope_lease_published( + const kzt_lazy_prebind_lease_t *lease) +{ + const kzt_lazy_prebind_entry_t *entry; + int published = 0; + + if (!lease || !lease->active || !lease->scope || !lease->entry) { + return 0; + } + entry = lease->entry; + pthread_mutex_lock(&lease->scope->lock); + published = !entry->retired && entry->published && + entry->record.scope_epoch == lease->scope->epoch && + entry->record.scope_epoch == lease->record.scope_epoch && + kzt_lazy_prebind_record_equal( + &entry->record, &lease->record); + pthread_mutex_unlock(&lease->scope->lock); + return published; +} + void kzt_lazy_prebind_scope_release(kzt_lazy_prebind_lease_t *lease) { kzt_lazy_prebind_entry_t *entry; @@ -524,8 +581,11 @@ int kzt_lazy_prebind_scope_retire( } pthread_mutex_lock(&scope->lock); for (entry = scope->entries; entry; entry = entry->next) { - if (kzt_lazy_prebind_identity_equal(&entry->record.source, identity) || - kzt_lazy_prebind_identity_equal(&entry->record.provider, identity)) { + if (!entry->record.loader_mutation_invariant && + (kzt_lazy_prebind_identity_equal( + &entry->record.source, identity) || + kzt_lazy_prebind_identity_equal( + &entry->record.provider, identity))) { entry->retired = 1; found = 1; } @@ -538,7 +598,7 @@ int kzt_lazy_prebind_scope_retire( unsigned long leases = 0; for (entry = scope->entries; entry; entry = entry->next) { - if (entry->retired && + if (!entry->record.loader_mutation_invariant && entry->retired && (kzt_lazy_prebind_identity_equal(&entry->record.source, identity) || kzt_lazy_prebind_identity_equal(&entry->record.provider, diff --git a/target/i386/latx/context/kzt_loader_event_hook.c b/target/i386/latx/context/kzt_loader_event_hook.c index 00a05321b72..a58aa49648c 100644 --- a/target/i386/latx/context/kzt_loader_event_hook.c +++ b/target/i386/latx/context/kzt_loader_event_hook.c @@ -1,5 +1,7 @@ #include "kzt_loader_event_hook.h" +#include "box64context.h" + #include #include #include @@ -69,10 +71,39 @@ static void kzt_loader_event_hook_lifecycle_unlock( __atomic_clear(&hook->lifecycle_lock, __ATOMIC_RELEASE); } +static int kzt_loader_event_hook_publisher_enter( + kzt_loader_event_hook_t *hook) +{ + if (!hook) { + return -1; + } + kzt_loader_event_hook_lifecycle_lock(hook); + if (!__atomic_load_n(&hook->lifecycle_enabled, __ATOMIC_ACQUIRE)) { + kzt_loader_event_hook_lifecycle_unlock(hook); + return -1; + } + __atomic_add_fetch(&hook->lifecycle_publishers, 1U, __ATOMIC_RELEASE); + kzt_loader_event_hook_lifecycle_unlock(hook); + return 0; +} + static int kzt_loader_event_hook_publisher_leave( kzt_loader_event_hook_t *hook, int result, - kzt_loader_lifecycle_result_t lifecycle_result) -{ + kzt_loader_lifecycle_result_t lifecycle_result, + int consistent_snapshot) +{ + if (lifecycle_result == KZT_LOADER_LIFECYCLE_OK) { + if (consistent_snapshot && + !__atomic_load_n(&hook->lifecycle_failed, __ATOMIC_ACQUIRE)) { + __atomic_store_n(&hook->lifecycle_confirmed, 1U, + __ATOMIC_RELEASE); + } else if (!consistent_snapshot) { + __atomic_store_n(&hook->lifecycle_confirmed, 0U, + __ATOMIC_RELEASE); + } + } else { + __atomic_store_n(&hook->lifecycle_failed, 1U, __ATOMIC_RELEASE); + } __atomic_store_n(&hook->lifecycle_result, lifecycle_result, __ATOMIC_RELEASE); __atomic_sub_fetch(&hook->lifecycle_publishers, 1U, __ATOMIC_RELEASE); @@ -473,6 +504,35 @@ static int kzt_loader_event_hook_disabled(void) return value && strcmp(value, "0") == 0; } +int kzt_loader_event_hook_lookup_layout( + const char *build_id, kzt_loader_event_layout_t *layout) +{ + if (!layout) { + return -1; + } + memset(layout, 0, sizeof(*layout)); + if (!build_id) { + return -1; + } + if (strcmp(build_id, KZT_LOADER_EVENT_HOOK_GLIBC_2_39_BUILD_ID) == 0) { + *layout = (kzt_loader_event_layout_t) { + .scope_layout = KZT_GUEST_SCOPE_LAYOUT_GLIBC_2_39_C591A5DF, + .debug_state_offset = KZT_LOADER_EVENT_HOOK_DEBUG_STATE_OFFSET, + .r_debug_offset = KZT_LOADER_EVENT_HOOK_R_DEBUG_OFFSET, + }; + return 0; + } + if (strcmp(build_id, KZT_LOADER_EVENT_HOOK_GLIBC_2_28_BUILD_ID) == 0) { + *layout = (kzt_loader_event_layout_t) { + .scope_layout = KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED, + .debug_state_offset = 0xfb10, + .r_debug_offset = 0x29160, + }; + return 0; + } + return -1; +} + int kzt_loader_event_hook_pattern_allowed(int pattern_matched) { const char *value = getenv("LATX_KZT_LOADER_EVENT_FORCE_PATTERN_MISMATCH"); @@ -487,6 +547,7 @@ int kzt_loader_event_hook_install(kzt_loader_event_hook_t *hook, int pattern_matched) { kzt_loader_event_hook_result_t result = KZT_LOADER_EVENT_HOOK_INSTALLED; + kzt_loader_event_layout_t layout; if (!hook) { return -1; @@ -496,7 +557,7 @@ int kzt_loader_event_hook_install(kzt_loader_event_hook_t *hook, result = KZT_LOADER_EVENT_HOOK_FAIL_OPEN_DISABLED; } else if (!build_id) { result = KZT_LOADER_EVENT_HOOK_FAIL_OPEN_BUILD_ID_READ; - } else if (strcmp(build_id, KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID) != 0) { + } else if (kzt_loader_event_hook_lookup_layout(build_id, &layout) != 0) { result = KZT_LOADER_EVENT_HOOK_FAIL_OPEN_UNKNOWN_BUILD_ID; } else if (!pattern_matched || !callback_addr || link_map_reg > 15) { result = KZT_LOADER_EVENT_HOOK_FAIL_OPEN_PATTERN_MISMATCH; @@ -508,7 +569,7 @@ int kzt_loader_event_hook_install(kzt_loader_event_hook_t *hook, memcpy(hook->build_id, build_id, KZT_LOADER_EVENT_HOOK_BUILD_ID_SIZE); hook->callback_addr = callback_addr; hook->link_map_reg = link_map_reg; - hook->scope_layout = KZT_GUEST_SCOPE_LAYOUT_GLIBC_2_39_C591A5DF; + hook->scope_layout = layout.scope_layout; __atomic_store_n(&hook->installed, 1U, __ATOMIC_RELEASE); return 0; } @@ -550,6 +611,7 @@ int kzt_loader_event_hook_enable_lifecycle( pending = kzt_loader_event_hook_calloc( KZT_LOADER_LIFECYCLE_INLINE_IDENTITIES, sizeof(*pending)); if (!pending) { + __atomic_store_n(&hook->lifecycle_failed, 1U, __ATOMIC_RELEASE); __atomic_store_n(&hook->lifecycle_result, KZT_LOADER_LIFECYCLE_ALLOCATION, __ATOMIC_RELEASE); @@ -593,14 +655,9 @@ int kzt_loader_event_hook_publish_lifecycle( size_t i; if (!hook || !resolve || !prepare || !cancel || !unload || - !__atomic_load_n(&hook->lifecycle_enabled, __ATOMIC_ACQUIRE)) { + kzt_loader_event_hook_publisher_enter(hook) != 0) { return -1; } - __atomic_add_fetch(&hook->lifecycle_publishers, 1U, __ATOMIC_ACQUIRE); - if (!__atomic_load_n(&hook->lifecycle_enabled, __ATOMIC_ACQUIRE)) { - return kzt_loader_event_hook_publisher_leave( - hook, -1, KZT_LOADER_LIFECYCLE_DISABLED); - } if (!kzt_loader_event_hook_live_maps_valid(live_maps, live_map_count) || (state != KZT_LOADER_DEBUG_CONSISTENT && state != KZT_LOADER_DEBUG_ADD && @@ -609,7 +666,7 @@ int kzt_loader_event_hook_publish_lifecycle( kzt_loader_lifecycle_cancel_buffer(&pending, cancel, opaque); kzt_loader_lifecycle_identity_buffer_release(&pending); return kzt_loader_event_hook_publisher_leave( - hook, -1, KZT_LOADER_LIFECYCLE_INVALID); + hook, -1, KZT_LOADER_LIFECYCLE_INVALID, 0); } if (state == KZT_LOADER_DEBUG_DELETE) { @@ -621,7 +678,11 @@ int kzt_loader_event_hook_publish_lifecycle( if (resolve(live_maps[i], &identity, opaque) != 0 || identity.link_map_addr != live_maps[i] || !identity.generation || prepare(&identity, opaque) != 0) { - continue; + kzt_loader_lifecycle_cancel_buffer( + &pending, cancel, opaque); + kzt_loader_lifecycle_identity_buffer_release(&pending); + return kzt_loader_event_hook_publisher_leave( + hook, -1, KZT_LOADER_LIFECYCLE_INVALID, 0); } lifecycle_result = kzt_loader_lifecycle_identity_buffer_append( @@ -632,7 +693,7 @@ int kzt_loader_event_hook_publish_lifecycle( &pending, cancel, opaque); kzt_loader_lifecycle_identity_buffer_release(&pending); return kzt_loader_event_hook_publisher_leave( - hook, -1, lifecycle_result); + hook, -1, lifecycle_result, 0); } } lifecycle_result = kzt_loader_event_hook_append_pending( @@ -643,35 +704,42 @@ int kzt_loader_event_hook_publish_lifecycle( kzt_loader_lifecycle_identity_buffer_release(&pending); return kzt_loader_event_hook_publisher_leave( hook, lifecycle_result == KZT_LOADER_LIFECYCLE_OK ? 0 : -1, - lifecycle_result); + lifecycle_result, 0); } if (state == KZT_LOADER_DEBUG_ADD) { return kzt_loader_event_hook_publisher_leave( - hook, 0, KZT_LOADER_LIFECYCLE_OK); + hook, 0, KZT_LOADER_LIFECYCLE_OK, 0); } kzt_loader_event_hook_take_pending(hook, &pending); + lifecycle_result = KZT_LOADER_LIFECYCLE_OK; for (i = 0; i < pending.count; ++i) { kzt_loader_lifecycle_identity_t current = { 0 }; + int transition_result; if (!kzt_loader_event_hook_map_present( pending.identities[i].link_map_addr, live_maps, live_map_count)) { - unload(&pending.identities[i], opaque); - } else if (resolve( - pending.identities[i].link_map_addr, - ¤t, opaque) == 0 && - !kzt_loader_lifecycle_identity_equal( + transition_result = unload(&pending.identities[i], opaque); + } else if (resolve(pending.identities[i].link_map_addr, + ¤t, opaque) != 0) { + transition_result = cancel(&pending.identities[i], opaque); + lifecycle_result = KZT_LOADER_LIFECYCLE_INVALID; + } else if (!kzt_loader_lifecycle_identity_equal( &pending.identities[i], ¤t)) { - unload(&pending.identities[i], opaque); + transition_result = unload(&pending.identities[i], opaque); } else { - (void)cancel(&pending.identities[i], opaque); + transition_result = cancel(&pending.identities[i], opaque); + } + if (transition_result != 0) { + lifecycle_result = KZT_LOADER_LIFECYCLE_INVALID; } } kzt_loader_lifecycle_identity_buffer_release(&pending); return kzt_loader_event_hook_publisher_leave( - hook, 0, KZT_LOADER_LIFECYCLE_OK); + hook, lifecycle_result == KZT_LOADER_LIFECYCLE_OK ? 0 : -1, + lifecycle_result, 1); } int kzt_loader_event_hook_destroy(kzt_loader_event_hook_t *hook) @@ -679,7 +747,9 @@ int kzt_loader_event_hook_destroy(kzt_loader_event_hook_t *hook) if (!hook) { return -1; } + kzt_loader_event_hook_lifecycle_lock(hook); __atomic_store_n(&hook->lifecycle_enabled, 0U, __ATOMIC_RELEASE); + kzt_loader_event_hook_lifecycle_unlock(hook); while (__atomic_load_n(&hook->lifecycle_publishers, __ATOMIC_ACQUIRE)) { } kzt_loader_event_hook_lifecycle_lock(hook); @@ -699,14 +769,39 @@ int kzt_loader_event_hook_destroy(kzt_loader_event_hook_t *hook) return 0; } +void kzt_loader_event_hook_context_init(kzt_loader_event_hook_t *hook) +{ + if (hook) { + memset(hook, 0, sizeof(*hook)); + } +} + +void kzt_loader_event_hook_context_destroy(kzt_loader_event_hook_t *hook) +{ + if (!hook) { + return; + } + kzt_loader_event_hook_lifecycle_lock(hook); + __atomic_store_n(&hook->lifecycle_enabled, 0U, __ATOMIC_RELEASE); + kzt_loader_event_hook_lifecycle_unlock(hook); + while (__atomic_load_n(&hook->lifecycle_publishers, __ATOMIC_ACQUIRE)) { + } + kzt_loader_event_hook_lifecycle_lock(hook); + free(hook->pending_delete); + hook->pending_delete = NULL; + hook->pending_delete_count = 0; + hook->pending_delete_capacity = 0; + kzt_loader_event_hook_lifecycle_unlock(hook); + memset(hook, 0, sizeof(*hook)); +} + kzt_guest_scope_layout_t kzt_loader_event_hook_scope_layout( const kzt_loader_event_hook_t *hook) { if (!hook || !__atomic_load_n(&hook->installed, __ATOMIC_ACQUIRE) || hook->result != KZT_LOADER_EVENT_HOOK_INSTALLED || - strcmp(hook->build_id, - KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID) != 0) { + hook->scope_layout == KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED) { return KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; } return hook->scope_layout; @@ -739,6 +834,26 @@ kzt_loader_lifecycle_result_t kzt_loader_event_hook_lifecycle_result( return __atomic_load_n(&hook->lifecycle_result, __ATOMIC_ACQUIRE); } +int kzt_loader_event_hook_lifecycle_healthy( + const kzt_loader_event_hook_t *hook) +{ + return hook && + __atomic_load_n(&hook->installed, __ATOMIC_ACQUIRE) && + __atomic_load_n(&hook->lifecycle_enabled, __ATOMIC_ACQUIRE) && + __atomic_load_n(&hook->lifecycle_confirmed, __ATOMIC_ACQUIRE) && + !__atomic_load_n(&hook->lifecycle_failed, __ATOMIC_ACQUIRE) && + kzt_loader_event_hook_lifecycle_result(hook) == + KZT_LOADER_LIFECYCLE_OK; +} + +#ifdef CONFIG_LATX_KZT +int kzt_loader_lifecycle_runtime_healthy(box64context_t *context) +{ + return context && kzt_loader_event_hook_lifecycle_healthy( + &context->kzt_loader_event_hook); +} +#endif + const char *kzt_loader_lifecycle_result_name( kzt_loader_lifecycle_result_t result) { diff --git a/target/i386/latx/context/kzt_observation_adapter.c b/target/i386/latx/context/kzt_observation_adapter.c index eb1a0ab598d..e2266a0a02a 100644 --- a/target/i386/latx/context/kzt_observation_adapter.c +++ b/target/i386/latx/context/kzt_observation_adapter.c @@ -410,8 +410,10 @@ int kzt_observe_guest_object_from_callback( if (request && request->per_object_flow && (observation_result == KZT_OBSERVATION_ADAPTER_ADDED || observation_result == KZT_OBSERVATION_ADAPTER_UPDATED)) { - (void)request->per_object_flow(request->link_map_addr, - request->per_object_opaque); + if (request->per_object_flow(request->link_map_addr, + request->per_object_opaque) != 0) { + observation_result = KZT_OBSERVATION_ADAPTER_PER_OBJECT_FAILED; + } } if (request && request->legacy_flow) { diff --git a/target/i386/latx/context/kzt_patch_spike_guard.c b/target/i386/latx/context/kzt_patch_spike_guard.c index 3903856e21d..93554b0952f 100644 --- a/target/i386/latx/context/kzt_patch_spike_guard.c +++ b/target/i386/latx/context/kzt_patch_spike_guard.c @@ -140,18 +140,13 @@ unsigned long kzt_patch_spike_guard_budget_remaining( static int kzt_patch_spike_guard_reserve_budget(kzt_patch_spike_guard_t *guard) { unsigned long attempts; - - for (;;) { - attempts = __atomic_load_n(&guard->write_attempts, __ATOMIC_ACQUIRE); - if (attempts >= guard->config.budget) { - return 0; - } - if (__atomic_compare_exchange_n(&guard->write_attempts, &attempts, - attempts + 1, 0, __ATOMIC_ACQ_REL, - __ATOMIC_ACQUIRE)) { - return 1; - } + attempts = __atomic_load_n(&guard->write_attempts, __ATOMIC_ACQUIRE); + if (attempts >= guard->config.budget) { + return 0; } + __atomic_store_n(&guard->write_attempts, attempts + 1, + __ATOMIC_RELEASE); + return 1; } static int kzt_patch_spike_failure_preserves_guest( @@ -170,7 +165,7 @@ static int kzt_patch_spike_decision_allows_write( decision->allow_native_bridge; } -int kzt_patch_spike_guard_try_write( +static int kzt_patch_spike_guard_try_write_internal( kzt_patch_spike_guard_t *guard, const kzt_patch_decision_t *decision, const kzt_patch_spike_writer_ops_t *writer, @@ -367,6 +362,16 @@ int kzt_patch_spike_guard_try_write( return 0; } +int kzt_patch_spike_guard_try_write( + kzt_patch_spike_guard_t *guard, + const kzt_patch_decision_t *decision, + const kzt_patch_spike_writer_ops_t *writer, + kzt_patch_spike_outcome_t *outcome) +{ + return kzt_patch_spike_guard_try_write_internal( + guard, decision, writer, outcome); +} + const char *kzt_patch_spike_result_name(kzt_patch_spike_result_t result) { switch (result) { diff --git a/target/i386/latx/context/kzt_patch_spike_writer.c b/target/i386/latx/context/kzt_patch_spike_writer.c index d9738239ad6..563a59c7ea1 100644 --- a/target/i386/latx/context/kzt_patch_spike_writer.c +++ b/target/i386/latx/context/kzt_patch_spike_writer.c @@ -154,7 +154,8 @@ static int kzt_patch_spike_writer_decision_supported( return decision && decision->kind == KZT_PATCH_DECISION_APPROVED && decision->reason == KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE && decision->allow_native_bridge && - decision->reloc_type == KZT_PATCH_RELOCATION_JUMP_SLOT && + (decision->reloc_type == KZT_PATCH_RELOCATION_JUMP_SLOT || + decision->reloc_type == KZT_PATCH_RELOCATION_GLOB_DAT) && decision->slot_addr && decision->slot_current_value_present && decision->bridge_target && decision->source.known && decision->source.link_map_addr && decision->source.generation && @@ -327,7 +328,7 @@ static void kzt_patch_spike_record_finish( record->previous_value = outcome->previous_value; } -int kzt_patch_spike_writer_try_apply_with_slot_ops( +static int kzt_patch_spike_writer_try_apply_internal( kzt_patch_spike_guard_t *guard, const kzt_patch_decision_t *decision, const kzt_patch_spike_slot_ops_t *slot_ops, @@ -367,8 +368,8 @@ int kzt_patch_spike_writer_try_apply_with_slot_ops( writer_ops.finish_slot = kzt_patch_spike_writer_finish_slot; writer_ops.opaque = &state; - if (kzt_patch_spike_guard_try_write(guard, decision, &writer_ops, - &outcome) != 0) { + if (kzt_patch_spike_guard_try_write( + guard, decision, &writer_ops, &outcome) != 0) { return -1; } @@ -376,6 +377,16 @@ int kzt_patch_spike_writer_try_apply_with_slot_ops( return 0; } +int kzt_patch_spike_writer_try_apply_with_slot_ops( + kzt_patch_spike_guard_t *guard, + const kzt_patch_decision_t *decision, + const kzt_patch_spike_slot_ops_t *slot_ops, + kzt_patch_spike_record_t *record) +{ + return kzt_patch_spike_writer_try_apply_internal( + guard, decision, slot_ops, record); +} + int kzt_patch_spike_writer_try_apply(kzt_patch_spike_guard_t *guard, const kzt_patch_decision_t *decision, kzt_patch_spike_record_t *record) diff --git a/target/i386/latx/context/kzt_plt_resolver_adapter.c b/target/i386/latx/context/kzt_plt_resolver_adapter.c index 6c794962a8d..713fc578565 100644 --- a/target/i386/latx/context/kzt_plt_resolver_adapter.c +++ b/target/i386/latx/context/kzt_plt_resolver_adapter.c @@ -49,15 +49,12 @@ int kzt_plt_resolver_enter( { const uint64_t *frame; kzt_plt_resolver_source_t source; - kzt_lazy_binding_begin_request_t request; - kzt_lazy_binding_result_t begin_result; - if (result) { memset(result, 0, sizeof(*result)); result->status = KZT_PLT_RESOLVER_ERROR; } if (!cpu || !ops || !result || !ops->lookup_source || - !ops->begin_lazy_binding || !ops->pending || !cpu->regs[R_ESP]) { + !cpu->regs[R_ESP]) { return -1; } @@ -73,38 +70,8 @@ int kzt_plt_resolver_enter( } cpu->regs[R_ESP] += 2 * sizeof(uint64_t); - memset(&request, 0, sizeof(request)); - request.enabled = source.enabled; - request.context_id = source.context_id; - request.source_link_map = source.source_link_map; - request.source_generation = source.source_generation; - request.namespace_id = source.namespace_id; - request.namespace_kind = source.namespace_kind; - request.relocation_index = result->relocation_slot; - request.slot_addr = source.slot_addr; - request.unresolved_stub = source.unresolved_stub; - request.symbol = source.symbol; - request.version_evidence = source.version_evidence; - request.version = source.version; - request.addend = source.addend; - request.guest_resolver = source.guest_resolver; - memset(&begin_result, 0, sizeof(begin_result)); - if (ops->begin_lazy_binding(&request, ops->pending, &begin_result, - ops->opaque) != 0) { - begin_result.status = KZT_LAZY_BINDING_GUEST_PRESERVED; - } - result->selected_resolver = source.guest_resolver; - result->pending_armed = begin_result.pending_armed; - if (begin_result.status == KZT_LAZY_BINDING_HANDOFF_GUEST) { - result->status = KZT_PLT_RESOLVER_HANDOFF_GUEST; - if (ops->completion_bridge && ops->original_return) { - *ops->original_return = *(uintptr_t *)cpu->regs[R_ESP]; - *(uintptr_t *)cpu->regs[R_ESP] = ops->completion_bridge; - } - } else { - result->status = KZT_PLT_RESOLVER_GUEST_PRESERVED; - } + result->status = KZT_PLT_RESOLVER_HANDOFF_GUEST; resolver_push64(cpu, result->relocation_slot); resolver_push64(cpu, source.source_link_map); resolver_push64(cpu, source.guest_resolver); diff --git a/target/i386/latx/context/kzt_rela_immediate_candidate.c b/target/i386/latx/context/kzt_rela_immediate_candidate.c index a03c72dc0b6..8821abc2479 100644 --- a/target/i386/latx/context/kzt_rela_immediate_candidate.c +++ b/target/i386/latx/context/kzt_rela_immediate_candidate.c @@ -162,7 +162,8 @@ int kzt_rela_immediate_jump_slot_try_write( } if (kzt_patch_spike_writer_try_apply_with_slot_ops( - guard, &result->plan.decision, slot_ops, &result->record) != 0) { + guard, &result->plan.decision, slot_ops, + &result->record) != 0) { return 0; } diff --git a/target/i386/latx/context/kzt_rela_runtime_bridge.c b/target/i386/latx/context/kzt_rela_runtime_bridge.c index d337fbe38de..c9fc6c271a9 100644 --- a/target/i386/latx/context/kzt_rela_runtime_bridge.c +++ b/target/i386/latx/context/kzt_rela_runtime_bridge.c @@ -14,10 +14,16 @@ #include "library.h" #include "library_private.h" +typedef struct bridge_s bridge_t; + extern uintptr_t CheckBridged(bridge_t *bridge, void *fnc); extern uintptr_t AddCheckBridge(bridge_t *bridge, wrapper_t wrapper, void *fnc, int stack_bytes, const char *name) __attribute__((weak)); +extern uintptr_t AddGuardedBridge( + bridge_t *bridge, wrapper_t wrapper, void *fnc, int stack_bytes, + const char *name, uintptr_t guest_fallback_target, + kzt_bridge_guard_kind_t guard_kind) __attribute__((weak)); extern int BridgeForkProtectionAvailable(void); extern void *GetNativeSymbolUnversionned( void *lib, const char *name) __attribute__((weak)); @@ -70,10 +76,18 @@ typedef struct kzt_rela_runtime_wrapper_candidate { typedef struct kzt_rela_runtime_provider_state { box64context_t *context; uintptr_t resolved_target; + uintptr_t guest_fallback_target; + kzt_bridge_guard_kind_t guard_kind; int discover_bridge; const kzt_guest_library_handle_t *retained_provider_handle; } kzt_rela_runtime_provider_state_t; +static int kzt_rela_runtime_provider_state_guarded( + const kzt_rela_runtime_provider_state_t *state) +{ + return state && state->guard_kind != KZT_BRIDGE_GUARD_NONE; +} + static void *kzt_rela_runtime_lookup_native( void *handle, const char *native_name, int custom_wrapper) { @@ -175,6 +189,7 @@ static int kzt_rela_runtime_retained_match_valid( if (!match || !(handle = match->retained_provider_handle) || !handle->bindings || !handle->entry || + handle->object_type != KZT_GUEST_LIBRARY_OBJECT_WRAPPED || !(context = match->context_owner) || !(provider = match->wrapper_provider) || handle->library != provider) { @@ -395,9 +410,11 @@ static int kzt_rela_runtime_provider_inspect( if (!match->native_symbol) { return -1; } - bridge_target = kzt_rela_runtime_provider_exact_bridge( - lib, state->resolved_target, wrapper, (void *)match->native_symbol); - if (!bridge_target && + bridge_target = kzt_rela_runtime_provider_state_guarded(state) ? 0 : + kzt_rela_runtime_provider_exact_bridge( + lib, state->resolved_target, wrapper, + (void *)match->native_symbol); + if (!kzt_rela_runtime_provider_state_guarded(state) && !bridge_target && kzt_rela_runtime_provider_cached_bridge( lib, (void *)match->native_symbol)) { return -1; @@ -446,6 +463,8 @@ static int kzt_rela_runtime_provider_inspect( match->wrapper_provider_lifetime_bound = 1; match->native_owner_lifetime_bound = 1; match->bridge_owner_lifetime_bound = 1; + match->guest_fallback_target = state->guest_fallback_target; + match->guard_kind = state->guard_kind; match->retained_provider_handle = state->retained_provider_handle; if (state->retained_provider_handle && !kzt_rela_runtime_retained_match_valid(match)) { @@ -480,7 +499,8 @@ static uintptr_t kzt_rela_runtime_provider_check( library_t *provider; (void)opaque; - if (!match || !match->bridge_owner || !match->abi_wrapper || + if (!match || match->guard_kind != KZT_BRIDGE_GUARD_NONE || + !match->bridge_owner || !match->abi_wrapper || !match->native_symbol || !kzt_rela_runtime_match_lifetime_valid(match) || (!!match->resolved_bridge_target != !!match->resolved_bridge_exact)) { @@ -510,19 +530,37 @@ static uintptr_t kzt_rela_runtime_provider_add( } provider = match->bridge_owner; - if (!provider->priv.w.bridge || !provider->context || !AddCheckBridge) { + if (!provider->priv.w.bridge || !provider->context) { + return 0; + } + + if (match->guard_kind != KZT_BRIDGE_GUARD_NONE) { + if (!AddGuardedBridge || !match->guest_fallback_target || + match->guard_kind != KZT_BRIDGE_GUARD_XCB_CONNECTION) { + return 0; + } + target = AddGuardedBridge( + provider->priv.w.bridge, match->abi_wrapper, + (void *)match->native_symbol, match->stack_bytes, + request->symbol_name, match->guest_fallback_target, + match->guard_kind); + return kzt_guarded_bridge_is_exact( + target, match->abi_wrapper, + (void *)match->native_symbol, + match->guest_fallback_target, match->guard_kind) ? + target : 0; + } + + if (!AddCheckBridge) { return 0; } target = AddCheckBridge(provider->priv.w.bridge, match->abi_wrapper, (void *)match->native_symbol, match->stack_bytes, request->symbol_name); - if (!target || - kzt_rela_runtime_provider_exact_bridge( - provider, target, match->abi_wrapper, - (void *)match->native_symbol) != target) { - target = 0; - } - return target; + return target && + kzt_rela_runtime_provider_exact_bridge( + provider, target, match->abi_wrapper, + (void *)match->native_symbol) == target ? target : 0; } static int kzt_rela_runtime_wrapper_provider_prepare_mode( @@ -531,11 +569,15 @@ static int kzt_rela_runtime_wrapper_provider_prepare_mode( kzt_symbol_version_evidence_t version_evidence, const char *symbol_version, int discover_bridge, const kzt_guest_library_handle_t *retained_provider_handle, + uintptr_t guest_fallback_target, + kzt_bridge_guard_kind_t guard_kind, kzt_wrapper_bridge_provider_t *provider) { kzt_rela_runtime_provider_state_t state = { .context = context, .resolved_target = resolved_target, + .guest_fallback_target = guest_fallback_target, + .guard_kind = guard_kind, .discover_bridge = discover_bridge, .retained_provider_handle = retained_provider_handle, }; @@ -551,6 +593,14 @@ static int kzt_rela_runtime_wrapper_provider_prepare_mode( if (!provider) { return -1; } + if ((guard_kind == KZT_BRIDGE_GUARD_NONE && guest_fallback_target) || + (guard_kind != KZT_BRIDGE_GUARD_NONE && + (!guest_fallback_target || + guard_kind != KZT_BRIDGE_GUARD_XCB_CONNECTION || + !discover_bridge || resolved_target))) { + memset(provider, 0, sizeof(*provider)); + return 0; + } if (!BridgeForkProtectionAvailable()) { memset(provider, 0, sizeof(*provider)); return 0; @@ -572,7 +622,7 @@ int kzt_rela_runtime_wrapper_provider_prepare( context, resolved_provider, resolved_target, symbol_name, symbol_version && symbol_version[0] ? KZT_SYMBOL_VERSION_VERSIONED : KZT_SYMBOL_VERSION_UNKNOWN, - symbol_version, 0, NULL, provider); + symbol_version, 0, NULL, 0, KZT_BRIDGE_GUARD_NONE, provider); } int kzt_rela_runtime_wrapper_provider_prepare_with_version_evidence( @@ -584,7 +634,8 @@ int kzt_rela_runtime_wrapper_provider_prepare_with_version_evidence( { return kzt_rela_runtime_wrapper_provider_prepare_mode( context, resolved_provider, resolved_target, symbol_name, - version_evidence, symbol_version, 0, NULL, provider); + version_evidence, symbol_version, 0, NULL, 0, + KZT_BRIDGE_GUARD_NONE, provider); } int kzt_rela_runtime_wrapper_provider_discover( @@ -596,7 +647,7 @@ int kzt_rela_runtime_wrapper_provider_discover( context, resolved_provider, 0, symbol_name, symbol_version && symbol_version[0] ? KZT_SYMBOL_VERSION_VERSIONED : KZT_SYMBOL_VERSION_UNKNOWN, - symbol_version, 1, NULL, provider); + symbol_version, 1, NULL, 0, KZT_BRIDGE_GUARD_NONE, provider); } int kzt_rela_runtime_wrapper_provider_discover_with_version_evidence( @@ -608,7 +659,21 @@ int kzt_rela_runtime_wrapper_provider_discover_with_version_evidence( { return kzt_rela_runtime_wrapper_provider_prepare_mode( context, resolved_provider, 0, symbol_name, version_evidence, - symbol_version, 1, NULL, provider); + symbol_version, 1, NULL, 0, KZT_BRIDGE_GUARD_NONE, provider); +} + +int kzt_rela_runtime_wrapper_provider_discover_guarded_with_version_evidence( + box64context_t *context, library_t *resolved_provider, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version, uintptr_t guest_fallback_target, + kzt_bridge_guard_kind_t guard_kind, + kzt_wrapper_bridge_provider_t *provider) +{ + return kzt_rela_runtime_wrapper_provider_prepare_mode( + context, resolved_provider, 0, symbol_name, version_evidence, + symbol_version, 1, NULL, guest_fallback_target, guard_kind, + provider); } int kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( @@ -625,7 +690,67 @@ int kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( return kzt_rela_runtime_wrapper_provider_prepare_mode( context, retained_provider_handle->library, 0, symbol_name, version_evidence, symbol_version, 1, retained_provider_handle, - provider); + 0, KZT_BRIDGE_GUARD_NONE, provider); +} + +uintptr_t kzt_rela_runtime_select_exact_wrapper_bridge_retained( + box64context_t *context, + const kzt_guest_library_handle_t *retained_provider_handle, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version) +{ + kzt_wrapper_bridge_provider_t provider = { 0 }; + kzt_wrapper_probe_request_t request = { + .symbol_name = symbol_name, + .symbol_version_evidence = version_evidence, + .symbol_version = symbol_version, + }; + kzt_wrapper_probe_result_t probe = { 0 }; + kzt_patch_wrapper_match_t expected_match; + + if (!context || !retained_provider_handle || + retained_provider_handle->object_type != + KZT_GUEST_LIBRARY_OBJECT_WRAPPED || + !symbol_name || !symbol_name[0] || + !kzt_symbol_version_evidence_valid(version_evidence, + symbol_version)) { + return 0; + } + expected_match = version_evidence == + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED + ? KZT_PATCH_WRAPPER_UNVERSIONED_MATCH + : KZT_PATCH_WRAPPER_VERSION_MATCH; + if (kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( + context, retained_provider_handle, symbol_name, version_evidence, + symbol_version, &provider) <= 0 || + kzt_wrapper_probe_minimal_manifest( + &provider.manifest, &request, &provider.bridge_ops, &probe) != 0 || + probe.wrapper_match != expected_match || !probe.bridge_target || + !kzt_symbol_version_evidence_matches( + version_evidence, symbol_version, + probe.wrapper_version_evidence, probe.wrapper_symbol_version)) { + return 0; + } + return probe.bridge_target; +} + +int kzt_rela_runtime_wrapper_provider_discover_guarded_retained_with_version_evidence( + box64context_t *context, + const kzt_guest_library_handle_t *retained_provider_handle, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version, uintptr_t guest_fallback_target, + kzt_bridge_guard_kind_t guard_kind, + kzt_wrapper_bridge_provider_t *provider) +{ + if (!retained_provider_handle || !retained_provider_handle->library) { + return 0; + } + return kzt_rela_runtime_wrapper_provider_prepare_mode( + context, retained_provider_handle->library, 0, symbol_name, + version_evidence, symbol_version, 1, retained_provider_handle, + guest_fallback_target, guard_kind, provider); } int kzt_rela_runtime_wrapper_provider_bind_retained_handle( diff --git a/target/i386/latx/context/kzt_wrapper_bridge_provider.c b/target/i386/latx/context/kzt_wrapper_bridge_provider.c index ed24ea5d046..abe2a729f11 100644 --- a/target/i386/latx/context/kzt_wrapper_bridge_provider.c +++ b/target/i386/latx/context/kzt_wrapper_bridge_provider.c @@ -15,6 +15,21 @@ static int kzt_wrapper_bridge_provider_string_equal(const char *left, strcmp(left, right) == 0; } +static int kzt_wrapper_bridge_provider_guard_valid( + const kzt_wrapper_bridge_provider_match_t *match) +{ + if (!match) { + return 0; + } + if (match->guard_kind == KZT_BRIDGE_GUARD_NONE) { + return match->guest_fallback_target == 0; + } + return match->guard_kind == KZT_BRIDGE_GUARD_XCB_CONNECTION && + match->guest_fallback_target != 0 && + !match->resolved_bridge_target && + !match->resolved_bridge_exact; +} + static uintptr_t kzt_wrapper_bridge_provider_check_bridge( uintptr_t native_symbol, void *opaque) { @@ -60,6 +75,12 @@ static uintptr_t kzt_wrapper_bridge_provider_add_bridge( if (!created) { return 0; } + /* Guarded bridges are intentionally unique and absent from CheckBridged. + Their runtime add callback validates the newly allocated entry before + returning it. Ordinary bridges retain the map-based post-add proof. */ + if (provider->match.guard_kind != KZT_BRIDGE_GUARD_NONE) { + return created; + } verified = provider->runtime_ops.check_bridge( &provider->match, provider->runtime_ops.opaque); return verified == created ? created : 0; @@ -128,6 +149,7 @@ int kzt_wrapper_bridge_provider_prepare_with_version_evidence( candidate.stack_bytes < 0 || !candidate.native_name[0] || (!!candidate.resolved_bridge_target != !!candidate.resolved_bridge_exact) || + !kzt_wrapper_bridge_provider_guard_valid(&candidate) || (!candidate.resolved_bridge_target && !runtime_ops->add_bridge) || !candidate.wrapper_provider_lifetime_bound || diff --git a/target/i386/latx/context/kzt_xcb_connection_guard.c b/target/i386/latx/context/kzt_xcb_connection_guard.c new file mode 100644 index 00000000000..32efb6d7deb --- /dev/null +++ b/target/i386/latx/context/kzt_xcb_connection_guard.c @@ -0,0 +1,274 @@ +#include "kzt_xcb_connection_guard.h" + +#include +#include +#include + +#define KZT_XCB_ACTIVE_LEASE_DEPTH 8 + +typedef struct kzt_xcb_pending_guard { + kzt_xcb_connection_map_t *map; + kzt_xcb_connection_lease_t lease; + int valid; +} kzt_xcb_pending_guard_t; + +typedef struct kzt_xcb_thread_leases { + kzt_xcb_pending_guard_t pending; + kzt_xcb_connection_lease_t active[KZT_XCB_ACTIVE_LEASE_DEPTH]; + size_t active_count; + int registered; +} kzt_xcb_thread_leases_t; + +static pthread_once_t kzt_xcb_thread_key_once = PTHREAD_ONCE_INIT; +static pthread_key_t kzt_xcb_thread_key; +static int kzt_xcb_thread_key_status = -1; +static __thread kzt_xcb_thread_leases_t kzt_xcb_thread_leases; + +static int kzt_xcb_cancel_disable(void) +{ + int old_state = PTHREAD_CANCEL_ENABLE; + + (void)pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); + return old_state; +} + +static void kzt_xcb_cancel_restore(int old_state) +{ + (void)pthread_setcancelstate(old_state, NULL); +} + +static void kzt_xcb_release_lease( + const kzt_xcb_connection_lease_t *lease) +{ + if (lease && lease->_map && lease->native && lease->guest) { + kzt_xcb_connection_map_release_pair( + lease->_map, lease->native, lease->guest); + } +} + +static void kzt_xcb_thread_leases_destroy(void *opaque) +{ + kzt_xcb_thread_leases_t *state = opaque; + + if (!state) { + return; + } + state->registered = 0; + if (state->pending.valid) { + kzt_xcb_release_lease(&state->pending.lease); + } + while (state->active_count) { + kzt_xcb_release_lease(&state->active[--state->active_count]); + } + memset(state, 0, sizeof(*state)); +} + +static void kzt_xcb_thread_key_init(void) +{ + kzt_xcb_thread_key_status = pthread_key_create( + &kzt_xcb_thread_key, kzt_xcb_thread_leases_destroy); +} + +static int kzt_xcb_thread_leases_register( + kzt_xcb_thread_leases_t *state) +{ + (void)pthread_once(&kzt_xcb_thread_key_once, kzt_xcb_thread_key_init); + if (kzt_xcb_thread_key_status != 0) { + return -1; + } + if (!state->registered) { + if (pthread_setspecific(kzt_xcb_thread_key, state) != 0) { + return -1; + } + state->registered = 1; + } + return 0; +} + +static void kzt_xcb_pending_release(kzt_xcb_thread_leases_t *state) +{ + if (!state->pending.valid) { + return; + } + kzt_xcb_release_lease(&state->pending.lease); + memset(&state->pending, 0, sizeof(state->pending)); +} + +static int kzt_xcb_active_push( + kzt_xcb_thread_leases_t *state, + const kzt_xcb_connection_lease_t *lease) +{ + if (state->active_count == KZT_XCB_ACTIVE_LEASE_DEPTH) { + return -1; + } + state->active[state->active_count++] = *lease; + return 0; +} + +void kzt_xcb_connection_guard_cancel(void) +{ + kzt_xcb_thread_leases_t *state = &kzt_xcb_thread_leases; + int old_state = kzt_xcb_cancel_disable(); + + kzt_xcb_pending_release(state); + kzt_xcb_cancel_restore(old_state); +} + +int kzt_xcb_connection_guard_prepare( + kzt_xcb_connection_map_t *map, void *guest) +{ + kzt_xcb_thread_leases_t *state = &kzt_xcb_thread_leases; + kzt_xcb_pending_guard_t *pending = &state->pending; + int old_state = kzt_xcb_cancel_disable(); + int result = -1; + + if (kzt_xcb_thread_leases_register(state) != 0) { + goto out; + } + kzt_xcb_pending_release(state); + if (map && guest && + kzt_xcb_connection_map_acquire_by_guest( + map, guest, &pending->lease) == 0) { + pending->map = map; + pending->valid = 1; + result = 0; + } +out: + kzt_xcb_cancel_restore(old_state); + return result; +} + +int kzt_xcb_connection_guard_take( + kzt_xcb_connection_map_t *map, void *guest, + kzt_xcb_connection_lease_t *lease) +{ + kzt_xcb_thread_leases_t *state = &kzt_xcb_thread_leases; + kzt_xcb_pending_guard_t *pending = &state->pending; + int old_state = kzt_xcb_cancel_disable(); + int result = -1; + + if (lease) { + memset(lease, 0, sizeof(*lease)); + } + if (!map || !guest || !lease || !pending->valid || + pending->map != map || pending->lease.guest != guest) { + goto out; + } + if (kzt_xcb_thread_leases_register(state) != 0 || + kzt_xcb_active_push(state, &pending->lease) != 0) { + goto out; + } + *lease = pending->lease; + memset(pending, 0, sizeof(*pending)); + result = 0; +out: + kzt_xcb_cancel_restore(old_state); + return result; +} + +int kzt_xcb_connection_guard_acquire( + kzt_xcb_connection_map_t *map, void *guest, + kzt_xcb_connection_lease_t *lease) +{ + kzt_xcb_thread_leases_t *state = &kzt_xcb_thread_leases; + kzt_xcb_pending_guard_t *pending = &state->pending; + kzt_xcb_connection_lease_t acquired = { 0 }; + int old_state = kzt_xcb_cancel_disable(); + int result = -1; + + if (lease) { + memset(lease, 0, sizeof(*lease)); + } + if (!map || !guest || !lease || + kzt_xcb_thread_leases_register(state) != 0) { + goto out; + } + if (state->active_count == KZT_XCB_ACTIVE_LEASE_DEPTH) { + kzt_xcb_pending_release(state); + goto out; + } + if (pending->valid) { + if (pending->map != map || pending->lease.guest != guest) { + kzt_xcb_pending_release(state); + goto out; + } + acquired = pending->lease; + memset(pending, 0, sizeof(*pending)); + } else if (kzt_xcb_connection_map_acquire_by_guest( + map, guest, &acquired) != 0) { + goto out; + } + if (kzt_xcb_active_push(state, &acquired) != 0) { + kzt_xcb_release_lease(&acquired); + goto out; + } + *lease = acquired; + result = 0; +out: + kzt_xcb_cancel_restore(old_state); + return result; +} + +int kzt_xcb_connection_guard_release( + kzt_xcb_connection_map_t *map, void *native, void *guest) +{ + kzt_xcb_thread_leases_t *state = &kzt_xcb_thread_leases; + kzt_xcb_connection_lease_t lease; + size_t index; + int old_state = kzt_xcb_cancel_disable(); + int result = -1; + + if (!map || !native || !guest) { + goto out; + } + for (index = state->active_count; index; --index) { + lease = state->active[index - 1]; + if (lease._map != map || lease.native != native || + lease.guest != guest) { + continue; + } + --state->active_count; + if (index - 1 != state->active_count) { + memmove(&state->active[index - 1], &state->active[index], + (state->active_count - (index - 1)) * + sizeof(state->active[0])); + } + memset(&state->active[state->active_count], 0, + sizeof(state->active[0])); + kzt_xcb_release_lease(&lease); + result = 0; + break; + } +out: + kzt_xcb_cancel_restore(old_state); + return result; +} + +int kzt_xcb_connection_guard_active_lease( + kzt_xcb_connection_map_t *map, void *native, void *guest, + kzt_xcb_connection_lease_t *lease) +{ + kzt_xcb_thread_leases_t *state = &kzt_xcb_thread_leases; + size_t index; + int old_state = kzt_xcb_cancel_disable(); + int result = -1; + + if (lease) { + memset(lease, 0, sizeof(*lease)); + } + if (!map || !native || !guest || !lease) { + goto out; + } + for (index = state->active_count; index; --index) { + if (state->active[index - 1]._map == map && + state->active[index - 1].native == native && + state->active[index - 1].guest == guest) { + *lease = state->active[index - 1]; + result = 0; + break; + } + } +out: + kzt_xcb_cancel_restore(old_state); + return result; +} diff --git a/target/i386/latx/context/kzt_xcb_connection_map.c b/target/i386/latx/context/kzt_xcb_connection_map.c new file mode 100644 index 00000000000..54cb3d8d563 --- /dev/null +++ b/target/i386/latx/context/kzt_xcb_connection_map.c @@ -0,0 +1,494 @@ +#include "kzt_xcb_connection_map.h" + +#include +#include +#include + +typedef struct kzt_xcb_connection_entry { + void *guest; + void *native; + uint64_t generation; + pthread_mutex_t operation_lock; + unsigned long users; + int closing; + int removal_pending; + int removing; + struct kzt_xcb_connection_entry *next; +} kzt_xcb_connection_entry_t; + +struct kzt_xcb_connection_map { + pthread_mutex_t lock; + pthread_cond_t changed; + kzt_xcb_connection_guest_destroy_fn destroy_guest; + void *destroy_opaque; + kzt_xcb_connection_entry_t *entries; + size_t count; + uint64_t next_generation; + unsigned long active_removals; + int teardown; +}; + +typedef struct kzt_xcb_remove_wait_cleanup { + kzt_xcb_connection_map_t *map; + kzt_xcb_connection_entry_t *entry; +} kzt_xcb_remove_wait_cleanup_t; + +static int kzt_xcb_cancel_disable(void) +{ + int old_state = PTHREAD_CANCEL_ENABLE; + + (void)pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); + return old_state; +} + +static void kzt_xcb_cancel_restore(int old_state) +{ + (void)pthread_setcancelstate(old_state, NULL); +} + +static void kzt_xcb_remove_wait_cancel(void *opaque) +{ + kzt_xcb_remove_wait_cleanup_t *cleanup = opaque; + + cleanup->entry->removal_pending = 0; + if (!cleanup->map->teardown) { + cleanup->entry->closing = 0; + } + pthread_cond_broadcast(&cleanup->map->changed); + pthread_mutex_unlock(&cleanup->map->lock); +} + +static int kzt_xcb_connection_entry_init( + kzt_xcb_connection_entry_t *entry) +{ + pthread_mutexattr_t attr; + int status; + + if (pthread_mutexattr_init(&attr) != 0) { + return -1; + } + status = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + if (status == 0) { + status = pthread_mutex_init(&entry->operation_lock, &attr); + } + pthread_mutexattr_destroy(&attr); + return status == 0 ? 0 : -1; +} + +static void kzt_xcb_connection_entry_free( + kzt_xcb_connection_entry_t *entry) +{ + pthread_mutex_destroy(&entry->operation_lock); + free(entry); +} + +static void kzt_xcb_connection_lease_clear( + kzt_xcb_connection_lease_t *lease) +{ + if (lease) { + memset(lease, 0, sizeof(*lease)); + } +} + +static kzt_xcb_connection_entry_t *kzt_xcb_connection_find_guest( + kzt_xcb_connection_map_t *map, void *guest) +{ + kzt_xcb_connection_entry_t *entry; + + for (entry = map->entries; entry; entry = entry->next) { + if (entry->guest == guest) { + return entry; + } + } + return NULL; +} + +static kzt_xcb_connection_entry_t *kzt_xcb_connection_find_native( + kzt_xcb_connection_map_t *map, void *native) +{ + kzt_xcb_connection_entry_t *entry; + + for (entry = map->entries; entry; entry = entry->next) { + if (entry->native == native) { + return entry; + } + } + return NULL; +} + +static void kzt_xcb_connection_publish_lease( + kzt_xcb_connection_map_t *map, kzt_xcb_connection_entry_t *entry, + int removal, kzt_xcb_connection_lease_t *lease) +{ + lease->guest = entry->guest; + lease->native = entry->native; + lease->generation = entry->generation; + lease->_map = map; + lease->_entry = entry; + lease->_removal = removal; +} + +static int kzt_xcb_connection_has_users_or_pending( + const kzt_xcb_connection_map_t *map) +{ + const kzt_xcb_connection_entry_t *entry; + + for (entry = map->entries; entry; entry = entry->next) { + if (entry->users || entry->removal_pending || entry->removing) { + return 1; + } + } + return map->active_removals != 0; +} + +kzt_xcb_connection_map_t *kzt_xcb_connection_map_init( + kzt_xcb_connection_guest_destroy_fn destroy_guest, void *opaque) +{ + kzt_xcb_connection_map_t *map; + + if (!destroy_guest) { + return NULL; + } + map = calloc(1, sizeof(*map)); + if (!map) { + return NULL; + } + if (pthread_mutex_init(&map->lock, NULL) != 0) { + free(map); + return NULL; + } + if (pthread_cond_init(&map->changed, NULL) != 0) { + pthread_mutex_destroy(&map->lock); + free(map); + return NULL; + } + map->destroy_guest = destroy_guest; + map->destroy_opaque = opaque; + map->next_generation = 1; + return map; +} + +void kzt_xcb_connection_map_destroy(kzt_xcb_connection_map_t **map_ptr) +{ + kzt_xcb_connection_map_t *map; + kzt_xcb_connection_entry_t *entry; + int old_cancel_state; + + if (!map_ptr || !(map = *map_ptr)) { + return; + } + old_cancel_state = kzt_xcb_cancel_disable(); + pthread_mutex_lock(&map->lock); + map->teardown = 1; + for (entry = map->entries; entry; entry = entry->next) { + entry->closing = 1; + } + pthread_cond_broadcast(&map->changed); + while (kzt_xcb_connection_has_users_or_pending(map)) { + pthread_cond_wait(&map->changed, &map->lock); + } + entry = map->entries; + map->entries = NULL; + map->count = 0; + pthread_mutex_unlock(&map->lock); + + while (entry) { + kzt_xcb_connection_entry_t *next = entry->next; + + map->destroy_guest(entry->guest, map->destroy_opaque); + kzt_xcb_connection_entry_free(entry); + entry = next; + } + pthread_cond_destroy(&map->changed); + pthread_mutex_destroy(&map->lock); + free(map); + *map_ptr = NULL; + kzt_xcb_cancel_restore(old_cancel_state); +} + +kzt_xcb_connection_map_result_t kzt_xcb_connection_map_register( + kzt_xcb_connection_map_t *map, void *native, void *proposed_guest, + void **canonical_guest, uint64_t *generation) +{ + kzt_xcb_connection_entry_t *entry; + kzt_xcb_connection_entry_t *created; + int old_cancel_state; + + if (canonical_guest) { + *canonical_guest = NULL; + } + if (generation) { + *generation = 0; + } + if (!map || !native || !proposed_guest || !canonical_guest || + !generation) { + return KZT_XCB_CONNECTION_MAP_ERROR; + } + + created = calloc(1, sizeof(*created)); + if (!created) { + return KZT_XCB_CONNECTION_MAP_ERROR; + } + if (kzt_xcb_connection_entry_init(created) != 0) { + free(created); + return KZT_XCB_CONNECTION_MAP_ERROR; + } + old_cancel_state = kzt_xcb_cancel_disable(); + pthread_mutex_lock(&map->lock); + if (map->teardown) { + pthread_mutex_unlock(&map->lock); + kzt_xcb_connection_entry_free(created); + kzt_xcb_cancel_restore(old_cancel_state); + return KZT_XCB_CONNECTION_MAP_ERROR; + } + entry = kzt_xcb_connection_find_native(map, native); + if (entry) { + if (entry->closing) { + pthread_mutex_unlock(&map->lock); + kzt_xcb_connection_entry_free(created); + kzt_xcb_cancel_restore(old_cancel_state); + return KZT_XCB_CONNECTION_MAP_ERROR; + } + *canonical_guest = entry->guest; + *generation = entry->generation; + pthread_mutex_unlock(&map->lock); + kzt_xcb_connection_entry_free(created); + kzt_xcb_cancel_restore(old_cancel_state); + return KZT_XCB_CONNECTION_MAP_UNCHANGED; + } + if (kzt_xcb_connection_find_guest(map, proposed_guest) || + map->next_generation == 0) { + pthread_mutex_unlock(&map->lock); + kzt_xcb_connection_entry_free(created); + kzt_xcb_cancel_restore(old_cancel_state); + return KZT_XCB_CONNECTION_MAP_ERROR; + } + + created->native = native; + created->guest = proposed_guest; + created->generation = map->next_generation++; + created->next = map->entries; + map->entries = created; + ++map->count; + *canonical_guest = created->guest; + *generation = created->generation; + pthread_mutex_unlock(&map->lock); + kzt_xcb_cancel_restore(old_cancel_state); + return KZT_XCB_CONNECTION_MAP_ADDED; +} + +static int kzt_xcb_connection_acquire( + kzt_xcb_connection_map_t *map, void *key, int by_guest, + kzt_xcb_connection_lease_t *lease) +{ + kzt_xcb_connection_entry_t *entry; + int old_cancel_state; + + kzt_xcb_connection_lease_clear(lease); + if (!map || !key || !lease) { + return -1; + } + old_cancel_state = kzt_xcb_cancel_disable(); + pthread_mutex_lock(&map->lock); + entry = by_guest ? kzt_xcb_connection_find_guest(map, key) + : kzt_xcb_connection_find_native(map, key); + if (map->teardown || !entry || entry->closing) { + pthread_mutex_unlock(&map->lock); + kzt_xcb_cancel_restore(old_cancel_state); + return -1; + } + ++entry->users; + kzt_xcb_connection_publish_lease(map, entry, 0, lease); + pthread_mutex_unlock(&map->lock); + kzt_xcb_cancel_restore(old_cancel_state); + return 0; +} + +int kzt_xcb_connection_map_acquire_by_guest( + kzt_xcb_connection_map_t *map, void *guest, + kzt_xcb_connection_lease_t *lease) +{ + return kzt_xcb_connection_acquire(map, guest, 1, lease); +} + +int kzt_xcb_connection_map_acquire_by_native( + kzt_xcb_connection_map_t *map, void *native, + kzt_xcb_connection_lease_t *lease) +{ + return kzt_xcb_connection_acquire(map, native, 0, lease); +} + +void kzt_xcb_connection_map_release_pair( + kzt_xcb_connection_map_t *map, void *native, void *guest) +{ + kzt_xcb_connection_entry_t *entry; + int old_cancel_state; + + if (!map || !native || !guest) { + return; + } + old_cancel_state = kzt_xcb_cancel_disable(); + pthread_mutex_lock(&map->lock); + entry = kzt_xcb_connection_find_native(map, native); + if (entry && entry->guest == guest && entry->users) { + --entry->users; + if (!entry->users) { + pthread_cond_broadcast(&map->changed); + } + } + pthread_mutex_unlock(&map->lock); + kzt_xcb_cancel_restore(old_cancel_state); +} + +int kzt_xcb_connection_lease_lock_mirror( + const kzt_xcb_connection_lease_t *lease) +{ + kzt_xcb_connection_entry_t *entry; + + if (!lease || lease->_removal || !lease->_map || !lease->_entry || + !lease->native || !lease->guest) { + return -1; + } + entry = lease->_entry; + return pthread_mutex_lock(&entry->operation_lock) == 0 ? 0 : -1; +} + +void kzt_xcb_connection_lease_unlock_mirror( + const kzt_xcb_connection_lease_t *lease) +{ + kzt_xcb_connection_entry_t *entry; + + if (!lease || lease->_removal || !lease->_entry) { + return; + } + entry = lease->_entry; + (void)pthread_mutex_unlock(&entry->operation_lock); +} + +static int kzt_xcb_connection_map_begin_remove( + kzt_xcb_connection_map_t *map, void *key, int by_guest, + kzt_xcb_connection_lease_t *lease) +{ + kzt_xcb_connection_entry_t *entry; + kzt_xcb_remove_wait_cleanup_t cleanup; + int old_cancel_state; + int wait_status = 0; + + kzt_xcb_connection_lease_clear(lease); + if (!map || !key || !lease) { + return -1; + } + old_cancel_state = kzt_xcb_cancel_disable(); + pthread_mutex_lock(&map->lock); + entry = by_guest ? kzt_xcb_connection_find_guest(map, key) + : kzt_xcb_connection_find_native(map, key); + if (map->teardown || !entry || entry->closing) { + pthread_mutex_unlock(&map->lock); + kzt_xcb_cancel_restore(old_cancel_state); + return -1; + } + entry->closing = 1; + entry->removal_pending = 1; + cleanup = (kzt_xcb_remove_wait_cleanup_t) { + .map = map, + .entry = entry, + }; + pthread_cleanup_push(kzt_xcb_remove_wait_cancel, &cleanup); + kzt_xcb_cancel_restore(old_cancel_state); + while (entry->users && wait_status == 0) { + wait_status = pthread_cond_wait(&map->changed, &map->lock); + } + (void)kzt_xcb_cancel_disable(); + pthread_cleanup_pop(0); + if (wait_status != 0) { + entry->removal_pending = 0; + if (!map->teardown) { + entry->closing = 0; + } + pthread_cond_broadcast(&map->changed); + pthread_mutex_unlock(&map->lock); + kzt_xcb_cancel_restore(old_cancel_state); + return -1; + } + entry->removal_pending = 0; + entry->removing = 1; + ++map->active_removals; + kzt_xcb_connection_publish_lease(map, entry, 1, lease); + pthread_cond_broadcast(&map->changed); + pthread_mutex_unlock(&map->lock); + kzt_xcb_cancel_restore(old_cancel_state); + return 0; +} + +int kzt_xcb_connection_map_begin_remove_by_guest( + kzt_xcb_connection_map_t *map, void *guest, + kzt_xcb_connection_lease_t *lease) +{ + return kzt_xcb_connection_map_begin_remove( + map, guest, 1, lease); +} + +int kzt_xcb_connection_map_begin_remove_by_native( + kzt_xcb_connection_map_t *map, void *native, + kzt_xcb_connection_lease_t *lease) +{ + return kzt_xcb_connection_map_begin_remove( + map, native, 0, lease); +} + +void kzt_xcb_connection_map_finish_remove( + kzt_xcb_connection_lease_t *lease) +{ + kzt_xcb_connection_map_t *map; + kzt_xcb_connection_entry_t *entry; + kzt_xcb_connection_entry_t **cursor; + int old_cancel_state; + + if (!lease || !lease->_removal || !(map = lease->_map) || + !(entry = lease->_entry)) { + return; + } + old_cancel_state = kzt_xcb_cancel_disable(); + pthread_mutex_lock(&map->lock); + cursor = &map->entries; + while (*cursor && *cursor != entry) { + cursor = &(*cursor)->next; + } + if (*cursor != entry || !entry->removing) { + pthread_mutex_unlock(&map->lock); + kzt_xcb_cancel_restore(old_cancel_state); + return; + } + *cursor = entry->next; + --map->count; + entry->removing = 0; + pthread_mutex_unlock(&map->lock); + + map->destroy_guest(entry->guest, map->destroy_opaque); + kzt_xcb_connection_entry_free(entry); + + pthread_mutex_lock(&map->lock); + if (map->active_removals) { + --map->active_removals; + } + pthread_cond_broadcast(&map->changed); + pthread_mutex_unlock(&map->lock); + kzt_xcb_connection_lease_clear(lease); + kzt_xcb_cancel_restore(old_cancel_state); +} + +size_t kzt_xcb_connection_map_size(kzt_xcb_connection_map_t *map) +{ + size_t count; + int old_cancel_state; + + if (!map) { + return 0; + } + old_cancel_state = kzt_xcb_cancel_disable(); + pthread_mutex_lock(&map->lock); + count = map->teardown ? 0 : map->count; + pthread_mutex_unlock(&map->lock); + kzt_xcb_cancel_restore(old_cancel_state); + return count; +} diff --git a/target/i386/latx/context/meson.build b/target/i386/latx/context/meson.build index f92d5d5dc7c..5fb6db4692f 100644 --- a/target/i386/latx/context/meson.build +++ b/target/i386/latx/context/meson.build @@ -61,7 +61,6 @@ my_file = files( 'library.c', 'pathcoll.c', 'fileutils.c', - 'dlopen_recycle_transaction.c', 'elfparser.c', 'elf_plt_relocation.c', 'elfmap.c', @@ -81,12 +80,14 @@ my_file = files( 'kzt_jump_slot_production.c', 'kzt_lazy_direct_route.c', 'kzt_lazy_prebind_scope.c', - 'kzt_lazy_binding.c', - 'kzt_lazy_diagnostics.c', 'kzt_plt_resolver_adapter.c', 'kzt_guest_library_adapter.c', + 'kzt_guest_glob_dat_target.c', 'kzt_guest_dl_api.c', 'kzt_guest_dl_init.c', + 'kzt_guest_runtime_entry.c', + 'kzt_guest_runtime_entry_state.c', + 'kzt_guest_cancel_scope.c', 'kzt_lifecycle_diagnostics.c', 'kzt_bridge_exact.c', 'kzt_guest_link_map_reader.c', @@ -108,6 +109,8 @@ my_file = files( 'kzt_wrapper_bridge_provider.c', 'kzt_patch_spike_guard.c', 'kzt_patch_spike_writer.c', + 'kzt_xcb_connection_map.c', + 'kzt_xcb_connection_guard.c', 'myalign.c', 'obstack.c', 'globalsymbols.c', diff --git a/target/i386/latx/context/myalign.c b/target/i386/latx/context/myalign.c index ed90b6524f4..6b134a4bed0 100644 --- a/target/i386/latx/context/myalign.c +++ b/target/i386/latx/context/myalign.c @@ -14,8 +14,11 @@ #include "elfloader.h" #include "elfloader_private.h" #include "kzt_loader_event_hook.h" +#include "kzt_xcb_connection_guard.h" +#include "kzt_xcb_queue_mirror.h" #ifdef CONFIG_LATX_KZT #include "kzt_guest_dl_api.h" +#include "kzt_guest_dl_init.h" #include "kzt_observation_adapter.h" #include "kzt_guest_library_adapter.h" #include "kzt_jump_slot_production.h" @@ -26,6 +29,7 @@ #include #include #include +#include #include #pragma GCC diagnostic push @@ -1657,22 +1661,12 @@ typedef struct x64_xcb_connection_s { x64_xcb_xid_t xid; } x64_xcb_connection_t; -#define NXCB 8 -my_xcb_connection_t* my_xcb_connects[NXCB] = {0}; -x64_xcb_connection_t x64_xcb_connects[NXCB] = {0}; #ifdef CONFIG_LATX_DEBUG -/* check x64_xcb_connects had sync my_xcb_connects*/ -/* -* Use align_xcb_connection and unalign_xcb_connection sync x64_xcb_connects. -* But, For latx, my_xcb_connects had changed by other FUNC, insead of xcb api. -*So we need latx_xcb_cmp for check xcb changed or not. -*/ - -static void latx_xcb_cmp(void* src, void* dst) +static void latx_xcb_cmp(void *guest, void *native) { - my_xcb_connection_t * dest = dst; - my_xcb_connection_t * source = src; - #define GO(member,mtype) do{lsassertm(source->member == dest->member, "x64_xcb_connects->"#member"="#mtype" != my_xcb_connects->"#member"="#mtype"\n", source->member, dest->member );} while(0) + x64_xcb_connection_t *source = guest; + my_xcb_connection_t *dest = native; + #define GO(member,mtype) do{lsassertm(source->member == dest->member, "guest_xcb->"#member"="#mtype" != native_xcb->"#member"="#mtype"\n", source->member, dest->member );} while(0) GO(has_error,"%d"); GO(setup, "%p"); GO(fd, "%d"); @@ -1686,78 +1680,113 @@ static void latx_xcb_cmp(void* src, void* dst) } #endif -EXPORT int32_t my_xcb_flush(void* v1); -void* align_xcb_connection(void* src) +uintptr_t kzt_xcb_guard_acquire_for_bridge( + CPUX86State *env, uintptr_t guest) { - if(!src) - return src; - // find it - my_xcb_connection_t * dest = NULL; - for(int i=0; ixid.last == ((my_xcb_connection_t *)dest)->xid.last)) { - my_xcb_flush(dest); + box64context_t *context = env ? env->kzt_runtime_context : NULL; + x64_xcb_connection_t *connection = (void *)guest; + int status; + + if (!guest) { + kzt_xcb_connection_guard_cancel(); + return 0; } -#ifdef CONFIG_LATX_DEBUG - if (dest) { - latx_xcb_cmp(src,dest); + if (!context || !context->kzt_xcb_connection_map) { + return 0; } -#endif - if(!dest) - dest = add_xcb_connection(src); - #else - if(!dest) { - printf_log(LOG_NONE, "BOX64: Error, xcb_connect %p not found\n", src); - abort(); + status = kzt_xcb_connection_guard_prepare( + context->kzt_xcb_connection_map, (void *)guest); + if (status != 0) { + printf_kzt_registry_diagnostics( + "kzt_xcb_guard schema=1 phase=prepare guest=%p map=%p " + "result=FALLBACK reason=unknown_connection\n", + (void *)guest, (void *)context->kzt_xcb_connection_map); + return 0; } - #endif - #if 1 - // do not update most values - x64_xcb_connection_t* source = src; + if (!kzt_xcb_flush_state_is_supported( + connection->out.queue_len, sizeof(connection->out.queue), + connection->out.out_fd.nfd, connection->out.out_fd.ifd, + connection->out.writing, connection->out.socket_moving, + (uintptr_t)connection->out.return_socket, + (uintptr_t)connection->out.socket_closure)) { + printf_kzt_registry_diagnostics( + "kzt_xcb_guard schema=1 phase=prepare guest=%p map=%p " + "result=FALLBACK reason=unsupported_flush_state\n", + (void *)guest, (void *)context->kzt_xcb_connection_map); + kzt_xcb_connection_guard_cancel(); + return 0; + } + printf_kzt_registry_diagnostics( + "kzt_xcb_guard schema=1 phase=prepare guest=%p map=%p result=READY\n", + (void *)guest, (void *)context->kzt_xcb_connection_map); + return 1; +} + +static void kzt_xcb_copy_guest_to_native(void *native, void *guest) +{ + my_xcb_connection_t *dest = native; + x64_xcb_connection_t *source = guest; + dest->has_error = source->has_error; dest->setup = source->setup; dest->fd = source->fd; - //memcpy(&dest->iolock, source->iolock, MUTEX_SIZE_X64); - //dest->in = source->in; - //dest->out = source->out; - //memcpy(&dest->ext.lock, source->ext.lock, MUTEX_SIZE_X64); dest->ext.extensions = source->ext.extensions; dest->ext.extensions_size = source->ext.extensions_size; - //memcpy(&dest->xid.lock, source->xid.lock, MUTEX_SIZE_X64); + kzt_xcb_queue_copy( + dest->out.queue, sizeof(dest->out.queue), &dest->out.queue_len, + source->out.queue, sizeof(source->out.queue), source->out.queue_len); + dest->out.request = source->out.request; + dest->out.request_written = source->out.request_written; + dest->out.out_fd = source->out.out_fd; dest->xid.base = source->xid.base; dest->xid.inc = source->xid.inc; if (dest->xid.last > source->xid.last) { source->xid.last = dest->xid.last; - //waring: dest->xid.last > source->xid.last skip. } else { dest->xid.last = source->xid.last; } dest->xid.max = source->xid.max; - #endif - return dest; +#ifdef CONFIG_LATX_DEBUG + latx_xcb_cmp(guest, native); +#endif } -void unalign_xcb_connection(void* src, void* dst) +static void kzt_xcb_copy_native_to_guest(void *native, void *guest) { - if(!src || !dst || src==dst) - return; - // update values - my_xcb_connection_t* source = src; - x64_xcb_connection_t* dest = dst; + my_xcb_connection_t *source = native; + x64_xcb_connection_t *dest = guest; + dest->has_error = source->has_error; dest->setup = source->setup; dest->fd = source->fd; memcpy(dest->iolock, &source->iolock, MUTEX_SIZE_X64); - dest->in = source->in; + dest->in.event_cond = source->in.event_cond; + dest->in.reading = source->in.reading; + kzt_xcb_queue_copy( + dest->in.queue, sizeof(dest->in.queue), &dest->in.queue_len, + source->in.queue, sizeof(source->in.queue), source->in.queue_len); + dest->in.request_expected = source->in.request_expected; + dest->in.request_read = source->in.request_read; + dest->in.request_completed = source->in.request_completed; + dest->in.current_reply = source->in.current_reply; + dest->in.current_reply_tail = source->in.current_reply_tail; + dest->in.replies = source->in.replies; + dest->in.events = source->in.events; + dest->in.events_tail = source->in.events_tail; + dest->in.readers = source->in.readers; + dest->in.special_waiters = source->in.special_waiters; + dest->in.pending_replies = source->in.pending_replies; + dest->in.pending_replies_tail = source->in.pending_replies_tail; + dest->in.in_fd = source->in.in_fd; + dest->in.special_events = source->in.special_events; memcpy(dest->out.reqlenlock, &source->out.reqlenlock, MUTEX_SIZE_X64); dest->out.cond = source->out.cond; dest->out.maximum_request_length = source->out.maximum_request_length; dest->out.maximum_request_length_tag = source->out.maximum_request_length_tag; dest->out.out_fd = source->out.out_fd; - memcpy(dest->out.queue, source->out.queue, sizeof(dest->out.queue)); - dest->out.queue_len = source->out.queue_len; + kzt_xcb_queue_copy( + dest->out.queue, sizeof(dest->out.queue), &dest->out.queue_len, + source->out.queue, sizeof(source->out.queue), source->out.queue_len); dest->out.request = source->out.request; dest->out.request_written = source->out.request_written; dest->out.return_socket = source->out.return_socket; @@ -1775,49 +1804,207 @@ void unalign_xcb_connection(void* src, void* dst) dest->xid.max = source->xid.max; } -void* add_xcb_connection(void* src) +static int kzt_xcb_call_cancel_disable(void) { - if(!src) - return src; - // check if already exist - for(int i=0; inative, lease->guest); + kzt_xcb_connection_lease_unlock_mirror(lease); + kzt_xcb_call_cancel_restore(old_cancel_state); + return 0; +} + +static int kzt_xcb_mirror_native_to_guest( + const kzt_xcb_connection_lease_t *lease) +{ + int old_cancel_state = kzt_xcb_call_cancel_disable(); + + if (kzt_xcb_connection_lease_lock_mirror(lease) != 0) { + kzt_xcb_call_cancel_restore(old_cancel_state); + return -1; + } + kzt_xcb_copy_native_to_guest(lease->native, lease->guest); + kzt_xcb_connection_lease_unlock_mirror(lease); + kzt_xcb_call_cancel_restore(old_cancel_state); + return 0; +} + +void *align_xcb_connection(void *guest) +{ + kzt_xcb_connection_map_t *map; + kzt_xcb_connection_lease_t lease = { 0 }; + + if (!guest || !my_context || + !(map = my_context->kzt_xcb_connection_map)) { + printf_kzt_registry_diagnostics( + "kzt_xcb_guard schema=1 phase=align guest=%p map=%p " + "result=FALLBACK reason=input\n", + guest, my_context ? (void *)my_context->kzt_xcb_connection_map : + NULL); + return NULL; + } + if (kzt_xcb_connection_guard_acquire(map, guest, &lease) != 0) { + printf_kzt_registry_diagnostics( + "kzt_xcb_guard schema=1 phase=align guest=%p map=%p " + "result=FALLBACK reason=unknown_connection\n", + guest, (void *)map); + return NULL; + } + printf_kzt_registry_diagnostics( + "kzt_xcb_guard schema=1 phase=align guest=%p native=%p map=%p " + "result=TRACKED_LEASE\n", + guest, lease.native, (void *)map); + if (kzt_xcb_mirror_guest_to_native(&lease) != 0) { + (void)kzt_xcb_connection_guard_release( + map, lease.native, lease.guest); + return NULL; + } + return lease.native; +} + +void unalign_xcb_connection(void *native, void *guest) +{ + kzt_xcb_connection_map_t *map; + kzt_xcb_connection_lease_t lease = { 0 }; + + if (!native || !guest || !my_context || + !(map = my_context->kzt_xcb_connection_map)) { return; - // find it - for(int i=0; ikzt_xcb_connection_map)) { + return NULL; + } + proposed_guest = calloc(1, sizeof(*proposed_guest)); + if (!proposed_guest) { + return NULL; + } + kzt_xcb_copy_native_to_guest(native, proposed_guest); + result = kzt_xcb_connection_map_register( + map, native, proposed_guest, &canonical_guest, &generation); + if (result == KZT_XCB_CONNECTION_MAP_ADDED) { + return canonical_guest; + } + free(proposed_guest); + if (result == KZT_XCB_CONNECTION_MAP_UNCHANGED) { + kzt_xcb_connection_lease_t lease = { 0 }; + int old_cancel_state = kzt_xcb_call_cancel_disable(); + + if (kzt_xcb_connection_map_acquire_by_native( + map, native, &lease) == 0) { + if (kzt_xcb_mirror_native_to_guest(&lease) != 0) { + kzt_xcb_connection_map_release_pair( + map, lease.native, lease.guest); + kzt_xcb_call_cancel_restore(old_cancel_state); + return NULL; + } + canonical_guest = lease.guest; + kzt_xcb_connection_map_release_pair( + map, lease.native, lease.guest); + kzt_xcb_call_cancel_restore(old_cancel_state); + return canonical_guest; } + kzt_xcb_call_cancel_restore(old_cancel_state); } - return -1; + return NULL; +} + +int sync_xcb_connection(void *connection) +{ + kzt_xcb_connection_map_t *map; + kzt_xcb_connection_lease_t lease = { 0 }; + int old_cancel_state; + + if (!connection || !my_context || + !(map = my_context->kzt_xcb_connection_map)) { + return -1; + } + old_cancel_state = kzt_xcb_call_cancel_disable(); + if (kzt_xcb_connection_map_acquire_by_native( + map, connection, &lease) != 0 && + kzt_xcb_connection_map_acquire_by_guest( + map, connection, &lease) != 0) { + kzt_xcb_call_cancel_restore(old_cancel_state); + return -1; + } + if (kzt_xcb_mirror_native_to_guest(&lease) != 0) { + kzt_xcb_connection_map_release_pair( + map, lease.native, lease.guest); + kzt_xcb_call_cancel_restore(old_cancel_state); + return -1; + } + kzt_xcb_connection_map_release_pair(map, lease.native, lease.guest); + kzt_xcb_call_cancel_restore(old_cancel_state); + return 0; +} + +int begin_xcb_connection_disconnect( + void *guest, kzt_xcb_connection_lease_t *lease) +{ + if (!guest || !lease || !my_context || + !my_context->kzt_xcb_connection_map) { + return -1; + } + return kzt_xcb_connection_map_begin_remove_by_guest( + my_context->kzt_xcb_connection_map, guest, lease); +} + +int begin_xcb_connection_disconnect_native( + void *native, kzt_xcb_connection_lease_t *lease) +{ + if (!native || !lease || !my_context || + !my_context->kzt_xcb_connection_map) { + return -1; + } + return kzt_xcb_connection_map_begin_remove_by_native( + my_context->kzt_xcb_connection_map, native, lease); +} + +void finish_xcb_connection_disconnect(kzt_xcb_connection_lease_t *lease) +{ + kzt_xcb_connection_map_finish_remove(lease); } static void LoadEnvPath(path_collection_t *col, const char* defpath, const char* env) { @@ -2149,7 +2336,6 @@ static void init_main_elf(elfheader_t* elf_header,int fd, uintptr_t load_addr, AddElfHeader(my_context, elf_header); elf_header->latx_type = LATX_ELF_TYPE_MAIN; ElfHeadReFix(elf_header, load_addr); - collectX86free(elf_header); if (CalcLoadAddrNative(elf_header, align)) { printf_log(LOG_INFO, "Error: reading elf header of %s\n", my_context->argv[0]); close(fd); @@ -2169,12 +2355,15 @@ static void init_main_elf(elfheader_t* elf_header,int fd, uintptr_t load_addr, ResetSpecialCaseMainElf(elf_header); } int wine_option_kzt; -int kzt_init(char** argv, int argc,char** target_argv, int target_argc, - struct linux_binprm* bprm) { +int kzt_init(CPUX86State *env, char** argv, int argc, char** target_argv, + int target_argc, struct linux_binprm* bprm) { + kzt_guest_dl_entries_t fallback = { 0 }; + if (!option_kzt && !wine_option_kzt) { return -1; } my_context = NewBox64Context(bprm->argc); + env->kzt_runtime_context = my_context; if (option_kzt && info->interpreter_path) { elf_header = LoadFromNative(bprm, info); } @@ -2199,6 +2388,10 @@ int kzt_init(char** argv, int argc,char** target_argv, int target_argc, if(elf_header != NULL){ init_main_elf(elf_header, bprm->exec_fd, info1.load_addr, info->alignment); } + if (!kzt_guest_dl_init_entries(my_context, &fallback)) { + printf_log(LOG_INFO, + "KZT: guest runtime entry initialization is unavailable\n"); + } return 0; } @@ -2210,80 +2403,6 @@ struct x86_ld_info { uintptr_t r_debug_addr; #endif }; -static struct x86_ld_info * ld_info = NULL; -#ifdef CONFIG_LATX_KZT -static kzt_loader_event_hook_t kzt_loader_event_hook; -#endif -extern void* x86free; -extern void* x86realloc; -extern void* x86pthread_setcanceltype; -static int x64free_fini = 0; -//before _init, free possibly be call.so x86free must be refleshed everytime. -int collectX86free(elfheader_t* h) -{ - if (x64free_fini) { - return 0; - } - int cnt; - Elf64_Rela *rela; - void* found_malloc = NULL; - void* found_free = NULL; - void* found_realloc = NULL; - struct malloc_map * m; - Elf64_Sym *sym = NULL; - for (size_t i=0; inumDynSym; ++i) { - sym = h->DynSym+i; - if (h->DynSym[i].st_shndx != SHN_UNDEF && sym->st_value) { - const char * symname = h->DynStr+sym->st_name; - if (!strcmp(symname, "malloc")) { - found_malloc = (void*)sym->st_value+h->delta; - printf_log(LOG_DEBUG, "latx x86malloc=%p type=0x%x from %s\n", x86free, ELF64_ST_TYPE(sym->st_info), h->path); - if (found_free && found_realloc) { - goto found; - } - } else if (!strcmp(symname, "free")) { - found_free = (void*)sym->st_value+h->delta; - printf_log(LOG_DEBUG, "latx x86free=%p type=0x%x from %s\n", x86free, ELF64_ST_TYPE(sym->st_info), h->path); - if (found_malloc && found_realloc) { - goto found; - } - } else if (!strcmp(symname, "realloc")) { - found_realloc = (void*)sym->st_value+h->delta; - printf_log(LOG_DEBUG, "latx x86realloc=%p type=0x%x from %s\n", x86realloc, ELF64_ST_TYPE(sym->st_info), h->path); - if (found_malloc && found_free) { - goto found; - } - } - } - } - return -1; -found: - /* - * If the plt of this elf file has free, __libc_free, __free jump_slot, this jump_slot will be rewrited by kzt bridge, a moment later. - * So this file should be skiped. If do not do this, when x86free be called, Target exe will fall into dead loop. - */ - cnt = h->pltsz / h->pltent; - rela = (Elf64_Rela *)(h->jmprel + h->delta); - for (int i=0; iDynSym[ELF64_R_SYM(rela[i].r_info)]; - const char* symname = SymName(h, sym); - if (!strcmp(symname, "free") || - !strcmp(symname, "__libc_free") || - !strcmp(symname, "__free")) { - return -1; - } - } - } - m = malloc(sizeof(struct malloc_map)); - m->mallocp = found_malloc; - m->freep = found_free; - m->reallocp = found_realloc; - m->h = h; - AddMallocMap(my_context, m); - return 0; -} #ifdef CONFIG_LATX_KZT static int kzt_callback_read_memory(uintptr_t guest_addr, void *dst, @@ -2312,61 +2431,86 @@ static int kzt_callback_read_memory(uintptr_t guest_addr, #endif #ifdef CONFIG_LATX_KZT +typedef struct kzt_tb_callback_scope { + box64context_t *context; + const kzt_guest_library_loader_scope_t *loader_scope; +} kzt_tb_callback_scope_t; + static int kzt_tb_callback_materialize_binding(uintptr_t link_map_addr, void *opaque) { - const kzt_guest_library_loader_scope_t *loader_scope = opaque; - struct link_map_x64 *link_map = (struct link_map_x64 *)link_map_addr; + const kzt_tb_callback_scope_t *scope = opaque; + box64context_t *context = scope ? scope->context : NULL; + const kzt_guest_library_loader_scope_t *loader_scope = + scope ? scope->loader_scope : NULL; + kzt_guest_registry_address_match_t match = { 0 }; kzt_guest_wrapper_source_proof_t source_proof = { 0 }; const char *name; const char *basename; library_t *library = NULL; + kzt_guest_library_binding_result_t binding_result; int loader_scope_active; - if (!link_map || !link_map->l_name || !link_map->l_name[0]) { + if (!context || !link_map_addr || + kzt_guest_registry_find_live_object( + KztGuestRegistryForContext(context), link_map_addr, &match) != 0 || + match.match_count != 1 || + match.path_status != KZT_GUEST_FIELD_OK) { + return -1; + } + if (!match.path[0]) { return 0; } - name = link_map->l_name; + name = match.path; basename = strrchr(name, '/'); basename = basename ? basename + 1 : name; - if (!basename[0] || !FindLibIsWrapped((char *)basename) || - kzt_guest_library_wrapper_source_acquire( - my_context, link_map_addr, name, basename, &source_proof) != 0) { + if (!basename[0] || !FindLibIsWrapped((char *)basename)) { return 0; } + if (kzt_guest_library_wrapper_source_acquire( + context, link_map_addr, name, basename, &source_proof) != 0) { + return -1; + } (void)AddNeededLibWithLibrary( - my_context->maplib, &my_context->neededlibs, NULL, 0, 1, - basename, my_context, &library); + context->maplib, &context->neededlibs, NULL, 0, 1, + basename, context, &library); if (!library) { kzt_guest_library_wrapper_source_release(&source_proof); - return 0; + return -1; } loader_scope_active = loader_scope && loader_scope->bindings && loader_scope->identity && loader_scope->cookie; if (loader_scope_active) { - kzt_guest_library_note_loader_pair_pending( - my_context, loader_scope, link_map_addr, library, &source_proof); + binding_result = kzt_guest_library_note_loader_pair_pending( + context, loader_scope, link_map_addr, library, &source_proof); } else { - kzt_guest_library_note_loader_pair( - my_context, link_map_addr, library, &source_proof); + binding_result = kzt_guest_library_note_loader_pair( + context, link_map_addr, library, &source_proof); } kzt_guest_library_wrapper_source_release(&source_proof); - return 0; + return binding_result == KZT_GUEST_LIBRARY_BINDING_ADDED || + binding_result == KZT_GUEST_LIBRARY_BINDING_UNCHANGED || + binding_result == KZT_GUEST_LIBRARY_BINDING_PENDING + ? 0 + : -1; } static int kzt_tb_callback_per_object_got_plt(uintptr_t link_map_addr, void *opaque) { + const kzt_tb_callback_scope_t *scope = opaque; + box64context_t *context = scope ? scope->context : NULL; kzt_per_object_got_plt_request_t request = { - .registry = KztGuestRegistryForContext(my_context), + .registry = KztGuestRegistryForContext(context), .link_map_addr = link_map_addr, .apply = KztPerObjectGotPltWrite, - .opaque = my_context, + .opaque = context, }; kzt_per_object_got_plt_result_t result = { 0 }; - (void)kzt_tb_callback_materialize_binding(link_map_addr, opaque); - if (kzt_per_object_got_plt_apply(&request, &result) != 0) { + if (kzt_tb_callback_materialize_binding(link_map_addr, opaque) != 0 || + kzt_per_object_got_plt_apply(&request, &result) != 0 || + result.status == KZT_PER_OBJECT_GOT_PLT_FAIL_OPEN) { return -1; } return 0; @@ -2390,12 +2534,13 @@ static int kzt_tb_callback_prebind_invalidate( #endif -static void kzt_tb_callback_consume(CPUX86State *env, +static void kzt_tb_callback_consume(box64context_t *context, + CPUX86State *env, const kzt_loader_event_t *event) { #ifdef CONFIG_LATX_KZT uintptr_t link_map_addr = event ? event->link_map_addr : 0; - kzt_guest_registry_t *registry = KztGuestRegistryForContext(my_context); + kzt_guest_registry_t *registry; int diagnostics_enabled = kzt_registry_diagnostics_enabled(); int lifecycle_scoped = env->kzt_guest_library_loader_scope.bindings && @@ -2419,15 +2564,25 @@ static void kzt_tb_callback_consume(CPUX86State *env, uintptr_t predecessor = 0; kzt_observation_adapter_result_t observation_result; int main_namespace; + kzt_tb_callback_scope_t callback_scope; + + if (!context || !env) { + return; + } + registry = KztGuestRegistryForContext(context); + callback_scope = (kzt_tb_callback_scope_t) { + .context = context, + .loader_scope = &env->kzt_guest_library_loader_scope, + }; (void)kzt_guest_registry_context_get_main_namespace_head( - &my_context->kzt_guest_registry_context, &confirmed_main_head); + &context->kzt_guest_registry_context, &confirmed_main_head); if (kzt_guest_link_map_read_identity( link_map_addr, &reader_ops, &object_identity) != 0) { main_namespace = -1; } else if (confirmed_main_head && kzt_guest_registry_context_has_main_namespace_evidence( - &my_context->kzt_guest_registry_context, registry, + &context->kzt_guest_registry_context, registry, link_map_addr, object_identity.load_bias, object_identity.dynamic_addr)) { main_namespace = 1; @@ -2448,8 +2603,8 @@ static void kzt_tb_callback_consume(CPUX86State *env, } if (main_namespace == 1 && !confirmed_main_head && kzt_guest_registry_context_confirm_main_namespace_head( - &my_context->kzt_guest_registry_context, - &my_context->mutex_lock, namespace_head) != 0) { + &context->kzt_guest_registry_context, + &context->mutex_lock, namespace_head) != 0) { main_namespace = -1; } kzt_observation_adapter_request_t request = { @@ -2457,17 +2612,17 @@ static void kzt_tb_callback_consume(CPUX86State *env, .diagnostics_enabled = diagnostics_enabled, .link_map_addr = link_map_addr, .registry = registry, - .library_bindings = KztGuestLibraryBindingsForContext(my_context), - .lazy_prebind_scope = KztLazyPrebindScopeForContext(my_context), + .library_bindings = KztGuestLibraryBindingsForContext(context), + .lazy_prebind_scope = KztLazyPrebindScopeForContext(context), .loader_scope = &env->kzt_guest_library_loader_scope, .reader_ops = &reader_ops, .reuse_complete_dynamic_view = 1, .namespace_id_present = main_namespace == 1, .namespace_id = 0, .prebind_invalidate = kzt_tb_callback_prebind_invalidate, - .prebind_invalidate_opaque = my_context, + .prebind_invalidate_opaque = context, .per_object_flow = kzt_tb_callback_per_object_got_plt, - .per_object_opaque = &env->kzt_guest_library_loader_scope, + .per_object_opaque = &callback_scope, .legacy_flow = NULL, .diagnostic = kzt_callback_diagnostic_log, .diagnostic_opaque = registry, @@ -2494,9 +2649,13 @@ static void kzt_tb_callback_consume(CPUX86State *env, observation_result == KZT_OBSERVATION_ADAPTER_UPDATED) { if (lifecycle_scoped) { env->kzt_guest_library_loader_scope.prebind_refresh_pending = 1; + } else if (!kzt_loader_lifecycle_runtime_healthy(context)) { + __atomic_store_n( + &context->kzt_lazy_prebind_refresh_pending, 1, + __ATOMIC_RELEASE); } else { kzt_production_lazy_prebind_refresh( - my_context, kzt_tb_callback_pretranslate_target, env); + context, kzt_tb_callback_pretranslate_target, env); } } if (diagnostics_enabled && event) { @@ -2516,6 +2675,7 @@ static void kzt_tb_callback_consume(CPUX86State *env, consumed_ns - event->published_ns : 0)); } #else + (void)context; (void)env; (void)event; #endif @@ -2524,25 +2684,29 @@ static void kzt_tb_callback_consume(CPUX86State *env, static void kzt_tb_callback(CPUX86State *env) { #ifdef CONFIG_LATX_KZT + box64context_t *context = my_context; + kzt_loader_event_hook_t *hook = + context ? &context->kzt_loader_event_hook : NULL; + struct x86_ld_info *ld_info = + context ? context->kzt_loader_bridge_info : NULL; kzt_loader_event_t event; uintptr_t link_map_addr; - if (!env || !ld_info) { + if (!env || !ld_info || !hook) { return; } link_map_addr = env->regs[R_EAX + ld_info->reg]; - if (kzt_loader_event_hook_publish(&kzt_loader_event_hook, link_map_addr, - &event) != 0) { + if (kzt_loader_event_hook_publish(hook, link_map_addr, &event) != 0) { return; } printf_kzt_registry_diagnostics( "kzt_loader_event schema=1 phase=published build_id=%s sequence=%lu " "link_map=0x%lx publish_ns=%lu\n", - kzt_loader_event_hook.build_id, (unsigned long)event.sequence, + hook->build_id, (unsigned long)event.sequence, (unsigned long)event.link_map_addr, (unsigned long)event.published_ns); - kzt_tb_callback_consume(env, &event); + kzt_tb_callback_consume(context, env, &event); #else - kzt_tb_callback_consume(env, NULL); + kzt_tb_callback_consume(my_context, env, NULL); #endif } @@ -2604,30 +2768,37 @@ static int kzt_tb_cancel_lifecycle_unload( return kzt_guest_dl_api_cancel_unload(opaque, &unload); } -static void kzt_tb_publish_lifecycle_unload( +static int kzt_tb_publish_lifecycle_unload( const kzt_loader_lifecycle_identity_t *identity, void *opaque) { kzt_guest_loader_identity_t unload; + int result; if (!identity) { - return; + return -1; } unload = (kzt_guest_loader_identity_t) { .link_map_addr = identity->link_map_addr, .generation = identity->generation, .namespace_id = identity->namespace_id, }; + result = kzt_guest_dl_api_publish_unload(opaque, &unload); printf_kzt_registry_diagnostics( "kzt_loader_event schema=1 phase=unload link_map=0x%lx " "generation=%lu namespace=0x%lx result=%d\n", (unsigned long)unload.link_map_addr, unload.generation, - (unsigned long)unload.namespace_id, - kzt_guest_dl_api_publish_unload(opaque, &unload)); + (unsigned long)unload.namespace_id, result); + return result; } static void kzt_tb_debug_state_callback(CPUX86State *env) { + box64context_t *context = my_context; + kzt_loader_event_hook_t *hook = + context ? &context->kzt_loader_event_hook : NULL; + struct x86_ld_info *ld_info = + context ? context->kzt_loader_bridge_info : NULL; kzt_loader_lifecycle_snapshot_t snapshot = { .result = KZT_LOADER_LIFECYCLE_SNAPSHOT_INVALID_INPUT, }; @@ -2635,8 +2806,8 @@ static void kzt_tb_debug_state_callback(CPUX86State *env) int publication_result; (void)env; - registry = KztGuestRegistryForContext(my_context); - if (!registry || + registry = KztGuestRegistryForContext(context); + if (!hook || !registry || !ld_info || !ld_info->r_debug_addr || kzt_loader_lifecycle_snapshot_capture( registry, ld_info->r_debug_addr, @@ -2650,11 +2821,11 @@ static void kzt_tb_debug_state_callback(CPUX86State *env) "result=FAIL_OPEN reason=%s\n", kzt_loader_lifecycle_snapshot_result_name(snapshot.result)); (void)kzt_loader_event_hook_publish_lifecycle( - &kzt_loader_event_hook, KZT_LOADER_DEBUG_ADD, NULL, 0, + hook, (kzt_loader_debug_state_t)-1, NULL, 0, kzt_tb_resolve_lifecycle_identity, kzt_tb_prepare_lifecycle_unload, kzt_tb_cancel_lifecycle_unload, - kzt_tb_publish_lifecycle_unload, my_context); + kzt_tb_publish_lifecycle_unload, context); kzt_loader_lifecycle_snapshot_release(&snapshot); return; } @@ -2663,19 +2834,25 @@ static void kzt_tb_debug_state_callback(CPUX86State *env) "maps=%lu result=OK\n", snapshot.state, (unsigned long)snapshot.live_map_count); publication_result = kzt_loader_event_hook_publish_lifecycle( - &kzt_loader_event_hook, snapshot.state, snapshot.live_maps, + hook, snapshot.state, snapshot.live_maps, snapshot.live_map_count, kzt_tb_resolve_lifecycle_identity, kzt_tb_prepare_lifecycle_unload, kzt_tb_cancel_lifecycle_unload, - kzt_tb_publish_lifecycle_unload, my_context); + kzt_tb_publish_lifecycle_unload, context); if (publication_result != 0) { printf_kzt_registry_diagnostics( "kzt_loader_event schema=1 phase=lifecycle-publication " "result=FAIL_OPEN reason=%s\n", kzt_loader_lifecycle_result_name( kzt_loader_event_hook_lifecycle_result( - &kzt_loader_event_hook))); + hook))); + } else if (snapshot.state == KZT_LOADER_DEBUG_CONSISTENT && + __atomic_exchange_n( + &context->kzt_lazy_prebind_refresh_pending, 0, + __ATOMIC_ACQ_REL)) { + kzt_production_lazy_prebind_refresh( + context, kzt_tb_callback_pretranslate_target, env); } kzt_loader_lifecycle_snapshot_release(&snapshot); } @@ -2900,19 +3077,22 @@ static struct x86_ld_info *find_ld_bridge( return NULL; } #ifdef CONFIG_LATX_KZT + kzt_loader_event_layout_t loader_layout = { 0 }; + if (kzt_loader_event_hook_read_build_id(real_dl_file, build_id) != 0) { return NULL; } + (void)kzt_loader_event_hook_lookup_layout(build_id, &loader_layout); #else (void)build_id; #endif size_t file_len = st.st_size; #ifdef CONFIG_LATX_KZT - uintptr_t debug_state_addr = (uintptr_t)ld_start + - KZT_LOADER_EVENT_HOOK_DEBUG_STATE_OFFSET; - uintptr_t r_debug_addr = (uintptr_t)ld_start + - KZT_LOADER_EVENT_HOOK_R_DEBUG_OFFSET; + uintptr_t debug_state_addr = loader_layout.debug_state_offset + ? (uintptr_t)ld_start + loader_layout.debug_state_offset : 0; + uintptr_t r_debug_addr = loader_layout.r_debug_offset + ? (uintptr_t)ld_start + loader_layout.r_debug_offset : 0; #endif uint8_t* fix_addr = (uint8_t *)(ld_start+0xbc15); @@ -2922,7 +3102,8 @@ static struct x86_ld_info *find_ld_bridge( ret->reg = (*(fix_addr+ 2)) & 0xf; #ifdef CONFIG_LATX_KZT ret->debug_state_addr = - *(uint8_t *)debug_state_addr == 0xc3 ? debug_state_addr : 0; + debug_state_addr && *(uint8_t *)debug_state_addr == 0xc3 + ? debug_state_addr : 0; ret->r_debug_addr = ret->debug_state_addr ? r_debug_addr : 0; #endif printf_log(LOG_DEBUG, "debug find ld so 0x%lx reg = %d\n", ret->addr, ret->reg); @@ -2935,7 +3116,8 @@ static struct x86_ld_info *find_ld_bridge( ret->reg = (*(fix_addr+ 2)) & 0xf; #ifdef CONFIG_LATX_KZT ret->debug_state_addr = - *(uint8_t *)debug_state_addr == 0xc3 ? debug_state_addr : 0; + debug_state_addr && *(uint8_t *)debug_state_addr == 0xc3 + ? debug_state_addr : 0; ret->r_debug_addr = ret->debug_state_addr ? r_debug_addr : 0; #endif printf_log(LOG_DEBUG, "debug find ld so 0x%lx reg = %d\n", ret->addr, ret->reg); @@ -2948,7 +3130,8 @@ static struct x86_ld_info *find_ld_bridge( ret->reg = (*(fix_addr+ 2)) & 0xf; #ifdef CONFIG_LATX_KZT ret->debug_state_addr = - *(uint8_t *)debug_state_addr == 0xc3 ? debug_state_addr : 0; + debug_state_addr && *(uint8_t *)debug_state_addr == 0xc3 + ? debug_state_addr : 0; ret->r_debug_addr = ret->debug_state_addr ? r_debug_addr : 0; #endif printf_log(LOG_DEBUG, "debug find ld so 0x%lx reg = %d\n", ret->addr, ret->reg); @@ -2961,7 +3144,8 @@ static struct x86_ld_info *find_ld_bridge( ret->reg = (*(fix_addr+ 2)) & 0xf; #ifdef CONFIG_LATX_KZT ret->debug_state_addr = - *(uint8_t *)debug_state_addr == 0xc3 ? debug_state_addr : 0; + debug_state_addr && *(uint8_t *)debug_state_addr == 0xc3 + ? debug_state_addr : 0; ret->r_debug_addr = ret->debug_state_addr ? r_debug_addr : 0; #endif printf_log(LOG_DEBUG, "debug find ld so 0x%lx reg = %d\n", ret->addr, ret->reg); @@ -2975,7 +3159,8 @@ static struct x86_ld_info *find_ld_bridge( ret->reg = (*(fix_addr+ 2)) & 0xf; #ifdef CONFIG_LATX_KZT ret->debug_state_addr = - *(uint8_t *)debug_state_addr == 0xc3 ? debug_state_addr : 0; + debug_state_addr && *(uint8_t *)debug_state_addr == 0xc3 + ? debug_state_addr : 0; ret->r_debug_addr = ret->debug_state_addr ? r_debug_addr : 0; #endif printf_log(LOG_DEBUG, "debug find ld so 0x%lx reg = %d\n", ret->addr, ret->reg); @@ -2989,7 +3174,8 @@ static struct x86_ld_info *find_ld_bridge( ret->reg = (*(fix_addr+ 2)) & 0xf; #ifdef CONFIG_LATX_KZT ret->debug_state_addr = - *(uint8_t *)debug_state_addr == 0xc3 ? debug_state_addr : 0; + debug_state_addr && *(uint8_t *)debug_state_addr == 0xc3 + ? debug_state_addr : 0; ret->r_debug_addr = ret->debug_state_addr ? r_debug_addr : 0; #endif printf_log(LOG_DEBUG, "debug find ld so 0x%lx reg = %d\n", ret->addr, ret->reg); @@ -2999,7 +3185,8 @@ static struct x86_ld_info *find_ld_bridge( #ifdef CONFIG_LATX_KZT if (ret) { ret->debug_state_addr = - *(uint8_t *)debug_state_addr == 0xc3 ? debug_state_addr : 0; + debug_state_addr && *(uint8_t *)debug_state_addr == 0xc3 + ? debug_state_addr : 0; ret->r_debug_addr = ret->debug_state_addr ? r_debug_addr : 0; } #endif @@ -3008,20 +3195,26 @@ static struct x86_ld_info *find_ld_bridge( void init_tb_callback_bridge(CPUState *cpu, void* info) { - static uint32 jmpinst_ld [2] = {0}; + box64context_t *context = my_context; + struct x86_ld_info *ld_info = + context ? context->kzt_loader_bridge_info : NULL; #ifdef CONFIG_LATX_KZT - static uint32 jmpinst_debug_state[2] = { 0 }; + kzt_loader_event_hook_t *hook = + context ? &context->kzt_loader_event_hook : NULL; #endif + if (!context) { + return; + } if (!ld_info) { char build_id[KZT_LOADER_EVENT_HOOK_BUILD_ID_SIZE] = { 0 }; struct x86_ld_info *candidate = find_ld_bridge(info, build_id); #ifdef CONFIG_LATX_KZT - if (my_context) { - my_context->kzt_guest_scope_layout = + if (context) { + context->kzt_guest_scope_layout = KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; } - if (!candidate || - kzt_loader_event_hook_install(&kzt_loader_event_hook, + if (!hook || !candidate || + kzt_loader_event_hook_install(hook, build_id[0] ? build_id : NULL, candidate ? candidate->addr : 0, candidate ? candidate->reg : 0, @@ -3030,24 +3223,26 @@ void init_tb_callback_bridge(CPUState *cpu, void* info) printf_kzt_registry_diagnostics( "kzt_loader_event schema=1 phase=install result=%s build_id=%s " "rollback=disabled\n", - kzt_loader_event_hook_result_name(kzt_loader_event_hook.result), + hook ? kzt_loader_event_hook_result_name(hook->result) + : "NO_CONTEXT", build_id[0] ? build_id : "(unavailable)"); free(candidate); return; } - if (my_context) { - my_context->kzt_guest_scope_layout = - kzt_loader_event_hook_scope_layout(&kzt_loader_event_hook); + if (context) { + context->kzt_guest_scope_layout = + kzt_loader_event_hook_scope_layout(hook); } if (!candidate->debug_state_addr || !candidate->r_debug_addr || kzt_loader_event_hook_enable_lifecycle( - &kzt_loader_event_hook, candidate->debug_state_addr, + hook, candidate->debug_state_addr, candidate->r_debug_addr) != 0) { printf_kzt_registry_diagnostics( "kzt_loader_event schema=1 phase=lifecycle-install " "result=FAIL_OPEN build_id=%s\n", build_id); } #endif + context->kzt_loader_bridge_info = candidate; ld_info = candidate; #ifdef CONFIG_LATX_KZT printf_kzt_registry_diagnostics( @@ -3063,13 +3258,15 @@ void init_tb_callback_bridge(CPUState *cpu, void* info) } CPUArchState *env = cpu->env_ptr; target_ulong eip = env->eip; - kzt_add_fucn_by_addr((uint32 *)&jmpinst_ld, cpu, ld_info->addr, target_latx_ld_callback, kzt_tb_callback); + kzt_add_fucn_by_addr( + context->kzt_loader_callback_original, cpu, ld_info->addr, + target_latx_ld_callback, kzt_tb_callback); #ifdef CONFIG_LATX_KZT - if (__atomic_load_n(&kzt_loader_event_hook.lifecycle_enabled, + if (hook && __atomic_load_n(&hook->lifecycle_enabled, __ATOMIC_ACQUIRE)) { kzt_add_fucn_by_addr( - (uint32 *)&jmpinst_debug_state, cpu, - kzt_loader_event_hook.debug_state_addr, + context->kzt_loader_debug_state_original, cpu, + hook->debug_state_addr, target_latx_ld_callback, kzt_tb_debug_state_callback); } #endif @@ -3191,31 +3388,18 @@ void kzt_wine_bridge(abi_ulong start, int fd) } } -void kzt_wine_init_x86(box64context_t *context, uintptr_t guest_dlsym) -{ - if (!context || !option_kzt || !latx_wine || context->mallocmapsize) { - return; - } - struct malloc_map* m = malloc(sizeof(struct malloc_map)); - m->mallocp = (void *)(uintptr_t)RunFunctionWithState(guest_dlsym, 2, - 0, "malloc"); - ; - m->freep = (void *)(uintptr_t)RunFunctionWithState(guest_dlsym, 2, - 0, "free"); - m->reallocp = (void *)(uintptr_t)RunFunctionWithState(guest_dlsym, 2, - 0, "realloc"); - m->h = wine_elf_header; - AddMallocMap(context, m); - x86free = m->freep; - x86realloc = m->reallocp; -} - -elfheader_t* tryLoadElfFromFile(const char* name) +elfheader_t* tryLoadElfFromFileForContext( + box64context_t *context, const char *name) { elfheader_t* h = NULL; - char *tmp = ResolveFile(name, &my_context->box64_ld_lib); + char *tmp; uintptr_t load_addr; + if (!context || !name) { + return NULL; + } + tmp = ResolveFile(name, &context->box64_ld_lib); + if (tmp && FileExist(tmp, IS_FILE)) { FILE *f = fopen(tmp, "rb"); if (!f) { @@ -3244,6 +3428,11 @@ elfheader_t* tryLoadElfFromFile(const char* name) return h; } +elfheader_t* tryLoadElfFromFile(const char* name) +{ + return tryLoadElfFromFileForContext(my_context, name); +} + void freeElfFromFile(elfheader_t **header) { FILE *file; diff --git a/target/i386/latx/context/wrappedlibc.c b/target/i386/latx/context/wrappedlibc.c index ea53ec9c46b..0282e8dfb82 100644 --- a/target/i386/latx/context/wrappedlibc.c +++ b/target/i386/latx/context/wrappedlibc.c @@ -87,9 +87,6 @@ const char* libcName = #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-prototypes" -void* x86free; -void* x86realloc; -void* x86pthread_setcanceltype; typedef int (*iFi_t)(int); typedef int (*iFp_t)(void*); typedef int (*iFL_t)(unsigned long); @@ -3657,16 +3654,20 @@ static uintptr_t kzt_guest_dlerror_entry_slow(box64context_t *context) static char *kzt_guest_dlerror_slow_path( box64context_t *context, CPUX86State *cpu, kzt_guest_dlerror_state_t *error_state, - uintptr_t guest_dlerror) __attribute__((noinline, cold)); + uintptr_t guest_dlerror, + int guest_route_may_have_pending_error) __attribute__((noinline, cold)); static char *kzt_guest_dlerror_slow_path( box64context_t *context, CPUX86State *cpu, - kzt_guest_dlerror_state_t *error_state, uintptr_t guest_dlerror) + kzt_guest_dlerror_state_t *error_state, uintptr_t guest_dlerror, + int guest_route_may_have_pending_error) { kzt_guest_dlerror_result_t result; if (kzt_guest_dl_api_dlerror_needs_slow_path(error_state)) { - result = kzt_guest_dl_api_dlerror(error_state, guest_dlerror); + result = kzt_guest_dl_api_dlerror( + error_state, guest_dlerror, + guest_route_may_have_pending_error); if (!result.forward_to_guest_caller) { return result.value; } @@ -3688,6 +3689,8 @@ static char *kzt_guest_dlerror_slow_path( char* my_dlerror(void) { + int guest_loader_route = my_context && __atomic_load_n( + &my_context->kzt_guest_loader_route_present, __ATOMIC_ACQUIRE); #if defined(__loongarch__) register char *fast_result __asm__("r4") = #else @@ -3697,14 +3700,18 @@ char* my_dlerror(void) /* Keep the clean sentinel in the return register across the cold test. */ __asm__ volatile("" : "+r"(fast_result)); - if (fast_result) { + if (fast_result || guest_loader_route) { dlprivate_t *dl = my_context->dlprivate; __MY_CPU; kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); uintptr_t guest_dlerror = error_state->guest_dlerror_entry; + if (guest_loader_route) { + kzt_guest_dl_api_set_slow_required(error_state, 1); + } return kzt_guest_dlerror_slow_path( - my_context, cpu, error_state, guest_dlerror); + my_context, cpu, error_state, guest_dlerror, + guest_loader_route); } return fast_result; } diff --git a/target/i386/latx/context/wrappedlibdl.c b/target/i386/latx/context/wrappedlibdl.c index ce99629a738..fb58dd64889 100644 --- a/target/i386/latx/context/wrappedlibdl.c +++ b/target/i386/latx/context/wrappedlibdl.c @@ -184,16 +184,20 @@ static uintptr_t kzt_guest_dlerror_entry_slow(box64context_t *context) static char *kzt_guest_dlerror_slow_path( box64context_t *context, CPUX86State *cpu, kzt_guest_dlerror_state_t *error_state, - uintptr_t guest_dlerror) __attribute__((noinline, cold)); + uintptr_t guest_dlerror, + int guest_route_may_have_pending_error) __attribute__((noinline, cold)); static char *kzt_guest_dlerror_slow_path( box64context_t *context, CPUX86State *cpu, - kzt_guest_dlerror_state_t *error_state, uintptr_t guest_dlerror) + kzt_guest_dlerror_state_t *error_state, uintptr_t guest_dlerror, + int guest_route_may_have_pending_error) { kzt_guest_dlerror_result_t result; if (kzt_guest_dl_api_dlerror_needs_slow_path(error_state)) { - result = kzt_guest_dl_api_dlerror(error_state, guest_dlerror); + result = kzt_guest_dl_api_dlerror( + error_state, guest_dlerror, + guest_route_may_have_pending_error); if (!result.forward_to_guest_caller) { return result.value; } @@ -215,6 +219,8 @@ static char *kzt_guest_dlerror_slow_path( char* my_dlerror(void) { + int guest_loader_route = my_context && __atomic_load_n( + &my_context->kzt_guest_loader_route_present, __ATOMIC_ACQUIRE); #if defined(__loongarch__) register char *fast_result __asm__("r4") = #else @@ -224,14 +230,18 @@ char* my_dlerror(void) /* Keep the clean sentinel in the return register across the cold test. */ __asm__ volatile("" : "+r"(fast_result)); - if (fast_result) { + if (fast_result || guest_loader_route) { dlprivate_t *dl = my_context->dlprivate; __MY_CPU; kzt_guest_dlerror_state_t *error_state = DLERROR_STATE(cpu, dl); uintptr_t guest_dlerror = error_state->guest_dlerror_entry; + if (guest_loader_route) { + kzt_guest_dl_api_set_slow_required(error_state, 1); + } return kzt_guest_dlerror_slow_path( - my_context, cpu, error_state, guest_dlerror); + my_context, cpu, error_state, guest_dlerror, + guest_loader_route); } return fast_result; } diff --git a/target/i386/latx/context/wrappedlibx11.c b/target/i386/latx/context/wrappedlibx11.c index f75e7e7a820..0db6ce7b700 100644 --- a/target/i386/latx/context/wrappedlibx11.c +++ b/target/i386/latx/context/wrappedlibx11.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "wrappedlibs.h" @@ -21,6 +22,7 @@ #include "callback.h" #include "librarian.h" #include "box64context.h" +#include "kzt_guest_cancel_scope.h" #include "myalign.h" #include "wrappertbbridge.h" @@ -1187,7 +1189,6 @@ void sub_image_wrapper(uintptr_t fnc) #undef R_RDX #undef R_RCX #undef R_R8 -extern void * x86free; abi_ulong latx_is_shm(abi_ulong maddr); EXPORT void* my_XCreateImage(void* disp, void* vis, uint32_t depth, int32_t fmt, int32_t off , void* data, uint32_t w, uint32_t h, int32_t pad, int32_t bpl) @@ -1253,11 +1254,34 @@ EXPORT void my_XDestroyImage(void* image) XImage* img = image; if (img->data && (uintptr_t)img->data < reserved_va && !latx_is_shm((abi_ulong)img->data)) { - size_t len = img->bytes_per_line * img->height; - void *la_data = malloc(len); - memcpy(la_data, img->data, len); - lsassert(x86free); - RunFunctionWithState((uintptr_t)x86free ,1, img->data); + kzt_guest_runtime_entry_scope_t guest_free = { 0 }; + size_t len; + void *la_data; + + if (img->bytes_per_line < 0 || img->height < 0 || + (img->height && + (size_t)img->bytes_per_line > SIZE_MAX / (size_t)img->height) || + kzt_guest_runtime_entry_acquire( + my_context, KZT_GUEST_RUNTIME_FREE, &guest_free) != 0) { + printf_log( + LOG_NONE, + "KZT: cannot safely destroy XImage guest-owned data\n"); + abort(); + } + len = (size_t)img->bytes_per_line * (size_t)img->height; + la_data = malloc(len ? len : 1); + if (!la_data) { + kzt_guest_runtime_entry_release(&guest_free); + printf_log( + LOG_NONE, + "KZT: cannot allocate native XImage destruction buffer\n"); + abort(); + } + if (len) { + memcpy(la_data, img->data, len); + } + RunFunctionWithState(guest_free.address, 1, img->data); + kzt_guest_runtime_entry_release(&guest_free); img->data = la_data; } my->XDestroyImage(image); @@ -1454,6 +1478,34 @@ EXPORT void* my_XOpenDisplay(void* d) return ret; } +EXPORT int32_t my_XCloseDisplay(my_XDisplay_t *dpy); +EXPORT int32_t my_XCloseDisplay(my_XDisplay_t *dpy) +{ + kzt_xcb_connection_lease_t lease = { 0 }; + void *native_xcb = dpy && dpy->xcb ? *dpy->xcb : NULL; + int old_cancel_state = PTHREAD_CANCEL_ENABLE; + int tracked = 0; + int32_t result; + + (void)pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancel_state); + if (native_xcb) { + tracked = begin_xcb_connection_disconnect_native( + native_xcb, &lease) == 0; + if (!tracked) { + printf_kzt_registry_diagnostics( + "kzt_xcb_guard schema=1 phase=XCloseDisplay " + "native=%p result=FALLBACK reason=untracked_connection\n", + native_xcb); + } + } + result = my->XCloseDisplay(dpy); + if (tracked) { + finish_xcb_connection_disconnect(&lease); + } + (void)pthread_setcancelstate(old_cancel_state, NULL); + return result; +} + EXPORT void* my_XInternAtom(my_XDisplay_t* dpy, void* name, int32_t onlyIfExists); EXPORT void* my_XInternAtom(my_XDisplay_t* dpy, void* name, int32_t onlyIfExists) { @@ -1503,16 +1555,17 @@ int latx_dpy_xcb_sync(void *v1) return sync_xcb_connection(*dpy->xcb); } -extern void* x86pthread_setcanceltype; EXPORT int32_t my_XNextEvent(void* v1, void* v2); EXPORT int32_t my_XNextEvent(void* v1, void* v2) { - int oldtype; + kzt_guest_cancel_scope_t cancel = { 0 }; int32_t ret; - uint64_t callbackret = RunFunctionWithState((uintptr_t)x86pthread_setcanceltype ,2, PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype); + + pthread_cleanup_push(kzt_guest_cancel_scope_cleanup, &cancel); + kzt_guest_cancel_scope_begin(my_context, &cancel); ret = my->XNextEvent(v1,v2); - callbackret = RunFunctionWithState((uintptr_t)x86pthread_setcanceltype ,2, oldtype, NULL); - (void)callbackret; + kzt_guest_cancel_scope_end(&cancel); + pthread_cleanup_pop(0); return ret; } diff --git a/target/i386/latx/context/wrappedlibxcb.c b/target/i386/latx/context/wrappedlibxcb.c index 68bfdcaaffa..9d8aa914d08 100644 --- a/target/i386/latx/context/wrappedlibxcb.c +++ b/target/i386/latx/context/wrappedlibxcb.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "wrappedlibs.h" @@ -20,6 +21,7 @@ #include "callback.h" #include "librarian.h" #include "box64context.h" +#include "kzt_guest_cancel_scope.h" const char* libxcbName = "libxcb.so.1"; #define LIBNAME libxcb @@ -30,59 +32,87 @@ const char* libxcbName = "libxcb.so.1"; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-prototypes" -extern void* x86pthread_setcanceltype; EXPORT void* my_xcb_wait_for_event(void* v1); EXPORT void* my_xcb_wait_for_event(void* v1) { - int oldtype; + void *native = align_xcb_connection(v1); + kzt_guest_cancel_scope_t cancel = { 0 }; void* ret; - uint64_t callbackret = RunFunctionWithState((uintptr_t)x86pthread_setcanceltype , 2, PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype); - ret = my->xcb_wait_for_event(v1); - callbackret = RunFunctionWithState((uintptr_t)x86pthread_setcanceltype , 2, oldtype, NULL); - (void)callbackret; + + if (!native) + return NULL; + pthread_cleanup_push(kzt_guest_cancel_scope_cleanup, &cancel); + kzt_guest_cancel_scope_begin(my_context, &cancel); + ret = my->xcb_wait_for_event(native); + kzt_guest_cancel_scope_end(&cancel); + pthread_cleanup_pop(0); + unalign_xcb_connection(native, v1); return ret; } EXPORT int32_t my_xcb_flush(void* v1); EXPORT int32_t my_xcb_flush(void* v1) { - int32_t ret = my->xcb_flush(v1); - sync_xcb_connection(v1); + void *native = align_xcb_connection(v1); + int32_t ret; + + if (!native) + return 0; + ret = my->xcb_flush(native); + unalign_xcb_connection(native, v1); return ret; } EXPORT void* my_xcb_wait_for_reply(void* v1, uint32_t v2, void* v3); EXPORT void* my_xcb_wait_for_reply(void* v1, uint32_t v2, void* v3) { - int oldtype; + void *native = align_xcb_connection(v1); + kzt_guest_cancel_scope_t cancel = { 0 }; void* ret; - uint64_t callbackret = RunFunctionWithState((uintptr_t)x86pthread_setcanceltype , 2, PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype); - ret = my->xcb_wait_for_reply(v1, v2, v3); - callbackret = RunFunctionWithState((uintptr_t)x86pthread_setcanceltype , 2, oldtype, NULL); - (void)callbackret; + + if (!native) + return NULL; + pthread_cleanup_push(kzt_guest_cancel_scope_cleanup, &cancel); + kzt_guest_cancel_scope_begin(my_context, &cancel); + ret = my->xcb_wait_for_reply(native, v2, v3); + kzt_guest_cancel_scope_end(&cancel); + pthread_cleanup_pop(0); + unalign_xcb_connection(native, v1); return ret; } EXPORT void* my_xcb_wait_for_reply64(void* v1, uint64_t v2, void* v3); EXPORT void* my_xcb_wait_for_reply64(void* v1, uint64_t v2, void* v3) { - int oldtype; + void *native = align_xcb_connection(v1); + kzt_guest_cancel_scope_t cancel = { 0 }; void* ret; - uint64_t callbackret = RunFunctionWithState((uintptr_t)x86pthread_setcanceltype , 2, PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype); - ret = my->xcb_wait_for_reply64(v1, v2, v3); - callbackret = RunFunctionWithState((uintptr_t)x86pthread_setcanceltype , 2, oldtype, NULL); - (void)callbackret; + + if (!native) + return NULL; + pthread_cleanup_push(kzt_guest_cancel_scope_cleanup, &cancel); + kzt_guest_cancel_scope_begin(my_context, &cancel); + ret = my->xcb_wait_for_reply64(native, v2, v3); + kzt_guest_cancel_scope_end(&cancel); + pthread_cleanup_pop(0); + unalign_xcb_connection(native, v1); return ret; } EXPORT void* my_xcb_wait_for_special_event(void* v1, void* v2); EXPORT void* my_xcb_wait_for_special_event(void* v1, void* v2) { - int oldtype; + void *native = align_xcb_connection(v1); + kzt_guest_cancel_scope_t cancel = { 0 }; void* ret; - uint64_t callbackret = RunFunctionWithState((uintptr_t)x86pthread_setcanceltype , 2, PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype); - ret = my->xcb_wait_for_special_event(v1, v2); - callbackret = RunFunctionWithState((uintptr_t)x86pthread_setcanceltype , 2, oldtype, NULL); - (void)callbackret; + + if (!native) + return NULL; + pthread_cleanup_push(kzt_guest_cancel_scope_cleanup, &cancel); + kzt_guest_cancel_scope_begin(my_context, &cancel); + ret = my->xcb_wait_for_special_event(native, v2); + kzt_guest_cancel_scope_end(&cancel); + pthread_cleanup_pop(0); + unalign_xcb_connection(native, v1); return ret; } @@ -93,8 +123,17 @@ EXPORT void* my_xcb_connect(void* dispname, void* screen) EXPORT void my_xcb_disconnect(void* conn) { - my->xcb_disconnect(align_xcb_connection(conn)); - del_xcb_connection(conn); + kzt_xcb_connection_lease_t lease = { 0 }; + int old_cancel_state = PTHREAD_CANCEL_ENABLE; + + (void)pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancel_state); + if (begin_xcb_connection_disconnect(conn, &lease) != 0) { + (void)pthread_setcancelstate(old_cancel_state, NULL); + return; + } + my->xcb_disconnect(lease.native); + finish_xcb_connection_disconnect(&lease); + (void)pthread_setcancelstate(old_cancel_state, NULL); } #pragma GCC diagnostic pop diff --git a/target/i386/latx/context/wrappedvulkan.c b/target/i386/latx/context/wrappedvulkan.c index e3b9a4389b0..2ab3b93d6ea 100644 --- a/target/i386/latx/context/wrappedvulkan.c +++ b/target/i386/latx/context/wrappedvulkan.c @@ -602,8 +602,12 @@ EXPORT int my_vkCreateXcbSurfaceKHR(void* instance, void* info, my_VkAllocationC my_VkAllocationCallbacks_t my_alloc; my_VkXcbSurfaceCreateInfoKHR_t* surfaceinfo = info; void* old_conn = surfaceinfo->connection; - surfaceinfo->connection = align_xcb_connection(old_conn); + void* native_conn = align_xcb_connection(old_conn); + if (!native_conn) + return -3; + surfaceinfo->connection = native_conn; int ret = my->vkCreateXcbSurfaceKHR(instance, info, find_VkAllocationCallbacks(&my_alloc, pAllocator), pFence); + unalign_xcb_connection(native_conn, old_conn); surfaceinfo->connection = old_conn; return ret; } diff --git a/target/i386/latx/context/wrapper.c b/target/i386/latx/context/wrapper.c index 5a1c4aca6cb..3acf5ba39d7 100644 --- a/target/i386/latx/context/wrapper.c +++ b/target/i386/latx/context/wrapper.c @@ -2855,7 +2855,7 @@ void vFpuupppp(uintptr_t fcn) { __CPU; vFpuupppp_t fn = (vFpuupppp_t)fcn; fn((vo void vFpuiiii(uintptr_t fcn) { __CPU; vFpuiiii_t fn = (vFpuiiii_t)fcn; fn((void*)R_RDI, (uint32_t)R_RSI, (int32_t)R_RDX, (int32_t)R_RCX, (int32_t)R_R8, (int32_t)R_R9); DEBUG_LOG; (void)cpu; } void vFpiiULipp(uintptr_t fcn) { __CPU; vFpiiULipp_t fn = (vFpiiULipp_t)fcn; fn((void*)R_RDI, (int32_t)R_RSI, (int32_t)R_RDX, (uint64_t)R_RCX, (uintptr_t)R_R8, (int32_t)R_R9, *(void**)(R_RSP + 8), *(void**)(R_RSP + 16)); DEBUG_LOG; (void)cpu; } void iFpuUp(uintptr_t fcn) { __CPU; iFpuUp_t fn = (iFpuUp_t)fcn; R_RAX=(int32_t)fn((void*)R_RDI, (uint32_t)R_RSI, (uint64_t)R_RDX, (void*)R_RCX); DEBUG_LOG; (void)cpu; } -void iFpubp(uintptr_t fcn) { __CPU; iFpubp_t fn = (iFpubp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDX); R_RAX=(int32_t)fn((void*)R_RDI, (uint32_t)R_RSI, aligned_xcb, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDX); DEBUG_LOG; (void)cpu; } +void iFpubp(uintptr_t fcn) { __CPU; iFpubp_t fn = (iFpubp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDX); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(int32_t)fn((void*)R_RDI, (uint32_t)R_RSI, aligned_xcb, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDX); DEBUG_LOG; (void)cpu; } void iFpUUUUp(uintptr_t fcn) { __CPU; iFpUUUUp_t fn = (iFpUUUUp_t)fcn; R_RAX=(int32_t)fn((void*)R_RDI, (uint64_t)R_RSI, (uint64_t)R_RDX, (uint64_t)R_RCX, (uint64_t)R_R8, (void*)R_R9); DEBUG_LOG; (void)cpu; } void vFpUuiu(uintptr_t fcn) { __CPU; vFpUuiu_t fn = (vFpUuiu_t)fcn; fn((void*)R_RDI, (uint64_t)R_RSI, (uint32_t)R_RDX, (int32_t)R_RCX, (uint32_t)R_R8); DEBUG_LOG; (void)cpu; } void vFpuuppp(uintptr_t fcn) { __CPU; vFpuuppp_t fn = (vFpuuppp_t)fcn; fn((void*)R_RDI, (uint32_t)R_RSI, (uint32_t)R_RDX, (void*)R_RCX, (void*)R_R8, (void*)R_R9); DEBUG_LOG; (void)cpu; } @@ -2900,108 +2900,108 @@ void iFEpuvvppp(uintptr_t fcn) { __CPU; iFEpuppp_t fn = (iFEpuppp_t)fcn; R_RAX=( void iFEpUUuppp(uintptr_t fcn) { __CPU; iFEpUUuppp_t fn = (iFEpUUuppp_t)fcn; R_RAX=(int32_t)fn((void*)R_RDI, (uint64_t)R_RSI, (uint64_t)R_RDX, (uint32_t)R_RCX, (void*)R_R8, (void*)R_R9, *(void**)(R_RSP + 8)); DEBUG_LOG; (void)cpu; } //endvulkan //xcbV2 -void pFb(uintptr_t fcn) { __CPU; pFb_t fn = (pFb_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuu(uintptr_t fcn) { __CPU; pFbuu_t fn = (pFbuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbup(uintptr_t fcn) { __CPU; pFbup_t fn = (pFbup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (void*)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbupuuuuup(uintptr_t fcn) { __CPU; pFbupuuuuup_t fn = (pFbupuuuuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (void*)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8, (uint32_t)R_R9, *(uint32_t*)(R_RSP + 8), *(uint32_t*)(R_RSP + 16), *(void**)(R_RSP + 24)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbWWiCpup(uintptr_t fcn) { __CPU; pFbWWiCpup_t fn = (pFbWWiCpup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint16_t)R_RSI, (uint16_t)R_RDX, (int32_t)R_RCX, (uint8_t)R_R8, (void*)R_R9, *(uint32_t*)(R_RSP + 8), *(void**)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbdwwWWui(uintptr_t fcn) { __CPU; pFbdwwWWui_t fn = (pFbdwwWWui_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, R_XMMD(0), (int16_t)R_RSI, (int16_t)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8, (uint32_t)R_R9, *(int32_t*)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbuupwwC(uintptr_t fcn) { __CPU; uFbuupwwC_t fn = (uFbuupwwC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (void*)R_RCX, (int16_t)R_R8, (int16_t)R_R9, *(uint8_t*)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void iFbupppWWu(uintptr_t fcn) { __CPU; iFbupppWWu_t fn = (iFbupppWWu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(int32_t)fn(aligned_xcb, (uint32_t)R_RSI, (void*)R_RDX, (void*)R_RCX, (void*)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(uint32_t*)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbp(uintptr_t fcn) { __CPU; pFbp_t fn = (pFbp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuuWWWCCi(uintptr_t fcn) { __CPU; pFbuuuWWWCCi_t fn = (pFbuuuWWWCCi_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint16_t)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(uint8_t*)(R_RSP + 16), *(uint8_t*)(R_RSP + 24), *(int32_t*)(R_RSP + 32)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbppu(uintptr_t fcn) { __CPU; pFbppu_t fn = (pFbppu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (void*)R_RDX, (uint32_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbuC(uintptr_t fcn) { __CPU; uFbuC_t fn = (uFbuC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint8_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbpu(uintptr_t fcn) { __CPU; pFbpu_t fn = (pFbpu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (uint32_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbppppuuCC(uintptr_t fcn) { __CPU; pFbppppuuCC_t fn = (pFbppppuuCC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (void*)R_RDX, (void*)R_RCX, (void*)R_R8, (uint32_t)R_R9, *(uint32_t*)(R_RSP + 8), *(uint8_t*)(R_RSP + 16), *(uint8_t*)(R_RSP + 24)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbuu(uintptr_t fcn) { __CPU; uFbuu_t fn = (uFbuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbuW(uintptr_t fcn) { __CPU; uFbuW_t fn = (uFbuW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint16_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuupwwp(uintptr_t fcn) { __CPU; pFbuuupwwp_t fn = (pFbuuupwwp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (void*)R_R8, (int16_t)R_R9, *(int16_t*)(R_RSP + 8), *(void**)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbpi(uintptr_t fcn) { __CPU; pFbpi_t fn = (pFbpi_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (int32_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbuuuwwu(uintptr_t fcn) { __CPU; uFbuuuwwu_t fn = (uFbuuuwwu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (int16_t)R_R8, (int16_t)R_R9, *(uint32_t*)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFb(uintptr_t fcn) { __CPU; uFb_t fn = (uFb_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbiiCpWWup(uintptr_t fcn) { __CPU; pFbiiCpWWup_t fn = (pFbiiCpWWup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (int32_t)R_RSI, (int32_t)R_RDX, (uint8_t)R_RCX, (void*)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(uint32_t*)(R_RSP + 16), *(void**)(R_RSP + 24)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbWWWCCCCCCCCWCCCCCC(uintptr_t fcn) { __CPU; uFbWWWCCCCCCCCWCCCCCC_t fn = (uFbWWWCCCCCCCCWCCCCCC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint16_t)R_RSI, (uint16_t)R_RDX, (uint16_t)R_RCX, (uint8_t)R_R8, (uint8_t)R_R9, *(uint8_t*)(R_RSP + 8), *(uint8_t*)(R_RSP + 16), *(uint8_t*)(R_RSP + 24), *(uint8_t*)(R_RSP + 32), *(uint8_t*)(R_RSP + 40), *(uint8_t*)(R_RSP + 48), *(uint16_t*)(R_RSP + 56), *(uint8_t*)(R_RSP + 64), *(uint8_t*)(R_RSP + 72), *(uint8_t*)(R_RSP + 80), *(uint8_t*)(R_RSP + 88), *(uint8_t*)(R_RSP + 96), *(uint8_t*)(R_RSP + 104)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbWu(uintptr_t fcn) { __CPU; uFbWu_t fn = (uFbWu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint16_t)R_RSI, (uint32_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbWWWWWWp(uintptr_t fcn) { __CPU; uFbWWWWWWp_t fn = (uFbWWWWWWp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint16_t)R_RSI, (uint16_t)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(void**)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbWW(uintptr_t fcn) { __CPU; uFbWW_t fn = (uFbWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint16_t)R_RSI, (uint16_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbuuC(uintptr_t fcn) { __CPU; uFbuuC_t fn = (uFbuuC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint8_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuC(uintptr_t fcn) { __CPU; pFbuuC_t fn = (pFbuuC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint8_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuWWCuu(uintptr_t fcn) { __CPU; pFbuuWWCuu_t fn = (pFbuuWWCuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8, (uint8_t)R_R9, *(uint32_t*)(R_RSP + 8), *(uint32_t*)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbu(uintptr_t fcn) { __CPU; uFbu_t fn = (uFbu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuwwWWuCuu(uintptr_t fcn) { __CPU; pFbuwwWWuCuu_t fn = (pFbuwwWWuCuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (int16_t)R_RDX, (int16_t)R_RCX, (uint16_t)R_R8, (uint16_t)R_R9, *(uint32_t*)(R_RSP + 8), *(uint8_t*)(R_RSP + 16), *(uint32_t*)(R_RSP + 24), *(uint32_t*)(R_RSP + 32)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuWWWWWWwwCCCuu(uintptr_t fcn) { __CPU; pFbuuWWWWWWwwCCCuu_t fn = (pFbuuWWWWWWwwCCCuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(uint16_t*)(R_RSP + 16), *(uint16_t*)(R_RSP + 24), *(int16_t*)(R_RSP + 32), *(int16_t*)(R_RSP + 40), *(uint8_t*)(R_RSP + 48), *(uint8_t*)(R_RSP + 56), *(uint8_t*)(R_RSP + 64), *(uint32_t*)(R_RSP + 72), *(uint32_t*)(R_RSP + 80)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbpp(uintptr_t fcn) { __CPU; pFbpp_t fn = (pFbpp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (void*)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuWWW(uintptr_t fcn) { __CPU; pFbuWWW_t fn = (pFbuWWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint16_t)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbC(uintptr_t fcn) { __CPU; pFbC_t fn = (pFbC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuup(uintptr_t fcn) { __CPU; pFbuup_t fn = (pFbuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbCuuuCup(uintptr_t fcn) { __CPU; uFbCuuuCup_t fn = (uFbCuuuCup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8, (uint8_t)R_R9, *(uint32_t*)(R_RSP + 8), *(void**)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbuup(uintptr_t fcn) { __CPU; uFbuup_t fn = (uFbuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCuwwWW(uintptr_t fcn) { __CPU; pFbCuwwWW_t fn = (pFbCuwwWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (int16_t)R_RCX, (int16_t)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbu(uintptr_t fcn) { __CPU; pFbu_t fn = (pFbu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuWp(uintptr_t fcn) { __CPU; pFbuWp_t fn = (pFbuWp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint16_t)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void iFb(uintptr_t fcn) { __CPU; iFb_t fn = (iFb_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(int32_t)fn(aligned_xcb); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuuuu(uintptr_t fcn) { __CPU; pFbuuuuu_t fn = (pFbuuuuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8, (uint32_t)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuuwwwwWW(uintptr_t fcn) { __CPU; pFbuuuwwwwWW_t fn = (pFbuuuwwwwWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (int16_t)R_R8, (int16_t)R_R9, *(int16_t*)(R_RSP + 8), *(int16_t*)(R_RSP + 16), *(uint16_t*)(R_RSP + 24), *(uint16_t*)(R_RSP + 32)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbCuuu(uintptr_t fcn) { __CPU; uFbCuuu_t fn = (uFbCuuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuuWWWWWWWW(uintptr_t fcn) { __CPU; pFbuuuWWWWWWWW_t fn = (pFbuuuWWWWWWWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint16_t)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(uint16_t*)(R_RSP + 16), *(uint16_t*)(R_RSP + 24), *(uint16_t*)(R_RSP + 32), *(uint16_t*)(R_RSP + 40), *(uint16_t*)(R_RSP + 48)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbuuup(uintptr_t fcn) { __CPU; uFbuuup_t fn = (uFbuuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (void*)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbCuuWW(uintptr_t fcn) { __CPU; uFbCuuWW_t fn = (uFbCuuWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint16_t)R_R8, (uint16_t)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbCuuwwWWWWuup(uintptr_t fcn) { __CPU; uFbCuuwwWWWWuup_t fn = (uFbCuuwwWWWWuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (int16_t)R_R8, (int16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(uint16_t*)(R_RSP + 16), *(uint16_t*)(R_RSP + 24), *(uint16_t*)(R_RSP + 32), *(uint32_t*)(R_RSP + 40), *(uint32_t*)(R_RSP + 48), *(void**)(R_RSP + 56)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void vFbu(uintptr_t fcn) { __CPU; vFbu_t fn = (vFbu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); fn(aligned_xcb, (uint32_t)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void vFbU(uintptr_t fcn) { __CPU; vFbU_t fn = (vFbU_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); fn(aligned_xcb, (uint64_t)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbpup(uintptr_t fcn) { __CPU; pFbpup_t fn = (pFbpup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (uint32_t)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCuwwWWu(uintptr_t fcn) { __CPU; pFbCuwwWWu_t fn = (pFbCuwwWWu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (int16_t)R_RCX, (int16_t)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(uint32_t*)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCC(uintptr_t fcn) { __CPU; pFbCC_t fn = (pFbCC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint8_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbCuuuuu(uintptr_t fcn) { __CPU; uFbCuuuuu_t fn = (uFbCuuuuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8, (uint32_t)R_R9, *(uint32_t*)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCuWCCuuCW(uintptr_t fcn) { __CPU; pFbCuWCCuuCW_t fn = (pFbCuWCCuuCW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX, (uint8_t)R_R8, (uint8_t)R_R9, *(uint32_t*)(R_RSP + 8), *(uint32_t*)(R_RSP + 16), *(uint8_t*)(R_RSP + 24), *(uint16_t*)(R_RSP + 32)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCuWCCC(uintptr_t fcn) { __CPU; pFbCuWCCC_t fn = (pFbCuWCCC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX, (uint8_t)R_R8, (uint8_t)R_R9, *(uint8_t*)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCuuCC(uintptr_t fcn) { __CPU; pFbCuuCC_t fn = (pFbCuuCC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint8_t)R_R8, (uint8_t)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCuWCCuuu(uintptr_t fcn) { __CPU; pFbCuWCCuuu_t fn = (pFbCuWCCuuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX, (uint8_t)R_R8, (uint8_t)R_R9, *(uint32_t*)(R_RSP + 8), *(uint32_t*)(R_RSP + 16), *(uint32_t*)(R_RSP + 24)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCuuwwp(uintptr_t fcn) { __CPU; pFbCuuwwp_t fn = (pFbCuuwwp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (int16_t)R_R8, (int16_t)R_R9, *(void**)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbCWp(uintptr_t fcn) { __CPU; uFbCWp_t fn = (uFbCWp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint16_t)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbuWp(uintptr_t fcn) { __CPU; uFbuWp_t fn = (uFbuWp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint16_t)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void iFbupp(uintptr_t fcn) { __CPU; iFbupp_t fn = (iFbupp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(int32_t)fn(aligned_xcb, (uint32_t)R_RSI, (void*)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuup(uintptr_t fcn) { __CPU; pFbuuup_t fn = (pFbuuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (void*)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCuuup(uintptr_t fcn) { __CPU; pFbCuuup_t fn = (pFbCuuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8, (void*)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void vFbp(uintptr_t fcn) { __CPU; vFbp_t fn = (vFbp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); fn(aligned_xcb, (void*)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void vFb(uintptr_t fcn) { __CPU; vFb_t fn = (vFb_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); fn(aligned_xcb); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCuuWWwwCCup(uintptr_t fcn) { __CPU; pFbCuuWWwwCCup_t fn = (pFbCuuWWwwCCup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint16_t)R_R8, (uint16_t)R_R9, *(int16_t*)(R_RSP + 8), *(int16_t*)(R_RSP + 16), *(uint8_t*)(R_RSP + 24), *(uint8_t*)(R_RSP + 32), *(uint32_t*)(R_RSP + 40), *(void**)(R_RSP + 48)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuWW(uintptr_t fcn) { __CPU; pFbuuWW_t fn = (pFbuuWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbCuup(uintptr_t fcn) { __CPU; uFbCuup_t fn = (uFbCuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (void*)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void vFbi(uintptr_t fcn) { __CPU; vFbi_t fn = (vFbi_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); fn(aligned_xcb, (int32_t)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbipp(uintptr_t fcn) { __CPU; uFbipp_t fn = (uFbipp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (int32_t)R_RSI, (void*)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void UFbipp(uintptr_t fcn) { __CPU; UFbipp_t fn = (UFbipp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=fn(aligned_xcb, (int32_t)R_RSI, (void*)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbippup(uintptr_t fcn) { __CPU; uFbippup_t fn = (uFbippup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (int32_t)R_RSI, (void*)R_RDX, (void*)R_RCX, (uint32_t)R_R8, (void*)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void UFbippup(uintptr_t fcn) { __CPU; UFbippup_t fn = (UFbippup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=fn(aligned_xcb, (int32_t)R_RSI, (void*)R_RDX, (void*)R_RCX, (uint32_t)R_R8, (void*)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCpWWup(uintptr_t fcn) { __CPU; pFbCpWWup_t fn = (pFbCpWWup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (void*)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8, (uint32_t)R_R9, *(void**)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCuu(uintptr_t fcn) { __CPU; pFbCuu_t fn = (pFbCuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbpppp(uintptr_t fcn) { __CPU; pFbpppp_t fn = (pFbpppp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (void*)R_RDX, (void*)R_RCX, (void*)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCuW(uintptr_t fcn) { __CPU; pFbCuW_t fn = (pFbCuW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbUp(uintptr_t fcn) { __CPU; pFbUp_t fn = (pFbUp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint64_t)R_RSI, (void*)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuwwWWww(uintptr_t fcn) { __CPU; pFbuuwwWWww_t fn = (pFbuuwwWWww_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (int16_t)R_RCX, (int16_t)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(int16_t*)(R_RSP + 16), *(int16_t*)(R_RSP + 24)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFb(uintptr_t fcn) { __CPU; pFb_t fn = (pFb_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuu(uintptr_t fcn) { __CPU; pFbuu_t fn = (pFbuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbup(uintptr_t fcn) { __CPU; pFbup_t fn = (pFbup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (void*)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbupuuuuup(uintptr_t fcn) { __CPU; pFbupuuuuup_t fn = (pFbupuuuuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (void*)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8, (uint32_t)R_R9, *(uint32_t*)(R_RSP + 8), *(uint32_t*)(R_RSP + 16), *(void**)(R_RSP + 24)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbWWiCpup(uintptr_t fcn) { __CPU; pFbWWiCpup_t fn = (pFbWWiCpup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint16_t)R_RSI, (uint16_t)R_RDX, (int32_t)R_RCX, (uint8_t)R_R8, (void*)R_R9, *(uint32_t*)(R_RSP + 8), *(void**)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbdwwWWui(uintptr_t fcn) { __CPU; pFbdwwWWui_t fn = (pFbdwwWWui_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, R_XMMD(0), (int16_t)R_RSI, (int16_t)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8, (uint32_t)R_R9, *(int32_t*)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbuupwwC(uintptr_t fcn) { __CPU; uFbuupwwC_t fn = (uFbuupwwC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (void*)R_RCX, (int16_t)R_R8, (int16_t)R_R9, *(uint8_t*)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void iFbupppWWu(uintptr_t fcn) { __CPU; iFbupppWWu_t fn = (iFbupppWWu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(int32_t)fn(aligned_xcb, (uint32_t)R_RSI, (void*)R_RDX, (void*)R_RCX, (void*)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(uint32_t*)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbp(uintptr_t fcn) { __CPU; pFbp_t fn = (pFbp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuuWWWCCi(uintptr_t fcn) { __CPU; pFbuuuWWWCCi_t fn = (pFbuuuWWWCCi_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint16_t)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(uint8_t*)(R_RSP + 16), *(uint8_t*)(R_RSP + 24), *(int32_t*)(R_RSP + 32)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbppu(uintptr_t fcn) { __CPU; pFbppu_t fn = (pFbppu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (void*)R_RDX, (uint32_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbuC(uintptr_t fcn) { __CPU; uFbuC_t fn = (uFbuC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint8_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbpu(uintptr_t fcn) { __CPU; pFbpu_t fn = (pFbpu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (uint32_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbppppuuCC(uintptr_t fcn) { __CPU; pFbppppuuCC_t fn = (pFbppppuuCC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (void*)R_RDX, (void*)R_RCX, (void*)R_R8, (uint32_t)R_R9, *(uint32_t*)(R_RSP + 8), *(uint8_t*)(R_RSP + 16), *(uint8_t*)(R_RSP + 24)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbuu(uintptr_t fcn) { __CPU; uFbuu_t fn = (uFbuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbuW(uintptr_t fcn) { __CPU; uFbuW_t fn = (uFbuW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint16_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuupwwp(uintptr_t fcn) { __CPU; pFbuuupwwp_t fn = (pFbuuupwwp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (void*)R_R8, (int16_t)R_R9, *(int16_t*)(R_RSP + 8), *(void**)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbpi(uintptr_t fcn) { __CPU; pFbpi_t fn = (pFbpi_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (int32_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbuuuwwu(uintptr_t fcn) { __CPU; uFbuuuwwu_t fn = (uFbuuuwwu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (int16_t)R_R8, (int16_t)R_R9, *(uint32_t*)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFb(uintptr_t fcn) { __CPU; uFb_t fn = (uFb_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbiiCpWWup(uintptr_t fcn) { __CPU; pFbiiCpWWup_t fn = (pFbiiCpWWup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (int32_t)R_RSI, (int32_t)R_RDX, (uint8_t)R_RCX, (void*)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(uint32_t*)(R_RSP + 16), *(void**)(R_RSP + 24)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbWWWCCCCCCCCWCCCCCC(uintptr_t fcn) { __CPU; uFbWWWCCCCCCCCWCCCCCC_t fn = (uFbWWWCCCCCCCCWCCCCCC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint16_t)R_RSI, (uint16_t)R_RDX, (uint16_t)R_RCX, (uint8_t)R_R8, (uint8_t)R_R9, *(uint8_t*)(R_RSP + 8), *(uint8_t*)(R_RSP + 16), *(uint8_t*)(R_RSP + 24), *(uint8_t*)(R_RSP + 32), *(uint8_t*)(R_RSP + 40), *(uint8_t*)(R_RSP + 48), *(uint16_t*)(R_RSP + 56), *(uint8_t*)(R_RSP + 64), *(uint8_t*)(R_RSP + 72), *(uint8_t*)(R_RSP + 80), *(uint8_t*)(R_RSP + 88), *(uint8_t*)(R_RSP + 96), *(uint8_t*)(R_RSP + 104)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbWu(uintptr_t fcn) { __CPU; uFbWu_t fn = (uFbWu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint16_t)R_RSI, (uint32_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbWWWWWWp(uintptr_t fcn) { __CPU; uFbWWWWWWp_t fn = (uFbWWWWWWp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint16_t)R_RSI, (uint16_t)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(void**)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbWW(uintptr_t fcn) { __CPU; uFbWW_t fn = (uFbWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint16_t)R_RSI, (uint16_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbuuC(uintptr_t fcn) { __CPU; uFbuuC_t fn = (uFbuuC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint8_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuC(uintptr_t fcn) { __CPU; pFbuuC_t fn = (pFbuuC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint8_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuWWCuu(uintptr_t fcn) { __CPU; pFbuuWWCuu_t fn = (pFbuuWWCuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8, (uint8_t)R_R9, *(uint32_t*)(R_RSP + 8), *(uint32_t*)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbu(uintptr_t fcn) { __CPU; uFbu_t fn = (uFbu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuwwWWuCuu(uintptr_t fcn) { __CPU; pFbuwwWWuCuu_t fn = (pFbuwwWWuCuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (int16_t)R_RDX, (int16_t)R_RCX, (uint16_t)R_R8, (uint16_t)R_R9, *(uint32_t*)(R_RSP + 8), *(uint8_t*)(R_RSP + 16), *(uint32_t*)(R_RSP + 24), *(uint32_t*)(R_RSP + 32)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuWWWWWWwwCCCuu(uintptr_t fcn) { __CPU; pFbuuWWWWWWwwCCCuu_t fn = (pFbuuWWWWWWwwCCCuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(uint16_t*)(R_RSP + 16), *(uint16_t*)(R_RSP + 24), *(int16_t*)(R_RSP + 32), *(int16_t*)(R_RSP + 40), *(uint8_t*)(R_RSP + 48), *(uint8_t*)(R_RSP + 56), *(uint8_t*)(R_RSP + 64), *(uint32_t*)(R_RSP + 72), *(uint32_t*)(R_RSP + 80)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbpp(uintptr_t fcn) { __CPU; pFbpp_t fn = (pFbpp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (void*)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuWWW(uintptr_t fcn) { __CPU; pFbuWWW_t fn = (pFbuWWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint16_t)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbC(uintptr_t fcn) { __CPU; pFbC_t fn = (pFbC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuup(uintptr_t fcn) { __CPU; pFbuup_t fn = (pFbuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbCuuuCup(uintptr_t fcn) { __CPU; uFbCuuuCup_t fn = (uFbCuuuCup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8, (uint8_t)R_R9, *(uint32_t*)(R_RSP + 8), *(void**)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbuup(uintptr_t fcn) { __CPU; uFbuup_t fn = (uFbuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCuwwWW(uintptr_t fcn) { __CPU; pFbCuwwWW_t fn = (pFbCuwwWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (int16_t)R_RCX, (int16_t)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbu(uintptr_t fcn) { __CPU; pFbu_t fn = (pFbu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuWp(uintptr_t fcn) { __CPU; pFbuWp_t fn = (pFbuWp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint16_t)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void iFb(uintptr_t fcn) { __CPU; iFb_t fn = (iFb_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(int32_t)fn(aligned_xcb); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuuuu(uintptr_t fcn) { __CPU; pFbuuuuu_t fn = (pFbuuuuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8, (uint32_t)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuuwwwwWW(uintptr_t fcn) { __CPU; pFbuuuwwwwWW_t fn = (pFbuuuwwwwWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (int16_t)R_R8, (int16_t)R_R9, *(int16_t*)(R_RSP + 8), *(int16_t*)(R_RSP + 16), *(uint16_t*)(R_RSP + 24), *(uint16_t*)(R_RSP + 32)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbCuuu(uintptr_t fcn) { __CPU; uFbCuuu_t fn = (uFbCuuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuuWWWWWWWW(uintptr_t fcn) { __CPU; pFbuuuWWWWWWWW_t fn = (pFbuuuWWWWWWWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint16_t)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(uint16_t*)(R_RSP + 16), *(uint16_t*)(R_RSP + 24), *(uint16_t*)(R_RSP + 32), *(uint16_t*)(R_RSP + 40), *(uint16_t*)(R_RSP + 48)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbuuup(uintptr_t fcn) { __CPU; uFbuuup_t fn = (uFbuuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (void*)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbCuuWW(uintptr_t fcn) { __CPU; uFbCuuWW_t fn = (uFbCuuWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint16_t)R_R8, (uint16_t)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbCuuwwWWWWuup(uintptr_t fcn) { __CPU; uFbCuuwwWWWWuup_t fn = (uFbCuuwwWWWWuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (int16_t)R_R8, (int16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(uint16_t*)(R_RSP + 16), *(uint16_t*)(R_RSP + 24), *(uint16_t*)(R_RSP + 32), *(uint32_t*)(R_RSP + 40), *(uint32_t*)(R_RSP + 48), *(void**)(R_RSP + 56)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void vFbu(uintptr_t fcn) { __CPU; vFbu_t fn = (vFbu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } fn(aligned_xcb, (uint32_t)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void vFbU(uintptr_t fcn) { __CPU; vFbU_t fn = (vFbU_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } fn(aligned_xcb, (uint64_t)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbpup(uintptr_t fcn) { __CPU; pFbpup_t fn = (pFbpup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (uint32_t)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCuwwWWu(uintptr_t fcn) { __CPU; pFbCuwwWWu_t fn = (pFbCuwwWWu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (int16_t)R_RCX, (int16_t)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(uint32_t*)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCC(uintptr_t fcn) { __CPU; pFbCC_t fn = (pFbCC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint8_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbCuuuuu(uintptr_t fcn) { __CPU; uFbCuuuuu_t fn = (uFbCuuuuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8, (uint32_t)R_R9, *(uint32_t*)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCuWCCuuCW(uintptr_t fcn) { __CPU; pFbCuWCCuuCW_t fn = (pFbCuWCCuuCW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX, (uint8_t)R_R8, (uint8_t)R_R9, *(uint32_t*)(R_RSP + 8), *(uint32_t*)(R_RSP + 16), *(uint8_t*)(R_RSP + 24), *(uint16_t*)(R_RSP + 32)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCuWCCC(uintptr_t fcn) { __CPU; pFbCuWCCC_t fn = (pFbCuWCCC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX, (uint8_t)R_R8, (uint8_t)R_R9, *(uint8_t*)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCuuCC(uintptr_t fcn) { __CPU; pFbCuuCC_t fn = (pFbCuuCC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint8_t)R_R8, (uint8_t)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCuWCCuuu(uintptr_t fcn) { __CPU; pFbCuWCCuuu_t fn = (pFbCuWCCuuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX, (uint8_t)R_R8, (uint8_t)R_R9, *(uint32_t*)(R_RSP + 8), *(uint32_t*)(R_RSP + 16), *(uint32_t*)(R_RSP + 24)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCuuwwp(uintptr_t fcn) { __CPU; pFbCuuwwp_t fn = (pFbCuuwwp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (int16_t)R_R8, (int16_t)R_R9, *(void**)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbCWp(uintptr_t fcn) { __CPU; uFbCWp_t fn = (uFbCWp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint16_t)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbuWp(uintptr_t fcn) { __CPU; uFbuWp_t fn = (uFbuWp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint16_t)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void iFbupp(uintptr_t fcn) { __CPU; iFbupp_t fn = (iFbupp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(int32_t)fn(aligned_xcb, (uint32_t)R_RSI, (void*)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuup(uintptr_t fcn) { __CPU; pFbuuup_t fn = (pFbuuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (void*)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCuuup(uintptr_t fcn) { __CPU; pFbCuuup_t fn = (pFbCuuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8, (void*)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void vFbp(uintptr_t fcn) { __CPU; vFbp_t fn = (vFbp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } fn(aligned_xcb, (void*)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void vFb(uintptr_t fcn) { __CPU; vFb_t fn = (vFb_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } fn(aligned_xcb); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCuuWWwwCCup(uintptr_t fcn) { __CPU; pFbCuuWWwwCCup_t fn = (pFbCuuWWwwCCup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint16_t)R_R8, (uint16_t)R_R9, *(int16_t*)(R_RSP + 8), *(int16_t*)(R_RSP + 16), *(uint8_t*)(R_RSP + 24), *(uint8_t*)(R_RSP + 32), *(uint32_t*)(R_RSP + 40), *(void**)(R_RSP + 48)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuWW(uintptr_t fcn) { __CPU; pFbuuWW_t fn = (pFbuuWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbCuup(uintptr_t fcn) { __CPU; uFbCuup_t fn = (uFbCuup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (void*)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void vFbi(uintptr_t fcn) { __CPU; vFbi_t fn = (vFbi_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } fn(aligned_xcb, (int32_t)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbipp(uintptr_t fcn) { __CPU; uFbipp_t fn = (uFbipp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (int32_t)R_RSI, (void*)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void UFbipp(uintptr_t fcn) { __CPU; UFbipp_t fn = (UFbipp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=fn(aligned_xcb, (int32_t)R_RSI, (void*)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbippup(uintptr_t fcn) { __CPU; uFbippup_t fn = (uFbippup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (int32_t)R_RSI, (void*)R_RDX, (void*)R_RCX, (uint32_t)R_R8, (void*)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void UFbippup(uintptr_t fcn) { __CPU; UFbippup_t fn = (UFbippup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=fn(aligned_xcb, (int32_t)R_RSI, (void*)R_RDX, (void*)R_RCX, (uint32_t)R_R8, (void*)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCpWWup(uintptr_t fcn) { __CPU; pFbCpWWup_t fn = (pFbCpWWup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (void*)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8, (uint32_t)R_R9, *(void**)(R_RSP + 8)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCuu(uintptr_t fcn) { __CPU; pFbCuu_t fn = (pFbCuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbpppp(uintptr_t fcn) { __CPU; pFbpppp_t fn = (pFbpppp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (void*)R_RDX, (void*)R_RCX, (void*)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCuW(uintptr_t fcn) { __CPU; pFbCuW_t fn = (pFbCuW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbUp(uintptr_t fcn) { __CPU; pFbUp_t fn = (pFbUp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint64_t)R_RSI, (void*)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuwwWWww(uintptr_t fcn) { __CPU; pFbuuwwWWww_t fn = (pFbuuwwWWww_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (int16_t)R_RCX, (int16_t)R_R8, (uint16_t)R_R9, *(uint16_t*)(R_RSP + 8), *(int16_t*)(R_RSP + 16), *(int16_t*)(R_RSP + 24)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } void vFEp(uintptr_t fcn) { __CPU; vFEp_t fn = (vFEp_t)fcn; fn((void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCu(uintptr_t fcn) { __CPU; pFbCu_t fn = (pFbCu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void iFbppip(uintptr_t fcn) { __CPU; iFbppip_t fn = (iFbppip_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(int32_t)fn(aligned_xcb, (void*)R_RSI, (void*)R_RDX, (int32_t)R_RCX, (void*)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void iFbpiU(uintptr_t fcn) { __CPU; iFbpiU_t fn = (iFbpiU_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(int32_t)fn(aligned_xcb, (void*)R_RSI, (int32_t)R_RDX, (uint64_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuu(uintptr_t fcn) { __CPU; pFbuuu_t fn = (pFbuuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbpWp(uintptr_t fcn) { __CPU; pFbpWp_t fn = (pFbpWp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (uint16_t)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuwwu(uintptr_t fcn) { __CPU; pFbuuwwu_t fn = (pFbuuwwu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (int16_t)R_RCX, (int16_t)R_R8, (uint32_t)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbCCuuwwC(uintptr_t fcn) { __CPU; pFbCCuuwwC_t fn = (pFbCCuuwwC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint8_t)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8, (int16_t)R_R9, *(int16_t*)(R_RSP + 8), *(uint8_t*)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbuuu(uintptr_t fcn) { __CPU; uFbuuu_t fn = (uFbuuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void iFbpp(uintptr_t fcn) { __CPU; iFbpp_t fn = (iFbpp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(int32_t)fn(aligned_xcb, (void*)R_RSI, (void*)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void CFbupp(uintptr_t fcn) { __CPU; CFbupp_t fn = (CFbupp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(unsigned char)fn(aligned_xcb, (uint32_t)R_RSI, (void*)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbup(uintptr_t fcn) { __CPU; uFbup_t fn = (uFbup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (void*)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCu(uintptr_t fcn) { __CPU; pFbCu_t fn = (pFbCu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint32_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void iFbppip(uintptr_t fcn) { __CPU; iFbppip_t fn = (iFbppip_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(int32_t)fn(aligned_xcb, (void*)R_RSI, (void*)R_RDX, (int32_t)R_RCX, (void*)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void iFbpiU(uintptr_t fcn) { __CPU; iFbpiU_t fn = (iFbpiU_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(int32_t)fn(aligned_xcb, (void*)R_RSI, (int32_t)R_RDX, (uint64_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuu(uintptr_t fcn) { __CPU; pFbuuu_t fn = (pFbuuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbpWp(uintptr_t fcn) { __CPU; pFbpWp_t fn = (pFbpWp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (uint16_t)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuwwu(uintptr_t fcn) { __CPU; pFbuuwwu_t fn = (pFbuuwwu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (int16_t)R_RCX, (int16_t)R_R8, (uint32_t)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbCCuuwwC(uintptr_t fcn) { __CPU; pFbCCuuwwC_t fn = (pFbCCuuwwC_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint8_t)R_RSI, (uint8_t)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8, (int16_t)R_R9, *(int16_t*)(R_RSP + 8), *(uint8_t*)(R_RSP + 16)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbuuu(uintptr_t fcn) { __CPU; uFbuuu_t fn = (uFbuuu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void iFbpp(uintptr_t fcn) { __CPU; iFbpp_t fn = (iFbpp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(int32_t)fn(aligned_xcb, (void*)R_RSI, (void*)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void CFbupp(uintptr_t fcn) { __CPU; CFbupp_t fn = (CFbupp_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(unsigned char)fn(aligned_xcb, (uint32_t)R_RSI, (void*)R_RDX, (void*)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbup(uintptr_t fcn) { __CPU; uFbup_t fn = (uFbup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (void*)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } void vFpC(uintptr_t fcn) { __CPU; vFpC_t fn = (vFpC_t)fcn; fn((void*)R_RDI, (uint8_t)R_RSI); DEBUG_LOG; (void)cpu; } void HFpp(uintptr_t fcn) { __CPU; HFpp_t fn = (HFpp_t)fcn; unsigned __int128 u128 = fn((void*)R_RDI, (void*)R_RSI); R_RAX=(u128&0xFFFFFFFFFFFFFFFFL); R_RDX=(u128>>64)&0xFFFFFFFFFFFFFFFFL; DEBUG_LOG; (void)cpu; } -void uFbuU(uintptr_t fcn) { __CPU; uFbuU_t fn = (uFbuU_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint64_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbppU(uintptr_t fcn) { __CPU; pFbppU_t fn = (pFbppU_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (void*)R_RDX, (uint64_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbpCpppwwwwwwWW(uintptr_t fcn) { __CPU; pFbpCpppwwwwwwWW_t fn = (pFbpCpppwwwwwwWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (uint8_t)R_RDX, (void*)R_RCX, (void*)R_R8, (void*)R_R9, *(int16_t*)(R_RSP + 8), *(int16_t*)(R_RSP + 16), *(int16_t*)(R_RSP + 24), *(int16_t*)(R_RSP + 32), *(int16_t*)(R_RSP + 40), *(int16_t*)(R_RSP + 48), *(uint16_t*)(R_RSP + 56), *(uint16_t*)(R_RSP + 64)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbuuWW(uintptr_t fcn) { __CPU; uFbuuWW_t fn = (uFbuuWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void uFbuuiup(uintptr_t fcn) { __CPU; uFbuuiup_t fn = (uFbuuiup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (int32_t)R_RCX, (uint32_t)R_R8, (void*)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void fFbu(uintptr_t fcn) { __CPU; fFbu_t fn = (fFbu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_XMMS(0)=fn(aligned_xcb, (uint32_t)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuUUU(uintptr_t fcn) { __CPU; pFbuuUUU_t fn = (pFbuuUUU_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint64_t)R_RCX, (uint64_t)R_R8, (uint64_t)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } -void pFbuuuuuwwuuuuUUUup(uintptr_t fcn) { __CPU; pFbuuuuuwwuuuuUUUup_t fn = (pFbuuuuuwwuuuuUUUup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8, (uint32_t)R_R9, *(int16_t*)(R_RSP + 8), *(int16_t*)(R_RSP + 16), *(uint32_t*)(R_RSP + 24), *(uint32_t*)(R_RSP + 32), *(uint32_t*)(R_RSP + 40), *(uint32_t*)(R_RSP + 48), *(uint64_t*)(R_RSP + 56), *(uint64_t*)(R_RSP + 64), *(uint64_t*)(R_RSP + 72), *(uint32_t*)(R_RSP + 80), *(void**)(R_RSP + 88)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbuU(uintptr_t fcn) { __CPU; uFbuU_t fn = (uFbuU_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint64_t)R_RDX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbppU(uintptr_t fcn) { __CPU; pFbppU_t fn = (pFbppU_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (void*)R_RDX, (uint64_t)R_RCX); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbpCpppwwwwwwWW(uintptr_t fcn) { __CPU; pFbpCpppwwwwwwWW_t fn = (pFbpCpppwwwwwwWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (void*)R_RSI, (uint8_t)R_RDX, (void*)R_RCX, (void*)R_R8, (void*)R_R9, *(int16_t*)(R_RSP + 8), *(int16_t*)(R_RSP + 16), *(int16_t*)(R_RSP + 24), *(int16_t*)(R_RSP + 32), *(int16_t*)(R_RSP + 40), *(int16_t*)(R_RSP + 48), *(uint16_t*)(R_RSP + 56), *(uint16_t*)(R_RSP + 64)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbuuWW(uintptr_t fcn) { __CPU; uFbuuWW_t fn = (uFbuuWW_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint16_t)R_RCX, (uint16_t)R_R8); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void uFbuuiup(uintptr_t fcn) { __CPU; uFbuuiup_t fn = (uFbuuiup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uint32_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (int32_t)R_RCX, (uint32_t)R_R8, (void*)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void fFbu(uintptr_t fcn) { __CPU; fFbu_t fn = (fFbu_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_XMMS(0)=0; DEBUG_LOG; return; } R_XMMS(0)=fn(aligned_xcb, (uint32_t)R_RSI); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuUUU(uintptr_t fcn) { __CPU; pFbuuUUU_t fn = (pFbuuUUU_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint64_t)R_RCX, (uint64_t)R_R8, (uint64_t)R_R9); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } +void pFbuuuuuwwuuuuUUUup(uintptr_t fcn) { __CPU; pFbuuuuuwwuuuuUUUup_t fn = (pFbuuuuuwwuuuuUUUup_t)fcn; void *aligned_xcb = align_xcb_connection((void*)R_RDI); if (!aligned_xcb) { R_RAX=0; DEBUG_LOG; return; } R_RAX=(uintptr_t)fn(aligned_xcb, (uint32_t)R_RSI, (uint32_t)R_RDX, (uint32_t)R_RCX, (uint32_t)R_R8, (uint32_t)R_R9, *(int16_t*)(R_RSP + 8), *(int16_t*)(R_RSP + 16), *(uint32_t*)(R_RSP + 24), *(uint32_t*)(R_RSP + 32), *(uint32_t*)(R_RSP + 40), *(uint32_t*)(R_RSP + 48), *(uint64_t*)(R_RSP + 56), *(uint64_t*)(R_RSP + 64), *(uint64_t*)(R_RSP + 72), *(uint32_t*)(R_RSP + 80), *(void**)(R_RSP + 88)); unalign_xcb_connection(aligned_xcb, (void*)R_RDI); DEBUG_LOG; (void)cpu; } //xcbV2end #undef R_RAX #undef R_RDI diff --git a/target/i386/latx/include/aot.h b/target/i386/latx/include/aot.h index f7b0c0dcef8..0eb4687a023 100644 --- a/target/i386/latx/include/aot.h +++ b/target/i386/latx/include/aot.h @@ -38,10 +38,17 @@ extern const char *aot_left_file_minsize_optarg; * | code caches | * +--------------+ */ +#ifdef CONFIG_LATX_KZT +#define KZT_AOT_VERSION_SUFFIX "-kzt-runtime-entry-v2" +#else +#define KZT_AOT_VERSION_SUFFIX "" +#endif #ifdef CONFIG_LATX_DEBUG -#define AOT_VERSION "Version: "LATX_VERSION"-debug" +#define AOT_VERSION \ + "Version: "LATX_VERSION"-debug" KZT_AOT_VERSION_SUFFIX #else -#define AOT_VERSION "Version: "LATX_VERSION"-release" +#define AOT_VERSION \ + "Version: "LATX_VERSION"-release" KZT_AOT_VERSION_SUFFIX #endif typedef struct aot_header { uint32_t lib_size; @@ -260,6 +267,8 @@ typedef enum aot_rel_kind { LOAD_HELPER_CVTPH2PS_XMM, LOAD_HELPER_CVTPS2PH_YMM, LOAD_HELPER_CVTPS2PH_XMM, + LOAD_HELPER_KZT_RUNTIME_GUEST_ENTRY, + LOAD_HELPER_KZT_XCB_GUARD_ACQUIRE, LOAD_HELPER_END, diff --git a/target/i386/latx/include/box64context.h b/target/i386/latx/include/box64context.h index eea79e35a5a..73620fd47e7 100755 --- a/target/i386/latx/include/box64context.h +++ b/target/i386/latx/include/box64context.h @@ -10,7 +10,9 @@ #include "kzt_guest_registry_context.h" #include "kzt_guest_scope_layout.h" #include "kzt_lazy_prebind_scope.h" +#include "kzt_loader_event_hook.h" #include "kzt_patch_spike_guard.h" +#include "kzt_xcb_connection_map.h" typedef struct elfheader_s elfheader_t; typedef struct cleanup_s cleanup_t; @@ -20,6 +22,7 @@ typedef struct kh_symbolmap_s kh_symbolmap_t; typedef struct kh_symbol1map_s kh_symbol1map_t; typedef struct library_s library_t; typedef struct linkmap_s linkmap_t; +struct x86_ld_info; typedef struct kh_threadstack_s kh_threadstack_t; typedef struct atfork_fnc_s { uintptr_t prepare; @@ -196,12 +199,6 @@ struct link_map_x64 { unsigned long long l_serial; struct auditstate l_audit[]; }; -struct malloc_map { - void* mallocp; - void* freep; - void* reallocp; - void* h; -}; typedef struct box64context_s { path_collection_t box64_path; // PATH env. variable path_collection_t box64_ld_lib; // LD_LIBRARY_PATH env. variable @@ -234,10 +231,6 @@ typedef struct box64context_s { int elfcap; int elfsize; // number of elf loaded - struct malloc_map **mallocmaps; // elf filepath and memory - int mallocmapcap; - int mallocmapsize; // number of elf filepath - needed_libs_t neededlibs; // needed libs for main elf uintptr_t ep; // entry point @@ -336,6 +329,9 @@ typedef struct box64context_s { int stack_clone_used; int current_line; + struct x86_ld_info *kzt_loader_bridge_info; + uint32_t kzt_loader_callback_original[2]; + uint32_t kzt_loader_debug_state_original[2]; #ifdef CONFIG_LATX_DEBUG struct latx_kzt_debug **latx_kzt_debugs; int latx_kzt_debugcap; @@ -345,11 +341,14 @@ typedef struct box64context_s { kzt_guest_registry_context_t kzt_guest_registry_context; kzt_guest_library_access_t kzt_guest_library_access; kzt_guest_scope_layout_t kzt_guest_scope_layout; + kzt_loader_event_hook_t kzt_loader_event_hook; kzt_lazy_prebind_scope_t *kzt_lazy_prebind_scope; kzt_patch_spike_guard_t kzt_patch_spike_guard; uintptr_t kzt_plt_resolver_bridge; - uintptr_t kzt_lazy_completion_bridge; + int kzt_lazy_prebind_refresh_pending; + int kzt_guest_loader_route_present; #endif + kzt_xcb_connection_map_t *kzt_xcb_connection_map; } box64context_t; extern box64context_t *my_context; // global context @@ -371,8 +370,6 @@ kzt_patch_spike_guard_t *KztPatchSpikeGuardForContext(box64context_t *context); // return the index of the added header int AddElfHeader(box64context_t* ctx, elfheader_t* head); -int AddMallocMap(box64context_t* ctx, struct malloc_map* map); -struct malloc_map * SearchMallocMap(box64context_t* ctx, char *elfname); #if defined(CONFIG_LATX_KZT) && defined(CONFIG_LATX_DEBUG) int AddKztDebugInfo(box64context_t* ctx, struct latx_kzt_debug* debuginfo); #endif diff --git a/target/i386/latx/include/bridge.h b/target/i386/latx/include/bridge.h index db7aa25a0c5..a3d85a7b02c 100755 --- a/target/i386/latx/include/bridge.h +++ b/target/i386/latx/include/bridge.h @@ -7,6 +7,15 @@ typedef struct bridge_s bridge_t; typedef struct box64context_s box64context_t; typedef void (*wrapper_t)( uintptr_t fnc); typedef struct brick_s brick_t; + +#ifndef KZT_BRIDGE_GUARD_KIND_DEFINED +#define KZT_BRIDGE_GUARD_KIND_DEFINED +typedef enum kzt_bridge_guard_kind { + KZT_BRIDGE_GUARD_NONE = 0, + KZT_BRIDGE_GUARD_XCB_CONNECTION = 1, +} kzt_bridge_guard_kind_t; +#endif + brick_t* NewBrick(void); bridge_t *NewBridge(void); /* Call only after all concurrent bridge users have stopped. */ @@ -17,6 +26,9 @@ void FreeBridge(bridge_t** bridge); int BridgeForkProtectionAvailable(void); uintptr_t AddBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N, const char* name); +uintptr_t AddGuardedBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N, + const char* name, uintptr_t fallback, + kzt_bridge_guard_kind_t guard_kind); uintptr_t CheckBridged(bridge_t* bridge, void* fnc); uintptr_t AddCheckBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N, const char* name); uintptr_t AddAutomaticBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N); @@ -33,6 +45,7 @@ typedef void (*bridge_test_hook_fn)(void *opaque); void bridge_test_set_after_check_hook(bridge_test_hook_fn hook, void *opaque); void bridge_test_set_before_free_hook(bridge_test_hook_fn hook, void *opaque); int bridge_test_lock_is_held(bridge_t *bridge); +int bridge_test_guarded_count(bridge_t *bridge); #endif void init_bridge_helper(void); diff --git a/target/i386/latx/include/bridge_private.h b/target/i386/latx/include/bridge_private.h index 1f71bc8814d..4edc46ee5ed 100755 --- a/target/i386/latx/include/bridge_private.h +++ b/target/i386/latx/include/bridge_private.h @@ -5,6 +5,14 @@ // the generic wrapper pointer functions typedef void (*wrapper_t)(uintptr_t fnc); +#ifndef KZT_BRIDGE_GUARD_KIND_DEFINED +#define KZT_BRIDGE_GUARD_KIND_DEFINED +typedef enum kzt_bridge_guard_kind { + KZT_BRIDGE_GUARD_NONE = 0, + KZT_BRIDGE_GUARD_XCB_CONNECTION = 1, +} kzt_bridge_guard_kind_t; +#endif + #pragma pack(push, 1) typedef union onebridge_s { struct { @@ -14,6 +22,8 @@ typedef union onebridge_s { uintptr_t f; // the function for the wrapper uint8_t C3; // C2 or C3 ret uint16_t N; // N in case of C2 ret + uintptr_t guest_fallback_target; + uint8_t guard_kind; }; struct { uint8_t B8; // B8 00 11 22 33 mov rax, num @@ -25,4 +35,6 @@ typedef union onebridge_s { } onebridge_t; #pragma pack(pop) +_Static_assert(sizeof(onebridge_t) == 32, "onebridge_t ABI must stay 32 bytes"); + #endif //__BRIDGE_PRIVATE_H_ diff --git a/target/i386/latx/include/dlopen_recycle_transaction.h b/target/i386/latx/include/dlopen_recycle_transaction.h deleted file mode 100644 index 89ac4df90f5..00000000000 --- a/target/i386/latx/include/dlopen_recycle_transaction.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef DLOPEN_RECYCLE_TRANSACTION_H -#define DLOPEN_RECYCLE_TRANSACTION_H - -#include - -typedef int (*dlopen_recycle_step_fn)(void *opaque); -typedef void (*dlopen_recycle_prepare_fn)(void *opaque); -typedef void (*dlopen_recycle_rollback_fn)(void *opaque, int guest_opened); - -void *dlopen_recycle_transaction( - void *success_handle, size_t *count, int increment_count, void *opaque, - dlopen_recycle_prepare_fn prepare, dlopen_recycle_step_fn guest_open, - dlopen_recycle_step_fn reload, - dlopen_recycle_rollback_fn rollback); - -#endif diff --git a/target/i386/latx/include/generated/wrappedlibx11types.h b/target/i386/latx/include/generated/wrappedlibx11types.h index 2a5ee0229c9..88757a7790e 100644 --- a/target/i386/latx/include/generated/wrappedlibx11types.h +++ b/target/i386/latx/include/generated/wrappedlibx11types.h @@ -37,6 +37,7 @@ typedef int32_t (*iFppLp_t)(void*, void*, uintptr_t, void*); typedef void* (*pFpppp_t)(void*, void*, void*, void*); #define SUPER() ADDED_FUNCTIONS() \ + GO(XCloseDisplay, iFp_t) \ GO(XDestroyImage, iFp_t) \ GO(XInitImage, iFp_t) \ GO(XOpenDisplay, pFp_t) \ diff --git a/target/i386/latx/include/kzt_bridge_exact.h b/target/i386/latx/include/kzt_bridge_exact.h index 42c9b027bef..5b074302f07 100644 --- a/target/i386/latx/include/kzt_bridge_exact.h +++ b/target/i386/latx/include/kzt_bridge_exact.h @@ -3,9 +3,14 @@ #include +#include "bridge_private.h" + typedef void (*kzt_bridge_wrapper_t)(uintptr_t fnc); int kzt_bridge_is_exact(uintptr_t target, kzt_bridge_wrapper_t wrapper, void *native_symbol); +int kzt_guarded_bridge_is_exact( + uintptr_t target, kzt_bridge_wrapper_t wrapper, void *native_symbol, + uintptr_t guest_fallback_target, kzt_bridge_guard_kind_t guard_kind); #endif diff --git a/target/i386/latx/include/kzt_guest_cancel_scope.h b/target/i386/latx/include/kzt_guest_cancel_scope.h new file mode 100644 index 00000000000..30e813208b9 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_cancel_scope.h @@ -0,0 +1,17 @@ +#ifndef KZT_GUEST_CANCEL_SCOPE_H +#define KZT_GUEST_CANCEL_SCOPE_H + +#include "kzt_guest_runtime_entry.h" + +typedef struct kzt_guest_cancel_scope { + kzt_guest_runtime_entry_scope_t runtime; + int oldtype; + int switched; +} kzt_guest_cancel_scope_t; + +void kzt_guest_cancel_scope_begin( + box64context_t *context, kzt_guest_cancel_scope_t *scope); +void kzt_guest_cancel_scope_end(kzt_guest_cancel_scope_t *scope); +void kzt_guest_cancel_scope_cleanup(void *opaque); + +#endif diff --git a/target/i386/latx/include/kzt_guest_dl_api.h b/target/i386/latx/include/kzt_guest_dl_api.h index 7ee9690bcfa..81b3a66b209 100644 --- a/target/i386/latx/include/kzt_guest_dl_api.h +++ b/target/i386/latx/include/kzt_guest_dl_api.h @@ -65,6 +65,7 @@ void kzt_guest_dl_api_bind_current_thread(kzt_guest_dlerror_state_t *state); void kzt_guest_dl_api_free_errors(kzt_guest_dlerror_state_t *state); int kzt_guest_dl_api_entry_state_init(dlprivate_t *dl); +void kzt_guest_dl_api_entry_state_begin_teardown(dlprivate_t *dl); void kzt_guest_dl_api_entry_state_destroy(dlprivate_t *dl); static inline const kzt_guest_dl_entries_t * kzt_guest_dl_api_load_entries(dlprivate_t *dl) @@ -119,7 +120,8 @@ kzt_guest_dl_symbol_result_t kzt_guest_dl_api_dlvsym( box64context_t *context, const kzt_guest_dl_entries_t *entries, void *handle, void *symbol, const char *version); kzt_guest_dlerror_result_t kzt_guest_dl_api_dlerror( - kzt_guest_dlerror_state_t *state, uintptr_t guest_dlerror); + kzt_guest_dlerror_state_t *state, uintptr_t guest_dlerror, + int guest_route_may_have_pending_error); int kzt_guest_dl_api_dlinfo( const kzt_guest_dl_entries_t *entries, void *handle, int request, void *info); diff --git a/target/i386/latx/include/kzt_guest_dl_state.h b/target/i386/latx/include/kzt_guest_dl_state.h index b321b56bfd1..0519b150c0b 100644 --- a/target/i386/latx/include/kzt_guest_dl_state.h +++ b/target/i386/latx/include/kzt_guest_dl_state.h @@ -32,9 +32,21 @@ typedef struct kzt_guest_dl_entries_s { typedef int (*kzt_guest_dl_entries_resolver_fn)( kzt_guest_dl_entries_t *entries, void *opaque); -typedef void (*kzt_guest_dl_entries_prepare_fn)( +typedef int (*kzt_guest_dl_entries_prepare_fn)( const kzt_guest_dl_entries_t *entries, void *opaque); +typedef enum kzt_guest_runtime_entry_id_e { + KZT_GUEST_RUNTIME_FREE = 0, + KZT_GUEST_RUNTIME_REALLOC, + KZT_GUEST_RUNTIME_PTHREAD_SETCANCELTYPE, + KZT_GUEST_RUNTIME_ENTRY_COUNT, +} kzt_guest_runtime_entry_id_t; + +#define KZT_GUEST_DL_LIFECYCLE_OPEN (1U << 31) +#define KZT_GUEST_DL_LIFECYCLE_CLOSING (1U << 30) +#define KZT_GUEST_DL_LIFECYCLE_USERS \ + ~(KZT_GUEST_DL_LIFECYCLE_OPEN | KZT_GUEST_DL_LIFECYCLE_CLOSING) + typedef struct kzt_guest_dl_entry_state_s { pthread_mutex_t mutex; pthread_cond_t ready; @@ -45,7 +57,12 @@ typedef struct kzt_guest_dl_entry_state_s { int initializing; int initializer_valid; int teardown; + unsigned int lifecycle; unsigned int slow_users; + unsigned int runtime_users; + uintptr_t runtime_entries[KZT_GUEST_RUNTIME_ENTRY_COUNT]; + pthread_t runtime_initializers[KZT_GUEST_RUNTIME_ENTRY_COUNT]; + unsigned int runtime_initializing; } kzt_guest_dl_entry_state_t; #endif diff --git a/target/i386/latx/include/kzt_guest_dynsym_lookup.h b/target/i386/latx/include/kzt_guest_dynsym_lookup.h index 4787a1ac251..b0776d2026e 100644 --- a/target/i386/latx/include/kzt_guest_dynsym_lookup.h +++ b/target/i386/latx/include/kzt_guest_dynsym_lookup.h @@ -7,6 +7,11 @@ #include "kzt_guest_link_map_reader.h" #include "kzt_patch_planner.h" +enum { + KZT_ELF_STB_GNU_UNIQUE = 10, + KZT_ELF_STT_GNU_IFUNC = 10, +}; + typedef enum kzt_guest_dynsym_lookup_status { KZT_GUEST_DYNSYM_LOOKUP_FOUND = 0, KZT_GUEST_DYNSYM_LOOKUP_NOT_FOUND, diff --git a/target/i386/latx/include/kzt_guest_glob_dat_target.h b/target/i386/latx/include/kzt_guest_glob_dat_target.h new file mode 100644 index 00000000000..b54a8e72d9f --- /dev/null +++ b/target/i386/latx/include/kzt_guest_glob_dat_target.h @@ -0,0 +1,53 @@ +#ifndef KZT_GUEST_GLOB_DAT_TARGET_H +#define KZT_GUEST_GLOB_DAT_TARGET_H + +#include + +#include "elf.h" +#include "kzt_guest_library_binding.h" +#include "kzt_guest_registry.h" +#include "kzt_guest_symbol_scope.h" +#include "kzt_jump_slot_production.h" +#include "kzt_patch_planner.h" + +typedef struct box64context_s box64context_t; +typedef struct elfheader_s elfheader_t; + +typedef struct kzt_guest_glob_dat_target { + uintptr_t guest_target; + uintptr_t selected_target; + kzt_patch_object_ref_t owner; + kzt_guest_registry_source_lease_t source_lease; + kzt_guest_registry_patch_decision_lease_t decision_lease; + kzt_guest_library_loader_quiescence_lease_t loader_quiescence_lease; + kzt_guest_symbol_scope_request_t scope_request; + kzt_guest_symbol_scope_result_t scope_proof; + int exact_bridge; +} kzt_guest_glob_dat_target_t; + +typedef struct kzt_guest_glob_dat_route_result { + uintptr_t guest_target; + uintptr_t selected_target; + uintptr_t final_value; + kzt_production_slot_transaction_result_t writer_result; +} kzt_guest_glob_dat_route_result_t; + +void kzt_guest_glob_dat_target_release( + kzt_guest_glob_dat_target_t *target); + +int kzt_guest_glob_dat_target_resolve( + box64context_t *context, elfheader_t *head, uintptr_t guest_target, + unsigned long symbol_index, const Elf64_Sym *symbol, + const char *symbol_name, int version, const char *version_name, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_glob_dat_target_t *target); + +int kzt_guest_glob_dat_route( + box64context_t *context, elfheader_t *head, uintptr_t slot_addr, + uintptr_t guest_target, unsigned long symbol_index, + const Elf64_Sym *symbol, const char *symbol_name, int version, + const char *version_name, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_glob_dat_route_result_t *result); + +#endif diff --git a/target/i386/latx/include/kzt_guest_library_adapter.h b/target/i386/latx/include/kzt_guest_library_adapter.h index 533f00ae4fc..4bac7a72f4f 100644 --- a/target/i386/latx/include/kzt_guest_library_adapter.h +++ b/target/i386/latx/include/kzt_guest_library_adapter.h @@ -15,6 +15,10 @@ typedef struct kzt_guest_wrapper_source_proof { kzt_guest_library_binding_key_t key; } kzt_guest_wrapper_source_proof_t; +const char *kzt_guest_library_wrapper_name_for_guest( + const char *guest_name); +int kzt_guest_library_wrapper_alias_symbol_allowed(const char *symbol); + int kzt_guest_library_wrapper_source_acquire( box64context_t *context, uintptr_t link_map_addr, const char *requested_path, const char *wrapper_name, @@ -65,6 +69,10 @@ int kzt_guest_library_run_dlinfo( uintptr_t kzt_guest_library_select_symbol_result( box64context_t *context, uintptr_t guest_handle, uintptr_t guest_result, const char *symbol, const char *version); +uintptr_t kzt_guest_library_select_symbol_result_with_identity( + box64context_t *context, uintptr_t guest_handle, + const kzt_guest_loader_identity_t *queried_identity, + uintptr_t guest_result, const char *symbol, const char *version); /* Shared production adapter used by wrappedlibc, wrappedlibdl, and loader * callback paths whenever one operation owns both exact values. */ diff --git a/target/i386/latx/include/kzt_guest_library_binding.h b/target/i386/latx/include/kzt_guest_library_binding.h index 3f2fa95d78c..5e897082dbe 100644 --- a/target/i386/latx/include/kzt_guest_library_binding.h +++ b/target/i386/latx/include/kzt_guest_library_binding.h @@ -203,6 +203,11 @@ int kzt_guest_library_access_lookup_by_library( kzt_guest_library_access_t *access, library_t *library, kzt_guest_library_binding_key_t *key, kzt_guest_library_handle_t *handle); +/* Revalidates that a retained handle still pins the same LIVE exact binding + * entry. This does not acquire a second reference. */ +int kzt_guest_library_handle_matches_key( + const kzt_guest_library_handle_t *handle, + const kzt_guest_library_binding_key_t *key); #ifdef KZT_GUEST_LIBRARY_BINDING_TEST int kzt_guest_library_lookup(kzt_guest_library_bindings_t *bindings, const kzt_guest_library_binding_key_t *key, @@ -212,11 +217,14 @@ void kzt_guest_library_handle_release(kzt_guest_library_handle_t *handle); int kzt_guest_library_symbol_evidence_lookup( const kzt_guest_library_handle_t *handle, const char *symbol, unsigned long dynamic_revision, uintptr_t *runtime_address, - unsigned char *symbol_type); + unsigned char *symbol_type, uintptr_t *bridge_target); void kzt_guest_library_symbol_evidence_store( const kzt_guest_library_handle_t *handle, const char *symbol, unsigned long dynamic_revision, uintptr_t runtime_address, unsigned char symbol_type); +void kzt_guest_library_symbol_bridge_store( + const kzt_guest_library_handle_t *handle, const char *symbol, + unsigned long dynamic_revision, uintptr_t bridge_target); /* Consumes one pinned exact handle, closes only that binding, and invokes a * non-blocking library cleanup callback before concurrent unbind can free the * library. The callback must not enter bindings or Registry APIs. */ diff --git a/target/i386/latx/include/kzt_guest_registry.h b/target/i386/latx/include/kzt_guest_registry.h index 88e00b422a0..6728b384d7c 100644 --- a/target/i386/latx/include/kzt_guest_registry.h +++ b/target/i386/latx/include/kzt_guest_registry.h @@ -167,6 +167,22 @@ typedef struct kzt_guest_registry_address_pair { kzt_guest_registry_address_match_t expected; } kzt_guest_registry_address_pair_t; +typedef struct kzt_guest_registry_symbol_candidate { + kzt_guest_registry_source_lease_t lease; + kzt_guest_dynamic_view_t dynamic_view; + uintptr_t link_map_addr; + uintptr_t map_start; + uintptr_t map_end; + uintptr_t namespace_id; + unsigned long generation; + unsigned long dynamic_view_revision; + kzt_guest_field_status_t dynamic_view_status; + kzt_guest_field_status_t path_status; + kzt_guest_field_status_t soname_status; + char path[KZT_GUEST_REGISTRY_ADDRESS_TEXT_LIMIT]; + char soname[KZT_GUEST_REGISTRY_ADDRESS_TEXT_LIMIT]; +} kzt_guest_registry_symbol_candidate_t; + typedef struct kzt_guest_registry_dump { kzt_guest_object_snapshot_t *objects; size_t count; @@ -324,6 +340,17 @@ int kzt_guest_registry_loader_symbol_source_acquire( kzt_guest_field_status_t *dynamic_status, unsigned long *dynamic_revision, kzt_guest_registry_source_lease_t *lease); +/* Acquires the same source proof from an exact guest dlinfo result without + * assuming that the opaque loader handle is a link_map or publishing a + * synthetic dlopen reference. */ +int kzt_guest_registry_loader_symbol_source_acquire_exact( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *queried_identity, + kzt_guest_loader_identity_t *identity, + kzt_guest_dynamic_view_t *dynamic_view, + kzt_guest_field_status_t *dynamic_status, + unsigned long *dynamic_revision, + kzt_guest_registry_source_lease_t *lease); /* Resolves a loader object while it is LIVE or pre-unmap UNLOADING. */ int kzt_guest_registry_find_loader_object_identity( kzt_guest_registry_t *registry, @@ -392,6 +419,15 @@ int kzt_guest_registry_patch_decision_lease_acquire( void kzt_guest_registry_patch_decision_lease_release( kzt_guest_registry_patch_decision_lease_t *lease); +/* Iterates main-namespace symbol-owner candidates while one source-derived + * decision lease freezes Registry mutations. Each returned candidate owns an + * exact source lease that must cover all guest-memory reads for that object. */ +int kzt_guest_registry_symbol_candidate_acquire_next( + const kzt_guest_registry_patch_decision_lease_t *decision_lease, + size_t *cursor, kzt_guest_registry_symbol_candidate_t *candidate); +void kzt_guest_registry_symbol_candidate_release( + kzt_guest_registry_symbol_candidate_t *candidate); + /* Claims the one PLTGOT injection transaction for the exact generation * protected by an active patch-decision lease. The supplied Dynamic View * must be the Registry's complete, matching view; otherwise this returns @@ -492,6 +528,9 @@ void kzt_guest_registry_dump_free(kzt_guest_registry_dump_t *dump); void kzt_guest_registry_test_set_alloc_failure_after(long allocations); void kzt_guest_registry_test_set_dynamic_commit_failure_after(long commits); void kzt_guest_registry_test_fail_next_cond_init(void); +int kzt_guest_registry_test_set_active_source_leases( + kzt_guest_registry_t *registry, uintptr_t link_map_addr, + unsigned long generation, unsigned long active_source_leases); #endif #endif diff --git a/target/i386/latx/include/kzt_guest_runtime_entry.h b/target/i386/latx/include/kzt_guest_runtime_entry.h new file mode 100644 index 00000000000..6aff51b4c25 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_runtime_entry.h @@ -0,0 +1,29 @@ +#ifndef KZT_GUEST_RUNTIME_ENTRY_H +#define KZT_GUEST_RUNTIME_ENTRY_H + +#include + +#include "kzt_guest_runtime_entry_state.h" + +typedef struct box64context_s box64context_t; +typedef struct dlprivate_s dlprivate_t; +typedef struct CPUX86State CPUX86State; + +uintptr_t kzt_guest_runtime_entry_load( + const box64context_t *context, kzt_guest_runtime_entry_id_t entry); + +uintptr_t kzt_guest_runtime_entry_ensure( + dlprivate_t *dl, kzt_guest_runtime_entry_id_t entry, + kzt_guest_runtime_entry_resolver_fn resolver, void *opaque); +uintptr_t kzt_guest_runtime_entry_for_guest_branch( + box64context_t *context, kzt_guest_runtime_entry_id_t entry); +int kzt_guest_runtime_entry_acquire( + box64context_t *context, kzt_guest_runtime_entry_id_t entry, + kzt_guest_runtime_entry_scope_t *scope); +void kzt_guest_runtime_entry_release( + kzt_guest_runtime_entry_scope_t *scope); + +uintptr_t kzt_runtime_guest_entry_or_abort( + CPUX86State *env, kzt_guest_runtime_entry_id_t entry); + +#endif diff --git a/target/i386/latx/include/kzt_guest_runtime_entry_state.h b/target/i386/latx/include/kzt_guest_runtime_entry_state.h new file mode 100644 index 00000000000..0eb37a2b4c7 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_runtime_entry_state.h @@ -0,0 +1,50 @@ +#ifndef KZT_GUEST_RUNTIME_ENTRY_STATE_H +#define KZT_GUEST_RUNTIME_ENTRY_STATE_H + +#include + +#include "kzt_guest_dl_state.h" + +typedef uintptr_t (*kzt_guest_runtime_entry_resolver_fn)( + const char *symbol, void *opaque); + +typedef struct kzt_guest_runtime_entry_scope_s { + kzt_guest_dl_entry_state_t *state; + uintptr_t address; +} kzt_guest_runtime_entry_scope_t; + +static inline uintptr_t kzt_guest_runtime_entry_state_load( + const kzt_guest_dl_entry_state_t *state, + kzt_guest_runtime_entry_id_t entry) +{ + if (!state || entry < 0 || entry >= KZT_GUEST_RUNTIME_ENTRY_COUNT || + !(__atomic_load_n(&state->lifecycle, __ATOMIC_ACQUIRE) & + KZT_GUEST_DL_LIFECYCLE_OPEN)) { + return 0; + } + return __atomic_load_n( + &state->runtime_entries[entry], __ATOMIC_ACQUIRE); +} + +int kzt_guest_dl_entry_state_enter(kzt_guest_dl_entry_state_t *state); +void kzt_guest_dl_entry_state_leave_locked( + kzt_guest_dl_entry_state_t *state); + +int kzt_guest_runtime_entry_state_publish( + kzt_guest_dl_entry_state_t *state, + const uintptr_t entries[KZT_GUEST_RUNTIME_ENTRY_COUNT]); + +uintptr_t kzt_guest_runtime_entry_state_ensure( + kzt_guest_dl_entry_state_t *state, + kzt_guest_runtime_entry_id_t entry, + kzt_guest_runtime_entry_resolver_fn resolver, void *opaque); +int kzt_guest_runtime_entry_state_acquire( + kzt_guest_dl_entry_state_t *state, + kzt_guest_runtime_entry_id_t entry, + kzt_guest_runtime_entry_scope_t *scope); +void kzt_guest_runtime_entry_state_release( + kzt_guest_runtime_entry_scope_t *scope); +void kzt_guest_runtime_entry_state_begin_teardown( + kzt_guest_dl_entry_state_t *state); + +#endif diff --git a/target/i386/latx/include/kzt_jump_slot_production.h b/target/i386/latx/include/kzt_jump_slot_production.h index 0b96b011fd2..94bf9c1e835 100644 --- a/target/i386/latx/include/kzt_jump_slot_production.h +++ b/target/i386/latx/include/kzt_jump_slot_production.h @@ -6,7 +6,6 @@ #include "elf.h" #include "kzt_guest_registry.h" #include "kzt_jump_slot_route.h" -#include "kzt_lazy_binding.h" #include "kzt_lazy_direct_route.h" #include "kzt_lazy_prebind_scope.h" @@ -15,6 +14,35 @@ typedef struct elfheader_s elfheader_t; typedef int (*kzt_lazy_prebind_target_prepare_fn)(uintptr_t target, void *opaque); +typedef enum kzt_production_slot_transaction_result { + KZT_PRODUCTION_SLOT_TRANSACTION_ERROR = -1, + KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH = 0, + KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED = 1, + KZT_PRODUCTION_SLOT_TRANSACTION_ROLLED_BACK = 2, + KZT_PRODUCTION_SLOT_TRANSACTION_UNRECOVERABLE = 3, + KZT_PRODUCTION_SLOT_TRANSACTION_CIRCUIT_OPEN = 4, +} kzt_production_slot_transaction_result_t; + +kzt_production_slot_transaction_result_t +kzt_production_eager_relocation_write( + box64context_t *context, uintptr_t source_link_map, + const kzt_patch_object_ref_t *owner, + kzt_patch_relocation_type_t reloc_type, uintptr_t slot_addr, + uintptr_t expected, uintptr_t replacement, const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, const char *version, + uintptr_t *final_value); + +/* Guest relocation is mandatory correctness work. It uses the same + * permission/CAS/rollback transaction as native patching, but is deliberately + * independent of the optional native-patch budget and circuit breaker. */ +kzt_production_slot_transaction_result_t +kzt_production_guest_relocation_write( + box64context_t *context, uintptr_t source_link_map, + kzt_patch_relocation_type_t reloc_type, uintptr_t slot_addr, + uintptr_t expected, uintptr_t replacement, const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, const char *version, + uintptr_t *final_value); + /* A nonzero return guarantees that no slot write was previously attempted or * committed by this route, so callers may safely use their legacy write. */ int kzt_production_jump_slot_route( @@ -67,39 +95,4 @@ int kzt_production_lazy_prebind_retire( box64context_t *context, const kzt_lazy_prebind_identity_t *identity); -int kzt_production_lazy_route_guest_target( - box64context_t *context, - const kzt_lazy_binding_pending_t *pending, - uintptr_t guest_target, - kzt_lazy_binding_route_result_t *result); - -/* Production resolver orchestration. One exact source lease covers the - * first slot load, post-bind validation, route transaction, and final - * release. */ -int kzt_production_lazy_complete( - box64context_t *context, - kzt_lazy_binding_pending_t *pending, - kzt_lazy_binding_result_t *result); - -int kzt_production_lazy_source_lease_acquire( - box64context_t *context, - const kzt_lazy_binding_pending_t *pending, - kzt_guest_registry_source_lease_t *source_lease); - -int kzt_production_lazy_load_slot_with_lease( - box64context_t *context, - const kzt_lazy_binding_pending_t *pending, - uintptr_t slot_addr, - uintptr_t *value, - kzt_guest_registry_source_lease_t *source_lease); - -/* The caller-held exact (link_map, generation, namespace) source lease must - * cover the first source slot read through the complete route transaction. */ -int kzt_production_lazy_route_guest_target_leased( - box64context_t *context, - const kzt_lazy_binding_pending_t *pending, - uintptr_t guest_target, - const kzt_guest_registry_source_lease_t *source_lease, - kzt_lazy_binding_route_result_t *result); - #endif diff --git a/target/i386/latx/include/kzt_lazy_binding.h b/target/i386/latx/include/kzt_lazy_binding.h deleted file mode 100644 index 3150ecab224..00000000000 --- a/target/i386/latx/include/kzt_lazy_binding.h +++ /dev/null @@ -1,131 +0,0 @@ -#ifndef KZT_LAZY_BINDING_H -#define KZT_LAZY_BINDING_H - -#include - -#include "kzt_guest_library_binding.h" -#include "kzt_patch_planner.h" - -#define KZT_LAZY_BINDING_SYMBOL_MAX 128 -#define KZT_LAZY_BINDING_VERSION_MAX 128 - -typedef enum kzt_lazy_binding_status { - KZT_LAZY_BINDING_BYPASS = 0, - KZT_LAZY_BINDING_HANDOFF_GUEST, - KZT_LAZY_BINDING_WAITING_GUEST_TARGET, - KZT_LAZY_BINDING_GUEST_PRESERVED, - KZT_LAZY_BINDING_NATIVE_APPLIED, - KZT_LAZY_BINDING_CAS_MISMATCH, - KZT_LAZY_BINDING_WRITE_ROLLED_BACK, - KZT_LAZY_BINDING_UNRECOVERABLE, - KZT_LAZY_BINDING_ERROR, -} kzt_lazy_binding_status_t; - -typedef enum kzt_lazy_binding_reason { - KZT_LAZY_BINDING_REASON_NONE = 0, - KZT_LAZY_BINDING_REASON_DISABLED, - KZT_LAZY_BINDING_REASON_INVALID_REQUEST, - KZT_LAZY_BINDING_REASON_PENDING_OCCUPIED, - KZT_LAZY_BINDING_REASON_GENERATION_CHANGED, - KZT_LAZY_BINDING_REASON_NON_MAIN_NAMESPACE, - KZT_LAZY_BINDING_REASON_RESOLVER_MISSING, - KZT_LAZY_BINDING_REASON_SLOT_UNCHANGED, - KZT_LAZY_BINDING_REASON_MISSING_VERSION, - KZT_LAZY_BINDING_REASON_POST_BIND_INVALID, - KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE, - KZT_LAZY_BINDING_REASON_NATIVE_APPLIED, - KZT_LAZY_BINDING_REASON_CAS_MISMATCH, - KZT_LAZY_BINDING_REASON_SLOT_READ_ERROR, - KZT_LAZY_BINDING_REASON_CAS_ERROR, - KZT_LAZY_BINDING_REASON_WRITE_ROLLED_BACK, - KZT_LAZY_BINDING_REASON_UNRECOVERABLE, -} kzt_lazy_binding_reason_t; - -#define KZT_LAZY_BINDING_REASON_PENDING_BUSY \ - KZT_LAZY_BINDING_REASON_PENDING_OCCUPIED - -typedef struct kzt_lazy_binding_begin_request { - int enabled; - uintptr_t context_id; - uintptr_t source_link_map; - unsigned long source_generation; - uintptr_t namespace_id; - kzt_guest_library_namespace_kind_t namespace_kind; - unsigned long relocation_index; - uintptr_t slot_addr; - uintptr_t unresolved_stub; - const char *symbol; - kzt_symbol_version_evidence_t version_evidence; - const char *version; - long addend; - uintptr_t guest_resolver; -} kzt_lazy_binding_begin_request_t; - -typedef struct kzt_lazy_binding_pending { - int armed; - uintptr_t context_id; - uintptr_t source_link_map; - unsigned long source_generation; - uintptr_t namespace_id; - kzt_guest_library_namespace_kind_t namespace_kind; - unsigned long relocation_index; - uintptr_t slot_addr; - uintptr_t unresolved_stub; - uintptr_t guest_resolver; - long addend; - const char *symbol; - kzt_symbol_version_evidence_t version_evidence; - const char *version; - char symbol_storage[KZT_LAZY_BINDING_SYMBOL_MAX]; - char version_storage[KZT_LAZY_BINDING_VERSION_MAX]; -} kzt_lazy_binding_pending_t; - -typedef enum kzt_lazy_binding_route_status { - KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED = 0, - KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED, - KZT_LAZY_BINDING_ROUTE_CAS_MISMATCH, - KZT_LAZY_BINDING_ROUTE_WRITE_ROLLED_BACK, - KZT_LAZY_BINDING_ROUTE_UNRECOVERABLE, - KZT_LAZY_BINDING_ROUTE_ERROR, -} kzt_lazy_binding_route_status_t; - -typedef struct kzt_lazy_binding_route_result { - kzt_lazy_binding_route_status_t status; - uintptr_t selected_target; - uintptr_t final_value; -} kzt_lazy_binding_route_result_t; - -typedef struct kzt_lazy_binding_ops { - int (*load_slot)(uintptr_t slot_addr, uintptr_t *value, void *opaque); - int (*validate_post_bind)(const kzt_lazy_binding_pending_t *pending, - uintptr_t guest_target, void *opaque); - int (*route_guest_target)(const kzt_lazy_binding_pending_t *pending, - uintptr_t guest_target, - kzt_lazy_binding_route_result_t *result, - void *opaque); - void *opaque; -} kzt_lazy_binding_ops_t; - -typedef struct kzt_lazy_binding_result { - kzt_lazy_binding_status_t status; - kzt_lazy_binding_reason_t reason; - uintptr_t slot_before; - uintptr_t slot_after; - uintptr_t selected_target; - int pending_armed; - int pending_cleared; -} kzt_lazy_binding_result_t; - -int kzt_lazy_binding_begin( - const kzt_lazy_binding_begin_request_t *request, - kzt_lazy_binding_pending_t *pending, - kzt_lazy_binding_result_t *result); - -int kzt_lazy_binding_complete( - kzt_lazy_binding_pending_t *pending, - const kzt_lazy_binding_ops_t *ops, - kzt_lazy_binding_result_t *result); - -void kzt_lazy_binding_cancel(kzt_lazy_binding_pending_t *pending); - -#endif diff --git a/target/i386/latx/include/kzt_lazy_diagnostics.h b/target/i386/latx/include/kzt_lazy_diagnostics.h deleted file mode 100644 index 9a1ef59231b..00000000000 --- a/target/i386/latx/include/kzt_lazy_diagnostics.h +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef KZT_LAZY_DIAGNOSTICS_H -#define KZT_LAZY_DIAGNOSTICS_H - -#include -#include - -#include "kzt_lazy_binding.h" - -#define KZT_LAZY_DIAGNOSTIC_SYMBOL_LIMIT KZT_LAZY_BINDING_SYMBOL_MAX -#define KZT_LAZY_DIAGNOSTIC_ROUTE_LIMIT 64 -#define KZT_LAZY_DIAGNOSTIC_REASON_LIMIT 64 -#define KZT_LAZY_DIAGNOSTIC_LINE_LIMIT 1024 - -typedef enum kzt_lazy_diagnostic_format_status { - KZT_LAZY_DIAGNOSTIC_FORMAT_ERROR = -1, - KZT_LAZY_DIAGNOSTIC_FORMAT_OK = 0, - KZT_LAZY_DIAGNOSTIC_FORMAT_TRUNCATED, -} kzt_lazy_diagnostic_format_status_t; - -typedef enum kzt_lazy_diagnostic_emit_status { - KZT_LAZY_DIAGNOSTIC_EMIT_DISABLED = 0, - KZT_LAZY_DIAGNOSTIC_EMIT_EMITTED, - KZT_LAZY_DIAGNOSTIC_EMIT_FORMAT_FAILED, - KZT_LAZY_DIAGNOSTIC_EMIT_FORMAT_TRUNCATED, - KZT_LAZY_DIAGNOSTIC_EMIT_SINK_FAILED, -} kzt_lazy_diagnostic_emit_status_t; - -typedef struct kzt_lazy_diagnostic_record { - unsigned long schema; - char symbol[KZT_LAZY_DIAGNOSTIC_SYMBOL_LIMIT]; - char first_execution_route[KZT_LAZY_DIAGNOSTIC_ROUTE_LIMIT]; - char completion_route_status[KZT_LAZY_DIAGNOSTIC_ROUTE_LIMIT]; - uintptr_t slot_before; - uintptr_t slot_after_guest; - uintptr_t selected_second_target; - char reason[KZT_LAZY_DIAGNOSTIC_REASON_LIMIT]; -} kzt_lazy_diagnostic_record_t; - -typedef int (*kzt_lazy_diagnostic_sink_fn)(const char *line, - size_t line_length, - void *opaque); - -typedef struct kzt_lazy_diagnostic_input { - int enabled; - const kzt_lazy_binding_pending_t *pending; - const kzt_lazy_binding_result_t *binding_result; - char *buffer; - size_t buffer_size; - kzt_lazy_diagnostic_sink_fn sink; - void *sink_opaque; -} kzt_lazy_diagnostic_input_t; - -typedef struct kzt_lazy_diagnostic_emit_result { - kzt_lazy_diagnostic_emit_status_t status; - int record_present; - kzt_lazy_diagnostic_record_t record; - kzt_lazy_diagnostic_format_status_t format_status; - int sink_status; -} kzt_lazy_diagnostic_emit_result_t; - -int kzt_lazy_diagnostic_record_build( - const kzt_lazy_binding_pending_t *pending, - const kzt_lazy_binding_result_t *binding_result, - kzt_lazy_diagnostic_record_t *record); - -kzt_lazy_diagnostic_format_status_t kzt_lazy_diagnostic_format( - const kzt_lazy_diagnostic_record_t *record, - char *buffer, - size_t buffer_size); - -int kzt_lazy_diagnostics_stderr_sink(const char *line, - size_t line_length, - void *opaque); - -int kzt_lazy_diagnostics_emit( - const kzt_lazy_diagnostic_input_t *input, - kzt_lazy_diagnostic_emit_result_t *result); - -int kzt_lazy_diagnostics_emit_production( - const kzt_lazy_binding_pending_t *pending, - const kzt_lazy_binding_result_t *binding_result, - kzt_lazy_diagnostic_emit_result_t *result); - -#endif diff --git a/target/i386/latx/include/kzt_lazy_direct_route.h b/target/i386/latx/include/kzt_lazy_direct_route.h index 6e0a24ee6a4..45957dbacc5 100644 --- a/target/i386/latx/include/kzt_lazy_direct_route.h +++ b/target/i386/latx/include/kzt_lazy_direct_route.h @@ -10,6 +10,9 @@ typedef enum kzt_lazy_direct_route_status { KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED = 0, KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED, + KZT_LAZY_DIRECT_ROUTE_NATIVE_TRANSIENT, + KZT_LAZY_DIRECT_ROUTE_WRITE_ROLLED_BACK, + KZT_LAZY_DIRECT_ROUTE_UNRECOVERABLE, } kzt_lazy_direct_route_status_t; typedef enum kzt_lazy_direct_route_reason { @@ -21,6 +24,7 @@ typedef enum kzt_lazy_direct_route_reason { KZT_LAZY_DIRECT_ROUTE_REASON_INVALID_VERSION, KZT_LAZY_DIRECT_ROUTE_REASON_UNSUPPORTED_SYMBOL_BINDING, KZT_LAZY_DIRECT_ROUTE_REASON_GUEST_OWNED_SYMBOL, + KZT_LAZY_DIRECT_ROUTE_REASON_DLERROR_PREBIND_REQUIRED, KZT_LAZY_DIRECT_ROUTE_REASON_PREEMPTION_UNPROVEN, KZT_LAZY_DIRECT_ROUTE_REASON_SOURCE_REJECTED, KZT_LAZY_DIRECT_ROUTE_REASON_PROVIDER_UNAVAILABLE, @@ -31,10 +35,17 @@ typedef enum kzt_lazy_direct_route_reason { KZT_LAZY_DIRECT_ROUTE_REASON_FINAL_VALIDATION_FAILED, KZT_LAZY_DIRECT_ROUTE_REASON_CAS_MISMATCH, KZT_LAZY_DIRECT_ROUTE_REASON_CAS_ERROR, + KZT_LAZY_DIRECT_ROUTE_REASON_BUDGET_EXHAUSTED, + KZT_LAZY_DIRECT_ROUTE_REASON_WRITE_ROLLED_BACK, + KZT_LAZY_DIRECT_ROUTE_REASON_UNRECOVERABLE, KZT_LAZY_DIRECT_ROUTE_REASON_NATIVE_APPLIED, + KZT_LAZY_DIRECT_ROUTE_REASON_NATIVE_TRANSIENT, } kzt_lazy_direct_route_reason_t; typedef enum kzt_lazy_direct_route_cas_status { + KZT_LAZY_DIRECT_ROUTE_CAS_UNRECOVERABLE = -4, + KZT_LAZY_DIRECT_ROUTE_CAS_BUDGET_EXHAUSTED = -3, + KZT_LAZY_DIRECT_ROUTE_CAS_ROLLED_BACK = -2, KZT_LAZY_DIRECT_ROUTE_CAS_ERROR = -1, KZT_LAZY_DIRECT_ROUTE_CAS_MISMATCH = 0, KZT_LAZY_DIRECT_ROUTE_CAS_APPLIED = 1, @@ -60,6 +71,7 @@ typedef struct kzt_lazy_direct_route_input { uintptr_t slot_addr; uintptr_t guest_unresolved_slot; uintptr_t expected_current_slot; + int allow_budget_transient_native; } kzt_lazy_direct_route_input_t; typedef struct kzt_lazy_direct_route_provider { @@ -74,6 +86,7 @@ typedef struct kzt_lazy_direct_route_bridge { uintptr_t target; kzt_symbol_version_evidence_t version_evidence; const char *version; + int transient_safe; } kzt_lazy_direct_route_bridge_t; typedef struct kzt_lazy_direct_route_lease { diff --git a/target/i386/latx/include/kzt_lazy_prebind_scope.h b/target/i386/latx/include/kzt_lazy_prebind_scope.h index a8e50b3ce19..f80aa59b041 100644 --- a/target/i386/latx/include/kzt_lazy_prebind_scope.h +++ b/target/i386/latx/include/kzt_lazy_prebind_scope.h @@ -46,6 +46,9 @@ typedef struct kzt_lazy_prebind_record { uintptr_t bridge_target; unsigned long bridge_generation; int bridge_custom_wrapper; + /* Set only for a process-resident source whose loader wrapper ownership + * cannot change across dlopen/dlclose namespace mutations. */ + int loader_mutation_invariant; kzt_symbol_version_evidence_t version_evidence; char symbol[KZT_LAZY_PREBIND_TEXT_MAX]; char version[KZT_LAZY_PREBIND_TEXT_MAX]; @@ -68,6 +71,13 @@ uint64_t kzt_lazy_prebind_scope_epoch(kzt_lazy_prebind_scope_t *scope); uint64_t kzt_lazy_prebind_scope_mutate( kzt_lazy_prebind_scope_t *scope, kzt_lazy_prebind_mutation_t mutation); +/* True only while this exact source has a current published custom dlerror + * bridge. Process-resident records can remain current across mutations; + * other records are invalidated with their scope epoch. */ +int kzt_lazy_prebind_scope_has_native_dlerror( + kzt_lazy_prebind_scope_t *scope, + const kzt_lazy_prebind_identity_t *source); + kzt_lazy_prebind_claim_result_t kzt_lazy_prebind_scope_claim( kzt_lazy_prebind_scope_t *scope, const kzt_lazy_prebind_record_t *record); @@ -78,6 +88,8 @@ int kzt_lazy_prebind_scope_acquire( kzt_lazy_prebind_scope_t *scope, const kzt_lazy_prebind_record_t *expected, kzt_lazy_prebind_lease_t *lease); +int kzt_lazy_prebind_scope_lease_published( + const kzt_lazy_prebind_lease_t *lease); void kzt_lazy_prebind_scope_release(kzt_lazy_prebind_lease_t *lease); /* Only one current record owner can publish a speculative bridge. The diff --git a/target/i386/latx/include/kzt_loader_event_hook.h b/target/i386/latx/include/kzt_loader_event_hook.h index 530ed32aad8..4fc76c0808d 100644 --- a/target/i386/latx/include/kzt_loader_event_hook.h +++ b/target/i386/latx/include/kzt_loader_event_hook.h @@ -6,12 +6,24 @@ #include "kzt_guest_scope_layout.h" +typedef struct box64context_s box64context_t; + #define KZT_LOADER_EVENT_HOOK_BUILD_ID_SIZE 41 -#define KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID \ +#define KZT_LOADER_EVENT_HOOK_GLIBC_2_28_BUILD_ID \ + "3b10a1b21d87ee3af8da437bce08fe1ca1a0aaff" +#define KZT_LOADER_EVENT_HOOK_GLIBC_2_39_BUILD_ID \ "c591a5df63f461bfdafb01908ca16845b375fa37" +#define KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID \ + KZT_LOADER_EVENT_HOOK_GLIBC_2_39_BUILD_ID #define KZT_LOADER_EVENT_HOOK_DEBUG_STATE_OFFSET 0x3630 #define KZT_LOADER_EVENT_HOOK_R_DEBUG_OFFSET 0x36e58 +typedef struct kzt_loader_event_layout { + kzt_guest_scope_layout_t scope_layout; + uintptr_t debug_state_offset; + uintptr_t r_debug_offset; +} kzt_loader_event_layout_t; + typedef enum kzt_loader_event_hook_result { KZT_LOADER_EVENT_HOOK_INSTALLED = 0, KZT_LOADER_EVENT_HOOK_FAIL_OPEN_DISABLED, @@ -42,6 +54,8 @@ typedef struct kzt_loader_event_hook { unsigned int lifecycle_lock; unsigned int lifecycle_publishers; unsigned int lifecycle_result; + unsigned int lifecycle_confirmed; + unsigned int lifecycle_failed; size_t pending_delete_count; size_t pending_delete_capacity; struct kzt_loader_lifecycle_identity *pending_delete; @@ -72,7 +86,7 @@ typedef int (*kzt_loader_lifecycle_resolve_fn)( typedef int (*kzt_loader_lifecycle_transition_fn)( const kzt_loader_lifecycle_identity_t *identity, void *opaque); -typedef void (*kzt_loader_lifecycle_unload_fn)( +typedef int (*kzt_loader_lifecycle_unload_fn)( const kzt_loader_lifecycle_identity_t *identity, void *opaque); @@ -81,6 +95,9 @@ typedef void (*kzt_loader_lifecycle_unload_fn)( int kzt_loader_event_hook_read_build_id( const char *path, char build_id[KZT_LOADER_EVENT_HOOK_BUILD_ID_SIZE]); +int kzt_loader_event_hook_lookup_layout( + const char *build_id, kzt_loader_event_layout_t *layout); + int kzt_loader_event_hook_install(kzt_loader_event_hook_t *hook, const char *build_id, uintptr_t callback_addr, @@ -111,6 +128,8 @@ int kzt_loader_event_hook_publish_lifecycle( kzt_loader_lifecycle_unload_fn unload, void *opaque); int kzt_loader_event_hook_destroy(kzt_loader_event_hook_t *hook); +void kzt_loader_event_hook_context_init(kzt_loader_event_hook_t *hook); +void kzt_loader_event_hook_context_destroy(kzt_loader_event_hook_t *hook); kzt_guest_scope_layout_t kzt_loader_event_hook_scope_layout( const kzt_loader_event_hook_t *hook); @@ -120,6 +139,9 @@ const char *kzt_loader_event_hook_result_name( kzt_loader_lifecycle_result_t kzt_loader_event_hook_lifecycle_result( const kzt_loader_event_hook_t *hook); +int kzt_loader_event_hook_lifecycle_healthy( + const kzt_loader_event_hook_t *hook); +int kzt_loader_lifecycle_runtime_healthy(box64context_t *context); const char *kzt_loader_lifecycle_result_name( kzt_loader_lifecycle_result_t result); diff --git a/target/i386/latx/include/kzt_observation_adapter.h b/target/i386/latx/include/kzt_observation_adapter.h index 1403a15efdc..7da6e635169 100644 --- a/target/i386/latx/include/kzt_observation_adapter.h +++ b/target/i386/latx/include/kzt_observation_adapter.h @@ -21,6 +21,7 @@ typedef enum kzt_observation_adapter_result { KZT_OBSERVATION_ADAPTER_CONFLICT, KZT_OBSERVATION_ADAPTER_READER_FAILED, KZT_OBSERVATION_ADAPTER_REGISTRY_FAILED, + KZT_OBSERVATION_ADAPTER_PER_OBJECT_FAILED, } kzt_observation_adapter_result_t; typedef int (*kzt_observation_legacy_flow_fn)(uintptr_t link_map_addr, diff --git a/target/i386/latx/include/kzt_patch_planner.h b/target/i386/latx/include/kzt_patch_planner.h index b1656ddad61..28d0f66cb45 100644 --- a/target/i386/latx/include/kzt_patch_planner.h +++ b/target/i386/latx/include/kzt_patch_planner.h @@ -5,6 +5,8 @@ #include #include +#include "kzt_xcb_route_policy.h" + typedef enum kzt_patch_table_kind { KZT_PATCH_TABLE_UNKNOWN = 0, KZT_PATCH_TABLE_RELA, @@ -161,7 +163,36 @@ int kzt_patch_planner_decide(const kzt_patch_candidate_t *candidate, static inline int kzt_patch_symbol_must_stay_guest( const char *symbol_name) { - return symbol_name && strcmp(symbol_name, "dlclose") == 0; + return symbol_name && + (strcmp(symbol_name, "dlclose") == 0 || + strcmp(symbol_name, "free") == 0 || + strcmp(symbol_name, "__free") == 0 || + strcmp(symbol_name, "__libc_free") == 0 || + strcmp(symbol_name, "realloc") == 0 || + strcmp(symbol_name, "XOpenDisplay") == 0 || + strcmp(symbol_name, "XSetEventQueueOwner") == 0 || + kzt_xcb_route_must_stay_guest(symbol_name)); +} + +static inline int kzt_patch_symbol_requires_dlerror_prebind( + const char *symbol_name) +{ + return symbol_name && + (strcmp(symbol_name, "dlopen") == 0 || + strcmp(symbol_name, "dlmopen") == 0 || + strcmp(symbol_name, "dlsym") == 0 || + strcmp(symbol_name, "dlvsym") == 0 || + strcmp(symbol_name, "dlinfo") == 0 || + strcmp(symbol_name, "dladdr") == 0 || + strcmp(symbol_name, "dladdr1") == 0); +} + +static inline int kzt_patch_symbol_is_loader_route_family( + const char *symbol_name) +{ + return symbol_name && + (strcmp(symbol_name, "dlerror") == 0 || + kzt_patch_symbol_requires_dlerror_prebind(symbol_name)); } const char *kzt_patch_decision_kind_name(kzt_patch_decision_kind_t kind); diff --git a/target/i386/latx/include/kzt_patch_spike_guard.h b/target/i386/latx/include/kzt_patch_spike_guard.h index c527c9d8f45..3acf19841a6 100644 --- a/target/i386/latx/include/kzt_patch_spike_guard.h +++ b/target/i386/latx/include/kzt_patch_spike_guard.h @@ -1,6 +1,7 @@ #ifndef KZT_PATCH_SPIKE_GUARD_H #define KZT_PATCH_SPIKE_GUARD_H +#include #include #include "kzt_patch_planner.h" @@ -59,12 +60,39 @@ typedef struct kzt_patch_spike_config { unsigned long budget; } kzt_patch_spike_config_t; +typedef enum kzt_patch_spike_cohort_kind { + KZT_PATCH_SPIKE_COHORT_NONE = 0, + KZT_PATCH_SPIKE_COHORT_X11_DISPLAY, +} kzt_patch_spike_cohort_kind_t; + +typedef enum kzt_patch_spike_cohort_origin { + KZT_PATCH_SPIKE_COHORT_ORIGIN_UNKNOWN = 0, + KZT_PATCH_SPIKE_COHORT_ORIGIN_PLT_RELA, + KZT_PATCH_SPIKE_COHORT_ORIGIN_DLSYM, +} kzt_patch_spike_cohort_origin_t; + +typedef struct kzt_patch_spike_cohort_context { + kzt_patch_spike_cohort_kind_t kind; + kzt_patch_spike_cohort_origin_t origin; + uintptr_t source_namespace_id; + uintptr_t provider_namespace_id; + int provider_wrapped; +} kzt_patch_spike_cohort_context_t; + +typedef struct kzt_patch_spike_cohort_entry + kzt_patch_spike_cohort_entry_t; + typedef struct kzt_patch_spike_guard { kzt_patch_spike_config_t config; unsigned long write_attempts; unsigned long write_successes; + unsigned long reserved_writes; int circuit_open; int transaction_gate; + int cohort_gate; + kzt_patch_spike_cohort_entry_t *cohort_entries; + size_t cohort_entry_count; + size_t cohort_entry_capacity; } kzt_patch_spike_guard_t; typedef struct kzt_patch_spike_outcome { @@ -99,6 +127,7 @@ typedef struct kzt_patch_spike_writer_ops { void kzt_patch_spike_config_from_options(kzt_patch_spike_config_t *config); void kzt_patch_spike_guard_init(kzt_patch_spike_guard_t *guard, const kzt_patch_spike_config_t *config); +void kzt_patch_spike_guard_destroy(kzt_patch_spike_guard_t *guard); int kzt_patch_spike_guard_should_plan( const kzt_patch_spike_guard_t *guard); @@ -107,12 +136,30 @@ int kzt_patch_spike_guard_circuit_open( void kzt_patch_spike_guard_trip(kzt_patch_spike_guard_t *guard); unsigned long kzt_patch_spike_guard_budget_remaining( const kzt_patch_spike_guard_t *guard); +int kzt_patch_spike_guard_reserve_writes( + kzt_patch_spike_guard_t *guard, unsigned long count); +void kzt_patch_spike_guard_release_reserved_writes( + kzt_patch_spike_guard_t *guard, unsigned long count); int kzt_patch_spike_guard_try_write( kzt_patch_spike_guard_t *guard, const kzt_patch_decision_t *decision, const kzt_patch_spike_writer_ops_t *writer, kzt_patch_spike_outcome_t *outcome); +int kzt_patch_spike_guard_try_reserved_write( + kzt_patch_spike_guard_t *guard, + const kzt_patch_decision_t *decision, + const kzt_patch_spike_writer_ops_t *writer, + kzt_patch_spike_outcome_t *outcome); +int kzt_patch_spike_guard_try_cohort_write( + kzt_patch_spike_guard_t *guard, + const kzt_patch_decision_t *decision, + const kzt_patch_spike_cohort_context_t *cohort, + const kzt_patch_spike_writer_ops_t *writer, + kzt_patch_spike_outcome_t *outcome); +void kzt_patch_spike_guard_retire_identity( + kzt_patch_spike_guard_t *guard, uintptr_t link_map_addr, + unsigned long generation, uintptr_t namespace_id); const char *kzt_patch_spike_result_name(kzt_patch_spike_result_t result); const char *kzt_patch_spike_failure_name(kzt_patch_spike_failure_t failure); diff --git a/target/i386/latx/include/kzt_patch_spike_writer.h b/target/i386/latx/include/kzt_patch_spike_writer.h index 178b74d9dcc..8f4891514ca 100644 --- a/target/i386/latx/include/kzt_patch_spike_writer.h +++ b/target/i386/latx/include/kzt_patch_spike_writer.h @@ -115,5 +115,4 @@ int kzt_patch_spike_writer_try_apply_with_slot_ops( const kzt_patch_decision_t *decision, const kzt_patch_spike_slot_ops_t *slot_ops, kzt_patch_spike_record_t *record); - #endif diff --git a/target/i386/latx/include/kzt_plt_resolver_adapter.h b/target/i386/latx/include/kzt_plt_resolver_adapter.h index bfdf9baf077..33cdd691885 100644 --- a/target/i386/latx/include/kzt_plt_resolver_adapter.h +++ b/target/i386/latx/include/kzt_plt_resolver_adapter.h @@ -4,8 +4,6 @@ #include #include -#include "kzt_lazy_binding.h" - #ifdef KZT_PLT_RESOLVER_ADAPTER_TEST typedef struct CPUX86State { uint64_t regs[16]; @@ -16,32 +14,13 @@ typedef struct CPUX86State CPUX86State; #endif typedef struct kzt_plt_resolver_source { - int enabled; - uintptr_t context_id; - uintptr_t object_head; uintptr_t source_link_map; - unsigned long source_generation; - uintptr_t namespace_id; - kzt_guest_library_namespace_kind_t namespace_kind; - uintptr_t slot_addr; - uintptr_t unresolved_stub; - const char *symbol; - kzt_symbol_version_evidence_t version_evidence; - const char *version; - long addend; uintptr_t guest_resolver; } kzt_plt_resolver_source_t; typedef struct kzt_plt_resolver_runtime_ops { int (*lookup_source)(uintptr_t object_head, kzt_plt_resolver_source_t *source, void *opaque); - int (*begin_lazy_binding)( - const kzt_lazy_binding_begin_request_t *request, - kzt_lazy_binding_pending_t *pending, - kzt_lazy_binding_result_t *result, void *opaque); - kzt_lazy_binding_pending_t *pending; - uintptr_t completion_bridge; - uintptr_t *original_return; void *opaque; } kzt_plt_resolver_runtime_ops_t; @@ -58,7 +37,6 @@ typedef struct kzt_plt_resolver_enter_result { unsigned long relocation_slot; uintptr_t return_address; uintptr_t selected_resolver; - int pending_armed; } kzt_plt_resolver_enter_result_t; int kzt_plt_resolver_injection_allowed( diff --git a/target/i386/latx/include/kzt_rela_immediate_candidate.h b/target/i386/latx/include/kzt_rela_immediate_candidate.h index 63b1217d091..0c215e27ce4 100644 --- a/target/i386/latx/include/kzt_rela_immediate_candidate.h +++ b/target/i386/latx/include/kzt_rela_immediate_candidate.h @@ -85,5 +85,4 @@ int kzt_rela_immediate_jump_slot_try_write( kzt_patch_spike_guard_t *guard, const kzt_patch_spike_slot_ops_t *slot_ops, kzt_rela_immediate_writer_result_t *result); - #endif diff --git a/target/i386/latx/include/kzt_rela_runtime_bridge.h b/target/i386/latx/include/kzt_rela_runtime_bridge.h index 81cbcddf44b..2aa330c0c12 100644 --- a/target/i386/latx/include/kzt_rela_runtime_bridge.h +++ b/target/i386/latx/include/kzt_rela_runtime_bridge.h @@ -33,6 +33,14 @@ int kzt_rela_runtime_wrapper_provider_discover_with_version_evidence( const char *symbol_version, kzt_wrapper_bridge_provider_t *provider); +int kzt_rela_runtime_wrapper_provider_discover_guarded_with_version_evidence( + box64context_t *context, library_t *resolved_provider, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version, uintptr_t guest_fallback_target, + kzt_bridge_guard_kind_t guard_kind, + kzt_wrapper_bridge_provider_t *provider); + int kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( box64context_t *context, const kzt_guest_library_handle_t *retained_provider_handle, @@ -41,6 +49,22 @@ int kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( const char *symbol_version, kzt_wrapper_bridge_provider_t *provider); +uintptr_t kzt_rela_runtime_select_exact_wrapper_bridge_retained( + box64context_t *context, + const kzt_guest_library_handle_t *retained_provider_handle, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version); + +int kzt_rela_runtime_wrapper_provider_discover_guarded_retained_with_version_evidence( + box64context_t *context, + const kzt_guest_library_handle_t *retained_provider_handle, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version, uintptr_t guest_fallback_target, + kzt_bridge_guard_kind_t guard_kind, + kzt_wrapper_bridge_provider_t *provider); + /* The exact guest-library handle remains acquired until the provider is * discarded, so bridge-map operations may reuse discovery's loader proof. */ int kzt_rela_runtime_wrapper_provider_bind_retained_handle( diff --git a/target/i386/latx/include/kzt_wrapper_bridge_provider.h b/target/i386/latx/include/kzt_wrapper_bridge_provider.h index 59a4004e705..7e8ed8dd0e8 100644 --- a/target/i386/latx/include/kzt_wrapper_bridge_provider.h +++ b/target/i386/latx/include/kzt_wrapper_bridge_provider.h @@ -4,6 +4,7 @@ #include #include +#include "bridge_private.h" #include "kzt_guest_library_binding.h" #include "kzt_wrapper_probe.h" @@ -29,6 +30,8 @@ typedef struct kzt_wrapper_bridge_provider_match { int wrapper_provider_lifetime_bound; int native_owner_lifetime_bound; int bridge_owner_lifetime_bound; + uintptr_t guest_fallback_target; + kzt_bridge_guard_kind_t guard_kind; const kzt_guest_library_handle_t *retained_provider_handle; } kzt_wrapper_bridge_provider_match_t; diff --git a/target/i386/latx/include/kzt_xcb_connection_guard.h b/target/i386/latx/include/kzt_xcb_connection_guard.h new file mode 100644 index 00000000000..e24eb933c59 --- /dev/null +++ b/target/i386/latx/include/kzt_xcb_connection_guard.h @@ -0,0 +1,21 @@ +#ifndef KZT_XCB_CONNECTION_GUARD_H +#define KZT_XCB_CONNECTION_GUARD_H + +#include "kzt_xcb_connection_map.h" + +int kzt_xcb_connection_guard_prepare( + kzt_xcb_connection_map_t *map, void *guest); +int kzt_xcb_connection_guard_take( + kzt_xcb_connection_map_t *map, void *guest, + kzt_xcb_connection_lease_t *lease); +int kzt_xcb_connection_guard_acquire( + kzt_xcb_connection_map_t *map, void *guest, + kzt_xcb_connection_lease_t *lease); +int kzt_xcb_connection_guard_release( + kzt_xcb_connection_map_t *map, void *native, void *guest); +int kzt_xcb_connection_guard_active_lease( + kzt_xcb_connection_map_t *map, void *native, void *guest, + kzt_xcb_connection_lease_t *lease); +void kzt_xcb_connection_guard_cancel(void); + +#endif diff --git a/target/i386/latx/include/kzt_xcb_connection_map.h b/target/i386/latx/include/kzt_xcb_connection_map.h new file mode 100644 index 00000000000..9fd1b718580 --- /dev/null +++ b/target/i386/latx/include/kzt_xcb_connection_map.h @@ -0,0 +1,59 @@ +#ifndef KZT_XCB_CONNECTION_MAP_H +#define KZT_XCB_CONNECTION_MAP_H + +#include +#include + +typedef struct kzt_xcb_connection_map kzt_xcb_connection_map_t; + +typedef void (*kzt_xcb_connection_guest_destroy_fn)(void *guest, + void *opaque); + +typedef enum kzt_xcb_connection_map_result { + KZT_XCB_CONNECTION_MAP_ERROR = -1, + KZT_XCB_CONNECTION_MAP_ADDED = 0, + KZT_XCB_CONNECTION_MAP_UNCHANGED = 1, +} kzt_xcb_connection_map_result_t; + +typedef struct kzt_xcb_connection_lease { + void *guest; + void *native; + uint64_t generation; + kzt_xcb_connection_map_t *_map; + void *_entry; + int _removal; +} kzt_xcb_connection_lease_t; + +kzt_xcb_connection_map_t *kzt_xcb_connection_map_init( + kzt_xcb_connection_guest_destroy_fn destroy_guest, void *opaque); +void kzt_xcb_connection_map_destroy(kzt_xcb_connection_map_t **map); + +kzt_xcb_connection_map_result_t kzt_xcb_connection_map_register( + kzt_xcb_connection_map_t *map, void *native, void *proposed_guest, + void **canonical_guest, uint64_t *generation); + +int kzt_xcb_connection_map_acquire_by_guest( + kzt_xcb_connection_map_t *map, void *guest, + kzt_xcb_connection_lease_t *lease); +int kzt_xcb_connection_map_acquire_by_native( + kzt_xcb_connection_map_t *map, void *native, + kzt_xcb_connection_lease_t *lease); +void kzt_xcb_connection_map_release_pair( + kzt_xcb_connection_map_t *map, void *native, void *guest); +int kzt_xcb_connection_lease_lock_mirror( + const kzt_xcb_connection_lease_t *lease); +void kzt_xcb_connection_lease_unlock_mirror( + const kzt_xcb_connection_lease_t *lease); + +int kzt_xcb_connection_map_begin_remove_by_guest( + kzt_xcb_connection_map_t *map, void *guest, + kzt_xcb_connection_lease_t *lease); +int kzt_xcb_connection_map_begin_remove_by_native( + kzt_xcb_connection_map_t *map, void *native, + kzt_xcb_connection_lease_t *lease); +void kzt_xcb_connection_map_finish_remove( + kzt_xcb_connection_lease_t *lease); + +size_t kzt_xcb_connection_map_size(kzt_xcb_connection_map_t *map); + +#endif diff --git a/target/i386/latx/include/kzt_xcb_queue_mirror.h b/target/i386/latx/include/kzt_xcb_queue_mirror.h new file mode 100644 index 00000000000..879d3266108 --- /dev/null +++ b/target/i386/latx/include/kzt_xcb_queue_mirror.h @@ -0,0 +1,41 @@ +#ifndef KZT_XCB_QUEUE_MIRROR_H +#define KZT_XCB_QUEUE_MIRROR_H + +#include +#include +#include + +static inline int kzt_xcb_flush_state_is_supported( + int queue_length, size_t queue_capacity, + int fd_count, int fd_index, int writing, int socket_moving, + uintptr_t return_socket, uintptr_t socket_closure) +{ + return queue_length >= 0 && (size_t)queue_length <= queue_capacity && + fd_count >= 0 && fd_count <= 16 && + fd_index >= 0 && fd_index <= fd_count && + !writing && !socket_moving && !return_socket && !socket_closure; +} + +static inline size_t kzt_xcb_queue_copy( + char *dest, size_t dest_capacity, int *dest_length, + const char *source, size_t source_capacity, int source_length) +{ + size_t bytes = 0; + + if (source_length > 0 && dest && source) { + bytes = (size_t)source_length; + if (bytes > source_capacity) { + bytes = source_capacity; + } + if (bytes > dest_capacity) { + bytes = dest_capacity; + } + memcpy(dest, source, bytes); + } + if (dest_length) { + *dest_length = (int)bytes; + } + return bytes; +} + +#endif diff --git a/target/i386/latx/include/kzt_xcb_route_policy.h b/target/i386/latx/include/kzt_xcb_route_policy.h new file mode 100644 index 00000000000..32e2f8e562e --- /dev/null +++ b/target/i386/latx/include/kzt_xcb_route_policy.h @@ -0,0 +1,57 @@ +#ifndef KZT_XCB_ROUTE_POLICY_H +#define KZT_XCB_ROUTE_POLICY_H + +#include + +typedef enum kzt_xcb_route_kind { + KZT_XCB_ROUTE_NOT_XCB = 0, + KZT_XCB_ROUTE_GUARDED_CONSUMER, + KZT_XCB_ROUTE_PRODUCER, + KZT_XCB_ROUTE_LIFECYCLE, + KZT_XCB_ROUTE_UNSUPPORTED, +} kzt_xcb_route_kind_t; + +static inline kzt_xcb_route_kind_t kzt_xcb_route_classify( + const char *symbol_name) +{ + if (!symbol_name) { + return KZT_XCB_ROUTE_NOT_XCB; + } + + if (strcmp(symbol_name, "xcb_flush") == 0 || + strcmp(symbol_name, "xcb_connection_has_error") == 0) { + return KZT_XCB_ROUTE_GUARDED_CONSUMER; + } + if (strcmp(symbol_name, "xcb_connect") == 0 || + strcmp(symbol_name, + "xcb_connect_to_display_with_auth_info") == 0 || + strcmp(symbol_name, "XGetXCBConnection") == 0) { + return KZT_XCB_ROUTE_PRODUCER; + } + if (strcmp(symbol_name, "xcb_disconnect") == 0 || + strcmp(symbol_name, "XCloseDisplay") == 0) { + return KZT_XCB_ROUTE_LIFECYCLE; + } + if (strncmp(symbol_name, "xcb_", 4) == 0) { + return KZT_XCB_ROUTE_UNSUPPORTED; + } + + return KZT_XCB_ROUTE_NOT_XCB; +} + +static inline int kzt_xcb_route_is_guarded_consumer( + const char *symbol_name) +{ + return kzt_xcb_route_classify(symbol_name) == + KZT_XCB_ROUTE_GUARDED_CONSUMER; +} + +static inline int kzt_xcb_route_must_stay_guest(const char *symbol_name) +{ + kzt_xcb_route_kind_t kind = kzt_xcb_route_classify(symbol_name); + + return kind != KZT_XCB_ROUTE_NOT_XCB && + kind != KZT_XCB_ROUTE_GUARDED_CONSUMER; +} + +#endif diff --git a/target/i386/latx/include/myalign.h b/target/i386/latx/include/myalign.h index 9873bf88141..649ffe76606 100644 --- a/target/i386/latx/include/myalign.h +++ b/target/i386/latx/include/myalign.h @@ -148,11 +148,16 @@ void AlignEpollEvent(void* dest, void* source, int nbr); // x86 -> Arm void* align_xcb_connection(void* src); void unalign_xcb_connection(void* src, void* dst); void* add_xcb_connection(void* src); -void del_xcb_connection(void* src); int sync_xcb_connection(void* src); -int kzt_init(char** argv, int argc,char** target_argv, int target_argc, - struct linux_binprm* bprm); -int collectX86free(elfheader_t* h); +int begin_xcb_connection_disconnect( + void *guest, kzt_xcb_connection_lease_t *lease); +int begin_xcb_connection_disconnect_native( + void *native, kzt_xcb_connection_lease_t *lease); +void finish_xcb_connection_disconnect(kzt_xcb_connection_lease_t *lease); +uintptr_t kzt_xcb_guard_acquire_for_bridge( + CPUX86State *env, uintptr_t guest); +int kzt_init(CPUX86State *env, char** argv, int argc, char** target_argv, + int target_argc, struct linux_binprm* bprm); TranslationBlock * kzt_tb_find_exp( CPUState *cpu, TranslationBlock *last_tb, @@ -169,6 +174,7 @@ void kzt_wine_bridge(abi_ulong start, int fd); int latx_dpy_xcb_sync(void *v1); elfheader_t* loadElfFromFile(const char* name); elfheader_t* tryLoadElfFromFile(const char* name); +elfheader_t* tryLoadElfFromFileForContext( + box64context_t *context, const char *name); void freeElfFromFile(elfheader_t **header); -void kzt_wine_init_x86(box64context_t *context, uintptr_t guest_dlsym); #endif //__MY_ALIGN__H_ diff --git a/target/i386/latx/include/wrappedlibx11_private.h b/target/i386/latx/include/wrappedlibx11_private.h index 5ca4e882cc2..e950f120b5e 100644 --- a/target/i386/latx/include/wrappedlibx11_private.h +++ b/target/i386/latx/include/wrappedlibx11_private.h @@ -102,7 +102,7 @@ GO(XCheckWindowEvent, iFpplp) GO(XClearArea, iFppiiuui) GO(XClearWindow, iFpp) GO(XClipBox, iFpp) -GO(XCloseDisplay, iFp) +GOM(XCloseDisplay, iFp) GO(XCloseIM, iFp) // _XCloseLC GO(XCloseOM, iFp) diff --git a/target/i386/latx/include/wrappedlibxcb_private.h b/target/i386/latx/include/wrappedlibxcb_private.h index de1bce53546..c8b5a4599d3 100644 --- a/target/i386/latx/include/wrappedlibxcb_private.h +++ b/target/i386/latx/include/wrappedlibxcb_private.h @@ -653,10 +653,10 @@ GO(xcb_unregister_for_special_event, vFbp) //GO(xcb_visualid_next, //GO(xcb_visualtype_end, GO(xcb_visualtype_next, vFp) -GO(xcb_wait_for_event, pFb) -GO(xcb_wait_for_reply, pFbup) -GO(xcb_wait_for_reply64, pFbUp) -GO(xcb_wait_for_special_event, pFbp) +GOM(xcb_wait_for_event, pFb) +GOM(xcb_wait_for_reply, pFbup) +GOM(xcb_wait_for_reply64, pFbUp) +GOM(xcb_wait_for_special_event, pFbp) GO(xcb_warp_pointer, pFbuuwwWWww) //GO(xcb_warp_pointer_checked, //GO(xcb_window_end, diff --git a/target/i386/latx/sbt/aot.c b/target/i386/latx/sbt/aot.c index 22701620000..cd50b8d93cb 100644 --- a/target/i386/latx/sbt/aot.c +++ b/target/i386/latx/sbt/aot.c @@ -29,6 +29,11 @@ #include #include "exec/translate-all.h" #include "latx-smc.h" +#ifdef CONFIG_LATX_KZT +#include "kzt_guest_runtime_entry.h" +extern uintptr_t kzt_xcb_guard_acquire_for_bridge( + CPUX86State *env, uintptr_t guest); +#endif #ifdef CONFIG_LATX_AOT /* Tbs vector with @tb_num@ elements. */ static TranslationBlock **tb_vector; @@ -1335,6 +1340,12 @@ static void* relkind_to_fixup_addr[] = { [LOAD_HELPER_CVTPS2PH_YMM] = helper_cvtps2ph_ymm, [LOAD_HELPER_CVTPS2PH_XMM] = helper_cvtps2ph_xmm, #endif +#ifdef CONFIG_LATX_KZT + [LOAD_HELPER_KZT_RUNTIME_GUEST_ENTRY] = + kzt_runtime_guest_entry_or_abort, + [LOAD_HELPER_KZT_XCB_GUARD_ACQUIRE] = + kzt_xcb_guard_acquire_for_bridge, +#endif }; diff --git a/target/i386/latx/translator/tr-misc.c b/target/i386/latx/translator/tr-misc.c index e3836b0412b..e984e9af7d3 100644 --- a/target/i386/latx/translator/tr-misc.c +++ b/target/i386/latx/translator/tr-misc.c @@ -93,14 +93,34 @@ static void* mmm_realloc(void *mem, size_t len) { } #include "box64context.h" +#include "kzt_guest_runtime_entry.h" extern void my___libc_free(void* m); extern void my_cfree(void* m); extern void my_free(void* m); extern void my___free(void* m); extern void my_realloc(void* m, void *old, uintptr_t len); extern char *my_dlerror(void); -extern void* x86free; -extern void* x86realloc; +extern uintptr_t kzt_xcb_guard_acquire_for_bridge( + CPUX86State *env, uintptr_t guest); +void kzt_native_to_wrapper(void); +void kzt_wrapper_to_native(void); + +uintptr_t kzt_runtime_guest_entry_or_abort( + CPUX86State *env, kzt_guest_runtime_entry_id_t entry) +{ + box64context_t *context = env ? env->kzt_runtime_context : NULL; + uintptr_t address = kzt_guest_runtime_entry_for_guest_branch( + context, entry); + + if (!address) { + printf_log(LOG_NONE, + "KZT: required guest runtime entry %d is unavailable\n", + entry); + abort(); + } + return address; +} + static void kzt_helper_ptr(ADDR func, IR2_OPND ptr) { IR2_OPND func_addr_opnd = ra_alloc_dbt_arg2(); @@ -120,7 +140,29 @@ static void kzt_helper_pFpL(ADDR func, IR2_OPND ptr, IR2_OPND L) la_st_d(a0_ir2_opnd, env_ir2_opnd, lsenv_offset_of_gpr(lsenv, R_EAX)); } -void kzt_native_to_wrapper(void); +static void kzt_generate_guest_runtime_branch( + kzt_guest_runtime_entry_id_t entry) +{ + IR2_OPND helper = ra_alloc_dbt_arg2(); + + kzt_native_to_wrapper(); + la_mov64(a0_ir2_opnd, env_ir2_opnd); + li_d(a1_ir2_opnd, entry); + aot_load_host_addr( + helper, (ADDR)kzt_runtime_guest_entry_or_abort, + LOAD_HELPER_KZT_RUNTIME_GUEST_ENTRY, 0); + tr_set_running_of_cs(false); + la_jirl(ra_ir2_opnd, helper, 0); + tr_set_running_of_cs(true); + lsassert(lsenv_offset_of_eip(lsenv) >= -2048 && + lsenv_offset_of_eip(lsenv) <= 2047); + la_mov64(helper, a0_ir2_opnd); + la_store_addrx( + helper, env_ir2_opnd, lsenv_offset_of_eip(lsenv)); + kzt_wrapper_to_native(); + tr_generate_exit_tb_for_bridge(); +} + void kzt_native_to_wrapper(void) { tr_save_registers_to_env(0xff, 0xff, option_save_xmm, options_to_save()); @@ -133,7 +175,6 @@ void kzt_native_to_wrapper(void) lsenv_offset_of_fcsr(lsenv)); } -void kzt_wrapper_to_native(void); void kzt_wrapper_to_native(void) { /* save dbt FCSR */ @@ -156,15 +197,29 @@ static void do_translate_dlerror_brick_tb(onebridge_t *bridge) { const int fast_result_offset = offsetof( CPUX86State, kzt_guest_dlerror_state.dlerror_fast_result); + const int context_offset = offsetof(CPUX86State, kzt_runtime_context); + const int guest_route_offset = offsetof( + box64context_t, kzt_guest_loader_route_present); IR2_OPND esp_ir2_opnd = ra_alloc_gpr(esp_index); IR2_OPND fast_result = ra_alloc_itemp(); + IR2_OPND context = ra_alloc_itemp(); + IR2_OPND guest_route = ra_alloc_itemp(); IR2_OPND slow_path = ra_alloc_label(); + IR2_OPND fast_null = ra_alloc_label(); IR2_OPND finish = ra_alloc_label(); lsassert(fast_result_offset >= -2048 && fast_result_offset <= 2047); + lsassert(context_offset >= -2048 && context_offset <= 2047); + lsassert(guest_route_offset >= -2048 && guest_route_offset <= 2047); kzt_native_to_wrapper(); la_ld_d(fast_result, env_ir2_opnd, fast_result_offset); la_bne(fast_result, zero_ir2_opnd, slow_path); + la_ld_d(context, env_ir2_opnd, context_offset); + la_beq(context, zero_ir2_opnd, fast_null); + la_ld_w(guest_route, context, guest_route_offset); + la_bne(guest_route, zero_ir2_opnd, slow_path); + + la_label(fast_null); la_st_d(zero_ir2_opnd, env_ir2_opnd, lsenv_offset_of_gpr(lsenv, R_EAX)); la_b(finish); @@ -178,6 +233,8 @@ static void do_translate_dlerror_brick_tb(onebridge_t *bridge) la_label(finish); ra_free_temp(fast_result); + ra_free_temp(context); + ra_free_temp(guest_route); kzt_wrapper_to_native(); gen_set_next_tb_code(&esp_ir2_opnd); tr_generate_exit_tb_for_bridge(); @@ -185,53 +242,82 @@ static void do_translate_dlerror_brick_tb(onebridge_t *bridge) static void do_translate_realloc_brick_tb(void) { - uintptr_t realloc_pc = (uint64_t)&x86realloc; IR2_OPND reserved_va_opnd = ra_alloc_itemp(); IR2_OPND gpr_rdi_opnd = ra_alloc_gpr(edi_index); - IR2_OPND back_to_x86realloc_opnd = ra_alloc_label(); + IR2_OPND back_to_guest_realloc_opnd = ra_alloc_label(); IR2_OPND esp_ir2_opnd = ra_alloc_gpr(esp_index); - lsassert(realloc_pc); li_d(reserved_va_opnd, (ADDR)reserved_va); - la_bltu(gpr_rdi_opnd, reserved_va_opnd, back_to_x86realloc_opnd); + la_bltu(gpr_rdi_opnd, reserved_va_opnd, back_to_guest_realloc_opnd); kzt_native_to_wrapper(); kzt_helper_pFpL((ADDR)mmm_realloc, gpr_rdi_opnd, ra_alloc_gpr(esi_index)); kzt_wrapper_to_native(); gen_set_next_tb_code(&esp_ir2_opnd); tr_generate_exit_tb_for_bridge(); - la_label(back_to_x86realloc_opnd); - IR2_OPND eip_opnd = ra_alloc_dbt_arg2(); - li_d(eip_opnd, (ADDR)realloc_pc); - la_ld_d(eip_opnd,eip_opnd, 0); - lsassert(lsenv_offset_of_eip(lsenv) >= -2048 && - lsenv_offset_of_eip(lsenv) <= 2047); - la_store_addrx(eip_opnd, env_ir2_opnd, - lsenv_offset_of_eip(lsenv)); - tr_generate_exit_tb_for_bridge(); + la_label(back_to_guest_realloc_opnd); + kzt_generate_guest_runtime_branch(KZT_GUEST_RUNTIME_REALLOC); } static void do_translate_free_brick_tb(void) { - uintptr_t free_pc = (uint64_t)&x86free; IR2_OPND reserved_va_opnd = ra_alloc_itemp(); IR2_OPND gpr_rdi_opnd = ra_alloc_gpr(edi_index); - IR2_OPND back_to_x86free_opnd = ra_alloc_label(); + IR2_OPND back_to_guest_free_opnd = ra_alloc_label(); IR2_OPND esp_ir2_opnd = ra_alloc_gpr(esp_index); - lsassert(free_pc); li_d(reserved_va_opnd, (ADDR)reserved_va); - la_bltu(gpr_rdi_opnd, reserved_va_opnd, back_to_x86free_opnd); + la_bltu(gpr_rdi_opnd, reserved_va_opnd, back_to_guest_free_opnd); kzt_native_to_wrapper(); kzt_helper_ptr((ADDR)mmm_free, gpr_rdi_opnd); kzt_wrapper_to_native(); gen_set_next_tb_code(&esp_ir2_opnd); tr_generate_exit_tb_for_bridge(); - la_label(back_to_x86free_opnd); - IR2_OPND eip_opnd = ra_alloc_dbt_arg2(); - li_d(eip_opnd, (ADDR)free_pc); - la_ld_d(eip_opnd,eip_opnd, 0); - lsassert(lsenv_offset_of_eip(lsenv) >= -2048 && - lsenv_offset_of_eip(lsenv) <= 2047); - la_store_addrx(eip_opnd, env_ir2_opnd, - lsenv_offset_of_eip(lsenv)); + la_label(back_to_guest_free_opnd); + kzt_generate_guest_runtime_branch(KZT_GUEST_RUNTIME_FREE); +} + +static void do_translate_xcb_guarded_brick_tb(onebridge_t *bridge) +{ + IR2_OPND helper = ra_alloc_dbt_arg2(); + IR2_OPND fallback = ra_alloc_label(); + IR2_OPND esp_ir2_opnd = ra_alloc_gpr(esp_index); + + lsassert(bridge->guest_fallback_target != 0); + kzt_native_to_wrapper(); + la_mov64(a0_ir2_opnd, env_ir2_opnd); + la_ld_d(a1_ir2_opnd, env_ir2_opnd, + lsenv_offset_of_gpr(lsenv, R_EDI)); + aot_load_host_addr( + helper, (ADDR)kzt_xcb_guard_acquire_for_bridge, + LOAD_HELPER_KZT_XCB_GUARD_ACQUIRE, 0); + tr_set_running_of_cs(false); + la_jirl(ra_ir2_opnd, helper, 0); + tr_set_running_of_cs(true); + la_beq(a0_ir2_opnd, zero_ir2_opnd, fallback); + + wrapper_gpr_trans((ADDR)bridge->f); + tr_set_running_of_cs(false); + li_d(ra_ir2_opnd, (ADDR)bridge->w); + la_jirl(ra_ir2_opnd, ra_ir2_opnd, 0); + tr_set_running_of_cs(true); + + /* A correctly classified wrapper consumes the prepared lease. Calling + * the helper with a null guest also releases it if the wrapper did not. */ + la_mov64(a0_ir2_opnd, env_ir2_opnd); + la_mov64(a1_ir2_opnd, zero_ir2_opnd); + aot_load_host_addr( + helper, (ADDR)kzt_xcb_guard_acquire_for_bridge, + LOAD_HELPER_KZT_XCB_GUARD_ACQUIRE, 0); + tr_set_running_of_cs(false); + la_jirl(ra_ir2_opnd, helper, 0); + tr_set_running_of_cs(true); + kzt_wrapper_to_native(); + gen_set_next_tb_code(&esp_ir2_opnd); + tr_generate_exit_tb_for_bridge(); + + la_label(fallback); + li_d(helper, bridge->guest_fallback_target); + la_store_addrx( + helper, env_ir2_opnd, lsenv_offset_of_eip(lsenv)); + kzt_wrapper_to_native(); tr_generate_exit_tb_for_bridge(); } @@ -239,7 +325,10 @@ static void do_translate_brick_tb(onebridge_t *bridge, struct cpu_state_info *st { tb = lsenv->tr_data->curr_tb; IR2_OPND esp_ir2_opnd = ra_alloc_gpr(esp_index); - if (bridge->f == (uintptr_t)my_free ||bridge->f == (uintptr_t)my___libc_free ||bridge->f == (uintptr_t)my___free ||bridge->f == (uintptr_t)my_cfree ) { + if (bridge->guard_kind == KZT_BRIDGE_GUARD_XCB_CONNECTION) { + do_translate_xcb_guarded_brick_tb(bridge); + return; + } else if (bridge->f == (uintptr_t)my_free ||bridge->f == (uintptr_t)my___libc_free ||bridge->f == (uintptr_t)my___free ||bridge->f == (uintptr_t)my_cfree ) { do_translate_free_brick_tb(); return; } else if (bridge->f == (uintptr_t)my_realloc) { diff --git a/tests/unit/kzt/test_dlopen_recycle_transaction.c b/tests/unit/kzt/test_dlopen_recycle_transaction.c deleted file mode 100644 index 6d77ea2a0bc..00000000000 --- a/tests/unit/kzt/test_dlopen_recycle_transaction.c +++ /dev/null @@ -1,354 +0,0 @@ -#include -#include -#include - -#include "dlopen_recycle_transaction.h" -#include "elf_plt_relocation.h" -#include "kzt_guest_library_binding.h" -#include "kzt_guest_registry.h" - -typedef struct fake_library { int id; } fake_library_t; - -typedef struct recycle_fixture { - kzt_guest_library_bindings_t *bindings; - kzt_guest_registry_t *registry; - fake_library_t library; - uintptr_t link_map_addr; - unsigned long generation; - int active; - int prepared; - int guest_open_result; - int reload_result; - int plt_relocation_result; - int guest_close_calls; - int guest_open_calls; - uintptr_t delta; - uintptr_t internal_l_addr; - int latx_hasfix; - int had_relocate_elf; - int had_relocate_plt; - int latx_type; -} recycle_fixture_t; - -static int failures; - -#define CHECK(name, expr) do { if (!(expr)) { \ - fprintf(stderr, "FAIL %s\n", name); ++failures; } } while (0) - -static kzt_guest_object_observation_t observation(uintptr_t map) -{ - return (kzt_guest_object_observation_t){ - .link_map_addr = map, - .load_bias = { .value = 0x500000, .status = KZT_GUEST_FIELD_OK }, - .dynamic_addr = { .status = KZT_GUEST_FIELD_UNKNOWN }, - .map_start = { .status = KZT_GUEST_FIELD_UNKNOWN }, - .map_end = { .status = KZT_GUEST_FIELD_UNKNOWN }, - .namespace_id = { .value = 0, .status = KZT_GUEST_FIELD_OK }, - .path = { .status = KZT_GUEST_FIELD_UNKNOWN }, - .soname = { .status = KZT_GUEST_FIELD_UNKNOWN }, - .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, - }; -} - -static kzt_guest_library_binding_key_t binding_key(uintptr_t map, - unsigned long generation) -{ - return (kzt_guest_library_binding_key_t){ - .link_map_addr = map, - .generation = generation, - .namespace_id = 0, - .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, - }; -} - -static unsigned long current_generation(recycle_fixture_t *fixture) -{ - kzt_guest_object_snapshot_t *snapshot = NULL; - unsigned long generation = 0; - CHECK("transaction registry snapshot", - kzt_guest_registry_find_by_link_map( - fixture->registry, fixture->link_map_addr, &snapshot) == 0 && - snapshot != NULL); - if (snapshot) generation = snapshot->generation; - kzt_guest_object_snapshot_free(snapshot); - return generation; -} - -static void fixture_init(recycle_fixture_t *fixture) -{ - kzt_guest_object_observation_t observed; - kzt_guest_library_binding_key_t key; - *fixture = (recycle_fixture_t){ - .bindings = kzt_guest_library_bindings_init(), - .registry = kzt_guest_registry_init(), - .library = { .id = 1 }, - .link_map_addr = 0xd000, - .active = 1, - .delta = 0x400000, - .internal_l_addr = 0x400000, - .latx_hasfix = 1, - .had_relocate_elf = 1, - .had_relocate_plt = 1, - .latx_type = 3, - }; - observed = observation(fixture->link_map_addr); - CHECK("transaction track", kzt_guest_library_track( - fixture->bindings, (library_t *)&fixture->library) == 0); - CHECK("transaction initial observe", kzt_guest_registry_observe( - fixture->registry, &observed) == KZT_GUEST_REGISTRY_ADDED); - fixture->generation = current_generation(fixture); - key = binding_key(fixture->link_map_addr, fixture->generation); - CHECK("transaction initial binding observation", - kzt_guest_library_note_observation(fixture->bindings, &key) == - KZT_GUEST_LIBRARY_BINDING_UNCHANGED); - CHECK("transaction initial pair", kzt_guest_library_note_exact_pair( - fixture->bindings, fixture->link_map_addr, - (library_t *)&fixture->library, - KZT_GUEST_LIBRARY_OBJECT_EMULATED) == - KZT_GUEST_LIBRARY_BINDING_ADDED); - kzt_guest_library_inactivate(fixture->bindings, fixture->registry, - (library_t *)&fixture->library, - fixture->link_map_addr); - fixture->active = 0; - /* Model the stale pointer retained by the old wrappers after dlclose. */ - fixture->link_map_addr = 0xd000; -} - -static void fixture_destroy(recycle_fixture_t *fixture) -{ - kzt_guest_library_bindings_destroy(&fixture->bindings); - kzt_guest_registry_destroy(&fixture->registry); -} - -static void recycle_prepare(void *opaque) -{ - recycle_fixture_t *fixture = opaque; - fixture->prepared = 1; - fixture->link_map_addr = 0; - fixture->delta = 0; - fixture->internal_l_addr = 0; - fixture->latx_hasfix = 0; - fixture->had_relocate_elf = 0; - fixture->had_relocate_plt = 0; - fixture->latx_type = 0; -} - -static int recycle_guest_open(void *opaque) -{ - recycle_fixture_t *fixture = opaque; - kzt_guest_object_observation_t observed; - kzt_guest_library_binding_key_t key; - if (fixture->guest_open_result != 0) - return fixture->guest_open_result; - ++fixture->guest_open_calls; - fixture->link_map_addr = 0xd000; - fixture->delta = 0x500000 + (uintptr_t)fixture->guest_open_calls * 0x100000; - fixture->internal_l_addr = fixture->delta; - fixture->latx_hasfix = 1; - fixture->active = 1; - observed = observation(fixture->link_map_addr); - CHECK("transaction guest observation", kzt_guest_registry_observe( - fixture->registry, &observed) == KZT_GUEST_REGISTRY_ADDED); - fixture->generation = current_generation(fixture); - key = binding_key(fixture->link_map_addr, fixture->generation); - CHECK("transaction guest binding observation", - kzt_guest_library_note_observation(fixture->bindings, &key) == - KZT_GUEST_LIBRARY_BINDING_UNCHANGED); - CHECK("transaction guest incarnation live", - kzt_guest_library_reactivate( - fixture->bindings, (library_t *)&fixture->library) == 0); - return 0; -} - -static int simulated_plt_relocation(void *opaque, int *need_resolver) -{ - recycle_fixture_t *fixture = opaque; - if (need_resolver) - *need_resolver = 1; - return fixture->plt_relocation_result; -} - -static int recycle_reload(void *opaque) -{ - recycle_fixture_t *fixture = opaque; - kzt_guest_library_binding_result_t pair_result; - int need_resolver = 0; - if (fixture->reload_result != 0) - return fixture->reload_result; - fixture->had_relocate_elf = 1; - if (elf_plt_relocation_apply(simulated_plt_relocation, fixture, - &need_resolver) != 0) - return -1; - fixture->had_relocate_plt = 1; - fixture->active = 1; - CHECK("transaction reactivate", kzt_guest_library_reactivate( - fixture->bindings, (library_t *)&fixture->library) == 0); - pair_result = kzt_guest_library_note_exact_pair( - fixture->bindings, fixture->link_map_addr, - (library_t *)&fixture->library, KZT_GUEST_LIBRARY_OBJECT_EMULATED); - CHECK("transaction publish pair", - pair_result == KZT_GUEST_LIBRARY_BINDING_ADDED || - pair_result == KZT_GUEST_LIBRARY_BINDING_UNCHANGED); - return 0; -} - -static void recycle_rollback(void *opaque, int guest_opened) -{ - recycle_fixture_t *fixture = opaque; - if (guest_opened && fixture->link_map_addr) - ++fixture->guest_close_calls; - kzt_guest_library_inactivate(fixture->bindings, fixture->registry, - (library_t *)&fixture->library, - fixture->link_map_addr); - fixture->active = 0; - fixture->link_map_addr = 0; - fixture->delta = 0; - fixture->internal_l_addr = 0; - fixture->latx_hasfix = 0; - fixture->had_relocate_elf = 0; - fixture->had_relocate_plt = 0; - fixture->latx_type = 0; -} - -static void check_failed_state(const char *prefix, recycle_fixture_t *fixture) -{ - kzt_guest_library_binding_state_t lifecycle; - size_t pending = 99, live_entries = 99; - (void)prefix; - CHECK("failure active false", fixture->active == 0); - CHECK("failure link_map cleared", fixture->link_map_addr == 0); - CHECK("failure delta cleared", fixture->delta == 0); - CHECK("failure internal l_addr cleared", fixture->internal_l_addr == 0); - CHECK("failure latx_hasfix cleared", fixture->latx_hasfix == 0); - CHECK("failure relocate flag cleared", fixture->had_relocate_elf == 0); - CHECK("failure PLT flag cleared", fixture->had_relocate_plt == 0); - CHECK("failure LATX type cleared", fixture->latx_type == 0); - CHECK("failure binding snapshot", - kzt_guest_library_binding_test_snapshot( - fixture->bindings, (library_t *)&fixture->library, - &lifecycle, &pending, &live_entries) == 0); - CHECK("failure lifecycle dead", - lifecycle == KZT_GUEST_LIBRARY_BINDING_DEAD); - CHECK("failure pending empty", pending == 0); - CHECK("failure live pair absent", live_entries == 0); -} - -static void test_guest_open_failure(void) -{ - recycle_fixture_t fixture; - size_t count = 0; - void *result; - fixture_init(&fixture); - fixture.guest_open_result = -1; - result = dlopen_recycle_transaction( - (void *)0x55, &count, 1, &fixture, recycle_prepare, - recycle_guest_open, recycle_reload, recycle_rollback); - CHECK("guest failure returned null", result == NULL); - CHECK("guest failure count unchanged", count == 0); - CHECK("guest failure prepared", fixture.prepared == 1); - CHECK("guest failure no close", fixture.guest_close_calls == 0); - check_failed_state("guest", &fixture); - fixture_destroy(&fixture); -} - -static void test_reload_failure(void) -{ - recycle_fixture_t fixture; - kzt_guest_object_observation_t observed; - unsigned long failed_generation, reused_generation; - size_t count = 0; - void *result; - fixture_init(&fixture); - fixture.reload_result = -1; - result = dlopen_recycle_transaction( - (void *)0x66, &count, 1, &fixture, recycle_prepare, - recycle_guest_open, recycle_reload, recycle_rollback); - failed_generation = fixture.generation; - CHECK("reload failure returned null", result == NULL); - CHECK("reload failure count unchanged", count == 0); - CHECK("reload failure closes guest once", fixture.guest_close_calls == 1); - check_failed_state("reload", &fixture); - - observed = observation(0xd000); - CHECK("reload failure observation retired", - kzt_guest_registry_observe(fixture.registry, &observed) == - KZT_GUEST_REGISTRY_ADDED); - fixture.link_map_addr = 0xd000; - reused_generation = current_generation(&fixture); - CHECK("reload failure reuse generation", - reused_generation > failed_generation); - fixture_destroy(&fixture); -} - -static void test_plt_relocation_failure_rolls_back_and_retry_overwrites(void) -{ - recycle_fixture_t fixture; - size_t count = 0; - void *result; - - fixture_init(&fixture); - fixture.plt_relocation_result = -1; - result = dlopen_recycle_transaction( - (void *)0x76, &count, 1, &fixture, recycle_prepare, - recycle_guest_open, recycle_reload, recycle_rollback); - CHECK("PLT failure returned null", result == NULL); - CHECK("PLT failure count unchanged", count == 0); - CHECK("PLT failure compensates once", fixture.guest_close_calls == 1); - check_failed_state("plt", &fixture); - - fixture.plt_relocation_result = 0; - result = dlopen_recycle_transaction( - (void *)0x76, &count, 1, &fixture, recycle_prepare, - recycle_guest_open, recycle_reload, recycle_rollback); - CHECK("PLT retry returned handle", result == (void *)0x76); - CHECK("PLT retry increments count", count == 1); - CHECK("PLT retry did not compensate", fixture.guest_close_calls == 1); - CHECK("PLT retry overwrote delta", fixture.delta == 0x700000); - CHECK("PLT retry overwrote l_addr", fixture.internal_l_addr == 0x700000); - CHECK("PLT retry set fix flag", fixture.latx_hasfix == 1); - CHECK("PLT retry set relocation flag", fixture.had_relocate_elf == 1); - CHECK("PLT retry set PLT flag", fixture.had_relocate_plt == 1); - fixture_destroy(&fixture); -} - -static void test_success_commits_handle_count_and_pair(void) -{ - recycle_fixture_t fixture; - kzt_guest_library_binding_state_t lifecycle; - kzt_guest_library_binding_key_t key; - kzt_guest_library_handle_t binding_handle; - size_t pending = 99, live_entries = 99, count = 0; - void *result; - fixture_init(&fixture); - result = dlopen_recycle_transaction( - (void *)0x77, &count, 1, &fixture, recycle_prepare, - recycle_guest_open, recycle_reload, recycle_rollback); - CHECK("success returned old handle", result == (void *)0x77); - CHECK("success count incremented", count == 1); - CHECK("success active", fixture.active == 1); - CHECK("success binding snapshot", - kzt_guest_library_binding_test_snapshot( - fixture.bindings, (library_t *)&fixture.library, &lifecycle, - &pending, &live_entries) == 0); - CHECK("success lifecycle live", - lifecycle == KZT_GUEST_LIBRARY_BINDING_LIVE); - CHECK("success pending empty", pending == 0); - CHECK("success exact pair live", live_entries == 1); - key = binding_key(fixture.link_map_addr, fixture.generation); - CHECK("success pair lookup", kzt_guest_library_lookup( - fixture.bindings, &key, &binding_handle) == 0); - kzt_guest_library_handle_release(&binding_handle); - fixture_destroy(&fixture); -} - -int main(void) -{ - test_guest_open_failure(); - test_reload_failure(); - test_plt_relocation_failure_rolls_back_and_retry_overwrites(); - test_success_commits_handle_count_and_pair(); - if (failures) - fprintf(stderr, "dlopen recycle transaction: %d failure(s)\n", - failures); - return failures ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/tests/unit/kzt/test_guest_dl_api.c b/tests/unit/kzt/test_guest_dl_api.c index feaf5453a20..3e4b56c3959 100644 --- a/tests/unit/kzt/test_guest_dl_api.c +++ b/tests/unit/kzt/test_guest_dl_api.c @@ -9,6 +9,7 @@ #include "target/i386/latx/include/elfloader.h" #include "target/i386/latx/include/kzt_guest_dl_api.h" #include "target/i386/latx/include/kzt_guest_dl_init.h" +#include "target/i386/latx/include/kzt_guest_runtime_entry.h" #include "target/i386/latx/include/kzt_guest_library_adapter.h" #include "target/i386/latx/include/kzt_guest_registry.h" #include "target/i386/latx/include/kzt_jump_slot_production.h" @@ -20,9 +21,9 @@ __thread uintptr_t kzt_guest_dlerror_fast_result_tls; int option_kzt = 1; int wine_option_kzt; -elfheader_t *tryLoadElfFromFile(const char *name); +elfheader_t *tryLoadElfFromFileForContext( + box64context_t *context, const char *name); void freeElfFromFile(elfheader_t **header); -void kzt_wine_init_x86(box64context_t *context, uintptr_t guest_dlsym); #define CHECK(label, condition) \ do { \ @@ -62,6 +63,7 @@ static uintptr_t dlopen_result; static int dlclose_result; static uintptr_t guest_result; static uintptr_t selected_result; +static uintptr_t seen_dlsym_function; static char guest_error[] = "guest error"; static int wrapper_known; static int attach_result; @@ -83,6 +85,7 @@ static int registry_identity_resident_calls; static uintptr_t registry_identity_handle; static uintptr_t registry_identity_link_map; static uintptr_t registry_identity_namespace; +static int selector_identity_required = -1; static int registry_close_complete_calls; static kzt_guest_loader_close_result_t registry_close_result; static int registry_close_identity_missing_calls; @@ -125,16 +128,35 @@ static int entry_destroy_done; static int default_entry_pause; static int default_entry_libc_calls; static int default_entry_libdl_calls; -static int default_entry_wine_calls; static int default_entry_path_calls; static int default_entry_header_frees; -static int default_entry_path_present; +static box64context_t *default_entry_contexts[2]; +static int default_entry_context_calls[2]; +static int default_entry_context_path_present[2]; +static int runtime_dlsym_error_model; +static int runtime_dlsym_error_pending; static int default_entry_incomplete; -static int default_wine_pause; -static int default_wine_paused; -static int default_wine_release; const char *interp_prefix = "/guest-root"; +typedef struct runtime_entry_resolver_state { + pthread_mutex_t mutex; + pthread_cond_t ready; + const char *expected_symbol; + uintptr_t result; + int failures; + int calls; + int pause; + int paused; + int release; +} runtime_entry_resolver_state_t; + +typedef struct runtime_entry_race { + dlprivate_t *dl; + kzt_guest_runtime_entry_id_t entry; + runtime_entry_resolver_state_t *resolver; + uintptr_t result; +} runtime_entry_race_t; + typedef struct dlerror_publish_race { dlprivate_t *dl; uintptr_t entry; @@ -231,7 +253,7 @@ static void *isolate_guest_dlerror(void *opaque) } result = kzt_guest_dl_api_dlerror( &race->error_state, - kzt_guest_dl_api_load_dlerror_entry(race->dl)); + kzt_guest_dl_api_load_dlerror_entry(race->dl), 0); race->forward_to_guest_caller = result.forward_to_guest_caller; snprintf(race->observed, sizeof(race->observed), "%s", result.value ? result.value : ""); @@ -375,20 +397,94 @@ static void *destroy_guest_dl_entries(void *opaque) dlentry_destroy_race_t *race = opaque; __atomic_store_n(&entry_destroy_started, 1, __ATOMIC_RELEASE); - kzt_guest_dl_api_entry_state_destroy(race->dl); + kzt_guest_dl_api_entry_state_begin_teardown(race->dl); __atomic_store_n(&entry_destroy_done, 1, __ATOMIC_RELEASE); return NULL; } -elfheader_t* tryLoadElfFromFile(const char *name) +static uintptr_t resolve_runtime_entry(const char *symbol, void *opaque) +{ + runtime_entry_resolver_state_t *state = opaque; + int call; + + CHECK("runtime resolver exact symbol", + strcmp(symbol, state->expected_symbol) == 0); + pthread_mutex_lock(&state->mutex); + call = ++state->calls; + if (state->pause) { + state->paused = 1; + pthread_cond_broadcast(&state->ready); + while (!state->release) { + pthread_cond_wait(&state->ready, &state->mutex); + } + } + pthread_mutex_unlock(&state->mutex); + return call <= state->failures ? 0 : state->result; +} + +static void *resolve_runtime_entry_in_thread(void *opaque) { + runtime_entry_race_t *race = opaque; + + race->result = kzt_guest_runtime_entry_ensure( + race->dl, race->entry, resolve_runtime_entry, race->resolver); + return NULL; +} + +static int wait_for_runtime_slow_users( + dlprivate_t *dl, unsigned int minimum) +{ + int attempt; + + for (attempt = 0; attempt < 100000; ++attempt) { + unsigned int users; + + pthread_mutex_lock(&dl->guest_dl_entries.mutex); + users = dl->guest_dl_entries.slow_users; + pthread_mutex_unlock(&dl->guest_dl_entries.mutex); + if (users >= minimum) { + return 0; + } + sched_yield(); + } + return -1; +} + +static void runtime_entry_resolver_state_init( + runtime_entry_resolver_state_t *state, const char *symbol, + uintptr_t result) +{ + memset(state, 0, sizeof(*state)); + CHECK("runtime resolver mutex initializes", + pthread_mutex_init(&state->mutex, NULL) == 0); + CHECK("runtime resolver condition initializes", + pthread_cond_init(&state->ready, NULL) == 0); + state->expected_symbol = symbol; + state->result = result; +} + +static void runtime_entry_resolver_state_destroy( + runtime_entry_resolver_state_t *state) +{ + pthread_cond_destroy(&state->ready); + pthread_mutex_destroy(&state->mutex); +} + +elfheader_t* tryLoadElfFromFileForContext( + box64context_t *context, const char *name) +{ + int context_index = context == default_entry_contexts[0] ? 0 : 1; + + CHECK("default resolver receives owning context", + context == default_entry_contexts[context_index]); + ++default_entry_context_calls[context_index]; if (strcmp(name, "libc.so.6") == 0) { ++default_entry_libc_calls; - return (elfheader_t *)(uintptr_t)1; + return (elfheader_t *)(uintptr_t)(1 + context_index * 2); } CHECK("default resolver requests libdl", strcmp(name, "libdl.so.2") == 0); ++default_entry_libdl_calls; - return (elfheader_t *)(uintptr_t)2; + return (elfheader_t *)(uintptr_t)(2 + context_index * 2); } void freeElfFromFile(elfheader_t **header) @@ -402,21 +498,28 @@ void ResetSpecialCaseElf( elfheader_t *header, const char **names, int name_count, void **resolved, int *resolved_count) { - int begin = header == (elfheader_t *)(uintptr_t)1 ? 0 : 4; - int end = header == (elfheader_t *)(uintptr_t)1 ? 4 : name_count; + uintptr_t header_id = (uintptr_t)header; + int second_context = header_id >= 3; + int is_libc = header_id == 1 || header_id == 3; + int begin = is_libc ? 0 : 4; + int end = is_libc ? name_count : 9; + uintptr_t base = second_context ? 0xa000 : 0x9000; - CHECK("default resolver symbol count", name_count == 9); + CHECK("default resolver symbol count", name_count == 12); for (int i = begin; i < end; ++i) { + if (is_libc && i >= 4 && i < 9) { + continue; + } CHECK("default resolver symbol name", names[i] && names[i][0]); - if (default_entry_incomplete && i == name_count - 1) { + if (default_entry_incomplete && i == 8) { continue; } if (!resolved[i]) { - resolved[i] = (void *)(uintptr_t)(0x9000 + i * 0x10); + resolved[i] = (void *)(base + i * 0x10); ++*resolved_count; } } - if (header == (elfheader_t *)(uintptr_t)1 && default_entry_pause) { + if (is_libc && default_entry_pause) { pthread_mutex_lock(&entry_init_lock); entry_init_paused = 1; pthread_cond_broadcast(&entry_init_ready); @@ -429,34 +532,28 @@ void ResetSpecialCaseElf( void PrependList(path_collection_t *collection, const char *list, int folder) { + int context_index = + collection == &default_entry_contexts[0]->box64_ld_lib ? 0 : 1; + CHECK("default resolver path collection", collection != NULL); + CHECK("default resolver owns path collection", + collection == &default_entry_contexts[context_index]->box64_ld_lib); CHECK("default resolver hwcap path", strstr(list, "x86-64-v2") != NULL); CHECK("default resolver path kind", folder == 1); ++default_entry_path_calls; - default_entry_path_present = 1; + default_entry_context_path_present[context_index] = 1; } int FindInCollection(const char *path, path_collection_t *collection) { + int context_index = + collection == &default_entry_contexts[0]->box64_ld_lib ? 0 : 1; + CHECK("default resolver path lookup collection", collection != NULL); + CHECK("default resolver path lookup owner", + collection == &default_entry_contexts[context_index]->box64_ld_lib); CHECK("default resolver path lookup", strstr(path, "x86-64-v2") != NULL); - return default_entry_path_present; -} - -void kzt_wine_init_x86(box64context_t *context, uintptr_t guest_dlsym) -{ - CHECK("default resolver wine context", context != NULL); - CHECK("default resolver wine dlsym", guest_dlsym == 0x9020); - ++default_entry_wine_calls; - if (default_wine_pause) { - pthread_mutex_lock(&entry_init_lock); - default_wine_paused = 1; - pthread_cond_broadcast(&entry_init_ready); - while (!default_wine_release) { - pthread_cond_wait(&entry_init_ready, &entry_init_lock); - } - pthread_mutex_unlock(&entry_init_lock); - } + return default_entry_context_path_present[context_index]; } static void *initialize_default_guest_dl_entries(void *opaque) @@ -626,10 +723,14 @@ void kzt_guest_library_wrapper_source_release( uint64_t kzt_guest_library_run_dlsym( uintptr_t function, void *handle, void *symbol) { + seen_dlsym_function = function; seen_function = function; seen_handle = handle; seen_symbol = symbol; ++dlsym_calls; + if (runtime_dlsym_error_model) { + runtime_dlsym_error_pending = guest_result == 0; + } return guest_result; } @@ -646,6 +747,14 @@ uint64_t kzt_guest_library_run_dlvsym( uint64_t kzt_guest_library_run_dlerror(uintptr_t function) { + if (runtime_dlsym_error_model) { + int pending = runtime_dlsym_error_pending; + + runtime_dlsym_error_pending = 0; + seen_function = function; + ++dlerror_calls; + return pending ? (uintptr_t)guest_error : 0; + } if (thread_guest_error) { __atomic_add_fetch(&dlerror_calls, 1, __ATOMIC_RELAXED); return (uintptr_t)thread_guest_error; @@ -745,6 +854,27 @@ uintptr_t kzt_guest_library_select_symbol_result( return selected_result; } +uintptr_t kzt_guest_library_select_symbol_result_with_identity( + box64context_t *context, uintptr_t guest_handle, + const kzt_guest_loader_identity_t *queried_identity, + uintptr_t actual_guest_result, const char *symbol, const char *version) +{ + if (selector_identity_required >= 0) { + CHECK("selector exact identity presence", + (queried_identity != NULL) == selector_identity_required); + } + if (queried_identity) { + CHECK("selector exact identity handle", + queried_identity->handle == guest_handle); + CHECK("selector exact identity link map", + queried_identity->link_map_addr == dlinfo_link_map); + CHECK("selector exact identity namespace", + queried_identity->namespace_id == dlinfo_lmid); + } + return kzt_guest_library_select_symbol_result( + context, guest_handle, actual_guest_result, symbol, version); +} + kzt_guest_registry_t *KztGuestRegistryForContext(box64context_t *context) { CHECK("registry context", context != NULL); @@ -1075,6 +1205,7 @@ void KztPerObjectGotPltRelease(uintptr_t object_head) static void reset_calls(void) { seen_function = 0; + seen_dlsym_function = 0; seen_handle = NULL; seen_symbol = NULL; seen_version = NULL; @@ -1126,6 +1257,7 @@ static void reset_calls(void) registry_identity_handle = 0; registry_identity_link_map = 0; registry_identity_namespace = 0; + selector_identity_required = -1; registry_close_complete_calls = 0; registry_close_result = KZT_GUEST_LOADER_CLOSE_UNLOAD_UNPROVEN; registry_close_identity_missing_calls = 0; @@ -1145,6 +1277,8 @@ static void reset_calls(void) close_race_wait_calls = 0; error_race_enabled = 0; error_race_phase = 0; + runtime_dlsym_error_model = 0; + runtime_dlsym_error_pending = 0; } static void set_exact_match(uintptr_t link_map_addr, uintptr_t namespace_id) @@ -1175,11 +1309,25 @@ int main(void) dlentry_init_race_t entry_races[2] = { 0 }; pthread_t entry_threads[2]; dlprivate_t destroy_dl = { 0 }; + dlprivate_t runtime_dl_a = { 0 }; + dlprivate_t runtime_dl_b = { 0 }; dlentry_destroy_race_t destroy_race = { .dl = &destroy_dl }; dlentry_init_race_t destroy_init_race = { .dl = &destroy_dl }; pthread_t destroy_threads[2]; + pthread_t runtime_threads[2]; + runtime_entry_resolver_state_t runtime_resolver_a; + runtime_entry_resolver_state_t runtime_resolver_b; + runtime_entry_race_t runtime_races[2]; + kzt_guest_runtime_entry_scope_t runtime_scope = { 0 }; + box64context_t runtime_context_a = { .dlprivate = &runtime_dl_a }; + box64context_t runtime_context_b = { .dlprivate = &runtime_dl_b }; + uintptr_t runtime_entries[KZT_GUEST_RUNTIME_ENTRY_COUNT] = { + 0xa610, 0xa620, 0xa630, + }; dlprivate_t default_dl = { 0 }; + dlprivate_t default_dl_b = { 0 }; box64context_t default_context = { .dlprivate = &default_dl }; + box64context_t default_context_b = { .dlprivate = &default_dl_b }; default_dlentry_race_t default_races[2] = { { .context = &default_context }, { .context = &default_context }, @@ -1205,6 +1353,9 @@ int main(void) .dladdr1 = 0x1090, }; + default_entry_contexts[0] = &default_context; + default_entry_contexts[1] = &default_context_b; + fast_error_state.dlerror_fast_result_mirror = &fast_error_mirror; fast_error_state.dlerror_slow_required = 1; fast_error_mirror = fast_error_state.dlerror_fast_result; @@ -1419,16 +1570,25 @@ int main(void) finish_calls == 1 && !finish_publish && finish_link_map == 0); CHECK("guest failure captures guest error", dlerror_calls == 1); error_result = kzt_guest_dl_api_dlerror( - &error_state, kzt_guest_dl_api_load_dlerror_entry(&dl)); + &error_state, kzt_guest_dl_api_load_dlerror_entry(&dl), 0); CHECK("captured guest error returned", error_result.value && strcmp(error_result.value, guest_error) == 0 && !error_result.forward_to_guest_caller); error_result = kzt_guest_dl_api_dlerror( - &error_state, kzt_guest_dl_api_load_dlerror_entry(&dl)); + &error_state, kzt_guest_dl_api_load_dlerror_entry(&dl), 0); CHECK("captured guest error is one-shot", !error_result.value && !error_result.forward_to_guest_caller); CHECK("consumed guest error stays on clean path", dlerror_calls == 1); + error_state.last_error_returned = strdup("prior wrapper error"); + CHECK("cached error test allocation", + error_state.last_error_returned != NULL); + error_state.last_error_guest_consumed = 1; + error_state.dlerror_slow_required = 1; + error_result = kzt_guest_dl_api_dlerror( + &error_state, kzt_guest_dl_api_load_dlerror_entry(&dl), 1); + CHECK("guest route can report an error after a cached error was consumed", + !error_result.value && error_result.forward_to_guest_caller); kzt_guest_dl_api_clear_error(&error_state); reset_calls(); @@ -1505,6 +1665,10 @@ int main(void) reset_calls(); guest_result = 0x2010; selected_result = 0x3010; + registry_match_enabled = 1; + registry_match.link_map_addr = 0x9000; + registry_match.generation = 1; + registry_match.namespace_id = 0; symbol_result = kzt_guest_dl_api_dlsym( &context, &direct_entries, (void *)(uintptr_t)0x9000, symbol); CHECK("direct dlsym call", dlsym_calls == 1); @@ -1516,6 +1680,10 @@ int main(void) reset_calls(); guest_result = 0x2020; selected_result = 0x3020; + registry_match_enabled = 1; + registry_match.link_map_addr = 1; + registry_match.generation = 1; + registry_match.namespace_id = 0; symbol_result = kzt_guest_dl_api_dlsym( &context, &direct_entries, (void *)(uintptr_t)1, symbol); CHECK("opaque low dlsym call", dlsym_calls == 1); @@ -1523,6 +1691,19 @@ int main(void) seen_handle == (void *)(uintptr_t)1); CHECK("opaque low dlsym result", symbol_result.value == 0x3020); + reset_calls(); + guest_result = 0x2028; + selected_result = 0x3028; + dlinfo_identity_enabled = 1; + dlinfo_link_map = 0x2098; + dlinfo_lmid = 0; + selector_identity_required = 1; + symbol_result = kzt_guest_dl_api_dlsym( + &context, &direct_entries, (void *)(uintptr_t)0x9028, symbol); + CHECK("unpublished handle queries exact dlinfo", dlinfo_calls == 2); + CHECK("unpublished handle selected result", + symbol_result.value == selected_result); + reset_calls(); symbol_result = kzt_guest_dl_api_dlsym( &context, &direct_entries, (void *)~0ULL, symbol); @@ -1533,6 +1714,10 @@ int main(void) reset_calls(); guest_result = 0x2030; selected_result = 0x3030; + registry_match_enabled = 1; + registry_match.link_map_addr = 1; + registry_match.generation = 1; + registry_match.namespace_id = 0; symbol_result = kzt_guest_dl_api_dlvsym( &context, &direct_entries, (void *)(uintptr_t)1, symbol, version); CHECK("dlvsym call", dlvsym_calls == 1); @@ -1970,83 +2155,20 @@ int main(void) __atomic_load_n(&entry_destroy_done, __ATOMIC_ACQUIRE)); CHECK("guest dl teardown prevents publication", destroy_init_race.result == &destroy_init_race.fallback); - - CHECK("default guest dl teardown state initializes", - kzt_guest_dl_api_entry_state_init(&default_dl) == 0); - default_entry_pause = 0; - default_entry_libc_calls = 0; - default_entry_libdl_calls = 0; - default_entry_wine_calls = 0; - default_entry_path_calls = 0; - default_entry_header_frees = 0; - default_entry_path_present = 0; - default_entry_incomplete = 0; - default_wine_pause = 1; - default_wine_paused = 0; - default_wine_release = 0; - entry_destroy_started = 0; - entry_destroy_done = 0; - CHECK("default guest dl prepare starts", - pthread_create(&default_threads[0], NULL, - initialize_default_guest_dl_entries, - &default_races[0]) == 0); - pthread_mutex_lock(&entry_init_lock); - while (!default_wine_paused) { - pthread_cond_wait(&entry_init_ready, &entry_init_lock); - } - pthread_mutex_unlock(&entry_init_lock); - CHECK("default guest dl table remains hidden during prepare", - kzt_guest_dl_api_load_entries(&default_dl) == NULL); - CHECK("default guest dl prepare waiter starts", - pthread_create(&default_threads[1], NULL, - initialize_default_guest_dl_entries, - &default_races[1]) == 0); - for (;;) { - unsigned int slow_users; - - pthread_mutex_lock(&default_dl.guest_dl_entries.mutex); - slow_users = default_dl.guest_dl_entries.slow_users; - pthread_mutex_unlock(&default_dl.guest_dl_entries.mutex); - if (slow_users == 2) { - break; - } - sched_yield(); - } - destroy_race.dl = &default_dl; - CHECK("default guest dl prepare destroy starts", - pthread_create(&destroy_threads[0], NULL, - destroy_guest_dl_entries, &destroy_race) == 0); - while (!__atomic_load_n(&entry_destroy_started, __ATOMIC_ACQUIRE)) { - sched_yield(); - } - CHECK("default guest dl destroy waits for prepare and waiter", - !__atomic_load_n(&entry_destroy_done, __ATOMIC_ACQUIRE)); - pthread_mutex_lock(&entry_init_lock); - default_wine_release = 1; - pthread_cond_broadcast(&entry_init_ready); - pthread_mutex_unlock(&entry_init_lock); - CHECK("default guest dl prepare initializer joins", - pthread_join(default_threads[0], NULL) == 0); - CHECK("default guest dl prepare waiter joins", - pthread_join(default_threads[1], NULL) == 0); - CHECK("default guest dl prepare destroy joins", - pthread_join(destroy_threads[0], NULL) == 0); - CHECK("default guest dl teardown blocks post-prepare publication", - default_races[0].result == &default_races[0].fallback && - default_races[1].result == NULL && - kzt_guest_dl_api_load_entries(&default_dl) == NULL); + kzt_guest_dl_api_entry_state_destroy(&destroy_dl); CHECK("default guest dl state initializes", kzt_guest_dl_api_entry_state_init(&default_dl) == 0); default_entry_pause = 1; default_entry_libc_calls = 0; default_entry_libdl_calls = 0; - default_entry_wine_calls = 0; default_entry_path_calls = 0; default_entry_header_frees = 0; - default_entry_path_present = 0; + memset(default_entry_context_calls, 0, + sizeof(default_entry_context_calls)); + memset(default_entry_context_path_present, 0, + sizeof(default_entry_context_path_present)); default_entry_incomplete = 0; - default_wine_pause = 0; entry_init_paused = 0; entry_init_release = 0; CHECK("default guest dl libc caller starts", @@ -2075,13 +2197,30 @@ int main(void) default_entry_path_calls == 1); CHECK("default guest dl resolver frees transient headers", default_entry_header_frees == 2); - CHECK("default guest dl post init runs once", - default_entry_wine_calls == 1); CHECK("default guest dl callers share complete table", default_races[0].result && default_races[0].result == default_races[1].result && default_races[0].result->dlopen == 0x9000 && default_races[0].result->dlerror == 0x9080); + CHECK("default initialization publishes runtime entries", + kzt_guest_runtime_entry_for_guest_branch( + &default_context, KZT_GUEST_RUNTIME_FREE) == 0x9090 && + kzt_guest_runtime_entry_for_guest_branch( + &default_context, KZT_GUEST_RUNTIME_REALLOC) == 0x90a0 && + kzt_guest_runtime_entry_for_guest_branch( + &default_context, + KZT_GUEST_RUNTIME_PTHREAD_SETCANCELTYPE) == 0x90b0); + reset_calls(); + runtime_dlsym_error_model = 1; + runtime_dlsym_error_pending = 1; + CHECK("runtime lookup does not consume an older guest error", + kzt_guest_runtime_entry_for_guest_branch( + &default_context, KZT_GUEST_RUNTIME_FREE) == 0x9090 && + dlsym_calls == 0 && dlerror_calls == 0 && + kzt_guest_library_run_dlerror(0x9080) == + (uintptr_t)guest_error && + dlerror_calls == 1); + runtime_dlsym_error_model = 0; default_entry_pause = 0; for (int i = 0; i < 1000; ++i) { CHECK("default guest dl steady table remains published", @@ -2091,17 +2230,37 @@ int main(void) } CHECK("default guest dl steady calls skip resolution", default_entry_libc_calls == 1 && default_entry_libdl_calls == 1 && - default_entry_wine_calls == 1 && default_entry_path_calls == 1); + default_entry_path_calls == 1); kzt_guest_dl_api_entry_state_destroy(&default_dl); + CHECK("second-context guest dl state initializes", + kzt_guest_dl_api_entry_state_init(&default_dl_b) == 0); + default_entry_libc_calls = 0; + default_entry_libdl_calls = 0; + default_entry_path_calls = 0; + default_entry_header_frees = 0; + default_entry_context_calls[0] = 0; + default_entry_context_calls[1] = 0; + default_entry_context_path_present[1] = 0; + default_races[1].context = &default_context_b; + default_races[1].result = kzt_guest_dl_entries_for_call( + &default_context_b, &default_races[1].fallback); + CHECK("second context resolves through its own path collection", + default_races[1].result && + default_races[1].result->dlopen == 0xa000 && + default_races[1].result->dlerror == 0xa080 && + default_entry_context_calls[0] == 0 && + default_entry_context_calls[1] == 2 && + default_entry_path_calls == 1); + kzt_guest_dl_api_entry_state_destroy(&default_dl_b); + CHECK("default guest dl failure resource state initializes", kzt_guest_dl_api_entry_state_init(&default_dl) == 0); default_entry_libc_calls = 0; default_entry_libdl_calls = 0; - default_entry_wine_calls = 0; default_entry_path_calls = 0; default_entry_header_frees = 0; - default_entry_path_present = 0; + default_entry_context_path_present[0] = 0; default_entry_incomplete = 1; for (int i = 0; i < 100; ++i) { default_races[0].result = kzt_guest_dl_entries_for_call( @@ -2114,8 +2273,7 @@ int main(void) default_entry_libc_calls == 100 && default_entry_libdl_calls == 100 && default_entry_header_frees == 200 && - default_entry_path_calls == 1 && - default_entry_wine_calls == 0); + default_entry_path_calls == 1); default_entry_incomplete = 0; default_races[0].result = kzt_guest_dl_entries_for_call( &default_context, &default_races[0].fallback); @@ -2125,10 +2283,211 @@ int main(void) default_entry_libc_calls == 101 && default_entry_libdl_calls == 101 && default_entry_header_frees == 202 && - default_entry_path_calls == 1 && - default_entry_wine_calls == 1); + default_entry_path_calls == 1); kzt_guest_dl_api_entry_state_destroy(&default_dl); + CHECK("runtime entry state A initializes", + kzt_guest_dl_api_entry_state_init(&runtime_dl_a) == 0); + runtime_entry_resolver_state_init( + &runtime_resolver_a, "free", 0xa100); + runtime_resolver_a.failures = 1; + CHECK("runtime entry failure is not cached", + kzt_guest_runtime_entry_ensure( + &runtime_dl_a, KZT_GUEST_RUNTIME_FREE, + resolve_runtime_entry, &runtime_resolver_a) == 0 && + kzt_guest_runtime_entry_load( + &runtime_context_a, KZT_GUEST_RUNTIME_FREE) == 0); + CHECK("runtime entry retries and publishes a valid address", + kzt_guest_runtime_entry_ensure( + &runtime_dl_a, KZT_GUEST_RUNTIME_FREE, + resolve_runtime_entry, &runtime_resolver_a) == 0xa100 && + runtime_resolver_a.calls == 2); + CHECK("runtime entry fast path is an atomic cached load", + kzt_guest_runtime_entry_load( + &runtime_context_a, KZT_GUEST_RUNTIME_FREE) == 0xa100); + + runtime_entry_resolver_state_init( + &runtime_resolver_b, "realloc", 0xa200); + CHECK("runtime entries cache independently", + kzt_guest_runtime_entry_ensure( + &runtime_dl_a, KZT_GUEST_RUNTIME_REALLOC, + resolve_runtime_entry, &runtime_resolver_b) == 0xa200 && + kzt_guest_runtime_entry_load( + &runtime_context_a, KZT_GUEST_RUNTIME_FREE) == 0xa100 && + runtime_resolver_a.calls == 2 && + runtime_resolver_b.calls == 1); + runtime_entry_resolver_state_destroy(&runtime_resolver_a); + runtime_entry_resolver_state_destroy(&runtime_resolver_b); + kzt_guest_dl_api_entry_state_destroy(&runtime_dl_a); + + CHECK("runtime concurrent state initializes", + kzt_guest_dl_api_entry_state_init(&runtime_dl_a) == 0); + runtime_entry_resolver_state_init( + &runtime_resolver_a, "pthread_setcanceltype", 0xa300); + runtime_resolver_a.pause = 1; + runtime_races[0] = (runtime_entry_race_t) { + .dl = &runtime_dl_a, + .entry = KZT_GUEST_RUNTIME_PTHREAD_SETCANCELTYPE, + .resolver = &runtime_resolver_a, + }; + runtime_races[1] = runtime_races[0]; + CHECK("runtime first resolver starts", + pthread_create(&runtime_threads[0], NULL, + resolve_runtime_entry_in_thread, + &runtime_races[0]) == 0); + pthread_mutex_lock(&runtime_resolver_a.mutex); + while (!runtime_resolver_a.paused) { + pthread_cond_wait( + &runtime_resolver_a.ready, &runtime_resolver_a.mutex); + } + pthread_mutex_unlock(&runtime_resolver_a.mutex); + CHECK("runtime concurrent waiter starts", + pthread_create(&runtime_threads[1], NULL, + resolve_runtime_entry_in_thread, + &runtime_races[1]) == 0); + CHECK("runtime concurrent waiter enters state gate", + wait_for_runtime_slow_users(&runtime_dl_a, 2) == 0); + CHECK("runtime entry remains hidden until nonzero publication", + kzt_guest_runtime_entry_load( + &runtime_context_a, + KZT_GUEST_RUNTIME_PTHREAD_SETCANCELTYPE) == 0); + pthread_mutex_lock(&runtime_resolver_a.mutex); + runtime_resolver_a.release = 1; + pthread_cond_broadcast(&runtime_resolver_a.ready); + pthread_mutex_unlock(&runtime_resolver_a.mutex); + CHECK("runtime resolver thread joins", + pthread_join(runtime_threads[0], NULL) == 0); + CHECK("runtime waiter thread joins", + pthread_join(runtime_threads[1], NULL) == 0); + CHECK("runtime concurrent first resolution publishes once", + runtime_resolver_a.calls == 1 && + runtime_races[0].result == 0xa300 && + runtime_races[1].result == 0xa300); + runtime_entry_resolver_state_destroy(&runtime_resolver_a); + kzt_guest_dl_api_entry_state_destroy(&runtime_dl_a); + + CHECK("runtime isolated state A initializes", + kzt_guest_dl_api_entry_state_init(&runtime_dl_a) == 0); + CHECK("runtime isolated state B initializes", + kzt_guest_dl_api_entry_state_init(&runtime_dl_b) == 0); + runtime_entry_resolver_state_init( + &runtime_resolver_a, "free", 0xa410); + runtime_entry_resolver_state_init( + &runtime_resolver_b, "free", 0xa420); + CHECK("runtime contexts publish isolated addresses", + kzt_guest_runtime_entry_ensure( + &runtime_dl_a, KZT_GUEST_RUNTIME_FREE, + resolve_runtime_entry, &runtime_resolver_a) == 0xa410 && + kzt_guest_runtime_entry_ensure( + &runtime_dl_b, KZT_GUEST_RUNTIME_FREE, + resolve_runtime_entry, &runtime_resolver_b) == 0xa420 && + kzt_guest_runtime_entry_load( + &runtime_context_a, KZT_GUEST_RUNTIME_FREE) == 0xa410 && + kzt_guest_runtime_entry_load( + &runtime_context_b, KZT_GUEST_RUNTIME_FREE) == 0xa420); + runtime_entry_resolver_state_destroy(&runtime_resolver_a); + runtime_entry_resolver_state_destroy(&runtime_resolver_b); + kzt_guest_dl_api_entry_state_destroy(&runtime_dl_a); + kzt_guest_dl_api_entry_state_destroy(&runtime_dl_b); + + CHECK("runtime pinned state initializes", + kzt_guest_dl_api_entry_state_init(&runtime_dl_a) == 0); + CHECK("runtime pinned entries publish", + kzt_guest_runtime_entry_state_publish( + &runtime_dl_a.guest_dl_entries, runtime_entries) == 0); + CHECK("runtime pinned scope acquires", + kzt_guest_runtime_entry_acquire( + &runtime_context_a, + KZT_GUEST_RUNTIME_PTHREAD_SETCANCELTYPE, + &runtime_scope) == 0 && runtime_scope.address == 0xa630); + entry_destroy_started = 0; + entry_destroy_done = 0; + destroy_race.dl = &runtime_dl_a; + CHECK("runtime pinned teardown starts", + pthread_create(&runtime_threads[0], NULL, + destroy_guest_dl_entries, &destroy_race) == 0); + while (!__atomic_load_n(&entry_destroy_started, __ATOMIC_ACQUIRE)) { + sched_yield(); + } + for (;;) { + int teardown; + + pthread_mutex_lock(&runtime_dl_a.guest_dl_entries.mutex); + teardown = runtime_dl_a.guest_dl_entries.teardown; + pthread_mutex_unlock(&runtime_dl_a.guest_dl_entries.mutex); + if (teardown) { + break; + } + sched_yield(); + } + CHECK("runtime pinned teardown waits", + !__atomic_load_n(&entry_destroy_done, __ATOMIC_ACQUIRE)); + kzt_guest_runtime_entry_release(&runtime_scope); + CHECK("runtime pinned teardown joins", + pthread_join(runtime_threads[0], NULL) == 0 && + __atomic_load_n(&entry_destroy_done, __ATOMIC_ACQUIRE)); + CHECK("runtime pinned teardown clears entries", + runtime_dl_a.guest_dl_entries.runtime_entries[ + KZT_GUEST_RUNTIME_PTHREAD_SETCANCELTYPE] == 0); + kzt_guest_dl_api_entry_state_destroy(&runtime_dl_a); + + CHECK("runtime teardown state initializes", + kzt_guest_dl_api_entry_state_init(&runtime_dl_a) == 0); + runtime_entry_resolver_state_init( + &runtime_resolver_a, "realloc", 0xa500); + runtime_resolver_a.pause = 1; + runtime_races[0] = (runtime_entry_race_t) { + .dl = &runtime_dl_a, + .entry = KZT_GUEST_RUNTIME_REALLOC, + .resolver = &runtime_resolver_a, + }; + CHECK("runtime teardown resolver starts", + pthread_create(&runtime_threads[0], NULL, + resolve_runtime_entry_in_thread, + &runtime_races[0]) == 0); + pthread_mutex_lock(&runtime_resolver_a.mutex); + while (!runtime_resolver_a.paused) { + pthread_cond_wait( + &runtime_resolver_a.ready, &runtime_resolver_a.mutex); + } + pthread_mutex_unlock(&runtime_resolver_a.mutex); + entry_destroy_started = 0; + entry_destroy_done = 0; + destroy_race.dl = &runtime_dl_a; + CHECK("runtime teardown destroy starts", + pthread_create(&runtime_threads[1], NULL, + destroy_guest_dl_entries, &destroy_race) == 0); + while (!__atomic_load_n(&entry_destroy_started, __ATOMIC_ACQUIRE)) { + sched_yield(); + } + for (;;) { + int teardown; + + pthread_mutex_lock(&runtime_dl_a.guest_dl_entries.mutex); + teardown = runtime_dl_a.guest_dl_entries.teardown; + pthread_mutex_unlock(&runtime_dl_a.guest_dl_entries.mutex); + if (teardown) { + break; + } + sched_yield(); + } + CHECK("runtime teardown waits for resolver", + !__atomic_load_n(&entry_destroy_done, __ATOMIC_ACQUIRE)); + pthread_mutex_lock(&runtime_resolver_a.mutex); + runtime_resolver_a.release = 1; + pthread_cond_broadcast(&runtime_resolver_a.ready); + pthread_mutex_unlock(&runtime_resolver_a.mutex); + CHECK("runtime teardown resolver joins", + pthread_join(runtime_threads[0], NULL) == 0); + CHECK("runtime teardown destroy joins", + pthread_join(runtime_threads[1], NULL) == 0); + CHECK("runtime teardown blocks late publication", + runtime_races[0].result == 0 && + runtime_dl_a.guest_dl_entries.runtime_entries[ + KZT_GUEST_RUNTIME_REALLOC] == 0); + kzt_guest_dl_api_entry_state_destroy(&runtime_dl_a); + runtime_entry_resolver_state_destroy(&runtime_resolver_a); + kzt_guest_dl_api_clear_error(&error_state); kzt_guest_dl_api_free_errors(&error_state); printf("kzt guest dl API tests: PASS\n"); diff --git a/tests/unit/kzt/test_guest_glob_dat_target.c b/tests/unit/kzt/test_guest_glob_dat_target.c new file mode 100644 index 00000000000..e6cab10af3c --- /dev/null +++ b/tests/unit/kzt/test_guest_glob_dat_target.c @@ -0,0 +1,536 @@ +#include +#include +#include +#include + +#include "target/i386/latx/include/box64context.h" +#include "target/i386/latx/include/elfloader_private.h" +#include "target/i386/latx/include/kzt_guest_glob_dat_target.h" +#include "target/i386/latx/include/kzt_owner_resolver.h" +#include "target/i386/latx/include/kzt_rela_runtime_bridge.h" + +#define SOURCE_LINK_MAP UINT64_C(0x1000) +#define OWNER_LINK_MAP UINT64_C(0x2000) +#define GUEST_TARGET UINT64_C(0x3000) +#define BRIDGE_TARGET UINT64_C(0x4000) + +static int failures; +static int source_release_count; +static int decision_release_count; +static int quiescence_release_count; +static int handle_release_count; +static int scope_check_count; +static int lookup_count; +static int selector_count; +static int revalidate_count; +static int writer_count; +static int scope_safe; +static int revalidate_safe; +static uintptr_t selector_result; +static kzt_production_slot_transaction_result_t writer_result; +static kzt_guest_library_object_type_t lookup_object_type; +static char events[16]; +static size_t event_count; + +#define CHECK(label, condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "%s: FAIL\n", label); \ + ++failures; \ + } \ + } while (0) + +static void note_event(char event) +{ + if (event_count + 1 < sizeof(events)) { + events[event_count++] = event; + events[event_count] = '\0'; + } +} + +static int read_guest_memory(uintptr_t address, void *destination, + size_t size, void *opaque) +{ + (void)address; + (void)destination; + (void)size; + (void)opaque; + return 0; +} + +kzt_guest_registry_t *KztGuestRegistryForContext(box64context_t *context) +{ + return context ? (kzt_guest_registry_t *)(uintptr_t)1 : NULL; +} + +kzt_guest_library_bindings_t *KztGuestLibraryBindingsForContext( + box64context_t *context) +{ + return context ? (kzt_guest_library_bindings_t *)(uintptr_t)2 : NULL; +} + +void kzt_owner_resolver_init(kzt_owner_resolution_t *resolution) +{ + memset(resolution, 0, sizeof(*resolution)); +} + +int kzt_owner_resolver_resolve_current( + kzt_guest_registry_t *registry, uintptr_t current_address, + uintptr_t expected_address, kzt_owner_resolution_t *resolution) +{ + CHECK("owner registry", registry != NULL); + CHECK("owner current address", current_address == GUEST_TARGET); + CHECK("owner expected address", expected_address == GUEST_TARGET); + resolution->status = KZT_OWNER_RESOLVER_RESOLVED; + resolution->owner_match = KZT_PATCH_OWNER_MATCH; + resolution->current_owner = (kzt_patch_object_ref_t) { + .known = 1, + .link_map_addr = OWNER_LINK_MAP, + .generation = 22, + }; + return 0; +} + +int kzt_guest_registry_find_live_object( + kzt_guest_registry_t *registry, uintptr_t link_map_addr, + kzt_guest_registry_address_match_t *match) +{ + CHECK("live registry", registry != NULL); + memset(match, 0, sizeof(*match)); + match->link_map_addr = link_map_addr; + match->generation = link_map_addr == SOURCE_LINK_MAP ? 11 : 22; + match->namespace_id_status = KZT_GUEST_FIELD_OK; + CHECK("live exact object", link_map_addr == SOURCE_LINK_MAP || + link_map_addr == OWNER_LINK_MAP); + return 0; +} + +int kzt_guest_registry_source_lease_acquire( + kzt_guest_registry_t *registry, uintptr_t link_map_addr, + unsigned long generation, uintptr_t namespace_id, + kzt_guest_registry_source_lease_t *lease) +{ + CHECK("source lease registry", registry != NULL); + CHECK("source lease identity", link_map_addr == SOURCE_LINK_MAP && + generation == 11 && namespace_id == 0); + *lease = (kzt_guest_registry_source_lease_t) { + .registry = registry, + .link_map_addr = link_map_addr, + .generation = generation, + .namespace_id = namespace_id, + .active = 1, + }; + return 0; +} + +void kzt_guest_registry_source_lease_release( + kzt_guest_registry_source_lease_t *lease) +{ + if (lease && lease->active) { + lease->active = 0; + ++source_release_count; + note_event('S'); + } +} + +int kzt_guest_registry_patch_decision_lease_acquire( + const kzt_guest_registry_source_lease_t *source_lease, + kzt_guest_registry_patch_decision_lease_t *lease) +{ + CHECK("decision source active", source_lease && source_lease->active); + *lease = (kzt_guest_registry_patch_decision_lease_t) { + .registry = source_lease->registry, + .link_map_addr = source_lease->link_map_addr, + .generation = source_lease->generation, + .namespace_id = source_lease->namespace_id, + .active = 1, + }; + return 0; +} + +void kzt_guest_registry_patch_decision_lease_release( + kzt_guest_registry_patch_decision_lease_t *lease) +{ + if (lease && lease->active) { + lease->active = 0; + ++decision_release_count; + note_event('D'); + } +} + +int kzt_guest_library_loader_quiescence_try_acquire( + kzt_guest_library_bindings_t *bindings, + kzt_guest_library_loader_quiescence_lease_t *lease) +{ + CHECK("quiescence bindings", bindings != NULL); + lease->bindings = bindings; + lease->cookie = 1; + return 0; +} + +void kzt_guest_library_loader_quiescence_release( + kzt_guest_library_loader_quiescence_lease_t *lease) +{ + if (lease && lease->bindings) { + lease->bindings = NULL; + ++quiescence_release_count; + note_event('Q'); + } +} + +int kzt_guest_registry_context_get_main_namespace_head( + const kzt_guest_registry_context_t *context, uintptr_t *head) +{ + CHECK("namespace context", context != NULL); + *head = SOURCE_LINK_MAP; + return 0; +} + +kzt_guest_symbol_scope_status_t kzt_guest_symbol_scope_check( + const kzt_guest_symbol_scope_request_t *request, + uintptr_t selected_provider_link_map, + uintptr_t selected_provider_address, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_symbol_scope_result_t *result) +{ + ++scope_check_count; + CHECK("scope source", request->source.link_map_addr == SOURCE_LINK_MAP && + request->source.generation == 11 && + request->source.namespace_id == 0); + CHECK("scope symbol", strcmp(request->symbol, "glob_symbol") == 0); + CHECK("scope version", request->version_evidence == + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED && !request->version); + CHECK("scope provider", selected_provider_link_map == OWNER_LINK_MAP && + selected_provider_address == GUEST_TARGET); + CHECK("scope reader", reader_ops && reader_ops->read_memory == + read_guest_memory); + memset(result, 0, sizeof(*result)); + result->status = scope_safe ? KZT_GUEST_SYMBOL_SCOPE_SAFE : + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED; + return result->status; +} + +kzt_guest_symbol_scope_status_t kzt_guest_symbol_scope_revalidate( + const kzt_guest_symbol_scope_result_t *proof, + const kzt_guest_symbol_scope_request_t *request, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_symbol_scope_result_t *result) +{ + ++revalidate_count; + note_event('R'); + CHECK("revalidate proof", proof != NULL); + CHECK("revalidate request", request && + strcmp(request->symbol, "glob_symbol") == 0); + CHECK("revalidate reader", reader_ops && + reader_ops->read_memory == read_guest_memory); + memset(result, 0, sizeof(*result)); + result->status = revalidate_safe ? KZT_GUEST_SYMBOL_SCOPE_SAFE : + KZT_GUEST_SYMBOL_SCOPE_GUEST_REQUIRED; + return result->status; +} + +int KztGuestLibraryLookupForContext( + box64context_t *context, const kzt_guest_library_binding_key_t *key, + kzt_guest_library_handle_t *handle) +{ + ++lookup_count; + CHECK("lookup context", context != NULL); + CHECK("lookup exact key", key->link_map_addr == OWNER_LINK_MAP && + key->generation == 22 && key->namespace_id == 0 && + key->namespace_kind == KZT_GUEST_LIBRARY_NAMESPACE_MAIN); + *handle = (kzt_guest_library_handle_t) { + .bindings = (kzt_guest_library_bindings_t *)(uintptr_t)2, + .entry = (void *)(uintptr_t)3, + .library = (library_t *)(uintptr_t)4, + .object_type = lookup_object_type, + }; + return 0; +} + +void kzt_guest_library_handle_release(kzt_guest_library_handle_t *handle) +{ + if (handle && handle->entry) { + ++handle_release_count; + note_event('H'); + memset(handle, 0, sizeof(*handle)); + } +} + +uintptr_t kzt_rela_runtime_select_exact_wrapper_bridge_retained( + box64context_t *context, + const kzt_guest_library_handle_t *retained_provider_handle, + const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, + const char *symbol_version) +{ + ++selector_count; + CHECK("selector context", context != NULL); + CHECK("selector retained handle", retained_provider_handle && + retained_provider_handle->entry); + CHECK("selector symbol", strcmp(symbol_name, "glob_symbol") == 0); + CHECK("selector unversioned", version_evidence == + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED && !symbol_version); + return selector_result; +} + +kzt_production_slot_transaction_result_t +kzt_production_eager_relocation_write( + box64context_t *context, uintptr_t source_link_map, + const kzt_patch_object_ref_t *owner, + kzt_patch_relocation_type_t reloc_type, uintptr_t slot_addr, + uintptr_t expected, uintptr_t replacement, const char *symbol_name, + kzt_symbol_version_evidence_t version_evidence, const char *version, + uintptr_t *final_value) +{ + uintptr_t *slot = (uintptr_t *)slot_addr; + + ++writer_count; + note_event('W'); + CHECK("writer context", context != NULL); + CHECK("writer source", source_link_map == SOURCE_LINK_MAP); + CHECK("writer owner", owner && owner->known && + owner->link_map_addr == OWNER_LINK_MAP && owner->generation == 22); + CHECK("writer relocation", reloc_type == KZT_PATCH_RELOCATION_GLOB_DAT); + CHECK("writer target", slot && *slot == expected && + expected == GUEST_TARGET && replacement == BRIDGE_TARGET); + CHECK("writer symbol", strcmp(symbol_name, "glob_symbol") == 0); + CHECK("writer version", version_evidence == + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED && !version); + if (writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED) { + *slot = replacement; + *final_value = replacement; + } else { + *final_value = expected; + } + return writer_result; +} + +static void reset_fixture(box64context_t *context, elfheader_t *head, + Elf64_Sym *symbol) +{ + memset(context, 0, sizeof(*context)); + memset(head, 0, sizeof(*head)); + memset(symbol, 0, sizeof(*symbol)); + context->kzt_guest_scope_layout = + KZT_GUEST_SCOPE_LAYOUT_GLIBC_2_39_C591A5DF; + head->self_link_map = SOURCE_LINK_MAP; + head->numDynSym = 1; + symbol->st_info = ELF64_ST_INFO(STB_GLOBAL, STT_FUNC); + source_release_count = 0; + decision_release_count = 0; + quiescence_release_count = 0; + handle_release_count = 0; + scope_check_count = 0; + lookup_count = 0; + selector_count = 0; + revalidate_count = 0; + writer_count = 0; + scope_safe = 1; + revalidate_safe = 1; + selector_result = BRIDGE_TARGET; + writer_result = KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED; + lookup_object_type = KZT_GUEST_LIBRARY_OBJECT_WRAPPED; + memset(events, 0, sizeof(events)); + event_count = 0; +} + +static int resolve(box64context_t *context, elfheader_t *head, + Elf64_Sym *symbol, int version, + const char *version_name, + kzt_guest_glob_dat_target_t *target) +{ + const kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = read_guest_memory, + }; + + return kzt_guest_glob_dat_target_resolve( + context, head, GUEST_TARGET, 0, symbol, "glob_symbol", version, + version_name, &reader_ops, target); +} + +static int route(box64context_t *context, elfheader_t *head, + Elf64_Sym *symbol, int version, + const char *version_name, uintptr_t *slot, + kzt_guest_glob_dat_route_result_t *result) +{ + const kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = read_guest_memory, + }; + + return kzt_guest_glob_dat_route( + context, head, (uintptr_t)slot, *slot, 0, symbol, "glob_symbol", + version, version_name, &reader_ops, result); +} + +static void test_unversioned_selects_exact_bridge_and_holds_leases(void) +{ + box64context_t context; + elfheader_t head; + Elf64_Sym symbol; + kzt_guest_glob_dat_target_t target; + + reset_fixture(&context, &head, &symbol); + CHECK("unversioned resolver handled", + resolve(&context, &head, &symbol, 1, NULL, &target) == 1); + CHECK("unversioned bridge selected", + target.guest_target == GUEST_TARGET && + target.selected_target == BRIDGE_TARGET && target.exact_bridge); + CHECK("unversioned exact path", scope_check_count == 1 && + lookup_count == 1 && selector_count == 1 && + handle_release_count == 1); + CHECK("success leases remain held", source_release_count == 0 && + decision_release_count == 0 && quiescence_release_count == 0); + kzt_guest_glob_dat_target_release(&target); + CHECK("caller releases success leases", source_release_count == 1 && + decision_release_count == 1 && quiescence_release_count == 1); +} + +static void test_versioned_preserves_guest_without_native_lookup(void) +{ + box64context_t context; + elfheader_t head; + Elf64_Sym symbol; + kzt_guest_glob_dat_target_t target; + + reset_fixture(&context, &head, &symbol); + CHECK("versioned resolver handled", + resolve(&context, &head, &symbol, 2, "VERS_1", &target) == 1); + CHECK("versioned guest preserved", + target.selected_target == GUEST_TARGET && !target.exact_bridge); + CHECK("versioned skips native route", scope_check_count == 0 && + lookup_count == 0 && selector_count == 0); + CHECK("versioned owns no leases", source_release_count == 0 && + decision_release_count == 0 && quiescence_release_count == 0); +} + +static void test_missing_bridge_preserves_guest_and_releases_leases(void) +{ + box64context_t context; + elfheader_t head; + Elf64_Sym symbol; + kzt_guest_glob_dat_target_t target; + + reset_fixture(&context, &head, &symbol); + selector_result = 0; + CHECK("missing bridge resolver handled", + resolve(&context, &head, &symbol, 1, NULL, &target) == 1); + CHECK("missing bridge guest preserved", + target.selected_target == GUEST_TARGET && !target.exact_bridge); + CHECK("missing bridge route attempted", scope_check_count == 1 && + lookup_count == 1 && selector_count == 1 && + handle_release_count == 1); + CHECK("missing bridge releases leases", source_release_count == 1 && + decision_release_count == 1 && quiescence_release_count == 1); +} + +static void test_scope_failure_preserves_guest_and_releases_leases(void) +{ + box64context_t context; + elfheader_t head; + Elf64_Sym symbol; + kzt_guest_glob_dat_target_t target; + + reset_fixture(&context, &head, &symbol); + scope_safe = 0; + CHECK("scope failure resolver handled", + resolve(&context, &head, &symbol, 1, NULL, &target) == 1); + CHECK("scope failure guest preserved", + target.selected_target == GUEST_TARGET && !target.exact_bridge); + CHECK("scope failure stops native route", scope_check_count == 1 && + lookup_count == 0 && selector_count == 0); + CHECK("scope failure releases leases", source_release_count == 1 && + decision_release_count == 1 && quiescence_release_count == 1); +} + +static void test_route_revalidates_and_writes_exact_bridge(void) +{ + box64context_t context; + elfheader_t head; + Elf64_Sym symbol; + uintptr_t slot = GUEST_TARGET; + kzt_guest_glob_dat_route_result_t result; + + reset_fixture(&context, &head, &symbol); + CHECK("route success handled", + route(&context, &head, &symbol, 1, NULL, &slot, &result) == 1); + CHECK("route success writes bridge", + slot == BRIDGE_TARGET && result.guest_target == GUEST_TARGET && + result.selected_target == BRIDGE_TARGET && + result.final_value == BRIDGE_TARGET && + result.writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED); + CHECK("route success calls", selector_count == 1 && + revalidate_count == 1 && writer_count == 1); + CHECK("route success release order", strcmp(events, "HRWDQS") == 0); +} + +static void test_route_revalidation_failure_preserves_guest(void) +{ + box64context_t context; + elfheader_t head; + Elf64_Sym symbol; + uintptr_t slot = GUEST_TARGET; + kzt_guest_glob_dat_route_result_t result; + + reset_fixture(&context, &head, &symbol); + revalidate_safe = 0; + CHECK("route stale scope handled", + route(&context, &head, &symbol, 1, NULL, &slot, &result) == 1); + CHECK("route stale scope preserves guest", + slot == GUEST_TARGET && result.final_value == GUEST_TARGET && + result.writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_ERROR); + CHECK("route stale scope stops writer", + revalidate_count == 1 && writer_count == 0); + CHECK("route stale scope releases", strcmp(events, "HRDQS") == 0); +} + +static void test_route_writer_failure_preserves_guest_and_releases(void) +{ + box64context_t context; + elfheader_t head; + Elf64_Sym symbol; + uintptr_t slot = GUEST_TARGET; + kzt_guest_glob_dat_route_result_t result; + + reset_fixture(&context, &head, &symbol); + writer_result = KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH; + CHECK("route writer failure handled", + route(&context, &head, &symbol, 1, NULL, &slot, &result) == 1); + CHECK("route writer failure preserves guest", + slot == GUEST_TARGET && result.final_value == GUEST_TARGET && + result.writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH); + CHECK("route writer failure calls", + revalidate_count == 1 && writer_count == 1); + CHECK("route writer failure releases", strcmp(events, "HRWDQS") == 0); +} + +static void test_route_versioned_symbol_never_reaches_writer(void) +{ + box64context_t context; + elfheader_t head; + Elf64_Sym symbol; + uintptr_t slot = GUEST_TARGET; + kzt_guest_glob_dat_route_result_t result; + + reset_fixture(&context, &head, &symbol); + CHECK("route versioned handled", + route(&context, &head, &symbol, 2, "VERS_1", &slot, &result) == 1); + CHECK("route versioned preserves guest", + slot == GUEST_TARGET && result.selected_target == GUEST_TARGET && + result.final_value == GUEST_TARGET); + CHECK("route versioned skips native path", + selector_count == 0 && revalidate_count == 0 && writer_count == 0); +} + +int main(void) +{ + test_unversioned_selects_exact_bridge_and_holds_leases(); + test_versioned_preserves_guest_without_native_lookup(); + test_missing_bridge_preserves_guest_and_releases_leases(); + test_scope_failure_preserves_guest_and_releases_leases(); + test_route_revalidates_and_writes_exact_bridge(); + test_route_revalidation_failure_preserves_guest(); + test_route_writer_failure_preserves_guest_and_releases(); + test_route_versioned_symbol_never_reaches_writer(); + if (failures) fprintf(stderr, "%d failure(s)\n", failures); + return failures ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/tests/unit/kzt/test_guest_library_adapter.c b/tests/unit/kzt/test_guest_library_adapter.c index ccd9f707819..3c88b1a4d7c 100644 --- a/tests/unit/kzt/test_guest_library_adapter.c +++ b/tests/unit/kzt/test_guest_library_adapter.c @@ -22,6 +22,12 @@ int kzt_registry_diagnostics; int option_kzt = 1; int wine_option_kzt; +int FindLibIsWrapped(char *name) +{ + return name && (strcmp(name, "libwi963.so") == 0 || + strcmp(name, "libc.so.6") == 0); +} + static struct kzt_guest_library_bindings bindings; static kzt_guest_library_loader_scope_t *thread_scope; static uintptr_t expected_function; @@ -62,8 +68,6 @@ static int resolve_result; static int lookup_result; static library_t *lookup_library; static kzt_guest_library_object_type_t lookup_object_type; -static int wrapper_lookup_result; -static int wrapper_function_lookup_result; static uintptr_t wrapper_result; static int resolve_count; static int lookup_count; @@ -80,6 +84,7 @@ static kzt_guest_loader_identity_t lookup_loader_identity; static int lookup_loader_identity_result; static int symbol_source_result; static int symbol_source_acquire_count; +static int exact_symbol_source_acquire_count; static int dynamic_view_count; static int dynsym_lookup_count; static int evidence_lookup_count; @@ -90,13 +95,12 @@ static uintptr_t evidence_runtime_address; static unsigned char evidence_symbol_type; static kzt_symbol_version_evidence_t expected_dynsym_version_evidence; static const char *expected_dynsym_version; -static int provider_discover_result; -static int provider_discover_count; -static int wrapper_probe_count; -static kzt_patch_wrapper_match_t wrapper_probe_match; -static kzt_symbol_version_evidence_t wrapper_probe_version_evidence; -static const char *wrapper_probe_version; -static uintptr_t wrapper_probe_bridge_target; +static int exact_selector_count; +static uintptr_t exact_selector_result; +static int bridge_evidence_store_count; +static int bridge_evidence_valid; +static unsigned long bridge_evidence_dynamic_revision; +static uintptr_t bridge_evidence_target; #define CHECK(label, condition) \ do { \ @@ -186,6 +190,48 @@ int kzt_guest_registry_loader_symbol_source_acquire( return 0; } +int kzt_guest_registry_loader_symbol_source_acquire_exact( + kzt_guest_registry_t *registry, + const kzt_guest_loader_identity_t *queried_identity, + kzt_guest_loader_identity_t *identity, + kzt_guest_dynamic_view_t *dynamic_view, + kzt_guest_field_status_t *dynamic_status, + unsigned long *dynamic_revision, + kzt_guest_registry_source_lease_t *lease) +{ + CHECK("exact symbol source registry", + registry == (kzt_guest_registry_t *)(uintptr_t)0x1110); + CHECK("exact symbol source identity", + queried_identity && + queried_identity->handle == lookup_guest_handle && + queried_identity->link_map_addr == + lookup_loader_identity.link_map_addr && + queried_identity->namespace_id == + lookup_loader_identity.namespace_id); + ++exact_symbol_source_acquire_count; + if (symbol_source_result != 0) { + memset(identity, 0, sizeof(*identity)); + memset(dynamic_view, 0, sizeof(*dynamic_view)); + *dynamic_status = KZT_GUEST_FIELD_UNKNOWN; + *dynamic_revision = 0; + memset(lease, 0, sizeof(*lease)); + return symbol_source_result; + } + *identity = lookup_loader_identity; + memset(dynamic_view, 0, sizeof(*dynamic_view)); + dynamic_view->status = KZT_GUEST_DYNAMIC_COMPLETE; + *dynamic_status = dynamic_view_status; + *dynamic_revision = 5; + *lease = (kzt_guest_registry_source_lease_t) { + .registry = registry, + .link_map_addr = identity->link_map_addr, + .generation = identity->generation, + .namespace_id = identity->namespace_id, + .active = 1, + }; + return 0; +} + int kzt_guest_library_access_lookup( kzt_guest_library_access_t *access, const kzt_guest_library_binding_key_t *key, @@ -300,66 +346,46 @@ kzt_guest_dynsym_lookup_status_t kzt_guest_dynsym_lookup( return result->status; } -int kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence( +uintptr_t kzt_rela_runtime_select_exact_wrapper_bridge_retained( box64context_t *context, const kzt_guest_library_handle_t *retained_provider_handle, const char *symbol_name, kzt_symbol_version_evidence_t version_evidence, - const char *symbol_version, - kzt_wrapper_bridge_provider_t *provider) + const char *symbol_version) { - CHECK("provider context", context != NULL); - CHECK("provider retained binding", + CHECK("selector context", context != NULL); + CHECK("selector retained binding", retained_provider_handle && retained_provider_handle->entry && retained_provider_handle->library == lookup_library); - CHECK("provider symbol", strcmp(symbol_name, "wi963_symbol") == 0); - CHECK("provider version evidence", + CHECK("selector symbol", strcmp(symbol_name, "wi963_symbol") == 0); + CHECK("selector version evidence", version_evidence == expected_dynsym_version_evidence); - CHECK("provider version", - expected_dynsym_version && symbol_version && - strcmp(symbol_version, expected_dynsym_version) == 0); - ++provider_discover_count; - memset(provider, 0, sizeof(*provider)); - provider->manifest.available = provider_discover_result > 0; - return provider_discover_result; -} - -int kzt_wrapper_probe_minimal_manifest( - const kzt_wrapper_probe_manifest_t *manifest, - const kzt_wrapper_probe_request_t *request, - const kzt_wrapper_probe_bridge_ops_t *bridge_ops, - kzt_wrapper_probe_result_t *result) -{ - CHECK("probe manifest", manifest && manifest->available); - CHECK("probe request", request != NULL); - CHECK("probe symbol", strcmp(request->symbol_name, "wi963_symbol") == 0); - CHECK("probe version evidence", - request->symbol_version_evidence == expected_dynsym_version_evidence); - CHECK("probe version", - expected_dynsym_version && request->symbol_version && - strcmp(request->symbol_version, expected_dynsym_version) == 0); - CHECK("probe bridge ops", bridge_ops != NULL); - ++wrapper_probe_count; - memset(result, 0, sizeof(*result)); - result->wrapper_match = wrapper_probe_match; - result->wrapper_version_evidence = wrapper_probe_version_evidence; - result->wrapper_symbol_version = wrapper_probe_version; - result->bridge_target = wrapper_probe_bridge_target; - return 0; + CHECK("selector version", + expected_dynsym_version + ? symbol_version && + strcmp(symbol_version, expected_dynsym_version) == 0 + : symbol_version == NULL); + ++exact_selector_count; + return exact_selector_result; } int kzt_guest_library_symbol_evidence_lookup( const kzt_guest_library_handle_t *handle, const char *symbol, unsigned long dynamic_revision, uintptr_t *runtime_address, - unsigned char *symbol_type) + unsigned char *symbol_type, uintptr_t *bridge_target) { CHECK("evidence lookup handle", handle && handle->entry); CHECK("evidence lookup symbol", strcmp(symbol, "wi963_symbol") == 0); ++evidence_lookup_count; + if (bridge_target) *bridge_target = 0; if (!evidence_valid || evidence_dynamic_revision != dynamic_revision) return -1; *runtime_address = evidence_runtime_address; *symbol_type = evidence_symbol_type; + if (bridge_target && bridge_evidence_valid && + bridge_evidence_dynamic_revision == dynamic_revision) { + *bridge_target = bridge_evidence_target; + } return 0; } @@ -377,35 +403,16 @@ void kzt_guest_library_symbol_evidence_store( evidence_symbol_type = symbol_type; } -int GetLibSymbolStartEnd( - library_t *library, const char *name, khint_t pre_k, - uintptr_t *start, uintptr_t *end, int version, - const char *vername, int local) -{ - CHECK("wrapper lookup library", library == lookup_library); - CHECK("wrapper lookup symbol", strcmp(name, "wi963_symbol") == 0); - CHECK("wrapper lookup hash", pre_k == 0); - CHECK("wrapper lookup version", version == -1 && vername == NULL); - CHECK("wrapper lookup local", local == 1); - if (!wrapper_lookup_result) - return 0; - *start = wrapper_result; - *end = wrapper_result + sizeof(void *); - return 1; -} - -int GetLibFunctionSymbolStartEnd( - library_t *library, const char *name, khint_t pre_k, - uintptr_t *start, uintptr_t *end) +void kzt_guest_library_symbol_bridge_store( + const kzt_guest_library_handle_t *handle, const char *symbol, + unsigned long dynamic_revision, uintptr_t bridge_target) { - CHECK("function lookup library", library == lookup_library); - CHECK("function lookup symbol", strcmp(name, "wi963_symbol") == 0); - CHECK("function lookup hash", pre_k == 0); - if (!wrapper_function_lookup_result) - return 0; - *start = wrapper_result; - *end = wrapper_result + sizeof(void *); - return 1; + CHECK("bridge evidence store handle", handle && handle->entry); + CHECK("bridge evidence store symbol", strcmp(symbol, "wi963_symbol") == 0); + ++bridge_evidence_store_count; + bridge_evidence_valid = 1; + bridge_evidence_dynamic_revision = dynamic_revision; + bridge_evidence_target = bridge_target; } int kzt_guest_library_loader_scope_begin( @@ -818,8 +825,6 @@ static void prepare_symbol_selection(library_t *library) lookup_result = 0; lookup_library = library; lookup_object_type = KZT_GUEST_LIBRARY_OBJECT_WRAPPED; - wrapper_lookup_result = 1; - wrapper_function_lookup_result = 1; wrapper_result = 0xf300; dynsym_type = STT_FUNC; dynsym_status = KZT_GUEST_DYNSYM_LOOKUP_FOUND; @@ -839,6 +844,7 @@ static void prepare_symbol_selection(library_t *library) lookup_loader_identity_result = 0; symbol_source_result = 0; symbol_source_acquire_count = 0; + exact_symbol_source_acquire_count = 0; dynamic_view_count = 0; dynsym_lookup_count = 0; evidence_lookup_count = 0; @@ -848,13 +854,12 @@ static void prepare_symbol_selection(library_t *library) expected_dynsym_version_evidence = KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; expected_dynsym_version = NULL; - provider_discover_result = 0; - provider_discover_count = 0; - wrapper_probe_count = 0; - wrapper_probe_match = KZT_PATCH_WRAPPER_NO_MANIFEST; - wrapper_probe_version_evidence = KZT_SYMBOL_VERSION_UNKNOWN; - wrapper_probe_version = NULL; - wrapper_probe_bridge_target = 0; + exact_selector_count = 0; + exact_selector_result = wrapper_result; + bridge_evidence_store_count = 0; + bridge_evidence_valid = 0; + bridge_evidence_dynamic_revision = 0; + bridge_evidence_target = 0; } static void prepare_trusted_source(library_t *library) @@ -961,6 +966,49 @@ static void test_wrapper_source_rejects_untrusted_same_basename(void) CHECK("trusted-prefix proof remains inactive", !proof.lease.active); } +static void test_new_world_libdl_uses_exact_libc_wrapper_alias(void) +{ + box64context_t context = { 0 }; + kzt_guest_wrapper_source_proof_t proof = { 0 }; + const char *wrapper_name; + + reset_counters(); + wrapper_name = kzt_guest_library_wrapper_name_for_guest("libdl.so.2"); + CHECK("libdl alias selected", wrapper_name && + strcmp(wrapper_name, "libc.so.6") == 0); + CHECK("libdl dlsym alias allowed", + kzt_guest_library_wrapper_alias_symbol_allowed("dlsym")); + CHECK("libdl dlvsym alias allowed", + kzt_guest_library_wrapper_alias_symbol_allowed("dlvsym")); + CHECK("libdl dlopen alias rejected", + !kzt_guest_library_wrapper_alias_symbol_allowed("dlopen")); + CHECK("libdl unrelated alias rejected", + !kzt_guest_library_wrapper_alias_symbol_allowed("uname")); + + prepare_symbol_selection(NULL); + resolved_match.path_status = KZT_GUEST_FIELD_OK; + strcpy(resolved_match.path, "/lib/x86_64-linux-gnu/libdl.so.2"); + resolved_match.soname_status = KZT_GUEST_FIELD_OK; + strcpy(resolved_match.soname, "libdl.so.2"); + CHECK("libdl alias source accepted", + kzt_guest_library_wrapper_source_acquire( + &context, resolved_match.link_map_addr, + "/lib/x86_64-linux-gnu/libdl.so.2", wrapper_name, + &proof) == 0); + kzt_guest_library_wrapper_source_release(&proof); + + prepare_symbol_selection(NULL); + resolved_match.path_status = KZT_GUEST_FIELD_OK; + strcpy(resolved_match.path, "/lib/x86_64-linux-gnu/libm.so.6"); + resolved_match.soname_status = KZT_GUEST_FIELD_OK; + strcpy(resolved_match.soname, "libm.so.6"); + CHECK("unapproved alias rejected", + kzt_guest_library_wrapper_source_acquire( + &context, resolved_match.link_map_addr, + "/lib/x86_64-linux-gnu/libm.so.6", "libc.so.6", + &proof) != 0); +} + static void test_symbol_selection_requires_exact_wrapped_owner(void) { box64context_t context = { 0 }; @@ -997,11 +1045,28 @@ static void test_symbol_selection_requires_exact_wrapped_owner(void) } CHECK("steady cached selector reads dynsym once", dynsym_lookup_count == 1 && evidence_lookup_count == 1002 && - evidence_store_count == 1 && resolve_count == 0); + evidence_store_count == 1 && exact_selector_count == 1 && + bridge_evidence_store_count == 1 && resolve_count == 0); + { + const kzt_guest_loader_identity_t queried_identity = { + .handle = lookup_guest_handle, + .link_map_addr = lookup_loader_identity.link_map_addr, + .namespace_id = lookup_loader_identity.namespace_id, + }; + + selected = kzt_guest_library_select_symbol_result_with_identity( + &context, lookup_guest_handle, &queried_identity, + resolved_guest_address, "wi963_symbol", NULL); + CHECK("dlinfo exact wrapped bridge selected", + selected == wrapper_result); + CHECK("dlinfo exact source avoids stored handle assumption", + exact_symbol_source_acquire_count == 1 && + symbol_source_acquire_count == 1002); + } reset_counters(); prepare_symbol_selection(&library); - wrapper_function_lookup_result = 0; + exact_selector_result = 0; selected = kzt_guest_library_select_symbol_result( &context, lookup_guest_handle, resolved_guest_address, "wi963_symbol", NULL); @@ -1117,8 +1182,7 @@ static void test_symbol_selection_requires_exact_wrapped_owner(void) reset_counters(); prepare_symbol_selection(&library); - wrapper_lookup_result = 0; - wrapper_function_lookup_result = 0; + exact_selector_result = 0; selected = kzt_guest_library_select_symbol_result( &context, lookup_guest_handle, resolved_guest_address, "wi963_symbol", NULL); @@ -1137,11 +1201,6 @@ static void test_symbol_selection_requires_exact_wrapped_owner(void) prepare_symbol_selection(&library); expected_dynsym_version_evidence = KZT_SYMBOL_VERSION_VERSIONED; expected_dynsym_version = "WI982_1.0"; - provider_discover_result = 1; - wrapper_probe_match = KZT_PATCH_WRAPPER_VERSION_MATCH; - wrapper_probe_version_evidence = KZT_SYMBOL_VERSION_VERSIONED; - wrapper_probe_version = expected_dynsym_version; - wrapper_probe_bridge_target = wrapper_result; selected = kzt_guest_library_select_symbol_result( &context, lookup_guest_handle, resolved_guest_address, "wi963_symbol", "WI982_1.0"); @@ -1149,8 +1208,8 @@ static void test_symbol_selection_requires_exact_wrapped_owner(void) selected == wrapper_result); CHECK("versioned selection proves exact owner", symbol_source_acquire_count == 1 && lookup_count == 1 && - dynsym_lookup_count == 1 && provider_discover_count == 1 && - wrapper_probe_count == 1 && release_count == 1); + dynsym_lookup_count == 1 && exact_selector_count == 1 && + release_count == 1); CHECK("versioned selection bypasses unversioned evidence cache", evidence_lookup_count == 0 && evidence_store_count == 0); @@ -1158,33 +1217,27 @@ static void test_symbol_selection_requires_exact_wrapped_owner(void) prepare_symbol_selection(&library); expected_dynsym_version_evidence = KZT_SYMBOL_VERSION_VERSIONED; expected_dynsym_version = "WI982_1.0"; - provider_discover_result = 1; - wrapper_probe_match = KZT_PATCH_WRAPPER_VERSION_MATCH; - wrapper_probe_version_evidence = KZT_SYMBOL_VERSION_VERSIONED; - wrapper_probe_version = "WI982_2.0"; - wrapper_probe_bridge_target = wrapper_result; + exact_selector_result = 0; selected = kzt_guest_library_select_symbol_result( &context, lookup_guest_handle, resolved_guest_address, "wi963_symbol", "WI982_1.0"); CHECK("wrapper version mismatch keeps guest", selected == resolved_guest_address); CHECK("wrapper version mismatch releases exact binding", - provider_discover_count == 1 && wrapper_probe_count == 1 && - release_count == 1); + exact_selector_count == 1 && release_count == 1); reset_counters(); prepare_symbol_selection(&library); expected_dynsym_version_evidence = KZT_SYMBOL_VERSION_VERSIONED; expected_dynsym_version = "WI982_1.0"; - provider_discover_result = 0; + exact_selector_result = 0; selected = kzt_guest_library_select_symbol_result( &context, lookup_guest_handle, resolved_guest_address, "wi963_symbol", "WI982_1.0"); CHECK("missing versioned wrapper evidence keeps guest", selected == resolved_guest_address); CHECK("missing versioned wrapper evidence releases exact binding", - provider_discover_count == 1 && wrapper_probe_count == 0 && - release_count == 1); + exact_selector_count == 1 && release_count == 1); reset_counters(); prepare_symbol_selection(&library); @@ -1209,6 +1262,7 @@ int main(void) test_symbol_selection_requires_exact_wrapped_owner(); test_wrapper_source_rejects_untrusted_same_basename(); test_wrapper_source_accepts_only_exact_trusted_identity(); + test_new_world_libdl_uses_exact_libc_wrapper_alias(); puts("kzt-guest-library-adapter: PASS"); return EXIT_SUCCESS; } diff --git a/tests/unit/kzt/test_guest_library_binding.c b/tests/unit/kzt/test_guest_library_binding.c index 68076877613..d258b315909 100644 --- a/tests/unit/kzt/test_guest_library_binding.c +++ b/tests/unit/kzt/test_guest_library_binding.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "kzt_guest_library_binding.h" @@ -186,7 +187,14 @@ static void test_reverse_lookup_unique_main_binding(void) CHECK("reverse unique handle", handle.library == (library_t *)&lib && handle.object_type == KZT_GUEST_LIBRARY_OBJECT_WRAPPED); + CHECK("reverse unique retained key", + kzt_guest_library_handle_matches_key(&handle, &expected)); + expected.generation++; + CHECK("reverse unique rejects different key", + !kzt_guest_library_handle_matches_key(&handle, &expected)); kzt_guest_library_handle_release(&handle); + CHECK("reverse unique released handle rejects key", + !kzt_guest_library_handle_matches_key(&handle, &found)); kzt_guest_library_access_destroy(&access); } @@ -2426,6 +2434,7 @@ static void test_symbol_evidence_cache_is_generation_local(void) kzt_guest_library_binding_key_t second = key(0x17000, 91); kzt_guest_library_handle_t handle = { 0 }; uintptr_t address = 0; + uintptr_t bridge = 0; unsigned char type = 0; CHECK("symbol cache access init", @@ -2442,15 +2451,34 @@ static void test_symbol_evidence_cache_is_generation_local(void) &handle, "cached_function", 1, 0x17100, 2); CHECK("symbol cache first hit", kzt_guest_library_symbol_evidence_lookup( - &handle, "cached_function", 1, &address, &type) == 0 && - address == 0x17100 && type == 2); + &handle, "cached_function", 1, &address, &type, &bridge) == 0 && + address == 0x17100 && type == 2 && !bridge); + CHECK("bridge cache misses before exact selection", + !bridge); + kzt_guest_library_symbol_bridge_store( + &handle, "cached_function", 1, 0x17200); + CHECK("bridge cache first hit", + kzt_guest_library_symbol_evidence_lookup( + &handle, "cached_function", 1, &address, &type, &bridge) == 0 && + bridge == 0x17200); CHECK("symbol cache dynamic revision misses", kzt_guest_library_symbol_evidence_lookup( - &handle, "cached_function", 2, &address, &type) != 0); + &handle, "cached_function", 2, &address, &type, &bridge) != 0 && + !bridge); CHECK("symbol cache first hit remains revision local", kzt_guest_library_symbol_evidence_lookup( - &handle, "cached_function", 1, &address, &type) == 0 && - address == 0x17100 && type == 2); + &handle, "cached_function", 1, &address, &type, &bridge) == 0 && + address == 0x17100 && type == 2 && bridge == 0x17200); + CHECK("bridge cache dynamic revision misses", + kzt_guest_library_symbol_evidence_lookup( + &handle, "cached_function", 2, &address, &type, &bridge) != 0 && + !bridge); + kzt_guest_library_symbol_evidence_store( + &handle, "cached_function", 2, 0x17300, 2); + CHECK("new symbol evidence invalidates old bridge", + kzt_guest_library_symbol_evidence_lookup( + &handle, "cached_function", 1, &address, &type, &bridge) != 0 && + !bridge); kzt_guest_library_handle_release(&handle); kzt_guest_library_inactivate( @@ -2465,18 +2493,227 @@ static void test_symbol_evidence_cache_is_generation_local(void) &access, &second, &handle) == 0); CHECK("symbol cache old generation misses", kzt_guest_library_symbol_evidence_lookup( - &handle, "cached_function", 1, &address, &type) != 0); + &handle, "cached_function", 1, &address, &type, &bridge) != 0); + CHECK("bridge cache old generation misses", + !bridge); + kzt_guest_library_handle_release(&handle); + kzt_guest_library_access_destroy(&access); +} + +static void test_symbol_bridge_cache_does_not_cross_symbol_aliases(void) +{ + fake_library_t lib = { 90 }; + kzt_guest_library_access_t access; + kzt_guest_library_binding_key_t binding_key = key(0x18000, 92); + kzt_guest_library_handle_t handle = { 0 }; + uintptr_t address = 0; + uintptr_t bridge = 0; + unsigned char type = 0; + char symbol[32]; + + CHECK("alias cache access init", + kzt_guest_library_access_init(&access) == 0); + CHECK("alias cache track", kzt_guest_library_track( + access.bindings, (library_t *)&lib) == 0); + CHECK("alias cache bind", kzt_guest_library_bind( + access.bindings, &binding_key, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("alias cache lookup", kzt_guest_library_access_lookup( + &access, &binding_key, &handle) == 0); + kzt_guest_library_symbol_evidence_store( + &handle, "original_alias", 1, 0x18100, 2); + kzt_guest_library_symbol_bridge_store( + &handle, "original_alias", 1, 0x18200); + for (size_t i = 0; i < 16; ++i) { + snprintf(symbol, sizeof(symbol), "replacement_alias_%zu", i); + kzt_guest_library_symbol_evidence_store( + &handle, symbol, 1, 0x18100, 2); + } + CHECK("evicted alias cannot inherit old bridge", + kzt_guest_library_symbol_evidence_lookup( + &handle, "replacement_alias_15", 1, &address, &type, + &bridge) == 0 && + address == 0x18100 && type == 2 && !bridge); + kzt_guest_library_handle_release(&handle); + kzt_guest_library_access_destroy(&access); +} + +static void test_symbol_bridge_cache_rejects_unloading_handle(void) +{ + fake_library_t lib = { 91 }; + kzt_guest_library_access_t access; + kzt_guest_library_binding_key_t binding_key = key(0x19000, 93); + kzt_guest_library_handle_t held = { 0 }, probe = { 0 }; + uintptr_t address = 1; + uintptr_t bridge = 1; + unsigned char type = 1; + pthread_t thread; + unbind_thread_arg_t arg = { + .library = (library_t *)&lib, + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + + CHECK("unload cache access init", + kzt_guest_library_access_init(&access) == 0); + arg.bindings = access.bindings; + CHECK("unload cache track", kzt_guest_library_track( + access.bindings, (library_t *)&lib) == 0); + CHECK("unload cache bind", kzt_guest_library_bind( + access.bindings, &binding_key, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("unload cache lookup", kzt_guest_library_access_lookup( + &access, &binding_key, &held) == 0); + kzt_guest_library_symbol_evidence_store( + &held, "unloading_symbol", 1, 0x19100, 2); + kzt_guest_library_symbol_bridge_store( + &held, "unloading_symbol", 1, 0x19200); + CHECK("unload cache thread", pthread_create( + &thread, NULL, unbind_thread, &arg) == 0); + pthread_mutex_lock(&arg.lock); + while (!arg.started) pthread_cond_wait(&arg.cond, &arg.lock); + pthread_mutex_unlock(&arg.lock); + while (kzt_guest_library_lookup( + access.bindings, &binding_key, &probe) == 0) { + kzt_guest_library_handle_release(&probe); + } + CHECK("unloading handle rejects cached evidence", + kzt_guest_library_symbol_evidence_lookup( + &held, "unloading_symbol", 1, &address, &type, + &bridge) != 0 && + !address && !type && !bridge); + pthread_mutex_lock(&arg.lock); + CHECK("unload cache waits for held handle", !arg.done); + pthread_mutex_unlock(&arg.lock); + kzt_guest_library_handle_release(&held); + pthread_join(thread, NULL); + pthread_cond_destroy(&arg.cond); + pthread_mutex_destroy(&arg.lock); + kzt_guest_library_access_destroy(&access); +} + +#define SYMBOL_CACHE_BENCHMARK_ROUNDS 21 +#define SYMBOL_CACHE_BENCHMARK_REPEATS 100000 + +static volatile uintptr_t symbol_cache_benchmark_sink; + +static uint64_t symbol_cache_benchmark_now(void) +{ + struct timespec now; + + clock_gettime(CLOCK_MONOTONIC, &now); + return (uint64_t)now.tv_sec * UINT64_C(1000000000) + now.tv_nsec; +} + +static int compare_u64(const void *left, const void *right) +{ + uint64_t a = *(const uint64_t *)left; + uint64_t b = *(const uint64_t *)right; + + return a < b ? -1 : a > b; +} + +static uint64_t benchmark_symbol_evidence_lookup( + const kzt_guest_library_handle_t *handle, int include_bridge) +{ + uintptr_t address = 0; + uintptr_t bridge = 0; + unsigned char type = 0; + uint64_t start = symbol_cache_benchmark_now(); + + for (size_t i = 0; i < SYMBOL_CACHE_BENCHMARK_REPEATS; ++i) { + if (kzt_guest_library_symbol_evidence_lookup( + handle, "benchmark_symbol", 1, &address, &type, + include_bridge ? &bridge : NULL) != 0) { + return 0; + } + symbol_cache_benchmark_sink ^= address ^ bridge ^ type; + } + return symbol_cache_benchmark_now() - start; +} + +static void test_symbol_bridge_cache_performance(void) +{ + fake_library_t lib = { 92 }; + kzt_guest_library_access_t access; + kzt_guest_library_binding_key_t binding_key = key(0x1a000, 94); + kzt_guest_library_handle_t handle = { 0 }; + uint64_t evidence_only[SYMBOL_CACHE_BENCHMARK_ROUNDS]; + uint64_t evidence_with_bridge[SYMBOL_CACHE_BENCHMARK_ROUNDS]; + uint64_t baseline; + uint64_t candidate; + uint64_t limit; + + CHECK("benchmark cache access init", + kzt_guest_library_access_init(&access) == 0); + CHECK("benchmark cache track", kzt_guest_library_track( + access.bindings, (library_t *)&lib) == 0); + CHECK("benchmark cache bind", kzt_guest_library_bind( + access.bindings, &binding_key, (library_t *)&lib, + KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == + KZT_GUEST_LIBRARY_BINDING_ADDED); + CHECK("benchmark cache lookup", kzt_guest_library_access_lookup( + &access, &binding_key, &handle) == 0); + kzt_guest_library_symbol_evidence_store( + &handle, "benchmark_symbol", 1, 0x1a100, 2); + kzt_guest_library_symbol_bridge_store( + &handle, "benchmark_symbol", 1, 0x1a200); + for (size_t i = 0; i < SYMBOL_CACHE_BENCHMARK_ROUNDS; ++i) { + if (i & 1) { + evidence_with_bridge[i] = + benchmark_symbol_evidence_lookup(&handle, 1); + evidence_only[i] = + benchmark_symbol_evidence_lookup(&handle, 0); + } else { + evidence_only[i] = + benchmark_symbol_evidence_lookup(&handle, 0); + evidence_with_bridge[i] = + benchmark_symbol_evidence_lookup(&handle, 1); + } + CHECK("benchmark cache sample", evidence_only[i] && + evidence_with_bridge[i]); + } + qsort(evidence_only, SYMBOL_CACHE_BENCHMARK_ROUNDS, + sizeof(evidence_only[0]), compare_u64); + qsort(evidence_with_bridge, SYMBOL_CACHE_BENCHMARK_ROUNDS, + sizeof(evidence_with_bridge[0]), compare_u64); + baseline = evidence_only[SYMBOL_CACHE_BENCHMARK_ROUNDS / 2]; + candidate = evidence_with_bridge[SYMBOL_CACHE_BENCHMARK_ROUNDS / 2]; + limit = baseline + baseline / 10 + + SYMBOL_CACHE_BENCHMARK_REPEATS * UINT64_C(5); + printf("symbol-bridge-cache-performance baseline_total_ns=%llu " + "candidate_total_ns=%llu limit_total_ns=%llu " + "candidate_ns_op=%llu result=%s\n", + (unsigned long long)baseline, + (unsigned long long)candidate, + (unsigned long long)limit, + (unsigned long long)(candidate / + SYMBOL_CACHE_BENCHMARK_REPEATS), + candidate <= limit ? "PASS" : "FAIL"); + CHECK("symbol bridge cache performance", candidate <= limit); kzt_guest_library_handle_release(&handle); kzt_guest_library_access_destroy(&access); } -int main(void) +int main(int argc, char **argv) { + if (argc == 2 && strcmp(argv[1], "--benchmark") == 0) { + test_symbol_bridge_cache_performance(); + return failures ? EXIT_FAILURE : EXIT_SUCCESS; + } + if (argc != 1) { + fprintf(stderr, "usage: %s [--benchmark]\n", argv[0]); + return 2; + } test_reverse_lookup_unique_main_binding(); test_reverse_lookup_zero_match_clears_outputs(); test_wrapped_producer_cannot_own_two_live_identities(); test_reverse_lookup_non_main_binding_is_rejected(); test_symbol_evidence_cache_is_generation_local(); + test_symbol_bridge_cache_does_not_cross_symbol_aliases(); + test_symbol_bridge_cache_rejects_unloading_handle(); test_init_failure_is_fail_open(); test_loader_quiescence_lease_acquire_release(); test_loader_quiescence_writer_token_is_stable(); diff --git a/tests/unit/kzt/test_guest_registry.c b/tests/unit/kzt/test_guest_registry.c index 10d469b58fe..7f85197c543 100644 --- a/tests/unit/kzt/test_guest_registry.c +++ b/tests/unit/kzt/test_guest_registry.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -1493,12 +1494,49 @@ static void test_loader_symbol_source_is_exact_main_generation(void) check_true("symbol-source.lease", lease.active); kzt_guest_registry_source_lease_release(&lease); + source = (kzt_guest_loader_identity_t) { 0 }; + { + const kzt_guest_loader_identity_t queried = { + .handle = 0x91f0, + .link_map_addr = main_object.link_map_addr, + .namespace_id = 0, + }; + + check_int("symbol-source.exact-dlinfo-acquire", + kzt_guest_registry_loader_symbol_source_acquire_exact( + registry, &queried, &source, &found_view, + &dynamic_status, &dynamic_revision, &lease), + 0); + check_uintptr("symbol-source.exact-handle", source.handle, + queried.handle); + check_uintptr("symbol-source.exact-link-map", source.link_map_addr, + queried.link_map_addr); + check_ulong("symbol-source.exact-generation", source.generation, + published.generation); + check_true("symbol-source.exact-lease", lease.active); + kzt_guest_registry_source_lease_release(&lease); + } + { + const kzt_guest_loader_identity_t wrong_namespace = { + .handle = 0x91f0, + .link_map_addr = main_object.link_map_addr, + .namespace_id = 7, + }; + + check_not_int("symbol-source.exact-non-main-rejected", + kzt_guest_registry_loader_symbol_source_acquire_exact( + registry, &wrong_namespace, &source, &found_view, + &dynamic_status, &dynamic_revision, &lease), + 0); + check_true("symbol-source.exact-non-main-no-lease", !lease.active); + } + view.strsz.present = 1; view.strsz.value = 0x80; view.strsz.address_semantics = KZT_GUEST_DYNAMIC_SCALAR; check_int("symbol-source.dynamic-main-update", kzt_guest_registry_commit_dynamic_view( - registry, main_object.link_map_addr, source.generation, + registry, main_object.link_map_addr, published.generation, &view), KZT_GUEST_REGISTRY_UPDATED); check_int("symbol-source.acquire-main-update", @@ -1541,6 +1579,110 @@ static void test_loader_symbol_source_is_exact_main_generation(void) &dynamic_revision, &lease), 0); check_true("symbol-source.retired-no-lease", !lease.active); + { + const kzt_guest_loader_identity_t retired = { + .handle = 0x91f0, + .link_map_addr = main_object.link_map_addr, + .namespace_id = 0, + }; + + check_not_int("symbol-source.exact-retired-rejected", + kzt_guest_registry_loader_symbol_source_acquire_exact( + registry, &retired, &source, &found_view, + &dynamic_status, &dynamic_revision, &lease), + 0); + check_true("symbol-source.exact-retired-no-lease", !lease.active); + } + kzt_guest_registry_destroy(®istry); +} + +static void test_source_lease_counter_overflow_is_rejected(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t object = make_observation(0x74a0); + kzt_guest_loader_identity_t published = { 0 }; + kzt_guest_loader_identity_t identity = { 0 }; + const kzt_guest_loader_identity_t queried = { + .handle = 0x91a0, + .link_map_addr = 0x74a0, + .namespace_id = 0, + }; + kzt_guest_dynamic_view_t view = { + .dynamic_addr = 0x101000, + .load_bias = 0x100000, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .has_null = 1, + }; + kzt_guest_dynamic_view_t found_view = { 0 }; + kzt_guest_field_status_t dynamic_status = KZT_GUEST_FIELD_UNKNOWN; + unsigned long dynamic_revision = 0; + kzt_guest_registry_source_lease_t anchor = { 0 }; + kzt_guest_registry_source_lease_t rejected = { 0 }; + kzt_guest_registry_patch_decision_lease_t decision = { 0 }; + kzt_guest_registry_symbol_candidate_t candidate = { 0 }; + size_t cursor = 0; + + check_true("lease-overflow.registry", registry != NULL); + if (!registry) return; + check_int("lease-overflow.observe", + kzt_guest_registry_observe(registry, &object), + KZT_GUEST_REGISTRY_ADDED); + check_int("lease-overflow.publish-handle", + kzt_guest_registry_publish_loader_identity( + registry, queried.handle, object.link_map_addr, 0, + &published), + 0); + check_int("lease-overflow.dynamic", + kzt_guest_registry_commit_dynamic_view( + registry, object.link_map_addr, published.generation, + &view), + KZT_GUEST_REGISTRY_UPDATED); + check_int("lease-overflow.anchor", + kzt_guest_registry_source_lease_acquire( + registry, object.link_map_addr, published.generation, 0, + &anchor), + 0); + check_int("lease-overflow.decision", + kzt_guest_registry_patch_decision_lease_acquire( + &anchor, &decision), + 0); + check_int("lease-overflow.force-max", + kzt_guest_registry_test_set_active_source_leases( + registry, object.link_map_addr, published.generation, + ULONG_MAX), + 0); + + check_not_int("lease-overflow.direct-rejected", + kzt_guest_registry_source_lease_acquire( + registry, object.link_map_addr, published.generation, + 0, &rejected), + 0); + check_true("lease-overflow.direct-cleared", !rejected.active); + check_not_int("lease-overflow.handle-rejected", + kzt_guest_registry_loader_symbol_source_acquire( + registry, queried.handle, &identity, &found_view, + &dynamic_status, &dynamic_revision, &rejected), + 0); + check_true("lease-overflow.handle-cleared", !rejected.active); + check_not_int("lease-overflow.exact-rejected", + kzt_guest_registry_loader_symbol_source_acquire_exact( + registry, &queried, &identity, &found_view, + &dynamic_status, &dynamic_revision, &rejected), + 0); + check_true("lease-overflow.exact-cleared", !rejected.active); + check_not_int("lease-overflow.candidate-rejected", + kzt_guest_registry_symbol_candidate_acquire_next( + &decision, &cursor, &candidate), + 1); + check_true("lease-overflow.candidate-cleared", + !candidate.lease.active); + + check_int("lease-overflow.restore-anchor", + kzt_guest_registry_test_set_active_source_leases( + registry, object.link_map_addr, published.generation, 1), + 0); + kzt_guest_registry_patch_decision_lease_release(&decision); + kzt_guest_registry_source_lease_release(&anchor); kzt_guest_registry_destroy(®istry); } @@ -1737,6 +1879,97 @@ static void test_address_match_overlong_path_is_unknown(void) kzt_guest_registry_destroy(®istry); } +static void test_symbol_candidate_iteration_is_leased_and_compact(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t source = make_observation(0x7700); + kzt_guest_object_observation_t owner = make_observation(0x7710); + kzt_guest_dynamic_view_t view = { + .dynamic_addr = 0x201000, + .load_bias = 0x200000, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .has_null = 1, + }; + kzt_guest_object_snapshot_t *snapshot; + kzt_guest_registry_source_lease_t source_lease = { 0 }; + kzt_guest_registry_patch_decision_lease_t decision_lease = { 0 }; + kzt_guest_registry_symbol_candidate_t candidate = { 0 }; + unsigned long source_generation; + unsigned long owner_generation; + size_t cursor = 0; + int count = 0; + + owner.load_bias.value += 0x100000; + owner.dynamic_addr.value += 0x100000; + source.namespace_id.status = KZT_GUEST_FIELD_OK; + owner.namespace_id.status = KZT_GUEST_FIELD_OK; + owner.path.value = "/lib/x86_64-linux-gnu/libdl.so.2"; + owner.soname.value = "libdl.so.2"; + owner.soname.status = KZT_GUEST_FIELD_OK; + check_true("symbol-candidate.registry", registry != NULL); + if (!registry) return; + check_int("symbol-candidate.observe-source", + kzt_guest_registry_observe(registry, &source), + KZT_GUEST_REGISTRY_ADDED); + check_int("symbol-candidate.observe-owner", + kzt_guest_registry_observe(registry, &owner), + KZT_GUEST_REGISTRY_ADDED); + snapshot = find_snapshot(registry, source.link_map_addr); + source_generation = snapshot->generation; + kzt_guest_object_snapshot_free(snapshot); + snapshot = find_snapshot(registry, owner.link_map_addr); + owner_generation = snapshot->generation; + kzt_guest_object_snapshot_free(snapshot); + check_int("symbol-candidate.dynamic-source", + kzt_guest_registry_commit_dynamic_view( + registry, source.link_map_addr, source_generation, &view), + KZT_GUEST_REGISTRY_UPDATED); + view.dynamic_addr = owner.dynamic_addr.value; + view.load_bias = owner.load_bias.value; + check_int("symbol-candidate.dynamic-owner", + kzt_guest_registry_commit_dynamic_view( + registry, owner.link_map_addr, owner_generation, &view), + KZT_GUEST_REGISTRY_UPDATED); + check_int("symbol-candidate.source-lease", + kzt_guest_registry_source_lease_acquire( + registry, source.link_map_addr, source_generation, 0, + &source_lease), + 0); + check_not_int("symbol-candidate.requires-decision", + kzt_guest_registry_symbol_candidate_acquire_next( + &decision_lease, &cursor, &candidate), + 1); + check_int("symbol-candidate.decision-lease", + kzt_guest_registry_patch_decision_lease_acquire( + &source_lease, &decision_lease), + 0); + kzt_guest_registry_test_set_alloc_failure_after(0); + while (kzt_guest_registry_symbol_candidate_acquire_next( + &decision_lease, &cursor, &candidate) == 1) { + check_true("symbol-candidate.lease-active", candidate.lease.active); + check_true("symbol-candidate.exact-identity", + candidate.link_map_addr == candidate.lease.link_map_addr && + candidate.generation == candidate.lease.generation && + candidate.namespace_id == candidate.lease.namespace_id); + if (candidate.link_map_addr == owner.link_map_addr) { + check_string("symbol-candidate.path", candidate.path, + owner.path.value); + check_string("symbol-candidate.soname", candidate.soname, + owner.soname.value); + check_ulong("symbol-candidate.owner-generation", + candidate.generation, owner_generation); + } + ++count; + kzt_guest_registry_symbol_candidate_release(&candidate); + } + kzt_guest_registry_test_set_alloc_failure_after(-1); + check_int("symbol-candidate.count", count, 2); + check_true("symbol-candidate.release-zero", !candidate.lease.active); + kzt_guest_registry_patch_decision_lease_release(&decision_lease); + kzt_guest_registry_source_lease_release(&source_lease); + kzt_guest_registry_destroy(®istry); +} + int main(void) { test_first_and_repeat_observation_keep_generation(); @@ -1759,10 +1992,12 @@ int main(void) test_loader_handle_binds_exact_link_map_generation_and_namespace(); test_unproven_unload_is_not_reused_without_resident_proof(); test_loader_symbol_source_is_exact_main_generation(); + test_source_lease_counter_overflow_is_rejected(); test_loader_unload_retires_only_exact_namespace_and_generation(); test_loader_unload_prepares_before_unmap_and_can_cancel(); test_namespace_evidence_supplements_exact_dependency(); test_address_match_overlong_path_is_unknown(); + test_symbol_candidate_iteration_is_leased_and_compact(); if (failures) { fprintf(stderr, "kzt-guest-registry: %d failure(s)\n", failures); diff --git a/tests/unit/kzt/test_lazy_prebind_scope.c b/tests/unit/kzt/test_lazy_prebind_scope.c index 5520355f046..ea84392f02e 100644 --- a/tests/unit/kzt/test_lazy_prebind_scope.c +++ b/tests/unit/kzt/test_lazy_prebind_scope.c @@ -167,6 +167,8 @@ static void test_epoch_invalidates_and_replaces_record(void) KZT_LAZY_PREBIND_CLAIM_CREATED); CHECK("epoch first acquire", kzt_lazy_prebind_scope_acquire( scope, &record, &lease) == 0); + CHECK("epoch claimed record is not published", + !kzt_lazy_prebind_scope_lease_published(&lease)); CHECK("epoch exact provider", lease.record.provider.link_map_addr == 0x2000 && lease.record.provider.generation == 9); kzt_lazy_prebind_scope_release(&lease); @@ -302,6 +304,76 @@ static void test_publication_is_unique_and_mutation_drains(void) pthread_mutex_destroy(&race.lock); } +static void test_loader_invariant_survives_loader_retire_prepare( + const char *symbol) +{ + kzt_lazy_prebind_scope_t *scope = kzt_lazy_prebind_scope_init(); + kzt_lazy_prebind_record_t record = record_for(1, 0x2400); + kzt_lazy_prebind_lease_t publish = { 0 }; + kzt_lazy_prebind_lease_t read = { 0 }; + kzt_lazy_prebind_lease_t revoke = { 0 }; + + record.bridge_custom_wrapper = 1; + record.loader_mutation_invariant = 1; + strcpy(record.symbol, symbol); + CHECK("invariant scope", scope != NULL); + CHECK("invariant claim", kzt_lazy_prebind_scope_claim(scope, &record) == + KZT_LAZY_PREBIND_CLAIM_CREATED); + CHECK("invariant publish", kzt_lazy_prebind_scope_publish_acquire( + scope, &record, &publish) == 0); + kzt_lazy_prebind_scope_publish_finish(&publish, 1); + CHECK("invariant dlerror publication query", + kzt_lazy_prebind_scope_has_native_dlerror( + scope, &record.source) == (strcmp(symbol, "dlerror") == 0)); + CHECK("invariant published lease acquire", + kzt_lazy_prebind_scope_acquire(scope, &record, &read) == 0); + CHECK("invariant published lease state", + kzt_lazy_prebind_scope_lease_published(&read)); + kzt_lazy_prebind_scope_release(&read); + CHECK("invariant mutate", kzt_lazy_prebind_scope_mutate( + scope, KZT_LAZY_PREBIND_MUTATION_DLOPEN) == 2); + CHECK("invariant remains current", kzt_lazy_prebind_scope_acquire( + scope, &record, &read) == 0 && read.record.scope_epoch == 2); + kzt_lazy_prebind_scope_release(&read); + CHECK("invariant not globally revoked", + kzt_lazy_prebind_scope_revoke_acquire( + scope, NULL, &revoke) == 1); + CHECK("invariant retire prepare ignored", kzt_lazy_prebind_scope_retire( + scope, &record.source) == 0); + CHECK("invariant remains after retire prepare", + kzt_lazy_prebind_scope_acquire(scope, &record, &read) == 0); + kzt_lazy_prebind_scope_release(&read); + CHECK("invariant dlerror query survives mutation", + kzt_lazy_prebind_scope_has_native_dlerror( + scope, &record.source) == (strcmp(symbol, "dlerror") == 0)); + CHECK("invariant exact revoke blocked", + kzt_lazy_prebind_scope_revoke_acquire( + scope, &record.source, &revoke) == 1); + kzt_lazy_prebind_scope_destroy(&scope); +} + +static void test_source_dlerror_publication_expires_with_scope(void) +{ + kzt_lazy_prebind_scope_t *scope = kzt_lazy_prebind_scope_init(); + kzt_lazy_prebind_record_t record = record_for(1, 0x2500); + kzt_lazy_prebind_lease_t publish = { 0 }; + + record.bridge_custom_wrapper = 1; + CHECK("source dlerror scope", scope != NULL); + CHECK("source dlerror claim", kzt_lazy_prebind_scope_claim( + scope, &record) == KZT_LAZY_PREBIND_CLAIM_CREATED); + CHECK("source dlerror publish", kzt_lazy_prebind_scope_publish_acquire( + scope, &record, &publish) == 0); + kzt_lazy_prebind_scope_publish_finish(&publish, 1); + CHECK("source dlerror current", kzt_lazy_prebind_scope_has_native_dlerror( + scope, &record.source)); + CHECK("source dlerror mutate", kzt_lazy_prebind_scope_mutate( + scope, KZT_LAZY_PREBIND_MUTATION_DLOPEN) == 2); + CHECK("source dlerror expired", !kzt_lazy_prebind_scope_has_native_dlerror( + scope, &record.source)); + kzt_lazy_prebind_scope_destroy(&scope); +} + int main(void) { test_epoch_invalidates_and_replaces_record(); @@ -309,6 +381,9 @@ int main(void) test_incomplete_proof_fails_open(); test_changed_scope_identity_is_not_reused(); test_publication_is_unique_and_mutation_drains(); + test_source_dlerror_publication_expires_with_scope(); + test_loader_invariant_survives_loader_retire_prepare("dlerror"); + test_loader_invariant_survives_loader_retire_prepare("dlopen"); puts("kzt-lazy-prebind-scope: all tests passed"); return 0; } diff --git a/tests/unit/kzt/test_lifecycle_snapshot_capacity.c b/tests/unit/kzt/test_lifecycle_snapshot_capacity.c index 55208e364e7..77764e914bf 100644 --- a/tests/unit/kzt/test_lifecycle_snapshot_capacity.c +++ b/tests/unit/kzt/test_lifecycle_snapshot_capacity.c @@ -140,22 +140,27 @@ static int cancel_unload( return 0; } -static void finish_unload( +static int finish_unload( const kzt_loader_lifecycle_identity_t *identity, void *opaque) { lifecycle_fixture_t *fixture = opaque; + int status; kzt_guest_loader_identity_t unload = { .link_map_addr = identity->link_map_addr, .generation = identity->generation, .namespace_id = identity->namespace_id, }; - CHECK("finish loader unload", - kzt_guest_registry_finish_loader_unload( - fixture->registry, &unload) == 0); + status = kzt_guest_registry_finish_loader_unload( + fixture->registry, &unload); + CHECK("finish loader unload", status == 0); + if (status != 0) { + return -1; + } fixture->unloaded = *identity; ++fixture->unload_calls; + return 0; } static void link_maps(test_link_map_chain_x64_t *maps, size_t count) diff --git a/tests/unit/kzt/test_loader_event_hook.c b/tests/unit/kzt/test_loader_event_hook.c index e0c7e60ccf3..ee630e48ea9 100644 --- a/tests/unit/kzt/test_loader_event_hook.c +++ b/tests/unit/kzt/test_loader_event_hook.c @@ -4,6 +4,8 @@ #include #include +#include "box64context.h" + #define CHECK(condition) \ do { \ if (!(condition)) { \ @@ -60,12 +62,40 @@ static void test_event_publishes_exact_map_with_monotonic_sequence(void) kzt_loader_event_hook_destroy(&hook); } +static void test_glibc_228_layout_is_exactly_authorized(void) +{ + kzt_loader_event_hook_t hook; + kzt_loader_event_layout_t layout; + + CHECK(kzt_loader_event_hook_lookup_layout( + KZT_LOADER_EVENT_HOOK_GLIBC_2_28_BUILD_ID, &layout) == 0); + CHECK(layout.scope_layout == KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED); + CHECK(layout.debug_state_offset == 0xfb10); + CHECK(layout.r_debug_offset == 0x29160); + CHECK(kzt_loader_event_hook_install( + &hook, KZT_LOADER_EVENT_HOOK_GLIBC_2_28_BUILD_ID, + 0x1000, 3, 1) == 0); + CHECK(kzt_loader_event_hook_scope_layout(&hook) == + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED); + kzt_loader_event_hook_destroy(&hook); + + memset(&layout, 0xa5, sizeof(layout)); + CHECK(kzt_loader_event_hook_lookup_layout("unknown", &layout) != 0); + CHECK(layout.scope_layout == KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED); + CHECK(layout.debug_state_offset == 0); + CHECK(layout.r_debug_offset == 0); +} + typedef struct lifecycle_fixture { kzt_loader_lifecycle_identity_t identities[2]; kzt_loader_lifecycle_identity_t unloaded; int unload_calls; int prepare_calls; int cancel_calls; + int fail_resolve; + int fail_prepare; + int fail_cancel; + int fail_unload; } lifecycle_fixture_t; static int resolve_lifecycle_identity( @@ -75,6 +105,9 @@ static int resolve_lifecycle_identity( { lifecycle_fixture_t *fixture = opaque; + if (fixture->fail_resolve) { + return -1; + } for (size_t i = 0; i < 2; ++i) { if (fixture->identities[i].link_map_addr == link_map_addr) { *identity = fixture->identities[i]; @@ -84,7 +117,7 @@ static int resolve_lifecycle_identity( return -1; } -static void publish_lifecycle_unload( +static int publish_lifecycle_unload( const kzt_loader_lifecycle_identity_t *identity, void *opaque) { @@ -92,6 +125,7 @@ static void publish_lifecycle_unload( fixture->unloaded = *identity; ++fixture->unload_calls; + return fixture->fail_unload ? -1 : 0; } static int prepare_lifecycle_unload( @@ -102,7 +136,7 @@ static int prepare_lifecycle_unload( CHECK(identity != NULL); ++fixture->prepare_calls; - return 0; + return fixture->fail_prepare ? -1 : 0; } static int cancel_lifecycle_unload( @@ -113,7 +147,7 @@ static int cancel_lifecycle_unload( CHECK(identity != NULL); ++fixture->cancel_calls; - return 0; + return fixture->fail_cancel ? -1 : 0; } static void test_delete_consistent_publishes_exact_removed_identity(void) @@ -133,6 +167,7 @@ static void test_delete_consistent_publishes_exact_removed_identity(void) 0x1000, 3, 1) == 0); CHECK(kzt_loader_event_hook_enable_lifecycle( &hook, 0x2000, 0x3000) == 0); + CHECK(!kzt_loader_event_hook_lifecycle_healthy(&hook)); CHECK(kzt_loader_event_hook_publish_lifecycle( &hook, KZT_LOADER_DEBUG_DELETE, before, 2, resolve_lifecycle_identity, @@ -147,12 +182,14 @@ static void test_delete_consistent_publishes_exact_removed_identity(void) before, 2, resolve_lifecycle_identity, prepare_lifecycle_unload, cancel_lifecycle_unload, publish_lifecycle_unload, &fixture) == 0); + CHECK(kzt_loader_event_hook_lifecycle_healthy(&hook)); CHECK(fixture.unload_calls == 0); CHECK(kzt_loader_event_hook_publish_lifecycle( &hook, KZT_LOADER_DEBUG_DELETE, before, 2, resolve_lifecycle_identity, prepare_lifecycle_unload, cancel_lifecycle_unload, publish_lifecycle_unload, &fixture) == 0); + CHECK(!kzt_loader_event_hook_lifecycle_healthy(&hook)); CHECK(kzt_loader_event_hook_publish_lifecycle( &hook, KZT_LOADER_DEBUG_CONSISTENT, after, 1, resolve_lifecycle_identity, @@ -198,6 +235,7 @@ static void test_add_does_not_discard_pending_delete(void) before, 2, resolve_lifecycle_identity, prepare_lifecycle_unload, cancel_lifecycle_unload, publish_lifecycle_unload, &fixture) == 0); + CHECK(!kzt_loader_event_hook_lifecycle_healthy(&hook)); CHECK(kzt_loader_event_hook_publish_lifecycle( &hook, KZT_LOADER_DEBUG_CONSISTENT, after, 1, resolve_lifecycle_identity, @@ -289,7 +327,7 @@ static int cancel_allocation_unload( return 0; } -static void publish_allocation_unload( +static int publish_allocation_unload( const kzt_loader_lifecycle_identity_t *identity, void *opaque) { @@ -297,6 +335,7 @@ static void publish_allocation_unload( CHECK(identity != NULL); ++fixture->unload_calls; + return 0; } static void test_pending_growth_allocation_failure_cancels_all(void) @@ -318,6 +357,12 @@ static void test_pending_growth_allocation_failure_cancels_all(void) 0x1000, 3, 1) == 0); CHECK(kzt_loader_event_hook_enable_lifecycle( &hook, 0x2000, 0x3000) == 0); + CHECK(kzt_loader_event_hook_publish_lifecycle( + &hook, KZT_LOADER_DEBUG_CONSISTENT, + live_maps, 1, resolve_allocation_identity, + prepare_allocation_unload, cancel_allocation_unload, + publish_allocation_unload, &fixture) == 0); + CHECK(kzt_loader_event_hook_lifecycle_healthy(&hook)); kzt_loader_event_hook_test_set_alloc_failure_after(1); CHECK(kzt_loader_event_hook_publish_lifecycle( &hook, KZT_LOADER_DEBUG_DELETE, @@ -330,18 +375,67 @@ static void test_pending_growth_allocation_failure_cancels_all(void) CHECK(fixture.cancel_calls == 65); CHECK(fixture.unload_calls == 0); CHECK(hook.pending_delete_count == 0); + CHECK(!kzt_loader_event_hook_lifecycle_healthy(&hook)); kzt_loader_event_hook_test_set_alloc_failure_after(-1); + CHECK(kzt_loader_event_hook_publish_lifecycle( + &hook, KZT_LOADER_DEBUG_CONSISTENT, + live_maps, 1, resolve_allocation_identity, + prepare_allocation_unload, cancel_allocation_unload, + publish_allocation_unload, &fixture) == 0); + CHECK(!kzt_loader_event_hook_lifecycle_healthy(&hook)); CHECK(kzt_loader_event_hook_destroy(&hook) == 0); } +static void make_context_lifecycle_healthy(box64context_t *context) +{ + lifecycle_fixture_t fixture = { 0 }; + + kzt_loader_event_hook_context_init(&context->kzt_loader_event_hook); + CHECK(kzt_loader_event_hook_install( + &context->kzt_loader_event_hook, + KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID, + 0x1000, 3, 1) == 0); + CHECK(kzt_loader_event_hook_enable_lifecycle( + &context->kzt_loader_event_hook, 0x2000, 0x3000) == 0); + CHECK(kzt_loader_event_hook_publish_lifecycle( + &context->kzt_loader_event_hook, + KZT_LOADER_DEBUG_CONSISTENT, NULL, 0, + resolve_lifecycle_identity, prepare_lifecycle_unload, + cancel_lifecycle_unload, publish_lifecycle_unload, + &fixture) == 0); +} + +static void test_context_owned_lifecycle_isolation_and_destroy(void) +{ + box64context_t first = { 0 }; + box64context_t second = { 0 }; + + CHECK(!kzt_loader_lifecycle_runtime_healthy(NULL)); + make_context_lifecycle_healthy(&first); + CHECK(kzt_loader_lifecycle_runtime_healthy(&first)); + CHECK(!kzt_loader_lifecycle_runtime_healthy(&second)); + + make_context_lifecycle_healthy(&second); + CHECK(kzt_loader_lifecycle_runtime_healthy(&first)); + CHECK(kzt_loader_lifecycle_runtime_healthy(&second)); + + kzt_loader_event_hook_context_destroy(&first.kzt_loader_event_hook); + CHECK(!kzt_loader_lifecycle_runtime_healthy(&first)); + CHECK(kzt_loader_lifecycle_runtime_healthy(&second)); + kzt_loader_event_hook_context_destroy(&second.kzt_loader_event_hook); + CHECK(!kzt_loader_lifecycle_runtime_healthy(&second)); +} + int main(void) { test_version_and_pattern_fail_open(); test_event_publishes_exact_map_with_monotonic_sequence(); + test_glibc_228_layout_is_exactly_authorized(); test_delete_consistent_publishes_exact_removed_identity(); test_add_does_not_discard_pending_delete(); test_same_address_new_identity_retires_old_generation(); test_pending_growth_allocation_failure_cancels_all(); + test_context_owned_lifecycle_isolation_and_destroy(); puts("kzt-loader-event-hook: all tests passed"); return 0; } diff --git a/tests/unit/kzt/test_observation_adapter.c b/tests/unit/kzt/test_observation_adapter.c index 91e5f90b747..29747d4bc12 100644 --- a/tests/unit/kzt/test_observation_adapter.c +++ b/tests/unit/kzt/test_observation_adapter.c @@ -58,6 +58,12 @@ typedef struct observation_trace { int dynamic_reader_calls_at_legacy; } observation_trace_t; +typedef struct per_object_trace { + int calls; + int return_value; + uintptr_t link_map_addr; +} per_object_trace_t; + typedef struct fake_callback_event { struct link_map_x64 link_map; char guest_name[64]; @@ -243,6 +249,15 @@ static int fake_legacy_flow(uintptr_t link_map_addr, void *opaque) return trace->legacy_return; } +static int fake_per_object_flow(uintptr_t link_map_addr, void *opaque) +{ + per_object_trace_t *trace = opaque; + + ++trace->calls; + trace->link_map_addr = link_map_addr; + return trace->return_value; +} + static void fake_diagnostic( const kzt_observation_adapter_diagnostic_t *diagnostic, void *opaque) @@ -2528,6 +2543,61 @@ static void test_disabled_adapter_diagnostics_are_throttled(void) kzt_guest_registry_destroy(®istry); } +static void test_per_object_failure_is_propagated(void) +{ + fake_callback_event_t event; + per_object_trace_t per_object = { + .return_value = -1, + }; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 4, + }; + + check_true("per-object.registry-init", registry != NULL); + if (!registry) { + return; + } + init_fake_callback_event(&event, "/guest/libper-object-failure.so", + 0x730000); + check_int("per-object.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), 0); + + kzt_observation_adapter_request_t request = { + .enabled = 1, + .diagnostics_enabled = 1, + .link_map_addr = (uintptr_t)&event.link_map, + .registry = registry, + .reader_ops = &event.ops, + .namespace_id_present = event.namespace_id_present, + .namespace_id = event.namespace_id, + .map_range_present = event.map_range_present, + .map_start = event.map_start, + .map_end = event.map_end, + .per_object_flow = fake_per_object_flow, + .per_object_opaque = &per_object, + .diagnostic = fake_diagnostic, + .diagnostic_opaque = &event.trace, + }; + + check_int("per-object.adapter-return", + kzt_observe_guest_object_from_callback(&request, &result), 0); + check_int("per-object.result", result, + KZT_OBSERVATION_ADAPTER_PER_OBJECT_FAILED); + check_int("per-object.calls", per_object.calls, 1); + check_uintptr("per-object.link-map", per_object.link_map_addr, + (uintptr_t)&event.link_map); + check_int("per-object.diagnostic-calls", event.trace.diagnostic_calls, 1); + check_int("per-object.diagnostic-result", event.trace.diagnostic_result, + KZT_OBSERVATION_ADAPTER_PER_OBJECT_FAILED); + check_ulong("per-object.registry-object-count", + registry_object_count(registry), 1); + + kzt_guest_registry_destroy(®istry); +} + int main(void) { test_active_observation_adds_object_and_preserves_old_flow(); @@ -2564,6 +2634,7 @@ int main(void) test_enabled_diagnostics_are_throttled_and_fail_open(); test_reader_failures_are_throttled(); test_disabled_adapter_diagnostics_are_throttled(); + test_per_object_failure_is_propagated(); if (failures) { fprintf(stderr, "kzt-observation-adapter: %d failure(s)\n", failures); diff --git a/tests/unit/kzt/test_patch_planner.c b/tests/unit/kzt/test_patch_planner.c index dd48fb58b99..62fb7b9ba96 100644 --- a/tests/unit/kzt/test_patch_planner.c +++ b/tests/unit/kzt/test_patch_planner.c @@ -450,6 +450,76 @@ static void test_guest_owned_dlclose_is_kept_guest(void) assert_decision("guest-dlclose", &decision, KZT_PATCH_DECISION_REJECTED, KZT_PATCH_REASON_POLICY_KEEP_GUEST, 0); + + candidate.symbol_name = "free"; + check_int("guest-free.policy", + kzt_patch_symbol_must_stay_guest(candidate.symbol_name), 1); + check_int("guest-free.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("guest-free", &decision, + KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_KEEP_GUEST, 0); + + check_int("guest-__free.policy", + kzt_patch_symbol_must_stay_guest("__free"), 1); + check_int("guest-__libc_free.policy", + kzt_patch_symbol_must_stay_guest("__libc_free"), 1); + check_int("guest-realloc.policy", + kzt_patch_symbol_must_stay_guest("realloc"), 1); + check_int("guest-XOpenDisplay.policy", + kzt_patch_symbol_must_stay_guest("XOpenDisplay"), 1); + check_int("guest-XCloseDisplay.policy", + kzt_patch_symbol_must_stay_guest("XCloseDisplay"), 1); + check_int("guest-XGetXCBConnection.policy", + kzt_patch_symbol_must_stay_guest("XGetXCBConnection"), 1); + check_int("guest-XSetEventQueueOwner.policy", + kzt_patch_symbol_must_stay_guest("XSetEventQueueOwner"), 1); + check_int("native-xcb-connection.policy", + kzt_patch_symbol_must_stay_guest("xcb_connection_has_error"), 0); + check_int("native-xcb-flush.policy", + kzt_patch_symbol_must_stay_guest("xcb_flush"), 0); + check_int("guest-xcb-connect.policy", + kzt_patch_symbol_must_stay_guest("xcb_connect"), 1); + check_int("guest-xcb-connect-auth.policy", + kzt_patch_symbol_must_stay_guest( + "xcb_connect_to_display_with_auth_info"), 1); + check_int("guest-xcb-disconnect.policy", + kzt_patch_symbol_must_stay_guest("xcb_disconnect"), 1); + check_int("guest-xcb-unknown.policy", + kzt_patch_symbol_must_stay_guest("xcb_send_request"), 1); + check_int("dlopen.requires-dlerror-prebind", + kzt_patch_symbol_requires_dlerror_prebind("dlopen"), 1); + check_int("dlmopen.requires-dlerror-prebind", + kzt_patch_symbol_requires_dlerror_prebind("dlmopen"), 1); + check_int("dlsym.requires-dlerror-prebind", + kzt_patch_symbol_requires_dlerror_prebind("dlsym"), 1); + check_int("dlvsym.requires-dlerror-prebind", + kzt_patch_symbol_requires_dlerror_prebind("dlvsym"), 1); + check_int("dlinfo.requires-dlerror-prebind", + kzt_patch_symbol_requires_dlerror_prebind("dlinfo"), 1); + check_int("dladdr.requires-dlerror-prebind", + kzt_patch_symbol_requires_dlerror_prebind("dladdr"), 1); + check_int("dladdr1.requires-dlerror-prebind", + kzt_patch_symbol_requires_dlerror_prebind("dladdr1"), 1); + check_int("dlerror.is-prebind-anchor", + kzt_patch_symbol_requires_dlerror_prebind("dlerror"), 0); + check_int("ordinary-symbol.no-dlerror-prebind", + kzt_patch_symbol_requires_dlerror_prebind("puts"), 0); + + candidate = base_candidate(); + candidate.symbol_name = "xcb_flush"; + check_int("native-xcb-flush.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("native-xcb-flush", &decision, + KZT_PATCH_DECISION_APPROVED, + KZT_PATCH_REASON_APPROVED_NATIVE_BRIDGE, 1); + + candidate.symbol_name = "xcb_send_request"; + check_int("guest-xcb-unknown.decide", + kzt_patch_planner_decide(&candidate, &decision), 0); + assert_decision("guest-xcb-unknown", &decision, + KZT_PATCH_DECISION_REJECTED, + KZT_PATCH_REASON_POLICY_KEEP_GUEST, 0); } static int test_matches_filter(const char *name, int argc, char **argv) diff --git a/tests/unit/kzt/test_patch_spike_writer.c b/tests/unit/kzt/test_patch_spike_writer.c index c8e0ddc398e..192cd02a291 100644 --- a/tests/unit/kzt/test_patch_spike_writer.c +++ b/tests/unit/kzt/test_patch_spike_writer.c @@ -729,7 +729,7 @@ static void test_non_approved_decision_does_not_write(void) check_no_slot_calls("rejected.slot", &slot); } -static void test_approved_non_jump_slot_does_not_consume_guard_budget(void) +static void test_approved_glob_dat_uses_transaction_and_guard_budget(void) { kzt_patch_decision_t decision = approved_decision(0x7100000018); kzt_patch_spike_guard_t guard = init_guard(1, 1, 8); @@ -750,12 +750,14 @@ static void test_approved_non_jump_slot_does_not_consume_guard_budget(void) &ops, &record), 0); check_int("glob-dat.result", record.result, - KZT_PATCH_SPIKE_RESULT_FAIL_OPEN); + KZT_PATCH_SPIKE_RESULT_APPLIED); check_int("glob-dat.failure", record.failure, - KZT_PATCH_SPIKE_FAILURE_INVALID_ARGUMENT); - check_int("glob-dat.writer-called", record.writer_called, 0); - check_ulong("glob-dat.remaining", record.writes_remaining, 8); - check_no_slot_calls("glob-dat.slot", &slot); + KZT_PATCH_SPIKE_FAILURE_NONE); + check_int("glob-dat.writer-called", record.writer_called, 1); + check_ulong("glob-dat.remaining", record.writes_remaining, 7); + check_uintptr("glob-dat.slot", slot.value, decision.bridge_target); + check_ulong("glob-dat.attempts", guard.write_attempts, 1); + check_ulong("glob-dat.successes", guard.write_successes, 1); } static void test_persistent_guard_budget_blocks_second_write(void) @@ -939,7 +941,7 @@ int main(void) test_partial_permission_enable_is_restored(); test_generation_mismatch_does_not_touch_permissions_or_slot(); test_non_approved_decision_does_not_write(); - test_approved_non_jump_slot_does_not_consume_guard_budget(); + test_approved_glob_dat_uses_transaction_and_guard_budget(); test_persistent_guard_budget_blocks_second_write(); test_persistent_guard_circuit_blocks_second_write(); test_record_fields_are_complete(); diff --git a/tests/unit/kzt/test_rela_runtime_helper_chain.c b/tests/unit/kzt/test_rela_runtime_helper_chain.c index f7bc3888c03..dff0c9885fd 100644 --- a/tests/unit/kzt/test_rela_runtime_helper_chain.c +++ b/tests/unit/kzt/test_rela_runtime_helper_chain.c @@ -38,6 +38,11 @@ typedef struct fixture_bridge_map { void *native_symbol; uintptr_t target; int check_calls; + int add_calls; + int guarded_add_calls; + int corrupt_guarded_entry; + onebridge_t ordinary_entry; + onebridge_t guarded_entry; } fixture_bridge_map_t; typedef struct runtime_fixture { @@ -56,6 +61,12 @@ enum fixture_setup_status { }; uintptr_t CheckBridged(bridge_t *bridge, void *fnc); +uintptr_t AddCheckBridge(bridge_t *bridge, wrapper_t wrapper, void *fnc, + int stack_bytes, const char *name); +uintptr_t AddGuardedBridge( + bridge_t *bridge, wrapper_t wrapper, void *fnc, int stack_bytes, + const char *name, uintptr_t guest_fallback_target, + kzt_bridge_guard_kind_t guard_kind); int BridgeForkProtectionAvailable(void); int BridgeForkProtectionAvailable(void) @@ -77,6 +88,59 @@ uintptr_t CheckBridged(bridge_t *bridge, void *fnc) return map->target; } +uintptr_t AddCheckBridge(bridge_t *bridge, wrapper_t wrapper, void *fnc, + int stack_bytes, const char *name) +{ + fixture_bridge_map_t *map = (fixture_bridge_map_t *)bridge; + onebridge_t *entry; + + (void)name; + if (!map || !wrapper || !fnc) { + return 0; + } + ++map->add_calls; + entry = &map->ordinary_entry; + memset(entry, 0, sizeof(*entry)); + entry->CC = 0xCC; + entry->S = 'S'; + entry->C = 'C'; + entry->w = wrapper; + entry->f = (uintptr_t)fnc; + entry->C3 = stack_bytes ? 0xC2 : 0xC3; + entry->N = stack_bytes; + map->target = (uintptr_t)&entry->CC; + return map->target; +} + +uintptr_t AddGuardedBridge( + bridge_t *bridge, wrapper_t wrapper, void *fnc, int stack_bytes, + const char *name, uintptr_t guest_fallback_target, + kzt_bridge_guard_kind_t guard_kind) +{ + fixture_bridge_map_t *map = (fixture_bridge_map_t *)bridge; + onebridge_t *entry; + + (void)name; + if (!map || !wrapper || !fnc || !guest_fallback_target || + guard_kind != KZT_BRIDGE_GUARD_XCB_CONNECTION) { + return 0; + } + ++map->guarded_add_calls; + entry = &map->guarded_entry; + memset(entry, 0, sizeof(*entry)); + entry->CC = 0xCC; + entry->S = 'S'; + entry->C = 'C'; + entry->w = wrapper; + entry->f = (uintptr_t)fnc; + entry->C3 = stack_bytes ? 0xC2 : 0xC3; + entry->N = stack_bytes; + entry->guest_fallback_target = map->corrupt_guarded_entry ? + guest_fallback_target + 1 : guest_fallback_target; + entry->guard_kind = guard_kind; + return (uintptr_t)&entry->CC; +} + typedef struct guarded_slot { uintptr_t value; uintptr_t canary; @@ -464,12 +528,223 @@ static void test_null_inputs_fail_without_bridge_inspection(void) runtime_fixture_destroy(&fixture); } +static void test_guarded_discovery_creates_unique_unmapped_bridge(void) +{ + const uintptr_t guest_fallback_target = 0x7100abcd; + runtime_fixture_t fixture; + kzt_wrapper_bridge_provider_t runtime_provider; + kzt_wrapper_probe_request_t request = { + .symbol_name = FIXTURE_SYMBOL, + .symbol_version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .symbol_version = FIXTURE_VERSION, + }; + kzt_wrapper_probe_result_t result; + uintptr_t ordinary_target; + int setup_status; + + setup_status = runtime_fixture_setup(&fixture); + if (setup_status == FIXTURE_SETUP_SKIP) { + return; + } + if (setup_status != FIXTURE_SETUP_OK) { + ++failures; + return; + } + ordinary_target = fixture.bridge_map.target; + check_int("guarded-runtime.discover", + kzt_rela_runtime_wrapper_provider_discover_guarded_with_version_evidence( + &fixture.context, &fixture.library, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_VERSIONED, FIXTURE_VERSION, + guest_fallback_target, + KZT_BRIDGE_GUARD_XCB_CONNECTION, &runtime_provider), + 1); + check_int("guarded-runtime.no-map-inspection", + fixture.bridge_map.check_calls, 0); + check_ulong("guarded-runtime.fallback", + runtime_provider.match.guest_fallback_target, + guest_fallback_target); + check_int("guarded-runtime.guard-kind", + runtime_provider.match.guard_kind, + KZT_BRIDGE_GUARD_XCB_CONNECTION); + check_int("guarded-runtime.probe", + kzt_wrapper_probe_minimal_manifest( + &runtime_provider.manifest, &request, + &runtime_provider.bridge_ops, &result), + 0); + check_int("guarded-runtime.add-once", + fixture.bridge_map.guarded_add_calls, 1); + check_ulong("guarded-runtime.unique-target", result.bridge_target, + (uintptr_t)&fixture.bridge_map.guarded_entry.CC); + check_int("guarded-runtime.metadata", + kzt_guarded_bridge_is_exact( + result.bridge_target, fixture_iFp, + (void *)fixture.native_symbol, guest_fallback_target, + KZT_BRIDGE_GUARD_XCB_CONNECTION), + 1); + check_ulong("guarded-runtime.map-unchanged", + fixture.bridge_map.target, ordinary_target); + check_int("guarded-runtime.no-post-add-map-check", + fixture.bridge_map.check_calls, 0); + runtime_fixture_destroy(&fixture); +} + +static void test_guarded_discovery_fail_open_contracts(void) +{ + const uintptr_t guest_fallback_target = 0x7100dcba; + runtime_fixture_t fixture; + kzt_wrapper_bridge_provider_t runtime_provider; + kzt_wrapper_probe_request_t request = { + .symbol_name = FIXTURE_SYMBOL, + .symbol_version_evidence = KZT_SYMBOL_VERSION_VERSIONED, + .symbol_version = FIXTURE_VERSION, + }; + kzt_wrapper_probe_result_t result; + kzt_guest_library_handle_t retained_handle; + int setup_status; + + setup_status = runtime_fixture_setup(&fixture); + if (setup_status == FIXTURE_SETUP_SKIP) { + return; + } + if (setup_status != FIXTURE_SETUP_OK) { + ++failures; + return; + } + check_int("guarded-invalid.zero-fallback", + kzt_rela_runtime_wrapper_provider_discover_guarded_with_version_evidence( + &fixture.context, &fixture.library, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_VERSIONED, FIXTURE_VERSION, 0, + KZT_BRIDGE_GUARD_XCB_CONNECTION, &runtime_provider), + 0); + check_int("guarded-invalid.none-guard", + kzt_rela_runtime_wrapper_provider_discover_guarded_with_version_evidence( + &fixture.context, &fixture.library, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_VERSIONED, FIXTURE_VERSION, + guest_fallback_target, KZT_BRIDGE_GUARD_NONE, + &runtime_provider), + 0); + + retained_handle = (kzt_guest_library_handle_t) { + .bindings = (kzt_guest_library_bindings_t *)1, + .entry = (void *)1, + .library = &fixture.library, + .object_type = KZT_GUEST_LIBRARY_OBJECT_WRAPPED, + }; + check_int("guarded-retained.discover", + kzt_rela_runtime_wrapper_provider_discover_guarded_retained_with_version_evidence( + &fixture.context, &retained_handle, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_VERSIONED, FIXTURE_VERSION, + guest_fallback_target, + KZT_BRIDGE_GUARD_XCB_CONNECTION, &runtime_provider), + 1); + fixture.bridge_map.corrupt_guarded_entry = 1; + check_int("guarded-retained.probe", + kzt_wrapper_probe_minimal_manifest( + &runtime_provider.manifest, &request, + &runtime_provider.bridge_ops, &result), + 0); + check_ulong("guarded-retained.corrupt-entry-rejected", + result.bridge_target, 0); + check_int("guarded-retained.add-attempted", + fixture.bridge_map.guarded_add_calls, 1); + runtime_fixture_destroy(&fixture); +} + +static void test_retained_exact_selector(void) +{ + runtime_fixture_t fixture; + kzt_guest_library_handle_t retained_handle; + int setup_status; + + setup_status = runtime_fixture_setup(&fixture); + if (setup_status == FIXTURE_SETUP_SKIP) { + return; + } + if (setup_status != FIXTURE_SETUP_OK) { + ++failures; + return; + } + retained_handle = (kzt_guest_library_handle_t) { + .bindings = (kzt_guest_library_bindings_t *)1, + .entry = (void *)1, + .library = &fixture.library, + .object_type = KZT_GUEST_LIBRARY_OBJECT_WRAPPED, + }; + check_ulong( + "retained-selector.versioned", + kzt_rela_runtime_select_exact_wrapper_bridge_retained( + &fixture.context, &retained_handle, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_VERSIONED, FIXTURE_VERSION), + fixture.bridge_map.target); + check_ulong( + "retained-selector.unversioned", + kzt_rela_runtime_select_exact_wrapper_bridge_retained( + &fixture.context, &retained_handle, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL), + fixture.bridge_map.target); + check_ulong( + "retained-selector.missing-version", + kzt_rela_runtime_select_exact_wrapper_bridge_retained( + &fixture.context, &retained_handle, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_VERSIONED, NULL), + 0); + retained_handle.object_type = KZT_GUEST_LIBRARY_OBJECT_EMULATED; + check_ulong( + "retained-selector.non-wrapper", + kzt_rela_runtime_select_exact_wrapper_bridge_retained( + &fixture.context, &retained_handle, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL), + 0); + runtime_fixture_destroy(&fixture); +} + +static void test_retained_exact_selector_creates_first_bridge(void) +{ + runtime_fixture_t fixture; + kzt_guest_library_handle_t retained_handle; + uintptr_t selected; + int setup_status; + + setup_status = runtime_fixture_setup(&fixture); + if (setup_status == FIXTURE_SETUP_SKIP) { + return; + } + if (setup_status != FIXTURE_SETUP_OK) { + ++failures; + return; + } + retained_handle = (kzt_guest_library_handle_t) { + .bindings = (kzt_guest_library_bindings_t *)1, + .entry = (void *)1, + .library = &fixture.library, + .object_type = KZT_GUEST_LIBRARY_OBJECT_WRAPPED, + }; + fixture.bridge_map.target = 0; + selected = kzt_rela_runtime_select_exact_wrapper_bridge_retained( + &fixture.context, &retained_handle, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL); + check_int("retained-selector-first.add-once", + fixture.bridge_map.add_calls, 1); + check_ulong("retained-selector-first.selected", selected, + fixture.bridge_map.target); + check_int("retained-selector-first.exact", + kzt_bridge_is_exact( + selected, fixture_iFp, + (void *)fixture.native_symbol), + 1); + runtime_fixture_destroy(&fixture); +} + int main(void) { test_runtime_adapter_helper_chain(); test_version_mismatch_leaves_helper_fallback_eligible(); test_bridge_owner_mismatch_fails_closed(); test_null_inputs_fail_without_bridge_inspection(); + test_guarded_discovery_creates_unique_unmapped_bridge(); + test_guarded_discovery_fail_open_contracts(); + test_retained_exact_selector(); + test_retained_exact_selector_creates_first_bridge(); if (failures) { fprintf(stderr, "kzt-rela-runtime-helper-chain: %d failure(s)\n", diff --git a/tests/unit/kzt/test_wi1009_lazy_slot_bridge_context_contract.py b/tests/unit/kzt/test_wi1009_lazy_slot_bridge_context_contract.py index 143e248d875..240b08af7ab 100644 --- a/tests/unit/kzt/test_wi1009_lazy_slot_bridge_context_contract.py +++ b/tests/unit/kzt/test_wi1009_lazy_slot_bridge_context_contract.py @@ -5,6 +5,13 @@ root = pathlib.Path(sys.argv[1]).resolve() +for removed_path in ( + "target/i386/latx/context/kzt_lazy_slot_bridge.c", + "target/i386/latx/include/kzt_lazy_slot_bridge.h", +): + if (root / removed_path).exists(): + raise AssertionError(f"superseded lazy slot module exists: {removed_path}") + header = ( root / "target/i386/latx/include/box64context.h" ).read_text(encoding="utf-8") @@ -36,4 +43,19 @@ if "'kzt_lazy_slot_bridge.c'" in meson: raise AssertionError("superseded lazy slot implementation is still built") +for path in (root / "target/i386/latx").rglob("*"): + if not path.is_file() or path.suffix not in {".c", ".h", ".build"}: + continue + text = path.read_text(encoding="utf-8", errors="ignore") + for forbidden in ( + "kzt_lazy_slot_bridge", + "kzt_lazy_slot_bridges", + "KZT_LAZY_SLOT_BRIDGE", + ): + if forbidden in text: + raise AssertionError( + f"production tree retains superseded lazy slot state: " + f"{path.relative_to(root)}: {forbidden}" + ) + print("WI-1009 lazy slot bridge removal contract: PASS") diff --git a/tests/unit/kzt/test_wi1021_dlerror_entry_source_contract.py b/tests/unit/kzt/test_wi1021_dlerror_entry_source_contract.py index 8f9a4cd413d..580a90a129c 100644 --- a/tests/unit/kzt/test_wi1021_dlerror_entry_source_contract.py +++ b/tests/unit/kzt/test_wi1021_dlerror_entry_source_contract.py @@ -70,6 +70,26 @@ def fail(message): if required not in publish: fail(f"publisher is missing {required}") +write_start = production.find( + "production_lazy_direct_guard_write(" +) +write_end = production.find( + "\n}", write_start +) +if write_start < 0 or write_end < 0: + fail("cannot locate guarded production lazy slot writer") +write = production[write_start:write_end] +publish_call = write.find("kzt_guest_dl_api_publish_dlerror_entry(") +slot_cas = write.find("__atomic_compare_exchange_n(") +if publish_call < 0 or slot_cas < 0 or publish_call > slot_cas: + fail("guest dlerror entry must be published before the final slot CAS") +for required in ( + "state->preemption_proof.selected_provider_address", + "state->wrapper_provider.match.custom_wrapper", +): + if required not in write: + fail(f"production publication is missing {required}") + cas_start = production.find( "production_lazy_direct_cas_slot(" ) @@ -79,16 +99,12 @@ def fail(message): if cas_start < 0 or cas_end < 0: fail("cannot locate production lazy CAS") cas = production[cas_start:cas_end] -publish_call = cas.find("kzt_guest_dl_api_publish_dlerror_entry(") -slot_cas = cas.find("__atomic_compare_exchange_n(") -if publish_call < 0 or slot_cas < 0 or publish_call > slot_cas: - fail("guest dlerror entry must be published before the final slot CAS") for required in ( - "state->preemption_proof.selected_provider_address", - "state->wrapper_provider.match.custom_wrapper", + ".write_slot = production_lazy_direct_guard_write", + "kzt_patch_spike_writer_try_apply_with_slot_ops(", ): if required not in cas: - fail(f"production publication is missing {required}") + fail(f"production lazy CAS bypasses the global guard via {required}") for name, source in ( ("wrappedlibdl", wrapped_dl), @@ -110,7 +126,8 @@ def fail(message): fail(f"cannot locate {name} dlerror state slow path") state_slow = source[state_start:state_end] for required in ( - "kzt_guest_dl_api_dlerror(error_state, guest_dlerror)", + "kzt_guest_dl_api_dlerror(", + "guest_route_may_have_pending_error", "kzt_guest_dl_api_load_dlerror_hint(", "kzt_guest_dlerror_entry_slow(context)", "error_state->guest_dlerror_entry = guest_dlerror", @@ -123,7 +140,7 @@ def fail(message): if function_start < 0 or function_end < 0: fail(f"cannot locate {name} my_dlerror") function = source[function_start:function_end] - state_check = function.find("if (fast_result)") + state_check = function.find("if (fast_result || guest_loader_route)") slow = function.find("kzt_guest_dlerror_slow_path(") fast_return = function.find("return fast_result;") if min(state_check, slow, fast_return) < 0 or not ( @@ -131,6 +148,10 @@ def fail(message): fail(f"{name} does not isolate the clean dlerror fast path") if "char *fast_result" not in function: fail(f"{name} materializes the clean dlerror result") + if "kzt_guest_loader_route_present" not in function: + fail(f"{name} loses the process-wide guest loader route") + if "guest_loader_route);" not in function: + fail(f"{name} does not pass the guest loader route to the slow path") if "kzt_guest_dl_entries_t fallback" in function: fail(f"{name} hot dlerror wrapper retains the cold fallback frame") if "kzt_guest_dl_api_dlerror(error_state" in function: diff --git a/tests/unit/kzt/test_wi1065_loader_hook_contract.py b/tests/unit/kzt/test_wi1065_loader_hook_contract.py index 0fc69c15c98..72c5c35e24f 100644 --- a/tests/unit/kzt/test_wi1065_loader_hook_contract.py +++ b/tests/unit/kzt/test_wi1065_loader_hook_contract.py @@ -7,6 +7,7 @@ ROOT = Path(__file__).resolve().parents[3] MYALIGN = ROOT / "target/i386/latx/context/myalign.c" HOOK = ROOT / "target/i386/latx/context/kzt_loader_event_hook.c" +HOOK_HEADER = ROOT / "target/i386/latx/include/kzt_loader_event_hook.h" def function_body(source, prefix): @@ -24,10 +25,10 @@ def function_body(source, prefix): hook = HOOK.read_text(encoding="utf-8") +hook_header = HOOK_HEADER.read_text(encoding="utf-8") myalign = MYALIGN.read_text(encoding="utf-8") for required in ( - "KZT_LOADER_EVENT_HOOK_SUPPORTED_BUILD_ID", "kzt_loader_event_hook_read_build_id", "kzt_loader_event_hook_install", "kzt_loader_event_hook_publish", @@ -36,6 +37,12 @@ def function_body(source, prefix): ): if required not in hook: raise AssertionError(f"WI-1065 hook misses {required}") +for required in ( + "KZT_LOADER_EVENT_HOOK_GLIBC_2_28_BUILD_ID", + "KZT_LOADER_EVENT_HOOK_GLIBC_2_39_BUILD_ID", +): + if required not in hook or required not in hook_header: + raise AssertionError(f"WI-1065 exact layout table misses {required}") publish = function_body(hook, "int kzt_loader_event_hook_publish(") for forbidden in ( diff --git a/tests/unit/kzt/test_wi1066_lazy_prebind_source_contract.py b/tests/unit/kzt/test_wi1066_lazy_prebind_source_contract.py index 82ae9793e02..4917de4af45 100644 --- a/tests/unit/kzt/test_wi1066_lazy_prebind_source_contract.py +++ b/tests/unit/kzt/test_wi1066_lazy_prebind_source_contract.py @@ -50,8 +50,19 @@ def function_body(source: str, signature: str) -> str: prepare = function_body( production, "static int production_lazy_prebind_object_prepare(" ) +find_symbol = function_body( + production, "static size_t production_lazy_prebind_find_symbol_index(" +) for required in ( "kzt_runtime_got_plt_candidates_collect(", + "KZT_PATCH_TABLE_PLT_RELA", + "candidate.symbol_name", +): + if required not in find_symbol: + raise AssertionError(f"structured prebind symbol scan lacks {required}") +for required in ( + "kzt_runtime_got_plt_candidates_collect(", + "production_lazy_prebind_find_symbol_index(", "production_symbol_scope_request(", "kzt_guest_symbol_scope_discover(", "kzt_guest_library_access_lookup(", @@ -129,7 +140,7 @@ def function_body(source: str, signature: str) -> str: for required in ( "kzt_lazy_prebind_scope_revoke_acquire(", "kzt_lazy_prebind_scope_revoke_finish(", - "production_lazy_prebind_slot_cas(", + "production_mandatory_slot_transaction(", "writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED", "writer_result == KZT_PRODUCTION_SLOT_TRANSACTION_CAS_MISMATCH", ): diff --git a/tests/unit/kzt/test_wi1083_legacy_callback_removal_source_contract.py b/tests/unit/kzt/test_wi1083_legacy_callback_removal_source_contract.py index 40e45ba8e6a..8cc481b67ac 100644 --- a/tests/unit/kzt/test_wi1083_legacy_callback_removal_source_contract.py +++ b/tests/unit/kzt/test_wi1083_legacy_callback_removal_source_contract.py @@ -79,9 +79,129 @@ def function_body(text: str, signature: str) -> str: fail("callback consumer lost its explicit KZT compile boundary") non_kzt = consumer.split("#else", 1)[1].split("#endif", 1)[0] non_kzt_lines = [line.strip() for line in non_kzt.splitlines() if line.strip()] -if non_kzt_lines != ["(void)env;", "(void)event;"]: +if non_kzt_lines != ["(void)context;", "(void)env;", "(void)event;"]: fail(f"non-KZT callback consumer is not a clear no-op: {non_kzt_lines}") +runtime_header = ( + root / "target/i386/latx/include/kzt_guest_runtime_entry.h" +).read_text(encoding="utf-8") +runtime_source = ( + root / "target/i386/latx/context/kzt_guest_runtime_entry.c" +).read_text(encoding="utf-8") +runtime_state_header = ( + root / "target/i386/latx/include/kzt_guest_runtime_entry_state.h" +).read_text(encoding="utf-8") +runtime_state_source = ( + root / "target/i386/latx/context/kzt_guest_runtime_entry_state.c" +).read_text(encoding="utf-8") +cancel_scope_source = ( + root / "target/i386/latx/context/kzt_guest_cancel_scope.c" +).read_text(encoding="utf-8") +wrappedlibc = ( + root / "target/i386/latx/context/wrappedlibc.c" +).read_text(encoding="utf-8") +tr_misc = ( + root / "target/i386/latx/translator/tr-misc.c" +).read_text(encoding="utf-8") +wrappedlibx11 = ( + root / "target/i386/latx/context/wrappedlibx11.c" +).read_text(encoding="utf-8") +wrappedlibxcb = ( + root / "target/i386/latx/context/wrappedlibxcb.c" +).read_text(encoding="utf-8") +guest_dl_init = ( + root / "target/i386/latx/context/kzt_guest_dl_init.c" +).read_text(encoding="utf-8") +guest_dl_api = ( + root / "target/i386/latx/context/kzt_guest_dl_api.c" +).read_text(encoding="utf-8") +box64context = ( + root / "target/i386/latx/include/box64context.h" +).read_text(encoding="utf-8") + +for symbol in ("free", "realloc", "pthread_setcanceltype"): + if f'"{symbol}"' not in guest_dl_init: + fail(f"guest ELF initialization misses exact symbol name: {symbol}") +for forbidden in ( + "kzt_guest_library_run_dlsym", + "kzt_guest_library_run_dlerror", + "dlsym(RTLD_DEFAULT", + "ResetSpecialCaseElf(", +): + if forbidden in runtime_source: + fail(f"runtime entry resolver retains forbidden lookup: {forbidden}") +for required in ( + "kzt_guest_runtime_entry_load(", + "kzt_guest_runtime_entry_for_guest_branch(", + "kzt_guest_runtime_entry_acquire(", +): + if required not in runtime_header: + fail(f"runtime entry fast path is incomplete: {required}") +if "__atomic_load_n(" not in runtime_state_header: + fail("runtime entry fast path is not an atomic load") + +production = "\n".join( + (myalign, wrappedlibc, tr_misc, wrappedlibx11, wrappedlibxcb, box64context) +) +for forbidden in ( + "x86" + "free", + "x86" + "realloc", + "x86" + "pthread_setcanceltype", + "collect" + "X86free", + "kzt_" + "wine_init_x86", + "malloc_" + "map", + "mallocmaps", +): + if forbidden in production: + fail(f"obsolete runtime-entry state remains: {forbidden}") + +for signature in ( + "static void do_translate_free_brick_tb(", + "static void do_translate_realloc_brick_tb(", +): + translation = function_body(tr_misc, signature) + if "kzt_generate_guest_runtime_branch(" not in translation: + fail(f"{signature} has no emitted runtime slow path") + if "kzt_guest_runtime_entry_for_guest_branch(" in translation or \ + "kzt_guest_runtime_entry_resolve(" in translation: + fail(f"{signature} still executes guest lookup while translating") +runtime_branch = function_body( + tr_misc, "static void kzt_generate_guest_runtime_branch(") +for required in ( + "kzt_runtime_guest_entry_or_abort", + "tr_set_running_of_cs(false)", + "tr_set_running_of_cs(true)", + "la_jirl(", + "la_store_addrx(", +): + if required not in runtime_branch: + fail(f"runtime guest branch misses {required}") +if "kzt_guest_runtime_entry_acquire(" not in cancel_scope_source or \ + "kzt_guest_runtime_entry_release(" not in cancel_scope_source: + fail("blocking consumers do not hold a runtime-entry lifecycle lease") +for consumer_source, label in ((wrappedlibx11, "X11"), + (wrappedlibxcb, "XCB")): + if "kzt_guest_cancel_scope_begin(" not in consumer_source or \ + "kzt_guest_cancel_scope_end(" not in consumer_source: + fail(f"{label} does not use the pinned cancel scope") +if "if (!scope || !scope->switched)" not in cancel_scope_source: + fail("wait consumers can restore cancel type without a successful switch") +x_destroy = function_body(wrappedlibx11, "EXPORT void my_XDestroyImage(") +for required in ("abort();", "len ? len : 1", "if (len)"): + if required not in x_destroy: + fail(f"XDestroyImage failure/zero-length handling misses {required}") + +for required in ( + "tryLoadElfFromFileForContext(context, \"libc.so.6\")", + "tryLoadElfFromFileForContext(context, \"libdl.so.2\")", +): + if required not in guest_dl_init: + fail(f"guest runtime table lookup is not context-explicit: {required}") +if "kzt_guest_runtime_entry_state_begin_teardown(state)" not in guest_dl_api: + fail("guest runtime entries are not closed before context storage teardown") +if "kzt_guest_runtime_entry_state_publish(" not in guest_dl_init: + fail("guest ELF initialization does not publish runtime entries") + for required in ( "elfheader_t* LoadAndCheckElfHeader(", "int RelocateElf(", diff --git a/tests/unit/kzt/test_wi1095_eager_transaction_source_contract.py b/tests/unit/kzt/test_wi1095_eager_transaction_source_contract.py new file mode 100644 index 00000000000..e54221ccdc2 --- /dev/null +++ b/tests/unit/kzt/test_wi1095_eager_transaction_source_contract.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start) + depth = 0 + for index in range(opening, len(text)): + if text[index] == "{": + depth += 1 + elif text[index] == "}": + depth -= 1 + if depth == 0: + return text[opening + 1:index] + raise AssertionError(f"unterminated function: {signature}") + + +root = pathlib.Path(sys.argv[1]).resolve() +elfloader = ( + root / "target/i386/latx/context/elfloader.c" +).read_text(encoding="utf-8") +production = ( + root / "target/i386/latx/context/kzt_jump_slot_production.c" +).read_text(encoding="utf-8") +glob_dat_source = ( + root / "target/i386/latx/context/kzt_guest_glob_dat_target.c" +).read_text(encoding="utf-8") + +rela = function_body(elfloader, "int RelocateElfRELA(") +glob_dat_route = function_body(glob_dat_source, "int kzt_guest_glob_dat_route(") +transaction = function_body( + production, "kzt_production_eager_relocation_write(" +) +guest_transaction = function_body( + production, "kzt_production_guest_relocation_write(" +) +mandatory_transaction = function_body( + production, "production_mandatory_slot_transaction(" +) +prebind_transaction = function_body( + production, "production_lazy_prebind_slot_cas(" +) + +if "__atomic_compare_exchange_n(" in rela: + raise AssertionError("eager relocation still performs a direct slot CAS") +if glob_dat_route.count("kzt_production_eager_relocation_write(") != 1: + raise AssertionError( + "native GLOB_DAT bridge must retain the optional transactional writer" + ) +if rela.count("kzt_production_guest_relocation_write(") < 3: + raise AssertionError( + "local GLOB_DAT, deferred stub, and local eager JUMP_SLOT must use " + "the mandatory guest transaction" + ) +for required in ( + "kzt_patch_spike_writer_try_apply_with_slot_ops(", + "KztPatchSpikeGuardForContext(", + ".begin_write = production_slot_begin_write", + ".end_write = production_slot_end_write", + "kzt_guest_registry_patch_decision_lease_acquire(", +): + if required not in transaction: + raise AssertionError(f"eager transaction misses {required}") +for forbidden in ( + "KztPatchSpikeGuardForContext(", + "kzt_patch_spike_writer_try_apply_with_slot_ops(", +): + if forbidden in guest_transaction or forbidden in mandatory_transaction: + raise AssertionError( + f"mandatory guest relocation incorrectly depends on {forbidden}" + ) +for required in ( + "kzt_guest_registry_source_lease_acquire(", + "production_mandatory_slot_transaction(", +): + if required not in guest_transaction: + raise AssertionError(f"guest transaction misses {required}") +for required in ( + "production_slot_begin_write(", + "production_slot_cas(", + "production_mandatory_finish_slot(", +): + if required not in mandatory_transaction: + raise AssertionError(f"mandatory slot transaction misses {required}") +for required in ( + "kzt_patch_spike_writer_try_apply_with_slot_ops(", + "KztPatchSpikeGuardForContext(", + ".begin_write = production_slot_begin_write", + ".end_write = production_slot_end_write", +): + if required not in prebind_transaction: + raise AssertionError(f"lazy prebind bypasses {required}") +if "__atomic_compare_exchange_n(" in prebind_transaction: + raise AssertionError("lazy prebind performs a direct slot CAS") + +revoke = function_body(production, "production_lazy_prebind_revoke_closed(") +if "production_mandatory_slot_transaction(" not in revoke: + raise AssertionError("lazy prebind revoke is still blocked by optional budget") + +print("WI-1095 eager relocation transaction source contract: PASS") diff --git a/tests/unit/kzt/test_wi1098_thread_local_dl_state_source_contract.py b/tests/unit/kzt/test_wi1098_thread_local_dl_state_source_contract.py index 203291a0e68..aeb14d219aa 100644 --- a/tests/unit/kzt/test_wi1098_thread_local_dl_state_source_contract.py +++ b/tests/unit/kzt/test_wi1098_thread_local_dl_state_source_contract.py @@ -60,8 +60,10 @@ if "kzt_guest_dl_entry_state_t guest_dl_entries;" not in context_header: raise AssertionError("context does not own the guest dl entry publication state") -if "kzt_guest_dl_api_entry_state_destroy(ctx->dlprivate)" not in box_context: - raise AssertionError("context destruction does not close guest dl initialization") +if "kzt_guest_dl_api_entry_state_begin_teardown(ctx->dlprivate)" not in box_context: + raise AssertionError("context destruction does not close guest dl admission") +if "kzt_guest_dl_api_entry_state_destroy(*dl)" not in box_context: + raise AssertionError("dlprivate destruction does not release guest dl state") for source in wrapped_sources: if "init_x86dlfun" in source: @@ -79,7 +81,7 @@ if start < 0 or end < 0: raise AssertionError("libc/libdl dlerror wrapper is missing") dlerror_body = source[start:end] - state_check = dlerror_body.find("if (fast_result)") + state_check = dlerror_body.find("if (fast_result || guest_loader_route)") slow = dlerror_body.find("kzt_guest_dlerror_slow_path(") fast_return = dlerror_body.find("return fast_result;") if min(state_check, slow, fast_return) < 0 or not ( @@ -125,8 +127,10 @@ raise AssertionError("guest dl resolver does not release transient headers") if "FindInCollection(path, &context->box64_ld_lib)" not in dl_init: raise AssertionError("guest dl resolver retry duplicates the hwcaps path") -if "kzt_wine_init_x86(opaque, entries->dlsym)" not in dl_init: - raise AssertionError("guest dl prepare is not bound to the owning context") +if "kzt_guest_dl_api_ensure_entries_prepared(" not in dl_init: + raise AssertionError("guest dl initialization is not bound to owning state") +if "kzt_wine_init_x86" in dl_init: + raise AssertionError("guest dl initialization retains Wine-only entry setup") for required in ( "load_addr = h ? loadSoaddrFromMap(tmp) : 0;", "if (!h || !load_addr)", diff --git a/tests/unit/kzt/test_wi1099_symbol_type_source_contract.py b/tests/unit/kzt/test_wi1099_symbol_type_source_contract.py index c81769111d6..e5a3b5b8294 100644 --- a/tests/unit/kzt/test_wi1099_symbol_type_source_contract.py +++ b/tests/unit/kzt/test_wi1099_symbol_type_source_contract.py @@ -23,11 +23,11 @@ def function_body(text: str, signature: str) -> str: adapter = ( root / "target/i386/latx/context/kzt_guest_library_adapter.c" ).read_text(encoding="utf-8") -library_source = ( - root / "target/i386/latx/context/library.c" +runtime_bridge = ( + root / "target/i386/latx/context/kzt_rela_runtime_bridge.c" ).read_text(encoding="utf-8") selector = function_body( - adapter, "uintptr_t kzt_guest_library_select_symbol_result(" + adapter, "uintptr_t kzt_guest_library_select_symbol_result_with_identity(" ) evidence = { @@ -40,8 +40,7 @@ def function_body(text: str, signature: str) -> str: "kzt_guest_library_symbol_evidence_store", "proven_runtime_address == guest_result", "proven_symbol_type == STT_FUNC", - "kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence", - "GetLibFunctionSymbolStartEnd", + "kzt_rela_runtime_select_exact_wrapper_bridge_retained", ) } if min(evidence.values()) < 0: @@ -50,28 +49,29 @@ def function_body(text: str, signature: str) -> str: binding = evidence["kzt_guest_library_access_lookup"] address = evidence["proven_runtime_address == guest_result"] symbol_type = evidence["proven_symbol_type == STT_FUNC"] -versioned_bridge = evidence[ - "kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence" -] -unversioned_bridge = evidence["GetLibFunctionSymbolStartEnd"] -if not (owner < binding < address <= symbol_type < versioned_bridge and - symbol_type < unversioned_bridge): - raise AssertionError(f"symbol proof does not guard both bridges: {evidence}") +exact_bridge = evidence["kzt_rela_runtime_select_exact_wrapper_bridge_retained"] +if not owner < binding < address <= symbol_type < exact_bridge: + raise AssertionError(f"symbol proof does not guard exact bridge: {evidence}") cleanup = ( selector.rfind("kzt_guest_registry_source_lease_release"), selector.rfind("kzt_guest_library_handle_release"), ) if (min(cleanup) < 0 or cleanup != tuple(sorted(cleanup)) or - cleanup[0] < max(versioned_bridge, unversioned_bridge)): + cleanup[0] < exact_bridge): raise AssertionError(f"symbol evidence cleanup is incomplete: {cleanup}") +shared_selector = function_body( + runtime_bridge, + "uintptr_t kzt_rela_runtime_select_exact_wrapper_bridge_retained(") for token in ( - "KZT_SYMBOL_VERSION_VERSIONED, version", + "kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence(", + "kzt_wrapper_probe_minimal_manifest(", "KZT_PATCH_WRAPPER_VERSION_MATCH", + "KZT_PATCH_WRAPPER_UNVERSIONED_MATCH", "kzt_symbol_version_evidence_matches(", ): - if token not in selector: - raise AssertionError(f"versioned symbol proof is incomplete: {token}") + if token not in shared_selector: + raise AssertionError(f"shared symbol proof is incomplete: {token}") for forbidden in ( "GetGlobalSymbolStartEnd", @@ -89,12 +89,10 @@ def function_body(text: str, signature: str) -> str: if forbidden in selector: raise AssertionError(f"selector retains ambiguous bypass: {forbidden}") -function_lookup = function_body( - library_source, "int GetLibFunctionSymbolStartEnd(") -if "wrapper_manifest_symbol_is_function" not in function_lookup: - raise AssertionError("wrapper result lacks function-only manifest proof") -for forbidden in ("getSymbolInMaps(", "getSymbolInDataMaps(", "lib->get("): - if forbidden in function_lookup: - raise AssertionError(f"function-only wrapper lookup uses data path: {forbidden}") +provider_inspect = function_body( + runtime_bridge, "static int kzt_rela_runtime_provider_inspect(") +for forbidden in ("datamap", "getSymbolInDataMaps(", "lib->get("): + if forbidden in provider_inspect: + raise AssertionError(f"wrapper provider uses a data-symbol path: {forbidden}") print("WI-1099 symbol type source contract: PASS") diff --git a/tests/unit/kzt/test_wi1571_xcb_context_map_source_contract.py b/tests/unit/kzt/test_wi1571_xcb_context_map_source_contract.py new file mode 100644 index 00000000000..298eadc0f3a --- /dev/null +++ b/tests/unit/kzt/test_wi1571_xcb_context_map_source_contract.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +import pathlib +import sys + + +def require(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) + + +root = pathlib.Path(sys.argv[1]) +context_header = (root / "target/i386/latx/include/box64context.h").read_text() +context_source = (root / "target/i386/latx/context/box64context.c").read_text() +align_source = (root / "target/i386/latx/context/myalign.c").read_text() +vulkan_source = (root / "target/i386/latx/context/wrappedvulkan.c").read_text() + +require( + "kzt_xcb_connection_map_t *kzt_xcb_connection_map" in context_header, + "XCB connection ownership must live in box64context_t", +) +require( + "kzt_xcb_connection_map_init" in context_source + and "kzt_xcb_connection_map_destroy" in context_source, + "the context must initialize and destroy its XCB connection map", +) +require("#define NXCB" not in align_source, "the fixed eight-slot map must stay removed") +require("my_xcb_connects" not in align_source, "the process-global native map must stay removed") +require("x64_xcb_connects" not in align_source, "the process-global guest map must stay removed") +require( + "kzt_xcb_connection_guard_acquire" in align_source, + "aligning an XCB connection must acquire a tracked context-owned lease", +) +require( + "dest = add_xcb_connection" not in align_source, + "an unknown guest connection must not be registered implicitly", +) +require( + "begin_xcb_connection_disconnect" in align_source + and "finish_xcb_connection_disconnect" in align_source, + "disconnect must use the exclusive removal protocol", +) +require( + "if (!native_conn)" in vulkan_source and "return -3;" in vulkan_source, + "Vulkan must reject an unknown XCB connection instead of passing NULL native-side", +) + +print("wi1571-xcb-context-map-source-contract: PASS") diff --git a/tests/unit/kzt/test_wi1572_guarded_bridge.c b/tests/unit/kzt/test_wi1572_guarded_bridge.c new file mode 100644 index 00000000000..568c33b2024 --- /dev/null +++ b/tests/unit/kzt/test_wi1572_guarded_bridge.c @@ -0,0 +1,305 @@ +#include +#include +#include +#include + +#define GUARDED_THREAD_COUNT 16 + +#include "target/i386/latx/include/box64context.h" +#include "target/i386/latx/include/bridge.h" +#include "target/i386/latx/include/bridge_private.h" +#include "target/i386/latx/include/elfloader.h" + +box64context_t *my_context; +int relocation_log; +int kzt_registry_diagnostics; + +elfheader_t *FindElfAddress(box64context_t *context, uintptr_t address) +{ + (void)context; + (void)address; + return NULL; +} + +static int failures; + +static void wrapper(uintptr_t fnc) +{ + (void)fnc; +} + +static void other_wrapper(uintptr_t fnc) +{ + (void)fnc; +} + +static void check_true(const char *name, int value) +{ + if (!value) { + fprintf(stderr, "%s: false\n", name); + ++failures; + } +} + +static void test_layout_stays_compatible(void) +{ + check_true("layout.size", sizeof(onebridge_t) == 32); + check_true("layout.CC", offsetof(onebridge_t, CC) == 0); + check_true("layout.S", offsetof(onebridge_t, S) == 1); + check_true("layout.C", offsetof(onebridge_t, C) == 2); + check_true("layout.w", offsetof(onebridge_t, w) == 3); + check_true("layout.f", offsetof(onebridge_t, f) == 11); + check_true("layout.C3", offsetof(onebridge_t, C3) == 19); + check_true("layout.N", offsetof(onebridge_t, N) == 20); + check_true("layout.fallback", + offsetof(onebridge_t, guest_fallback_target) == 22); + check_true("layout.guard", offsetof(onebridge_t, guard_kind) == 30); +} + +static void test_guarded_bridge_semantic_reuse(void) +{ + bridge_t *bridge = NewBridge(); + void *native = (void *)(uintptr_t)0x410000; + uintptr_t base; + uintptr_t same; + uintptr_t other_fallback; + uintptr_t other_wrapper_bridge; + uintptr_t other_native; + uintptr_t other_stack_bytes; + onebridge_t *entry; + + check_true("guarded.new", bridge != NULL); + if (!bridge) { + return; + } + base = AddGuardedBridge(bridge, wrapper, native, 0, "xcb_flush", + 0x510000, + KZT_BRIDGE_GUARD_XCB_CONNECTION); + same = AddGuardedBridge(bridge, wrapper, native, 0, "different-name", + 0x510000, + KZT_BRIDGE_GUARD_XCB_CONNECTION); + other_fallback = AddGuardedBridge( + bridge, wrapper, native, 0, "xcb_flush", 0x520000, + KZT_BRIDGE_GUARD_XCB_CONNECTION); + other_wrapper_bridge = AddGuardedBridge( + bridge, other_wrapper, native, 0, "xcb_flush", 0x510000, + KZT_BRIDGE_GUARD_XCB_CONNECTION); + other_native = AddGuardedBridge( + bridge, wrapper, (void *)(uintptr_t)0x410008, 0, "xcb_flush", + 0x510000, KZT_BRIDGE_GUARD_XCB_CONNECTION); + other_stack_bytes = AddGuardedBridge( + bridge, wrapper, native, 4, "xcb_flush", 0x510000, + KZT_BRIDGE_GUARD_XCB_CONNECTION); + entry = (onebridge_t *)base; + + check_true("guarded.base", base != 0); + check_true("guarded.same-semantic-key", same == base); + check_true("guarded.other-fallback", other_fallback != base); + check_true("guarded.other-wrapper", other_wrapper_bridge != base); + check_true("guarded.other-native", other_native != base); + check_true("guarded.other-stack-bytes", other_stack_bytes != base); + check_true("guarded.other-guard-rejected", + AddGuardedBridge(bridge, wrapper, native, 0, "xcb_flush", + 0x510000, KZT_BRIDGE_GUARD_NONE) == 0); + check_true("guarded.unique-count", + bridge_test_guarded_count(bridge) == 5); + check_true("guarded.not-mapped", CheckBridged(bridge, native) == 0); + check_true("guarded.native-hidden", GetNativeFnc(base) == NULL); + check_true("guarded.bridge-preserved", + GetNativeFncOrFnc(base) == (void *)base); + if (entry) { + check_true("guarded.wrapper", entry->w == wrapper); + check_true("guarded.native", entry->f == (uintptr_t)native); + check_true("guarded.fallback", + entry->guest_fallback_target == 0x510000); + check_true("guarded.kind", + entry->guard_kind == + KZT_BRIDGE_GUARD_XCB_CONNECTION); + } + FreeBridge(&bridge); +} + +static void test_normal_bridge_deduplication_is_unchanged(void) +{ + bridge_t *bridge = NewBridge(); + void *native = (void *)(uintptr_t)0x420000; + uintptr_t guarded; + uintptr_t first; + uintptr_t second; + uintptr_t guarded_again; + onebridge_t *entry; + + check_true("normal.new", bridge != NULL); + if (!bridge) { + return; + } + guarded = AddGuardedBridge(bridge, wrapper, native, 0, "guarded", + 0x520000, + KZT_BRIDGE_GUARD_XCB_CONNECTION); + check_true("normal.guarded-created", guarded != 0); + check_true("normal.guarded-not-mapped", + CheckBridged(bridge, native) == 0); + first = AddCheckBridge(bridge, wrapper, native, 0, "normal"); + second = AddCheckBridge(bridge, wrapper, native, 0, "normal"); + guarded_again = AddGuardedBridge( + bridge, wrapper, native, 0, "guarded-again", 0x520000, + KZT_BRIDGE_GUARD_XCB_CONNECTION); + entry = (onebridge_t *)first; + + check_true("normal.first", first != 0); + check_true("normal.separate-from-guarded", first != guarded); + check_true("normal.deduplicated", first == second); + check_true("normal.guarded-still-deduplicated", + guarded_again == guarded); + check_true("normal.guarded-count", + bridge_test_guarded_count(bridge) == 1); + check_true("normal.mapped", CheckBridged(bridge, native) == first); + check_true("normal.native-visible", GetNativeFnc(first) == native); + check_true("normal.native-unwrapped", GetNativeFncOrFnc(first) == native); + if (entry) { + check_true("normal.no-fallback", entry->guest_fallback_target == 0); + check_true("normal.no-guard", + entry->guard_kind == KZT_BRIDGE_GUARD_NONE); + } + FreeBridge(&bridge); +} + +typedef struct guarded_start_gate_s { + pthread_mutex_t lock; + pthread_cond_t cond; + int ready; + int start; +} guarded_start_gate_t; + +typedef struct guarded_worker_s { + bridge_t *bridge; + guarded_start_gate_t *gate; + uintptr_t result; +} guarded_worker_t; + +static void *guarded_worker_main(void *opaque) +{ + guarded_worker_t *worker = opaque; + + pthread_mutex_lock(&worker->gate->lock); + ++worker->gate->ready; + pthread_cond_broadcast(&worker->gate->cond); + while (!worker->gate->start) { + pthread_cond_wait(&worker->gate->cond, &worker->gate->lock); + } + pthread_mutex_unlock(&worker->gate->lock); + worker->result = AddGuardedBridge( + worker->bridge, wrapper, (void *)(uintptr_t)0x440000, 0, + "xcb_connection_has_error", 0x540000, + KZT_BRIDGE_GUARD_XCB_CONNECTION); + return NULL; +} + +static void test_guarded_bridge_concurrent_reuse(void) +{ + bridge_t *bridge = NewBridge(); + guarded_start_gate_t gate; + guarded_worker_t workers[GUARDED_THREAD_COUNT]; + pthread_t threads[GUARDED_THREAD_COUNT]; + int created = 0; + int i; + + check_true("concurrent.new", bridge != NULL); + if (!bridge) { + return; + } + gate.ready = 0; + gate.start = 0; + check_true("concurrent.mutex-init", + pthread_mutex_init(&gate.lock, NULL) == 0); + check_true("concurrent.cond-init", + pthread_cond_init(&gate.cond, NULL) == 0); + for (i = 0; i < GUARDED_THREAD_COUNT; ++i) { + workers[i] = (guarded_worker_t) { + .bridge = bridge, + .gate = &gate, + .result = 0, + }; + if (pthread_create(&threads[i], NULL, guarded_worker_main, + &workers[i]) != 0) { + check_true("concurrent.thread-create", 0); + break; + } + ++created; + } + pthread_mutex_lock(&gate.lock); + while (gate.ready < created) { + pthread_cond_wait(&gate.cond, &gate.lock); + } + gate.start = 1; + pthread_cond_broadcast(&gate.cond); + pthread_mutex_unlock(&gate.lock); + for (i = 0; i < created; ++i) { + pthread_join(threads[i], NULL); + } + check_true("concurrent.all-created", + created == GUARDED_THREAD_COUNT); + if (created > 0) { + check_true("concurrent.nonzero", workers[0].result != 0); + for (i = 1; i < created; ++i) { + check_true("concurrent.same-result", + workers[i].result == workers[0].result); + } + } + check_true("concurrent.single-entry", + bridge_test_guarded_count(bridge) == 1); + pthread_cond_destroy(&gate.cond); + pthread_mutex_destroy(&gate.lock); + FreeBridge(&bridge); +} + +static void test_invalid_guarded_bridge_is_rejected(void) +{ + bridge_t *bridge = NewBridge(); + void *native = (void *)(uintptr_t)0x430000; + + check_true("invalid.new", bridge != NULL); + if (!bridge) { + return; + } + check_true("invalid.bridge", + AddGuardedBridge(NULL, wrapper, native, 0, "invalid", + 0x530000, + KZT_BRIDGE_GUARD_XCB_CONNECTION) == 0); + check_true("invalid.wrapper", + AddGuardedBridge(bridge, NULL, native, 0, "invalid", + 0x530000, + KZT_BRIDGE_GUARD_XCB_CONNECTION) == 0); + check_true("invalid.native", + AddGuardedBridge(bridge, wrapper, NULL, 0, "invalid", + 0x530000, + KZT_BRIDGE_GUARD_XCB_CONNECTION) == 0); + check_true("invalid.fallback", + AddGuardedBridge(bridge, wrapper, native, 0, "invalid", 0, + KZT_BRIDGE_GUARD_XCB_CONNECTION) == 0); + check_true("invalid.none", + AddGuardedBridge(bridge, wrapper, native, 0, "invalid", + 0x530000, KZT_BRIDGE_GUARD_NONE) == 0); + check_true("invalid.unknown", + AddGuardedBridge(bridge, wrapper, native, 0, "invalid", + 0x530000, + (kzt_bridge_guard_kind_t)2) == 0); + check_true("invalid.no-map", CheckBridged(bridge, native) == 0); + FreeBridge(&bridge); +} + +int main(void) +{ + test_layout_stays_compatible(); + test_guarded_bridge_semantic_reuse(); + test_normal_bridge_deduplication_is_unchanged(); + test_guarded_bridge_concurrent_reuse(); + test_invalid_guarded_bridge_is_rejected(); + if (failures) { + fprintf(stderr, "kzt-wi1572-guarded-bridge: %d failure(s)\n", + failures); + return 1; + } + puts("kzt-wi1572-guarded-bridge: ok"); + return 0; +} diff --git a/tests/unit/kzt/test_wi1572_guarded_xcb_bridge_source_contract.py b/tests/unit/kzt/test_wi1572_guarded_xcb_bridge_source_contract.py new file mode 100644 index 00000000000..a0dd847ac84 --- /dev/null +++ b/tests/unit/kzt/test_wi1572_guarded_xcb_bridge_source_contract.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 + +import pathlib +import sys + + +def require(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) + + +root = pathlib.Path(sys.argv[1]) +bridge_private = (root / "target/i386/latx/include/bridge_private.h").read_text() +bridge_header = (root / "target/i386/latx/include/bridge.h").read_text() +bridge_source = (root / "target/i386/latx/context/bridge.c").read_text() +translator = (root / "target/i386/latx/translator/tr-misc.c").read_text() +align_source = (root / "target/i386/latx/context/myalign.c").read_text() +wrapper_source = (root / "target/i386/latx/context/wrapper.c").read_text() +aot_header = (root / "target/i386/latx/include/aot.h").read_text() +aot_source = (root / "target/i386/latx/sbt/aot.c").read_text() + +require("guest_fallback_target" in bridge_private, "bridge must store guest fallback") +require("guard_kind" in bridge_private, "bridge must store guard kind") +require( + "KZT_BRIDGE_GUARD_XCB_CONNECTION" in bridge_private, + "the XCB connection guard must have an explicit kind", +) +require("AddGuardedBridge" in bridge_header, "guarded bridge API must be public") +require("AddGuardedBridge" in bridge_source, "guarded bridge API must be implemented") +require( + "KZT_BRIDGE_GUARD_XCB_CONNECTION" in translator + and "kzt_xcb_guard_acquire_for_bridge" in translator, + "translator must guard XCB connections before the native wrapper", +) +require( + "bridge->guest_fallback_target" in translator + and "lsenv_offset_of_eip" in translator, + "unknown connections must return to the proven guest target", +) +guarded_start = translator.index( + "static void do_translate_xcb_guarded_brick_tb(" +) +guarded_end = translator.index( + "static void do_translate_brick_tb(", guarded_start +) +guarded = translator[guarded_start:guarded_end] +require( + "li_d(helper, bridge->guest_fallback_target);" in guarded + and "helper, env_ir2_opnd, lsenv_offset_of_eip(lsenv)" in guarded, + "guarded fallback must update both the next-PC register and env eip", +) +runtime_start = translator.index( + "static void kzt_generate_guest_runtime_branch(" +) +runtime_end = translator.index("void kzt_native_to_wrapper(", runtime_start) +runtime_branch = translator[runtime_start:runtime_end] +require( + "la_mov64(helper, a0_ir2_opnd);" in runtime_branch + and "helper, env_ir2_opnd, lsenv_offset_of_eip(lsenv)" in runtime_branch, + "guest runtime fallback must update both the next-PC register and env eip", +) +require( + "kzt_xcb_connection_guard_acquire" in align_source, + "align must acquire a cancellation-safe lease prepared by the bridge guard", +) +align_start = align_source.index("void *align_xcb_connection(void *guest)") +align_end = align_source.index("void unalign_xcb_connection", align_start) +align_function = align_source[align_start:align_end] +require( + "calloc" not in align_function and "malloc" not in align_function, + "the XCB alignment hot path must not allocate a per-call scope", +) +aligned_wrappers = [ + line for line in wrapper_source.splitlines() + if "aligned_xcb = align_xcb_connection" in line +] +require(aligned_wrappers, "generated XCB wrappers must be present") +for line in aligned_wrappers: + guard = line.find("if (!aligned_xcb)") + native_call = line.find("fn(", guard) + require( + guard >= 0 and native_call > guard, + "every XCB wrapper must reject failed alignment before native call", + ) +require( + "LOAD_HELPER_KZT_XCB_GUARD_ACQUIRE" in aot_header + and "LOAD_HELPER_KZT_XCB_GUARD_ACQUIRE" in aot_source, + "AOT and immediate translation must share the XCB guard helper", +) +require( + "-kzt-runtime-entry-v2" in aot_header, + "adding an AOT helper must invalidate the previous KZT AOT format", +) + +production = "\n".join( + path.read_text(errors="ignore") + for path in (root / "target/i386/latx").rglob("*.[ch]") +) +require( + "kzt_lazy_slot_bridge" not in production, + "guarded XCB fallback must not restore the removed per-slot bridge table", +) + +print("wi1572-guarded-xcb-bridge-source-contract: PASS") diff --git a/tests/unit/kzt/test_wi1573_guarded_xcb_production_source_contract.py b/tests/unit/kzt/test_wi1573_guarded_xcb_production_source_contract.py new file mode 100644 index 00000000000..51ad0e76530 --- /dev/null +++ b/tests/unit/kzt/test_wi1573_guarded_xcb_production_source_contract.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 + +import pathlib +import re +import sys + + +def require(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) + + +def function_body(source: str, name: str, next_name: str) -> str: + start = source.index(f"static int {name}(") + end = source.index(f"static int {next_name}(", start) + return source[start:end] + + +root = pathlib.Path(sys.argv[1]) +source = ( + root / "target/i386/latx/context/kzt_jump_slot_production.c" +).read_text() + +selector = function_body( + source, + "production_symbol_uses_guarded_xcb_bridge", + "production_exact_provider_handle_matches", +) +require( + "return kzt_xcb_route_is_guarded_consumer(symbol_name);" in selector, + "production bridge selection must reuse the central XCB route policy", +) + +prebind = function_body( + source, + "production_lazy_prebind_object_prepare", + "production_lazy_prebind_revoke_closed", +) +require( + prebind.index("kzt_patch_symbol_must_stay_guest(candidate.symbol_name)") + < prebind.index( + "kzt_rela_runtime_wrapper_provider_discover_guarded_retained_with_version_evidence" + ), + "lazy prebind must apply planner guest policy before bridge discovery", +) +require( + re.search( + r"discover_guarded_retained_with_version_evidence\(.*?" + r"scope_proof\.selected_provider_address,\s*" + r"KZT_BRIDGE_GUARD_XCB_CONNECTION", + prebind, + re.S, + ), + "lazy prebind fallback must be the proven scope provider address", +) + +lazy_first_call = function_body( + source, + "production_lazy_direct_find_bridge", + "production_lazy_direct_acquire_decision_lease", +) +require( + re.search( + r"discover_guarded_retained_with_version_evidence\(.*?" + r"state->preemption_proof\.selected_provider_address,\s*" + r"KZT_BRIDGE_GUARD_XCB_CONNECTION", + lazy_first_call, + re.S, + ), + "lazy first-call fallback must be the proven preemption provider address", +) + +eager = function_body( + source, + "production_enrich_bridge", + "production_validate_source_identity", +) +require( + eager.index("kzt_patch_symbol_must_stay_guest(request->symbol_name)") + < eager.index("discover_guarded_with_version_evidence"), + "eager production must apply planner guest policy before discovery", +) +require( + re.search( + r"discover_guarded_with_version_evidence\(.*?" + r"request->slot_current_value,\s*" + r"KZT_BRIDGE_GUARD_XCB_CONNECTION", + eager, + re.S, + ), + "completion/eager fallback must be the revalidated current guest slot target", +) + +for path_name, body in ( + ("lazy prebind", prebind), + ("lazy first-call", lazy_first_call), + ("eager", eager), +): + require( + "expected_guest_target" not in body[ + body.index("discover_guarded") : body.index("&wrapper_provider") + if "&wrapper_provider" in body[body.index("discover_guarded") :] + else len(body) + ], + f"{path_name} must not use the expected slot target as fallback", + ) + +lazy_route = source[source.index("int kzt_production_lazy_direct_route(") :] +require( + lazy_route.index("kzt_patch_symbol_must_stay_guest(symbol_name)") + < lazy_route.index(".find_wrapper_bridge = production_lazy_direct_find_bridge"), + "lazy first-call route must apply planner guest policy before discovery", +) + +glob_dat = ( + root / "target/i386/latx/context/kzt_guest_glob_dat_target.c" +).read_text() +require( + "kzt_xcb_route_classify(symbol_name) != KZT_XCB_ROUTE_NOT_XCB" in glob_dat, + "GLOB_DAT must preserve every XCB symbol until it has an equivalent guarded route", +) + +bridge = (root / "target/i386/latx/context/bridge.c").read_text() +native_start = bridge.index("void* GetNativeFnc(") +native_or_start = bridge.index("void* GetNativeFncOrFnc(", native_start) +native = bridge[native_start:native_or_start] +native_or = bridge[ + native_or_start : bridge.index("// Alternate address handling", native_or_start) +] +require( + "b->guard_kind != KZT_BRIDGE_GUARD_NONE" in native and + "return NULL;" in native, + "GetNativeFnc must not unwrap a guarded bridge", +) +require( + "b->guard_kind != KZT_BRIDGE_GUARD_NONE" in native_or and + "return (void*)fnc;" in native_or, + "GetNativeFncOrFnc must preserve a guarded bridge target", +) + +print("wi1573-guarded-xcb-production-source-contract: PASS") diff --git a/tests/unit/kzt/test_wi1574_dlerror_route_coherence_source_contract.py b/tests/unit/kzt/test_wi1574_dlerror_route_coherence_source_contract.py new file mode 100644 index 00000000000..c8f84b0bd00 --- /dev/null +++ b/tests/unit/kzt/test_wi1574_dlerror_route_coherence_source_contract.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def function_body(text: str, signature: str) -> str: + start = text.index(signature) + opening = text.index("{", start) + depth = 0 + for pos in range(opening, len(text)): + if text[pos] == "{": + depth += 1 + elif text[pos] == "}": + depth -= 1 + if depth == 0: + return text[opening:pos + 1] + raise AssertionError(f"unterminated function: {signature}") + + +root = pathlib.Path(sys.argv[1]).resolve() +production = ( + root / "target/i386/latx/context/kzt_jump_slot_production.c" +).read_text() +elfloader = (root / "target/i386/latx/context/elfloader.c").read_text() +myalign = (root / "target/i386/latx/context/myalign.c").read_text() +translator = ( + root / "target/i386/latx/translator/tr-misc.c" +).read_text() +planner = ( + root / "target/i386/latx/include/kzt_patch_planner.h" +).read_text() +wrappers = ( + root / "target/i386/latx/context/wrappedlibc.c", + root / "target/i386/latx/context/wrappedlibdl.c", +) + +policy = function_body( + planner, "static inline int kzt_patch_symbol_requires_dlerror_prebind(" +) +for symbol in ( + "dlopen", "dlmopen", "dlsym", "dlvsym", "dlinfo", "dladdr", "dladdr1" +): + assert f'strcmp(symbol_name, "{symbol}") == 0' in policy +assert 'strcmp(symbol_name, "dlerror")' not in policy + +prebind = function_body( + production, "static int production_lazy_prebind_object_prepare(" +) +find_symbol = function_body( + production, "static size_t production_lazy_prebind_find_symbol_index(" +) +assert "kzt_runtime_got_plt_candidates_collect(" in find_symbol +assert "strcmp(candidate.symbol_name, symbol_name) == 0" in find_symbol +assert "production_lazy_prebind_find_symbol_index(" in prebind +assert 'relocation_count, "dlerror")' in prebind +assert "SymName(" not in prebind +assert "index = dlerror_index;" in prebind +assert prebind.index("index = dlerror_index;") < prebind.index( + "kzt_patch_symbol_requires_dlerror_prebind(" +) +assert "!source_dlerror_native" in prebind +assert 'strcmp(record.symbol, "dlerror") == 0' in prebind +assert "source_dlerror_native = 1;" in prebind +assert "record.loader_mutation_invariant =" in prebind +invariant = prebind[prebind.index("record.loader_mutation_invariant ="):] +assert "kzt_patch_symbol_is_loader_route_family(record.symbol)" in invariant +assert "record.source.link_map_addr == namespace_head" in invariant + +direct = function_body(production, "int kzt_production_lazy_direct_route(") +assert "kzt_patch_symbol_is_loader_route_family(symbol_name)" in direct +assert "kzt_lazy_prebind_scope_has_native_dlerror(" in direct +assert "kzt_lazy_prebind_scope_lease_published(" in direct +assert "KZT_LAZY_DIRECT_ROUTE_REASON_DLERROR_PREBIND_REQUIRED" in direct +assert ".allow_budget_transient_native = loader_write_enabled" in direct +assert "KZT_LAZY_DIRECT_ROUTE_CAS_BUDGET_EXHAUSTED" in production + +assert "kzt_production_lazy_route_guest_target" not in production +assert "kzt_production_lazy_complete" not in production + +resolver = function_body(elfloader, "void PltResolver(void)\n{") +guest_fallback = resolver[resolver.index( + "kzt_patch_symbol_requires_dlerror_prebind(symname)" +):] +assert "&my_context->kzt_guest_loader_route_present" in guest_fallback +assert "__ATOMIC_RELEASE" in guest_fallback +assert "kzt_guest_dl_api_set_slow_required(" in guest_fallback +assert guest_fallback.index("kzt_guest_dl_api_set_slow_required(") < ( + guest_fallback.index("kzt_plt_resolver_enter(") +) + +dlerror_tb = function_body( + translator, "static void do_translate_dlerror_brick_tb(onebridge_t *bridge)" +) +assert "offsetof(CPUX86State, kzt_runtime_context)" in dlerror_tb +assert "offsetof(" in dlerror_tb +assert "box64context_t, kzt_guest_loader_route_present" in dlerror_tb +assert "la_bne(guest_route, zero_ir2_opnd, slow_path);" in dlerror_tb + +writer = function_body(elfloader, "int KztPerObjectGotPltWrite(") +assert "kzt_production_lazy_prebind_object(" not in writer + +relocate = function_body(elfloader, "int RelocateElfPlt(") +assert "kzt_production_lazy_prebind_object(" not in relocate +assert "kzt_production_lazy_prebind_refresh(" not in relocate + +consumer = function_body(myalign, "static void kzt_tb_callback_consume(") +assert "!kzt_loader_lifecycle_runtime_healthy(context)" in consumer +assert "&context->kzt_lazy_prebind_refresh_pending" in consumer + +lifecycle = function_body(myalign, "static void kzt_tb_debug_state_callback(") +consistent = lifecycle.index("snapshot.state == KZT_LOADER_DEBUG_CONSISTENT") +pending = lifecycle.index("&context->kzt_lazy_prebind_refresh_pending") +refresh = lifecycle.index("kzt_production_lazy_prebind_refresh(", pending) +assert consistent < pending < refresh + +for wrapper_path in wrappers: + wrapper = wrapper_path.read_text() + slow = function_body(wrapper, "static char *kzt_guest_dlerror_slow_path(") + assert "Push64(cpu, guest_dlerror)" in slow + entry = function_body(wrapper, "\nchar* my_dlerror(void)\n") + assert "kzt_guest_loader_route_present" in entry + assert "__ATOMIC_ACQUIRE" in entry + assert "kzt_guest_dl_api_set_slow_required(error_state, 1)" in entry + assert "guest_loader_route);" in entry + assert "guest_route_may_have_pending_error" in slow + +print("KZT WI-1574 dlerror route coherence source contract: PASS") diff --git a/tests/unit/kzt/test_wi1611_xcb_cancellation_source_contract.py b/tests/unit/kzt/test_wi1611_xcb_cancellation_source_contract.py new file mode 100644 index 00000000000..6a4e7850b95 --- /dev/null +++ b/tests/unit/kzt/test_wi1611_xcb_cancellation_source_contract.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 + +import pathlib +import sys + + +def require(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) + + +root = pathlib.Path(sys.argv[1]) +guard = (root / "target/i386/latx/context/kzt_xcb_connection_guard.c").read_text() +mapping = (root / "target/i386/latx/context/kzt_xcb_connection_map.c").read_text() +wrapped = (root / "target/i386/latx/context/wrappedlibxcb.c").read_text() +private = (root / "target/i386/latx/include/wrappedlibxcb_private.h").read_text() + +require( + "pthread_key_create" in guard + and "kzt_xcb_thread_leases_destroy" in guard + and "active_count" in guard, + "thread exit must release both pending and active XCB leases", +) +require( + "pthread_setcancelstate(PTHREAD_CANCEL_DISABLE" in guard, + "guard lease transfers must close asynchronous cancellation windows", +) +require( + "pthread_cleanup_push(kzt_xcb_remove_wait_cancel" in mapping + and "entry->removal_pending = 0" in mapping + and "entry->closing = 0" in mapping, + "cancelled removal waits must roll back closing state and unlock the map", +) +require( + mapping.count("kzt_xcb_cancel_disable()") >= 6, + "map mutation and teardown critical sections must reject async cancellation", +) + +for symbol in ( + "xcb_wait_for_event", + "xcb_wait_for_reply", + "xcb_wait_for_reply64", + "xcb_wait_for_special_event", +): + require( + f"GOM({symbol}," in private, + f"{symbol} must use its cancellation-aware custom wrapper", + ) + start = wrapped.index(f"my_{symbol}(") + end = wrapped.index("\n}", start) + body = wrapped[start:end] + require( + "align_xcb_connection" in body + and "unalign_xcb_connection" in body, + f"{symbol} must hold and release a tracked native connection lease", + ) + +disconnect_start = wrapped.index("my_xcb_disconnect(") +disconnect_end = wrapped.index("\n}", disconnect_start) +disconnect = wrapped[disconnect_start:disconnect_end] +require( + "PTHREAD_CANCEL_DISABLE" in disconnect + and "finish_xcb_connection_disconnect" in disconnect, + "disconnect must not be cancelled between removal begin and finish", +) + +print("wi1611-xcb-cancellation-source-contract: PASS") diff --git a/tests/unit/kzt/test_wi1612_xcb_serialization_source_contract.py b/tests/unit/kzt/test_wi1612_xcb_serialization_source_contract.py new file mode 100644 index 00000000000..a5ef38c06af --- /dev/null +++ b/tests/unit/kzt/test_wi1612_xcb_serialization_source_contract.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +import pathlib +import sys + + +def require(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) + + +root = pathlib.Path(sys.argv[1]) +mapping = (root / "target/i386/latx/context/kzt_xcb_connection_map.c").read_text() +align = (root / "target/i386/latx/context/myalign.c").read_text() +tests = (root / "tests/unit/kzt/test_xcb_connection_map.c").read_text() + +require( + "pthread_mutex_t operation_lock" in mapping + and "PTHREAD_MUTEX_RECURSIVE" in mapping, + "each XCB connection must own a recursive operation lock", +) +require( + "kzt_xcb_connection_lease_lock_mirror" in mapping + and "pthread_mutex_lock(&entry->operation_lock)" in mapping, + "guest/native mirror copies must lock the connection entry", +) +acquire_start = mapping.index("static int kzt_xcb_connection_acquire(") +acquire_end = mapping.index( + "int kzt_xcb_connection_map_acquire_by_guest", acquire_start +) +require( + "operation_lock" not in mapping[acquire_start:acquire_end], + "lifetime acquisition must not hold the mirror lock across blocking libxcb calls", +) +require( + "kzt_xcb_connection_lease_unlock_mirror" in mapping + and "pthread_mutex_unlock(&entry->operation_lock)" in mapping, + "mirror copies must release the connection entry lock", +) +require( + "kzt_xcb_mirror_guest_to_native" in align + and "kzt_xcb_mirror_native_to_guest" in align, + "alignment must serialize only mirror copies around the native call", +) +require( + "kzt_xcb_queue_copy(" in align + and "memcpy(dest->out.queue, source->out.queue, sizeof(dest->out.queue))" + not in align + and "dest->in = source->in" not in align, + "hot mirror updates must copy only live queue bytes, not fixed 20 KiB buffers", +) +require( + "test_same_connection_serializes_and_different_connections_run" in tests + and "test_mirror_lock_is_recursive" in tests + and "run_operation_benchmark" in tests, + "whitebox tests must cover recursive locking, serialization, parallelism and lock overhead", +) + +print("wi1612-xcb-serialization-source-contract: PASS") diff --git a/tests/unit/kzt/test_wi1618_guest_cancel_scope.c b/tests/unit/kzt/test_wi1618_guest_cancel_scope.c new file mode 100644 index 00000000000..a024197b2ad --- /dev/null +++ b/tests/unit/kzt/test_wi1618_guest_cancel_scope.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include + +#include "callback.h" +#include "kzt_guest_cancel_scope.h" + +#define CHECK(label, condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "%s: FAIL\n", label); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +static uintptr_t published_address; +static uintptr_t called_address[4]; +static int called_type[4]; +static int call_count; +static int acquire_calls; +static int release_calls; +static int call_result; + +uint64_t RunFunctionWithState(uintptr_t function, int nargs, ...) +{ + va_list args; + int type; + int *oldtype; + + CHECK("cancel call argument count", nargs == 2); + va_start(args, nargs); + type = va_arg(args, int); + oldtype = va_arg(args, int *); + va_end(args); + called_address[call_count] = function; + called_type[call_count] = type; + ++call_count; + if (!call_result && oldtype) { + *oldtype = 17; + } + return call_result; +} + +int kzt_guest_runtime_entry_acquire( + box64context_t *context, kzt_guest_runtime_entry_id_t entry, + kzt_guest_runtime_entry_scope_t *scope) +{ + (void)context; + ++acquire_calls; + *scope = (kzt_guest_runtime_entry_scope_t) { 0 }; + if (entry != KZT_GUEST_RUNTIME_PTHREAD_SETCANCELTYPE || + !published_address) { + return -1; + } + scope->state = (kzt_guest_dl_entry_state_t *)(uintptr_t)1; + scope->address = published_address; + return 0; +} + +void kzt_guest_runtime_entry_release( + kzt_guest_runtime_entry_scope_t *scope) +{ + if (scope && scope->state) { + ++release_calls; + *scope = (kzt_guest_runtime_entry_scope_t) { 0 }; + } +} + +static void reset_state(void) +{ + published_address = 0xa100; + call_count = 0; + acquire_calls = 0; + release_calls = 0; + call_result = 0; +} + +static void test_normal_return_uses_pinned_entry(void) +{ + kzt_guest_cancel_scope_t scope = { 0 }; + + reset_state(); + kzt_guest_cancel_scope_begin((box64context_t *)(uintptr_t)1, &scope); + CHECK("cancel begin acquires once", + acquire_calls == 1 && scope.switched && scope.oldtype == 17); + CHECK("cancel begin uses published entry", + call_count == 1 && called_address[0] == 0xa100); + published_address = 0xb200; + kzt_guest_cancel_scope_end(&scope); + CHECK("cancel end does not reacquire", acquire_calls == 1); + CHECK("cancel end uses same pinned entry", + call_count == 2 && called_address[1] == 0xa100 && + called_type[1] == 17); + CHECK("cancel end releases scope", + release_calls == 1 && !scope.runtime.state && !scope.switched); +} + +static void test_switch_failure_releases_entry(void) +{ + kzt_guest_cancel_scope_t scope = { 0 }; + + reset_state(); + call_result = 1; + kzt_guest_cancel_scope_begin((box64context_t *)(uintptr_t)1, &scope); + CHECK("cancel failure releases scope", + acquire_calls == 1 && release_calls == 1 && + !scope.runtime.state && !scope.switched); + kzt_guest_cancel_scope_end(&scope); + CHECK("cancel failure does not restore", call_count == 1); +} + +static void test_cancel_cleanup_releases_entry(void) +{ + kzt_guest_cancel_scope_t scope = { 0 }; + + reset_state(); + kzt_guest_cancel_scope_begin((box64context_t *)(uintptr_t)1, &scope); + kzt_guest_cancel_scope_cleanup(&scope); + CHECK("cancel cleanup releases without restore", + acquire_calls == 1 && release_calls == 1 && call_count == 1 && + !scope.runtime.state && !scope.switched); + kzt_guest_cancel_scope_cleanup(&scope); + CHECK("cancel cleanup is idempotent", release_calls == 1); +} + +int main(void) +{ + test_normal_return_uses_pinned_entry(); + test_switch_failure_releases_entry(); + test_cancel_cleanup_releases_entry(); + puts("wi1618-guest-cancel-scope: PASS"); + return EXIT_SUCCESS; +} diff --git a/tests/unit/kzt/test_wi1618_guest_cancel_scope_source_contract.py b/tests/unit/kzt/test_wi1618_guest_cancel_scope_source_contract.py new file mode 100644 index 00000000000..0b37b58c8f4 --- /dev/null +++ b/tests/unit/kzt/test_wi1618_guest_cancel_scope_source_contract.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +import pathlib +import sys + + +def require(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) + + +root = pathlib.Path(sys.argv[1]) +helper = (root / "target/i386/latx/context/kzt_guest_cancel_scope.c").read_text() +x11 = (root / "target/i386/latx/context/wrappedlibx11.c").read_text() +xcb = (root / "target/i386/latx/context/wrappedlibxcb.c").read_text() + +require( + helper.count("kzt_guest_runtime_entry_acquire(") == 1, + "cancel restoration must reuse the entry acquired before the blocking call", +) +require( + "scope->runtime.address, 2, scope->oldtype, NULL" in helper, + "normal return must restore through the pinned guest entry", +) +require( + "kzt_guest_cancel_scope_cleanup" in helper + and "kzt_guest_runtime_entry_release(&scope->runtime)" in helper, + "thread cancellation must release the pinned runtime entry", +) +for name, source in (("X11", x11), ("XCB", xcb)): + require( + "pthread_cleanup_push(kzt_guest_cancel_scope_cleanup" in source + and "kzt_guest_cancel_scope_begin(my_context" in source + and "kzt_guest_cancel_scope_end(&cancel)" in source + and "pthread_cleanup_pop(0)" in source, + f"{name} blocking wrappers must protect the whole cancel scope", + ) + +print("wi1618-guest-cancel-scope-source-contract: PASS") diff --git a/tests/unit/kzt/test_wi1619_xcb_flush_state_source_contract.py b/tests/unit/kzt/test_wi1619_xcb_flush_state_source_contract.py new file mode 100644 index 00000000000..721648b9139 --- /dev/null +++ b/tests/unit/kzt/test_wi1619_xcb_flush_state_source_contract.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +import pathlib +import sys + + +def require(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) + + +root = pathlib.Path(sys.argv[1]) +align = (root / "target/i386/latx/context/myalign.c").read_text() + +guard_start = align.index("uintptr_t kzt_xcb_guard_acquire_for_bridge(") +guard_end = align.index("static void kzt_xcb_copy_guest_to_native(", guard_start) +guard = align[guard_start:guard_end] +require( + "kzt_xcb_flush_state_is_supported(" in guard + and "reason=unsupported_flush_state" in guard + and "kzt_xcb_connection_guard_cancel();" in guard + and guard.index("kzt_xcb_connection_guard_prepare(") + < guard.index("kzt_xcb_flush_state_is_supported("), + "guard must keep unsupported guest socket/queue state on the guest path", +) + +start = align.index("static void kzt_xcb_copy_guest_to_native(") +end = align.index("static void kzt_xcb_copy_native_to_guest(", start) +guest_to_native = align[start:end] + +for required in ( + "kzt_xcb_queue_copy(", + "dest->out.queue", + "source->out.queue", + "&dest->out.queue_len", + "dest->out.request = source->out.request", + "dest->out.request_written = source->out.request_written", + "dest->out.out_fd = source->out.out_fd", +): + require( + required in guest_to_native, + f"guest-to-native XCB flush state misses {required}", + ) +for forbidden in ( + "dest->out.cond =", + "dest->out.socket_cond =", + "dest->out.return_socket =", + "dest->out.socket_closure =", + "dest->out.reqlenlock =", +): + require( + forbidden not in guest_to_native, + f"guest-to-native mirror must not overwrite native synchronization state: {forbidden}", + ) + +print("wi1619-xcb-flush-state-source-contract: PASS") diff --git a/tests/unit/kzt/test_wi1619_xcb_queue_mirror.c b/tests/unit/kzt/test_wi1619_xcb_queue_mirror.c new file mode 100644 index 00000000000..63cf29d36af --- /dev/null +++ b/tests/unit/kzt/test_wi1619_xcb_queue_mirror.c @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include + +#include "kzt_xcb_queue_mirror.h" + +#define CHECK(label, condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "%s: FAIL\n", label); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +typedef struct guarded_queue { + uint8_t before; + char data[8]; + uint8_t after; +} guarded_queue_t; + +static void test_live_bytes_are_copied(void) +{ + guarded_queue_t dest = { .before = 0x41, .after = 0x42 }; + char source[8] = "request"; + int length = -1; + + CHECK("queue live size", kzt_xcb_queue_copy( + dest.data, sizeof(dest.data), &length, + source, sizeof(source), 7) == 7); + CHECK("queue live bytes", + length == 7 && memcmp(dest.data, source, 7) == 0); + CHECK("queue live bounds", dest.before == 0x41 && dest.after == 0x42); +} + +static void test_invalid_lengths_are_clamped(void) +{ + guarded_queue_t dest = { + .before = 0x51, + .data = "unchange", + .after = 0x52, + }; + char source[16] = "0123456789abcde"; + int length = -1; + + CHECK("queue negative size", kzt_xcb_queue_copy( + dest.data, sizeof(dest.data), &length, + source, sizeof(source), -1) == 0 && length == 0); + CHECK("queue negative unchanged", + memcmp(dest.data, "unchange", sizeof(dest.data)) == 0); + CHECK("queue oversized size", kzt_xcb_queue_copy( + dest.data, sizeof(dest.data), &length, + source, sizeof(source), 32) == sizeof(dest.data)); + CHECK("queue oversized bytes", + length == (int)sizeof(dest.data) && + memcmp(dest.data, source, sizeof(dest.data)) == 0); + CHECK("queue oversized bounds", + dest.before == 0x51 && dest.after == 0x52); +} + +static void test_unsupported_flush_state_falls_back(void) +{ + CHECK("flush state ordinary", kzt_xcb_flush_state_is_supported( + 64, 128, 2, 1, 0, 0, 0, 0)); + CHECK("flush state negative queue", !kzt_xcb_flush_state_is_supported( + -1, 128, 0, 0, 0, 0, 0, 0)); + CHECK("flush state oversized queue", !kzt_xcb_flush_state_is_supported( + 129, 128, 0, 0, 0, 0, 0, 0)); + CHECK("flush state invalid fds", !kzt_xcb_flush_state_is_supported( + 0, 128, 17, 0, 0, 0, 0, 0)); + CHECK("flush state active writer", !kzt_xcb_flush_state_is_supported( + 0, 128, 0, 0, 1, 0, 0, 0)); + CHECK("flush state socket owner", !kzt_xcb_flush_state_is_supported( + 0, 128, 0, 0, 0, 1, 1, 1)); +} + +static __attribute__((noinline)) size_t benchmark_queue_copy( + char *dest, int *length, const char *source) +{ + return kzt_xcb_queue_copy( + dest, 64, length, source, 64, 64); +} + +static void run_benchmark(void) +{ + const unsigned int iterations = 200000; + char source[64] = { 0 }; + char dest[64] = { 0 }; + struct timespec start; + struct timespec end; + uint64_t elapsed_ns; + double ns_per_copy; + volatile unsigned int checksum = 0; + int length = 0; + unsigned int i; + + clock_gettime(CLOCK_MONOTONIC, &start); + for (i = 0; i < iterations; ++i) { + source[i & 63] = (char)i; + benchmark_queue_copy(dest, &length, source); + checksum += (unsigned char)dest[i & 63]; + } + clock_gettime(CLOCK_MONOTONIC, &end); + elapsed_ns = (uint64_t)( + (end.tv_sec - start.tv_sec) * 1000000000LL + + end.tv_nsec - start.tv_nsec); + ns_per_copy = (double)elapsed_ns / iterations; + printf("wi1619-xcb-queue-mirror-performance: %.2f ns/64-byte-copy\n", + ns_per_copy); + CHECK("queue benchmark result", + length == 64 && dest[0] == source[0] && checksum != 0); + CHECK("queue benchmark upper bound", ns_per_copy < 500.0); +} + +int main(int argc, char **argv) +{ + if (argc == 2 && strcmp(argv[1], "--benchmark") == 0) { + run_benchmark(); + return EXIT_SUCCESS; + } + test_live_bytes_are_copied(); + test_invalid_lengths_are_clamped(); + test_unsupported_flush_state_falls_back(); + puts("wi1619-xcb-queue-mirror: PASS"); + return EXIT_SUCCESS; +} diff --git a/tests/unit/kzt/test_wi1621_x11_xcb_close_source_contract.py b/tests/unit/kzt/test_wi1621_x11_xcb_close_source_contract.py new file mode 100644 index 00000000000..3c197bd1212 --- /dev/null +++ b/tests/unit/kzt/test_wi1621_x11_xcb_close_source_contract.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +import pathlib +import sys + + +def require(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) + + +def function_body(source: str, signature: str) -> str: + start = source.index(signature) + brace = source.index("{", start) + depth = 0 + for index in range(brace, len(source)): + if source[index] == "{": + depth += 1 + elif source[index] == "}": + depth -= 1 + if depth == 0: + return source[brace:index + 1] + raise AssertionError(f"unterminated function: {signature}") + + +root = pathlib.Path(sys.argv[1]) +x11 = (root / "target/i386/latx/context/wrappedlibx11.c").read_text() +x11_private = ( + root / "target/i386/latx/include/wrappedlibx11_private.h" +).read_text() +context = (root / "target/i386/latx/context/box64context.c").read_text() + +close = function_body(x11, "EXPORT int32_t my_XCloseDisplay(") +for required in ( + "PTHREAD_CANCEL_DISABLE", + "begin_xcb_connection_disconnect_native(", + "my->XCloseDisplay(", + "finish_xcb_connection_disconnect(", +): + require(required in close, f"XCloseDisplay lifecycle misses {required}") +require( + close.index("begin_xcb_connection_disconnect_native(") + < close.index("my->XCloseDisplay(") + < close.index("finish_xcb_connection_disconnect("), + "XCloseDisplay must drain leases before native close and remove afterward", +) +active_x11_entries = { + line.strip() for line in x11_private.splitlines() + if not line.lstrip().startswith("//") +} +require( + "GOM(XCloseDisplay, iFp)" in active_x11_entries + and "GO(XCloseDisplay, iFp)" not in active_x11_entries, + "XCloseDisplay must use the lifecycle-aware custom wrapper", +) + +free_context = function_body(context, "void FreeBox64Context(") +map_destroy = free_context.index("kzt_xcb_connection_map_destroy(") +require( + map_destroy < free_context.index("FreeLibrarian(&ctx->local_maplib)") + and map_destroy < free_context.index("FreeLibrarian(&ctx->maplib)"), + "XCB leases must drain before wrapped X11/XCB libraries are unloaded", +) + +print("wi1621-x11-xcb-close-source-contract: PASS") diff --git a/tests/unit/kzt/test_wi1629_loader_callback_snapshot_source_contract.py b/tests/unit/kzt/test_wi1629_loader_callback_snapshot_source_contract.py new file mode 100644 index 00000000000..c5950d01ad4 --- /dev/null +++ b/tests/unit/kzt/test_wi1629_loader_callback_snapshot_source_contract.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def function_body(text, signature): + start = text.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start) + depth = 0 + for index in range(opening, len(text)): + if text[index] == "{": + depth += 1 + elif text[index] == "}": + depth -= 1 + if depth == 0: + return text[opening + 1:index] + raise AssertionError(f"unterminated function: {signature}") + + +root = pathlib.Path(sys.argv[1]).resolve() +myalign = (root / "target/i386/latx/context/myalign.c").read_text() +adapter = (root / "target/i386/latx/context/kzt_observation_adapter.c").read_text() + +materialize = function_body( + myalign, "static int kzt_tb_callback_materialize_binding(") +for forbidden in ("struct link_map_x64", "link_map->"): + if forbidden in materialize: + raise AssertionError( + f"loader binding still directly reads guest link_map: {forbidden}") +for required in ( + "kzt_guest_registry_find_live_object(", + "match.path_status", + "match.path", + "kzt_guest_library_wrapper_source_acquire(", + "kzt_guest_library_binding_result_t", +): + if required not in materialize: + raise AssertionError(f"loader binding misses copied proof: {required}") + +per_object = function_body( + myalign, "static int kzt_tb_callback_per_object_got_plt(") +materialize_call = per_object.find( + "kzt_tb_callback_materialize_binding(link_map_addr, opaque)") +apply_call = per_object.find("kzt_per_object_got_plt_apply(&request, &result)") +if materialize_call < 0 or apply_call < 0 or materialize_call >= apply_call: + raise AssertionError("wrapper binding is not checked before native GOT/PLT work") +if "result.status == KZT_PER_OBJECT_GOT_PLT_FAIL_OPEN" not in per_object: + raise AssertionError("per-object fail-open status is not propagated") +if "kzt_tb_callback_materialize_binding(link_map_addr, opaque) != 0 ||" not in per_object: + raise AssertionError("binding failure does not stop native GOT/PLT work") + +observe = function_body( + adapter, "int kzt_observe_guest_object_from_callback(") +if "(void)request->per_object_flow(" in observe: + raise AssertionError("adapter still ignores per-object failure") +if "request->per_object_flow(request->link_map_addr," not in observe or \ + "observation_result = KZT_OBSERVATION_ADAPTER_PER_OBJECT_FAILED" not in observe: + raise AssertionError("adapter does not expose per-object failure") + +consumer = function_body(myalign, "static void kzt_tb_callback_consume(") +refresh = consumer.find("kzt_production_lazy_prebind_refresh(") +failure = consumer.find("KZT_OBSERVATION_ADAPTER_PER_OBJECT_FAILED") +if refresh < 0 or failure >= 0: + raise AssertionError("loader refresh accepts a failed per-object result") +for required in ( + "observation_result == KZT_OBSERVATION_ADAPTER_ADDED", + "observation_result == KZT_OBSERVATION_ADAPTER_UPDATED", +): + if required not in consumer[:refresh]: + raise AssertionError("loader refresh is not limited to successful updates") + +print("WI-1629 loader callback snapshot source contract: PASS") diff --git a/tests/unit/kzt/test_wi1633_exact_wrapper_selection_source_contract.py b/tests/unit/kzt/test_wi1633_exact_wrapper_selection_source_contract.py new file mode 100644 index 00000000000..2074e4adb0f --- /dev/null +++ b/tests/unit/kzt/test_wi1633_exact_wrapper_selection_source_contract.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def matching_brace(text: str, opening: int) -> int: + depth = 0 + quote = None + escaped = False + index = opening + + while index < len(text): + char = text[index] + following = text[index + 1] if index + 1 < len(text) else "" + if quote: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == quote: + quote = None + index += 1 + continue + if char in ("'", '"'): + quote = char + index += 1 + continue + if char == "/" and following == "/": + newline = text.find("\n", index + 2) + index = len(text) if newline < 0 else newline + 1 + continue + if char == "/" and following == "*": + end = text.find("*/", index + 2) + if end < 0: + raise AssertionError("unterminated C comment") + index = end + 2 + continue + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + return index + index += 1 + raise AssertionError("unterminated C function") + + +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + raise AssertionError(f"missing function: {signature}") + opening = text.find("{", start + len(signature)) + if opening < 0: + raise AssertionError(f"missing function body: {signature}") + return text[opening + 1:matching_brace(text, opening)] + + +root = pathlib.Path(sys.argv[1]).resolve() +bridge_header = ( + root / "target/i386/latx/include/kzt_rela_runtime_bridge.h" +).read_text(encoding="utf-8") +bridge_source = ( + root / "target/i386/latx/context/kzt_rela_runtime_bridge.c" +).read_text(encoding="utf-8") +adapter = ( + root / "target/i386/latx/context/kzt_guest_library_adapter.c" +).read_text(encoding="utf-8") +glob_dat_source = ( + root / "target/i386/latx/context/kzt_guest_glob_dat_target.c" +).read_text(encoding="utf-8") + +selector_name = "kzt_rela_runtime_select_exact_wrapper_bridge_retained(" +if selector_name not in bridge_header: + raise AssertionError("shared retained-handle bridge selector is not declared") + +shared = function_body(bridge_source, selector_name) +for token in ( + "kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence(", + "kzt_wrapper_probe_minimal_manifest(", + "KZT_PATCH_WRAPPER_UNVERSIONED_MATCH", + "KZT_PATCH_WRAPPER_VERSION_MATCH", + "kzt_symbol_version_evidence_matches(", + "probe.bridge_target", +): + if token not in shared: + raise AssertionError(f"shared selector lacks exact wrapper proof: {token}") + +dlsym_selector = function_body( + adapter, "uintptr_t kzt_guest_library_select_symbol_result_with_identity(") +if "GetLibFunctionSymbolStartEnd(" in dlsym_selector: + raise AssertionError("dlsym selector still uses the generic name-keyed lookup") +if selector_name not in dlsym_selector: + raise AssertionError("dlsym selector does not use the shared exact selector") +if dlsym_selector.count("kzt_guest_library_symbol_evidence_lookup(") != 1: + raise AssertionError("dlsym selector must read symbol and bridge evidence once") +if "kzt_guest_library_symbol_bridge_lookup(" in dlsym_selector: + raise AssertionError("dlsym selector takes a second binding lock for bridge evidence") +for token in ( + "KZT_SYMBOL_VERSION_VERSIONED", + "KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED", + "kzt_guest_library_symbol_evidence_lookup(", + "kzt_guest_library_symbol_bridge_store(", +): + if token not in dlsym_selector: + raise AssertionError(f"dlsym selector lacks version evidence: {token}") + +glob_dat = function_body( + glob_dat_source, "kzt_guest_glob_dat_target_resolve(") +glob_dat_route = function_body( + glob_dat_source, "int kzt_guest_glob_dat_route(") +if "GetLibSymbolStartEnd(" in glob_dat: + raise AssertionError("GLOB_DAT still uses the generic name-keyed lookup") +if selector_name not in glob_dat: + raise AssertionError("GLOB_DAT does not use the shared exact selector") +for token in ( + "KZT_GUEST_LIBRARY_OBJECT_WRAPPED", + "KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED", + "version >= 2", + ".version = NULL", +): + if token not in glob_dat: + raise AssertionError(f"GLOB_DAT lacks exact version/owner proof: {token}") +if "target->selected_target = guest_target" not in glob_dat: + raise AssertionError("GLOB_DAT lost its fail-open guest target") +if "KZT_SYMBOL_VERSION_VERSIONED" in glob_dat: + raise AssertionError("GLOB_DAT widened beyond confirmed unversioned symbols") +for token in ( + "kzt_guest_symbol_scope_revalidate(", + "kzt_production_eager_relocation_write(", + "KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED", + "kzt_guest_glob_dat_target_release(", +): + if token not in glob_dat_route: + raise AssertionError(f"GLOB_DAT route lacks executable fail-open flow: {token}") +if "KZT_SYMBOL_VERSION_VERSIONED" in glob_dat_route: + raise AssertionError("GLOB_DAT writer widened beyond confirmed unversioned symbols") + +print("WI-1633 exact wrapper selection source contract: PASS") diff --git a/tests/unit/kzt/test_wi1633_real_bridge_selection.c b/tests/unit/kzt/test_wi1633_real_bridge_selection.c new file mode 100644 index 00000000000..cd3e03ef0fc --- /dev/null +++ b/tests/unit/kzt/test_wi1633_real_bridge_selection.c @@ -0,0 +1,109 @@ +#include +#include +#include +#include + +#include "target/i386/latx/include/box64context.h" +#include "target/i386/latx/include/bridge.h" +#include "target/i386/latx/include/elfloader.h" +#include "target/i386/latx/include/khash.h" +#include "target/i386/latx/include/kzt_bridge_exact.h" +#include "target/i386/latx/include/kzt_rela_runtime_bridge.h" +#include "target/i386/latx/include/librarian_private.h" +#include "target/i386/latx/include/library.h" +#include "target/i386/latx/include/library_private.h" + +#define FIXTURE_SYMBOL "uname" + +box64context_t *my_context; +int relocation_log; +int kzt_registry_diagnostics; + +KHASH_MAP_IMPL_STR(symbolmap, wrapper_t) +KHASH_MAP_IMPL_STR(symbol2map, symbol2_t) + +elfheader_t *FindElfAddress(box64context_t *context, uintptr_t address) +{ + (void)context; + (void)address; + return NULL; +} + +static void wrapper(uintptr_t fnc) +{ + (void)fnc; +} + +int main(void) +{ + static char libc_name[] = "libc.so.6"; + box64context_t context = { 0 }; + lib_t scope = { 0 }; + library_t provider = { 0 }; + library_t *libraries[] = { &provider }; + kzt_guest_library_handle_t handle = { + .bindings = (kzt_guest_library_bindings_t *)(uintptr_t)1, + .entry = (void *)(uintptr_t)1, + .library = &provider, + .object_type = KZT_GUEST_LIBRARY_OBJECT_WRAPPED, + }; + bridge_t *bridge = NULL; + void *native_symbol = NULL; + uintptr_t first = 0; + uintptr_t second = 0; + khint_t key; + int inserted; + int failed = 0; + + bridge = NewBridge(); + provider.priv.w.lib = dlopen(libc_name, RTLD_LAZY | RTLD_LOCAL); + provider.symbolmap = kh_init(symbolmap); + if (!bridge || !provider.priv.w.lib || !provider.symbolmap) { + fprintf(stderr, "cannot initialize real bridge fixture\n"); + failed = 1; + goto out; + } + key = kh_put(symbolmap, provider.symbolmap, FIXTURE_SYMBOL, &inserted); + if (inserted == -1 || key == kh_end(provider.symbolmap)) { + fprintf(stderr, "cannot add wrapper manifest entry\n"); + failed = 1; + goto out; + } + kh_value(provider.symbolmap, key) = wrapper; + scope.libraries = libraries; + scope.libsz = 1; + scope.context = &context; + context.maplib = &scope; + provider.name = libc_name; + provider.path = libc_name; + provider.type = LIB_WRAPPED; + provider.active = 1; + provider.context = &context; + provider.priv.w.bridge = bridge; + + native_symbol = dlsym(provider.priv.w.lib, FIXTURE_SYMBOL); + if (!native_symbol || CheckBridged(bridge, native_symbol)) { + fprintf(stderr, "native fixture is unavailable or already bridged\n"); + failed = 1; + goto out; + } + first = kzt_rela_runtime_select_exact_wrapper_bridge_retained( + &context, &handle, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL); + second = kzt_rela_runtime_select_exact_wrapper_bridge_retained( + &context, &handle, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL); + if (!first || second != first || + CheckBridged(bridge, native_symbol) != first || + !kzt_bridge_is_exact(first, wrapper, native_symbol)) { + fprintf(stderr, + "real selector did not create and reuse one exact bridge\n"); + failed = 1; + } + +out: + if (provider.symbolmap) kh_destroy(symbolmap, provider.symbolmap); + if (provider.priv.w.lib) dlclose(provider.priv.w.lib); + if (bridge) FreeBridge(&bridge); + return failed; +} diff --git a/tests/unit/kzt/test_wi256_lazy_binding.c b/tests/unit/kzt/test_wi256_lazy_binding.c deleted file mode 100644 index 6e1482e1f43..00000000000 --- a/tests/unit/kzt/test_wi256_lazy_binding.c +++ /dev/null @@ -1,543 +0,0 @@ -#include -#include - -#include "target/i386/latx/include/kzt_lazy_binding.h" - -typedef struct fixture { - uintptr_t slot; - uintptr_t guest_target; - uintptr_t native_bridge; - uintptr_t competing_target; - int post_bind_valid; - kzt_lazy_binding_route_status_t route_status; - int load_calls; - int validate_calls; - int route_calls; -} fixture_t; - -static int failures; - -#define CHECK(name, condition) do { \ - if (!(condition)) { \ - fprintf(stderr, "%s failed at line %d\n", name, __LINE__); \ - ++failures; \ - } \ -} while (0) - -static int load_slot(uintptr_t slot_addr, uintptr_t *value, void *opaque) -{ - fixture_t *fixture = opaque; - - ++fixture->load_calls; - *value = *(uintptr_t *)slot_addr; - return 0; -} - -static int validate_post_bind(const kzt_lazy_binding_pending_t *pending, - uintptr_t guest_target, void *opaque) -{ - fixture_t *fixture = opaque; - - ++fixture->validate_calls; - CHECK("validate.slot", pending->slot_addr == (uintptr_t)&fixture->slot); - CHECK("validate.guest-target", guest_target == fixture->guest_target); - return fixture->post_bind_valid; -} - -static int route_guest_target(const kzt_lazy_binding_pending_t *pending, - uintptr_t guest_target, - kzt_lazy_binding_route_result_t *result, - void *opaque) -{ - fixture_t *fixture = opaque; - - ++fixture->route_calls; - CHECK("route.main-namespace", pending->namespace_id == 0); - CHECK("route.main-namespace-kind", - pending->namespace_kind == KZT_GUEST_LIBRARY_NAMESPACE_MAIN); - CHECK("route.version-evidence", - kzt_symbol_version_evidence_valid(pending->version_evidence, - pending->version)); - CHECK("route.guest-target", guest_target == fixture->guest_target); - result->status = fixture->route_status; - switch (fixture->route_status) { - case KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED: - result->selected_target = fixture->native_bridge; - result->final_value = fixture->native_bridge; - fixture->slot = fixture->native_bridge; - break; - case KZT_LAZY_BINDING_ROUTE_CAS_MISMATCH: - result->selected_target = fixture->competing_target; - result->final_value = fixture->competing_target; - fixture->slot = fixture->competing_target; - break; - case KZT_LAZY_BINDING_ROUTE_WRITE_ROLLED_BACK: - result->selected_target = guest_target; - result->final_value = guest_target; - fixture->slot = guest_target; - break; - case KZT_LAZY_BINDING_ROUTE_UNRECOVERABLE: - result->selected_target = fixture->competing_target; - result->final_value = fixture->competing_target; - fixture->slot = fixture->competing_target; - break; - case KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED: - case KZT_LAZY_BINDING_ROUTE_ERROR: - result->selected_target = guest_target; - result->final_value = guest_target; - break; - } - return 0; -} - -static fixture_t fixture(void) -{ - return (fixture_t) { - .slot = 0x71000100, - .guest_target = 0x72000200, - .native_bridge = 0x73000300, - .competing_target = 0x74000400, - .post_bind_valid = 1, - .route_status = KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED, - }; -} - -static kzt_lazy_binding_begin_request_t begin_request_for(fixture_t *fixture) -{ - return (kzt_lazy_binding_begin_request_t) { - .enabled = 1, - .context_id = 0x1000, - .source_link_map = 0x2000, - .source_generation = 7, - .namespace_id = 0, - .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, - .relocation_index = 3, - .slot_addr = (uintptr_t)&fixture->slot, - .unresolved_stub = fixture->slot, - .symbol = "realloc", - .version_evidence = KZT_SYMBOL_VERSION_VERSIONED, - .version = "GLIBC_2.2.5", - .guest_resolver = 0x75000500, - }; -} - -static kzt_lazy_binding_ops_t ops_for(fixture_t *fixture) -{ - return (kzt_lazy_binding_ops_t) { - .load_slot = load_slot, - .validate_post_bind = validate_post_bind, - .route_guest_target = route_guest_target, - .opaque = fixture, - }; -} - -static void begin_binding(kzt_lazy_binding_begin_request_t *request, - kzt_lazy_binding_pending_t *pending, - kzt_lazy_binding_result_t *result) -{ - memset(pending, 0, sizeof(*pending)); - CHECK("begin.call", - kzt_lazy_binding_begin(request, pending, result) == 0); -} - -static void test_first_call_hands_off_to_guest(void) -{ - fixture_t f = fixture(); - kzt_lazy_binding_begin_request_t request = begin_request_for(&f); - kzt_lazy_binding_result_t result; - kzt_lazy_binding_pending_t pending; - - begin_binding(&request, &pending, &result); - - CHECK("first.status", - result.status == KZT_LAZY_BINDING_HANDOFF_GUEST); - CHECK("first.armed", pending.armed == 1); - CHECK("first.result-armed", result.pending_armed == 1); - CHECK("first.guest-resolver", - result.selected_target == request.guest_resolver); - CHECK("first.slot-still-stub", f.slot == request.unresolved_stub); - CHECK("first.no-route", f.route_calls == 0); -} - -static void test_post_bind_call_can_install_native_bridge(void) -{ - fixture_t f = fixture(); - kzt_lazy_binding_begin_request_t request = begin_request_for(&f); - kzt_lazy_binding_ops_t ops = ops_for(&f); - kzt_lazy_binding_result_t result; - kzt_lazy_binding_pending_t pending; - - begin_binding(&request, &pending, &result); - - /* This write represents the guest dynamic linker completing call one. */ - f.slot = f.guest_target; - CHECK("complete.call", - kzt_lazy_binding_complete(&pending, &ops, &result) == 0); - CHECK("complete.status", - result.status == KZT_LAZY_BINDING_NATIVE_APPLIED); - CHECK("complete.before", result.slot_before == f.guest_target); - CHECK("complete.after", result.slot_after == f.native_bridge); - CHECK("complete.slot", f.slot == f.native_bridge); - CHECK("complete.one-route", f.route_calls == 1); - CHECK("complete.consumed", result.pending_armed == 0); -} - -static void test_expected_target_mismatch_preserves_competitor_without_retry(void) -{ - fixture_t f = fixture(); - kzt_lazy_binding_begin_request_t request = begin_request_for(&f); - kzt_lazy_binding_ops_t ops = ops_for(&f); - kzt_lazy_binding_result_t result; - kzt_lazy_binding_pending_t pending; - - begin_binding(&request, &pending, &result); - - f.slot = f.guest_target; - f.route_status = KZT_LAZY_BINDING_ROUTE_CAS_MISMATCH; - CHECK("race.call", - kzt_lazy_binding_complete(&pending, &ops, &result) == 0); - CHECK("race.status", result.status == KZT_LAZY_BINDING_CAS_MISMATCH); - CHECK("race.preserve", f.slot == f.competing_target); - CHECK("race.report-final", result.slot_after == f.competing_target); - CHECK("race.one-route", f.route_calls == 1); - CHECK("race.consumed", result.pending_armed == 0); -} - -static void test_transaction_failure_statuses_are_not_guest_preserved(void) -{ - const struct { - kzt_lazy_binding_route_status_t route_status; - kzt_lazy_binding_status_t status; - kzt_lazy_binding_reason_t reason; - uintptr_t final_value; - } cases[] = { - { - KZT_LAZY_BINDING_ROUTE_WRITE_ROLLED_BACK, - KZT_LAZY_BINDING_WRITE_ROLLED_BACK, - KZT_LAZY_BINDING_REASON_WRITE_ROLLED_BACK, - 0, - }, - { - KZT_LAZY_BINDING_ROUTE_UNRECOVERABLE, - KZT_LAZY_BINDING_UNRECOVERABLE, - KZT_LAZY_BINDING_REASON_UNRECOVERABLE, - 1, - }, - }; - size_t i; - - for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { - fixture_t f = fixture(); - kzt_lazy_binding_begin_request_t request = begin_request_for(&f); - kzt_lazy_binding_ops_t ops = ops_for(&f); - kzt_lazy_binding_result_t result; - kzt_lazy_binding_pending_t pending; - - begin_binding(&request, &pending, &result); - f.slot = f.guest_target; - f.route_status = cases[i].route_status; - CHECK("transaction-status.complete", - kzt_lazy_binding_complete(&pending, &ops, &result) == 0); - CHECK("transaction-status.status", result.status == cases[i].status); - CHECK("transaction-status.reason", result.reason == cases[i].reason); - CHECK("transaction-status.final", - result.slot_after == (cases[i].final_value ? - f.competing_target : f.guest_target)); - } -} - -static void test_missing_symbol_version_fails_open(void) -{ - fixture_t f = fixture(); - kzt_lazy_binding_begin_request_t request = begin_request_for(&f); - kzt_lazy_binding_ops_t ops = ops_for(&f); - kzt_lazy_binding_result_t result; - kzt_lazy_binding_pending_t pending; - - request.version = NULL; - begin_binding(&request, &pending, &result); - f.slot = f.guest_target; - CHECK("version.complete", - kzt_lazy_binding_complete(&pending, &ops, &result) == 0); - CHECK("version.status", - result.status == KZT_LAZY_BINDING_GUEST_PRESERVED); - CHECK("version.reason", - result.reason == KZT_LAZY_BINDING_REASON_MISSING_VERSION); - CHECK("version.slot", f.slot == f.guest_target); - CHECK("version.no-route", f.route_calls == 0); - CHECK("version.consumed", result.pending_armed == 0); -} - -static void test_confirmed_unversioned_binding_can_apply(void) -{ - fixture_t f = fixture(); - kzt_lazy_binding_begin_request_t request = begin_request_for(&f); - kzt_lazy_binding_ops_t ops = ops_for(&f); - kzt_lazy_binding_result_t result; - kzt_lazy_binding_pending_t pending; - - request.version_evidence = KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; - request.version = NULL; - begin_binding(&request, &pending, &result); - CHECK("unversioned.begin", - result.status == KZT_LAZY_BINDING_HANDOFF_GUEST && - pending.version_evidence == - KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED && - pending.version == NULL); - f.slot = f.guest_target; - CHECK("unversioned.complete", - kzt_lazy_binding_complete(&pending, &ops, &result) == 0); - CHECK("unversioned.applied", - result.status == KZT_LAZY_BINDING_NATIVE_APPLIED && - f.route_calls == 1 && f.slot == f.native_bridge); -} - -static void test_unknown_and_error_version_evidence_fail_open(void) -{ - kzt_symbol_version_evidence_t evidence[] = { - KZT_SYMBOL_VERSION_UNKNOWN, - KZT_SYMBOL_VERSION_ERROR, - }; - size_t i; - - for (i = 0; i < sizeof(evidence) / sizeof(evidence[0]); ++i) { - fixture_t f = fixture(); - kzt_lazy_binding_begin_request_t request = begin_request_for(&f); - kzt_lazy_binding_ops_t ops = ops_for(&f); - kzt_lazy_binding_result_t result; - kzt_lazy_binding_pending_t pending; - - request.version_evidence = evidence[i]; - begin_binding(&request, &pending, &result); - CHECK("untrusted.evidence-propagated", - pending.version_evidence == evidence[i]); - f.slot = f.guest_target; - CHECK("untrusted.complete", - kzt_lazy_binding_complete(&pending, &ops, &result) == 0); - CHECK("untrusted.preserved", - result.status == KZT_LAZY_BINDING_GUEST_PRESERVED && - result.reason == KZT_LAZY_BINDING_REASON_MISSING_VERSION && - f.route_calls == 0 && f.slot == f.guest_target); - } -} - -static void test_post_bind_validation_failure_keeps_guest_target(void) -{ - fixture_t f = fixture(); - kzt_lazy_binding_begin_request_t request = begin_request_for(&f); - kzt_lazy_binding_ops_t ops = ops_for(&f); - kzt_lazy_binding_result_t result; - kzt_lazy_binding_pending_t pending; - - begin_binding(&request, &pending, &result); - - f.slot = f.guest_target; - f.post_bind_valid = 0; - CHECK("validation.complete", - kzt_lazy_binding_complete(&pending, &ops, &result) == 0); - CHECK("validation.status", - result.status == KZT_LAZY_BINDING_GUEST_PRESERVED); - CHECK("validation.reason", - result.reason == KZT_LAZY_BINDING_REASON_POST_BIND_INVALID); - CHECK("validation.slot", f.slot == f.guest_target); - CHECK("validation.one-check", f.validate_calls == 1); - CHECK("validation.no-route", f.route_calls == 0); - CHECK("validation.consumed", result.pending_armed == 0); -} - -static void test_slot_unchanged_consumes_pending(void) -{ - fixture_t f = fixture(); - kzt_lazy_binding_begin_request_t request = begin_request_for(&f); - kzt_lazy_binding_ops_t ops = ops_for(&f); - kzt_lazy_binding_result_t result; - kzt_lazy_binding_pending_t pending; - - begin_binding(&request, &pending, &result); - - CHECK("unchanged.complete", - kzt_lazy_binding_complete(&pending, &ops, &result) == 0); - CHECK("unchanged.status", - result.status == KZT_LAZY_BINDING_GUEST_PRESERVED); - CHECK("unchanged.reason", - result.reason == KZT_LAZY_BINDING_REASON_SLOT_UNCHANGED); - CHECK("unchanged.no-route", f.route_calls == 0); - CHECK("unchanged.consumed", pending.armed == 0 && - result.pending_cleared == 1); -} - -static void test_overlong_symbol_and_version_fail_open(void) -{ - char overlong[KZT_LAZY_BINDING_SYMBOL_MAX + 1]; - fixture_t f = fixture(); - kzt_lazy_binding_begin_request_t request = begin_request_for(&f); - kzt_lazy_binding_result_t result; - kzt_lazy_binding_pending_t pending; - - memset(overlong, 'x', sizeof(overlong) - 1); - overlong[sizeof(overlong) - 1] = '\0'; - memset(&pending, 0, sizeof(pending)); - request.symbol = overlong; - CHECK("long-symbol.begin", - kzt_lazy_binding_begin(&request, &pending, &result) == 0); - CHECK("long-symbol.preserved", - result.status == KZT_LAZY_BINDING_GUEST_PRESERVED && - result.reason == KZT_LAZY_BINDING_REASON_INVALID_REQUEST && - pending.armed == 0); - - memset(&pending, 0, sizeof(pending)); - request = begin_request_for(&f); - request.version = overlong; - CHECK("long-version.begin", - kzt_lazy_binding_begin(&request, &pending, &result) == 0); - CHECK("long-version.preserved", - result.status == KZT_LAZY_BINDING_GUEST_PRESERVED && - result.reason == KZT_LAZY_BINDING_REASON_INVALID_REQUEST && - pending.armed == 0); -} - -static void test_nested_pending_busy_preserves_outer_state(void) -{ - fixture_t f = fixture(); - kzt_lazy_binding_begin_request_t outer = begin_request_for(&f); - kzt_lazy_binding_begin_request_t nested = begin_request_for(&f); - kzt_lazy_binding_result_t result; - kzt_lazy_binding_pending_t pending; - kzt_lazy_binding_pending_t saved; - - begin_binding(&outer, &pending, &result); - saved = pending; - saved.symbol = pending.symbol ? saved.symbol_storage : NULL; - saved.version = pending.version ? saved.version_storage : NULL; - - nested.relocation_index = outer.relocation_index + 1; - nested.slot_addr += sizeof(uintptr_t); - CHECK("nested.begin", - kzt_lazy_binding_begin(&nested, &pending, &result) == 0); - CHECK("nested.busy", - result.status == KZT_LAZY_BINDING_GUEST_PRESERVED && - result.reason == KZT_LAZY_BINDING_REASON_PENDING_BUSY); - CHECK("nested.outer-armed", pending.armed == 1); - CHECK("nested.outer-identity", - pending.context_id == saved.context_id && - pending.source_link_map == saved.source_link_map && - pending.source_generation == saved.source_generation && - pending.relocation_index == saved.relocation_index && - pending.slot_addr == saved.slot_addr && - !strcmp(pending.symbol, saved.symbol) && - !strcmp(pending.version, saved.version)); -} - -static void test_new_context_replaces_abandoned_pending(void) -{ - fixture_t f = fixture(); - kzt_lazy_binding_begin_request_t abandoned = begin_request_for(&f); - kzt_lazy_binding_begin_request_t next = begin_request_for(&f); - kzt_lazy_binding_result_t result; - kzt_lazy_binding_pending_t pending; - - begin_binding(&abandoned, &pending, &result); - - next.context_id = abandoned.context_id + 1; - next.source_link_map = abandoned.source_link_map + 0x1000; - next.source_generation = abandoned.source_generation + 1; - next.relocation_index = abandoned.relocation_index + 1; - next.slot_addr += sizeof(uintptr_t); - next.symbol = "next_context_symbol"; - - CHECK("new-context.begin", - kzt_lazy_binding_begin(&next, &pending, &result) == 0); - CHECK("new-context.handoff", - result.status == KZT_LAZY_BINDING_HANDOFF_GUEST && - result.reason == KZT_LAZY_BINDING_REASON_NONE && - result.pending_armed == 1); - CHECK("new-context.replaced", - pending.armed == 1 && - pending.context_id == next.context_id && - pending.source_link_map == next.source_link_map && - pending.source_generation == next.source_generation && - pending.relocation_index == next.relocation_index && - pending.slot_addr == next.slot_addr && - !strcmp(pending.symbol, next.symbol)); -} - -static void test_cancelled_pending_allows_same_context_retry(void) -{ - fixture_t f = fixture(); - kzt_lazy_binding_begin_request_t first = begin_request_for(&f); - kzt_lazy_binding_begin_request_t retry = begin_request_for(&f); - kzt_lazy_binding_result_t result; - kzt_lazy_binding_pending_t pending; - - begin_binding(&first, &pending, &result); - - kzt_lazy_binding_cancel(&pending); - CHECK("cancel.cleared", pending.armed == 0 && - pending.context_id == 0 && pending.source_link_map == 0 && - pending.slot_addr == 0 && pending.symbol == NULL && - pending.version == NULL); - - retry.relocation_index = first.relocation_index + 1; - retry.slot_addr += sizeof(uintptr_t); - retry.symbol = "retry_symbol"; - CHECK("cancel.retry", - kzt_lazy_binding_begin(&retry, &pending, &result) == 0); - CHECK("cancel.retry-armed", - result.status == KZT_LAZY_BINDING_HANDOFF_GUEST && - result.pending_armed == 1 && pending.armed == 1 && - pending.context_id == retry.context_id && - pending.relocation_index == retry.relocation_index && - pending.slot_addr == retry.slot_addr && - !strcmp(pending.symbol, retry.symbol)); -} - -static void test_only_main_namespace_is_armed(void) -{ - fixture_t f = fixture(); - kzt_lazy_binding_begin_request_t request = begin_request_for(&f); - kzt_lazy_binding_result_t result; - kzt_lazy_binding_pending_t pending; - - request.namespace_id = 1; - request.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_EXPLICIT; - begin_binding(&request, &pending, &result); - CHECK("namespace.status", - result.status == KZT_LAZY_BINDING_GUEST_PRESERVED); - CHECK("namespace.reason", - result.reason == KZT_LAZY_BINDING_REASON_NON_MAIN_NAMESPACE); - CHECK("namespace.not-armed", pending.armed == 0); - CHECK("namespace.guest-resolver", - result.selected_target == request.guest_resolver); - CHECK("namespace.slot", f.slot == request.unresolved_stub); - - request.namespace_id = 0; - request.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN; - begin_binding(&request, &pending, &result); - CHECK("main.status", - result.status == KZT_LAZY_BINDING_HANDOFF_GUEST); - CHECK("main.armed", pending.armed == 1); -} - -int main(void) -{ - test_first_call_hands_off_to_guest(); - test_post_bind_call_can_install_native_bridge(); - test_expected_target_mismatch_preserves_competitor_without_retry(); - test_transaction_failure_statuses_are_not_guest_preserved(); - test_missing_symbol_version_fails_open(); - test_confirmed_unversioned_binding_can_apply(); - test_unknown_and_error_version_evidence_fail_open(); - test_post_bind_validation_failure_keeps_guest_target(); - test_only_main_namespace_is_armed(); - test_slot_unchanged_consumes_pending(); - test_overlong_symbol_and_version_fail_open(); - test_nested_pending_busy_preserves_outer_state(); - test_new_context_replaces_abandoned_pending(); - test_cancelled_pending_allows_same_context_retry(); - if (failures) { - fprintf(stderr, "%d WI-256 lazy-binding checks failed\n", failures); - return 1; - } - puts("KZT WI-256 lazy binding: PASS"); - return 0; -} diff --git a/tests/unit/kzt/test_wi256_lazy_diagnostics.c b/tests/unit/kzt/test_wi256_lazy_diagnostics.c deleted file mode 100644 index 3cb355e9156..00000000000 --- a/tests/unit/kzt/test_wi256_lazy_diagnostics.c +++ /dev/null @@ -1,392 +0,0 @@ -#include -#include -#include -#include - -#include "target/i386/latx/include/kzt_lazy_binding.h" -#include "target/i386/latx/include/kzt_lazy_diagnostics.h" - -static int failures; - -static void check_true(const char *name, int condition) -{ - if (condition) { - return; - } - - fprintf(stderr, "%s: condition failed\n", name); - ++failures; -} - -static void check_int(const char *name, int got, int expected) -{ - if (got == expected) { - return; - } - - fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); - ++failures; -} - -static void check_ulong(const char *name, unsigned long got, - unsigned long expected) -{ - if (got == expected) { - return; - } - - fprintf(stderr, "%s: got %lu expected %lu\n", name, got, expected); - ++failures; -} - -static void check_string(const char *name, const char *got, - const char *expected) -{ - if (got && expected && strcmp(got, expected) == 0) { - return; - } - - fprintf(stderr, "%s: got \"%s\" expected \"%s\"\n", name, - got ? got : "(null)", expected ? expected : "(null)"); - ++failures; -} - -static void check_contains(const char *name, const char *text, - const char *needle) -{ - if (text && needle && strstr(text, needle)) { - return; - } - - fprintf(stderr, "%s: \"%s\" missing \"%s\"\n", name, - text ? text : "(null)", needle ? needle : "(null)"); - ++failures; -} - -typedef struct capture_sink { - int calls; - int fail; - char line[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; -} capture_sink_t; - -static int capture_line(const char *line, size_t line_length, void *opaque) -{ - capture_sink_t *capture = opaque; - size_t copy_length = line_length; - - ++capture->calls; - if (copy_length >= sizeof(capture->line)) { - copy_length = sizeof(capture->line) - 1; - } - memcpy(capture->line, line, copy_length); - capture->line[copy_length] = '\0'; - return capture->fail ? -1 : 0; -} - -static kzt_lazy_binding_pending_t base_pending(void) -{ - kzt_lazy_binding_pending_t pending; - - memset(&pending, 0, sizeof(pending)); - pending.armed = 1; - pending.slot_addr = 0x71000018; - pending.unresolved_stub = 0x72000020; - strcpy(pending.symbol_storage, "realloc"); - pending.symbol = pending.symbol_storage; - strcpy(pending.version_storage, "GLIBC_2.2.5"); - pending.version = pending.version_storage; - return pending; -} - -static kzt_lazy_binding_result_t base_result(void) -{ - kzt_lazy_binding_result_t result; - - memset(&result, 0, sizeof(result)); - result.status = KZT_LAZY_BINDING_NATIVE_APPLIED; - result.reason = KZT_LAZY_BINDING_REASON_NATIVE_APPLIED; - result.slot_before = 0x73000030; - result.slot_after = 0x74000040; - result.selected_target = 0x74000040; - return result; -} - -static void test_emit_disabled_skips_format_and_sink(void) -{ - kzt_lazy_binding_pending_t pending = base_pending(); - kzt_lazy_binding_result_t binding_result = base_result(); - capture_sink_t capture = { 0 }; - char buffer[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; - kzt_lazy_diagnostic_input_t input = { - .enabled = 0, - .pending = &pending, - .binding_result = &binding_result, - .buffer = buffer, - .buffer_size = sizeof(buffer), - .sink = capture_line, - .sink_opaque = &capture, - }; - kzt_lazy_diagnostic_emit_result_t emit_result; - - memset(buffer, 'X', sizeof(buffer)); - check_int("disabled.emit", - kzt_lazy_diagnostics_emit(&input, &emit_result), 0); - check_int("disabled.status", emit_result.status, - KZT_LAZY_DIAGNOSTIC_EMIT_DISABLED); - check_int("disabled.sink", capture.calls, 0); - check_int("disabled.record", emit_result.record_present, 0); - check_int("disabled.buffer-untouched", buffer[0], 'X'); -} - -static void test_native_applied_records_schema_one(void) -{ - kzt_lazy_binding_pending_t pending = base_pending(); - kzt_lazy_binding_result_t binding_result = base_result(); - capture_sink_t capture = { 0 }; - char buffer[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; - kzt_lazy_diagnostic_input_t input = { - .enabled = 1, - .pending = &pending, - .binding_result = &binding_result, - .buffer = buffer, - .buffer_size = sizeof(buffer), - .sink = capture_line, - .sink_opaque = &capture, - }; - kzt_lazy_diagnostic_emit_result_t emit_result; - - check_int("native.emit", - kzt_lazy_diagnostics_emit(&input, &emit_result), 0); - check_int("native.status", emit_result.status, - KZT_LAZY_DIAGNOSTIC_EMIT_EMITTED); - check_int("native.record", emit_result.record_present, 1); - check_ulong("native.schema", emit_result.record.schema, 1); - check_string("native.symbol", emit_result.record.symbol, "realloc"); - check_string("native.first-route", - emit_result.record.first_execution_route, "guest"); - check_string("native.route-status", - emit_result.record.completion_route_status, - "NATIVE_APPLIED"); - check_ulong("native.slot-before", emit_result.record.slot_before, - pending.unresolved_stub); - check_ulong("native.slot-after-guest", - emit_result.record.slot_after_guest, - binding_result.slot_before); - check_ulong("native.second-target", - emit_result.record.selected_second_target, - binding_result.slot_after); - check_string("native.reason", emit_result.record.reason, - "NATIVE_APPLIED"); - check_int("native.sink", capture.calls, 1); - check_contains("native.line.schema", capture.line, "schema=1"); - check_contains("native.line.symbol", capture.line, "symbol=realloc"); - check_contains("native.line.route", capture.line, - "completion_route_status=NATIVE_APPLIED"); - check_contains("native.line.before", capture.line, - "slot_before=0x72000020"); - check_contains("native.line.after-guest", capture.line, - "slot_after_guest=0x73000030"); - check_contains("native.line.second-target", capture.line, - "selected_second_target=0x74000040"); -} - -static void test_slot_unchanged_uses_stable_status_name(void) -{ - kzt_lazy_binding_pending_t pending = base_pending(); - kzt_lazy_binding_result_t binding_result = base_result(); - capture_sink_t capture = { 0 }; - char buffer[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; - kzt_lazy_diagnostic_input_t input = { - .enabled = 1, - .pending = &pending, - .binding_result = &binding_result, - .buffer = buffer, - .buffer_size = sizeof(buffer), - .sink = capture_line, - .sink_opaque = &capture, - }; - kzt_lazy_diagnostic_emit_result_t emit_result; - - binding_result.status = KZT_LAZY_BINDING_GUEST_PRESERVED; - binding_result.reason = KZT_LAZY_BINDING_REASON_SLOT_UNCHANGED; - binding_result.slot_before = pending.unresolved_stub; - binding_result.slot_after = pending.unresolved_stub; - check_int("slot-unchanged.emit", - kzt_lazy_diagnostics_emit(&input, &emit_result), 0); - check_string("slot-unchanged.status", - emit_result.record.completion_route_status, - "SLOT_UNCHANGED"); - check_string("slot-unchanged.reason", emit_result.record.reason, - "SLOT_UNCHANGED"); -} - -static void test_missing_version_uses_stable_status_name(void) -{ - kzt_lazy_binding_pending_t pending = base_pending(); - kzt_lazy_binding_result_t binding_result = base_result(); - capture_sink_t capture = { 0 }; - char buffer[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; - kzt_lazy_diagnostic_input_t input = { - .enabled = 1, - .pending = &pending, - .binding_result = &binding_result, - .buffer = buffer, - .buffer_size = sizeof(buffer), - .sink = capture_line, - .sink_opaque = &capture, - }; - kzt_lazy_diagnostic_emit_result_t emit_result; - - pending.version = NULL; - pending.version_storage[0] = '\0'; - binding_result.status = KZT_LAZY_BINDING_GUEST_PRESERVED; - binding_result.reason = KZT_LAZY_BINDING_REASON_MISSING_VERSION; - binding_result.slot_after = binding_result.slot_before; - check_int("missing-version.emit", - kzt_lazy_diagnostics_emit(&input, &emit_result), 0); - check_string("missing-version.status", - emit_result.record.completion_route_status, - "MISSING_VERSION"); - check_string("missing-version.reason", emit_result.record.reason, - "MISSING_VERSION"); -} - -static void test_cas_mismatch_uses_final_competitor_target(void) -{ - kzt_lazy_binding_pending_t pending = base_pending(); - kzt_lazy_binding_result_t binding_result = base_result(); - capture_sink_t capture = { 0 }; - char buffer[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; - kzt_lazy_diagnostic_input_t input = { - .enabled = 1, - .pending = &pending, - .binding_result = &binding_result, - .buffer = buffer, - .buffer_size = sizeof(buffer), - .sink = capture_line, - .sink_opaque = &capture, - }; - kzt_lazy_diagnostic_emit_result_t emit_result; - - binding_result.status = KZT_LAZY_BINDING_CAS_MISMATCH; - binding_result.reason = KZT_LAZY_BINDING_REASON_CAS_MISMATCH; - binding_result.slot_after = 0x75000050; - binding_result.selected_target = 0x76000060; - check_int("cas.emit", - kzt_lazy_diagnostics_emit(&input, &emit_result), 0); - check_string("cas.status", emit_result.record.completion_route_status, - "CAS_MISMATCH"); - check_ulong("cas.second-target", - emit_result.record.selected_second_target, - 0x75000050); - check_contains("cas.line.second-target", capture.line, - "selected_second_target=0x75000050"); -} - -static void test_transaction_failure_statuses_are_explicit(void) -{ - const struct { - kzt_lazy_binding_status_t status; - kzt_lazy_binding_reason_t reason; - const char *name; - } cases[] = { - { - KZT_LAZY_BINDING_WRITE_ROLLED_BACK, - KZT_LAZY_BINDING_REASON_WRITE_ROLLED_BACK, - "WRITE_ROLLED_BACK", - }, - { - KZT_LAZY_BINDING_UNRECOVERABLE, - KZT_LAZY_BINDING_REASON_UNRECOVERABLE, - "UNRECOVERABLE", - }, - }; - size_t i; - - for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { - kzt_lazy_binding_pending_t pending = base_pending(); - kzt_lazy_binding_result_t binding_result = base_result(); - capture_sink_t capture = { 0 }; - char buffer[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; - kzt_lazy_diagnostic_input_t input = { - .enabled = 1, - .pending = &pending, - .binding_result = &binding_result, - .buffer = buffer, - .buffer_size = sizeof(buffer), - .sink = capture_line, - .sink_opaque = &capture, - }; - kzt_lazy_diagnostic_emit_result_t emit_result; - - binding_result.status = cases[i].status; - binding_result.reason = cases[i].reason; - check_int("transaction-diagnostic.emit", - kzt_lazy_diagnostics_emit(&input, &emit_result), 0); - check_string("transaction-diagnostic.status", - emit_result.record.completion_route_status, - cases[i].name); - check_string("transaction-diagnostic.reason", - emit_result.record.reason, cases[i].name); - check_contains("transaction-diagnostic.line", capture.line, - cases[i].name); - } -} - -static void test_production_emit_writes_stderr(void) -{ - kzt_lazy_binding_pending_t pending = base_pending(); - kzt_lazy_binding_result_t binding_result = base_result(); - kzt_lazy_diagnostic_emit_result_t emit_result; - int saved_stderr; - int pipefd[2]; - char output[KZT_LAZY_DIAGNOSTIC_LINE_LIMIT]; - ssize_t count; - - check_int("production.pipe", pipe(pipefd), 0); - saved_stderr = dup(STDERR_FILENO); - check_true("production.saved-stderr", saved_stderr >= 0); - check_true("production.redirect", - dup2(pipefd[1], STDERR_FILENO) >= 0); - close(pipefd[1]); - - check_int("production.enabled.emit", - kzt_lazy_diagnostics_emit_production( - &pending, &binding_result, &emit_result), - 0); - check_int("production.enabled.status", emit_result.status, - KZT_LAZY_DIAGNOSTIC_EMIT_EMITTED); - - fflush(stderr); - check_true("production.restore", dup2(saved_stderr, STDERR_FILENO) >= 0); - close(saved_stderr); - - count = read(pipefd[0], output, sizeof(output) - 1); - check_true("production.read", count > 0); - if (count > 0) { - output[count] = '\0'; - check_contains("production.output.schema", output, "schema=1"); - check_contains("production.output.symbol", output, "symbol=realloc"); - } - close(pipefd[0]); -} - -int main(void) -{ - test_emit_disabled_skips_format_and_sink(); - test_native_applied_records_schema_one(); - test_slot_unchanged_uses_stable_status_name(); - test_missing_version_uses_stable_status_name(); - test_cas_mismatch_uses_final_competitor_target(); - test_transaction_failure_statuses_are_explicit(); - test_production_emit_writes_stderr(); - - if (failures) { - fprintf(stderr, "kzt-wi256-lazy-diagnostics: %d failure(s)\n", - failures); - return 1; - } - - puts("KZT WI-256 lazy diagnostics: PASS"); - return 0; -} diff --git a/tests/unit/kzt/test_wi256_lazy_diagnostics_source_contract.py b/tests/unit/kzt/test_wi256_lazy_diagnostics_source_contract.py deleted file mode 100644 index 7aa48daab30..00000000000 --- a/tests/unit/kzt/test_wi256_lazy_diagnostics_source_contract.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python3 -import pathlib -import re -import sys - - -def function_body(text: str, signature: str) -> str: - start = -1 - search_from = 0 - while True: - start = text.index(signature, search_from) - after_signature = start + len(signature) - brace = text.index("{", after_signature) - semicolon = text.find(";", after_signature, brace) - if semicolon < 0: - break - search_from = after_signature - depth = 0 - for pos in range(brace, len(text)): - if text[pos] == "{": - depth += 1 - elif text[pos] == "}": - depth -= 1 - if depth == 0: - return text[brace + 1:pos] - raise AssertionError(f"unterminated function: {signature}") - - -def braced_block(text: str, brace: int) -> tuple[str, int]: - depth = 0 - for pos in range(brace, len(text)): - if text[pos] == "{": - depth += 1 - elif text[pos] == "}": - depth -= 1 - if depth == 0: - return text[brace + 1:pos], pos + 1 - raise AssertionError("unterminated block") - - -root = pathlib.Path(sys.argv[1]).resolve() -main = (root / "linux-user/main.c").read_text(encoding="utf-8") -options = (root / "target/i386/latx/include/latx-options.h").read_text( - encoding="utf-8" -) -options_c = (root / "target/i386/latx/latx-options.c").read_text( - encoding="utf-8" -) -elfloader = (root / "target/i386/latx/context/elfloader.c").read_text( - encoding="utf-8" -) -diagnostics = ( - root / "target/i386/latx/context/kzt_lazy_diagnostics.c" -).read_text(encoding="utf-8") - -# The lazy diagnostics flag is independent and closed by default. -assert re.search(r"\bint\s+option_kzt_lazy_diagnostics\s*=\s*0\s*;", options_c) -assert not re.search(r"\bint\s+option_kzt_lazy_diagnostics\s*=", main) -assert re.search( - r'"LATX_KZT_LAZY_DIAGNOSTICS"\s*,\s*true\s*,\s*' - r'handle_arg_latx_kzt_lazy_diagnostics', - main, -) -assert "ENVFUN(LATX_KZT_LAZY_DIAGNOSTICS" in options -handler = function_body( - main, "static void handle_arg_latx_kzt_lazy_diagnostics(const char *arg)" -) -assert "option_kzt_lazy_diagnostics" in handler -assert "kzt_registry_diagnostics" not in handler -assert "option_kzt_lazy_diagnostics" not in function_body( - diagnostics, "int kzt_lazy_diagnostics_emit_production(" -) - -resolver = function_body( - elfloader, "static void KztLazyBindingCompleteResolver(void)" -) -armed_start = resolver.index("if (pending->armed)") -armed_brace = resolver.index("{", armed_start) -armed, _ = braced_block(resolver, armed_brace) - -# The option conditional is the sole diagnostics gate on the completion path. -gate = re.search(r"if\s*\(\s*option_kzt_lazy_diagnostics\s*\)\s*\{", armed) -assert gate is not None -enabled_brace = armed.index("{", gate.start()) -enabled, enabled_end = braced_block(armed, enabled_brace) -else_match = re.match(r"\s*else\s*\{", armed[enabled_end:]) -assert else_match is not None -disabled_brace = armed.index("{", enabled_end + else_match.start()) -disabled, _ = braced_block(armed, disabled_brace) - -assert "pending_snapshot = *pending;" in enabled -assert re.search( - r"pending_snapshot\.symbol\s*=\s*pending->symbol\s*\?\s*" - r"pending_snapshot\.symbol_storage\s*:\s*NULL\s*;", - enabled, - re.S, -) -assert re.search( - r"pending_snapshot\.version\s*=\s*pending->version\s*\?\s*" - r"pending_snapshot\.version_storage\s*:\s*NULL\s*;", - enabled, - re.S, -) -assert enabled.index("pending_snapshot = *pending;") < enabled.index( - "kzt_production_lazy_complete(" -) -assert enabled.index("kzt_production_lazy_complete(") < enabled.index( - "kzt_lazy_diagnostics_emit_production(" -) -assert re.search( - r"kzt_lazy_diagnostics_emit_production\s*\(\s*" - r"&pending_snapshot\s*,\s*&result\s*,", - enabled, - re.S, -) -assert "kzt_production_lazy_complete(" in disabled -for forbidden in ("pending_snapshot", "diagnostic", "emit"): - assert forbidden not in disabled - -# Record construction consumes only the pre-clear pending snapshot and the -# existing completion result; it must not rediscover or reroute the binding. -record = function_body(diagnostics, "int kzt_lazy_diagnostic_record_build(") -assert "record->slot_before = pending->unresolved_stub;" in record -assert "record->slot_after_guest = binding_result->slot_before;" in record -assert "record->selected_second_target = binding_result->slot_after;" in record -for forbidden in ( - "owner", - "provider", - "wrapper", - "bridge", - "planner", - "route_guest_target", - "compare_exchange", -): - assert forbidden not in record - -print("WI-256 lazy diagnostics source contract: PASS") diff --git a/tests/unit/kzt/test_wi256_lazy_production_bridge.c b/tests/unit/kzt/test_wi256_lazy_production_bridge.c index 4428b6af819..07be7042c7c 100644 --- a/tests/unit/kzt/test_wi256_lazy_production_bridge.c +++ b/tests/unit/kzt/test_wi256_lazy_production_bridge.c @@ -1,12 +1,9 @@ -#include #include #include #include -#include #include #include #include -#include #include "elf.h" #include "target/i386/latx/include/box64context.h" @@ -14,9 +11,13 @@ #include "target/i386/latx/include/elfloader_private.h" #include "target/i386/latx/include/khash.h" #include "target/i386/latx/include/kzt_guest_dl_api.h" +#include "target/i386/latx/include/kzt_guest_dynsym_lookup.h" +#include "target/i386/latx/include/kzt_guest_library_adapter.h" #include "target/i386/latx/include/kzt_guest_registry.h" #include "target/i386/latx/include/kzt_guest_symbol_scope.h" #include "target/i386/latx/include/kzt_jump_slot_production.h" +#include "target/i386/latx/include/kzt_loader_event_hook.h" +#include "target/i386/latx/include/kzt_patch_spike_writer.h" #include "target/i386/latx/include/kzt_rela_runtime_bridge.h" #include "target/i386/latx/include/librarian_private.h" #include "target/i386/latx/include/library.h" @@ -26,6 +27,7 @@ #define FIXTURE_VERSION "GLIBC_2.2.5" #define SOURCE_LINK_MAP 0x1000 #define PROVIDER_LINK_MAP 0x2000 +#define ALIAS_PROVIDER_LINK_MAP 0x3000 #define SOURCE_START 0x70000000 #define GUEST_TARGET 0x71000020 #define STRESS_ITERATIONS 1000 @@ -33,9 +35,80 @@ static int failures; static uintptr_t fixture_native_symbol; static uintptr_t fixture_native_bridge; +static const char *fixture_symbol_name = FIXTURE_SYMBOL; +static int loader_lifecycle_healthy = 1; int relocation_log; int kzt_registry_diagnostics; +const char *kzt_guest_library_wrapper_name_for_guest(const char *guest_name) +{ + if (!guest_name || !guest_name[0]) { + return NULL; + } + return strcmp(guest_name, "libdl.so.2") == 0 ? "libc.so.6" : NULL; +} + +int kzt_guest_library_wrapper_alias_symbol_allowed(const char *symbol) +{ + return symbol && + (strcmp(symbol, "dlsym") == 0 || strcmp(symbol, "dlvsym") == 0); +} + +int kzt_loader_lifecycle_runtime_healthy(box64context_t *context) +{ + return context && loader_lifecycle_healthy; +} + +int kzt_guest_library_wrapper_source_acquire( + box64context_t *context, uintptr_t link_map_addr, + const char *requested_path, const char *wrapper_name, + kzt_guest_wrapper_source_proof_t *proof) +{ + kzt_guest_registry_address_match_t match = { 0 }; + const char *guest_name; + const char *approved_wrapper; + + if (proof) { + memset(proof, 0, sizeof(*proof)); + } + if (!context || !link_map_addr || !requested_path || !wrapper_name || + !proof || kzt_guest_registry_find_live_object( + context->kzt_guest_registry_context.registry, + link_map_addr, &match) != 0 || + !match.generation || + match.namespace_id_status != KZT_GUEST_FIELD_OK || + match.namespace_id != 0) { + return -1; + } + guest_name = strrchr(requested_path, '/'); + guest_name = guest_name ? guest_name + 1 : requested_path; + approved_wrapper = + kzt_guest_library_wrapper_name_for_guest(guest_name); + if (!approved_wrapper || strcmp(approved_wrapper, wrapper_name) != 0 || + kzt_guest_registry_source_lease_acquire( + context->kzt_guest_registry_context.registry, link_map_addr, + match.generation, match.namespace_id, &proof->lease) != 0) { + return -1; + } + proof->key = (kzt_guest_library_binding_key_t) { + .link_map_addr = link_map_addr, + .generation = match.generation, + .namespace_id = match.namespace_id, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + return 0; +} + +void kzt_guest_library_wrapper_source_release( + kzt_guest_wrapper_source_proof_t *proof) +{ + if (!proof) { + return; + } + kzt_guest_registry_source_lease_release(&proof->lease); + memset(proof, 0, sizeof(*proof)); +} + KHASH_MAP_IMPL_STR(symbolmap, wrapper_t) KHASH_MAP_IMPL_STR(symbol2map, symbol2_t) @@ -97,6 +170,15 @@ typedef struct fixture_version_def { Elf64_Verdaux auxiliary; } fixture_version_def_t; +typedef struct fixture_lazy_source { + unsigned long generation; + uintptr_t guest_resolver; + uintptr_t unresolved_stub; + const char *symbol; + kzt_symbol_version_evidence_t version_evidence; + const char *version; +} fixture_lazy_source_t; + typedef struct fixture { box64context_t context; library_t provider; @@ -114,56 +196,48 @@ typedef struct fixture { fixture_scope_link_map_t provider_map; Elf64_Dyn source_scope_dynamic[6]; Elf64_Dyn provider_scope_dynamic[9]; + Elf64_Dyn alias_provider_dynamic[6]; Elf64_Sym source_scope_symbols[1]; Elf64_Sym provider_scope_symbols[2]; + Elf64_Sym alias_provider_symbols[2]; + char dynamic_strings[128]; char source_scope_strings[1]; char provider_scope_strings[32]; + char alias_provider_strings[32]; fixture_sysv_hash_t source_scope_hash; fixture_sysv_hash_t provider_scope_hash; + fixture_sysv_hash_t alias_provider_hash; Elf64_Half provider_scope_versym[2]; + Elf64_Half alias_provider_versym[2]; fixture_version_def_t provider_scope_verdef; + fixture_version_def_t alias_provider_verdef; fixture_scope_elem_t source_scope_elem; uintptr_t source_scope_array[2]; uintptr_t source_scope_maps[2]; uintptr_t slot; - kzt_lazy_binding_pending_t pending; + uintptr_t dlerror_slot; + fixture_lazy_source_t source; kzt_guest_object_observation_t source_observation; kzt_guest_dynamic_view_t dynamic_view; + kzt_guest_dynamic_view_t provider_dynamic_view; + kzt_guest_dynamic_view_t alias_provider_dynamic_view; } fixture_t; typedef enum hook_mode { HOOK_NONE = 0, HOOK_RECYCLE_BEFORE_ACQUIRE, - HOOK_RETIRE_AFTER_CAS, - HOOK_RETIRE_AFTER_CAS_ROLLBACK, HOOK_VIEW_CHANGE_BEFORE_DECISION_ACQUIRE, - HOOK_OWNER_AMBIGUOUS_BEFORE_DECISION_ACQUIRE, - HOOK_SLOT_CONFLICT_BEFORE_VALIDATE, HOOK_SLOT_CONFLICT_BEFORE_FINAL_LOAD, } hook_mode_t; -typedef struct retire_sync { - pthread_mutex_t lock; - pthread_cond_t cond; - fixture_t *fixture; - unsigned long generation; - int go; - int retire_called; - int retire_done; - int retire_result; - int unloading_observed; - int completed_inside_hook; -} retire_sync_t; - static fixture_t *hook_fixture; -static retire_sync_t *hook_retire; static hook_mode_t hook_mode; static int before_acquire_calls; static int source_memory_access_calls; +static int owner_memory_access_calls; +static int owner_memory_all_lifetimes_held; static int slot_load_calls; static int after_cas_calls; -static int lease_seen_at_first_slot_load; -static int force_verify_mismatch; static int shadow_run_calls; static unsigned long recycled_generation; static int permission_begin_calls; @@ -171,8 +245,6 @@ static int permission_end_calls; static int fail_permission_begin; static int fail_permission_end; static int fail_permission_end_once; -static int full_enrich_calls; -static int wrapper_only_enrich_calls; static int decision_lease_active; static int decision_lease_acquires; static int decision_lease_releases; @@ -183,8 +255,6 @@ static int decision_lease_held_at_cas; static int generation_validate_calls; static int runtime_full_lifetime_validation_calls; static pthread_mutex_t mapping_transaction_lock = PTHREAD_MUTEX_INITIALIZER; -static int mapping_lock_calls; -static int mapping_unlock_calls; static int mapping_lock_active; int kzt_jump_slot_production_test_read_guest_memory( @@ -236,37 +306,6 @@ int kzt_jump_slot_production_test_read_guest_memory( return 0; } -typedef struct mapping_mutator_sync { - pthread_mutex_t lock; - pthread_cond_t cond; - pthread_t thread; - int enabled; - int go; - int first_try_complete; - int first_try_error; - int blocked_at_cas; - int route_finished; - int released_after_route; - int mutated; - int mutated_before_permission_end; -} mapping_mutator_sync_t; - -static mapping_mutator_sync_t mapping_mutator; - -typedef struct post_acquire_mutator_sync { - pthread_mutex_t lock; - pthread_cond_t cond; - fixture_t *fixture; - pthread_t thread; - int enabled; - int wait_registered; - int worker_done; - int worker_result; - int done_before_release; -} post_acquire_mutator_sync_t; - -static post_acquire_mutator_sync_t post_acquire_mutator; - uintptr_t CheckBridged(bridge_t *bridge, void *fnc); uintptr_t AddCheckBridge(bridge_t *bridge, wrapper_t wrapper, void *fnc, int stack_bytes, const char *name); @@ -274,6 +313,10 @@ int BridgeForkProtectionAvailable(void); const char *SymName(elfheader_t *head, Elf64_Sym *sym); void kzt_jump_slot_production_test_before_source_lease_acquire(void); void kzt_jump_slot_production_test_before_source_memory_access(void); +void kzt_jump_slot_production_test_before_owner_memory_access( + int source_lease_active, int decision_lease_active, + int quiescence_active, int retained_provider_active, + int owner_lease_active); void kzt_jump_slot_production_test_before_slot_load(void); void kzt_jump_slot_production_test_after_slot_load(uintptr_t *value); void kzt_jump_slot_production_test_after_slot_cas(int exchanged); @@ -318,48 +361,6 @@ static void fixture_iFp(uintptr_t fnc) (void)fnc; } -static void post_acquire_registry_wait(void *opaque) -{ - post_acquire_mutator_sync_t *sync = opaque; - - pthread_mutex_lock(&sync->lock); - sync->wait_registered = 1; - pthread_cond_broadcast(&sync->cond); - pthread_mutex_unlock(&sync->lock); -} - -static void *post_acquire_mutator_main(void *opaque) -{ - post_acquire_mutator_sync_t *sync = opaque; - kzt_guest_dynamic_view_t changed_view = sync->fixture->dynamic_view; - - changed_view.dynamic_addr += 0x3000; - sync->worker_result = kzt_guest_registry_commit_dynamic_view( - sync->fixture->context.kzt_guest_registry_context.registry, - SOURCE_LINK_MAP, sync->fixture->pending.source_generation, - &changed_view); - pthread_mutex_lock(&sync->lock); - sync->worker_done = 1; - pthread_cond_broadcast(&sync->cond); - pthread_mutex_unlock(&sync->lock); - return NULL; -} - -static int post_acquire_mutator_init(fixture_t *fixture) -{ - memset(&post_acquire_mutator, 0, sizeof(post_acquire_mutator)); - post_acquire_mutator.fixture = fixture; - return pthread_mutex_init(&post_acquire_mutator.lock, NULL) || - pthread_cond_init(&post_acquire_mutator.cond, NULL) ? -1 : 0; -} - -static void post_acquire_mutator_destroy(void) -{ - pthread_cond_destroy(&post_acquire_mutator.cond); - pthread_mutex_destroy(&post_acquire_mutator.lock); - memset(&post_acquire_mutator, 0, sizeof(post_acquire_mutator)); -} - uintptr_t CheckBridged(bridge_t *bridge, void *fnc) { fixture_bridge_map_t *map = (fixture_bridge_map_t *)bridge; @@ -436,7 +437,7 @@ const char *SymName(elfheader_t *head, Elf64_Sym *sym) { (void)head; (void)sym; - return FIXTURE_SYMBOL; + return fixture_symbol_name; } const char *GetSymbolVersion(elfheader_t *head, int version) @@ -512,22 +513,33 @@ static unsigned long fixture_publish_source(fixture_t *fixture) return generation; } -static void fixture_publish_resolver(fixture_t *fixture) +static void fixture_publish_resolver_for_head(fixture_t *fixture, + elfheader_t *head, + int registry_owned_head) { kzt_guest_lazy_resolver_t resolver = { .link_map_slot = SOURCE_START + 0x2000, .resolver_slot = SOURCE_START + 0x2008, .guest_link_map = SOURCE_LINK_MAP, .guest_resolver = SOURCE_START + 0x3000, + .object_head = (uintptr_t)head, + .registry_owned_head = registry_owned_head, }; - fixture->pending.guest_resolver = resolver.guest_resolver; + fixture->source.guest_resolver = resolver.guest_resolver; CHECK("source.resolver", kzt_guest_registry_publish_lazy_resolver( fixture->context.kzt_guest_registry_context.registry, SOURCE_LINK_MAP, - fixture->pending.source_generation, 0, &resolver) == 0); + fixture->source.generation, 0, &resolver) == 0); +} + +static void fixture_publish_resolver(fixture_t *fixture) +{ + fixture_publish_resolver_for_head(fixture, &fixture->head, 0); } + + static void reset_guard(fixture_t *fixture, int enabled) { kzt_patch_spike_config_t config = { enabled, 1, 1 }; @@ -535,6 +547,14 @@ static void reset_guard(fixture_t *fixture, int enabled) &config); } +static void reset_guard_budget(box64context_t *context, + unsigned long budget) +{ + kzt_patch_spike_config_t config = { 1, 1, budget }; + + kzt_patch_spike_guard_init(&context->kzt_patch_spike_guard, &config); +} + static void fixture_init_guest_scope(fixture_t *fixture) { static const char provider_strings[] = @@ -621,20 +641,64 @@ static void fixture_init_guest_scope(fixture_t *fixture) fixture->provider_scope_dynamic[7].d_tag = DT_VERDEFNUM; fixture->provider_scope_dynamic[7].d_un.d_val = 1; fixture->provider_scope_dynamic[8].d_tag = DT_NULL; -} -static int fixture_init(fixture_t *fixture) + fixture->alias_provider_dynamic[0].d_tag = DT_SYMTAB; + fixture->alias_provider_dynamic[0].d_un.d_ptr = + (uintptr_t)fixture->alias_provider_symbols; + fixture->alias_provider_dynamic[1].d_tag = DT_STRTAB; + fixture->alias_provider_dynamic[1].d_un.d_ptr = + (uintptr_t)fixture->alias_provider_strings; + fixture->alias_provider_dynamic[2].d_tag = DT_SYMENT; + fixture->alias_provider_dynamic[2].d_un.d_val = sizeof(Elf64_Sym); + fixture->alias_provider_dynamic[3].d_tag = DT_STRSZ; + fixture->alias_provider_dynamic[3].d_un.d_val = + sizeof(fixture->alias_provider_strings); + fixture->alias_provider_dynamic[4].d_tag = DT_HASH; + fixture->alias_provider_dynamic[4].d_un.d_ptr = + (uintptr_t)&fixture->alias_provider_hash; + fixture->alias_provider_dynamic[5].d_tag = DT_NULL; + fixture->alias_provider_hash.nbucket = 1; + fixture->alias_provider_hash.nchain = 1; +} + +static int fixture_init_with_options(fixture_t *fixture, + int provider_range_available, + int wrapper_alias) { static char source_name[] = "librequester.so"; static char provider_name[] = "libc.so.6"; - static char dynstr[] = "\0uname\0" FIXTURE_VERSION "\0KZT_BAD_VERSION\0"; + static char alias_owner_name[] = "libdl.so.2"; + size_t string_offset; + size_t version_offset; + size_t libdl_offset; + kzt_guest_object_observation_t binding_observation; kzt_guest_object_observation_t provider_observation; kzt_guest_library_binding_key_t provider_key; + unsigned long provider_generation; khint_t map_key; int inserted; int initial_failures = failures; memset(fixture, 0, sizeof(*fixture)); + fixture_symbol_name = FIXTURE_SYMBOL; + string_offset = 1; + memcpy(fixture->dynamic_strings + string_offset, + FIXTURE_SYMBOL, sizeof(FIXTURE_SYMBOL)); + string_offset += sizeof(FIXTURE_SYMBOL); + version_offset = string_offset; + memcpy(fixture->dynamic_strings + string_offset, + FIXTURE_VERSION, sizeof(FIXTURE_VERSION)); + string_offset += sizeof(FIXTURE_VERSION); + memcpy(fixture->dynamic_strings + string_offset, + "KZT_BAD_VERSION", sizeof("KZT_BAD_VERSION")); + string_offset += sizeof("KZT_BAD_VERSION"); + libdl_offset = string_offset; + memcpy(fixture->dynamic_strings + string_offset, + "libdl.so.2", sizeof("libdl.so.2")); + string_offset += sizeof("libdl.so.2"); + memcpy(fixture->dynamic_strings + string_offset, + "libm.so.6", sizeof("libm.so.6")); + string_offset += sizeof("libm.so.6"); fixture_init_guest_scope(fixture); fixture->versym = 2; fixture->slot = GUEST_TARGET; @@ -667,6 +731,7 @@ static int fixture_init(fixture_t *fixture) fixture->provider.type = LIB_WRAPPED; fixture->provider.active = 1; fixture->provider.context = &fixture->context; + fixture->context.libclib = &fixture->provider; fixture->provider.priv.w.lib = dlopen( "libc.so.6", RTLD_LAZY | RTLD_LOCAL); CHECK("provider.dlopen", fixture->provider.priv.w.lib != NULL); @@ -703,7 +768,16 @@ static int fixture_init(fixture_t *fixture) fixture->source_observation = observation( SOURCE_LINK_MAP, SOURCE_START, SOURCE_START + 0x10000, source_name); provider_observation = observation( - PROVIDER_LINK_MAP, 0x71000000, 0x71010000, provider_name); + PROVIDER_LINK_MAP, 0x71000000, 0x71010000, + wrapper_alias ? alias_owner_name : provider_name); + if (!provider_range_available) { + provider_observation.map_start = (kzt_guest_scalar_field_t) { + 0, KZT_GUEST_FIELD_UNKNOWN, + }; + provider_observation.map_end = (kzt_guest_scalar_field_t) { + 0, KZT_GUEST_FIELD_UNKNOWN, + }; + } fixture->dynamic_view = (kzt_guest_dynamic_view_t) { .dynamic_addr = SOURCE_START + 0x1000, .load_bias = 0, @@ -716,13 +790,62 @@ static int fixture_init(fixture_t *fixture) fixture->dynamic_view.pltrelsz = scalar_field(sizeof(fixture->rela)); fixture->dynamic_view.pltrel = scalar_field(DT_RELA); fixture->dynamic_view.symtab = runtime_field((uintptr_t)&fixture->sym); + fixture->dynamic_view.pltgot = runtime_field((uintptr_t)&fixture->slot); fixture->dynamic_view.syment = scalar_field(sizeof(fixture->sym)); - fixture->dynamic_view.strtab = runtime_field((uintptr_t)dynstr); - fixture->dynamic_view.strsz = scalar_field(sizeof(dynstr)); + fixture->dynamic_view.strtab = runtime_field( + (uintptr_t)fixture->dynamic_strings); + fixture->dynamic_view.strsz = scalar_field(string_offset); + fixture->dynamic_view.needed_count = 1; + fixture->dynamic_view.needed_offsets[0] = libdl_offset; + fixture->dynamic_view.needed_address_semantics = + KZT_GUEST_DYNAMIC_STRING_TABLE_OFFSET; fixture->dynamic_view.versym = runtime_field((uintptr_t)&fixture->versym); fixture->dynamic_view.verneed = runtime_field( (uintptr_t)&fixture->version_need); fixture->dynamic_view.verneednum = scalar_field(1); + fixture->provider_dynamic_view = (kzt_guest_dynamic_view_t) { + .dynamic_addr = (uintptr_t)fixture->provider_scope_dynamic, + .load_bias = 0, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .entry_count = 9, + .has_null = 1, + }; + fixture->provider_dynamic_view.symtab = runtime_field( + (uintptr_t)fixture->provider_scope_symbols); + fixture->provider_dynamic_view.strtab = runtime_field( + (uintptr_t)fixture->provider_scope_strings); + fixture->provider_dynamic_view.syment = scalar_field(sizeof(Elf64_Sym)); + fixture->provider_dynamic_view.strsz = scalar_field( + sizeof(fixture->provider_scope_strings)); + fixture->provider_dynamic_view.hash = runtime_field( + (uintptr_t)&fixture->provider_scope_hash); + fixture->provider_dynamic_view.versym = runtime_field( + (uintptr_t)fixture->provider_scope_versym); + fixture->provider_dynamic_view.verdef = runtime_field( + (uintptr_t)&fixture->provider_scope_verdef); + fixture->provider_dynamic_view.verdefnum = scalar_field(1); + fixture->alias_provider_dynamic_view = (kzt_guest_dynamic_view_t) { + .dynamic_addr = 0x72001000, + .load_bias = 0, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .entry_count = 6, + .has_null = 1, + }; + fixture->alias_provider_dynamic_view.symtab = runtime_field( + (uintptr_t)fixture->alias_provider_symbols); + fixture->alias_provider_dynamic_view.strtab = runtime_field( + (uintptr_t)fixture->alias_provider_strings); + fixture->alias_provider_dynamic_view.syment = scalar_field( + sizeof(Elf64_Sym)); + fixture->alias_provider_dynamic_view.strsz = scalar_field( + sizeof(fixture->alias_provider_strings)); + fixture->alias_provider_dynamic_view.hash = runtime_field( + (uintptr_t)&fixture->alias_provider_hash); + fixture->alias_provider_dynamic_view.versym = runtime_field( + (uintptr_t)fixture->alias_provider_versym); + fixture->alias_provider_dynamic_view.verdef = runtime_field( + (uintptr_t)&fixture->alias_provider_verdef); + fixture->alias_provider_dynamic_view.verdefnum = scalar_field(1); fixture->sym.st_name = 1; fixture->sym.st_info = ELF64_ST_INFO(STB_GLOBAL, STT_FUNC); fixture->sym.st_other = STV_DEFAULT; @@ -730,23 +853,46 @@ static int fixture_init(fixture_t *fixture) fixture->version_need.need.vn_cnt = 1; fixture->version_need.need.vn_aux = sizeof(Elf64_Verneed); fixture->version_need.aux.vna_other = 2; - fixture->version_need.aux.vna_name = 7; - fixture->pending.source_generation = fixture_publish_source(fixture); + fixture->version_need.aux.vna_name = version_offset; + fixture->source.generation = fixture_publish_source(fixture); fixture_publish_resolver(fixture); + provider_generation = observe_object( + fixture->context.kzt_guest_registry_context.registry, + &provider_observation); + binding_observation = wrapper_alias + ? observation(ALIAS_PROVIDER_LINK_MAP, 0x72000000, 0x72010000, + provider_name) + : provider_observation; provider_key = (kzt_guest_library_binding_key_t) { - .link_map_addr = PROVIDER_LINK_MAP, - .generation = observe_object( - fixture->context.kzt_guest_registry_context.registry, - &provider_observation), + .link_map_addr = wrapper_alias ? ALIAS_PROVIDER_LINK_MAP + : PROVIDER_LINK_MAP, + .generation = wrapper_alias + ? observe_object( + fixture->context + .kzt_guest_registry_context.registry, + &binding_observation) + : provider_generation, .namespace_id = 0, .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, }; + CHECK("provider.dynamic", kzt_guest_registry_commit_dynamic_view( + fixture->context.kzt_guest_registry_context.registry, + PROVIDER_LINK_MAP, provider_generation, + &fixture->provider_dynamic_view) == KZT_GUEST_REGISTRY_UPDATED); + if (wrapper_alias) { + CHECK("alias-provider.dynamic", + kzt_guest_registry_commit_dynamic_view( + fixture->context.kzt_guest_registry_context.registry, + ALIAS_PROVIDER_LINK_MAP, provider_key.generation, + &fixture->alias_provider_dynamic_view) == + KZT_GUEST_REGISTRY_UPDATED); + } CHECK("provider.track", kzt_guest_library_track( fixture->context.kzt_guest_library_access.bindings, &fixture->provider) == 0); CHECK("provider.pair", kzt_guest_library_note_exact_pair( fixture->context.kzt_guest_library_access.bindings, - PROVIDER_LINK_MAP, &fixture->provider, + provider_key.link_map_addr, &fixture->provider, KZT_GUEST_LIBRARY_OBJECT_WRAPPED) == KZT_GUEST_LIBRARY_BINDING_PENDING); CHECK("provider.publish", kzt_guest_library_note_observation( @@ -757,6 +903,7 @@ static int fixture_init(fixture_t *fixture) fixture->rela.r_offset = (uintptr_t)&fixture->slot; fixture->head.name = source_name; fixture->head.path = source_name; + fixture->head.latx_type = LATX_ELF_TYPE_EMUED; fixture->head.memory = (char *)SOURCE_START; fixture->head.memsz = 0x10000; fixture->head.Dynamic = (Elf64_Dyn *)(SOURCE_START + 0x1000); @@ -767,28 +914,179 @@ static int fixture_init(fixture_t *fixture) fixture->head.numDynSym = 1; fixture->head.VerSym = &fixture->versym; fixture->head.self_link_map = SOURCE_LINK_MAP; + fixture->head.kzt_guest_resolver = fixture->source.guest_resolver; fixture->elfs[0] = &fixture->head; fixture->context.elfs = fixture->elfs; fixture->context.elfsize = 1; - fixture->pending.armed = 1; - fixture->pending.context_id = (uintptr_t)&fixture->context; - fixture->pending.source_link_map = SOURCE_LINK_MAP; - fixture->pending.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN; - fixture->pending.namespace_id = 0; - fixture->pending.slot_addr = (uintptr_t)&fixture->slot; - fixture->pending.unresolved_stub = GUEST_TARGET - 0x10; - fixture->pending.symbol = FIXTURE_SYMBOL; - fixture->pending.version_evidence = KZT_SYMBOL_VERSION_VERSIONED; - fixture->pending.version = FIXTURE_VERSION; + fixture->source.unresolved_stub = GUEST_TARGET - 0x10; + fixture->source.symbol = FIXTURE_SYMBOL; + fixture->source.version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + fixture->source.version = FIXTURE_VERSION; return failures == initial_failures ? 0 : -1; } +static int fixture_init(fixture_t *fixture) +{ + return fixture_init_with_options(fixture, 1, 0); +} + +static void fixture_use_alias_symbol(fixture_t *fixture, + const char *alias_symbol) +{ + char *source_strings = + (char *)(uintptr_t)fixture->dynamic_view.strtab.value; + size_t symbol_size = strlen(alias_symbol) + 1; + size_t version_offset = 1 + symbol_size; + size_t string_offset = version_offset; + size_t libdl_offset; + khint_t map_key; + int inserted; + + memset(source_strings, 0, sizeof(fixture->dynamic_strings)); + memcpy(source_strings + 1, alias_symbol, symbol_size); + memcpy(source_strings + string_offset, + FIXTURE_VERSION, sizeof(FIXTURE_VERSION)); + string_offset += sizeof(FIXTURE_VERSION); + memcpy(source_strings + string_offset, + "KZT_BAD_VERSION", sizeof("KZT_BAD_VERSION")); + string_offset += sizeof("KZT_BAD_VERSION"); + libdl_offset = string_offset; + memcpy(source_strings + string_offset, + "libdl.so.2", sizeof("libdl.so.2")); + string_offset += sizeof("libdl.so.2"); + memcpy(source_strings + string_offset, + "libm.so.6", sizeof("libm.so.6")); + string_offset += sizeof("libm.so.6"); + fixture->dynamic_view.strsz = scalar_field(string_offset); + fixture->dynamic_view.needed_offsets[0] = libdl_offset; + fixture->version_need.aux.vna_name = version_offset; + + memset(fixture->provider_scope_strings, 0, + sizeof(fixture->provider_scope_strings)); + memcpy(fixture->provider_scope_strings + 1, + alias_symbol, symbol_size); + memcpy(fixture->provider_scope_strings + version_offset, + FIXTURE_VERSION, sizeof(FIXTURE_VERSION)); + fixture->provider_scope_verdef.auxiliary.vda_name = version_offset; + fixture->source.symbol = alias_symbol; + fixture_symbol_name = alias_symbol; + map_key = kh_put(symbolmap, fixture->provider.symbolmap, + alias_symbol, &inserted); + CHECK("alias-symbol.map-entry", + inserted != -1 && map_key != kh_end(fixture->provider.symbolmap)); + kh_value(fixture->provider.symbolmap, map_key) = fixture_iFp; + dlerror(); + fixture_native_symbol = (uintptr_t)dlsym( + fixture->provider.priv.w.lib, alias_symbol); + CHECK("alias-symbol.native", + fixture_native_symbol != 0 && dlerror() == NULL); + fixture->bridge_entry.f = fixture_native_symbol; + fixture->bridge_map.native_symbol = (void *)fixture_native_symbol; +} + +static void fixture_use_dlsym_alias_symbol(fixture_t *fixture) +{ + static char custom_prefix[] = ""; + khint_t key; + int inserted; + + fixture_use_alias_symbol(fixture, "dlsym"); + fixture->context.kzt_guest_registry_context.main_namespace_head = + SOURCE_LINK_MAP + 0x100; + key = kh_get(symbolmap, fixture->provider.symbolmap, "dlsym"); + CHECK("dlsym-custom.normal-map-entry", + key != kh_end(fixture->provider.symbolmap)); + if (key != kh_end(fixture->provider.symbolmap)) { + kh_del(symbolmap, fixture->provider.symbolmap, key); + } + fixture->provider.mysymbolmap = kh_init(symbolmap); + CHECK("dlsym-custom.map", fixture->provider.mysymbolmap != NULL); + if (!fixture->provider.mysymbolmap) { + return; + } + key = kh_put(symbolmap, fixture->provider.mysymbolmap, + "dlsym", &inserted); + CHECK("dlsym-custom.map-entry", + inserted != -1 && key != kh_end(fixture->provider.mysymbolmap)); + if (key != kh_end(fixture->provider.mysymbolmap)) { + kh_value(fixture->provider.mysymbolmap, key) = fixture_iFp; + } + fixture->provider.altmy = custom_prefix; + fixture->provider.priv.w.box64lib = fixture->provider.priv.w.lib; +} + +static int fixture_publish_native_dlerror(fixture_t *fixture) +{ + kzt_guest_registry_address_match_t provider = { 0 }; + kzt_lazy_prebind_record_t record = { 0 }; + kzt_lazy_prebind_lease_t publish = { 0 }; + + if (kzt_guest_registry_find_live_object( + fixture->context.kzt_guest_registry_context.registry, + ALIAS_PROVIDER_LINK_MAP, &provider) != 0) { + return -1; + } + record.source = (kzt_lazy_prebind_identity_t) { + .link_map_addr = SOURCE_LINK_MAP, + .generation = fixture->source.generation, + .namespace_id = 0, + }; + record.provider = (kzt_lazy_prebind_identity_t) { + .link_map_addr = ALIAS_PROVIDER_LINK_MAP, + .generation = provider.generation, + .namespace_id = 0, + }; + fixture->dlerror_slot = GUEST_TARGET - 0x20; + record.slot_addr = (uintptr_t)&fixture->dlerror_slot; + record.expected_slot = fixture->dlerror_slot; + record.relocation_index = 1; + record.bridge_target = fixture_native_bridge; + record.bridge_generation = provider.generation; + record.bridge_custom_wrapper = 1; + record.version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + strcpy(record.symbol, "dlerror"); + strcpy(record.version, FIXTURE_VERSION); + record.scope_proof = (kzt_guest_symbol_scope_result_t) { + .status = KZT_GUEST_SYMBOL_SCOPE_SAFE, + .reason = KZT_GUEST_SYMBOL_SCOPE_REASON_SELECTED_PROVIDER, + .scope_complete = 1, + .lookup_order_known = 1, + .selected_provider_link_map = record.provider.link_map_addr, + .selected_provider_address = fixture_native_symbol, + .selected_provider_binding = STB_GLOBAL, + .selected_provider_type = STT_FUNC, + .selected_provider_visibility = STV_DEFAULT, + .scope_identity = { + .source = { + .link_map_addr = record.source.link_map_addr, + .generation = record.source.generation, + .namespace_id = record.source.namespace_id, + .namespace_head = SOURCE_LINK_MAP + 0x100, + .layout = KZT_GUEST_SCOPE_LAYOUT_GLIBC_2_39_C591A5DF, + }, + }, + }; + if (kzt_lazy_prebind_scope_claim( + fixture->context.kzt_lazy_prebind_scope, &record) != + KZT_LAZY_PREBIND_CLAIM_CREATED || + kzt_lazy_prebind_scope_publish_acquire( + fixture->context.kzt_lazy_prebind_scope, &record, &publish) != 0) { + return -1; + } + kzt_lazy_prebind_scope_publish_finish(&publish, 1); + fixture->dlerror_slot = record.bridge_target; + return 0; +} + static void fixture_destroy(fixture_t *fixture) { if (fixture->provider.symbolmap) { kh_destroy(symbolmap, fixture->provider.symbolmap); } + if (fixture->provider.mysymbolmap) { + kh_destroy(symbolmap, fixture->provider.mysymbolmap); + } if (fixture->provider.priv.w.lib) { dlclose(fixture->provider.priv.w.lib); } @@ -798,25 +1096,20 @@ static void fixture_destroy(fixture_t *fixture) &fixture->context.kzt_guest_registry_context.registry); } -static int fixture_route(fixture_t *fixture, - kzt_lazy_binding_route_result_t *result) -{ - return kzt_production_lazy_route_guest_target( - &fixture->context, &fixture->pending, GUEST_TARGET, result); -} static int fixture_lazy_direct_route( fixture_t *fixture, kzt_lazy_direct_route_result_t *result) { return kzt_production_lazy_direct_route( &fixture->context, &fixture->head, 0, &fixture->rela, - (uint64_t *)&fixture->slot, fixture->slot, 0, FIXTURE_SYMBOL, - KZT_SYMBOL_VERSION_VERSIONED, FIXTURE_VERSION, result); + (uint64_t *)&fixture->slot, fixture->slot, 0, + fixture->source.symbol, + fixture->source.version_evidence, fixture->source.version, result); } static void fixture_set_unresolved_slot(fixture_t *fixture) { - fixture->slot = fixture->pending.unresolved_stub; + fixture->slot = fixture->source.unresolved_stub; fixture->head.plt = fixture->slot - 8; fixture->head.plt_end = fixture->slot + 8; } @@ -830,7 +1123,7 @@ static int fixture_claim_prebind_record(fixture_t *fixture) kzt_guest_symbol_scope_request_t request = { .source = { .link_map_addr = SOURCE_LINK_MAP, - .generation = fixture->pending.source_generation, + .generation = fixture->source.generation, .namespace_id = 0, .namespace_head = SOURCE_LINK_MAP, .layout = KZT_GUEST_SCOPE_LAYOUT_GLIBC_2_39_C591A5DF, @@ -854,7 +1147,7 @@ static int fixture_claim_prebind_record(fixture_t *fixture) } record.source = (kzt_lazy_prebind_identity_t) { .link_map_addr = SOURCE_LINK_MAP, - .generation = fixture->pending.source_generation, + .generation = fixture->source.generation, .namespace_id = 0, }; record.provider = (kzt_lazy_prebind_identity_t) { @@ -914,14 +1207,13 @@ static int fixture_eager_registry_route( static void hooks_reset(fixture_t *fixture, hook_mode_t mode) { hook_fixture = fixture; - hook_retire = NULL; hook_mode = mode; before_acquire_calls = 0; source_memory_access_calls = 0; + owner_memory_access_calls = 0; + owner_memory_all_lifetimes_held = 1; slot_load_calls = 0; after_cas_calls = 0; - lease_seen_at_first_slot_load = 0; - force_verify_mismatch = mode == HOOK_RETIRE_AFTER_CAS_ROLLBACK; shadow_run_calls = 0; recycled_generation = 0; permission_begin_calls = 0; @@ -929,8 +1221,6 @@ static void hooks_reset(fixture_t *fixture, hook_mode_t mode) fail_permission_begin = 0; fail_permission_end = 0; fail_permission_end_once = 0; - full_enrich_calls = 0; - wrapper_only_enrich_calls = 0; decision_lease_active = 0; decision_lease_acquires = 0; decision_lease_releases = 0; @@ -940,8 +1230,6 @@ static void hooks_reset(fixture_t *fixture, hook_mode_t mode) decision_lease_held_at_cas = 0; generation_validate_calls = 0; runtime_full_lifetime_validation_calls = 0; - mapping_lock_calls = 0; - mapping_unlock_calls = 0; mapping_lock_active = 0; } @@ -949,7 +1237,6 @@ void kzt_jump_slot_production_test_mapping_lock(void) { CHECK("mapping-lock.acquire", pthread_mutex_lock(&mapping_transaction_lock) == 0); - ++mapping_lock_calls; mapping_lock_active = 1; } @@ -957,7 +1244,6 @@ void kzt_jump_slot_production_test_mapping_unlock(void) { CHECK("mapping-lock.active", mapping_lock_active == 1); mapping_lock_active = 0; - ++mapping_unlock_calls; CHECK("mapping-lock.release", pthread_mutex_unlock(&mapping_transaction_lock) == 0); } @@ -989,12 +1275,6 @@ int kzt_jump_slot_production_test_end_slot_write( } ++permission_end_calls; decision_lease_held_at_permission_end |= decision_lease_active; - if (mapping_mutator.enabled) { - pthread_mutex_lock(&mapping_mutator.lock); - mapping_mutator.mutated_before_permission_end |= - mapping_mutator.mutated; - pthread_mutex_unlock(&mapping_mutator.lock); - } if (fail_permission_end_once) { fail_permission_end_once = 0; return -1; @@ -1002,105 +1282,6 @@ int kzt_jump_slot_production_test_end_slot_write( return fail_permission_end ? -1 : 0; } -static void *mapping_mutator_main(void *opaque) -{ - mapping_mutator_sync_t *sync = opaque; - int first_result; - int second_result = EBUSY; - - pthread_mutex_lock(&sync->lock); - while (!sync->go) { - pthread_cond_wait(&sync->cond, &sync->lock); - } - pthread_mutex_unlock(&sync->lock); - - first_result = pthread_mutex_trylock(&mapping_transaction_lock); - if (first_result == 0) { - pthread_mutex_unlock(&mapping_transaction_lock); - } - pthread_mutex_lock(&sync->lock); - sync->first_try_error = - first_result != 0 && first_result != EBUSY ? first_result : 0; - sync->blocked_at_cas = first_result == EBUSY; - if (first_result == 0) { - sync->mutated = 1; - } - sync->first_try_complete = 1; - pthread_cond_broadcast(&sync->cond); - while (!sync->route_finished) { - pthread_cond_wait(&sync->cond, &sync->lock); - } - pthread_mutex_unlock(&sync->lock); - - if (first_result == EBUSY) { - second_result = pthread_mutex_trylock(&mapping_transaction_lock); - if (second_result == 0) { - pthread_mutex_unlock(&mapping_transaction_lock); - } - pthread_mutex_lock(&sync->lock); - sync->released_after_route = second_result == 0; - if (second_result == 0) { - sync->mutated = 1; - } - pthread_mutex_unlock(&sync->lock); - } - return NULL; -} - -static int mapping_mutator_init(void) -{ - memset(&mapping_mutator, 0, sizeof(mapping_mutator)); - if (pthread_mutex_init(&mapping_mutator.lock, NULL) != 0 || - pthread_cond_init(&mapping_mutator.cond, NULL) != 0) { - return -1; - } - mapping_mutator.enabled = 1; - if (pthread_create(&mapping_mutator.thread, NULL, mapping_mutator_main, - &mapping_mutator) != 0) { - pthread_cond_destroy(&mapping_mutator.cond); - pthread_mutex_destroy(&mapping_mutator.lock); - memset(&mapping_mutator, 0, sizeof(mapping_mutator)); - return -1; - } - return 0; -} - -static void mapping_mutator_trigger(void) -{ - if (!mapping_mutator.enabled) { - return; - } - pthread_mutex_lock(&mapping_mutator.lock); - mapping_mutator.go = 1; - pthread_cond_broadcast(&mapping_mutator.cond); - while (!mapping_mutator.first_try_complete) { - pthread_cond_wait(&mapping_mutator.cond, &mapping_mutator.lock); - } - pthread_mutex_unlock(&mapping_mutator.lock); -} - -static void mapping_mutator_finish(void) -{ - if (!mapping_mutator.enabled) { - return; - } - pthread_mutex_lock(&mapping_mutator.lock); - mapping_mutator.route_finished = 1; - pthread_cond_broadcast(&mapping_mutator.cond); - pthread_mutex_unlock(&mapping_mutator.lock); - pthread_join(mapping_mutator.thread, NULL); -} - -static void mapping_mutator_destroy(void) -{ - if (!mapping_mutator.enabled) { - return; - } - pthread_cond_destroy(&mapping_mutator.cond); - pthread_mutex_destroy(&mapping_mutator.lock); - memset(&mapping_mutator, 0, sizeof(mapping_mutator)); -} - void kzt_jump_slot_production_test_before_source_lease_acquire(void) { ++before_acquire_calls; @@ -1110,7 +1291,7 @@ void kzt_jump_slot_production_test_before_source_lease_acquire(void) CHECK("before.retire", kzt_guest_registry_retire( hook_fixture->context.kzt_guest_registry_context.registry, SOURCE_LINK_MAP, - hook_fixture->pending.source_generation) == 0); + hook_fixture->source.generation) == 0); recycled_generation = fixture_publish_source(hook_fixture); hook_mode = HOOK_NONE; } @@ -1120,6 +1301,17 @@ void kzt_jump_slot_production_test_before_source_memory_access(void) ++source_memory_access_calls; } +void kzt_jump_slot_production_test_before_owner_memory_access( + int source_lease_active, int decision_lease_active, + int quiescence_active, int retained_provider_active, + int owner_lease_active) +{ + ++owner_memory_access_calls; + owner_memory_all_lifetimes_held &= + source_lease_active && decision_lease_active && quiescence_active && + retained_provider_active && owner_lease_active; +} + void kzt_jump_slot_production_test_before_slot_load(void) { if (hook_mode == HOOK_SLOT_CONFLICT_BEFORE_FINAL_LOAD && @@ -1127,102 +1319,19 @@ void kzt_jump_slot_production_test_before_slot_load(void) hook_fixture->slot ^= 0x80; hook_mode = HOOK_NONE; } - if (slot_load_calls == 0 && hook_fixture) { - kzt_guest_object_snapshot_t *snapshot = NULL; - - if (kzt_guest_registry_find_by_link_map( - hook_fixture->context.kzt_guest_registry_context.registry, - SOURCE_LINK_MAP, - &snapshot) == 0 && snapshot && - snapshot->active_source_leases == 1) { - lease_seen_at_first_slot_load = 1; - } - kzt_guest_object_snapshot_free(snapshot); - } ++slot_load_calls; } void kzt_jump_slot_production_test_after_slot_load(uintptr_t *value) { - /* Final under-lease validation adds one load before writer read/verify. */ - if (force_verify_mismatch && slot_load_calls == 4 && value) { - *value ^= 0x10; - force_verify_mismatch = 0; - } -} - -static void *retire_worker(void *opaque) -{ - retire_sync_t *sync = opaque; - int result; - - pthread_mutex_lock(&sync->lock); - while (!sync->go) { - pthread_cond_wait(&sync->cond, &sync->lock); - } - sync->retire_called = 1; - pthread_cond_broadcast(&sync->cond); - pthread_mutex_unlock(&sync->lock); - - result = kzt_guest_registry_retire( - sync->fixture->context.kzt_guest_registry_context.registry, - SOURCE_LINK_MAP, - sync->generation); - pthread_mutex_lock(&sync->lock); - sync->retire_result = result; - sync->retire_done = 1; - pthread_cond_broadcast(&sync->cond); - pthread_mutex_unlock(&sync->lock); - return NULL; + (void)value; } void kzt_jump_slot_production_test_after_slot_cas(int exchanged) { - retire_sync_t *sync = hook_retire; - + (void)exchanged; ++after_cas_calls; decision_lease_held_at_cas |= decision_lease_active; - if (exchanged) { - mapping_mutator_trigger(); - } - if (!exchanged || - (hook_mode != HOOK_RETIRE_AFTER_CAS && - hook_mode != HOOK_RETIRE_AFTER_CAS_ROLLBACK) || !sync) { - return; - } - hook_mode = HOOK_NONE; - pthread_mutex_lock(&sync->lock); - sync->go = 1; - pthread_cond_broadcast(&sync->cond); - while (!sync->retire_called) { - pthread_cond_wait(&sync->cond, &sync->lock); - } - pthread_mutex_unlock(&sync->lock); - - for (;;) { - kzt_guest_object_snapshot_t *snapshot = NULL; - int done; - - if (kzt_guest_registry_find_by_link_map( - sync->fixture->context.kzt_guest_registry_context.registry, - SOURCE_LINK_MAP, - &snapshot) != 0) { - pthread_mutex_lock(&sync->lock); - done = sync->retire_done; - sync->unloading_observed = !done; - sync->completed_inside_hook = done; - pthread_mutex_unlock(&sync->lock); - kzt_guest_object_snapshot_free(snapshot); - break; - } - kzt_guest_object_snapshot_free(snapshot); - pthread_mutex_lock(&sync->lock); - done = sync->retire_done; - sync->unloading_observed = 0; - sync->completed_inside_hook = done; - pthread_mutex_unlock(&sync->lock); - break; - } } void kzt_jump_slot_production_test_shadow_run(void) @@ -1232,12 +1341,10 @@ void kzt_jump_slot_production_test_shadow_run(void) void kzt_jump_slot_production_test_full_enrich(void) { - ++full_enrich_calls; } void kzt_jump_slot_production_test_wrapper_only_enrich(void) { - ++wrapper_only_enrich_calls; } void kzt_jump_slot_production_test_before_generation_validate(void) @@ -1247,10 +1354,6 @@ void kzt_jump_slot_production_test_before_generation_validate(void) } ++generation_validate_calls; decision_lease_held_at_validate |= decision_lease_active; - if (hook_mode == HOOK_SLOT_CONFLICT_BEFORE_VALIDATE) { - hook_fixture->slot ^= 0x80; - hook_mode = HOOK_NONE; - } } void kzt_jump_slot_production_test_before_patch_decision_lease_acquire(void) @@ -1266,16 +1369,9 @@ void kzt_jump_slot_production_test_before_patch_decision_lease_acquire(void) changed_view.dynamic_addr += 0x2000; CHECK("pre-acquire.view-change", kzt_guest_registry_commit_dynamic_view( - registry, SOURCE_LINK_MAP, hook_fixture->pending.source_generation, + registry, SOURCE_LINK_MAP, hook_fixture->source.generation, &changed_view) == KZT_GUEST_REGISTRY_UPDATED); hook_mode = HOOK_NONE; - } else if (hook_mode == HOOK_OWNER_AMBIGUOUS_BEFORE_DECISION_ACQUIRE) { - kzt_guest_object_observation_t overlapping = observation( - 0x3000, 0x71000000, 0x71010000, "liboverlap.so"); - - CHECK("pre-acquire.owner-change", kzt_guest_registry_observe( - registry, &overlapping) == KZT_GUEST_REGISTRY_ADDED); - hook_mode = HOOK_NONE; } } @@ -1283,17 +1379,6 @@ void kzt_jump_slot_production_test_after_patch_decision_lease_acquire(void) { decision_lease_active = 1; ++decision_lease_acquires; - if (post_acquire_mutator.enabled) { - CHECK("post-acquire.thread", pthread_create( - &post_acquire_mutator.thread, NULL, post_acquire_mutator_main, - &post_acquire_mutator) == 0); - pthread_mutex_lock(&post_acquire_mutator.lock); - while (!post_acquire_mutator.wait_registered) { - pthread_cond_wait(&post_acquire_mutator.cond, - &post_acquire_mutator.lock); - } - pthread_mutex_unlock(&post_acquire_mutator.lock); - } } void kzt_jump_slot_production_test_before_patch_decision_lease_release(void) @@ -1302,67 +1387,10 @@ void kzt_jump_slot_production_test_before_patch_decision_lease_release(void) permission_end_calls == 0 || decision_lease_held_at_permission_end == 1); CHECK("decision-lease.release-after-cas", after_cas_calls == 0 || decision_lease_held_at_cas == 1); - if (post_acquire_mutator.enabled) { - pthread_mutex_lock(&post_acquire_mutator.lock); - post_acquire_mutator.done_before_release = - post_acquire_mutator.worker_done; - pthread_mutex_unlock(&post_acquire_mutator.lock); - } decision_lease_active = 0; ++decision_lease_releases; } -static int retire_sync_init(retire_sync_t *sync, fixture_t *fixture) -{ - memset(sync, 0, sizeof(*sync)); - sync->fixture = fixture; - sync->generation = fixture->pending.source_generation; - if (pthread_mutex_init(&sync->lock, NULL) != 0) { - return -1; - } - if (pthread_cond_init(&sync->cond, NULL) != 0) { - pthread_mutex_destroy(&sync->lock); - return -1; - } - return 0; -} - -static void retire_sync_destroy(retire_sync_t *sync) -{ - pthread_cond_destroy(&sync->cond); - pthread_mutex_destroy(&sync->lock); -} - -static void test_successful_production_route(void) -{ - fixture_t fixture; - kzt_lazy_binding_route_result_t result; - - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } - hooks_reset(&fixture, HOOK_NONE); - CHECK("success.route", fixture_route(&fixture, &result) == 0); - CHECK("success.applied", - result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED); - CHECK("success.slot", fixture.slot == fixture_native_bridge); - CHECK("success.lease", before_acquire_calls == 1); - CHECK("success.full-enrich-once", full_enrich_calls == 1); - CHECK("success.wrapper-only-once", wrapper_only_enrich_calls == 1); - CHECK("success.cached-bridge.no-add", fixture.bridge_map.add_calls == 0); - CHECK("success.cached-bridge.single-provider-check", - fixture.bridge_map.check_calls == 2); - CHECK("success.decision-lease-lifetime", - decision_lease_acquires == 1 && decision_lease_releases == 1 && - decision_lease_active == 0 && decision_lease_held_at_validate == 1 && - decision_lease_held_at_permission_begin == 1 && - decision_lease_held_at_permission_end == 1 && - decision_lease_held_at_cas == 1); - CHECK("success.single-under-lease-evidence-validation", - generation_validate_calls == 1); - fixture_destroy(&fixture); -} static void test_active_loader_scope_forces_guest_fallback(void) { @@ -1384,7 +1412,7 @@ static void test_active_loader_scope_forces_guest_fallback(void) fixture_lazy_direct_route(&fixture, &result) == 0); CHECK("loader-active.guest", result.status == KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED && - fixture.slot == fixture.pending.unresolved_stub); + fixture.slot == fixture.source.unresolved_stub); CHECK("loader-active.no-writer", after_cas_calls == 0); kzt_guest_library_loader_scope_end(&loader_scope); fixture_destroy(&fixture); @@ -1412,51 +1440,13 @@ static void test_active_loader_scope_rejects_cached_scope_proof(void) fixture_lazy_direct_route(&fixture, &result) == 0); CHECK("loader-active-cache.guest", result.status == KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED && - fixture.slot == fixture.pending.unresolved_stub); + fixture.slot == fixture.source.unresolved_stub); CHECK("loader-active-cache.no-writer", after_cas_calls == 0); kzt_guest_library_loader_scope_end(&loader_scope); fixture_destroy(&fixture); } -static void test_production_route_does_not_need_registry_snapshot_allocation(void) -{ - fixture_t fixture; - kzt_lazy_binding_route_result_t result; - - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } - hooks_reset(&fixture, HOOK_NONE); - kzt_guest_registry_test_set_alloc_failure_after(0); - CHECK("compact-production.route", fixture_route(&fixture, &result) == 0); - kzt_guest_registry_test_set_alloc_failure_after(-1); - CHECK("compact-production.applied", - result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED && - fixture.slot == fixture_native_bridge); - CHECK("compact-production.leases", - decision_lease_acquires == 1 && decision_lease_releases == 1 && - before_acquire_calls == 1 && after_cas_calls == 1); - fixture_destroy(&fixture); -} - -static void test_retained_exact_handle_avoids_repeated_lifetime_validation(void) -{ - fixture_t fixture; - kzt_lazy_binding_route_result_t result; - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } - hooks_reset(&fixture, HOOK_NONE); - CHECK("retained-handle.route", fixture_route(&fixture, &result) == 0); - CHECK("retained-handle.applied", - result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED); - CHECK("retained-handle.no-repeated-full-lifetime-validation", - runtime_full_lifetime_validation_calls == 0); - fixture_destroy(&fixture); -} static void test_retained_exact_handle_avoids_discovery_owner_walk(void) { @@ -1642,38 +1632,6 @@ static void test_dependency_symbol_owner_mismatch_fails_open(void) fixture_destroy(&fixture); } -static void test_missing_bridge_is_created_from_exact_provider(void) -{ - fixture_t fixture; - kzt_lazy_binding_route_result_t result; - kzt_jump_slot_route_result_t eager_result; - - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } - fixture.bridge_map.target = 0; - hooks_reset(&fixture, HOOK_NONE); - CHECK("create-bridge.route", fixture_route(&fixture, &result) == 0); - CHECK("create-bridge.applied", - result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED); - CHECK("create-bridge.add-once", fixture.bridge_map.add_calls == 1); - CHECK("create-bridge.slot", fixture.slot == fixture_native_bridge); - CHECK("create-bridge.exact", - CheckBridged(fixture.provider.priv.w.bridge, - (void *)fixture_native_symbol) == - fixture_native_bridge); - fixture.slot = GUEST_TARGET; - fixture.bridge_map.target = 0; - reset_guard(&fixture, 1); - hooks_reset(&fixture, HOOK_NONE); - CHECK("create-bridge.eager-route", fixture_eager_route( - &fixture, GUEST_TARGET, FIXTURE_VERSION, &eager_result) == 0); - CHECK("create-bridge.eager-add-once", fixture.bridge_map.add_calls == 2); - CHECK("create-bridge.eager-applied", - eager_result.status == KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED); - fixture_destroy(&fixture); -} static void test_eager_registry_binding_selects_exact_provider(void) { @@ -1699,7 +1657,214 @@ static void test_eager_registry_binding_selects_exact_provider(void) fixture_destroy(&fixture); } -static void test_created_inexact_bridge_fails_open(void) +static void test_exact_owner_bridge_survives_unsupported_scope_layout(void) +{ + fixture_t fixture; + kzt_jump_slot_route_result_t result; + + if (fixture_init(&fixture) != 0) { + fixture_destroy(&fixture); + return; + } + fixture.context.kzt_guest_scope_layout = + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; + hooks_reset(&fixture, HOOK_NONE); + CHECK("unsupported-scope.route", + fixture_eager_registry_route( + &fixture, GUEST_TARGET, &result) == 0); + CHECK("unsupported-scope.status", + result.status == KZT_JUMP_SLOT_ROUTE_NATIVE_APPLIED); + CHECK("unsupported-scope.exact-provider", + result.exact_provider_acquired && result.exact_provider_matched); + CHECK("unsupported-scope.writer", result.native_writer_called); + CHECK("unsupported-scope.slot", fixture.slot == fixture_native_bridge); + fixture_destroy(&fixture); +} + + + + + + +static void test_lazy_direct_no_scope_uses_global_guard(void) +{ + fixture_t fixture; + kzt_lazy_direct_route_result_t result; + + if (fixture_init_with_options(&fixture, 0, 0) != 0) { + fixture_destroy(&fixture); + return; + } + fixture.context.kzt_guest_scope_layout = + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; + fixture_set_unresolved_slot(&fixture); + reset_guard(&fixture, 0); + hooks_reset(&fixture, HOOK_NONE); + CHECK("direct-no-scope-disabled.route", + fixture_lazy_direct_route(&fixture, &result) == 0); + CHECK("direct-no-scope-disabled.guest", + result.status == KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED && + fixture.slot == fixture.source.unresolved_stub); + CHECK("direct-no-scope-disabled.no-cas", after_cas_calls == 0); + CHECK("direct-no-scope-disabled.no-budget", + fixture.context.kzt_patch_spike_guard.write_attempts == 0); + fixture_destroy(&fixture); + + if (fixture_init_with_options(&fixture, 0, 1) != 0) { + fixture_destroy(&fixture); + return; + } + fixture_use_dlsym_alias_symbol(&fixture); + CHECK("direct-no-scope-enabled.dlerror", + fixture_publish_native_dlerror(&fixture) == 0); + fixture.context.kzt_guest_scope_layout = + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; + fixture_set_unresolved_slot(&fixture); + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_NONE); + CHECK("direct-no-scope-enabled.route", + fixture_lazy_direct_route(&fixture, &result) == 0); + CHECK("direct-no-scope-enabled.native", + result.status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED && + result.selected_target == fixture_native_bridge && + fixture.slot == fixture_native_bridge); + CHECK("direct-no-scope-enabled.cas", after_cas_calls == 1); + CHECK("direct-no-scope-enabled.budget", + fixture.context.kzt_patch_spike_guard.write_attempts == 1); + CHECK("direct-no-scope-enabled.owner-reread", + owner_memory_access_calls > 0 && owner_memory_all_lifetimes_held); + fixture_destroy(&fixture); + + if (fixture_init_with_options(&fixture, 0, 0) != 0) { + fixture_destroy(&fixture); + return; + } + fixture.context.kzt_guest_scope_layout = + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; + fixture_set_unresolved_slot(&fixture); + reset_guard(&fixture, 1); + kzt_patch_spike_guard_trip(&fixture.context.kzt_patch_spike_guard); + hooks_reset(&fixture, HOOK_NONE); + CHECK("direct-no-scope-circuit.route", + fixture_lazy_direct_route(&fixture, &result) == 0); + CHECK("direct-no-scope-circuit.guest", + result.status == KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED && + fixture.slot == fixture.source.unresolved_stub); + CHECK("direct-no-scope-circuit.no-cas", after_cas_calls == 0); + CHECK("direct-no-scope-circuit.no-budget", + fixture.context.kzt_patch_spike_guard.write_attempts == 0); + fixture_destroy(&fixture); +} + +static void test_lazy_direct_no_scope_borrows_libdl_alias(void) +{ + fixture_t fixture; + kzt_lazy_direct_route_result_t result; + + if (fixture_init_with_options(&fixture, 0, 1) != 0) { + fixture_destroy(&fixture); + return; + } + fixture_use_dlsym_alias_symbol(&fixture); + CHECK("direct-libdl-alias.dlerror", + fixture_publish_native_dlerror(&fixture) == 0); + fixture.context.kzt_guest_scope_layout = + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; + fixture_set_unresolved_slot(&fixture); + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_NONE); + CHECK("direct-libdl-alias.route", + fixture_lazy_direct_route(&fixture, &result) == 0); + CHECK("direct-libdl-alias.native", + result.status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED && + result.selected_target == fixture_native_bridge && + fixture.slot == fixture_native_bridge); + CHECK("direct-libdl-alias.cas", after_cas_calls == 1); + CHECK("direct-libdl-alias.budget", + fixture.context.kzt_patch_spike_guard.write_attempts == 1); + CHECK("direct-libdl-alias.owner-reread", + owner_memory_access_calls > 0 && owner_memory_all_lifetimes_held); + fixture_destroy(&fixture); +} + + + + + + +static void test_dlsym_requires_non_main_source_boundary(void) +{ + fixture_t fixture; + kzt_lazy_direct_route_result_t result; + + if (fixture_init_with_options(&fixture, 0, 1) != 0) { + fixture_destroy(&fixture); + return; + } + fixture_use_dlsym_alias_symbol(&fixture); + fixture.context.kzt_guest_scope_layout = + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; + fixture_set_unresolved_slot(&fixture); + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_NONE); + CHECK("dlsym-dso-no-dlerror.route", + fixture_lazy_direct_route(&fixture, &result) == 0); + CHECK("dlsym-dso-no-dlerror.guest", + result.status == KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED && + fixture.slot == fixture.source.unresolved_stub); + CHECK("dlsym-dso-no-dlerror.no-cas", after_cas_calls == 0); + fixture_destroy(&fixture); + + if (fixture_init_with_options(&fixture, 0, 1) != 0) { + fixture_destroy(&fixture); + return; + } + fixture_use_dlsym_alias_symbol(&fixture); + CHECK("dlsym-dso.dlerror", + fixture_publish_native_dlerror(&fixture) == 0); + fixture.context.kzt_guest_scope_layout = + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; + fixture_set_unresolved_slot(&fixture); + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_NONE); + CHECK("dlsym-dso.route", + fixture_lazy_direct_route(&fixture, &result) == 0); + CHECK("dlsym-dso.applied", + result.status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED && + fixture.slot == fixture_native_bridge); + CHECK("dlsym-dso.budget", + fixture.context.kzt_patch_spike_guard.write_attempts == 1); + fixture_destroy(&fixture); + + if (fixture_init_with_options(&fixture, 0, 1) != 0) { + fixture_destroy(&fixture); + return; + } + fixture_use_dlsym_alias_symbol(&fixture); + CHECK("dlsym-main.dlerror", + fixture_publish_native_dlerror(&fixture) == 0); + fixture.head.latx_type = LATX_ELF_TYPE_MAIN; + fixture.context.kzt_guest_registry_context.main_namespace_head = + SOURCE_LINK_MAP; + fixture.context.kzt_guest_scope_layout = + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; + fixture_set_unresolved_slot(&fixture); + reset_guard(&fixture, 1); + hooks_reset(&fixture, HOOK_NONE); + CHECK("dlsym-main.route", + fixture_lazy_direct_route(&fixture, &result) == 0); + CHECK("dlsym-main.guest", + result.status == KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED && + fixture.slot == fixture.source.unresolved_stub); + CHECK("dlsym-main.no-writer-budget", + fixture.context.kzt_patch_spike_guard.write_attempts == 0 && + fixture.context.kzt_patch_spike_guard.write_successes == 0); + CHECK("dlsym-main.no-cas", after_cas_calls == 0); + fixture_destroy(&fixture); +} + + +static void test_created_inexact_bridge_fails_open(void) { fixture_t fixture; kzt_wrapper_bridge_provider_t provider; @@ -1708,7 +1873,6 @@ static void test_created_inexact_bridge_fails_open(void) .symbol_version = FIXTURE_VERSION, }; kzt_wrapper_probe_result_t result; - kzt_lazy_binding_route_result_t route_result; if (fixture_init(&fixture) != 0) { fixture_destroy(&fixture); @@ -1726,15 +1890,6 @@ static void test_created_inexact_bridge_fails_open(void) &result) == 0); CHECK("inexact-add.no-bridge", result.bridge_target == 0); CHECK("inexact-add.add-once", fixture.bridge_map.add_calls == 1); - fixture.slot = GUEST_TARGET; - fixture.bridge_map.target = 0; - fixture.bridge_map.add_calls = 0; - hooks_reset(&fixture, HOOK_NONE); - CHECK("inexact-add.route", fixture_route(&fixture, &route_result) == 0); - CHECK("inexact-add.route-preserved", - route_result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED && - fixture.slot == GUEST_TARGET); - CHECK("inexact-add.route-add-once", fixture.bridge_map.add_calls == 1); fixture_destroy(&fixture); } @@ -1816,7 +1971,7 @@ static void test_eager_production_request_evidence(void) CHECK("eager.generation-race.call", fixture_eager_route( &fixture, GUEST_TARGET, FIXTURE_VERSION, &result) == 0); CHECK("eager.generation-race.declined-without-write", - recycled_generation > fixture.pending.source_generation && + recycled_generation > fixture.source.generation && result.status == KZT_JUMP_SLOT_ROUTE_BYPASS && result.legacy_fallback_attempted == 0 && after_cas_calls == 0 && fixture.slot == GUEST_TARGET); @@ -1830,19 +1985,19 @@ static void fixture_set_confirmed_unversioned(fixture_t *fixture) fixture->dynamic_view.verneed.present = 0; fixture->dynamic_view.verneednum.present = 0; fixture->head.VerSym = NULL; - fixture->pending.version_evidence = + fixture->source.version_evidence = KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; - fixture->pending.version = NULL; + fixture->source.version = NULL; CHECK("unversioned.dynamic-view", kzt_guest_registry_commit_dynamic_view( fixture->context.kzt_guest_registry_context.registry, - SOURCE_LINK_MAP, fixture->pending.source_generation, + SOURCE_LINK_MAP, fixture->source.generation, &fixture->dynamic_view) != KZT_GUEST_REGISTRY_ERROR); } static void test_confirmed_unversioned_production_paths_apply(void) { fixture_t fixture; - kzt_lazy_binding_route_result_t lazy_result; + kzt_lazy_direct_route_result_t lazy_result; kzt_jump_slot_route_result_t eager_result; if (fixture_init(&fixture) != 0) { @@ -1850,11 +2005,12 @@ static void test_confirmed_unversioned_production_paths_apply(void) return; } fixture_set_confirmed_unversioned(&fixture); + fixture_set_unresolved_slot(&fixture); hooks_reset(&fixture, HOOK_NONE); CHECK("unversioned-lazy.route", - fixture_route(&fixture, &lazy_result) == 0); + fixture_lazy_direct_route(&fixture, &lazy_result) == 0); CHECK("unversioned-lazy.applied", - lazy_result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED && + lazy_result.status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED && fixture.slot == fixture_native_bridge); fixture.slot = GUEST_TARGET; @@ -1894,172 +2050,82 @@ static void test_unknown_version_evidence_preserves_guest_path(void) fixture_destroy(&fixture); } -static void test_production_permission_transaction_rolls_back_on_restore_error(void) + + +static void test_eager_transaction_rolls_back_to_zero(void) { fixture_t fixture; - kzt_lazy_binding_route_result_t result; + uintptr_t final_value = GUEST_TARGET; + kzt_production_slot_transaction_result_t result; if (fixture_init(&fixture) != 0) { fixture_destroy(&fixture); return; } + fixture.slot = 0; hooks_reset(&fixture, HOOK_NONE); fail_permission_end_once = 1; - CHECK("permission.route", fixture_route(&fixture, &result) == 0); - CHECK("permission.begin", permission_begin_calls == 1); - CHECK("permission.end", permission_end_calls == 2); - CHECK("permission.explicit-status", - result.status == KZT_LAZY_BINDING_ROUTE_WRITE_ROLLED_BACK); - CHECK("permission.rollback-cas", after_cas_calls == 2); - CHECK("permission.guest-restored", fixture.slot == GUEST_TARGET); - CHECK("permission.decision-lease.released", - decision_lease_acquires == 1 && decision_lease_releases == 1 && - decision_lease_active == 0); - CHECK("permission.mapping-lock.released", - mapping_lock_calls == 1 && mapping_unlock_calls == 1 && - mapping_lock_active == 0); - CHECK("permission.circuit", + result = kzt_production_guest_relocation_write( + &fixture.context, SOURCE_LINK_MAP, + KZT_PATCH_RELOCATION_GLOB_DAT, (uintptr_t)&fixture.slot, 0, + GUEST_TARGET, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &final_value); + CHECK("eager-zero.result", + result == KZT_PRODUCTION_SLOT_TRANSACTION_ROLLED_BACK); + CHECK("eager-zero.slot-restored", fixture.slot == 0 && final_value == 0); + CHECK("eager-zero.two-cas", after_cas_calls == 2); + CHECK("eager-zero.permission-restored", permission_end_calls == 2); + CHECK("eager-zero.circuit-closed", kzt_patch_spike_guard_circuit_open( &fixture.context.kzt_patch_spike_guard) == 0); fixture_destroy(&fixture); } -static void test_production_permission_transaction_reports_unrecoverable(void) +static void test_guest_relocation_ignores_optional_patch_gate(void) { fixture_t fixture; - kzt_lazy_binding_route_result_t result; + uintptr_t final_value; + kzt_production_slot_transaction_result_t result; if (fixture_init(&fixture) != 0) { fixture_destroy(&fixture); return; } hooks_reset(&fixture, HOOK_NONE); - fail_permission_end = 1; - CHECK("permission-unrecoverable.route", - fixture_route(&fixture, &result) == 0); - CHECK("permission-unrecoverable.status", - result.status == KZT_LAZY_BINDING_ROUTE_UNRECOVERABLE); - CHECK("permission-unrecoverable.end", permission_end_calls == 2); - CHECK("permission-unrecoverable.rollback-cas", after_cas_calls == 2); - CHECK("permission-unrecoverable.guest-restored", - fixture.slot == GUEST_TARGET); - CHECK("permission-unrecoverable.circuit", - kzt_patch_spike_guard_circuit_open( - &fixture.context.kzt_patch_spike_guard) == 1); - CHECK("permission-unrecoverable.mapping-lock-released", - mapping_lock_calls == 1 && mapping_unlock_calls == 1 && - mapping_lock_active == 0); - fixture_destroy(&fixture); -} - -static void test_mapping_change_waits_for_permission_transaction(void) -{ - fixture_t fixture; - kzt_lazy_binding_route_result_t result; + reset_guard(&fixture, 0); + fixture.slot = 0; + final_value = 0; + result = kzt_production_guest_relocation_write( + &fixture.context, SOURCE_LINK_MAP, + KZT_PATCH_RELOCATION_GLOB_DAT, (uintptr_t)&fixture.slot, 0, + GUEST_TARGET, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &final_value); + CHECK("guest-mandatory.disabled.applied", + result == KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED && + fixture.slot == GUEST_TARGET && final_value == GUEST_TARGET); + CHECK("guest-mandatory.disabled.no-budget-use", + fixture.context.kzt_patch_spike_guard.write_attempts == 0); - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } hooks_reset(&fixture, HOOK_NONE); - if (mapping_mutator_init() != 0) { - CHECK("mapping-transaction.worker", 0); - fixture_destroy(&fixture); - return; - } - - CHECK("mapping-transaction.route", fixture_route(&fixture, &result) == 0); - mapping_mutator_finish(); - CHECK("mapping-transaction.applied", - result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED); - CHECK("mapping-transaction.lock-once", - mapping_lock_calls == 1 && mapping_unlock_calls == 1); - CHECK("mapping-transaction.first-try", mapping_mutator.first_try_error == 0); - CHECK("mapping-transaction.blocked-at-cas", - mapping_mutator.blocked_at_cas == 1); - CHECK("mapping-transaction.no-early-mutation", - mapping_mutator.mutated_before_permission_end == 0); - CHECK("mapping-transaction.released-after-route", - mapping_mutator.released_after_route == 1 && - mapping_mutator.mutated == 1 && mapping_lock_active == 0); - - mapping_mutator_destroy(); + reset_guard_budget(&fixture.context, 0); + fixture.slot = 0; + final_value = 0; + result = kzt_production_guest_relocation_write( + &fixture.context, SOURCE_LINK_MAP, + KZT_PATCH_RELOCATION_JUMP_SLOT, (uintptr_t)&fixture.slot, 0, + GUEST_TARGET, FIXTURE_SYMBOL, + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL, &final_value); + CHECK("guest-mandatory.budget-exhausted.applied", + result == KZT_PRODUCTION_SLOT_TRANSACTION_APPLIED && + fixture.slot == GUEST_TARGET && final_value == GUEST_TARGET); + CHECK("guest-mandatory.budget-exhausted.no-budget-use", + fixture.context.kzt_patch_spike_guard.write_attempts == 0); fixture_destroy(&fixture); } -static void test_permission_begin_failure_releases_decision_lease(void) -{ - fixture_t fixture; - kzt_lazy_binding_route_result_t result; - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } - hooks_reset(&fixture, HOOK_NONE); - fail_permission_begin = 1; - CHECK("permission-begin.route", fixture_route(&fixture, &result) == 0); - CHECK("permission-begin.preserved", - result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED); - CHECK("permission-begin.decision-release", - decision_lease_acquires == 1 && decision_lease_releases == 1 && - decision_lease_active == 0); - CHECK("permission-begin.mapping-lock.released", - mapping_lock_calls == 1 && mapping_unlock_calls == 1 && - mapping_lock_active == 0); - fixture_destroy(&fixture); -} -static void test_pre_cas_rechecks_fail_open_before_permissions(void) -{ - fixture_t fixture; - kzt_lazy_binding_route_result_t result; - - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } - hooks_reset(&fixture, HOOK_SLOT_CONFLICT_BEFORE_VALIDATE); - CHECK("slot-conflict", fixture_route(&fixture, &result) == 0); - CHECK("pre-cas.preserved", - result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED); - CHECK("pre-cas.no-permission", permission_begin_calls == 0); - CHECK("pre-cas.no-cas", after_cas_calls == 0); - CHECK("pre-cas.decision-lease.released", - decision_lease_acquires == 1 && decision_lease_releases == 1 && - decision_lease_active == 0); - fixture_destroy(&fixture); -} -static void test_pre_acquire_evidence_changes_fail_open(void) -{ - const hook_mode_t modes[] = { - HOOK_VIEW_CHANGE_BEFORE_DECISION_ACQUIRE, - HOOK_OWNER_AMBIGUOUS_BEFORE_DECISION_ACQUIRE, - }; - size_t i; - - for (i = 0; i < sizeof(modes) / sizeof(modes[0]); ++i) { - fixture_t fixture; - kzt_lazy_binding_route_result_t result; - - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - continue; - } - hooks_reset(&fixture, modes[i]); - CHECK("pre-acquire.route", fixture_route(&fixture, &result) == 0); - CHECK("pre-acquire.preserved", - result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED); - CHECK("pre-acquire.no-permission", permission_begin_calls == 0); - CHECK("pre-acquire.no-cas", after_cas_calls == 0); - CHECK("pre-acquire.no-add", fixture.bridge_map.add_calls == 0); - CHECK("pre-acquire.decision-release", - decision_lease_acquires == 1 && decision_lease_releases == 1 && - decision_lease_active == 0); - fixture_destroy(&fixture); - } -} static void test_eager_pre_acquire_evidence_change_fails_open(void) { @@ -2087,7 +2153,7 @@ static void test_eager_pre_acquire_evidence_change_fails_open(void) static void test_final_slot_stale_before_bridge_creation_fails_open(void) { fixture_t fixture; - kzt_lazy_binding_route_result_t lazy_result; + kzt_lazy_direct_route_result_t lazy_result; kzt_jump_slot_route_result_t eager_result; if (fixture_init(&fixture) != 0) { @@ -2095,14 +2161,18 @@ static void test_final_slot_stale_before_bridge_creation_fails_open(void) return; } fixture.bridge_map.target = 0; + fixture_set_unresolved_slot(&fixture); hooks_reset(&fixture, HOOK_SLOT_CONFLICT_BEFORE_FINAL_LOAD); - CHECK("lazy-final-stale.route", fixture_route(&fixture, &lazy_result) == 0); - CHECK("lazy-final-stale.preserved", - lazy_result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED && - fixture.bridge_map.add_calls == 0 && permission_begin_calls == 0 && - fixture.bridge_map.check_calls == 0 && after_cas_calls == 0 && - decision_lease_acquires == 1 && - decision_lease_releases == 1 && decision_lease_active == 0); + CHECK("lazy-final-stale.route", + fixture_lazy_direct_route(&fixture, &lazy_result) == 0); + CHECK("lazy-final-stale.status", + lazy_result.status == KZT_LAZY_DIRECT_ROUTE_GUEST_REQUIRED); + CHECK("lazy-final-stale.slot", fixture.slot != fixture_native_bridge); + CHECK("lazy-final-stale.no-write", + permission_begin_calls == 0 && after_cas_calls == 0); + CHECK("lazy-final-stale.decision-release", + decision_lease_acquires == 1 && decision_lease_releases == 1 && + decision_lease_active == 0); fixture.slot = GUEST_TARGET; fixture.bridge_map.target = 0; @@ -2124,7 +2194,7 @@ static void test_final_slot_stale_before_bridge_creation_fails_open(void) static void test_bridge_first_bindings(void) { fixture_t fixture; - kzt_lazy_binding_route_result_t lazy_result; + kzt_lazy_direct_route_result_t lazy_result; kzt_jump_slot_route_result_t eager_result; if (fixture_init(&fixture) != 0) { @@ -2132,10 +2202,12 @@ static void test_bridge_first_bindings(void) return; } fixture.bridge_map.target = 0; + fixture_set_unresolved_slot(&fixture); hooks_reset(&fixture, HOOK_NONE); - CHECK("bridge.lazy-route", fixture_route(&fixture, &lazy_result) == 0); + CHECK("bridge.lazy-route", + fixture_lazy_direct_route(&fixture, &lazy_result) == 0); CHECK("bridge.lazy-first-create", - lazy_result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED && + lazy_result.status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED && fixture.bridge_map.add_calls == 1); fixture.slot = GUEST_TARGET; @@ -2151,453 +2223,58 @@ static void test_bridge_first_bindings(void) fixture_destroy(&fixture); } -static void test_post_acquire_mutator_waits_for_writer_transaction(void) -{ - fixture_t fixture; - kzt_lazy_binding_route_result_t result; - kzt_guest_dynamic_view_t view; - kzt_guest_field_status_t status; - unsigned long generation; - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } - if (post_acquire_mutator_init(&fixture) != 0) { - CHECK("post-acquire.sync", 0); - fixture_destroy(&fixture); - return; - } - hooks_reset(&fixture, HOOK_NONE); - post_acquire_mutator.enabled = 1; - kzt_guest_registry_test_set_before_patch_decision_wait( - post_acquire_registry_wait, &post_acquire_mutator); - CHECK("post-acquire.route", fixture_route(&fixture, &result) == 0); - CHECK("post-acquire.applied", - result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED); - CHECK("post-acquire.join", pthread_join(post_acquire_mutator.thread, NULL) == 0); - CHECK("post-acquire.not-before-release", - post_acquire_mutator.done_before_release == 0); - CHECK("post-acquire.mutator-committed", - post_acquire_mutator.worker_result == KZT_GUEST_REGISTRY_UPDATED); - CHECK("post-acquire.view", kzt_guest_registry_find_dynamic_view( - fixture.context.kzt_guest_registry_context.registry, SOURCE_LINK_MAP, - &view, &status, &generation) == 0 && - status == KZT_GUEST_FIELD_OK && generation == fixture.pending.source_generation && - view.dynamic_addr == fixture.dynamic_view.dynamic_addr + 0x3000); - kzt_guest_registry_test_set_before_patch_decision_wait(NULL, NULL); - post_acquire_mutator_destroy(); - fixture_destroy(&fixture); -} -static void test_recycle_before_acquire_preserves_guest(void) -{ - fixture_t fixture; - kzt_lazy_binding_route_result_t result; - unsigned long old_generation; - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } - old_generation = fixture.pending.source_generation; - hooks_reset(&fixture, HOOK_RECYCLE_BEFORE_ACQUIRE); - CHECK("before.route", fixture_route(&fixture, &result) == 0); - CHECK("before.recycled", recycled_generation > old_generation); - CHECK("before.preserved", - result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED); - CHECK("before.slot", fixture.slot == GUEST_TARGET); - CHECK("before.no-source-memory", source_memory_access_calls == 0); - CHECK("before.no-slot-read", slot_load_calls == 0); - fixture_destroy(&fixture); -} -static void test_retire_before_first_slot_read_skips_guest_memory(void) -{ - fixture_t fixture; - kzt_guest_registry_source_lease_t source_lease = { 0 }; - uintptr_t value = 0; - unsigned long old_generation; - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } - old_generation = fixture.pending.source_generation; - hooks_reset(&fixture, HOOK_RECYCLE_BEFORE_ACQUIRE); - CHECK("first-read.load-rejected", - kzt_production_lazy_load_slot_with_lease( - &fixture.context, &fixture.pending, - (uintptr_t)&fixture.slot, &value, &source_lease) != 0); - CHECK("first-read.recycled", recycled_generation > old_generation); - CHECK("first-read.no-lease", source_lease.active == 0); - CHECK("first-read.no-slot-access", slot_load_calls == 0); - CHECK("first-read.slot-untouched", fixture.slot == GUEST_TARGET); - fixture_destroy(&fixture); -} -static void test_namespace_mismatch_precedes_any_source_read(void) -{ - fixture_t fixture; - kzt_lazy_binding_result_t result; - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } - fixture.pending.namespace_id = 1; - fixture.pending.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_EXPLICIT; - hooks_reset(&fixture, HOOK_NONE); - CHECK("namespace.complete", kzt_production_lazy_complete( - &fixture.context, &fixture.pending, &result) == 0); - CHECK("namespace.preserved", - result.status == KZT_LAZY_BINDING_GUEST_PRESERVED); - CHECK("namespace.reason", - result.reason == KZT_LAZY_BINDING_REASON_SLOT_READ_ERROR); - CHECK("namespace.real-result-empty", result.slot_before == 0 && - result.slot_after == 0 && result.selected_target == 0); - CHECK("namespace.pending-consumed", fixture.pending.armed == 0 && - result.pending_cleared == 1); - CHECK("namespace.no-lease-hook", before_acquire_calls == 0); - CHECK("namespace.no-source-memory", source_memory_access_calls == 0); - CHECK("namespace.no-slot-read", slot_load_calls == 0); - CHECK("namespace.no-memory-change", fixture.slot == GUEST_TARGET); - fixture_destroy(&fixture); -} -typedef enum guard_fail_open_mode { - GUARD_FAIL_OPEN_DISABLED = 0, - GUARD_FAIL_OPEN_CIRCUIT, - GUARD_FAIL_OPEN_BUDGET_ZERO, -} guard_fail_open_mode_t; -static void run_completion_guard_fail_open(guard_fail_open_mode_t mode, - const char *prefix) -{ - fixture_t fixture; - kzt_lazy_binding_result_t result; - kzt_patch_spike_config_t config = { 0 }; - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } - switch (mode) { - case GUARD_FAIL_OPEN_DISABLED: - /* Matches the real verifier default-options all-zero guard. */ - break; - case GUARD_FAIL_OPEN_CIRCUIT: - config = (kzt_patch_spike_config_t) { 1, 1, 1 }; - break; - case GUARD_FAIL_OPEN_BUDGET_ZERO: - config = (kzt_patch_spike_config_t) { 1, 1, 0 }; - break; - } - kzt_patch_spike_guard_init(&fixture.context.kzt_patch_spike_guard, - &config); - if (mode == GUARD_FAIL_OPEN_CIRCUIT) { - fixture.context.kzt_patch_spike_guard.circuit_open = 1; - } - hooks_reset(&fixture, HOOK_NONE); - CHECK(prefix, kzt_production_lazy_complete( - &fixture.context, &fixture.pending, &result) == 0); - CHECK(prefix, result.status == KZT_LAZY_BINDING_GUEST_PRESERVED); - CHECK(prefix, result.reason == KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE); - CHECK(prefix, result.slot_before == GUEST_TARGET); - CHECK(prefix, result.slot_after == GUEST_TARGET); - CHECK(prefix, result.selected_target == GUEST_TARGET); - CHECK(prefix, fixture.slot == GUEST_TARGET); - CHECK(prefix, fixture.context.kzt_patch_spike_guard.write_attempts == 0 && - fixture.context.kzt_patch_spike_guard.write_successes == 0); - CHECK(prefix, before_acquire_calls == 1); - CHECK(prefix, lease_seen_at_first_slot_load == 1); - CHECK(prefix, slot_load_calls > 0 && source_memory_access_calls > 0); - CHECK(prefix, fixture.pending.armed == 0 && result.pending_cleared == 1); - fixture_destroy(&fixture); -} -static void test_guard_fail_open_keeps_real_completion_evidence(void) -{ - run_completion_guard_fail_open(GUARD_FAIL_OPEN_DISABLED, - "guard-disabled-completion"); - run_completion_guard_fail_open(GUARD_FAIL_OPEN_CIRCUIT, - "guard-circuit-completion"); - run_completion_guard_fail_open(GUARD_FAIL_OPEN_BUDGET_ZERO, - "guard-budget-completion"); -} - -static void run_after_cas_retire(fixture_t *fixture, const char *prefix) -{ - retire_sync_t sync; - pthread_t thread; - kzt_lazy_binding_route_result_t result; - int created; - - if (retire_sync_init(&sync, fixture) != 0) { - CHECK(prefix, 0); - return; - } - hooks_reset(fixture, HOOK_RETIRE_AFTER_CAS); - hook_retire = &sync; - created = pthread_create(&thread, NULL, retire_worker, &sync); - CHECK(prefix, created == 0); - if (created != 0) { - retire_sync_destroy(&sync); - return; - } - CHECK(prefix, fixture_route(fixture, &result) == 0); - CHECK(prefix, pthread_join(thread, NULL) == 0); - CHECK(prefix, sync.completed_inside_hook == 0); - CHECK(prefix, sync.retire_done == 1 && sync.retire_result == 0); - CHECK(prefix, result.status == KZT_LAZY_BINDING_ROUTE_NATIVE_APPLIED); - CHECK(prefix, fixture->slot == fixture_native_bridge); - retire_sync_destroy(&sync); -} -static void test_retire_waits_for_writer_transaction(void) +static int finish_test_run(const char *pass_message) { - fixture_t fixture; - - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } - run_after_cas_retire(&fixture, "after-cas"); - fixture_destroy(&fixture); -} - -static void test_completion_holds_one_lease_until_final_release(void) -{ - fixture_t fixture; - retire_sync_t sync; - pthread_t thread; - kzt_lazy_binding_result_t result; - int created; - - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } - if (retire_sync_init(&sync, &fixture) != 0) { - CHECK("completion.sync", 0); - fixture_destroy(&fixture); - return; - } - hooks_reset(&fixture, HOOK_RETIRE_AFTER_CAS); - hook_retire = &sync; - created = pthread_create(&thread, NULL, retire_worker, &sync); - CHECK("completion.thread", created == 0); - if (created != 0) { - retire_sync_destroy(&sync); - fixture_destroy(&fixture); - return; - } - CHECK("completion.call", kzt_production_lazy_complete( - &fixture.context, &fixture.pending, &result) == 0); - CHECK("completion.join", pthread_join(thread, NULL) == 0); - CHECK("completion.first-read-leased", lease_seen_at_first_slot_load == 1); - CHECK("completion.single-acquire", before_acquire_calls == 1); - CHECK("completion.retire-blocked", sync.completed_inside_hook == 0); - CHECK("completion.release-allows-retire", sync.retire_done == 1 && - sync.retire_result == 0); - CHECK("completion.applied", result.status == KZT_LAZY_BINDING_NATIVE_APPLIED); - CHECK("completion.pending-cleared", fixture.pending.armed == 0); - retire_sync_destroy(&sync); - fixture_destroy(&fixture); -} - -static void test_retire_waits_through_verify_failure_and_rollback(void) -{ - fixture_t fixture; - retire_sync_t sync; - pthread_t thread; - kzt_lazy_binding_route_result_t result; - int created; - - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; - } - if (retire_sync_init(&sync, &fixture) != 0) { - CHECK("rollback.sync", 0); - fixture_destroy(&fixture); - return; - } - hooks_reset(&fixture, HOOK_RETIRE_AFTER_CAS_ROLLBACK); - hook_retire = &sync; - created = pthread_create(&thread, NULL, retire_worker, &sync); - CHECK("rollback.thread", created == 0); - if (created != 0) { - retire_sync_destroy(&sync); - fixture_destroy(&fixture); - return; - } - CHECK("rollback.route", fixture_route(&fixture, &result) == 0); - CHECK("rollback.join", pthread_join(thread, NULL) == 0); - CHECK("rollback.retire-blocked", sync.completed_inside_hook == 0); - CHECK("rollback.two-cas", after_cas_calls == 2); - CHECK("rollback.slot-restored", fixture.slot == GUEST_TARGET); - CHECK("rollback.guest-preserved", - result.status == KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED); - CHECK("rollback.retire-after-release", sync.retire_done == 1 && - sync.retire_result == 0); - retire_sync_destroy(&sync); - fixture_destroy(&fixture); -} - -static void test_fail_open_and_non_writer_paths(void) -{ - fixture_t fixture; - kzt_lazy_binding_route_result_t result; - kzt_lazy_binding_result_t completion_result; - kzt_guest_registry_t *live_registry; - kzt_guest_registry_t *disabled_registry; - unsigned long live_generation; - - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; + if (failures) { + fprintf(stderr, "%d lazy production lease checks failed\n", failures); + return 1; } - live_registry = fixture.context.kzt_guest_registry_context.registry; - live_generation = fixture.pending.source_generation; - - hooks_reset(&fixture, HOOK_NONE); - fixture.context.kzt_guest_registry_context.registry = NULL; - CHECK("null.route", fixture_route(&fixture, &result) == 0); - CHECK("null.preserved", result.status == - KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED && - fixture.slot == GUEST_TARGET); - CHECK("null.no-lease", before_acquire_calls == 0); - fixture.context.kzt_guest_registry_context.registry = live_registry; - - kzt_guest_registry_test_set_alloc_failure_after(1); - disabled_registry = kzt_guest_registry_init(); - kzt_guest_registry_test_set_alloc_failure_after(-1); - CHECK("disabled.registry", disabled_registry != NULL); - hooks_reset(&fixture, HOOK_NONE); - fixture.context.kzt_guest_registry_context.registry = disabled_registry; - CHECK("disabled.route", fixture_route(&fixture, &result) == 0); - CHECK("disabled.preserved", result.status == - KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED && - fixture.slot == GUEST_TARGET); - CHECK("disabled.lease-attempt", before_acquire_calls == 1); - CHECK("disabled.no-source-memory", source_memory_access_calls == 0); - CHECK("disabled.no-slot-read", slot_load_calls == 0); - fixture.context.kzt_guest_registry_context.registry = live_registry; - kzt_guest_registry_destroy(&disabled_registry); - - hooks_reset(&fixture, HOOK_NONE); - fixture.pending.source_generation = live_generation + 1; - CHECK("stale.route", fixture_route(&fixture, &result) == 0); - CHECK("stale.preserved", result.status == - KZT_LAZY_BINDING_ROUTE_GUEST_PRESERVED && - fixture.slot == GUEST_TARGET); - CHECK("stale.lease-attempt", before_acquire_calls == 1); - CHECK("stale.no-source-memory", source_memory_access_calls == 0); - CHECK("stale.no-slot-read", slot_load_calls == 0); - fixture.pending.source_generation = live_generation; - - hooks_reset(&fixture, HOOK_NONE); - reset_guard(&fixture, 0); - CHECK("disabled-guard.route", fixture_route(&fixture, &result) == 0); - CHECK("disabled-guard.exact-lease", before_acquire_calls == 1); - CHECK("disabled-guard.source-validated", source_memory_access_calls > 0); - CHECK("disabled-guard.slot", fixture.slot == GUEST_TARGET); - - reset_guard(&fixture, 1); - hooks_reset(&fixture, HOOK_NONE); - CHECK("bound.first", fixture_route(&fixture, &result) == 0); - CHECK("bound.first-applied", fixture.slot == fixture_native_bridge); - hooks_reset(&fixture, HOOK_NONE); - CHECK("bound.second", kzt_production_lazy_complete( - &fixture.context, &fixture.pending, &completion_result) == 0); - CHECK("bound.revalidation-lease", before_acquire_calls == 1); - CHECK("bound.second-result", - completion_result.status == KZT_LAZY_BINDING_GUEST_PRESERVED && - completion_result.reason == - KZT_LAZY_BINDING_REASON_NATIVE_UNAVAILABLE); - CHECK("bound.second-values", - completion_result.slot_before == fixture_native_bridge && - completion_result.slot_after == fixture_native_bridge && - completion_result.selected_target == fixture_native_bridge); - CHECK("bound.second-pending", - fixture.pending.armed == 0 && completion_result.pending_armed == 0 && - completion_result.pending_cleared == 1); - CHECK("bound.slot-preserved", fixture.slot == fixture_native_bridge); - fixture_destroy(&fixture); + puts(pass_message); + return 0; } -static void test_retire_writer_stress_1000(void) +int main(int argc, char **argv) { - fixture_t fixture; - int baseline_failures; - int i; - - if (fixture_init(&fixture) != 0) { - fixture_destroy(&fixture); - return; + if (argc != 1) { + fprintf(stderr, "this test does not accept selectors\n"); + return 2; } - baseline_failures = failures; - for (i = 0; i < STRESS_ITERATIONS; ++i) { - fixture.slot = GUEST_TARGET; - reset_guard(&fixture, 1); - run_after_cas_retire(&fixture, "stress"); - if (failures != baseline_failures) { - fprintf(stderr, "stress failed at iteration %d\n", i); - break; - } - if (i + 1 < STRESS_ITERATIONS) { - fixture.pending.source_generation = fixture_publish_source(&fixture); - } - } - CHECK("stress.iterations", i == STRESS_ITERATIONS); - fixture_destroy(&fixture); -} - -int main(void) -{ - test_successful_production_route(); test_active_loader_scope_forces_guest_fallback(); test_active_loader_scope_rejects_cached_scope_proof(); - test_production_route_does_not_need_registry_snapshot_allocation(); - test_retained_exact_handle_avoids_repeated_lifetime_validation(); test_retained_exact_handle_avoids_discovery_owner_walk(); test_unretained_provider_revalidates_lifetime(); test_eager_decision_lease_lifetime(); test_guest_version_is_not_used_for_host_lookup(); test_native_symbol_missing_fails_open(); test_dependency_symbol_owner_mismatch_fails_open(); - test_missing_bridge_is_created_from_exact_provider(); test_eager_registry_binding_selects_exact_provider(); + test_exact_owner_bridge_survives_unsupported_scope_layout(); + test_lazy_direct_no_scope_uses_global_guard(); + test_lazy_direct_no_scope_borrows_libdl_alias(); + test_dlsym_requires_non_main_source_boundary(); test_created_inexact_bridge_fails_open(); test_eager_production_request_evidence(); test_confirmed_unversioned_production_paths_apply(); test_unknown_version_evidence_preserves_guest_path(); - test_production_permission_transaction_rolls_back_on_restore_error(); - test_production_permission_transaction_reports_unrecoverable(); - test_mapping_change_waits_for_permission_transaction(); - test_permission_begin_failure_releases_decision_lease(); - test_pre_cas_rechecks_fail_open_before_permissions(); - test_pre_acquire_evidence_changes_fail_open(); + test_eager_transaction_rolls_back_to_zero(); + test_guest_relocation_ignores_optional_patch_gate(); test_eager_pre_acquire_evidence_change_fails_open(); test_final_slot_stale_before_bridge_creation_fails_open(); test_bridge_first_bindings(); - test_post_acquire_mutator_waits_for_writer_transaction(); - test_recycle_before_acquire_preserves_guest(); - test_retire_before_first_slot_read_skips_guest_memory(); - test_namespace_mismatch_precedes_any_source_read(); - test_guard_fail_open_keeps_real_completion_evidence(); - test_retire_waits_for_writer_transaction(); - test_completion_holds_one_lease_until_final_release(); - test_retire_waits_through_verify_failure_and_rollback(); - test_fail_open_and_non_writer_paths(); - test_retire_writer_stress_1000(); - if (failures) { - fprintf(stderr, "%d lazy production lease checks failed\n", failures); - return 1; - } - puts("KZT WI-256 lazy production source lease: PASS (1000 races)"); - return 0; + return finish_test_run( + "KZT lazy direct and eager production routes: PASS"); } diff --git a/tests/unit/kzt/test_wi256_plt_resolver_adapter.c b/tests/unit/kzt/test_wi256_plt_resolver_adapter.c index 6f18b225193..eaf7d82be32 100644 --- a/tests/unit/kzt/test_wi256_plt_resolver_adapter.c +++ b/tests/unit/kzt/test_wi256_plt_resolver_adapter.c @@ -3,14 +3,6 @@ #include "target/i386/latx/include/kzt_plt_resolver_adapter.h" -typedef enum begin_outcome { - BEGIN_ARMED = 0, - BEGIN_PENDING_OCCUPIED, - BEGIN_GENERATION_CHANGED, - BEGIN_NON_MAIN_NAMESPACE, - BEGIN_CALLBACK_FAILED, -} begin_outcome_t; - typedef struct fixture { CPUX86State cpu; uint64_t stack[10]; @@ -19,22 +11,8 @@ typedef struct fixture { uintptr_t return_address; uintptr_t self_link_map; uintptr_t object_guest_resolver; - uintptr_t forbidden_global_resolver; - unsigned long generation; - uintptr_t namespace_id; - kzt_guest_library_namespace_kind_t namespace_kind; - kzt_symbol_version_evidence_t version_evidence; - const char *version; - int source_enabled; int source_present; - begin_outcome_t begin_outcome; int lookup_calls; - int begin_calls; - int pending_creations; - uintptr_t completion_bridge; - uintptr_t original_return; - kzt_lazy_binding_pending_t pending; - kzt_lazy_binding_begin_request_t captured_request; } fixture_t; static int failures; @@ -63,14 +41,7 @@ static void fixture_init(fixture_t *fixture) fixture->return_address = 0x12000200; fixture->self_link_map = 0x13000300; fixture->object_guest_resolver = 0x14000400; - fixture->forbidden_global_resolver = 0x15000500; - fixture->generation = 7; - fixture->namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN; - fixture->version_evidence = KZT_SYMBOL_VERSION_VERSIONED; - fixture->version = "GLIBC_2.2.5"; - fixture->source_enabled = 1; fixture->source_present = 1; - fixture->begin_outcome = BEGIN_ARMED; reset_frame(fixture); } @@ -85,76 +56,16 @@ static int lookup_source(uintptr_t object_head, return -1; } *source = (kzt_plt_resolver_source_t) { - .enabled = fixture->source_enabled, - .context_id = 0x16000600, - .object_head = fixture->object_head, .source_link_map = fixture->self_link_map, - .source_generation = fixture->generation, - .namespace_id = fixture->namespace_id, - .namespace_kind = fixture->namespace_kind, - .version_evidence = fixture->version_evidence, - .version = fixture->version, .guest_resolver = fixture->object_guest_resolver, }; return 0; } -/* Contract 1 is mocked as a boundary. No owner/provider/native decision is - * reproduced in this adapter test. */ -static int begin_lazy_binding( - const kzt_lazy_binding_begin_request_t *request, - kzt_lazy_binding_pending_t *pending, - kzt_lazy_binding_result_t *result, void *opaque) -{ - fixture_t *fixture = opaque; - - ++fixture->begin_calls; - fixture->captured_request = *request; - memset(result, 0, sizeof(*result)); - result->selected_target = fixture->object_guest_resolver; - if (!request->enabled) { - result->status = KZT_LAZY_BINDING_BYPASS; - result->reason = KZT_LAZY_BINDING_REASON_DISABLED; - return 0; - } - switch (fixture->begin_outcome) { - case BEGIN_ARMED: - if (!pending->armed) { - pending->armed = 1; - ++fixture->pending_creations; - } - result->status = KZT_LAZY_BINDING_HANDOFF_GUEST; - result->pending_armed = 1; - break; - case BEGIN_PENDING_OCCUPIED: - result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; - result->reason = KZT_LAZY_BINDING_REASON_PENDING_OCCUPIED; - result->pending_armed = 0; - break; - case BEGIN_GENERATION_CHANGED: - result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; - result->reason = KZT_LAZY_BINDING_REASON_GENERATION_CHANGED; - result->pending_armed = 0; - break; - case BEGIN_NON_MAIN_NAMESPACE: - result->status = KZT_LAZY_BINDING_GUEST_PRESERVED; - result->reason = KZT_LAZY_BINDING_REASON_NON_MAIN_NAMESPACE; - result->pending_armed = 0; - break; - case BEGIN_CALLBACK_FAILED: - return -1; - } - return 0; -} - static kzt_plt_resolver_runtime_ops_t ops_for(fixture_t *fixture) { return (kzt_plt_resolver_runtime_ops_t) { .lookup_source = lookup_source, - .begin_lazy_binding = begin_lazy_binding, - .pending = &fixture->pending, - .completion_bridge = fixture->completion_bridge, - .original_return = &fixture->original_return, .opaque = fixture, }; } @@ -168,8 +79,6 @@ static void check_guest_frame(const char *prefix, fixture_t *fixture) CHECK("frame.self-link-map", sp[1] == fixture->self_link_map); CHECK("frame.relocation-slot", sp[2] == fixture->relocation_slot); CHECK("frame.return-address", sp[3] == fixture->return_address); - CHECK("frame.no-global-resolver", - sp[0] != fixture->forbidden_global_resolver); } static void check_original_frame(const char *prefix, fixture_t *fixture) @@ -182,274 +91,78 @@ static void check_original_frame(const char *prefix, fixture_t *fixture) CHECK("original.return-address", sp[2] == fixture->return_address); } -static void test_reads_real_frame_and_preserves_return_address(void) +static void test_handoff_preserves_exact_return_address(void) { - fixture_t f; + fixture_t fixture; kzt_plt_resolver_runtime_ops_t ops; kzt_plt_resolver_enter_result_t result; - fixture_init(&f); - ops = ops_for(&f); - CHECK("frame.enter", kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); - CHECK("frame.status", - result.status == KZT_PLT_RESOLVER_HANDOFF_GUEST); - CHECK("frame.object", result.object_head == f.object_head); - CHECK("frame.slot", result.relocation_slot == f.relocation_slot); - CHECK("frame.return", result.return_address == f.return_address); - CHECK("frame.lookup-once", f.lookup_calls == 1); - CHECK("frame.begin-once", f.begin_calls == 1); - check_guest_frame("frame.stack-pointer", &f); + fixture_init(&fixture); + ops = ops_for(&fixture); + CHECK("handoff.enter", + kzt_plt_resolver_enter(&fixture.cpu, &ops, &result) == 0); + CHECK("handoff.status", result.status == KZT_PLT_RESOLVER_HANDOFF_GUEST); + CHECK("handoff.object", result.object_head == fixture.object_head); + CHECK("handoff.slot", result.relocation_slot == fixture.relocation_slot); + CHECK("handoff.return", result.return_address == fixture.return_address); + CHECK("handoff.selected", + result.selected_resolver == fixture.object_guest_resolver); + CHECK("handoff.lookup-once", fixture.lookup_calls == 1); + check_guest_frame("handoff.stack-pointer", &fixture); } -static void test_handoff_without_completion_bridge_keeps_return_slot(void) +static void test_each_object_uses_its_own_guest_resolver(void) { - fixture_t f; + fixture_t fixture; kzt_plt_resolver_runtime_ops_t ops; kzt_plt_resolver_enter_result_t result; - uint64_t *sp; - - fixture_init(&f); - f.original_return = 0xfeedface; - ops = ops_for(&f); - CHECK("no-bridge.enter", - kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); - CHECK("no-bridge.handoff", - result.status == KZT_PLT_RESOLVER_HANDOFF_GUEST); - sp = (uint64_t *)f.cpu.regs[R_ESP]; - CHECK("no-bridge.final-rsp", sp == &f.stack[2]); - CHECK("no-bridge.resolver", sp[0] == f.object_guest_resolver); - CHECK("no-bridge.link-map", sp[1] == f.self_link_map); - CHECK("no-bridge.slot", sp[2] == f.relocation_slot); - CHECK("no-bridge.return", sp[3] == f.return_address); - CHECK("no-bridge.original-return-unchanged", - f.original_return == 0xfeedface); -} -static void test_handoff_with_completion_bridge_preserves_original_return(void) -{ - fixture_t f; - kzt_plt_resolver_runtime_ops_t ops; - kzt_plt_resolver_enter_result_t result; - uint64_t *sp; - - fixture_init(&f); - f.completion_bridge = 0x17000700; - ops = ops_for(&f); - CHECK("bridge.enter", - kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); - CHECK("bridge.handoff", result.status == KZT_PLT_RESOLVER_HANDOFF_GUEST); - sp = (uint64_t *)f.cpu.regs[R_ESP]; - CHECK("bridge.final-rsp", sp == &f.stack[2]); - CHECK("bridge.resolver", sp[0] == f.object_guest_resolver); - CHECK("bridge.link-map", sp[1] == f.self_link_map); - CHECK("bridge.slot", sp[2] == f.relocation_slot); - CHECK("bridge.return", sp[3] == f.completion_bridge); - CHECK("bridge.original-return", f.original_return == f.return_address); -} - -static void test_uses_per_object_resolver_and_calls_only_contract_one(void) -{ - fixture_t f; - kzt_plt_resolver_runtime_ops_t ops; - kzt_plt_resolver_enter_result_t result; - - fixture_init(&f); - ops = ops_for(&f); - CHECK("object.enter", kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); + fixture_init(&fixture); + fixture.object_head = 0x21000100; + fixture.self_link_map = 0x23000300; + fixture.object_guest_resolver = 0x24000400; + reset_frame(&fixture); + ops = ops_for(&fixture); + CHECK("object.enter", + kzt_plt_resolver_enter(&fixture.cpu, &ops, &result) == 0); CHECK("object.selected", - result.selected_resolver == f.object_guest_resolver); - CHECK("object.not-global", - result.selected_resolver != f.forbidden_global_resolver); - CHECK("object.contract-one-once", f.begin_calls == 1); - CHECK("object.request-link-map", - f.captured_request.source_link_map == f.self_link_map); - CHECK("object.request-generation", - f.captured_request.source_generation == f.generation); - CHECK("object.request-namespace", - f.captured_request.namespace_id == f.namespace_id && - f.captured_request.namespace_kind == f.namespace_kind); - CHECK("object.request-relocation", - f.captured_request.relocation_index == f.relocation_slot); -} - -static void test_forwards_explicit_version_evidence(void) -{ - static const struct { - kzt_symbol_version_evidence_t evidence; - const char *version; - } cases[] = { - { KZT_SYMBOL_VERSION_VERSIONED, "GLIBC_2.2.5" }, - { KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED, NULL }, - { KZT_SYMBOL_VERSION_ERROR, NULL }, - }; - size_t i; - - for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { - fixture_t f; - kzt_plt_resolver_runtime_ops_t ops; - kzt_plt_resolver_enter_result_t result; - - fixture_init(&f); - f.version_evidence = cases[i].evidence; - f.version = cases[i].version; - ops = ops_for(&f); - CHECK("version.enter", - kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); - CHECK("version.evidence", - f.captured_request.version_evidence == cases[i].evidence); - CHECK("version.value", - f.captured_request.version == cases[i].version); - } + result.selected_resolver == fixture.object_guest_resolver); + check_guest_frame("object.stack-pointer", &fixture); } -static void test_main_generation_match_creates_one_pending(void) +static void test_missing_source_leaves_intercepted_frame_unchanged(void) { - fixture_t f; + fixture_t fixture; kzt_plt_resolver_runtime_ops_t ops; kzt_plt_resolver_enter_result_t result; - fixture_init(&f); - ops = ops_for(&f); - CHECK("pending.first", kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); - CHECK("pending.first-armed", result.pending_armed == 1); - CHECK("pending.created-once", f.pending_creations == 1); - - reset_frame(&f); - f.begin_outcome = BEGIN_PENDING_OCCUPIED; - CHECK("pending.second", kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); - CHECK("pending.second-fail-open", - result.status == KZT_PLT_RESOLVER_GUEST_PRESERVED); - CHECK("pending.still-created-once", f.pending_creations == 1); - CHECK("pending.contract-called-per-entry", f.begin_calls == 2); - check_guest_frame("pending.second-stack", &f); -} - -static void test_nested_pending_busy_preserves_outer_cpu_pending(void) -{ - fixture_t f; - kzt_plt_resolver_runtime_ops_t ops; - kzt_plt_resolver_enter_result_t result; - kzt_lazy_binding_pending_t saved; - - fixture_init(&f); - f.pending = (kzt_lazy_binding_pending_t) { - .armed = 1, - .context_id = 0x9000, - .source_link_map = 0x9100, - .source_generation = 11, - .relocation_index = 4, - .slot_addr = 0x9200, - .guest_resolver = 0x9300, - }; - saved = f.pending; - f.begin_outcome = BEGIN_PENDING_OCCUPIED; - ops = ops_for(&f); - CHECK("nested.enter", kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); - CHECK("nested.preserved", !memcmp(&f.pending, &saved, sizeof(saved))); - CHECK("nested.status", - result.status == KZT_PLT_RESOLVER_GUEST_PRESERVED); - check_guest_frame("nested.stack-pointer", &f); -} - -static void test_missing_object_restores_original_frame(void) -{ - fixture_t f; - kzt_plt_resolver_runtime_ops_t ops; - kzt_plt_resolver_enter_result_t result; - - fixture_init(&f); - ops = ops_for(&f); - f.source_present = 0; - CHECK("missing.enter", kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); + fixture_init(&fixture); + fixture.source_present = 0; + ops = ops_for(&fixture); + CHECK("missing.enter", + kzt_plt_resolver_enter(&fixture.cpu, &ops, &result) == 0); CHECK("missing.status", result.status == KZT_PLT_RESOLVER_LEGACY_FRAME_RESTORED); - CHECK("missing.no-contract-one", f.begin_calls == 0); - CHECK("missing.no-pending", f.pending_creations == 0); - check_original_frame("missing.stack-pointer", &f); + CHECK("missing.no-selected-resolver", result.selected_resolver == 0); + check_original_frame("missing.stack-pointer", &fixture); } -static void test_second_object_lookup_failure_never_uses_first_resolver(void) +static void test_invalid_arguments_fail_without_touching_the_frame(void) { - fixture_t f; - kzt_plt_resolver_runtime_ops_t ops; - kzt_plt_resolver_enter_result_t result; - - fixture_init(&f); - f.object_head = 0x21000100; - f.self_link_map = 0x23000300; - f.object_guest_resolver = 0x24000400; - f.forbidden_global_resolver = 0x14000400; - f.source_present = 0; - reset_frame(&f); - ops = ops_for(&f); - CHECK("second-missing.enter", - kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); - CHECK("second-missing.legacy-frame", - result.status == KZT_PLT_RESOLVER_LEGACY_FRAME_RESTORED); - CHECK("second-missing.no-selected-resolver", - result.selected_resolver == 0); - CHECK("second-missing.not-first-resolver", - result.selected_resolver != f.forbidden_global_resolver); - CHECK("second-missing.no-begin", f.begin_calls == 0); - check_original_frame("second-missing.stack-pointer", &f); -} - -static void test_disabled_object_hands_off_once_to_its_guest_resolver(void) -{ - fixture_t f; + fixture_t fixture; kzt_plt_resolver_runtime_ops_t ops; kzt_plt_resolver_enter_result_t result; uintptr_t original_sp; - fixture_init(&f); - f.source_enabled = 0; - f.generation = 0; - f.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_UNSUPPORTED; - original_sp = f.cpu.regs[R_ESP]; - ops = ops_for(&f); - CHECK("disabled-fallback.enter", - kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); - CHECK("disabled-fallback.status", - result.status == KZT_PLT_RESOLVER_GUEST_PRESERVED); - CHECK("disabled-fallback.resolver", - result.selected_resolver == f.object_guest_resolver); - CHECK("disabled-fallback.lookup-once", f.lookup_calls == 1); - CHECK("disabled-fallback.begin-once", f.begin_calls == 1); - CHECK("disabled-fallback.no-pending", f.pending_creations == 0); - CHECK("disabled-fallback.one-resolver-push", - f.cpu.regs[R_ESP] + sizeof(uint64_t) == original_sp); - check_guest_frame("disabled-fallback.stack-pointer", &f); -} - -static void test_contract_one_rejections_restore_guest_frame(void) -{ - const begin_outcome_t outcomes[] = { - BEGIN_PENDING_OCCUPIED, - BEGIN_GENERATION_CHANGED, - BEGIN_NON_MAIN_NAMESPACE, - BEGIN_CALLBACK_FAILED, - }; - - for (size_t i = 0; i < sizeof(outcomes) / sizeof(outcomes[0]); ++i) { - fixture_t f; - kzt_plt_resolver_runtime_ops_t ops; - kzt_plt_resolver_enter_result_t result; - - fixture_init(&f); - ops = ops_for(&f); - f.begin_outcome = outcomes[i]; - if (outcomes[i] == BEGIN_NON_MAIN_NAMESPACE) { - f.namespace_id = 1; - f.namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_EXPLICIT; - } - CHECK("reject.enter", - kzt_plt_resolver_enter(&f.cpu, &ops, &result) == 0); - CHECK("reject.status", - result.status == KZT_PLT_RESOLVER_GUEST_PRESERVED); - CHECK("reject.contract-one-once", f.begin_calls == 1); - CHECK("reject.no-pending", f.pending_creations == 0); - check_guest_frame("reject.stack-pointer", &f); - } + fixture_init(&fixture); + ops = ops_for(&fixture); + original_sp = fixture.cpu.regs[R_ESP]; + CHECK("invalid.no-cpu", kzt_plt_resolver_enter(NULL, &ops, &result) == -1); + CHECK("invalid.no-ops", + kzt_plt_resolver_enter(&fixture.cpu, NULL, &result) == -1); + CHECK("invalid.no-result", + kzt_plt_resolver_enter(&fixture.cpu, &ops, NULL) == -1); + CHECK("invalid.stack-unchanged", fixture.cpu.regs[R_ESP] == original_sp); } static void test_resolver_injection_requires_distinct_guest_target(void) @@ -505,17 +218,10 @@ static void test_resolver_entry_bounds_are_checked_before_access(void) int main(void) { - test_reads_real_frame_and_preserves_return_address(); - test_handoff_without_completion_bridge_keeps_return_slot(); - test_handoff_with_completion_bridge_preserves_original_return(); - test_uses_per_object_resolver_and_calls_only_contract_one(); - test_forwards_explicit_version_evidence(); - test_main_generation_match_creates_one_pending(); - test_nested_pending_busy_preserves_outer_cpu_pending(); - test_missing_object_restores_original_frame(); - test_second_object_lookup_failure_never_uses_first_resolver(); - test_disabled_object_hands_off_once_to_its_guest_resolver(); - test_contract_one_rejections_restore_guest_frame(); + test_handoff_preserves_exact_return_address(); + test_each_object_uses_its_own_guest_resolver(); + test_missing_source_leaves_intercepted_frame_unchanged(); + test_invalid_arguments_fail_without_touching_the_frame(); test_resolver_injection_requires_distinct_guest_target(); test_resolver_entry_bounds_are_checked_before_access(); if (failures) { @@ -523,6 +229,6 @@ int main(void) failures); return 1; } - puts("KZT WI-256 PLT resolver adapter: PASS"); + puts("WI-256 PLT resolver adapter tests: PASS"); return 0; } diff --git a/tests/unit/kzt/test_wi256_plt_resolver_source_contract.py b/tests/unit/kzt/test_wi256_plt_resolver_source_contract.py index a24c72c60e6..28714dab5e1 100644 --- a/tests/unit/kzt/test_wi256_plt_resolver_source_contract.py +++ b/tests/unit/kzt/test_wi256_plt_resolver_source_contract.py @@ -7,30 +7,56 @@ def fail(message: str) -> None: raise AssertionError(message) -root = pathlib.Path(sys.argv[1]).resolve() -adapter = root / "target/i386/latx/context/kzt_plt_resolver_adapter.c" -elfloader = root / "target/i386/latx/context/elfloader.c" +def function_body(text: str, signature: str) -> str: + start = text.find(signature) + if start < 0: + fail(f"missing function: {signature}") + body_start = text.find("{", start) + depth = 0 + for offset, char in enumerate(text[body_start:], start=body_start): + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + return text[start:offset + 1] + fail(f"unterminated function: {signature}") -if not adapter.is_file(): - fail(f"missing production adapter: {adapter}") -adapter_text = adapter.read_text(encoding="utf-8") -elfloader_text = elfloader.read_text(encoding="utf-8") -elfloader_private_text = ( - root / "target/i386/latx/include/elfloader_private.h" -).read_text(encoding="utf-8") -lazy_header = (root / "target/i386/latx/include/kzt_lazy_binding.h").read_text( - encoding="utf-8" -) -lazy_core = (root / "target/i386/latx/context/kzt_lazy_binding.c").read_text( - encoding="utf-8" +root = pathlib.Path(sys.argv[1]).resolve() +adapter_path = root / "target/i386/latx/context/kzt_plt_resolver_adapter.c" +elfloader_path = root / "target/i386/latx/context/elfloader.c" +production_path = ( + root / "target/i386/latx/context/kzt_jump_slot_production.c" ) +elf_private_path = root / "target/i386/latx/include/elfloader_private.h" -if not any( - call in adapter_text - for call in ("kzt_lazy_binding_begin(", "->begin_lazy_binding(") +for path in (adapter_path, elfloader_path, production_path, elf_private_path): + if not path.is_file(): + fail(f"missing production source: {path}") + +adapter = adapter_path.read_text(encoding="utf-8") +elfloader = elfloader_path.read_text(encoding="utf-8") +production = production_path.read_text(encoding="utf-8") +elf_private = elf_private_path.read_text(encoding="utf-8") + +for removed_path in ( + root / "target/i386/latx/context/kzt_lazy_binding.c", + root / "target/i386/latx/include/kzt_lazy_binding.h", + root / "target/i386/latx/context/kzt_lazy_diagnostics.c", + root / "target/i386/latx/include/kzt_lazy_diagnostics.h", ): - fail("resolver adapter must delegate pending decisions to Contract 1") + if removed_path.exists(): + fail(f"post-bind completion source still exists: {removed_path}") + +for forbidden in ( + "completion_bridge", + "original_return", + "begin_lazy_binding", + "kzt_lazy_binding_pending", +): + if forbidden in adapter: + fail(f"resolver adapter rewrites the guest return frame: {forbidden}") for forbidden in ( "getAlternate(", @@ -38,285 +64,124 @@ def fail(message: str) -> None: "kzt_owner_resolver", "kzt_wrapper_probe", "kzt_bridge_exact", + "dl_runtime_resolver", ): - if forbidden in adapter_text: + if forbidden in adapter: fail(f"resolver adapter duplicates native resolution: {forbidden}") -if "dl_runtime_resolver" in adapter_text: - fail("resolver adapter must never substitute a process-global resolver") - -production_state_start = elfloader_text.find( - "typedef struct kzt_plt_resolver_production_state" -) -production_lookup_start = elfloader_text.find( - "static int kzt_plt_resolver_lookup_source(", production_state_start +lookup = function_body( + elfloader, "static int kzt_plt_resolver_lookup_source(" ) -resolver_start = elfloader_text.find("void PltResolver(void)") -if min(production_state_start, production_lookup_start, resolver_start) < 0: - fail("missing production PLT resolver evidence path") -production_state = elfloader_text[ - production_state_start:production_lookup_start -] -production_lookup = elfloader_text[production_lookup_start:resolver_start] -if "kzt_symbol_version_evidence_t version_evidence;" not in production_state: - fail("production resolver state must retain explicit version evidence") -if ".version_evidence = state->version_evidence," not in production_lookup: - fail("production lookup must forward explicit version evidence") -if "kzt_guest_registry_find_lazy_source(" not in production_lookup: - fail("production lookup must use the single-lock, allocation-free Registry source query") -if "state->head->kzt_guest_resolver" not in production_lookup: - fail("production lookup must retain a per-object guest resolver fallback") -if ".enabled = 0," not in production_lookup: - fail("per-object resolver fallback must disable native completion") -if ".guest_resolver = state->head->kzt_guest_resolver," not in production_lookup: - fail("per-object fallback must select the current object's guest resolver") -for forbidden_lookup_step in ( +if "kzt_guest_registry_find_lazy_source(" not in lookup: + fail("production lookup must use the allocation-free Registry query") +if "state->head->kzt_guest_resolver" not in lookup: + fail("production lookup must retain the per-object guest resolver") +for forbidden in ( "kzt_guest_registry_find_by_link_map(", "kzt_guest_registry_find_lazy_resolver(", "kzt_guest_object_snapshot_free(", ): - if forbidden_lookup_step in production_lookup: - fail( - "production lookup retains the allocating or double-lock Registry path: " - f"{forbidden_lookup_step}" - ) + if forbidden in lookup: + fail(f"production lookup uses stale Registry path: {forbidden}") -entry = "kzt_plt_resolver_enter(" -if resolver_start < 0: - fail("missing production PltResolver slow path") -body_start = elfloader_text.find("{", resolver_start) -depth = 0 -resolver_end = -1 -for offset, char in enumerate(elfloader_text[body_start:], start=body_start): - if char == "{": - depth += 1 - elif char == "}": - depth -= 1 - if depth == 0: - resolver_end = offset + 1 - break -if resolver_end < 0: - fail("could not delimit production PltResolver") -resolver_body = elfloader_text[resolver_start:resolver_end] -for required_version_mapping in ( +resolver = function_body(elfloader, "void PltResolver(void)") +for required in ( "version == -1 || version < 2", "KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED", - "vername && vername[0]", "KZT_SYMBOL_VERSION_VERSIONED", "KZT_SYMBOL_VERSION_ERROR", - ".version_evidence = version_evidence,", ): - if required_version_mapping not in resolver_body: - fail( - "PltResolver must explicitly classify versioned, unversioned, " - f"and malformed version evidence: {required_version_mapping}" - ) -if entry not in resolver_body: - fail("PltResolver slow path does not call kzt_plt_resolver_enter") + if required not in resolver: + fail(f"resolver misses version evidence mapping: {required}") -direct_entry = "kzt_production_lazy_direct_route(" -if direct_entry not in resolver_body: - fail("PltResolver must try the WI-837 evidence-backed direct route") -direct_call = resolver_body.find(direct_entry) -adapter_call = resolver_body.find(entry) -guest_handoff_call = resolver_body.rfind( - "plt_resolver_handoff_guest_or_abort(" -) -if not (direct_call < adapter_call < guest_handoff_call): - fail("PltResolver must try direct route, then adapter, then guest handoff") +direct_call = resolver.find("kzt_production_lazy_direct_route(") +adapter_call = resolver.find("kzt_plt_resolver_enter(") +guest_call = resolver.rfind("plt_resolver_handoff_guest_or_abort(") +if not (0 <= direct_call < adapter_call < guest_call): + fail("resolver must try direct route, then adapter, then guest handoff") -for forbidden_lazy_step in ( +for forbidden in ( "plt_resolver_lookup_host_symbol(", "getAlternate(", "KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE", "dl_runtime_resolver", + "KztLazyBindingCompleteResolver", + "kzt_production_lazy_complete(", ): - if forbidden_lazy_step in resolver_body: - fail(f"PltResolver retains removed lazy legacy logic: {forbidden_lazy_step}") -if "*p = offs" in resolver_body or "*p = legacy_target" in resolver_body: - fail("PltResolver must not perform the historical direct GOT write") + if forbidden in resolver: + fail(f"resolver retains removed post-bind logic: {forbidden}") +if "*p = offs" in resolver or "*p = legacy_target" in resolver: + fail("resolver must not perform the historical direct GOT write") -for required_read in ( +for required in ( "uintptr_t *intercepted_frame = (uintptr_t *)cpu->regs[R_ESP];", "uintptr_t addr = intercepted_frame[0];", "uint64_t relocation_slot = intercepted_frame[1];", "uintptr_t return_address = intercepted_frame[2];", ): - if required_read not in resolver_body[:direct_call]: - fail(f"PltResolver must read, not consume, the adapter frame: {required_read}") + if required not in resolver[:direct_call]: + fail(f"resolver must read the original frame: {required}") -slot_check = resolver_body.find( - "kzt_plt_resolver_relocation_index_valid(" -) -relocation_access = resolver_body.find("Elf64_Rela * rel") -symbol_index_read = resolver_body.find( - "symbol_index = ELF64_R_SYM(rel->r_info);" -) -symbol_check = resolver_body.find( - "kzt_plt_resolver_symbol_index_valid(" -) -symbol_access = resolver_body.find("Elf64_Sym *sym = &h->DynSym[symbol_index];") -if not ( - 0 <= slot_check < relocation_access < symbol_index_read < - symbol_check < symbol_access < direct_call -): - fail( - "PltResolver must validate relocation and symbol indexes before " - "accessing their tables" - ) - -direct_prefix = resolver_body[direct_call:adapter_call] +direct_prefix = resolver[direct_call:adapter_call] if direct_prefix.count("Pop64(cpu)") != 2: fail("direct route must consume exactly object_head and relocation_slot") -if ( - "direct_result.status == KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED" - not in direct_prefix - or "Push64(cpu, direct_result.selected_target);" not in direct_prefix - or "return;" not in direct_prefix -): - fail("direct route success must return its selected bridge") - -if "Return=%p" not in resolver_body or "(void*)return_address" not in resolver_body: - fail("PltResolver Return diagnostic must report intercepted_frame[2]") -if "*(void**)(cpu->regs[R_ESP])" in resolver_body: - fail("PltResolver Return diagnostic must not report intercepted_frame[0]") - -guest_prefix = resolver_body[adapter_call:guest_handoff_call] -if ( - "enter_result.status != KZT_PLT_RESOLVER_LEGACY_FRAME_RESTORED" - not in guest_prefix - or "return;" not in guest_prefix +for required in ( + "KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED", + "KZT_LAZY_DIRECT_ROUTE_NATIVE_TRANSIENT", + "Push64(cpu, direct_result.selected_target);", + "return;", ): - fail("adapter-handled PltResolver paths must return before direct guest handoff") -guest_suffix = resolver_body[guest_handoff_call:] -if "return;" not in guest_suffix: - fail("per-object guest handoff must return without legacy host resolution") -handoff_helper_start = elfloader_text.find( - "static void plt_resolver_handoff_guest_or_abort(" -) -if handoff_helper_start < 0: - fail("missing checked guest resolver handoff") -handoff_helper_end = elfloader_text.find( - "\nvoid PltResolver(void)", handoff_helper_start -) -handoff_helper = elfloader_text[handoff_helper_start:handoff_helper_end] -if "abort();" not in handoff_helper: - fail("missing guest resolver must not return with an intercepted frame") - -relocate_start = elfloader_text.find("int RelocateElfPlt(") -relocate_end = elfloader_text.find("\n#if 0", relocate_start) -if relocate_start < 0 or relocate_end < 0: - fail("could not delimit RelocateElfPlt cold path") -relocate_body = elfloader_text[relocate_start:relocate_end] -if "kzt_lazy_completion_bridge = AddBridge(" not in relocate_body: - fail("completion bridge must be created in RelocateElfPlt cold path") -if "uintptr_t kzt_guest_resolver;" not in elfloader_private_text: + if required not in direct_prefix: + fail(f"direct route success contract missing: {required}") +if "plt_resolver_abort_unrecoverable(" not in direct_prefix: + fail("unrecoverable direct writer failure can reach guest handoff") + +adapter_prefix = resolver[adapter_call:guest_call] +if "KZT_PLT_RESOLVER_LEGACY_FRAME_RESTORED" not in adapter_prefix: + fail("adapter result is not checked before guest handoff") +if "return;" not in resolver[guest_call:]: + fail("guest handoff must return without post-bind completion") +if "Return=%p" not in resolver or "(void*)return_address" not in resolver: + fail("resolver diagnostic must report the original return address") + +relocate = function_body(elfloader, "int RelocateElfPlt(") +if "uintptr_t kzt_guest_resolver;" not in elf_private: fail("elfheader must retain its own guest PLT resolver") -if "head->kzt_guest_resolver = guest_resolver;" not in relocate_body: - fail("RelocateElfPlt must retain the current object's guest resolver") -injection_check = relocate_body.find( - "kzt_plt_resolver_injection_allowed(" -) -resolver_save = relocate_body.find( - "head->kzt_guest_resolver = guest_resolver;" -) -resolver_install = relocate_body.find( - "*(uintptr_t*)(resolver_got_runtime+16) = resolver_bridge;" -) -if not (0 <= injection_check < resolver_save < resolver_install): - fail( - "RelocateElfPlt must validate and retain the guest resolver before " - "injecting KZT" - ) -if "dl_runtime_resolver" in relocate_body: - fail("RelocateElfPlt must not retain a process-global guest resolver") -if "*(uintptr_t*)(head->got+head->delta+16)" in relocate_body: - fail("RelocateElfPlt must not derive the guest resolver from head->got") - -for forbidden_seam in ("resolve_native_bridge", "compare_exchange_slot"): - if forbidden_seam in lazy_header or forbidden_seam in lazy_core: - fail(f"lazy core retains test-only fallback seam: {forbidden_seam}") - -production_route = ( - root / "target/i386/latx/context/kzt_jump_slot_production.c" -).read_text(encoding="utf-8") -runtime_bridge = ( - root / "target/i386/latx/context/kzt_rela_runtime_bridge.c" -).read_text(encoding="utf-8") -if "production_probe_prepared_wrapper_cache" in production_route: - fail("production route retains an unused cache-only wrapper probe") - -mutex_cursor = 0 -while True: - mutex_start = runtime_bridge.find("pthread_mutex_lock", mutex_cursor) - if mutex_start < 0: - break - mutex_end = runtime_bridge.find("pthread_mutex_unlock", mutex_start) - if mutex_end < 0: - fail("bridge mutex critical section has no matching unlock") - critical_section = runtime_bridge[mutex_start:mutex_end] - for loader_call in ("dlsym(", "dlvsym(", "dlinfo(", "dladdr(", - "dladdr1("): - if loader_call in critical_section: - fail(f"bridge mutex encloses loader inspection: {loader_call}") - mutex_cursor = mutex_end + 1 -resolver_complete_start = elfloader_text.find( - "static void KztLazyBindingCompleteResolver(void)" -) -resolver_complete_end = elfloader_text.find( - "typedef struct kzt_plt_resolver_production_state", resolver_complete_start -) -if resolver_complete_start < 0 or resolver_complete_end < 0: - fail("could not delimit lazy completion resolver") -if "kzt_production_lazy_complete(" not in elfloader_text[ - resolver_complete_start:resolver_complete_end -]: - fail("completion resolver must call the testable production orchestration") -if "kzt_lazy_binding_complete(" in elfloader_text[ - resolver_complete_start:resolver_complete_end -]: - fail("completion resolver bypasses production lease orchestration") -lazy_production_start = production_route.find( - "int kzt_production_lazy_route_guest_target(" -) -if lazy_production_start < 0: - fail("missing lazy production route") -lazy_production = production_route[lazy_production_start:] -for forbidden_lookup in ( - "GetGlobalSymbolStartEndWithProvider", - "GetGlobalSymbolStartEnd", - "GetMaplib", -): - if forbidden_lookup in lazy_production: - fail(f"lazy completion repeats legacy symbol lookup: {forbidden_lookup}") -if "validate_source_identity" not in production_route: - fail("production route lacks write-adjacent source generation recheck") for required in ( + "head->kzt_guest_resolver = guest_resolver;", + ".object_head = (uintptr_t)head,", +): + if required not in relocate: + fail(f"RelocateElfPlt misses per-object resolver evidence: {required}") +for forbidden in ( + "KztLazyBindingCompleteResolver", + "kzt_lazy_completion_bridge", + "dl_runtime_resolver", +): + if forbidden in elfloader: + fail(f"guest fallback retains synthetic completion state: {forbidden}") + +direct = function_body(production, "int kzt_production_lazy_direct_route(") +if "kzt_lazy_direct_route_apply(" not in direct: + fail("production direct route does not use the verified direct core") +for removed_api in ( + "kzt_lazy_binding_", + "kzt_production_lazy_complete(", + "kzt_production_lazy_route_guest_target(", + "kzt_production_lazy_source_lease_acquire(", "kzt_production_lazy_load_slot_with_lease(", - "kzt_lazy_binding_complete(", - "kzt_guest_registry_source_lease_release(", ): - if required not in production_route: - fail(f"production completion orchestration missing: {required}") + if removed_api in production: + fail(f"production retains post-bind completion API: {removed_api}") +entry = "kzt_plt_resolver_enter(" production_hits = [] for path in (root / "target/i386").rglob("*.[ch]"): - if path == adapter or path.name == "kzt_plt_resolver_adapter.h": + if path == adapter_path or path.name == "kzt_plt_resolver_adapter.h": continue if entry in path.read_text(encoding="utf-8", errors="ignore"): production_hits.append(path.relative_to(root).as_posix()) - if production_hits != ["target/i386/latx/context/elfloader.c"]: - fail(f"resolver enter escaped the one-shot PltResolver path: {production_hits}") - -for hot_root in ( - root / "target/i386/tcg", - root / "target/i386/latx/translator", -): - if not hot_root.exists(): - continue - for path in hot_root.rglob("*.[ch]"): - if entry in path.read_text(encoding="utf-8", errors="ignore"): - fail(f"per-TB resolver check introduced in {path.relative_to(root)}") + fail(f"resolver enter escaped the one-shot path: {production_hits}") -print("WI-256 PLT resolver one-shot source contract: PASS") +print("WI-256 PLT resolver direct-route source contract: PASS") diff --git a/tests/unit/kzt/test_wi382_registry_observation_source_contract.py b/tests/unit/kzt/test_wi382_registry_observation_source_contract.py index 55e32c6775d..527bed85257 100644 --- a/tests/unit/kzt/test_wi382_registry_observation_source_contract.py +++ b/tests/unit/kzt/test_wi382_registry_observation_source_contract.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import pathlib +import re import sys @@ -17,9 +18,26 @@ def function_body(text: str, signature: str) -> str: raise AssertionError(f"unterminated function: {signature}") +def if_condition(text: str, marker: str) -> str: + start = text.index(marker) + opening = text.index("(", start) + depth = 0 + for pos in range(opening, len(text)): + if text[pos] == "(": + depth += 1 + elif text[pos] == ")": + depth -= 1 + if depth == 0: + return " ".join(text[opening + 1:pos].split()) + raise AssertionError(f"unterminated if condition: {marker}") + + root = pathlib.Path(sys.argv[1]).resolve() myalign = (root / "target/i386/latx/context/myalign.c").read_text() elfloader = (root / "target/i386/latx/context/elfloader.c").read_text() +registry = ( + root / "target/i386/latx/context/kzt_guest_registry.c" +).read_text() adapter = ( root / "target/i386/latx/context/kzt_observation_adapter.c" ).read_text() @@ -36,14 +54,57 @@ def function_body(text: str, signature: str) -> str: root / "target/i386/latx/context/kzt_jump_slot_production.c" ).read_text() -# The KZT callback materializes only the wrapper binding under the callback -# gate. It does not reconstruct an ELF object or derive private glibc data. +# Registry observation materializes the exact observed link_map address as the +# object identity. Invalid identity input must fail before registry lookup. +snapshot = function_body( + registry, "static int kzt_snapshot_from_observation(" +) +assert "snapshot->link_map_addr = observation->link_map_addr;" in snapshot +observe = function_body( + registry, "kzt_guest_registry_result_t kzt_guest_registry_observe_with_diagnostic(" +) +invalid_identity = if_condition(observe, "if (!observation") +assert invalid_identity == "!observation || observation->link_map_addr == 0" +assert observe.index("if (!observation") < observe.index( + "kzt_find_object_index(registry, observation->link_map_addr)" +) + +# The KZT callback consumes that exact identity through a Registry-owned copy. +# No guest link_map pointer escapes the reader/observation boundary. materialize = function_body( myalign, "static int kzt_tb_callback_materialize_binding(" ) -identity_guard = materialize.index("if (!link_map") -first_dereference = materialize.index("link_map->") -assert identity_guard < first_dereference +assert "struct link_map_x64" not in materialize +assert "link_map->" not in materialize +assert "kzt_guest_registry_address_match_t match = { 0 };" in materialize +assert "kzt_guest_registry_find_live_object(" in materialize +identity_guard = if_condition(materialize, "if (!context") +guard_terms = ( + "!context", + "!link_map_addr", + "kzt_guest_registry_find_live_object(", + "match.match_count != 1", + "match.path_status != KZT_GUEST_FIELD_OK", +) +assert all(term in identity_guard for term in guard_terms) +assert [identity_guard.index(term) for term in guard_terms] == sorted( + identity_guard.index(term) for term in guard_terms +) +assert "name = match.path;" in materialize +assert re.search( + r"kzt_guest_library_wrapper_source_acquire\(\s*" + r"context,\s*link_map_addr,\s*name,\s*basename,\s*&source_proof\)", + materialize, +) +for note_call in ( + "kzt_guest_library_note_loader_pair_pending", + "kzt_guest_library_note_loader_pair", +): + assert re.search( + rf"{note_call}\(\s*context,.*?\blink_map_addr\b", + materialize, + re.DOTALL, + ) assert "l_map_start" not in materialize assert "l_map_end" not in materialize assert "->l_ns" not in materialize @@ -89,8 +150,10 @@ def function_body(text: str, signature: str) -> str: assert "if (registry && diagnostics_enabled)" in consumer callback = function_body(myalign, "static void kzt_tb_callback(") +assert "box64context_t *context = my_context;" in callback +assert "context ? &context->kzt_loader_event_hook : NULL" in callback assert "kzt_loader_event_hook_publish(" in callback -assert "kzt_tb_callback_consume(env, &event);" in callback +assert "kzt_tb_callback_consume(context, env, &event);" in callback for forbidden in ( "LoadAndCheckElfHeader", "LoadNeededLibs", "RelocateElf", "kzt_main_elf_identity", "kzt_observe_guest_object", diff --git a/tests/unit/kzt/test_wi601_jump_slot_single_writer_source_contract.py b/tests/unit/kzt/test_wi601_jump_slot_single_writer_source_contract.py index 2ba80b5e1f3..1f92a8bb350 100644 --- a/tests/unit/kzt/test_wi601_jump_slot_single_writer_source_contract.py +++ b/tests/unit/kzt/test_wi601_jump_slot_single_writer_source_contract.py @@ -226,10 +226,22 @@ def detector_coordinates(body: str, value: str) -> list[str]: eager = function_body(elfloader, "int RelocateElfRELA(") +glob_dat_source = ( + root / "target/i386/latx/context/kzt_guest_glob_dat_target.c" +).read_text(encoding="utf-8") +glob_dat_route = function_body( + glob_dat_source, "int kzt_guest_glob_dat_route(" +) compatibility_writer = function_body( elfloader, "static int kzt_eager_compatibility_write(" ) lazy = function_body(elfloader, "void PltResolver(void)") +glob_dat_start = eager.find("case R_X86_64_GLOB_DAT:") +jump_slot_start = eager.find("case R_X86_64_JUMP_SLOT:") +if glob_dat_start < 0 or jump_slot_start < 0 or glob_dat_start >= jump_slot_start: + fail("RelocateElfRELA: missing ordered GLOB_DAT/JUMP_SLOT cases") +glob_dat = eager[glob_dat_start:jump_slot_start] +jump_slot = eager[jump_slot_start:] eager_decision = decision_name(eager, "RelocateElfRELA") arguments = decision_arguments("RelocateElfRELA", eager) if arguments != "route_call_succeeded,&route_result,0,final_value_usable": @@ -246,21 +258,76 @@ def detector_coordinates(body: str, value: str) -> list[str]: if route_call < 0 or fallback < route_call: fail("RelocateElfRELA: compatibility write must follow guest-first route") for required in ( - "option_kzt || wine_option_kzt", - "__atomic_compare_exchange_n(", - "kzt_patch_spike_guard_circuit_open(", + "if (!slot)", "*slot = replacement;", + "*final_value = replacement;", + "return KZT_EAGER_COMPATIBILITY_WRITE_APPLIED;", ): if required not in compatibility_writer: fail(f"compatibility writer lacks {required}") -if eager.count("kzt_eager_compatibility_write(") < 3: - fail("RelocateElfRELA: eager compatibility writes do not share the CAS helper") +for forbidden in ( + "option_kzt", + "wine_option_kzt", + "__atomic_compare_exchange_n(", + "kzt_patch_spike_guard_", + "kzt_production_", +): + if forbidden in compatibility_writer: + fail(f"compatibility writer must remain policy-free: {forbidden}") +if compatibility_writer.count("*slot = replacement;") != 1: + fail("compatibility writer must contain exactly one simple slot store") +if glob_dat.count("kzt_eager_compatibility_write(") != 1 or \ + jump_slot.count("kzt_eager_compatibility_write(") != 2: + fail("non-KZT GLOB_DAT/JUMP_SLOT writes must share the compatibility writer") + +# KZT owns eager relocation writes. Native GLOB_DAT bridges use the guarded +# writer transaction, while guest/local writes use the mandatory transaction. +if glob_dat_route.count("kzt_production_eager_relocation_write(") != 1: + fail("KZT GLOB_DAT native bridge must use one guarded transaction") +if glob_dat.count("kzt_production_guest_relocation_write(") != 1: + fail("KZT local GLOB_DAT must use one mandatory transaction") if not re.search( - r"if\s*\(\s*\(option_kzt\s*\|\|\s*wine_option_kzt\)\s*&&\s*" - r"bind\s*!=\s*STB_LOCAL\s*\)\s*\{[\s\S]*?" - r"route=GUEST_PRESERVED[\s\S]*?host_lookup=0[\s\S]*?break\s*;", - eager, + rf"{re.escape('kzt_production_eager_relocation_write(')}" + r"[\s\S]*?KZT_PATCH_RELOCATION_GLOB_DAT", + glob_dat_route, ): + fail("KZT native GLOB_DAT transaction has the wrong relocation type") +if not re.search( + rf"{re.escape('kzt_production_guest_relocation_write(')}" + r"[\s\S]*?KZT_PATCH_RELOCATION_GLOB_DAT", + glob_dat, +): + fail("KZT local GLOB_DAT transaction has the wrong relocation type") +glob_legacy = glob_dat.find("kzt_resolve_legacy_rela_target(") +if glob_legacy < 0 or glob_dat.find("kzt_guest_glob_dat_route(") > glob_legacy or \ + glob_dat.find("kzt_production_guest_relocation_write(") > glob_legacy: + fail("KZT GLOB_DAT transactions must precede the compatibility path") + +# KZT JUMP_SLOT writes have three controlled routes: deferred/local guest +# relocations use the mandatory transaction, and non-local eager binding uses +# the shared Registry-backed route. The simple writer is only the KZT-off arm. +if jump_slot.count("kzt_production_guest_relocation_write(") != 2: + fail("KZT deferred/local JUMP_SLOT writes must use mandatory transactions") +if jump_slot.count("kzt_production_jump_slot_route(") != 1: + fail("KZT non-local JUMP_SLOT must use the shared transactional route") +if not re.search( + r"if\s*\(\s*option_kzt\s*\|\|\s*wine_option_kzt\s*\)\s*\{" + r"[\s\S]*?kzt_production_guest_relocation_write\s*\(" + r"[\s\S]*?KZT_PATCH_RELOCATION_JUMP_SLOT[\s\S]*?\}\s*else" + r"[\s\S]*?kzt_eager_compatibility_write\s*\(", + jump_slot, +): + fail("deferred JUMP_SLOT does not separate KZT transaction from compatibility") +preserve_blocks = [ + (start, end) + for start, end, block in guarded_blocks( + jump_slot, r"\boption_kzt\s*\|\|\s*wine_option_kzt\b" + ) + if 'route=GUEST_PRESERVED' in block and 'host_lookup=0' in block and + re.search(r"\bbreak\s*;", block) +] +jump_legacy = jump_slot.find("kzt_resolve_legacy_rela_target(") +if jump_legacy < 0 or not any(end < jump_legacy for _, end in preserve_blocks): fail("RelocateElfRELA: KZT evidence failure can reach host lookup") for forbidden_lazy_writer in ( @@ -314,7 +381,18 @@ def detector_coordinates(body: str, value: str) -> list[str]: eager, ): fail("RelocateElfRELA: production route does not receive observed stub fact") -if "return -1;" in eager: +route_blocks = [ + block + for _, _, block in guarded_blocks( + jump_slot, + r"\boption_kzt\s*\|\|\s*wine_option_kzt\b[\s\S]*?" + r"\bbind\s*!=\s*STB_LOCAL\b", + ) + if "kzt_production_jump_slot_route(" in block +] +if len(route_blocks) != 1: + fail("RelocateElfRELA: expected one KZT non-local route block") +if "return -1;" in route_blocks[0]: fail("RelocateElfRELA: route-owned unusable slot must fail open") if re.search( r"kzt_rela_slot_current_is_unresolved_stub\s*\(\s*" diff --git a/tests/unit/kzt/test_wi602_handle_state_removal_source_contract.py b/tests/unit/kzt/test_wi602_handle_state_removal_source_contract.py index cb5cf32d1ca..5ba239b7e40 100644 --- a/tests/unit/kzt/test_wi602_handle_state_removal_source_contract.py +++ b/tests/unit/kzt/test_wi602_handle_state_removal_source_contract.py @@ -32,6 +32,23 @@ def function_body(text: str, signature: str) -> str: api = (root / "target/i386/latx/context/kzt_guest_dl_api.c").read_text( encoding="utf-8" ) +production_meson = ( + root / "target/i386/latx/context/meson.build" +).read_text(encoding="utf-8") +test_meson = (root / "tests/unit/meson.build").read_text(encoding="utf-8") + +for removed_path in ( + "target/i386/latx/context/dlopen_recycle_transaction.c", + "target/i386/latx/include/dlopen_recycle_transaction.h", + "tests/unit/kzt/test_dlopen_recycle_transaction.c", +): + if (root / removed_path).exists(): + raise AssertionError(f"removed recycle helper still exists: {removed_path}") + +for meson_text in (production_meson, test_meson): + if "dlopen_recycle_transaction" in meson_text: + raise AssertionError("build graph still references removed recycle helper") + dlprivate_start = header.find("typedef struct dlprivate_s {") dlprivate_end = header.find("} dlprivate_t;", dlprivate_start) if dlprivate_start < 0 or dlprivate_end < 0: diff --git a/tests/unit/kzt/test_wi603_callback_replacement_source_contract.py b/tests/unit/kzt/test_wi603_callback_replacement_source_contract.py index 89878c16511..ce2bc396c64 100644 --- a/tests/unit/kzt/test_wi603_callback_replacement_source_contract.py +++ b/tests/unit/kzt/test_wi603_callback_replacement_source_contract.py @@ -25,6 +25,12 @@ def function_body(text: str, signature: str) -> str: myalign = (root / "target/i386/latx/context/myalign.c").read_text( encoding="utf-8" ) +elfloader = (root / "target/i386/latx/context/elfloader.c").read_text( + encoding="utf-8" +) +production = ( + root / "target/i386/latx/context/kzt_jump_slot_production.c" +).read_text(encoding="utf-8") callback = function_body(myalign, "static void kzt_tb_callback(") if "kzt_loader_event_hook_publish(" not in callback: @@ -32,7 +38,14 @@ def function_body(text: str, signature: str) -> str: consumer = function_body(myalign, "static void kzt_tb_callback_consume(") if ".per_object_flow = kzt_tb_callback_per_object_got_plt," not in consumer: raise AssertionError("loader event does not retain Registry per-object flow") -if ".per_object_opaque = &env->kzt_guest_library_loader_scope," not in consumer: +for required in ( + ".context = context,", + ".loader_scope = &env->kzt_guest_library_loader_scope,", + ".per_object_opaque = &callback_scope,", +): + if required not in consumer: + raise AssertionError(f"per-object flow misses scoped handoff: {required}") +if "kzt_tb_callback_scope_t callback_scope;" not in consumer: raise AssertionError("per-object flow does not receive scoped binding state") if ".legacy_flow = NULL," not in consumer: raise AssertionError("KZT loader event still enables legacy callback flow") @@ -49,8 +62,19 @@ def function_body(text: str, signature: str) -> str: materialize = function_body( myalign, "static int kzt_tb_callback_materialize_binding(" ) -if "kzt_tb_callback_materialize_binding(link_map_addr, opaque)" not in per_object: +materialize_call = per_object.find( + "kzt_tb_callback_materialize_binding(link_map_addr, opaque)" +) +write_call = per_object.find("kzt_per_object_got_plt_apply(&request, &result)") +if materialize_call < 0 or write_call < 0 or materialize_call >= write_call: raise AssertionError("per-object flow does not publish its library binding") +for required in ( + ".registry = KztGuestRegistryForContext(context),", + ".apply = KztPerObjectGotPltWrite,", + ".opaque = context,", +): + if required not in per_object: + raise AssertionError(f"per-object write request misses {required}") for required in ( "AddNeededLibWithLibrary(", "kzt_guest_library_note_loader_pair_pending(", @@ -62,6 +86,45 @@ def function_body(text: str, signature: str) -> str: if forbidden in materialize: raise AssertionError(f"materialization retains raw ELF work: {forbidden}") +writer = function_body(elfloader, "int KztPerObjectGotPltWrite(") +for required in ( + "registry = KztGuestRegistryForContext(context);", + "kzt_elfloader_write_guest_word(resolver.link_map_slot,", + "kzt_elfloader_write_guest_word(resolver.resolver_slot,", + "kzt_guest_registry_publish_lazy_resolver(", +): + if required not in writer: + raise AssertionError(f"per-object writer misses {required}") +if "kzt_production_lazy_prebind_object(" in writer: + raise AssertionError("per-object writer prebinds before guest PLT relocation") +relocate = function_body(elfloader, "int RelocateElfPlt(") +if "kzt_production_lazy_prebind_object(" in relocate or \ + "kzt_production_lazy_prebind_refresh(" in relocate: + raise AssertionError("RelocateElfPlt prebinds before the guest loader event") + +prebind = function_body( + production, "static int production_lazy_prebind_object_prepare(" +) +for required in ( + "kzt_guest_registry_source_lease_acquire(", + "kzt_guest_registry_patch_decision_lease_acquire(", + "kzt_guest_library_loader_quiescence_try_acquire(", + "production_lazy_prebind_publish_record(", +): + if required not in prebind: + raise AssertionError(f"per-object prebind misses guarded write step: {required}") + +publish_record = function_body( + production, "static int production_lazy_prebind_publish_record(" +) +for required in ( + "kzt_lazy_prebind_scope_publish_acquire(scope, record, &lease)", + "production_lazy_prebind_slot_cas(", + "kzt_lazy_prebind_scope_publish_finish(&lease, committed)", +): + if required not in publish_record: + raise AssertionError(f"per-object slot publication misses {required}") + bridge = function_body(myalign, "void init_tb_callback_bridge(") if "kzt_tb_callback" not in bridge or "ld_info->addr" not in bridge: raise AssertionError("versioned loader event hook is missing") diff --git a/tests/unit/kzt/test_wi603_loader_hook_timing_source_contract.py b/tests/unit/kzt/test_wi603_loader_hook_timing_source_contract.py index 1fbb5f8019a..0a9adcfd237 100644 --- a/tests/unit/kzt/test_wi603_loader_hook_timing_source_contract.py +++ b/tests/unit/kzt/test_wi603_loader_hook_timing_source_contract.py @@ -28,6 +28,9 @@ def function_body(text: str, signature: str) -> str: adapter = (root / "target/i386/latx/context/kzt_observation_adapter.c").read_text( encoding="utf-8" ) +hook_source = ( + root / "target/i386/latx/context/kzt_loader_event_hook.c" +).read_text(encoding="utf-8") locator = function_body(myalign, "static struct x86_ld_info * find_ld_part(") for required in ( @@ -53,15 +56,30 @@ def function_body(text: str, signature: str) -> str: raise AssertionError("loader hook does not capture link_map from located register") if "kzt_loader_event_hook_publish(" not in callback: raise AssertionError("loader hook does not publish through the event seam") -if "kzt_tb_callback_consume(env, &event)" not in callback: +if "kzt_tb_callback_consume(context, env, &event)" not in callback: raise AssertionError("loader hook does not hand events to the consumer") +publish = function_body(hook_source, "int kzt_loader_event_hook_publish(") +for required in ( + "!__atomic_load_n(&hook->installed, __ATOMIC_ACQUIRE)", + "event->link_map_addr = link_map_addr;", + "event->sequence = __atomic_add_fetch(&hook->event_sequence", + "clock_gettime(CLOCK_MONOTONIC_RAW, ×tamp)", + "event->published_ns =", +): + if required not in publish: + raise AssertionError(f"loader event publication misses {required}") + consumer = function_body(myalign, "static void kzt_tb_callback_consume(") for required in ( - ".link_map_addr = link_map_addr,", + ".context = context,", ".loader_scope = &env->kzt_guest_library_loader_scope,", + ".link_map_addr = link_map_addr,", + ".registry = registry,", + ".library_bindings = KztGuestLibraryBindingsForContext(context),", ".reuse_complete_dynamic_view = 1,", ".per_object_flow = kzt_tb_callback_per_object_got_plt,", + ".per_object_opaque = &callback_scope,", ".legacy_flow = NULL,", "kzt_observe_guest_object_from_callback(&request, &observation_result)", ): diff --git a/tests/unit/kzt/test_wi837_lazy_direct_route.c b/tests/unit/kzt/test_wi837_lazy_direct_route.c index 02ec5f10a3e..e8e418ff9ab 100644 --- a/tests/unit/kzt/test_wi837_lazy_direct_route.c +++ b/tests/unit/kzt/test_wi837_lazy_direct_route.c @@ -11,6 +11,7 @@ typedef struct fixture { int provider_available; int provider_generation_delta; int bridge_available; + int transient_safe; int wrong_bridge_version; int lease_available; int final_valid; @@ -97,6 +98,7 @@ static int find_wrapper_bridge( .version_evidence = input->version_evidence, .version = fixture->wrong_bridge_version ? "GLIBC_2.2.5" : input->version, + .transient_safe = fixture->transient_safe, }; return 0; } @@ -178,6 +180,7 @@ static fixture_t fixture(void) .source_valid = 1, .provider_available = 1, .bridge_available = 1, + .transient_safe = 1, .lease_available = 1, .final_valid = 1, .cas_status = KZT_LAZY_DIRECT_ROUTE_CAS_APPLIED, @@ -632,6 +635,21 @@ static void test_guest_owned_dlclose_requires_guest_without_callbacks(void) f.lease_acquire_calls == 0 && f.final_validate_calls == 0 && f.cas_calls == 0); + + f = fixture(); + input = input_for(&f, &view); + ops = ops_for(&f); + original = f.slot; + input.symbol = "free"; + check_guest_required( + "guest-free", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_GUEST_OWNED_SYMBOL, + &f, original); + CHECK("guest-free.no-callbacks", + f.source_calls == 0 && f.provider_acquire_calls == 0 && + f.bridge_calls == 0 && f.lease_acquire_calls == 0 && + f.final_validate_calls == 0 && f.cas_calls == 0); } static void test_cas_mismatch_requires_guest_without_writing(void) @@ -675,6 +693,119 @@ static void test_cas_error_requires_guest_without_writing(void) f.cas_calls == 1 && f.write_calls == 0); } +static void test_budget_exhaustion_can_use_verified_loader_bridge_once(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + input.symbol = "dlopen"; + input.allow_budget_transient_native = 1; + f.cas_status = KZT_LAZY_DIRECT_ROUTE_CAS_BUDGET_EXHAUSTED; + CHECK("budget-transient.status", + kzt_lazy_direct_route_apply(&input, &ops, &result) == + KZT_LAZY_DIRECT_ROUTE_NATIVE_TRANSIENT); + CHECK("budget-transient.result", + result.status == KZT_LAZY_DIRECT_ROUTE_NATIVE_TRANSIENT && + result.reason == KZT_LAZY_DIRECT_ROUTE_REASON_NATIVE_TRANSIENT && + result.selected_target == f.bridge_target); + CHECK("budget-transient.no-write", + f.slot == original && f.write_calls == 0 && f.cas_calls == 1); + CHECK("budget-transient.cleanup", + f.lease_release_calls == 1 && f.provider_release_calls == 1); +} + +static void test_transient_flags_do_not_apply_to_non_loader_symbols(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + input.symbol = "puts"; + input.allow_budget_transient_native = 1; + f.cas_status = KZT_LAZY_DIRECT_ROUTE_CAS_BUDGET_EXHAUSTED; + check_guest_required( + "non-loader-transient", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_BUDGET_EXHAUSTED, + &f, original); + CHECK("non-loader-transient.one-attempt", + f.cas_calls == 1 && f.write_calls == 0); +} + +static void test_budget_transient_requires_context_owned_wrapper(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + input.symbol = "dlopen"; + input.allow_budget_transient_native = 1; + f.transient_safe = 0; + f.cas_status = KZT_LAZY_DIRECT_ROUTE_CAS_BUDGET_EXHAUSTED; + check_guest_required( + "unsafe-budget-transient", + kzt_lazy_direct_route_apply(&input, &ops, &result), + &result, KZT_LAZY_DIRECT_ROUTE_REASON_BUDGET_EXHAUSTED, + &f, original); + CHECK("unsafe-budget-transient.one-attempt", + f.cas_calls == 1 && f.write_calls == 0); +} + +static void test_writer_rollback_remains_distinct_from_cas_error(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + f.cas_status = KZT_LAZY_DIRECT_ROUTE_CAS_ROLLED_BACK; + CHECK("rolled-back.status", + kzt_lazy_direct_route_apply(&input, &ops, &result) == + KZT_LAZY_DIRECT_ROUTE_WRITE_ROLLED_BACK); + CHECK("rolled-back.result", + result.status == KZT_LAZY_DIRECT_ROUTE_WRITE_ROLLED_BACK && + result.reason == KZT_LAZY_DIRECT_ROUTE_REASON_WRITE_ROLLED_BACK); + CHECK("rolled-back.slot", f.slot == original && f.write_calls == 0); + CHECK("rolled-back.cleanup", + f.cas_calls == 1 && f.lease_release_calls == 1 && + f.provider_release_calls == 1); +} + +static void test_unrecoverable_writer_failure_is_terminal(void) +{ + fixture_t f = fixture(); + kzt_guest_dynamic_view_t view = complete_dynamic_view(); + kzt_lazy_direct_route_input_t input = input_for(&f, &view); + kzt_lazy_direct_route_ops_t ops = ops_for(&f); + kzt_lazy_direct_route_result_t result; + uintptr_t original = f.slot; + + f.cas_status = KZT_LAZY_DIRECT_ROUTE_CAS_UNRECOVERABLE; + CHECK("unrecoverable.status", + kzt_lazy_direct_route_apply(&input, &ops, &result) == + KZT_LAZY_DIRECT_ROUTE_UNRECOVERABLE); + CHECK("unrecoverable.result", + result.status == KZT_LAZY_DIRECT_ROUTE_UNRECOVERABLE && + result.reason == KZT_LAZY_DIRECT_ROUTE_REASON_UNRECOVERABLE); + CHECK("unrecoverable.no-fake-write", + f.slot == original && f.write_calls == 0); + CHECK("unrecoverable.cleanup", + f.cas_calls == 1 && f.lease_release_calls == 1 && + f.provider_release_calls == 1); +} + int main(void) { test_only_strong_global_binding_is_eligible(); @@ -697,6 +828,11 @@ int main(void) test_final_validation_failure_requires_guest(); test_cas_mismatch_requires_guest_without_writing(); test_cas_error_requires_guest_without_writing(); + test_budget_exhaustion_can_use_verified_loader_bridge_once(); + test_transient_flags_do_not_apply_to_non_loader_symbols(); + test_budget_transient_requires_context_owned_wrapper(); + test_writer_rollback_remains_distinct_from_cas_error(); + test_unrecoverable_writer_failure_is_terminal(); if (failures) { fprintf(stderr, "%d WI-837 lazy direct route checks failed\n", diff --git a/tests/unit/kzt/test_wi982_versioned_wrapper_selection_source_contract.py b/tests/unit/kzt/test_wi982_versioned_wrapper_selection_source_contract.py index 729b8b09152..b8f573983b1 100644 --- a/tests/unit/kzt/test_wi982_versioned_wrapper_selection_source_contract.py +++ b/tests/unit/kzt/test_wi982_versioned_wrapper_selection_source_contract.py @@ -40,17 +40,16 @@ def function_body(text: str, signature: str) -> str: ).read_text(encoding="utf-8") selector = function_body( - adapter, "uintptr_t kzt_guest_library_select_symbol_result(") + adapter, "uintptr_t kzt_guest_library_select_symbol_result_with_identity(") required_selector_tokens = ( - "KZT_SYMBOL_VERSION_VERSIONED, version", - "kzt_rela_runtime_wrapper_provider_discover_retained_with_version_evidence(", - "kzt_wrapper_probe_minimal_manifest(", - "KZT_PATCH_WRAPPER_VERSION_MATCH", - "kzt_symbol_version_evidence_matches(", + "kzt_rela_runtime_select_exact_wrapper_bridge_retained(", + "KZT_SYMBOL_VERSION_VERSIONED", + "KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED", ) for token in required_selector_tokens: if token not in selector: raise AssertionError(f"versioned selector misses exact evidence: {token}") + if "(version && version[0]) ||" in selector: raise AssertionError("versioned selection still returns before owner proof") if "versioned = version != NULL" not in selector or \ diff --git a/tests/unit/kzt/test_wi987_guest_relocation_authority_source_contract.py b/tests/unit/kzt/test_wi987_guest_relocation_authority_source_contract.py index 6dc979f92fc..8b731242315 100644 --- a/tests/unit/kzt/test_wi987_guest_relocation_authority_source_contract.py +++ b/tests/unit/kzt/test_wi987_guest_relocation_authority_source_contract.py @@ -70,11 +70,17 @@ def function_body(text: str, signature: str) -> str: production = ( root / "target/i386/latx/context/kzt_jump_slot_production.c" ).read_text(encoding="utf-8") +glob_dat_source = ( + root / "target/i386/latx/context/kzt_guest_glob_dat_target.c" +).read_text(encoding="utf-8") rela = function_body(elfloader, "int RelocateElfRELA(") legacy = function_body(elfloader, "kzt_resolve_legacy_rela_target(") glob_dat = function_body( - elfloader, "kzt_guest_glob_dat_target_resolve(" + glob_dat_source, "kzt_guest_glob_dat_target_resolve(" +) +glob_dat_route = function_body( + glob_dat_source, "int kzt_guest_glob_dat_route(" ) route_apply = function_body(route, "int kzt_jump_slot_route_apply(") acquire = function_body(production, "static int production_acquire_exact(") @@ -92,7 +98,7 @@ def function_body(text: str, signature: str) -> str: "KZT_PATCH_OWNER_MATCH", "current_owner.known", "KztGuestLibraryLookupForContext(", - "GetLibSymbolStartEnd(", + "kzt_rela_runtime_select_exact_wrapper_bridge_retained(", "STT_FUNC", "version >= 2", ) @@ -119,26 +125,27 @@ def function_body(text: str, signature: str) -> str: if fallback < 0 or guest_route.start() > fallback: fail("host compatibility lookup must happen after the guest-first route") if not re.search( - r"kzt_guest_glob_dat_target_resolve\s*\([\s\S]*?\)\s*\)\s*\{" + r"kzt_guest_glob_dat_route\s*\([\s\S]*?\)\s*\)\s*\{" r"[\s\S]*?continue\s*;", rela, ): fail("authoritative GLOB_DAT guest targets must bypass host lookup") if not re.search( - r"if\s*\(\s*bind\s*!=\s*STB_LOCAL\s*\)\s*\{[\s\S]*?" + r"if\s*\(\s*observed\s*\|\|\s*bind\s*!=\s*STB_LOCAL[\s\S]*?" r"route=GUEST_PRESERVED[\s\S]*?host_lookup=0[\s\S]*?continue\s*;", rela, ): fail("KZT GLOB_DAT evidence failure can reach host lookup") if not re.search( - r"if\s*\(\s*\(option_kzt\s*\|\|\s*wine_option_kzt\)\s*&&\s*" - r"bind\s*!=\s*STB_LOCAL\s*\)\s*\{[\s\S]*?" + r"if\s*\(\s*option_kzt\s*\|\|\s*wine_option_kzt\s*\)\s*\{[\s\S]*?" r"route=GUEST_PRESERVED[\s\S]*?host_lookup=0[\s\S]*?break\s*;", rela, ): fail("KZT JUMP_SLOT evidence failure can reach host lookup") -if "__atomic_compare_exchange_n(" not in rela: - fail("GLOB_DAT exact-owner bridge must not overwrite a competitor") +if "__atomic_compare_exchange_n(" in rela: + fail("eager relocation bypasses the transactional writer") +if "kzt_production_eager_relocation_write(" not in glob_dat_route: + fail("GLOB_DAT exact-owner bridge lacks the transactional writer") for forbidden in ("input->resolved_target_matches_legacy",): if forbidden in route_apply: diff --git a/tests/unit/kzt/test_wi994_dlerror_tail_forward_source_contract.py b/tests/unit/kzt/test_wi994_dlerror_tail_forward_source_contract.py index a0b1e14bc35..3e0bddefdeb 100644 --- a/tests/unit/kzt/test_wi994_dlerror_tail_forward_source_contract.py +++ b/tests/unit/kzt/test_wi994_dlerror_tail_forward_source_contract.py @@ -93,7 +93,8 @@ def function_body(text: str, signature: str) -> str: wrapper, "static char *kzt_guest_dlerror_slow_path(" ) for required in ( - "kzt_guest_dl_api_dlerror(error_state, guest_dlerror)", + "kzt_guest_dl_api_dlerror(", + "guest_route_may_have_pending_error", "kzt_guest_dl_api_load_dlerror_hint(", "kzt_guest_dlerror_entry_slow(context)", "error_state->guest_dlerror_entry = guest_dlerror", @@ -108,7 +109,7 @@ def function_body(text: str, signature: str) -> str: raise AssertionError(f"{relative}: dlerror does not use shared result") if "forward_to_guest_caller" not in state_slow: raise AssertionError(f"{relative}: dlerror does not check tail forwarding") - state_check = body.find("if (fast_result)") + state_check = body.find("if (fast_result || guest_loader_route)") slow = body.find("kzt_guest_dlerror_slow_path(") fast_return = body.find("return fast_result;") if min(state_check, slow, fast_return) < 0 or not ( @@ -118,6 +119,12 @@ def function_body(text: str, signature: str) -> str: ) if "char *fast_result" not in body: raise AssertionError(f"{relative}: clean dlerror result is materialized") + if "kzt_guest_loader_route_present" not in body: + raise AssertionError(f"{relative}: guest loader route is not preserved") + if "guest_loader_route);" not in body: + raise AssertionError( + f"{relative}: guest loader route is not passed to the slow path" + ) if "kzt_guest_dl_entries_t fallback" in body: raise AssertionError(f"{relative}: hot dlerror retains cold stack state") if "RunFunctionWithState" in body: diff --git a/tests/unit/kzt/test_wrapper_bridge_provider.c b/tests/unit/kzt/test_wrapper_bridge_provider.c index f9f40398ee8..231c809c895 100644 --- a/tests/unit/kzt/test_wrapper_bridge_provider.c +++ b/tests/unit/kzt/test_wrapper_bridge_provider.c @@ -12,7 +12,10 @@ typedef struct fake_library { uintptr_t bridge_target; uintptr_t add_target; uintptr_t post_add_target; + uintptr_t guest_fallback_target; + kzt_bridge_guard_kind_t guard_kind; int bridge_exact; + int guarded_absent_from_map; int lifetime_bound; int inspect_calls; int check_calls; @@ -75,6 +78,8 @@ static int fake_inspect( match->wrapper_provider_lifetime_bound = lib->lifetime_bound; match->native_owner_lifetime_bound = lib->lifetime_bound; match->bridge_owner_lifetime_bound = lib->lifetime_bound; + match->guest_fallback_target = lib->guest_fallback_target; + match->guard_kind = lib->guard_kind; return 1; } @@ -99,8 +104,10 @@ static uintptr_t fake_add( return 0; } ++lib->add_calls; - lib->bridge_target = lib->post_add_target ? lib->post_add_target : - lib->add_target; + if (!lib->guarded_absent_from_map) { + lib->bridge_target = lib->post_add_target ? lib->post_add_target : + lib->add_target; + } return lib->add_target; } @@ -361,6 +368,46 @@ static void test_created_bridge_must_match_recheck(void) check_int("inexact-created.add-once", inexact.add_calls, 1); } +static void test_guarded_created_bridge_does_not_require_map_recheck(void) +{ + fake_library_t guarded = { + .inspect_status = 1, + .name = "libgtk-3.so.0", + .native_symbol = 0x71000000, + .add_target = 0x72000000, + .guest_fallback_target = 0x73000000, + .guard_kind = KZT_BRIDGE_GUARD_XCB_CONNECTION, + .guarded_absent_from_map = 1, + .lifetime_bound = 1, + }; + void *libraries[] = { &guarded }; + kzt_wrapper_bridge_provider_runtime_ops_t ops = fake_ops(); + kzt_wrapper_bridge_provider_t provider; + kzt_wrapper_probe_result_t result; + kzt_wrapper_probe_request_t request = { + .symbol_name = "gtk_widget_show", + .symbol_version = "GTK_3.0", + }; + + check_int("guarded.prepare", + kzt_wrapper_bridge_provider_prepare( + &provider, libraries, 1, request.symbol_name, + request.symbol_version, &ops), + 1); + check_int("guarded.probe", + kzt_wrapper_probe_minimal_manifest( + &provider.manifest, &request, &provider.bridge_ops, + &result), + 0); + check_ulong("guarded.bridge", result.bridge_target, + guarded.add_target); + check_int("guarded.bridge-source", result.bridge_source, + KZT_WRAPPER_PROBE_BRIDGE_ADD_BRIDGE); + check_int("guarded.initial-check-only", guarded.check_calls, 1); + check_int("guarded.add-once", guarded.add_calls, 1); + check_ulong("guarded.map-remains-empty", guarded.bridge_target, 0); +} + int main(void) { test_exact_unique_candidate_builds_temporary_provider(); @@ -370,6 +417,7 @@ int main(void) test_bridge_abi_conflict_fails_open_without_add(); test_missing_bridge_is_created_then_rechecked(); test_created_bridge_must_match_recheck(); + test_guarded_created_bridge_does_not_require_map_recheck(); if (failures) { fprintf(stderr, "kzt-wrapper-bridge-provider: %d failure(s)\n", diff --git a/tests/unit/kzt/test_xcb_connection_guard.c b/tests/unit/kzt/test_xcb_connection_guard.c new file mode 100644 index 00000000000..6c6f24c8758 --- /dev/null +++ b/tests/unit/kzt/test_xcb_connection_guard.c @@ -0,0 +1,281 @@ +#include "kzt_xcb_connection_guard.h" + +#include +#include +#include +#include +#include +#include + +#define CHECK(label, condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "%s: FAIL\n", label); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +typedef struct destroy_log { + unsigned int count; +} destroy_log_t; + +typedef struct guard_worker { + kzt_xcb_connection_map_t *map; + void *guest; + pthread_mutex_t lock; + pthread_cond_t changed; + int ready; + int take_active; + int hold; + int result; +} guard_worker_t; + +typedef struct destroy_worker { + kzt_xcb_connection_map_t *map; + pthread_mutex_t lock; + pthread_cond_t changed; + int started; + int finished; +} destroy_worker_t; + +static void destroy_guest(void *guest, void *opaque) +{ + destroy_log_t *log = opaque; + + ++log->count; + free(guest); +} + +static void register_pair(kzt_xcb_connection_map_t *map, void *native, + void **guest) +{ + void *canonical = NULL; + uint64_t generation = 0; + + *guest = malloc(1); + CHECK("guest allocation", *guest != NULL); + CHECK("register pair", + kzt_xcb_connection_map_register( + map, native, *guest, &canonical, &generation) == + KZT_XCB_CONNECTION_MAP_ADDED); + CHECK("canonical guest", canonical == *guest && generation != 0); +} + +static void deadline_after(struct timespec *deadline, long milliseconds) +{ + clock_gettime(CLOCK_REALTIME, deadline); + deadline->tv_nsec += milliseconds * 1000 * 1000; + while (deadline->tv_nsec >= 1000 * 1000 * 1000) { + ++deadline->tv_sec; + deadline->tv_nsec -= 1000 * 1000 * 1000; + } +} + +static int wait_flag(pthread_mutex_t *lock, pthread_cond_t *changed, + int *flag, long milliseconds) +{ + struct timespec deadline; + + deadline_after(&deadline, milliseconds); + pthread_mutex_lock(lock); + while (!*flag && + pthread_cond_timedwait(changed, lock, &deadline) == 0) { + } + pthread_mutex_unlock(lock); + return *flag; +} + +static void *guard_worker_run(void *opaque) +{ + guard_worker_t *worker = opaque; + kzt_xcb_connection_lease_t lease = { 0 }; + + worker->result = kzt_xcb_connection_guard_prepare( + worker->map, worker->guest); + if (worker->result == 0 && worker->take_active) { + worker->result = kzt_xcb_connection_guard_take( + worker->map, worker->guest, &lease); + } + pthread_mutex_lock(&worker->lock); + worker->ready = 1; + pthread_cond_broadcast(&worker->changed); + pthread_mutex_unlock(&worker->lock); + while (worker->hold) { + pthread_testcancel(); + } + return NULL; +} + +static void *destroy_worker_run(void *opaque) +{ + destroy_worker_t *worker = opaque; + + pthread_mutex_lock(&worker->lock); + worker->started = 1; + pthread_cond_broadcast(&worker->changed); + pthread_mutex_unlock(&worker->lock); + kzt_xcb_connection_map_destroy(&worker->map); + pthread_mutex_lock(&worker->lock); + worker->finished = 1; + pthread_cond_broadcast(&worker->changed); + pthread_mutex_unlock(&worker->lock); + return NULL; +} + +static void test_prepare_take_and_cancel(void) +{ + destroy_log_t log = { 0 }; + kzt_xcb_connection_map_t *map = + kzt_xcb_connection_map_init(destroy_guest, &log); + void *native_a = (void *)(uintptr_t)0x1000; + void *native_b = (void *)(uintptr_t)0x2000; + void *guest_a; + void *guest_b; + kzt_xcb_connection_lease_t lease = { 0 }; + kzt_xcb_connection_lease_t removal = { 0 }; + + CHECK("map", map != NULL); + register_pair(map, native_a, &guest_a); + register_pair(map, native_b, &guest_b); + + CHECK("prepare known", + kzt_xcb_connection_guard_prepare(map, guest_a) == 0); + CHECK("take wrong guest", + kzt_xcb_connection_guard_take(map, guest_b, &lease) != 0); + CHECK("take known", + kzt_xcb_connection_guard_take(map, guest_a, &lease) == 0); + CHECK("taken pair", lease.native == native_a && lease.guest == guest_a); + CHECK("single take", + kzt_xcb_connection_guard_take(map, guest_a, &removal) != 0); + memset(&removal, 0, sizeof(removal)); + CHECK("lookup active lease", kzt_xcb_connection_guard_active_lease( + map, native_a, guest_a, &removal) == 0 && + removal.generation == lease.generation); + CHECK("release taken lease", kzt_xcb_connection_guard_release( + map, lease.native, lease.guest) == 0); + + CHECK("prepare stale", kzt_xcb_connection_guard_prepare(map, guest_a) == 0); + CHECK("prepare replaces and releases stale", + kzt_xcb_connection_guard_prepare(map, guest_b) == 0); + kzt_xcb_connection_guard_cancel(); + CHECK("remove first after stale release", + kzt_xcb_connection_map_begin_remove_by_guest( + map, guest_a, &removal) == 0); + kzt_xcb_connection_map_finish_remove(&removal); + CHECK("remove second after cancel", + kzt_xcb_connection_map_begin_remove_by_guest( + map, guest_b, &removal) == 0); + kzt_xcb_connection_map_finish_remove(&removal); + CHECK("destroy callbacks", log.count == 2); + kzt_xcb_connection_map_destroy(&map); +} + +static void test_unknown_never_creates_pending_lease(void) +{ + destroy_log_t log = { 0 }; + kzt_xcb_connection_map_t *map = + kzt_xcb_connection_map_init(destroy_guest, &log); + kzt_xcb_connection_lease_t lease = { 0 }; + + CHECK("unknown map", map != NULL); + CHECK("unknown prepare", + kzt_xcb_connection_guard_prepare( + map, (void *)(uintptr_t)0x3000) != 0); + CHECK("unknown take", + kzt_xcb_connection_guard_take( + map, (void *)(uintptr_t)0x3000, &lease) != 0); + kzt_xcb_connection_guard_cancel(); + kzt_xcb_connection_map_destroy(&map); + CHECK("unknown destroy", log.count == 0); +} + +static void test_thread_exit_releases_pending_lease(void) +{ + destroy_log_t log = { 0 }; + kzt_xcb_connection_map_t *map = + kzt_xcb_connection_map_init(destroy_guest, &log); + void *guest; + kzt_xcb_connection_lease_t removal = { 0 }; + guard_worker_t worker = { + .map = map, + .lock = PTHREAD_MUTEX_INITIALIZER, + .changed = PTHREAD_COND_INITIALIZER, + }; + pthread_t thread; + + CHECK("pending exit map", map != NULL); + register_pair(map, (void *)(uintptr_t)0x4000, &guest); + worker.guest = guest; + CHECK("pending exit thread", pthread_create( + &thread, NULL, guard_worker_run, &worker) == 0); + CHECK("pending exit join", pthread_join(thread, NULL) == 0); + CHECK("pending exit prepare", worker.result == 0); + CHECK("pending exit remove", kzt_xcb_connection_map_begin_remove_by_guest( + map, guest, &removal) == 0); + kzt_xcb_connection_map_finish_remove(&removal); + kzt_xcb_connection_map_destroy(&map); + CHECK("pending exit cleanup", log.count == 1); + pthread_cond_destroy(&worker.changed); + pthread_mutex_destroy(&worker.lock); +} + +static void test_cancelled_thread_releases_active_lease_for_destroy(void) +{ + destroy_log_t log = { 0 }; + kzt_xcb_connection_map_t *map = + kzt_xcb_connection_map_init(destroy_guest, &log); + void *guest; + guard_worker_t guard = { + .map = map, + .lock = PTHREAD_MUTEX_INITIALIZER, + .changed = PTHREAD_COND_INITIALIZER, + .take_active = 1, + .hold = 1, + }; + destroy_worker_t destroy = { + .map = map, + .lock = PTHREAD_MUTEX_INITIALIZER, + .changed = PTHREAD_COND_INITIALIZER, + }; + pthread_t guard_thread; + pthread_t destroy_thread; + void *thread_result = NULL; + + CHECK("active cancel map", map != NULL); + register_pair(map, (void *)(uintptr_t)0x5000, &guest); + guard.guest = guest; + CHECK("active cancel guard thread", pthread_create( + &guard_thread, NULL, guard_worker_run, &guard) == 0); + CHECK("active cancel ready", wait_flag( + &guard.lock, &guard.changed, &guard.ready, 100)); + CHECK("active cancel take", guard.result == 0); + CHECK("active cancel destroy thread", pthread_create( + &destroy_thread, NULL, destroy_worker_run, &destroy) == 0); + CHECK("active cancel destroy starts", wait_flag( + &destroy.lock, &destroy.changed, &destroy.started, 100)); + CHECK("active cancel destroy waits", !wait_flag( + &destroy.lock, &destroy.changed, &destroy.finished, 30)); + CHECK("active cancel request", pthread_cancel(guard_thread) == 0); + CHECK("active cancel join", pthread_join( + guard_thread, &thread_result) == 0 && + thread_result == PTHREAD_CANCELED); + CHECK("active cancel destroy completes", wait_flag( + &destroy.lock, &destroy.changed, &destroy.finished, 100)); + CHECK("active cancel destroy join", pthread_join( + destroy_thread, NULL) == 0); + CHECK("active cancel cleanup", destroy.map == NULL && log.count == 1); + pthread_cond_destroy(&guard.changed); + pthread_mutex_destroy(&guard.lock); + pthread_cond_destroy(&destroy.changed); + pthread_mutex_destroy(&destroy.lock); +} + +int main(void) +{ + test_prepare_take_and_cancel(); + test_unknown_never_creates_pending_lease(); + test_thread_exit_releases_pending_lease(); + test_cancelled_thread_releases_active_lease_for_destroy(); + puts("kzt-xcb-connection-guard: all tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_xcb_connection_map.c b/tests/unit/kzt/test_xcb_connection_map.c new file mode 100644 index 00000000000..c499d170afb --- /dev/null +++ b/tests/unit/kzt/test_xcb_connection_map.c @@ -0,0 +1,684 @@ +#include "kzt_xcb_connection_map.h" + +#include +#include +#include +#include +#include +#include + +#define CHECK(label, condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "%s: FAIL\n", label); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +typedef struct destroy_log { + pthread_mutex_t lock; + unsigned int count; +} destroy_log_t; + +typedef struct remove_race { + kzt_xcb_connection_map_t *map; + void *guest; + void *native; + int by_native; + pthread_mutex_t lock; + pthread_cond_t changed; + int started; + int finished; + int result; + kzt_xcb_connection_lease_t lease; +} remove_race_t; + +typedef struct destroy_race { + kzt_xcb_connection_map_t *map; + pthread_mutex_t lock; + pthread_cond_t changed; + int started; + int finished; +} destroy_race_t; + +typedef struct operation_race { + kzt_xcb_connection_map_t *map; + void *guest; + pthread_mutex_t lock; + pthread_cond_t changed; + int started; + int acquired; + int result; +} operation_race_t; + +static void destroy_guest(void *guest, void *opaque) +{ + destroy_log_t *log = opaque; + + pthread_mutex_lock(&log->lock); + ++log->count; + pthread_mutex_unlock(&log->lock); + free(guest); +} + +static void *new_guest(void) +{ + void *guest = malloc(1); + + CHECK("guest allocation", guest != NULL); + return guest; +} + +static void deadline_after(struct timespec *deadline, long milliseconds) +{ + clock_gettime(CLOCK_REALTIME, deadline); + deadline->tv_nsec += milliseconds * 1000 * 1000; + while (deadline->tv_nsec >= 1000 * 1000 * 1000) { + ++deadline->tv_sec; + deadline->tv_nsec -= 1000 * 1000 * 1000; + } +} + +static int wait_flag(pthread_mutex_t *lock, pthread_cond_t *changed, + int *flag, long milliseconds) +{ + struct timespec deadline; + + deadline_after(&deadline, milliseconds); + pthread_mutex_lock(lock); + while (!*flag && + pthread_cond_timedwait(changed, lock, &deadline) == 0) { + } + pthread_mutex_unlock(lock); + return *flag; +} + +static void *remove_worker(void *opaque) +{ + remove_race_t *race = opaque; + + pthread_mutex_lock(&race->lock); + race->started = 1; + pthread_cond_broadcast(&race->changed); + pthread_mutex_unlock(&race->lock); + if (race->by_native) { + race->result = kzt_xcb_connection_map_begin_remove_by_native( + race->map, race->native, &race->lease); + } else { + race->result = kzt_xcb_connection_map_begin_remove_by_guest( + race->map, race->guest, &race->lease); + } + pthread_mutex_lock(&race->lock); + race->finished = 1; + pthread_cond_broadcast(&race->changed); + pthread_mutex_unlock(&race->lock); + return NULL; +} + +static void *destroy_worker(void *opaque) +{ + destroy_race_t *race = opaque; + + pthread_mutex_lock(&race->lock); + race->started = 1; + pthread_cond_broadcast(&race->changed); + pthread_mutex_unlock(&race->lock); + kzt_xcb_connection_map_destroy(&race->map); + pthread_mutex_lock(&race->lock); + race->finished = 1; + pthread_cond_broadcast(&race->changed); + pthread_mutex_unlock(&race->lock); + return NULL; +} + +static void *operation_worker(void *opaque) +{ + operation_race_t *race = opaque; + kzt_xcb_connection_lease_t lease = { 0 }; + + pthread_mutex_lock(&race->lock); + race->started = 1; + pthread_cond_broadcast(&race->changed); + pthread_mutex_unlock(&race->lock); + race->result = kzt_xcb_connection_map_acquire_by_guest( + race->map, race->guest, &lease); + if (race->result == 0) { + race->result = kzt_xcb_connection_lease_lock_mirror(&lease); + } + pthread_mutex_lock(&race->lock); + race->acquired = race->result == 0; + pthread_cond_broadcast(&race->changed); + pthread_mutex_unlock(&race->lock); + if (race->result == 0) { + kzt_xcb_connection_lease_unlock_mirror(&lease); + kzt_xcb_connection_map_release_pair( + race->map, lease.native, lease.guest); + } + return NULL; +} + +static void test_dynamic_capacity_and_lookup(void) +{ + destroy_log_t log = { .lock = PTHREAD_MUTEX_INITIALIZER }; + kzt_xcb_connection_map_t *map = kzt_xcb_connection_map_init( + destroy_guest, &log); + void *guests[16]; + uint64_t previous_generation = 0; + size_t i; + + CHECK("capacity map", map != NULL); + for (i = 0; i < 16; ++i) { + void *canonical = NULL; + uint64_t generation = 0; + void *native = (void *)(uintptr_t)(0x1000 + i * 0x10); + + guests[i] = new_guest(); + CHECK("capacity register", kzt_xcb_connection_map_register( + map, native, guests[i], &canonical, &generation) == + KZT_XCB_CONNECTION_MAP_ADDED); + CHECK("capacity canonical", canonical == guests[i]); + CHECK("capacity monotonic", generation > previous_generation); + previous_generation = generation; + } + CHECK("capacity size", kzt_xcb_connection_map_size(map) == 16); + for (i = 0; i < 16; ++i) { + kzt_xcb_connection_lease_t by_guest = { 0 }; + kzt_xcb_connection_lease_t by_native = { 0 }; + void *native = (void *)(uintptr_t)(0x1000 + i * 0x10); + + CHECK("capacity acquire guest", + kzt_xcb_connection_map_acquire_by_guest( + map, guests[i], &by_guest) == 0); + CHECK("capacity acquire native", + kzt_xcb_connection_map_acquire_by_native( + map, native, &by_native) == 0); + CHECK("capacity guest pair", + by_guest.guest == guests[i] && by_guest.native == native); + CHECK("capacity native pair", + by_native.guest == guests[i] && by_native.native == native); + CHECK("capacity generation", + by_guest.generation == by_native.generation); + kzt_xcb_connection_map_release_pair(map, native, guests[i]); + kzt_xcb_connection_map_release_pair(map, native, guests[i]); + } + kzt_xcb_connection_map_destroy(&map); + CHECK("capacity destroy", map == NULL && log.count == 16); + pthread_mutex_destroy(&log.lock); +} + +static void test_duplicate_native_and_unknown_connection(void) +{ + destroy_log_t log = { .lock = PTHREAD_MUTEX_INITIALIZER }; + kzt_xcb_connection_map_t *map = kzt_xcb_connection_map_init( + destroy_guest, &log); + void *native = (void *)(uintptr_t)0x2000; + void *first = new_guest(); + void *duplicate = new_guest(); + void *canonical = NULL; + uint64_t first_generation = 0; + uint64_t duplicate_generation = 0; + kzt_xcb_connection_lease_t lease = { + .guest = (void *)(uintptr_t)1, + }; + + CHECK("duplicate map", map != NULL); + CHECK("duplicate first", kzt_xcb_connection_map_register( + map, native, first, &canonical, &first_generation) == + KZT_XCB_CONNECTION_MAP_ADDED); + canonical = NULL; + CHECK("duplicate unchanged", kzt_xcb_connection_map_register( + map, native, duplicate, &canonical, &duplicate_generation) == + KZT_XCB_CONNECTION_MAP_UNCHANGED); + CHECK("duplicate canonical", canonical == first); + CHECK("duplicate generation", + duplicate_generation == first_generation); + CHECK("duplicate size", kzt_xcb_connection_map_size(map) == 1); + CHECK("unknown guest", kzt_xcb_connection_map_acquire_by_guest( + map, duplicate, &lease) != 0 && lease.guest == NULL); + CHECK("unknown native", kzt_xcb_connection_map_acquire_by_native( + map, (void *)(uintptr_t)0x2010, &lease) != 0 && + lease.native == NULL); + free(duplicate); + kzt_xcb_connection_map_destroy(&map); + CHECK("duplicate ownership", log.count == 1); + pthread_mutex_destroy(&log.lock); +} + +static void test_maps_are_isolated(void) +{ + destroy_log_t first_log = { .lock = PTHREAD_MUTEX_INITIALIZER }; + destroy_log_t second_log = { .lock = PTHREAD_MUTEX_INITIALIZER }; + kzt_xcb_connection_map_t *first = kzt_xcb_connection_map_init( + destroy_guest, &first_log); + kzt_xcb_connection_map_t *second = kzt_xcb_connection_map_init( + destroy_guest, &second_log); + void *native = (void *)(uintptr_t)0x3000; + void *first_guest = new_guest(); + void *second_guest = new_guest(); + void *canonical; + uint64_t generation; + kzt_xcb_connection_lease_t lease; + + CHECK("isolation maps", first != NULL && second != NULL); + CHECK("isolation first", kzt_xcb_connection_map_register( + first, native, first_guest, &canonical, &generation) == + KZT_XCB_CONNECTION_MAP_ADDED); + CHECK("isolation second", kzt_xcb_connection_map_register( + second, native, second_guest, &canonical, &generation) == + KZT_XCB_CONNECTION_MAP_ADDED); + CHECK("isolation first lookup", kzt_xcb_connection_map_acquire_by_native( + first, native, &lease) == 0 && lease.guest == first_guest); + kzt_xcb_connection_map_release_pair(first, native, first_guest); + CHECK("isolation second lookup", kzt_xcb_connection_map_acquire_by_native( + second, native, &lease) == 0 && lease.guest == second_guest); + kzt_xcb_connection_map_release_pair(second, native, second_guest); + kzt_xcb_connection_map_destroy(&first); + kzt_xcb_connection_map_destroy(&second); + CHECK("isolation destroy", first_log.count == 1 && second_log.count == 1); + pthread_mutex_destroy(&first_log.lock); + pthread_mutex_destroy(&second_log.lock); +} + +static void test_remove_waits_and_generation_is_not_reused(void) +{ + destroy_log_t log = { .lock = PTHREAD_MUTEX_INITIALIZER }; + kzt_xcb_connection_map_t *map = kzt_xcb_connection_map_init( + destroy_guest, &log); + void *native = (void *)(uintptr_t)0x4000; + void *old_guest = new_guest(); + void *new_connection_guest = new_guest(); + void *canonical; + uint64_t old_generation; + uint64_t new_generation; + kzt_xcb_connection_lease_t held = { 0 }; + remove_race_t race = { + .map = map, + .guest = old_guest, + .lock = PTHREAD_MUTEX_INITIALIZER, + .changed = PTHREAD_COND_INITIALIZER, + }; + pthread_t thread; + + CHECK("remove map", map != NULL); + CHECK("remove register", kzt_xcb_connection_map_register( + map, native, old_guest, &canonical, &old_generation) == + KZT_XCB_CONNECTION_MAP_ADDED); + CHECK("remove held lease", kzt_xcb_connection_map_acquire_by_guest( + map, old_guest, &held) == 0); + CHECK("remove thread", pthread_create( + &thread, NULL, remove_worker, &race) == 0); + CHECK("remove starts", wait_flag( + &race.lock, &race.changed, &race.started, 100)); + for (;;) { + kzt_xcb_connection_lease_t probe = { 0 }; + + if (kzt_xcb_connection_map_acquire_by_guest( + map, old_guest, &probe) != 0) { + break; + } + kzt_xcb_connection_map_release_pair( + map, probe.native, probe.guest); + } + CHECK("remove waits lease", !wait_flag( + &race.lock, &race.changed, &race.finished, 30)); + kzt_xcb_connection_map_release_pair(map, held.native, held.guest); + CHECK("remove finishes", wait_flag( + &race.lock, &race.changed, &race.finished, 100)); + CHECK("remove result", race.result == 0); + CHECK("remove join", pthread_join(thread, NULL) == 0); + CHECK("remove lease", race.lease.guest == old_guest && + race.lease.native == native && + race.lease.generation == old_generation); + kzt_xcb_connection_map_finish_remove(&race.lease); + CHECK("remove callback", log.count == 1); + CHECK("remove absent", kzt_xcb_connection_map_size(map) == 0); + CHECK("remove reregister", kzt_xcb_connection_map_register( + map, native, new_connection_guest, &canonical, &new_generation) == + KZT_XCB_CONNECTION_MAP_ADDED); + CHECK("remove generation", new_generation > old_generation); + kzt_xcb_connection_map_destroy(&map); + CHECK("remove destroy", log.count == 2); + pthread_cond_destroy(&race.changed); + pthread_mutex_destroy(&race.lock); + pthread_mutex_destroy(&log.lock); +} + +static void test_remove_by_native_waits_for_guest_lease(void) +{ + destroy_log_t log = { .lock = PTHREAD_MUTEX_INITIALIZER }; + kzt_xcb_connection_map_t *map = kzt_xcb_connection_map_init( + destroy_guest, &log); + void *native = (void *)(uintptr_t)0x4800; + void *guest = new_guest(); + void *canonical; + uint64_t generation; + kzt_xcb_connection_lease_t held = { 0 }; + remove_race_t race = { + .map = map, + .native = native, + .by_native = 1, + .lock = PTHREAD_MUTEX_INITIALIZER, + .changed = PTHREAD_COND_INITIALIZER, + }; + pthread_t thread; + + CHECK("native remove map", map != NULL); + CHECK("native remove register", kzt_xcb_connection_map_register( + map, native, guest, &canonical, &generation) == + KZT_XCB_CONNECTION_MAP_ADDED); + CHECK("native remove held guest lease", + kzt_xcb_connection_map_acquire_by_guest( + map, guest, &held) == 0); + CHECK("native remove thread", pthread_create( + &thread, NULL, remove_worker, &race) == 0); + CHECK("native remove starts", wait_flag( + &race.lock, &race.changed, &race.started, 100)); + for (;;) { + kzt_xcb_connection_lease_t probe = { 0 }; + + if (kzt_xcb_connection_map_acquire_by_guest( + map, guest, &probe) != 0) { + break; + } + kzt_xcb_connection_map_release_pair( + map, probe.native, probe.guest); + } + CHECK("native remove waits guest lease", !wait_flag( + &race.lock, &race.changed, &race.finished, 30)); + kzt_xcb_connection_map_release_pair(map, held.native, held.guest); + CHECK("native remove finishes", wait_flag( + &race.lock, &race.changed, &race.finished, 100)); + CHECK("native remove result", race.result == 0); + CHECK("native remove join", pthread_join(thread, NULL) == 0); + CHECK("native remove lease", race.lease.guest == guest && + race.lease.native == native && + race.lease.generation == generation); + kzt_xcb_connection_map_finish_remove(&race.lease); + CHECK("native remove absent", kzt_xcb_connection_map_size(map) == 0); + CHECK("native remove callback", log.count == 1); + kzt_xcb_connection_map_destroy(&map); + pthread_cond_destroy(&race.changed); + pthread_mutex_destroy(&race.lock); + pthread_mutex_destroy(&log.lock); +} + +static void test_destroy_waits_for_users_and_removal(void) +{ + destroy_log_t log = { .lock = PTHREAD_MUTEX_INITIALIZER }; + kzt_xcb_connection_map_t *map = kzt_xcb_connection_map_init( + destroy_guest, &log); + void *native = (void *)(uintptr_t)0x5000; + void *guest = new_guest(); + void *canonical; + uint64_t generation; + kzt_xcb_connection_lease_t held = { 0 }; + destroy_race_t race = { + .map = map, + .lock = PTHREAD_MUTEX_INITIALIZER, + .changed = PTHREAD_COND_INITIALIZER, + }; + pthread_t thread; + + CHECK("destroy map", map != NULL); + CHECK("destroy register", kzt_xcb_connection_map_register( + map, native, guest, &canonical, &generation) == + KZT_XCB_CONNECTION_MAP_ADDED); + CHECK("destroy acquire", kzt_xcb_connection_map_acquire_by_guest( + map, guest, &held) == 0); + CHECK("destroy thread", pthread_create( + &thread, NULL, destroy_worker, &race) == 0); + CHECK("destroy starts", wait_flag( + &race.lock, &race.changed, &race.started, 100)); + CHECK("destroy waits user", !wait_flag( + &race.lock, &race.changed, &race.finished, 30)); + kzt_xcb_connection_map_release_pair(map, native, guest); + CHECK("destroy finishes", wait_flag( + &race.lock, &race.changed, &race.finished, 100)); + CHECK("destroy join", pthread_join(thread, NULL) == 0); + CHECK("destroy cleanup", race.map == NULL && log.count == 1); + pthread_cond_destroy(&race.changed); + pthread_mutex_destroy(&race.lock); + pthread_mutex_destroy(&log.lock); + + log = (destroy_log_t) { .lock = PTHREAD_MUTEX_INITIALIZER }; + map = kzt_xcb_connection_map_init(destroy_guest, &log); + guest = new_guest(); + CHECK("destroy removal register", kzt_xcb_connection_map_register( + map, native, guest, &canonical, &generation) == + KZT_XCB_CONNECTION_MAP_ADDED); + CHECK("destroy removal begin", + kzt_xcb_connection_map_begin_remove_by_guest( + map, guest, &held) == 0); + race = (destroy_race_t) { + .map = map, + .lock = PTHREAD_MUTEX_INITIALIZER, + .changed = PTHREAD_COND_INITIALIZER, + }; + CHECK("destroy removal thread", pthread_create( + &thread, NULL, destroy_worker, &race) == 0); + CHECK("destroy removal starts", wait_flag( + &race.lock, &race.changed, &race.started, 100)); + CHECK("destroy waits removal", !wait_flag( + &race.lock, &race.changed, &race.finished, 30)); + kzt_xcb_connection_map_finish_remove(&held); + CHECK("destroy removal finishes", wait_flag( + &race.lock, &race.changed, &race.finished, 100)); + CHECK("destroy removal join", pthread_join(thread, NULL) == 0); + CHECK("destroy removal cleanup", race.map == NULL && log.count == 1); + pthread_cond_destroy(&race.changed); + pthread_mutex_destroy(&race.lock); + pthread_mutex_destroy(&log.lock); +} + +static void test_cancelled_remove_rolls_back_closing_state(void) +{ + destroy_log_t log = { .lock = PTHREAD_MUTEX_INITIALIZER }; + kzt_xcb_connection_map_t *map = kzt_xcb_connection_map_init( + destroy_guest, &log); + void *native = (void *)(uintptr_t)0x6000; + void *guest = new_guest(); + void *canonical; + uint64_t generation; + kzt_xcb_connection_lease_t held = { 0 }; + kzt_xcb_connection_lease_t probe = { 0 }; + remove_race_t race = { + .map = map, + .guest = guest, + .lock = PTHREAD_MUTEX_INITIALIZER, + .changed = PTHREAD_COND_INITIALIZER, + }; + pthread_t thread; + void *thread_result = NULL; + + CHECK("cancel remove map", map != NULL); + CHECK("cancel remove register", kzt_xcb_connection_map_register( + map, native, guest, &canonical, &generation) == + KZT_XCB_CONNECTION_MAP_ADDED); + CHECK("cancel remove held", kzt_xcb_connection_map_acquire_by_guest( + map, guest, &held) == 0); + CHECK("cancel remove thread", pthread_create( + &thread, NULL, remove_worker, &race) == 0); + CHECK("cancel remove starts", wait_flag( + &race.lock, &race.changed, &race.started, 100)); + for (;;) { + if (kzt_xcb_connection_map_acquire_by_guest( + map, guest, &probe) != 0) { + break; + } + kzt_xcb_connection_map_release_pair( + map, probe.native, probe.guest); + } + CHECK("cancel remove request", pthread_cancel(thread) == 0); + CHECK("cancel remove join", pthread_join( + thread, &thread_result) == 0 && + thread_result == PTHREAD_CANCELED); + CHECK("cancel remove rollback", kzt_xcb_connection_map_acquire_by_guest( + map, guest, &probe) == 0); + kzt_xcb_connection_map_release_pair(map, probe.native, probe.guest); + kzt_xcb_connection_map_release_pair(map, held.native, held.guest); + kzt_xcb_connection_map_destroy(&map); + CHECK("cancel remove destroy", map == NULL && log.count == 1); + pthread_cond_destroy(&race.changed); + pthread_mutex_destroy(&race.lock); + pthread_mutex_destroy(&log.lock); +} + +static void test_same_connection_serializes_and_different_connections_run(void) +{ + destroy_log_t log = { .lock = PTHREAD_MUTEX_INITIALIZER }; + kzt_xcb_connection_map_t *map = kzt_xcb_connection_map_init( + destroy_guest, &log); + void *guest_a = new_guest(); + void *guest_b = new_guest(); + void *canonical; + uint64_t generation; + kzt_xcb_connection_lease_t held = { 0 }; + operation_race_t same = { + .map = map, + .guest = guest_a, + .lock = PTHREAD_MUTEX_INITIALIZER, + .changed = PTHREAD_COND_INITIALIZER, + }; + operation_race_t different = { + .map = map, + .guest = guest_b, + .lock = PTHREAD_MUTEX_INITIALIZER, + .changed = PTHREAD_COND_INITIALIZER, + }; + pthread_t same_thread; + pthread_t different_thread; + + CHECK("operation map", map != NULL); + CHECK("operation register a", kzt_xcb_connection_map_register( + map, (void *)(uintptr_t)0x7000, guest_a, + &canonical, &generation) == KZT_XCB_CONNECTION_MAP_ADDED); + CHECK("operation register b", kzt_xcb_connection_map_register( + map, (void *)(uintptr_t)0x8000, guest_b, + &canonical, &generation) == KZT_XCB_CONNECTION_MAP_ADDED); + CHECK("operation hold a", kzt_xcb_connection_map_acquire_by_guest( + map, guest_a, &held) == 0); + CHECK("operation lock a", kzt_xcb_connection_lease_lock_mirror( + &held) == 0); + CHECK("operation same thread", pthread_create( + &same_thread, NULL, operation_worker, &same) == 0); + CHECK("operation same starts", wait_flag( + &same.lock, &same.changed, &same.started, 100)); + CHECK("operation same waits", !wait_flag( + &same.lock, &same.changed, &same.acquired, 30)); + CHECK("operation different thread", pthread_create( + &different_thread, NULL, operation_worker, &different) == 0); + CHECK("operation different runs", wait_flag( + &different.lock, &different.changed, &different.acquired, 100)); + CHECK("operation different result", different.result == 0); + CHECK("operation different join", pthread_join( + different_thread, NULL) == 0); + kzt_xcb_connection_lease_unlock_mirror(&held); + kzt_xcb_connection_map_release_pair(map, held.native, held.guest); + CHECK("operation same continues", wait_flag( + &same.lock, &same.changed, &same.acquired, 100)); + CHECK("operation same result", same.result == 0); + CHECK("operation same join", pthread_join(same_thread, NULL) == 0); + kzt_xcb_connection_map_destroy(&map); + CHECK("operation cleanup", map == NULL && log.count == 2); + pthread_cond_destroy(&same.changed); + pthread_mutex_destroy(&same.lock); + pthread_cond_destroy(&different.changed); + pthread_mutex_destroy(&different.lock); + pthread_mutex_destroy(&log.lock); +} + +static void test_mirror_lock_is_recursive(void) +{ + destroy_log_t log = { .lock = PTHREAD_MUTEX_INITIALIZER }; + kzt_xcb_connection_map_t *map = kzt_xcb_connection_map_init( + destroy_guest, &log); + void *native = (void *)(uintptr_t)0x8800; + void *guest = new_guest(); + void *canonical; + uint64_t generation; + kzt_xcb_connection_lease_t lease = { 0 }; + + CHECK("recursive map", map != NULL); + CHECK("recursive register", kzt_xcb_connection_map_register( + map, native, guest, &canonical, &generation) == + KZT_XCB_CONNECTION_MAP_ADDED); + CHECK("recursive acquire", kzt_xcb_connection_map_acquire_by_guest( + map, guest, &lease) == 0); + CHECK("recursive first lock", + kzt_xcb_connection_lease_lock_mirror(&lease) == 0); + CHECK("recursive second lock", + kzt_xcb_connection_lease_lock_mirror(&lease) == 0); + kzt_xcb_connection_lease_unlock_mirror(&lease); + kzt_xcb_connection_lease_unlock_mirror(&lease); + kzt_xcb_connection_map_release_pair(map, lease.native, lease.guest); + kzt_xcb_connection_map_destroy(&map); + CHECK("recursive cleanup", log.count == 1); + pthread_mutex_destroy(&log.lock); +} + +static void run_operation_benchmark(void) +{ + const unsigned int iterations = 200000; + destroy_log_t log = { .lock = PTHREAD_MUTEX_INITIALIZER }; + kzt_xcb_connection_map_t *map = kzt_xcb_connection_map_init( + destroy_guest, &log); + void *native = (void *)(uintptr_t)0x9000; + void *guest = new_guest(); + void *canonical; + uint64_t generation; + struct timespec start; + struct timespec end; + uint64_t elapsed_ns; + double ns_per_pair; + unsigned int i; + + CHECK("benchmark map", map != NULL); + CHECK("benchmark register", kzt_xcb_connection_map_register( + map, native, guest, &canonical, &generation) == + KZT_XCB_CONNECTION_MAP_ADDED); + clock_gettime(CLOCK_MONOTONIC, &start); + for (i = 0; i < iterations; ++i) { + kzt_xcb_connection_lease_t lease = { 0 }; + + CHECK("benchmark acquire", kzt_xcb_connection_map_acquire_by_guest( + map, guest, &lease) == 0); + CHECK("benchmark mirror in", kzt_xcb_connection_lease_lock_mirror( + &lease) == 0); + kzt_xcb_connection_lease_unlock_mirror(&lease); + CHECK("benchmark mirror out", kzt_xcb_connection_lease_lock_mirror( + &lease) == 0); + kzt_xcb_connection_lease_unlock_mirror(&lease); + kzt_xcb_connection_map_release_pair( + map, lease.native, lease.guest); + } + clock_gettime(CLOCK_MONOTONIC, &end); + elapsed_ns = (uint64_t)( + (end.tv_sec - start.tv_sec) * 1000000000LL + + end.tv_nsec - start.tv_nsec); + ns_per_pair = (double)elapsed_ns / iterations; + printf("kzt-xcb-connection-map-performance: %.2f ns/wrapper-lease\n", + ns_per_pair); + CHECK("benchmark upper bound", ns_per_pair < 500.0); + kzt_xcb_connection_map_destroy(&map); + CHECK("benchmark cleanup", log.count == 1); + pthread_mutex_destroy(&log.lock); +} + +int main(int argc, char **argv) +{ + if (argc == 2 && strcmp(argv[1], "--benchmark") == 0) { + run_operation_benchmark(); + return 0; + } + test_dynamic_capacity_and_lookup(); + test_duplicate_native_and_unknown_connection(); + test_maps_are_isolated(); + test_remove_waits_and_generation_is_not_reused(); + test_remove_by_native_waits_for_guest_lease(); + test_destroy_waits_for_users_and_removal(); + test_cancelled_remove_rolls_back_closing_state(); + test_same_connection_serializes_and_different_connections_run(); + test_mirror_lock_is_recursive(); + puts("kzt-xcb-connection-map: all tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_xcb_route_policy.c b/tests/unit/kzt/test_xcb_route_policy.c new file mode 100644 index 00000000000..ba072649a41 --- /dev/null +++ b/tests/unit/kzt/test_xcb_route_policy.c @@ -0,0 +1,81 @@ +#include + +#include "target/i386/latx/include/kzt_xcb_route_policy.h" + +static int failures; + +static void check_kind(const char *symbol_name, + kzt_xcb_route_kind_t expected) +{ + kzt_xcb_route_kind_t got = kzt_xcb_route_classify(symbol_name); + + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got route %d expected %d\n", + symbol_name ? symbol_name : "(null)", got, expected); + ++failures; +} + +static void check_policy(const char *symbol_name, int expected_guarded, + int expected_guest) +{ + int guarded = kzt_xcb_route_is_guarded_consumer(symbol_name); + int guest = kzt_xcb_route_must_stay_guest(symbol_name); + + if (guarded != expected_guarded) { + fprintf(stderr, "%s: guarded got %d expected %d\n", + symbol_name ? symbol_name : "(null)", guarded, + expected_guarded); + ++failures; + } + if (guest != expected_guest) { + fprintf(stderr, "%s: guest got %d expected %d\n", + symbol_name ? symbol_name : "(null)", guest, + expected_guest); + ++failures; + } +} + +int main(void) +{ + check_kind("xcb_flush", KZT_XCB_ROUTE_GUARDED_CONSUMER); + check_kind("xcb_connection_has_error", + KZT_XCB_ROUTE_GUARDED_CONSUMER); + check_policy("xcb_flush", 1, 0); + check_policy("xcb_connection_has_error", 1, 0); + + check_kind("xcb_connect", KZT_XCB_ROUTE_PRODUCER); + check_kind("xcb_connect_to_display_with_auth_info", + KZT_XCB_ROUTE_PRODUCER); + check_kind("XGetXCBConnection", KZT_XCB_ROUTE_PRODUCER); + check_policy("xcb_connect", 0, 1); + check_policy("xcb_connect_to_display_with_auth_info", 0, 1); + check_policy("XGetXCBConnection", 0, 1); + + check_kind("xcb_disconnect", KZT_XCB_ROUTE_LIFECYCLE); + check_kind("XCloseDisplay", KZT_XCB_ROUTE_LIFECYCLE); + check_policy("xcb_disconnect", 0, 1); + check_policy("XCloseDisplay", 0, 1); + + check_kind("xcb_send_request", KZT_XCB_ROUTE_UNSUPPORTED); + check_kind("xcb_flush_checked", KZT_XCB_ROUTE_UNSUPPORTED); + check_policy("xcb_send_request", 0, 1); + check_policy("xcb_flush_checked", 0, 1); + + check_kind("gtk_widget_show", KZT_XCB_ROUTE_NOT_XCB); + check_kind("", KZT_XCB_ROUTE_NOT_XCB); + check_kind(NULL, KZT_XCB_ROUTE_NOT_XCB); + check_policy("gtk_widget_show", 0, 0); + check_policy("", 0, 0); + check_policy(NULL, 0, 0); + + if (failures) { + fprintf(stderr, "kzt-xcb-route-policy: %d failure(s)\n", failures); + return 1; + } + + puts("kzt-xcb-route-policy: contract tests passed"); + return 0; +} diff --git a/tests/unit/meson.build b/tests/unit/meson.build index aca50865f33..bbca18b3577 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -20,6 +20,186 @@ kzt_guest_registry_test = executable( test('kzt-guest-registry', kzt_guest_registry_test) +kzt_xcb_connection_map_test = executable( + 'kzt-xcb-connection-map', + files( + 'kzt/test_xcb_connection_map.c', + '../../target/i386/latx/context/kzt_xcb_connection_map.c', + ), + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-xcb-connection-map', kzt_xcb_connection_map_test) + +test( + 'kzt-xcb-connection-map-performance', + kzt_xcb_connection_map_test, + args: '--benchmark', + timeout: 90, + is_parallel: false, +) + +kzt_xcb_connection_guard_test = executable( + 'kzt-xcb-connection-guard', + files( + 'kzt/test_xcb_connection_guard.c', + '../../target/i386/latx/context/kzt_xcb_connection_guard.c', + '../../target/i386/latx/context/kzt_xcb_connection_map.c', + ), + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-xcb-connection-guard', kzt_xcb_connection_guard_test) + +kzt_wi1618_guest_cancel_scope_test = executable( + 'kzt-wi1618-guest-cancel-scope', + files( + 'kzt/test_wi1618_guest_cancel_scope.c', + '../../target/i386/latx/context/kzt_guest_cancel_scope.c', + ), + include_directories: include_directories( + '../..', + '../../target/i386/latx/include', + ), +) + +test('kzt-wi1618-guest-cancel-scope', + kzt_wi1618_guest_cancel_scope_test) + +test( + 'kzt-wi1618-guest-cancel-scope-source-contract', + find_program('python3'), + args: [ + files('kzt/test_wi1618_guest_cancel_scope_source_contract.py'), + meson.project_source_root(), + ], +) + +kzt_wi1619_xcb_queue_mirror_test = executable( + 'kzt-wi1619-xcb-queue-mirror', + files('kzt/test_wi1619_xcb_queue_mirror.c'), + include_directories: include_directories( + '../../target/i386/latx/include', + ), +) + +kzt_wi1621_x11_xcb_close_source_contract = files( + 'kzt/test_wi1621_x11_xcb_close_source_contract.py', +) +test( + 'kzt-wi1621-x11-xcb-close-source-contract', + find_program('python3'), + args: [kzt_wi1621_x11_xcb_close_source_contract, meson.project_source_root()], + suite: ['unit', 'kzt'], +) + +test( + 'kzt-wi1629-loader-callback-snapshot-source-contract', + find_program('python3'), + args: [ + files('kzt/test_wi1629_loader_callback_snapshot_source_contract.py'), + meson.project_source_root(), + ], + suite: ['unit', 'kzt'], +) + +test( + 'kzt-wi1633-exact-wrapper-selection-source-contract', + find_program('python3'), + args: [ + files('kzt/test_wi1633_exact_wrapper_selection_source_contract.py'), + meson.project_source_root(), + ], + suite: ['unit', 'kzt'], +) + +kzt_guest_glob_dat_target_test = executable( + 'kzt-guest-glob-dat-target', + files( + 'kzt/test_guest_glob_dat_target.c', + '../../target/i386/latx/context/kzt_guest_glob_dat_target.c', + ), + c_args: ['-DCONFIG_LATX_KZT'], + include_directories: include_directories( + '../..', + '../../target/i386/latx/include', + ), +) + +test('kzt-guest-glob-dat-target', kzt_guest_glob_dat_target_test) + +test('kzt-wi1619-xcb-queue-mirror', + kzt_wi1619_xcb_queue_mirror_test) + +test('kzt-wi1619-xcb-queue-mirror-performance', + kzt_wi1619_xcb_queue_mirror_test, + args: ['--benchmark']) + +test( + 'kzt-wi1619-xcb-flush-state-source-contract', + find_program('python3'), + args: [ + files('kzt/test_wi1619_xcb_flush_state_source_contract.py'), + meson.project_source_root(), + ], +) + +test( + 'kzt-wi1571-xcb-context-map-source-contract', + find_program('python3'), + args: [ + files('kzt/test_wi1571_xcb_context_map_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1572-guarded-xcb-bridge-source-contract', + find_program('python3'), + args: [ + files('kzt/test_wi1572_guarded_xcb_bridge_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1573-guarded-xcb-production-source-contract', + find_program('python3'), + args: [ + files('kzt/test_wi1573_guarded_xcb_production_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1611-xcb-cancellation-source-contract', + find_program('python3'), + args: [ + files('kzt/test_wi1611_xcb_cancellation_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1612-xcb-serialization-source-contract', + find_program('python3'), + args: [ + files('kzt/test_wi1612_xcb_serialization_source_contract.py'), + meson.source_root(), + ], +) + +test( + 'kzt-wi1574-dlerror-route-coherence-source-contract', + find_program('python3'), + args: [ + files('kzt/test_wi1574_dlerror_route_coherence_source_contract.py'), + meson.source_root(), + ], +) + test( 'kzt-wi1009-lazy-slot-bridge-removal-contract', find_program('python3'), @@ -110,6 +290,63 @@ if 'x86_64-linux-user' in target_dirs test('kzt-bridge-concurrency', kzt_bridge_concurrency_test) + kzt_wi1633_real_bridge_selection_test = executable( + 'kzt-wi1633-real-bridge-selection', + files( + 'kzt/test_wi1633_real_bridge_selection.c', + 'kzt/kzt_test_options.c', + '../../target/i386/latx/context/bridge.c', + '../../target/i386/latx/context/kzt_rela_runtime_bridge.c', + '../../target/i386/latx/context/kzt_wrapper_bridge_provider.c', + '../../target/i386/latx/context/kzt_wrapper_probe.c', + '../../target/i386/latx/context/kzt_bridge_exact.c', + ), + c_args: [ + '-DBRIDGE_TEST', + '-DCONFIG_LATX_KZT', + '-DNEED_CPU_H', + '-DCONFIG_TARGET="x86_64-linux-user-config-target.h"', + '-DCONFIG_DEVICES="x86_64-linux-user-config-devices.h"', + ], + include_directories: include_directories( + '../..', + '../../target/i386', + '../../linux-user/host/loongarch', + '../../linux-user', + '../../linux-user/x86_64', + '../../target/i386/latx/include', + ), + dependencies: [dependency('threads'), cc.find_library('dl')], + ) + + test('kzt-wi1633-real-bridge-selection', + kzt_wi1633_real_bridge_selection_test) + + kzt_wi1572_guarded_bridge_test = executable( + 'kzt-wi1572-guarded-bridge', + files( + 'kzt/test_wi1572_guarded_bridge.c', + '../../target/i386/latx/context/bridge.c', + ), + c_args: [ + '-DBRIDGE_TEST', + '-DNEED_CPU_H', + '-DCONFIG_TARGET="x86_64-linux-user-config-target.h"', + '-DCONFIG_DEVICES="x86_64-linux-user-config-devices.h"', + ], + include_directories: include_directories( + '../..', + '../../target/i386', + '../../linux-user/host/loongarch', + '../../linux-user', + '../../linux-user/x86_64', + '../../target/i386/latx/include', + ), + dependencies: [dependency('threads'), cc.find_library('dl')], + ) + + test('kzt-wi1572-guarded-bridge', kzt_wi1572_guarded_bridge_test) + kzt_bridge_atfork_fail_open_test = executable( 'kzt-bridge-atfork-fail-open', files( @@ -186,6 +423,13 @@ kzt_guest_library_binding_test = executable( ) test('kzt-guest-library-binding', kzt_guest_library_binding_test) +test( + 'kzt-guest-library-binding-performance', + kzt_guest_library_binding_test, + args: '--benchmark', + timeout: 90, + is_parallel: false, +) kzt_guest_library_binding_teardown_test = executable( 'kzt-guest-library-binding-teardown', @@ -227,6 +471,8 @@ kzt_guest_dl_api_test = executable( 'kzt/test_guest_dl_api.c', '../../target/i386/latx/context/kzt_guest_dl_api.c', '../../target/i386/latx/context/kzt_guest_dl_init.c', + '../../target/i386/latx/context/kzt_guest_runtime_entry.c', + '../../target/i386/latx/context/kzt_guest_runtime_entry_state.c', '../../target/i386/latx/context/kzt_lifecycle_diagnostics.c', '../../target/i386/latx/context/kzt_lazy_prebind_scope.c', ), @@ -266,24 +512,6 @@ kzt_guest_dlclose_quiescence_test = executable( test('kzt-guest-dlclose-quiescence', kzt_guest_dlclose_quiescence_test) -kzt_dlopen_recycle_transaction_test = executable( - 'kzt-dlopen-recycle-transaction', - files( - 'kzt/test_dlopen_recycle_transaction.c', - '../../target/i386/latx/context/dlopen_recycle_transaction.c', - '../../target/i386/latx/context/elf_plt_relocation.c', - '../../target/i386/latx/context/kzt_guest_library_binding.c', - '../../target/i386/latx/context/kzt_lifecycle_diagnostics.c', - '../../target/i386/latx/context/kzt_guest_registry.c', - ), - c_args: ['-DKZT_GUEST_LIBRARY_BINDING_TEST'], - include_directories: include_directories('../../target/i386/latx/include'), - dependencies: dependency('threads'), -) - -test('kzt-dlopen-recycle-transaction', - kzt_dlopen_recycle_transaction_test) - python3 = find_program('python3') test( 'kzt-wi1021-dlerror-entry-source-contract', @@ -501,6 +729,14 @@ kzt_patch_planner_test = executable( test('kzt-patch-planner', kzt_patch_planner_test) +kzt_xcb_route_policy_test = executable( + 'kzt-xcb-route-policy', + files('kzt/test_xcb_route_policy.c'), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-xcb-route-policy', kzt_xcb_route_policy_test) + kzt_runtime_got_plt_candidate_test = executable( 'kzt-runtime-got-plt-candidate', files( @@ -884,7 +1120,10 @@ kzt_loader_event_hook_test = executable( 'kzt/test_loader_event_hook.c', '../../target/i386/latx/context/kzt_loader_event_hook.c', ), - c_args: ['-DKZT_LOADER_EVENT_HOOK_TEST'], + c_args: [ + '-DCONFIG_LATX_KZT', + '-DKZT_LOADER_EVENT_HOOK_TEST', + ], include_directories: include_directories('../..', '../../target/i386/latx/include'), ) @@ -899,6 +1138,7 @@ kzt_lifecycle_snapshot_capacity_test = executable( '../../target/i386/latx/context/kzt_guest_registry.c', ), c_args: [ + '-DCONFIG_LATX_KZT', '-DKZT_LOADER_EVENT_HOOK_TEST', '-DKZT_LOADER_LIFECYCLE_SNAPSHOT_TEST', ], @@ -952,6 +1192,15 @@ test( ], ) +test( + 'kzt-wi1095-eager-transaction-source-contract', + python3, + args: [ + files('kzt/test_wi1095_eager_transaction_source_contract.py'), + meson.source_root(), + ], +) + test( 'kzt-wi994-dlerror-tail-forward-source-contract', python3, @@ -1006,17 +1255,6 @@ test( ], ) -kzt_wi256_lazy_binding_test = executable( - 'kzt-wi256-lazy-binding', - files( - 'kzt/test_wi256_lazy_binding.c', - '../../target/i386/latx/context/kzt_lazy_binding.c', - ), - include_directories: include_directories('../..', '../../target/i386/latx/include'), -) - -test('kzt-wi256-lazy-binding', kzt_wi256_lazy_binding_test) - kzt_wi837_lazy_direct_route_test = executable( 'kzt-wi837-lazy-direct-route', files( @@ -1028,27 +1266,6 @@ kzt_wi837_lazy_direct_route_test = executable( test('kzt-wi837-lazy-direct-route', kzt_wi837_lazy_direct_route_test) -kzt_wi256_lazy_diagnostics_test = executable( - 'kzt-wi256-lazy-diagnostics', - files( - 'kzt/test_wi256_lazy_diagnostics.c', - '../../target/i386/latx/context/kzt_lazy_diagnostics.c', - ), - c_args: ['-DCONFIG_LATX_KZT'], - include_directories: include_directories('../..', '../../target/i386/latx/include'), -) - -test('kzt-wi256-lazy-diagnostics', kzt_wi256_lazy_diagnostics_test) - -test( - 'kzt-wi256-lazy-diagnostics-source-contract', - python3, - args: [ - files('kzt/test_wi256_lazy_diagnostics_source_contract.py'), - meson.source_root(), - ], -) - kzt_wi256_plt_resolver_adapter_test = executable( 'kzt-wi256-plt-resolver-adapter', files( @@ -1067,7 +1284,6 @@ kzt_wi256_lazy_production_bridge_test = executable( files( 'kzt/test_wi256_lazy_production_bridge.c', 'kzt/kzt_test_options.c', - '../../target/i386/latx/context/kzt_lazy_binding.c', '../../target/i386/latx/context/kzt_lazy_prebind_scope.c', '../../target/i386/latx/context/kzt_lazy_direct_route.c', '../../target/i386/latx/context/kzt_jump_slot_production.c', @@ -1105,7 +1321,8 @@ kzt_wi256_lazy_production_bridge_test = executable( ) test('kzt-wi256-lazy-production-bridge', - kzt_wi256_lazy_production_bridge_test) + kzt_wi256_lazy_production_bridge_test, + timeout: 120) test( 'kzt-wi256-plt-resolver-source-contract',