Skip to content

Commit bfac60b

Browse files
committed
fix: Clean up post-redesign technical debt
Four fixes identified by codebase audit: 1. EpochCheckpoint.salt no longer misused for insn_index_in_bb. Added dedicated saved_insn_index field to EpochCheckpoint. CALL_VM/RET_VM handlers updated to use the new field. 2. Extracted enc_state replay from vm_engine.hpp step() into pipeline::replay_enc_state() non-template function. step() reduced from ~40 lines of inline SipHash chain replay to a single function call. Replay logic properly handles REKEY instructions during chain advancement. 3. Removed unused MockEditor class from test_loader.cpp (93 lines of dead code). Replaced stale TODO in vm_runtime_init.cpp with proper documentation of hardcoded section name rationale. 4. Added 11 atomic handler tests: LOCK_ADD (basic + zero + overflow), XCHG (basic + same-value), CMPXCHG (success + failure + combined), ATOMIC_LOAD (basic + zero + different registers). All handlers verified for encode/decode correctness through composition tables. 51 comprehensive tests, 696 total tests passing.
1 parent c40794c commit bfac60b

8 files changed

Lines changed: 374 additions & 146 deletions

File tree

common/include/vm/vm_context.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ struct EpochCheckpoint {
5353
uint64_t vm_ip;
5454
uint32_t bb_id;
5555
uint8_t epoch_seed[32];
56-
uint64_t salt;
56+
uint64_t salt; ///< TRNG salt for epoch derivation
57+
uint32_t saved_insn_index; ///< insn_index_in_bb at CALL_VM time (for resume after RET_VM)
5758
uint64_t encoded_regs_snapshot[16];
5859
};
5960

loader/tests/test_loader.cpp

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -342,99 +342,6 @@ TEST(PayloadBuilder, MultipleRegionsLayout) {
342342
}
343343
}
344344

345-
// ============================================================================
346-
// MockEditor — DI for Loader::patch() tests
347-
// ============================================================================
348-
349-
class MockEditor : public Loader::EditorBase<MockEditor> {
350-
friend class Loader::EditorBase<MockEditor>;
351-
352-
public:
353-
struct Config {
354-
Loader::TextSectionInfo text = {0x1000, 0x2000};
355-
uint64_t next_va = 0x10000;
356-
bool fail_open = false;
357-
bool fail_add_segment = false;
358-
bool fail_overwrite = false;
359-
bool fail_save = false;
360-
};
361-
static Config cfg;
362-
363-
struct Calls {
364-
bool saved = false;
365-
int overwrite_count = 0;
366-
std::vector<uint64_t> overwrite_vas;
367-
};
368-
static Calls calls;
369-
370-
static void reset() { cfg = {}; calls = {}; }
371-
372-
Loader::TextSectionInfo text_section_impl() const noexcept {
373-
return {cfg.text.base_addr, cfg.text.size};
374-
}
375-
376-
uint64_t next_segment_va_impl(uint64_t) const noexcept {
377-
return cfg.next_va;
378-
}
379-
380-
tl::expected<Loader::NewSegmentInfo, DC>
381-
add_segment_impl(std::string_view, const std::vector<uint8_t>& payload,
382-
uint64_t, Common::DiagnosticCollector& diag) noexcept {
383-
if (cfg.fail_add_segment) {
384-
diag.error("mock", DC::PatchSegmentCreationFailed, "fail");
385-
return tl::unexpected(DC::PatchSegmentCreationFailed);
386-
}
387-
return Loader::NewSegmentInfo{cfg.next_va, payload.size()};
388-
}
389-
390-
bool cfi_enforced_impl() const noexcept { return false; }
391-
392-
std::vector<Loader::TextGap>
393-
find_text_gaps_impl(std::size_t) const noexcept { return {}; }
394-
395-
tl::expected<Loader::NewSegmentInfo, DC>
396-
extend_text_impl(const std::vector<uint8_t>& data, uint64_t,
397-
Common::DiagnosticCollector&) noexcept {
398-
return Loader::NewSegmentInfo{cfg.next_va, data.size()};
399-
}
400-
401-
tl::expected<void, DC>
402-
overwrite_text_impl(uint64_t va, const uint8_t*, size_t,
403-
Common::DiagnosticCollector& diag) noexcept {
404-
calls.overwrite_count++;
405-
calls.overwrite_vas.push_back(va);
406-
if (cfg.fail_overwrite) {
407-
diag.error("mock", DC::PatchSegmentCreationFailed, "fail");
408-
return tl::unexpected(DC::PatchSegmentCreationFailed);
409-
}
410-
return {};
411-
}
412-
413-
tl::expected<void, DC>
414-
add_runtime_dep_impl(std::string_view, Common::DiagnosticCollector&) noexcept {
415-
return {};
416-
}
417-
418-
void invalidate_signature_impl() noexcept {}
419-
420-
tl::expected<void, DC>
421-
save_impl(const std::string&, Common::DiagnosticCollector& diag) noexcept {
422-
calls.saved = true;
423-
if (cfg.fail_save) {
424-
diag.error("mock", DC::PatchBinaryWriteFailed, "fail");
425-
return tl::unexpected(DC::PatchBinaryWriteFailed);
426-
}
427-
return {};
428-
}
429-
};
430-
431-
MockEditor::Config MockEditor::cfg;
432-
MockEditor::Calls MockEditor::calls;
433-
434-
// NOTE: Loader::patch() uses open_binary() which creates real editors.
435-
// For MockEditor DI tests, we test PayloadBuilder + StubEmitter directly
436-
// since those are the core logic. Integration tests use real binaries.
437-
438345
// ============================================================================
439346
// DiagnosticCode helpers
440347
// ============================================================================

