Re: [Intel-gfx] [PATCH v10 0/4] drm: update locking for modesetting

2021-09-07 Thread Desmond Cheong Zhi Xi

On 31/8/21 3:24 am, Desmond Cheong Zhi Xi wrote:

Sorry for the noise, rebasing on top of drm-misc-next. Please ignore the
v9 series.

Hi,

I updated the patch set with some suggestions by Daniel Vetter, and
dropped the patches after patch 4 so that we can stick the landing for
avoiding races with modesetting rights before dealing with the tricky
spinlock.

Overall, this series fixes races with modesetting rights, and converts
drm_device.master_mutex into master_rwsem.

- Patch 1: Fix a potential null ptr dereference in drm_master_release

- Patch 2: Convert master_mutex into rwsem (avoids creating a new lock)

- Patch 3: Update global mutex locking in the ioctl handler (avoids
deadlock when grabbing read lock on master_rwsem in drm_ioctl_kernel)

- Patch 4: Plug races with drm modesetting rights

v9 -> v10:
- Rebase on top of drm-misc-next, caught by Intel-gfx CI

v8 -> v9 (suggested by Daniel Vetter):
- Drop patches 5-7 to handle it in another series
- Add the appropriate Fixes: tag for the null ptr dereference fix
(patch 1)
- Create a locked_ioctl bool to clarify locking/unlocking patterns in
the ioctl handler (patch 3)
- Clarify the kernel doc for master_rwsem (patch 4)

v7 -> v8:
- Avoid calling drm_lease_held in drm_mode_setcrtc and
drm_wait_vblank_ioctl, caught by Intel-gfx CI

v6 -> v7:
- Export __drm_mode_object_find for loadable modules, caught by the
Intel-gfx CI

v5 -> v6:
- Fix recursive locking on master_rwsem, caught by the Intel-gfx CI

v4 -> v5:
- Avoid calling drm_file_get_master while holding on to the modeset
mutex, caught by the Intel-gfx CI

v3 -> v4 (suggested by Daniel Vetter):
- Drop a patch that added an unnecessary master_lookup_lock in
drm_master_release
- Drop a patch that addressed a non-existent race in
drm_is_current_master_locked
- Remove fixes for non-existent null ptr dereferences
- Protect drm_master.magic_map,unique{_len} with master_rwsem instead of
master_lookup_lock
- Drop the patch that moved master_lookup_lock into struct drm_device
- Drop a patch to export task_work_add
- Revert the check for the global mutex in the ioctl handler to use
drm_core_check_feature instead of drm_dev_needs_global_mutex
- Push down master_rwsem locking for selected ioctls to avoid lock
hierarchy inversions, and to allow us to hold write locks on
master_rwsem instead of flushing readers
- Remove master_lookup_lock by replacing it with master_rwsem

v2 -> v3:
- Unexport drm_master_flush, as suggested by Daniel Vetter.
- Merge master_mutex and master_rwsem, as suggested by Daniel Vetter.
- Export task_work_add, reported by kernel test robot.
- Make master_flush static, reported by kernel test robot.
- Move master_lookup_lock into struct drm_device.
- Add a missing lock on master_lookup_lock in drm_master_release.
- Fix a potential race in drm_is_current_master_locked.
- Fix potential null ptr dereferences in drm_{auth, ioctl}.
- Protect magic_map,unique{_len} with  master_lookup_lock.
- Convert master_mutex into a rwsem.
- Update global mutex locking in the ioctl handler.

v1 -> v2 (suggested by Daniel Vetter):
- Address an additional race when drm_open runs.
- Switch from SRCU to rwsem to synchronise readers and writers.
- Implement drm_master_flush with task_work so that flushes can be
queued to run before returning to userspace without creating a new
DRM_MASTER_FLUSH ioctl flag.

Best wishes,
Desmond

Desmond Cheong Zhi Xi (4):
   drm: fix null ptr dereference in drm_master_release
   drm: convert drm_device.master_mutex into a rwsem
   drm: lock drm_global_mutex earlier in the ioctl handler
   drm: avoid races with modesetting rights

  drivers/gpu/drm/drm_auth.c| 39 
  drivers/gpu/drm/drm_debugfs.c |  4 +--
  drivers/gpu/drm/drm_drv.c |  3 +--
  drivers/gpu/drm/drm_file.c|  6 ++---
  drivers/gpu/drm/drm_ioctl.c   | 49 ++-
  drivers/gpu/drm/drm_lease.c   | 35 +
  include/drm/drm_auth.h|  6 ++---
  include/drm/drm_device.h  | 16 +---
  include/drm/drm_file.h| 12 -
  9 files changed, 104 insertions(+), 66 deletions(-)



Hi Daniel,

Just pinging so this doesn't get buried, though I guess it's also a busy 
merge window. Any thoughts on the series as it is? Tests seemed to have 
passed with the Intel-gfx CI [1].


Not sure if I can set up the CI to do otherwise, but I think this series 
has to go in before I can test new patches to remove the 
drm_file.master_lookup_lock spinlock.


As always, thank you for your time.

Link: https://patchwork.freedesktop.org/series/93864/ [1]

Best wishes,
Desmond


[Intel-gfx] [PATCH v10 4/4] drm: avoid races with modesetting rights

2021-08-31 Thread Desmond Cheong Zhi Xi
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 hold a write lock.

To avoid deadlocks with master_rwsem, for ioctls that need to check
for modesetting permissions, but also need to hold a write lock on
master_rwsem to protect some other attribute (or recurses to some
function that holds a write lock, like drm_mode_create_lease_ioctl
which eventually calls drm_master_open), we remove the DRM_MASTER flag
and push the master_rwsem lock and permissions check into the ioctl.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
Reviewed-by: Daniel Vetter 
---
 drivers/gpu/drm/drm_auth.c  |  4 
 drivers/gpu/drm/drm_ioctl.c | 20 +++-
 drivers/gpu/drm/drm_lease.c | 35 ---
 include/drm/drm_device.h|  6 ++
 4 files changed, 49 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 73ade0513ccb..65065f7e1499 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -120,6 +120,10 @@ int drm_authmagic(struct drm_device *dev, void *data,
DRM_DEBUG("%u\n", auth->magic);
 
down_write(&dev->master_rwsem);
+   if (unlikely(!drm_is_current_master(file_priv))) {
+   up_write(&dev->master_rwsem);
+   return -EACCES;
+   }
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index fe9c4c0264a9..f6a8aa6c53b3 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -386,6 +386,10 @@ static int drm_setversion(struct drm_device *dev, void 
*data, struct drm_file *f
int if_version, retcode = 0;
 
down_write(&dev->master_rwsem);
+   if (unlikely(!drm_is_current_master(file_priv))) {
+   retcode = -EACCES;
+   goto unlock;
+   }
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) {
@@ -420,8 +424,9 @@ 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_write(&dev->master_rwsem);
 
+unlock:
+   up_write(&dev->master_rwsem);
return retcode;
 }
 
@@ -574,12 +579,12 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
DRM_IOCTL_DEF(DRM_IOCTL_GET_STATS, drm_getstats, 0),
DRM_IOCTL_DEF(DRM_IOCTL_GET_CAP, drm_getcap, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_SET_CLIENT_CAP, drm_setclientcap, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, 0),
 
DRM_IOCTL_DEF(DRM_IOCTL_SET_UNIQUE, drm_invalid_op, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_BLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_UNBLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
-   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, 0),
 
DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_ADD_MAP, drm_legacy_addmap_ioctl, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_RM_MAP, drm_legacy_rmmap_ioctl, 
DRM_AUTH),
@@ -706,10 +711,10 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
  DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_CRTC_GET_SEQUENCE, drm_crtc_get_sequence_ioctl, 
0),
DRM_IOCTL_DEF(DRM_IOCTL_CRTC_QUEUE_SEQUENCE, 
drm_crtc_queue_sequence_ioctl, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_LEASE, drm_mode_create_lease_ioctl, 
DRM_MASTER),
+   DRM_IOCTL_DEF

[Intel-gfx] [PATCH v10 3/4] drm: lock drm_global_mutex earlier in the ioctl handler

2021-08-31 Thread Desmond Cheong Zhi Xi
In a future patch, a read lock on drm_device.master_rwsem is
held in the ioctl handler before the check for ioctl
permissions. However, this inverts the lock hierarchy of
drm_global_mutex --> master_rwsem.

To avoid this, we do some prep work to grab the drm_global_mutex
before checking for ioctl permissions.

Signed-off-by: Desmond Cheong Zhi Xi 
Reviewed-by: Daniel Vetter 
---
 drivers/gpu/drm/drm_ioctl.c | 21 -
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 9fc00e36c5d6..fe9c4c0264a9 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -767,24 +767,27 @@ long drm_ioctl_kernel(struct file *file, drm_ioctl_t 
*func, void *kdata,
 {
struct drm_file *file_priv = file->private_data;
struct drm_device *dev = file_priv->minor->dev;
+   bool locked_ioctl;
int retcode;
 
if (drm_dev_is_unplugged(dev))
return -ENODEV;
 
+   /* Enforce sane locking for modern driver ioctls. */
+   locked_ioctl = (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY)) &&
+   !(flags & DRM_UNLOCKED));
+   if (locked_ioctl)
+   mutex_lock(&drm_global_mutex);
+
retcode = drm_ioctl_permit(flags, file_priv);
if (unlikely(retcode))
-   return retcode;
+   goto out;
 
-   /* Enforce sane locking for modern driver ioctls. */
-   if (likely(!drm_core_check_feature(dev, DRIVER_LEGACY)) ||
-   (flags & DRM_UNLOCKED))
-   retcode = func(dev, kdata, file_priv);
-   else {
-   mutex_lock(&drm_global_mutex);
-   retcode = func(dev, kdata, file_priv);
+   retcode = func(dev, kdata, file_priv);
+
+out:
+   if (locked_ioctl)
mutex_unlock(&drm_global_mutex);
-   }
return retcode;
 }
 EXPORT_SYMBOL(drm_ioctl_kernel);
-- 
2.25.1



[Intel-gfx] [PATCH v10 2/4] drm: convert drm_device.master_mutex into a rwsem

2021-08-31 Thread Desmond Cheong Zhi Xi
drm_device.master_mutex currently protects the following:
- drm_device.master
- drm_file.master
- drm_file.was_master
- drm_file.is_master
- drm_master.unique
- drm_master.unique_len
- drm_master.magic_map

There is a clear separation between functions that read or change
these attributes. Hence, convert master_mutex into a rwsem to enable
concurrent readers.

Signed-off-by: Desmond Cheong Zhi Xi 
Reviewed-by: Daniel Vetter 
---
 drivers/gpu/drm/drm_auth.c| 35 ++-
 drivers/gpu/drm/drm_debugfs.c |  4 ++--
 drivers/gpu/drm/drm_drv.c |  3 +--
 drivers/gpu/drm/drm_ioctl.c   | 10 +-
 include/drm/drm_auth.h|  6 +++---
 include/drm/drm_device.h  | 10 ++
 include/drm/drm_file.h| 12 ++--
 7 files changed, 41 insertions(+), 39 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 60a6b21474b1..73ade0513ccb 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -64,7 +64,7 @@
 static bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
-   lockdep_is_held(&fpriv->minor->dev->master_mutex));
+   lockdep_is_held(&fpriv->minor->dev->master_rwsem));
 
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
@@ -96,7 +96,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
struct drm_auth *auth = data;
int ret = 0;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!file_priv->magic) {
ret = idr_alloc(&file_priv->master->magic_map, file_priv,
1, 0, GFP_KERNEL);
@@ -104,7 +104,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
file_priv->magic = ret;
}
auth->magic = file_priv->magic;
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
DRM_DEBUG("%u\n", auth->magic);
 
@@ -119,13 +119,13 @@ int drm_authmagic(struct drm_device *dev, void *data,
 
DRM_DEBUG("%u\n", auth->magic);
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
idr_replace(&file_priv->master->magic_map, NULL, auth->magic);
}
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
return file ? 0 : -EINVAL;
 }
@@ -167,7 +167,7 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
struct drm_master *old_master;
struct drm_master *new_master;
 
-   lockdep_assert_held_once(&dev->master_mutex);
+   lockdep_assert_held_once(&dev->master_rwsem);
 
WARN_ON(fpriv->is_master);
old_master = fpriv->master;
@@ -249,7 +249,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -281,7 +281,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 
drm_set_master(dev, file_priv, false);
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -298,7 +298,7 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -321,8 +321,9 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
}
 
drm_drop_master(dev, file_priv);
+
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -334,7 +335,7 @@ int drm_master_open(struct drm_file *file_priv)
/* if there is no current master make this fd it, but do not create
 * any master object for render clients
 */
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
} else {
@@ -342,7 +343,7 @@ int drm_master_open(struct drm_file *file_priv)
file_priv->master = drm_master_get(dev->master);
spin_unlock(&file_priv->master_lookup_lock);
}
-   mutex_unlock(&dev->master_mutex);
+  

[Intel-gfx] [PATCH v10 1/4] drm: fix null ptr dereference in drm_master_release

2021-08-31 Thread Desmond Cheong Zhi Xi
drm_master_release can be called on a drm_file without a master, which
results in a null ptr dereference of file_priv->master->magic_map. The
three cases are:

1. Error path in drm_open_helper
  drm_open():
drm_open_helper():
  drm_master_open():
drm_new_set_master(); <--- returns -ENOMEM,
   drm_file.master not set
  drm_file_free():
drm_master_release(); <--- NULL ptr dereference
   (file_priv->master->magic_map)

2. Error path in mock_drm_getfile
  mock_drm_getfile():
anon_inode_getfile(); <--- returns error, drm_file.master not set
drm_file_free():
  drm_master_release(); <--- NULL ptr dereference
 (file_priv->master->magic_map)

3. In drm_client_close, as drm_client_open doesn't set up a master

drm_file.master is set up in drm_open_helper through the call to
drm_master_open, so we mirror it with a call to drm_master_release in
drm_close_helper, and remove drm_master_release from drm_file_free to
avoid the null ptr dereference.

Fixes: 7eeaeb90a6a5 ("drm/file: Don't set master on in-kernel clients")
Signed-off-by: Desmond Cheong Zhi Xi 
Cc: sta...@vger.kernel.org
Reviewed-by: Daniel Vetter 
---
 drivers/gpu/drm/drm_file.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index ed25168619fc..90b62f360da1 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -282,9 +282,6 @@ void drm_file_free(struct drm_file *file)
 
drm_legacy_ctxbitmap_flush(dev, file);
 
-   if (drm_is_primary_client(file))
-   drm_master_release(file);
-
if (dev->driver->postclose)
dev->driver->postclose(dev, file);
 
@@ -305,6 +302,9 @@ static void drm_close_helper(struct file *filp)
list_del(&file_priv->lhead);
mutex_unlock(&dev->filelist_mutex);
 
+   if (drm_is_primary_client(file_priv))
+   drm_master_release(file_priv);
+
drm_file_free(file_priv);
 }
 
-- 
2.25.1



[Intel-gfx] [PATCH v10 0/4] drm: update locking for modesetting

2021-08-31 Thread Desmond Cheong Zhi Xi
Sorry for the noise, rebasing on top of drm-misc-next. Please ignore the
v9 series.

Hi,

I updated the patch set with some suggestions by Daniel Vetter, and
dropped the patches after patch 4 so that we can stick the landing for
avoiding races with modesetting rights before dealing with the tricky
spinlock.

Overall, this series fixes races with modesetting rights, and converts
drm_device.master_mutex into master_rwsem.

- Patch 1: Fix a potential null ptr dereference in drm_master_release

- Patch 2: Convert master_mutex into rwsem (avoids creating a new lock)

- Patch 3: Update global mutex locking in the ioctl handler (avoids
deadlock when grabbing read lock on master_rwsem in drm_ioctl_kernel)

- Patch 4: Plug races with drm modesetting rights

v9 -> v10:
- Rebase on top of drm-misc-next, caught by Intel-gfx CI

v8 -> v9 (suggested by Daniel Vetter):
- Drop patches 5-7 to handle it in another series
- Add the appropriate Fixes: tag for the null ptr dereference fix
(patch 1)
- Create a locked_ioctl bool to clarify locking/unlocking patterns in
the ioctl handler (patch 3)
- Clarify the kernel doc for master_rwsem (patch 4)

v7 -> v8:
- Avoid calling drm_lease_held in drm_mode_setcrtc and
drm_wait_vblank_ioctl, caught by Intel-gfx CI

v6 -> v7:
- Export __drm_mode_object_find for loadable modules, caught by the
Intel-gfx CI

v5 -> v6:
- Fix recursive locking on master_rwsem, caught by the Intel-gfx CI

v4 -> v5:
- Avoid calling drm_file_get_master while holding on to the modeset
mutex, caught by the Intel-gfx CI

v3 -> v4 (suggested by Daniel Vetter):
- Drop a patch that added an unnecessary master_lookup_lock in
drm_master_release
- Drop a patch that addressed a non-existent race in
drm_is_current_master_locked
- Remove fixes for non-existent null ptr dereferences
- Protect drm_master.magic_map,unique{_len} with master_rwsem instead of
master_lookup_lock
- Drop the patch that moved master_lookup_lock into struct drm_device
- Drop a patch to export task_work_add
- Revert the check for the global mutex in the ioctl handler to use
drm_core_check_feature instead of drm_dev_needs_global_mutex
- Push down master_rwsem locking for selected ioctls to avoid lock
hierarchy inversions, and to allow us to hold write locks on
master_rwsem instead of flushing readers
- Remove master_lookup_lock by replacing it with master_rwsem

v2 -> v3:
- Unexport drm_master_flush, as suggested by Daniel Vetter.
- Merge master_mutex and master_rwsem, as suggested by Daniel Vetter.
- Export task_work_add, reported by kernel test robot.
- Make master_flush static, reported by kernel test robot.
- Move master_lookup_lock into struct drm_device.
- Add a missing lock on master_lookup_lock in drm_master_release.
- Fix a potential race in drm_is_current_master_locked.
- Fix potential null ptr dereferences in drm_{auth, ioctl}.
- Protect magic_map,unique{_len} with  master_lookup_lock.
- Convert master_mutex into a rwsem.
- Update global mutex locking in the ioctl handler.

v1 -> v2 (suggested by Daniel Vetter):
- Address an additional race when drm_open runs.
- Switch from SRCU to rwsem to synchronise readers and writers.
- Implement drm_master_flush with task_work so that flushes can be
queued to run before returning to userspace without creating a new
DRM_MASTER_FLUSH ioctl flag.

Best wishes,
Desmond

Desmond Cheong Zhi Xi (4):
  drm: fix null ptr dereference in drm_master_release
  drm: convert drm_device.master_mutex into a rwsem
  drm: lock drm_global_mutex earlier in the ioctl handler
  drm: avoid races with modesetting rights

 drivers/gpu/drm/drm_auth.c| 39 
 drivers/gpu/drm/drm_debugfs.c |  4 +--
 drivers/gpu/drm/drm_drv.c |  3 +--
 drivers/gpu/drm/drm_file.c|  6 ++---
 drivers/gpu/drm/drm_ioctl.c   | 49 ++-
 drivers/gpu/drm/drm_lease.c   | 35 +
 include/drm/drm_auth.h|  6 ++---
 include/drm/drm_device.h  | 16 +---
 include/drm/drm_file.h| 12 -
 9 files changed, 104 insertions(+), 66 deletions(-)

-- 
2.25.1



[Intel-gfx] [PATCH v9 4/4] drm: avoid races with modesetting rights

2021-08-30 Thread Desmond Cheong Zhi Xi
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 hold a write lock.

To avoid deadlocks with master_rwsem, for ioctls that need to check
for modesetting permissions, but also need to hold a write lock on
master_rwsem to protect some other attribute (or recurses to some
function that holds a write lock, like drm_mode_create_lease_ioctl
which eventually calls drm_master_open), we remove the DRM_MASTER flag
and push the master_rwsem lock and permissions check into the ioctl.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
Reviewed-by: Daniel Vetter 
---
 drivers/gpu/drm/drm_auth.c  |  4 
 drivers/gpu/drm/drm_ioctl.c | 20 +++-
 drivers/gpu/drm/drm_lease.c | 35 ---
 include/drm/drm_device.h|  6 ++
 4 files changed, 49 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 73ade0513ccb..65065f7e1499 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -120,6 +120,10 @@ int drm_authmagic(struct drm_device *dev, void *data,
DRM_DEBUG("%u\n", auth->magic);
 
down_write(&dev->master_rwsem);
+   if (unlikely(!drm_is_current_master(file_priv))) {
+   up_write(&dev->master_rwsem);
+   return -EACCES;
+   }
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 59c5aa850dd5..96ae6b661c0a 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -386,6 +386,10 @@ static int drm_setversion(struct drm_device *dev, void 
*data, struct drm_file *f
int if_version, retcode = 0;
 
down_write(&dev->master_rwsem);
+   if (unlikely(!drm_is_current_master(file_priv))) {
+   retcode = -EACCES;
+   goto unlock;
+   }
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) {
@@ -420,8 +424,9 @@ 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_write(&dev->master_rwsem);
 
+unlock:
+   up_write(&dev->master_rwsem);
return retcode;
 }
 
@@ -574,12 +579,12 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
DRM_IOCTL_DEF(DRM_IOCTL_GET_STATS, drm_getstats, 0),
DRM_IOCTL_DEF(DRM_IOCTL_GET_CAP, drm_getcap, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_SET_CLIENT_CAP, drm_setclientcap, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, 0),
 
DRM_IOCTL_DEF(DRM_IOCTL_SET_UNIQUE, drm_invalid_op, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_BLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_UNBLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
-   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, 0),
 
DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_ADD_MAP, drm_legacy_addmap_ioctl, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_RM_MAP, drm_legacy_rmmap_ioctl, 
DRM_AUTH),
@@ -706,10 +711,10 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
  DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_CRTC_GET_SEQUENCE, drm_crtc_get_sequence_ioctl, 
0),
DRM_IOCTL_DEF(DRM_IOCTL_CRTC_QUEUE_SEQUENCE, 
drm_crtc_queue_sequence_ioctl, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_LEASE, drm_mode_create_lease_ioctl, 
DRM_MASTER),
+   DRM_IOCTL_DEF

[Intel-gfx] [PATCH v9 3/4] drm: lock drm_global_mutex earlier in the ioctl handler

2021-08-30 Thread Desmond Cheong Zhi Xi
In a future patch, a read lock on drm_device.master_rwsem is
held in the ioctl handler before the check for ioctl
permissions. However, this inverts the lock hierarchy of
drm_global_mutex --> master_rwsem.

To avoid this, we do some prep work to grab the drm_global_mutex
before checking for ioctl permissions.

Signed-off-by: Desmond Cheong Zhi Xi 
Reviewed-by: Daniel Vetter 
---
 drivers/gpu/drm/drm_ioctl.c | 21 -
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index d25713b09b80..59c5aa850dd5 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -767,24 +767,27 @@ long drm_ioctl_kernel(struct file *file, drm_ioctl_t 
*func, void *kdata,
 {
struct drm_file *file_priv = file->private_data;
struct drm_device *dev = file_priv->minor->dev;
+   bool locked_ioctl;
int retcode;
 
if (drm_dev_is_unplugged(dev))
return -ENODEV;
 
+   /* Enforce sane locking for modern driver ioctls. */
+   locked_ioctl = (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY)) &&
+   !(flags & DRM_UNLOCKED));
+   if (locked_ioctl)
+   mutex_lock(&drm_global_mutex);
+
retcode = drm_ioctl_permit(flags, file_priv);
if (unlikely(retcode))
-   return retcode;
+   goto out;
 
-   /* Enforce sane locking for modern driver ioctls. */
-   if (likely(!drm_core_check_feature(dev, DRIVER_LEGACY)) ||
-   (flags & DRM_UNLOCKED))
-   retcode = func(dev, kdata, file_priv);
-   else {
-   mutex_lock(&drm_global_mutex);
-   retcode = func(dev, kdata, file_priv);
+   retcode = func(dev, kdata, file_priv);
+
+out:
+   if (locked_ioctl)
mutex_unlock(&drm_global_mutex);
-   }
return retcode;
 }
 EXPORT_SYMBOL(drm_ioctl_kernel);
-- 
2.25.1



[Intel-gfx] [PATCH v9 2/4] drm: convert drm_device.master_mutex into a rwsem

2021-08-30 Thread Desmond Cheong Zhi Xi
drm_device.master_mutex currently protects the following:
- drm_device.master
- drm_file.master
- drm_file.was_master
- drm_file.is_master
- drm_master.unique
- drm_master.unique_len
- drm_master.magic_map

There is a clear separation between functions that read or change
these attributes. Hence, convert master_mutex into a rwsem to enable
concurrent readers.

Signed-off-by: Desmond Cheong Zhi Xi 
Reviewed-by: Daniel Vetter 
---
 drivers/gpu/drm/drm_auth.c| 35 ++-
 drivers/gpu/drm/drm_debugfs.c |  4 ++--
 drivers/gpu/drm/drm_drv.c |  3 +--
 drivers/gpu/drm/drm_ioctl.c   | 10 +-
 include/drm/drm_auth.h|  6 +++---
 include/drm/drm_device.h  | 10 ++
 include/drm/drm_file.h| 12 ++--
 7 files changed, 41 insertions(+), 39 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 60a6b21474b1..73ade0513ccb 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -64,7 +64,7 @@
 static bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
-   lockdep_is_held(&fpriv->minor->dev->master_mutex));
+   lockdep_is_held(&fpriv->minor->dev->master_rwsem));
 
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
@@ -96,7 +96,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
struct drm_auth *auth = data;
int ret = 0;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!file_priv->magic) {
ret = idr_alloc(&file_priv->master->magic_map, file_priv,
1, 0, GFP_KERNEL);
@@ -104,7 +104,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
file_priv->magic = ret;
}
auth->magic = file_priv->magic;
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
DRM_DEBUG("%u\n", auth->magic);
 
@@ -119,13 +119,13 @@ int drm_authmagic(struct drm_device *dev, void *data,
 
DRM_DEBUG("%u\n", auth->magic);
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
idr_replace(&file_priv->master->magic_map, NULL, auth->magic);
}
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
return file ? 0 : -EINVAL;
 }
@@ -167,7 +167,7 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
struct drm_master *old_master;
struct drm_master *new_master;
 
-   lockdep_assert_held_once(&dev->master_mutex);
+   lockdep_assert_held_once(&dev->master_rwsem);
 
WARN_ON(fpriv->is_master);
old_master = fpriv->master;
@@ -249,7 +249,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -281,7 +281,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 
drm_set_master(dev, file_priv, false);
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -298,7 +298,7 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -321,8 +321,9 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
}
 
drm_drop_master(dev, file_priv);
+
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -334,7 +335,7 @@ int drm_master_open(struct drm_file *file_priv)
/* if there is no current master make this fd it, but do not create
 * any master object for render clients
 */
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
} else {
@@ -342,7 +343,7 @@ int drm_master_open(struct drm_file *file_priv)
file_priv->master = drm_master_get(dev->master);
spin_unlock(&file_priv->master_lookup_lock);
}
-   mutex_unlock(&dev->master_mutex);
+  

[Intel-gfx] [PATCH v9 1/4] drm: fix null ptr dereference in drm_master_release

2021-08-30 Thread Desmond Cheong Zhi Xi
drm_master_release can be called on a drm_file without a master, which
results in a null ptr dereference of file_priv->master->magic_map. The
three cases are:

1. Error path in drm_open_helper
  drm_open():
drm_open_helper():
  drm_master_open():
drm_new_set_master(); <--- returns -ENOMEM,
   drm_file.master not set
  drm_file_free():
drm_master_release(); <--- NULL ptr dereference
   (file_priv->master->magic_map)

2. Error path in mock_drm_getfile
  mock_drm_getfile():
anon_inode_getfile(); <--- returns error, drm_file.master not set
drm_file_free():
  drm_master_release(); <--- NULL ptr dereference
 (file_priv->master->magic_map)

3. In drm_client_close, as drm_client_open doesn't set up a master

drm_file.master is set up in drm_open_helper through the call to
drm_master_open, so we mirror it with a call to drm_master_release in
drm_close_helper, and remove drm_master_release from drm_file_free to
avoid the null ptr dereference.

Fixes: 7eeaeb90a6a5 ("drm/file: Don't set master on in-kernel clients")
Signed-off-by: Desmond Cheong Zhi Xi 
Cc: sta...@vger.kernel.org
Reviewed-by: Daniel Vetter 
---
 drivers/gpu/drm/drm_file.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index ed25168619fc..90b62f360da1 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -282,9 +282,6 @@ void drm_file_free(struct drm_file *file)
 
drm_legacy_ctxbitmap_flush(dev, file);
 
-   if (drm_is_primary_client(file))
-   drm_master_release(file);
-
if (dev->driver->postclose)
dev->driver->postclose(dev, file);
 
@@ -305,6 +302,9 @@ static void drm_close_helper(struct file *filp)
list_del(&file_priv->lhead);
mutex_unlock(&dev->filelist_mutex);
 
+   if (drm_is_primary_client(file_priv))
+   drm_master_release(file_priv);
+
drm_file_free(file_priv);
 }
 
-- 
2.25.1



[Intel-gfx] [PATCH v9 0/4] drm: update locking for modesetting

2021-08-30 Thread Desmond Cheong Zhi Xi
Hi,

I updated the patch set with some suggestions by Daniel Vetter, and
dropped the patches after patch 4 so that we can stick the landing for
avoiding races with modesetting rights before dealing with the tricky
spinlock.

Overall, this series fixes races with modesetting rights, and converts
drm_device.master_mutex into master_rwsem.

- Patch 1: Fix a potential null ptr dereference in drm_master_release

- Patch 2: Convert master_mutex into rwsem (avoids creating a new lock)

- Patch 3: Update global mutex locking in the ioctl handler (avoids
deadlock when grabbing read lock on master_rwsem in drm_ioctl_kernel)

- Patch 4: Plug races with drm modesetting rights

v8 -> v9 (suggested by Daniel Vetter):
- Dropped patches 5-7 to handle it in another series
- Added the appropriate Fixes: tag for the null ptr dereference fix
(patch 1)
- Create a locked_ioctl bool to clarify locking/unlocking patterns in
the ioctl handler (patch 3)
- Clarified the kernel doc for master_rwsem (patch 4)

v7 -> v8:
- Avoid calling drm_lease_held in drm_mode_setcrtc and
drm_wait_vblank_ioctl, caught by Intel-gfx CI

v6 -> v7:
- Export __drm_mode_object_find for loadable modules, caught by the
Intel-gfx CI

v5 -> v6:
- Fix recursive locking on master_rwsem, caught by the Intel-gfx CI

v4 -> v5:
- Avoid calling drm_file_get_master while holding on to the modeset
mutex, caught by the Intel-gfx CI

v3 -> v4 (suggested by Daniel Vetter):
- Drop a patch that added an unnecessary master_lookup_lock in
drm_master_release
- Drop a patch that addressed a non-existent race in
drm_is_current_master_locked
- Remove fixes for non-existent null ptr dereferences
- Protect drm_master.magic_map,unique{_len} with master_rwsem instead of
master_lookup_lock
- Drop the patch that moved master_lookup_lock into struct drm_device
- Drop a patch to export task_work_add
- Revert the check for the global mutex in the ioctl handler to use
drm_core_check_feature instead of drm_dev_needs_global_mutex
- Push down master_rwsem locking for selected ioctls to avoid lock
hierarchy inversions, and to allow us to hold write locks on
master_rwsem instead of flushing readers
- Remove master_lookup_lock by replacing it with master_rwsem

v2 -> v3:
- Unexport drm_master_flush, as suggested by Daniel Vetter.
- Merge master_mutex and master_rwsem, as suggested by Daniel Vetter.
- Export task_work_add, reported by kernel test robot.
- Make master_flush static, reported by kernel test robot.
- Move master_lookup_lock into struct drm_device.
- Add a missing lock on master_lookup_lock in drm_master_release.
- Fix a potential race in drm_is_current_master_locked.
- Fix potential null ptr dereferences in drm_{auth, ioctl}.
- Protect magic_map,unique{_len} with  master_lookup_lock.
- Convert master_mutex into a rwsem.
- Update global mutex locking in the ioctl handler.

v1 -> v2 (suggested by Daniel Vetter):
- Address an additional race when drm_open runs.
- Switch from SRCU to rwsem to synchronise readers and writers.
- Implement drm_master_flush with task_work so that flushes can be
queued to run before returning to userspace without creating a new
DRM_MASTER_FLUSH ioctl flag.

Best wishes,
Desmond

Desmond Cheong Zhi Xi (4):
  drm: fix null ptr dereference in drm_master_release
  drm: convert drm_device.master_mutex into a rwsem
  drm: lock drm_global_mutex earlier in the ioctl handler
  drm: avoid races with modesetting rights

 drivers/gpu/drm/drm_auth.c| 39 
 drivers/gpu/drm/drm_debugfs.c |  4 +--
 drivers/gpu/drm/drm_drv.c |  3 +--
 drivers/gpu/drm/drm_file.c|  6 ++---
 drivers/gpu/drm/drm_ioctl.c   | 49 ++-
 drivers/gpu/drm/drm_lease.c   | 35 +
 include/drm/drm_auth.h|  6 ++---
 include/drm/drm_device.h  | 16 +---
 include/drm/drm_file.h| 12 -
 9 files changed, 104 insertions(+), 66 deletions(-)

-- 
2.25.1



Re: [Intel-gfx] [PATCH v8 7/7] drm: remove drm_file.master_lookup_lock

2021-08-30 Thread Desmond Cheong Zhi Xi

On 26/8/21 9:21 pm, Daniel Vetter wrote:

On Thu, Aug 26, 2021 at 10:01:22AM +0800, Desmond Cheong Zhi Xi wrote:

Previously, master_lookup_lock was introduced in
commit 0b0860a3cf5e ("drm: serialize drm_file.master with a new
spinlock") to serialize accesses to drm_file.master. This then allowed
us to write drm_file_get_master in commit 56f0729a510f ("drm: protect
drm_master pointers in drm_lease.c").

The rationale behind introducing a new spinlock at the time was that
the other lock that could have been used (drm_device.master_mutex) was
the outermost lock, so embedding calls to drm_file_get_master and
drm_is_current_master in various functions easily caused us to invert
the lock hierarchy.

Following the conversion of master_mutex into a rwsem, and its use to
plug races with modesetting rights, we've untangled some lock
hierarchies and removed the need for using drm_file_get_master and the
unlocked version of drm_is_current_master in multiple places.

Hence, we can take this opportunity to clean up the locking design by
replacing master_lookup_lock with drm_device.master_rwsem.

Signed-off-by: Desmond Cheong Zhi Xi 
---
  drivers/gpu/drm/drm_auth.c | 19 +++
  drivers/gpu/drm/drm_file.c |  1 -
  drivers/gpu/drm/drm_internal.h |  1 +
  drivers/gpu/drm/drm_ioctl.c|  4 ++--
  drivers/gpu/drm/drm_lease.c| 18 --
  include/drm/drm_file.h |  9 +
  6 files changed, 19 insertions(+), 33 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index f2b2f197052a..232416119407 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -61,10 +61,9 @@
   * trusted clients.
   */
  
-static bool drm_is_current_master_locked(struct drm_file *fpriv)

+bool drm_is_current_master_locked(struct drm_file *fpriv)
  {
-   lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
-   lockdep_is_held(&fpriv->minor->dev->master_rwsem));
+   lockdep_assert_held_once(&fpriv->minor->dev->master_rwsem);
  
  	return fpriv->is_master && drm_lease_owner(fpriv->master) == fpriv->minor->dev->master;

  }
@@ -83,9 +82,9 @@ bool drm_is_current_master(struct drm_file *fpriv)
  {
bool ret;
  
-	spin_lock(&fpriv->master_lookup_lock);

+   down_read(&fpriv->minor->dev->master_rwsem);


Looking at the 3 patches and the need to have a locked version of pretty
much everything I'm wondering: Can't we just drop the spinlock completely,
and everywhere we've taking it thus far replace it with a
lockdep_assert_held_once?

The thing is, if there's any path left that doesn't hold the rwsem in at
least read mode we have a bug. And the right way to fix such a bug is to
grab the rwsem sufficiently high up in the callchain. That way I think we
should be able to avoid all these tedious changes to everything, including
touching i915 and vmwgfx drivers.

Or am I missing something big time?
-Daniel



Thanks for taking a look at all the patches and for the suggestions, Daniel.

Just my two cents. I think it makes sense to replace the lock with the 
lockdep assertion. This avoids the weirdness with the lock being taken 
both as an outer lock and sometimes as a deeply embedded inner lock.


But we'll probably have to fix some stuff because I don't think we 
always hold the rwsem in the places where the spinlock is grabbed (i.e. 
when drm_is_current_master or drm_file_get_master is called).


I'll split the series as suggested so we can test things up to PATCH 4 
("drm: avoid races with modesetting rights"). For the rest of the series 
to remove the spinlock, I'll take a closer look and probably send out a 
patch later this week.


Best wishes,
Desmond


ret = drm_is_current_master_locked(fpriv);
-   spin_unlock(&fpriv->master_lookup_lock);
+   up_read(&fpriv->minor->dev->master_rwsem);
  
  	return ret;

  }
@@ -120,7 +119,7 @@ int drm_authmagic(struct drm_device *dev, void *data,
DRM_DEBUG("%u\n", auth->magic);
  
  	down_write(&dev->master_rwsem);

-   if (unlikely(!drm_is_current_master(file_priv))) {
+   if (unlikely(!drm_is_current_master_locked(file_priv))) {
up_write(&dev->master_rwsem);
return -EACCES;
}
@@ -178,9 +177,7 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
new_master = drm_master_create(dev);
if (!new_master)
return -ENOMEM;
-   spin_lock(&fpriv->master_lookup_lock);
fpriv->master = new_master;
-   spin_unlock(&fpriv->master_lookup_lock);
  
  	fpriv->is_master = 1;

fpriv->authenticated = 1;
@@ -343,9 +340,7 @@ int drm_master_open(struct drm_file *file_priv)
if (!dev->master) {

Re: [Intel-gfx] [PATCH v8 4/7] drm: avoid races with modesetting rights

2021-08-30 Thread Desmond Cheong Zhi Xi

On 26/8/21 8:59 pm, Daniel Vetter wrote:

On Thu, Aug 26, 2021 at 10:01:19AM +0800, Desmond Cheong Zhi Xi wrote:

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 hold a write lock.

To avoid deadlocks with master_rwsem, for ioctls that need to check
for modesetting permissions, but also need to hold a write lock on
master_rwsem to protect some other attribute (or recurses to some
function that holds a write lock, like drm_mode_create_lease_ioctl
which eventually calls drm_master_open), we remove the DRM_MASTER flag
and push the master_rwsem lock and permissions check into the ioctl.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
---
  drivers/gpu/drm/drm_auth.c  |  4 
  drivers/gpu/drm/drm_ioctl.c | 20 +++-
  drivers/gpu/drm/drm_lease.c | 35 ---
  include/drm/drm_device.h|  5 +
  4 files changed, 48 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 73ade0513ccb..65065f7e1499 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -120,6 +120,10 @@ int drm_authmagic(struct drm_device *dev, void *data,
DRM_DEBUG("%u\n", auth->magic);
  
  	down_write(&dev->master_rwsem);

+   if (unlikely(!drm_is_current_master(file_priv))) {
+   up_write(&dev->master_rwsem);
+   return -EACCES;
+   }
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 158629d88319..8bea39ffc5c0 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -386,6 +386,10 @@ static int drm_setversion(struct drm_device *dev, void 
*data, struct drm_file *f
int if_version, retcode = 0;
  
  	down_write(&dev->master_rwsem);

+   if (unlikely(!drm_is_current_master(file_priv))) {
+   retcode = -EACCES;
+   goto unlock;
+   }
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) {
@@ -420,8 +424,9 @@ 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_write(&dev->master_rwsem);
  
+unlock:

+   up_write(&dev->master_rwsem);
return retcode;
  }
  
@@ -574,12 +579,12 @@ static const struct drm_ioctl_desc drm_ioctls[] = {

DRM_IOCTL_DEF(DRM_IOCTL_GET_STATS, drm_getstats, 0),
DRM_IOCTL_DEF(DRM_IOCTL_GET_CAP, drm_getcap, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_SET_CLIENT_CAP, drm_setclientcap, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, 0),


Random bikeshed, if you're bored: In newer code we've given ioctl
callbacks an _ioctl suffix, so they'r easier to spot. Could do that in a
follow-up if you want.



Makes sense, this was a small pain point when auditing the ioctl 
functions. I'll do this on a rainier day.


  
  	DRM_IOCTL_DEF(DRM_IOCTL_SET_UNIQUE, drm_invalid_op, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),

DRM_IOCTL_DEF(DRM_IOCTL_BLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_UNBLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
-   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, 0),
  
  	DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_ADD_MAP, drm_legacy_addmap_ioctl, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),

DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_RM_MAP, drm_legacy_rmmap_ioctl, 
DRM_AUTH),
@@ -706,10 +711,10 @@ static co

Re: [Intel-gfx] [PATCH v8 3/7] drm: lock drm_global_mutex earlier in the ioctl handler

2021-08-30 Thread Desmond Cheong Zhi Xi

On 26/8/21 5:58 pm, Daniel Vetter wrote:

On Thu, Aug 26, 2021 at 10:01:18AM +0800, Desmond Cheong Zhi Xi wrote:

In a future patch, a read lock on drm_device.master_rwsem is
held in the ioctl handler before the check for ioctl
permissions. However, this inverts the lock hierarchy of
drm_global_mutex --> master_rwsem.

To avoid this, we do some prep work to grab the drm_global_mutex
before checking for ioctl permissions.

Signed-off-by: Desmond Cheong Zhi Xi 
---
  drivers/gpu/drm/drm_ioctl.c | 18 +-
  1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index d25713b09b80..158629d88319 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -772,19 +772,19 @@ long drm_ioctl_kernel(struct file *file, drm_ioctl_t 
*func, void *kdata,
if (drm_dev_is_unplugged(dev))
return -ENODEV;
  
+	/* Enforce sane locking for modern driver ioctls. */

+   if (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY)) && !(flags & 
DRM_UNLOCKED))


Maybe have a local bool locked_ioctl for this so it's extremely clear it's
the same condition in both?

Either way: Reviewed-by: Daniel Vetter 



Thanks for the suggestion and review. Sounds good, I'll update and send 
out a new version.


(Sorry for delays, been busy with moving)


+   mutex_lock(&drm_global_mutex);
+
retcode = drm_ioctl_permit(flags, file_priv);
if (unlikely(retcode))
-   return retcode;
+   goto out;
  
-	/* Enforce sane locking for modern driver ioctls. */

-   if (likely(!drm_core_check_feature(dev, DRIVER_LEGACY)) ||
-   (flags & DRM_UNLOCKED))
-   retcode = func(dev, kdata, file_priv);
-   else {
-   mutex_lock(&drm_global_mutex);
-   retcode = func(dev, kdata, file_priv);
+   retcode = func(dev, kdata, file_priv);
+
+out:
+   if (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY)) && !(flags & 
DRM_UNLOCKED))
mutex_unlock(&drm_global_mutex);
-   }
return retcode;
  }
  EXPORT_SYMBOL(drm_ioctl_kernel);
--
2.25.1







Re: [Intel-gfx] [PATCH v8 1/7] drm: fix null ptr dereference in drm_master_release

2021-08-26 Thread Desmond Cheong Zhi Xi

On 26/8/21 5:53 pm, Daniel Vetter wrote:

On Thu, Aug 26, 2021 at 10:01:16AM +0800, Desmond Cheong Zhi Xi wrote:

drm_master_release can be called on a drm_file without a master, which
results in a null ptr dereference of file_priv->master->magic_map. The
three cases are:

1. Error path in drm_open_helper
   drm_open():
 drm_open_helper():
   drm_master_open():
 drm_new_set_master(); <--- returns -ENOMEM,
drm_file.master not set
   drm_file_free():
 drm_master_release(); <--- NULL ptr dereference
(file_priv->master->magic_map)

2. Error path in mock_drm_getfile
   mock_drm_getfile():
 anon_inode_getfile(); <--- returns error, drm_file.master not set
 drm_file_free():
   drm_master_release(); <--- NULL ptr dereference
  (file_priv->master->magic_map)

3. In drm_client_close, as drm_client_open doesn't set up a master

drm_file.master is set up in drm_open_helper through the call to
drm_master_open, so we mirror it with a call to drm_master_release in
drm_close_helper, and remove drm_master_release from drm_file_free to
avoid the null ptr dereference.

Signed-off-by: Desmond Cheong Zhi Xi 


Reviewed-by: Daniel Vetter 

I guess we should also have a cc: stable on this one? I think this bug
existed since pretty much forever, but maybe more prominent with the
drm_client stuff added a while ago.
-Daniel



Thanks for the reviews, Daniel.

Took a closer look. I think if we cc: stable, this fix should accompany 
commit 7eeaeb90a6a5 ("drm/file: Don't set master on in-kernel clients") 
which moves the drm_master_open out from drm_file_alloc into 
drm_open_helper.



---
  drivers/gpu/drm/drm_file.c | 6 +++---
  1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index ed25168619fc..90b62f360da1 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -282,9 +282,6 @@ void drm_file_free(struct drm_file *file)
  
  	drm_legacy_ctxbitmap_flush(dev, file);
  
-	if (drm_is_primary_client(file))

-   drm_master_release(file);
-
if (dev->driver->postclose)
dev->driver->postclose(dev, file);
  
@@ -305,6 +302,9 @@ static void drm_close_helper(struct file *filp)

list_del(&file_priv->lhead);
mutex_unlock(&dev->filelist_mutex);
  
+	if (drm_is_primary_client(file_priv))

+   drm_master_release(file_priv);
+
drm_file_free(file_priv);
  }
  
--

2.25.1







[Intel-gfx] [PATCH v8 7/7] drm: remove drm_file.master_lookup_lock

2021-08-25 Thread Desmond Cheong Zhi Xi
Previously, master_lookup_lock was introduced in
commit 0b0860a3cf5e ("drm: serialize drm_file.master with a new
spinlock") to serialize accesses to drm_file.master. This then allowed
us to write drm_file_get_master in commit 56f0729a510f ("drm: protect
drm_master pointers in drm_lease.c").

The rationale behind introducing a new spinlock at the time was that
the other lock that could have been used (drm_device.master_mutex) was
the outermost lock, so embedding calls to drm_file_get_master and
drm_is_current_master in various functions easily caused us to invert
the lock hierarchy.

Following the conversion of master_mutex into a rwsem, and its use to
plug races with modesetting rights, we've untangled some lock
hierarchies and removed the need for using drm_file_get_master and the
unlocked version of drm_is_current_master in multiple places.

Hence, we can take this opportunity to clean up the locking design by
replacing master_lookup_lock with drm_device.master_rwsem.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c | 19 +++
 drivers/gpu/drm/drm_file.c |  1 -
 drivers/gpu/drm/drm_internal.h |  1 +
 drivers/gpu/drm/drm_ioctl.c|  4 ++--
 drivers/gpu/drm/drm_lease.c| 18 --
 include/drm/drm_file.h |  9 +
 6 files changed, 19 insertions(+), 33 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index f2b2f197052a..232416119407 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -61,10 +61,9 @@
  * trusted clients.
  */
 
-static bool drm_is_current_master_locked(struct drm_file *fpriv)
+bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
-   lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
-   lockdep_is_held(&fpriv->minor->dev->master_rwsem));
+   lockdep_assert_held_once(&fpriv->minor->dev->master_rwsem);
 
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
@@ -83,9 +82,9 @@ bool drm_is_current_master(struct drm_file *fpriv)
 {
bool ret;
 
-   spin_lock(&fpriv->master_lookup_lock);
+   down_read(&fpriv->minor->dev->master_rwsem);
ret = drm_is_current_master_locked(fpriv);
-   spin_unlock(&fpriv->master_lookup_lock);
+   up_read(&fpriv->minor->dev->master_rwsem);
 
return ret;
 }
@@ -120,7 +119,7 @@ int drm_authmagic(struct drm_device *dev, void *data,
DRM_DEBUG("%u\n", auth->magic);
 
down_write(&dev->master_rwsem);
-   if (unlikely(!drm_is_current_master(file_priv))) {
+   if (unlikely(!drm_is_current_master_locked(file_priv))) {
up_write(&dev->master_rwsem);
return -EACCES;
}
@@ -178,9 +177,7 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
new_master = drm_master_create(dev);
if (!new_master)
return -ENOMEM;
-   spin_lock(&fpriv->master_lookup_lock);
fpriv->master = new_master;
-   spin_unlock(&fpriv->master_lookup_lock);
 
fpriv->is_master = 1;
fpriv->authenticated = 1;
@@ -343,9 +340,7 @@ int drm_master_open(struct drm_file *file_priv)
if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
} else {
-   spin_lock(&file_priv->master_lookup_lock);
file_priv->master = drm_master_get(dev->master);
-   spin_unlock(&file_priv->master_lookup_lock);
}
up_write(&dev->master_rwsem);
 
@@ -413,13 +408,13 @@ struct drm_master *drm_file_get_master(struct drm_file 
*file_priv)
if (!file_priv)
return NULL;
 
-   spin_lock(&file_priv->master_lookup_lock);
+   down_read(&file_priv->minor->dev->master_rwsem);
if (!file_priv->master)
goto unlock;
master = drm_master_get(file_priv->master);
 
 unlock:
-   spin_unlock(&file_priv->master_lookup_lock);
+   up_read(&file_priv->minor->dev->master_rwsem);
return master;
 }
 EXPORT_SYMBOL(drm_file_get_master);
diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index 90b62f360da1..8c846e0179d7 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -176,7 +176,6 @@ struct drm_file *drm_file_alloc(struct drm_minor *minor)
init_waitqueue_head(&file->event_wait);
file->event_space = 4096; /* set aside 4k for event buffer */
 
-   spin_lock_init(&file->master_lookup_lock);
mutex_init(&file->event_read_lock);
 
if (drm_core_check_feature(dev, DRIVER_GEM))
diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_

[Intel-gfx] [PATCH v8 6/7] drm: avoid circular locks in drm_lease_held

2021-08-25 Thread Desmond Cheong Zhi Xi
drm_lease_held calls drm_file_get_master. However, this function is
sometimes called while holding on to drm_device.master_rwsem or
modeset_mutex. Since master_rwsem will replace
drm_file.master_lookup_lock in drm_file_get_master in a future patch,
this results in both recursive locking, and an inversion of the
master_rwsem --> modeset_mutex lock hierarchy.

To fix this, we create a new drm_lease_held_master helper function
that enables us to avoid calling drm_file_get_master after locking
master_rwsem or modeset_mutex.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c|  3 +++
 drivers/gpu/drm/drm_crtc.c|  4 +++-
 drivers/gpu/drm/drm_encoder.c |  7 ++-
 drivers/gpu/drm/drm_lease.c   | 30 +++---
 drivers/gpu/drm/drm_plane.c   | 18 ++
 include/drm/drm_lease.h   |  2 ++
 6 files changed, 43 insertions(+), 21 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 65065f7e1499..f2b2f197052a 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -410,6 +410,9 @@ struct drm_master *drm_file_get_master(struct drm_file 
*file_priv)
 {
struct drm_master *master = NULL;
 
+   if (!file_priv)
+   return NULL;
+
spin_lock(&file_priv->master_lookup_lock);
if (!file_priv->master)
goto unlock;
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index b1279bb3fa61..0b1e76d2f9ff 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -665,8 +665,10 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
 
plane = crtc->primary;
 
+   lockdep_assert_held_once(&dev->master_rwsem);
/* allow disabling with the primary plane leased */
-   if (crtc_req->mode_valid && !drm_lease_held(file_priv, plane->base.id))
+   if (crtc_req->mode_valid &&
+   !drm_lease_held_master(file_priv->master, plane->base.id))
return -EACCES;
 
DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx,
diff --git a/drivers/gpu/drm/drm_encoder.c b/drivers/gpu/drm/drm_encoder.c
index 72e982323a5e..bacb2f6a325c 100644
--- a/drivers/gpu/drm/drm_encoder.c
+++ b/drivers/gpu/drm/drm_encoder.c
@@ -22,6 +22,7 @@
 
 #include 
 
+#include 
 #include 
 #include 
 #include 
@@ -281,6 +282,7 @@ int drm_mode_getencoder(struct drm_device *dev, void *data,
struct drm_mode_get_encoder *enc_resp = data;
struct drm_encoder *encoder;
struct drm_crtc *crtc;
+   struct drm_master *master;
 
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return -EOPNOTSUPP;
@@ -289,13 +291,16 @@ int drm_mode_getencoder(struct drm_device *dev, void 
*data,
if (!encoder)
return -ENOENT;
 
+   master = drm_file_get_master(file_priv);
drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
crtc = drm_encoder_get_crtc(encoder);
-   if (crtc && drm_lease_held(file_priv, crtc->base.id))
+   if (crtc && drm_lease_held_master(master, crtc->base.id))
enc_resp->crtc_id = crtc->base.id;
else
enc_resp->crtc_id = 0;
drm_modeset_unlock(&dev->mode_config.connection_mutex);
+   if (master)
+   drm_master_put(&master);
 
enc_resp->encoder_type = encoder->encoder_type;
enc_resp->encoder_id = encoder->base.id;
diff --git a/drivers/gpu/drm/drm_lease.c b/drivers/gpu/drm/drm_lease.c
index 1b156c85d1c8..15bf3a3c76d1 100644
--- a/drivers/gpu/drm/drm_lease.c
+++ b/drivers/gpu/drm/drm_lease.c
@@ -114,27 +114,30 @@ bool _drm_lease_held(struct drm_file *file_priv, int id)
return _drm_lease_held_master(file_priv->master, id);
 }
 
-bool drm_lease_held(struct drm_file *file_priv, int id)
+bool drm_lease_held_master(struct drm_master *master, int id)
 {
-   struct drm_master *master;
bool ret;
 
-   if (!file_priv)
+   if (!master || !master->lessor)
return true;
 
-   master = drm_file_get_master(file_priv);
-   if (!master)
-   return true;
-   if (!master->lessor) {
-   ret = true;
-   goto out;
-   }
mutex_lock(&master->dev->mode_config.idr_mutex);
ret = _drm_lease_held_master(master, id);
mutex_unlock(&master->dev->mode_config.idr_mutex);
 
-out:
-   drm_master_put(&master);
+   return ret;
+}
+
+bool drm_lease_held(struct drm_file *file_priv, int id)
+{
+   struct drm_master *master;
+   bool ret;
+
+   master = drm_file_get_master(file_priv);
+   ret = drm_lease_held_master(master, id);
+   if (master)
+   drm_master_put(&master);
+
return ret;
 }
 
@@ -150,9 +153,6 @@ uint32_t drm_lease_filter_crtcs(struct drm_file *file_priv, 
uint32_

[Intel-gfx] [PATCH v8 5/7] drm: avoid circular locks in drm_mode_object_find

2021-08-25 Thread Desmond Cheong Zhi Xi
__drm_mode_object_find checks if the given drm file holds the required
lease on a object by calling _drm_lease_held. _drm_lease_held in turn
uses drm_file_get_master to access drm_file.master.

However, in a future patch, the drm_file.master_lookup_lock in
drm_file_get_master will be replaced by drm_device.master_rwsem. This
is an issue for two reasons:

1. master_rwsem is sometimes already held when __drm_mode_object_find
is called, which leads to recursive locks on master_rwsem

2. drm_mode_object_find is sometimes called with the modeset_mutex
held, which leads to an inversion of the master_rwsem -->
modeset_mutex lock hierarchy

To fix this, we make __drm_mode_object_find the locked version of
drm_mode_object_find, and wrap calls to __drm_mode_object_find with
locks on master_rwsem. This allows us to safely access drm_file.master
in _drm_lease_held (__drm_mode_object_find is its only caller) without
the use of drm_file_get_master.

Functions that already lock master_rwsem are modified to call
__drm_mode_object_find, whereas functions that haven't locked
master_rwsem should call drm_mode_object_find. These two options
allow us to grab master_rwsem before modeset_mutex (such as in
drm_mode_get_obj_get_properties_ioctl).

This new rule requires more extensive changes to three functions:
drn_connector_lookup, drm_crtc_find, and drm_plane_find. These
functions are only sometimes called with master_rwsem held. Hence, we
have to further split them into locked and unlocked versions that call
__drm_mode_object_find and drm_mode_object_find respectively.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_atomic_uapi.c|  7 ++---
 drivers/gpu/drm/drm_color_mgmt.c |  2 +-
 drivers/gpu/drm/drm_crtc.c   |  5 ++--
 drivers/gpu/drm/drm_framebuffer.c|  2 +-
 drivers/gpu/drm/drm_lease.c  | 21 +--
 drivers/gpu/drm/drm_mode_object.c| 28 +---
 drivers/gpu/drm/drm_plane.c  |  8 +++---
 drivers/gpu/drm/drm_property.c   |  6 ++---
 drivers/gpu/drm/i915/display/intel_overlay.c |  2 +-
 drivers/gpu/drm/i915/display/intel_sprite.c  |  2 +-
 drivers/gpu/drm/vmwgfx/vmwgfx_kms.c  |  2 +-
 include/drm/drm_connector.h  | 23 
 include/drm/drm_crtc.h   | 22 +++
 include/drm/drm_mode_object.h|  3 +++
 include/drm/drm_plane.h  | 20 ++
 15 files changed, 118 insertions(+), 35 deletions(-)

diff --git a/drivers/gpu/drm/drm_atomic_uapi.c 
b/drivers/gpu/drm/drm_atomic_uapi.c
index 909f31833181..cda9a501cf74 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -557,7 +557,7 @@ static int drm_atomic_plane_set_property(struct drm_plane 
*plane,
return -EINVAL;
 
} else if (property == config->prop_crtc_id) {
-   struct drm_crtc *crtc = drm_crtc_find(dev, file_priv, val);
+   struct drm_crtc *crtc = __drm_crtc_find(dev, file_priv, val);
 
if (val && !crtc)
return -EACCES;
@@ -709,7 +709,7 @@ static int drm_atomic_connector_set_property(struct 
drm_connector *connector,
int ret;
 
if (property == config->prop_crtc_id) {
-   struct drm_crtc *crtc = drm_crtc_find(dev, file_priv, val);
+   struct drm_crtc *crtc = __drm_crtc_find(dev, file_priv, val);
 
if (val && !crtc)
return -EACCES;
@@ -1385,7 +1385,8 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
goto out;
}
 
-   obj = drm_mode_object_find(dev, file_priv, obj_id, 
DRM_MODE_OBJECT_ANY);
+   obj = __drm_mode_object_find(dev, file_priv, obj_id,
+DRM_MODE_OBJECT_ANY);
if (!obj) {
ret = -ENOENT;
goto out;
diff --git a/drivers/gpu/drm/drm_color_mgmt.c b/drivers/gpu/drm/drm_color_mgmt.c
index bb14f488c8f6..9dcb2ccca3ab 100644
--- a/drivers/gpu/drm/drm_color_mgmt.c
+++ b/drivers/gpu/drm/drm_color_mgmt.c
@@ -365,7 +365,7 @@ int drm_mode_gamma_set_ioctl(struct drm_device *dev,
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return -EOPNOTSUPP;
 
-   crtc = drm_crtc_find(dev, file_priv, crtc_lut->crtc_id);
+   crtc = __drm_crtc_find(dev, file_priv, crtc_lut->crtc_id);
if (!crtc)
return -ENOENT;
 
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index 26a77a735905..b1279bb3fa61 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -656,7 +656,7 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
if (crtc_req->x & 0x || crtc_req->y & 0x)
return -ERANGE;
 
-  

[Intel-gfx] [PATCH v8 4/7] drm: avoid races with modesetting rights

2021-08-25 Thread Desmond Cheong Zhi Xi
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 hold a write lock.

To avoid deadlocks with master_rwsem, for ioctls that need to check
for modesetting permissions, but also need to hold a write lock on
master_rwsem to protect some other attribute (or recurses to some
function that holds a write lock, like drm_mode_create_lease_ioctl
which eventually calls drm_master_open), we remove the DRM_MASTER flag
and push the master_rwsem lock and permissions check into the ioctl.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c  |  4 
 drivers/gpu/drm/drm_ioctl.c | 20 +++-
 drivers/gpu/drm/drm_lease.c | 35 ---
 include/drm/drm_device.h|  5 +
 4 files changed, 48 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 73ade0513ccb..65065f7e1499 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -120,6 +120,10 @@ int drm_authmagic(struct drm_device *dev, void *data,
DRM_DEBUG("%u\n", auth->magic);
 
down_write(&dev->master_rwsem);
+   if (unlikely(!drm_is_current_master(file_priv))) {
+   up_write(&dev->master_rwsem);
+   return -EACCES;
+   }
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 158629d88319..8bea39ffc5c0 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -386,6 +386,10 @@ static int drm_setversion(struct drm_device *dev, void 
*data, struct drm_file *f
int if_version, retcode = 0;
 
down_write(&dev->master_rwsem);
+   if (unlikely(!drm_is_current_master(file_priv))) {
+   retcode = -EACCES;
+   goto unlock;
+   }
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) {
@@ -420,8 +424,9 @@ 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_write(&dev->master_rwsem);
 
+unlock:
+   up_write(&dev->master_rwsem);
return retcode;
 }
 
@@ -574,12 +579,12 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
DRM_IOCTL_DEF(DRM_IOCTL_GET_STATS, drm_getstats, 0),
DRM_IOCTL_DEF(DRM_IOCTL_GET_CAP, drm_getcap, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_SET_CLIENT_CAP, drm_setclientcap, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, 0),
 
DRM_IOCTL_DEF(DRM_IOCTL_SET_UNIQUE, drm_invalid_op, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_BLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_UNBLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
-   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, 0),
 
DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_ADD_MAP, drm_legacy_addmap_ioctl, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_RM_MAP, drm_legacy_rmmap_ioctl, 
DRM_AUTH),
@@ -706,10 +711,10 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
  DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_CRTC_GET_SEQUENCE, drm_crtc_get_sequence_ioctl, 
0),
DRM_IOCTL_DEF(DRM_IOCTL_CRTC_QUEUE_SEQUENCE, 
drm_crtc_queue_sequence_ioctl, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_LEASE, drm_mode_create_lease_ioctl, 
DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_LEASE, drm_mode_cre

[Intel-gfx] [PATCH v8 3/7] drm: lock drm_global_mutex earlier in the ioctl handler

2021-08-25 Thread Desmond Cheong Zhi Xi
In a future patch, a read lock on drm_device.master_rwsem is
held in the ioctl handler before the check for ioctl
permissions. However, this inverts the lock hierarchy of
drm_global_mutex --> master_rwsem.

To avoid this, we do some prep work to grab the drm_global_mutex
before checking for ioctl permissions.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_ioctl.c | 18 +-
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index d25713b09b80..158629d88319 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -772,19 +772,19 @@ long drm_ioctl_kernel(struct file *file, drm_ioctl_t 
*func, void *kdata,
if (drm_dev_is_unplugged(dev))
return -ENODEV;
 
+   /* Enforce sane locking for modern driver ioctls. */
+   if (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY)) && !(flags & 
DRM_UNLOCKED))
+   mutex_lock(&drm_global_mutex);
+
retcode = drm_ioctl_permit(flags, file_priv);
if (unlikely(retcode))
-   return retcode;
+   goto out;
 
-   /* Enforce sane locking for modern driver ioctls. */
-   if (likely(!drm_core_check_feature(dev, DRIVER_LEGACY)) ||
-   (flags & DRM_UNLOCKED))
-   retcode = func(dev, kdata, file_priv);
-   else {
-   mutex_lock(&drm_global_mutex);
-   retcode = func(dev, kdata, file_priv);
+   retcode = func(dev, kdata, file_priv);
+
+out:
+   if (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY)) && !(flags & 
DRM_UNLOCKED))
mutex_unlock(&drm_global_mutex);
-   }
return retcode;
 }
 EXPORT_SYMBOL(drm_ioctl_kernel);
-- 
2.25.1



[Intel-gfx] [PATCH v8 2/7] drm: convert drm_device.master_mutex into a rwsem

2021-08-25 Thread Desmond Cheong Zhi Xi
drm_device.master_mutex currently protects the following:
- drm_device.master
- drm_file.master
- drm_file.was_master
- drm_file.is_master
- drm_master.unique
- drm_master.unique_len
- drm_master.magic_map

There is a clear separation between functions that read or change
these attributes. Hence, convert master_mutex into a rwsem to enable
concurrent readers.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c| 35 ++-
 drivers/gpu/drm/drm_debugfs.c |  4 ++--
 drivers/gpu/drm/drm_drv.c |  3 +--
 drivers/gpu/drm/drm_ioctl.c   | 10 +-
 include/drm/drm_auth.h|  6 +++---
 include/drm/drm_device.h  | 10 ++
 include/drm/drm_file.h| 12 ++--
 7 files changed, 41 insertions(+), 39 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 60a6b21474b1..73ade0513ccb 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -64,7 +64,7 @@
 static bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
-   lockdep_is_held(&fpriv->minor->dev->master_mutex));
+   lockdep_is_held(&fpriv->minor->dev->master_rwsem));
 
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
@@ -96,7 +96,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
struct drm_auth *auth = data;
int ret = 0;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!file_priv->magic) {
ret = idr_alloc(&file_priv->master->magic_map, file_priv,
1, 0, GFP_KERNEL);
@@ -104,7 +104,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
file_priv->magic = ret;
}
auth->magic = file_priv->magic;
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
DRM_DEBUG("%u\n", auth->magic);
 
@@ -119,13 +119,13 @@ int drm_authmagic(struct drm_device *dev, void *data,
 
DRM_DEBUG("%u\n", auth->magic);
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
idr_replace(&file_priv->master->magic_map, NULL, auth->magic);
}
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
return file ? 0 : -EINVAL;
 }
@@ -167,7 +167,7 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
struct drm_master *old_master;
struct drm_master *new_master;
 
-   lockdep_assert_held_once(&dev->master_mutex);
+   lockdep_assert_held_once(&dev->master_rwsem);
 
WARN_ON(fpriv->is_master);
old_master = fpriv->master;
@@ -249,7 +249,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -281,7 +281,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 
drm_set_master(dev, file_priv, false);
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -298,7 +298,7 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -321,8 +321,9 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
}
 
drm_drop_master(dev, file_priv);
+
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -334,7 +335,7 @@ int drm_master_open(struct drm_file *file_priv)
/* if there is no current master make this fd it, but do not create
 * any master object for render clients
 */
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
} else {
@@ -342,7 +343,7 @@ int drm_master_open(struct drm_file *file_priv)
file_priv->master = drm_master_get(dev->master);
spin_unlock(&file_priv->master_lookup_lock);
}
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->ma

[Intel-gfx] [PATCH v8 1/7] drm: fix null ptr dereference in drm_master_release

2021-08-25 Thread Desmond Cheong Zhi Xi
drm_master_release can be called on a drm_file without a master, which
results in a null ptr dereference of file_priv->master->magic_map. The
three cases are:

1. Error path in drm_open_helper
  drm_open():
drm_open_helper():
  drm_master_open():
drm_new_set_master(); <--- returns -ENOMEM,
   drm_file.master not set
  drm_file_free():
drm_master_release(); <--- NULL ptr dereference
   (file_priv->master->magic_map)

2. Error path in mock_drm_getfile
  mock_drm_getfile():
anon_inode_getfile(); <--- returns error, drm_file.master not set
drm_file_free():
  drm_master_release(); <--- NULL ptr dereference
 (file_priv->master->magic_map)

3. In drm_client_close, as drm_client_open doesn't set up a master

drm_file.master is set up in drm_open_helper through the call to
drm_master_open, so we mirror it with a call to drm_master_release in
drm_close_helper, and remove drm_master_release from drm_file_free to
avoid the null ptr dereference.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_file.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index ed25168619fc..90b62f360da1 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -282,9 +282,6 @@ void drm_file_free(struct drm_file *file)
 
drm_legacy_ctxbitmap_flush(dev, file);
 
-   if (drm_is_primary_client(file))
-   drm_master_release(file);
-
if (dev->driver->postclose)
dev->driver->postclose(dev, file);
 
@@ -305,6 +302,9 @@ static void drm_close_helper(struct file *filp)
list_del(&file_priv->lhead);
mutex_unlock(&dev->filelist_mutex);
 
+   if (drm_is_primary_client(file_priv))
+   drm_master_release(file_priv);
+
drm_file_free(file_priv);
 }
 
-- 
2.25.1



[Intel-gfx] [PATCH v8 0/7] drm: update locking for modesetting

2021-08-25 Thread Desmond Cheong Zhi Xi
Hi,

Seems that Intel-gfx CI still doesn't like what's going on, so I updated
the series to remove more recursive locking again.

Note: patch 5 touches a number of files, including the Intel and VMware
drivers, but most changes are simply switching a function call to the
appropriate locked/unlocked version.

Overall, this series fixes races with modesetting rights, converts
drm_device.master_mutex into master_rwsem, and removes
drm_file.master_lookup_lock.

- Patch 1: Fix a potential null ptr dereference in drm_master_release

- Patch 2: Convert master_mutex into rwsem (avoids creating a new lock)

- Patch 3: Update global mutex locking in the ioctl handler (avoids
deadlock when grabbing read lock on master_rwsem in drm_ioctl_kernel)

- Patch 4: Plug races with drm modesetting rights

- Patch 5: Modify drm_mode_object_find to fix potential recursive
locking of master_rwsem and lock inversions between modeset_mutex and
master_rwsem

- Patch 6: Remove remaining potential recursive locking of master_rwsem
and lock inversions between modeset_mutex and master_rwsem from calling
drm_lease_held

- Patch 7: Replace master_lookup_lock with master_rwsem

v7 -> v8:
- Avoid calling drm_lease_held in drm_mode_setcrtc and
drm_wait_vblank_ioctl, caught by Intel-gfx CI (patch 6)

v6 -> v7:
- Export __drm_mode_object_find for loadable modules, caught by the
Intel-gfx CI (patch 5)

v5 -> v6:
- Fix recursive locking on master_rwsem, caught by the Intel-gfx CI
(patch 5 & 6)

v4 -> v5:
- Avoid calling drm_file_get_master while holding on to the modeset
mutex, caught by the Intel-gfx CI (patch 5 & 6)

v3 -> v4 (suggested by Daniel Vetter):
- Drop a patch that added an unnecessary master_lookup_lock in
drm_master_release
- Drop a patch that addressed a non-existent race in
drm_is_current_master_locked
- Remove fixes for non-existent null ptr dereferences
- Protect drm_master.magic_map,unique{_len} with master_rwsem instead of
master_lookup_lock
- Drop the patch that moved master_lookup_lock into struct drm_device
- Drop a patch to export task_work_add
- Revert the check for the global mutex in the ioctl handler to use
drm_core_check_feature instead of drm_dev_needs_global_mutex
- Push down master_rwsem locking for selected ioctls to avoid lock
hierarchy inversions, and to allow us to hold write locks on
master_rwsem instead of flushing readers
- Remove master_lookup_lock by replacing it with master_rwsem

v2 -> v3:
- Unexport drm_master_flush, as suggested by Daniel Vetter.
- Merge master_mutex and master_rwsem, as suggested by Daniel Vetter.
- Export task_work_add, reported by kernel test robot.
- Make master_flush static, reported by kernel test robot.
- Move master_lookup_lock into struct drm_device.
- Add a missing lock on master_lookup_lock in drm_master_release.
- Fix a potential race in drm_is_current_master_locked.
- Fix potential null ptr dereferences in drm_{auth, ioctl}.
- Protect magic_map,unique{_len} with  master_lookup_lock.
- Convert master_mutex into a rwsem.
- Update global mutex locking in the ioctl handler.

v1 -> v2 (suggested by Daniel Vetter):
- Address an additional race when drm_open runs.
- Switch from SRCU to rwsem to synchronise readers and writers.
- Implement drm_master_flush with task_work so that flushes can be
queued to run before returning to userspace without creating a new
DRM_MASTER_FLUSH ioctl flag.

Best wishes,
Desmond

Desmond Cheong Zhi Xi (7):
  drm: fix null ptr dereference in drm_master_release
  drm: convert drm_device.master_mutex into a rwsem
  drm: lock drm_global_mutex earlier in the ioctl handler
  drm: avoid races with modesetting rights
  drm: avoid circular locks in drm_mode_object_find
  drm: avoid circular locks in drm_lease_held
  drm: remove drm_file.master_lookup_lock

 drivers/gpu/drm/drm_atomic_uapi.c|  7 +-
 drivers/gpu/drm/drm_auth.c   | 57 ++--
 drivers/gpu/drm/drm_color_mgmt.c |  2 +-
 drivers/gpu/drm/drm_crtc.c   |  9 +-
 drivers/gpu/drm/drm_debugfs.c|  4 +-
 drivers/gpu/drm/drm_drv.c|  3 +-
 drivers/gpu/drm/drm_encoder.c|  7 +-
 drivers/gpu/drm/drm_file.c   |  7 +-
 drivers/gpu/drm/drm_framebuffer.c|  2 +-
 drivers/gpu/drm/drm_internal.h   |  1 +
 drivers/gpu/drm/drm_ioctl.c  | 48 ++
 drivers/gpu/drm/drm_lease.c  | 94 ++--
 drivers/gpu/drm/drm_mode_object.c| 28 +-
 drivers/gpu/drm/drm_plane.c  | 26 --
 drivers/gpu/drm/drm_property.c   |  6 +-
 drivers/gpu/drm/i915/display/intel_overlay.c |  2 +-
 drivers/gpu/drm/i915/display/intel_sprite.c  |  2 +-
 drivers/gpu/drm/vmwgfx/vmwgfx_kms.c  |  2 +-
 include/drm/drm_auth.h   |  6 +-
 include/drm/drm_connector.h  | 23 +
 include/drm/drm_crtc.h

[Intel-gfx] [PATCH v7 7/7] drm: remove drm_file.master_lookup_lock

2021-08-25 Thread Desmond Cheong Zhi Xi
Previously, master_lookup_lock was introduced in
commit 0b0860a3cf5e ("drm: serialize drm_file.master with a new
spinlock") to serialize accesses to drm_file.master. This then allowed
us to write drm_file_get_master in commit 56f0729a510f ("drm: protect
drm_master pointers in drm_lease.c").

The rationale behind introducing a new spinlock at the time was that
the other lock that could have been used (drm_device.master_mutex) was
the outermost lock, so embedding calls to drm_file_get_master and
drm_is_current_master in various functions easily caused us to invert
the lock hierarchy.

Following the conversion of master_mutex into a rwsem, and its use to
plug races with modesetting rights, we've untangled some lock
hierarchies and removed the need for using drm_file_get_master and the
unlocked version of drm_is_current_master in multiple places.

Hence, we can take this opportunity to clean up the locking design by
replacing master_lookup_lock with drm_device.master_rwsem.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c | 19 +++
 drivers/gpu/drm/drm_file.c |  1 -
 drivers/gpu/drm/drm_internal.h |  1 +
 drivers/gpu/drm/drm_ioctl.c|  4 ++--
 drivers/gpu/drm/drm_lease.c| 18 --
 include/drm/drm_file.h |  9 +
 6 files changed, 19 insertions(+), 33 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index f2b2f197052a..232416119407 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -61,10 +61,9 @@
  * trusted clients.
  */
 
-static bool drm_is_current_master_locked(struct drm_file *fpriv)
+bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
-   lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
-   lockdep_is_held(&fpriv->minor->dev->master_rwsem));
+   lockdep_assert_held_once(&fpriv->minor->dev->master_rwsem);
 
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
@@ -83,9 +82,9 @@ bool drm_is_current_master(struct drm_file *fpriv)
 {
bool ret;
 
-   spin_lock(&fpriv->master_lookup_lock);
+   down_read(&fpriv->minor->dev->master_rwsem);
ret = drm_is_current_master_locked(fpriv);
-   spin_unlock(&fpriv->master_lookup_lock);
+   up_read(&fpriv->minor->dev->master_rwsem);
 
return ret;
 }
@@ -120,7 +119,7 @@ int drm_authmagic(struct drm_device *dev, void *data,
DRM_DEBUG("%u\n", auth->magic);
 
down_write(&dev->master_rwsem);
-   if (unlikely(!drm_is_current_master(file_priv))) {
+   if (unlikely(!drm_is_current_master_locked(file_priv))) {
up_write(&dev->master_rwsem);
return -EACCES;
}
@@ -178,9 +177,7 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
new_master = drm_master_create(dev);
if (!new_master)
return -ENOMEM;
-   spin_lock(&fpriv->master_lookup_lock);
fpriv->master = new_master;
-   spin_unlock(&fpriv->master_lookup_lock);
 
fpriv->is_master = 1;
fpriv->authenticated = 1;
@@ -343,9 +340,7 @@ int drm_master_open(struct drm_file *file_priv)
if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
} else {
-   spin_lock(&file_priv->master_lookup_lock);
file_priv->master = drm_master_get(dev->master);
-   spin_unlock(&file_priv->master_lookup_lock);
}
up_write(&dev->master_rwsem);
 
@@ -413,13 +408,13 @@ struct drm_master *drm_file_get_master(struct drm_file 
*file_priv)
if (!file_priv)
return NULL;
 
-   spin_lock(&file_priv->master_lookup_lock);
+   down_read(&file_priv->minor->dev->master_rwsem);
if (!file_priv->master)
goto unlock;
master = drm_master_get(file_priv->master);
 
 unlock:
-   spin_unlock(&file_priv->master_lookup_lock);
+   up_read(&file_priv->minor->dev->master_rwsem);
return master;
 }
 EXPORT_SYMBOL(drm_file_get_master);
diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index 90b62f360da1..8c846e0179d7 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -176,7 +176,6 @@ struct drm_file *drm_file_alloc(struct drm_minor *minor)
init_waitqueue_head(&file->event_wait);
file->event_space = 4096; /* set aside 4k for event buffer */
 
-   spin_lock_init(&file->master_lookup_lock);
mutex_init(&file->event_read_lock);
 
if (drm_core_check_feature(dev, DRIVER_GEM))
diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_

[Intel-gfx] [PATCH v7 6/7] drm: avoid circular locks with modeset_mutex and master_rwsem

2021-08-25 Thread Desmond Cheong Zhi Xi
drm_lease_held calls drm_file_get_master. However, this function is
sometimes called while holding on to modeset_mutex. Since
drm_device.master_rwsem will replace drm_file.master_lookup_lock in
drm_file_get_master in a future patch, this inverts the master_rwsem
--> modeset_mutex lock hierarchy.

To fix this, we create a new drm_lease_held_master helper function
that enables us to avoid calling drm_file_get_master after locking
modeset_mutex.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c|  3 +++
 drivers/gpu/drm/drm_encoder.c |  7 ++-
 drivers/gpu/drm/drm_lease.c   | 30 +++---
 drivers/gpu/drm/drm_plane.c   | 14 +++---
 include/drm/drm_lease.h   |  2 ++
 5 files changed, 37 insertions(+), 19 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 65065f7e1499..f2b2f197052a 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -410,6 +410,9 @@ struct drm_master *drm_file_get_master(struct drm_file 
*file_priv)
 {
struct drm_master *master = NULL;
 
+   if (!file_priv)
+   return NULL;
+
spin_lock(&file_priv->master_lookup_lock);
if (!file_priv->master)
goto unlock;
diff --git a/drivers/gpu/drm/drm_encoder.c b/drivers/gpu/drm/drm_encoder.c
index 72e982323a5e..bacb2f6a325c 100644
--- a/drivers/gpu/drm/drm_encoder.c
+++ b/drivers/gpu/drm/drm_encoder.c
@@ -22,6 +22,7 @@
 
 #include 
 
+#include 
 #include 
 #include 
 #include 
@@ -281,6 +282,7 @@ int drm_mode_getencoder(struct drm_device *dev, void *data,
struct drm_mode_get_encoder *enc_resp = data;
struct drm_encoder *encoder;
struct drm_crtc *crtc;
+   struct drm_master *master;
 
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return -EOPNOTSUPP;
@@ -289,13 +291,16 @@ int drm_mode_getencoder(struct drm_device *dev, void 
*data,
if (!encoder)
return -ENOENT;
 
+   master = drm_file_get_master(file_priv);
drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
crtc = drm_encoder_get_crtc(encoder);
-   if (crtc && drm_lease_held(file_priv, crtc->base.id))
+   if (crtc && drm_lease_held_master(master, crtc->base.id))
enc_resp->crtc_id = crtc->base.id;
else
enc_resp->crtc_id = 0;
drm_modeset_unlock(&dev->mode_config.connection_mutex);
+   if (master)
+   drm_master_put(&master);
 
enc_resp->encoder_type = encoder->encoder_type;
enc_resp->encoder_id = encoder->base.id;
diff --git a/drivers/gpu/drm/drm_lease.c b/drivers/gpu/drm/drm_lease.c
index 1b156c85d1c8..15bf3a3c76d1 100644
--- a/drivers/gpu/drm/drm_lease.c
+++ b/drivers/gpu/drm/drm_lease.c
@@ -114,27 +114,30 @@ bool _drm_lease_held(struct drm_file *file_priv, int id)
return _drm_lease_held_master(file_priv->master, id);
 }
 
-bool drm_lease_held(struct drm_file *file_priv, int id)
+bool drm_lease_held_master(struct drm_master *master, int id)
 {
-   struct drm_master *master;
bool ret;
 
-   if (!file_priv)
+   if (!master || !master->lessor)
return true;
 
-   master = drm_file_get_master(file_priv);
-   if (!master)
-   return true;
-   if (!master->lessor) {
-   ret = true;
-   goto out;
-   }
mutex_lock(&master->dev->mode_config.idr_mutex);
ret = _drm_lease_held_master(master, id);
mutex_unlock(&master->dev->mode_config.idr_mutex);
 
-out:
-   drm_master_put(&master);
+   return ret;
+}
+
+bool drm_lease_held(struct drm_file *file_priv, int id)
+{
+   struct drm_master *master;
+   bool ret;
+
+   master = drm_file_get_master(file_priv);
+   ret = drm_lease_held_master(master, id);
+   if (master)
+   drm_master_put(&master);
+
return ret;
 }
 
@@ -150,9 +153,6 @@ uint32_t drm_lease_filter_crtcs(struct drm_file *file_priv, 
uint32_t crtcs_in)
int count_in, count_out;
uint32_t crtcs_out = 0;
 
-   if (!file_priv)
-   return crtcs_in;
-
master = drm_file_get_master(file_priv);
if (!master)
return crtcs_in;
diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c
index b5566167a798..af9f65bf74d4 100644
--- a/drivers/gpu/drm/drm_plane.c
+++ b/drivers/gpu/drm/drm_plane.c
@@ -23,6 +23,7 @@
 #include 
 #include 
 
+#include 
 #include 
 #include 
 #include 
@@ -687,6 +688,7 @@ int drm_mode_getplane(struct drm_device *dev, void *data,
struct drm_mode_get_plane *plane_resp = data;
struct drm_plane *plane;
uint32_t __user *format_ptr;
+   struct drm_master *master;
 
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return -EOPNOTSUPP;
@@ -695,10 

[Intel-gfx] [PATCH v7 5/7] drm: avoid circular locks in drm_mode_object_find

2021-08-25 Thread Desmond Cheong Zhi Xi
__drm_mode_object_find checks if the given drm file holds the required
lease on a object by calling _drm_lease_held. _drm_lease_held in turn
uses drm_file_get_master to access drm_file.master.

However, in a future patch, the drm_file.master_lookup_lock in
drm_file_get_master will be replaced by drm_device.master_rwsem. This
is an issue for two reasons:

1. master_rwsem is sometimes already held when __drm_mode_object_find
is called, which leads to recursive locks on master_rwsem

2. drm_mode_object_find is sometimes called with the modeset_mutex
held, which leads to an inversion of the master_rwsem -->
modeset_mutex lock hierarchy

To fix this, we make __drm_mode_object_find the locked version of
drm_mode_object_find, and wrap calls to __drm_mode_object_find with
locks on master_rwsem. This allows us to safely access drm_file.master
in _drm_lease_held (__drm_mode_object_find is its only caller) without
the use of drm_file_get_master.

Functions that already lock master_rwsem are modified to call
__drm_mode_object_find, whereas functions that haven't locked
master_rwsem should call drm_mode_object_find. These two options
allow us to grab master_rwsem before modeset_mutex (such as in
drm_mode_get_obj_get_properties_ioctl).

This new rule requires more extensive changes to three functions:
drn_connector_lookup, drm_crtc_find, and drm_plane_find. These
functions are only sometimes called with master_rwsem held. Hence, we
have to further split them into locked and unlocked versions that call
__drm_mode_object_find and drm_mode_object_find respectively.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_atomic_uapi.c|  7 ++---
 drivers/gpu/drm/drm_color_mgmt.c |  2 +-
 drivers/gpu/drm/drm_crtc.c   |  5 ++--
 drivers/gpu/drm/drm_framebuffer.c|  2 +-
 drivers/gpu/drm/drm_lease.c  | 21 +--
 drivers/gpu/drm/drm_mode_object.c| 28 +---
 drivers/gpu/drm/drm_plane.c  |  8 +++---
 drivers/gpu/drm/drm_property.c   |  6 ++---
 drivers/gpu/drm/i915/display/intel_overlay.c |  2 +-
 drivers/gpu/drm/i915/display/intel_sprite.c  |  2 +-
 drivers/gpu/drm/vmwgfx/vmwgfx_kms.c  |  2 +-
 include/drm/drm_connector.h  | 23 
 include/drm/drm_crtc.h   | 22 +++
 include/drm/drm_mode_object.h|  3 +++
 include/drm/drm_plane.h  | 20 ++
 15 files changed, 118 insertions(+), 35 deletions(-)

diff --git a/drivers/gpu/drm/drm_atomic_uapi.c 
b/drivers/gpu/drm/drm_atomic_uapi.c
index 909f31833181..cda9a501cf74 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -557,7 +557,7 @@ static int drm_atomic_plane_set_property(struct drm_plane 
*plane,
return -EINVAL;
 
} else if (property == config->prop_crtc_id) {
-   struct drm_crtc *crtc = drm_crtc_find(dev, file_priv, val);
+   struct drm_crtc *crtc = __drm_crtc_find(dev, file_priv, val);
 
if (val && !crtc)
return -EACCES;
@@ -709,7 +709,7 @@ static int drm_atomic_connector_set_property(struct 
drm_connector *connector,
int ret;
 
if (property == config->prop_crtc_id) {
-   struct drm_crtc *crtc = drm_crtc_find(dev, file_priv, val);
+   struct drm_crtc *crtc = __drm_crtc_find(dev, file_priv, val);
 
if (val && !crtc)
return -EACCES;
@@ -1385,7 +1385,8 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
goto out;
}
 
-   obj = drm_mode_object_find(dev, file_priv, obj_id, 
DRM_MODE_OBJECT_ANY);
+   obj = __drm_mode_object_find(dev, file_priv, obj_id,
+DRM_MODE_OBJECT_ANY);
if (!obj) {
ret = -ENOENT;
goto out;
diff --git a/drivers/gpu/drm/drm_color_mgmt.c b/drivers/gpu/drm/drm_color_mgmt.c
index bb14f488c8f6..9dcb2ccca3ab 100644
--- a/drivers/gpu/drm/drm_color_mgmt.c
+++ b/drivers/gpu/drm/drm_color_mgmt.c
@@ -365,7 +365,7 @@ int drm_mode_gamma_set_ioctl(struct drm_device *dev,
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return -EOPNOTSUPP;
 
-   crtc = drm_crtc_find(dev, file_priv, crtc_lut->crtc_id);
+   crtc = __drm_crtc_find(dev, file_priv, crtc_lut->crtc_id);
if (!crtc)
return -ENOENT;
 
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index 26a77a735905..b1279bb3fa61 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -656,7 +656,7 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
if (crtc_req->x & 0x || crtc_req->y & 0x)
return -ERANGE;
 
-  

[Intel-gfx] [PATCH v7 4/7] drm: avoid races with modesetting rights

2021-08-25 Thread Desmond Cheong Zhi Xi
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 hold a write lock.

To avoid deadlocks with master_rwsem, for ioctls that need to check
for modesetting permissions, but also need to hold a write lock on
master_rwsem to protect some other attribute (or recurses to some
function that holds a write lock, like drm_mode_create_lease_ioctl
which eventually calls drm_master_open), we remove the DRM_MASTER flag
and push the master_rwsem lock and permissions check into the ioctl.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c  |  4 
 drivers/gpu/drm/drm_ioctl.c | 20 +++-
 drivers/gpu/drm/drm_lease.c | 35 ---
 include/drm/drm_device.h|  5 +
 4 files changed, 48 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 73ade0513ccb..65065f7e1499 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -120,6 +120,10 @@ int drm_authmagic(struct drm_device *dev, void *data,
DRM_DEBUG("%u\n", auth->magic);
 
down_write(&dev->master_rwsem);
+   if (unlikely(!drm_is_current_master(file_priv))) {
+   up_write(&dev->master_rwsem);
+   return -EACCES;
+   }
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 158629d88319..8bea39ffc5c0 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -386,6 +386,10 @@ static int drm_setversion(struct drm_device *dev, void 
*data, struct drm_file *f
int if_version, retcode = 0;
 
down_write(&dev->master_rwsem);
+   if (unlikely(!drm_is_current_master(file_priv))) {
+   retcode = -EACCES;
+   goto unlock;
+   }
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) {
@@ -420,8 +424,9 @@ 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_write(&dev->master_rwsem);
 
+unlock:
+   up_write(&dev->master_rwsem);
return retcode;
 }
 
@@ -574,12 +579,12 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
DRM_IOCTL_DEF(DRM_IOCTL_GET_STATS, drm_getstats, 0),
DRM_IOCTL_DEF(DRM_IOCTL_GET_CAP, drm_getcap, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_SET_CLIENT_CAP, drm_setclientcap, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, 0),
 
DRM_IOCTL_DEF(DRM_IOCTL_SET_UNIQUE, drm_invalid_op, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_BLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_UNBLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
-   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, 0),
 
DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_ADD_MAP, drm_legacy_addmap_ioctl, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_RM_MAP, drm_legacy_rmmap_ioctl, 
DRM_AUTH),
@@ -706,10 +711,10 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
  DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_CRTC_GET_SEQUENCE, drm_crtc_get_sequence_ioctl, 
0),
DRM_IOCTL_DEF(DRM_IOCTL_CRTC_QUEUE_SEQUENCE, 
drm_crtc_queue_sequence_ioctl, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_LEASE, drm_mode_create_lease_ioctl, 
DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_LEASE, drm_mode_cre

[Intel-gfx] [PATCH v7 3/7] drm: lock drm_global_mutex earlier in the ioctl handler

2021-08-25 Thread Desmond Cheong Zhi Xi
In a future patch, a read lock on drm_device.master_rwsem is
held in the ioctl handler before the check for ioctl
permissions. However, this inverts the lock hierarchy of
drm_global_mutex --> master_rwsem.

To avoid this, we do some prep work to grab the drm_global_mutex
before checking for ioctl permissions.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_ioctl.c | 18 +-
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index d25713b09b80..158629d88319 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -772,19 +772,19 @@ long drm_ioctl_kernel(struct file *file, drm_ioctl_t 
*func, void *kdata,
if (drm_dev_is_unplugged(dev))
return -ENODEV;
 
+   /* Enforce sane locking for modern driver ioctls. */
+   if (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY)) && !(flags & 
DRM_UNLOCKED))
+   mutex_lock(&drm_global_mutex);
+
retcode = drm_ioctl_permit(flags, file_priv);
if (unlikely(retcode))
-   return retcode;
+   goto out;
 
-   /* Enforce sane locking for modern driver ioctls. */
-   if (likely(!drm_core_check_feature(dev, DRIVER_LEGACY)) ||
-   (flags & DRM_UNLOCKED))
-   retcode = func(dev, kdata, file_priv);
-   else {
-   mutex_lock(&drm_global_mutex);
-   retcode = func(dev, kdata, file_priv);
+   retcode = func(dev, kdata, file_priv);
+
+out:
+   if (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY)) && !(flags & 
DRM_UNLOCKED))
mutex_unlock(&drm_global_mutex);
-   }
return retcode;
 }
 EXPORT_SYMBOL(drm_ioctl_kernel);
-- 
2.25.1



[Intel-gfx] [PATCH v7 2/7] drm: convert drm_device.master_mutex into a rwsem

2021-08-25 Thread Desmond Cheong Zhi Xi
drm_device.master_mutex currently protects the following:
- drm_device.master
- drm_file.master
- drm_file.was_master
- drm_file.is_master
- drm_master.unique
- drm_master.unique_len
- drm_master.magic_map

There is a clear separation between functions that read or change
these attributes. Hence, convert master_mutex into a rwsem to enable
concurrent readers.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c| 35 ++-
 drivers/gpu/drm/drm_debugfs.c |  4 ++--
 drivers/gpu/drm/drm_drv.c |  3 +--
 drivers/gpu/drm/drm_ioctl.c   | 10 +-
 include/drm/drm_auth.h|  6 +++---
 include/drm/drm_device.h  | 10 ++
 include/drm/drm_file.h| 12 ++--
 7 files changed, 41 insertions(+), 39 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 60a6b21474b1..73ade0513ccb 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -64,7 +64,7 @@
 static bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
-   lockdep_is_held(&fpriv->minor->dev->master_mutex));
+   lockdep_is_held(&fpriv->minor->dev->master_rwsem));
 
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
@@ -96,7 +96,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
struct drm_auth *auth = data;
int ret = 0;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!file_priv->magic) {
ret = idr_alloc(&file_priv->master->magic_map, file_priv,
1, 0, GFP_KERNEL);
@@ -104,7 +104,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
file_priv->magic = ret;
}
auth->magic = file_priv->magic;
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
DRM_DEBUG("%u\n", auth->magic);
 
@@ -119,13 +119,13 @@ int drm_authmagic(struct drm_device *dev, void *data,
 
DRM_DEBUG("%u\n", auth->magic);
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
idr_replace(&file_priv->master->magic_map, NULL, auth->magic);
}
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
return file ? 0 : -EINVAL;
 }
@@ -167,7 +167,7 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
struct drm_master *old_master;
struct drm_master *new_master;
 
-   lockdep_assert_held_once(&dev->master_mutex);
+   lockdep_assert_held_once(&dev->master_rwsem);
 
WARN_ON(fpriv->is_master);
old_master = fpriv->master;
@@ -249,7 +249,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -281,7 +281,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 
drm_set_master(dev, file_priv, false);
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -298,7 +298,7 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -321,8 +321,9 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
}
 
drm_drop_master(dev, file_priv);
+
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -334,7 +335,7 @@ int drm_master_open(struct drm_file *file_priv)
/* if there is no current master make this fd it, but do not create
 * any master object for render clients
 */
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
} else {
@@ -342,7 +343,7 @@ int drm_master_open(struct drm_file *file_priv)
file_priv->master = drm_master_get(dev->master);
spin_unlock(&file_priv->master_lookup_lock);
}
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->ma

[Intel-gfx] [PATCH v7 1/7] drm: fix null ptr dereference in drm_master_release

2021-08-25 Thread Desmond Cheong Zhi Xi
drm_master_release can be called on a drm_file without a master, which
results in a null ptr dereference of file_priv->master->magic_map. The
three cases are:

1. Error path in drm_open_helper
  drm_open():
drm_open_helper():
  drm_master_open():
drm_new_set_master(); <--- returns -ENOMEM,
   drm_file.master not set
  drm_file_free():
drm_master_release(); <--- NULL ptr dereference
   (file_priv->master->magic_map)

2. Error path in mock_drm_getfile
  mock_drm_getfile():
anon_inode_getfile(); <--- returns error, drm_file.master not set
drm_file_free():
  drm_master_release(); <--- NULL ptr dereference
 (file_priv->master->magic_map)

3. In drm_client_close, as drm_client_open doesn't set up a master

drm_file.master is set up in drm_open_helper through the call to
drm_master_open, so we mirror it with a call to drm_master_release in
drm_close_helper, and remove drm_master_release from drm_file_free to
avoid the null ptr dereference.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_file.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index ed25168619fc..90b62f360da1 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -282,9 +282,6 @@ void drm_file_free(struct drm_file *file)
 
drm_legacy_ctxbitmap_flush(dev, file);
 
-   if (drm_is_primary_client(file))
-   drm_master_release(file);
-
if (dev->driver->postclose)
dev->driver->postclose(dev, file);
 
@@ -305,6 +302,9 @@ static void drm_close_helper(struct file *filp)
list_del(&file_priv->lhead);
mutex_unlock(&dev->filelist_mutex);
 
+   if (drm_is_primary_client(file_priv))
+   drm_master_release(file_priv);
+
drm_file_free(file_priv);
 }
 
-- 
2.25.1



[Intel-gfx] [PATCH v7 0/7] drm: update locking for modesetting

2021-08-25 Thread Desmond Cheong Zhi Xi
Sorry for the noise. Sending out a new version because the Intel-gfx CI
caught that __drm_mode_object_find has to be exported for loadable
modules like the Intel and VMware DRM drivers.

Hi,

Updated the series again to avoid recursive locking caught by the
Intel-gfx CI. Patch 5 touches a number of files, including the Intel and
VMware drivers, but most changes are simply switching a function call to
the appropriate locked/unlocked version.

Overall, this series fixes races with modesetting rights, converts
drm_device.master_mutex into master_rwsem, and removes
drm_file.master_lookup_lock.

- Patch 1: Fix a potential null ptr dereference in drm_master_release

- Patch 2: Convert master_mutex into rwsem (avoids creating a new lock)

- Patch 3: Update global mutex locking in the ioctl handler (avoids
deadlock when grabbing read lock on master_rwsem in drm_ioctl_kernel)

- Patch 4: Plug races with drm modesetting rights

- Patch 5: Modify drm_mode_object_find to fix potential recursive
locking of master_rwsem and lock inversions between modeset_mutex and
master_rwsem

- Patch 6: Remove remaining potential lock inversions between
modeset_mutex and master_rwsem

- Patch 7: Replace master_lookup_lock with master_rwsem

v6 -> v7:
- Export __drm_mode_object_find for loadable modules, caught by the
Intel-gfx CI (patch 5)

v5 -> v6:
- Fix recursive locking on master_rwsem, caught by the Intel-gfx CI
(patch 5 & 6)

v4 -> v5:
- Avoid calling drm_file_get_master while holding on to the modeset
mutex, caught by the Intel-gfx CI (patch 5 & 6)

v3 -> v4 (suggested by Daniel Vetter):
- Drop a patch that added an unnecessary master_lookup_lock in
drm_master_release
- Drop a patch that addressed a non-existent race in
drm_is_current_master_locked
- Remove fixes for non-existent null ptr dereferences
- Protect drm_master.magic_map,unique{_len} with master_rwsem instead of
master_lookup_lock
- Drop the patch that moved master_lookup_lock into struct drm_device
- Drop a patch to export task_work_add
- Revert the check for the global mutex in the ioctl handler to use
drm_core_check_feature instead of drm_dev_needs_global_mutex
- Push down master_rwsem locking for selected ioctls to avoid lock
hierarchy inversions, and to allow us to hold write locks on
master_rwsem instead of flushing readers
- Remove master_lookup_lock by replacing it with master_rwsem

v2 -> v3:
- Unexport drm_master_flush, as suggested by Daniel Vetter.
- Merge master_mutex and master_rwsem, as suggested by Daniel Vetter.
- Export task_work_add, reported by kernel test robot.
- Make master_flush static, reported by kernel test robot.
- Move master_lookup_lock into struct drm_device.
- Add a missing lock on master_lookup_lock in drm_master_release.
- Fix a potential race in drm_is_current_master_locked.
- Fix potential null ptr dereferences in drm_{auth, ioctl}.
- Protect magic_map,unique{_len} with  master_lookup_lock.
- Convert master_mutex into a rwsem.
- Update global mutex locking in the ioctl handler.

v1 -> v2 (suggested by Daniel Vetter):
- Address an additional race when drm_open runs.
- Switch from SRCU to rwsem to synchronise readers and writers.
- Implement drm_master_flush with task_work so that flushes can be
queued to run before returning to userspace without creating a new
DRM_MASTER_FLUSH ioctl flag.

Best wishes,
Desmond

Desmond Cheong Zhi Xi (7):
  drm: fix null ptr dereference in drm_master_release
  drm: convert drm_device.master_mutex into a rwsem
  drm: lock drm_global_mutex earlier in the ioctl handler
  drm: avoid races with modesetting rights
  drm: avoid circular locks in drm_mode_object_find
  drm: avoid circular locks with modeset_mutex and master_rwsem
  drm: remove drm_file.master_lookup_lock

 drivers/gpu/drm/drm_atomic_uapi.c|  7 +-
 drivers/gpu/drm/drm_auth.c   | 57 ++--
 drivers/gpu/drm/drm_color_mgmt.c |  2 +-
 drivers/gpu/drm/drm_crtc.c   |  5 +-
 drivers/gpu/drm/drm_debugfs.c|  4 +-
 drivers/gpu/drm/drm_drv.c|  3 +-
 drivers/gpu/drm/drm_encoder.c|  7 +-
 drivers/gpu/drm/drm_file.c   |  7 +-
 drivers/gpu/drm/drm_framebuffer.c|  2 +-
 drivers/gpu/drm/drm_internal.h   |  1 +
 drivers/gpu/drm/drm_ioctl.c  | 48 ++
 drivers/gpu/drm/drm_lease.c  | 94 ++--
 drivers/gpu/drm/drm_mode_object.c| 28 +-
 drivers/gpu/drm/drm_plane.c  | 22 +++--
 drivers/gpu/drm/drm_property.c   |  6 +-
 drivers/gpu/drm/i915/display/intel_overlay.c |  2 +-
 drivers/gpu/drm/i915/display/intel_sprite.c  |  2 +-
 drivers/gpu/drm/vmwgfx/vmwgfx_kms.c  |  2 +-
 include/drm/drm_auth.h   |  6 +-
 include/drm/drm_connector.h  | 23 +
 include/drm/drm_crtc.h   | 22 +
 include/drm/drm_device.h 

[Intel-gfx] [PATCH v6 7/7] drm: remove drm_file.master_lookup_lock

2021-08-25 Thread Desmond Cheong Zhi Xi
Previously, master_lookup_lock was introduced in
commit 0b0860a3cf5e ("drm: serialize drm_file.master with a new
spinlock") to serialize accesses to drm_file.master. This then allowed
us to write drm_file_get_master in commit 56f0729a510f ("drm: protect
drm_master pointers in drm_lease.c").

The rationale behind introducing a new spinlock at the time was that
the other lock that could have been used (drm_device.master_mutex) was
the outermost lock, so embedding calls to drm_file_get_master and
drm_is_current_master in various functions easily caused us to invert
the lock hierarchy.

Following the conversion of master_mutex into a rwsem, and its use to
plug races with modesetting rights, we've untangled some lock
hierarchies and removed the need for using drm_file_get_master and the
unlocked version of drm_is_current_master in multiple places.

Hence, we can take this opportunity to clean up the locking design by
replacing master_lookup_lock with drm_device.master_rwsem.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c | 19 +++
 drivers/gpu/drm/drm_file.c |  1 -
 drivers/gpu/drm/drm_internal.h |  1 +
 drivers/gpu/drm/drm_ioctl.c|  4 ++--
 drivers/gpu/drm/drm_lease.c| 18 --
 include/drm/drm_file.h |  9 +
 6 files changed, 19 insertions(+), 33 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index f2b2f197052a..232416119407 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -61,10 +61,9 @@
  * trusted clients.
  */
 
-static bool drm_is_current_master_locked(struct drm_file *fpriv)
+bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
-   lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
-   lockdep_is_held(&fpriv->minor->dev->master_rwsem));
+   lockdep_assert_held_once(&fpriv->minor->dev->master_rwsem);
 
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
@@ -83,9 +82,9 @@ bool drm_is_current_master(struct drm_file *fpriv)
 {
bool ret;
 
-   spin_lock(&fpriv->master_lookup_lock);
+   down_read(&fpriv->minor->dev->master_rwsem);
ret = drm_is_current_master_locked(fpriv);
-   spin_unlock(&fpriv->master_lookup_lock);
+   up_read(&fpriv->minor->dev->master_rwsem);
 
return ret;
 }
@@ -120,7 +119,7 @@ int drm_authmagic(struct drm_device *dev, void *data,
DRM_DEBUG("%u\n", auth->magic);
 
down_write(&dev->master_rwsem);
-   if (unlikely(!drm_is_current_master(file_priv))) {
+   if (unlikely(!drm_is_current_master_locked(file_priv))) {
up_write(&dev->master_rwsem);
return -EACCES;
}
@@ -178,9 +177,7 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
new_master = drm_master_create(dev);
if (!new_master)
return -ENOMEM;
-   spin_lock(&fpriv->master_lookup_lock);
fpriv->master = new_master;
-   spin_unlock(&fpriv->master_lookup_lock);
 
fpriv->is_master = 1;
fpriv->authenticated = 1;
@@ -343,9 +340,7 @@ int drm_master_open(struct drm_file *file_priv)
if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
} else {
-   spin_lock(&file_priv->master_lookup_lock);
file_priv->master = drm_master_get(dev->master);
-   spin_unlock(&file_priv->master_lookup_lock);
}
up_write(&dev->master_rwsem);
 
@@ -413,13 +408,13 @@ struct drm_master *drm_file_get_master(struct drm_file 
*file_priv)
if (!file_priv)
return NULL;
 
-   spin_lock(&file_priv->master_lookup_lock);
+   down_read(&file_priv->minor->dev->master_rwsem);
if (!file_priv->master)
goto unlock;
master = drm_master_get(file_priv->master);
 
 unlock:
-   spin_unlock(&file_priv->master_lookup_lock);
+   up_read(&file_priv->minor->dev->master_rwsem);
return master;
 }
 EXPORT_SYMBOL(drm_file_get_master);
diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index 90b62f360da1..8c846e0179d7 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -176,7 +176,6 @@ struct drm_file *drm_file_alloc(struct drm_minor *minor)
init_waitqueue_head(&file->event_wait);
file->event_space = 4096; /* set aside 4k for event buffer */
 
-   spin_lock_init(&file->master_lookup_lock);
mutex_init(&file->event_read_lock);
 
if (drm_core_check_feature(dev, DRIVER_GEM))
diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_

[Intel-gfx] [PATCH v6 6/7] drm: avoid circular locks with modeset_mutex and master_rwsem

2021-08-25 Thread Desmond Cheong Zhi Xi
drm_lease_held calls drm_file_get_master. However, this function is
sometimes called while holding on to modeset_mutex. Since
drm_device.master_rwsem will replace drm_file.master_lookup_lock in
drm_file_get_master in a future patch, this inverts the master_rwsem
--> modeset_mutex lock hierarchy.

To fix this, we create a new drm_lease_held_master helper function
that enables us to avoid calling drm_file_get_master after locking
modeset_mutex.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c|  3 +++
 drivers/gpu/drm/drm_encoder.c |  7 ++-
 drivers/gpu/drm/drm_lease.c   | 30 +++---
 drivers/gpu/drm/drm_plane.c   | 14 +++---
 include/drm/drm_lease.h   |  2 ++
 5 files changed, 37 insertions(+), 19 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 65065f7e1499..f2b2f197052a 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -410,6 +410,9 @@ struct drm_master *drm_file_get_master(struct drm_file 
*file_priv)
 {
struct drm_master *master = NULL;
 
+   if (!file_priv)
+   return NULL;
+
spin_lock(&file_priv->master_lookup_lock);
if (!file_priv->master)
goto unlock;
diff --git a/drivers/gpu/drm/drm_encoder.c b/drivers/gpu/drm/drm_encoder.c
index 72e982323a5e..bacb2f6a325c 100644
--- a/drivers/gpu/drm/drm_encoder.c
+++ b/drivers/gpu/drm/drm_encoder.c
@@ -22,6 +22,7 @@
 
 #include 
 
+#include 
 #include 
 #include 
 #include 
@@ -281,6 +282,7 @@ int drm_mode_getencoder(struct drm_device *dev, void *data,
struct drm_mode_get_encoder *enc_resp = data;
struct drm_encoder *encoder;
struct drm_crtc *crtc;
+   struct drm_master *master;
 
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return -EOPNOTSUPP;
@@ -289,13 +291,16 @@ int drm_mode_getencoder(struct drm_device *dev, void 
*data,
if (!encoder)
return -ENOENT;
 
+   master = drm_file_get_master(file_priv);
drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
crtc = drm_encoder_get_crtc(encoder);
-   if (crtc && drm_lease_held(file_priv, crtc->base.id))
+   if (crtc && drm_lease_held_master(master, crtc->base.id))
enc_resp->crtc_id = crtc->base.id;
else
enc_resp->crtc_id = 0;
drm_modeset_unlock(&dev->mode_config.connection_mutex);
+   if (master)
+   drm_master_put(&master);
 
enc_resp->encoder_type = encoder->encoder_type;
enc_resp->encoder_id = encoder->base.id;
diff --git a/drivers/gpu/drm/drm_lease.c b/drivers/gpu/drm/drm_lease.c
index 1b156c85d1c8..15bf3a3c76d1 100644
--- a/drivers/gpu/drm/drm_lease.c
+++ b/drivers/gpu/drm/drm_lease.c
@@ -114,27 +114,30 @@ bool _drm_lease_held(struct drm_file *file_priv, int id)
return _drm_lease_held_master(file_priv->master, id);
 }
 
-bool drm_lease_held(struct drm_file *file_priv, int id)
+bool drm_lease_held_master(struct drm_master *master, int id)
 {
-   struct drm_master *master;
bool ret;
 
-   if (!file_priv)
+   if (!master || !master->lessor)
return true;
 
-   master = drm_file_get_master(file_priv);
-   if (!master)
-   return true;
-   if (!master->lessor) {
-   ret = true;
-   goto out;
-   }
mutex_lock(&master->dev->mode_config.idr_mutex);
ret = _drm_lease_held_master(master, id);
mutex_unlock(&master->dev->mode_config.idr_mutex);
 
-out:
-   drm_master_put(&master);
+   return ret;
+}
+
+bool drm_lease_held(struct drm_file *file_priv, int id)
+{
+   struct drm_master *master;
+   bool ret;
+
+   master = drm_file_get_master(file_priv);
+   ret = drm_lease_held_master(master, id);
+   if (master)
+   drm_master_put(&master);
+
return ret;
 }
 
@@ -150,9 +153,6 @@ uint32_t drm_lease_filter_crtcs(struct drm_file *file_priv, 
uint32_t crtcs_in)
int count_in, count_out;
uint32_t crtcs_out = 0;
 
-   if (!file_priv)
-   return crtcs_in;
-
master = drm_file_get_master(file_priv);
if (!master)
return crtcs_in;
diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c
index b5566167a798..af9f65bf74d4 100644
--- a/drivers/gpu/drm/drm_plane.c
+++ b/drivers/gpu/drm/drm_plane.c
@@ -23,6 +23,7 @@
 #include 
 #include 
 
+#include 
 #include 
 #include 
 #include 
@@ -687,6 +688,7 @@ int drm_mode_getplane(struct drm_device *dev, void *data,
struct drm_mode_get_plane *plane_resp = data;
struct drm_plane *plane;
uint32_t __user *format_ptr;
+   struct drm_master *master;
 
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return -EOPNOTSUPP;
@@ -695,10 

[Intel-gfx] [PATCH v6 5/7] drm: avoid circular locks in drm_mode_object_find

2021-08-25 Thread Desmond Cheong Zhi Xi
__drm_mode_object_find checks if the given drm file holds the required
lease on a object by calling _drm_lease_held. _drm_lease_held in turn
uses drm_file_get_master to access drm_file.master.

However, in a future patch, the drm_file.master_lookup_lock in
drm_file_get_master will be replaced by drm_device.master_rwsem. This
is an issue for two reasons:

1. master_rwsem is sometimes already held when __drm_mode_object_find
is called, which leads to recursive locks on master_rwsem

2. drm_mode_object_find is sometimes called with the modeset_mutex
held, which leads to an inversion of the master_rwsem -->
modeset_mutex lock hierarchy

To fix this, we make __drm_mode_object_find the locked version of
drm_mode_object_find, and wrap calls to __drm_mode_object_find with
locks on master_rwsem. This allows us to safely access drm_file.master
in _drm_lease_held (__drm_mode_object_find is its only caller) without
the use of drm_file_get_master.

Functions that already lock master_rwsem are modified to call
__drm_mode_object_find, whereas functions that haven't locked
master_rwsem should call drm_mode_object_find. These two options
allow us to grab master_rwsem before modeset_mutex (such as in
drm_mode_get_obj_get_properties_ioctl).

This new rule requires more extensive changes to three functions:
drn_connector_lookup, drm_crtc_find, and drm_plane_find. These
functions are only sometimes called with master_rwsem held. Hence, we
have to further split them into locked and unlocked versions that call
__drm_mode_object_find and drm_mode_object_find respectively.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_atomic_uapi.c|  7 +++---
 drivers/gpu/drm/drm_color_mgmt.c |  2 +-
 drivers/gpu/drm/drm_crtc.c   |  5 +++--
 drivers/gpu/drm/drm_framebuffer.c|  2 +-
 drivers/gpu/drm/drm_lease.c  | 21 +-
 drivers/gpu/drm/drm_mode_object.c| 13 ---
 drivers/gpu/drm/drm_plane.c  |  8 +++
 drivers/gpu/drm/drm_property.c   |  6 ++---
 drivers/gpu/drm/i915/display/intel_overlay.c |  2 +-
 drivers/gpu/drm/i915/display/intel_sprite.c  |  2 +-
 drivers/gpu/drm/vmwgfx/vmwgfx_kms.c  |  2 +-
 include/drm/drm_connector.h  | 23 
 include/drm/drm_crtc.h   | 22 +++
 include/drm/drm_mode_object.h|  3 +++
 include/drm/drm_plane.h  | 20 +
 15 files changed, 103 insertions(+), 35 deletions(-)

diff --git a/drivers/gpu/drm/drm_atomic_uapi.c 
b/drivers/gpu/drm/drm_atomic_uapi.c
index 909f31833181..cda9a501cf74 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -557,7 +557,7 @@ static int drm_atomic_plane_set_property(struct drm_plane 
*plane,
return -EINVAL;
 
} else if (property == config->prop_crtc_id) {
-   struct drm_crtc *crtc = drm_crtc_find(dev, file_priv, val);
+   struct drm_crtc *crtc = __drm_crtc_find(dev, file_priv, val);
 
if (val && !crtc)
return -EACCES;
@@ -709,7 +709,7 @@ static int drm_atomic_connector_set_property(struct 
drm_connector *connector,
int ret;
 
if (property == config->prop_crtc_id) {
-   struct drm_crtc *crtc = drm_crtc_find(dev, file_priv, val);
+   struct drm_crtc *crtc = __drm_crtc_find(dev, file_priv, val);
 
if (val && !crtc)
return -EACCES;
@@ -1385,7 +1385,8 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
goto out;
}
 
-   obj = drm_mode_object_find(dev, file_priv, obj_id, 
DRM_MODE_OBJECT_ANY);
+   obj = __drm_mode_object_find(dev, file_priv, obj_id,
+DRM_MODE_OBJECT_ANY);
if (!obj) {
ret = -ENOENT;
goto out;
diff --git a/drivers/gpu/drm/drm_color_mgmt.c b/drivers/gpu/drm/drm_color_mgmt.c
index bb14f488c8f6..9dcb2ccca3ab 100644
--- a/drivers/gpu/drm/drm_color_mgmt.c
+++ b/drivers/gpu/drm/drm_color_mgmt.c
@@ -365,7 +365,7 @@ int drm_mode_gamma_set_ioctl(struct drm_device *dev,
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return -EOPNOTSUPP;
 
-   crtc = drm_crtc_find(dev, file_priv, crtc_lut->crtc_id);
+   crtc = __drm_crtc_find(dev, file_priv, crtc_lut->crtc_id);
if (!crtc)
return -ENOENT;
 
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index 26a77a735905..b1279bb3fa61 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -656,7 +656,7 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
if (crtc_req->x & 0x || crtc_req->y & 0x)
return -ERA

[Intel-gfx] [PATCH v6 4/7] drm: avoid races with modesetting rights

2021-08-25 Thread Desmond Cheong Zhi Xi
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 hold a write lock.

To avoid deadlocks with master_rwsem, for ioctls that need to check
for modesetting permissions, but also need to hold a write lock on
master_rwsem to protect some other attribute (or recurses to some
function that holds a write lock, like drm_mode_create_lease_ioctl
which eventually calls drm_master_open), we remove the DRM_MASTER flag
and push the master_rwsem lock and permissions check into the ioctl.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c  |  4 
 drivers/gpu/drm/drm_ioctl.c | 20 +++-
 drivers/gpu/drm/drm_lease.c | 35 ---
 include/drm/drm_device.h|  5 +
 4 files changed, 48 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 73ade0513ccb..65065f7e1499 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -120,6 +120,10 @@ int drm_authmagic(struct drm_device *dev, void *data,
DRM_DEBUG("%u\n", auth->magic);
 
down_write(&dev->master_rwsem);
+   if (unlikely(!drm_is_current_master(file_priv))) {
+   up_write(&dev->master_rwsem);
+   return -EACCES;
+   }
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 158629d88319..8bea39ffc5c0 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -386,6 +386,10 @@ static int drm_setversion(struct drm_device *dev, void 
*data, struct drm_file *f
int if_version, retcode = 0;
 
down_write(&dev->master_rwsem);
+   if (unlikely(!drm_is_current_master(file_priv))) {
+   retcode = -EACCES;
+   goto unlock;
+   }
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) {
@@ -420,8 +424,9 @@ 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_write(&dev->master_rwsem);
 
+unlock:
+   up_write(&dev->master_rwsem);
return retcode;
 }
 
@@ -574,12 +579,12 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
DRM_IOCTL_DEF(DRM_IOCTL_GET_STATS, drm_getstats, 0),
DRM_IOCTL_DEF(DRM_IOCTL_GET_CAP, drm_getcap, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_SET_CLIENT_CAP, drm_setclientcap, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, 0),
 
DRM_IOCTL_DEF(DRM_IOCTL_SET_UNIQUE, drm_invalid_op, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_BLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_UNBLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
-   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, 0),
 
DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_ADD_MAP, drm_legacy_addmap_ioctl, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_RM_MAP, drm_legacy_rmmap_ioctl, 
DRM_AUTH),
@@ -706,10 +711,10 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
  DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_CRTC_GET_SEQUENCE, drm_crtc_get_sequence_ioctl, 
0),
DRM_IOCTL_DEF(DRM_IOCTL_CRTC_QUEUE_SEQUENCE, 
drm_crtc_queue_sequence_ioctl, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_LEASE, drm_mode_create_lease_ioctl, 
DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_LEASE, drm_mode_cre

[Intel-gfx] [PATCH v6 3/7] drm: lock drm_global_mutex earlier in the ioctl handler

2021-08-25 Thread Desmond Cheong Zhi Xi
In a future patch, a read lock on drm_device.master_rwsem is
held in the ioctl handler before the check for ioctl
permissions. However, this inverts the lock hierarchy of
drm_global_mutex --> master_rwsem.

To avoid this, we do some prep work to grab the drm_global_mutex
before checking for ioctl permissions.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_ioctl.c | 18 +-
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index d25713b09b80..158629d88319 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -772,19 +772,19 @@ long drm_ioctl_kernel(struct file *file, drm_ioctl_t 
*func, void *kdata,
if (drm_dev_is_unplugged(dev))
return -ENODEV;
 
+   /* Enforce sane locking for modern driver ioctls. */
+   if (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY)) && !(flags & 
DRM_UNLOCKED))
+   mutex_lock(&drm_global_mutex);
+
retcode = drm_ioctl_permit(flags, file_priv);
if (unlikely(retcode))
-   return retcode;
+   goto out;
 
-   /* Enforce sane locking for modern driver ioctls. */
-   if (likely(!drm_core_check_feature(dev, DRIVER_LEGACY)) ||
-   (flags & DRM_UNLOCKED))
-   retcode = func(dev, kdata, file_priv);
-   else {
-   mutex_lock(&drm_global_mutex);
-   retcode = func(dev, kdata, file_priv);
+   retcode = func(dev, kdata, file_priv);
+
+out:
+   if (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY)) && !(flags & 
DRM_UNLOCKED))
mutex_unlock(&drm_global_mutex);
-   }
return retcode;
 }
 EXPORT_SYMBOL(drm_ioctl_kernel);
-- 
2.25.1



[Intel-gfx] [PATCH v6 2/7] drm: convert drm_device.master_mutex into a rwsem

2021-08-25 Thread Desmond Cheong Zhi Xi
drm_device.master_mutex currently protects the following:
- drm_device.master
- drm_file.master
- drm_file.was_master
- drm_file.is_master
- drm_master.unique
- drm_master.unique_len
- drm_master.magic_map

There is a clear separation between functions that read or change
these attributes. Hence, convert master_mutex into a rwsem to enable
concurrent readers.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c| 35 ++-
 drivers/gpu/drm/drm_debugfs.c |  4 ++--
 drivers/gpu/drm/drm_drv.c |  3 +--
 drivers/gpu/drm/drm_ioctl.c   | 10 +-
 include/drm/drm_auth.h|  6 +++---
 include/drm/drm_device.h  | 10 ++
 include/drm/drm_file.h| 12 ++--
 7 files changed, 41 insertions(+), 39 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 60a6b21474b1..73ade0513ccb 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -64,7 +64,7 @@
 static bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
-   lockdep_is_held(&fpriv->minor->dev->master_mutex));
+   lockdep_is_held(&fpriv->minor->dev->master_rwsem));
 
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
@@ -96,7 +96,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
struct drm_auth *auth = data;
int ret = 0;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!file_priv->magic) {
ret = idr_alloc(&file_priv->master->magic_map, file_priv,
1, 0, GFP_KERNEL);
@@ -104,7 +104,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
file_priv->magic = ret;
}
auth->magic = file_priv->magic;
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
DRM_DEBUG("%u\n", auth->magic);
 
@@ -119,13 +119,13 @@ int drm_authmagic(struct drm_device *dev, void *data,
 
DRM_DEBUG("%u\n", auth->magic);
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
idr_replace(&file_priv->master->magic_map, NULL, auth->magic);
}
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
return file ? 0 : -EINVAL;
 }
@@ -167,7 +167,7 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
struct drm_master *old_master;
struct drm_master *new_master;
 
-   lockdep_assert_held_once(&dev->master_mutex);
+   lockdep_assert_held_once(&dev->master_rwsem);
 
WARN_ON(fpriv->is_master);
old_master = fpriv->master;
@@ -249,7 +249,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -281,7 +281,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 
drm_set_master(dev, file_priv, false);
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -298,7 +298,7 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -321,8 +321,9 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
}
 
drm_drop_master(dev, file_priv);
+
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -334,7 +335,7 @@ int drm_master_open(struct drm_file *file_priv)
/* if there is no current master make this fd it, but do not create
 * any master object for render clients
 */
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
} else {
@@ -342,7 +343,7 @@ int drm_master_open(struct drm_file *file_priv)
file_priv->master = drm_master_get(dev->master);
spin_unlock(&file_priv->master_lookup_lock);
}
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->ma

[Intel-gfx] [PATCH v6 1/7] drm: fix null ptr dereference in drm_master_release

2021-08-25 Thread Desmond Cheong Zhi Xi
drm_master_release can be called on a drm_file without a master, which
results in a null ptr dereference of file_priv->master->magic_map. The
three cases are:

1. Error path in drm_open_helper
  drm_open():
drm_open_helper():
  drm_master_open():
drm_new_set_master(); <--- returns -ENOMEM,
   drm_file.master not set
  drm_file_free():
drm_master_release(); <--- NULL ptr dereference
   (file_priv->master->magic_map)

2. Error path in mock_drm_getfile
  mock_drm_getfile():
anon_inode_getfile(); <--- returns error, drm_file.master not set
drm_file_free():
  drm_master_release(); <--- NULL ptr dereference
 (file_priv->master->magic_map)

3. In drm_client_close, as drm_client_open doesn't set up a master

drm_file.master is set up in drm_open_helper through the call to
drm_master_open, so we mirror it with a call to drm_master_release in
drm_close_helper, and remove drm_master_release from drm_file_free to
avoid the null ptr dereference.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_file.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index ed25168619fc..90b62f360da1 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -282,9 +282,6 @@ void drm_file_free(struct drm_file *file)
 
drm_legacy_ctxbitmap_flush(dev, file);
 
-   if (drm_is_primary_client(file))
-   drm_master_release(file);
-
if (dev->driver->postclose)
dev->driver->postclose(dev, file);
 
@@ -305,6 +302,9 @@ static void drm_close_helper(struct file *filp)
list_del(&file_priv->lhead);
mutex_unlock(&dev->filelist_mutex);
 
+   if (drm_is_primary_client(file_priv))
+   drm_master_release(file_priv);
+
drm_file_free(file_priv);
 }
 
-- 
2.25.1



[Intel-gfx] [PATCH v6 0/7] drm: update locking for modesetting

2021-08-25 Thread Desmond Cheong Zhi Xi
Hi,

Updated the series again to avoid recursive locking caught by the
Intel-gfx CI. Patch 5 touches a number of files, including the Intel and
VMware drivers, but most changes are simply switching a function call to
the appropriate locked/unlocked version.

Overall, this series fixes races with modesetting rights, converts
drm_device.master_mutex into master_rwsem, and removes
drm_file.master_lookup_lock.

- Patch 1: Fix a potential null ptr dereference in drm_master_release

- Patch 2: Convert master_mutex into rwsem (avoids creating a new lock)

- Patch 3: Update global mutex locking in the ioctl handler (avoids
deadlock when grabbing read lock on master_rwsem in drm_ioctl_kernel)

- Patch 4: Plug races with drm modesetting rights

- Patch 5: Modify drm_mode_object_find to fix potential recursive
locking of master_rwsem and lock inversions between modeset_mutex and
master_rwsem

- Patch 6: Remove remaining potential lock inversions between
modeset_mutex and master_rwsem

- Patch 7: Replace master_lookup_lock with master_rwsem

v5 -> v6:
- Fix recursive locking on master_rwsem, caught by the Intel-gfx CI
(only current patch 5 & 6 changed)

v4 -> v5:
- Avoid calling drm_file_get_master while holding on to the modeset
mutex, caught by the Intel-gfx CI

v3 -> v4 (suggested by Daniel Vetter):
- Drop a patch that added an unnecessary master_lookup_lock in
drm_master_release
- Drop a patch that addressed a non-existent race in
drm_is_current_master_locked
- Remove fixes for non-existent null ptr dereferences
- Protect drm_master.magic_map,unique{_len} with master_rwsem instead of
master_lookup_lock
- Drop the patch that moved master_lookup_lock into struct drm_device
- Drop a patch to export task_work_add
- Revert the check for the global mutex in the ioctl handler to use
drm_core_check_feature instead of drm_dev_needs_global_mutex
- Push down master_rwsem locking for selected ioctls to avoid lock
hierarchy inversions, and to allow us to hold write locks on
master_rwsem instead of flushing readers
- Remove master_lookup_lock by replacing it with master_rwsem

v2 -> v3:
- Unexport drm_master_flush, as suggested by Daniel Vetter.
- Merge master_mutex and master_rwsem, as suggested by Daniel Vetter.
- Export task_work_add, reported by kernel test robot.
- Make master_flush static, reported by kernel test robot.
- Move master_lookup_lock into struct drm_device.
- Add a missing lock on master_lookup_lock in drm_master_release.
- Fix a potential race in drm_is_current_master_locked.
- Fix potential null ptr dereferences in drm_{auth, ioctl}.
- Protect magic_map,unique{_len} with  master_lookup_lock.
- Convert master_mutex into a rwsem.
- Update global mutex locking in the ioctl handler.

v1 -> v2 (suggested by Daniel Vetter):
- Address an additional race when drm_open runs.
- Switch from SRCU to rwsem to synchronise readers and writers.
- Implement drm_master_flush with task_work so that flushes can be
queued to run before returning to userspace without creating a new
DRM_MASTER_FLUSH ioctl flag.

Best wishes,
Desmond

Desmond Cheong Zhi Xi (7):
  drm: fix null ptr dereference in drm_master_release
  drm: convert drm_device.master_mutex into a rwsem
  drm: lock drm_global_mutex earlier in the ioctl handler
  drm: avoid races with modesetting rights
  drm: avoid circular locks in drm_mode_object_find
  drm: avoid circular locks with modeset_mutex and master_rwsem
  drm: remove drm_file.master_lookup_lock

 drivers/gpu/drm/drm_atomic_uapi.c|  7 +-
 drivers/gpu/drm/drm_auth.c   | 57 ++--
 drivers/gpu/drm/drm_color_mgmt.c |  2 +-
 drivers/gpu/drm/drm_crtc.c   |  5 +-
 drivers/gpu/drm/drm_debugfs.c|  4 +-
 drivers/gpu/drm/drm_drv.c|  3 +-
 drivers/gpu/drm/drm_encoder.c|  7 +-
 drivers/gpu/drm/drm_file.c   |  7 +-
 drivers/gpu/drm/drm_framebuffer.c|  2 +-
 drivers/gpu/drm/drm_internal.h   |  1 +
 drivers/gpu/drm/drm_ioctl.c  | 48 ++
 drivers/gpu/drm/drm_lease.c  | 94 ++--
 drivers/gpu/drm/drm_mode_object.c| 13 ++-
 drivers/gpu/drm/drm_plane.c  | 22 +++--
 drivers/gpu/drm/drm_property.c   |  6 +-
 drivers/gpu/drm/i915/display/intel_overlay.c |  2 +-
 drivers/gpu/drm/i915/display/intel_sprite.c  |  2 +-
 drivers/gpu/drm/vmwgfx/vmwgfx_kms.c  |  2 +-
 include/drm/drm_auth.h   |  6 +-
 include/drm/drm_connector.h  | 23 +
 include/drm/drm_crtc.h   | 22 +
 include/drm/drm_device.h | 15 +++-
 include/drm/drm_file.h   | 17 ++--
 include/drm/drm_lease.h  |  2 +
 include/drm/drm_mode_object.h|  3 +
 include/drm/drm_plane.h  | 20 +
 26 files changed, 249 insertions(+), 143 deletions(-)

-- 
2.25.1



[Intel-gfx] [PATCH v5 6/6] drm: remove drm_file.master_lookup_lock

2021-08-23 Thread Desmond Cheong Zhi Xi
Previously, master_lookup_lock was introduced in
commit 0b0860a3cf5e ("drm: serialize drm_file.master with a new
spinlock") to serialize accesses to drm_file.master. This then allowed
us to write drm_file_get_master in commit 56f0729a510f ("drm: protect
drm_master pointers in drm_lease.c").

The rationale behind introducing a new spinlock at the time was that
the other lock that could have been used (drm_device.master_mutex) was
the outermost lock, so embedding calls to drm_file_get_master and
drm_is_current_master in various functions easily caused us to invert
the lock hierarchy.

Following the conversion of master_mutex into a rwsem, and its use to
plug races with modesetting rights, we've untangled some lock
hierarchies and removed the need for using drm_file_get_master and the
unlocked version of drm_is_current_master in multiple places.

Additionally, a previous patch fixed other remaining inversions
involving master_rwsem and modeset_mutex.

Hence, we can take this opportunity to clean up the locking design by
replacing master_lookup_lock with drm_device.master_rwsem.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c | 19 +++
 drivers/gpu/drm/drm_file.c |  1 -
 drivers/gpu/drm/drm_internal.h |  1 +
 drivers/gpu/drm/drm_ioctl.c|  4 ++--
 drivers/gpu/drm/drm_lease.c| 18 --
 include/drm/drm_file.h |  9 +
 6 files changed, 19 insertions(+), 33 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index f2b2f197052a..232416119407 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -61,10 +61,9 @@
  * trusted clients.
  */
 
-static bool drm_is_current_master_locked(struct drm_file *fpriv)
+bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
-   lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
-   lockdep_is_held(&fpriv->minor->dev->master_rwsem));
+   lockdep_assert_held_once(&fpriv->minor->dev->master_rwsem);
 
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
@@ -83,9 +82,9 @@ bool drm_is_current_master(struct drm_file *fpriv)
 {
bool ret;
 
-   spin_lock(&fpriv->master_lookup_lock);
+   down_read(&fpriv->minor->dev->master_rwsem);
ret = drm_is_current_master_locked(fpriv);
-   spin_unlock(&fpriv->master_lookup_lock);
+   up_read(&fpriv->minor->dev->master_rwsem);
 
return ret;
 }
@@ -120,7 +119,7 @@ int drm_authmagic(struct drm_device *dev, void *data,
DRM_DEBUG("%u\n", auth->magic);
 
down_write(&dev->master_rwsem);
-   if (unlikely(!drm_is_current_master(file_priv))) {
+   if (unlikely(!drm_is_current_master_locked(file_priv))) {
up_write(&dev->master_rwsem);
return -EACCES;
}
@@ -178,9 +177,7 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
new_master = drm_master_create(dev);
if (!new_master)
return -ENOMEM;
-   spin_lock(&fpriv->master_lookup_lock);
fpriv->master = new_master;
-   spin_unlock(&fpriv->master_lookup_lock);
 
fpriv->is_master = 1;
fpriv->authenticated = 1;
@@ -343,9 +340,7 @@ int drm_master_open(struct drm_file *file_priv)
if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
} else {
-   spin_lock(&file_priv->master_lookup_lock);
file_priv->master = drm_master_get(dev->master);
-   spin_unlock(&file_priv->master_lookup_lock);
}
up_write(&dev->master_rwsem);
 
@@ -413,13 +408,13 @@ struct drm_master *drm_file_get_master(struct drm_file 
*file_priv)
if (!file_priv)
return NULL;
 
-   spin_lock(&file_priv->master_lookup_lock);
+   down_read(&file_priv->minor->dev->master_rwsem);
if (!file_priv->master)
goto unlock;
master = drm_master_get(file_priv->master);
 
 unlock:
-   spin_unlock(&file_priv->master_lookup_lock);
+   up_read(&file_priv->minor->dev->master_rwsem);
return master;
 }
 EXPORT_SYMBOL(drm_file_get_master);
diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index 90b62f360da1..8c846e0179d7 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -176,7 +176,6 @@ struct drm_file *drm_file_alloc(struct drm_minor *minor)
init_waitqueue_head(&file->event_wait);
file->event_space = 4096; /* set aside 4k for event buffer */
 
-   spin_lock_init(&file->master_lookup_lock);
mutex_init(&file->event_read_lock)

[Intel-gfx] [PATCH v5 5/6] drm: avoid circular locks with modeset_mutex and master_rwsem

2021-08-23 Thread Desmond Cheong Zhi Xi
drm_device.master_rwsem is an outer lock that's grabbed in the ioctl
handler. However, in a future patch, master_rwsem will replace
drm_file.master_lookup_lock in drm_file_get_master, which is sometimes
called while holding other locks that depend on master_rwsem. This
circular locking should be avoided to prevent deadlocks.

_drm_lease_held and drm_lease_held call drm_file_get_master. However,
both functions are called while holding on to modeset_mutex, inverting
the master_rwsem --> modeset_mutex lock hierarchy.

To fix this, we do two things:

1. Wrap __drm_mode_object_find with read locks on master_rwsem before
locking modeset mutex so that we can still safely access
drm_file.master without drm_file_get_master

2. Call drm_file_get_master before locking modeset_mutex, then check
for leases with the new drm_lease_held_master function instead of
drm_lease_held

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_atomic_uapi.c |  4 +++-
 drivers/gpu/drm/drm_auth.c|  3 +++
 drivers/gpu/drm/drm_encoder.c |  7 ++-
 drivers/gpu/drm/drm_framebuffer.c |  2 +-
 drivers/gpu/drm/drm_lease.c   | 34 ---
 drivers/gpu/drm/drm_mode_object.c | 16 +++
 drivers/gpu/drm/drm_plane.c   | 17 +---
 drivers/gpu/drm/drm_property.c|  6 +++---
 include/drm/drm_lease.h   |  4 +++-
 9 files changed, 53 insertions(+), 40 deletions(-)

diff --git a/drivers/gpu/drm/drm_atomic_uapi.c 
b/drivers/gpu/drm/drm_atomic_uapi.c
index 909f31833181..203b0936f7f4 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -1366,6 +1366,7 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
if (!state)
return -ENOMEM;
 
+   down_read(&dev->master_rwsem);
drm_modeset_acquire_init(&ctx, DRM_MODESET_ACQUIRE_INTERRUPTIBLE);
state->acquire_ctx = &ctx;
state->allow_modeset = !!(arg->flags & DRM_MODE_ATOMIC_ALLOW_MODESET);
@@ -1385,7 +1386,7 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
goto out;
}
 
-   obj = drm_mode_object_find(dev, file_priv, obj_id, 
DRM_MODE_OBJECT_ANY);
+   obj = __drm_mode_object_find(dev, file_priv, obj_id, 
DRM_MODE_OBJECT_ANY);
if (!obj) {
ret = -ENOENT;
goto out;
@@ -1474,6 +1475,7 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
 
drm_modeset_drop_locks(&ctx);
drm_modeset_acquire_fini(&ctx);
+   up_read(&dev->master_rwsem);
 
return ret;
 }
diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 65065f7e1499..f2b2f197052a 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -410,6 +410,9 @@ struct drm_master *drm_file_get_master(struct drm_file 
*file_priv)
 {
struct drm_master *master = NULL;
 
+   if (!file_priv)
+   return NULL;
+
spin_lock(&file_priv->master_lookup_lock);
if (!file_priv->master)
goto unlock;
diff --git a/drivers/gpu/drm/drm_encoder.c b/drivers/gpu/drm/drm_encoder.c
index 72e982323a5e..a4852876f91f 100644
--- a/drivers/gpu/drm/drm_encoder.c
+++ b/drivers/gpu/drm/drm_encoder.c
@@ -22,6 +22,7 @@
 
 #include 
 
+#include 
 #include 
 #include 
 #include 
@@ -281,6 +282,7 @@ int drm_mode_getencoder(struct drm_device *dev, void *data,
struct drm_mode_get_encoder *enc_resp = data;
struct drm_encoder *encoder;
struct drm_crtc *crtc;
+   struct drm_master *master = NULL;
 
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return -EOPNOTSUPP;
@@ -289,13 +291,16 @@ int drm_mode_getencoder(struct drm_device *dev, void 
*data,
if (!encoder)
return -ENOENT;
 
+   master = drm_file_get_master(file_priv);
drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
crtc = drm_encoder_get_crtc(encoder);
-   if (crtc && drm_lease_held(file_priv, crtc->base.id))
+   if (crtc && drm_lease_held_master(master, crtc->base.id))
enc_resp->crtc_id = crtc->base.id;
else
enc_resp->crtc_id = 0;
drm_modeset_unlock(&dev->mode_config.connection_mutex);
+   if (master)
+   drm_master_put(&master);
 
enc_resp->encoder_type = encoder->encoder_type;
enc_resp->encoder_id = encoder->base.id;
diff --git a/drivers/gpu/drm/drm_framebuffer.c 
b/drivers/gpu/drm/drm_framebuffer.c
index 07f5abc875e9..9c1db91b150d 100644
--- a/drivers/gpu/drm/drm_framebuffer.c
+++ b/drivers/gpu/drm/drm_framebuffer.c
@@ -887,7 +887,7 @@ struct drm_framebuffer *drm_framebuffer_lookup(struct 
drm_device *dev,
struct drm_mode_object *obj;
struct drm_framebuffer *fb = NULL;
 
-   obj = __drm_mode_object_find(dev, 

[Intel-gfx] [PATCH v5 4/6] drm: avoid races with modesetting rights

2021-08-23 Thread Desmond Cheong Zhi Xi
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 hold a write lock.

To avoid deadlocks with master_rwsem, for ioctls that need to check
for modesetting permissions, but also need to hold a write lock on
master_rwsem to protect some other attribute (or recurses to some
function that holds a write lock, like drm_mode_create_lease_ioctl
which eventually calls drm_master_open), we remove the DRM_MASTER flag
and push the master_rwsem lock and permissions check into the ioctl.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c  |  4 
 drivers/gpu/drm/drm_ioctl.c | 20 +++-
 drivers/gpu/drm/drm_lease.c | 35 ---
 include/drm/drm_device.h|  5 +
 4 files changed, 48 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 73ade0513ccb..65065f7e1499 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -120,6 +120,10 @@ int drm_authmagic(struct drm_device *dev, void *data,
DRM_DEBUG("%u\n", auth->magic);
 
down_write(&dev->master_rwsem);
+   if (unlikely(!drm_is_current_master(file_priv))) {
+   up_write(&dev->master_rwsem);
+   return -EACCES;
+   }
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 158629d88319..8bea39ffc5c0 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -386,6 +386,10 @@ static int drm_setversion(struct drm_device *dev, void 
*data, struct drm_file *f
int if_version, retcode = 0;
 
down_write(&dev->master_rwsem);
+   if (unlikely(!drm_is_current_master(file_priv))) {
+   retcode = -EACCES;
+   goto unlock;
+   }
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) {
@@ -420,8 +424,9 @@ 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_write(&dev->master_rwsem);
 
+unlock:
+   up_write(&dev->master_rwsem);
return retcode;
 }
 
@@ -574,12 +579,12 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
DRM_IOCTL_DEF(DRM_IOCTL_GET_STATS, drm_getstats, 0),
DRM_IOCTL_DEF(DRM_IOCTL_GET_CAP, drm_getcap, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_SET_CLIENT_CAP, drm_setclientcap, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, 0),
 
DRM_IOCTL_DEF(DRM_IOCTL_SET_UNIQUE, drm_invalid_op, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_BLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_UNBLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
-   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, 0),
 
DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_ADD_MAP, drm_legacy_addmap_ioctl, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_RM_MAP, drm_legacy_rmmap_ioctl, 
DRM_AUTH),
@@ -706,10 +711,10 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
  DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_CRTC_GET_SEQUENCE, drm_crtc_get_sequence_ioctl, 
0),
DRM_IOCTL_DEF(DRM_IOCTL_CRTC_QUEUE_SEQUENCE, 
drm_crtc_queue_sequence_ioctl, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_LEASE, drm_mode_create_lease_ioctl, 
DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_LEASE, drm_mode_cre

[Intel-gfx] [PATCH v5 3/6] drm: lock drm_global_mutex earlier in the ioctl handler

2021-08-23 Thread Desmond Cheong Zhi Xi
In a future patch, a read lock on drm_device.master_rwsem is
held in the ioctl handler before the check for ioctl
permissions. However, this inverts the lock hierarchy of
drm_global_mutex --> master_rwsem.

To avoid this, we do some prep work to grab the drm_global_mutex
before checking for ioctl permissions.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_ioctl.c | 18 +-
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index d25713b09b80..158629d88319 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -772,19 +772,19 @@ long drm_ioctl_kernel(struct file *file, drm_ioctl_t 
*func, void *kdata,
if (drm_dev_is_unplugged(dev))
return -ENODEV;
 
+   /* Enforce sane locking for modern driver ioctls. */
+   if (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY)) && !(flags & 
DRM_UNLOCKED))
+   mutex_lock(&drm_global_mutex);
+
retcode = drm_ioctl_permit(flags, file_priv);
if (unlikely(retcode))
-   return retcode;
+   goto out;
 
-   /* Enforce sane locking for modern driver ioctls. */
-   if (likely(!drm_core_check_feature(dev, DRIVER_LEGACY)) ||
-   (flags & DRM_UNLOCKED))
-   retcode = func(dev, kdata, file_priv);
-   else {
-   mutex_lock(&drm_global_mutex);
-   retcode = func(dev, kdata, file_priv);
+   retcode = func(dev, kdata, file_priv);
+
+out:
+   if (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY)) && !(flags & 
DRM_UNLOCKED))
mutex_unlock(&drm_global_mutex);
-   }
return retcode;
 }
 EXPORT_SYMBOL(drm_ioctl_kernel);
-- 
2.25.1



[Intel-gfx] [PATCH v5 2/6] drm: convert drm_device.master_mutex into a rwsem

2021-08-23 Thread Desmond Cheong Zhi Xi
drm_device.master_mutex currently protects the following:
- drm_device.master
- drm_file.master
- drm_file.was_master
- drm_file.is_master
- drm_master.unique
- drm_master.unique_len
- drm_master.magic_map

There is a clear separation between functions that read or change
these attributes. Hence, convert master_mutex into a rwsem to enable
concurrent readers.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c| 35 ++-
 drivers/gpu/drm/drm_debugfs.c |  4 ++--
 drivers/gpu/drm/drm_drv.c |  3 +--
 drivers/gpu/drm/drm_ioctl.c   | 10 +-
 include/drm/drm_auth.h|  6 +++---
 include/drm/drm_device.h  | 10 ++
 include/drm/drm_file.h| 12 ++--
 7 files changed, 41 insertions(+), 39 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 60a6b21474b1..73ade0513ccb 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -64,7 +64,7 @@
 static bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
-   lockdep_is_held(&fpriv->minor->dev->master_mutex));
+   lockdep_is_held(&fpriv->minor->dev->master_rwsem));
 
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
@@ -96,7 +96,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
struct drm_auth *auth = data;
int ret = 0;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!file_priv->magic) {
ret = idr_alloc(&file_priv->master->magic_map, file_priv,
1, 0, GFP_KERNEL);
@@ -104,7 +104,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
file_priv->magic = ret;
}
auth->magic = file_priv->magic;
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
DRM_DEBUG("%u\n", auth->magic);
 
@@ -119,13 +119,13 @@ int drm_authmagic(struct drm_device *dev, void *data,
 
DRM_DEBUG("%u\n", auth->magic);
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
idr_replace(&file_priv->master->magic_map, NULL, auth->magic);
}
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
return file ? 0 : -EINVAL;
 }
@@ -167,7 +167,7 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
struct drm_master *old_master;
struct drm_master *new_master;
 
-   lockdep_assert_held_once(&dev->master_mutex);
+   lockdep_assert_held_once(&dev->master_rwsem);
 
WARN_ON(fpriv->is_master);
old_master = fpriv->master;
@@ -249,7 +249,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -281,7 +281,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 
drm_set_master(dev, file_priv, false);
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -298,7 +298,7 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -321,8 +321,9 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
}
 
drm_drop_master(dev, file_priv);
+
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -334,7 +335,7 @@ int drm_master_open(struct drm_file *file_priv)
/* if there is no current master make this fd it, but do not create
 * any master object for render clients
 */
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
} else {
@@ -342,7 +343,7 @@ int drm_master_open(struct drm_file *file_priv)
file_priv->master = drm_master_get(dev->master);
spin_unlock(&file_priv->master_lookup_lock);
}
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->ma

[Intel-gfx] [PATCH v5 1/6] drm: fix null ptr dereference in drm_master_release

2021-08-23 Thread Desmond Cheong Zhi Xi
drm_master_release can be called on a drm_file without a master, which
results in a null ptr dereference of file_priv->master->magic_map. The
three cases are:

1. Error path in drm_open_helper
  drm_open():
drm_open_helper():
  drm_master_open():
drm_new_set_master(); <--- returns -ENOMEM,
   drm_file.master not set
  drm_file_free():
drm_master_release(); <--- NULL ptr dereference
   (file_priv->master->magic_map)

2. Error path in mock_drm_getfile
  mock_drm_getfile():
anon_inode_getfile(); <--- returns error, drm_file.master not set
drm_file_free():
  drm_master_release(); <--- NULL ptr dereference
 (file_priv->master->magic_map)

3. In drm_client_close, as drm_client_open doesn't set up a master

drm_file.master is set up in drm_open_helper through the call to
drm_master_open, so we mirror it with a call to drm_master_release in
drm_close_helper, and remove drm_master_release from drm_file_free to
avoid the null ptr dereference.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_file.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index ed25168619fc..90b62f360da1 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -282,9 +282,6 @@ void drm_file_free(struct drm_file *file)
 
drm_legacy_ctxbitmap_flush(dev, file);
 
-   if (drm_is_primary_client(file))
-   drm_master_release(file);
-
if (dev->driver->postclose)
dev->driver->postclose(dev, file);
 
@@ -305,6 +302,9 @@ static void drm_close_helper(struct file *filp)
list_del(&file_priv->lhead);
mutex_unlock(&dev->filelist_mutex);
 
+   if (drm_is_primary_client(file_priv))
+   drm_master_release(file_priv);
+
drm_file_free(file_priv);
 }
 
-- 
2.25.1



[Intel-gfx] [PATCH v5 0/6] drm: update locking for modesetting

2021-08-23 Thread Desmond Cheong Zhi Xi
Hi,

I updated the series to untangle more lock inversions caught by the
Intel-gfx CI, but again there might be more that I missed.

This series now converts drm_device.master_mutex into master_rwsem, and
also removes drm_file.master_lookup_lock.

Overall, this series makes the following changes:

- Patch 1: Fix a potential null ptr dereference in drm_master_release

- Patch 2: Convert master_mutex into rwsem (avoids creating a new lock)

- Patch 3: Update global mutex locking in the ioctl handler (avoids
deadlock when grabbing read lock on master_rwsem in drm_ioctl_kernel)

- Patch 4: Plug races with drm modesetting rights

- Patch 5: Untagle remaining lock hierarchy inversions between
modeset_mutex and master_rwsem

- Patch 6: Replace master_lookup_lock with master_rwsem

v4 -> v5:
- Avoid calling drm_file_get_master while holding on to the modeset
mutex, caught by the Intel-gfx CI (split previous patch 5 into the new
patch 5 & 6)

v3 -> v4 (suggested by Daniel Vetter):
- Drop a patch that added an unnecessary master_lookup_lock in
drm_master_release (previously patch 2)
- Drop a patch that addressed a non-existent race in
drm_is_current_master_locked (previously patch 3)
- Remove fixes for non-existent null ptr dereferences (previous patch 4)
- Protect drm_master.magic_map,unique{_len} with master_rwsem instead of
master_lookup_lock (dropped previous patch 5)
- Drop the patch that moved master_lookup_lock into struct drm_device
(previously patch 1)
- Drop a patch to export task_work_add (previously patch 8)
- Revert the check for the global mutex in the ioctl handler to use
drm_core_check_feature instead of drm_dev_needs_global_mutex
- Push down master_rwsem locking for selected ioctls to avoid lock
hierarchy inversions, and to allow us to hold write locks on
master_rwsem instead of flushing readers
- Remove master_lookup_lock by replacing it with master_rwsem

v2 -> v3:
- Unexport drm_master_flush, as suggested by Daniel Vetter.
- Merge master_mutex and master_rwsem, as suggested by Daniel Vetter.
- Export task_work_add, reported by kernel test robot.
- Make master_flush static, reported by kernel test robot.
- Move master_lookup_lock into struct drm_device.
- Add a missing lock on master_lookup_lock in drm_master_release.
- Fix a potential race in drm_is_current_master_locked.
- Fix potential null ptr dereferences in drm_{auth, ioctl}.
- Protect magic_map,unique{_len} with  master_lookup_lock.
- Convert master_mutex into a rwsem.
- Update global mutex locking in the ioctl handler.

v1 -> v2 (suggested by Daniel Vetter):
- Address an additional race when drm_open runs.
- Switch from SRCU to rwsem to synchronise readers and writers.
- Implement drm_master_flush with task_work so that flushes can be
queued to run before returning to userspace without creating a new
DRM_MASTER_FLUSH ioctl flag.

Best wishes,
Desmond

Desmond Cheong Zhi Xi (6):
  drm: fix null ptr dereference in drm_master_release
  drm: convert drm_device.master_mutex into a rwsem
  drm: lock drm_global_mutex earlier in the ioctl handler
  drm: avoid races with modesetting rights
  drm: avoid circular locks with modeset_mutex and master_rwsem
  drm: remove drm_file.master_lookup_lock

 drivers/gpu/drm/drm_atomic_uapi.c |  4 +-
 drivers/gpu/drm/drm_auth.c| 57 +++--
 drivers/gpu/drm/drm_debugfs.c |  4 +-
 drivers/gpu/drm/drm_drv.c |  3 +-
 drivers/gpu/drm/drm_encoder.c |  7 ++-
 drivers/gpu/drm/drm_file.c|  7 ++-
 drivers/gpu/drm/drm_framebuffer.c |  2 +-
 drivers/gpu/drm/drm_internal.h|  1 +
 drivers/gpu/drm/drm_ioctl.c   | 48 +++---
 drivers/gpu/drm/drm_lease.c   | 83 ++-
 drivers/gpu/drm/drm_mode_object.c | 16 --
 drivers/gpu/drm/drm_plane.c   | 17 +--
 drivers/gpu/drm/drm_property.c|  6 +--
 include/drm/drm_auth.h|  6 +--
 include/drm/drm_device.h  | 15 --
 include/drm/drm_file.h| 17 ++-
 include/drm/drm_lease.h   |  4 +-
 17 files changed, 165 insertions(+), 132 deletions(-)

-- 
2.25.1



[Intel-gfx] [PATCH v4 5/5] drm: remove drm_file.master_lookup_lock

2021-08-20 Thread Desmond Cheong Zhi Xi
Previously, master_lookup_lock was introduced in
commit 0b0860a3cf5e ("drm: serialize drm_file.master with a new
spinlock") to serialize accesses to drm_file.master. This then allowed
us to write drm_file_get_master in commit 56f0729a510f ("drm: protect
drm_master pointers in drm_lease.c").

The rationale behind introducing a new spinlock at the time was that
the other lock that could have been used (drm_device.master_mutex) was
the outermost lock, so embedding calls to drm_file_get_master in
various functions easily caused us to invert the lock hierarchy. For
example, we would invert the master_mutex --> mode_config.idr_mutex
hierarchy.

Following the conversion of master_mutex into a rwsem, and its use to
plug races with modesetting rights, we've untangled some lock
hierarchies and removed the need for using drm_file_get_master and the
unlocked version of drm_is_current_master in multiple places.

Hence, we can take this opportunity to clean up the locking design. To
do so, we rewrite drm_file_get_master and drm_is_current_master to use
master_rwsem, clean up their uses, fix remaining inversions (such as
with _drm_lease_held master grabbing master_rwsem while holding onto
mode_config.idr_mutex), then remove master_lookup_lock.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c| 19 ++--
 drivers/gpu/drm/drm_file.c|  1 -
 drivers/gpu/drm/drm_internal.h|  1 +
 drivers/gpu/drm/drm_ioctl.c   |  4 ++--
 drivers/gpu/drm/drm_lease.c   | 38 ---
 drivers/gpu/drm/drm_mode_object.c | 14 +---
 include/drm/drm_file.h|  9 +---
 include/drm/drm_lease.h   |  2 +-
 8 files changed, 32 insertions(+), 56 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 65065f7e1499..ff720f67eacb 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -61,10 +61,9 @@
  * trusted clients.
  */
 
-static bool drm_is_current_master_locked(struct drm_file *fpriv)
+bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
-   lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
-   lockdep_is_held(&fpriv->minor->dev->master_rwsem));
+   lockdep_assert_held_once(&fpriv->minor->dev->master_rwsem);
 
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
@@ -83,9 +82,9 @@ bool drm_is_current_master(struct drm_file *fpriv)
 {
bool ret;
 
-   spin_lock(&fpriv->master_lookup_lock);
+   down_read(&fpriv->minor->dev->master_rwsem);
ret = drm_is_current_master_locked(fpriv);
-   spin_unlock(&fpriv->master_lookup_lock);
+   up_read(&fpriv->minor->dev->master_rwsem);
 
return ret;
 }
@@ -120,7 +119,7 @@ int drm_authmagic(struct drm_device *dev, void *data,
DRM_DEBUG("%u\n", auth->magic);
 
down_write(&dev->master_rwsem);
-   if (unlikely(!drm_is_current_master(file_priv))) {
+   if (unlikely(!drm_is_current_master_locked(file_priv))) {
up_write(&dev->master_rwsem);
return -EACCES;
}
@@ -178,9 +177,7 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
new_master = drm_master_create(dev);
if (!new_master)
return -ENOMEM;
-   spin_lock(&fpriv->master_lookup_lock);
fpriv->master = new_master;
-   spin_unlock(&fpriv->master_lookup_lock);
 
fpriv->is_master = 1;
fpriv->authenticated = 1;
@@ -343,9 +340,7 @@ int drm_master_open(struct drm_file *file_priv)
if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
} else {
-   spin_lock(&file_priv->master_lookup_lock);
file_priv->master = drm_master_get(dev->master);
-   spin_unlock(&file_priv->master_lookup_lock);
}
up_write(&dev->master_rwsem);
 
@@ -410,13 +405,13 @@ struct drm_master *drm_file_get_master(struct drm_file 
*file_priv)
 {
struct drm_master *master = NULL;
 
-   spin_lock(&file_priv->master_lookup_lock);
+   down_read(&file_priv->minor->dev->master_rwsem);
if (!file_priv->master)
goto unlock;
master = drm_master_get(file_priv->master);
 
 unlock:
-   spin_unlock(&file_priv->master_lookup_lock);
+   up_read(&file_priv->minor->dev->master_rwsem);
return master;
 }
 EXPORT_SYMBOL(drm_file_get_master);
diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index 90b62f360da1..8c846e0179d7 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -176,7 +176,6 @@ struct drm_file *drm_file_alloc(st

[Intel-gfx] [PATCH v4 4/5] drm: avoid races with modesetting rights

2021-08-20 Thread Desmond Cheong Zhi Xi
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 hold a write lock.

To avoid deadlocks with master_rwsem, for ioctls that need to check
for modesetting permissions, but also need to hold a write lock on
master_rwsem to protect some other attribute (or recurses to some
function that holds a write lock, like drm_mode_create_lease_ioctl
which eventually calls drm_master_open), we remove the DRM_MASTER flag
and push the master_rwsem lock and permissions check into the ioctl.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c  |  4 
 drivers/gpu/drm/drm_ioctl.c | 20 +++-
 drivers/gpu/drm/drm_lease.c | 35 ---
 include/drm/drm_device.h|  5 +
 4 files changed, 48 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 73ade0513ccb..65065f7e1499 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -120,6 +120,10 @@ int drm_authmagic(struct drm_device *dev, void *data,
DRM_DEBUG("%u\n", auth->magic);
 
down_write(&dev->master_rwsem);
+   if (unlikely(!drm_is_current_master(file_priv))) {
+   up_write(&dev->master_rwsem);
+   return -EACCES;
+   }
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 158629d88319..8bea39ffc5c0 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -386,6 +386,10 @@ static int drm_setversion(struct drm_device *dev, void 
*data, struct drm_file *f
int if_version, retcode = 0;
 
down_write(&dev->master_rwsem);
+   if (unlikely(!drm_is_current_master(file_priv))) {
+   retcode = -EACCES;
+   goto unlock;
+   }
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) {
@@ -420,8 +424,9 @@ 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_write(&dev->master_rwsem);
 
+unlock:
+   up_write(&dev->master_rwsem);
return retcode;
 }
 
@@ -574,12 +579,12 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
DRM_IOCTL_DEF(DRM_IOCTL_GET_STATS, drm_getstats, 0),
DRM_IOCTL_DEF(DRM_IOCTL_GET_CAP, drm_getcap, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_SET_CLIENT_CAP, drm_setclientcap, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_SET_VERSION, drm_setversion, 0),
 
DRM_IOCTL_DEF(DRM_IOCTL_SET_UNIQUE, drm_invalid_op, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_BLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_IOCTL_UNBLOCK, drm_noop, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
-   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_AUTH_MAGIC, drm_authmagic, 0),
 
DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_ADD_MAP, drm_legacy_addmap_ioctl, 
DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_RM_MAP, drm_legacy_rmmap_ioctl, 
DRM_AUTH),
@@ -706,10 +711,10 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
  DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_CRTC_GET_SEQUENCE, drm_crtc_get_sequence_ioctl, 
0),
DRM_IOCTL_DEF(DRM_IOCTL_CRTC_QUEUE_SEQUENCE, 
drm_crtc_queue_sequence_ioctl, 0),
-   DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_LEASE, drm_mode_create_lease_ioctl, 
DRM_MASTER),
+   DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_LEASE, drm_mode_cre

[Intel-gfx] [PATCH v4 3/5] drm: lock drm_global_mutex earlier in the ioctl handler

2021-08-20 Thread Desmond Cheong Zhi Xi
In a future patch, a read lock on drm_device.master_rwsem is
held in the ioctl handler before the check for ioctl
permissions. However, this inverts the lock hierarchy of
drm_global_mutex --> master_rwsem.

To avoid this, we do some prep work to grab the drm_global_mutex
before checking for ioctl permissions.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_ioctl.c | 18 +-
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index d25713b09b80..158629d88319 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -772,19 +772,19 @@ long drm_ioctl_kernel(struct file *file, drm_ioctl_t 
*func, void *kdata,
if (drm_dev_is_unplugged(dev))
return -ENODEV;
 
+   /* Enforce sane locking for modern driver ioctls. */
+   if (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY)) && !(flags & 
DRM_UNLOCKED))
+   mutex_lock(&drm_global_mutex);
+
retcode = drm_ioctl_permit(flags, file_priv);
if (unlikely(retcode))
-   return retcode;
+   goto out;
 
-   /* Enforce sane locking for modern driver ioctls. */
-   if (likely(!drm_core_check_feature(dev, DRIVER_LEGACY)) ||
-   (flags & DRM_UNLOCKED))
-   retcode = func(dev, kdata, file_priv);
-   else {
-   mutex_lock(&drm_global_mutex);
-   retcode = func(dev, kdata, file_priv);
+   retcode = func(dev, kdata, file_priv);
+
+out:
+   if (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY)) && !(flags & 
DRM_UNLOCKED))
mutex_unlock(&drm_global_mutex);
-   }
return retcode;
 }
 EXPORT_SYMBOL(drm_ioctl_kernel);
-- 
2.25.1



[Intel-gfx] [PATCH v4 2/5] drm: convert drm_device.master_mutex into a rwsem

2021-08-20 Thread Desmond Cheong Zhi Xi
drm_device.master_mutex currently protects the following:
- drm_device.master
- drm_file.master
- drm_file.was_master
- drm_file.is_master
- drm_master.unique
- drm_master.unique_len
- drm_master.magic_map

There is a clear separation between functions that read or change
these attributes. Hence, convert master_mutex into a rwsem to enable
concurrent readers.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c| 35 ++-
 drivers/gpu/drm/drm_debugfs.c |  4 ++--
 drivers/gpu/drm/drm_drv.c |  3 +--
 drivers/gpu/drm/drm_ioctl.c   | 10 +-
 include/drm/drm_auth.h|  6 +++---
 include/drm/drm_device.h  | 10 ++
 include/drm/drm_file.h| 12 ++--
 7 files changed, 41 insertions(+), 39 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 60a6b21474b1..73ade0513ccb 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -64,7 +64,7 @@
 static bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
-   lockdep_is_held(&fpriv->minor->dev->master_mutex));
+   lockdep_is_held(&fpriv->minor->dev->master_rwsem));
 
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
@@ -96,7 +96,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
struct drm_auth *auth = data;
int ret = 0;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!file_priv->magic) {
ret = idr_alloc(&file_priv->master->magic_map, file_priv,
1, 0, GFP_KERNEL);
@@ -104,7 +104,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
file_priv->magic = ret;
}
auth->magic = file_priv->magic;
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
DRM_DEBUG("%u\n", auth->magic);
 
@@ -119,13 +119,13 @@ int drm_authmagic(struct drm_device *dev, void *data,
 
DRM_DEBUG("%u\n", auth->magic);
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
idr_replace(&file_priv->master->magic_map, NULL, auth->magic);
}
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
return file ? 0 : -EINVAL;
 }
@@ -167,7 +167,7 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
struct drm_master *old_master;
struct drm_master *new_master;
 
-   lockdep_assert_held_once(&dev->master_mutex);
+   lockdep_assert_held_once(&dev->master_rwsem);
 
WARN_ON(fpriv->is_master);
old_master = fpriv->master;
@@ -249,7 +249,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -281,7 +281,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 
drm_set_master(dev, file_priv, false);
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -298,7 +298,7 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -321,8 +321,9 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
}
 
drm_drop_master(dev, file_priv);
+
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -334,7 +335,7 @@ int drm_master_open(struct drm_file *file_priv)
/* if there is no current master make this fd it, but do not create
 * any master object for render clients
 */
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
} else {
@@ -342,7 +343,7 @@ int drm_master_open(struct drm_file *file_priv)
file_priv->master = drm_master_get(dev->master);
spin_unlock(&file_priv->master_lookup_lock);
}
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->ma

[Intel-gfx] [PATCH v4 1/5] drm: fix null ptr dereference in drm_master_release

2021-08-20 Thread Desmond Cheong Zhi Xi
drm_master_release can be called on a drm_file without a master, which
results in a null ptr dereference of file_priv->master->magic_map. The
three cases are:

1. Error path in drm_open_helper
  drm_open():
drm_open_helper():
  drm_master_open():
drm_new_set_master(); <--- returns -ENOMEM,
   drm_file.master not set
  drm_file_free():
drm_master_release(); <--- NULL ptr dereference
   (file_priv->master->magic_map)

2. Error path in mock_drm_getfile
  mock_drm_getfile():
anon_inode_getfile(); <--- returns error, drm_file.master not set
drm_file_free():
  drm_master_release(); <--- NULL ptr dereference
 (file_priv->master->magic_map)

3. In drm_client_close, as drm_client_open doesn't set up a master

drm_file.master is set up in drm_open_helper through the call to
drm_master_open, so we mirror it with a call to drm_master_release in
drm_close_helper, and remove drm_master_release from drm_file_free to
avoid the null ptr dereference.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_file.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index ed25168619fc..90b62f360da1 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -282,9 +282,6 @@ void drm_file_free(struct drm_file *file)
 
drm_legacy_ctxbitmap_flush(dev, file);
 
-   if (drm_is_primary_client(file))
-   drm_master_release(file);
-
if (dev->driver->postclose)
dev->driver->postclose(dev, file);
 
@@ -305,6 +302,9 @@ static void drm_close_helper(struct file *filp)
list_del(&file_priv->lhead);
mutex_unlock(&dev->filelist_mutex);
 
+   if (drm_is_primary_client(file_priv))
+   drm_master_release(file_priv);
+
drm_file_free(file_priv);
 }
 
-- 
2.25.1



[Intel-gfx] [PATCH v4 0/5] drm: update locking for modesetting

2021-08-20 Thread Desmond Cheong Zhi Xi
Hi,

Thanks for all the helpful feedback on the previous version.

Taking all the suggestions together, this series now converts
drm_device.master_mutex into master_rwsem, and also attempts to remove
drm_file.master_lookup_lock. There might still be lock inversions
lurking, so the output from Intel-gfx CI should be interesting to see.

Overall, this series makes the following changes:

- Patch 1: Fix a potential null ptr dereference in drm_master_release

- Patch 2: Convert master_mutex into rwsem (avoids creating a new lock)

- Patch 3: Update global mutex locking in the ioctl handler (avoids
deadlock when grabbing read lock on master_rwsem in drm_ioctl_kernel)

- Patch 4: Plug races with drm modesetting rights

- Patch 5: Replace master_lookup_lock with master_rwsem by untangling
remaining lock hierarchy inversions

v3 -> v4 (suggested by Daniel Vetter):
- Drop a patch that added an unnecessary master_lookup_lock in
drm_master_release (previously patch 2)
- Drop a patch that addressed a non-existent race in
drm_is_current_master_locked (previously patch 3)
- Remove fixes for non-existent null ptr dereferences (previous patch 4)
- Protect drm_master.magic_map,unique{_len} with master_rwsem instead of
master_lookup_lock (dropped previous patch 5)
- Drop the patch that moved master_lookup_lock into struct drm_device
(previously patch 1)
- Drop a patch to export task_work_add (previously patch 8)
- Revert the check for the global mutex in the ioctl handler to use
drm_core_check_feature instead of drm_dev_needs_global_mutex
- Push down master_rwsem locking for selected ioctls to avoid lock
hierarchy inversions, and to allow us to hold write locks on
master_rwsem instead of flushing readers
- Remove master_lookup_lock by replacing it with master_rwsem

v2 -> v3:
- Unexport drm_master_flush, as suggested by Daniel Vetter.
- Merge master_mutex and master_rwsem, as suggested by Daniel Vetter.
- Export task_work_add, reported by kernel test robot.
- Make master_flush static, reported by kernel test robot.
- Move master_lookup_lock into struct drm_device.
- Add a missing lock on master_lookup_lock in drm_master_release.
- Fix a potential race in drm_is_current_master_locked.
- Fix potential null ptr dereferences in drm_{auth, ioctl}.
- Protect magic_map,unique{_len} with  master_lookup_lock.
- Convert master_mutex into a rwsem.
- Update global mutex locking in the ioctl handler.

v1 -> v2 (suggested by Daniel Vetter):
- Address an additional race when drm_open runs.
- Switch from SRCU to rwsem to synchronise readers and writers.
- Implement drm_master_flush with task_work so that flushes can be
queued to run before returning to userspace without creating a new
DRM_MASTER_FLUSH ioctl flag.

Best wishes,
Desmond

Desmond Cheong Zhi Xi (5):
  drm: fix null ptr dereference in drm_master_release
  drm: convert drm_device.master_mutex into a rwsem
  drm: lock drm_global_mutex earlier in the ioctl handler
  drm: avoid races with modesetting rights
  drm: remove drm_file.master_lookup_lock

 drivers/gpu/drm/drm_auth.c| 54 
 drivers/gpu/drm/drm_debugfs.c |  4 +-
 drivers/gpu/drm/drm_drv.c |  3 +-
 drivers/gpu/drm/drm_file.c|  7 ++--
 drivers/gpu/drm/drm_internal.h|  1 +
 drivers/gpu/drm/drm_ioctl.c   | 48 -
 drivers/gpu/drm/drm_lease.c   | 69 ++-
 drivers/gpu/drm/drm_mode_object.c | 14 +--
 include/drm/drm_auth.h|  6 +--
 include/drm/drm_device.h  | 15 +--
 include/drm/drm_file.h| 17 +++-
 include/drm/drm_lease.h   |  2 +-
 12 files changed, 125 insertions(+), 115 deletions(-)

-- 
2.25.1



Re: [Intel-gfx] [PATCH v3 7/9] drm: update global mutex lock in the ioctl handler

2021-08-19 Thread Desmond Cheong Zhi Xi

On 18/8/21 7:02 pm, Daniel Vetter wrote:

On Wed, Aug 18, 2021 at 03:38:22PM +0800, Desmond Cheong Zhi Xi wrote:

In a future patch, a read lock on drm_device.master_rwsem is
held in the ioctl handler before the check for ioctl
permissions. However, this produces the following lockdep splat:

==
WARNING: possible circular locking dependency detected
5.14.0-rc6-CI-Patchwork_20831+ #1 Tainted: G U
--
kms_lease/1752 is trying to acquire lock:
827bad88 (drm_global_mutex){+.+.}-{3:3}, at: drm_open+0x64/0x280

but task is already holding lock:
88812e350108 (&dev->master_rwsem){}-{3:3}, at:
drm_ioctl_kernel+0xfb/0x1a0

which lock already depends on the new lock.

the existing dependency chain (in reverse order) is:

-> #2 (&dev->master_rwsem){}-{3:3}:
lock_acquire+0xd3/0x310
down_read+0x3b/0x140
drm_master_internal_acquire+0x1d/0x60
drm_client_modeset_commit+0x10/0x40
__drm_fb_helper_restore_fbdev_mode_unlocked+0x88/0xb0
drm_fb_helper_set_par+0x34/0x40
intel_fbdev_set_par+0x11/0x40 [i915]
fbcon_init+0x270/0x4f0
visual_init+0xc6/0x130
do_bind_con_driver+0x1de/0x2c0
do_take_over_console+0x10e/0x180
do_fbcon_takeover+0x53/0xb0
register_framebuffer+0x22d/0x310
__drm_fb_helper_initial_config_and_unlock+0x36c/0x540
intel_fbdev_initial_config+0xf/0x20 [i915]
async_run_entry_fn+0x28/0x130
process_one_work+0x26d/0x5c0
worker_thread+0x37/0x390
kthread+0x13b/0x170
ret_from_fork+0x1f/0x30

-> #1 (&helper->lock){+.+.}-{3:3}:
lock_acquire+0xd3/0x310
__mutex_lock+0xa8/0x930
__drm_fb_helper_restore_fbdev_mode_unlocked+0x44/0xb0
intel_fbdev_restore_mode+0x2b/0x50 [i915]
drm_lastclose+0x27/0x50
drm_release_noglobal+0x42/0x60
__fput+0x9e/0x250
task_work_run+0x6b/0xb0
exit_to_user_mode_prepare+0x1c5/0x1d0
syscall_exit_to_user_mode+0x19/0x50
do_syscall_64+0x46/0xb0
entry_SYSCALL_64_after_hwframe+0x44/0xae

-> #0 (drm_global_mutex){+.+.}-{3:3}:
validate_chain+0xb39/0x1e70
__lock_acquire+0x5a1/0xb70
lock_acquire+0xd3/0x310
__mutex_lock+0xa8/0x930
drm_open+0x64/0x280
drm_stub_open+0x9f/0x100
chrdev_open+0x9f/0x1d0
do_dentry_open+0x14a/0x3a0
dentry_open+0x53/0x70
drm_mode_create_lease_ioctl+0x3cb/0x970
drm_ioctl_kernel+0xc9/0x1a0
drm_ioctl+0x201/0x3d0
__x64_sys_ioctl+0x6a/0xa0
do_syscall_64+0x37/0xb0
entry_SYSCALL_64_after_hwframe+0x44/0xae

other info that might help us debug this:
Chain exists of:
   drm_global_mutex --> &helper->lock --> &dev->master_rwsem
  Possible unsafe locking scenario:
CPU0CPU1

   lock(&dev->master_rwsem);
lock(&helper->lock);
lock(&dev->master_rwsem);
   lock(drm_global_mutex);

  *** DEADLOCK ***

The lock hierarchy inversion happens because we grab the
drm_global_mutex while already holding on to master_rwsem. To avoid
this, we do some prep work to grab the drm_global_mutex before
checking for ioctl permissions.

At the same time, we update the check for the global mutex to use the
drm_dev_needs_global_mutex helper function.


This is intentional, essentially we force all non-legacy drivers to have
unlocked ioctl (otherwise everyone forgets to set that flag).

For non-legacy drivers the global lock only ensures ordering between
drm_open and lastclose (I think at least), and between
drm_dev_register/unregister and the backwards ->load/unload callbacks
(which are called in the wrong place, but we cannot fix that for legacy
drivers).

->load/unload should be completely unused (maybe radeon still uses it),
and ->lastclose is also on the decline.



Ah ok got it, I'll change the check back to
drm_core_check_feature(dev, DRIVER_LEGACY) then.


Maybe we should update the comment of drm_global_mutex to explain what it
protects and why.



The comments in drm_dev_needs_global_mutex make sense I think, I just
didn't read the code closely enough.


I'm also confused how this patch connects to the splat, since for i915 we


Right, my bad, this is a separate instance of circular locking. I was
too hasty when I saw that for legacy drivers we might grab master_rwsem
then drm_global_mutex in the ioctl handler.


shouldn't be taking the drm_global_lock here at all. The problem seems to
be the drm_open_helper when we create a new lease, which is an entirely
different can of worms.

I'm honestly not sure how to best do that, but we should be able to create
a file and then call drm_open_helper directly

Re: [Intel-gfx] [PATCH v3 8/9] kernel: export task_work_add

2021-08-19 Thread Desmond Cheong Zhi Xi

On 19/8/21 5:26 pm, Christoph Hellwig wrote:

On Wed, Aug 18, 2021 at 03:38:23PM +0800, Desmond Cheong Zhi Xi wrote:

+EXPORT_SYMBOL(task_work_add);


EXPORT_SYMBOL_GPL for this kinds of functionality, please.



Thanks, I wasn't aware of the GPL-only export. I'll update this in a 
future series if we still need the export.


Re: [Intel-gfx] [PATCH v3 4/9] drm: fix potential null ptr dereferences in drm_{auth, ioctl}

2021-08-18 Thread Desmond Cheong Zhi Xi

On 19/8/21 12:33 am, Daniel Vetter wrote:

On Wed, Aug 18, 2021 at 5:37 PM Desmond Cheong Zhi Xi
 wrote:


On 18/8/21 6:11 pm, Daniel Vetter wrote:

On Wed, Aug 18, 2021 at 03:38:19PM +0800, Desmond Cheong Zhi Xi wrote:

There are three areas where we dereference struct drm_master without
checking if the pointer is non-NULL.

1. drm_getmagic is called from the ioctl_handler. Since
DRM_IOCTL_GET_MAGIC has no ioctl flags, drm_getmagic is run without
any check that drm_file.master has been set.

2. Similarly, drm_getunique is called from the ioctl_handler, but
DRM_IOCTL_GET_UNIQUE has no ioctl flags. So there is no guarantee that
drm_file.master has been set.


I think the above two are impossible, due to the refcounting rules for
struct file.



Right, will drop those two parts from the patch.


3. drm_master_release can also be called without having a
drm_file.master set. Here is one error path:
drm_open():
  drm_open_helper():
drm_master_open():
  drm_new_set_master(); <--- returns -ENOMEM,
 drm_file.master not set
drm_file_free():
  drm_master_release(); <--- NULL ptr dereference
 (file_priv->master->magic_map)

Fix these by checking if the master pointers are NULL before use.

Signed-off-by: Desmond Cheong Zhi Xi 
---
   drivers/gpu/drm/drm_auth.c  | 16 ++--
   drivers/gpu/drm/drm_ioctl.c |  5 +
   2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index f9267b21556e..b7230604496b 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -95,11 +95,18 @@ EXPORT_SYMBOL(drm_is_current_master);
   int drm_getmagic(struct drm_device *dev, void *data, struct drm_file 
*file_priv)
   {
  struct drm_auth *auth = data;
+struct drm_master *master;
  int ret = 0;

  mutex_lock(&dev->master_mutex);
+master = file_priv->master;
+if (!master) {
+mutex_unlock(&dev->master_mutex);
+return -EINVAL;
+}
+
  if (!file_priv->magic) {
-ret = idr_alloc(&file_priv->master->magic_map, file_priv,
+ret = idr_alloc(&master->magic_map, file_priv,
  1, 0, GFP_KERNEL);
  if (ret >= 0)
  file_priv->magic = ret;
@@ -355,8 +362,12 @@ void drm_master_release(struct drm_file *file_priv)

  mutex_lock(&dev->master_mutex);
  master = file_priv->master;
+
+if (!master)
+goto unlock;


This is a bit convoluted, since we're in the single-threaded release path
we don't need any locking for file_priv related things. Therefore we can
pull the master check out and just directly return.

But since it's a bit surprising maybe a comment that this can happen when
drm_master_open in drm_open_helper fails?



Sounds good. This can actually also happen in the failure path of
mock_drm_getfile if anon_inode_getfile fails. I'll leave a short note
about both of them.


Another option, and maybe cleaner, would be to move the drm_master_release
from drm_file_free into drm_close_helper. That would be fully symmetrical
and should also fix the bug here?
-Daniel


Hmmm maybe the first option to move the check out of the lock might be
better. If I'm not wrong, we would otherwise also need to move
drm_master_release into drm_client_close.


Do we have to?

If I haven't missed anything, the drm_client stuff only calls
drm_file_alloc and doesn't set up a master. So this should work?
-Daniel



A ok yes, I understand what's going on better now. I think you're 
right, moving drm_master_release into drm_close_helper should fix all 
the places it can go wrong.







+
  if (file_priv->magic)
-idr_remove(&file_priv->master->magic_map, file_priv->magic);
+idr_remove(&master->magic_map, file_priv->magic);

  if (!drm_is_current_master_locked(file_priv))
  goto out;
@@ -379,6 +390,7 @@ void drm_master_release(struct drm_file *file_priv)
  drm_master_put(&file_priv->master);
  spin_unlock(&dev->master_lookup_lock);
  }
+unlock:
  mutex_unlock(&dev->master_mutex);
   }

diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 26f3a9ede8fe..4d029d3061d9 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -121,6 +121,11 @@ int drm_getunique(struct drm_device *dev, void *data,

  mutex_lock(&dev->master_mutex);
  master = file_priv->master;
+if (!master) {
+mutex_unlock(&dev->master_mutex);
+return -EINVAL;
+}
+
  if (u->unique_len >= master->unique_len) {
  if (copy_to_user(u->unique, master->unique, master->unique_len)) {
  mutex_unlock(&dev->master_mutex);
--
2.25.1












Re: [Intel-gfx] [PATCH v3 4/9] drm: fix potential null ptr dereferences in drm_{auth, ioctl}

2021-08-18 Thread Desmond Cheong Zhi Xi

On 18/8/21 6:11 pm, Daniel Vetter wrote:

On Wed, Aug 18, 2021 at 03:38:19PM +0800, Desmond Cheong Zhi Xi wrote:

There are three areas where we dereference struct drm_master without
checking if the pointer is non-NULL.

1. drm_getmagic is called from the ioctl_handler. Since
DRM_IOCTL_GET_MAGIC has no ioctl flags, drm_getmagic is run without
any check that drm_file.master has been set.

2. Similarly, drm_getunique is called from the ioctl_handler, but
DRM_IOCTL_GET_UNIQUE has no ioctl flags. So there is no guarantee that
drm_file.master has been set.


I think the above two are impossible, due to the refcounting rules for
struct file.



Right, will drop those two parts from the patch.


3. drm_master_release can also be called without having a
drm_file.master set. Here is one error path:
   drm_open():
 drm_open_helper():
   drm_master_open():
 drm_new_set_master(); <--- returns -ENOMEM,
drm_file.master not set
   drm_file_free():
 drm_master_release(); <--- NULL ptr dereference
(file_priv->master->magic_map)

Fix these by checking if the master pointers are NULL before use.

Signed-off-by: Desmond Cheong Zhi Xi 
---
  drivers/gpu/drm/drm_auth.c  | 16 ++--
  drivers/gpu/drm/drm_ioctl.c |  5 +
  2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index f9267b21556e..b7230604496b 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -95,11 +95,18 @@ EXPORT_SYMBOL(drm_is_current_master);
  int drm_getmagic(struct drm_device *dev, void *data, struct drm_file 
*file_priv)
  {
struct drm_auth *auth = data;
+   struct drm_master *master;
int ret = 0;
  
  	mutex_lock(&dev->master_mutex);

+   master = file_priv->master;
+   if (!master) {
+   mutex_unlock(&dev->master_mutex);
+   return -EINVAL;
+   }
+
if (!file_priv->magic) {
-   ret = idr_alloc(&file_priv->master->magic_map, file_priv,
+   ret = idr_alloc(&master->magic_map, file_priv,
1, 0, GFP_KERNEL);
if (ret >= 0)
file_priv->magic = ret;
@@ -355,8 +362,12 @@ void drm_master_release(struct drm_file *file_priv)
  
  	mutex_lock(&dev->master_mutex);

master = file_priv->master;
+
+   if (!master)
+   goto unlock;


This is a bit convoluted, since we're in the single-threaded release path
we don't need any locking for file_priv related things. Therefore we can
pull the master check out and just directly return.

But since it's a bit surprising maybe a comment that this can happen when
drm_master_open in drm_open_helper fails?



Sounds good. This can actually also happen in the failure path of 
mock_drm_getfile if anon_inode_getfile fails. I'll leave a short note 
about both of them.



Another option, and maybe cleaner, would be to move the drm_master_release
from drm_file_free into drm_close_helper. That would be fully symmetrical
and should also fix the bug here?
-Daniel

Hmmm maybe the first option to move the check out of the lock might be 
better. If I'm not wrong, we would otherwise also need to move 
drm_master_release into drm_client_close.





+
if (file_priv->magic)
-   idr_remove(&file_priv->master->magic_map, file_priv->magic);
+   idr_remove(&master->magic_map, file_priv->magic);
  
  	if (!drm_is_current_master_locked(file_priv))

goto out;
@@ -379,6 +390,7 @@ void drm_master_release(struct drm_file *file_priv)
drm_master_put(&file_priv->master);
spin_unlock(&dev->master_lookup_lock);
}
+unlock:
mutex_unlock(&dev->master_mutex);
  }
  
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c

index 26f3a9ede8fe..4d029d3061d9 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -121,6 +121,11 @@ int drm_getunique(struct drm_device *dev, void *data,
  
  	mutex_lock(&dev->master_mutex);

master = file_priv->master;
+   if (!master) {
+   mutex_unlock(&dev->master_mutex);
+   return -EINVAL;
+   }
+
if (u->unique_len >= master->unique_len) {
if (copy_to_user(u->unique, master->unique, 
master->unique_len)) {
mutex_unlock(&dev->master_mutex);
--
2.25.1







Re: [Intel-gfx] [PATCH v3 2/9] drm: hold master_lookup_lock when releasing a drm_file's master

2021-08-18 Thread Desmond Cheong Zhi Xi

On 18/8/21 6:05 pm, Daniel Vetter wrote:

On Wed, Aug 18, 2021 at 03:38:17PM +0800, Desmond Cheong Zhi Xi wrote:

When drm_file.master changes value, the corresponding
drm_device.master_lookup_lock should be held.

In drm_master_release, a call to drm_master_put sets the
file_priv->master to NULL, so we protect this section with
drm_device.master_lookup_lock.

Signed-off-by: Desmond Cheong Zhi Xi 


At this points all refcounts to drm_file have disappeared, so yeah this is
a lockless access, but also no one can observe it anymore. See also next
patch.

Hence I think the current code is fine.
-Daniel



Ah ok got it, I didn't realize that. I'll drop patch 2 and 3 from the 
series then.



---
  drivers/gpu/drm/drm_auth.c | 5 -
  1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 8efb58aa7d95..8c0e0dba1611 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -373,8 +373,11 @@ void drm_master_release(struct drm_file *file_priv)
}
  
  	/* drop the master reference held by the file priv */

-   if (file_priv->master)
+   if (file_priv->master) {
+   spin_lock(&dev->master_lookup_lock);
drm_master_put(&file_priv->master);
+   spin_unlock(&dev->master_lookup_lock);
+   }
mutex_unlock(&dev->master_mutex);
  }
  
--

2.25.1







[Intel-gfx] [PATCH v3 6/9] drm: convert drm_device.master_mutex into a rwsem

2021-08-18 Thread Desmond Cheong Zhi Xi
drm_device.master_mutex currently protects the following:
- drm_device.master
- drm_file.master
- drm_file.was_master
- drm_file.is_master

These attributes determine modesetting permissions for drm devices,
and there is a clear separation between functions that read or change
the permissions. Hence, convert master_mutex into a rwsem to enable
concurrent readers.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c| 27 ++-
 drivers/gpu/drm/drm_debugfs.c |  4 ++--
 drivers/gpu/drm/drm_drv.c |  3 +--
 drivers/gpu/drm/drm_ioctl.c   |  4 ++--
 include/drm/drm_device.h  | 11 +++
 include/drm/drm_file.h| 12 ++--
 6 files changed, 32 insertions(+), 29 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 0acb444fbbac..b65681ff42fc 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -64,7 +64,7 @@
 static bool drm_is_current_master_locked(struct drm_file *fpriv)
 {

lockdep_assert_once(lockdep_is_held(&fpriv->minor->dev->master_lookup_lock) ||
-   lockdep_is_held(&fpriv->minor->dev->master_mutex));
+   lockdep_is_held(&fpriv->minor->dev->master_rwsem));
 
return (fpriv->is_master && fpriv->master &&
drm_lease_owner(fpriv->master) == fpriv->minor->dev->master);
@@ -175,7 +175,7 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
struct drm_master *old_master;
struct drm_master *new_master;
 
-   lockdep_assert_held_once(&dev->master_mutex);
+   lockdep_assert_held_once(&dev->master_rwsem);
 
WARN_ON(fpriv->is_master);
old_master = fpriv->master;
@@ -257,7 +257,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -289,7 +289,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
 
drm_set_master(dev, file_priv, false);
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -306,7 +306,7 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
 {
int ret;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
 
ret = drm_master_check_perm(dev, file_priv);
if (ret)
@@ -329,8 +329,9 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
}
 
drm_drop_master(dev, file_priv);
+
 out_unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
return ret;
 }
 
@@ -342,7 +343,7 @@ int drm_master_open(struct drm_file *file_priv)
/* if there is no current master make this fd it, but do not create
 * any master object for render clients
 */
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
} else {
@@ -350,7 +351,7 @@ int drm_master_open(struct drm_file *file_priv)
file_priv->master = drm_master_get(dev->master);
spin_unlock(&dev->master_lookup_lock);
}
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 
return ret;
 }
@@ -360,7 +361,7 @@ void drm_master_release(struct drm_file *file_priv)
struct drm_device *dev = file_priv->minor->dev;
struct drm_master *master;
 
-   mutex_lock(&dev->master_mutex);
+   down_write(&dev->master_rwsem);
master = file_priv->master;
 
if (!master)
@@ -393,7 +394,7 @@ void drm_master_release(struct drm_file *file_priv)
spin_unlock(&dev->master_lookup_lock);
}
 unlock:
-   mutex_unlock(&dev->master_mutex);
+   up_write(&dev->master_rwsem);
 }
 
 /**
@@ -468,9 +469,9 @@ EXPORT_SYMBOL(drm_master_put);
 /* Used by drm_client and drm_fb_helper */
 bool drm_master_internal_acquire(struct drm_device *dev)
 {
-   mutex_lock(&dev->master_mutex);
+   down_read(&dev->master_rwsem);
if (dev->master) {
-   mutex_unlock(&dev->master_mutex);
+   up_read(&dev->master_rwsem);
return false;
}
 
@@ -481,6 +482,6 @@ EXPORT_SYMBOL(drm_master_internal_acquire);
 /* Used by drm_client and drm_fb_helper */
 void drm_master_internal_release(struct drm_device *dev)
 {
-   mutex_unlock(&dev->master_mutex);
+   up_read(&dev->master_rwsem);
 }
 EXPORT_SYMBOL(drm_master_internal_release);
diff --git a/drivers/gp

[Intel-gfx] [PATCH v3 9/9] drm: avoid races with modesetting rights

2021-08-18 Thread Desmond Cheong Zhi Xi
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 
Signed-off-by: Desmond Cheong Zhi Xi 
---
 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 
+#include 
 
 #include 
 #include 
@@ -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 (unli

[Intel-gfx] [PATCH v3 2/9] drm: hold master_lookup_lock when releasing a drm_file's master

2021-08-18 Thread Desmond Cheong Zhi Xi
When drm_file.master changes value, the corresponding
drm_device.master_lookup_lock should be held.

In drm_master_release, a call to drm_master_put sets the
file_priv->master to NULL, so we protect this section with
drm_device.master_lookup_lock.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c | 5 -
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 8efb58aa7d95..8c0e0dba1611 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -373,8 +373,11 @@ void drm_master_release(struct drm_file *file_priv)
}
 
/* drop the master reference held by the file priv */
-   if (file_priv->master)
+   if (file_priv->master) {
+   spin_lock(&dev->master_lookup_lock);
drm_master_put(&file_priv->master);
+   spin_unlock(&dev->master_lookup_lock);
+   }
mutex_unlock(&dev->master_mutex);
 }
 
-- 
2.25.1



[Intel-gfx] [PATCH v3 4/9] drm: fix potential null ptr dereferences in drm_{auth, ioctl}

2021-08-18 Thread Desmond Cheong Zhi Xi
There are three areas where we dereference struct drm_master without
checking if the pointer is non-NULL.

1. drm_getmagic is called from the ioctl_handler. Since
DRM_IOCTL_GET_MAGIC has no ioctl flags, drm_getmagic is run without
any check that drm_file.master has been set.

2. Similarly, drm_getunique is called from the ioctl_handler, but
DRM_IOCTL_GET_UNIQUE has no ioctl flags. So there is no guarantee that
drm_file.master has been set.

3. drm_master_release can also be called without having a
drm_file.master set. Here is one error path:
  drm_open():
drm_open_helper():
  drm_master_open():
drm_new_set_master(); <--- returns -ENOMEM,
   drm_file.master not set
  drm_file_free():
drm_master_release(); <--- NULL ptr dereference
   (file_priv->master->magic_map)

Fix these by checking if the master pointers are NULL before use.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c  | 16 ++--
 drivers/gpu/drm/drm_ioctl.c |  5 +
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index f9267b21556e..b7230604496b 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -95,11 +95,18 @@ EXPORT_SYMBOL(drm_is_current_master);
 int drm_getmagic(struct drm_device *dev, void *data, struct drm_file 
*file_priv)
 {
struct drm_auth *auth = data;
+   struct drm_master *master;
int ret = 0;
 
mutex_lock(&dev->master_mutex);
+   master = file_priv->master;
+   if (!master) {
+   mutex_unlock(&dev->master_mutex);
+   return -EINVAL;
+   }
+
if (!file_priv->magic) {
-   ret = idr_alloc(&file_priv->master->magic_map, file_priv,
+   ret = idr_alloc(&master->magic_map, file_priv,
1, 0, GFP_KERNEL);
if (ret >= 0)
file_priv->magic = ret;
@@ -355,8 +362,12 @@ void drm_master_release(struct drm_file *file_priv)
 
mutex_lock(&dev->master_mutex);
master = file_priv->master;
+
+   if (!master)
+   goto unlock;
+
if (file_priv->magic)
-   idr_remove(&file_priv->master->magic_map, file_priv->magic);
+   idr_remove(&master->magic_map, file_priv->magic);
 
if (!drm_is_current_master_locked(file_priv))
goto out;
@@ -379,6 +390,7 @@ void drm_master_release(struct drm_file *file_priv)
drm_master_put(&file_priv->master);
spin_unlock(&dev->master_lookup_lock);
}
+unlock:
mutex_unlock(&dev->master_mutex);
 }
 
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 26f3a9ede8fe..4d029d3061d9 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -121,6 +121,11 @@ int drm_getunique(struct drm_device *dev, void *data,
 
mutex_lock(&dev->master_mutex);
master = file_priv->master;
+   if (!master) {
+   mutex_unlock(&dev->master_mutex);
+   return -EINVAL;
+   }
+
if (u->unique_len >= master->unique_len) {
if (copy_to_user(u->unique, master->unique, 
master->unique_len)) {
mutex_unlock(&dev->master_mutex);
-- 
2.25.1



[Intel-gfx] [PATCH v3 8/9] kernel: export task_work_add

2021-08-18 Thread Desmond Cheong Zhi Xi
The task_work_add function is needed to prevent userspace races with
DRM modesetting rights.

Some DRM ioctls can change modesetting permissions while other
concurrent users are performing modesetting. To prevent races with
userspace, such functions should flush readers of old permissions
before returning to user mode. As the function that changes
permissions might itself be a reader of the old permissions, we intend
to schedule this flush using task_work_add.

However, when DRM is compiled as a loadable kernel module without
exporting task_work_add, we get the following compilation error:

ERROR: modpost: "task_work_add" [drivers/gpu/drm/drm.ko] undefined!

Reported-by: kernel test robot 
Signed-off-by: Desmond Cheong Zhi Xi 
---
 kernel/task_work.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/kernel/task_work.c b/kernel/task_work.c
index 1698fbe6f0e1..9404af2b 100644
--- a/kernel/task_work.c
+++ b/kernel/task_work.c
@@ -60,6 +60,7 @@ int task_work_add(struct task_struct *task, struct 
callback_head *work,
 
return 0;
 }
+EXPORT_SYMBOL(task_work_add);
 
 /**
  * task_work_cancel_match - cancel a pending work added by task_work_add()
-- 
2.25.1



[Intel-gfx] [PATCH v3 7/9] drm: update global mutex lock in the ioctl handler

2021-08-18 Thread Desmond Cheong Zhi Xi
In a future patch, a read lock on drm_device.master_rwsem is
held in the ioctl handler before the check for ioctl
permissions. However, this produces the following lockdep splat:

==
WARNING: possible circular locking dependency detected
5.14.0-rc6-CI-Patchwork_20831+ #1 Tainted: G U
--
kms_lease/1752 is trying to acquire lock:
827bad88 (drm_global_mutex){+.+.}-{3:3}, at: drm_open+0x64/0x280

but task is already holding lock:
88812e350108 (&dev->master_rwsem){}-{3:3}, at:
drm_ioctl_kernel+0xfb/0x1a0

which lock already depends on the new lock.

the existing dependency chain (in reverse order) is:

-> #2 (&dev->master_rwsem){}-{3:3}:
   lock_acquire+0xd3/0x310
   down_read+0x3b/0x140
   drm_master_internal_acquire+0x1d/0x60
   drm_client_modeset_commit+0x10/0x40
   __drm_fb_helper_restore_fbdev_mode_unlocked+0x88/0xb0
   drm_fb_helper_set_par+0x34/0x40
   intel_fbdev_set_par+0x11/0x40 [i915]
   fbcon_init+0x270/0x4f0
   visual_init+0xc6/0x130
   do_bind_con_driver+0x1de/0x2c0
   do_take_over_console+0x10e/0x180
   do_fbcon_takeover+0x53/0xb0
   register_framebuffer+0x22d/0x310
   __drm_fb_helper_initial_config_and_unlock+0x36c/0x540
   intel_fbdev_initial_config+0xf/0x20 [i915]
   async_run_entry_fn+0x28/0x130
   process_one_work+0x26d/0x5c0
   worker_thread+0x37/0x390
   kthread+0x13b/0x170
   ret_from_fork+0x1f/0x30

-> #1 (&helper->lock){+.+.}-{3:3}:
   lock_acquire+0xd3/0x310
   __mutex_lock+0xa8/0x930
   __drm_fb_helper_restore_fbdev_mode_unlocked+0x44/0xb0
   intel_fbdev_restore_mode+0x2b/0x50 [i915]
   drm_lastclose+0x27/0x50
   drm_release_noglobal+0x42/0x60
   __fput+0x9e/0x250
   task_work_run+0x6b/0xb0
   exit_to_user_mode_prepare+0x1c5/0x1d0
   syscall_exit_to_user_mode+0x19/0x50
   do_syscall_64+0x46/0xb0
   entry_SYSCALL_64_after_hwframe+0x44/0xae

-> #0 (drm_global_mutex){+.+.}-{3:3}:
   validate_chain+0xb39/0x1e70
   __lock_acquire+0x5a1/0xb70
   lock_acquire+0xd3/0x310
   __mutex_lock+0xa8/0x930
   drm_open+0x64/0x280
   drm_stub_open+0x9f/0x100
   chrdev_open+0x9f/0x1d0
   do_dentry_open+0x14a/0x3a0
   dentry_open+0x53/0x70
   drm_mode_create_lease_ioctl+0x3cb/0x970
   drm_ioctl_kernel+0xc9/0x1a0
   drm_ioctl+0x201/0x3d0
   __x64_sys_ioctl+0x6a/0xa0
   do_syscall_64+0x37/0xb0
   entry_SYSCALL_64_after_hwframe+0x44/0xae

other info that might help us debug this:
Chain exists of:
  drm_global_mutex --> &helper->lock --> &dev->master_rwsem
 Possible unsafe locking scenario:
   CPU0CPU1
   
  lock(&dev->master_rwsem);
   lock(&helper->lock);
   lock(&dev->master_rwsem);
  lock(drm_global_mutex);

 *** DEADLOCK ***

The lock hierarchy inversion happens because we grab the
drm_global_mutex while already holding on to master_rwsem. To avoid
this, we do some prep work to grab the drm_global_mutex before
checking for ioctl permissions.

At the same time, we update the check for the global mutex to use the
drm_dev_needs_global_mutex helper function.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_ioctl.c | 18 +-
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 880fc565d599..2cb57378a787 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -779,19 +779,19 @@ long drm_ioctl_kernel(struct file *file, drm_ioctl_t 
*func, void *kdata,
if (drm_dev_is_unplugged(dev))
return -ENODEV;
 
+   /* Enforce sane locking for modern driver ioctls. */
+   if (unlikely(drm_dev_needs_global_mutex(dev)) && !(flags & 
DRM_UNLOCKED))
+   mutex_lock(&drm_global_mutex);
+
retcode = drm_ioctl_permit(flags, file_priv);
if (unlikely(retcode))
-   return retcode;
+   goto out;
 
-   /* Enforce sane locking for modern driver ioctls. */
-   if (likely(!drm_core_check_feature(dev, DRIVER_LEGACY)) ||
-   (flags & DRM_UNLOCKED))
-   retcode = func(dev, kdata, file_priv);
-   else {
-   mutex_lock(&drm_global_mutex);
-   retcode = func(dev, kdata, file_priv);
+   retcode = func(dev, kdata, file_priv);
+
+out:
+   if (unlikely(drm_dev_needs_global_mutex(dev)) && !(flags & 
DRM_UNLOCKED))
mutex_unlock(&drm_global_mutex);
-   }
return retcode;
 }
 EXPORT_SYMBOL(drm_ioctl_kernel);
-- 
2.25.1



[Intel-gfx] [PATCH v3 5/9] drm: protect magic_map, unique{_len} with master_lookup_lock

2021-08-18 Thread Desmond Cheong Zhi Xi
Currently, drm_device.master_mutex is used to serialize writes to the
drm_master.magic_map idr and to protect drm_master.unique{_len}.

In preparation for converting drm_device.master_mutex into an outer
rwsem that might be read locked before entering some of these
functions, we can instead serialize access to drm_master.magic_map and
drm_master.unique{_len} using drm_device.master_lookup_lock which is
an inner lock.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c  | 12 +++-
 drivers/gpu/drm/drm_ioctl.c | 10 ++
 include/drm/drm_auth.h  |  6 +++---
 include/drm/drm_device.h|  7 ++-
 4 files changed, 22 insertions(+), 13 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index b7230604496b..0acb444fbbac 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -98,10 +98,10 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
struct drm_master *master;
int ret = 0;
 
-   mutex_lock(&dev->master_mutex);
+   spin_lock(&dev->master_lookup_lock);
master = file_priv->master;
if (!master) {
-   mutex_unlock(&dev->master_mutex);
+   spin_unlock(&dev->master_lookup_lock);
return -EINVAL;
}
 
@@ -112,7 +112,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct 
drm_file *file_priv)
file_priv->magic = ret;
}
auth->magic = file_priv->magic;
-   mutex_unlock(&dev->master_mutex);
+   spin_unlock(&dev->master_lookup_lock);
 
DRM_DEBUG("%u\n", auth->magic);
 
@@ -127,13 +127,13 @@ int drm_authmagic(struct drm_device *dev, void *data,
 
DRM_DEBUG("%u\n", auth->magic);
 
-   mutex_lock(&dev->master_mutex);
+   spin_lock(&dev->master_lookup_lock);
file = idr_find(&file_priv->master->magic_map, auth->magic);
if (file) {
file->authenticated = 1;
idr_replace(&file_priv->master->magic_map, NULL, auth->magic);
}
-   mutex_unlock(&dev->master_mutex);
+   spin_unlock(&dev->master_lookup_lock);
 
return file ? 0 : -EINVAL;
 }
@@ -366,8 +366,10 @@ void drm_master_release(struct drm_file *file_priv)
if (!master)
goto unlock;
 
+   spin_lock(&dev->master_lookup_lock);
if (file_priv->magic)
idr_remove(&master->magic_map, file_priv->magic);
+   spin_unlock(&dev->master_lookup_lock);
 
if (!drm_is_current_master_locked(file_priv))
goto out;
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 4d029d3061d9..e5c3845b6e62 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -119,21 +119,21 @@ int drm_getunique(struct drm_device *dev, void *data,
struct drm_unique *u = data;
struct drm_master *master;
 
-   mutex_lock(&dev->master_mutex);
+   spin_lock(&dev->master_lookup_lock);
master = file_priv->master;
if (!master) {
-   mutex_unlock(&dev->master_mutex);
+   spin_unlock(&dev->master_lookup_lock);
return -EINVAL;
}
 
if (u->unique_len >= master->unique_len) {
if (copy_to_user(u->unique, master->unique, 
master->unique_len)) {
-   mutex_unlock(&dev->master_mutex);
+   spin_unlock(&dev->master_lookup_lock);
return -EFAULT;
}
}
u->unique_len = master->unique_len;
-   mutex_unlock(&dev->master_mutex);
+   spin_unlock(&dev->master_lookup_lock);
 
return 0;
 }
@@ -405,7 +405,9 @@ static int drm_setversion(struct drm_device *dev, void 
*data, struct drm_file *f
 * Version 1.1 includes tying of DRM to specific device
 * Version 1.4 has proper PCI domain support
 */
+   spin_lock(&dev->master_lookup_lock);
retcode = drm_set_busid(dev, file_priv);
+   spin_unlock(&dev->master_lookup_lock);
if (retcode)
goto done;
}
diff --git a/include/drm/drm_auth.h b/include/drm/drm_auth.h
index ba248ca8866f..f5be73153798 100644
--- a/include/drm/drm_auth.h
+++ b/include/drm/drm_auth.h
@@ -67,17 +67,17 @@ struct drm_master {
struct drm_device *dev;
/**
 * @unique: Unique identifier: e.g. busid. Protected by
-* &drm_device.master_mutex.
+* &drm_device.master_lookup_lock.
 */
char *unique;
/**
 * @unique_len: Le

[Intel-gfx] [PATCH v3 0/9] drm, kernel: update locking for DRM

2021-08-18 Thread Desmond Cheong Zhi Xi
Hi,

The patches in this series are largely fixes and prepwork leading up to
the final patch which plugs races with modesetting rights. Most of the
fixes don't have bug reports, so comments would be very appreciated.

The biggest change from the previous version is that we convert
drm_device.master_mutex into master_rwsem, instead of introducing
master_rwsem as a third lock.

Overall, this series makes the following changes:

- Patch 1: Move master_lookup_lock into struct drm_device (enables us to
use it to protect attributes accessed by different drm_files)

- Patch 2: Add a missing master_lookup_lock in drm_master_release

- Patch 3: Fix a potential race in drm_is_current_master_locked

- Patch 4: Fix potential null ptr dereferences in drm_{auth, ioctl}

- Patch 5: Move magic_map,unique{_len} out from master_mutex's
protection into master_lookup_lock's protection (allows us to avoid
read_lock -> write_lock deadlocks)

- Patch 6: Convert master_mutex into rwsem (avoids creating a new lock)

- Patch 7: Update global mutex locking in the ioctl handler (avoids
deadlock when grabbing read lock on master_rwsem in drm_ioctl_kernel)

- Patch 8: Export task_work_add (enables us to write drm_master_flush)

- Patch 9: Plug races with drm modesetting rights

v2 -> v3:
- Unexport drm_master_flush, as suggested by Daniel Vetter.
- Merge master_mutex and master_rwsem, as suggested by Daniel Vetter.
- Export task_work_add, reported by kernel test robot.
- Make master_flush static, reported by kernel test robot.
- Move master_lookup_lock into struct drm_device.
- Add a missing lock on master_lookup_lock in drm_master_release.
- Fix a potential race in drm_is_current_master_locked.
- Fix potential null ptr dereferences in drm_{auth, ioctl}.
- Protect magic_map,unique{_len} with  master_lookup_lock.
- Convert master_mutex into a rwsem.
- Update global mutex locking in the ioctl handler.

v1 -> v2 (suggested by Daniel Vetter):
- Address an additional race when drm_open runs.
- Switch from SRCU to rwsem to synchronise readers and writers.
- Implement drm_master_flush with task_work so that flushes can be
queued to run before returning to userspace without creating a new
DRM_MASTER_FLUSH ioctl flag.

Best wishes,
Desmond

Desmond Cheong Zhi Xi (9):
  drm: move master_lookup_lock into drm_device
  drm: hold master_lookup_lock when releasing a drm_file's master
  drm: check for null master in drm_is_current_master_locked
  drm: fix potential null ptr dereferences in drm_{auth,ioctl}
  drm: protect magic_map,unique{_len} with master_lookup_lock
  drm: convert drm_device.master_mutex into a rwsem
  drm: update global mutex lock in the ioctl handler
  kernel: export task_work_add
  drm: avoid races with modesetting rights

 drivers/gpu/drm/drm_auth.c | 108 -
 drivers/gpu/drm/drm_debugfs.c  |   4 +-
 drivers/gpu/drm/drm_drv.c  |   4 +-
 drivers/gpu/drm/drm_file.c |   1 -
 drivers/gpu/drm/drm_internal.h |   1 +
 drivers/gpu/drm/drm_ioctl.c|  39 +++-
 drivers/gpu/drm/drm_lease.c|   1 +
 include/drm/drm_auth.h |   6 +-
 include/drm/drm_device.h   |  27 +++--
 include/drm/drm_file.h |  20 +++---
 kernel/task_work.c |   1 +
 11 files changed, 145 insertions(+), 67 deletions(-)

-- 
2.25.1



[Intel-gfx] [PATCH v3 1/9] drm: move master_lookup_lock into drm_device

2021-08-18 Thread Desmond Cheong Zhi Xi
The master_lookup_lock spinlock can be useful as an inner lock for
other attributes that are currently protected by
drm_device.master_mutex.

However, since the spinlock belongs to struct drm_file, its use case
is limited to serializing accesses by a single drm_file. Moving this
lock into struct drm_device allows us to use it for structures that
are accessed by multiple drm_files, such as drm_master.magic_map.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c | 18 +-
 drivers/gpu/drm/drm_drv.c  |  1 +
 drivers/gpu/drm/drm_file.c |  1 -
 include/drm/drm_device.h   |  3 +++
 include/drm/drm_file.h | 10 --
 5 files changed, 17 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 60a6b21474b1..8efb58aa7d95 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -63,7 +63,7 @@
 
 static bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
-   lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
+   
lockdep_assert_once(lockdep_is_held(&fpriv->minor->dev->master_lookup_lock) ||
lockdep_is_held(&fpriv->minor->dev->master_mutex));
 
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
@@ -83,9 +83,9 @@ bool drm_is_current_master(struct drm_file *fpriv)
 {
bool ret;
 
-   spin_lock(&fpriv->master_lookup_lock);
+   spin_lock(&fpriv->minor->dev->master_lookup_lock);
ret = drm_is_current_master_locked(fpriv);
-   spin_unlock(&fpriv->master_lookup_lock);
+   spin_unlock(&fpriv->minor->dev->master_lookup_lock);
 
return ret;
 }
@@ -174,9 +174,9 @@ static int drm_new_set_master(struct drm_device *dev, 
struct drm_file *fpriv)
new_master = drm_master_create(dev);
if (!new_master)
return -ENOMEM;
-   spin_lock(&fpriv->master_lookup_lock);
+   spin_lock(&dev->master_lookup_lock);
fpriv->master = new_master;
-   spin_unlock(&fpriv->master_lookup_lock);
+   spin_unlock(&dev->master_lookup_lock);
 
fpriv->is_master = 1;
fpriv->authenticated = 1;
@@ -338,9 +338,9 @@ int drm_master_open(struct drm_file *file_priv)
if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
} else {
-   spin_lock(&file_priv->master_lookup_lock);
+   spin_lock(&dev->master_lookup_lock);
file_priv->master = drm_master_get(dev->master);
-   spin_unlock(&file_priv->master_lookup_lock);
+   spin_unlock(&dev->master_lookup_lock);
}
mutex_unlock(&dev->master_mutex);
 
@@ -405,13 +405,13 @@ struct drm_master *drm_file_get_master(struct drm_file 
*file_priv)
 {
struct drm_master *master = NULL;
 
-   spin_lock(&file_priv->master_lookup_lock);
+   spin_lock(&file_priv->minor->dev->master_lookup_lock);
if (!file_priv->master)
goto unlock;
master = drm_master_get(file_priv->master);
 
 unlock:
-   spin_unlock(&file_priv->master_lookup_lock);
+   spin_unlock(&file_priv->minor->dev->master_lookup_lock);
return master;
 }
 EXPORT_SYMBOL(drm_file_get_master);
diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
index 7a5097467ba5..218c16f11c80 100644
--- a/drivers/gpu/drm/drm_drv.c
+++ b/drivers/gpu/drm/drm_drv.c
@@ -612,6 +612,7 @@ static int drm_dev_init(struct drm_device *dev,
mutex_init(&dev->filelist_mutex);
mutex_init(&dev->clientlist_mutex);
mutex_init(&dev->master_mutex);
+   spin_lock_init(&dev->master_lookup_lock);
 
ret = drmm_add_action(dev, drm_dev_init_release, NULL);
if (ret)
diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index ed25168619fc..b8679bbaea69 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -176,7 +176,6 @@ struct drm_file *drm_file_alloc(struct drm_minor *minor)
init_waitqueue_head(&file->event_wait);
file->event_space = 4096; /* set aside 4k for event buffer */
 
-   spin_lock_init(&file->master_lookup_lock);
mutex_init(&file->event_read_lock);
 
if (drm_core_check_feature(dev, DRIVER_GEM))
diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h
index 604b1d1b2d72..506eb2784819 100644
--- a/include/drm/drm_device.h
+++ b/include/drm/drm_device.h
@@ -152,6 +152,9 @@ struct drm_device {
 */
struct mutex master_mutex;
 
+   /** @master_lookup_lock: Serializes &drm_file.master. */
+   spinlock_t master_lookup_lock;
+
/**
 * @open_count:
 *
diff --git a/include/

[Intel-gfx] [PATCH v3 3/9] drm: check for null master in drm_is_current_master_locked

2021-08-18 Thread Desmond Cheong Zhi Xi
There is a window after calling drm_master_release, and before a file
is freed, where drm_file can have is_master set to true, but both the
drm_file and drm_device have no master.

This could result in wrongly approving permissions in
drm_is_current_master_locked. Add a check that fpriv->master is
non-NULl to guard against this scenario.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 8c0e0dba1611..f9267b21556e 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -66,7 +66,8 @@ static bool drm_is_current_master_locked(struct drm_file 
*fpriv)

lockdep_assert_once(lockdep_is_held(&fpriv->minor->dev->master_lookup_lock) ||
lockdep_is_held(&fpriv->minor->dev->master_mutex));
 
-   return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
+   return (fpriv->is_master && fpriv->master &&
+   drm_lease_owner(fpriv->master) == fpriv->minor->dev->master);
 }
 
 /**
-- 
2.25.1



Re: [Intel-gfx] [PATCH v2] drm: avoid races with modesetting rights

2021-08-17 Thread Desmond Cheong Zhi Xi

On 16/8/21 9:59 pm, Daniel Vetter wrote:

On Mon, Aug 16, 2021 at 12:31 PM Desmond Cheong Zhi Xi
 wrote:


On 16/8/21 5:04 pm, Daniel Vetter wrote:

On Mon, Aug 16, 2021 at 10:53 AM Desmond Cheong Zhi Xi
 wrote:

On 16/8/21 2:47 am, kernel test robot wrote:

Hi Desmond,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on next-20210813]
[also build test ERROR on v5.14-rc5]
[cannot apply to linus/master v5.14-rc5 v5.14-rc4 v5.14-rc3]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:
https://github.com/0day-ci/linux/commits/Desmond-Cheong-Zhi-Xi/drm-avoid-races-with-modesetting-rights/20210815-234145
base:4b358aabb93a2c654cd1dcab1a25a589f6e2b153
config: i386-randconfig-a004-20210815 (attached as .config)
compiler: gcc-9 (Debian 9.3.0-22) 9.3.0
reproduce (this is a W=1 build):
   # 
https://github.com/0day-ci/linux/commit/cf6d8354b7d7953cd866fad004cbb189adfa074f
   git remote add linux-review https://github.com/0day-ci/linux
   git fetch --no-tags linux-review 
Desmond-Cheong-Zhi-Xi/drm-avoid-races-with-modesetting-rights/20210815-234145
   git checkout cf6d8354b7d7953cd866fad004cbb189adfa074f
   # save the attached .config to linux build tree
   make W=1 ARCH=i386

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot 

All errors (new ones prefixed by >>, old ones prefixed by <<):


ERROR: modpost: "task_work_add" [drivers/gpu/drm/drm.ko] undefined!




I'm a bit uncertain about this. Looking into the .config used, this
error seems to happen because task_work_add isn't an exported symbol,
but DRM is being compiled as a loadable kernel module (CONFIG_DRM=m).

One way to deal with this is to export the symbol, but there was a
proposed patch to do this a few months back that wasn't picked up [1],
so I'm not sure what to make of this.

I'll export the symbol as part of a v3 series, and check in with the
task-work maintainers.

Link:
https://lore.kernel.org/lkml/20210127150029.13766-3-josh...@samsung.com/ [1]


Yeah that sounds best. I have two more thoughts on the patch:
- drm_master_flush isn't used by any modules outside of drm.ko, so we
can unexport it and drop the kerneldoc (the comment is still good).
These kind of internal functions have their declaration in
drm-internal.h - there's already a few there from drm_auth.c



Sounds good, I'll do that and move the declaration from drm_auth.h to
drm_internal.h.


- We know have 3 locks for master state, that feels a bit like
overkill. The spinlock I think we need to keep due to lock inversions,
but the master_mutex and master_rwsem look like we should be able to
merge them? I.e. anywhere we currently grab the master_mutex we could
instead grab the rwsem in either write mode (when we change stuff) or
read mode (when we just check, like in master_internal_acquire).

Thoughts?
-Daniel



Using rwsem in the places where we currently hold the mutex seems pretty
doable.

There are some tricky bits once we add rwsem read locks to the ioctl
handler. Some ioctl functions like drm_authmagic need a write lock.


Ah yes, I only looked at the dropmaster/setmaster ioctl, and those
don't have the DRM_MASTER bit set.


In this particular case, it might make sense to break master_mutex down
into finer-grained locks, since the function doesn't change master
permissions. It just needs to prevent concurrent writes to the
drm_master.magic_map idr.


Yeah for authmagic we could perhaps just reuse the spinlock to protect
->magic_map?



Yup, I had to move the spinlock from struct drm_file to struct 
drm_device, but I think that should work.



For other ioctls, I'll take a closer look on a case-by-case basis.


If it's too much shuffling then I think totally fine to leave things
as-is. Just feels a bit silly to have 3 locks, on of which is an
rwlock itself, for this fairly small amount of state.
-Daniel



Agreed, there's a lot of overlap between the master_mutex and rwsem so 
this a good opportunity to refactor things.


I'm cleaning up a v3 series now. There's some movement, but most of it 
are fixes to potential bugs that I saw while refactoring. We can see if 
the new version is a better design.







---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-...@lists.01.org















Re: [Intel-gfx] [PATCH v2] drm: avoid races with modesetting rights

2021-08-16 Thread Desmond Cheong Zhi Xi

On 16/8/21 5:04 pm, Daniel Vetter wrote:

On Mon, Aug 16, 2021 at 10:53 AM Desmond Cheong Zhi Xi
 wrote:

On 16/8/21 2:47 am, kernel test robot wrote:

Hi Desmond,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on next-20210813]
[also build test ERROR on v5.14-rc5]
[cannot apply to linus/master v5.14-rc5 v5.14-rc4 v5.14-rc3]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:
https://github.com/0day-ci/linux/commits/Desmond-Cheong-Zhi-Xi/drm-avoid-races-with-modesetting-rights/20210815-234145
base:4b358aabb93a2c654cd1dcab1a25a589f6e2b153
config: i386-randconfig-a004-20210815 (attached as .config)
compiler: gcc-9 (Debian 9.3.0-22) 9.3.0
reproduce (this is a W=1 build):
  # 
https://github.com/0day-ci/linux/commit/cf6d8354b7d7953cd866fad004cbb189adfa074f
  git remote add linux-review https://github.com/0day-ci/linux
  git fetch --no-tags linux-review 
Desmond-Cheong-Zhi-Xi/drm-avoid-races-with-modesetting-rights/20210815-234145
  git checkout cf6d8354b7d7953cd866fad004cbb189adfa074f
  # save the attached .config to linux build tree
  make W=1 ARCH=i386

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot 

All errors (new ones prefixed by >>, old ones prefixed by <<):


ERROR: modpost: "task_work_add" [drivers/gpu/drm/drm.ko] undefined!




I'm a bit uncertain about this. Looking into the .config used, this
error seems to happen because task_work_add isn't an exported symbol,
but DRM is being compiled as a loadable kernel module (CONFIG_DRM=m).

One way to deal with this is to export the symbol, but there was a
proposed patch to do this a few months back that wasn't picked up [1],
so I'm not sure what to make of this.

I'll export the symbol as part of a v3 series, and check in with the
task-work maintainers.

Link:
https://lore.kernel.org/lkml/20210127150029.13766-3-josh...@samsung.com/ [1]


Yeah that sounds best. I have two more thoughts on the patch:
- drm_master_flush isn't used by any modules outside of drm.ko, so we
can unexport it and drop the kerneldoc (the comment is still good).
These kind of internal functions have their declaration in
drm-internal.h - there's already a few there from drm_auth.c



Sounds good, I'll do that and move the declaration from drm_auth.h to 
drm_internal.h.



- We know have 3 locks for master state, that feels a bit like
overkill. The spinlock I think we need to keep due to lock inversions,
but the master_mutex and master_rwsem look like we should be able to
merge them? I.e. anywhere we currently grab the master_mutex we could
instead grab the rwsem in either write mode (when we change stuff) or
read mode (when we just check, like in master_internal_acquire).

Thoughts?
-Daniel



Using rwsem in the places where we currently hold the mutex seems pretty 
doable.


There are some tricky bits once we add rwsem read locks to the ioctl 
handler. Some ioctl functions like drm_authmagic need a write lock.


In this particular case, it might make sense to break master_mutex down 
into finer-grained locks, since the function doesn't change master 
permissions. It just needs to prevent concurrent writes to the 
drm_master.magic_map idr.


For other ioctls, I'll take a closer look on a case-by-case basis.




---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-...@lists.01.org










Re: [Intel-gfx] [PATCH 1/2] drm: avoid races with modesetting rights

2021-08-16 Thread Desmond Cheong Zhi Xi

On 13/8/21 11:49 pm, Daniel Vetter wrote:

On Fri, Aug 13, 2021 at 04:54:49PM +0800, Desmond Cheong Zhi Xi wrote:

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_mutex while
committing, and bail if there's already a master.

However, ioctls can still race between themselves. 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 three ioctls that can affect modesetting permissions:

- 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

All these races can be avoided by introducing an SRCU that acts as a
barrier for ioctls that can change modesetting permissions. Processes
that perform modesetting should hold a read lock on the new
drm_device.master_barrier_srcu, and ioctls that change these
permissions should call synchronize_srcu before returning.

This ensures that any process that might have seen old permissions are
flushed out before DROP_MASTER/REVOKE_LEASE/SET_MASTER ioctls return
to userspace.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 


This looks pretty solid, but I think there's one gap where we can still
race. Scenario.

Process A has a drm fd with master rights and two threads:
- thread 1 does a long-running display operation (like a modeset or
   whatever)
- thread 2 does a drop-master

Then we start a new process B, which acquires master in drm_open (there is
no other one left). This is like setmaster ioctl, but your
DRM_MASTER_FLUSH bit doesn't work there.



Ah, I see the race. I think a good place to plug this would be in 
drm_master_open using the drm_master_flush (or 
drm_master_unlock_and_flush) that you suggested below.



The other thing is that for modeset stuff (which this all is) srcu is
probably massive overkill, and a simple rwsem should be good enough too.
Maybe even better, since the rwsem guarantees that no new reader can start
once you try to acquire the write side.



Makes sense, I'll switch to a rwsem then.


Finally, and this is a bit a bikeshed: I don't like much how
DRM_MASTER_FLUSH leaks the need of these very few places into the very
core drm_ioctl function. One idea I had was to use task_work in a special
function, roughly

void master_flush()
{
down_write(master_rwsem);
up_write(master_rwms);
}
void drm_master_flush()
{
init_task_work(fpriv->master_flush_work, master_flush)
task_work_add(fpriv->master_flush_work);
/* if task_work_add fails we're exiting, at which point the lack
 * of master flush doesn't matter);
}

And maybe put a comment above the function explaining why and how this
works.

We could even do a drm_master_unlock_and_flush helper, since that's really
what everyone wants, and it would make it very clear which master state
changes need this flush. Instead of setting a flag bit in an ioctl table
very far away ...

Thoughts?
-Daniel



Sounds good. I wasn't aware of the task_work_add mechanism to queue work 
before the task returns to usermode, but this seems like a more explicit 
way to flush.


Thanks for the feedback, Daniel. I'll fix this up in a v2.


---
  drivers/gpu/drm/drm_auth.c   | 17 ++---
  drivers/gpu/drm/drm_client_modeset.c | 10 ++
  drivers/gpu/drm/drm_drv.c|  2 ++
  drivers/gpu/drm/drm_fb_helper.c  | 20 
  drivers/gpu/drm/drm_internal.h   |  5 +++--
  drivers/gpu/drm/drm_ioctl.c  | 25 +
  include/drm/drm_device.h | 11 +++
  include/drm/drm_ioctl.h  |  7 +++
  8 files changed, 76 insertions(+), 21 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 60a6b21474b1..004506608e76 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -29,6 +29,7 @@
   */
  
  #include 

+#include 
  
  #include 

  #include 
@@ -448,21 +449,31 @@ void drm_master_put(struct drm_master **master)
  EXPORT_SYMBOL(drm_master_put);
  
  /* Used by drm_client and drm_fb_helper */

-bool drm_master_internal_acquire(struct drm_device *dev)
+bool drm_master_internal_acquire(struct drm_device *dev, int *idx)
  {
+   *idx = srcu_read_lock(&dev->master_barrier_srcu);
+
mutex_lock(&dev->master_mutex);
if (dev->master) {
mutex_unlock(&dev->master_mutex);
+   srcu_read_unlock(&dev->master_barrier_srcu, *idx);
return false;
}
+   mutex_unlock(&dev->master_mutex);
  
  	return true;

  }
  EXPORT_SYMBOL(drm_master_internal_acquire);
  
  /* Used 

Re: [Intel-gfx] [PATCH v2] drm: avoid races with modesetting rights

2021-08-16 Thread Desmond Cheong Zhi Xi

On 16/8/21 2:47 am, kernel test robot wrote:

Hi Desmond,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on next-20210813]
[also build test ERROR on v5.14-rc5]
[cannot apply to linus/master v5.14-rc5 v5.14-rc4 v5.14-rc3]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:
https://github.com/0day-ci/linux/commits/Desmond-Cheong-Zhi-Xi/drm-avoid-races-with-modesetting-rights/20210815-234145
base:4b358aabb93a2c654cd1dcab1a25a589f6e2b153
config: i386-randconfig-a004-20210815 (attached as .config)
compiler: gcc-9 (Debian 9.3.0-22) 9.3.0
reproduce (this is a W=1 build):
 # 
https://github.com/0day-ci/linux/commit/cf6d8354b7d7953cd866fad004cbb189adfa074f
 git remote add linux-review https://github.com/0day-ci/linux
 git fetch --no-tags linux-review 
Desmond-Cheong-Zhi-Xi/drm-avoid-races-with-modesetting-rights/20210815-234145
 git checkout cf6d8354b7d7953cd866fad004cbb189adfa074f
 # save the attached .config to linux build tree
 make W=1 ARCH=i386

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot 

All errors (new ones prefixed by >>, old ones prefixed by <<):


ERROR: modpost: "task_work_add" [drivers/gpu/drm/drm.ko] undefined!




I'm a bit uncertain about this. Looking into the .config used, this 
error seems to happen because task_work_add isn't an exported symbol, 
but DRM is being compiled as a loadable kernel module (CONFIG_DRM=m).


One way to deal with this is to export the symbol, but there was a 
proposed patch to do this a few months back that wasn't picked up [1], 
so I'm not sure what to make of this.


I'll export the symbol as part of a v3 series, and check in with the 
task-work maintainers.


Link: 
https://lore.kernel.org/lkml/20210127150029.13766-3-josh...@samsung.com/ [1]



---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-...@lists.01.org





Re: [Intel-gfx] [PATCH v2] drm: avoid races with modesetting rights

2021-08-16 Thread Desmond Cheong Zhi Xi

On 16/8/21 2:47 am, kernel test robot wrote:

Hi Desmond,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on next-20210813]
[also build test WARNING on v5.14-rc5]
[cannot apply to linus/master v5.14-rc5 v5.14-rc4 v5.14-rc3]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:
https://github.com/0day-ci/linux/commits/Desmond-Cheong-Zhi-Xi/drm-avoid-races-with-modesetting-rights/20210815-234145
base:4b358aabb93a2c654cd1dcab1a25a589f6e2b153
config: arc-randconfig-r031-20210815 (attached as .config)
compiler: arceb-elf-gcc (GCC) 11.2.0
reproduce (this is a W=1 build):
 wget 
https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O 
~/bin/make.cross
 chmod +x ~/bin/make.cross
 # 
https://github.com/0day-ci/linux/commit/cf6d8354b7d7953cd866fad004cbb189adfa074f
 git remote add linux-review https://github.com/0day-ci/linux
 git fetch --no-tags linux-review 
Desmond-Cheong-Zhi-Xi/drm-avoid-races-with-modesetting-rights/20210815-234145
 git checkout cf6d8354b7d7953cd866fad004cbb189adfa074f
 # save the attached .config to linux build tree
 COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-11.2.0 make.cross 
ARCH=arc

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot 

All warnings (new ones prefixed by >>):


drivers/gpu/drm/drm_auth.c:483:6: warning: no previous prototype for 
'master_flush' [-Wmissing-prototypes]

  483 | void master_flush(struct callback_head *cb)
  |  ^~~~



My bad, this should have been declared with static. I'll add it in, thanks.



vim +/master_flush +483 drivers/gpu/drm/drm_auth.c

479 
480 /* After flushing, all readers that might have seen old master/lease
481  * permissions are guaranteed to have completed.
482  */
  > 483  void master_flush(struct callback_head *cb)
484 {
485 struct drm_device *dev = container_of(cb, struct drm_device,
486   master_flush_work);
487 
488 down_write(&dev->master_rwsem);
489 up_write(&dev->master_rwsem);
490 }
491 

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-...@lists.01.org





[Intel-gfx] [PATCH v2] drm: avoid races with modesetting rights

2021-08-16 Thread Desmond Cheong Zhi Xi
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_mutex 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 by flushing all users that might have seen
old modesetting permissions before returning to userspace.

We do this using rwsem: users that perform modesetting should hold a
read lock on the new drm_device.master_rwsem, and users that change
these permissions should flush these readers before returning to
userspace.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
---

Hi,

I opted to leave the drm_master_unlock_and_flush helper out of this
patch, but happy to add it in if it'd be useful. Imo, when comparing it
with a mutex_unlock followed by drm_master_flush, it didn't add clarity.
And since we don't always hold the drm_device.master_mutex before
flushing (such as in drm_mode_revoke_lease_ioctl), perhaps it's better
to stick with one method to flush readers with drm_master_flush.

v1 -> v2 (suggested by Daniel Vetter):
- Address an additional race when drm_open runs.
- Switch from SRCU to rwsem to synchronise readers and writers.
- Implement drm_master_flush with task_work so that flushes can be
queued to run before returning to userspace without creating a new
DRM_MASTER_FLUSH ioctl flag.

Best wishes,
Desmond

 drivers/gpu/drm/drm_auth.c  | 45 -
 drivers/gpu/drm/drm_drv.c   |  1 +
 drivers/gpu/drm/drm_ioctl.c |  9 +++-
 drivers/gpu/drm/drm_lease.c |  1 +
 include/drm/drm_auth.h  |  1 +
 include/drm/drm_device.h| 18 +++
 6 files changed, 73 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 60a6b21474b1..175bc4d1e4b4 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -29,6 +29,7 @@
  */
 
 #include 
+#include 
 
 #include 
 #include 
@@ -282,6 +283,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
drm_set_master(dev, file_priv, false);
 out_unlock:
mutex_unlock(&dev->master_mutex);
+   drm_master_flush(dev);
return ret;
 }
 
@@ -321,8 +323,10 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void 
*data,
}
 
drm_drop_master(dev, file_priv);
+
 out_unlock:
mutex_unlock(&dev->master_mutex);
+   drm_master_flush(dev);
return ret;
 }
 
@@ -344,6 +348,8 @@ int drm_master_open(struct drm_file *file_priv)
}
mutex_unlock(&dev->master_mutex);
 
+   drm_master_flush(dev);
+
return ret;
 }
 
@@ -450,11 +456,15 @@ EXPORT_SYMBOL(drm_master_put);
 /* Used by drm_client and drm_fb_helper */
 bool drm_master_internal_acquire(struct drm_device *dev)
 {
+   down_read(&dev->master_rwsem);
+
mutex_lock(&dev->master_mutex);
if (dev->master) {
mutex_unlock(&dev->master_mutex);
+   up_read(&dev->master_rwsem);
return false;
}
+   mutex_unlock(&dev->master_mutex);
 
return true;
 }
@@ -463,6 +473,39 @@ EXPORT_SYMBOL(drm_master_internal_acquire);
 /* Used by drm_client and drm_fb_helper */
 void drm_master_internal_release(struct drm_device *dev)
 {
-   mutex_unlock(&dev->master_mutex);
+   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.
+ */
+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);
+}
+
+/**
+ * drm_master_flush - queues work to flush readers of master/lease permissions
+ * before returning to userspace
+ * @dev: DRM device
+ *
+ * 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(struc

[Intel-gfx] [PATCH 0/2] drm: update the ioctl handler

2021-08-13 Thread Desmond Cheong Zhi Xi
Hi,

Finally got around to it. This patchset implements some updates to the
drm ioctl handler that were first raised by Daniel Vetter in [1].
Namely:

- Flush concurrent processes that can change the modeset when
DRM masters are set/dropped or a lease is revoked
- Unexport drm_ioctl_permit()

Thoughts and comments would be very appreciated.

Link: https://lore.kernel.org/lkml/YN9kAFcfGoB13x7f@phenom.ffwll.local/ [1]

Best wishes,
Desmond

Desmond Cheong Zhi Xi (2):
  drm: avoid races with modesetting rights
  drm: unexport drm_ioctl_permit

 drivers/gpu/drm/drm_auth.c   | 17 +---
 drivers/gpu/drm/drm_client_modeset.c | 10 ---
 drivers/gpu/drm/drm_drv.c|  2 ++
 drivers/gpu/drm/drm_fb_helper.c  | 20 --
 drivers/gpu/drm/drm_internal.h   |  5 ++--
 drivers/gpu/drm/drm_ioctl.c  | 40 +++-
 include/drm/drm_device.h | 11 
 include/drm/drm_ioctl.h  |  8 +-
 8 files changed, 77 insertions(+), 36 deletions(-)

-- 
2.25.1



[Intel-gfx] [PATCH 2/2] drm: unexport drm_ioctl_permit

2021-08-13 Thread Desmond Cheong Zhi Xi
Since the last user of drm_ioctl_permit was removed, and it's now only
used in drm_ioctl.c, unexport the symbol.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_ioctl.c | 15 +--
 include/drm/drm_ioctl.h |  1 -
 2 files changed, 1 insertion(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index eb4ec3fab7d1..fe271f6f96ab 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -522,19 +522,7 @@ int drm_version(struct drm_device *dev, void *data,
return err;
 }
 
-/**
- * drm_ioctl_permit - Check ioctl permissions against caller
- *
- * @flags: ioctl permission flags.
- * @file_priv: Pointer to struct drm_file identifying the caller.
- *
- * Checks whether the caller is allowed to run an ioctl with the
- * indicated permissions.
- *
- * Returns:
- * Zero if allowed, -EACCES otherwise.
- */
-int drm_ioctl_permit(u32 flags, struct drm_file *file_priv)
+static int drm_ioctl_permit(u32 flags, struct drm_file *file_priv)
 {
/* ROOT_ONLY is only for CAP_SYS_ADMIN */
if (unlikely((flags & DRM_ROOT_ONLY) && !capable(CAP_SYS_ADMIN)))
@@ -557,7 +545,6 @@ int drm_ioctl_permit(u32 flags, struct drm_file *file_priv)
 
return 0;
 }
-EXPORT_SYMBOL(drm_ioctl_permit);
 
 #define DRM_IOCTL_DEF(ioctl, _func, _flags)\
[DRM_IOCTL_NR(ioctl)] = {   \
diff --git a/include/drm/drm_ioctl.h b/include/drm/drm_ioctl.h
index 13a68cdcea36..fd29842127e5 100644
--- a/include/drm/drm_ioctl.h
+++ b/include/drm/drm_ioctl.h
@@ -174,7 +174,6 @@ struct drm_ioctl_desc {
.name = #ioctl  \
}
 
-int drm_ioctl_permit(u32 flags, struct drm_file *file_priv);
 long drm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
 long drm_ioctl_kernel(struct file *, drm_ioctl_t, void *, u32);
 #ifdef CONFIG_COMPAT
-- 
2.25.1



[Intel-gfx] [PATCH 1/2] drm: avoid races with modesetting rights

2021-08-13 Thread Desmond Cheong Zhi Xi
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_mutex while
committing, and bail if there's already a master.

However, ioctls can still race between themselves. 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 three ioctls that can affect modesetting permissions:

- 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

All these races can be avoided by introducing an SRCU that acts as a
barrier for ioctls that can change modesetting permissions. Processes
that perform modesetting should hold a read lock on the new
drm_device.master_barrier_srcu, and ioctls that change these
permissions should call synchronize_srcu before returning.

This ensures that any process that might have seen old permissions are
flushed out before DROP_MASTER/REVOKE_LEASE/SET_MASTER ioctls return
to userspace.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c   | 17 ++---
 drivers/gpu/drm/drm_client_modeset.c | 10 ++
 drivers/gpu/drm/drm_drv.c|  2 ++
 drivers/gpu/drm/drm_fb_helper.c  | 20 
 drivers/gpu/drm/drm_internal.h   |  5 +++--
 drivers/gpu/drm/drm_ioctl.c  | 25 +
 include/drm/drm_device.h | 11 +++
 include/drm/drm_ioctl.h  |  7 +++
 8 files changed, 76 insertions(+), 21 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 60a6b21474b1..004506608e76 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -29,6 +29,7 @@
  */
 
 #include 
+#include 
 
 #include 
 #include 
@@ -448,21 +449,31 @@ void drm_master_put(struct drm_master **master)
 EXPORT_SYMBOL(drm_master_put);
 
 /* Used by drm_client and drm_fb_helper */
-bool drm_master_internal_acquire(struct drm_device *dev)
+bool drm_master_internal_acquire(struct drm_device *dev, int *idx)
 {
+   *idx = srcu_read_lock(&dev->master_barrier_srcu);
+
mutex_lock(&dev->master_mutex);
if (dev->master) {
mutex_unlock(&dev->master_mutex);
+   srcu_read_unlock(&dev->master_barrier_srcu, *idx);
return false;
}
+   mutex_unlock(&dev->master_mutex);
 
return true;
 }
 EXPORT_SYMBOL(drm_master_internal_acquire);
 
 /* Used by drm_client and drm_fb_helper */
-void drm_master_internal_release(struct drm_device *dev)
+void drm_master_internal_release(struct drm_device *dev, int idx)
 {
-   mutex_unlock(&dev->master_mutex);
+   srcu_read_unlock(&dev->master_barrier_srcu, idx);
 }
 EXPORT_SYMBOL(drm_master_internal_release);
+
+/* Used by drm_ioctl */
+void drm_master_flush(struct drm_device *dev)
+{
+   synchronize_srcu(&dev->master_barrier_srcu);
+}
diff --git a/drivers/gpu/drm/drm_client_modeset.c 
b/drivers/gpu/drm/drm_client_modeset.c
index ced09c7c06f9..9885f36f71b7 100644
--- a/drivers/gpu/drm/drm_client_modeset.c
+++ b/drivers/gpu/drm/drm_client_modeset.c
@@ -1165,13 +1165,14 @@ int drm_client_modeset_commit(struct drm_client_dev 
*client)
 {
struct drm_device *dev = client->dev;
int ret;
+   int idx;
 
-   if (!drm_master_internal_acquire(dev))
+   if (!drm_master_internal_acquire(dev, &idx))
return -EBUSY;
 
ret = drm_client_modeset_commit_locked(client);
 
-   drm_master_internal_release(dev);
+   drm_master_internal_release(dev, idx);
 
return ret;
 }
@@ -1215,8 +1216,9 @@ int drm_client_modeset_dpms(struct drm_client_dev 
*client, int mode)
 {
struct drm_device *dev = client->dev;
int ret = 0;
+   int idx;
 
-   if (!drm_master_internal_acquire(dev))
+   if (!drm_master_internal_acquire(dev, &idx))
return -EBUSY;
 
mutex_lock(&client->modeset_mutex);
@@ -1226,7 +1228,7 @@ int drm_client_modeset_dpms(struct drm_client_dev 
*client, int mode)
drm_client_modeset_dpms_legacy(client, mode);
mutex_unlock(&client->modeset_mutex);
 
-   drm_master_internal_release(dev);
+   drm_master_internal_release(dev, idx);
 
return ret;
 }
diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
index 7a5097467ba5..c313f0674db3 100644
--- a/drivers/gpu/drm/drm_drv.c
+++ b/drivers/gpu/drm/drm_drv.c
@@ -574,6 +574,7 @@ static void drm_dev_init_release(struct drm_device *dev, 
void *res)
mutex_destroy(&dev->clientlist_mutex);
mutex_destroy(&dev->fi

[Intel-gfx] [RESEND PATCH v2 2/2] drm: add lockdep assert to drm_is_current_master_locked

2021-08-02 Thread Desmond Cheong Zhi Xi
In drm_is_current_master_locked, accessing drm_file.master should be
protected by either drm_file.master_lookup_lock or
drm_device.master_mutex. This was previously awkward to assert with
lockdep.

Following patch ("locking/lockdep: Provide lockdep_assert{,_once}()
helpers"), this assertion is now convenient. So we add in the
assertion and explain this lock design in the kerneldoc.

Signed-off-by: Desmond Cheong Zhi Xi 
Acked-by: Boqun Feng 
Acked-by: Waiman Long 
Acked-by: Peter Zijlstra (Intel) 
---
 drivers/gpu/drm/drm_auth.c | 6 +++---
 include/drm/drm_file.h | 4 
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 9c24b8cc8e36..6f4d7ff23c80 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -63,9 +63,9 @@
 
 static bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
-   /* Either drm_device.master_mutex or drm_file.master_lookup_lock
-* should be held here.
-*/
+   lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) ||
+   lockdep_is_held(&fpriv->minor->dev->master_mutex));
+
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
 
diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
index 726cfe0ff5f5..a3acb7ac3550 100644
--- a/include/drm/drm_file.h
+++ b/include/drm/drm_file.h
@@ -233,6 +233,10 @@ struct drm_file {
 * this only matches &drm_device.master if the master is the currently
 * active one.
 *
+* To update @master, both &drm_device.master_mutex and
+* @master_lookup_lock need to be held, therefore holding either of
+* them is safe and enough for the read side.
+*
 * When dereferencing this pointer, either hold struct
 * &drm_device.master_mutex for the duration of the pointer's use, or
 * use drm_file_get_master() if struct &drm_device.master_mutex is not
-- 
2.25.1



[Intel-gfx] [RESEND PATCH v2 1/2] locking/lockdep: Provide lockdep_assert{, _once}() helpers

2021-08-02 Thread Desmond Cheong Zhi Xi
From: Peter Zijlstra 

Extract lockdep_assert{,_once}() helpers to more easily write composite
assertions like, for example:

lockdep_assert(lockdep_is_held(&drm_device.master_mutex) ||
   lockdep_is_held(&drm_file.master_lookup_lock));

Signed-off-by: Peter Zijlstra (Intel) 
Signed-off-by: Desmond Cheong Zhi Xi 
Acked-by: Boqun Feng 
Acked-by: Waiman Long 
Acked-by: Peter Zijlstra (Intel) 
---
 include/linux/lockdep.h | 41 +
 1 file changed, 21 insertions(+), 20 deletions(-)

diff --git a/include/linux/lockdep.h b/include/linux/lockdep.h
index 5cf387813754..9fe165beb0f9 100644
--- a/include/linux/lockdep.h
+++ b/include/linux/lockdep.h
@@ -306,31 +306,29 @@ extern void lock_unpin_lock(struct lockdep_map *lock, 
struct pin_cookie);
 
 #define lockdep_depth(tsk) (debug_locks ? (tsk)->lockdep_depth : 0)
 
-#define lockdep_assert_held(l) do {\
-   WARN_ON(debug_locks &&  \
-   lockdep_is_held(l) == LOCK_STATE_NOT_HELD); \
-   } while (0)
+#define lockdep_assert(cond)   \
+   do { WARN_ON(debug_locks && !(cond)); } while (0)
 
-#define lockdep_assert_not_held(l) do {\
-   WARN_ON(debug_locks &&  \
-   lockdep_is_held(l) == LOCK_STATE_HELD); \
-   } while (0)
+#define lockdep_assert_once(cond)  \
+   do { WARN_ON_ONCE(debug_locks && !(cond)); } while (0)
 
-#define lockdep_assert_held_write(l)   do {\
-   WARN_ON(debug_locks && !lockdep_is_held_type(l, 0));\
-   } while (0)
+#define lockdep_assert_held(l) \
+   lockdep_assert(lockdep_is_held(l) != LOCK_STATE_NOT_HELD)
 
-#define lockdep_assert_held_read(l)do {\
-   WARN_ON(debug_locks && !lockdep_is_held_type(l, 1));\
-   } while (0)
+#define lockdep_assert_not_held(l) \
+   lockdep_assert(lockdep_is_held(l) != LOCK_STATE_HELD)
 
-#define lockdep_assert_held_once(l)do {\
-   WARN_ON_ONCE(debug_locks && !lockdep_is_held(l));   \
-   } while (0)
+#define lockdep_assert_held_write(l)   \
+   lockdep_assert(lockdep_is_held_type(l, 0))
 
-#define lockdep_assert_none_held_once()do {
\
-   WARN_ON_ONCE(debug_locks && current->lockdep_depth);\
-   } while (0)
+#define lockdep_assert_held_read(l)\
+   lockdep_assert(lockdep_is_held_type(l, 1))
+
+#define lockdep_assert_held_once(l)\
+   lockdep_assert_once(lockdep_is_held(l) != LOCK_STATE_NOT_HELD)
+
+#define lockdep_assert_none_held_once()\
+   lockdep_assert_once(!current->lockdep_depth)
 
 #define lockdep_recursing(tsk) ((tsk)->lockdep_recursion)
 
@@ -407,6 +405,9 @@ extern int lock_is_held(const void *);
 extern int lockdep_is_held(const void *);
 #define lockdep_is_held_type(l, r) (1)
 
+#define lockdep_assert(c)  do { } while (0)
+#define lockdep_assert_once(c) do { } while (0)
+
 #define lockdep_assert_held(l) do { (void)(l); } while (0)
 #define lockdep_assert_not_held(l) do { (void)(l); } while (0)
 #define lockdep_assert_held_write(l)   do { (void)(l); } while (0)
-- 
2.25.1



[Intel-gfx] [RESEND PATCH v2 0/2] locking/lockdep, drm: apply new lockdep assert in drm_auth.c

2021-08-02 Thread Desmond Cheong Zhi Xi
Hi all,

My bad for the resend. Adding cc: intel-gfx, and the maintainers and
mailing lists for include/drm/drm_file.h.

Following a discussion on the patch ("drm: use the lookup lock in
drm_is_current_master") [1], Peter Zijlstra proposed new lockdep_assert
helpers to make it convenient to compose lockdep checks together.

This series includes the patch that introduces the new lockdep helpers,
then utilizes these helpers in drm_is_current_master_locked in the
following patch.

v1 -> v2:
Patch 2:
- Updated the kerneldoc on the lock design of drm_file.master to explain
the use of lockdep_assert(). As suggested by Boqun Feng.

Link: 
https://lore.kernel.org/lkml/20210722092929.244629-2-desmondcheon...@gmail.com/ 
[1]

Best wishes,
Desmond

Desmond Cheong Zhi Xi (1):
  drm: add lockdep assert to drm_is_current_master_locked

Peter Zijlstra (1):
  locking/lockdep: Provide lockdep_assert{,_once}() helpers

 drivers/gpu/drm/drm_auth.c |  6 +++---
 include/drm/drm_file.h |  4 
 include/linux/lockdep.h| 41 +++---
 3 files changed, 28 insertions(+), 23 deletions(-)

-- 
2.25.1



Re: [Intel-gfx] [PATCH 1/3] drm: use the lookup lock in drm_is_current_master

2021-07-29 Thread Desmond Cheong Zhi Xi

On 29/7/21 3:00 pm, Daniel Vetter wrote:

On Tue, Jul 27, 2021 at 04:37:22PM +0200, Peter Zijlstra wrote:

On Thu, Jul 22, 2021 at 12:38:10PM +0200, Daniel Vetter wrote:

On Thu, Jul 22, 2021 at 05:29:27PM +0800, Desmond Cheong Zhi Xi wrote:

Inside drm_is_current_master, using the outer drm_device.master_mutex
to protect reads of drm_file.master makes the function prone to creating
lock hierarchy inversions. Instead, we can use the
drm_file.master_lookup_lock that sits at the bottom of the lock
hierarchy.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
---
  drivers/gpu/drm/drm_auth.c | 9 +
  1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index f00354bec3fb..9c24b8cc8e36 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -63,8 +63,9 @@
  
  static bool drm_is_current_master_locked(struct drm_file *fpriv)

  {
-   lockdep_assert_held_once(&fpriv->minor->dev->master_mutex);
-
+   /* Either drm_device.master_mutex or drm_file.master_lookup_lock
+* should be held here.
+*/


Disappointing that lockdep can't check or conditions for us, a
lockdep_assert_held_either would be really neat in some cases.

Adding lockdep folks, maybe they have ideas.


#ifdef CONFIG_LOCKDEP
WARN_ON_ONCE(debug_locks && !(lockdep_is_held(&drm_device.master_mutex) 
||
  
lockdep_is_held(&drm_file.master_lookup_lock)));
#endif

doesn't exactly roll off the tongue, but should do as you want I
suppose.

Would something like:

#define lockdep_assert(cond)WARN_ON_ONCE(debug_locks && !(cond))

Such that we can write:

lockdep_assert(lockdep_is_held(&drm_device.master_mutex) ||
   lockdep_is_held(&drm_file.master_lookup_lock));

make it better ?


Yeah I think that's pretty tidy and flexible.

Desmond, can you pls give this a shot with Peter's patch below?
-Daniel


Sounds good, will do. Thanks for the patch, Peter.

Just going to make a small edit:
s/LOCK_STAT_NOT_HELD/LOCK_STATE_NOT_HELD/

Best wishes,
Desmond



---
Subject: locking/lockdep: Provide lockdep_assert{,_once}() helpers

Extract lockdep_assert{,_once}() helpers to more easily write composite
assertions like, for example:

lockdep_assert(lockdep_is_held(&drm_device.master_mutex) ||
   lockdep_is_held(&drm_file.master_lookup_lock));

Signed-off-by: Peter Zijlstra (Intel) 
---
diff --git a/include/linux/lockdep.h b/include/linux/lockdep.h
index 5cf387813754..0da67341c1fb 100644
--- a/include/linux/lockdep.h
+++ b/include/linux/lockdep.h
@@ -306,31 +306,29 @@ extern void lock_unpin_lock(struct lockdep_map *lock, 
struct pin_cookie);
  
  #define lockdep_depth(tsk)	(debug_locks ? (tsk)->lockdep_depth : 0)
  
-#define lockdep_assert_held(l)	do {	\

-   WARN_ON(debug_locks &&  \
-   lockdep_is_held(l) == LOCK_STATE_NOT_HELD); \
-   } while (0)
+#define lockdep_assert(cond)   \
+   do { WARN_ON(debug_locks && !(cond)); } while (0)
  
-#define lockdep_assert_not_held(l)	do {\

-   WARN_ON(debug_locks &&  \
-   lockdep_is_held(l) == LOCK_STATE_HELD); \
-   } while (0)
+#define lockdep_assert_once(cond)  \
+   do { WARN_ON_ONCE(debug_locks && !(cond)); } while (0)
  
-#define lockdep_assert_held_write(l)	do {			\

-   WARN_ON(debug_locks && !lockdep_is_held_type(l, 0));\
-   } while (0)
+#define lockdep_assert_held(l) \
+   lockdep_assert(lockdep_is_held(l) != LOCK_STAT_NOT_HELD)
  
-#define lockdep_assert_held_read(l)	do {\

-   WARN_ON(debug_locks && !lockdep_is_held_type(l, 1));\
-   } while (0)
+#define lockdep_assert_not_held(l) \
+   lockdep_assert(lockdep_is_held(l) != LOCK_STATE_HELD)
  
-#define lockdep_assert_held_once(l)	do {\

-   WARN_ON_ONCE(debug_locks && !lockdep_is_held(l));   \
-   } while (0)
+#define lockdep_assert_held_write(l)   \
+   lockdep_assert(lockdep_is_held_type(l, 0))
  
-#define lockdep_assert_none_held_once()	do {\

-   WARN_ON_ONCE(debug_locks && current->lockdep_depth); \
-   } while (0)
+#define lockdep_assert_held_read(l)\
+   lockdep_assert(lockdep_is_held_type(l, 1))
+
+#define lockdep_assert_held_once(l)\
+   lockdep_assert_once(lockdep_is_held(l) != LOCK_STAT_NOT_HELD)
+
+#define lockdep_assert_none_held_once()\
+   lockdep_assert_once(!current->lockdep_depth)
  
  #define lockdep_recursing(tsk)	((tsk)->lockdep_recursion)
  
@@ -407,6 +405,9 @@ extern int lock_is_held(const void *);

  extern int lockdep_is_hel

Re: [Intel-gfx] [PATCH v2 2/3] drm: clarify usage of drm leases

2021-07-28 Thread Desmond Cheong Zhi Xi

On 27/7/21 9:04 pm, Daniel Vetter wrote:

On Sat, Jul 24, 2021 at 07:18:23PM +0800, Desmond Cheong Zhi Xi wrote:

We make the following changes to the documentation of drm leases to
make it easier to reason about their usage. In particular, we clarify
the lifetime and locking rules of lease fields in drm_master:

1. Make it clear that &drm_device.mode_config.idr_mutex protects the
lease idr and list structures for drm_master. The lessor field itself
doesn't need to be protected as it doesn't change after it's set in
drm_lease_create.

2. Add descriptions for the lifetime of lessors and leases.

3. Add an overview DOC: section in drm-uapi.rst that defines the
terminology for drm leasing, and explains how leases work and why
they're used.

4. Clean up function documentation in drm_lease.c to use kernel-doc
formatting.

Signed-off-by: Desmond Cheong Zhi Xi 
---

Hi,

After I updated the formatting for comments in drm_lease.c, I noticed
that none of these were driver interfaces (i.e. no structs/inline
functions declared in headers, and no exported symbols in .c files).

I left the kernel-doc links inside drm-uapi.rst so that if any such
interfaces are defined in the future, they'll go to the appropriate
place. But if these should be removed, or if the formatting changes for
function comments should be removed, please let me know.



Hm indeed, so there's not really any need to either include the
drm_lease.c or drm_lease.h kerneldoc. The DOC section itself is still
useful.

For the internal pieces usually what we do is remove the comment outright
if it doesn't provide anything useful (like just repeats what the function
name says already). If there's something interesting in the comment then
we leave it that sentence in there as a normal comment, but without any of
the structured comment stuff (so no /**, nor @arguments, or the function
summary).





Best wishes,
Desmond

  Documentation/gpu/drm-uapi.rst |  15 +++
  drivers/gpu/drm/drm_lease.c| 182 -
  include/drm/drm_auth.h |  67 ++--
  3 files changed, 180 insertions(+), 84 deletions(-)

diff --git a/Documentation/gpu/drm-uapi.rst b/Documentation/gpu/drm-uapi.rst
index 7e51dd40bf6e..6d7233a9fb14 100644
--- a/Documentation/gpu/drm-uapi.rst
+++ b/Documentation/gpu/drm-uapi.rst
@@ -37,6 +37,21 @@ Primary Nodes, DRM Master and Authentication
  .. kernel-doc:: include/drm/drm_auth.h
 :internal:
  
+

+.. _drm_leasing:
+
+DRM Display Resource Leasing
+
+
+.. kernel-doc:: drivers/gpu/drm/drm_lease.c
+   :doc: drm leasing
+
+.. kernel-doc:: drivers/gpu/drm/drm_lease.c
+   :export:
+
+.. kernel-doc:: include/drm/drm_lease.h
+   :internal:
+
  Open-Source Userspace Requirements
  ==
  
diff --git a/drivers/gpu/drm/drm_lease.c b/drivers/gpu/drm/drm_lease.c

index 92eac73d9001..9b68617840ed 100644
--- a/drivers/gpu/drm/drm_lease.c
+++ b/drivers/gpu/drm/drm_lease.c
@@ -15,18 +15,67 @@
  #include "drm_crtc_internal.h"
  #include "drm_internal.h"
  
+/**

+ * DOC: drm leasing
+ *
+ * DRM leases provide information about whether a DRM master may control a DRM
+ * mode setting object. This enables the creation of multiple DRM masters that
+ * manage subsets of display resources.
+ *
+ * The original DRM master of a device 'owns' the available drm resources. It
+ * may create additional DRM masters and 'lease' resources which it controls
+ * to the new DRM master. This gives the new DRM master control over the
+ * leased resources until the owner revokes the lease, or the new DRM master
+ * is closed. Some helpful terminology:
+ *
+ * - An 'owner' is a &struct drm_master that is not leasing objects from
+ *   another &struct drm_master, and hence 'owns' the objects. The owner can be
+ *   identified as the &struct drm_master for which &drm_master.lessor is NULL.
+ *
+ * - A 'lessor' is a &struct drm_master which is leasing objects to one or more
+ *   other &struct drm_master. Currently, lessees are not allowed to
+ *   create sub-leases, hence the lessor is the same as the owner.
+ *
+ * - A 'lessee' is a &struct drm_master which is leasing objects from some
+ *   other &struct drm_master. Each lessee only leases resources from a single
+ *   lessor recorded in &drm_master.lessor, and holds the set of objects that
+ *   it is leasing in &drm_master.leases.
+ *
+ * - A 'lease' is a contract between the lessor and lessee that identifies
+ *   which resources may be controlled by the lessee. All of the resources
+ *   that are leased must be owned by or leased to the lessor, and lessors are
+ *   not permitted to lease the same object to multiple lessees.
+ *
+ * The set of objects any &struct drm_master 'controls' is limited to the set
+ * of objects it leases (for lessees) o

[Intel-gfx] [PATCH v2 2/3] drm: clarify usage of drm leases

2021-07-26 Thread Desmond Cheong Zhi Xi
We make the following changes to the documentation of drm leases to
make it easier to reason about their usage. In particular, we clarify
the lifetime and locking rules of lease fields in drm_master:

1. Make it clear that &drm_device.mode_config.idr_mutex protects the
lease idr and list structures for drm_master. The lessor field itself
doesn't need to be protected as it doesn't change after it's set in
drm_lease_create.

2. Add descriptions for the lifetime of lessors and leases.

3. Add an overview DOC: section in drm-uapi.rst that defines the
terminology for drm leasing, and explains how leases work and why
they're used.

4. Clean up function documentation in drm_lease.c to use kernel-doc
formatting.

Signed-off-by: Desmond Cheong Zhi Xi 
---

Hi,

After I updated the formatting for comments in drm_lease.c, I noticed
that none of these were driver interfaces (i.e. no structs/inline
functions declared in headers, and no exported symbols in .c files).

I left the kernel-doc links inside drm-uapi.rst so that if any such
interfaces are defined in the future, they'll go to the appropriate
place. But if these should be removed, or if the formatting changes for
function comments should be removed, please let me know.

Best wishes,
Desmond

 Documentation/gpu/drm-uapi.rst |  15 +++
 drivers/gpu/drm/drm_lease.c| 182 -
 include/drm/drm_auth.h |  67 ++--
 3 files changed, 180 insertions(+), 84 deletions(-)

diff --git a/Documentation/gpu/drm-uapi.rst b/Documentation/gpu/drm-uapi.rst
index 7e51dd40bf6e..6d7233a9fb14 100644
--- a/Documentation/gpu/drm-uapi.rst
+++ b/Documentation/gpu/drm-uapi.rst
@@ -37,6 +37,21 @@ Primary Nodes, DRM Master and Authentication
 .. kernel-doc:: include/drm/drm_auth.h
:internal:
 
+
+.. _drm_leasing:
+
+DRM Display Resource Leasing
+
+
+.. kernel-doc:: drivers/gpu/drm/drm_lease.c
+   :doc: drm leasing
+
+.. kernel-doc:: drivers/gpu/drm/drm_lease.c
+   :export:
+
+.. kernel-doc:: include/drm/drm_lease.h
+   :internal:
+
 Open-Source Userspace Requirements
 ==
 
diff --git a/drivers/gpu/drm/drm_lease.c b/drivers/gpu/drm/drm_lease.c
index 92eac73d9001..9b68617840ed 100644
--- a/drivers/gpu/drm/drm_lease.c
+++ b/drivers/gpu/drm/drm_lease.c
@@ -15,18 +15,67 @@
 #include "drm_crtc_internal.h"
 #include "drm_internal.h"
 
+/**
+ * DOC: drm leasing
+ *
+ * DRM leases provide information about whether a DRM master may control a DRM
+ * mode setting object. This enables the creation of multiple DRM masters that
+ * manage subsets of display resources.
+ *
+ * The original DRM master of a device 'owns' the available drm resources. It
+ * may create additional DRM masters and 'lease' resources which it controls
+ * to the new DRM master. This gives the new DRM master control over the
+ * leased resources until the owner revokes the lease, or the new DRM master
+ * is closed. Some helpful terminology:
+ *
+ * - An 'owner' is a &struct drm_master that is not leasing objects from
+ *   another &struct drm_master, and hence 'owns' the objects. The owner can be
+ *   identified as the &struct drm_master for which &drm_master.lessor is NULL.
+ *
+ * - A 'lessor' is a &struct drm_master which is leasing objects to one or more
+ *   other &struct drm_master. Currently, lessees are not allowed to
+ *   create sub-leases, hence the lessor is the same as the owner.
+ *
+ * - A 'lessee' is a &struct drm_master which is leasing objects from some
+ *   other &struct drm_master. Each lessee only leases resources from a single
+ *   lessor recorded in &drm_master.lessor, and holds the set of objects that
+ *   it is leasing in &drm_master.leases.
+ *
+ * - A 'lease' is a contract between the lessor and lessee that identifies
+ *   which resources may be controlled by the lessee. All of the resources
+ *   that are leased must be owned by or leased to the lessor, and lessors are
+ *   not permitted to lease the same object to multiple lessees.
+ *
+ * The set of objects any &struct drm_master 'controls' is limited to the set
+ * of objects it leases (for lessees) or all objects (for owners).
+ *
+ * Objects not controlled by a &struct drm_master cannot be modified through
+ * the various state manipulating ioctls, and any state reported back to user
+ * space will be edited to make them appear idle and/or unusable. For
+ * instance, connectors always report 'disconnected', while encoders
+ * report no possible crtcs or clones.
+ *
+ * Since each lessee may lease objects from a single lessor, display resource
+ * leases form a tree of &struct drm_master. As lessees are currently not
+ * allowed to create sub-leases, the tree depth is limited to 1. All of
+ * these get activated simultaneously, so &drm_device

[Intel-gfx] [PATCH v2 3/3] drm/vmwgfx: fix potential UAF in vmwgfx_surface.c

2021-07-26 Thread Desmond Cheong Zhi Xi
drm_file.master should be protected by either drm_device.master_mutex
or drm_file.master_lookup_lock when being dereferenced. However,
drm_master_get is called on unprotected file_priv->master pointers in
vmw_surface_define_ioctl and vmw_gb_surface_define_internal.

This is fixed by replacing drm_master_get with drm_file_get_master.

Signed-off-by: Desmond Cheong Zhi Xi 
Reviewed-by: Daniel Vetter 
Reviewed-by: Zack Rusin 
---
 drivers/gpu/drm/vmwgfx/vmwgfx_surface.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c 
b/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c
index 0eba47762bed..5d53a5f9d123 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c
@@ -865,7 +865,7 @@ int vmw_surface_define_ioctl(struct drm_device *dev, void 
*data,
user_srf->prime.base.shareable = false;
user_srf->prime.base.tfile = NULL;
if (drm_is_primary_client(file_priv))
-   user_srf->master = drm_master_get(file_priv->master);
+   user_srf->master = drm_file_get_master(file_priv);
 
/**
 * From this point, the generic resource management functions
@@ -1534,7 +1534,7 @@ vmw_gb_surface_define_internal(struct drm_device *dev,
 
user_srf = container_of(srf, struct vmw_user_surface, srf);
if (drm_is_primary_client(file_priv))
-   user_srf->master = drm_master_get(file_priv->master);
+   user_srf->master = drm_file_get_master(file_priv);
 
res = &user_srf->srf.res;
 
-- 
2.25.1

___
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx


[Intel-gfx] [PATCH v2 1/3] drm: use the lookup lock in drm_is_current_master

2021-07-26 Thread Desmond Cheong Zhi Xi
Inside drm_is_current_master, using the outer drm_device.master_mutex
to protect reads of drm_file.master makes the function prone to creating
lock hierarchy inversions. Instead, we can use the
drm_file.master_lookup_lock that sits at the bottom of the lock
hierarchy.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
Reviewed-by: Daniel Vetter 
---
 drivers/gpu/drm/drm_auth.c | 9 +
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index f00354bec3fb..9c24b8cc8e36 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -63,8 +63,9 @@
 
 static bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
-   lockdep_assert_held_once(&fpriv->minor->dev->master_mutex);
-
+   /* Either drm_device.master_mutex or drm_file.master_lookup_lock
+* should be held here.
+*/
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
 
@@ -82,9 +83,9 @@ bool drm_is_current_master(struct drm_file *fpriv)
 {
bool ret;
 
-   mutex_lock(&fpriv->minor->dev->master_mutex);
+   spin_lock(&fpriv->master_lookup_lock);
ret = drm_is_current_master_locked(fpriv);
-   mutex_unlock(&fpriv->minor->dev->master_mutex);
+   spin_unlock(&fpriv->master_lookup_lock);
 
return ret;
 }
-- 
2.25.1

___
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx


Re: [Intel-gfx] [PATCH 3/3] drm/vmwgfx: fix potential UAF in vmwgfx_surface.c

2021-07-23 Thread Desmond Cheong Zhi Xi

On 23/7/21 3:17 am, Zack Rusin wrote:

On 7/22/21 5:29 AM, Desmond Cheong Zhi Xi wrote:

drm_file.master should be protected by either drm_device.master_mutex
or drm_file.master_lookup_lock when being dereferenced. However,
drm_master_get is called on unprotected file_priv->master pointers in
vmw_surface_define_ioctl and vmw_gb_surface_define_internal.

This is fixed by replacing drm_master_get with drm_file_get_master.

Signed-off-by: Desmond Cheong Zhi Xi 


Reviewed-by: Zack Rusin 

Thanks for taking the time to fix this. Apart from the clear logic 
error, do you happen to know under what circumstances would this be hit? 
We have someone looking at writing some vmwgfx specific igt tests and I 
was wondering if I could add this to the list.


z


Hi Zack,

Thanks for the review.

For some context, the use-after-free happens when there's a race between 
accessing the value of drm_file.master, and a call to 
drm_setmaster_ioctl. If drm_file is not the creator of master, then the 
ioctl allocates a new master for drm_file and puts the old master.


Thus for example, the old value of drm_file.master could be freed in 
between getting the value of file_priv->master, and the call to 
drm_master_get.


I'm not entirely sure whether this scenario is a good candidate for a test?

For further reference, the issue was originally caught by Syzbot here:
https://syzkaller.appspot.com/bug?id=148d2f1dfac64af52ffd27b661981a540724f803

And from the logs it seems that the reproducer set up a race between 
DRM_IOCTL_GET_UNIQUE and DRM_IOCTL_SET_MASTER. So possibly a race 
between VMW_CREATE_SURFACE and DRM_IOCTL_SET_MASTER could trigger the 
same bug.


Best wishes,
Desmond

___
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx


[Intel-gfx] [PATCH 0/3] drm, drm/vmwgfx: fixes and updates related to drm_master

2021-07-22 Thread Desmond Cheong Zhi Xi
Hi,

This series contains some improvements that Daniel Vetter proposed following a 
discussion on a recent series:
https://lore.kernel.org/lkml/20210712043508.11584-1-desmondcheon...@gmail.com/

While preparing these patches, I also noticed some unprotected uses of 
drm_master in the vmwgfx driver that can be addressed by new functions from the 
previous series.

This series is thus broken up into three patches:

1. Switch from the outer drm_device.master_mutex to the inner 
drm_file.master_lookup_lock in drm_is_current_master.

2. Update the kerneldoc for lease fields in drm_master to clarify 
lifetime/locking rules.

3. Prevent potential use-after-free bugs by replacing calls to drm_master_get 
with drm_file_get_master in vmwgfx_surface.c.

Best wishes,
Desmond

Desmond Cheong Zhi Xi (3):
  drm: use the lookup lock in drm_is_current_master
  drm: clarify lifetime/locking for drm_master's lease fields
  drm/vmwgfx: fix potential UAF in vmwgfx_surface.c

 drivers/gpu/drm/drm_auth.c  |  9 ++--
 drivers/gpu/drm/vmwgfx/vmwgfx_surface.c |  4 +-
 include/drm/drm_auth.h  | 62 -
 3 files changed, 58 insertions(+), 17 deletions(-)

-- 
2.25.1

___
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx


Re: [Intel-gfx] [PATCH 2/3] drm: clarify lifetime/locking for drm_master's lease fields

2021-07-22 Thread Desmond Cheong Zhi Xi

On 22/7/21 6:35 pm, Daniel Vetter wrote:

On Thu, Jul 22, 2021 at 05:29:28PM +0800, Desmond Cheong Zhi Xi wrote:

In particular, we make it clear that &drm_device.mode_config.idr_mutex
protects the lease idr and list structures for drm_master. The lessor
field itself doesn't need to be protected as it doesn't change after
it's set in drm_lease_create.

Additionally, we add descriptions for the lifetime of lessors and
leases to make it easier to reason about them.

Signed-off-by: Desmond Cheong Zhi Xi 
---
  include/drm/drm_auth.h | 62 ++
  1 file changed, 51 insertions(+), 11 deletions(-)

diff --git a/include/drm/drm_auth.h b/include/drm/drm_auth.h
index f99d3417f304..c978c85464fa 100644
--- a/include/drm/drm_auth.h
+++ b/include/drm/drm_auth.h
@@ -58,12 +58,6 @@ struct drm_lock_data {
   * @refcount: Refcount for this master object.
   * @dev: Link back to the DRM device
   * @driver_priv: Pointer to driver-private information.
- * @lessor: Lease holder
- * @lessee_id: id for lessees. Owners always have id 0
- * @lessee_list: other lessees of the same master
- * @lessees: drm_masters leasing from this one
- * @leases: Objects leased to this drm_master.
- * @lessee_idr: All lessees under this owner (only used where lessor == NULL)
   *
   * Note that master structures are only relevant for the legacy/primary device
   * nodes, hence there can only be one per device, not one per drm_minor.
@@ -88,17 +82,63 @@ struct drm_master {
struct idr magic_map;
void *driver_priv;
  
-	/* Tree of display resource leases, each of which is a drm_master struct

-* All of these get activated simultaneously, so drm_device master 
points
-* at the top of the tree (for which lessor is NULL). Protected by
-* &drm_device.mode_config.idr_mutex.
+   /**
+* @lessor:
+*
+* Lease holder. The lessor does not change once it's set in


Lessor is the lease grantor, lessee is the one receiving the lease. Maybe
clarify this with

"Lease grantor, only set if this struct drm_master represents a lessee
holding a lease of objects from @lessor. Full owners of the device have
this set to NULL."


+* drm_lease_create(). Each lessee holds a reference to its lessor that


I also figured it'd be a good idea to link to the drm_lease docs here to
explain the concepts, but alas we don't have those :-( Hence at least
define what we mean with lessor, lessee, lease and owner.



Thanks for the suggestions, Daniel. I'll incorporate them in a v2.

Regarding the missing drm_lease docs... any reason we can't just add it 
in? Seems like most of the comments in drm_lease.c are almost correctly 
formatted too. I could clean them up, define the terms in a preamble, 
then add it to the v2 patch.



+* it releases upon being destroyed in drm_lease_destroy().
+*
+* Display resource leases form a tree of &struct drm_master. All of


For now we've limited the tree to a depth of 1, you can't create another
lease if yourself are a lease. I guess another reason to update the
drm_lease.c docs.

So maybe add "Currently the lease tree depth is limited to 1."


+* these get activated simultaneously, so &drm_device.master
+* points at the top of the tree (for which lessor is NULL).
 */
-
struct drm_master *lessor;
+
+   /**
+* @lessee_id:
+*
+* ID for lessees. Owners always have ID 0. Protected by


Maybe clarify to "Owners (i.e. @lessor is NULL) ..."


+* &drm_device.mode_config's &drm_mode_config.idr_mutex.
+*/
int lessee_id;
+
+   /**
+* @lessee_list:
+*
+* List of lessees of the same master. Protected by


We try to distinguis between list entries and the list, even though it's
the same struct. So maybe

"List entry of lessees of @lessor, where they are linked to @lessee. Not
use for owners."


+* &drm_device.mode_config's &drm_mode_config.idr_mutex.



+*/
struct list_head lessee_list;
+
+   /**
+* @lessees:
+*
+* List of drm_masters leasing from this one. Protected by
+* &drm_device.mode_config's &drm_mode_config.idr_mutex.
+*
+* This master cannot be destroyed unless this list is empty as lessors
+* are referenced by all their lessees.


Maybe add "this list is empty of no leases have been granted."


+*/
struct list_head lessees;
+
+   /**
+* @leases:
+*
+* Objects leased to this drm_master. Protected by
+* &drm_device.mode_config's &drm_mode_config.idr_mutex.
+*
+* Objects are leased all together in drm_lease_create(), and are
+* removed all together when the lease is revoked.
+ 

Re: [Intel-gfx] [PATCH v8 0/5] drm: address potential UAF bugs with drm_master ptrs

2021-07-22 Thread Desmond Cheong Zhi Xi

On 21/7/21 9:23 pm, Daniel Vetter wrote:

On Wed, Jul 21, 2021 at 2:44 PM Desmond Cheong Zhi Xi
 wrote:

On 21/7/21 6:29 pm, Daniel Vetter wrote:

On Wed, Jul 21, 2021 at 6:12 AM Desmond Cheong Zhi Xi
 wrote:

On 21/7/21 2:24 am, Daniel Vetter wrote:

On Mon, Jul 12, 2021 at 12:35:03PM +0800, Desmond Cheong Zhi Xi wrote:

Hi,

In the previous thread on this series we decided to remove a patch that was 
violating a lockdep requirement in drm_lease. In addition to this change, I 
took a closer look at the CI logs for the Basic Acceptance Tests and noticed 
that another regression was introduced. The new patch 2 is a response to this.

Overall, this series addresses potential use-after-free errors when 
dereferencing pointers to struct drm_master. These were identified after one 
such bug was caught by Syzbot in drm_getunique():
https://syzkaller.appspot.com/bug?id=148d2f1dfac64af52ffd27b661981a540724f803

The series is broken up into five patches:

1. Move a call to drm_is_current_master() out from a section locked by 
&dev->mode_config.mutex in drm_mode_getconnector(). This patch does not apply 
to stable.

2. Move a call to drm_is_current_master() out from the RCU read-side critical 
section in drm_clients_info().

3. Implement a locked version of drm_is_current_master() function that's used 
within drm_auth.c.

4. Serialize drm_file.master by introducing a new spinlock that's held whenever 
the value of drm_file.master changes.

5. Identify areas in drm_lease.c where pointers to struct drm_master are 
dereferenced, and ensure that the master pointers are not freed during use.

v7 -> v8:
- Remove the patch that moves the call to _drm_lease_held out from the section locked 
by &dev->mode_config.idr_mutex in __drm_mode_object_find. This patch violated 
an existing lockdep requirement as reported by the intel-gfx CI.
- Added a new patch that moves a call to drm_is_current_master out from the RCU 
critical section in drm_clients_info. This was reported by the intel-gfx CI.

v6 -> v7:
- Modify code alignment as suggested by the intel-gfx CI.
- Add a new patch to the series that adds a new lock to serialize 
drm_file.master, in response to the lockdep splat by the intel-gfx CI.
- Update drm_file_get_master to use the new drm_file.master_lock instead of 
drm_device.master_mutex, in response to the lockdep splat by the intel-gfx CI.

v5 -> v6:
- Add a new patch to the series that moves the call to _drm_lease_held out from the 
section locked by &dev->mode_config.idr_mutex in __drm_mode_object_find.
- Clarify the kerneldoc for dereferencing drm_file.master, as suggested by 
Daniel Vetter.
- Refactor error paths with goto labels so that each function only has a single 
drm_master_put(), as suggested by Emil Velikov.
- Modify comparisons to NULL into "!master", as suggested by the intel-gfx CI.

v4 -> v5:
- Add a new patch to the series that moves the call to drm_is_current_master in 
drm_mode_getconnector out from the section locked by &dev->mode_config.mutex.
- Additionally, added a missing semicolon to the patch, caught by the intel-gfx 
CI.

v3 -> v4:
- Move the call to drm_is_current_master in drm_mode_getconnector out from the section 
locked by &dev->mode_config.mutex. As suggested by Daniel Vetter. This avoids a 
circular lock lock dependency as reported here 
https://patchwork.freedesktop.org/patch/440406/
- Inside drm_is_current_master, instead of grabbing &fpriv->master->dev->master_mutex, we grab 
&fpriv->minor->dev->master_mutex to avoid dereferencing a null ptr if fpriv->master is not 
set.
- Modify kerneldoc formatting for drm_file.master, as suggested by Daniel 
Vetter.
- Additionally, add a file_priv->master NULL check inside drm_file_get_master, 
and handle the NULL result accordingly in drm_lease.c. As suggested by Daniel 
Vetter.

v2 -> v3:
- Move the definition of drm_is_current_master and the _locked version higher 
up in drm_auth.c to avoid needing a forward declaration of 
drm_is_current_master_locked. As suggested by Daniel Vetter.
- Instead of leaking drm_device.master_mutex into drm_lease.c to protect drm_master 
pointers, add a new drm_file_get_master() function that returns drm_file->master 
while increasing its reference count, to prevent drm_file->master from being 
freed. As suggested by Daniel Vetter.

v1 -> v2:
- Move the lock and assignment before the DRM_DEBUG_LEASE in 
drm_mode_get_lease_ioctl, as suggested by Emil Velikov.


Apologies for the delay, I missed your series. Maybe just ping next time
around there's silence.

Looks all great, merged to drm-misc-next. Given how complex this was I'm
vary of just pushing this to -fixes without some solid testing.



Hi Daniel,

Thanks for merging, more testing definitely sounds good to me.


One thing I noticed is that drm_is_current_master could just use the
spinlock, since it's only doing a read access. Ca

[Intel-gfx] [PATCH 1/3] drm: use the lookup lock in drm_is_current_master

2021-07-22 Thread Desmond Cheong Zhi Xi
Inside drm_is_current_master, using the outer drm_device.master_mutex
to protect reads of drm_file.master makes the function prone to creating
lock hierarchy inversions. Instead, we can use the
drm_file.master_lookup_lock that sits at the bottom of the lock
hierarchy.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c | 9 +
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index f00354bec3fb..9c24b8cc8e36 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -63,8 +63,9 @@
 
 static bool drm_is_current_master_locked(struct drm_file *fpriv)
 {
-   lockdep_assert_held_once(&fpriv->minor->dev->master_mutex);
-
+   /* Either drm_device.master_mutex or drm_file.master_lookup_lock
+* should be held here.
+*/
return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
 }
 
@@ -82,9 +83,9 @@ bool drm_is_current_master(struct drm_file *fpriv)
 {
bool ret;
 
-   mutex_lock(&fpriv->minor->dev->master_mutex);
+   spin_lock(&fpriv->master_lookup_lock);
ret = drm_is_current_master_locked(fpriv);
-   mutex_unlock(&fpriv->minor->dev->master_mutex);
+   spin_unlock(&fpriv->master_lookup_lock);
 
return ret;
 }
-- 
2.25.1

___
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx


[Intel-gfx] [PATCH 3/3] drm/vmwgfx: fix potential UAF in vmwgfx_surface.c

2021-07-22 Thread Desmond Cheong Zhi Xi
drm_file.master should be protected by either drm_device.master_mutex
or drm_file.master_lookup_lock when being dereferenced. However,
drm_master_get is called on unprotected file_priv->master pointers in
vmw_surface_define_ioctl and vmw_gb_surface_define_internal.

This is fixed by replacing drm_master_get with drm_file_get_master.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/vmwgfx/vmwgfx_surface.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c 
b/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c
index 0eba47762bed..5d53a5f9d123 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c
@@ -865,7 +865,7 @@ int vmw_surface_define_ioctl(struct drm_device *dev, void 
*data,
user_srf->prime.base.shareable = false;
user_srf->prime.base.tfile = NULL;
if (drm_is_primary_client(file_priv))
-   user_srf->master = drm_master_get(file_priv->master);
+   user_srf->master = drm_file_get_master(file_priv);
 
/**
 * From this point, the generic resource management functions
@@ -1534,7 +1534,7 @@ vmw_gb_surface_define_internal(struct drm_device *dev,
 
user_srf = container_of(srf, struct vmw_user_surface, srf);
if (drm_is_primary_client(file_priv))
-   user_srf->master = drm_master_get(file_priv->master);
+   user_srf->master = drm_file_get_master(file_priv);
 
res = &user_srf->srf.res;
 
-- 
2.25.1

___
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx


[Intel-gfx] [PATCH 2/3] drm: clarify lifetime/locking for drm_master's lease fields

2021-07-22 Thread Desmond Cheong Zhi Xi
In particular, we make it clear that &drm_device.mode_config.idr_mutex
protects the lease idr and list structures for drm_master. The lessor
field itself doesn't need to be protected as it doesn't change after
it's set in drm_lease_create.

Additionally, we add descriptions for the lifetime of lessors and
leases to make it easier to reason about them.

Signed-off-by: Desmond Cheong Zhi Xi 
---
 include/drm/drm_auth.h | 62 ++
 1 file changed, 51 insertions(+), 11 deletions(-)

diff --git a/include/drm/drm_auth.h b/include/drm/drm_auth.h
index f99d3417f304..c978c85464fa 100644
--- a/include/drm/drm_auth.h
+++ b/include/drm/drm_auth.h
@@ -58,12 +58,6 @@ struct drm_lock_data {
  * @refcount: Refcount for this master object.
  * @dev: Link back to the DRM device
  * @driver_priv: Pointer to driver-private information.
- * @lessor: Lease holder
- * @lessee_id: id for lessees. Owners always have id 0
- * @lessee_list: other lessees of the same master
- * @lessees: drm_masters leasing from this one
- * @leases: Objects leased to this drm_master.
- * @lessee_idr: All lessees under this owner (only used where lessor == NULL)
  *
  * Note that master structures are only relevant for the legacy/primary device
  * nodes, hence there can only be one per device, not one per drm_minor.
@@ -88,17 +82,63 @@ struct drm_master {
struct idr magic_map;
void *driver_priv;
 
-   /* Tree of display resource leases, each of which is a drm_master struct
-* All of these get activated simultaneously, so drm_device master 
points
-* at the top of the tree (for which lessor is NULL). Protected by
-* &drm_device.mode_config.idr_mutex.
+   /**
+* @lessor:
+*
+* Lease holder. The lessor does not change once it's set in
+* drm_lease_create(). Each lessee holds a reference to its lessor that
+* it releases upon being destroyed in drm_lease_destroy().
+*
+* Display resource leases form a tree of &struct drm_master. All of
+* these get activated simultaneously, so &drm_device.master
+* points at the top of the tree (for which lessor is NULL).
 */
-
struct drm_master *lessor;
+
+   /**
+* @lessee_id:
+*
+* ID for lessees. Owners always have ID 0. Protected by
+* &drm_device.mode_config's &drm_mode_config.idr_mutex.
+*/
int lessee_id;
+
+   /**
+* @lessee_list:
+*
+* List of lessees of the same master. Protected by
+* &drm_device.mode_config's &drm_mode_config.idr_mutex.
+*/
struct list_head lessee_list;
+
+   /**
+* @lessees:
+*
+* List of drm_masters leasing from this one. Protected by
+* &drm_device.mode_config's &drm_mode_config.idr_mutex.
+*
+* This master cannot be destroyed unless this list is empty as lessors
+* are referenced by all their lessees.
+*/
struct list_head lessees;
+
+   /**
+* @leases:
+*
+* Objects leased to this drm_master. Protected by
+* &drm_device.mode_config's &drm_mode_config.idr_mutex.
+*
+* Objects are leased all together in drm_lease_create(), and are
+* removed all together when the lease is revoked.
+*/
struct idr leases;
+
+   /**
+* @lessee_idr:
+*
+* All lessees under this owner (only used where lessor is NULL).
+* Protected by &drm_device.mode_config's &drm_mode_config.idr_mutex.
+*/
struct idr lessee_idr;
/* private: */
 #if IS_ENABLED(CONFIG_DRM_LEGACY)
-- 
2.25.1

___
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx


Re: [Intel-gfx] [PATCH v8 0/5] drm: address potential UAF bugs with drm_master ptrs

2021-07-21 Thread Desmond Cheong Zhi Xi

On 21/7/21 2:24 am, Daniel Vetter wrote:

On Mon, Jul 12, 2021 at 12:35:03PM +0800, Desmond Cheong Zhi Xi wrote:

Hi,

In the previous thread on this series we decided to remove a patch that was 
violating a lockdep requirement in drm_lease. In addition to this change, I 
took a closer look at the CI logs for the Basic Acceptance Tests and noticed 
that another regression was introduced. The new patch 2 is a response to this.

Overall, this series addresses potential use-after-free errors when 
dereferencing pointers to struct drm_master. These were identified after one 
such bug was caught by Syzbot in drm_getunique():
https://syzkaller.appspot.com/bug?id=148d2f1dfac64af52ffd27b661981a540724f803

The series is broken up into five patches:

1. Move a call to drm_is_current_master() out from a section locked by 
&dev->mode_config.mutex in drm_mode_getconnector(). This patch does not apply 
to stable.

2. Move a call to drm_is_current_master() out from the RCU read-side critical 
section in drm_clients_info().

3. Implement a locked version of drm_is_current_master() function that's used 
within drm_auth.c.

4. Serialize drm_file.master by introducing a new spinlock that's held whenever 
the value of drm_file.master changes.

5. Identify areas in drm_lease.c where pointers to struct drm_master are 
dereferenced, and ensure that the master pointers are not freed during use.

v7 -> v8:
- Remove the patch that moves the call to _drm_lease_held out from the section locked 
by &dev->mode_config.idr_mutex in __drm_mode_object_find. This patch violated 
an existing lockdep requirement as reported by the intel-gfx CI.
- Added a new patch that moves a call to drm_is_current_master out from the RCU 
critical section in drm_clients_info. This was reported by the intel-gfx CI.

v6 -> v7:
- Modify code alignment as suggested by the intel-gfx CI.
- Add a new patch to the series that adds a new lock to serialize 
drm_file.master, in response to the lockdep splat by the intel-gfx CI.
- Update drm_file_get_master to use the new drm_file.master_lock instead of 
drm_device.master_mutex, in response to the lockdep splat by the intel-gfx CI.

v5 -> v6:
- Add a new patch to the series that moves the call to _drm_lease_held out from the 
section locked by &dev->mode_config.idr_mutex in __drm_mode_object_find.
- Clarify the kerneldoc for dereferencing drm_file.master, as suggested by 
Daniel Vetter.
- Refactor error paths with goto labels so that each function only has a single 
drm_master_put(), as suggested by Emil Velikov.
- Modify comparisons to NULL into "!master", as suggested by the intel-gfx CI.

v4 -> v5:
- Add a new patch to the series that moves the call to drm_is_current_master in 
drm_mode_getconnector out from the section locked by &dev->mode_config.mutex.
- Additionally, added a missing semicolon to the patch, caught by the intel-gfx 
CI.

v3 -> v4:
- Move the call to drm_is_current_master in drm_mode_getconnector out from the section 
locked by &dev->mode_config.mutex. As suggested by Daniel Vetter. This avoids a 
circular lock lock dependency as reported here 
https://patchwork.freedesktop.org/patch/440406/
- Inside drm_is_current_master, instead of grabbing &fpriv->master->dev->master_mutex, we grab 
&fpriv->minor->dev->master_mutex to avoid dereferencing a null ptr if fpriv->master is not 
set.
- Modify kerneldoc formatting for drm_file.master, as suggested by Daniel 
Vetter.
- Additionally, add a file_priv->master NULL check inside drm_file_get_master, 
and handle the NULL result accordingly in drm_lease.c. As suggested by Daniel 
Vetter.

v2 -> v3:
- Move the definition of drm_is_current_master and the _locked version higher 
up in drm_auth.c to avoid needing a forward declaration of 
drm_is_current_master_locked. As suggested by Daniel Vetter.
- Instead of leaking drm_device.master_mutex into drm_lease.c to protect drm_master 
pointers, add a new drm_file_get_master() function that returns drm_file->master 
while increasing its reference count, to prevent drm_file->master from being 
freed. As suggested by Daniel Vetter.

v1 -> v2:
- Move the lock and assignment before the DRM_DEBUG_LEASE in 
drm_mode_get_lease_ioctl, as suggested by Emil Velikov.


Apologies for the delay, I missed your series. Maybe just ping next time
around there's silence.

Looks all great, merged to drm-misc-next. Given how complex this was I'm
vary of just pushing this to -fixes without some solid testing.



Hi Daniel,

Thanks for merging, more testing definitely sounds good to me.


One thing I noticed is that drm_is_current_master could just use the
spinlock, since it's only doing a read access. Care to type up that patch?



I thought about this too, but I'm not sure if that's the best solution.

drm_is_current_master calls drm_lease_owner which then walks up the tree 
of master lessors. Th

[Intel-gfx] [PATCH v8 1/5] drm: avoid circular locks in drm_mode_getconnector

2021-07-12 Thread Desmond Cheong Zhi Xi
In preparation for a future patch to take a lock on
drm_device.master_mutex inside drm_is_current_master(), we first move
the call to drm_is_current_master() in drm_mode_getconnector out from the
section locked by &dev->mode_config.mutex. This avoids creating a
circular lock dependency.

Failing to avoid this lock dependency produces the following lockdep
splat:

==
WARNING: possible circular locking dependency detected
5.13.0-rc7-CI-CI_DRM_10254+ #1 Not tainted
--
kms_frontbuffer/1087 is trying to acquire lock:
88810dcd01a8 (&dev->master_mutex){+.+.}-{3:3}, at: 
drm_is_current_master+0x1b/0x40
but task is already holding lock:
88810dcd0488 (&dev->mode_config.mutex){+.+.}-{3:3}, at: 
drm_mode_getconnector+0x1c6/0x4a0
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #2 (&dev->mode_config.mutex){+.+.}-{3:3}:
   __mutex_lock+0xab/0x970
   drm_client_modeset_probe+0x22e/0xca0
   __drm_fb_helper_initial_config_and_unlock+0x42/0x540
   intel_fbdev_initial_config+0xf/0x20 [i915]
   async_run_entry_fn+0x28/0x130
   process_one_work+0x26d/0x5c0
   worker_thread+0x37/0x380
   kthread+0x144/0x170
   ret_from_fork+0x1f/0x30
-> #1 (&client->modeset_mutex){+.+.}-{3:3}:
   __mutex_lock+0xab/0x970
   drm_client_modeset_commit_locked+0x1c/0x180
   drm_client_modeset_commit+0x1c/0x40
   __drm_fb_helper_restore_fbdev_mode_unlocked+0x88/0xb0
   drm_fb_helper_set_par+0x34/0x40
   intel_fbdev_set_par+0x11/0x40 [i915]
   fbcon_init+0x270/0x4f0
   visual_init+0xc6/0x130
   do_bind_con_driver+0x1e5/0x2d0
   do_take_over_console+0x10e/0x180
   do_fbcon_takeover+0x53/0xb0
   register_framebuffer+0x22d/0x310
   __drm_fb_helper_initial_config_and_unlock+0x36c/0x540
   intel_fbdev_initial_config+0xf/0x20 [i915]
   async_run_entry_fn+0x28/0x130
   process_one_work+0x26d/0x5c0
   worker_thread+0x37/0x380
   kthread+0x144/0x170
   ret_from_fork+0x1f/0x30
-> #0 (&dev->master_mutex){+.+.}-{3:3}:
   __lock_acquire+0x151e/0x2590
   lock_acquire+0xd1/0x3d0
   __mutex_lock+0xab/0x970
   drm_is_current_master+0x1b/0x40
   drm_mode_getconnector+0x37e/0x4a0
   drm_ioctl_kernel+0xa8/0xf0
   drm_ioctl+0x1e8/0x390
   __x64_sys_ioctl+0x6a/0xa0
   do_syscall_64+0x39/0xb0
   entry_SYSCALL_64_after_hwframe+0x44/0xae
other info that might help us debug this:
Chain exists of: &dev->master_mutex --> &client->modeset_mutex --> 
&dev->mode_config.mutex
 Possible unsafe locking scenario:
   CPU0CPU1
   
  lock(&dev->mode_config.mutex);
   lock(&client->modeset_mutex);
   lock(&dev->mode_config.mutex);
  lock(&dev->master_mutex);
*** DEADLOCK ***
1 lock held by kms_frontbuffer/1087:
 #0: 88810dcd0488 (&dev->mode_config.mutex){+.+.}-{3:3}, at: 
drm_mode_getconnector+0x1c6/0x4a0
stack backtrace:
CPU: 7 PID: 1087 Comm: kms_frontbuffer Not tainted 5.13.0-rc7-CI-CI_DRM_10254+ 
#1
Hardware name: Intel Corporation Ice Lake Client Platform/IceLake U DDR4 SODIMM 
PD RVP TLC, BIOS ICLSFWR1.R00.3234.A01.1906141750 06/14/2019
Call Trace:
 dump_stack+0x7f/0xad
 check_noncircular+0x12e/0x150
 __lock_acquire+0x151e/0x2590
 lock_acquire+0xd1/0x3d0
 __mutex_lock+0xab/0x970
 drm_is_current_master+0x1b/0x40
 drm_mode_getconnector+0x37e/0x4a0
 drm_ioctl_kernel+0xa8/0xf0
 drm_ioctl+0x1e8/0x390
 __x64_sys_ioctl+0x6a/0xa0
 do_syscall_64+0x39/0xb0
 entry_SYSCALL_64_after_hwframe+0x44/0xae

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
Reviewed-by: Emil Velikov 
---
 drivers/gpu/drm/drm_connector.c | 5 -
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index da39e7ff6965..2ba257b1ae20 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -2414,6 +2414,7 @@ int drm_mode_getconnector(struct drm_device *dev, void 
*data,
struct drm_mode_modeinfo u_mode;
struct drm_mode_modeinfo __user *mode_ptr;
uint32_t __user *encoder_ptr;
+   bool is_current_master;
 
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return -EOPNOTSUPP;
@@ -2444,9 +2445,11 @@ int drm_mode_getconnector(struct drm_device *dev, void 
*data,
out_resp->connector_type = connector->connector_type;
out_resp->connector_type_id = connector->connector_type_id;
 
+   is_current_master = drm_is_current_master(file_priv);
+
mutex_lock(&dev->mode_config.mutex);
if (out_resp->count_modes == 0) {
-   if (

[Intel-gfx] [PATCH v8 2/5] drm: avoid blocking in drm_clients_info's rcu section

2021-07-12 Thread Desmond Cheong Zhi Xi
Inside drm_clients_info, the rcu_read_lock is held to lock
pid_task()->comm. However, within this protected section, a call to
drm_is_current_master is made, which involves a mutex lock in a future
patch. However, this is illegal because the mutex lock might block
while in the RCU read-side critical section.

Since drm_is_current_master isn't protected by rcu_read_lock, we avoid
this by moving it out of the RCU critical section.

The following report came from intel-gfx ci's
igt@debugfs_test@read_all_entries testcase:

=
[ BUG: Invalid wait context ]
5.13.0-CI-Patchwork_20515+ #1 Tainted: GW
-
debugfs_test/1101 is trying to lock:
888132d901a8 (&dev->master_mutex){+.+.}-{3:3}, at:
drm_is_current_master+0x1e/0x50
other info that might help us debug this:
context-{4:4}
3 locks held by debugfs_test/1101:
 #0: 88810fdffc90 (&p->lock){+.+.}-{3:3}, at:
 seq_read_iter+0x53/0x3b0
 #1: 888132d90240 (&dev->filelist_mutex){+.+.}-{3:3}, at:
 drm_clients_info+0x63/0x2a0
 #2: 82734220 (rcu_read_lock){}-{1:2}, at:
 drm_clients_info+0x1b1/0x2a0
stack backtrace:
CPU: 8 PID: 1101 Comm: debugfs_test Tainted: GW
5.13.0-CI-Patchwork_20515+ #1
Hardware name: Intel Corporation CometLake Client Platform/CometLake S
UDIMM (ERB/CRB), BIOS CMLSFWR1.R00.1263.D00.1906260926 06/26/2019
Call Trace:
 dump_stack+0x7f/0xad
 __lock_acquire.cold.78+0x2af/0x2ca
 lock_acquire+0xd3/0x300
 ? drm_is_current_master+0x1e/0x50
 ? __mutex_lock+0x76/0x970
 ? lockdep_hardirqs_on+0xbf/0x130
 __mutex_lock+0xab/0x970
 ? drm_is_current_master+0x1e/0x50
 ? drm_is_current_master+0x1e/0x50
 ? drm_is_current_master+0x1e/0x50
 drm_is_current_master+0x1e/0x50
 drm_clients_info+0x107/0x2a0
 seq_read_iter+0x178/0x3b0
 seq_read+0x104/0x150
 full_proxy_read+0x4e/0x80
 vfs_read+0xa5/0x1b0
 ksys_read+0x5a/0xd0
 do_syscall_64+0x39/0xb0
 entry_SYSCALL_64_after_hwframe+0x44/0xae

Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_debugfs.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_debugfs.c b/drivers/gpu/drm/drm_debugfs.c
index 3d7182001004..b0a826489488 100644
--- a/drivers/gpu/drm/drm_debugfs.c
+++ b/drivers/gpu/drm/drm_debugfs.c
@@ -91,6 +91,7 @@ static int drm_clients_info(struct seq_file *m, void *data)
mutex_lock(&dev->filelist_mutex);
list_for_each_entry_reverse(priv, &dev->filelist, lhead) {
struct task_struct *task;
+   bool is_current_master = drm_is_current_master(priv);
 
rcu_read_lock(); /* locks pid_task()->comm */
task = pid_task(priv->pid, PIDTYPE_PID);
@@ -99,7 +100,7 @@ static int drm_clients_info(struct seq_file *m, void *data)
   task ? task->comm : "",
   pid_vnr(priv->pid),
   priv->minor->index,
-  drm_is_current_master(priv) ? 'y' : 'n',
+  is_current_master ? 'y' : 'n',
   priv->authenticated ? 'y' : 'n',
   from_kuid_munged(seq_user_ns(m), uid),
   priv->magic);
-- 
2.25.1

___
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx


[Intel-gfx] [PATCH v8 5/5] drm: protect drm_master pointers in drm_lease.c

2021-07-12 Thread Desmond Cheong Zhi Xi
drm_file->master pointers should be protected by
drm_device.master_mutex or drm_file.master_lookup_lock when being
dereferenced.

However, in drm_lease.c, there are multiple instances where
drm_file->master is accessed and dereferenced while neither lock is
held. This makes drm_lease.c vulnerable to use-after-free bugs.

We address this issue in 2 ways:

1. Add a new drm_file_get_master() function that calls drm_master_get
on drm_file->master while holding on to
drm_file.master_lookup_lock. Since drm_master_get increments the
reference count of master, this prevents master from being freed until
we unreference it with drm_master_put.

2. In each case where drm_file->master is directly accessed and
eventually dereferenced in drm_lease.c, we wrap the access in a call
to the new drm_file_get_master function, then unreference the master
pointer once we are done using it.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
Reviewed-by: Emil Velikov 
---
 drivers/gpu/drm/drm_auth.c  | 25 
 drivers/gpu/drm/drm_lease.c | 81 -
 include/drm/drm_auth.h  |  1 +
 include/drm/drm_file.h  |  6 +++
 4 files changed, 93 insertions(+), 20 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index 30a239901b36..f00354bec3fb 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -389,6 +389,31 @@ struct drm_master *drm_master_get(struct drm_master 
*master)
 }
 EXPORT_SYMBOL(drm_master_get);
 
+/**
+ * drm_file_get_master - reference &drm_file.master of @file_priv
+ * @file_priv: DRM file private
+ *
+ * Increments the reference count of @file_priv's &drm_file.master and returns
+ * the &drm_file.master. If @file_priv has no &drm_file.master, returns NULL.
+ *
+ * Master pointers returned from this function should be unreferenced using
+ * drm_master_put().
+ */
+struct drm_master *drm_file_get_master(struct drm_file *file_priv)
+{
+   struct drm_master *master = NULL;
+
+   spin_lock(&file_priv->master_lookup_lock);
+   if (!file_priv->master)
+   goto unlock;
+   master = drm_master_get(file_priv->master);
+
+unlock:
+   spin_unlock(&file_priv->master_lookup_lock);
+   return master;
+}
+EXPORT_SYMBOL(drm_file_get_master);
+
 static void drm_master_destroy(struct kref *kref)
 {
struct drm_master *master = container_of(kref, struct drm_master, 
refcount);
diff --git a/drivers/gpu/drm/drm_lease.c b/drivers/gpu/drm/drm_lease.c
index 00fb433bcef1..92eac73d9001 100644
--- a/drivers/gpu/drm/drm_lease.c
+++ b/drivers/gpu/drm/drm_lease.c
@@ -106,10 +106,19 @@ static bool _drm_has_leased(struct drm_master *master, 
int id)
  */
 bool _drm_lease_held(struct drm_file *file_priv, int id)
 {
-   if (!file_priv || !file_priv->master)
+   bool ret;
+   struct drm_master *master;
+
+   if (!file_priv)
return true;
 
-   return _drm_lease_held_master(file_priv->master, id);
+   master = drm_file_get_master(file_priv);
+   if (!master)
+   return true;
+   ret = _drm_lease_held_master(master, id);
+   drm_master_put(&master);
+
+   return ret;
 }
 
 /**
@@ -128,13 +137,22 @@ bool drm_lease_held(struct drm_file *file_priv, int id)
struct drm_master *master;
bool ret;
 
-   if (!file_priv || !file_priv->master || !file_priv->master->lessor)
+   if (!file_priv)
return true;
 
-   master = file_priv->master;
+   master = drm_file_get_master(file_priv);
+   if (!master)
+   return true;
+   if (!master->lessor) {
+   ret = true;
+   goto out;
+   }
mutex_lock(&master->dev->mode_config.idr_mutex);
ret = _drm_lease_held_master(master, id);
mutex_unlock(&master->dev->mode_config.idr_mutex);
+
+out:
+   drm_master_put(&master);
return ret;
 }
 
@@ -154,10 +172,16 @@ uint32_t drm_lease_filter_crtcs(struct drm_file 
*file_priv, uint32_t crtcs_in)
int count_in, count_out;
uint32_t crtcs_out = 0;
 
-   if (!file_priv || !file_priv->master || !file_priv->master->lessor)
+   if (!file_priv)
return crtcs_in;
 
-   master = file_priv->master;
+   master = drm_file_get_master(file_priv);
+   if (!master)
+   return crtcs_in;
+   if (!master->lessor) {
+   crtcs_out = crtcs_in;
+   goto out;
+   }
dev = master->dev;
 
count_in = count_out = 0;
@@ -176,6 +200,9 @@ uint32_t drm_lease_filter_crtcs(struct drm_file *file_priv, 
uint32_t crtcs_in)
count_in++;
}
mutex_unlock(&master->dev->mode_config.idr_mutex);
+
+out:
+   drm_master_put(&master);
return crtcs_out;
 }
 
@@ -489,7 +516,7 @@ int drm_mode_create_lease_ioctl(

[Intel-gfx] [PATCH v8 0/5] drm: address potential UAF bugs with drm_master ptrs

2021-07-12 Thread Desmond Cheong Zhi Xi
Hi,

In the previous thread on this series we decided to remove a patch that was 
violating a lockdep requirement in drm_lease. In addition to this change, I 
took a closer look at the CI logs for the Basic Acceptance Tests and noticed 
that another regression was introduced. The new patch 2 is a response to this.

Overall, this series addresses potential use-after-free errors when 
dereferencing pointers to struct drm_master. These were identified after one 
such bug was caught by Syzbot in drm_getunique():
https://syzkaller.appspot.com/bug?id=148d2f1dfac64af52ffd27b661981a540724f803

The series is broken up into five patches:

1. Move a call to drm_is_current_master() out from a section locked by 
&dev->mode_config.mutex in drm_mode_getconnector(). This patch does not apply 
to stable.

2. Move a call to drm_is_current_master() out from the RCU read-side critical 
section in drm_clients_info().

3. Implement a locked version of drm_is_current_master() function that's used 
within drm_auth.c.

4. Serialize drm_file.master by introducing a new spinlock that's held whenever 
the value of drm_file.master changes.

5. Identify areas in drm_lease.c where pointers to struct drm_master are 
dereferenced, and ensure that the master pointers are not freed during use.

v7 -> v8:
- Remove the patch that moves the call to _drm_lease_held out from the section 
locked by &dev->mode_config.idr_mutex in __drm_mode_object_find. This patch 
violated an existing lockdep requirement as reported by the intel-gfx CI.
- Added a new patch that moves a call to drm_is_current_master out from the RCU 
critical section in drm_clients_info. This was reported by the intel-gfx CI.

v6 -> v7:
- Modify code alignment as suggested by the intel-gfx CI.
- Add a new patch to the series that adds a new lock to serialize 
drm_file.master, in response to the lockdep splat by the intel-gfx CI.
- Update drm_file_get_master to use the new drm_file.master_lock instead of 
drm_device.master_mutex, in response to the lockdep splat by the intel-gfx CI.

v5 -> v6:
- Add a new patch to the series that moves the call to _drm_lease_held out from 
the section locked by &dev->mode_config.idr_mutex in __drm_mode_object_find.
- Clarify the kerneldoc for dereferencing drm_file.master, as suggested by 
Daniel Vetter.
- Refactor error paths with goto labels so that each function only has a single 
drm_master_put(), as suggested by Emil Velikov.
- Modify comparisons to NULL into "!master", as suggested by the intel-gfx CI.

v4 -> v5:
- Add a new patch to the series that moves the call to drm_is_current_master in 
drm_mode_getconnector out from the section locked by &dev->mode_config.mutex.
- Additionally, added a missing semicolon to the patch, caught by the intel-gfx 
CI.

v3 -> v4:
- Move the call to drm_is_current_master in drm_mode_getconnector out from the 
section locked by &dev->mode_config.mutex. As suggested by Daniel Vetter. This 
avoids a circular lock lock dependency as reported here 
https://patchwork.freedesktop.org/patch/440406/
- Inside drm_is_current_master, instead of grabbing 
&fpriv->master->dev->master_mutex, we grab &fpriv->minor->dev->master_mutex to 
avoid dereferencing a null ptr if fpriv->master is not set.
- Modify kerneldoc formatting for drm_file.master, as suggested by Daniel 
Vetter.
- Additionally, add a file_priv->master NULL check inside drm_file_get_master, 
and handle the NULL result accordingly in drm_lease.c. As suggested by Daniel 
Vetter.

v2 -> v3:
- Move the definition of drm_is_current_master and the _locked version higher 
up in drm_auth.c to avoid needing a forward declaration of 
drm_is_current_master_locked. As suggested by Daniel Vetter.
- Instead of leaking drm_device.master_mutex into drm_lease.c to protect 
drm_master pointers, add a new drm_file_get_master() function that returns 
drm_file->master while increasing its reference count, to prevent 
drm_file->master from being freed. As suggested by Daniel Vetter.

v1 -> v2:
- Move the lock and assignment before the DRM_DEBUG_LEASE in 
drm_mode_get_lease_ioctl, as suggested by Emil Velikov.

Desmond Cheong Zhi Xi (5):
  drm: avoid circular locks in drm_mode_getconnector
  drm: avoid blocking in drm_clients_info's rcu section
  drm: add a locked version of drm_is_current_master
  drm: serialize drm_file.master with a new spinlock
  drm: protect drm_master pointers in drm_lease.c

 drivers/gpu/drm/drm_auth.c  | 93 -
 drivers/gpu/drm/drm_connector.c |  5 +-
 drivers/gpu/drm/drm_debugfs.c   |  3 +-
 drivers/gpu/drm/drm_file.c  |  1 +
 drivers/gpu/drm/drm_lease.c | 81 +---
 include/drm/drm_auth.h  |  1 +
 include/drm/drm_file.h  | 18 +--
 7 files changed, 152 insertions(+), 50 deletions(-)

-- 
2.25.1

___
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx


[Intel-gfx] [PATCH v8 4/5] drm: serialize drm_file.master with a new spinlock

2021-07-12 Thread Desmond Cheong Zhi Xi
Currently, drm_file.master pointers should be protected by
drm_device.master_mutex when being dereferenced. This is because
drm_file.master is not invariant for the lifetime of drm_file. If
drm_file is not the creator of master, then drm_file.is_master is
false, and a call to drm_setmaster_ioctl will invoke
drm_new_set_master, which then allocates a new master for drm_file and
puts the old master.

Thus, without holding drm_device.master_mutex, the old value of
drm_file.master could be freed while it is being used by another
concurrent process.

However, it is not always possible to lock drm_device.master_mutex to
dereference drm_file.master. Through the fbdev emulation code, this
might occur in a deep nest of other locks. But drm_device.master_mutex
is also the outermost lock in the nesting hierarchy, so this leads to
potential deadlocks.

To address this, we introduce a new spin lock at the bottom of the
lock hierarchy that only serializes drm_file.master. With this change,
the value of drm_file.master changes only when both
drm_device.master_mutex and drm_file.master_lookup_lock are
held. Hence, any process holding either of those locks can ensure that
the value of drm_file.master will not change concurrently.

Since no lock depends on the new drm_file.master_lookup_lock, when
drm_file.master is dereferenced, but drm_device.master_mutex cannot be
held, we can safely protect the master pointer with
drm_file.master_lookup_lock.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
---
 drivers/gpu/drm/drm_auth.c | 17 +++--
 drivers/gpu/drm/drm_file.c |  1 +
 include/drm/drm_file.h | 12 +---
 3 files changed, 21 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index ab1863c5a5a0..30a239901b36 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -164,16 +164,18 @@ static void drm_set_master(struct drm_device *dev, struct 
drm_file *fpriv,
 static int drm_new_set_master(struct drm_device *dev, struct drm_file *fpriv)
 {
struct drm_master *old_master;
+   struct drm_master *new_master;
 
lockdep_assert_held_once(&dev->master_mutex);
 
WARN_ON(fpriv->is_master);
old_master = fpriv->master;
-   fpriv->master = drm_master_create(dev);
-   if (!fpriv->master) {
-   fpriv->master = old_master;
+   new_master = drm_master_create(dev);
+   if (!new_master)
return -ENOMEM;
-   }
+   spin_lock(&fpriv->master_lookup_lock);
+   fpriv->master = new_master;
+   spin_unlock(&fpriv->master_lookup_lock);
 
fpriv->is_master = 1;
fpriv->authenticated = 1;
@@ -332,10 +334,13 @@ int drm_master_open(struct drm_file *file_priv)
 * any master object for render clients
 */
mutex_lock(&dev->master_mutex);
-   if (!dev->master)
+   if (!dev->master) {
ret = drm_new_set_master(dev, file_priv);
-   else
+   } else {
+   spin_lock(&file_priv->master_lookup_lock);
file_priv->master = drm_master_get(dev->master);
+   spin_unlock(&file_priv->master_lookup_lock);
+   }
mutex_unlock(&dev->master_mutex);
 
return ret;
diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index d4f0bac6f8f8..ceb1a9723855 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -176,6 +176,7 @@ struct drm_file *drm_file_alloc(struct drm_minor *minor)
init_waitqueue_head(&file->event_wait);
file->event_space = 4096; /* set aside 4k for event buffer */
 
+   spin_lock_init(&file->master_lookup_lock);
mutex_init(&file->event_read_lock);
 
if (drm_core_check_feature(dev, DRIVER_GEM))
diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
index b81b3bfb08c8..9b82988e3427 100644
--- a/include/drm/drm_file.h
+++ b/include/drm/drm_file.h
@@ -226,15 +226,21 @@ struct drm_file {
/**
 * @master:
 *
-* Master this node is currently associated with. Only relevant if
-* drm_is_primary_client() returns true. Note that this only
-* matches &drm_device.master if the master is the currently active one.
+* Master this node is currently associated with. Protected by struct
+* &drm_device.master_mutex, and serialized by @master_lookup_lock.
+*
+* Only relevant if drm_is_primary_client() returns true. Note that
+* this only matches &drm_device.master if the master is the currently
+* active one.
 *
 * See also @authentication and @is_master and the :ref:`section on
 * primary nodes and authentication `.
 */
struct drm_master *master;
 
+   /** @master_lock: Serializes @master. */
+   s

[Intel-gfx] [PATCH v8 3/5] drm: add a locked version of drm_is_current_master

2021-07-12 Thread Desmond Cheong Zhi Xi
While checking the master status of the DRM file in
drm_is_current_master(), the device's master mutex should be
held. Without the mutex, the pointer fpriv->master may be freed
concurrently by another process calling drm_setmaster_ioctl(). This
could lead to use-after-free errors when the pointer is subsequently
dereferenced in drm_lease_owner().

The callers of drm_is_current_master() from drm_auth.c hold the
device's master mutex, but external callers do not. Hence, we implement
drm_is_current_master_locked() to be used within drm_auth.c, and
modify drm_is_current_master() to grab the device's master mutex
before checking the master status.

Reported-by: Daniel Vetter 
Signed-off-by: Desmond Cheong Zhi Xi 
Reviewed-by: Emil Velikov 
---
 drivers/gpu/drm/drm_auth.c | 51 --
 1 file changed, 32 insertions(+), 19 deletions(-)

diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c
index f00e5abdbbf4..ab1863c5a5a0 100644
--- a/drivers/gpu/drm/drm_auth.c
+++ b/drivers/gpu/drm/drm_auth.c
@@ -61,6 +61,35 @@
  * trusted clients.
  */
 
+static bool drm_is_current_master_locked(struct drm_file *fpriv)
+{
+   lockdep_assert_held_once(&fpriv->minor->dev->master_mutex);
+
+   return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
+}
+
+/**
+ * drm_is_current_master - checks whether @priv is the current master
+ * @fpriv: DRM file private
+ *
+ * Checks whether @fpriv is current master on its device. This decides whether 
a
+ * client is allowed to run DRM_MASTER IOCTLs.
+ *
+ * Most of the modern IOCTL which require DRM_MASTER are for kernel modesetting
+ * - the current master is assumed to own the non-shareable display hardware.
+ */
+bool drm_is_current_master(struct drm_file *fpriv)
+{
+   bool ret;
+
+   mutex_lock(&fpriv->minor->dev->master_mutex);
+   ret = drm_is_current_master_locked(fpriv);
+   mutex_unlock(&fpriv->minor->dev->master_mutex);
+
+   return ret;
+}
+EXPORT_SYMBOL(drm_is_current_master);
+
 int drm_getmagic(struct drm_device *dev, void *data, struct drm_file 
*file_priv)
 {
struct drm_auth *auth = data;
@@ -223,7 +252,7 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
if (ret)
goto out_unlock;
 
-   if (drm_is_current_master(file_priv))
+   if (drm_is_current_master_locked(file_priv))
goto out_unlock;
 
if (dev->master) {
@@ -272,7 +301,7 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
if (ret)
goto out_unlock;
 
-   if (!drm_is_current_master(file_priv)) {
+   if (!drm_is_current_master_locked(file_priv)) {
ret = -EINVAL;
goto out_unlock;
}
@@ -321,7 +350,7 @@ void drm_master_release(struct drm_file *file_priv)
if (file_priv->magic)
idr_remove(&file_priv->master->magic_map, file_priv->magic);
 
-   if (!drm_is_current_master(file_priv))
+   if (!drm_is_current_master_locked(file_priv))
goto out;
 
drm_legacy_lock_master_cleanup(dev, master);
@@ -342,22 +371,6 @@ void drm_master_release(struct drm_file *file_priv)
mutex_unlock(&dev->master_mutex);
 }
 
-/**
- * drm_is_current_master - checks whether @priv is the current master
- * @fpriv: DRM file private
- *
- * Checks whether @fpriv is current master on its device. This decides whether 
a
- * client is allowed to run DRM_MASTER IOCTLs.
- *
- * Most of the modern IOCTL which require DRM_MASTER are for kernel modesetting
- * - the current master is assumed to own the non-shareable display hardware.
- */
-bool drm_is_current_master(struct drm_file *fpriv)
-{
-   return fpriv->is_master && drm_lease_owner(fpriv->master) == 
fpriv->minor->dev->master;
-}
-EXPORT_SYMBOL(drm_is_current_master);
-
 /**
  * drm_master_get - reference a master pointer
  * @master: &struct drm_master
-- 
2.25.1

___
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx


Re: [Intel-gfx] [PATCH v7 0/5] drm: address potential UAF bugs with drm_master ptrs

2021-07-06 Thread Desmond Cheong Zhi Xi

On 5/7/21 10:34 pm, Daniel Vetter wrote:

On Mon, Jul 05, 2021 at 10:15:45AM +0800, Desmond Cheong Zhi Xi wrote:

On 3/7/21 3:07 am, Daniel Vetter wrote:

On Fri, Jul 02, 2021 at 12:53:53AM +0800, Desmond Cheong Zhi Xi wrote:

This patch series addresses potential use-after-free errors when dereferencing 
pointers to struct drm_master. These were identified after one such bug was 
caught by Syzbot in drm_getunique():
https://syzkaller.appspot.com/bug?id=148d2f1dfac64af52ffd27b661981a540724f803

The series is broken up into five patches:

1. Move a call to drm_is_current_master() out from a section locked by 
&dev->mode_config.mutex in drm_mode_getconnector(). This patch does not apply 
to stable.

2. Move a call to _drm_lease_held() out from the section locked by 
&dev->mode_config.idr_mutex in __drm_mode_object_find().

3. Implement a locked version of drm_is_current_master() function that's used 
within drm_auth.c.

4. Serialize drm_file.master by introducing a new lock that's held whenever the 
value of drm_file.master changes.

5. Identify areas in drm_lease.c where pointers to struct drm_master are 
dereferenced, and ensure that the master pointers are not freed during use.

Changes in v6 -> v7:
- Patch 2:
Modify code alignment as suggested by the intel-gfx CI.

Update commit message based on the changes to patch 5.

- Patch 4:
Add patch 4 to the series. This patch adds a new lock to serialize 
drm_file.master, in response to the lockdep splat by the intel-gfx CI.

- Patch 5:
Move kerneldoc comment about protecting drm_file.master with 
drm_device.master_mutex into patch 4.

Update drm_file_get_master to use the new drm_file.master_lock instead of 
drm_device.master_mutex, in response to the lockdep splat by the intel-gfx CI.


So there's another one now because master->leases is protected by the
mode_config.idr_mutex, and that's a bit awkward to untangle.

Also I'm really surprised that there was now lockdep through the atomic
code anywhere. The reason seems to be that somehow CI reboot first before
it managed to run any of the kms_atomic tests, and we can only hit this
when we go through the atomic kms ioctl, the legacy kms ioctl don't have
that specific issue.

Anyway I think this approach doesn't look too workable, and we need
something new.

But first things first: Are you still on board working on this? You
started with a simple patch to fix a UAF bug, now we're deep into
reworking tricky locking ... If you feel like you want out I'm totally
fine with that.



Hi Daniel,

Thanks for asking, but I'm committed to seeing this through :) In fact, I
really appreciate all your guidance and patience as the simple patch evolved
into the current state of things.


Cool, it's definitely been fun trying to figure out a good solution for
this tricky problem here :-)


Anyway, I think we need to split drm_device->master_mutex up into two
parts:

- One part that protects the actual access/changes, which I think for
simplicity we'll just leave as the current lock. That lock is a very
inner lock, since for the drm_lease.c stuff it has to nest within
mode_config.idr_mutex even.

- Now the issue with checking master status/leases/whatever as an
innermost lock is that you can race, it's a classic time of check vs
time of use race: By the time we actually use the thing we validate
we'er allowed to use, we might now have access anymore. There's two
reasons for that:

* DROPMASTER ioctl could remove the master rights, which removes access
  rights also for all leases

* REVOKE_LEASE ioctl can do the same but only for a specific lease

This is the thing we're trying to protect against in fbcon code, but
that's very spotty protection because all the ioctls by other users
aren't actually protected against this.

So I think for this we need some kind of big reader lock.

Now for the implementation, there's a few things:

- I think best option for this big reader lock would be to just use srcu.
We only need to flush out all current readers when we drop master or
revoke a lease, so synchronize_srcu is perfectly good enough for this
purpose.

- The fbdev code would switch over to srcu in
drm_master_internal_acquire() and drm_master_internal_release(). Ofc
within drm_master_internal_acquire we'd still need to check master
status with the normal master_mutex.

- While we revamp all this we should fix the ioctl checks in drm_ioctl.c.
Just noticed that drm_ioctl_permit() could and should be unexported,
last user was removed.

Within drm_ioctl_kernel we'd then replace the check for
drm_is_current_master with the drm_master_internal_acquire/release.

- This alone does nothing, we still need to make sure that dropmaster and
revoke_lease ioctl flush out all other access before they return to
userspace.

Re: [Intel-gfx] [PATCH v7 0/5] drm: address potential UAF bugs with drm_master ptrs

2021-07-06 Thread Desmond Cheong Zhi Xi

On 3/7/21 3:07 am, Daniel Vetter wrote:

On Fri, Jul 02, 2021 at 12:53:53AM +0800, Desmond Cheong Zhi Xi wrote:

This patch series addresses potential use-after-free errors when dereferencing 
pointers to struct drm_master. These were identified after one such bug was 
caught by Syzbot in drm_getunique():
https://syzkaller.appspot.com/bug?id=148d2f1dfac64af52ffd27b661981a540724f803

The series is broken up into five patches:

1. Move a call to drm_is_current_master() out from a section locked by 
&dev->mode_config.mutex in drm_mode_getconnector(). This patch does not apply 
to stable.

2. Move a call to _drm_lease_held() out from the section locked by 
&dev->mode_config.idr_mutex in __drm_mode_object_find().

3. Implement a locked version of drm_is_current_master() function that's used 
within drm_auth.c.

4. Serialize drm_file.master by introducing a new lock that's held whenever the 
value of drm_file.master changes.

5. Identify areas in drm_lease.c where pointers to struct drm_master are 
dereferenced, and ensure that the master pointers are not freed during use.

Changes in v6 -> v7:
- Patch 2:
Modify code alignment as suggested by the intel-gfx CI.

Update commit message based on the changes to patch 5.

- Patch 4:
Add patch 4 to the series. This patch adds a new lock to serialize 
drm_file.master, in response to the lockdep splat by the intel-gfx CI.

- Patch 5:
Move kerneldoc comment about protecting drm_file.master with 
drm_device.master_mutex into patch 4.

Update drm_file_get_master to use the new drm_file.master_lock instead of 
drm_device.master_mutex, in response to the lockdep splat by the intel-gfx CI.


So there's another one now because master->leases is protected by the
mode_config.idr_mutex, and that's a bit awkward to untangle.

Also I'm really surprised that there was now lockdep through the atomic
code anywhere. The reason seems to be that somehow CI reboot first before
it managed to run any of the kms_atomic tests, and we can only hit this
when we go through the atomic kms ioctl, the legacy kms ioctl don't have
that specific issue.

Anyway I think this approach doesn't look too workable, and we need
something new.

But first things first: Are you still on board working on this? You
started with a simple patch to fix a UAF bug, now we're deep into
reworking tricky locking ... If you feel like you want out I'm totally
fine with that.



Hi Daniel,

Thanks for asking, but I'm committed to seeing this through :) In fact, 
I really appreciate all your guidance and patience as the simple patch 
evolved into the current state of things.



Anyway, I think we need to split drm_device->master_mutex up into two
parts:

- One part that protects the actual access/changes, which I think for
   simplicity we'll just leave as the current lock. That lock is a very
   inner lock, since for the drm_lease.c stuff it has to nest within
   mode_config.idr_mutex even.

- Now the issue with checking master status/leases/whatever as an
   innermost lock is that you can race, it's a classic time of check vs
   time of use race: By the time we actually use the thing we validate
   we'er allowed to use, we might now have access anymore. There's two
   reasons for that:

   * DROPMASTER ioctl could remove the master rights, which removes access
 rights also for all leases

   * REVOKE_LEASE ioctl can do the same but only for a specific lease

   This is the thing we're trying to protect against in fbcon code, but
   that's very spotty protection because all the ioctls by other users
   aren't actually protected against this.

   So I think for this we need some kind of big reader lock.

Now for the implementation, there's a few things:

- I think best option for this big reader lock would be to just use srcu.
   We only need to flush out all current readers when we drop master or
   revoke a lease, so synchronize_srcu is perfectly good enough for this
   purpose.

- The fbdev code would switch over to srcu in
   drm_master_internal_acquire() and drm_master_internal_release(). Ofc
   within drm_master_internal_acquire we'd still need to check master
   status with the normal master_mutex.

- While we revamp all this we should fix the ioctl checks in drm_ioctl.c.
   Just noticed that drm_ioctl_permit() could and should be unexported,
   last user was removed.

   Within drm_ioctl_kernel we'd then replace the check for
   drm_is_current_master with the drm_master_internal_acquire/release.

- This alone does nothing, we still need to make sure that dropmaster and
   revoke_lease ioctl flush out all other access before they return to
   userspace. We can't just call synchronize_srcu because due to the ioctl
   code in drm_ioctl_kernel we're in that sruc section, we'd need to add a
   DRM_MASTER_FLUSH ioctl flag which we'd check only when DRM_MASTER is
   set, and

  1   2   >