| .. | .. |
|---|
| 28 | 28 | * OTHER DEALINGS IN THE SOFTWARE. |
|---|
| 29 | 29 | */ |
|---|
| 30 | 30 | |
|---|
| 31 | | -#include <drm/drmP.h> |
|---|
| 31 | +#include <linux/slab.h> |
|---|
| 32 | + |
|---|
| 33 | +#include <drm/drm_auth.h> |
|---|
| 34 | +#include <drm/drm_drv.h> |
|---|
| 35 | +#include <drm/drm_file.h> |
|---|
| 36 | +#include <drm/drm_lease.h> |
|---|
| 37 | +#include <drm/drm_print.h> |
|---|
| 38 | + |
|---|
| 32 | 39 | #include "drm_internal.h" |
|---|
| 33 | 40 | #include "drm_legacy.h" |
|---|
| 34 | | -#include <drm/drm_lease.h> |
|---|
| 35 | 41 | |
|---|
| 36 | 42 | /** |
|---|
| 37 | 43 | * DOC: master and authentication |
|---|
| .. | .. |
|---|
| 103 | 109 | return NULL; |
|---|
| 104 | 110 | |
|---|
| 105 | 111 | kref_init(&master->refcount); |
|---|
| 106 | | - spin_lock_init(&master->lock.spinlock); |
|---|
| 107 | | - init_waitqueue_head(&master->lock.lock_queue); |
|---|
| 112 | + drm_master_legacy_init(master); |
|---|
| 108 | 113 | idr_init(&master->magic_map); |
|---|
| 109 | 114 | master->dev = dev; |
|---|
| 110 | 115 | |
|---|
| 111 | 116 | /* initialize the tree of output resource lessees */ |
|---|
| 112 | | - master->lessor = NULL; |
|---|
| 113 | | - master->lessee_id = 0; |
|---|
| 114 | 117 | INIT_LIST_HEAD(&master->lessees); |
|---|
| 115 | 118 | INIT_LIST_HEAD(&master->lessee_list); |
|---|
| 116 | 119 | idr_init(&master->leases); |
|---|
| .. | .. |
|---|
| 119 | 122 | return master; |
|---|
| 120 | 123 | } |
|---|
| 121 | 124 | |
|---|
| 122 | | -static int drm_set_master(struct drm_device *dev, struct drm_file *fpriv, |
|---|
| 123 | | - bool new_master) |
|---|
| 125 | +static void drm_set_master(struct drm_device *dev, struct drm_file *fpriv, |
|---|
| 126 | + bool new_master) |
|---|
| 124 | 127 | { |
|---|
| 125 | | - int ret = 0; |
|---|
| 126 | | - |
|---|
| 127 | 128 | dev->master = drm_master_get(fpriv->master); |
|---|
| 128 | | - if (dev->driver->master_set) { |
|---|
| 129 | | - ret = dev->driver->master_set(dev, fpriv, new_master); |
|---|
| 130 | | - if (unlikely(ret != 0)) { |
|---|
| 131 | | - drm_master_put(&dev->master); |
|---|
| 132 | | - } |
|---|
| 133 | | - } |
|---|
| 129 | + if (dev->driver->master_set) |
|---|
| 130 | + dev->driver->master_set(dev, fpriv, new_master); |
|---|
| 134 | 131 | |
|---|
| 135 | | - return ret; |
|---|
| 132 | + fpriv->was_master = true; |
|---|
| 136 | 133 | } |
|---|
| 137 | 134 | |
|---|
| 138 | 135 | static int drm_new_set_master(struct drm_device *dev, struct drm_file *fpriv) |
|---|
| 139 | 136 | { |
|---|
| 140 | 137 | struct drm_master *old_master; |
|---|
| 141 | | - int ret; |
|---|
| 142 | 138 | |
|---|
| 143 | 139 | lockdep_assert_held_once(&dev->master_mutex); |
|---|
| 144 | 140 | |
|---|
| .. | .. |
|---|
| 150 | 146 | return -ENOMEM; |
|---|
| 151 | 147 | } |
|---|
| 152 | 148 | |
|---|
| 153 | | - if (dev->driver->master_create) { |
|---|
| 154 | | - ret = dev->driver->master_create(dev, fpriv->master); |
|---|
| 155 | | - if (ret) |
|---|
| 156 | | - goto out_err; |
|---|
| 157 | | - } |
|---|
| 158 | 149 | fpriv->is_master = 1; |
|---|
| 159 | 150 | fpriv->authenticated = 1; |
|---|
| 160 | 151 | |
|---|
| 161 | | - ret = drm_set_master(dev, fpriv, true); |
|---|
| 162 | | - if (ret) |
|---|
| 163 | | - goto out_err; |
|---|
| 152 | + drm_set_master(dev, fpriv, true); |
|---|
| 164 | 153 | |
|---|
| 165 | 154 | if (old_master) |
|---|
| 166 | 155 | drm_master_put(&old_master); |
|---|
| 167 | 156 | |
|---|
| 168 | 157 | return 0; |
|---|
| 158 | +} |
|---|
| 169 | 159 | |
|---|
| 170 | | -out_err: |
|---|
| 171 | | - /* drop references and restore old master on failure */ |
|---|
| 172 | | - drm_master_put(&fpriv->master); |
|---|
| 173 | | - fpriv->master = old_master; |
|---|
| 174 | | - fpriv->is_master = 0; |
|---|
| 160 | +/* |
|---|
| 161 | + * In the olden days the SET/DROP_MASTER ioctls used to return EACCES when |
|---|
| 162 | + * CAP_SYS_ADMIN was not set. This was used to prevent rogue applications |
|---|
| 163 | + * from becoming master and/or failing to release it. |
|---|
| 164 | + * |
|---|
| 165 | + * At the same time, the first client (for a given VT) is _always_ master. |
|---|
| 166 | + * Thus in order for the ioctls to succeed, one had to _explicitly_ run the |
|---|
| 167 | + * application as root or flip the setuid bit. |
|---|
| 168 | + * |
|---|
| 169 | + * If the CAP_SYS_ADMIN was missing, no other client could become master... |
|---|
| 170 | + * EVER :-( Leading to a) the graphics session dying badly or b) a completely |
|---|
| 171 | + * locked session. |
|---|
| 172 | + * |
|---|
| 173 | + * |
|---|
| 174 | + * As some point systemd-logind was introduced to orchestrate and delegate |
|---|
| 175 | + * master as applicable. It does so by opening the fd and passing it to users |
|---|
| 176 | + * while in itself logind a) does the set/drop master per users' request and |
|---|
| 177 | + * b) * implicitly drops master on VT switch. |
|---|
| 178 | + * |
|---|
| 179 | + * Even though logind looks like the future, there are a few issues: |
|---|
| 180 | + * - some platforms don't have equivalent (Android, CrOS, some BSDs) so |
|---|
| 181 | + * root is required _solely_ for SET/DROP MASTER. |
|---|
| 182 | + * - applications may not be updated to use it, |
|---|
| 183 | + * - any client which fails to drop master* can DoS the application using |
|---|
| 184 | + * logind, to a varying degree. |
|---|
| 185 | + * |
|---|
| 186 | + * * Either due missing CAP_SYS_ADMIN or simply not calling DROP_MASTER. |
|---|
| 187 | + * |
|---|
| 188 | + * |
|---|
| 189 | + * Here we implement the next best thing: |
|---|
| 190 | + * - ensure the logind style of fd passing works unchanged, and |
|---|
| 191 | + * - allow a client to drop/set master, iff it is/was master at a given point |
|---|
| 192 | + * in time. |
|---|
| 193 | + * |
|---|
| 194 | + * Note: DROP_MASTER cannot be free for all, as an arbitrator user could: |
|---|
| 195 | + * - DoS/crash the arbitrator - details would be implementation specific |
|---|
| 196 | + * - open the node, become master implicitly and cause issues |
|---|
| 197 | + * |
|---|
| 198 | + * As a result this fixes the following when using root-less build w/o logind |
|---|
| 199 | + * - startx |
|---|
| 200 | + * - weston |
|---|
| 201 | + * - various compositors based on wlroots |
|---|
| 202 | + */ |
|---|
| 203 | +static int |
|---|
| 204 | +drm_master_check_perm(struct drm_device *dev, struct drm_file *file_priv) |
|---|
| 205 | +{ |
|---|
| 206 | + if (file_priv->pid == task_pid(current) && file_priv->was_master) |
|---|
| 207 | + return 0; |
|---|
| 175 | 208 | |
|---|
| 176 | | - return ret; |
|---|
| 209 | + if (!capable(CAP_SYS_ADMIN)) |
|---|
| 210 | + return -EACCES; |
|---|
| 211 | + |
|---|
| 212 | + return 0; |
|---|
| 177 | 213 | } |
|---|
| 178 | 214 | |
|---|
| 179 | 215 | int drm_setmaster_ioctl(struct drm_device *dev, void *data, |
|---|
| 180 | 216 | struct drm_file *file_priv) |
|---|
| 181 | 217 | { |
|---|
| 182 | | - int ret = 0; |
|---|
| 218 | + int ret; |
|---|
| 183 | 219 | |
|---|
| 184 | 220 | mutex_lock(&dev->master_mutex); |
|---|
| 221 | + |
|---|
| 222 | + ret = drm_master_check_perm(dev, file_priv); |
|---|
| 223 | + if (ret) |
|---|
| 224 | + goto out_unlock; |
|---|
| 225 | + |
|---|
| 185 | 226 | if (drm_is_current_master(file_priv)) |
|---|
| 186 | 227 | goto out_unlock; |
|---|
| 187 | 228 | |
|---|
| 188 | 229 | if (dev->master) { |
|---|
| 189 | | - ret = -EINVAL; |
|---|
| 230 | + ret = -EBUSY; |
|---|
| 190 | 231 | goto out_unlock; |
|---|
| 191 | 232 | } |
|---|
| 192 | 233 | |
|---|
| .. | .. |
|---|
| 206 | 247 | goto out_unlock; |
|---|
| 207 | 248 | } |
|---|
| 208 | 249 | |
|---|
| 209 | | - ret = drm_set_master(dev, file_priv, false); |
|---|
| 250 | + drm_set_master(dev, file_priv, false); |
|---|
| 210 | 251 | out_unlock: |
|---|
| 211 | 252 | mutex_unlock(&dev->master_mutex); |
|---|
| 212 | 253 | return ret; |
|---|
| .. | .. |
|---|
| 223 | 264 | int drm_dropmaster_ioctl(struct drm_device *dev, void *data, |
|---|
| 224 | 265 | struct drm_file *file_priv) |
|---|
| 225 | 266 | { |
|---|
| 226 | | - int ret = -EINVAL; |
|---|
| 267 | + int ret; |
|---|
| 227 | 268 | |
|---|
| 228 | 269 | mutex_lock(&dev->master_mutex); |
|---|
| 229 | | - if (!drm_is_current_master(file_priv)) |
|---|
| 270 | + |
|---|
| 271 | + ret = drm_master_check_perm(dev, file_priv); |
|---|
| 272 | + if (ret) |
|---|
| 230 | 273 | goto out_unlock; |
|---|
| 231 | 274 | |
|---|
| 232 | | - if (!dev->master) |
|---|
| 275 | + if (!drm_is_current_master(file_priv)) { |
|---|
| 276 | + ret = -EINVAL; |
|---|
| 233 | 277 | goto out_unlock; |
|---|
| 278 | + } |
|---|
| 279 | + |
|---|
| 280 | + if (!dev->master) { |
|---|
| 281 | + ret = -EINVAL; |
|---|
| 282 | + goto out_unlock; |
|---|
| 283 | + } |
|---|
| 234 | 284 | |
|---|
| 235 | 285 | if (file_priv->master->lessor != NULL) { |
|---|
| 236 | 286 | DRM_DEBUG_LEASE("Attempt to drop lessee %d as master\n", file_priv->master->lessee_id); |
|---|
| .. | .. |
|---|
| 238 | 288 | goto out_unlock; |
|---|
| 239 | 289 | } |
|---|
| 240 | 290 | |
|---|
| 241 | | - ret = 0; |
|---|
| 242 | 291 | drm_drop_master(dev, file_priv); |
|---|
| 243 | 292 | out_unlock: |
|---|
| 244 | 293 | mutex_unlock(&dev->master_mutex); |
|---|
| .. | .. |
|---|
| 275 | 324 | if (!drm_is_current_master(file_priv)) |
|---|
| 276 | 325 | goto out; |
|---|
| 277 | 326 | |
|---|
| 278 | | - if (drm_core_check_feature(dev, DRIVER_LEGACY)) { |
|---|
| 279 | | - /* |
|---|
| 280 | | - * Since the master is disappearing, so is the |
|---|
| 281 | | - * possibility to lock. |
|---|
| 282 | | - */ |
|---|
| 283 | | - mutex_lock(&dev->struct_mutex); |
|---|
| 284 | | - if (master->lock.hw_lock) { |
|---|
| 285 | | - if (dev->sigdata.lock == master->lock.hw_lock) |
|---|
| 286 | | - dev->sigdata.lock = NULL; |
|---|
| 287 | | - master->lock.hw_lock = NULL; |
|---|
| 288 | | - master->lock.file_priv = NULL; |
|---|
| 289 | | - wake_up_interruptible_all(&master->lock.lock_queue); |
|---|
| 290 | | - } |
|---|
| 291 | | - mutex_unlock(&dev->struct_mutex); |
|---|
| 292 | | - } |
|---|
| 327 | + drm_legacy_lock_master_cleanup(dev, master); |
|---|
| 293 | 328 | |
|---|
| 294 | 329 | if (dev->master == file_priv->master) |
|---|
| 295 | 330 | drm_drop_master(dev, file_priv); |
|---|
| .. | .. |
|---|
| 344 | 379 | if (drm_core_check_feature(dev, DRIVER_MODESET)) |
|---|
| 345 | 380 | drm_lease_destroy(master); |
|---|
| 346 | 381 | |
|---|
| 347 | | - if (dev->driver->master_destroy) |
|---|
| 348 | | - dev->driver->master_destroy(dev, master); |
|---|
| 349 | | - |
|---|
| 350 | 382 | drm_legacy_master_rmmaps(dev, master); |
|---|
| 351 | 383 | |
|---|
| 352 | 384 | idr_destroy(&master->magic_map); |
|---|
| .. | .. |
|---|
| 369 | 401 | *master = NULL; |
|---|
| 370 | 402 | } |
|---|
| 371 | 403 | EXPORT_SYMBOL(drm_master_put); |
|---|
| 404 | + |
|---|
| 405 | +/* Used by drm_client and drm_fb_helper */ |
|---|
| 406 | +bool drm_master_internal_acquire(struct drm_device *dev) |
|---|
| 407 | +{ |
|---|
| 408 | + mutex_lock(&dev->master_mutex); |
|---|
| 409 | + if (dev->master) { |
|---|
| 410 | + mutex_unlock(&dev->master_mutex); |
|---|
| 411 | + return false; |
|---|
| 412 | + } |
|---|
| 413 | + |
|---|
| 414 | + return true; |
|---|
| 415 | +} |
|---|
| 416 | +EXPORT_SYMBOL(drm_master_internal_acquire); |
|---|
| 417 | + |
|---|
| 418 | +/* Used by drm_client and drm_fb_helper */ |
|---|
| 419 | +void drm_master_internal_release(struct drm_device *dev) |
|---|
| 420 | +{ |
|---|
| 421 | + mutex_unlock(&dev->master_mutex); |
|---|
| 422 | +} |
|---|
| 423 | +EXPORT_SYMBOL(drm_master_internal_release); |
|---|