Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm: rework SET_MASTER and DROP_MASTER perm handling

This commit reworks the permission handling of the two ioctls. In
particular it enforced the CAP_SYS_ADMIN check only, if:
- we're issuing the ioctl from process other than the one which opened
the node, and
- we are, or were master in the past

This ensures that we:
- do not regress the systemd-logind style of DRM_MASTER arbitrator
- allow applications which do not use systemd-logind to drop their
master capabilities (and regain them at later point) ... w/o running as
root.

See the comment above drm_master_check_perm() for more details.

v1:
- Tweak wording, fixup all checks, add igt test

v2:
- Add a few more comments, grammar nitpicks.

Cc: Adam Jackson <ajax@redhat.com>
Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
Cc: Pekka Paalanen <ppaalanen@gmail.com>
Testcase: igt/core_setmaster/master-drop-set-user
Signed-off-by: Emil Velikov <emil.velikov@collabora.com>
Reviewed-by: Adam Jackson <ajax@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20200319172930.230583-1-emil.l.velikov@gmail.com

authored by

Emil Velikov and committed by
Emil Velikov
45bc3d26 c7ccc1b7

+80 -2
+67
drivers/gpu/drm/drm_auth.c
··· 135 135 } 136 136 } 137 137 138 + fpriv->was_master = (ret == 0); 138 139 return ret; 139 140 } 140 141 ··· 175 174 return ret; 176 175 } 177 176 177 + /* 178 + * In the olden days the SET/DROP_MASTER ioctls used to return EACCES when 179 + * CAP_SYS_ADMIN was not set. This was used to prevent rogue applications 180 + * from becoming master and/or failing to release it. 181 + * 182 + * At the same time, the first client (for a given VT) is _always_ master. 183 + * Thus in order for the ioctls to succeed, one had to _explicitly_ run the 184 + * application as root or flip the setuid bit. 185 + * 186 + * If the CAP_SYS_ADMIN was missing, no other client could become master... 187 + * EVER :-( Leading to a) the graphics session dying badly or b) a completely 188 + * locked session. 189 + * 190 + * 191 + * As some point systemd-logind was introduced to orchestrate and delegate 192 + * master as applicable. It does so by opening the fd and passing it to users 193 + * while in itself logind a) does the set/drop master per users' request and 194 + * b) * implicitly drops master on VT switch. 195 + * 196 + * Even though logind looks like the future, there are a few issues: 197 + * - some platforms don't have equivalent (Android, CrOS, some BSDs) so 198 + * root is required _solely_ for SET/DROP MASTER. 199 + * - applications may not be updated to use it, 200 + * - any client which fails to drop master* can DoS the application using 201 + * logind, to a varying degree. 202 + * 203 + * * Either due missing CAP_SYS_ADMIN or simply not calling DROP_MASTER. 204 + * 205 + * 206 + * Here we implement the next best thing: 207 + * - ensure the logind style of fd passing works unchanged, and 208 + * - allow a client to drop/set master, iff it is/was master at a given point 209 + * in time. 210 + * 211 + * Note: DROP_MASTER cannot be free for all, as an arbitrator user could: 212 + * - DoS/crash the arbitrator - details would be implementation specific 213 + * - open the node, become master implicitly and cause issues 214 + * 215 + * As a result this fixes the following when using root-less build w/o logind 216 + * - startx 217 + * - weston 218 + * - various compositors based on wlroots 219 + */ 220 + static int 221 + drm_master_check_perm(struct drm_device *dev, struct drm_file *file_priv) 222 + { 223 + if (file_priv->pid == task_pid(current) && file_priv->was_master) 224 + return 0; 225 + 226 + if (!capable(CAP_SYS_ADMIN)) 227 + return -EACCES; 228 + 229 + return 0; 230 + } 231 + 178 232 int drm_setmaster_ioctl(struct drm_device *dev, void *data, 179 233 struct drm_file *file_priv) 180 234 { 181 235 int ret = 0; 182 236 183 237 mutex_lock(&dev->master_mutex); 238 + 239 + ret = drm_master_check_perm(dev, file_priv); 240 + if (ret) 241 + goto out_unlock; 242 + 184 243 if (drm_is_current_master(file_priv)) 185 244 goto out_unlock; 186 245 ··· 285 224 int ret = -EINVAL; 286 225 287 226 mutex_lock(&dev->master_mutex); 227 + 228 + ret = drm_master_check_perm(dev, file_priv); 229 + if (ret) 230 + goto out_unlock; 231 + 232 + ret = -EINVAL; 288 233 if (!drm_is_current_master(file_priv)) 289 234 goto out_unlock; 290 235
+2 -2
drivers/gpu/drm/drm_ioctl.c
··· 599 599 DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_SET_SAREA_CTX, drm_legacy_setsareactx, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), 600 600 DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_GET_SAREA_CTX, drm_legacy_getsareactx, DRM_AUTH), 601 601 602 - DRM_IOCTL_DEF(DRM_IOCTL_SET_MASTER, drm_setmaster_ioctl, DRM_ROOT_ONLY), 603 - DRM_IOCTL_DEF(DRM_IOCTL_DROP_MASTER, drm_dropmaster_ioctl, DRM_ROOT_ONLY), 602 + DRM_IOCTL_DEF(DRM_IOCTL_SET_MASTER, drm_setmaster_ioctl, 0), 603 + DRM_IOCTL_DEF(DRM_IOCTL_DROP_MASTER, drm_dropmaster_ioctl, 0), 604 604 605 605 DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_ADD_CTX, drm_legacy_addctx, DRM_AUTH|DRM_ROOT_ONLY), 606 606 DRM_LEGACY_IOCTL_DEF(DRM_IOCTL_RM_CTX, drm_legacy_rmctx, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
+11
include/drm/drm_file.h
··· 202 202 bool writeback_connectors; 203 203 204 204 /** 205 + * @was_master: 206 + * 207 + * This client has or had, master capability. Protected by struct 208 + * &drm_device.master_mutex. 209 + * 210 + * This is used to ensure that CAP_SYS_ADMIN is not enforced, if the 211 + * client is or was master in the past. 212 + */ 213 + bool was_master; 214 + 215 + /** 205 216 * @is_master: 206 217 * 207 218 * This client is the creator of @master. Protected by struct