]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - drivers/scsi/sg.c
isdn: fix kernel-infoleak in capi_unlocked_ioctl
[mirror_ubuntu-bionic-kernel.git] / drivers / scsi / sg.c
index f098877eed4aa83fb4c1245e49b378a7699389d5..a3862cf5e18471bedc1ac8b9d7117a3173cb77e5 100644 (file)
@@ -51,6 +51,7 @@ static int sg_version_num = 30536;    /* 2 digits for each component */
 #include <linux/atomic.h>
 #include <linux/ratelimit.h>
 #include <linux/uio.h>
+#include <linux/cred.h> /* for sg_check_file_access() */
 
 #include "scsi.h"
 #include <scsi/scsi_dbg.h>
@@ -210,6 +211,33 @@ static void sg_device_destroy(struct kref *kref);
        sdev_prefix_printk(prefix, (sdp)->device,               \
                           (sdp)->disk->disk_name, fmt, ##a)
 
+/*
+ * The SCSI interfaces that use read() and write() as an asynchronous variant of
+ * ioctl(..., SG_IO, ...) are fundamentally unsafe, since there are lots of ways
+ * to trigger read() and write() calls from various contexts with elevated
+ * privileges. This can lead to kernel memory corruption (e.g. if these
+ * interfaces are called through splice()) and privilege escalation inside
+ * userspace (e.g. if a process with access to such a device passes a file
+ * descriptor to a SUID binary as stdin/stdout/stderr).
+ *
+ * This function provides protection for the legacy API by restricting the
+ * calling context.
+ */
+static int sg_check_file_access(struct file *filp, const char *caller)
+{
+       if (filp->f_cred != current_real_cred()) {
+               pr_err_once("%s: process %d (%s) changed security contexts after opening file descriptor, this is not allowed.\n",
+                       caller, task_tgid_vnr(current), current->comm);
+               return -EPERM;
+       }
+       if (uaccess_kernel()) {
+               pr_err_once("%s: process %d (%s) called from kernel context, this is not allowed.\n",
+                       caller, task_tgid_vnr(current), current->comm);
+               return -EACCES;
+       }
+       return 0;
+}
+
 static int sg_allow_access(struct file *filp, unsigned char *cmd)
 {
        struct sg_fd *sfp = filp->private_data;
@@ -394,6 +422,14 @@ sg_read(struct file *filp, char __user *buf, size_t count, loff_t * ppos)
        struct sg_header *old_hdr = NULL;
        int retval = 0;
 
+       /*
+        * This could cause a response to be stranded. Close the associated
+        * file descriptor to free up any resources being held.
+        */
+       retval = sg_check_file_access(filp, __func__);
+       if (retval)
+               return retval;
+
        if ((!(sfp = (Sg_fd *) filp->private_data)) || (!(sdp = sfp->parentdp)))
                return -ENXIO;
        SCSI_LOG_TIMEOUT(3, sg_printk(KERN_INFO, sdp,
@@ -581,9 +617,11 @@ sg_write(struct file *filp, const char __user *buf, size_t count, loff_t * ppos)
        struct sg_header old_hdr;
        sg_io_hdr_t *hp;
        unsigned char cmnd[SG_MAX_CDB_SIZE];
+       int retval;
 
-       if (unlikely(uaccess_kernel()))
-               return -EINVAL;
+       retval = sg_check_file_access(filp, __func__);
+       if (retval)
+               return retval;
 
        if ((!(sfp = (Sg_fd *) filp->private_data)) || (!(sdp = sfp->parentdp)))
                return -ENXIO;
@@ -1894,7 +1932,7 @@ retry:
                num = (rem_sz > scatter_elem_sz_prev) ?
                        scatter_elem_sz_prev : rem_sz;
 
-               schp->pages[k] = alloc_pages(gfp_mask, order);
+               schp->pages[k] = alloc_pages(gfp_mask | __GFP_ZERO, order);
                if (!schp->pages[k])
                        goto out;
 
@@ -2148,6 +2186,7 @@ sg_add_sfp(Sg_device * sdp)
        write_lock_irqsave(&sdp->sfd_lock, iflags);
        if (atomic_read(&sdp->detaching)) {
                write_unlock_irqrestore(&sdp->sfd_lock, iflags);
+               kfree(sfp);
                return ERR_PTR(-ENODEV);
        }
        list_add_tail(&sfp->sfd_siblings, &sdp->sfds);