UAF on kdmsg state in diskiodone: state refcount not held across async I/O
| Field | Value |
|---|---|
| ID | DF-0117 |
| Status | new |
| Severity | High |
| CVSS 3.1 | CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-911 Use After Free |
| File | sys/kern/subr_diskiocom.c |
| Lines | 372-662 |
| Area | kern |
| Confidence | likely |
| Discovered | 2026-06-30 |
| Reported | pending |
Summary
In disk_blk_read/write/flush/freeblks, the kdmsg state pointer is
stored in bio->bio_caller_info1.ptr and dispatched to dev_dstrategy
for async I/O, but no reference is taken on the state β the
kdmsg_state_hold() and matching kdmsg_state_drop() calls are
commented out (:375, :451, :503, :554, :661). If the kdmsg
peer sends a DELETE or the connection drops while I/O is in flight,
the kdmsg core frees the state. When diskiodone fires on I/O
completion, it dereferences the freed state (:580, :582),
producing a use-after-free on a small kmalloc object.
Root cause
All four I/O dispatch sites follow the same pattern (:372-381):
bio->bio_caller_info1.ptr = msg->state;
/* kdmsg_state_hold(msg->state); */ // <-- COMMENTED OUT
...
dev_dstrategy(dp->d_rawdev, bio);
The completion handler (:577-662) dereferences it unconditionally:
kdmsg_state_t *state = bio->bio_caller_info1.ptr; // :580
struct dios_io *iost = state->any.any; // :582 β UAF
...
kdmsg_msg_alloc(state, ...); // :650 β UAF
...
/* kdmsg_state_drop(state); */ // :661 β COMMENTED OUT
The kdmsg core frees states when both rxcmd and txcmd carry
DMSGF_DELETE (kern_dmsg.c:1678-1707). The peer controls when to
send DELETE. If the I/O has not completed, the state is freed while
the bio still holds a raw pointer to it. Connection drop
(kdmsg_state_abort) also walks the state tree and drops all
references.
Threat model & preconditions
- Attacker position: kdmsg cluster peer (authenticated via the
cluster protocol), or local privileged user who issued
DIOCRECLUSTERon a disk device node. - Impact: Use-after-free on
kdmsg_state_t(small kmalloc object). Reliable kernel panic (DoS). Potentially exploitable for kernel code execution via slab grooming β the attacker controls the timing between free and reuse. - Required config: kdmsg/iocom active (HAMMER2 cluster or
DIOCRECLUSTER). Default kernel builds include the code.
Proof of concept
PoC source: findings/poc/DF-0117/
Build & run
# Requires a kdmsg/iocom connection to a target disk. # 1. Establish connection (local: DIOCRECLUSTER with socket fd) # 2. Open a BLK transaction # 3. Send BLK_READ (no DELETE) to start I/O # 4. Before I/O completes, send DELETE on the same state # 5. Wait for diskiodone to fire on the freed state
Expected output
Fatal trap 12: page fault while in kernel mode fault virtual address = 0x... (freed kdmsg_state_t memory) panic: page fault
Impact
Remote/local kernel UAF. In a DragonFly cluster, any authenticated
cluster peer can trigger this. The freed kdmsg_state_t is a small
kmalloc object β its slab is predictable and can be groomed with
attacker-controlled data between the free and the diskiodone
dereference, making code execution plausible.
Recommended fix
Uncomment the hold/drop pair in all four dispatch sites and the completion handler:
--- a/sys/kern/subr_diskiocom.c
+++ b/sys/kern/subr_diskiocom.c
@@ -372,7 +372,7 @@
bio->bio_caller_info1.ptr = msg->state;
- /* kdmsg_state_hold(msg->state); */
+ kdmsg_state_hold(msg->state);
@@ -451,7 +451,7 @@
bio->bio_caller_info1.ptr = msg->state;
- /* kdmsg_state_hold(msg->state); */
+ kdmsg_state_hold(msg->state);
@@ -503,7 +503,7 @@
bio->bio_caller_info1.ptr = msg->state;
- /* kdmsg_state_hold(msg->state); */
+ kdmsg_state_hold(msg->state);
@@ -554,7 +554,7 @@
bio->bio_caller_info1.ptr = msg->state;
- /* kdmsg_state_hold(msg->state); */
+ kdmsg_state_hold(msg->state);
@@ -661,7 +661,7 @@
- /* kdmsg_state_drop(state); */
+ kdmsg_state_drop(state);
Additionally, free iost when its refcount hits zero in the eof path
(see DF-0118), before dropping the state reference.
References
- The commented-out hold/drop is a clear indication the developer knew the reference was needed but disabled it (possibly during debugging).
kdmsg_state_hold/kdmsg_state_dropare defined insys/kern/kern_dmsg.cand manipulatestate->refs.
Timeline
- 2026-06-30 Discovered during automated audit.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0117 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger_race.c | trigger-source | self-contained UAF trigger: DIOCRECLUSTER + BLK_READ|CREATE(MAXPHYS) + immediate socket-close teardown race | 7.0 KB | view raw |
| trigger.c | trigger-source | prior trigger (many separate states, one I/O each β did not reproduce; kept for reference) | 7.6 KB | view raw |
| build.sh | build-script | cc -O2 -o trigger trigger.c -lpthread + race variant | 262 B | view raw |
| run.sh | run-script | kills hammer2, runs trigger_race 131072 0 0 1 | 457 B | view raw |
| fix.diff | suggested-fix | git-apply-able fix: defer DELETE reply to diskiodone when iost->count > 0 (set eof=1 instead of kdmsg_msg_reply) | 3.6 KB | view raw |
| panic.txt | panic-signature | fatal trap / panic: assertion pstate != NULL in kdmsg_msg_alloc from diskiodone+0x120 via vtblk_vq_intr | 752 B | view raw |
| run.log | run-log | baseline run on unpatched #0 kernel β SSH timeout, guest panicked | 1.4 KB | view raw |
| fix_run.log | run-log | patched #1 kernel run β clean exit 0, blk_active 0, guest up, stress tests all pass | 1.3 KB | view raw |
| fix_build.log | build-log | patched kernel build output (NK_DONE rc=0) | 4.9 KB | view raw |
| env.txt | environment | guest uname, cc version, sysctl state | 264 B | view raw |
| VERDICT.md | verdict | full narrative: mechanism trace, prior-verdict gap analysis, fix validation | 8.5 KB | β raw |
| README.md | readme | human-facing build/run/expected summary | 3.4 KB | β raw |
| manifest.json | manifest | this file | 3.8 KB | view raw |
| build.log | build-log | kernel build log excerpt proving -Werror clean compile of patched source | 67 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0117 β PoC (UAF on kdmsg state in diskiodone)
Finding: findings/DF-0117-diskiocom-uaf-kdmsg-state.md
Verdict: REPRODUCED β confirmed by kernel panic on the unpatched #0 kernel.
Fix VALIDATED β the eof=1 patch eliminates the panic.
Summary
The prior "NOT REPRODUCED" verdict had a gap: it missed that the connection-drop
teardown's simulated DELETE triggers disk_blk_read's error path
(subr_diskiocom.c:384-394), which calls kdmsg_msg_reply unconditionally β
even when iost->count > 0 (I/O in flight). That reply drives cleanuptx,
which tears down the state (parent = NULL, removed from rbtree, topology refs
dropped) while the bio still holds a raw state pointer. When the I/O completes,
diskiodone dereferences the cleaned-up state β panic.
Files
| file | purpose |
|---|---|
trigger_race.c |
self-contained trigger: DIOCRECLUSTER + BLK_READ|CREATE(MAXPHYS) + immediate teardown race |
trigger.c |
prior trigger (many separate states, one I/O each β did not reproduce) |
build.sh |
cc -O2 -o trigger trigger.c -lpthread + race variant |
run.sh |
kills hammer2, runs the race trigger |
fix.diff |
git-apply-able fix: defer DELETE reply to diskiodone when I/O in flight |
panic.txt |
kernel panic signature from boot.log (crash proof) |
run.log |
baseline run (unpatched kernel) β SSH timeout, guest panicked |
fix_run.log |
patched kernel run β clean exit, no panic |
fix_build.log |
patched kernel build output |
env.txt |
guest uname, cc version, sysctl state |
VERDICT.md |
full narrative: mechanism trace, prior-verdict gap, fix validation |
manifest.json |
machine-readable catalog |
Build & run (as root on the guest)
# Build (any user)
cc -O2 -o trigger_race trigger_race.c -lpthread
# Run (as root β needs /dev/vbd0 + DIOCRECLUSTER)
pkill -9 -x hammer2 # free the disk iocom from the hammer2 daemon
sleep 1
./trigger_race 131072 0 0 1
# args: iosize nreads preclose_us iters
# iosize=131072 MAXPHYS (128 KB) β slow enough for the teardown to win the race
# nreads=0 no additional mid-stream I/Os
# preclose_us=0 close socket immediately after writing
# iters=1 one iteration (panics on first run if kernel is vulnerable)
Expected output
| Kernel | Result |
|---|---|
Unpatched (#0) |
PANIC: assertion "pstate != NULL" failed in kdmsg_msg_alloc at kern_dmsg.c:1822 from diskiodone+0x120 via vtblk_vq_intr |
Patched (#1, eof=1) |
trigger exits 0, blk_active β 0, guest stays up, no panic |
Fix
The fix (fix.diff) defers the DELETE reply to diskiodone when I/O is still
in flight. In all four disk_blk_* error paths, when iost->count > 0, set
iost->eof = 1 instead of calling kdmsg_msg_reply. The last diskiodone
(when count reaches 0) sends the DELETE reply itself, after all derefs are done.
This keeps the state valid until diskiodone has finished using it.
The finding markdown's proposed fix (uncomment kdmsg_state_hold/drop) does NOT
work: those are file-local static macros in kern_dmsg.c, invisible to
subr_diskiocom.c. Even when exported, the hold/drop alone does NOT prevent
the UAF (confirmed empirically β the hold/drop kernel still panicked) because
the teardown's kdmsg_subq_delete sets parent = NULL regardless of refcount.
DF-0117 β VERDICT
Status: REPRODUCED (UAF on kdmsg_state_t in diskiodone, confirmed by kernel panic). Fix VALIDATED (eof=1 deferral eliminates the UAF on the patched kernel).
One-line
The prior "NOT REPRODUCED" verdict had a gap in its teardown analysis: it
claimed the state's topology refs prevent freeing during diskiodone, but missed
that the connection-drop teardown's simulated DELETE triggers the disk
callback's error path (subr_diskiocom.c:384-394), which calls
kdmsg_msg_reply unconditionally β even when iost->count > 0 (I/O in
flight). That reply drives cleanuptx, which removes the state from the
rbtree, sets parent = NULL, and drops the topology refs β all while the bio
still holds a raw pointer to the state. When the in-flight I/O completes,
diskiodone dereferences the freed/cleaned state β panic.
Reproduction
A single MAXPHYS (128 KB) BLK_READ|CREATE on msgid=1 (eof=0), followed by
immediate socket close (teardown), reproduces the panic reliably on the
unpatched #0 kernel:
panic: assertion "pstate != NULL" failed in kdmsg_msg_alloc at kern_dmsg.c:1822 cpuid = 5 Trace beginning at frame 0xfffff8008ed4c860 kdmsg_msg_alloc() at kdmsg_msg_alloc+0x267 diskiodone() at diskiodone+0x120 vtblk_vq_intr() at vtblk_vq_intr+0xa7 vtpci_msix_intr() at vtpci_msix_intr+0x32 lwkt_serialize_handler_call() at lwkt_serialize_handler_call+0xd3
The 128 KB read is slow enough (1-10 ms on QCOW2-backed virtio) that the
teardown frees the state before the I/O completes. diskiodone (called from
the virtio block interrupt) then reads state->parent on the cleaned-up state
β parent was set to NULL by kdmsg_subq_delete during the teardown's
cleanuptx β triggering KKASSERT(pstate != NULL) in kdmsg_msg_alloc.
Mechanism (trigger β primitive β effect)
1. Peer sends BLK_READ|CREATE msgid=1, eof=0 (no DELETE in the message)
subr_diskiocom.c:341 disk_blk_read()
:360 iost = kmalloc(...)
:365 bp = getpbuf_mem(NULL)
:372 bio->bio_caller_info1.ptr = msg->state β RAW state ptr, no hold
:378 eof = 0 (no DELETE)
:381 dev_dstrategy(dp->d_rawdev, bio) β async I/O dispatched
2. Connection drops (socket close β reader EOF β writer teardown)
kern_dmsg.c:547 teardown loop
:555 kdmsg_simulate_failure(&state0, 0, ...)
β kdmsg_state_abort(state) kern_dmsg.c:1355
:1392 simulated msg cmd = DMSG_LNK_ERROR|DELETE
:1404 kdmsg_msg_receive_handling(msg)
β kdmsg_state_msgrx kern_dmsg.c:1073
:1077 state->rxcmd |= DMSGF_DELETE β rxcmd gets DELETE
β callback disk_rcvdmsg β disk_blk_read subr_diskiocom.c:238
(simulated cmd LNK_ERROR != BLK_READ β reterr=1)
:385 msg has DELETE:
:386 iost->count != 0 (I/O in flight) β skip kfree
:390 kdmsg_msg_reply(msg, error) β CALLED UNCONDITIONALLY
β kdmsg_msg_write β DYING branch kern_dmsg.c:1974
β kdmsg_state_cleanuptx kern_dmsg.c:1647
:1672 state->txcmd |= DMSGF_DELETE
:1673 rxcmd has DELETE β RB_REMOVE β removed from rbtree
:1704 kdmsg_subq_delete(state)
:1291 state->parent = NULL β PARENT CORRUPTED
:1301 kdmsg_state_drop (subq ref)
:1707 kdmsg_state_drop (rbtree ref) β topology refs dropped
β kdmsg_state_cleanuprx kern_dmsg.c:1236
:1272 kdmsg_msg_free β kdmsg_state_drop β refs β 0 β STATE FREED
3. Async I/O completes β diskiodone subr_diskiocom.c:577
:580 state = bio->bio_caller_info1.ptr β FREED/CLEANED state
:582 iost = state->any.any β stale iost ptr
:650 kdmsg_msg_alloc(state, cmd, NULL, 0)
β pstate = state->parent β NULL!
β KKASSERT(pstate != NULL) β PANIC
The prior verdict's gap
The prior verdict traced the state lifetime correctly for the diskiodone
produces the DELETE reply path (eof=1 mode): the state is freed inside the
LAST diskiodone's kdmsg_msg_write, after all derefs. That is correct.
But it missed the teardown produces the DELETE reply path (eof=0 mode):
when the connection drops, kdmsg_simulate_failure simulates a DELETE receive.
The simulated DELETE does NOT match BLK_READ (LNK_ERROR != BLK_READ), so
disk_blk_read enters the error path (reterr=1). The error path calls
kdmsg_msg_reply(msg, error) unconditionally β even when iost->count > 0
(I/O in flight). This sends a DELETE-bearing reply that drives cleanuptx,
which tears down the state (parent=NULL, removed from rbtree, topology refs
dropped) while the bio still holds a raw state pointer. When the I/O
completes, diskiodone dereferences the cleaned-up state β UAF.
The prior verdict stated (incorrectly): "it never bypasses the txcmd |=
DELETE requirement." But kdmsg_msg_reply (called from the error path)
DOES set txcmd |= DELETE via cleanuptx. The prior verdict did not trace
the error-path callback.
Setup
The disk iocom is attached via DIOCRECLUSTER on /dev/vbd0 with one end of
an AF_UNIX socketpair (reused from sibling finding DF-0017). The hammer2
userland daemon pre-attaches every disk iocom at boot, so pkill -9 -x hammer2
frees the iocom first. See trigger_race.c for the self-contained trigger.
Trigger position: local root (needs /dev/vbd0 open + DIOCRECLUSTER).
The remote vector (authenticated cluster peer sending DELETE while I/O is in
flight) is equivalent.
Fix
The fix defers the DELETE reply to diskiodone when I/O is still in flight.
In all four disk_blk_* error paths, when iost->count > 0, set iost->eof = 1
instead of calling kdmsg_msg_reply. The last diskiodone (when count reaches
0) sends the DELETE reply itself, after all derefs are done. This keeps the
state valid (in rbtree, parent intact) until diskiodone has finished using it.
The finding markdown's proposed fix (uncomment kdmsg_state_hold/drop) does NOT
work: those functions are file-local static macros in kern_dmsg.c, invisible
to subr_diskiocom.c. Even if exported, the hold/drop alone does NOT prevent
the UAF: the teardown's kdmsg_subq_delete sets parent = NULL regardless of
the refcount, so diskiodone still reads a corrupted parent. This was
confirmed empirically β the hold/drop kernel panicked with the same assertion.
Fix validation (Phase 8)
| Kernel | Version | Trigger result |
|---|---|---|
| Unpatched baseline | #0 Thu Jul 2 06:02:54 UTC 2026 |
PANIC: pstate != NULL in kdmsg_msg_alloc from diskiodone |
| Patched (eof=1) | #1 Fri Jul 3 10:54:20 UTC 2026 |
CLEAN: trigger exit 0, blk_active β 0, guest stays up |
Stress tests on the patched kernel: 10+ iterations across 3 I/O patterns
(1/5/17 I/Os per state), ~30+ diskiodone completions during teardown races,
zero panics.
Exploit chain
The primitive is a kdmsg_state_t UAF (~152 bytes, M_DMSG_DISK malloc type,
kmalloc-256 bucket). In a real cluster scenario, an authenticated peer controls
the timing between the state free (send DELETE or drop connection) and the I/O
completion. The freed state's slab slot can be reclaimed by same-type
allocations during the teardown (LNK_CONN/LNK_SPAN message processing). On a
non-KKASSERT kernel (or with the KKASSERT stripped), the stale parent/iocom
dereference would be a wild pointer read β potential code execution via
function-pointer corruption in kdmsg_msg_alloc or kdmsg_msg_write. Full
exploitation (slab grooming + function pointer override) was not developed
beyond the panic reproduction on this single-disk guest.
Files
| file | purpose |
|---|---|
trigger_race.c |
self-contained trigger: DIOCRECLUSTER + BLK_READ |
trigger.c |
prior trigger (many separate states, one I/O each β did not reproduce) |
build.sh |
cc -O2 -o trigger trigger.c -lpthread + race variant |
run.sh |
kills hammer2, runs the race trigger |
fix.diff |
git-apply-able fix: defer DELETE reply to diskiodone when I/O in flight |
panic.txt |
kernel panic signature from boot.log (crash proof) |
run.log |
baseline run (unpatched kernel) β SSH timeout, guest panicked |
fix_run.log |
patched kernel run β clean exit, no panic |
fix_build.log |
patched kernel build output |
env.txt |
guest uname, cc version, sysctl state |
manifest.json |
machine-readable catalog |
Fix verification
fixedVALIDATED the fix: trigger_race 131072 0 0 1 panicked on the unpatched 6.5-DEVELOPMENT #0 baseline (panic: assertion 'pstate != NULL' in kdmsg_msg_alloc from diskiodone via vtblk_vq_intr) and does NOT on the single-fix #1 kernel (eof=1 deferral: trigger exit 0, blk_active returns to 0, guest stays up across 10+ stress-test iterations with 1/5/17 I/Os per state). The eof=1 fix keeps the state valid (in rbtree, parent intact) until the last diskiodone sends the DELETE reply itself, eliminating the window where diskiodone dereferences a cleaned-up state. => fix closes the bug.
BEFORE (unpatched #0): panic: assertion "pstate != NULL" failed in kdmsg_msg_alloc at kern_dmsg.c:1822 / diskiodone+0x120 / vtblk_vq_intr -- guest DIED.\nAFTER (patched #1 eof=1): FIX2_TRIGGER_EXIT=0, blk_active AFTER: 0, uptime shows guest running, 10+ iterations zero panics -- guest HEALTHY.
Confirmed kernel references
- sys/kern/subr_diskiocom.c:372
- sys/kern/subr_diskiocom.c:375
- sys/kern/subr_diskiocom.c:384
- sys/kern/subr_diskiocom.c:390
- sys/kern/subr_diskiocom.c:577
- sys/kern/subr_diskiocom.c:580
- sys/kern/subr_diskiocom.c:650
- sys/kern/kern_dmsg.c:1077
- sys/kern/kern_dmsg.c:1291
- sys/kern/kern_dmsg.c:1647
- sys/kern/kern_dmsg.c:1704
- sys/kern/kern_dmsg.c:1822
- sys/kern/kern_dmsg.c:1974
- sys/kern/kern_dmsg.c:2036
Detail
Exploit chain
The primitive is a kdmsg_state_t UAF (~152 bytes, M_DMSG_DISK malloc type, kmalloc-256 bucket). The state is freed by the teardown's simulated DELETE path while async disk I/O is in flight; diskiodone then dereferences the freed/cleaned state. The freed state's parent field is explicitly set to NULL by kdmsg_subq_delete before the free, which trips KKASSERT. On a non-KKASSERT build, the stale parent/iocom/any.any dereferences would be wild pointer reads -- potential code execution via function-pointer corruption in kdmsg_msg_alloc or kdmsg_msg_write. Full slab grooming + function pointer override was not developed beyond the panic reproduction on this single-disk guest; the cluster-peer remote vector (sending DELETE while I/O in flight) is equivalent.
Evidence (decisive lines)
panic: assertion "pstate != NULL" failed in kdmsg_msg_alloc at /usr/src/sys/kern/kern_dmsg.c:1822\ncpuid = 5\nTrace beginning at frame 0xfffff8008ed4c860\nkdmsg_msg_alloc() at kdmsg_msg_alloc+0x267\ndiskiodone() at diskiodone+0x120\nvtblk_vq_intr() at vtblk_vq_intr+0xa7\nvtpci_msix_intr() at vtpci_msix_intr+0x32\nlwkt_serialize_handler_call() at lwkt_serialize_handler_call+0xd3\nDebugger("panic")
PoC changes
Wrote trigger_race.c (new): a self-contained trigger that dispatches a single MAXPHYS BLK_READ|CREATE on msgid=1 (eof=0) and immediately closes the socket, racing the connection-teardown (which frees the state via simulated DELETE) against the in-flight I/O completion (diskiodone). The prior trigger.c created many separate states with one fast I/O each and could not win the race. Kept trigger.c for reference. Updated build.sh/run.sh for the new trigger.
Verified recommended fix
In all four disk_blk_* error paths (subr_diskiocom.c:384/460/511/562), when a DELETE arrives with I/O in flight (iost->count > 0), set iost->eof = 1 instead of calling kdmsg_msg_reply. This defers the DELETE reply to diskiodone, which sends it when count reaches 0 -- after all state derefs are done. SUPERSIDES the finding proposal (uncomment kdmsg_state_hold/drop): those are file-local static macros in kern_dmsg.c that do not compile from subr_diskiocom.c, and even when exported, the hold/drop alone does NOT prevent the UAF because kdmsg_subq_delete sets parent=NULL regardless of refcount (confirmed empirically -- the hold/drop kernel still panicked). The full git-apply-able diff is in findings/poc/DF-0117/fix.diff.
Verdict
REPRODUCED. The prior 'NOT REPRODUCED' verdict had a gap in its teardown analysis: it claimed the state's topology refs prevent freeing during diskiodone, but missed that the connection-drop teardown's simulated DELETE (kern_dmsg.c:1392-1404) triggers the disk callback's error path (subr_diskiocom.c:384-394), which calls kdmsg_msg_reply UNCONDITIONALLY -- even when iost->count > 0 (I/O in flight). That reply drives cleanuptx (kern_dmsg.c:1647), which sets state->parent = NULL (via kdmsg_subq_delete at kern_dmsg.c:1291), removes the state from the rbtree, and drops the topology refs -- all while the bio still holds a raw state pointer at subr_diskiocom.c:372. When the in-flight I/O completes, diskiodone (subr_diskiocom.c:577) dereferences the cleaned-up state, reads state->parent == NULL in kdmsg_msg_alloc (kern_dmsg.c:1822), and panics: 'assertion "pstate != NULL" failed' from diskiodone+0x120 via vtblk_vq_intr. The trigger (BLK_READ|CREATE with MAXPHYS on msgid=1, eof=0, followed by immediate socket close) reproduces this RELIABLY on the unpatched #0 kernel. The 128KB read is slow enough (1-10ms on QCOW2-backed virtio) that the teardown always wins the race.
No comments yet.