Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * Created: Tue Feb 2 08:37:54 1999 by faith@valinux.com | |
3 | * | |
4 | * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas. | |
5 | * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California. | |
6 | * All Rights Reserved. | |
7 | * | |
32e7b94a DH |
8 | * Author Rickard E. (Rik) Faith <faith@valinux.com> |
9 | * Author Gareth Hughes <gareth@valinux.com> | |
10 | * | |
1da177e4 LT |
11 | * Permission is hereby granted, free of charge, to any person obtaining a |
12 | * copy of this software and associated documentation files (the "Software"), | |
13 | * to deal in the Software without restriction, including without limitation | |
14 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
15 | * and/or sell copies of the Software, and to permit persons to whom the | |
16 | * Software is furnished to do so, subject to the following conditions: | |
17 | * | |
18 | * The above copyright notice and this permission notice (including the next | |
19 | * paragraph) shall be included in all copies or substantial portions of the | |
20 | * Software. | |
21 | * | |
22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
25 | * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
26 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
27 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
28 | * OTHER DEALINGS IN THE SOFTWARE. | |
29 | */ | |
30 | ||
0500c04e SR |
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 | ||
67d0ec4e | 39 | #include "drm_internal.h" |
6548f4e7 | 40 | #include "drm_legacy.h" |
1da177e4 | 41 | |
1da177e4 | 42 | /** |
3b96a0b1 | 43 | * DOC: master and authentication |
1da177e4 | 44 | * |
ea0dd85a DV |
45 | * &struct drm_master is used to track groups of clients with open |
46 | * primary/legacy device nodes. For every &struct drm_file which has had at | |
3b96a0b1 DV |
47 | * least once successfully became the device master (either through the |
48 | * SET_MASTER IOCTL, or implicitly through opening the primary device node when | |
49 | * no one else is the current master that time) there exists one &drm_master. | |
ef40cbf9 DV |
50 | * This is noted in &drm_file.is_master. All other clients have just a pointer |
51 | * to the &drm_master they are associated with. | |
1da177e4 | 52 | * |
3b96a0b1 DV |
53 | * In addition only one &drm_master can be the current master for a &drm_device. |
54 | * It can be switched through the DROP_MASTER and SET_MASTER IOCTL, or | |
0ae865ef | 55 | * implicitly through closing/opening the primary device node. See also |
3b96a0b1 DV |
56 | * drm_is_current_master(). |
57 | * | |
58 | * Clients can authenticate against the current master (if it matches their own) | |
59 | * using the GETMAGIC and AUTHMAGIC IOCTLs. Together with exchanging masters, | |
60 | * this allows controlled access to the device for an entire group of mutually | |
61 | * trusted clients. | |
1da177e4 | 62 | */ |
3b96a0b1 | 63 | |
1f7ef07c DCZX |
64 | static bool drm_is_current_master_locked(struct drm_file *fpriv) |
65 | { | |
649839d7 DCZX |
66 | lockdep_assert_once(lockdep_is_held(&fpriv->master_lookup_lock) || |
67 | lockdep_is_held(&fpriv->minor->dev->master_mutex)); | |
68 | ||
1f7ef07c DCZX |
69 | return fpriv->is_master && drm_lease_owner(fpriv->master) == fpriv->minor->dev->master; |
70 | } | |
71 | ||
72 | /** | |
73 | * drm_is_current_master - checks whether @priv is the current master | |
74 | * @fpriv: DRM file private | |
75 | * | |
76 | * Checks whether @fpriv is current master on its device. This decides whether a | |
77 | * client is allowed to run DRM_MASTER IOCTLs. | |
78 | * | |
79 | * Most of the modern IOCTL which require DRM_MASTER are for kernel modesetting | |
80 | * - the current master is assumed to own the non-shareable display hardware. | |
81 | */ | |
82 | bool drm_is_current_master(struct drm_file *fpriv) | |
83 | { | |
84 | bool ret; | |
85 | ||
28be2405 | 86 | spin_lock(&fpriv->master_lookup_lock); |
1f7ef07c | 87 | ret = drm_is_current_master_locked(fpriv); |
28be2405 | 88 | spin_unlock(&fpriv->master_lookup_lock); |
1f7ef07c DCZX |
89 | |
90 | return ret; | |
91 | } | |
92 | EXPORT_SYMBOL(drm_is_current_master); | |
93 | ||
c153f45f | 94 | int drm_getmagic(struct drm_device *dev, void *data, struct drm_file *file_priv) |
1da177e4 | 95 | { |
c153f45f | 96 | struct drm_auth *auth = data; |
32e7b94a | 97 | int ret = 0; |
1da177e4 | 98 | |
d2b34ee6 | 99 | mutex_lock(&dev->master_mutex); |
32e7b94a DH |
100 | if (!file_priv->magic) { |
101 | ret = idr_alloc(&file_priv->master->magic_map, file_priv, | |
102 | 1, 0, GFP_KERNEL); | |
103 | if (ret >= 0) | |
104 | file_priv->magic = ret; | |
1da177e4 | 105 | } |
32e7b94a | 106 | auth->magic = file_priv->magic; |
d2b34ee6 | 107 | mutex_unlock(&dev->master_mutex); |
1da177e4 | 108 | |
6e22dc35 | 109 | drm_dbg_core(dev, "%u\n", auth->magic); |
c153f45f | 110 | |
32e7b94a | 111 | return ret < 0 ? ret : 0; |
1da177e4 LT |
112 | } |
113 | ||
c153f45f EA |
114 | int drm_authmagic(struct drm_device *dev, void *data, |
115 | struct drm_file *file_priv) | |
1da177e4 | 116 | { |
c153f45f | 117 | struct drm_auth *auth = data; |
84b1fd10 | 118 | struct drm_file *file; |
1da177e4 | 119 | |
6e22dc35 | 120 | drm_dbg_core(dev, "%u\n", auth->magic); |
32e7b94a | 121 | |
d2b34ee6 | 122 | mutex_lock(&dev->master_mutex); |
32e7b94a DH |
123 | file = idr_find(&file_priv->master->magic_map, auth->magic); |
124 | if (file) { | |
1da177e4 | 125 | file->authenticated = 1; |
32e7b94a | 126 | idr_replace(&file_priv->master->magic_map, NULL, auth->magic); |
1da177e4 | 127 | } |
d2b34ee6 | 128 | mutex_unlock(&dev->master_mutex); |
32e7b94a DH |
129 | |
130 | return file ? 0 : -EINVAL; | |
1da177e4 | 131 | } |
6548f4e7 | 132 | |
2ed077e4 | 133 | struct drm_master *drm_master_create(struct drm_device *dev) |
6548f4e7 DV |
134 | { |
135 | struct drm_master *master; | |
136 | ||
137 | master = kzalloc(sizeof(*master), GFP_KERNEL); | |
138 | if (!master) | |
139 | return NULL; | |
140 | ||
141 | kref_init(&master->refcount); | |
ee22f763 | 142 | drm_master_legacy_init(master); |
6548f4e7 DV |
143 | idr_init(&master->magic_map); |
144 | master->dev = dev; | |
145 | ||
2ed077e4 | 146 | /* initialize the tree of output resource lessees */ |
2ed077e4 KP |
147 | INIT_LIST_HEAD(&master->lessees); |
148 | INIT_LIST_HEAD(&master->lessee_list); | |
149 | idr_init(&master->leases); | |
150 | idr_init(&master->lessee_idr); | |
151 | ||
6548f4e7 DV |
152 | return master; |
153 | } | |
154 | ||
907f5320 EV |
155 | static void drm_set_master(struct drm_device *dev, struct drm_file *fpriv, |
156 | bool new_master) | |
d6ed682e | 157 | { |
d6ed682e | 158 | dev->master = drm_master_get(fpriv->master); |
907f5320 EV |
159 | if (dev->driver->master_set) |
160 | dev->driver->master_set(dev, fpriv, new_master); | |
d6ed682e | 161 | |
907f5320 | 162 | fpriv->was_master = true; |
d6ed682e DV |
163 | } |
164 | ||
2cbae7e6 | 165 | static int drm_new_set_master(struct drm_device *dev, struct drm_file *fpriv) |
6548f4e7 DV |
166 | { |
167 | struct drm_master *old_master; | |
0b0860a3 | 168 | struct drm_master *new_master; |
6548f4e7 DV |
169 | |
170 | lockdep_assert_held_once(&dev->master_mutex); | |
171 | ||
23a336b3 | 172 | WARN_ON(fpriv->is_master); |
6548f4e7 | 173 | old_master = fpriv->master; |
0b0860a3 DCZX |
174 | new_master = drm_master_create(dev); |
175 | if (!new_master) | |
d6ed682e | 176 | return -ENOMEM; |
0b0860a3 DCZX |
177 | spin_lock(&fpriv->master_lookup_lock); |
178 | fpriv->master = new_master; | |
179 | spin_unlock(&fpriv->master_lookup_lock); | |
6548f4e7 | 180 | |
0aae5920 | 181 | fpriv->is_master = 1; |
6548f4e7 | 182 | fpriv->authenticated = 1; |
d6ed682e | 183 | |
907f5320 | 184 | drm_set_master(dev, fpriv, true); |
d6ed682e | 185 | |
6548f4e7 DV |
186 | if (old_master) |
187 | drm_master_put(&old_master); | |
188 | ||
189 | return 0; | |
6548f4e7 DV |
190 | } |
191 | ||
45bc3d26 EV |
192 | /* |
193 | * In the olden days the SET/DROP_MASTER ioctls used to return EACCES when | |
194 | * CAP_SYS_ADMIN was not set. This was used to prevent rogue applications | |
195 | * from becoming master and/or failing to release it. | |
196 | * | |
197 | * At the same time, the first client (for a given VT) is _always_ master. | |
198 | * Thus in order for the ioctls to succeed, one had to _explicitly_ run the | |
199 | * application as root or flip the setuid bit. | |
200 | * | |
201 | * If the CAP_SYS_ADMIN was missing, no other client could become master... | |
202 | * EVER :-( Leading to a) the graphics session dying badly or b) a completely | |
203 | * locked session. | |
204 | * | |
205 | * | |
206 | * As some point systemd-logind was introduced to orchestrate and delegate | |
207 | * master as applicable. It does so by opening the fd and passing it to users | |
208 | * while in itself logind a) does the set/drop master per users' request and | |
209 | * b) * implicitly drops master on VT switch. | |
210 | * | |
211 | * Even though logind looks like the future, there are a few issues: | |
212 | * - some platforms don't have equivalent (Android, CrOS, some BSDs) so | |
213 | * root is required _solely_ for SET/DROP MASTER. | |
214 | * - applications may not be updated to use it, | |
215 | * - any client which fails to drop master* can DoS the application using | |
216 | * logind, to a varying degree. | |
217 | * | |
218 | * * Either due missing CAP_SYS_ADMIN or simply not calling DROP_MASTER. | |
219 | * | |
220 | * | |
221 | * Here we implement the next best thing: | |
222 | * - ensure the logind style of fd passing works unchanged, and | |
223 | * - allow a client to drop/set master, iff it is/was master at a given point | |
224 | * in time. | |
225 | * | |
226 | * Note: DROP_MASTER cannot be free for all, as an arbitrator user could: | |
227 | * - DoS/crash the arbitrator - details would be implementation specific | |
228 | * - open the node, become master implicitly and cause issues | |
229 | * | |
230 | * As a result this fixes the following when using root-less build w/o logind | |
231 | * - startx | |
232 | * - weston | |
233 | * - various compositors based on wlroots | |
234 | */ | |
235 | static int | |
236 | drm_master_check_perm(struct drm_device *dev, struct drm_file *file_priv) | |
237 | { | |
238 | if (file_priv->pid == task_pid(current) && file_priv->was_master) | |
239 | return 0; | |
240 | ||
241 | if (!capable(CAP_SYS_ADMIN)) | |
242 | return -EACCES; | |
243 | ||
244 | return 0; | |
245 | } | |
246 | ||
6548f4e7 DV |
247 | int drm_setmaster_ioctl(struct drm_device *dev, void *data, |
248 | struct drm_file *file_priv) | |
249 | { | |
264ddd07 | 250 | int ret; |
6548f4e7 DV |
251 | |
252 | mutex_lock(&dev->master_mutex); | |
45bc3d26 EV |
253 | |
254 | ret = drm_master_check_perm(dev, file_priv); | |
255 | if (ret) | |
256 | goto out_unlock; | |
257 | ||
1f7ef07c | 258 | if (drm_is_current_master_locked(file_priv)) |
6548f4e7 DV |
259 | goto out_unlock; |
260 | ||
95c081c1 | 261 | if (dev->master) { |
2bf99b22 | 262 | ret = -EBUSY; |
6548f4e7 DV |
263 | goto out_unlock; |
264 | } | |
265 | ||
266 | if (!file_priv->master) { | |
267 | ret = -EINVAL; | |
268 | goto out_unlock; | |
269 | } | |
270 | ||
0aae5920 | 271 | if (!file_priv->is_master) { |
6548f4e7 DV |
272 | ret = drm_new_set_master(dev, file_priv); |
273 | goto out_unlock; | |
274 | } | |
275 | ||
2ed077e4 | 276 | if (file_priv->master->lessor != NULL) { |
6e22dc35 CS |
277 | drm_dbg_lease(dev, |
278 | "Attempt to set lessee %d as master\n", | |
279 | file_priv->master->lessee_id); | |
2ed077e4 KP |
280 | ret = -EINVAL; |
281 | goto out_unlock; | |
282 | } | |
283 | ||
907f5320 | 284 | drm_set_master(dev, file_priv, false); |
6548f4e7 DV |
285 | out_unlock: |
286 | mutex_unlock(&dev->master_mutex); | |
287 | return ret; | |
288 | } | |
289 | ||
d6ed682e DV |
290 | static void drm_drop_master(struct drm_device *dev, |
291 | struct drm_file *fpriv) | |
292 | { | |
293 | if (dev->driver->master_drop) | |
294 | dev->driver->master_drop(dev, fpriv); | |
295 | drm_master_put(&dev->master); | |
d6ed682e DV |
296 | } |
297 | ||
6548f4e7 DV |
298 | int drm_dropmaster_ioctl(struct drm_device *dev, void *data, |
299 | struct drm_file *file_priv) | |
300 | { | |
2217d3bc | 301 | int ret; |
6548f4e7 DV |
302 | |
303 | mutex_lock(&dev->master_mutex); | |
45bc3d26 EV |
304 | |
305 | ret = drm_master_check_perm(dev, file_priv); | |
306 | if (ret) | |
307 | goto out_unlock; | |
308 | ||
1f7ef07c | 309 | if (!drm_is_current_master_locked(file_priv)) { |
264ddd07 | 310 | ret = -EINVAL; |
6548f4e7 | 311 | goto out_unlock; |
264ddd07 | 312 | } |
6548f4e7 | 313 | |
264ddd07 EV |
314 | if (!dev->master) { |
315 | ret = -EINVAL; | |
6548f4e7 | 316 | goto out_unlock; |
264ddd07 | 317 | } |
6548f4e7 | 318 | |
761e05a7 | 319 | if (file_priv->master->lessor != NULL) { |
6e22dc35 CS |
320 | drm_dbg_lease(dev, |
321 | "Attempt to drop lessee %d as master\n", | |
322 | file_priv->master->lessee_id); | |
761e05a7 KP |
323 | ret = -EINVAL; |
324 | goto out_unlock; | |
325 | } | |
326 | ||
d6ed682e | 327 | drm_drop_master(dev, file_priv); |
6548f4e7 DV |
328 | out_unlock: |
329 | mutex_unlock(&dev->master_mutex); | |
330 | return ret; | |
331 | } | |
332 | ||
2cbae7e6 DV |
333 | int drm_master_open(struct drm_file *file_priv) |
334 | { | |
335 | struct drm_device *dev = file_priv->minor->dev; | |
336 | int ret = 0; | |
337 | ||
338 | /* if there is no current master make this fd it, but do not create | |
d00e3d9e BMC |
339 | * any master object for render clients |
340 | */ | |
2cbae7e6 | 341 | mutex_lock(&dev->master_mutex); |
0b0860a3 | 342 | if (!dev->master) { |
2cbae7e6 | 343 | ret = drm_new_set_master(dev, file_priv); |
0b0860a3 DCZX |
344 | } else { |
345 | spin_lock(&file_priv->master_lookup_lock); | |
95c081c1 | 346 | file_priv->master = drm_master_get(dev->master); |
0b0860a3 DCZX |
347 | spin_unlock(&file_priv->master_lookup_lock); |
348 | } | |
2cbae7e6 DV |
349 | mutex_unlock(&dev->master_mutex); |
350 | ||
351 | return ret; | |
352 | } | |
353 | ||
14d71ebd DV |
354 | void drm_master_release(struct drm_file *file_priv) |
355 | { | |
356 | struct drm_device *dev = file_priv->minor->dev; | |
c336a5ee | 357 | struct drm_master *master; |
14d71ebd | 358 | |
d2b34ee6 | 359 | mutex_lock(&dev->master_mutex); |
c336a5ee | 360 | master = file_priv->master; |
a77316bf DV |
361 | if (file_priv->magic) |
362 | idr_remove(&file_priv->master->magic_map, file_priv->magic); | |
a77316bf | 363 | |
1f7ef07c | 364 | if (!drm_is_current_master_locked(file_priv)) |
0de4cc99 | 365 | goto out; |
14d71ebd | 366 | |
058ca50c | 367 | drm_legacy_lock_master_cleanup(dev, master); |
14d71ebd | 368 | |
d6ed682e DV |
369 | if (dev->master == file_priv->master) |
370 | drm_drop_master(dev, file_priv); | |
0de4cc99 | 371 | out: |
2ed077e4 KP |
372 | if (drm_core_check_feature(dev, DRIVER_MODESET) && file_priv->is_master) { |
373 | /* Revoke any leases held by this or lessees, but only if | |
374 | * this is the "real" master | |
375 | */ | |
376 | drm_lease_revoke(master); | |
377 | } | |
378 | ||
14d71ebd DV |
379 | /* drop the master reference held by the file priv */ |
380 | if (file_priv->master) | |
381 | drm_master_put(&file_priv->master); | |
14d71ebd DV |
382 | mutex_unlock(&dev->master_mutex); |
383 | } | |
384 | ||
3b96a0b1 DV |
385 | /** |
386 | * drm_master_get - reference a master pointer | |
ea0dd85a | 387 | * @master: &struct drm_master |
3b96a0b1 DV |
388 | * |
389 | * Increments the reference count of @master and returns a pointer to @master. | |
390 | */ | |
6548f4e7 DV |
391 | struct drm_master *drm_master_get(struct drm_master *master) |
392 | { | |
393 | kref_get(&master->refcount); | |
394 | return master; | |
395 | } | |
396 | EXPORT_SYMBOL(drm_master_get); | |
397 | ||
56f0729a DCZX |
398 | /** |
399 | * drm_file_get_master - reference &drm_file.master of @file_priv | |
400 | * @file_priv: DRM file private | |
401 | * | |
402 | * Increments the reference count of @file_priv's &drm_file.master and returns | |
403 | * the &drm_file.master. If @file_priv has no &drm_file.master, returns NULL. | |
404 | * | |
405 | * Master pointers returned from this function should be unreferenced using | |
406 | * drm_master_put(). | |
407 | */ | |
408 | struct drm_master *drm_file_get_master(struct drm_file *file_priv) | |
409 | { | |
410 | struct drm_master *master = NULL; | |
411 | ||
412 | spin_lock(&file_priv->master_lookup_lock); | |
413 | if (!file_priv->master) | |
414 | goto unlock; | |
415 | master = drm_master_get(file_priv->master); | |
416 | ||
417 | unlock: | |
418 | spin_unlock(&file_priv->master_lookup_lock); | |
419 | return master; | |
420 | } | |
421 | EXPORT_SYMBOL(drm_file_get_master); | |
422 | ||
6548f4e7 DV |
423 | static void drm_master_destroy(struct kref *kref) |
424 | { | |
425 | struct drm_master *master = container_of(kref, struct drm_master, refcount); | |
426 | struct drm_device *dev = master->dev; | |
427 | ||
2ed077e4 KP |
428 | if (drm_core_check_feature(dev, DRIVER_MODESET)) |
429 | drm_lease_destroy(master); | |
430 | ||
6548f4e7 DV |
431 | drm_legacy_master_rmmaps(dev, master); |
432 | ||
433 | idr_destroy(&master->magic_map); | |
2ed077e4 KP |
434 | idr_destroy(&master->leases); |
435 | idr_destroy(&master->lessee_idr); | |
436 | ||
6548f4e7 DV |
437 | kfree(master->unique); |
438 | kfree(master); | |
439 | } | |
440 | ||
3b96a0b1 DV |
441 | /** |
442 | * drm_master_put - unreference and clear a master pointer | |
ea0dd85a | 443 | * @master: pointer to a pointer of &struct drm_master |
3b96a0b1 DV |
444 | * |
445 | * This decrements the &drm_master behind @master and sets it to NULL. | |
446 | */ | |
6548f4e7 DV |
447 | void drm_master_put(struct drm_master **master) |
448 | { | |
449 | kref_put(&(*master)->refcount, drm_master_destroy); | |
450 | *master = NULL; | |
451 | } | |
452 | EXPORT_SYMBOL(drm_master_put); | |
03a9606e NT |
453 | |
454 | /* Used by drm_client and drm_fb_helper */ | |
455 | bool drm_master_internal_acquire(struct drm_device *dev) | |
456 | { | |
457 | mutex_lock(&dev->master_mutex); | |
458 | if (dev->master) { | |
459 | mutex_unlock(&dev->master_mutex); | |
460 | return false; | |
461 | } | |
462 | ||
463 | return true; | |
464 | } | |
465 | EXPORT_SYMBOL(drm_master_internal_acquire); | |
466 | ||
467 | /* Used by drm_client and drm_fb_helper */ | |
468 | void drm_master_internal_release(struct drm_device *dev) | |
469 | { | |
470 | mutex_unlock(&dev->master_mutex); | |
471 | } | |
472 | EXPORT_SYMBOL(drm_master_internal_release); |