LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
From: Desmond Cheong Zhi Xi <desmondcheongzx@gmail.com>
To: maarten.lankhorst@linux.intel.com, mripard@kernel.org,
	tzimmermann@suse.de, airlied@linux.ie, daniel@ffwll.ch,
	sumit.semwal@linaro.org, christian.koenig@amd.com,
	axboe@kernel.dk, oleg@redhat.com, tglx@linutronix.de,
	dvyukov@google.com, walter-zh.wu@mediatek.com
Cc: Desmond Cheong Zhi Xi <desmondcheongzx@gmail.com>,
	dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org,
	intel-gfx@lists.freedesktop.org, linux-media@vger.kernel.org,
	linaro-mm-sig@lists.linaro.org, skhan@linuxfoundation.org,
	gregkh@linuxfoundation.org,
	linux-kernel-mentees@lists.linuxfoundation.org,
	Daniel Vetter <daniel.vetter@ffwll.ch>
Subject: [PATCH v3 9/9] drm: avoid races with modesetting rights
Date: Wed, 18 Aug 2021 15:38:24 +0800	[thread overview]
Message-ID: <20210818073824.1560124-10-desmondcheongzx@gmail.com> (raw)
In-Reply-To: <20210818073824.1560124-1-desmondcheongzx@gmail.com>

In drm_client_modeset.c and drm_fb_helper.c,
drm_master_internal_{acquire,release} are used to avoid races with DRM
userspace. These functions hold onto drm_device.master_rwsem while
committing, and bail if there's already a master.

However, there are other places where modesetting rights can race. A
time-of-check-to-time-of-use error can occur if an ioctl that changes
the modeset has its rights revoked after it validates its permissions,
but before it completes.

There are four places where modesetting permissions can change:

- DROP_MASTER ioctl removes rights for a master and its leases

- REVOKE_LEASE ioctl revokes rights for a specific lease

- SET_MASTER ioctl sets the device master if the master role hasn't
been acquired yet

- drm_open which can create a new master for a device if one does not
currently exist

These races can be avoided using drm_device.master_rwsem: users that
perform modesetting should hold a read lock on the new
drm_device.master_rwsem, and users that change these permissions
should either hold a write lock, or should flush readers before
returning to userspace.

Reported-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Desmond Cheong Zhi Xi <desmondcheongzx@gmail.com>
---
 drivers/gpu/drm/drm_auth.c     | 29 +++++++++++++++++++++++++++++
 drivers/gpu/drm/drm_internal.h |  1 +
 drivers/gpu/drm/drm_ioctl.c    |  8 ++++++--
 drivers/gpu/drm/drm_lease.c    |  1 +
 include/drm/drm_device.h       | 10 +++++++++-
 5 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index b65681ff42fc..84d00275ff8a 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -29,6 +29,7 @@
  */
 
 #include <linux/slab.h>
+#include <linux/task_work.h>
 
 #include <drm/drm_auth.h>
 #include <drm/drm_drv.h>