runtime/include/handler_impls.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ struct HandlerTraits<VmOpcode::CALL_VM, P> {
627627
cp.vm_ip = e.vm_ip;
628628
cp.bb_id = e.current_bb_id;
629629
std::memcpy(cp.epoch_seed, ep.reg.encode[0], 32);
630-
cp.salt = static_cast<uint64_t>(e.insn_index_in_bb); // save insn index for resume
630+
cp.saved_insn_index = e.insn_index_in_bb;
631631
for (int r = 0; r < VM_REG_COUNT; ++r)
632632
cp.encoded_regs_snapshot[r] = e.regs[r].bits;
633633
e.shadow_depth++;
@@ -653,11 +653,11 @@ struct HandlerTraits<VmOpcode::RET_VM, P> {
653653
e.branch_target_bb = cp.bb_id;
654654
e.branch_taken = true;
655655
// Resume AFTER the CALL_VM instruction (cp.vm_ip is CALL's ip,
656-
// cp.salt stores the insn_index_in_bb at CALL time).
656+
// cp.saved_insn_index stores the insn_index_in_bb at CALL time).
657657
// The dispatcher will use these to override enter_basic_block's
658658
// default vm_ip = entry_ip.
659659
e.return_resume_ip = cp.vm_ip + 1;
660-
e.return_resume_insn_idx = static_cast<uint32_t>(cp.salt) + 1;
660+
e.return_resume_insn_idx = cp.saved_insn_index + 1;
661661
return {};
662662
}
663663
};

runtime/include/pipeline.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ verify_bb_mac(const VmImmutable& imm,
8080
const VmExecution& exec,
8181
const VmEpoch& epoch) noexcept;
8282

