Skip to content

Commit e82ce16

Browse files
committed
fix(native): preserve reassemble preload diagnostics
1 parent 50c5e1b commit e82ce16

2 files changed

Lines changed: 47 additions & 7 deletions

File tree

core-engine/src/main/native/js_vm_resource.c

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ static void js_vm_set_prepare_stage(const char *s) {
4444
js_vm_last_prepare_stage[n] = 0;
4545
}
4646

47+
static int js_vm_prepare_stage_has_prefix(const char *prefix) {
48+
if (!prefix) return 0;
49+
size_t n = strlen(prefix);
50+
return n > 0 && strncmp(js_vm_last_prepare_stage, prefix, n) == 0;
51+
}
52+
4753
JS_HIDDEN unsigned char* js_vbc4_zstd_decompress_owned(const unsigned char *stored, uint32_t stored_len, uint32_t plain_len) {
4854
if (!stored || stored_len == 0) return NULL;
4955
if (stored_len == plain_len) {
@@ -621,15 +627,15 @@ JS_PROTECTED static int js_manifest_order_token(const char *mesh, uint32_t ordin
621627
JS_HIDDEN unsigned char* js_vm_reassemble_sliced_resource(JNIEnv *env, jclass helper_cls, unsigned char *decoded, int decoded_len, int *out_len) {
622628
if (!decoded || decoded_len < 10 || !out_len || memcmp(decoded, "VBC4S|1|", 8) != 0) return decoded;
623629
char *manifest = (char*)calloc((size_t)decoded_len + 1u, 1u);
624-
if (!manifest) return NULL;
630+
if (!manifest) { js_vm_set_prepare_stage("reassemble-manifest-alloc"); return NULL; }
625631
memcpy(manifest, decoded, (size_t)decoded_len);
626632
js_vbc4_wipe_volatile(decoded, (size_t)decoded_len);
627633
free(decoded);
628634
decoded = NULL;
629635

630636
char *line = manifest;
631637
char *next_line = strchr(line, '\n');
632-
if (!next_line) { js_vbc4_wipe_volatile(manifest, (size_t)decoded_len); free(manifest); return NULL; }
638+
if (!next_line) { js_vm_set_prepare_stage("reassemble-manifest-newline"); js_vbc4_wipe_volatile(manifest, (size_t)decoded_len); free(manifest); return NULL; }
633639
*next_line++ = 0;
634640
char *cursor = line;
635641
char *magic = js_next_manifest_field(&cursor);
@@ -649,10 +655,12 @@ JS_HIDDEN unsigned char* js_vm_reassemble_sliced_resource(JNIEnv *env, jclass he
649655
!js_parse_u32_token(entry_count_text, &manifest_entry_count) || manifest_entry_count == 0 ||
650656
manifest_ordinal >= manifest_entry_count || total_len == 0 || shard_count < 2 || shard_count > 16 ||
651657
total_len > 64u * 1024u * 1024u) {
658+
js_vm_set_prepare_stage("reassemble-header");
652659
js_vbc4_wipe_volatile(manifest, (size_t)decoded_len); free(manifest); return NULL;
653660
}
654661
unsigned char mesh_digest_bytes[32];
655662
if (!js_hex32_to_bytes(mesh_text, mesh_digest_bytes)) {
663+
js_vm_set_prepare_stage("reassemble-mesh-digest");
656664
js_vbc4_wipe_volatile(mesh_digest_bytes, sizeof(mesh_digest_bytes));
657665
js_vbc4_wipe_volatile(manifest, (size_t)decoded_len); free(manifest); return NULL;
658666
}
@@ -661,6 +669,7 @@ JS_HIDDEN unsigned char* js_vm_reassemble_sliced_resource(JNIEnv *env, jclass he
661669
unsigned char *seen = (unsigned char*)calloc((size_t)shard_count, 1u);
662670
char (*order_tokens)[17] = (char (*)[17])calloc((size_t)shard_count, sizeof(*order_tokens));
663671
if (!assembled || !seen || !order_tokens) {
672+
js_vm_set_prepare_stage("reassemble-alloc");
664673
if (assembled) free(assembled);
665674
if (seen) free(seen);
666675
if (order_tokens) free(order_tokens);
@@ -691,19 +700,22 @@ JS_HIDDEN unsigned char* js_vm_reassemble_sliced_resource(JNIEnv *env, jclass he
691700
length > total_len - offset || !sha_text || strlen(sha_text) != 64 ||
692701
!js_hex32_to_bytes(sha_text, expected_sha) || !path_text || !path_text[0] ||
693702
!js_hex16_is_valid(mesh_link_text) || !js_hex16_is_valid(peer_link_text)) {
703+
js_vm_set_prepare_stage("reassemble-row");
694704
ok = 0;
695705
} else {
696706
char expected_order_token[17];
697707
if (!js_manifest_mesh_link_matches(mesh_text, manifest_ordinal, index, offset, length, sha_text, path_text, mesh_link_text) ||
698708
!js_manifest_peer_link_matches(mesh_text, manifest_ordinal, manifest_entry_count, index, offset, length, sha_text, path_text, peer_ordinal, peer_link_text) ||
699709
!js_manifest_order_token(mesh_text, manifest_ordinal, index, path_text, sha_text, expected_order_token) ||
700710
(loaded_count > 0 && strcmp(order_tokens[loaded_count - 1], expected_order_token) > 0)) {
711+
js_vm_set_prepare_stage("reassemble-mesh-link");
701712
ok = 0;
702713
}
703714
if (ok) memcpy(order_tokens[loaded_count], expected_order_token, sizeof(expected_order_token));
704715
int shard_len = 0;
705716
unsigned char *shard = js_vm_decode_resource_path_owned(env, helper_cls, path_text, &shard_len);
706717
if (!shard || shard_len != (int)length) {
718+
js_vm_set_prepare_stage("reassemble-shard-load");
707719
ok = 0;
708720
} else {
709721
unsigned char actual_sha[32];
@@ -712,6 +724,7 @@ JS_HIDDEN unsigned char* js_vm_reassemble_sliced_resource(JNIEnv *env, jclass he
712724
js_sha256_update(&sha_ctx, shard, shard_len);
713725
js_sha256_final(&sha_ctx, actual_sha);
714726
if (memcmp(actual_sha, expected_sha, 32) != 0) {
727+
js_vm_set_prepare_stage("reassemble-shard-digest");
715728
ok = 0;
716729
} else {
717730
memcpy(assembled + offset, shard, (size_t)length);
@@ -726,8 +739,8 @@ JS_HIDDEN unsigned char* js_vm_reassemble_sliced_resource(JNIEnv *env, jclass he
726739
}
727740
line = line_end ? line_end + 1 : NULL;
728741
}
729-
for (uint32_t i = 0; ok && i < shard_count; i++) if (!seen[i]) ok = 0;
730-
if (loaded_count != shard_count) ok = 0;
742+
for (uint32_t i = 0; ok && i < shard_count; i++) if (!seen[i]) { js_vm_set_prepare_stage("reassemble-missing-shard"); ok = 0; }
743+
if (loaded_count != shard_count) { js_vm_set_prepare_stage("reassemble-count"); ok = 0; }
731744
js_vbc4_wipe_volatile(order_tokens, (size_t)shard_count * sizeof(*order_tokens));
732745
free(order_tokens);
733746
js_vbc4_wipe_volatile(seen, (size_t)shard_count);
@@ -872,7 +885,7 @@ JS_HIDDEN js_vm_program* js_vm_prepare_resource_program_bound(JNIEnv *env, jclas
872885
}
873886
decoded = js_vm_reassemble_sliced_resource(env, resource_cls, decoded, decoded_len, &decoded_len);
874887
if (!decoded) {
875-
js_vm_set_prepare_stage("reassemble");
888+
if (!js_vm_prepare_stage_has_prefix("reassemble-")) js_vm_set_prepare_stage("reassemble");
876889
rls(env, resourcePath, resource_path);
877890
return NULL;
878891
}

core-engine/src/test/kotlin/io/github/hht0rro/javashroud/NativeHelperHardeningTest.kt

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,8 +1085,8 @@ class NativeHelperHardeningTest {
10851085
assertTrue(
10861086
helperSource.contains("version == RUNTIME_RESOURCE_VERSION") &&
10871087
helperSource.contains("LEGACY_RUNTIME_RESOURCE_VERSION") &&
1088-
helperSource.contains("decodeRuntimeResourceCurrent(raw)") &&
1089-
helperSource.contains("decodeRuntimeResourceLegacy(raw)") &&
1088+
helperSource.contains("decodeRuntimeResourceCurrent(raw, allowCompressed)") &&
1089+
helperSource.contains("decodeRuntimeResourceLegacy(raw, allowCompressed)") &&
10901090
helperSource.contains("throw new IllegalArgumentException(\"unsupported runtime resource envelope\")") &&
10911091
!helperSource.contains("decoded != null ? decoded : raw") &&
10921092
helperSource.contains("constantTimeEquals(expected, raw, tagOffset)") &&
@@ -1302,6 +1302,33 @@ class NativeHelperHardeningTest {
13021302
)
13031303
}
13041304
@Test
1305+
fun native_vm_reports_granular_sliced_resource_reassemble_failures() {
1306+
val nativeSource = nativeRuntimeSources()
1307+
for (stage in listOf(
1308+
"reassemble-manifest-alloc",
1309+
"reassemble-manifest-newline",
1310+
"reassemble-header",
1311+
"reassemble-mesh-digest",
1312+
"reassemble-alloc",
1313+
"reassemble-row",
1314+
"reassemble-mesh-link",
1315+
"reassemble-shard-load",
1316+
"reassemble-shard-digest",
1317+
"reassemble-missing-shard",
1318+
"reassemble-count",
1319+
)) {
1320+
assertTrue(
1321+
nativeSource.contains("js_vm_set_prepare_stage(\"$stage\")"),
1322+
"Sliced VBC4 resource reassembly failures must report granular preload stage: $stage",
1323+
)
1324+
}
1325+
assertTrue(
1326+
nativeSource.contains("js_vm_prepare_stage_has_prefix(\"reassemble-\")") &&
1327+
nativeSource.contains("if (!js_vm_prepare_stage_has_prefix(\"reassemble-\")) js_vm_set_prepare_stage(\"reassemble\")"),
1328+
"The caller must preserve granular reassemble-* diagnostics instead of overwriting them with the generic stage.",
1329+
)
1330+
}
1331+
@Test
13051332
fun native_vm_state_bound_resources_derive_keys_from_runtime_context() {
13061333
val nativeSource = nativeRuntimeSources()
13071334
assertTrue(

0 commit comments

Comments
 (0)