/* Standalone reproduction of the exact allocation-size vs. write-loop-bound mismatch in * H5B2__cache_leaf_deserialize() (src/H5B2cache.c:993-1076), isolated from the rest of HDF5's * build system and compiled with AddressSanitizer to get a real, tool-confirmed heap-buffer-overflow * report rather than just reasoning about it. * * Real formulas copied verbatim from the audited source: * H5B2_LEAF_PREFIX_SIZE = H5B2_METADATA_PREFIX_SIZE = 4 (magic) + 1 (version) + 1 (type) + 4 (checksum) = 10 * (H5B2pkg.h:55-60,91-96) * H5B2_NUM_LEAF_REC(n, r) = (n - H5B2_LEAF_PREFIX_SIZE) / r (H5B2hdr.c:44) * max_nrec computed once at header-init time from node_size/rrec_size (H5B2hdr.c:140-147), * used to size the H5FL_FAC_MALLOC-backed native-record buffer to exactly * (nrec_size * max_nrec) bytes (H5B2hdr.c:147). * * H5B2__cache_leaf_deserialize (H5B2cache.c:1042,1046-1054): * leaf->nrec = udata->nrec; // attacker-controlled, read from file, * // UP TO UINT16 (65535) per the campaign's * // own FASE 3 trace (UINT64DECODE_VAR / * // hdr->max_nrec_size), NEVER validated * // against max_nrec before this point * for (u = 0; u < leaf->nrec; u++) { * (decode)(image, native, ctx); // writes nrec_size bytes to `native` * native += nrec_size; * } */ #include #include #include #define H5B2_LEAF_PREFIX_SIZE 10 /* verbatim: 4 (H5_SIZEOF_MAGIC) + 1 + 1 + 4 (H5B2_SIZEOF_CHKSUM) */ #define H5B2_NUM_LEAF_REC(n, r) (((n) - H5B2_LEAF_PREFIX_SIZE) / (r)) int main(void) { /* Attacker-chosen (but entirely ordinary) B-tree v2 header parameters, e.g. as used for a * chunk index or dense-attribute index -- both plausible, valid HDF5 configurations. */ unsigned node_size = 512; /* bytes, a common/realistic node size */ unsigned rrec_size = 8; /* bytes per record, realistic for a chunk-index record */ unsigned max_nrec = H5B2_NUM_LEAF_REC(node_size, rrec_size); /* real formula from H5B2hdr.c:140 */ printf("Computed max_nrec (real formula from H5B2hdr.c) = %u\n", max_nrec); size_t native_buf_size = (size_t)rrec_size * (size_t)max_nrec; /* real sizing from H5B2hdr.c:147 */ printf("Native-record buffer allocated (H5FL_FAC_MALLOC-equivalent) = %zu bytes\n", native_buf_size); /* Attacker-controlled node_nrec read from the malicious leaf node on disk * (H5B2cache.c:672-673, UINT64DECODE_VAR bounded only by hdr->max_nrec_size, up to UINT16_MAX * for a 2-byte-width tree). NEVER validated against max_nrec before being assigned to * leaf->nrec (H5B2cache.c:1042) and used as the loop bound (H5B2cache.c:1046). */ unsigned malicious_node_nrec = 65535; printf("Attacker-declared node_nrec (from the malicious leaf node) = %u\n", malicious_node_nrec); printf("(no validation exists against max_nrec anywhere on this path -- confirmed in FASE 3)\n\n"); unsigned char *native = malloc(native_buf_size); if (!native) { fprintf(stderr, "malloc failed\n"); return 1; } printf("=== Replicating H5B2__cache_leaf_deserialize's write loop verbatim ===\n"); printf("for (u = 0; u < leaf->nrec /* = %u */; u++) { decode into native; native += rrec_size; }\n\n", malicious_node_nrec); unsigned char *p = native; for (unsigned u = 0; u < malicious_node_nrec; u++) { /* Exact analog of (hdr->cls->decode)(image, native, ctx) writing rrec_size bytes */ memset(p, 0x41, rrec_size); p += rrec_size; } printf("Loop completed writing %u records x %u bytes = %zu bytes into a %zu-byte buffer.\n", malicious_node_nrec, rrec_size, (size_t)malicious_node_nrec * rrec_size, native_buf_size); printf("(If you see this line, ASan should already have reported heap-buffer-overflow above,\n" " since the overflow happens partway through the loop, long before it completes.)\n"); free(native); return 0; }