Date: Mon, 6 Oct 2014 22:53:01 +0000 From: "Laier, Max" <max.laier@isilon.com> To: "freebsd-geom@FreeBSD.org" <freebsd-geom@FreeBSD.org> Cc: "Rice, Benno" <Benno.Rice@emc.com>, "Ferris, Scott" <scott.ferris@isilon.com> Subject: biodone vs geom_up Message-ID: <44A3D2875585FB4C857FA60A14A16C895C6ABE38@MX104CL02.corp.emc.com>
next in thread | raw e-mail | index | archive | help
It seems to me that there is a problem with biodone (particular vs. g_disk_= start/g_disk_done_single): void biodone(struct bio *bp) { struct mtx *mtxp; void (*done)(struct bio *); // ... done =3D bp->bio_done; if (done =3D=3D NULL) {=20 mtxp =3D mtx_pool_find(mtxpool_sleep, bp); mtx_lock(mtxp); bp->bio_flags |=3D BIO_DONE; // XXX [0] wakeup(bp); mtx_unlock(mtxp); } else { bp->bio_flags |=3D BIO_DONE; // XXX [1] done(bp); // XXX [2] } } static void g_disk_start(struct bio *bp) { // ... case BIO_READ: case BIO_WRITE: d_maxsize =3D (bp->bio_cmd =3D=3D BIO_DELETE) ? dp->d_delmaxsize : dp->d_maxsize; if (bp->bio_length <=3D d_maxsize) { bp->bio_disk =3D dp; bp->bio_to =3D (void *)bp->bio_done; bp->bio_done =3D g_disk_done_single; // XXX [3] // ... dp->d_strategy(bp); break; // ... } static void g_disk_done_single(struct bio *bp) { struct bintime now; struct g_disk_softc *sc; bp->bio_completed =3D bp->bio_length - bp->bio_resid; bp->bio_done =3D (void *)bp->bio_to; bp->bio_to =3D LIST_FIRST(&bp->bio_disk->d_geom->provider); // ... g_io_deliver(bp, bp->bio_error); // XXX [4] } And a normal caller doing something like: void * g_read_data(struct g_consumer *cp, off_t offset, off_t length, int *error) { // ... bp =3D g_alloc_bio(); bp->bio_cmd =3D BIO_READ; bp->bio_done =3D NULL; g_io_request(bp, cp); errorc =3D biowait(bp, "gread"); if (error !=3D NULL) *error =3D errorc; g_destroy_bio(bp); // ... } With this code, it is possible that thread 1(the i/o initiator) only gets t= o biowait *after* the g_io_request() has already made it back, called (the = first) biodone, set BIO_DONE at [1], and the bio is now either queued in th= e g_bio_run_up.bio_queue, or in direct dispatch (via g_io_deliver at [4] vi= a done(bp) [aka bio_done set at [3] called at [2]]. biowait will just return (because BIO_DONE is set), and the initiator threa= d will g_destroy_bio while that bio is still being worked on, on the g_up t= hread ... use-after-free. Does anyone see a reason why we need to set BIO_DONE at [1] at all? It see= ms to me that the default case, where there is no callback left [0], is eno= ugh? Removing the BIO_DONE setting at [1] resolves the issue I'm seeing, but I w= onder if there is a better way to resolve this? Should g_disk_start() clon= e a bio for its callback instead? Are there other places that set a bio_do= ne callback on the way down? Any comments, questions, or answers greatly appreciated. Thanks, Max=
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?44A3D2875585FB4C857FA60A14A16C895C6ABE38>