# DF-4758 — vinum `vinumsize()` answers `d_psize` for the wrong object type and reads `VOL[]` out of bounds (raw sd/plex minors index the volume array with the subdisk/plex number)

* File: `sys/dev/raid/vinum/vinum.c:558-572` (`vinumsize`)
* Severity: Medium (root-gated; heap OOB read + provably wrong swap sizing → swap writes beyond the device extent)
* Bucket: memcorrupt / base:raid

## What

`vinumsize()` is the `d_psize` handler of **every** vinum device (one
`dev_ops` for all types). It does:

```c
vol = &VOL[Volno(dev)];          /* vinum.c:564 */
if (vol->state == volume_up) {
    ap->a_result = (int64_t)vol->size;
```

with **no `DEVTYPE()` dispatch and no bounds check**. `Volno(dev)` is a raw
bit extraction (`minor & 0xff`, vinumutil.c:239-243). Raw subdisk and raw
plex minors are built by `VINUM_SD(s)` / `VINUM_PLEX(p)`
(vinumvar.h:124-132), which encode the **subdisk/plex number in the low 8
bits** — exactly the bits `Volno()` returns. So:

* `d_psize` on `/dev/vinum/sd/sN` indexes `VOL[N]` — the *volume* array —
  with the **subdisk number**;
* `d_psize` on `/dev/vinum/plex/pN` indexes `VOL[N]` with the **plex number**;
* whenever that number `>= vinum_conf.volumes_allocated` (4 initially,
  `INITIAL_VOLUMES`, vinumvar.h:194) it is a **kernel heap OOB read** of
  `sizeof(struct volume) * N` bytes past the array (`state` at +64,
  `size` at +104 of the phantom entry);
* even for in-bounds numbers it returns the **volume's** size (and only if
  that volume is up) instead of the subdisk's/plex's own size.

The only kernel caller of `d_psize` is `dev_dpsize()` from the **swapon**
path (`sys/vm/vm_swap.c:308`), gated by `caps_priv_check_self
(SYSCAP_RESTRICTEDROOT)` (vm_swap.c:202) — root-only.

## Reproduced (root on the DragonFly guest, stock kernel)

Config: 1 volume `v0` (up, 6144 kB), 1 concat plex, 6 subdisks
`s0..s5` (1024 kB each, all up); `volumes_allocated == 4`.

```
$ swapon /dev/vinum/sd/s0          # a 1 MB subdisk (2048 512-blocks)
OK
$ swapinfo
Device          512-blocks     Used    Avail Capacity  Type
/dev/vinum/sd/s0      12288        0    12288     0%    Interleaved
```

**12288 blocks = 6 MB = exactly volume `v0`'s size** — `vinumsize()` sized
the subdisk device from the *wrong object*. The kernel now believes a 1 MB
subdisk provides 6 MB of swap backing; under swap pressure it writes swap
pages at offsets 2048..12287 blocks, **past the end of the subdisk**, onto
whatever occupies those offsets on the drive (other subdisks / vinum
config / other data) — silent data corruption plus unrecoverable swap-in
of garbage pages.

`s4`/`s5` (sdno ≥ volumes_allocated) executed the OOB indexing
`VOL[4]`/`VOL[5]` past the 4-entry array; on this boot the heap bytes at
those offsets did not equal `volume_up`, so swapon returned ENXIO — the
result of the OOB read is heap-layout-dependent (a matching `state` word
installs a garbage `size`, e.g. a huge value → vm_swap.c:344 rejects with
"exceeded maximum of %ld blocks per swap unit" on console; a mid-range
garbage value installs quietly).

## Files

* `df4758_swapon.c` — trigger (swapon(2) on device paths, prints exact errno + swapinfo)
* `run.sh` — full fresh-boot repro (label + drive + volume/plex/6 sds + probes)
* `run2.sh` — probe-only (config already exists)
* `df4758_run.log`, `df4758_run2.log` — full untrimmed baseline runs
* `fix.diff` — combined vinum.c fix (vinumsize hunk + the DF-4760 hunks)
* `fix_run.log` — patched-module validation (bad behavior GONE, volume path still works)
* `build.log` — trigger build

## Build / run (guest, root)

```
kldload vinum
dd if=label.bin of=/dev/md0 bs=512 seek=1 conv=notrunc   # from findings/poc/DF-4592
cc -O -o /tmp/df4758_swapon /tmp/df4758_swapon.c
sh run.sh          # or run2.sh after the config exists
```

## Expected

* Vulnerable: `swapon /dev/vinum/sd/s0` → OK, swapinfo shows the volume's
  size (12288) on a 1 MB subdisk; sd ≥ 4 probes OOB-read VOL[] (ENXIO /
  garbage-size / panic depending on heap).
* Patched: every `sd`/`plex` swapon → ENXIO; `/dev/vinum/vol/v0` → OK with
  the correct 12288 blocks.
