ublk_drv: only allow owner to open unprivileged disk
[linux-block.git] / drivers / block / ublk_drv.c
index 883f7974d105d41c6fd6b1b6e1803e79bc03824f..c932e9ea5a0ff117fae1a468fa13e4803cc8cd1c 100644 (file)
@@ -42,6 +42,7 @@
 #include <linux/mm.h>
 #include <asm/page.h>
 #include <linux/task_work.h>
+#include <linux/namei.h>
 #include <uapi/linux/ublk_cmd.h>
 
 #define UBLK_MINORS            (1U << MINORBITS)
@@ -51,7 +52,8 @@
                | UBLK_F_URING_CMD_COMP_IN_TASK \
                | UBLK_F_NEED_GET_DATA \
                | UBLK_F_USER_RECOVERY \
-               | UBLK_F_USER_RECOVERY_REISSUE)
+               | UBLK_F_USER_RECOVERY_REISSUE \
+               | UBLK_F_UNPRIVILEGED_DEV)
 
 /* All UBLK_PARAM_TYPE_* should be included here */
 #define UBLK_PARAM_TYPE_ALL (UBLK_PARAM_TYPE_BASIC | \
@@ -375,8 +377,50 @@ static void ublk_free_disk(struct gendisk *disk)
        put_device(&ub->cdev_dev);
 }
 
+static void ublk_store_owner_uid_gid(unsigned int *owner_uid,
+               unsigned int *owner_gid)
+{
+       kuid_t uid;
+       kgid_t gid;
+
+       current_uid_gid(&uid, &gid);
+
+       *owner_uid = from_kuid(&init_user_ns, uid);
+       *owner_gid = from_kgid(&init_user_ns, gid);
+}
+
+static int ublk_open(struct block_device *bdev, fmode_t mode)
+{
+       struct ublk_device *ub = bdev->bd_disk->private_data;
+
+       if (capable(CAP_SYS_ADMIN))
+               return 0;
+
+       /*
+        * If it is one unprivileged device, only owner can open
+        * the disk. Otherwise it could be one trap made by one
+        * evil user who grants this disk's privileges to other
+        * users deliberately.
+        *
+        * This way is reasonable too given anyone can create
+        * unprivileged device, and no need other's grant.
+        */
+       if (ub->dev_info.flags & UBLK_F_UNPRIVILEGED_DEV) {
+               unsigned int curr_uid, curr_gid;
+
+               ublk_store_owner_uid_gid(&curr_uid, &curr_gid);
+
+               if (curr_uid != ub->dev_info.owner_uid || curr_gid !=
+                               ub->dev_info.owner_gid)
+                       return -EPERM;
+       }
+
+       return 0;
+}
+
 static const struct block_device_operations ub_fops = {
        .owner =        THIS_MODULE,
+       .open =         ublk_open,
        .free_disk =    ublk_free_disk,
 };
 
@@ -1641,15 +1685,26 @@ static int ublk_ctrl_add_dev(struct io_uring_cmd *cmd)
                        __func__, header->queue_id);
                return -EINVAL;
        }
+
        if (copy_from_user(&info, argp, sizeof(info)))
                return -EFAULT;
-       ublk_dump_dev_info(&info);
+
+       if (capable(CAP_SYS_ADMIN))
+               info.flags &= ~UBLK_F_UNPRIVILEGED_DEV;
+       else if (!(info.flags & UBLK_F_UNPRIVILEGED_DEV))
+               return -EPERM;
+
+       /* the created device is always owned by current user */
+       ublk_store_owner_uid_gid(&info.owner_uid, &info.owner_gid);
+
        if (header->dev_id != info.dev_id) {
                pr_warn("%s: dev id not match %u %u\n",
                        __func__, header->dev_id, info.dev_id);
                return -EINVAL;
        }
 
+       ublk_dump_dev_info(&info);
+
        ret = mutex_lock_killable(&ublk_ctl_mutex);
        if (ret)
                return ret;
@@ -1982,6 +2037,115 @@ static int ublk_ctrl_end_recovery(struct ublk_device *ub,
        return ret;
 }
 
