diff mbox series

[5/5] virtiofsd: prevent races with lo_dirp_put()

Message ID 20190726091103.23503-6-stefanha@redhat.com (mailing list archive)
State New, archived
Headers show
Series virtiofsd: multithreading preparation | expand

Commit Message

Stefan Hajnoczi July 26, 2019, 9:11 a.m. UTC
Introduce lo_dirp_put() so that FUSE_RELEASEDIR does not cause
use-after-free races with other threads that are accessing lo_dirp.

Also make lo_releasedir() atomic to prevent FUSE_RELEASEDIR racing with
itself.  This prevents double-frees.

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 contrib/virtiofsd/passthrough_ll.c | 42 +++++++++++++++++++++++++-----
 1 file changed, 36 insertions(+), 6 deletions(-)

Comments

Dr. David Alan Gilbert July 31, 2019, 5:44 p.m. UTC | #1
* Stefan Hajnoczi (stefanha@redhat.com) wrote:
> Introduce lo_dirp_put() so that FUSE_RELEASEDIR does not cause
> use-after-free races with other threads that are accessing lo_dirp.
> 
> Also make lo_releasedir() atomic to prevent FUSE_RELEASEDIR racing with
> itself.  This prevents double-frees.
> 
> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
> ---
>  contrib/virtiofsd/passthrough_ll.c | 42 +++++++++++++++++++++++++-----
>  1 file changed, 36 insertions(+), 6 deletions(-)
> 
> diff --git a/contrib/virtiofsd/passthrough_ll.c b/contrib/virtiofsd/passthrough_ll.c
> index ad3abdd532..f74e7d2d21 100644
> --- a/contrib/virtiofsd/passthrough_ll.c
> +++ b/contrib/virtiofsd/passthrough_ll.c
> @@ -1293,11 +1293,28 @@ static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
>  }
>  
>  struct lo_dirp {
> +	gint refcount;
>  	DIR *dp;
>  	struct dirent *entry;
>  	off_t offset;
>  };
>  
> +static void lo_dirp_put(struct lo_dirp **dp)
> +{
> +	struct lo_dirp *d = *dp;
> +
> +	if (!d) {
> +		return;
> +	}
> +	*dp = NULL;
> +
> +	if (g_atomic_int_dec_and_test(&d->refcount)) {
> +		closedir(d->dp);
> +		free(d);
> +	}
> +}
> +
> +/* Call lo_dirp_put() on the return value when no longer needed */
>  static struct lo_dirp *lo_dirp(fuse_req_t req, struct fuse_file_info *fi)
>  {
>  	struct lo_data *lo = lo_data(req);
> @@ -1305,6 +1322,9 @@ static struct lo_dirp *lo_dirp(fuse_req_t req, struct fuse_file_info *fi)
>  
>  	pthread_mutex_lock(&lo->mutex);
>  	elem = lo_map_get(&lo->dirp_map, fi->fh);
> +	if (elem) {
> +		g_atomic_int_inc(&elem->dirp->refcount);

I don't understand what protects against reading the elem->dirp
here at the same time it's free'd by lo_releasedir's call to lo_dirp_put

> +	}
>  	pthread_mutex_unlock(&lo->mutex);
>  	if (!elem)
>  		return NULL;
> @@ -1335,6 +1355,8 @@ static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi
>  	d->offset = 0;
>  	d->entry = NULL;
>  
> +	g_atomic_int_set(&d->refcount, 1); /* paired with lo_releasedir() */
> +
>  	fh = lo_add_dirp_mapping(req, d);
>  	if (fh == -1)
>  		goto out_err;
> @@ -1363,7 +1385,7 @@ static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
>  			  off_t offset, struct fuse_file_info *fi, int plus)
>  {
>  	struct lo_data *lo = lo_data(req);
> -	struct lo_dirp *d;
> +	struct lo_dirp *d = NULL;
>  	struct lo_inode *dinode;
>  	char *buf = NULL;
>  	char *p;
> @@ -1451,6 +1473,8 @@ static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
>  
>      err = 0;
>  error:
> +    lo_dirp_put(&d);
> +
>      // If there's an error, we can only signal it if we haven't stored
>      // any entries yet - otherwise we'd end up with wrong lookup
>      // counts for the entries that are already in the buffer. So we
> @@ -1477,22 +1501,25 @@ static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
>  static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
>  {
>  	struct lo_data *lo = lo_data(req);
> +	struct lo_map_elem *elem;
>  	struct lo_dirp *d;
>  
>  	(void) ino;
>  
> -	d = lo_dirp(req, fi);
> -	if (!d) {
> +	pthread_mutex_lock(&lo->mutex);
> +	elem = lo_map_get(&lo->dirp_map, fi->fh);
> +	if (!elem) {
> +		pthread_mutex_unlock(&lo->mutex);
>  		fuse_reply_err(req, EBADF);
>  		return;
>  	}
>  
> -	pthread_mutex_lock(&lo->mutex);
> +	d = elem->dirp;
>  	lo_map_remove(&lo->dirp_map, fi->fh);
>  	pthread_mutex_unlock(&lo->mutex);
>  
> -	closedir(d->dp);
> -	free(d);
> +	lo_dirp_put(&d); /* paired with lo_opendir() */

Is the &d really what's intended? That's the local stack variable, so
lo_dirp_put will set that local to NULL rather than the elem->dirp wont
it?

Dave

> +
>  	fuse_reply_err(req, 0);
>  }
>  
> @@ -1701,6 +1728,9 @@ static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
>  		res = fdatasync(fd);
>  	else
>  		res = fsync(fd);
> +
> +	lo_dirp_put(&d);
> +
>  	fuse_reply_err(req, res == -1 ? errno : 0);
>  }
>  
> -- 
> 2.21.0
> 
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
Stefan Hajnoczi Aug. 1, 2019, 9:15 a.m. UTC | #2
On Wed, Jul 31, 2019 at 06:44:52PM +0100, Dr. David Alan Gilbert wrote:
> * Stefan Hajnoczi (stefanha@redhat.com) wrote:
> > Introduce lo_dirp_put() so that FUSE_RELEASEDIR does not cause
> > use-after-free races with other threads that are accessing lo_dirp.
> > 
> > Also make lo_releasedir() atomic to prevent FUSE_RELEASEDIR racing with
> > itself.  This prevents double-frees.
> > 
> > Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
> > ---
> >  contrib/virtiofsd/passthrough_ll.c | 42 +++++++++++++++++++++++++-----
> >  1 file changed, 36 insertions(+), 6 deletions(-)
> > 
> > diff --git a/contrib/virtiofsd/passthrough_ll.c b/contrib/virtiofsd/passthrough_ll.c
> > index ad3abdd532..f74e7d2d21 100644
> > --- a/contrib/virtiofsd/passthrough_ll.c
> > +++ b/contrib/virtiofsd/passthrough_ll.c
> > @@ -1293,11 +1293,28 @@ static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
> >  }
> >  
> >  struct lo_dirp {
> > +	gint refcount;
> >  	DIR *dp;
> >  	struct dirent *entry;
> >  	off_t offset;
> >  };
> >  
> > +static void lo_dirp_put(struct lo_dirp **dp)
> > +{
> > +	struct lo_dirp *d = *dp;
> > +
> > +	if (!d) {
> > +		return;
> > +	}
> > +	*dp = NULL;
> > +
> > +	if (g_atomic_int_dec_and_test(&d->refcount)) {
> > +		closedir(d->dp);
> > +		free(d);
> > +	}
> > +}
> > +
> > +/* Call lo_dirp_put() on the return value when no longer needed */
> >  static struct lo_dirp *lo_dirp(fuse_req_t req, struct fuse_file_info *fi)
> >  {
> >  	struct lo_data *lo = lo_data(req);
> > @@ -1305,6 +1322,9 @@ static struct lo_dirp *lo_dirp(fuse_req_t req, struct fuse_file_info *fi)
> >  
> >  	pthread_mutex_lock(&lo->mutex);
> >  	elem = lo_map_get(&lo->dirp_map, fi->fh);
> > +	if (elem) {
> > +		g_atomic_int_inc(&elem->dirp->refcount);
> 
> I don't understand what protects against reading the elem->dirp
> here at the same time it's free'd by lo_releasedir's call to lo_dirp_put

It is lo->mutex and not the refcount that prevents the race with
lo_releasedir().  Two cases:

1. lo_releasedir() runs before lo_dirp().  lo_map_get() returns NULL and
   lo_dirp() fails.

2. lo_releasedir() runs after lo_dirp().  lo_map_get() succeeds and the
   lo_dirp() caller keeps the object alive until lo_dirp_put(), when we
   finally free it.

There is no third case since lo->mutex ensures that lo_releasedir() and
lo_dirp() are serialized in the dirp_map lookup.

> > +	}
> >  	pthread_mutex_unlock(&lo->mutex);
> >  	if (!elem)
> >  		return NULL;
> > @@ -1335,6 +1355,8 @@ static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi
> >  	d->offset = 0;
> >  	d->entry = NULL;
> >  
> > +	g_atomic_int_set(&d->refcount, 1); /* paired with lo_releasedir() */
> > +
> >  	fh = lo_add_dirp_mapping(req, d);
> >  	if (fh == -1)
> >  		goto out_err;
> > @@ -1363,7 +1385,7 @@ static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
> >  			  off_t offset, struct fuse_file_info *fi, int plus)
> >  {
> >  	struct lo_data *lo = lo_data(req);
> > -	struct lo_dirp *d;
> > +	struct lo_dirp *d = NULL;
> >  	struct lo_inode *dinode;
> >  	char *buf = NULL;
> >  	char *p;
> > @@ -1451,6 +1473,8 @@ static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
> >  
> >      err = 0;
> >  error:
> > +    lo_dirp_put(&d);
> > +
> >      // If there's an error, we can only signal it if we haven't stored
> >      // any entries yet - otherwise we'd end up with wrong lookup
> >      // counts for the entries that are already in the buffer. So we
> > @@ -1477,22 +1501,25 @@ static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
> >  static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
> >  {
> >  	struct lo_data *lo = lo_data(req);
> > +	struct lo_map_elem *elem;
> >  	struct lo_dirp *d;
> >  
> >  	(void) ino;
> >  
> > -	d = lo_dirp(req, fi);
> > -	if (!d) {
> > +	pthread_mutex_lock(&lo->mutex);
> > +	elem = lo_map_get(&lo->dirp_map, fi->fh);
> > +	if (!elem) {
> > +		pthread_mutex_unlock(&lo->mutex);
> >  		fuse_reply_err(req, EBADF);
> >  		return;
> >  	}
> >  
> > -	pthread_mutex_lock(&lo->mutex);
> > +	d = elem->dirp;
> >  	lo_map_remove(&lo->dirp_map, fi->fh);
> >  	pthread_mutex_unlock(&lo->mutex);
> >  
> > -	closedir(d->dp);
> > -	free(d);
> > +	lo_dirp_put(&d); /* paired with lo_opendir() */
> 
> Is the &d really what's intended? That's the local stack variable, so
> lo_dirp_put will set that local to NULL rather than the elem->dirp wont
> it?

Yes, the put(&ptr) pattern prevents user-after-free in the caller.  It's
slightly safer than put(ptr) since that leaves ptr initialized and the
caller might access it later by accident.

elem has already been returned to the freelist by lo_map_remove() and we
must not touch it anymore.

Stefan
Dr. David Alan Gilbert Aug. 1, 2019, 11:14 a.m. UTC | #3
* Stefan Hajnoczi (stefanha@redhat.com) wrote:
> On Wed, Jul 31, 2019 at 06:44:52PM +0100, Dr. David Alan Gilbert wrote:
> > * Stefan Hajnoczi (stefanha@redhat.com) wrote:
> > > Introduce lo_dirp_put() so that FUSE_RELEASEDIR does not cause
> > > use-after-free races with other threads that are accessing lo_dirp.
> > > 
> > > Also make lo_releasedir() atomic to prevent FUSE_RELEASEDIR racing with
> > > itself.  This prevents double-frees.
> > > 
> > > Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
> > > ---
> > >  contrib/virtiofsd/passthrough_ll.c | 42 +++++++++++++++++++++++++-----
> > >  1 file changed, 36 insertions(+), 6 deletions(-)
> > > 
> > > diff --git a/contrib/virtiofsd/passthrough_ll.c b/contrib/virtiofsd/passthrough_ll.c
> > > index ad3abdd532..f74e7d2d21 100644
> > > --- a/contrib/virtiofsd/passthrough_ll.c
> > > +++ b/contrib/virtiofsd/passthrough_ll.c
> > > @@ -1293,11 +1293,28 @@ static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
> > >  }
> > >  
> > >  struct lo_dirp {
> > > +	gint refcount;
> > >  	DIR *dp;
> > >  	struct dirent *entry;
> > >  	off_t offset;
> > >  };
> > >  
> > > +static void lo_dirp_put(struct lo_dirp **dp)
> > > +{
> > > +	struct lo_dirp *d = *dp;
> > > +
> > > +	if (!d) {
> > > +		return;
> > > +	}
> > > +	*dp = NULL;
> > > +
> > > +	if (g_atomic_int_dec_and_test(&d->refcount)) {
> > > +		closedir(d->dp);
> > > +		free(d);
> > > +	}
> > > +}
> > > +
> > > +/* Call lo_dirp_put() on the return value when no longer needed */
> > >  static struct lo_dirp *lo_dirp(fuse_req_t req, struct fuse_file_info *fi)
> > >  {
> > >  	struct lo_data *lo = lo_data(req);
> > > @@ -1305,6 +1322,9 @@ static struct lo_dirp *lo_dirp(fuse_req_t req, struct fuse_file_info *fi)
> > >  
> > >  	pthread_mutex_lock(&lo->mutex);
> > >  	elem = lo_map_get(&lo->dirp_map, fi->fh);
> > > +	if (elem) {
> > > +		g_atomic_int_inc(&elem->dirp->refcount);
> > 
> > I don't understand what protects against reading the elem->dirp
> > here at the same time it's free'd by lo_releasedir's call to lo_dirp_put
> 
> It is lo->mutex and not the refcount that prevents the race with
> lo_releasedir().  Two cases:
> 
> 1. lo_releasedir() runs before lo_dirp().  lo_map_get() returns NULL and
>    lo_dirp() fails.

Ah that's what I was missing; it's that the lo_releasedir doesn't need
to have completed before lo_dirp runs, it's just that it's lo_map_remove
has happened.

> 2. lo_releasedir() runs after lo_dirp().  lo_map_get() succeeds and the
>    lo_dirp() caller keeps the object alive until lo_dirp_put(), when we
>    finally free it.
> 
> There is no third case since lo->mutex ensures that lo_releasedir() and
> lo_dirp() are serialized in the dirp_map lookup.

> > > +	}
> > >  	pthread_mutex_unlock(&lo->mutex);
> > >  	if (!elem)
> > >  		return NULL;
> > > @@ -1335,6 +1355,8 @@ static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi
> > >  	d->offset = 0;
> > >  	d->entry = NULL;
> > >  
> > > +	g_atomic_int_set(&d->refcount, 1); /* paired with lo_releasedir() */
> > > +
> > >  	fh = lo_add_dirp_mapping(req, d);
> > >  	if (fh == -1)
> > >  		goto out_err;
> > > @@ -1363,7 +1385,7 @@ static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
> > >  			  off_t offset, struct fuse_file_info *fi, int plus)
> > >  {
> > >  	struct lo_data *lo = lo_data(req);
> > > -	struct lo_dirp *d;
> > > +	struct lo_dirp *d = NULL;
> > >  	struct lo_inode *dinode;
> > >  	char *buf = NULL;
> > >  	char *p;
> > > @@ -1451,6 +1473,8 @@ static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
> > >  
> > >      err = 0;
> > >  error:
> > > +    lo_dirp_put(&d);
> > > +
> > >      // If there's an error, we can only signal it if we haven't stored
> > >      // any entries yet - otherwise we'd end up with wrong lookup
> > >      // counts for the entries that are already in the buffer. So we
> > > @@ -1477,22 +1501,25 @@ static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
> > >  static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
> > >  {
> > >  	struct lo_data *lo = lo_data(req);
> > > +	struct lo_map_elem *elem;
> > >  	struct lo_dirp *d;
> > >  
> > >  	(void) ino;
> > >  
> > > -	d = lo_dirp(req, fi);
> > > -	if (!d) {
> > > +	pthread_mutex_lock(&lo->mutex);
> > > +	elem = lo_map_get(&lo->dirp_map, fi->fh);
> > > +	if (!elem) {
> > > +		pthread_mutex_unlock(&lo->mutex);
> > >  		fuse_reply_err(req, EBADF);
> > >  		return;
> > >  	}
> > >  
> > > -	pthread_mutex_lock(&lo->mutex);
> > > +	d = elem->dirp;
> > >  	lo_map_remove(&lo->dirp_map, fi->fh);
> > >  	pthread_mutex_unlock(&lo->mutex);
> > >  
> > > -	closedir(d->dp);
> > > -	free(d);
> > > +	lo_dirp_put(&d); /* paired with lo_opendir() */
> > 
> > Is the &d really what's intended? That's the local stack variable, so
> > lo_dirp_put will set that local to NULL rather than the elem->dirp wont
> > it?
> 
> Yes, the put(&ptr) pattern prevents user-after-free in the caller.  It's
> slightly safer than put(ptr) since that leaves ptr initialized and the
> caller might access it later by accident.
> 
> elem has already been returned to the freelist by lo_map_remove() and we
> must not touch it anymore.

OK, thanks.

> Stefan


--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
diff mbox series

Patch

diff --git a/contrib/virtiofsd/passthrough_ll.c b/contrib/virtiofsd/passthrough_ll.c
index ad3abdd532..f74e7d2d21 100644
--- a/contrib/virtiofsd/passthrough_ll.c
+++ b/contrib/virtiofsd/passthrough_ll.c
@@ -1293,11 +1293,28 @@  static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
 }
 
 struct lo_dirp {
+	gint refcount;
 	DIR *dp;
 	struct dirent *entry;
 	off_t offset;
 };
 
+static void lo_dirp_put(struct lo_dirp **dp)
+{
+	struct lo_dirp *d = *dp;
+
+	if (!d) {
+		return;
+	}
+	*dp = NULL;
+
+	if (g_atomic_int_dec_and_test(&d->refcount)) {
+		closedir(d->dp);
+		free(d);
+	}
+}
+
+/* Call lo_dirp_put() on the return value when no longer needed */
 static struct lo_dirp *lo_dirp(fuse_req_t req, struct fuse_file_info *fi)
 {
 	struct lo_data *lo = lo_data(req);
@@ -1305,6 +1322,9 @@  static struct lo_dirp *lo_dirp(fuse_req_t req, struct fuse_file_info *fi)
 
 	pthread_mutex_lock(&lo->mutex);
 	elem = lo_map_get(&lo->dirp_map, fi->fh);
+	if (elem) {
+		g_atomic_int_inc(&elem->dirp->refcount);
+	}
 	pthread_mutex_unlock(&lo->mutex);
 	if (!elem)
 		return NULL;
@@ -1335,6 +1355,8 @@  static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi
 	d->offset = 0;
 	d->entry = NULL;
 
+	g_atomic_int_set(&d->refcount, 1); /* paired with lo_releasedir() */
+
 	fh = lo_add_dirp_mapping(req, d);
 	if (fh == -1)
 		goto out_err;
@@ -1363,7 +1385,7 @@  static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
 			  off_t offset, struct fuse_file_info *fi, int plus)
 {
 	struct lo_data *lo = lo_data(req);
-	struct lo_dirp *d;
+	struct lo_dirp *d = NULL;
 	struct lo_inode *dinode;
 	char *buf = NULL;
 	char *p;
@@ -1451,6 +1473,8 @@  static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
 
     err = 0;
 error:
+    lo_dirp_put(&d);
+
     // If there's an error, we can only signal it if we haven't stored
     // any entries yet - otherwise we'd end up with wrong lookup
     // counts for the entries that are already in the buffer. So we
@@ -1477,22 +1501,25 @@  static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
 static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
 {
 	struct lo_data *lo = lo_data(req);
+	struct lo_map_elem *elem;
 	struct lo_dirp *d;
 
 	(void) ino;
 
-	d = lo_dirp(req, fi);
-	if (!d) {
+	pthread_mutex_lock(&lo->mutex);
+	elem = lo_map_get(&lo->dirp_map, fi->fh);
+	if (!elem) {
+		pthread_mutex_unlock(&lo->mutex);
 		fuse_reply_err(req, EBADF);
 		return;
 	}
 
-	pthread_mutex_lock(&lo->mutex);
+	d = elem->dirp;
 	lo_map_remove(&lo->dirp_map, fi->fh);
 	pthread_mutex_unlock(&lo->mutex);
 
-	closedir(d->dp);
-	free(d);
+	lo_dirp_put(&d); /* paired with lo_opendir() */
+
 	fuse_reply_err(req, 0);
 }
 
@@ -1701,6 +1728,9 @@  static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
 		res = fdatasync(fd);
 	else
 		res = fsync(fd);
+
+	lo_dirp_put(&d);
+
 	fuse_reply_err(req, res == -1 ? errno : 0);
 }