KVM: s390: fix get_all_floating_irqs

This fixes a bug introduced with commit c05c4186bb ("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+: 69a8d45626 KVM: s390: no need to hold...

Reviewed-by: David Hildenbrand <dahi@linux.vnet.ibm.com>
Signed-off-by: Jens Freimann <jfrei@linux.vnet.ibm.com>
Signed-off-by: Christian Borntraeger <borntraeger@de.ibm.com>
Acked-by: Cornelia Huck <cornelia.huck@de.ibm.com>
This commit is contained in:
Jens Freimann 2015-03-16 12:17:13 +01:00 committed by Christian Borntraeger
parent a3ed8dae6e
commit 94aa033efc
2 changed files with 35 additions and 26 deletions

View File

@ -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

View File

@ -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)
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)
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: