diff options
author | Jens Freimann <jfrei@linux.vnet.ibm.com> | 2015-03-16 07:17:13 -0400 |
---|---|---|
committer | Christian Borntraeger <borntraeger@de.ibm.com> | 2015-03-31 15:05:51 -0400 |
commit | 94aa033efcac47b09db22cb561e135baf37b7887 (patch) | |
tree | 2370d7dbd93fd307db16399de6229dbb0aa46d04 /arch/s390/kvm | |
parent | a3ed8dae6e3db479ca275883ba7fe994170b0ae6 (diff) |
KVM: s390: fix get_all_floating_irqs
This fixes a bug introduced with commit c05c4186bbe4 ("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+: 69a8d4562638 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>
Diffstat (limited to 'arch/s390/kvm')
-rw-r--r-- | arch/s390/kvm/interrupt.c | 58 |
1 files changed, 32 insertions, 26 deletions
diff --git a/arch/s390/kvm/interrupt.c b/arch/s390/kvm/interrupt.c index 2361b8ed0a50..5ebd500e6400 100644 --- a/arch/s390/kvm/interrupt.c +++ b/arch/s390/kvm/interrupt.c | |||
@@ -17,6 +17,7 @@ | |||
17 | #include <linux/signal.h> | 17 | #include <linux/signal.h> |
18 | #include <linux/slab.h> | 18 | #include <linux/slab.h> |
19 | #include <linux/bitmap.h> | 19 | #include <linux/bitmap.h> |
20 | #include <linux/vmalloc.h> | ||
20 | #include <asm/asm-offsets.h> | 21 | #include <asm/asm-offsets.h> |
21 | #include <asm/dis.h> | 22 | #include <asm/dis.h> |
22 | #include <asm/uaccess.h> | 23 | #include <asm/uaccess.h> |
@@ -1477,61 +1478,66 @@ void kvm_s390_clear_float_irqs(struct kvm *kvm) | |||
1477 | spin_unlock(&fi->lock); | 1478 | spin_unlock(&fi->lock); |
1478 | } | 1479 | } |
1479 | 1480 | ||
1480 | static inline int copy_irq_to_user(struct kvm_s390_interrupt_info *inti, | 1481 | static void inti_to_irq(struct kvm_s390_interrupt_info *inti, |
1481 | u8 *addr) | 1482 | struct kvm_s390_irq *irq) |
1482 | { | 1483 | { |
1483 | struct kvm_s390_irq __user *uptr = (struct kvm_s390_irq __user *) addr; | 1484 | irq->type = inti->type; |
1484 | struct kvm_s390_irq irq = {0}; | ||
1485 | |||
1486 | irq.type = inti->type; | ||
1487 | switch (inti->type) { | 1485 | switch (inti->type) { |
1488 | case KVM_S390_INT_PFAULT_INIT: | 1486 | case KVM_S390_INT_PFAULT_INIT: |
1489 | case KVM_S390_INT_PFAULT_DONE: | 1487 | case KVM_S390_INT_PFAULT_DONE: |
1490 | case KVM_S390_INT_VIRTIO: | 1488 | case KVM_S390_INT_VIRTIO: |
1491 | case KVM_S390_INT_SERVICE: | 1489 | case KVM_S390_INT_SERVICE: |
1492 | irq.u.ext = inti->ext; | 1490 | irq->u.ext = inti->ext; |
1493 | break; | 1491 | break; |
1494 | case KVM_S390_INT_IO_MIN...KVM_S390_INT_IO_MAX: | 1492 | case KVM_S390_INT_IO_MIN...KVM_S390_INT_IO_MAX: |
1495 | irq.u.io = inti->io; | 1493 | irq->u.io = inti->io; |
1496 | break; | 1494 | break; |
1497 | case KVM_S390_MCHK: | 1495 | case KVM_S390_MCHK: |
1498 | irq.u.mchk = inti->mchk; | 1496 | irq->u.mchk = inti->mchk; |
1499 | break; | 1497 | break; |
1500 | default: | ||
1501 | return -EINVAL; | ||
1502 | } | 1498 | } |
1503 | |||
1504 | if (copy_to_user(uptr, &irq, sizeof(irq))) | ||
1505 | return -EFAULT; | ||
1506 | |||
1507 | return 0; | ||
1508 | } | 1499 | } |
1509 | 1500 | ||
1510 | static int get_all_floating_irqs(struct kvm *kvm, __u8 *buf, __u64 len) | 1501 | static int get_all_floating_irqs(struct kvm *kvm, u8 __user *usrbuf, u64 len) |
1511 | { | 1502 | { |
1512 | struct kvm_s390_interrupt_info *inti; | 1503 | struct kvm_s390_interrupt_info *inti; |
1513 | struct kvm_s390_float_interrupt *fi; | 1504 | struct kvm_s390_float_interrupt *fi; |
1505 | struct kvm_s390_irq *buf; | ||
1506 | int max_irqs; | ||
1514 | int ret = 0; | 1507 | int ret = 0; |
1515 | int n = 0; | 1508 | int n = 0; |
1516 | 1509 | ||
1510 | if (len > KVM_S390_FLIC_MAX_BUFFER || len == 0) | ||
1511 | return -EINVAL; | ||
1512 | |||
1513 | /* | ||
1514 | * We are already using -ENOMEM to signal | ||
1515 | * userspace it may retry with a bigger buffer, | ||
1516 | * so we need to use something else for this case | ||
1517 | */ | ||
1518 | buf = vzalloc(len); | ||
1519 | if (!buf) | ||
1520 | return -ENOBUFS; | ||
1521 | |||
1522 | max_irqs = len / sizeof(struct kvm_s390_irq); | ||
1523 | |||
1517 | fi = &kvm->arch.float_int; | 1524 | fi = &kvm->arch.float_int; |
1518 | spin_lock(&fi->lock); | 1525 | spin_lock(&fi->lock); |
1519 | |||
1520 | list_for_each_entry(inti, &fi->list, list) { | 1526 | list_for_each_entry(inti, &fi->list, list) { |
1521 | if (len < sizeof(struct kvm_s390_irq)) { | 1527 | if (n == max_irqs) { |
1522 | /* signal userspace to try again */ | 1528 | /* signal userspace to try again */ |
1523 | ret = -ENOMEM; | 1529 | ret = -ENOMEM; |
1524 | break; | 1530 | break; |
1525 | } | 1531 | } |
1526 | ret = copy_irq_to_user(inti, buf); | 1532 | inti_to_irq(inti, &buf[n]); |
1527 | if (ret) | ||
1528 | break; | ||
1529 | buf += sizeof(struct kvm_s390_irq); | ||
1530 | len -= sizeof(struct kvm_s390_irq); | ||
1531 | n++; | 1533 | n++; |
1532 | } | 1534 | } |
1533 | |||
1534 | spin_unlock(&fi->lock); | 1535 | spin_unlock(&fi->lock); |
1536 | if (!ret && n > 0) { | ||
1537 | if (copy_to_user(usrbuf, buf, sizeof(struct kvm_s390_irq) * n)) | ||
1538 | ret = -EFAULT; | ||
1539 | } | ||
1540 | vfree(buf); | ||
1535 | 1541 | ||
1536 | return ret < 0 ? ret : n; | 1542 | return ret < 0 ? ret : n; |
1537 | } | 1543 | } |
@@ -1542,7 +1548,7 @@ static int flic_get_attr(struct kvm_device *dev, struct kvm_device_attr *attr) | |||
1542 | 1548 | ||
1543 | switch (attr->group) { | 1549 | switch (attr->group) { |
1544 | case KVM_DEV_FLIC_GET_ALL_IRQS: | 1550 | case KVM_DEV_FLIC_GET_ALL_IRQS: |
1545 | r = get_all_floating_irqs(dev->kvm, (u8 *) attr->addr, | 1551 | r = get_all_floating_irqs(dev->kvm, (u8 __user *) attr->addr, |
1546 | attr->attr); | 1552 | attr->attr); |
1547 | break; | 1553 | break; |
1548 | default: | 1554 | default: |