Commit 94aa033e authored by Jens Freimann's avatar Jens Freimann Committed by Christian Borntraeger

KVM: s390: fix get_all_floating_irqs

This fixes a bug introduced with commit c05c4186 ("KVM: s390:
add floating irq controller").

get_all_floating_irqs() does copy_to_user() while holding
a spin lock. Let's fix this by filling a temporary buffer
first and copy it to userspace after giving up the lock.

Cc: <stable@vger.kernel.org> # 3.18+: 69a8d456 KVM: s390: no need to hold...
Reviewed-by: default avatarDavid Hildenbrand <dahi@linux.vnet.ibm.com>
Signed-off-by: default avatarJens Freimann <jfrei@linux.vnet.ibm.com>
Signed-off-by: default avatarChristian Borntraeger <borntraeger@de.ibm.com>
Acked-by: default avatarCornelia Huck <cornelia.huck@de.ibm.com>
parent a3ed8dae
...@@ -27,6 +27,9 @@ Groups: ...@@ -27,6 +27,9 @@ Groups:
Copies all floating interrupts into a buffer provided by userspace. Copies all floating interrupts into a buffer provided by userspace.
When the buffer is too small it returns -ENOMEM, which is the indication When the buffer is too small it returns -ENOMEM, which is the indication
for userspace to try again with a bigger buffer. for userspace to try again with a bigger buffer.
-ENOBUFS is returned when the allocation of a kernelspace buffer has
failed.
-EFAULT is returned when copying data to userspace failed.
All interrupts remain pending, i.e. are not deleted from the list of All interrupts remain pending, i.e. are not deleted from the list of
currently pending interrupts. currently pending interrupts.
attr->addr contains the userspace address of the buffer into which all attr->addr contains the userspace address of the buffer into which all
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include <linux/signal.h> #include <linux/signal.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/bitmap.h> #include <linux/bitmap.h>
#include <linux/vmalloc.h>
#include <asm/asm-offsets.h> #include <asm/asm-offsets.h>
#include <asm/dis.h> #include <asm/dis.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
...@@ -1477,61 +1478,66 @@ void kvm_s390_clear_float_irqs(struct kvm *kvm) ...@@ -1477,61 +1478,66 @@ void kvm_s390_clear_float_irqs(struct kvm *kvm)
spin_unlock(&fi->lock); spin_unlock(&fi->lock);
} }
static inline int copy_irq_to_user(struct kvm_s390_interrupt_info *inti, static void inti_to_irq(struct kvm_s390_interrupt_info *inti,
u8 *addr) struct kvm_s390_irq *irq)
{ {
struct kvm_s390_irq __user *uptr = (struct kvm_s390_irq __user *) addr; irq->type = inti->type;
struct kvm_s390_irq irq = {0};
irq.type = inti->type;
switch (inti->type) { switch (inti->type) {
case KVM_S390_INT_PFAULT_INIT: case KVM_S390_INT_PFAULT_INIT:
case KVM_S390_INT_PFAULT_DONE: case KVM_S390_INT_PFAULT_DONE:
case KVM_S390_INT_VIRTIO: case KVM_S390_INT_VIRTIO:
case KVM_S390_INT_SERVICE: case KVM_S390_INT_SERVICE:
irq.u.ext = inti->ext; irq->u.ext = inti->ext;
break; break;
case KVM_S390_INT_IO_MIN...KVM_S390_INT_IO_MAX: case KVM_S390_INT_IO_MIN...KVM_S390_INT_IO_MAX:
irq.u.io = inti->io; irq->u.io = inti->io;
break; break;
case KVM_S390_MCHK: case KVM_S390_MCHK:
irq.u.mchk = inti->mchk; irq->u.mchk = inti->mchk;
break; break;
default:
return -EINVAL;
} }
if (copy_to_user(uptr, &irq, sizeof(irq)))
return -EFAULT;
return 0;
} }
static int get_all_floating_irqs(struct kvm *kvm, __u8 *buf, __u64 len) static int get_all_floating_irqs(struct kvm *kvm, u8 __user *usrbuf, u64 len)
{ {
struct kvm_s390_interrupt_info *inti; struct kvm_s390_interrupt_info *inti;
struct kvm_s390_float_interrupt *fi; struct kvm_s390_float_interrupt *fi;
struct kvm_s390_irq *buf;
int max_irqs;
int ret = 0; int ret = 0;
int n = 0; int n = 0;
if (len > KVM_S390_FLIC_MAX_BUFFER || len == 0)
return -EINVAL;
/*
* We are already using -ENOMEM to signal
* userspace it may retry with a bigger buffer,
* so we need to use something else for this case
*/
buf = vzalloc(len);
if (!buf)
return -ENOBUFS;
max_irqs = len / sizeof(struct kvm_s390_irq);
fi = &kvm->arch.float_int; fi = &kvm->arch.float_int;
spin_lock(&fi->lock); spin_lock(&fi->lock);
list_for_each_entry(inti, &fi->list, list) { list_for_each_entry(inti, &fi->list, list) {
if (len < sizeof(struct kvm_s390_irq)) { if (n == max_irqs) {
/* signal userspace to try again */ /* signal userspace to try again */
ret = -ENOMEM; ret = -ENOMEM;
break; break;
} }
ret = copy_irq_to_user(inti, buf); inti_to_irq(inti, &buf[n]);
if (ret)
break;
buf += sizeof(struct kvm_s390_irq);
len -= sizeof(struct kvm_s390_irq);
n++; n++;
} }
spin_unlock(&fi->lock); spin_unlock(&fi->lock);
if (!ret && n > 0) {
if (copy_to_user(usrbuf, buf, sizeof(struct kvm_s390_irq) * n))
ret = -EFAULT;
}
vfree(buf);
return ret < 0 ? ret : n; return ret < 0 ? ret : n;
} }
...@@ -1542,7 +1548,7 @@ static int flic_get_attr(struct kvm_device *dev, struct kvm_device_attr *attr) ...@@ -1542,7 +1548,7 @@ static int flic_get_attr(struct kvm_device *dev, struct kvm_device_attr *attr)
switch (attr->group) { switch (attr->group) {
case KVM_DEV_FLIC_GET_ALL_IRQS: case KVM_DEV_FLIC_GET_ALL_IRQS:
r = get_all_floating_irqs(dev->kvm, (u8 *) attr->addr, r = get_all_floating_irqs(dev->kvm, (u8 __user *) attr->addr,
attr->attr); attr->attr);
break; break;
default: default:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment