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 e397b76516d..19acb6bead2 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; @@ -89,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 @@ -293,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. @@ -635,6 +641,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 +851,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"}, @@ -1514,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/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 7a3f8aeb894..24f4063db22 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_guest_dl_state.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,13 @@ typedef struct HVFX86LazyFlags { typedef struct CPUX86State { #ifdef CONFIG_LATX +#ifdef CONFIG_LATX_KZT + 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; +#endif +#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..e91f52c396d 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,10 +21,78 @@ #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 +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) +{ + 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)); @@ -35,13 +105,86 @@ 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); +#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. */ + (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(); + } + 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; } +#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; +} + +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) +{ +#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 +196,31 @@ void FreeBox64Context(box64context_t** context) box64context_t* ctx = *context; // local copy to do the cleanning + 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 + * 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 + + 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, + &ctx->mutex_lock); +#endif FreeDictionnary(&ctx->versym); for(int i=0; ielfsize; ++i) { @@ -94,36 +258,15 @@ 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) { - 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 6083f9d3575..df386abed17 100755 --- a/target/i386/latx/context/bridge.c +++ b/target/i386/latx/context/bridge.c @@ -9,19 +9,58 @@ #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" 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; @@ -32,11 +71,163 @@ 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; + kh_guardedbridgemap_t *guardedmap; + 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 +246,76 @@ 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); + b->guardedmap = kh_init(guardedbridgemap); + 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); + kh_destroy(guardedbridgemap, current->guardedmap); *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, int add_to_map, + uintptr_t guest_fallback_target, + kzt_bridge_guard_kind_t guard_kind) { 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'; @@ -96,15 +323,19 @@ uintptr_t AddBridge(bridge_t* bridge, wrapper_t w, void* fnc, int N, const char* 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; } -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 +344,137 @@ 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, 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; + + 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, 1, 0, + KZT_BRIDGE_GUARD_NONE); + 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, 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); - addAlternate(fnc, (void*)ret); } return ret; } @@ -164,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; } @@ -172,47 +516,117 @@ 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; } -// 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/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..816a25b9955 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,6 +34,50 @@ #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_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_plt_resolver_adapter.h" +#include "elf_plt_relocation.h" +#include "elfmap.h" + +#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; void* my__IO_2_1_stdin_ = NULL; @@ -634,9 +681,87 @@ 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; + } + (void)context; + (void)observed; + *slot = replacement; + if (final_value) { + *final_value = replacement; + } + return KZT_EAGER_COMPATIBILITY_WRITE_APPLIED; +} + +#ifdef CONFIG_LATX_KZT +static void kzt_relocation_abort_unrecoverable( + const elfheader_t *head, const char *symbol, const char *stage) +{ + 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 + +#ifdef CONFIG_LATX_KZT +static int kzt_elfloader_read_guest_memory(uintptr_t guest_addr, void *dst, + size_t size, void *opaque); + +#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; + 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) { - GetGlobalSymbolStartEnd(maplib, symname, &offs, &end, head, version, vername); - if(!offs && !end && local_maplib) { - GetGlobalSymbolStartEnd(local_maplib, symname, &offs, &end, head, version, vername); - } - } - } - //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_route_result_t route_result; + kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = kzt_elfloader_read_guest_memory, + }; + + if (observed && bind != STB_LOCAL && + kzt_guest_glob_dat_route( + my_context, head, (uintptr_t)p, observed, + ELF64_R_SYM(rela[i].r_info), sym, symname, + 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)", + 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 *)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 (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 " + "guest=%p final=%p evidence=UNAVAILABLE\n", + symname ? symname : "(none)", (void *)observed, + (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( + 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: + 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); - if (bind==STB_LOCAL - || !tmp - || !((tmp>=head->plt && tmpplt_end) || (tmp>=head->gotplt && tmpgotplt_end)) - || !need_resolv - || bindnow - ) { - if (offs){ - if(p) { - 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); - } else { - printf_log(LOG_INFO, "Warning, Symbol %s found, but Jump Slot Offset is NULL \n", symname); + uintptr_t slot_observation = (uintptr_t)(*p); + uintptr_t expected_guest_target = slot_observation; + 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 = + 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; + +#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 && + write_status != + KZT_PRODUCTION_SLOT_TRANSACTION_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); + if (final_value != slot_observation + head->delta) { + return -1; + } } } - } 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); + 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 && 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; + + 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; + } /* case R_X86_64_NONE: break; @@ -730,9 +1121,586 @@ 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 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 +} + +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"); + } + 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; + } + 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) +{ + 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.reuse_complete_dynamic_view = 1; + 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_bridge = 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 }; + kzt_guest_registry_address_match_t resolver_match = { 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; + { + 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; if(head->pltrel) { int cnt = head->pltsz / head->pltent; @@ -743,32 +1711,110 @@ 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) { +#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"); } - if(head->pltgot) { - if(dl_runtime_resolver ==~0LL){ - dl_runtime_resolver = *(uintptr_t*)(head->pltgot+head->delta+16); + resolver_bridge = pltResolver; +#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 } - *(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) { - if(dl_runtime_resolver ==~0LL){ - dl_runtime_resolver = *(uintptr_t*)(head->got+head->delta+16); +#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_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, + .object_head = (uintptr_t)head, + }; + if (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) { + (void)kzt_guest_registry_publish_lazy_resolver( + KztGuestRegistryForContext(my_context), + guest_link_map, resolver_match.generation, 0, + &resolver); + } + } +#endif + *(uintptr_t*)(resolver_got_runtime+16) = resolver_bridge; +#ifdef CONFIG_LATX_KZT + if (!(option_kzt || wine_option_kzt) || + resolver_snapshot_available) { + head->self_link_map = guest_link_map; } - *(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)); +#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)); } } } @@ -1288,55 +2334,329 @@ 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 +typedef struct kzt_plt_resolver_production_state { + elfheader_t *head; +} 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_registry_lazy_source_t registry_source; + + if (!state || !state->head || object_head != (uintptr_t)state->head || + !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) { + .source_link_map = state->head->self_link_map, + .guest_resolver = registry_source.guest_resolver, + }; + return 0; + } + if (!state->head->kzt_guest_resolver) { + return -1; + } + *source = (kzt_plt_resolver_source_t) { + .source_link_map = state->head->self_link_map, + .guest_resolver = state->head->kzt_guest_resolver, + }; + return 0; +} +#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_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) +{ + 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 - 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); - uint64_t *p = (uint64_t*)(rel->r_offset + h->delta); - uintptr_t offs = 0; - uintptr_t end = 0; - - library_t* lib = h->lib; - lib_t* local_maplib = GetMaplib(lib); - GetGlobalSymbolStartEnd(my_context->maplib, symname, &offs, &end, h, version, vername); - if(!offs && !end && local_maplib) { - GetGlobalSymbolStartEnd(local_maplib, symname, &offs, &end, h, version, vername); - } - if(!offs && !end && !version) - GetGlobalSymbolStartEnd(my_context->maplib, symname, &offs, &end, h, -1, NULL); - - 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); + 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 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 { - offs = (uintptr_t)getAlternate((void*)offs); - if(p) { - 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:""); + version_evidence = KZT_SYMBOL_VERSION_ERROR; + vername = NULL; + } + uint64_t *p = (uint64_t*)(rel->r_offset + h->delta); + +#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_lazy_direct_route_result_t direct_result; + kzt_plt_resolver_production_state_t state = { + .head = h, + }; + kzt_plt_resolver_runtime_ops_t ops = { + .lookup_source = kzt_plt_resolver_lookup_source, + .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 || + direct_result.status == + KZT_LAZY_DIRECT_ROUTE_NATIVE_TRANSIENT)) { + 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=%s " + "guest_handoff=0 legacy_lookup=0 legacy_write=0\n", + 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); + 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 (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( + "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; } - //next_tb is the onebridge of the function - Push64(cpu, offs); } +#endif + + plt_resolver_handoff_guest_or_abort( + cpu, h, relocation_slot, "missing per-object resolver"); +#ifdef CONFIG_LATX_KZT + 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 + return; } 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..d8b72dbc0ab --- /dev/null +++ b/target/i386/latx/context/kzt_bridge_exact.c @@ -0,0 +1,29 @@ +#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; +} + +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 new file mode 100644 index 00000000000..38db77ebb56 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_dl_api.c @@ -0,0 +1,925 @@ +#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_guest_runtime_entry_state.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; + } + __atomic_store_n( + &state->lifecycle, KZT_GUEST_DL_LIFECYCLE_OPEN, + __ATOMIC_RELEASE); + __atomic_store_n(&state->initialized, 1, __ATOMIC_RELEASE); + 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 prepared = 1; + int resolved; + + if (published_now) { + *published_now = 0; + } + 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; + } + ++state->slow_users; + for (;;) { + 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; + } + 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); + kzt_guest_dl_entry_state_leave_locked(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); + prepared = prepare(candidate, opaque) == 0; + pthread_mutex_lock(&state->mutex); + } + observed_dlerror = __atomic_load_n( + &state->observed_dlerror, __ATOMIC_RELAXED); + if (!state->teardown && prepared && + 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); + kzt_guest_dl_entry_state_leave_locked(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_begin_teardown(dlprivate_t *dl) +{ + kzt_guest_dl_entry_state_t *state; + kzt_guest_dl_entries_t *published; + + if (!dl) { + return; + } + state = &dl->guest_dl_entries; + kzt_guest_runtime_entry_state_begin_teardown(state); + published = __atomic_exchange_n( + &state->published, NULL, __ATOMIC_ACQ_REL); + 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)); +} + +#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 }; + 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) { + result.forward_to_guest_caller = 1; + return result; + } + guest_result = kzt_guest_library_run_dlsym( + entries->dlsym, handle, symbol); +#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; +} + +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 }; + 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) { + result.forward_to_guest_caller = 1; + return result; + } + guest_result = kzt_guest_library_run_dlvsym( + entries->dlvsym, handle, symbol, version); +#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, + int guest_route_may_have_pending_error) +{ + 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); + if (!guest_route_may_have_pending_error) { + return result; + } + } + } + if (state && !state->dlerror_slow_required && + !guest_route_may_have_pending_error) { + 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..aa6a912f103 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_dl_init.c @@ -0,0 +1,101 @@ +#include "qemu/osdep.h" + +#include "kzt_guest_dl_init.h" + +#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 *tryLoadElfFromFileForContext( + box64context_t *context, const char *name); +void freeElfFromFile(elfheader_t **header); + +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) +{ + static const char *symbols[] = { + "dlopen", "dlmopen", "dlsym", "dlclose", "dladdr", "dladdr1", + "dlinfo", "dlvsym", "dlerror", + "free", "realloc", "pthread_setcanceltype", + }; + kzt_guest_dl_init_scope_t *scope = opaque; + box64context_t *context = scope->context; + elfheader_t *header; + void *resolved[ARRAY_SIZE(symbols)] = { 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); + } +#endif + header = tryLoadElfFromFileForContext(context, "libc.so.6"); + if (header) { + ResetSpecialCaseElf( + header, symbols, ARRAY_SIZE(symbols), resolved, + &resolved_count); + freeElfFromFile(&header); + } + if (resolved_count != ARRAY_SIZE(symbols)) { + header = tryLoadElfFromFileForContext(context, "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], + }; + 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 int kzt_guest_dl_prepare_entries( + const kzt_guest_dl_entries_t *entries, void *opaque) +{ + 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, &scope, fallback, NULL); +} 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..9185144f425 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_dynamic.c @@ -0,0 +1,280 @@ +#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, + uintptr_t load_bias) +{ + 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: + /* 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: + kzt_guest_dynamic_field_set(&view->verneednum, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_VERDEF: + 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: + 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; + } + + { + 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; + } + } + } + + 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_dynsym_lookup.c b/target/i386/latx/context/kzt_guest_dynsym_lookup.c new file mode 100644 index 00000000000..791905ce94f --- /dev/null +++ b/target/i386/latx/context/kzt_guest_dynsym_lookup.c @@ -0,0 +1,577 @@ +#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 && + binding != KZT_ELF_STB_GNU_UNIQUE) || + (type != STT_NOTYPE && type != STT_OBJECT && + 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) { + 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_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 new file mode 100644 index 00000000000..6304716e007 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_library_adapter.c @@ -0,0 +1,625 @@ +#include "kzt_guest_library_adapter.h" + +#include + +#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" +#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 +#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; +} + +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; + + 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_is_trusted(const char *path) +{ + 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) || + !(name = strrchr(path, '/')) || !name[1]) { + 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; + 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_is_trusted(match->path)) { + return 0; + } + observed_name = kzt_guest_library_basename(match->path); + 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_name, observed_name) != 0) || + (match->soname_status == KZT_GUEST_FIELD_OK && match->soname[0] && + strcmp(match->soname, observed_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_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 + 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; + uintptr_t cached_bridge_target = 0; + unsigned char proven_symbol_type = 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] || + (versioned && !version[0]) || + !(registry = KztGuestRegistryForContext(context)) || + (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.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); + 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 (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, + &cached_bridge_target) != 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) { + 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); + } + } + if (selected) { + diagnostic_reason = NULL; + } else { + selected = guest_result; + } + } + 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)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 + ? KZT_GUEST_LIBRARY_OBJECT_WRAPPED + : library && library->type == LIB_EMULATED + ? KZT_GUEST_LIBRARY_OBJECT_EMULATED + : KZT_GUEST_LIBRARY_OBJECT_UNSUPPORTED; +} + +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 KZT_GUEST_LIBRARY_BINDING_ERROR; + type = loader_object_type(library); + 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 +} + +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, + const kzt_guest_wrapper_source_proof_t *proof) +{ +#ifdef CONFIG_LATX_KZT + 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)proof; + return KZT_GUEST_LIBRARY_BINDING_DISABLED; +#endif +} + +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, + const kzt_guest_wrapper_source_proof_t *proof) +{ +#ifdef CONFIG_LATX_KZT + 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)proof; + return KZT_GUEST_LIBRARY_BINDING_DISABLED; +#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..f10a3de2992 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_library_binding.c @@ -0,0 +1,2134 @@ +#include "kzt_guest_library_binding.h" + +#include +#include +#include +#include +#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; + 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; + +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; + 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 { + 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; + 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; + } 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 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) +{ + 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 (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) + 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); + 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) + 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; + 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); + } + 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_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; + 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_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, + 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; +} + +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_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, uintptr_t *bridge_target) +{ + 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 (bridge_target) *bridge_target = 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; + if (bridge_target && cached->bridge_valid) + *bridge_target = cached->bridge_target; + 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) { + 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; + selected->dynamic_revision = dynamic_revision; + selected->age = ++entry->symbol_cache_age; + selected->valid = 1; + } +out: + 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; + 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)); +} + +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, + uintptr_t guest_link_map_hint, int permanent) +{ + 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 + * 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 { + 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); + + 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); + 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 +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; +} + +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, + 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..100b49c0989 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_link_map_reader.c @@ -0,0 +1,493 @@ +#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_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, + 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) { + 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; + } + } + + kzt_link_map_reader_free(snapshot); + 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..f283004d82d --- /dev/null +++ b/target/i386/latx/context/kzt_guest_registry.c @@ -0,0 +1,3103 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "kzt_guest_registry.h" + +#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; + int lock_ready; + 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[ + 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; +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) { + 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 (kzt_registry_destroying(registry)) { + 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); +} + +/* 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; +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_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; +} + +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; +} + +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) { + 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; +} + +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 == KZT_GUEST_FIELD_TRUNCATED + ? KZT_GUEST_FIELD_UNKNOWN + : 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 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) { + 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->next_handle_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 (registry->active_patch_decision_leases || + registry->evidence_mutators_waiting) { + busy = 1; + } + 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; + 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) { + 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 (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; + } + + 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; +} + +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 || + 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 || + (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); + 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 || + (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; + } + 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; + 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) +{ + 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 || 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 || + 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); + 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_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 (lease) { + memset(lease, 0, sizeof(*lease)); + } + 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; + } + 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].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; +} + +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)); +} + +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) +{ + 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; + } + + 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; +} + +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->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 = 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_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, + 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, + 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; + } + + 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 || + 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; + } + + 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) { + 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_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, + 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 && + 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; + } + object->lazy_resolver = *resolver; + object->lazy_resolver.valid = 1; + kzt_registry_api_unlock(registry); + 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, + 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 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; + } + + 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..935219cce06 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_registry_context.c @@ -0,0 +1,135 @@ +#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) +{ + uintptr_t main_head = 0; + + if (!registry || !link_map_addr || !dynamic_addr || + kzt_guest_registry_context_get_main_namespace_head( + context, &main_head) != 0 || !main_head) { + return 0; + } + 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_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_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_production.c b/target/i386/latx/context/kzt_jump_slot_production.c new file mode 100644 index 00000000000..47f9499041f --- /dev/null +++ b/target/i386/latx/context/kzt_jump_slot_production.c @@ -0,0 +1,4205 @@ +#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_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" +#include "kzt_wrapper_probe.h" + +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; + 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; + 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; + 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; + +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); +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) +{ + (void)opaque; + if (!address || !dst || !size) { + return -1; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + return kzt_jump_slot_production_test_read_guest_memory( + address, dst, 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_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_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) +{ + 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); +} + +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, + void *opaque) +{ + (void)opaque; + if (!slot_addr || !lease) { + return -1; + } + 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; + 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; + 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; + } + 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); + permissions = flags & PAGE_BITS; + lease->checked = 1; + lease->original_permissions = permissions; + lease->was_writable = (flags & PAGE_WRITE) != 0; + if (!(flags & PAGE_VALID) || !permissions) { + goto fail; + } + if (lease->was_writable) { + return 0; + } + if (target_mprotect(lease->guest_page, TARGET_PAGE_SIZE, + permissions | PAGE_WRITE) != 0) { + goto fail; + } + lease->write_enabled = 1; + return 0; +fail: + production_slot_mapping_unlock(lease); + return -1; + } +#else + return -1; +#endif +} + +static int production_slot_end_write(kzt_patch_spike_permission_lease_t *lease, + void *opaque) +{ + int result; + + (void)opaque; + if (!lease || !lease->mmap_lock_held) { + return -1; + } +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + 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; +} + +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_mandatory_finish_slot( + kzt_patch_spike_permission_lease_t *permission) +{ + if (!permission || !permission->mmap_lock_held) { + return -1; + } + permission->restore_attempted = 1; + ++permission->restore_attempts; + if (production_slot_end_write(permission, NULL) != 0) { + return -1; + } + permission->restored = 1; + return 0; +} + +static kzt_production_slot_transaction_result_t +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 = { 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 (production_slot_load(slot_addr, &observed, NULL) != 0) { + goto fail_before_write; + } + if (observed != expected) { + if (final_value) { + *final_value = observed; + } + 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, &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 + 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 (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; + +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(exchanged); +#endif + 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; + } + } + if (observed) { + *observed = __atomic_load_n( + (uintptr_t *)record->slot_addr, __ATOMIC_ACQUIRE); + } + return result; +} + +static int production_prevalidate_write_evidence( + const kzt_patch_decision_t *decision, void *opaque) +{ + kzt_production_jump_slot_state_t *state = opaque; + 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 || + !decision->current_owner.link_map_addr || + !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 || + !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 || + 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_live_object( + KztGuestRegistryForContext(state->context), + 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 || + 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; + } + 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: + 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->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 && + 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_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->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) || + __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; +} + +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, + }; + int status; +#ifdef KZT_JUMP_SLOT_PRODUCTION_TEST + kzt_jump_slot_production_test_full_enrich(); +#endif + status = kzt_rela_immediate_request_enrich( + request, &enrich_input, enrich_result); + return status; +} + +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_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_live_object( + KztGuestRegistryForContext(state->context), + request->source.link_map_addr, &match) != 0) { + return -1; + } + is_main = match.generation == request->source.generation && + match.namespace_id_status == KZT_GUEST_FIELD_OK && + match.namespace_id == 0; + 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; + 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; + request->symbol_name = state->runtime_candidate.symbol_name; + request->version_evidence = + state->runtime_candidate.version_evidence; + request->version = state->runtime_candidate.version; + 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_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; + 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 { + 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, &lease, 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; +} + +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. */ +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 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 || + !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, + }; + 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; + 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; + 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)); + 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) || + (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, + 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_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; + } + 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.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); + + if (claim == KZT_LAZY_PREBIND_CLAIM_CREATED) { + ++prepared; + } + if (claim == KZT_LAZY_PREBIND_CLAIM_CREATED || + claim == KZT_LAZY_PREBIND_CLAIM_REUSED) { + 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); + } + + 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_mandatory_slot_transaction( + 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 " + "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 ? + "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) { + return -1; + } + if (!(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.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->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; + } + *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->evidence.wrapper_provider = state->wrapper_provider; + 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, + .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(); + state->timing->bridge_done = + state->timing->bridge_discover_done; + } + return 0; + } + memset(&state->wrapper_provider, 0, sizeof(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(); + } + 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; + } + 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(); + } + 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) { + 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) { + 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) { +#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)); + } +} + +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; + 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 || + 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->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->evidence.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) { + 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 = 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(); + } + 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; + 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 || !state->guard_input || + !state->guard_provider || !state->guard_bridge || + !slot_addr || !replacement) { + return KZT_LAZY_DIRECT_ROUTE_CAS_ERROR; + } + 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; + } + if (state->timing_enabled) { + state->timing->cas_done = production_lazy_direct_timing_now(); + } + 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"; + 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) { + 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"; + } + 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_registry_address_match_t match; + int result = -1; + + state->failure_stage = "EXACT_LIBRARY_BINDING"; + if (!owner || !owner->known || !owner->link_map_addr || + !owner->generation || !handle) { + return -1; + } + if (kzt_guest_registry_find_live_object( + KztGuestRegistryForContext(state->context), + 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 = match.namespace_id, + .namespace_kind = KZT_GUEST_LIBRARY_NAMESPACE_MAIN, + }; + if (KztGuestLibraryLookupForContext(state->context, &key, handle) != 0) { + 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: + return result; +} + +static void production_release_exact(kzt_guest_library_handle_t *handle, + void *opaque) +{ + (void)opaque; + 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; + } + if (!state->held_decision_lease || + !state->held_decision_lease->active || + !state->loader_quiescence_lease.bindings || + !state->loader_quiescence_lease.cookie) { + return -1; + } + 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; + 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 (!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 || + 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.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) { + 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 (!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)); + 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( + &state->wrapper_provider, state->retained_provider_handle) != 0) { + production_release_decision_lease(state); + return -1; + } + /* 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; + } + 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, + 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; + 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))) { + return 0; + } + state->failure_stage = "WRITER"; + return 1; +} + +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 (!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; + } + 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; + } + 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, + 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; + 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_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, + .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){ + .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, + .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_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.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, + "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_CONFIRMED_UNVERSIONED, + version, expected_guest_target_present, expected_guest_target, + legacy_target, 0, 0, 0, 0, NULL, 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, 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; + int scope_supported; + int loader_route_family; + int loader_write_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; + } + 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(); + } + state.context = context; + state.registry = KztGuestRegistryForContext(context); + 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 || + !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; + } + state.evidence.held_source_lease = &state.source_lease; + if (timing_enabled) { + timing.source = production_lazy_direct_timing_now(); + } + 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 (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 && + 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_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 && + 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; + } + } + 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 || + (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)) { + 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.evidence.loader_quiescence_lease); + if (timing_enabled) { + timing.quiescence = production_lazy_direct_timing_now(); + } + if (quiescence_status != 0) { + if (scope_supported && !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 (!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) { + 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 (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); + 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 (!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(); + } + + 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, + .allow_budget_transient_native = loader_write_enabled, + }; + 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 || + 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=%s writer_result=%s " + "slot_before=%p slot_after=%p selected_target=%p\n", + 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); + } + +done: + 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.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(); + 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; +} + +#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..5a52267308a --- /dev/null +++ b/target/i386/latx/context/kzt_jump_slot_route.c @@ -0,0 +1,310 @@ +#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 write_attempted; + 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; + state->write_attempted = 1; + 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_decline_without_write( + const kzt_jump_slot_route_input_t *input, + kzt_jump_slot_route_result_t *result) +{ + 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->status = KZT_JUMP_SLOT_ROUTE_BYPASS; + result->selected_target = input->request.legacy_target; + 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; + } + + 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; + } + return decision; +} + +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 -1; + } + 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->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 && + 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 || + 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 && + 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; + 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->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 ? + slot_state.last_read : result->observed_value; + result->final_value = result->selected_target; + return 0; + } + + return route_decline_without_write(input, result); +} 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..8699a52ace2 --- /dev/null +++ b/target/i386/latx/context/kzt_lazy_direct_route.c @@ -0,0 +1,218 @@ +#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 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; + } + +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/kzt_lazy_prebind_scope.c b/target/i386/latx/context/kzt_lazy_prebind_scope.c new file mode 100644 index 00000000000..a85a3f66a52 --- /dev/null +++ b/target/i386/latx/context/kzt_lazy_prebind_scope.c @@ -0,0 +1,616 @@ +#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) && + (!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 && + 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->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 == + 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); + 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) +{ + 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; +} + +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; + + 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 (!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; + } + } + if (!found) { + pthread_mutex_unlock(&scope->lock); + return 0; + } + for (;;) { + unsigned long leases = 0; + + for (entry = scope->entries; entry; entry = entry->next) { + 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, + 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..a58aa49648c --- /dev/null +++ b/target/i386/latx/context/kzt_loader_event_hook.c @@ -0,0 +1,873 @@ +#include "kzt_loader_event_hook.h" + +#include "box64context.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_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, + 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); + 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_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"); + + 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; + kzt_loader_event_layout_t layout; + + 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 (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; + } + 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 = layout.scope_layout; + __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_failed, 1U, __ATOMIC_RELEASE); + __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 || + kzt_loader_event_hook_publisher_enter(hook) != 0) { + return -1; + } + 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, 0); + } + + 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) { + 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( + &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, 0); + } + } + 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, 0); + } + if (state == KZT_LOADER_DEBUG_ADD) { + return kzt_loader_event_hook_publisher_leave( + 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)) { + 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)) { + transition_result = unload(&pending.identities[i], opaque); + } else { + 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, lifecycle_result == KZT_LOADER_LIFECYCLE_OK ? 0 : -1, + lifecycle_result, 1); +} + +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); + 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; +} + +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 || + hook->scope_layout == KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED) { + 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); +} + +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) +{ + 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/context/kzt_observation_adapter.c b/target/i386/latx/context/kzt_observation_adapter.c new file mode 100644 index 00000000000..e2266a0a02a --- /dev/null +++ b/target/i386/latx/context/kzt_observation_adapter.c @@ -0,0 +1,478 @@ +#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) +{ + 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->reuse_complete_dynamic_view) || + request->dynamic_diagnostics_force_compare || + (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, + &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 }; + 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, + 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. */ + 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 (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)) { + 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) { + 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 new file mode 100644 index 00000000000..f7d0dd86be7 --- /dev/null +++ b/target/i386/latx/context/kzt_owner_resolver.c @@ -0,0 +1,247 @@ +#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_string_has_value(kzt_guest_field_status_t status) +{ + return status == KZT_GUEST_FIELD_OK || + status == KZT_GUEST_FIELD_TRUNCATED; +} + +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 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 = NULL; + const char *path = NULL; + + memset(ref, 0, sizeof(*ref)); + if (!match || !text) { + return; + } + + 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 = 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_address_match_t *match, + kzt_owner_resolver_text_t *text, + kzt_patch_object_ref_t *ref, + size_t *match_count) +{ + memset(ref, 0, sizeof(*ref)); + if (text) { + memset(text, 0, sizeof(*text)); + } + if (match_count) { + *match_count = 0; + } + + if (!match || !ref) { + return KZT_OWNER_LOOKUP_NOT_FOUND; + } + + if (match_count) { + *match_count = match->match_count; + } + if (match->match_count == 0) { + return KZT_OWNER_LOOKUP_NOT_FOUND; + } + if (match->match_count > 1) { + return KZT_OWNER_LOOKUP_AMBIGUOUS; + } + + kzt_owner_ref_from_match(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_address_pair_t pair; + 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_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( + &pair.current, &resolution->current_text, + &resolution->current_owner, &resolution->current_match_count); + expected_status = kzt_owner_find_by_address( + &pair.expected, &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: + 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..ee9f2df29ea --- /dev/null +++ b/target/i386/latx/context/kzt_patch_planner.c @@ -0,0 +1,403 @@ +#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_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( + 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..93554b0952f --- /dev/null +++ b/target/i386/latx/context/kzt_patch_spike_guard.c @@ -0,0 +1,437 @@ +#include "qemu/osdep.h" + +#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); +} + +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) +{ + 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; + 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( + 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; +} + +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, + 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) { + 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 : + 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) { + (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_UNRECOVERABLE, + KZT_PATCH_SPIKE_FAILURE_TRANSACTION_UNRECOVERABLE, + KZT_PATCH_SPIKE_ACTION_TRANSACTION_UNRECOVERABLE, guard); + kzt_patch_spike_guard_unlock(guard); + return 0; + } + + if (writer->finish_slot && + writer->finish_slot(decision, writer->opaque) != + KZT_PATCH_SPIKE_WRITER_OK) { + 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( + 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) { + 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_UNRECOVERABLE, + KZT_PATCH_SPIKE_FAILURE_TRANSACTION_UNRECOVERABLE, + KZT_PATCH_SPIKE_ACTION_TRANSACTION_UNRECOVERABLE, 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; +} + +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) { + 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"; + case KZT_PATCH_SPIKE_RESULT_ROLLED_BACK: + return "ROLLED_BACK"; + case KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE: + return "UNRECOVERABLE"; + } + + 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"; + 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 new file mode 100644 index 00000000000..563a59c7ea1 --- /dev/null +++ b/target/i386/latx/context/kzt_patch_spike_writer.c @@ -0,0 +1,396 @@ +#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_restore_attempts = lease->restore_attempts; + 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; + ++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); + 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->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 && + 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; +} + +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, + 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_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) +{ + return kzt_patch_spike_writer_try_apply_with_slot_ops(guard, decision, + NULL, record); +} 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 new file mode 100644 index 00000000000..713fc578565 --- /dev/null +++ b/target/i386/latx/context/kzt_plt_resolver_adapter.c @@ -0,0 +1,79 @@ +#ifndef KZT_PLT_RESOLVER_ADAPTER_TEST +#include "qemu/osdep.h" +#endif + +#include "kzt_plt_resolver_adapter.h" + +#include +#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_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) +{ + const uint64_t *frame; + kzt_plt_resolver_source_t source; + if (result) { + memset(result, 0, sizeof(*result)); + result->status = KZT_PLT_RESOLVER_ERROR; + } + if (!cpu || !ops || !result || !ops->lookup_source || + !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); + result->selected_resolver = source.guest_resolver; + 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); + 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..8821abc2479 --- /dev/null +++ b/target/i386/latx/context/kzt_rela_immediate_candidate.c @@ -0,0 +1,173 @@ +#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.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..07edbfff694 --- /dev/null +++ b/target/i386/latx/context/kzt_rela_request_enricher.c @@ -0,0 +1,259 @@ +#include "kzt_rela_request_enricher.h" +#include "kzt_rela_stub_detector.h" + +#include +#include + +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 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 = NULL; + const char *path = NULL; + + memset(ref, 0, sizeof(*ref)); + if (!match || !text) { + return; + } + + 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 = 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 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_address_pair_t pair; + + if (!registry || !address || !result) { + return -1; + } + if (kzt_guest_registry_resolve_address_pair( + registry, address, address, &pair) != 0) { + return -1; + } + 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; + } + 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 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_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 || !result) { + return -1; + } + + 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 ? 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 ? input->wrapper_manifest : NULL, &probe_request, bridge_ops, + &result->wrapper_probe) != 0) { + return -1; + } + + 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; + return 0; +} + +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); + /* 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 new file mode 100644 index 00000000000..c9fc6c271a9 --- /dev/null +++ b/target/i386/latx/context/kzt_rela_runtime_bridge.c @@ -0,0 +1,771 @@ +#include "kzt_rela_runtime_bridge.h" + +#include +#include +#include +#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" + +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)); +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) +{ + 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; + 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) +{ + 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; +} + +/* 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) +{ + 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 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) +{ + 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_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 || + handle->object_type != KZT_GUEST_LIBRARY_OBJECT_WRAPPED || + !(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) +{ + 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->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 || + !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; + 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; + + (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->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)) { + 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; + } + 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 + 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 (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"))) { + 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; + } + 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; + } + 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; + } + + 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; + 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)) { + 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; +} + +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->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)) { + 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 || !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); + 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( + 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, + 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, + }; + 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; + } + 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; + } + 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, NULL, 0, KZT_BRIDGE_GUARD_NONE, 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, NULL, 0, + KZT_BRIDGE_GUARD_NONE, 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, NULL, 0, KZT_BRIDGE_GUARD_NONE, 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, 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( + 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, + 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( + 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_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..abe2a729f11 --- /dev/null +++ b/target/i386/latx/context/kzt_wrapper_bridge_provider.c @@ -0,0 +1,207 @@ +#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 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) +{ + 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; + } + /* 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; +} + +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.retained_provider_handle) || + !candidate.bridge_owner || !candidate.bridge_storage || + 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 || + !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/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/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..27325e8f988 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); \ @@ -223,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); @@ -352,7 +363,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,12 +393,62 @@ 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; + } } +#ifdef CONFIG_LATX_KZT + 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) { + (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; } @@ -396,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); @@ -782,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 f07cf9b44a0..5fb6db4692f 100644 --- a/target/i386/latx/context/meson.build +++ b/target/i386/latx/context/meson.build @@ -62,12 +62,55 @@ my_file = files( 'pathcoll.c', 'fileutils.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_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_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', + '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', + 'kzt_owner_resolver.c', + 'kzt_wrapper_probe.c', + '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 9e4981a9d57..6b134a4bed0 100644 --- a/target/i386/latx/context/myalign.c +++ b/target/i386/latx/context/myalign.c @@ -9,11 +9,28 @@ #include "config-host.h" #include "lsenv.h" #include "myalign.h" +#include "debug.h" +#include "elfmap.h" #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" +#include "kzt_lifecycle_diagnostics.h" +#include "kzt_loader_lifecycle_snapshot.h" +#include "kzt_per_object_got_plt.h" +#endif #include #include #include +#include +#include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-prototypes" @@ -41,6 +58,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; @@ -1572,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"); @@ -1601,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; @@ -1690,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 del_xcb_connection(void* src) +void *align_xcb_connection(void *guest) { - if(!src) + 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) { @@ -2064,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); @@ -2084,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); } @@ -2114,283 +2388,476 @@ 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; } 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; -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; +#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; } - 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; - } - } - } + if (!size) { + return 0; } - 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; - } - } + + host_ptr = lock_user(VERIFY_READ, (abi_ulong)guest_addr, size, true); + if (!host_ptr) { + 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); + + memcpy(dst, host_ptr, size); + unlock_user(host_ptr, (abi_ulong)guest_addr, 0); return 0; } -static int findx86pthread_setcanceltype(elfheader_t* h) +#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) { - if (x86pthread_setcanceltype ||x64free_fini) { + 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 (!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; } - 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; - } - } + name = match.path; + basename = strrchr(name, '/'); + basename = basename ? basename + 1 : name; + if (!basename[0] || !FindLibIsWrapped((char *)basename)) { + return 0; } - return -1; + if (kzt_guest_library_wrapper_source_acquire( + context, link_map_addr, name, basename, &source_proof) != 0) { + return -1; + } + (void)AddNeededLibWithLibrary( + context->maplib, &context->neededlibs, NULL, 0, 1, + basename, context, &library); + if (!library) { + kzt_guest_library_wrapper_source_release(&source_proof); + return -1; + } + loader_scope_active = loader_scope && loader_scope->bindings && + loader_scope->identity && loader_scope->cookie; + if (loader_scope_active) { + binding_result = kzt_guest_library_note_loader_pair_pending( + context, loader_scope, link_map_addr, library, &source_proof); + } else { + binding_result = kzt_guest_library_note_loader_pair( + context, link_map_addr, library, &source_proof); + } + kzt_guest_library_wrapper_source_release(&source_proof); + 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 char* kzt_find_realsofilepath(char * filepath, char *filetmp) + +static int kzt_tb_callback_per_object_got_plt(uintptr_t link_map_addr, + void *opaque) { - 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; + 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(context), + .link_map_addr = link_map_addr, + .apply = KztPerObjectGotPltWrite, + .opaque = context, + }; + kzt_per_object_got_plt_result_t 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; } - //file must exist for kzt_tb_callback. - return filepath; + return 0; } -extern const char* libcName; -static void kzt_tb_callback(CPUX86State *env) + +static int kzt_tb_callback_pretranslate_target(uintptr_t target, void *opaque) { - struct link_map_x64 * my_lm = (struct link_map_x64 *)env->regs[R_EAX + ld_info->reg]; - 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) { - 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; + CPUX86State *env = opaque; + + if (!target || !env || !lsenv || lsenv->cpu_state != env) { + return -1; } - 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 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(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; + 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, + }; + 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; + kzt_observation_adapter_result_t observation_result; + int main_namespace; + kzt_tb_callback_scope_t callback_scope; + + if (!context || !env) { return; } - char filetmp[PATH_MAX] = {0}; - if (rfilename[0] == '/') { - rfilename = kzt_find_realsofilepath(rfilename, filetmp); - } - 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); + 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( + &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( + &context->kzt_guest_registry_context, registry, + link_map_addr, object_identity.load_bias, + object_identity.dynamic_addr)) { + main_namespace = 1; + } else { + 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 (!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); + if (main_namespace == 1 && !confirmed_main_head && + kzt_guest_registry_context_confirm_main_namespace_head( + &context->kzt_guest_registry_context, + &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(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 = context, + .per_object_flow = kzt_tb_callback_per_object_got_plt, + .per_object_opaque = &callback_scope, + .legacy_flow = NULL, + .diagnostic = kzt_callback_diagnostic_log, + .diagnostic_opaque = registry, + }; + + if (registry && diagnostics_enabled) { + (void)kzt_guest_registry_configure_diagnostics(registry, + &diagnostic_config); + } + 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); + } } - FILE *f = fopen(rfilename, "rb"); - if(!f) { - printf_log(LOG_INFO, "%s Error: Cannot open \"%s\"\n", __func__, rfilename); - return; + 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 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( + context, kzt_tb_callback_pretranslate_target, env); + } } - h = LoadAndCheckElfHeader(f, rfilename, 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); - 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); - } else { - AddDebugInfo(LIB_EMULATED, my_lm->l_name, my_lm->l_map_start, my_lm->l_map_end); + 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)context; + (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 + 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 || !hook) { 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(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", + hook->build_id, (unsigned long)event.sequence, + (unsigned long)event.link_map_addr, (unsigned long)event.published_ns); + kzt_tb_callback_consume(context, env, &event); #else - tb_phys_invalidate(test_tb, test_tb->page_addr[0]); + kzt_tb_callback_consume(my_context, 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; } - cnt++; + *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 inline gint tb_sort_cmp(const void *ap, const void *bp) + +static int kzt_tb_prepare_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_prepare_unload(opaque, &unload); } -static void finiReFlesh(elfheader_t* exech) + +static int kzt_tb_cancel_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; - return; + if (!identity) { + return -1; } - /* 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; - } + 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 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 -1; } - 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, + }; + 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, result); + return result; } -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); + 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, + }; + kzt_guest_registry_t *registry; + int publication_result; + + (void)env; + registry = KztGuestRegistryForContext(context); + if (!hook || !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( + 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, 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( + 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, 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( + 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); } +#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}; @@ -2591,10 +3058,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; @@ -2604,13 +3076,36 @@ static struct x86_ld_info *find_ld_bridge(void* info) if (stat(real_dl_file, &st)) { 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 = 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); 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 = + 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; } @@ -2619,6 +3114,12 @@ 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 = + 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; } @@ -2627,6 +3128,12 @@ 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 = + 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; } @@ -2635,6 +3142,12 @@ 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 = + 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; } @@ -2644,6 +3157,12 @@ 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 = + 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; } @@ -2653,30 +3172,104 @@ 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 = + 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 = + 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}; + box64context_t *context = my_context; + struct x86_ld_info *ld_info = + context ? context->kzt_loader_bridge_info : NULL; +#ifdef CONFIG_LATX_KZT + kzt_loader_event_hook_t *hook = + context ? &context->kzt_loader_event_hook : NULL; +#endif + if (!context) { + return; + } 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 (context) { + context->kzt_guest_scope_layout = + KZT_GUEST_SCOPE_LAYOUT_UNSUPPORTED; + } + if (!hook || !candidate || + kzt_loader_event_hook_install(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", + hook ? kzt_loader_event_hook_result_name(hook->result) + : "NO_CONTEXT", + build_id[0] ? build_id : "(unavailable)"); + free(candidate); + return; + } + 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( + 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( + "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); + 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 (hook && __atomic_load_n(&hook->lifecycle_enabled, + __ATOMIC_ACQUIRE)) { + kzt_add_fucn_by_addr( + context->kzt_loader_debug_state_original, cpu, + hook->debug_state_addr, + target_latx_ld_callback, kzt_tb_debug_state_callback); + } +#endif env->eip = eip; } void kzt_bridge_init(void) @@ -2795,43 +3388,71 @@ void kzt_wine_bridge(abi_ulong start, int fd) } } -void kzt_wine_init_x86(void) +elfheader_t* tryLoadElfFromFileForContext( + box64context_t *context, const char *name) { - if (!option_kzt ||!latx_wine ||my_context->mallocmapsize) { - return; + elfheader_t* h = NULL; + char *tmp; + uintptr_t load_addr; + + if (!context || !name) { + return NULL; } - struct malloc_map* m = malloc(sizeof(struct malloc_map)); - m->mallocp = (void *)(uintptr_t)RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, - 0, "malloc"); - ; - m->freep = (void *)(uintptr_t)RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, - 0, "free"); - m->reallocp = (void *)(uintptr_t)RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlsym, 2, - 0, "realloc"); - m->h = wine_elf_header; - AddMallocMap(my_context, m); - x86free = m->freep; - x86realloc = m->reallocp; -} + tmp = ResolveFile(name, &context->box64_ld_lib); -elfheader_t* loadElfFromFile(const char* name) -{ - elfheader_t* h = NULL; - char *tmp = ResolveFile(name, &my_context->box64_ld_lib); - if (FileExist(tmp, IS_FILE)) { + 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; } + +elfheader_t* tryLoadElfFromFile(const char* name) +{ + return tryLoadElfFromFileForContext(my_context, name); +} + +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 c6bced3cbd0..0282e8dfb82 100644 --- a/target/i386/latx/context/wrappedlibc.c +++ b/target/i386/latx/context/wrappedlibc.c @@ -68,6 +68,13 @@ #include "elfloader_private.h" #include "bridge.h" #include "globalsymbols.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" +#endif +#include "kzt_loader_callback_scope.h" #define LIBNAME libc const char* libcName = @@ -77,11 +84,9 @@ const char* libcName = "libc.so.6" #endif ; + #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); @@ -3503,14 +3508,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; @@ -3523,7 +3520,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) { @@ -3531,543 +3536,208 @@ 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) { - struct link_map* ret = (struct link_map*)(uintptr_t)RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlopen, 2, filename, flag); - 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 - 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); - } - 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; -} 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); - //TODO - if (IsEmuLib(dl->libs[i])) { - elfheader_t * h = my_context->elfs[idx]; - lsassert(h); - LatxResetElf(h); - callx86dlopen(rfilename, flag, h, is_local); - } - ReloadLibrary(dl->libs[i]); // reset memory image, redo reloc, run inits - } - } - 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 - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlopen, 2, filename, flag); - 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(AddNeededLib(NULL, NULL, NULL, is_local, bindnow, libs, 1, my_context)) { - 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 - 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); - } - } - //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); + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); + if (!entries || !entries->dlsym) { 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"); + 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; } - 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 (result.value) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); } - --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); - 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 - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlopen, 2, dl->libs[nlib]->name, dl->libs[nlib]->x86dlopenflag); - if (!ret) {//user sometime test for finding a func. - printf_dlsym(LOG_NEVER, "redlopen %p return %p\n", rsymbol, (void*)NULL); - return NULL; - } - lsassert(ret); - dl->libs[nlib]->x86linkmap = (void *)ret; - 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; - } - 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, + 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, + 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, + guest_route_may_have_pending_error); + 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; + 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 + 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 || 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, + guest_loader_route); + } + 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; @@ -4087,80 +3757,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 1f7c3ce8626..fb58dd64889 100644 --- a/target/i386/latx/context/wrappedlibdl.c +++ b/target/i386/latx/context/wrappedlibdl.c @@ -27,16 +27,15 @@ #include "callback.h" #include "myalign.h" #include "fileutils.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" +#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; @@ -51,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) { @@ -59,533 +66,208 @@ 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 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); - 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 - 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); - } - 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; -} 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); - //TODO - if (IsEmuLib(dl->libs[i])) { - elfheader_t * h = my_context->elfs[idx]; - lsassert(h); - LatxResetElf(h); - callx86dlopen(rfilename, flag, h, is_local); - } - ReloadLibrary(dl->libs[i]); // reset memory image, redo reloc, run inits - } - } - 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 - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlopen, 2, filename, flag); - 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(AddNeededLib(NULL, NULL, NULL, is_local, bindnow, libs, 1, my_context)) { - 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 - 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); - } - } - //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 -#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); + entries = kzt_guest_dl_entries_for_call(my_context, &fallback); + if (!entries || !entries->dlsym) { 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"); + 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; } - 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 (result.value) { + kzt_guest_dl_api_finish_success(error_state, guest_error_was_clean); } - --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); - 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 - uint64_t ret = RunFunctionWithState((uintptr_t)my_context->dlprivate->x86dlopen, 2, dl->libs[nlib]->name, dl->libs[nlib]->x86dlopenflag); - if (!ret) {//user sometime test for finding a func. - printf_dlsym(LOG_NEVER, "redlopen %p return %p\n", rsymbol, (void*)NULL); - return NULL; - } - lsassert(ret); - dl->libs[nlib]->x86linkmap = (void *)ret; - 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; - } - 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, + 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, + 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, + guest_route_may_have_pending_error); + 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; + 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 + 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 || 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, + guest_loader_route); + } + 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; @@ -605,42 +287,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/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..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" @@ -304,7 +306,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 +339,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 +372,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 +405,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 +438,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 +471,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 +504,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 +738,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 @@ -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/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/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 489d1b79ad4..73620fd47e7 100755 --- a/target/i386/latx/include/box64context.h +++ b/target/i386/latx/include/box64context.h @@ -5,6 +5,14 @@ #include "dictionnary.h" #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_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; @@ -14,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; @@ -41,19 +50,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; @@ -201,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 @@ -239,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 @@ -341,22 +329,47 @@ 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; 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_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; + 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 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_lazy_prebind_scope_t *KztLazyPrebindScopeForContext( + box64context_t *context); +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); -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 67166a9f3ba..a3d85a7b02c 100755 --- a/target/i386/latx/include/bridge.h +++ b/target/i386/latx/include/bridge.h @@ -7,11 +7,28 @@ 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. */ 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 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); @@ -23,6 +40,14 @@ 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); +int bridge_test_guarded_count(bridge_t *bridge); +#endif + void init_bridge_helper(void); void fini_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/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/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/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/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/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/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 new file mode 100644 index 00000000000..5b074302f07 --- /dev/null +++ b/target/i386/latx/include/kzt_bridge_exact.h @@ -0,0 +1,16 @@ +#ifndef KZT_BRIDGE_EXACT_H +#define KZT_BRIDGE_EXACT_H + +#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 new file mode 100644 index 00000000000..81b3a66b209 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_dl_api.h @@ -0,0 +1,129 @@ +#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_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) +{ + 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 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); + +#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..0519b150c0b --- /dev/null +++ b/target/i386/latx/include/kzt_guest_dl_state.h @@ -0,0 +1,68 @@ +#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 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; + kzt_guest_dl_entries_t *published; + uintptr_t observed_dlerror; + pthread_t initializer; + int initialized; + 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_dynamic.h b/target/i386/latx/include/kzt_guest_dynamic.h new file mode 100644 index 00000000000..0eff4fec03f --- /dev/null +++ b/target/i386/latx/include/kzt_guest_dynamic.h @@ -0,0 +1,48 @@ +#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_ADDRESS_OVERFLOW, +} 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_dynsym_lookup.h b/target/i386/latx/include/kzt_guest_dynsym_lookup.h new file mode 100644 index 00000000000..b0776d2026e --- /dev/null +++ b/target/i386/latx/include/kzt_guest_dynsym_lookup.h @@ -0,0 +1,38 @@ +#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" + +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, + 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_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 new file mode 100644 index 00000000000..4bac7a72f4f --- /dev/null +++ b/target/i386/latx/include/kzt_guest_library_adapter.h @@ -0,0 +1,99 @@ +#ifndef KZT_GUEST_LIBRARY_ADAPTER_H +#define KZT_GUEST_LIBRARY_ADAPTER_H + +#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; + +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, + 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); +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. */ +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, + 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, + 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, + 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..5e897082dbe --- /dev/null +++ b/target/i386/latx/include/kzt_guest_library_binding.h @@ -0,0 +1,280 @@ +#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 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; + void *gate; + 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); +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); +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, + 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); +/* 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); +/* 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, + 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, 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. */ +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. + * 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); +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_link_map_reader.h b/target/i386/latx/include/kzt_guest_link_map_reader.h new file mode 100644 index 00000000000..1ff6c3ac4b8 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_link_map_reader.h @@ -0,0 +1,98 @@ +#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; + +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, + 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); + +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. + * 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..6728b384d7c --- /dev/null +++ b/target/i386/latx/include/kzt_guest_registry.h @@ -0,0 +1,536 @@ +#ifndef KZT_GUEST_REGISTRY_H +#define KZT_GUEST_REGISTRY_H + +#include +#include + +#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; + unsigned long generation; + uintptr_t namespace_id; + 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, + KZT_GUEST_OBJECT_WRAPPER_READY, + KZT_GUEST_OBJECT_PATCHED, + KZT_GUEST_OBJECT_UNLOADING, + 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, + 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; + 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; + 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; + 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_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; +} 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; + 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 { + 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_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 + +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); + +/* 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); +/* 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, + 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. */ +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); + +/* 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); + +/* 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 + * 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, + 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); + +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. */ +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); + +/* 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, + 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); +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_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_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_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_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_production.h b/target/i386/latx/include/kzt_jump_slot_production.h new file mode 100644 index 00000000000..94bf9c1e835 --- /dev/null +++ b/target/i386/latx/include/kzt_jump_slot_production.h @@ -0,0 +1,98 @@ +#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_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); + +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( + 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); + +/* 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, + 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); + +/* 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); + +#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..c9b29ca2465 --- /dev/null +++ b/target/i386/latx/include/kzt_jump_slot_route.h @@ -0,0 +1,110 @@ +#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_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_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); +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); + 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); + 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; + 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; + +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); + +#endif 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..45957dbacc5 --- /dev/null +++ b/target/i386/latx/include/kzt_lazy_direct_route.h @@ -0,0 +1,145 @@ +#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_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 { + 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_DLERROR_PREBIND_REQUIRED, + 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_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, +} 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; + int allow_budget_transient_native; +} 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; + int transient_safe; +} 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/kzt_lazy_prebind_scope.h b/target/i386/latx/include/kzt_lazy_prebind_scope.h new file mode 100644 index 00000000000..f80aa59b041 --- /dev/null +++ b/target/i386/latx/include/kzt_lazy_prebind_scope.h @@ -0,0 +1,125 @@ +#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; + /* 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]; + 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); + +/* 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); + +/* `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); +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 + * 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 new file mode 100644 index 00000000000..9ff8480a8bb --- /dev/null +++ b/target/i386/latx/include/kzt_loader_callback_scope.h @@ -0,0 +1,14 @@ +#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; + 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..4fc76c0808d --- /dev/null +++ b/target/i386/latx/include/kzt_loader_event_hook.h @@ -0,0 +1,153 @@ +#ifndef KZT_LOADER_EVENT_HOOK_H +#define KZT_LOADER_EVENT_HOOK_H + +#include +#include + +#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_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, + 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; + 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; +} 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 int (*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_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, + 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); +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); + +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); +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); + +#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/target/i386/latx/include/kzt_observation_adapter.h b/target/i386/latx/include/kzt_observation_adapter.h new file mode 100644 index 00000000000..7da6e635169 --- /dev/null +++ b/target/i386/latx/include/kzt_observation_adapter.h @@ -0,0 +1,105 @@ +#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_lazy_prebind_scope.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_PER_OBJECT_FAILED, +} kzt_observation_adapter_result_t; + +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; + 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; + 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. */ + int namespace_id_present; + uintptr_t namespace_id; + 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; + +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..28d0f66cb45 --- /dev/null +++ b/target/i386/latx/include/kzt_patch_planner.h @@ -0,0 +1,241 @@ +#ifndef KZT_PATCH_PLANNER_H +#define KZT_PATCH_PLANNER_H + +#include +#include +#include + +#include "kzt_xcb_route_policy.h" + +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); + +static inline int kzt_patch_symbol_must_stay_guest( + const char *symbol_name) +{ + 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); +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..3acf19841a6 --- /dev/null +++ b/target/i386/latx/include/kzt_patch_spike_guard.h @@ -0,0 +1,167 @@ +#ifndef KZT_PATCH_SPIKE_GUARD_H +#define KZT_PATCH_SPIKE_GUARD_H + +#include +#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_ROLLBACK_COMPLETE, + KZT_PATCH_SPIKE_ACTION_TRANSACTION_UNRECOVERABLE, +} 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_ROLLED_BACK, + KZT_PATCH_SPIKE_RESULT_UNRECOVERABLE, +} 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_TRANSACTION_UNRECOVERABLE, +} 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 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 { + 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); +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); +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); +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); + +#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..8f4891514ca --- /dev/null +++ b/target/i386/latx/include/kzt_patch_spike_writer.h @@ -0,0 +1,118 @@ +#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 mmap_lock_held; + int restore_attempted; + unsigned int restore_attempts; + 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; + unsigned int permission_restore_attempts; + 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_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 new file mode 100644 index 00000000000..33cdd691885 --- /dev/null +++ b/target/i386/latx/include/kzt_plt_resolver_adapter.h @@ -0,0 +1,57 @@ +#ifndef KZT_PLT_RESOLVER_ADAPTER_H +#define KZT_PLT_RESOLVER_ADAPTER_H + +#include +#include + +#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 { + uintptr_t source_link_map; + 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); + 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; +} 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); + +#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..0c215e27ce4 --- /dev/null +++ b/target/i386/latx/include/kzt_rela_immediate_candidate.h @@ -0,0 +1,88 @@ +#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..fbb7ba6b9dc --- /dev/null +++ b/target/i386/latx/include/kzt_rela_request_enricher.h @@ -0,0 +1,56 @@ +#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; + +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, + 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..2aa330c0c12 --- /dev/null +++ b/target/i386/latx/include/kzt_rela_runtime_bridge.h @@ -0,0 +1,74 @@ +#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); + +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, + const char *symbol_name, + kzt_symbol_version_evidence_t 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( + 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 new file mode 100644 index 00000000000..a8480ac6a01 --- /dev/null +++ b/target/i386/latx/include/kzt_rela_stub_detector.h @@ -0,0 +1,39 @@ +#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; + +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_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..7e8ed8dd0e8 --- /dev/null +++ b/target/i386/latx/include/kzt_wrapper_bridge_provider.h @@ -0,0 +1,77 @@ +#ifndef KZT_WRAPPER_BRIDGE_PROVIDER_H +#define KZT_WRAPPER_BRIDGE_PROVIDER_H + +#include +#include + +#include "bridge_private.h" +#include "kzt_guest_library_binding.h" +#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; + 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; + +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/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/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/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..649ffe76606 100644 --- a/target/i386/latx/include/myalign.h +++ b/target/i386/latx/include/myalign.h @@ -148,17 +148,33 @@ 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, 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); +elfheader_t* tryLoadElfFromFileForContext( + box64context_t *context, const char *name); +void freeElfFromFile(elfheader_t **header); #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/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/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/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 cf711e49b0b..e984e9af7d3 100644 --- a/target/i386/latx/translator/tr-misc.c +++ b/target/i386/latx/translator/tr-misc.c @@ -93,13 +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 void* x86free; -extern void* x86realloc; +extern char *my_dlerror(void); +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(); @@ -119,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()); @@ -132,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 */ @@ -151,55 +193,131 @@ 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); + 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); + + 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); + 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(); +} + 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(); } @@ -207,12 +325,18 @@ 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) { 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/guest_e2e/build_guest_probe.sh b/tests/unit/kzt/guest_e2e/build_guest_probe.sh new file mode 100755 index 00000000000..56d1f934d7b --- /dev/null +++ b/tests/unit/kzt/guest_e2e/build_guest_probe.sh @@ -0,0 +1,312 @@ +#!/usr/bin/env bash +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} +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 -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 + echo "Guest main is not an x86-64 ELF." >&2 + 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") +e2e_jump_slots=$(grep -Ec \ + "R_X86_64_(JUMP_SLOT|JUMP_SLO).*${e2e_symbol}(@|[[:space:]])" \ + <<< "$relocations" || true) +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") +e2e_versions=$(sed -n \ + "s/.* UND ${e2e_symbol}@\\(GLIBC_[^[:space:]]*\\).*/\\1/p" \ + <<< "$symbols" | sort -u) +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' "$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 + echo "Guest DSO must use lazy binding." >&2 + 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 + echo "Guest main is not linked to the probe DSO." >&2 + 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 '%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 new file mode 100644 index 00000000000..a24f7a51bfe --- /dev/null +++ b/tests/unit/kzt/guest_e2e/kzt_guest_main.c @@ -0,0 +1,95 @@ +#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; + + __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; + +#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); + } + 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_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 new file mode 100644 index 00000000000..73d834cf335 --- /dev/null +++ b/tests/unit/kzt/guest_e2e/kzt_guest_probe.c @@ -0,0 +1,74 @@ +#include "kzt_guest_probe.h" + +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); +} + +int kzt_guest_probe(struct kzt_guest_probe_result *result) +{ + uintptr_t *slot; + uint64_t before_call; + uint64_t after_call; + + 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 || + ((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 || dlerror() != 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/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/kzt_test_options.c b/tests/unit/kzt/kzt_test_options.c new file mode 100644 index 00000000000..d94c19dac4a --- /dev/null +++ b/tests/unit/kzt/kzt_test_options.c @@ -0,0 +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/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/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_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_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_dl_api.c b/tests/unit/kzt/test_guest_dl_api.c new file mode 100644 index 00000000000..3e4b56c3959 --- /dev/null +++ b/tests/unit/kzt/test_guest_dl_api.c @@ -0,0 +1,2495 @@ +#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_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" +#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 *tryLoadElfFromFileForContext( + box64context_t *context, const char *name); +void freeElfFromFile(elfheader_t **header); + +#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 uintptr_t seen_dlsym_function; +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 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; +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_path_calls; +static int default_entry_header_frees; +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; +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; + 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), 0); + 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_begin_teardown(race->dl); + __atomic_store_n(&entry_destroy_done, 1, __ATOMIC_RELEASE); + return NULL; +} + +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 + context_index * 2); + } + CHECK("default resolver requests libdl", strcmp(name, "libdl.so.2") == 0); + ++default_entry_libdl_calls; + return (elfheader_t *)(uintptr_t)(2 + context_index * 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) +{ + 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 == 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 == 8) { + continue; + } + if (!resolved[i]) { + resolved[i] = (void *)(base + i * 0x10); + ++*resolved_count; + } + } + if (is_libc && 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) +{ + 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_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_context_path_present[context_index]; +} + +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_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; +} + +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 (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; + } + 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; +} + +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); + 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_dlsym_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; + selector_identity_required = -1; + 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; + runtime_dlsym_error_model = 0; + runtime_dlsym_error_pending = 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 }; + 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 }, + }; + 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, + }; + + 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; + 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), 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), 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(); + 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; + 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); + 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; + 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); + CHECK("opaque low dlsym handle preserved", + 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); + 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; + 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); + 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); + 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_path_calls = 0; + default_entry_header_frees = 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; + 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 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", + 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_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_path_calls = 0; + default_entry_header_frees = 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( + &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_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); + 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"); + 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_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..c0ca4ceb0ad --- /dev/null +++ b/tests/unit/kzt/test_guest_dynamic_parser.c @@ -0,0 +1,527 @@ +#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 = 0x6fffc60000 }, + { .d_tag = DT_VERNEEDNUM, .d_un.d_val = 2 }, + { .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 }, + { .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_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[] = { + { .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("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(); + } + 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_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_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 new file mode 100644 index 00000000000..3c88b1a4d7c --- /dev/null +++ b/tests/unit/kzt/test_guest_library_adapter.c @@ -0,0 +1,1268 @@ +#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/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" + +struct kzt_guest_library_bindings { + int unused; +}; + +int relocation_log; +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; +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 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 exact_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; +static kzt_symbol_version_evidence_t expected_dynsym_version_evidence; +static const char *expected_dynsym_version; +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 { \ + 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_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, + 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 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; + result->type = dynsym_type; + result->visibility = STV_DEFAULT; + result->runtime_address = dynsym_runtime_address; + return result->status; +} + +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) +{ + CHECK("selector context", context != NULL); + CHECK("selector retained binding", + retained_provider_handle && retained_provider_handle->entry && + retained_provider_handle->library == lookup_library); + CHECK("selector symbol", strcmp(symbol_name, "wi963_symbol") == 0); + CHECK("selector version evidence", + version_evidence == expected_dynsym_version_evidence); + 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, 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; +} + +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; +} + +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("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( + 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_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; + exact_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; + expected_dynsym_version_evidence = + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + expected_dynsym_version = NULL; + 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) +{ + 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_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 }; + 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 && 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); + exact_selector_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); + exact_selector_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); + expected_dynsym_version_evidence = KZT_SYMBOL_VERSION_VERSIONED; + expected_dynsym_version = "WI982_1.0"; + 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 && exact_selector_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"; + 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", + 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"; + 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", + exact_selector_count == 1 && 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("empty dlvsym version skips unversioned selection", + symbol_source_acquire_count == 0 && lookup_count == 0 && + dynsym_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(); + 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 new file mode 100644 index 00000000000..d258b315909 --- /dev/null +++ b/tests/unit/kzt/test_guest_library_binding.c @@ -0,0 +1,2755 @@ +#include +#include +#include +#include +#include +#include + +#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) +{ + 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 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); + 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); +} + +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_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 }; + 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_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 }; + 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); +} + +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; + 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 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; + 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); +} + +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; + uintptr_t bridge = 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, &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, &bridge) != 0 && + !bridge); + CHECK("symbol cache first hit remains revision local", + kzt_guest_library_symbol_evidence_lookup( + &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( + 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, &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(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(); + 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(); + 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_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(); + 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_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(); + 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..f522f666c71 --- /dev/null +++ b/tests/unit/kzt/test_guest_link_map_reader.c @@ -0,0 +1,810 @@ +#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.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", + 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_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 }; + 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_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(); + + 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..7f85197c543 --- /dev/null +++ b/tests/unit/kzt/test_guest_registry.c @@ -0,0 +1,2009 @@ +#include +#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.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); + 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-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, + "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; + kzt_guest_registry_lazy_source_t source; + 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); + 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", + 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); + 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); + + 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, published.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); + { + 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); +} + +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); +} + +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(); + 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(); + 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_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); + 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..19e004711c0 --- /dev/null +++ b/tests/unit/kzt/test_guest_registry_concurrency.c @@ -0,0 +1,675 @@ +#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); +} + +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", + 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..e76c7fb03bc --- /dev/null +++ b/tests/unit/kzt/test_guest_registry_context.c @@ -0,0 +1,385 @@ +#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)); + 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, + 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_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_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 new file mode 100644 index 00000000000..7e283472826 --- /dev/null +++ b/tests/unit/kzt/test_jump_slot_route.c @@ -0,0 +1,842 @@ +#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 load_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 ? resolved_provider : + (library_t *)&f->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; + ++f->load_calls; + 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_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_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; + + CHECK("target-mismatch.call", + kzt_jump_slot_route_apply(&input, &ops, &result) == 0); + CHECK("target-mismatch.status", + 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) +{ + 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_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); + 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.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_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); + 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_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); +} + +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_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(); + 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_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); +} + +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; + 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_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_provider_failure_declines_without_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; + 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_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) +{ + 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_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", + 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); + 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_decline_without_write(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_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); + } +} + +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); +} + +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_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_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_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_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; + } + puts("KZT shared jump-slot route: PASS"); + return 0; +} 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..ea84392f02e --- /dev/null +++ b/tests/unit/kzt/test_lazy_prebind_scope.c @@ -0,0 +1,389 @@ +#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 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); + 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); +} + +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(); + 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(); + 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 new file mode 100644 index 00000000000..77764e914bf --- /dev/null +++ b/tests/unit/kzt/test_lifecycle_snapshot_capacity.c @@ -0,0 +1,346 @@ +#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 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, + }; + + 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) +{ + 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..ee630e48ea9 --- /dev/null +++ b/tests/unit/kzt/test_loader_event_hook.c @@ -0,0 +1,441 @@ +#include "kzt_loader_event_hook.h" + +#include +#include +#include + +#include "box64context.h" + +#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); +} + +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( + uintptr_t link_map_addr, + kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + 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]; + return 0; + } + } + return -1; +} + +static int publish_lifecycle_unload( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + lifecycle_fixture_t *fixture = opaque; + + fixture->unloaded = *identity; + ++fixture->unload_calls; + return fixture->fail_unload ? -1 : 0; +} + +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 fixture->fail_prepare ? -1 : 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 fixture->fail_cancel ? -1 : 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_lifecycle_healthy(&hook)); + 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(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, + 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_lifecycle_healthy(&hook)); + 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 int publish_allocation_unload( + const kzt_loader_lifecycle_identity_t *identity, + void *opaque) +{ + lifecycle_allocation_fixture_t *fixture = opaque; + + CHECK(identity != NULL); + ++fixture->unload_calls; + return 0; +} + +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); + 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, + 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); + 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 new file mode 100644 index 00000000000..29747d4bc12 --- /dev/null +++ b/tests/unit/kzt/test_observation_adapter.c @@ -0,0 +1,2646 @@ +#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; + int reader_calls_at_legacy; + 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]; + 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; + 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 { + 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; + 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 = + kzt_guest_library_loader_scope_note_pair( + event->loader_scope, link_map_addr, + 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 && + !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 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) +{ + 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 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 }; + 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_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; + 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); +} + +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(); + 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_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(); + 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); + 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..b074e6f48dc --- /dev/null +++ b/tests/unit/kzt/test_owner_resolver.c @@ -0,0 +1,363 @@ +#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); +} + +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(); + 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(); + test_unique_owner_resolution_does_not_need_snapshot_allocation(); + + 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..62fb7b9ba96 --- /dev/null +++ b/tests/unit/kzt/test_patch_planner.c @@ -0,0 +1,606 @@ +#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 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); + + 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) +{ + 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 (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); + 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..ebaea1be439 --- /dev/null +++ b/tests/unit/kzt/test_patch_spike_guard.c @@ -0,0 +1,535 @@ +#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_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(); + 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_UNRECOVERABLE); + check_int("rollback-failure.failure", outcome.failure, + 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", + 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_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(); + 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..192cd02a291 --- /dev/null +++ b/tests/unit/kzt/test_patch_spike_writer.c @@ -0,0 +1,956 @@ +#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_permission_end_once; + 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_once) { + slot->fail_permission_end_once = 0; + return -1; + } + 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_UNRECOVERABLE); + check_int("rollback-fail.failure", record.failure, + 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", + 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_rolls_back_and_recovers(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_once = 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_ROLLED_BACK); + check_int("permission-restore.failure", record.failure, + KZT_PATCH_SPIKE_FAILURE_PERMISSION_RESTORE_FAILED); + 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.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), 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); + 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_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); + 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_APPLIED); + check_int("glob-dat.failure", record.failure, + 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) +{ + 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_UNRECOVERABLE); + check_int("persistent-circuit.first-failure", first_record.failure, + KZT_PATCH_SPIKE_FAILURE_TRANSACTION_UNRECOVERABLE); + 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_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(); + 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(); + + 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_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_real_guest_e2e.py b/tests/unit/kzt/test_real_guest_e2e.py new file mode 100644 index 00000000000..9e7542a38e6 --- /dev/null +++ b/tests/unit/kzt/test_real_guest_e2e.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +import argparse +from pathlib import Path +import subprocess +import sys + +import test_wi849_real_guest_preemption as preemption + + +SCENARIOS = ( + { + **preemption.SCENARIOS[0], + "name": "wi601-direct", + }, + { + **preemption.SCENARIOS[1], + "name": "wi601-guest-handoff", + }, +) + + +def run_scenario(args, scenario): + return preemption.run_scenario(args, scenario) + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Run the WI-601 real guest direct and guest-handoff gate." + ) + parser.add_argument( + "--latx", required=True, type=preemption.existing_file + ) + parser.add_argument( + "--guest-root", required=True, type=preemption.existing_directory + ) + parser.add_argument( + "--fixture-dir", required=True, type=preemption.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 / "wi601-real-guest-logs" + ) + args.log_dir.mkdir(parents=True, exist_ok=True) + 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() + 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 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 new file mode 100644 index 00000000000..11cf14fbefe --- /dev/null +++ b/tests/unit/kzt/test_real_guest_performance.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +import argparse +import json +import math +from pathlib import Path +import sys + +from real_guest_harness import ( + BASELINE_BINDING_STATES, + EAGER_FINAL, + GateResult, + HarnessConfig, + run_performance_gate, +) + + +DEFAULT_SEED = 20260721 + + +def absolute_path(value): + return Path(value).expanduser().resolve() + + +def parse_args(): + parser = argparse.ArgumentParser( + description=( + "Compare legacy and candidate release LATX KZT performance." + ) + ) + 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.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 + + +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({ + "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: + 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_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..183c9f389f9 --- /dev/null +++ b/tests/unit/kzt/test_rela_immediate_candidate.c @@ -0,0 +1,619 @@ +#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_TRANSACTION_UNRECOVERABLE); + check_int("wi231.rollback_fail.rollback", + route.record.rollback_called, 1); + 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); +} + +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..6eaae007077 --- /dev/null +++ b/tests/unit/kzt/test_rela_request_enricher.c @@ -0,0 +1,965 @@ +#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_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; + 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_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; + 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); +} + +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(); + 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(); + test_malformed_wrapper_evidence_does_not_fail_base_enrichment(); + + 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..dff0c9885fd --- /dev/null +++ b/tests/unit/kzt/test_rela_runtime_helper_chain.c @@ -0,0 +1,760 @@ +#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; + 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 { + 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 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) +{ + return 1; +} + +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; +} + +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; +} 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); +} + +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", + 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_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..240b08af7ab --- /dev/null +++ b/tests/unit/kzt/test_wi1009_lazy_slot_bridge_context_contract.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + +import pathlib +import sys + + +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") +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") + +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 new file mode 100644 index 00000000000..580a90a129c --- /dev/null +++ b/tests/unit/kzt/test_wi1021_dlerror_entry_source_contract.py @@ -0,0 +1,160 @@ +#!/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}") + +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(" +) +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] +for required in ( + ".write_slot = production_lazy_direct_guard_write", + "kzt_patch_spike_writer_try_apply_with_slot_ops(", +): + if required not in cas: + fail(f"production lazy CAS bypasses the global guard via {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(", + "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", + "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 || 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 ( + 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_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: + 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_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_wi1065_loader_hook_contract.py b/tests/unit/kzt/test_wi1065_loader_hook_contract.py new file mode 100644 index 00000000000..72c5c35e24f --- /dev/null +++ b/tests/unit/kzt/test_wi1065_loader_hook_contract.py @@ -0,0 +1,78 @@ +#!/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" +HOOK_HEADER = ROOT / "target/i386/latx/include/kzt_loader_event_hook.h" + + +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") +hook_header = HOOK_HEADER.read_text(encoding="utf-8") +myalign = MYALIGN.read_text(encoding="utf-8") + +for required in ( + "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}") +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 ( + "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_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_wi1066_lazy_prebind_source_contract.py b/tests/unit/kzt/test_wi1066_lazy_prebind_source_contract.py new file mode 100644 index 00000000000..4917de4af45 --- /dev/null +++ b/tests/unit/kzt/test_wi1066_lazy_prebind_source_contract.py @@ -0,0 +1,181 @@ +#!/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(" +) +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(", + "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_mandatory_slot_transaction(", + "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..8cc481b67ac --- /dev/null +++ b/tests/unit/kzt/test_wi1083_legacy_callback_removal_source_contract.py @@ -0,0 +1,221 @@ +#!/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)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(", + "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_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_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_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..aeb14d219aa --- /dev/null +++ b/tests/unit/kzt/test_wi1098_thread_local_dl_state_source_contract.py @@ -0,0 +1,143 @@ +#!/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_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: + 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 || 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 ( + 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_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)", + "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..e5a3b5b8294 --- /dev/null +++ b/tests/unit/kzt/test_wi1099_symbol_type_source_contract.py @@ -0,0 +1,98 @@ +#!/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") +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_with_identity(" +) + +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_select_exact_wrapper_bridge_retained", + ) +} +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"] +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] < 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_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 shared_selector: + raise AssertionError(f"shared symbol proof is incomplete: {token}") + +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}") + +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_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_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_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_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_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..3b5ac0c9bdc --- /dev/null +++ b/tests/unit/kzt/test_wi237_jump_slot_route_contract.py @@ -0,0 +1,109 @@ +#!/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 eager.count("uintptr_t slot_observation = (uintptr_t)(*p);") == 1 +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 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 + +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", +] + +# 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 "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 +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 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) +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 + +print("WI-237 eager route and lazy guest-handoff 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..8636f85d783 --- /dev/null +++ b/tests/unit/kzt/test_wi254_loader_contract.py @@ -0,0 +1,136 @@ +#!/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") +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"): + 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 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 "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)): + 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. +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 + +# 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( + 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_production_bridge.c b/tests/unit/kzt/test_wi256_lazy_production_bridge.c new file mode 100644 index 00000000000..07be7042c7c --- /dev/null +++ b/tests/unit/kzt/test_wi256_lazy_production_bridge.c @@ -0,0 +1,2280 @@ +#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_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" +#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 ALIAS_PROVIDER_LINK_MAP 0x3000 +#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; +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) + +#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_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_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; + 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; + 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_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; + 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_VIEW_CHANGE_BEFORE_DECISION_ACQUIRE, + HOOK_SLOT_CONFLICT_BEFORE_FINAL_LOAD, +} hook_mode_t; + +static fixture_t *hook_fixture; +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 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; +static int fail_permission_end_once; +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_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; +} + +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); +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); +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; +} + +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; +} + +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) +{ + 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_name; +} + +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_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->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->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 }; + kzt_patch_spike_guard_init(&fixture->context.kzt_patch_spike_guard, + &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[] = + "\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; + + 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 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; + 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); + 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_lazy_prebind_scope) { + 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->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); + 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, + 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, + .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.pltgot = runtime_field((uintptr_t)&fixture->slot); + fixture->dynamic_view.syment = scalar_field(sizeof(fixture->sym)); + 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; + 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 = 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 = 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_key.link_map_addr, &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.latx_type = LATX_ELF_TYPE_EMUED; + 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.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->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); + } + 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); +} + + +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->source.symbol, + fixture->source.version_evidence, fixture->source.version, result); +} + +static void fixture_set_unresolved_slot(fixture_t *fixture) +{ + fixture->slot = fixture->source.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->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->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, + 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 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; + 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; + shadow_run_calls = 0; + recycled_generation = 0; + permission_begin_calls = 0; + permission_end_calls = 0; + fail_permission_begin = 0; + fail_permission_end = 0; + fail_permission_end_once = 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_active = 0; +} + +void kzt_jump_slot_production_test_mapping_lock(void) +{ + CHECK("mapping-lock.acquire", + pthread_mutex_lock(&mapping_transaction_lock) == 0); + 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; + CHECK("mapping-lock.release", + pthread_mutex_unlock(&mapping_transaction_lock) == 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; + 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; + 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; + decision_lease_held_at_permission_end |= decision_lease_active; + if (fail_permission_end_once) { + fail_permission_end_once = 0; + return -1; + } + 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->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_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 && + decision_lease_active && hook_fixture) { + hook_fixture->slot ^= 0x80; + hook_mode = HOOK_NONE; + } + ++slot_load_calls; +} + +void kzt_jump_slot_production_test_after_slot_load(uintptr_t *value) +{ + (void)value; +} + +void kzt_jump_slot_production_test_after_slot_cas(int exchanged) +{ + (void)exchanged; + ++after_cas_calls; + decision_lease_held_at_cas |= decision_lease_active; +} + +void kzt_jump_slot_production_test_shadow_run(void) +{ + ++shadow_run_calls; +} + +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) +{ + if (!hook_fixture) { + return; + } + ++generation_validate_calls; + decision_lease_held_at_validate |= decision_lease_active; +} + +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->source.generation, + &changed_view) == KZT_GUEST_REGISTRY_UPDATED); + hook_mode = HOOK_NONE; + } +} + +void kzt_jump_slot_production_test_after_patch_decision_lease_acquire(void) +{ + decision_lease_active = 1; + ++decision_lease_acquires; +} + +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); + decision_lease_active = 0; + ++decision_lease_releases; +} + + +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.source.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.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_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); +} + +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_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); +} + +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; + 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.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.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.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); + 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.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; + 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.declined-without-write", + 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); + 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->source.version_evidence = + KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED; + 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->source.generation, + &fixture->dynamic_view) != KZT_GUEST_REGISTRY_ERROR); +} + +static void test_confirmed_unversioned_production_paths_apply(void) +{ + fixture_t fixture; + kzt_lazy_direct_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); + fixture_set_unresolved_slot(&fixture); + hooks_reset(&fixture, HOOK_NONE); + CHECK("unversioned-lazy.route", + fixture_lazy_direct_route(&fixture, &lazy_result) == 0); + CHECK("unversioned-lazy.applied", + lazy_result.status == KZT_LAZY_DIRECT_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.declined", + result.status == KZT_JUMP_SLOT_ROUTE_BYPASS && + result.legacy_fallback_attempted == 0 && + fixture.slot == GUEST_TARGET); + fixture_destroy(&fixture); +} + + + +static void test_eager_transaction_rolls_back_to_zero(void) +{ + fixture_t fixture; + 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; + 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_guest_relocation_ignores_optional_patch_gate(void) +{ + fixture_t fixture; + 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); + 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); + + hooks_reset(&fixture, HOOK_NONE); + 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_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_direct_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; + fixture_set_unresolved_slot(&fixture); + hooks_reset(&fixture, HOOK_SLOT_CONFLICT_BEFORE_FINAL_LOAD); + 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; + 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_direct_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; + fixture_set_unresolved_slot(&fixture); + hooks_reset(&fixture, HOOK_NONE); + CHECK("bridge.lazy-route", + fixture_lazy_direct_route(&fixture, &lazy_result) == 0); + CHECK("bridge.lazy-first-create", + lazy_result.status == KZT_LAZY_DIRECT_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 int finish_test_run(const char *pass_message) +{ + if (failures) { + fprintf(stderr, "%d lazy production lease checks failed\n", failures); + return 1; + } + puts(pass_message); + return 0; +} + +int main(int argc, char **argv) +{ + if (argc != 1) { + fprintf(stderr, "this test does not accept selectors\n"); + return 2; + } + test_active_loader_scope_forces_guest_fallback(); + test_active_loader_scope_rejects_cached_scope_proof(); + 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_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_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(); + + 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 new file mode 100644 index 00000000000..eaf7d82be32 --- /dev/null +++ b/tests/unit/kzt/test_wi256_plt_resolver_adapter.c @@ -0,0 +1,234 @@ +#include +#include + +#include "target/i386/latx/include/kzt_plt_resolver_adapter.h" + +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; + int source_present; + int lookup_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 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->source_present = 1; + 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) { + .source_link_map = fixture->self_link_map, + .guest_resolver = fixture->object_guest_resolver, + }; + 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, + .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); +} + +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_handoff_preserves_exact_return_address(void) +{ + fixture_t fixture; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + + 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_each_object_uses_its_own_guest_resolver(void) +{ + fixture_t fixture; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + + 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 == fixture.object_guest_resolver); + check_guest_frame("object.stack-pointer", &fixture); +} + +static void test_missing_source_leaves_intercepted_frame_unchanged(void) +{ + fixture_t fixture; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + + 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-selected-resolver", result.selected_resolver == 0); + check_original_frame("missing.stack-pointer", &fixture); +} + +static void test_invalid_arguments_fail_without_touching_the_frame(void) +{ + fixture_t fixture; + kzt_plt_resolver_runtime_ops_t ops; + kzt_plt_resolver_enter_result_t result; + uintptr_t original_sp; + + 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) +{ + 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_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) { + fprintf(stderr, "%d WI-256 resolver-adapter checks failed\n", + failures); + return 1; + } + 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 new file mode 100644 index 00000000000..28714dab5e1 --- /dev/null +++ b/tests/unit/kzt/test_wi256_plt_resolver_source_contract.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +import pathlib +import sys + + +def fail(message: str) -> None: + raise AssertionError(message) + + +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}") + + +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" + +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", +): + 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(", + "GetGlobalSymbolStartEnd", + "kzt_owner_resolver", + "kzt_wrapper_probe", + "kzt_bridge_exact", + "dl_runtime_resolver", +): + if forbidden in adapter: + fail(f"resolver adapter duplicates native resolution: {forbidden}") + +lookup = function_body( + elfloader, "static int kzt_plt_resolver_lookup_source(" +) +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 in lookup: + fail(f"production lookup uses stale Registry path: {forbidden}") + +resolver = function_body(elfloader, "void PltResolver(void)") +for required in ( + "version == -1 || version < 2", + "KZT_SYMBOL_VERSION_CONFIRMED_UNVERSIONED", + "KZT_SYMBOL_VERSION_VERSIONED", + "KZT_SYMBOL_VERSION_ERROR", +): + if required not in resolver: + fail(f"resolver misses version evidence mapping: {required}") + +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 in ( + "plt_resolver_lookup_host_symbol(", + "getAlternate(", + "KZT_JUMP_SLOT_ROUTE_SLOT_LEGACY_WRITE", + "dl_runtime_resolver", + "KztLazyBindingCompleteResolver", + "kzt_production_lazy_complete(", +): + 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 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 not in resolver[:direct_call]: + fail(f"resolver must read the original frame: {required}") + +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") +for required in ( + "KZT_LAZY_DIRECT_ROUTE_NATIVE_APPLIED", + "KZT_LAZY_DIRECT_ROUTE_NATIVE_TRANSIENT", + "Push64(cpu, direct_result.selected_target);", + "return;", +): + 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") +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(", +): + 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_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 path: {production_hits}") + +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 new file mode 100644 index 00000000000..527bed85257 --- /dev/null +++ b/tests/unit/kzt/test_wi382_registry_observation_source_contract.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +import pathlib +import re +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}") + + +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() +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() + +# 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(" +) +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 +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 +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 +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 +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" +) + +# 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 "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(context, 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. +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_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_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..1f92a8bb350 --- /dev/null +++ b/tests/unit/kzt/test_wi601_jump_slot_single_writer_source_contract.py @@ -0,0 +1,448 @@ +#!/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(") +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": + 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 ( + "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}") +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( + 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 ( + "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") +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*" + 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_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_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..5ba239b7e40 --- /dev/null +++ b/tests/unit/kzt/test_wi602_handle_state_removal_source_contract.py @@ -0,0 +1,76 @@ +#!/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" +) +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: + 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..ce2bc396c64 --- /dev/null +++ b/tests/unit/kzt/test_wi603_callback_replacement_source_contract.py @@ -0,0 +1,143 @@ +#!/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" +) +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: + 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") +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") +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(" +) +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(", + "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}") + +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") +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..0a9adcfd237 --- /dev/null +++ b/tests/unit/kzt/test_wi603_loader_hook_timing_source_contract.py @@ -0,0 +1,97 @@ +#!/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" +) +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 ( + "_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(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 ( + ".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)", +): + 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..e8e418ff9ab --- /dev/null +++ b/tests/unit/kzt/test_wi837_lazy_direct_route.c @@ -0,0 +1,844 @@ +#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 transient_safe; + 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, + .transient_safe = fixture->transient_safe, + }; + 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, + .transient_safe = 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); + + 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) +{ + 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); +} + +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(); + 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(); + 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", + failures); + return 1; + } + puts("WI-837 lazy direct route checks passed"); + return 0; +} 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/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..b8f573983b1 --- /dev/null +++ b/tests/unit/kzt/test_wi982_versioned_wrapper_selection_source_contract.py @@ -0,0 +1,73 @@ +#!/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") +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_with_identity(") +required_selector_tokens = ( + "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 \ + "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 "(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") 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..8b731242315 --- /dev/null +++ b/tests/unit/kzt/test_wi987_guest_relocation_authority_source_contract.py @@ -0,0 +1,165 @@ +#!/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") +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( + 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(") + +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(", + "kzt_rela_runtime_select_exact_wrapper_bridge_retained(", + "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_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*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*\{[\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(" 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: + 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..3e0bddefdeb --- /dev/null +++ b/tests/unit/kzt/test_wi994_dlerror_tail_forward_source_contract.py @@ -0,0 +1,133 @@ +#!/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(", + "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", + "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 || 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 ( + 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_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: + 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") 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..231c809c895 --- /dev/null +++ b/tests/unit/kzt/test_wrapper_bridge_provider.c @@ -0,0 +1,429 @@ +#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; + 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; + 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; + match->guest_fallback_target = lib->guest_fallback_target; + match->guard_kind = lib->guard_kind; + 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; + if (!lib->guarded_absent_from_map) { + 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); +} + +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(); + 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(); + test_guarded_created_bridge_does_not_require_map_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/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/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..bbca18b3577 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -6,3 +6,1356 @@ 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_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'), + 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( + '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_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_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( + '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( + '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_lifecycle_diagnostics.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) +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', + 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'], + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +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_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', + ), + 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) + +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, + 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(), + ], +) + +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( + '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_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( + '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_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', + '-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_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', + '-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_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( + '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', + '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', + '../../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', + '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', + '../../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', + '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', + ), + 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', + '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', + '../../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) + +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: [ + '-DCONFIG_LATX_KZT', + '-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: [ + '-DCONFIG_LATX_KZT', + '-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-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, + 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_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_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_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', + '../../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_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: [ + '-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, + timeout: 120) + +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', + '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', + '../../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)