@@ -127,6 +128,7 @@ int drm_authmagic(struct drm_device *dev, void *data,
 
 	DRM_DEBUG("%u\n", auth->magic);
 
+	lockdep_assert_held_once(&dev->master_rwsem);
 	spin_lock(&dev->master_lookup_lock);
 	file = idr_find(&file_priv->master->magic_map, auth->magic);
 	if (file) {
@@ -485,3 +487,30 @@ void drm_master_internal_release(struct drm_device *dev)
 	up_read(&dev->master_rwsem);
 }
 EXPORT_SYMBOL(drm_master_internal_release);
+
+/* After flushing, all readers that might have seen old master/lease
+ * permissions are guaranteed to have completed.
+ */
+static void master_flush(struct callback_head *cb)
+{
+	struct drm_device *dev = container_of(cb, struct drm_device,
+					      master_flush_work);
+
+	down_write(&dev->master_rwsem);
+	up_write(&dev->master_rwsem);
+}
+
+/* Queues up work to flush all readers of master/lease permissions. This work
+ * is run before this task returns to user mode. Calling this function when a
+ * task changes modesetting rights ensures that other processes that perform
+ * modesetting do not race with userspace.
+ */
+void drm_master_flush(struct drm_device *dev)
+{
+	init_task_work(&dev->master_flush_work, master_flush);
+	task_work_add(current, &dev->master_flush_work, TWA_RESUME);
+	/* If task_work_add fails, then the task is exiting. In this case, it
+	 * doesn't matter if master_flush is run, so we don't need an
+	 * alternative mechanism for flushing.
+	 */
+}
diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h
index 17f3548c8ed2..b1cd39338756 100644
--- a/drivers/gpu/drm/drm_internal.h
+++ b/drivers/gpu/drm/drm_internal.h
@@ -144,6 +144,7 @@ int drm_master_open(struct drm_file *file_priv);
 void drm_master_release(struct drm_file *file_priv);
 bool drm_master_internal_acquire(struct drm_device *dev);
 void drm_master_internal_release(struct drm_device *dev);
+void drm_master_flush(struct drm_device *dev);
 
 /* drm_sysfs.c */
 extern struct class *drm_class;
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 2cb57378a787..7f523e1c5650 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -390,7 +390,7 @@ static int drm_setversion(struct drm_device *dev, void *data, struct drm_file *f
 	struct drm_set_version *sv = data;
 	int if_version, retcode = 0;
 
-	down_read(&dev->master_rwsem);
+	lockdep_assert_held_once(&dev->master_rwsem);
 	if (sv->drm_di_major != -1) {
 		if (sv->drm_di_major != DRM_IF_MAJOR ||
 		    sv->drm_di_minor < 0 || sv->drm_di_minor > DRM_IF_MINOR) {
@@ -427,7 +427,6 @@ static int drm_setversion(struct drm_device *dev, void *data, struct drm_file *f
 	sv->drm_di_minor = DRM_IF_MINOR;
 	sv->drm_dd_major = dev->driver->major;
 	sv->drm_dd_minor = dev->driver->minor;
-	up_read(&dev->master_rwsem);
 
 	return retcode;
 }
@@ -783,6 +782,9 @@ long drm_ioctl_kernel(struct file *file, drm_ioctl_t *func, void *kdata,
 	if (unlikely(drm_dev_needs_global_mutex(dev)) && !(flags & DRM_UNLOCKED))
 		mutex_lock(&drm_global_mutex);
 
+	if (unlikely(flags & DRM_MASTER))
+		down_read(&dev->master_rwsem);
+
 	retcode = drm_ioctl_permit(flags, file_priv);
 	if (unlikely(retcode))
 		goto out;
@@ -790,6 +792,8 @@ long drm_ioctl_kernel(struct file *file, drm_ioctl_t *func, void *kdata,
 	retcode = func(dev, kdata, file_priv);
 
 out:
+	if (unlikely(flags & DRM_MASTER))
+		up_read(&dev->master_rwsem);
 	if (unlikely(drm_dev_needs_global_mutex(dev)) && !(flags & DRM_UNLOCKED))
 		mutex_unlock(&drm_global_mutex);
 	return retcode;
diff --git a/drivers/gpu/drm/drm_lease.c b/drivers/gpu/drm/drm_lease.c
index dee4f24a1808..983701198ffd 100644
--- a/drivers/gpu/drm/drm_lease.c
+++ b/drivers/gpu/drm/drm_lease.c
@@ -723,6 +723,7 @@ int drm_mode_revoke_lease_ioctl(struct drm_device *dev,
 	}
 
 	_drm_lease_revoke(lessee);
+	drm_master_flush(dev);
 
 fail:
 	mutex_unlock(&dev->mode_config.idr_mutex);
diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h
index f1ae4570a20a..617f7fe1d3bf 100644
--- a/include/drm/drm_device.h
+++ b/include/drm/drm_device.h
@@ -151,10 +151,18 @@ struct drm_device {
 	 * Synchronizes modesetting rights between multiple users. Users that
 	 * can change the modeset or display state must hold a read lock on
 	 * @master_rwsem, and users that change modesetting rights should hold
-	 * a write lock.
+	 * a write lock or flush readers before returning to userspace.
 	 */
 	struct rw_semaphore master_rwsem;
 
+	/**
+	 * @master_flush_work:
+	 *
+	 * Callback structure used internally to queue work to flush readers of
+	 * master/lease permissions.
+	 */
+	struct callback_head master_flush_work;
+
 	/**
 	 * @master_lookup_lock:
 	 *
-- 
2.25.1


      parent reply	other threads:[~2021-08-18  7:41 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-08-18  7:38 [PATCH v3 0/9] drm, kernel: update locking for DRM Desmond Cheong Zhi Xi
2021-08-18  7:38 ` [PATCH v3 1/9] drm: move master_lookup_lock into drm_device Desmond Cheong Zhi Xi
2021-08-18  7:38 ` [PATCH v3 2/9] drm: hold master_lookup_lock when releasing a drm_file's master Desmond Cheong Zhi Xi
2021-08-18 10:05   ` Daniel Vetter
2021-08-18 14:50     ` Desmond Cheong Zhi Xi
2021-08-18  7:38 ` [PATCH v3 3/9] drm: check for null master in drm_is_current_master_locked Desmond Cheong Zhi Xi
2021-08-18 10:04   ` Daniel Vetter
2021-08-18  7:38 ` [PATCH v3 4/9] drm: fix potential null ptr dereferences in drm_{auth,ioctl} Desmond Cheong Zhi Xi
2021-08-18 10:11   ` Daniel Vetter
2021-08-18 15:37     ` Desmond Cheong Zhi Xi
2021-08-18 16:33       ` [Intel-gfx] [PATCH v3 4/9] drm: fix potential null ptr dereferences in drm_{auth, ioctl} Daniel Vetter
2021-08-19  5:31         ` Desmond Cheong Zhi Xi
2021-08-18  7:38 ` [PATCH v3 5/9] drm: protect magic_map,unique{_len} with master_lookup_lock Desmond Cheong Zhi Xi
2021-08-18 10:43   ` Daniel Vetter
2021-08-18  7:38 ` [PATCH v3 6/9] drm: convert drm_device.master_mutex into a rwsem Desmond Cheong Zhi Xi
2021-08-18  7:38 ` [PATCH v3 7/9] drm: update global mutex lock in the ioctl handler Desmond Cheong Zhi Xi
2021-08-18 11:02   ` Daniel Vetter
2021-08-19 10:52     ` Desmond Cheong Zhi Xi
2021-08-19 11:21       ` [Intel-gfx] " Daniel Vetter
2021-08-18  7:38 ` [PATCH v3 8/9] kernel: export task_work_add Desmond Cheong Zhi Xi
2021-08-18 11:06   ` Daniel Vetter
2021-08-19  9:26   ` Christoph Hellwig
2021-08-19  9:40     ` Desmond Cheong Zhi Xi
2021-08-18  7:38 ` Desmond Cheong Zhi Xi [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210818073824.1560124-10-desmondcheongzx@gmail.com \
    --to=desmondcheongzx@gmail.com \
    --cc=airlied@linux.ie \
    --cc=axboe@kernel.dk \
    --cc=christian.koenig@amd.com \
    --cc=daniel.vetter@ffwll.ch \
    --cc=daniel@ffwll.ch \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=dvyukov@google.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=intel-gfx@lists.freedesktop.org \
    --cc=linaro-mm-sig@lists.linaro.org \
    --cc=linux-kernel-mentees@lists.linuxfoundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=maarten.lankhorst@linux.intel.com \
    --cc=mripard@kernel.org \
    --cc=oleg@redhat.com \
    --cc=skhan@linuxfoundation.org \
    --cc=sumit.semwal@linaro.org \
    --cc=tglx@linutronix.de \
    --cc=tzimmermann@suse.de \
    --cc=walter-zh.wu@mediatek.com \
    --subject='Re: [PATCH v3 9/9] drm: avoid races with modesetting rights' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).