⬢ DragonFlyBSD Kernel Audit
DF-5554 / calcgeo.c
← back to finding ↓ download raw
/*
 * df5554.ko -- pass-2 evidence harness for sys/bus/cam/cam.c
 * cam_calc_geometry() 64->32-bit truncation (cam.c:389, cam.c:398).
 *
 * Drives the PRODUCTION kernel cam_calc_geometry() (linked from the
 * running kernel image, not a copy) with crafted volume_size values.
 * volume_size (struct ccb_calc_geometry, cam_ccb.h:779) is u_int64_t and
 * is filled by scsi_da.c:dasetgeom() (scsi_da.c:2303) straight from the
 * device-reported READ CAPACITY(16) value (scsi_8btou64, scsi_da.c:1851)
 * -- i.e. fully controlled by whatever SCSI target the host talks to
 * (USB gadget, iSCSI/NBD export, virtio-scsi backend).
 *
 * All arithmetic locals inside cam_calc_geometry are uint32_t:
 *
 *   uint32_t size_mb, secs_per_cylinder;                (cam.c:378)
 *   size_mb = ccg->volume_size / size_mb;               (cam.c:389) u64->u32
 *   ccg->cylinders = ccg->volume_size / secs_per_cylinder; (cam.c:398) u64->u32
 *
 * For volume_size >= 2^32 * secs_per_cylinder both quotients wrap mod 2^32:
 * wrong extended-translation decision (H/S) and an arbitrary wrapped
 * cylinder count, including cylinders == 0.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/types.h>

#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>

struct df_case {
	uint64_t	vol;
	uint32_t	bs;
	int		ext;
	const char	*tag;
};

static const struct df_case cases[] = {
	{ 15628000000ULL,		512, 1, "sane-8TB-disk" },
	{ 1ULL << 43,			512, 1, "2^43 sectors (4PiB@512B): size_mb wraps to 0" },
	{ 16065ULL << 32,		512, 1, "2^32*16065: cylinders wraps exactly to 0" },
	{ (16065ULL << 32) + 16064,	512, 1, "2^32*16065+16064: cylinders wraps to 0" },
	{ 0xFFFFFFFFFFFFFFFFULL,	512, 1, "max-u64 (crafted READ CAPACITY(16))" },
	{ 0,				512, 1, "zero-volume" },
	{ 1000000,			0,   1, "block_size=0 (div-guard check)" },
};

static void
run_case(const struct df_case *c)
{
	struct ccb_calc_geometry ccg;
	uint64_t	mb_div, mb_true, spc_true, cyl_true;
	uint32_t	heads_true, secs_true;

	bzero(&ccg, sizeof(ccg));
	ccg.block_size = c->bs;
	ccg.volume_size = c->vol;

	/* exact 64-bit replay of what the function *should* compute */
	if (c->bs == 0) {
		mb_true = 0;
	} else {
		mb_div = (uint64_t)1024 * 1024 / c->bs;
		mb_true = mb_div ? (c->vol / mb_div) : 0;
	}
	heads_true = (mb_true > 1024 && c->ext) ? 255 : 64;
	secs_true  = (heads_true == 255) ? 63 : 32;
	spc_true   = (uint64_t)heads_true * secs_true;
	cyl_true   = spc_true ? (c->vol / spc_true) : 0;

	cam_calc_geometry(&ccg, c->ext);

	kprintf("DF5554[%s]\n", c->tag);
	kprintf("DF5554[%s]   in : vol=%ju bs=%u ext=%d\n",
	    c->tag, (uintmax_t)c->vol, c->bs, c->ext);
	kprintf("DF5554[%s]   ker: H=%u S=%u C=%u status=0x%02x\n",
	    c->tag, ccg.heads, ccg.secs_per_track, ccg.cylinders,
	    ccg.ccb_h.status & CAM_STATUS_MASK);
	kprintf("DF5554[%s]   tru: H=%u S=%u C=%ju (true size_mb=%ju)\n",
	    c->tag, heads_true, secs_true, (uintmax_t)cyl_true,
	    (uintmax_t)mb_true);

	if ((ccg.ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP &&
	    (ccg.heads != heads_true ||
	     ccg.secs_per_track != secs_true ||
	     ccg.cylinders != (uint32_t)cyl_true ||
	     cyl_true > 0xFFFFFFFFu)) {
		kprintf("DF5554[%s]   <<< MISMATCH: u64->u32 truncation "
		    "in cam_calc_geometry (cam.c:389/cam.c:398)\n", c->tag);
	} else if ((ccg.ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
		kprintf("DF5554[%s]   ok (matches 64-bit math)\n", c->tag);
	} else {
		kprintf("DF5554[%s]   rejected by guard (status != CMP)\n",
		    c->tag);
	}
}

static int
df5554_handler(module_t mod, int what, void *arg)
{
	int i;

	switch (what) {
	case MOD_LOAD:
		kprintf("DF5554: cam_calc_geometry truncation harness "
		    "(production %s)\n", version);
		for (i = 0; i < (int)NELEM(cases); i++)
			run_case(&cases[i]);
		kprintf("DF5554: done\n");
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t df5554_mod = {
	"df5554",
	df5554_handler,
	NULL
};
DECLARE_MODULE(df5554, df5554_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);