vdev_disk: use calling cred to check for device access

bdev_file_open_by_path() does not do any kind of credential check on the
given device path, so we need to do our own. We temporarily swap in the
passed in credential as the task credential, then call kern_path() and
inode_permission(), which together will ensure the credential can both
see and access the given path.

For the reopening case, we use the kernel credential. The idea here is
that since the device was already open, we shouldn't fail to reopen just
because the calling user can't see it, which would prevent device
removal, offline, online, etc.

Include some light reorganising in the error paths, since we might not
always have a device handle to carry the current error.

Sponsored-by: TrueNAS
Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Alexander Motin <[email protected]>
Signed-off-by: Rob Norris <[email protected]>
Closes #18960
This commit is contained in:
Rob Norris authored and Brian Behlendorf committed 2026-08-20 16:20:45 -07:00
1 parent d8ef49fca3
commit 88261a83ae
1 file changed
+68 -17
+68 -17
View File
@@ -16,6 +16,7 @@
* LLNL-CODE-403049.
* Copyright (c) 2012, 2019 by Delphix. All rights reserved.
* Copyright (c) 2023, 2024, 2025, Klara, Inc.
* Copyright (c) 2026, TrueNAS.
*/
#include <sys/zfs_context.h>
@@ -279,15 +280,29 @@ vdev_blkdev_put(zfs_bdev_handle_t *bdh, spa_mode_t smode, void *holder)
#endif
}
static int
vdev_path_backing_permission(struct path *path, int mask)
{
#if defined(HAVE_IDMAP_MNTIDMAP)
return (inode_permission(mnt_idmap(path->mnt),
d_backing_inode(path->dentry), mask));
#elif defined(HAVE_IDMAP_USERNS)
return (inode_permission(mnt_user_ns(path->mnt),
d_backing_inode(path->dentry), mask));
#else
return (inode_permission(d_backing_inode(path->dentry), mask));
#endif
}
static int
vdev_disk_open(vdev_t *v, uint64_t *psize, uint64_t *max_psize,
uint64_t *logical_ashift, uint64_t *physical_ashift, cred_t *cr)
{
(void) cr;
zfs_bdev_handle_t *bdh;
spa_mode_t smode = spa_mode(v->vdev_spa);
hrtime_t timeout = MSEC2NSEC(zfs_vdev_open_timeout_ms);
vdev_disk_t *vd;
const cred_t *oldcr = NULL;
/* Must have a pathname and it must be absolute. */
if (v->vdev_path == NULL || v->vdev_path[0] != '/') {
@@ -309,6 +324,13 @@ vdev_disk_open(vdev_t *v, uint64_t *psize, uint64_t *max_psize,
char disk_name[BDEVNAME_SIZE + 6] = "/dev/";
boolean_t reread_part = B_FALSE;
/*
* Reopening an already-open device, so the caller credential
* is irrelevant - we had access to it before, we can assume
* we still do.
*/
oldcr = override_creds(kcred);
rw_enter(&vd->vd_lock, RW_WRITER);
bdh = vd->vd_bdh;
vd->vd_bdh = NULL;
@@ -357,6 +379,9 @@ vdev_disk_open(vdev_t *v, uint64_t *psize, uint64_t *max_psize,
rw_init(&vd->vd_lock, NULL, RW_DEFAULT, NULL);
rw_enter(&vd->vd_lock, RW_WRITER);
/* Restrict device access below to caller's permissions. */
oldcr = override_creds(cr);
}
/*
@@ -387,12 +412,37 @@ vdev_disk_open(vdev_t *v, uint64_t *psize, uint64_t *max_psize,
* logic in zvol_open(). Extend the timeout and retry the open
* subsequent attempts are expected to eventually succeed.
*/
hrtime_t start = gethrtime();
bdh = BDH_ERR_PTR(-ENXIO);
while (BDH_IS_ERR(bdh) && ((gethrtime() - start) < timeout)) {
bdh = vdev_blkdev_get_by_path(v->vdev_path, smode,
zfs_vdev_holder);
if (unlikely(BDH_PTR_ERR(bdh) == -ENOENT)) {
int err = ENXIO;
while (err != 0 && ((gethrtime() - start) < timeout)) {
/*
* Ensure the caller credential (made live by override_creds()
* above) has access to the device node. This will include
* checking file permissions and considering the
* CAP_DAC_OVERRIDE capability.
*/
struct path devpath;
err = kern_path(v->vdev_path, LOOKUP_FOLLOW, &devpath);
if (err == 0) {
int mask =
(smode & SPA_MODE_READ ? MAY_READ : 0) |
(smode & SPA_MODE_WRITE ? MAY_WRITE : 0);
err = vdev_path_backing_permission(&devpath, mask);
path_put(&devpath);
}
if (err == 0) {
bdh = vdev_blkdev_get_by_path(v->vdev_path, smode,
zfs_vdev_holder);
err = BDH_IS_ERR(bdh) ? BDH_PTR_ERR(bdh) : 0;
}
if (err == 0)
break;
if (unlikely(err == -ENOENT)) {
/*
* There is no point of waiting since device is removed
* explicitly
@@ -401,29 +451,30 @@ vdev_disk_open(vdev_t *v, uint64_t *psize, uint64_t *max_psize,
break;
schedule_timeout_interruptible(MSEC_TO_TICK(10));
} else if (unlikely(BDH_PTR_ERR(bdh) == -ERESTARTSYS)) {
} else if (unlikely(err == -ERESTARTSYS)) {
timeout = MSEC2NSEC(zfs_vdev_open_timeout_ms * 10);
continue;
} else if (BDH_IS_ERR(bdh)) {
break;
}
break;
}
if (BDH_IS_ERR(bdh)) {
int error = -BDH_PTR_ERR(bdh);
vdev_dbgmsg(v, "open error=%d timeout=%llu/%llu", error,
revert_creds(oldcr);
if (err != 0) {
vdev_dbgmsg(v, "open error=%d timeout=%llu/%llu", -err,
(u_longlong_t)(gethrtime() - start),
(u_longlong_t)timeout);
vd->vd_bdh = NULL;
v->vdev_tsd = vd;
rw_exit(&vd->vd_lock);
return (SET_ERROR(error));
} else {
vd->vd_bdh = bdh;
v->vdev_tsd = vd;
rw_exit(&vd->vd_lock);
return (SET_ERROR(-err));
}
vd->vd_bdh = bdh;
v->vdev_tsd = vd;
rw_exit(&vd->vd_lock);
struct block_device *bdev = BDH_BDEV(vd->vd_bdh);
/* Determine the physical block size */