83+
/// Replay enc_state SipHash chain from BB entry to a specific instruction index.
84+
/// Used by RET_VM to resume execution after CALL_VM.
85+
void replay_enc_state(VmExecution& exec, const VmEpoch& epoch,
86+
const VmImmutable& imm, uint32_t target_insn_idx) noexcept;
87+
8388
/// Get the current BB's instruction count (for boundary detection).
8489
[[nodiscard]] uint32_t current_bb_insn_count(
8590
const VmImmutable& imm,

runtime/include/vm_engine.hpp

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -412,53 +412,7 @@ VmEngine<Policy, Oram>::step() noexcept
412412
if (exec_.return_resume_ip != 0) {
413413
exec_.vm_ip = exec_.return_resume_ip;
414414
exec_.insn_index_in_bb = exec_.return_resume_insn_idx;
415-
// Re-derive enc_state to match the resume position:
416-
// We need to replay the SipHash chain from entry_ip to resume_ip.
417-
// The enc_state was reset by enter_basic_block to bb_enc_seed.
418-
// We replay by re-decrypting instructions [entry..resume) to
419-
// advance enc_state. This is expensive but correct (Performance Never).
420-
uint8_t enc_seed[8];
421-
const auto& bb = imm_->bb_metadata[exec_.current_bb_index];
422-
uint8_t msg[7];
423-
std::memcpy(msg, "enc", 3);
424-
std::memcpy(msg + 3, &bb.bb_id, 4);
425-
Common::VM::Crypto::blake3_keyed_hash(imm_->stored_seed, msg, 7, enc_seed, 8);
426-
uint64_t es = 0;
427-
std::memcpy(&es, enc_seed, 8);
428-
429-
auto insns = imm_->blob.instructions();
430-
for (uint32_t j = 0; j < exec_.return_resume_insn_idx; ++j) {
431-
uint64_t encrypted = 0;
432-
std::memcpy(&encrypted, &insns[bb.entry_ip + j], 8);
433-
uint64_t ks = Common::VM::Crypto::siphash_keystream(
434-
imm_->fast_key, es, j);
435-
uint64_t plain_u64 = encrypted ^ ks;
436-
Common::VM::VmInsn vi{};
437-
std::memcpy(&vi, &plain_u64, 8);
438-
439-
// Check for REKEY
440-
uint8_t enc_alias = static_cast<uint8_t>(vi.opcode & 0xFF);
441-
uint8_t alias = epoch_->opcode_perm_inv[enc_alias];
442-
uint8_t sem = imm_->alias_lut[alias];
443-
if (sem == static_cast<uint8_t>(Common::VM::VmOpcode::REKEY)) {
444-
uint32_t cnt = vi.aux;
445-
uint8_t rk[9]; std::memcpy(rk, "rekey", 5);
446-
std::memcpy(rk+5, &cnt, 4);
447-
uint8_t mat[16];
448-
Common::VM::Crypto::blake3_kdf(imm_->stored_seed,
449-
reinterpret_cast<const char*>(rk), 9, mat, 16);
450-
uint8_t esb[8]; std::memcpy(esb, &es, 8);
451-
es = Common::VM::Crypto::siphash_2_4(mat, esb, 8);
452-
}
453-
454-
uint8_t key16[16] = {};
455-
std::memcpy(key16, &es, 8);
456-
uint8_t msg6[6];
457-
std::memcpy(msg6, &vi.opcode, 2);
458-
std::memcpy(msg6+2, &vi.aux, 4);
459-
es = Common::VM::Crypto::siphash_2_4(key16, msg6, 6);
460-
}
461-
exec_.enc_state = es;
415+
pipeline::replay_enc_state(exec_, *epoch_, *imm_, exec_.return_resume_insn_idx);
462416
exec_.return_resume_ip = 0;
463417
exec_.return_resume_insn_idx = 0;
464418
}

runtime/src/pipeline.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,61 @@ verify_bb_mac(const VmImmutable& imm,
292292
return {};
293293
}
294294

295+
// ─────────────────────────────────────────────────────────────────────────────
296+
// replay_enc_state (RET_VM resume)
297+
// ─────────────────────────────────────────────────────────────────────────────
298+
299+
void replay_enc_state(VmExecution& exec, const VmEpoch& epoch,
300+
const VmImmutable& imm,
301+
uint32_t target_insn_idx) noexcept {
302+
303+
const auto& bb = imm.bb_metadata[exec.current_bb_index];
304+
305+
// Derive bb_enc_seed from scratch (enter_basic_block already set enc_state,
306+
// but we need the seed for the keystream replay).
307+
uint8_t enc_seed_bytes[8];
308+
derive_bb_enc_seed(imm.stored_seed, bb.bb_id, enc_seed_bytes);
309+
310+
uint64_t es = 0;
311+
std::memcpy(&es, enc_seed_bytes, 8);
312+
313+
// Replay SipHash chain: decrypt each instruction [0..target_insn_idx) and
314+
// advance enc_state, handling REKEY mutations along the way.
315+
auto insns = imm.blob.instructions();
316+
for (uint32_t j = 0; j < target_insn_idx; ++j) {
317+
uint64_t encrypted = 0;
318+
std::memcpy(&encrypted, &insns[bb.entry_ip + j], 8);
319+
320+
uint64_t ks = siphash_keystream(imm.fast_key, es, j);
321+
uint64_t plain_u64 = encrypted ^ ks;
322+
323+
VmInsn vi{};
324+
std::memcpy(&vi, &plain_u64, 8);
325+
326+
// Check for REKEY — must replay its enc_state mutation
327+
uint8_t enc_alias = static_cast<uint8_t>(vi.opcode & 0xFF);
328+
uint8_t alias = epoch.opcode_perm_inv[enc_alias];
329+
uint8_t sem = imm.alias_lut[alias];
330+
if (sem == static_cast<uint8_t>(VmOpcode::REKEY)) {
331+
uint32_t cnt = vi.aux;
332+
uint8_t rk_ctx[9];
333+
std::memcpy(rk_ctx, "rekey", 5);
334+
std::memcpy(rk_ctx + 5, &cnt, 4);
335+
uint8_t rk_mat[16];
336+
blake3_kdf(imm.stored_seed,
337+
reinterpret_cast<const char*>(rk_ctx), 9,
338+
rk_mat, 16);
339+
uint8_t esb[8];
340+
std::memcpy(esb, &es, 8);
341+
es = siphash_2_4(rk_mat, esb, 8);
342+
}
343+
344+
es = update_enc_state_impl(es, vi.opcode, vi.aux);
345+
}
346+
347+
exec.enc_state = es;
348+
}
349+
295350
// ─────────────────────────────────────────────────────────────────────────────
296351
// current_bb_insn_count
297352
// ─────────────────────────────────────────────────────────────────────────────

runtime/src/vm_runtime_init.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@
4646
#include <cstdint>
4747
#include <cstring>
4848

49-
// The section name the loader uses (must match PlatformTraits).
50-
// TODO: make this configurable or discover via a magic marker.
49+
// The section name the loader uses — must match FormatConfig::section_name
50+
// in PlatformTraits.hpp. Hardcoded because the runtime is compiled once
51+
// per platform while FormatConfig is a loader-side constexpr; keeping them
52+
// in sync is enforced by integration tests (test_loader + test_extend_text).
5153
#if defined(__APPLE__)
5254
static constexpr const char* VMPILOT_SEGMENT = "__VMPILOT";
5355
#elif defined(_WIN32)

0 commit comments

Comments
 (0)