+/*
+ * All control commands are sent via /dev/ublk-control, so we have to check
+ * the destination device's permission
+ */
+static int ublk_char_dev_permission(struct ublk_device *ub,
+               const char *dev_path, int mask)
+{
+       int err;
+       struct path path;
+       struct kstat stat;
+
+       err = kern_path(dev_path, LOOKUP_FOLLOW, &path);
+       if (err)
+               return err;
+
+       err = vfs_getattr(&path, &stat, STATX_TYPE, AT_STATX_SYNC_AS_STAT);
+       if (err)
+               goto exit;
+
+       err = -EPERM;
+       if (stat.rdev != ub->cdev_dev.devt || !S_ISCHR(stat.mode))
+               goto exit;
+
+       err = inode_permission(&init_user_ns,
+                       d_backing_inode(path.dentry), mask);
+exit:
+       path_put(&path);
+       return err;
+}
+
+static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
+               struct io_uring_cmd *cmd)
+{
+       struct ublksrv_ctrl_cmd *header = (struct ublksrv_ctrl_cmd *)cmd->cmd;
+       bool unprivileged = ub->dev_info.flags & UBLK_F_UNPRIVILEGED_DEV;
+       void __user *argp = (void __user *)(unsigned long)header->addr;
+       char *dev_path = NULL;
+       int ret = 0;
+       int mask;
+
+       if (!unprivileged) {
+               if (!capable(CAP_SYS_ADMIN))
+                       return -EPERM;
+               /*
+                * The new added command of UBLK_CMD_GET_DEV_INFO2 includes
+                * char_dev_path in payload too, since userspace may not
+                * know if the specified device is created as unprivileged
+                * mode.
+                */
+               if (cmd->cmd_op != UBLK_CMD_GET_DEV_INFO2)
+                       return 0;
+       }
+
+       /*
+        * User has to provide the char device path for unprivileged ublk
+        *
+        * header->addr always points to the dev path buffer, and
+        * header->dev_path_len records length of dev path buffer.
+        */
+       if (!header->dev_path_len || header->dev_path_len > PATH_MAX)
+               return -EINVAL;
+
+       if (header->len < header->dev_path_len)
+               return -EINVAL;
+
+       dev_path = kmalloc(header->dev_path_len + 1, GFP_KERNEL);
+       if (!dev_path)
+               return -ENOMEM;
+
+       ret = -EFAULT;
+       if (copy_from_user(dev_path, argp, header->dev_path_len))
+               goto exit;
+       dev_path[header->dev_path_len] = 0;
+
+       ret = -EINVAL;
+       switch (cmd->cmd_op) {
+       case UBLK_CMD_GET_DEV_INFO:
+       case UBLK_CMD_GET_DEV_INFO2:
+       case UBLK_CMD_GET_QUEUE_AFFINITY:
+       case UBLK_CMD_GET_PARAMS:
+               mask = MAY_READ;
+               break;
+       case UBLK_CMD_START_DEV:
+       case UBLK_CMD_STOP_DEV:
+       case UBLK_CMD_ADD_DEV:
+       case UBLK_CMD_DEL_DEV:
+       case UBLK_CMD_SET_PARAMS:
+       case UBLK_CMD_START_USER_RECOVERY:
+       case UBLK_CMD_END_USER_RECOVERY:
+               mask = MAY_READ | MAY_WRITE;
+               break;
+       default:
+               goto exit;
+       }
+
+       ret = ublk_char_dev_permission(ub, dev_path, mask);
+       if (!ret) {
+               header->len -= header->dev_path_len;
+               header->addr += header->dev_path_len;
+       }
+       pr_devel("%s: dev id %d cmd_op %x uid %d gid %d path %s ret %d\n",
+                       __func__, ub->ub_number, cmd->cmd_op,
+                       ub->dev_info.owner_uid, ub->dev_info.owner_gid,
+                       dev_path, ret);
+exit:
+       kfree(dev_path);
+       return ret;
+}
+
 static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
                unsigned int issue_flags)
 {
@@ -1997,17 +2161,21 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
        if (!(issue_flags & IO_URING_F_SQE128))
                goto out;
 
-       ret = -EPERM;
-       if (!capable(CAP_SYS_ADMIN))
-               goto out;
-
        if (cmd->cmd_op != UBLK_CMD_ADD_DEV) {
                ret = -ENODEV;
                ub = ublk_get_device_from_id(header->dev_id);
                if (!ub)
                        goto out;
+
+               ret = ublk_ctrl_uring_cmd_permission(ub, cmd);
+       } else {
+               /* ADD_DEV permission check is done in command handler */
+               ret = 0;
        }
 
+       if (ret)
+               goto put_dev;
+
        switch (cmd->cmd_op) {
        case UBLK_CMD_START_DEV:
                ret = ublk_ctrl_start_dev(ub, cmd);
@@ -2016,6 +2184,7 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
                ret = ublk_ctrl_stop_dev(ub);
                break;
        case UBLK_CMD_GET_DEV_INFO:
+       case UBLK_CMD_GET_DEV_INFO2:
                ret = ublk_ctrl_get_dev_info(ub, cmd);
                break;
        case UBLK_CMD_ADD_DEV:
@@ -2043,6 +2212,8 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
                ret = -ENOTSUPP;
                break;
        }
+
+ put_dev:
        if (ub)
                ublk_put_device(ub);
  out: